From 4a38bf0649f4527e0f043f0356c7c9e023b92787 Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Mon, 20 Jul 2026 18:44:26 -0400 Subject: [PATCH 1/9] fix(graphite-to-github-button): restore TypeScript source Commit 4fc2806 renamed src/index.ts straight to dist/script.user.js without ever compiling it, leaving raw TypeScript (with type annotations) committed under a .js extension. Recover the original TypeScript source at its pre-rename content so it can be compiled properly again. --- .../graphite-to-github-button/src/index.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 packages/graphite-to-github-button/src/index.ts diff --git a/packages/graphite-to-github-button/src/index.ts b/packages/graphite-to-github-button/src/index.ts new file mode 100644 index 0000000..cdcdbff --- /dev/null +++ b/packages/graphite-to-github-button/src/index.ts @@ -0,0 +1,67 @@ +// ==UserScript== +// @name Graphite => GitHub button +// @description Add a button to go from app.graphite.dev to github.com +// @match https://app.graphite.dev/* +// @version 0.3.3 +// @run-at document-start +// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== +// @grant none +// @license MIT +// @namespace https://app.graphite.dev +// @downloadURL https://update.greasyfork.org/scripts/509841/Graphite%20%3D%3E%20GitHub%20button.user.js +// @updateURL https://update.greasyfork.org/scripts/509841/Graphite%20%3D%3E%20GitHub%20button.meta.js +// ==/UserScript== + +const PATH_REGEX = /^\/github\/pr\/([^\/]+)\/([^\/]+)\/([^\/]+).*$/; +const SELECTOR = + '[class^="PullRequestTitleBar_container_"] > div:nth-child(1) > div:nth-child(2)'; + +const addButton = (toolbar: HTMLElement) => { + const match = window.location.pathname.match(PATH_REGEX); + if (!match) return; + + const [_, org, repo, pr] = match; + const gitHubLink = `https://github.com/${org}/${repo}/pull/${pr}`; + + if (document.getElementById("gitHubLink") != null) { + return; + } + + const anchorEl = document.createElement("a"); + anchorEl.setAttribute("id", "gitHubLink"); + anchorEl.setAttribute("href", gitHubLink); + anchorEl.setAttribute("target", "_blank"); + anchorEl.setAttribute( + "style", + "background: #f0f0f333; padding: 6px; border-radius: 4px; flex-shrink: 0;" + ); + anchorEl.appendChild(document.createTextNode("GitHub ↗️")); + + toolbar.appendChild(anchorEl); +}; + +const toolbarObserver = new MutationObserver((_, observer) => { + const toolbar = document.querySelector(SELECTOR) as HTMLElement; + if (toolbar) { + observer.disconnect(); + addButton(toolbar); + } +}); + +let lastPathname: string | undefined; +const routeChangeObserver = new MutationObserver(() => { + const { pathname } = window.location; + + if (pathname !== lastPathname) { + lastPathname = pathname; + + if (pathname.match(PATH_REGEX)) { + toolbarObserver.observe(document.body, { + childList: true, + subtree: true, + }); + } + } +}); + +routeChangeObserver.observe(document.body, { childList: true, subtree: true }); From f79e6b8d2d3bf347029b04afb69c2b7156f10c5a Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Mon, 20 Jul 2026 18:44:26 -0400 Subject: [PATCH 2/9] build(graphite-to-github-button): compile src to dist/script.user.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a real tsc build step, matching the tsc --build convention used by sibling packages in this Nx workspace. Two per-package tsconfig overrides are needed beyond the shared pattern: - alwaysStrict: false — this file has no import/export, so tsc treats it as a global script and (since strict implies alwaysStrict) would prepend a `"use strict";` line above the `// ==UserScript==` header that Greasy Fork/userscript managers scan for at the top of the file. - sourceMap: false — dist/script.user.js is the only artifact shipped (via a stable raw.githubusercontent.com URL) to Greasy Fork; skip the sourceMap so the compiled output doesn't end with a dangling `//# sourceMappingURL=...` reference to a file nobody serves. The build script chains the dist/index.js -> dist/script.user.js copy after `tsc --build` directly (rather than as a separate `postbuild` script) since Nx invokes the `build` target script directly and does not honor npm/yarn's pre/post script hook convention. --- .../graphite-to-github-button/package.json | 2 +- .../graphite-to-github-button/tsconfig.json | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/graphite-to-github-button/package.json b/packages/graphite-to-github-button/package.json index 8697d08..08e9ff4 100644 --- a/packages/graphite-to-github-button/package.json +++ b/packages/graphite-to-github-button/package.json @@ -3,7 +3,7 @@ "version": "0.3.3", "main": "dist/index.js", "scripts": { - "build": "tsc --build", + "build": "tsc --build && node -e \"require('fs').copyFileSync('dist/index.js', 'dist/script.user.js')\"", "lint": "oxlint" }, "devDependencies": { diff --git a/packages/graphite-to-github-button/tsconfig.json b/packages/graphite-to-github-button/tsconfig.json index 33034f9..b3dcbaf 100644 --- a/packages/graphite-to-github-button/tsconfig.json +++ b/packages/graphite-to-github-button/tsconfig.json @@ -2,7 +2,23 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + // This package's src/index.ts has no import/export statements, so tsc + // treats it as a global script rather than a module and (because + // "strict" implies "alwaysStrict") prepends a `"use strict";` line to + // the compiled output. That line would sit above the + // `// ==UserScript==` header block that Greasy Fork/userscript managers + // scan for, which is meant to be the first thing in the file. Disable + // just this sub-flag so the compiled dist/script.user.js starts with + // the header, matching the original hand-written source. + "alwaysStrict": false, + // dist/script.user.js is the only build artifact committed and served + // (via a stable raw.githubusercontent.com URL) to Greasy Fork; the + // sibling index.js.map is not shipped alongside it. Skip emitting a + // sourceMap for this package so the compiled output doesn't end with a + // `//# sourceMappingURL=...` comment pointing at a file that won't be + // there for end users. + "sourceMap": false }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] From 9675e647a4df1bc664778c03e354dd3d95d0ebc4 Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Mon, 20 Jul 2026 18:44:26 -0400 Subject: [PATCH 3/9] fix(graphite-to-github-button): regenerate compiled dist/script.user.js Replace the raw-TypeScript-with-a-.js-extension file with the actual tsc build output, so the file Greasy Fork syncs from raw.githubusercontent.com is valid, runnable JavaScript again. --- .../dist/script.user.js | 78 ++++++++----------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/packages/graphite-to-github-button/dist/script.user.js b/packages/graphite-to-github-button/dist/script.user.js index cdcdbff..abbb1f8 100644 --- a/packages/graphite-to-github-button/dist/script.user.js +++ b/packages/graphite-to-github-button/dist/script.user.js @@ -11,57 +11,43 @@ // @downloadURL https://update.greasyfork.org/scripts/509841/Graphite%20%3D%3E%20GitHub%20button.user.js // @updateURL https://update.greasyfork.org/scripts/509841/Graphite%20%3D%3E%20GitHub%20button.meta.js // ==/UserScript== - const PATH_REGEX = /^\/github\/pr\/([^\/]+)\/([^\/]+)\/([^\/]+).*$/; -const SELECTOR = - '[class^="PullRequestTitleBar_container_"] > div:nth-child(1) > div:nth-child(2)'; - -const addButton = (toolbar: HTMLElement) => { - const match = window.location.pathname.match(PATH_REGEX); - if (!match) return; - - const [_, org, repo, pr] = match; - const gitHubLink = `https://github.com/${org}/${repo}/pull/${pr}`; - - if (document.getElementById("gitHubLink") != null) { - return; - } - - const anchorEl = document.createElement("a"); - anchorEl.setAttribute("id", "gitHubLink"); - anchorEl.setAttribute("href", gitHubLink); - anchorEl.setAttribute("target", "_blank"); - anchorEl.setAttribute( - "style", - "background: #f0f0f333; padding: 6px; border-radius: 4px; flex-shrink: 0;" - ); - anchorEl.appendChild(document.createTextNode("GitHub ↗️")); - - toolbar.appendChild(anchorEl); +const SELECTOR = '[class^="PullRequestTitleBar_container_"] > div:nth-child(1) > div:nth-child(2)'; +const addButton = (toolbar) => { + const match = window.location.pathname.match(PATH_REGEX); + if (!match) + return; + const [_, org, repo, pr] = match; + const gitHubLink = `https://github.com/${org}/${repo}/pull/${pr}`; + if (document.getElementById("gitHubLink") != null) { + return; + } + const anchorEl = document.createElement("a"); + anchorEl.setAttribute("id", "gitHubLink"); + anchorEl.setAttribute("href", gitHubLink); + anchorEl.setAttribute("target", "_blank"); + anchorEl.setAttribute("style", "background: #f0f0f333; padding: 6px; border-radius: 4px; flex-shrink: 0;"); + anchorEl.appendChild(document.createTextNode("GitHub ↗️")); + toolbar.appendChild(anchorEl); }; - const toolbarObserver = new MutationObserver((_, observer) => { - const toolbar = document.querySelector(SELECTOR) as HTMLElement; - if (toolbar) { - observer.disconnect(); - addButton(toolbar); - } + const toolbar = document.querySelector(SELECTOR); + if (toolbar) { + observer.disconnect(); + addButton(toolbar); + } }); - -let lastPathname: string | undefined; +let lastPathname; const routeChangeObserver = new MutationObserver(() => { - const { pathname } = window.location; - - if (pathname !== lastPathname) { - lastPathname = pathname; - - if (pathname.match(PATH_REGEX)) { - toolbarObserver.observe(document.body, { - childList: true, - subtree: true, - }); + const { pathname } = window.location; + if (pathname !== lastPathname) { + lastPathname = pathname; + if (pathname.match(PATH_REGEX)) { + toolbarObserver.observe(document.body, { + childList: true, + subtree: true, + }); + } } - } }); - routeChangeObserver.observe(document.body, { childList: true, subtree: true }); From d143e1841efe1d9babcc3c26c1fe9ce703586d54 Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Mon, 20 Jul 2026 18:47:07 -0400 Subject: [PATCH 4/9] ci(graphite-to-github-button): auto-regenerate dist/script.user.js Add a workflow that rebuilds and commits the compiled userscript on every push to main touching the package's source or build config, so the class of bug in 4fc2806 (raw TypeScript committed under a .js path, silently going stale relative to src/) can't recur. Loop-safe via two layers: the path filter excludes the package's own dist/ output, and the commit step only pushes when the rebuild actually produced a diff. Uses the default GITHUB_TOKEN scoped to contents: write on this job only, and pinned action commit hashes. --- .github/workflows/regen-dist.yaml | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/regen-dist.yaml diff --git a/.github/workflows/regen-dist.yaml b/.github/workflows/regen-dist.yaml new file mode 100644 index 0000000..c0e5099 --- /dev/null +++ b/.github/workflows/regen-dist.yaml @@ -0,0 +1,75 @@ +name: Regenerate Compiled Userscript + +# Keeps packages/graphite-to-github-button/dist/script.user.js in sync with +# its TypeScript source on every push to main. dist/ is gitignored +# workspace-wide, but this one file is force-committed because Greasy +# Fork's sync mechanism points at a stable raw.githubusercontent.com URL +# for it — see the package's own history (4fc2806 renamed the raw .ts +# source straight to this path without ever compiling it; this workflow +# exists so that class of bug can't silently recur). +# +# Loop safety (same pattern as nsheaps/github-actions' sync-plugin-specs +# reusable workflow): two independent layers — +# 1. The `paths` filter below only matches the package's source/config +# files, not its dist/ output, so the bot's own commit (which only +# touches dist/script.user.js) can't re-trigger this workflow. +# 2. The commit step only runs `git commit`/`push` when the rebuild +# actually produced a diff. Rebuilding from unchanged source is +# deterministic, so even a manual workflow_dispatch re-run right +# after a bot commit is a no-op. + +on: + push: + branches: [main] + paths: + - "packages/graphite-to-github-button/src/**" + - "packages/graphite-to-github-button/package.json" + - "packages/graphite-to-github-button/tsconfig.json" + - "tsconfig.base.json" + - ".github/workflows/regen-dist.yaml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + # Serialize per-ref so a fast follow-up push waits for the in-flight run + # instead of racing it on the same branch. + group: regen-dist-${{ github.ref }} + cancel-in-progress: false + +jobs: + regen: + name: Rebuild dist/script.user.js + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: ".nvmrc" + + - name: Set up corepack + run: corepack enable && corepack install + + - name: Install dependencies + run: yarn install --immutable + + - name: Build package + run: yarn workspace @nsheaps/gm-graphite-to-github-button run build + + - name: Commit regenerated dist output if it changed + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -f packages/graphite-to-github-button/dist/script.user.js + if git diff --cached --quiet; then + echo "dist/script.user.js already up to date; nothing to commit." + exit 0 + fi + git commit -m "chore(graphite-to-github-button): regenerate compiled userscript" + git push From 487b36ade2fad3b0bd49c211a46a6a3d6e66bcfc Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Wed, 22 Jul 2026 17:28:21 -0400 Subject: [PATCH 5/9] ci(regen-dist): fall back to a PR when direct push to main is blocked main has an active require-pr ruleset with no bypass actors, so the workflow's plain `git push` to main would always fail once merged. Follow the same "try direct push, fall back to PR" idiom as nsheaps/github-actions' sync-main-to-edge reusable workflow: attempt a direct push first, and only if that's rejected, push to a branch and open a PR via that repo's open-pr-if-needed composite action. Addresses review comment: https://github.com/nsheaps/greasemonkey-scripts/pull/7#discussion_r3633874266 --- .github/workflows/regen-dist.yaml | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/workflows/regen-dist.yaml b/.github/workflows/regen-dist.yaml index c0e5099..e5678e8 100644 --- a/.github/workflows/regen-dist.yaml +++ b/.github/workflows/regen-dist.yaml @@ -17,6 +17,14 @@ name: Regenerate Compiled Userscript # actually produced a diff. Rebuilding from unchanged source is # deterministic, so even a manual workflow_dispatch re-run right # after a bot commit is a no-op. +# +# main is protected by an active `require-pr` ruleset with no bypass actors, +# so a plain `git push` straight to main always fails here. This follows the +# same "try direct push, fall back to PR" idiom as nsheaps/github-actions' +# sync-main-to-edge reusable workflow: attempt the push, and only if branch +# protection rejects it, push to a branch and open a PR via that repo's +# open-pr-if-needed composite action (idempotent — reuses an already-open PR +# instead of piling up duplicates). on: push: @@ -44,6 +52,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + pull-requests: write steps: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 @@ -62,14 +71,43 @@ jobs: - name: Build package run: yarn workspace @nsheaps/gm-graphite-to-github-button run build - - name: Commit regenerated dist output if it changed + - name: Stage regenerated dist output if it changed + id: stage run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add -f packages/graphite-to-github-button/dist/script.user.js if git diff --cached --quiet; then echo "dist/script.user.js already up to date; nothing to commit." + echo "changed=false" >> "$GITHUB_OUTPUT" exit 0 fi git commit -m "chore(graphite-to-github-button): regenerate compiled userscript" - git push + echo "changed=true" >> "$GITHUB_OUTPUT" + + - name: Try direct push to main + if: steps.stage.outputs.changed == 'true' + id: push + continue-on-error: true + run: git push origin HEAD:main + + - name: Push to a branch if direct push was blocked + if: steps.stage.outputs.changed == 'true' && steps.push.outcome == 'failure' + id: branch + env: + BRANCH: bot/regen-dist-graphite-to-github-button + run: | + git push -f origin "HEAD:refs/heads/${BRANCH}" + echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT" + + - name: Open PR if direct push was blocked + if: steps.stage.outputs.changed == 'true' && steps.push.outcome == 'failure' + uses: nsheaps/github-actions/.github/actions/open-pr-if-needed@main + with: + title: "chore(graphite-to-github-button): regenerate compiled userscript" + body: | + Direct push to `main` was blocked by branch protection, so the + regenerated `dist/script.user.js` is going through a PR instead. + base: main + head: ${{ steps.branch.outputs.branch }} + token: ${{ secrets.GITHUB_TOKEN }} From d7344f3aa30d11d04cabc0fab53df98959e43b2d Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Fri, 24 Jul 2026 14:22:15 -0400 Subject: [PATCH 6/9] ci(regen-dist): auth as automation bot to push directly to main Swap the plain checkout for checkout-as-app so the push authenticates as the org automation GitHub App, which settings.yml grants an always-exemption on the require-pr ruleset (same as repo admins). checkout-as-app also sets git identity to the app's bot identity, so the manual git config lines are no longer needed. Keep the existing branch+PR fallback as defense-in-depth per task #45's live-ruleset sync-gap finding. Addresses review comment: https://github.com/nsheaps/greasemonkey-scripts/pull/7#discussion_r3633966676 Co-Authored-By: Claude Code (~/src/nsheaps/greasemonkey-scripts) --- .github/workflows/regen-dist.yaml | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/regen-dist.yaml b/.github/workflows/regen-dist.yaml index e5678e8..d34a2c3 100644 --- a/.github/workflows/regen-dist.yaml +++ b/.github/workflows/regen-dist.yaml @@ -18,11 +18,17 @@ name: Regenerate Compiled Userscript # deterministic, so even a manual workflow_dispatch re-run right # after a bot commit is a no-op. # -# main is protected by an active `require-pr` ruleset with no bypass actors, -# so a plain `git push` straight to main always fails here. This follows the -# same "try direct push, fall back to PR" idiom as nsheaps/github-actions' -# sync-main-to-edge reusable workflow: attempt the push, and only if branch -# protection rejects it, push to a branch and open a PR via that repo's +# main is protected by an active `require-pr` ruleset, but this repo's +# settings.yml grants the automation GitHub App an always-exemption +# (bypass_mode: always) on that ruleset — same as repo admins — so +# authenticating as that app via checkout-as-app (the same action +# apply-repo-settings.yaml uses) should let this push straight to main. +# The branch+PR fallback below is kept as defense-in-depth: the live +# ruleset hasn't been confirmed to actually reflect that settings.yml +# exemption yet (see task #45's bypass_actors sync-gap investigation), +# so if the push is still rejected, this falls back to the same +# "try direct push, fall back to PR" idiom as nsheaps/github-actions' +# sync-main-to-edge reusable workflow, opening a PR via that repo's # open-pr-if-needed composite action (idempotent — reuses an already-open PR # instead of piling up duplicates). @@ -54,8 +60,11 @@ jobs: contents: write pull-requests: write steps: - - name: Checkout - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - name: Checkout as automation bot + uses: nsheaps/github-actions/.github/actions/checkout-as-app@main + with: + app-id: ${{ secrets.AUTOMATION_GITHUB_APP_ID }} + private-key: ${{ secrets.AUTOMATION_GITHUB_APP_PRIVATE_KEY }} - name: Set up Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -74,8 +83,6 @@ jobs: - name: Stage regenerated dist output if it changed id: stage run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" git add -f packages/graphite-to-github-button/dist/script.user.js if git diff --cached --quiet; then echo "dist/script.user.js already up to date; nothing to commit." From e3a23bed13907a7667dd87cb5784c81127fa5dd7 Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Fri, 24 Jul 2026 17:34:33 -0400 Subject: [PATCH 7/9] fix(build): stop using deprecated tsconfig options (TS5107) - tsconfig.base.json: moduleResolution "node" (deprecated alias for node10) -> "bundler", the modern pairing for module: ESNext. No package imports external modules, so this has no effect on output. - graphite-to-github-button/tsconfig.json: drop alwaysStrict: false, which is being deprecated/removed upstream. Instead, let tsc emit its normal "use strict" prologue and strip it in the package's own build script when copying dist/index.js to dist/script.user.js, so the UserScript header still leads the file regardless of whether a future TypeScript keeps an escape hatch for this at all. Verified locally: a full `nx run-many --target=build --all --skip-nx-cache` rebuild succeeds across all 4 packages, and dist/script.user.js comes out byte-identical to what's already committed. --- packages/graphite-to-github-button/package.json | 2 +- packages/graphite-to-github-button/tsconfig.json | 13 +++++++++---- tsconfig.base.json | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/graphite-to-github-button/package.json b/packages/graphite-to-github-button/package.json index 08e9ff4..869d1b4 100644 --- a/packages/graphite-to-github-button/package.json +++ b/packages/graphite-to-github-button/package.json @@ -3,7 +3,7 @@ "version": "0.3.3", "main": "dist/index.js", "scripts": { - "build": "tsc --build && node -e \"require('fs').copyFileSync('dist/index.js', 'dist/script.user.js')\"", + "build": "tsc --build && node -e \"const fs=require('fs');const c=fs.readFileSync('dist/index.js','utf8').replace(/^[\\\"']use strict[\\\"'];?\\r?\\n/,'');fs.writeFileSync('dist/script.user.js',c)\"", "lint": "oxlint" }, "devDependencies": { diff --git a/packages/graphite-to-github-button/tsconfig.json b/packages/graphite-to-github-button/tsconfig.json index b3dcbaf..f2b90f6 100644 --- a/packages/graphite-to-github-button/tsconfig.json +++ b/packages/graphite-to-github-button/tsconfig.json @@ -8,10 +8,15 @@ // "strict" implies "alwaysStrict") prepends a `"use strict";` line to // the compiled output. That line would sit above the // `// ==UserScript==` header block that Greasy Fork/userscript managers - // scan for, which is meant to be the first thing in the file. Disable - // just this sub-flag so the compiled dist/script.user.js starts with - // the header, matching the original hand-written source. - "alwaysStrict": false, + // scan for, which is meant to be the first thing in the file. + // + // We used to disable this via `"alwaysStrict": false`, but that value + // is being deprecated/removed upstream, so instead we let tsc emit the + // line as normal and strip it in this package's own `build` script + // (see package.json) when copying dist/index.js to dist/script.user.js + // — that's resilient regardless of whether a future TypeScript keeps + // an escape hatch for this at all. + // // dist/script.user.js is the only build artifact committed and served // (via a stable raw.githubusercontent.com URL) to Greasy Fork; the // sibling index.js.map is not shipped alongside it. Skip emitting a diff --git a/tsconfig.base.json b/tsconfig.base.json index 1173bc3..9bd8dca 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -3,7 +3,7 @@ "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM"], - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, From d524a85fcff95ca7e30f2bd19cf9d50be0340e12 Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Fri, 24 Jul 2026 18:13:24 -0400 Subject: [PATCH 8/9] fix(github-actions-grafana-jump): fix toolchain drift after merging main Merging main (which bumped typescript to ~6.0.3 org-wide) in brought this package along for the first time on this branch, still carrying two pre-existing gaps that block a clean build/immutable install: - package.json still declared typescript ~5.8.3 (this package was added later via the ADX-525 merge, after the org-wide v6 bump, and never got Renovate's attention). That mismatch against every other package's ~6.0.3 left yarn.lock without a consistent resolution, so `yarn install --immutable` failed. Bumped to match; yarn.lock regenerated. - tsconfig.json needed an explicit `"types": ["node", "greasemonkey"]` override: under this repo's moduleResolution: bundler setting, tsc doesn't auto-include @types/node/@types/greasemonkey's ambient globals (`module`, `GM`) the way it did under "node". Same fixes as nsheaps/greasemonkey-scripts#23 (opened separately for main directly); applying them here too since main hadn't merged that PR yet when this branch picked up the package. --- packages/github-actions-grafana-jump/package.json | 2 +- packages/github-actions-grafana-jump/tsconfig.json | 8 +++++++- yarn.lock | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/github-actions-grafana-jump/package.json b/packages/github-actions-grafana-jump/package.json index b8d3a9b..5381507 100644 --- a/packages/github-actions-grafana-jump/package.json +++ b/packages/github-actions-grafana-jump/package.json @@ -10,6 +10,6 @@ }, "devDependencies": { "@types/greasemonkey": "~4.0.7", - "typescript": "~5.8.3" + "typescript": "~6.0.3" } } diff --git a/packages/github-actions-grafana-jump/tsconfig.json b/packages/github-actions-grafana-jump/tsconfig.json index b813d42..7dc0f4f 100644 --- a/packages/github-actions-grafana-jump/tsconfig.json +++ b/packages/github-actions-grafana-jump/tsconfig.json @@ -2,7 +2,13 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + // This is the only package that references ambient globals from + // @types/node (the `module` test-export guard) and @types/greasemonkey + // (`GM`). Under moduleResolution: bundler, tsc does not auto-include + // these from node_modules/@types the way it does under "node"/"node10" - + // list them explicitly rather than relying on that auto-discovery. + "types": ["node", "greasemonkey"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/yarn.lock b/yarn.lock index b0e4be6..e7cab71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -410,7 +410,7 @@ __metadata: resolution: "@nsheaps/gm-github-actions-grafana-jump@workspace:packages/github-actions-grafana-jump" dependencies: "@types/greasemonkey": "npm:~4.0.7" - typescript: "npm:~5.8.3" + typescript: "npm:~6.0.3" languageName: unknown linkType: soft From f4f6f735f3ed2dea492628382ed94aa1546bb425 Mon Sep 17 00:00:00 2001 From: Nathan Heaps Date: Fri, 24 Jul 2026 18:17:04 -0400 Subject: [PATCH 9/9] ci: generalize regen-dist.yaml into an Nx-monorepo-wide build workflow Renamed to regen-userscripts.yaml. Generalizes from hardcoding graphite-to-github-button as the only package this workflow knows about, now that this is a real Nx monorepo with multiple packages (and a 5th, github-actions-grafana-jump, just landed via the ADX-525 merge): - Build: `yarn run build` (== `nx run-many --target=build --all`, the same command ci.yml already runs for every package) instead of the hardcoded `yarn workspace @nsheaps/gm-graphite-to-github-button run build`. Deliberately not `nx affected` - computing a correct base/head for a push-triggered affected run adds real complexity (fetch-depth, first-push-to-a-branch edge cases) for a handful of packages where a full build is cheap; revisit if that stops being true. - Staging: `find packages -maxdepth 3 -type f -path "*/dist/*.user.js"` instead of one hardcoded path. Only packages that actually emit a *.user.js artifact are ever staged - packages/_template, template, and github-actions-grafana-jump all build fine but produce no *.user.js, so they're naturally never touched, no special-casing needed. Picks up any future package that adds one automatically. - paths filter: `packages/**` minus `packages/**/dist/**`, so any package's source/config changes trigger a rebuild while the bot's own dist-only commits still can't re-trigger it (same loop-safety property as before, now repo-wide instead of one package). - Renamed workflow/job/concurrency-group/branch-fallback/commit-message off the single-package naming to reflect the new scope. Kept unchanged: checkout-as-app auth, the direct-push-to-main with branch+open-pr-if-needed fallback pattern, task #45's defense-in-depth reasoning for that fallback. Verified: `yarn run build` (the workflow's exact build command) succeeds cleanly for all 5 packages under the current toolchain (TS 6.0.3, nx 23.0.2), and the staging glob matches exactly the one real *.user.js artifact with zero diff against what's already committed. --- .github/workflows/regen-dist.yaml | 120 ------------------- .github/workflows/regen-userscripts.yaml | 144 +++++++++++++++++++++++ 2 files changed, 144 insertions(+), 120 deletions(-) delete mode 100644 .github/workflows/regen-dist.yaml create mode 100644 .github/workflows/regen-userscripts.yaml diff --git a/.github/workflows/regen-dist.yaml b/.github/workflows/regen-dist.yaml deleted file mode 100644 index d34a2c3..0000000 --- a/.github/workflows/regen-dist.yaml +++ /dev/null @@ -1,120 +0,0 @@ -name: Regenerate Compiled Userscript - -# Keeps packages/graphite-to-github-button/dist/script.user.js in sync with -# its TypeScript source on every push to main. dist/ is gitignored -# workspace-wide, but this one file is force-committed because Greasy -# Fork's sync mechanism points at a stable raw.githubusercontent.com URL -# for it — see the package's own history (4fc2806 renamed the raw .ts -# source straight to this path without ever compiling it; this workflow -# exists so that class of bug can't silently recur). -# -# Loop safety (same pattern as nsheaps/github-actions' sync-plugin-specs -# reusable workflow): two independent layers — -# 1. The `paths` filter below only matches the package's source/config -# files, not its dist/ output, so the bot's own commit (which only -# touches dist/script.user.js) can't re-trigger this workflow. -# 2. The commit step only runs `git commit`/`push` when the rebuild -# actually produced a diff. Rebuilding from unchanged source is -# deterministic, so even a manual workflow_dispatch re-run right -# after a bot commit is a no-op. -# -# main is protected by an active `require-pr` ruleset, but this repo's -# settings.yml grants the automation GitHub App an always-exemption -# (bypass_mode: always) on that ruleset — same as repo admins — so -# authenticating as that app via checkout-as-app (the same action -# apply-repo-settings.yaml uses) should let this push straight to main. -# The branch+PR fallback below is kept as defense-in-depth: the live -# ruleset hasn't been confirmed to actually reflect that settings.yml -# exemption yet (see task #45's bypass_actors sync-gap investigation), -# so if the push is still rejected, this falls back to the same -# "try direct push, fall back to PR" idiom as nsheaps/github-actions' -# sync-main-to-edge reusable workflow, opening a PR via that repo's -# open-pr-if-needed composite action (idempotent — reuses an already-open PR -# instead of piling up duplicates). - -on: - push: - branches: [main] - paths: - - "packages/graphite-to-github-button/src/**" - - "packages/graphite-to-github-button/package.json" - - "packages/graphite-to-github-button/tsconfig.json" - - "tsconfig.base.json" - - ".github/workflows/regen-dist.yaml" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - # Serialize per-ref so a fast follow-up push waits for the in-flight run - # instead of racing it on the same branch. - group: regen-dist-${{ github.ref }} - cancel-in-progress: false - -jobs: - regen: - name: Rebuild dist/script.user.js - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout as automation bot - uses: nsheaps/github-actions/.github/actions/checkout-as-app@main - with: - app-id: ${{ secrets.AUTOMATION_GITHUB_APP_ID }} - private-key: ${{ secrets.AUTOMATION_GITHUB_APP_PRIVATE_KEY }} - - - name: Set up Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version-file: ".nvmrc" - - - name: Set up corepack - run: corepack enable && corepack install - - - name: Install dependencies - run: yarn install --immutable - - - name: Build package - run: yarn workspace @nsheaps/gm-graphite-to-github-button run build - - - name: Stage regenerated dist output if it changed - id: stage - run: | - git add -f packages/graphite-to-github-button/dist/script.user.js - if git diff --cached --quiet; then - echo "dist/script.user.js already up to date; nothing to commit." - echo "changed=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - git commit -m "chore(graphite-to-github-button): regenerate compiled userscript" - echo "changed=true" >> "$GITHUB_OUTPUT" - - - name: Try direct push to main - if: steps.stage.outputs.changed == 'true' - id: push - continue-on-error: true - run: git push origin HEAD:main - - - name: Push to a branch if direct push was blocked - if: steps.stage.outputs.changed == 'true' && steps.push.outcome == 'failure' - id: branch - env: - BRANCH: bot/regen-dist-graphite-to-github-button - run: | - git push -f origin "HEAD:refs/heads/${BRANCH}" - echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT" - - - name: Open PR if direct push was blocked - if: steps.stage.outputs.changed == 'true' && steps.push.outcome == 'failure' - uses: nsheaps/github-actions/.github/actions/open-pr-if-needed@main - with: - title: "chore(graphite-to-github-button): regenerate compiled userscript" - body: | - Direct push to `main` was blocked by branch protection, so the - regenerated `dist/script.user.js` is going through a PR instead. - base: main - head: ${{ steps.branch.outputs.branch }} - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/regen-userscripts.yaml b/.github/workflows/regen-userscripts.yaml new file mode 100644 index 0000000..6c67f6b --- /dev/null +++ b/.github/workflows/regen-userscripts.yaml @@ -0,0 +1,144 @@ +name: Regenerate Compiled Userscripts + +# Keeps every package's committed dist/*.user.js in sync with its TypeScript +# source. dist/ is gitignored workspace-wide, but each package that publishes +# a userscript force-commits its dist/*.user.js because Greasy Fork's sync +# mechanism points at a stable raw.githubusercontent.com URL for it — see +# graphite-to-github-button's history (4fc2806 renamed the raw .ts source +# straight to this path without ever compiling it; this workflow exists so +# that class of bug can't silently recur, now for every package, not just +# that one). +# +# This repo is an Nx monorepo (yarn workspaces "packages/*", nx.json's +# "build" target cached per-project). Rather than hardcoding one package's +# build command, this always builds every project (`yarn run build`, i.e. +# `nx run-many --target=build --all` — the same command ci.yml already +# runs) and then stages whatever dist/*.user.js artifacts actually changed. +# We intentionally skip `nx affected` here: computing a correct base/head +# for a push-triggered affected run adds real complexity (fetch-depth, +# first-push-to-a-branch edge cases), and with only a handful of packages a +# full build is cheap. Revisit if the package count grows enough that this +# stops being true. +# +# Loop safety (same pattern as nsheaps/github-actions' sync-plugin-specs +# reusable workflow): two independent layers — +# 1. The `paths` filter below matches any package's source/config files +# but excludes packages/**/dist/**, so the bot's own commit (which +# only touches dist/*.user.js files) can't re-trigger this workflow. +# 2. The staging step only commits/pushes when the rebuild actually +# changed a tracked dist/*.user.js. Rebuilding from unchanged source +# is deterministic, so even a manual workflow_dispatch re-run right +# after a bot commit is a no-op. +# +# Only packages that actually produce a dist/*.user.js are ever staged — +# packages/_template and packages/template build fine (same as in ci.yml) +# but produce no *.user.js output, so they're naturally never touched by +# the staging step below; no special-casing needed. Likewise, +# github-actions-grafana-jump builds fine but has no *.user.js output of +# its own yet either. +# +# main is protected by an active `require-pr` ruleset. This repo's +# settings.yml grants the org automation GitHub App an always-exemption +# (bypass_mode: always) on that ruleset — same as repo admins — so +# authenticating as that app via checkout-as-app (the same action +# apply-repo-settings.yaml uses) should let this push straight to main. +# The branch+PR fallback below is kept as defense-in-depth: the live +# ruleset hasn't been confirmed to actually reflect that settings.yml +# exemption yet (see task #45's bypass_actors sync-gap investigation), +# so if the push is still rejected, this falls back to the same +# "try direct push, fall back to PR" idiom as nsheaps/github-actions' +# sync-main-to-edge reusable workflow, opening a PR via that repo's +# open-pr-if-needed composite action (idempotent — reuses an already-open PR +# instead of piling up duplicates). + +on: + push: + branches: [main] + paths: + - "packages/**" + - "!packages/**/dist/**" + - "tsconfig.base.json" + - ".github/workflows/regen-userscripts.yaml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + # Serialize per-ref so a fast follow-up push waits for the in-flight run + # instead of racing it on the same branch. + group: regen-userscripts-${{ github.ref }} + cancel-in-progress: false + +jobs: + regen: + name: Rebuild compiled userscripts + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout as automation bot + uses: nsheaps/github-actions/.github/actions/checkout-as-app@main + with: + app-id: ${{ secrets.AUTOMATION_GITHUB_APP_ID }} + private-key: ${{ secrets.AUTOMATION_GITHUB_APP_PRIVATE_KEY }} + + - name: Set up Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: ".nvmrc" + + - name: Set up corepack + run: corepack enable && corepack install + + - name: Install dependencies + run: yarn install --immutable + + - name: Build all packages + run: yarn run build + + - name: Stage regenerated userscripts if any changed + id: stage + run: | + mapfile -t USER_SCRIPTS < <(find packages -maxdepth 3 -type f -path "*/dist/*.user.js") + if [ "${#USER_SCRIPTS[@]}" -eq 0 ]; then + echo "No packages/*/dist/*.user.js build artifacts found; nothing to stage." + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + git add -f "${USER_SCRIPTS[@]}" + if git diff --cached --quiet; then + echo "Compiled userscripts already up to date; nothing to commit." + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + git commit -m "chore: regenerate compiled userscripts" + echo "changed=true" >> "$GITHUB_OUTPUT" + + - name: Try direct push to main + if: steps.stage.outputs.changed == 'true' + id: push + continue-on-error: true + run: git push origin HEAD:main + + - name: Push to a branch if direct push was blocked + if: steps.stage.outputs.changed == 'true' && steps.push.outcome == 'failure' + id: branch + env: + BRANCH: bot/regen-userscripts + run: | + git push -f origin "HEAD:refs/heads/${BRANCH}" + echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT" + + - name: Open PR if direct push was blocked + if: steps.stage.outputs.changed == 'true' && steps.push.outcome == 'failure' + uses: nsheaps/github-actions/.github/actions/open-pr-if-needed@main + with: + title: "chore: regenerate compiled userscripts" + body: | + Direct push to `main` was blocked, so the regenerated + compiled userscript(s) are going through a PR instead. + base: main + head: ${{ steps.branch.outputs.branch }} + token: ${{ secrets.GITHUB_TOKEN }}