fix: rebase monorepo release pipeline onto main, fix bump/security issues (supersedes #25) - #28
Merged
Merged
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
…ersion Also drops package.json's leftover "version": "0.1.0" field - dead weight now that this branch has moved to per-package versioning with no repo-wide version.
…le change The "did anything else change" filter ran before the "did a human already bump this package's version" check, and the filter ignores package.json. A package bumped by hand with no other file touched fell through the filter's continue before ever reaching the already-bumped branch, so its higher version was silently never released - despite the script's own docs and the README both promising a manual bump is always respected. Moved the already-bumped check first so it runs regardless of what else changed; the file-change filter now only gates the normal patch-bump path.
Every other action in ci.yml/release.yaml is pinned to a commit SHA; this one was left on a floating branch ref despite being the step that mints the automation GitHub App token with contents: write, used to push to protected main and cut releases. nsheaps/github-actions has no tags, so pinned to that repo's current main tip. docs(readme): correct the GreasyFork auto-update claim The README claimed the GitHub Release's stable download URL "is how GreasyFork picks up new versions," but GreasyFork forcibly rewrites @downloadURL/@updateURL for any script actually listed on its site (this was the whole reason the pipeline targets GitHub Releases directly instead of GreasyFork's own update infra). Only a direct-from-GitHub install benefits from the new URL; GreasyFork's existing listings keep updating through GreasyFork's own mechanism, unaffected by this pipeline.
Userscript Version PreviewPreview only — versions and CHANGELOGs are bumped automatically on merge to
|
ci.yml's own comment deferred running `nx run-many --target=test --all` because packages/template's placeholder test script always failed (`exit 1`), which would break the gate on that scaffold package alone. Rather than leave tests unwired, give every package without real coverage yet a no-op passing `test` script (template, _template, and the two userscript packages) and add a Test step to ci.yml. Real coverage (github-actions-grafana-jump's `node --test`) is untouched. Also restores packages/_template, removed by an earlier commit on this same branch as a "duplicate" of packages/template. Brought back exactly as it existed on main (name, UserScript header, tsconfig), minus the now-redundant per-package `typescript` devDependency that this branch already hoisted to the workspace root for every other package. Added its yarn.lock workspace entry back by hand alongside it - no network access in this environment to regenerate the lockfile via `yarn install`.
Rewrote a few comments that were denser than they needed to be - saying things plainly instead of using terms like "scaffold", "no-op passing test", or "coverage" when a simpler sentence works just as well.
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.
What this does
This picks up #25 (the release pipeline that builds a GitHub Release for each userscript) and gets it ready to merge. #25's description doesn't actually match its own final code anymore - it says the repo uses one shared version number, but a later commit on that same branch changed it so each userscript has its own version. It also says built files get committed to the repo, but another later commit removed that too. I read through the actual commits and code instead of trusting the description, found a few real problems, and fixed them here.
What changed
oxlintto a newer version after this branch was created. Took main's version and fixed upyarn.lockby hand, since I don't have network access in this environment to regenerate it automatically."version": "0.1.0"from the rootpackage.json. It's not used for anything now that each package has its own version.mainand publish releases, so it's the one that most needed pinning.packages/_template. An earlier commit on this branch deleted it, treating it as a duplicate ofpackages/template. It's still onmaintoday, so I restored it as it was.What I didn't do
yarn install, the build, or the tests in this environment - there's no network access to any package registry here. I checked that all the JSON, YAML, and shell files are at least syntactically valid, but CI running on GitHub is the first real test of all this.main's branch protection actually lets the automation bot push to it. If it doesn't, the release job will fail the same way an earlier version of this pipeline did. Not something I can verify from here.packages/templateandpackages/_templateare still both there with no explanation of which one to use. I just restored_templateas asked - didn't try to resolve that.Testing
main🤖 Generated with Claude Code