G
Generating APEXlang is the easy part. The challenge is the day after, when four people share one DEV instance. APEXlang imports the whole application by id. There is no partial import in 26.1, so deploying your one changed page means deploying the whole application, including whatever your colleague did not finish this morning.
That is the problem this article is about, and it is workable today with plain git and a few rules. None of it is tied to a specific tool. I built it into ADT, but you can script every step with SQLcl, git and a bit of shell.
The export is your commit
Export the application as APEXlang and commit it. Two details matter later. First, record the application checksum at export time, next to the files. That is the number you compare before every import.
Second, keep one shared branch that always holds what DEV looks like right now. Every deploy updates it and every task branch starts from it. That branch is what turns "somebody was faster than you" into a rebase instead of a redo.
Nobody edits the deploy target
The rule that makes a team work is short: nobody edits the deploy target. DEV app 100 is what "main" deployed, and it is written by the deploy and by nothing else.
Your task gets a copy instead. Install app 100 for task 725 under its own id (100725, for example), so two developers never collide. Similar to a working copy, since you can't create a working copy app thru import. One task, one app. After testing your app, you deploy to real target (app 100).
When somebody was faster than you
This is the part the full import makes inevitable. You exported at nine, a colleague deployed at ten, and your import would now throw their page away.
So right before the import, compare the checksum you recorded at export with the checksum of the application on the target. If they differ, stop before writing anything, and rebase on the shared branch.
You know the commit your tree was exported from, the branch holds what the target looks like now, and git knows what to do with two commits and a base.
Two people editing the same page is a real conflict, and this is where an AI agent earns its keep. A conflict in ".apx" is a text conflict in a readable format, so the agent resolves it the way it resolves one in a PL/SQL package: keep both intents, reapply your change on top of the version that is now live, validate, deploy again. "Reapply my change to the newer app" is a much smaller ask than "redo your morning".
So the daily cost of the full-app import is one rebase, usually automatic, occasionally a page somebody has to look at.
Builder or files, your choice
Both editing surfaces stay open. A developer who thinks in App Builder keeps working in App Builder, in a copy, and the export is the commit. A developer who thinks in files edits the files, and so can an agent. Both end as ".apx" in the same branch, and integration happens at the merge, where it belongs.
Teams that solve this by banning the Builder are making half their developers slower. The rule is about the deploy target, not about the tools.
Validate before, check after
Validate the app before the import. The APEXlang compiler ships inside SQLcl and runs without a database connection, so it fits in CI with no environment and no credentials. Fail on errors, list the warnings, and treat a run that checked nothing as a failure.
After the import, check what actually landed. A page can validate offline and still break on the target, for example a region query pointing at a column that does not exist there. So you should scan the app (or at least affected pages) after import to make sure everything is fine. You can also take a backup export right before the import, and when the check fails, import the backup back.
The locks, and what APEX still owes us
Now the missing part. Before importing your app, you should lock the target app, so others would not be able to do any changes there while you are deploying. The Builder's application lock in 26.1 looks like the answer and is not. There is no public API for it, so no deploy can take it, and an import deletes it. I reproduced that locally, the lock row is simply gone after the import, which is exactly the moment you needed it (bug 39557252 in the 26.1 known issues).
Build status is what is left, and it is enough. It is public API, it has been in the product for years, and the deploy can hold it across its own import: switch the app to "Run Only", import, then switch it back (or leave it shut, if the target is deploy-only).
Two behaviours are worth knowing before you rely on it, both measured on APEX 26.1.0:
- It closes the door, not the room. A Page Designer session that was already open when the status flips still saves, and the save lands. "Run Only" only refuses entering the Builder.
- Every APEXlang import resets the status to "Run and Develop", and "apex_application_install.set_build_status", which pins it for a classic "f100.sql" install, is ignored by the APEXlang importer. So the deploy has to set it again after every import.
One more trap in 26.1.0. An APEXlang import into a Working Copy quietly cuts the link to the main application, so the copy stops being a copy. The fix is bug 39344501 in the Patch Set Bundle; on 26.1.4 the link survived the same import for me. On an unpatched 26.1.0, use a separate app id.
And the honest wish list: a supported lock with an API, and a partial import. Deploying one page would make most of this article unnecessary, and until that exists the whole application is the unit of deploy, whatever your process says. The gap is closable today though, with a shared branch, one ownership rule and two gates.
Try it on one application
Export one application, keep the shared branch, give a task its own copy, and deploy from the branch. The first time the checksum check stops you, rebase instead of re-exporting. That is the whole change, and it is the difference between APEXlang as a nice export format and APEXlang as something a team of five can actually deploy.
Happy patching!

Comments
Post a Comment