Repository navigation
ci(build): package plugin into archive-root zip with validate/release workflows - #9
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.jsonfor Claude,mcp.jsonfor Cursor) still carry their required interpolation placeholders. This is D3 of the approved distribution spec (art_RH6inobj).What
scripts/build-zip.sh— readsVERSIONfrom.claude-plugin/plugin.json, stages the plugin surface into a temp dir undersynter/(excluding.git,.github,dist,sdk,.cursor-plugin), zips todist/synter-plugin-<version>.zip, and hard-fails the build ifsynter/.claude-plugin/plugin.jsonisn'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.jsonplugins[0].namematches.claude-plugin/plugin.jsonname, (c) everyskills/*/SKILL.mdfrontmatter has anamematching its directory and a non-emptydescription, (d).mcp.jsoncontains the literaluser_config.synter_api_keyandmcp.jsoncontains the literalSYNTER_API_KEY..github/workflows/validate.yml— runs the validation script on every PR tomain..github/workflows/release.yml— on tag push matchingsynter--v*: validates, builds the zip, and publishes a GitHub Release with the zip attached viasoftprops/action-gh-release(chosen over hand-rolledgh release createscripting — built-in multi-file asset upload and release-notes generation, one declarative step instead of several imperativeghcalls)..gitignore: addeddist/(the script's generated output).Rejected alternative:
actions/upload-artifactalone 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-releasehandles the actual public Release + asset attach in one step.How to Review
scripts/build-zip.shis 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.jsis intentionally dependency-free (nojs-yaml/frontmatter libs) — frontmatter is parsed with a small regex since SKILL.md frontmatter here is flatkey: valueYAML.Test Evidence
Local run on this branch:
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