Skip to content

ci(build): package plugin into archive-root zip with validate/release workflows - #9

Merged
obvious-autobuild[bot] merged 4 commits into
mainfrom
ci/plugin-packaging
Aug 27, 2026
Merged

obvious-autobuild[bot] merged 4 commits into
mainfrom
ci/plugin-packaging

Conversation

@obvious-autobuild

Copy link
Copy Markdown
Contributor

Why

Synter-Media-AI/plugin has no build artifact and no CI. Claude Desktop's "Upload plugin" flow needs a zip with the manifest at the archive root, and there's currently no automated check that the six JSON manifests parse, that the marketplace listing name matches the plugin name, that skill frontmatter is internally consistent, or that the two MCP client configs (.mcp.json for Claude, mcp.json for Cursor) still carry their required interpolation placeholders. This is D3 of the approved distribution spec (art_RH6inobj).

What

  • scripts/build-zip.sh — reads VERSION from .claude-plugin/plugin.json, stages the plugin surface into a temp dir under synter/ (excluding .git, .github, dist, sdk, .cursor-plugin), zips to dist/synter-plugin-<version>.zip, and hard-fails the build if synter/.claude-plugin/plugin.json isn't at the archive root.
  • scripts/validate-manifests.js — node stdlib only, no new dependencies. Validates: (a) all six JSON manifests parse, (b) .claude-plugin/marketplace.json plugins[0].name matches .claude-plugin/plugin.json name, (c) every skills/*/SKILL.md frontmatter has a name matching its directory and a non-empty description, (d) .mcp.json contains the literal user_config.synter_api_key and mcp.json contains the literal SYNTER_API_KEY.
  • .github/workflows/validate.yml — runs the validation script on every PR to main.
  • .github/workflows/release.yml — on tag push matching synter--v*: validates, builds the zip, and publishes a GitHub Release with the zip attached via softprops/action-gh-release (chosen over hand-rolled gh release create scripting — built-in multi-file asset upload and release-notes generation, one declarative step instead of several imperative gh calls).
  • .gitignore: added dist/ (the script's generated output).

Rejected alternative: actions/upload-artifact alone doesn't create a Release — it only stores a CI-run artifact, which isn't what Claude Desktop's install docs will link to. softprops/action-gh-release handles the actual public Release + asset attach in one step.

How to Review

  • scripts/build-zip.sh is the packaging core — the root-check assertion at the end is the safety net; it's the only thing standing between a broken zip and a broken Desktop upload.
  • scripts/validate-manifests.js is intentionally dependency-free (no js-yaml/frontmatter libs) — frontmatter is parsed with a small regex since SKILL.md frontmatter here is flat key: value YAML.
  • Nothing in this PR touches plugin runtime behavior (gating, skills content) — that's D1/D2 in sibling PRs.

Test Evidence

Local run on this branch:

$ bash scripts/build-zip.sh
Built dist/synter-plugin-0.4.0.zip
OK: synter/.claude-plugin/plugin.json present at archive root

$ unzip -l dist/synter-plugin-0.4.0.zip | head -3
Archive:  dist/synter-plugin-0.4.0.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2026-08-27 20:35   synter/
      184  2026-08-27 20:35   synter/mcp.json
...
      1261  2026-08-27 20:35   synter/plugin.json
---------                     -------
   388224                     147 files

$ node scripts/validate-manifests.js
All manifests, skill frontmatter, and interpolation guards passed.

Confirmed no .git/, .github/, dist/, sdk/, or .cursor-plugin/ paths present in the zip listing.

Human author: Joel Horwitz (joel@synterai.com)

🔗 Obvious Project · 🧵 Obvious Thread

ObviousApp and others added 4 commits August 27, 2026 21:01
Add scripts/build-zip.sh: reads VERSION from .claude-plugin/plugin.json,
stages the plugin surface (excluding .git, .github, dist, sdk,
.cursor-plugin) into a synter/ folder, and zips it to
dist/synter-plugin-<version>.zip with the plugin manifest at the archive
root — the layout Claude Desktop's "Upload plugin" zip import requires.
Ends with a hard root-check assertion that fails the build if
synter/.claude-plugin/plugin.json is not present at that path.

Also gitignore dist/, the script's generated output.

Co-authored-by: Joel Horwitz <joel@synterai.com>
…ation on every PR

Add scripts/validate-manifests.js (node stdlib only, no dependencies):
- parse-validates all six JSON manifests (.claude-plugin and .cursor-plugin
  plugin.json/marketplace.json, .mcp.json, mcp.json)
- asserts .claude-plugin/marketplace.json plugins[0].name matches
  .claude-plugin/plugin.json name
- checks every skills/*/SKILL.md frontmatter: name matches its directory,
  description is present
- guards the two mcp client configs against silent interpolation breakage:
  .mcp.json must contain the literal "user_config.synter_api_key" (Claude)
  and mcp.json must contain "SYNTER_API_KEY" (Cursor)

Add .github/workflows/validate.yml to run this script on every PR to main.

Co-authored-by: Joel Horwitz <joel@synterai.com>
Add .github/workflows/release.yml, triggered on tags matching
synter--v*: runs manifest validation, builds the archive-root zip via
scripts/build-zip.sh, and creates a GitHub Release for the tag with
dist/synter-plugin-<version>.zip attached via softprops/action-gh-release
(chosen over hand-rolled gh CLI release scripting for its built-in asset
upload and release-notes generation).

Co-authored-by: Joel Horwitz <joel@synterai.com>
@obvious-autobuild
obvious-autobuild Bot marked this pull request as ready for review August 27, 2026 21:02
@obvious-autobuild
obvious-autobuild Bot merged commit cc42c51 into main Aug 27, 2026
3 checks passed
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.

2 participants