Skip to content

fix(plugins): treat a same-version upload as a reinstall, not a first install - #498

Open
mostafasadeghidev wants to merge 1 commit into
CoreBunch:mainfrom
mostafasadeghidev:fix/plugin-same-version-reinstall
Open

fix(plugins): treat a same-version upload as a reinstall, not a first install#498
mostafasadeghidev wants to merge 1 commit into
CoreBunch:mainfrom
mostafasadeghidev:fix/plugin-same-version-reinstall

Conversation

@mostafasadeghidev

Copy link
Copy Markdown
Contributor

Re-uploading a plugin at an unchanged version number falls through both semver guards — semverGt false, semverLt false — and lands in installFreshFromPackage, the path written for a plugin that is not there.

On a live install that means:

  • the running version's deactivate never runs
  • install runs a second time on an already-installed plugin
  • migrate is skipped
  • there is no rollback if a hook throws, so a failure returns 201 with the plugin parked in error and its assets already overwritten
  • the audit event says plugin.install, recording a replacement as a first-time install

Rebuilding a package without bumping the version is the normal inner loop of plugin development, so this is the path a developer hits most often.

The fix

A lower version already returns above, so anything reaching the branch is installed at the same version or higher — existing alone now selects the upgrade path.

That surfaced two places where "old version" and "new version" name the same directory, and both would have deleted a working plugin:

Step 6 — drop the old version's assets after a successful upgrade with equal versions, that is the directory step 2 just wrote
rollbackUpgrade — drop the new version's assets on failure with equal versions, that is the only copy, and the restored row still points at it

Both are now guarded on the versions actually differing. The new test fails without either guard.

The dialog was silent too

It only set upgradeFromVersion when the versions differed, so a reinstall rendered the first-install screen: no mention of the plugin already being there, and every permission badged "new" though all were already approved.

It now carries the installed version whenever there is one, and distinguishes three cases:

  • Reinstall — "Version 1.6.1 is already installed. Reinstalling replaces its files in place…"
  • Update — unchanged
  • Downgrade — previously promised "settings and stored data are preserved; the plugin runs its migrate hook" and was then refused by the server. It now says so before the operator commits, and the confirm button is disabled rather than buying them a round trip and an error banner.
bun test src/__tests__/server/cmsPlugins.test.ts   23 pass, 0 fail
bun test src/__tests__/plugins/                   174 pass, 0 fail
tsc -b, eslint                                    clean

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

… install

Re-uploading a plugin at an unchanged version number fell through both semver
guards — `semverGt` false, `semverLt` false — and landed in
`installFreshFromPackage`, the path written for a plugin that is not there.

On a live install that meant: the running version's `deactivate` never ran;
`install` ran a second time on a plugin that was already installed; `migrate`
was skipped; there was no rollback if a hook threw, so a failure returned 201
with the plugin parked in `error` and its assets already overwritten; and the
audit event said `plugin.install`, recording a replacement as a first-time
install.

Rebuilding a package without bumping the version is the normal inner loop of
plugin development, so this is the path a developer hits most.

Since a lower version already returned above, anything reaching the branch is
installed at the same version or higher — so `existing` alone now selects the
upgrade path.

That surfaced two places where "old version" and "new version" are the same
directory, and both would have deleted a working plugin:

  - Step 6 drops the old version's assets after a successful upgrade. With
    equal versions that names the directory step 2 has just written.
  - `rollbackUpgrade` drops the new version's assets on failure. With equal
    versions that is the only copy, and the restored row still points at it.

Both are now guarded on the versions actually differing.

The review dialog told the operator none of this: it only set
`upgradeFromVersion` when the versions differed, so a reinstall rendered the
first-install screen — no mention of the plugin already being there, every
permission badged "new" though all were already approved. It now carries the
installed version whenever there is one and distinguishes three cases:
reinstall, update, and downgrade. Downgrade previously promised "settings and
stored data are preserved; the plugin runs its migrate hook" and was then
refused by the server; it now says so before the operator commits, and the
confirm button is disabled rather than buying them a round trip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mostafasadeghidev added a commit to mostafasadeghidev/Instatic that referenced this pull request Sep 6, 2026
…at had no gate

The fork gate stopped at CoreBunch#359. Everything merged onto the stack since —
CoreBunch#497 through CoreBunch#501, and now CoreBunch#505 — was carried with nothing to catch it
being dropped by a merge resolution, which is the exact failure the gate
exists for.

Pinning them turned one up immediately: CoreBunch#498's guard around
`removePluginVersionAssets(…, fromVersion)` is gone, because CoreBunch#359 removed
that call outright — an upgrade now leaves the old version's assets on
disk so published pages keep resolving. What survives of CoreBunch#498 is the
rollback guard, so that is what the row pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant