Add release infrastructure for reproducible, versioned extension builds#1462
Merged
Conversation
PROBLEM
Before this change the repo had no structured release process. Versions
were maintained in two places (package.json at 0.8.0, manifest.json at
0.9.5) with no enforcement that they agree. Building for Chrome Web Store
submission was identical to a local dev build — no minification, no
validation. Packaging was a one-liner shell zip with no versioning in the
filename and no safety checks. There was no mechanism to create GitHub
releases. The CI pipeline uploaded raw dist/ as an artifact, which is not
directly submittable to the Chrome Web Store.
DESIGN DECISIONS
Version single-source-of-truth: package.json is the sole authority for
the extension version. The checked-in manifest.json carries "0.0.0" as a
placeholder. At build time, build.mjs reads the version from package.json
and writes it into dist/manifest.json. This eliminates version drift and
means `npm version patch|minor|major` is the only version-bumping gesture
needed. The canonical version was set to 0.9.5 (the manifest value, which
is what users see in the Chrome Web Store).
Two build modes: `npm run build` produces unminified output for local
development and debugging. `npm run build:release` (RELEASE=1 env var)
enables esbuild minification and is what CI and the release pipeline use.
Both modes inject the version identically. Source maps are off in both
modes by default; developers can flip the flag locally.
Structured packaging (scripts/package-release.js): Creates
release/videospeed-{version}.zip at maximum compression. Before zipping
it validates that the manifest version in dist/ matches package.json,
excludes source maps and .DS_Store, and warns if the zip exceeds the
Chrome Web Store 128 MB limit. Uses the archiver library for portable,
deterministic zip creation instead of shelling out to the zip CLI.
GitHub release automation (scripts/github-release.js): Creates a draft
GitHub release via the gh CLI, attaches the versioned zip, and
auto-generates release notes from the commit log since the previous tag.
Draft status is intentional — it requires manual review before publishing,
which is the right safety tradeoff for a single-maintainer project. The
script validates that gh is installed and authenticated, and that the git
tag exists, before attempting anything.
Pre-push quality gate (.husky/pre-push): Runs lint and the full test
suite before every push. This catches issues earlier than CI and
complements the existing pre-commit hook (which only runs lint-staged on
changed files). The tradeoff is slower pushes (~30s), but for this
project size that is acceptable.
CI produces release-ready artifacts: The CI workflow now runs
build:release instead of build, then runs package-release.js to produce
the versioned zip. The uploaded artifact is the same zip that would be
submitted to the Chrome Web Store, so any CI run on master produces a
directly usable release candidate.
INTENDED RELEASE WORKFLOW
1. npm version patch|minor|major (bumps package.json, creates commit)
2. npm run release (clean → test → build:release → zip)
3. git tag v{version} && git push origin v{version}
4. npm run release:github (draft release with zip on GitHub)
5. Review and publish the draft on GitHub
6. Upload the same zip to Chrome Web Store
FILES CHANGED
manifest.json → version set to "0.0.0" (build-time placeholder)
package.json → version 0.9.5, new scripts, archiver dep
scripts/build.mjs → version injection, RELEASE mode support
scripts/package-release.js → NEW: validated zip packaging
scripts/github-release.js → NEW: draft GitHub release creation
.husky/pre-push → NEW: lint + test gate
.github/workflows/ci.yml → release build + versioned zip artifact
.gitignore → added release/
docs/release.md covers versioning, build modes, release steps, and quality gates for anyone onboarding to the release process.
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.
PROBLEM
Before this change the repo had no structured release process. Versions
were maintained in two places (package.json at 0.8.0, manifest.json at
0.9.5) with no enforcement that they agree. Building for Chrome Web Store
submission was identical to a local dev build — no minification, no
validation. Packaging was a one-liner shell zip with no versioning in the
filename and no safety checks. There was no mechanism to create GitHub
releases. The CI pipeline uploaded raw dist/ as an artifact, which is not
directly submittable to the Chrome Web Store.
DESIGN DECISIONS
Version single-source-of-truth: package.json is the sole authority for
the extension version. The checked-in manifest.json carries "0.0.0" as a
placeholder. At build time, build.mjs reads the version from package.json
and writes it into dist/manifest.json. This eliminates version drift and
means
npm version patch|minor|majoris the only version-bumping gestureneeded. The canonical version was set to 0.9.5 (the manifest value, which
is what users see in the Chrome Web Store).
Two build modes:
npm run buildproduces unminified output for localdevelopment and debugging.
npm run build:release(RELEASE=1 env var)enables esbuild minification and is what CI and the release pipeline use.
Both modes inject the version identically. Source maps are off in both
modes by default; developers can flip the flag locally.
Structured packaging (scripts/package-release.js): Creates
release/videospeed-{version}.zip at maximum compression. Before zipping
it validates that the manifest version in dist/ matches package.json,
excludes source maps and .DS_Store, and warns if the zip exceeds the
Chrome Web Store 128 MB limit. Uses the archiver library for portable,
deterministic zip creation instead of shelling out to the zip CLI.
GitHub release automation (scripts/github-release.js): Creates a draft
GitHub release via the gh CLI, attaches the versioned zip, and
auto-generates release notes from the commit log since the previous tag.
Draft status is intentional — it requires manual review before publishing,
which is the right safety tradeoff for a single-maintainer project. The
script validates that gh is installed and authenticated, and that the git
tag exists, before attempting anything.
Pre-push quality gate (.husky/pre-push): Runs lint and the full test
suite before every push. This catches issues earlier than CI and
complements the existing pre-commit hook (which only runs lint-staged on
changed files). The tradeoff is slower pushes (~30s), but for this
project size that is acceptable.
CI produces release-ready artifacts: The CI workflow now runs
build:release instead of build, then runs package-release.js to produce
the versioned zip. The uploaded artifact is the same zip that would be
submitted to the Chrome Web Store, so any CI run on master produces a
directly usable release candidate.
INTENDED RELEASE WORKFLOW
FILES CHANGED
manifest.json → version set to "0.0.0" (build-time placeholder)
package.json → version 0.9.5, new scripts, archiver dep
scripts/build.mjs → version injection, RELEASE mode support
scripts/package-release.js → NEW: validated zip packaging
scripts/github-release.js → NEW: draft GitHub release creation
.husky/pre-push → NEW: lint + test gate
.github/workflows/ci.yml → release build + versioned zip artifact
.gitignore → added release/