From 8d175739ccb173fda81da2e759dca5d3305ca0c4 Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 23 Oct 2026 10:56:57 +0100 Subject: [PATCH 1/5] ci: check package exports and declarations with publint and attw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing in CI looked at the package from a consumer's point of view. `npm run type-check` covers src/ only, and `npm run build` succeeding says nothing about whether the emitted declarations resolve for someone importing the package. Reviewing the tsup 9 bump meant packing the tarball and type-checking a throwaway consumer against it by hand to rule out a dts regression on the react/server subpaths. attw is what closes that gap: a declaration file that imports a chunk the build never emitted passes publint and fails attw with an internal resolution error on exactly those subpaths. publint covers the complementary cases — missing files, unpublished files, crossed ESM/CJS types conditions. attw runs with --profile node16. The node10 profile fails today for the ./react and ./server subpaths because legacy resolution cannot see exports subpaths. typesVersions would turn those cells green, but only for types: `require('@jackmisner/utm-toolkit/react')` still has nothing to resolve on disk for that cohort, so it would publish types that lie. Declaring node10 unsupported is the honest option. The job runs once rather than per Node version; the build output is byte-identical across 22/24/26. --- .github/workflows/ci.yml | 4 + .github/workflows/package-check.yml | 24 + .github/workflows/publish.yml | 3 + package-lock.json | 703 +++++++++++++++++++++++++++- package.json | 5 +- 5 files changed, 737 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/package-check.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d95064..ff380a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,10 @@ jobs: uses: ./.github/workflows/format.yml secrets: inherit + package-check: + needs: format + uses: ./.github/workflows/package-check.yml + test-node22: needs: format uses: ./.github/workflows/ci-node22.yml diff --git a/.github/workflows/package-check.yml b/.github/workflows/package-check.yml new file mode 100644 index 0000000..2a25bc1 --- /dev/null +++ b/.github/workflows/package-check.yml @@ -0,0 +1,24 @@ +name: Package check + +on: + workflow_call: + +jobs: + package-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup Node.js 22 + uses: actions/setup-node@v7 + with: + node-version: 22 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Check package exports and declarations + run: npm run check:package diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 633bdde..279ae20 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -44,6 +44,9 @@ jobs: - name: Build package run: npm run build + - name: Check package exports and declarations + run: npm run check:package + publish: needs: ci runs-on: ubuntu-latest diff --git a/package-lock.json b/package-lock.json index c290c38..fe78e9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,14 @@ "version": "0.3.2", "license": "MIT", "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", "@testing-library/react": "^16.0.0", "@types/react": "^18.0.0", "@vitest/coverage-v8": "^5.0.0", "jsdom": "^30.0.1", "oxlint": "^1.0.0", "prettier": "^3.0.0", + "publint": "^0.3.25", "react": "^18.0.0", "react-dom": "^18.0.0", "tsup": "^8.5.1", @@ -33,6 +35,79 @@ } } }, + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.4.tgz", + "integrity": "sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.5.tgz", + "integrity": "sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@arethetypeswrong/core": "0.18.5", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^7.1.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.5.tgz", + "integrity": "sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "@loaderkit/resolve": "^1.0.2", + "cjs-module-lexer": "^1.2.3", + "fflate": "^0.8.3", + "lru-cache": "^11.0.1", + "semver": "^7.5.4", + "typescript": "5.6.1-rc", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.6.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", + "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.1.3.tgz", @@ -152,6 +227,13 @@ "node": ">=18" } }, + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", + "dev": true, + "license": "ISC" + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -165,6 +247,17 @@ "specificity": "bin/cli.js" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@csstools/color-helpers": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.2.tgz", @@ -804,6 +897,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@loaderkit/resolve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.6.tgz", + "integrity": "sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@braidai/lang": "^1.0.0" + } + }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", @@ -1182,6 +1285,32 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@publint/pack": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.7.tgz", + "integrity": "sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyexec": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/@publint/pack/node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", @@ -1867,6 +1996,19 @@ "win32" ] }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -2080,13 +2222,28 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -2198,6 +2355,49 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2214,6 +2414,90 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2331,6 +2615,20 @@ "license": "MIT", "peer": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", @@ -2344,6 +2642,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-module-lexer": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", @@ -2393,6 +2704,16 @@ "@esbuild/win32-x64": "0.27.7" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -2431,6 +2752,13 @@ } } }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, "node_modules/fix-dts-default-cjs-exports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", @@ -2458,6 +2786,36 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/html-encoding-sniffer": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-7.0.0.tgz", @@ -2471,6 +2829,16 @@ "node": "^22.13.0 || >=24.0.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -2906,6 +3274,67 @@ "source-map-js": "^1.2.1" } }, + "node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -2926,6 +3355,16 @@ "ufo": "^1.6.3" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2965,6 +3404,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3038,6 +3493,13 @@ } } }, + "node_modules/package-manager-detector": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.9.0.tgz", + "integrity": "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==", + "dev": true, + "license": "MIT" + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -3051,6 +3513,23 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -3205,6 +3684,28 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/publint": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.25.tgz", + "integrity": "sha512-WMMbn/d7JqRlfXQL/JUI4n5skC8cJ5WVr8/wT5mmiYWkUdENkZr1I5rgs2+dQPVRgoJd+JRYtB46MTsuDylw9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@publint/pack": "^0.1.7", + "package-manager-detector": "^1.8.0", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "src/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3264,6 +3765,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3365,6 +3876,19 @@ "fsevents": "~2.3.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -3388,6 +3912,32 @@ "loose-envify": "^1.1.0" } }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -3415,6 +3965,34 @@ "dev": true, "license": "MIT" }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -3438,6 +4016,36 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3652,6 +4260,26 @@ "node": ">=22.19.0" } }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/vite": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", @@ -3895,6 +4523,40 @@ "node": ">=20.11" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -3911,6 +4573,45 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } } } } diff --git a/package.json b/package.json index 556d347..862eca6 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "test:watch": "vitest", "coverage": "vitest run --coverage", "type-check": "tsc --noEmit", + "check:package": "npm run build && publint --strict && attw --pack . --profile node16", "lint": "oxlint", "lint:fix": "oxlint --fix", "format": "prettier --write \"src/**/*.{ts,tsx}\" \"__tests__/**/*.{ts,tsx}\"", @@ -68,12 +69,14 @@ } }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", "@testing-library/react": "^16.0.0", "@types/react": "^18.0.0", "@vitest/coverage-v8": "^5.0.0", "jsdom": "^30.0.1", "oxlint": "^1.0.0", "prettier": "^3.0.0", + "publint": "^0.3.25", "react": "^18.0.0", "react-dom": "^18.0.0", "tsup": "^8.5.1", @@ -93,7 +96,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/jackmisner/utm-toolkit.git" + "url": "git+https://github.com/jackmisner/utm-toolkit.git" }, "author": "Jack Misner", "bugs": { From 8753415b14f076ac182ffb7b82b534e6b648a363 Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 23 Oct 2026 11:13:47 +0100 Subject: [PATCH 2/5] ci: narrow the package-check token, drop its redundant build, ungroup its deps Three review findings. The package-check job inherited `contents: write` and `pull-requests: write` from ci.yml and needs neither; it only reads the repo, installs, builds and packs. A called workflow can only reduce the caller's scope, so declaring `contents: read` here is enough. publish.yml built twice: the standalone step, then `check:package`'s own `npm run build`. tsup runs with `clean: true`, so the first build's output was deleted before anything looked at it. publint and @arethetypeswrong/cli get their own Dependabot group. Both are pre-1.0, where a breaking change ships as a minor, and either can start failing `check:package` against unchanged package code. In the grouped npm-minor-patch PR that would block every unrelated bump behind it. --- .github/dependabot.yml | 9 +++++++++ .github/workflows/package-check.yml | 3 +++ .github/workflows/publish.yml | 3 --- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4803f4c..065ae77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -79,6 +79,15 @@ updates: patterns: - "vitest" - "@vitest/*" + # publint and @arethetypeswrong/cli are both pre-1.0, so a breaking + # change arrives as a minor that Dependabot classifies as `minor` by + # numeric component. Either tool can add a rule that fails + # `npm run check:package` against unchanged package code, so they move in + # their own PR rather than reddening the grouped bump and blocking it. + package-check: + patterns: + - "publint" + - "@arethetypeswrong/*" npm-minor-patch: patterns: - "*" diff --git a/.github/workflows/package-check.yml b/.github/workflows/package-check.yml index 2a25bc1..6185a1a 100644 --- a/.github/workflows/package-check.yml +++ b/.github/workflows/package-check.yml @@ -3,6 +3,9 @@ name: Package check on: workflow_call: +permissions: + contents: read + jobs: package-check: runs-on: ubuntu-latest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 279ae20..fe15e7c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -41,9 +41,6 @@ jobs: NODE_ENV: test run: npm test - - name: Build package - run: npm run build - - name: Check package exports and declarations run: npm run check:package From 1205405a2a555a480d0fdc5609d2bf25ddf3a41d Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 23 Oct 2026 11:20:10 +0100 Subject: [PATCH 3/5] docs: record the node10 resolution non-target and the check:package policy A Node 20+ CommonJS TypeScript consumer with the default moduleResolution gets TS2307 on @jackmisner/utm-toolkit/server and /react while the same install resolves fine at runtime, because TypeScript's legacy resolution cannot see exports subpaths. Nothing said so; the README's "All three ship dual ESM/CJS builds with TypeScript declarations" reads as a promise that those subpaths type-resolve anywhere. The earlier rationale for --profile node16 was that typesVersions would publish types that lie. That is wrong about who is affected: the cohort it describes, exports-blind bundlers, already fails loudly at bundle time and cannot use the subpaths either way. For the cohort that actually hits this the runtime works, so typesVersions would be accurate rather than a lie. It is still deliberately not added, but as a scope and maintenance call rather than a correctness one, and that is now written down where the other support-matrix decisions live. --- CLAUDE.md | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index f513f83..176104d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,7 @@ - **Lint**: `npm run lint` (oxlint) - **Format**: `npm run format` (prettier) - **Type check**: `npm run type-check` +- **Package check**: `npm run check:package` (builds, then publint + attw against the packed tarball) ## Gotchas @@ -14,6 +15,8 @@ - `validator.ts` has module-level mutable state (`defaultProtocol`). Tests that call `setDefaultProtocol()` must restore the original value. - The React hook freezes config at mount via `useRef` — config prop changes after mount are ignored. - All URL-facing operations convert keys to `snake_case` internally, regardless of the consumer's `keyFormat` setting. +- `check:package` runs attw with `--profile node16`. The `node10` row fails for `/react` and `/server` because legacy resolution cannot see `exports` subpaths; that is a declared non-target, not a bug to fix. `typesVersions` would green the row for types only, so it was deliberately left out. +- publint and attw each catch what the other misses. attw exits 0 on a tarball containing no declarations at all, so publint is what catches missing or unpublished files; publint passes a correctly-wired `exports` map whose declarations import chunks the build never emitted, so attw is what catches that. Dropping either silently loses a class of failure. ## Releases @@ -26,6 +29,7 @@ Dependabot runs weekly (`.github/dependabot.yml`). Minor/patch npm bumps arrive - **React majors are ignored on purpose.** Which React line the tests run against is a support-matrix decision tied to the `peerDependencies: react >=16.8.0` claim, so it gets changed deliberately rather than by a weekly bump. - **TypeScript majors are ignored because they are blocked upstream.** tsup bundles `rollup-plugin-dts` pinned against TypeScript 5.x, and TS 7 removed the `useCaseSensitiveFileNames` API it calls, so `npm run build` fails before emitting declarations. Remove the ignore once tsup ships a TS 7-compatible `rollup-plugin-dts`. - **vitest and `@vitest/coverage-v8` are grouped, majors included.** `coverage-v8` peer-requires the exact matching vitest version, so raised separately neither can pass `npm ci`. +- **publint and `@arethetypeswrong/cli` are grouped on their own, majors included.** Both are pre-1.0, where a breaking change ships as a minor. Either can start failing `npm run check:package` against unchanged package code, and in the grouped `npm-minor-patch` PR that would block every unrelated bump behind it. - **The test toolchain sets the Node floor, not the library.** jsdom requires Node 22+ and vitest requires 20+, so CI tests 22/24/26. `engines.node` is `>=20.0.0`: the published bundle has no Node-version-specific code, but nothing below 22 is exercised. Raise the floor rather than pinning the toolchain back if this ever conflicts. ## Test Setup diff --git a/README.md b/README.md index 9eed0ff..11dd210 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Three entry points: | `@jackmisner/utm-toolkit/react` | Hook, provider and components (needs React) | | `@jackmisner/utm-toolkit/server` | DOM-free normalization for ingest endpoints | -All three ship dual ESM/CJS builds with TypeScript declarations. +All three ship dual ESM/CJS builds with TypeScript declarations. The `/react` and `/server` subpaths need TypeScript `moduleResolution` set to `node16`, `nodenext` or `bundler` (TS 4.7+); under the legacy `node`/`node10` setting only the root entry resolves types, though all three resolve at runtime. ## Quick Start From 48595166f34f8e31cf30c5d51605d24f9b01bf27 Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 23 Oct 2026 11:26:49 +0100 Subject: [PATCH 4/5] docs: correct the bundler resolution floor and surface the constraint in Platform Support `moduleResolution: bundler` needs TypeScript 5.0, not 4.7: on 4.9.5 it is rejected with TS6046 while `node16` is accepted, so the single version floor covered two of the three settings and not the third. Platform Support is where a server consumer checks what is supported, and its Node.js list ended by calling /server the supported server surface with no mention of the one constraint a TypeScript consumer of it hits. The TypeScript Types section above shows that exact subpath type import with no caveat, so a reader could land there and copy an import that does not resolve under their default settings. The typesVersions clause recorded who decided rather than what to do; it now reads as the instruction a dev reaching for it needs. --- .github/dependabot.yml | 13 +++++++------ CLAUDE.md | 2 +- README.md | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 065ae77..3a59087 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,7 +21,8 @@ updates: # npm dependencies (published dual ESM/CJS library). # * minor + patch → grouped into ONE weekly PR. # * major bumps are deliberately NOT grouped — Dependabot raises one PR per - # major so each can be triaged on its own (breaking changes / migrations). + # major so each can be triaged on its own (breaking changes / migrations), + # except the lockstep and pre-1.0 tool families grouped below. # A grouped batch must never be blocked by one incompatible major. # # Every dependency here is a devDependency (the package has zero runtime @@ -79,11 +80,11 @@ updates: patterns: - "vitest" - "@vitest/*" - # publint and @arethetypeswrong/cli are both pre-1.0, so a breaking - # change arrives as a minor that Dependabot classifies as `minor` by - # numeric component. Either tool can add a rule that fails - # `npm run check:package` against unchanged package code, so they move in - # their own PR rather than reddening the grouped bump and blocking it. + # publint and @arethetypeswrong/cli are both pre-1.0, so breaking changes + # ship as minors. Either tool can add a rule that fails + # `npm run check:package` against unchanged package code, and in the + # grouped minor/patch PR that would block every unrelated bump behind it. + # This group covers ALL update types, majors included. package-check: patterns: - "publint" diff --git a/CLAUDE.md b/CLAUDE.md index 176104d..dd72720 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ - `validator.ts` has module-level mutable state (`defaultProtocol`). Tests that call `setDefaultProtocol()` must restore the original value. - The React hook freezes config at mount via `useRef` — config prop changes after mount are ignored. - All URL-facing operations convert keys to `snake_case` internally, regardless of the consumer's `keyFormat` setting. -- `check:package` runs attw with `--profile node16`. The `node10` row fails for `/react` and `/server` because legacy resolution cannot see `exports` subpaths; that is a declared non-target, not a bug to fix. `typesVersions` would green the row for types only, so it was deliberately left out. +- `check:package` runs attw with `--profile node16`. The `node10` row fails for `/react` and `/server` because legacy resolution cannot see `exports` subpaths; that is a declared non-target, not a bug to fix. Do not add `typesVersions`: it would green the row for types only. - publint and attw each catch what the other misses. attw exits 0 on a tarball containing no declarations at all, so publint is what catches missing or unpublished files; publint passes a correctly-wired `exports` map whose declarations import chunks the build never emitted, so attw is what catches that. Dropping either silently loses a class of failure. ## Releases diff --git a/README.md b/README.md index 11dd210..e33dd74 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Three entry points: | `@jackmisner/utm-toolkit/react` | Hook, provider and components (needs React) | | `@jackmisner/utm-toolkit/server` | DOM-free normalization for ingest endpoints | -All three ship dual ESM/CJS builds with TypeScript declarations. The `/react` and `/server` subpaths need TypeScript `moduleResolution` set to `node16`, `nodenext` or `bundler` (TS 4.7+); under the legacy `node`/`node10` setting only the root entry resolves types, though all three resolve at runtime. +All three ship dual ESM/CJS builds with TypeScript declarations. The `/react` and `/server` subpaths need TypeScript `moduleResolution` set to `node16` or `nodenext` (TS 4.7+), or `bundler` (TS 5.0+); under the legacy `node`/`node10` setting only the root entry resolves types, though all three resolve at runtime. ## Quick Start @@ -941,6 +941,7 @@ import type { - `engines.node` is `>=20.0.0`; CI exercises 22, 24 and 26 - The root entry is DOM-free at runtime — every `window` access sits behind a guard — so it imports and runs in Node without a DOM - `@jackmisner/utm-toolkit/server` is the supported surface for server use: it cannot reach storage or the DOM, and that restriction is enforced by a test rather than only documented +- Types for the `/react` and `/server` subpaths need `moduleResolution` `node16`, `nodenext` or `bundler`; see [Installation](#installation) ## Migration from Existing Projects From 1e6f3da25048f4a9716adb5bd9f4a0a37bc35132 Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Fri, 23 Oct 2026 11:32:58 +0100 Subject: [PATCH 5/5] docs: say the node10 row is ignored rather than failing "The node10 row fails" sets up a reader to expect check:package to go red. Under --profile node16 attw still prints the row, marks every cell (ignored) and leaves it out of the exit code, so the wording described the wrong consequence. Scopes the runtime claim to Node, since an exports-unaware resolver is exactly what a consumer stuck on node10 resolution may be using, and trims the Dependabot group comment to the part the YAML cannot express: that the absent update-types filter is deliberate. --- .github/dependabot.yml | 9 ++++----- CLAUDE.md | 2 +- README.md | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3a59087..f754ead 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -80,11 +80,10 @@ updates: patterns: - "vitest" - "@vitest/*" - # publint and @arethetypeswrong/cli are both pre-1.0, so breaking changes - # ship as minors. Either tool can add a rule that fails - # `npm run check:package` against unchanged package code, and in the - # grouped minor/patch PR that would block every unrelated bump behind it. - # This group covers ALL update types, majors included. + # publint and @arethetypeswrong/cli are pre-1.0, so breaking changes ship + # as minors; either can start failing `npm run check:package` against + # unchanged package code. This group covers ALL update types, majors + # included, so such a bump cannot block the grouped batch behind it. package-check: patterns: - "publint" diff --git a/CLAUDE.md b/CLAUDE.md index dd72720..6c198a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ - `validator.ts` has module-level mutable state (`defaultProtocol`). Tests that call `setDefaultProtocol()` must restore the original value. - The React hook freezes config at mount via `useRef` — config prop changes after mount are ignored. - All URL-facing operations convert keys to `snake_case` internally, regardless of the consumer's `keyFormat` setting. -- `check:package` runs attw with `--profile node16`. The `node10` row fails for `/react` and `/server` because legacy resolution cannot see `exports` subpaths; that is a declared non-target, not a bug to fix. Do not add `typesVersions`: it would green the row for types only. +- `check:package` runs attw with `--profile node16`, so the `node10` row is still printed but every cell is marked `(ignored)` and it cannot affect the exit code. It is red for `/react` and `/server` because legacy resolution cannot see `exports` subpaths; that is a declared non-target, not a bug to fix. Do not add `typesVersions`: it would green the row for types only. - publint and attw each catch what the other misses. attw exits 0 on a tarball containing no declarations at all, so publint is what catches missing or unpublished files; publint passes a correctly-wired `exports` map whose declarations import chunks the build never emitted, so attw is what catches that. Dropping either silently loses a class of failure. ## Releases diff --git a/README.md b/README.md index e33dd74..9ffdf30 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Three entry points: | `@jackmisner/utm-toolkit/react` | Hook, provider and components (needs React) | | `@jackmisner/utm-toolkit/server` | DOM-free normalization for ingest endpoints | -All three ship dual ESM/CJS builds with TypeScript declarations. The `/react` and `/server` subpaths need TypeScript `moduleResolution` set to `node16` or `nodenext` (TS 4.7+), or `bundler` (TS 5.0+); under the legacy `node`/`node10` setting only the root entry resolves types, though all three resolve at runtime. +All three ship dual ESM/CJS builds with TypeScript declarations. The `/react` and `/server` subpaths need TypeScript `moduleResolution` set to `node16` or `nodenext` (TS 4.7+), or `bundler` (TS 5.0+); under the legacy `node`/`node10` setting only the root entry resolves types, though all three resolve at runtime in Node. ## Quick Start