Skip to content

CEXT-6511: [SPEC] Execute Custom Installation Steps During Application Upgrade - #628

Open
jcuerdo wants to merge 3 commits into
mainfrom
CEXT-6511-spec
Open

CEXT-6511: [SPEC] Execute Custom Installation Steps During Application Upgrade#628
jcuerdo wants to merge 3 commits into
mainfrom
CEXT-6511-spec

Conversation

@jcuerdo

@jcuerdo jcuerdo commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Description

Related Issue

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have read the DEVELOPMENT document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@changeset-bot

changeset-bot Bot commented Aug 13, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d472ca0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added without-changeset The PR does not contain a Changeset file spec Includes changes in `specs/features` labels Aug 13, 2026

@obarcelonap obarcelonap left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of simplicity, I'd go with A. It's easy to explain, automatically reconciles any drift that may have occurred, doesn't introduce additional version coordinates that need to be maintained manually, doesn't require persisting any associated state and code changes in the SDK are minimal.

We can always revisit this decision later if it turns out not to be sufficient.

the last persisted value. Removed steps behave the same as in Option A. The extra state needed is
the last-run version per step name.

### Option C: Migration-style steps (append-only)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go with A, this is something you can always build yourself by defining a custom step that delegates the work to a JavaScript migration framework.

We can explore that approach later and provide it as a sample implementation, or even develop a more opinionated abstraction on top of option A.

@iivvaannxx iivvaannxx left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main difference here is that custom steps are code, not just configuration.

For example, if a webhook is removed from the config, the SDK already knows how to delete that webhook. But if a custom step is removed, the SDK also loses the reference to the script and its uninstall function. Comparing the old and new config can tell the SDK that the step was removed, but it cannot run uninstall if the old script is no longer included in the application. Custom steps therefore need a way to keep that old code available.

With that in mind, I think the main decision is between Options A and C. Option B seems mostly dominated by the other two: if the latest script must handle upgrades from every previous state, it is effectively Option A with additional version bookkeeping; if it depends on intermediate script versions having run, direct upgrades can fail because those versions are not preserved.

Option A is simpler, but requires every script to be safe to run repeatedly and able to bring the app from any previous state to the desired state. Option C keeps scripts as an ordered migration history, but requires every published script to remain available and unchanged, stable step identities, and a record of which steps have already run so that only new ones execute. I would discard Option B unless we identify a concrete use case that neither A nor C covers.

Comment on lines +26 to +27
that already ran run again, and under what condition? And if a step is no longer in the config,
should its `uninstall` run, and when?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this is that you have to keep track of where the uninstall code is, because it's deployed with the app itself, so that it can be executed. The only bridge between the SDK and the uninstall code is its explicit declaration in the app.commerce.config. Without that entry, we don't have a way to "find" and "invoke" that code.

The only way (that I see) for doing that is by making the app store the full history of the app.commerce.config file across versions. Something like:

src
  commerce-extensibility-1/
    .generated/
    .history/
      app.commerce.config-v1.0.0
      app.commerce.config-v1.0.1
      app.commerce.config-v1.0.2
      // ...rest

Each version would then contain the install and uninstall code inlined (self-contained). Which would keep it invocable.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sidenote: Keeping this history solves other problems in a nice way, too:

  • We always have baselines to compare for upgrade (this was also suggested by @asalloum5)
  • Rolling back to a previous config is straightforward (you can even roll back several at a time)

But at the cost of making things slightly more fragile (Git is the only safeguard against losing a previous version)

Comment on lines +121 to +124
This adds a required field per step, and asks developers to bump `version` whenever the logic
changes in a way that needs to re-run. The SDK can't verify that, it only compares against
the last persisted value. Removed steps behave the same as in Option A. The extra state needed is
the last-run version per step name.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to detect changes in custom installation steps is to hash the bodies of the install and uninstall and compare those hashes. We tested this with @obarcelonap, and it was indeed possible to do

Comment on lines +162 to +163
- Version bumps are self-reported and unverifiable: forget to bump it and a step silently skips;
bump it by mistake and it re-runs for no reason.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing the hashes as I suggested above could cause a failure if the version is the same but hashes do not match.

Comment on lines +164 to +168
- A step only has one script body at a time. If 1.0.0 and 1.1.0 are meant to be two separate
actions, not 1.1.0 replacing 1.0.0 outright, there's no way to express that in a single versioned
file. A fresh install only ever runs whatever the current file contains, once. Getting this right
means the same idempotency discipline as Option A, just scoped to "since the last version bump"
instead of "always."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an important limitation of Option B. For example:

  • In 1.0.1, step updateDatabase version 1.1.0 creates a new column.
  • In 1.0.2, the same step becomes version 1.2.0 and writes data to that column, assuming the 1.1.0 logic already ran.

An app upgrading directly from 1.0.0 to 1.0.2 would execute only the current 1.2.0 script. It would not execute the previous 1.1.0 script first, so the upgrade could fail because the column does not exist.

In other words, the version field tells us whether to run the current script, but it does not preserve or execute the intermediate versions. Avoiding this would require every new script version to handle upgrades from every supported previous state, which brings us back to requiring idempotent/reconciling scripts.

means the same idempotency discipline as Option A, just scoped to "since the last version bump"
instead of "always."

**Option C:**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option C is essentially a small-scale equivalent to keeping a history of app configurations as I proposed above. If we were going to do that I would rather go for keeping a history of the whole config, which also brings other benefits to the table

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

spec Includes changes in `specs/features` without-changeset The PR does not contain a Changeset file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants