From 13584654104e3799790c0708306c93c3e180d83a Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Fri, 14 Aug 2026 01:46:53 +0200 Subject: [PATCH 1/3] ci: streamline package publishing --- .github/workflows/publish.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index be8377d..3e6eb67 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -46,12 +46,6 @@ jobs: exit 1 fi - - name: Typecheck - run: npm run typecheck - - - name: Test - run: npm test - - name: Build run: npm run build @@ -59,6 +53,8 @@ jobs: run: npm run verify:package-closure - name: Publish to npm - run: npm publish + # Full typecheck and test coverage run on pull requests and main. + # This tag job publishes only the artifact built and verified above. + run: npm publish --ignore-scripts env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From fc56f89d1dfda2ab9dc898c31b7e7b54b5d6b987 Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Fri, 14 Aug 2026 01:46:54 +0200 Subject: [PATCH 2/3] chore: remove stale football census hooks --- .githooks/fixture-ownership-census-guard.mjs | 42 -------------------- .githooks/install.mjs | 29 -------------- .githooks/post-merge | 15 ------- .githooks/pre-commit | 2 - .githooks/pre-merge-commit | 2 - package.json | 4 +- 6 files changed, 1 insertion(+), 93 deletions(-) delete mode 100644 .githooks/fixture-ownership-census-guard.mjs delete mode 100644 .githooks/install.mjs delete mode 100755 .githooks/post-merge delete mode 100755 .githooks/pre-commit delete mode 100755 .githooks/pre-merge-commit diff --git a/.githooks/fixture-ownership-census-guard.mjs b/.githooks/fixture-ownership-census-guard.mjs deleted file mode 100644 index b71b03b..0000000 --- a/.githooks/fixture-ownership-census-guard.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { spawnSync } from "node:child_process"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const pattern = process.env.SIMFILE_CENSUS_HOOK_PATTERN ?? "fixture-ownership census permanently enforces"; -const regenerationCommand = 'SIMFILE_UPDATE_FIXTURE_OWNERSHIP_CENSUS=1 node --import tsx --test --test-name-pattern "explicit maintainer command refreshes the derived ownership census" src/ownership/fixtureOwnershipRatchet.test.ts'; - -if (process.env.SIMFILE_SKIP_CENSUS_HOOK === "1") { - console.error("WARNING: SIMFILE_SKIP_CENSUS_HOOK=1 bypassed the fixture-ownership census guard."); - process.exit(0); -} - -const env = { ...process.env }; -// The child must never be able to repair the artifact this guard is checking. -delete env.SIMFILE_UPDATE_FIXTURE_OWNERSHIP_CENSUS; -// When the guard is itself invoked from inside a node:test run, an inherited -// NODE_TEST_CONTEXT makes the child refuse to run any file ("run() is being called -// recursively") and emit no TAP at all. Without this the guard would report a -// failure it never actually measured. -delete env.NODE_TEST_CONTEXT; -const result = spawnSync(process.execPath, [ - "--import", "tsx", "--test", "--test-reporter=tap", "--test-name-pattern", pattern, - "src/ownership/fixtureOwnershipRatchet.test.ts", -], { cwd: root, env, encoding: "utf8" }); -const stdout = result.stdout ?? ""; -const stderr = result.stderr ?? ""; -const hasExpectedTap = /^# pass 1$/m.test(stdout) - && /^# fail 0$/m.test(stdout) - && /^# tests 1$/m.test(stdout); -const matchedATest = !/^1\.\.0$/m.test(stdout); - -if (result.status !== 0 || !hasExpectedTap || !matchedATest) { - console.error("Fixture-ownership census guard failed or matched no test."); - console.error(`Regenerate with: ${regenerationCommand}`); - console.error("Then git add fixtures/sims/tiny-football/ownership-census.json and retry."); - if (stderr.trim()) console.error(stderr.trim()); - // node --test reports the failing assertion on stdout, so a guard that printed only - // stderr would refuse the commit without ever saying what was uncovered. - if (stdout.trim()) console.error(stdout.trim().split("\n").slice(-60).join("\n")); - process.exit(result.status && result.status > 0 ? result.status : 1); -} diff --git a/.githooks/install.mjs b/.githooks/install.mjs deleted file mode 100644 index 89221a4..0000000 --- a/.githooks/install.mjs +++ /dev/null @@ -1,29 +0,0 @@ -import { spawnSync } from "node:child_process"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const probe = spawnSync("git", ["rev-parse", "--show-toplevel"], { - cwd: root, - encoding: "utf8", -}); - -if (probe.status !== 0) { - console.error("Simfile hooks: not a git repository; skipping hook installation."); - process.exit(0); -} - -// `prepare` also runs for git/folder/link installs and for `npm pack`/publish. Only -// configure hooks when this package IS the checkout, never when it is a dependency -// nested inside someone else's repository. -const toplevel = path.resolve(probe.stdout.trim()); -if (toplevel !== path.resolve(root)) { - console.error(`Simfile hooks: ${root} is not the git toplevel (${toplevel}); skipping hook installation.`); - process.exit(0); -} - -const install = spawnSync("git", ["config", "core.hooksPath", ".githooks"], { - cwd: root, - stdio: "inherit", -}); -process.exit(install.status ?? 1); diff --git a/.githooks/post-merge b/.githooks/post-merge deleted file mode 100755 index 85bcceb..0000000 --- a/.githooks/post-merge +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# A fast-forward merge creates no commit, so neither pre-commit nor pre-merge-commit -# runs. post-merge always runs, but git ignores its exit code — so this cannot refuse -# the merge, only make a stale census impossible to miss at the moment it lands. -if node "$(dirname "$0")/fixture-ownership-census-guard.mjs"; then - exit 0 -fi -echo "" >&2 -echo "########################################################################" >&2 -echo "# THE MERGE YOU JUST LANDED LEFT THE FIXTURE OWNERSHIP CENSUS STALE. #" >&2 -echo "# post-merge cannot refuse a fast-forward. Regenerate and amend NOW, #" >&2 -echo "# or the next lane reads a failure it did not cause. #" >&2 -echo "########################################################################" >&2 -echo "" >&2 -exit 0 diff --git a/.githooks/pre-commit b/.githooks/pre-commit deleted file mode 100755 index 4204190..0000000 --- a/.githooks/pre-commit +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec node "$(dirname "$0")/fixture-ownership-census-guard.mjs" diff --git a/.githooks/pre-merge-commit b/.githooks/pre-merge-commit deleted file mode 100755 index 4204190..0000000 --- a/.githooks/pre-merge-commit +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec node "$(dirname "$0")/fixture-ownership-census-guard.mjs" diff --git a/package.json b/package.json index 2645cf3..6f72c76 100644 --- a/package.json +++ b/package.json @@ -100,9 +100,7 @@ "test": "node scripts/run-tests.mjs", "coverage:render": "node --import tsx scripts/render-coverage.ts", "verify:package-closure": "node tools/verify-package-closure.mjs", - "emit-causal-fixture": "tsx src/runtime/emit-causal-fixture.ts", - "hooks:install": "node .githooks/install.mjs", - "prepare": "node .githooks/install.mjs" + "emit-causal-fixture": "tsx src/runtime/emit-causal-fixture.ts" }, "engines": { "node": ">=22.19.0" From 9cf599e315a0ae83b0f7b8ec7fc94e34ff915310 Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Fri, 14 Aug 2026 02:02:53 +0200 Subject: [PATCH 3/3] ci: add safe release retries --- .github/workflows/publish.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3e6eb67..284ff70 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,20 +4,30 @@ on: push: tags: - "v*" + workflow_dispatch: + inputs: + tag: + description: Existing release tag to publish + required: true + type: string permissions: contents: read concurrency: - group: publish-${{ github.ref }} + group: publish-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} cancel-in-progress: false jobs: publish: runs-on: ubuntu-latest + env: + RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} steps: - name: Check out uses: actions/checkout@v4 + with: + ref: ${{ env.RELEASE_TAG }} - name: Set up Node uses: actions/setup-node@v4 @@ -38,11 +48,16 @@ jobs: run: | set -euo pipefail + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then + echo "Release tag ${RELEASE_TAG} is not a supported version tag." + exit 1 + fi + package_version="$(node -p "require('./package.json').version")" - tag_version="${GITHUB_REF_NAME#v}" + tag_version="${RELEASE_TAG#v}" if [[ "${tag_version}" != "${package_version}" ]]; then - echo "Tag ${GITHUB_REF_NAME} does not match package.json version ${package_version}." + echo "Tag ${RELEASE_TAG} does not match package.json version ${package_version}." exit 1 fi