Skip to content

ci: catch plugin manifest-version mismatches before the tag, and stop them suppressing the release channels - #93

Merged
kipavy merged 3 commits into
devfrom
fix/92-plugin-version-preflight
Aug 4, 2026
Merged

ci: catch plugin manifest-version mismatches before the tag, and stop them suppressing the release channels#93
kipavy merged 3 commits into
devfrom
fix/92-plugin-version-preflight

Conversation

@kipavy

@kipavy kipavy commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #92.

Why

The v0.16.0 release run failed at release / publish-plugins: fix(links) changed src/plugins/gist-sync/ without bumping its manifest.json, so the freshly built bundle no longer matched the published plugin-gist-sync-v1.1.0 that the live marketplace catalogue pins a hash to. The guard was right to refuse — but it only runs needs: [changelog, publish-tauri], i.e. ~20 minutes in and after tag made v0.16.0 immutable, when the cheap fix (amend the commit) no longer exists.

Worse, publish-installers and publish-repo were needs: [tag, release], so that unrelated failure skipped both: Homebrew, winget and the apt/yum repos never published for v0.16.0, while the GitHub release itself published fine. Their release: types: [published] triggers are dead for our own releases (GITHUB_TOKEN-authored events don't fire workflows), so there was no fallback and no retry — the only evidence was a skipped job inside a run already red for another reason.

What this does

1. A cheap pre-flight check (scripts/check-plugin-versions.mjs): for each src/plugins/<id>/, fail if bundled source changed since that plugin's last published <id>-v<version> tag while manifest.json's version did not. Test files are excluded — they never reach the bundle.

This is deliberately a proxy, not a replacement: the authoritative check compares built bytes against published bytes and cannot run without a build. A source edit can be a bundler no-op, and a dependency change outside the plugin folder can alter the bytes without tripping this. The guard in publish-plugins.yml is untouched and remains the authority.

2. It runs in two places, for different reasons. In ci.yml so a PR fails review rather than a release. And as a plugin-versions job in tag-release.yml that tag depends on — because tag-release triggers on the same push to main and never waited for ci, so a release cut by pushing straight to main could tag while the check was still running. Gating tag is what actually holds the "fail before the tag exists" property.

3. Distribution channels gated on the release publishing, not on every job succeeding. release.yml exposes a published output from publish-release; tag-release.yml adds a release-published gate that reads it, falling back to the release's own draft state on GitHub — a reusable workflow whose overall conclusion is failure is exactly the case that matters, and it must not leave the gate blank. publish-installers and publish-repo now depend on that gate. They still do not run when the release genuinely did not publish.

Verification

  • node scripts/check-plugin-versions.mjs on this tree → all 6 plugins OK, exit 0. Confirmed it is a real pass, not a no-op: independently diffing each plugin folder against its own tag gives 0 changed files for all six.
  • Reproduced the real failure: a detached worktree at abc8158 (the dead-links commit, before the 1.1.1 bump) → exit 1, naming plugin-gist-sync and both SettingsPage.tsx and openExternal.ts. With tag now depending on this job, that tag would never have been created.
  • Gate logic exercised locally under bash -e across all four branches (reported true / output lost but not draft / still draft / release missing) — opens only when the release actually published, and the || echo missing does not abort the step.
  • npx tsc --noEmit clean. npx vitest run → 261 files, 1615 tests passed. tests/pluginVersionPreflight.test.ts → 7 passed.
  • YAML validated for ci, release, tag-release, publish-plugins, publish-installers, publish-repo.

Not verifiable outside a real release

Whether GitHub blanks a reusable-workflow output when the called workflow's conclusion is failure. The fallback makes the gate correct either way, but the exact path it takes is only observable on the next dev→main cut.

Known limits

  • attach-plugin-catalog still needs: publish-plugins. That coupling looks correct — the catalogue describes the plugin releases — so it is left as is.
  • Shared code outside src/plugins/<id>/ can change bundled bytes without tripping the pre-flight. Documented in the script; the release-time hash guard remains the backstop.

kipavy added 3 commits August 4, 2026 17:54
…n bump

The publish-plugins hash guard only fires after a build, deep inside a
release run and after the version tag is immutable. Add the cheap
git-level proxy to ci so the fix is still a one-line manifest bump.
publish-installers and publish-repo consume only the published release,
but needed the whole release workflow, so v0.16.0 published on GitHub
while Homebrew, winget and the apt/yum repos silently stayed behind.
Gate them on the release actually being out of draft.
@kipavy
kipavy merged commit ba8ed14 into dev Aug 4, 2026
4 checks passed
@kipavy
kipavy deleted the fix/92-plugin-version-preflight branch August 4, 2026 18:16
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