feat: monorepo release pipeline (release-it + GitHub Release assets) - #25
Open
nsheaps-oura wants to merge 9 commits into
Open
feat: monorepo release pipeline (release-it + GitHub Release assets)#25nsheaps-oura wants to merge 9 commits into
nsheaps-oura wants to merge 9 commits into
Conversation
Ignores PR #7's approach per direct instruction; replaces it with a release-based publish pipeline instead of a bot-commit-only one. PR #7's one fix not already on main (graphite-to-github-button's TypeScript source restoration) landed separately via #23's merge, so nothing from it is lost. Versioning (fork 1, per team-lead's resolution): single repo-wide release-it version/tag/changelog/GitHub-Release, matching the convention used by every other nsheaps repo (dotfiles, git-wt, claude-utils, op-exec, agents, ...) - researched live via `gh search code`, an existing internal patterns doc, and direct inspection of nsheaps/agents (the org's actual Nx/yarn monorepo). No nsheaps repo has genuinely independent per-package release tags/changelogs to copy, so this is the established pattern, not a per-package one. Removes the unused, unconfigured @changesets/cli scaffold (no .changeset/ dir ever existed) in favor of release-it. Distribution (fork 2, per team-lead's resolution): skips GreasyFork's own hosting/update infra (confirmed live via GreasyFork's own docs and our existing listing that GreasyFork forcibly rewrites @downloadURL/ @updateURL for any script listed on its site, so pointing those at GitHub directly only works for scripts NOT relying on GreasyFork's own update mechanism). Both public packages' @downloadURL/@updateURL now point at `.../releases/latest/download/<package>.user.js` - verified live against a real nsheaps release (git-wt) that this URL shape 302-redirects to the actual latest-tagged asset. Existing GreasyFork listings (509840, 509841) are untouched; this doesn't retroactively affect users who already installed from there. Per-package opt-in: `"greasyforkPublish": true` in a package's own package.json controls whether its dist/script.user.js gets uploaded to the GitHub Release. github-actions-grafana-jump does NOT set it - its header already says it's intentionally internal-only, and this preserves that. Pipeline (.github/workflows/release.yaml, triggered on push to main): build all packages -> commit regenerated dist/*.user.js if changed (generalizes the same idea #58 built, redesigned as one job instead of a separate workflow to avoid racing release-it's own commit+tag) -> release-it --ci (no-ops if there are no releasable commits since the last tag) -> if a release was actually cut, upload each opted-in package's dist/script.user.js, renamed to <package>.user.js, to that release. ci.yml unchanged in spirit (yarn run build is still the required per-PR/per-push correctness gate - a build/typecheck failure already fails this check) - pinned actions/checkout and actions/setup-node to hashes while touching this file, since they were on floating major- version tags. Formalized gaps found in the existing packages while verifying current state per the task's own instruction: - github-to-graphite-button never had the copy-and-strip-use-strict build step graphite-to-github-button already had, so it never produced a dist/script.user.js at all - added the same step. - Both packages' tsconfig.json gained the same sourceMap:false + explanatory comments graphite-to-github-button already carried from earlier work, now that github-to-graphite-button needs the same reasoning documented. - github-actions-grafana-jump's "types": ["node", "greasemonkey"] override (needed under this repo's moduleResolution: bundler setting) was silently relying on @types/node being present only as an accidental transitive dependency of the now-removed @changesets/cli - added @types/node as an explicit devDependency. - Pinned a freshly-resolved transitive dependency (yoctocolors, pulled in by release-it) to the same 2.1.2 already vetted/used elsewhere in the org, after a fresh resolution pulled in 2.2.0 and got blocked by a local supply-chain safe-chain minimum-package-age check. Verified live, not from memory: - Full clean `nx run-many --target=build --all` succeeds for all 5 packages under the current toolchain (TS 6.0.3, nx 23.0.2). - Both public packages' dist/script.user.js start with the UserScript header (use-strict-stripping still works) and carry the new @downloadURL/@updateURL. - oxlint passes (exit 0) for all 5 packages; github-actions-grafana- jump's 14 existing unit tests pass. - `release-it --ci --dry-run` runs non-interactively end-to-end: correctly computes v0.1.0 (no existing tags), generates a real conventional-commits CHANGELOG.md grouped by type from actual repo history, and shows the exact git add/commit/tag/push sequence and GitHub Release URL it would execute - this is what release.yaml actually runs in CI (release:ci), so this is a genuine dry run of the real pipeline, not a hand-simulated approximation. Known open risk, not resolved here (task #45): main's live `require-pr` ruleset hasn't been confirmed to actually grant the automation app's settings.yml-declared bypass yet. release.yaml's pushes (both the dist-regen commit and release-it's own commit+tag) depend on that bypass actually working; if it doesn't, this pipeline's pushes to main will fail the same way regen-dist.yaml's did before checkout-as-app was added. No branch/PR fallback is implemented for this workflow (unlike the narrower one this supersedes) since a release run that can't land on main isn't meaningfully retryable via a side branch. Not done here, left for a human call per the task's own instruction: formally closing PR #7 - it should be closed with an explanatory comment once this PR is confirmed to actually supersede it, not unilaterally before that's certain.
User's explicit call: dist/ should never be committed to the repo at all - it's a purely transient build artifact from now on, attached directly to GitHub Releases as its only distribution mechanism. release.yaml changes: - Removed the "stage regenerated userscripts if any changed" step (git add/commit/push of dist/*.user.js) entirely. - Reordered: release-it now runs FIRST (deciding whether there's anything releasable at all), and the build only happens conditionally, after, only when a release was actually cut - no wasted build when there's nothing to release. - The asset-upload step already read from the job's own freshly-built dist/ (never a committed file), so no change needed there beyond the reordering. Also untracked the two dist/script.user.js files this repo had force-committed historically (graphite-to-github-button, and github-to-graphite-button as added by this same PR) - both are now build-only like every other package's dist output, consistent with the release-only model. Nothing regenerates or re-commits them going forward. This removes the need for bypass-actor coverage on a dist-commit step entirely. release-it's own version-bump commit+tag still needs to push straight to protected main as the automation bot - that bypass question is being handled separately at the org level (task #103, fixing it at the nsheaps/.github settings-sync source rather than patching the live ruleset directly), not re-litigated in this PR. Verified: full clean build still succeeds (Nx cache correctly restores identical output after deleting dist/ locally - checked content, not just cache-hit claim); `git status` confirms dist/ stays untracked after a fresh build; `release-it --ci --dry-run` re-run shows the changeset now contains only the intended file changes (workflow + the two dist untrackings), with no dist noise going forward once this commit lands.
…plate packages/_template and packages/template were near-identical scaffolds, both introduced by the same initial commit (7ff42bb) and both undocumented. Keep packages/template: ci.yml already refers to it by that exact name as the package whose placeholder `exit 1` test script keeps a repo-wide test gate from being wired up yet. Refs #25
typescript was pinned identically (~6.0.3) in every package as well as the root. Yarn workspace hoisting already makes the root copy resolvable from every package, so the per-package entries were redundant duplication that had to be bumped in lockstep. Genuinely package-specific devDependencies (@types/greasemonkey, @types/node) are left alone. The two greasyforkPublish packages' copies are removed in the following commit, which rewrites those same package.json files' build scripts. Refs #25
…"use strict" Each userscript's metadata block moves out of the top of src/index.ts and into a per-package src/meta.json. scripts/build-userscript.mjs renders the `// ==UserScript==` block from it - taking @Version from the package's own package.json - and prepends it to the tsc-compiled dist/index.js to produce dist/script.user.js. This replaces the Node one-liner that string-stripped tsc's leading `"use strict";` line. That prologue only needed removing because the header was committed inside the TS source and so ended up one line below the top of the file; real Greasemonkey silently ignores a metablock that isn't the literal first thing in the file. Generating the header post-compile makes it unconditionally first and lets tsc's prologue simply land underneath it. Switching tsc to module mode was the other option and is not viable: it emits a bare `export {};` marker, a SyntaxError once a userscript manager injects the file as a classic script. package.json is the only source of the version, so the builder rejects a meta.json that declares one. nx's build inputs gain package.json and the builder itself, so a version bump or a change to the renderer invalidates the cached build rather than re-emitting a stale header. Also drops the now-redundant per-package typescript devDependency from these two packages (see previous commit) and rewrites the tsconfig comments, whose "use strict"-stripping rationale no longer applies; the sourceMap: false rationale still does and is kept. Refs #25
…release-it
Replaces the single repo-wide release-it config with a shared
.release-it.base.json plus a per-package .release-it.js in each
greasyforkPublish package, so each script owns its own version and its own
CHANGELOG.md instead of sharing one repo-wide version and changelog.
Each package's package.json is now the single source of truth for its version.
The base config uses release-it's native npm plugin with
`npm: { publish: false }` rather than @release-it/bumper - bumper is only
needed when the version lives in a non-package.json manifest, which is why the
marketplace repos this pattern came from need it and we don't. Note the old
root config set `npm: false`, which disables the npm plugin outright including
version bumping; that is why the repo-wide version was never actually bumped
and releases were driven purely by git tags.
The conventional-changelog preset carries over unchanged, with
gitRawCommitsOpts.path scoping each changelog to commits that touched that
package - otherwise every package's CHANGELOG would list the whole monorepo's
history. Committing, tagging and pushing are disabled per package; the release
workflow aggregates them into one commit and one push.
github-actions-grafana-jump is internal-only (no greasyforkPublish field) and
deliberately gets none of this.
Refs #25
Adds a root mise.toml pinning node 24.18.0 (matching .nvmrc and package.json's engines.node) and yarn 4.17.0 (matching package.json's packageManager), so a local checkout and CI resolve the same toolchain from one file. jq is pinned too - scripts/auto-bump-packages.sh uses it to build its JSON summary, and pinning it here means local runs match the runners' preinstalled copy. CI swaps actions/setup-node for jdx/mise-action, which also removes the separate corepack enable/install step since mise provisions yarn directly. Refs #25
…on PRs Rewrites release.yaml around scripts/auto-bump-packages.sh, which patch-bumps each greasyforkPublish package whose own files changed since a base ref and preserves any package a human already bumped higher. Change detection excludes each package's package.json and CHANGELOG.md so a bump commit cannot re-trigger a bump for itself. On a pull request the script runs with --preview (arithmetic next-patch, no release-it invocation, no writes) and posts a sticky Package/Base/New/Action table. Nothing is committed to the PR branch. On push to main a single job bumps, lints, commits once, builds, and does one atomic push of the bump commit plus the release tags. Doing all of it in one push is what prevents a bump from triggering a follow-up lint commit that would trigger another bump. Change detection uses a moving release/last-run tag, which replaces the old before/after `git describe` comparison - that only worked when a single repo-wide tag was cut per run. Releases stay one-per-run rather than one-per-package. Every script's @downloadurl points at the stable releases/latest/download/<name>.user.js path, so per-package tags would make "latest" ambiguous and leave some scripts' URLs resolving to a release with no asset for them; one release per run carries every bumped script's asset. Per-package provenance lives in each package's own version and CHANGELOG.md. This judgement call is documented inline in the workflow. Refs #25
The README still documented Changesets, which this repo has not used since it moved to release-it, and an nvm/corepack setup that mise now replaces. Documents how to add a new script (copy packages/template, which was previously undocumented), where a script's UserScript metadata now lives, and how per-package versioning and releases work. Refs #25
Userscript Version PreviewPreview only — versions and CHANGELOGs are bumped automatically on merge to
|
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.
Summary
Supersedes PR #7 (ignored per direct instruction — see below for what wasn't lost). Builds the release/publish pipeline the task actually asked for: single repo-wide
release-itversioning, a GitHub Release per version, and each opted-in package's compiled userscript uploaded as a release asset that GreasyFork-independent auto-update can point at.Fork 1 resolution (versioning): single repo-wide version
Researched live (
gh search codeacross the org, an existing internal patterns doc, and direct inspection ofnsheaps/agents, the org's actual Nx/yarn monorepo) — every nsheaps repo usingrelease-ituses ONE repo-wide.release-it.json/tag/changelog/release, includingagentsitself. There's no established independent-per-package pattern anywhere in the org to copy, so this repo now matches that convention. Removed the unused@changesets/cliscaffold (no.changeset/config ever existed) in favor of it.Fork 2 resolution (distribution): GitHub Releases directly, skip GreasyFork's own update infra
Confirmed live via GreasyFork's own docs/discussions and our existing listings: GreasyFork forcibly rewrites
@downloadURL/@updateURLfor any script actually listed on its site, so pointing those at a GitHub Releases URL only works for scripts not relying on GreasyFork's own update mechanism. Both public packages now point@downloadURL/@updateURLathttps://github.com/nsheaps/greasemonkey-scripts/releases/latest/download/<package>.user.js— verified live that this exact URL shape 302-redirects to the correct latest-tagged asset (tested against a real existing nsheaps release). The existing GreasyFork listings (509840, 509841) are untouched by this — no retroactive effect on anyone who already installed from there.Per-package opt-in
A package sets
"greasyforkPublish": truein its ownpackage.jsonto get itsdist/script.user.jsuploaded to releases.github-actions-grafana-jumpdoes not set it — its header already documents it as intentionally internal-only, and this preserves that.Pipeline
New
.github/workflows/release.yaml, triggered on push to main: build all packages → commit regenerateddist/*.user.jsif changed (same idea as #58, redesigned into one job instead of a separate workflow to avoid racing release-it's own commit+tag) →release-it --ci(no-ops if there are no releasable commits since the last tag) → if a release was actually cut, upload each opted-in package's asset.ci.ymlis functionally unchanged (yarn run buildis still the required correctness gate for every push/PR) — pinnedactions/checkout/actions/setup-nodeto hashes while touching the file, since they were on floating major-version tags.Gaps formalized while verifying current repo state
github-to-graphite-buttonnever had the copy-and-strip-use-strict build stepgraphite-to-github-buttonalready had, so it never actually produced adist/script.user.js— added the same step + matchingtsconfig.jsoncomments.github-actions-grafana-jump's"types": ["node", "greasemonkey"]override was silently relying on@types/nodebeing present only as an accidental transitive dependency of the now-removed@changesets/cli— added it as an explicit devDependency.yoctocolors, via release-it) to2.1.2, matching what's already vetted/used elsewhere in the org, after a fresh resolution pulled2.2.0and got blocked by a local supply-chain minimum-package-age check.On PR #7
Ignored its approach per instruction, but checked what it actually contained first: the one fix not already superseded elsewhere —
graphite-to-github-button's TypeScript source restoration — already landed on main via #23's merge, so nothing real is lost.#7 will need to be closed manually alongside this PR's merge — checked live via GitHub's GraphQL API (
willCloseTargeton the cross-reference event this PR's#7mention creates) rather than trusting GitHub's own prose docs, which read as if a closing keyword works the same for a referenced PR as for a referenced Issue. It doesn't:willCloseTargetcame backfalsefor this exact PR-to-PR reference, confirming closing keywords (Closes/Fixes/Resolves) only auto-close Issues, not Pull Requests, regardless of wording. No keyword added here since one wouldn't do anything — close #7 by hand once this PR merges.Known open risk (not resolved here — task #45)
main's live
require-prruleset hasn't been confirmed to actually grant the automation app'ssettings.yml-declared bypass yet.release.yaml's pushes (the dist-regen commit and release-it's own commit+tag) depend on that bypass working. If it doesn't, this pipeline's pushes will fail the same way the pre-checkout-as-app version of the old workflow did. No branch/PR fallback is implemented here (unlike the narrower workflow this supersedes) since a release run that can't land on main isn't meaningfully retryable via a side branch.Test plan
nx run-many --target=build --allsucceeds for all 5 packages under the current toolchain (TS 6.0.3, nx 23.0.2)dist/script.user.jsstart with the UserScript header (use-strict-stripping still works) and carry the new@downloadURL/@updateURLoxlintpasses (exit 0) for all 5 packages;github-actions-grafana-jump's 14 existing unit tests passrelease-it --ci --dry-runruns non-interactively end-to-end: correctly computesv0.1.0, generates a real conventional-commitsCHANGELOG.mdfrom actual repo history, and shows the exact add/commit/tag/push sequence + GitHub Release URL it would hit — this is whatrelease.yamlactually runs, so it's a genuine dry run of the real pipeline.../releases/latest/download/<asset>302-redirects correctly🤖 Generated with Claude Code