From ab82909d7a8f964b0fb5fb2eb2b527c3d8cf19d6 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Fri, 17 Jul 2026 12:23:49 -0400 Subject: [PATCH 1/3] ci: add GitHub Actions CI and publish workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modeled on the pino-cloudwatch-ts setup, adapted to this repo: CI (push/PR to main): - test job across Node 18/20/22/24 (18 = the engines floor): install, test:coverage, build, then verify:dist — the artifact gate that caught the internal-symbol leak (ADR-007) while src sat at 100% coverage. Runs per-Node so a Node-specific build regression fails here too. - static job (once, Node 24): format:check, lint, typecheck, typecheck:test-types. Publish (on v* tag): install, test:coverage, build, verify:dist, then `npm publish --provenance --access public` and a GitHub release. Dormant until a tag is pushed; requires npm trusted publishing (OIDC) configured for the package, same as the other libs. The repo had no CI — a stack-overflow crash (#1) and a state leak (#2) both shipped to npm with 100% coverage. These gates would have caught both. --- .github/workflows/ci.yml | 66 +++++++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 37 ++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b37db18 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + # Pin the floor (18 matches `engines` in package.json) so a regression + # that only appears on the oldest supported Node still fails CI. + node-version: [18, 20, 22, 24] + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - run: pnpm run test:coverage + + - run: pnpm run build + + # verify:dist reads the built dist/ and is the gate that caught the + # internal-symbol leak (ADR-007) while src sat at 100% coverage. Runs + # after build, on every Node so a Node-specific build regression fails here. + - run: pnpm run verify:dist + + - uses: actions/upload-artifact@v7 + if: always() + with: + name: coverage-node-${{ matrix.node-version }} + path: coverage/ + + static: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + + # Static checks (format, lint, type) are Node-independent — run once on a + # modern Node rather than across the runtime matrix above. + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - run: pnpm run format:check + + - run: pnpm run lint + + - run: pnpm run typecheck + + - run: pnpm run typecheck:test-types diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ec4389e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,37 @@ +name: Publish + +on: + push: + tags: ['v*'] + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + registry-url: https://registry.npmjs.org + + - run: pnpm install --frozen-lockfile + + - run: pnpm run test:coverage + + - run: pnpm run build + + - run: pnpm run verify:dist + + - run: npm publish --provenance --access public + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "${GITHUB_REF_NAME}" --verify-tag --generate-notes --title "${GITHUB_REF_NAME}" From 5c3d884a58039091115510bb8110ee8cf362720e Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Fri, 17 Jul 2026 12:25:34 -0400 Subject: [PATCH 2/3] ci: pin packageManager so pnpm/action-setup can resolve pnpm pnpm/action-setup@v6 needs either a `version` input or a `packageManager` field; without either it errors "No pnpm version is specified" and every CI job fails at setup. Pin pnpm@10.33.4 (matches the other libs) as the single source of truth. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index d801721..41d6c60 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "javascript" ], "license": "MIT", + "packageManager": "pnpm@10.33.4", "engines": { "node": ">=18" } From 7c048e95c4da174ef4441fdc54ba448a49afde01 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Fri, 17 Jul 2026 13:21:48 -0400 Subject: [PATCH 3/3] ci: drop Node 18 (EOL), fix prettier on test-types/tsconfig.json CI surfaced two pre-existing issues the repo never had a gate for: - test-types/tsconfig.json was 2-space indented and never prettier-formatted, so `prettier --check .` (format:check) failed on it. Reformat to 4-space. - On Node 18 one test fails: it constructs `new File([], 'file.txt')` but the File/Blob globals only exist on Node 20+, so on 18 it falls back to `{}` while still asserting the File name. The library code is runtime-agnostic; only the test infra assumes 20+. Node 18 is EOL (Apr 2025) and the rimraf devDep already requires >=20, so bump `engines` to >=20 and drop 18 from the matrix rather than carry per-test global guards for an EOL runtime. --- .github/workflows/ci.yml | 4 ++-- package.json | 2 +- test-types/tsconfig.json | 30 +++++++++++++++--------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b37db18..236e190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - # Pin the floor (18 matches `engines` in package.json) so a regression + # Pin the floor (20 matches `engines` in package.json) so a regression # that only appears on the oldest supported Node still fails CI. - node-version: [18, 20, 22, 24] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v7 diff --git a/package.json b/package.json index 41d6c60..2a9fa7a 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,6 @@ "license": "MIT", "packageManager": "pnpm@10.33.4", "engines": { - "node": ">=18" + "node": ">=20" } } diff --git a/test-types/tsconfig.json b/test-types/tsconfig.json index bb29354..7fd2203 100644 --- a/test-types/tsconfig.json +++ b/test-types/tsconfig.json @@ -1,17 +1,17 @@ { - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "lib": ["ESNext"], - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "noEmit": true - }, - "include": ["./**/*.ts"], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "noEmit": true + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] }