From 013c670ba850e341692eb84308c2b558c8ab954f Mon Sep 17 00:00:00 2001 From: Jared Wray Date: Wed, 3 Jun 2026 10:34:34 -0700 Subject: [PATCH 1/2] chore: adding in build validation --- .github/workflows/tests.yml | 3 + package.json | 5 +- pnpm-lock.yaml | 326 +++++++++++++++++++++++++++++++++++- scripts/test-build.mjs | 257 ++++++++++++++++++++++++++++ 4 files changed, 581 insertions(+), 10 deletions(-) create mode 100644 scripts/test-build.mjs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22222530..bd4c4b79 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,5 +41,8 @@ jobs: - name: Build run: pnpm build + - name: Validate Build Output + run: node scripts/test-build.mjs + - name: Test run: pnpm test:ci diff --git a/package.json b/package.json index 6402b1d2..3557be01 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "test": "pnpm -r test", "test:list": "pnpm -r --workspace-concurrency 1 test", "test:ci": "pnpm -r test:ci", + "test:build": "pnpm build && node scripts/test-build.mjs", "test:services:start": "docker compose up -d", "test:services:stop": "docker compose down", "website:build": "pnpm recursive --filter @cacheable/website run website:build", @@ -19,13 +20,15 @@ "keywords": [], "author": "Jared Wray ", "license": "MIT", - "packageManager": "pnpm@11.3.0", + "packageManager": "pnpm@11.5.1+sha512.93f7b57422ea7068257235b4c16eb60762eb68e1dc23723199cc739043ea9be2c4143274a399d8c6defa2b1176226d9ca1c4b63482d6200c1a8fbaa78c1d1485", "devDependencies": { + "@arethetypeswrong/cli": "^0.18.3", "@biomejs/biome": "^2.4.14", "@faker-js/faker": "^10.4.0", "@types/node": "^24.12.2", "@vitest/coverage-v8": "^4.1.5", "@vitest/spy": "^4.1.5", + "publint": "^0.3.21", "rimraf": "^6.1.3", "vitest": "^4.1.5", "wrangler": "^4.87.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7dcbe4ff..9a47d5df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@arethetypeswrong/cli': + specifier: ^0.18.3 + version: 0.18.3 '@biomejs/biome': specifier: ^2.4.14 version: 2.4.14 @@ -23,6 +26,9 @@ importers: '@vitest/spy': specifier: ^4.1.5 version: 4.1.5 + publint: + specifier: ^0.3.21 + version: 0.3.21 rimraf: specifier: ^6.1.3 version: 6.1.3 @@ -93,7 +99,7 @@ importers: version: link:../cacheable tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -130,7 +136,7 @@ importers: version: 11.3.6 tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -201,7 +207,7 @@ importers: version: 10.3.1 tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -220,7 +226,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -242,7 +248,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -267,7 +273,7 @@ importers: version: 4.2.0 tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -286,7 +292,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -302,7 +308,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.0 - version: 0.22.0(tsx@4.21.0)(typescript@5.9.3) + version: 0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -352,6 +358,18 @@ packages: resolution: {integrity: sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==} engines: {node: '>=18'} + '@andrewbranch/untar.js@1.0.3': + resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} + + '@arethetypeswrong/cli@0.18.3': + resolution: {integrity: sha512-GeAlc+lUD4gKHD/LDQNvQY30FfQ+xAXg2inbQKUjFZgTOdI5ygEweaOnGHGBPSKXSLGQC7VLhpXu9zMnYk/4sQ==} + engines: {node: '>=20'} + hasBin: true + + '@arethetypeswrong/core@0.18.3': + resolution: {integrity: sha512-sWBB/tdIktaT5xMq0Dz6CJyqcf6oMNdmiKiuPU1lWoJLTL6gjRSsksBuSgqot21hylkklBQY1wiSu+PkZhW7sw==} + engines: {node: '>=20'} + '@babel/generator@8.0.0-rc.5': resolution: {integrity: sha512-nFZPWz3FHIS7y6rMIVoa/WBwjdutfIaRJIBQjzn+t3RnecZoRNlGmGcyR2wb0T/IgSd50Kz/6dG8/LvMCRunjg==} engines: {node: ^22.18.0 || >=24.11.0} @@ -469,6 +487,9 @@ packages: ioredis: optional: true + '@braidai/lang@1.1.2': + resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==} + '@cacheable/memory@2.0.8': resolution: {integrity: sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==} @@ -521,6 +542,10 @@ packages: cpu: [x64] os: [win32] + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -1219,6 +1244,9 @@ packages: resolution: {integrity: sha512-uXf4TftClagJxquhlo1QFKd7JIcmxVm9vlWkDZGlkLQAIesocKaap2ojJocMDvon6h56L2SczoWkkL5OZs4AfQ==} engines: {node: '>= 18'} + '@loaderkit/resolve@1.0.6': + resolution: {integrity: sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==} + '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} @@ -1295,6 +1323,10 @@ packages: resolution: {integrity: sha512-da+MMyeXhBaKtxQiWPfy7+056wk3lVIhioJnXHXkJ2/OHDaZfFcyKHNl1R06sdYO8lIRXcXdoZ6LO2ARmkAREA==} engines: {node: '>=18.16.0'} + '@publint/pack@0.1.4': + resolution: {integrity: sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==} + engines: {node: '>=18'} + '@qified/redis@0.10.1': resolution: {integrity: sha512-TLdYDkljT8PeluzovLTud5eIA9SZDAFQ7p9Pbrz8x1iJolAG2lOR3TfpM6CwYlyJmqqbgPaDRqPsR4TI5CDAjQ==} engines: {node: '>=20'} @@ -1813,6 +1845,10 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1821,6 +1857,10 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -2019,6 +2059,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -2061,6 +2105,9 @@ packages: resolution: {integrity: sha512-glI4YY2Jy6JII5l3d5FN6rcrIbKSQqKPhWsIRYPK2IK8Mm4Q1ZZFdYIaDqglUNf7gNwG+kWIzTn0omzzE0VkvQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2069,10 +2116,22 @@ packages: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + cli-truncate@4.0.0: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + clone@2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} @@ -2081,6 +2140,13 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true @@ -2304,6 +2370,10 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -2347,6 +2417,10 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-goat@4.0.0: resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} engines: {node: '>=12'} @@ -2405,6 +2479,9 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -2461,6 +2538,10 @@ packages: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.2.0: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} engines: {node: '>=18'} @@ -2590,6 +2671,9 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + highlight.js@11.11.1: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} @@ -2937,6 +3021,17 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked-terminal@7.3.0: + resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <16' + + marked@9.1.6: + resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} + engines: {node: '>= 16'} + hasBin: true + math-intrinsics@1.0.0: resolution: {integrity: sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==} engines: {node: '>= 0.4'} @@ -3212,6 +3307,10 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3339,9 +3438,21 @@ packages: resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} engines: {node: '>=18'} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -3482,6 +3593,11 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + publint@0.3.21: + resolution: {integrity: sha512-OqejcnMV6E9zel2oCrUOJEiiFkGiAAni0A6ibfQNh1k9Gu5z4F+Yso8lllam7AzmV6Do0vp7u3UpZNRBwuXaHQ==} + engines: {node: '>=18'} + hasBin: true + pug-attrs@3.0.0: resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} @@ -3665,6 +3781,10 @@ packages: engines: {node: '>= 6.0.0'} hasBin: true + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -3728,6 +3848,10 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -3937,6 +4061,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -4101,6 +4229,11 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -4187,6 +4320,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -4335,6 +4472,10 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -4362,6 +4503,10 @@ packages: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -4369,6 +4514,14 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} @@ -4419,6 +4572,29 @@ snapshots: dependencies: json-schema: 0.4.0 + '@andrewbranch/untar.js@1.0.3': {} + + '@arethetypeswrong/cli@0.18.3': + dependencies: + '@arethetypeswrong/core': 0.18.3 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.3.0(marked@9.1.6) + semver: 7.7.4 + + '@arethetypeswrong/core@0.18.3': + dependencies: + '@andrewbranch/untar.js': 1.0.3 + '@loaderkit/resolve': 1.0.6 + cjs-module-lexer: 1.4.3 + fflate: 0.8.3 + lru-cache: 11.3.6 + semver: 7.7.4 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 + '@babel/generator@8.0.0-rc.5': dependencies: '@babel/parser': 8.0.0-rc.6 @@ -4503,6 +4679,8 @@ snapshots: '@poppinss/utils': 6.10.1 object-hash: 3.0.0 + '@braidai/lang@1.1.2': {} + '@cacheable/memory@2.0.8': dependencies: '@cacheable/utils': 2.4.1 @@ -4545,6 +4723,9 @@ snapshots: '@cloudflare/workerd-windows-64@1.20260430.1': optional: true + '@colors/colors@1.5.0': + optional: true + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -4979,6 +5160,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@loaderkit/resolve@1.0.6': + dependencies: + '@braidai/lang': 1.1.2 + '@lukeed/ms@2.0.2': {} '@monstermann/tables@0.0.0': @@ -5072,6 +5257,8 @@ snapshots: safe-stable-stringify: 2.5.0 secure-json-parse: 4.0.0 + '@publint/pack@0.1.4': {} + '@qified/redis@0.10.1(@opentelemetry/api@1.9.0)(qified@0.10.1)': dependencies: hookified: 2.2.0 @@ -5482,10 +5669,18 @@ snapshots: dependencies: string-width: 4.2.3 + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@6.2.3: {} ansis@4.3.0: {} @@ -5711,6 +5906,11 @@ snapshots: chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@5.6.2: {} char-regex@1.0.2: {} @@ -5739,20 +5939,49 @@ snapshots: chrono-node@2.9.0: {} + cjs-module-lexer@1.4.3: {} + clean-stack@2.2.0: optional: true cli-boxes@3.0.0: {} + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + cli-truncate@4.0.0: dependencies: slice-ansi: 5.0.0 string-width: 7.2.0 + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone@2.1.2: {} cluster-key-slot@1.1.2: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + color-support@1.1.3: optional: true @@ -5957,6 +6186,8 @@ snapshots: env-paths@2.2.1: optional: true + environment@1.1.0: {} + err-code@2.0.3: optional: true @@ -6065,6 +6296,8 @@ snapshots: '@esbuild/win32-ia32': 0.27.7 '@esbuild/win32-x64': 0.27.7 + escalade@3.2.0: {} + escape-goat@4.0.0: {} escape-html@1.0.3: {} @@ -6134,6 +6367,8 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fflate@0.8.3: {} + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -6193,6 +6428,8 @@ snapshots: generic-pool@3.9.0: {} + get-caller-file@2.0.5: {} + get-east-asian-width@1.2.0: {} get-intrinsic@1.2.6: @@ -6409,6 +6646,8 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 + highlight.js@10.7.3: {} + highlight.js@11.11.1: {} hookable@6.1.1: {} @@ -6752,6 +6991,19 @@ snapshots: markdown-table@3.0.4: {} + marked-terminal@7.3.0(marked@9.1.6): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.2.2 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + + marked@9.1.6: {} + math-intrinsics@1.0.0: {} math-intrinsics@1.1.0: {} @@ -7324,6 +7576,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.4 + mri@1.2.0: {} + ms@2.1.3: {} mz@2.7.0: @@ -7455,6 +7709,8 @@ snapshots: registry-url: 6.0.1 semver: 7.7.4 + package-manager-detector@1.6.0: {} + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -7465,6 +7721,14 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -7602,6 +7866,13 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + publint@0.3.21: + dependencies: + '@publint/pack': 0.1.4 + package-manager-detector: 1.6.0 + picocolors: 1.1.1 + sade: 1.8.1 + pug-attrs@3.0.0: dependencies: constantinople: 4.0.1 @@ -7880,6 +8151,8 @@ snapshots: argparse: 1.0.10 autolinker: 3.16.2 + require-directory@2.1.1: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -7985,6 +8258,10 @@ snapshots: transitivePeerDependencies: - supports-color + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-buffer@5.2.1: {} safe-regex-test@1.1.0: @@ -8262,6 +8539,11 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + supports-preserve-symlinks-flag@1.0.0: {} tar-fs@2.1.4: @@ -8350,7 +8632,7 @@ snapshots: ts-interface-checker@0.1.13: {} - tsdown@0.22.0(tsx@4.21.0)(typescript@5.9.3): + tsdown@0.22.0(@arethetypeswrong/core@0.18.3)(publint@0.3.21)(tsx@4.21.0)(typescript@5.9.3): dependencies: ansis: 4.3.0 cac: 7.0.0 @@ -8368,6 +8650,8 @@ snapshots: tree-kill: 1.2.2 unconfig-core: 7.5.0 optionalDependencies: + '@arethetypeswrong/core': 0.18.3 + publint: 0.3.21 tsx: 4.21.0 typescript: 5.9.3 transitivePeerDependencies: @@ -8425,6 +8709,8 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.1 + typescript@5.6.1-rc: {} + typescript@5.9.3: {} ufo@1.6.4: {} @@ -8528,6 +8814,8 @@ snapshots: util-deprecate@1.0.2: {} + validate-npm-package-name@5.0.1: {} + vary@1.1.2: {} vfile-location@5.0.3: @@ -8651,6 +8939,12 @@ snapshots: - bufferutil - utf-8-validate + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 @@ -8691,10 +8985,24 @@ snapshots: xdg-basedir@5.1.0: {} + y18n@5.0.8: {} + yallist@4.0.0: {} yallist@5.0.0: {} + yargs-parser@20.2.9: {} + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + youch-core@0.3.3: dependencies: '@poppinss/exception': 1.2.3 diff --git a/scripts/test-build.mjs b/scripts/test-build.mjs new file mode 100644 index 00000000..f0fff63f --- /dev/null +++ b/scripts/test-build.mjs @@ -0,0 +1,257 @@ +#!/usr/bin/env node +/** + * Build validation harness for the cacheable monorepo. + * + * After packages are built (tsdown / tsc) this script validates, for every + * publishable package, that the generated `dist/` output actually works for + * real ESM and CJS consumers: + * + * 1. Exports-path existence — every file referenced by the package's + * `exports` map (and `main`/`module`/`types`) exists on disk. + * 2. Runtime load + export parity — the built ESM bundle can be `import()`ed + * and the CJS bundle `require()`d, both expose ≥1 export, and their named + * + default exports match. + * 3. Packaging + types — `publint` reports no errors and + * `@arethetypeswrong/cli` (attw) finds no type-resolution problems. + * + * Run via `pnpm test:build` (builds first) or directly with `node + * scripts/test-build.mjs` when packages are already built. Exits non-zero if + * any package fails any check; all packages are checked even when one fails. + */ + +import { spawnSync } from "node:child_process"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { publint } from "publint"; +import { formatMessage } from "publint/utils"; + +const rootDir = path.resolve(fileURLToPath(import.meta.url), "../.."); +const packagesDir = path.join(rootDir, "packages"); +const require = createRequire(import.meta.url); + +/** Collect every string value nested inside an exports/condition object. */ +function collectExportTargets(value, out = []) { + if (typeof value === "string") { + if (value.startsWith(".")) { + out.push(value); + } + } else if (value && typeof value === "object") { + for (const nested of Object.values(value)) { + collectExportTargets(nested, out); + } + } + + return out; +} + +/** Resolve the runtime entry for a given condition ("import" | "require"). */ +function resolveConditionEntry(pkg, condition) { + const exportsField = pkg.exports; + + // Simple string form (e.g. cacheable-request: "exports": "./dist/index.js"). + if (typeof exportsField === "string") { + return condition === "import" ? exportsField : undefined; + } + + const root = + exportsField && typeof exportsField === "object" + ? (exportsField["."] ?? exportsField) + : undefined; + + const branch = root?.[condition]; + if (typeof branch === "string") { + return branch; + } + + if (branch && typeof branch === "object") { + return branch.default ?? branch.node ?? undefined; + } + + // Fall back to legacy fields when no conditional export exists. + if (condition === "import") { + return pkg.module ?? (typeof exportsField === "string" ? exportsField : undefined); + } + + return pkg.main; +} + +function checkExportPaths(pkgDir, pkg) { + const errors = []; + const targets = new Set(); + + collectExportTargets(pkg.exports, [...targets]).forEach((t) => targets.add(t)); + for (const field of ["main", "module", "types"]) { + if (typeof pkg[field] === "string") { + targets.add(pkg[field]); + } + } + + for (const target of targets) { + const abs = path.resolve(pkgDir, target); + if (!existsSync(abs)) { + errors.push(`referenced file does not exist: ${target}`); + } + } + + if (targets.size === 0) { + errors.push("package declares no exports/main/module/types to validate"); + } + + return errors; +} + +async function checkRuntime(pkgDir, pkg) { + const errors = []; + + const esmTarget = resolveConditionEntry(pkg, "import"); + const cjsTarget = resolveConditionEntry(pkg, "require"); + + let esmKeys; + let esmHasDefault; + if (esmTarget) { + try { + const url = pathToFileURL(path.resolve(pkgDir, esmTarget)).href; + const ns = await import(url); + const keys = Object.keys(ns).filter((k) => k !== "default"); + esmKeys = new Set(keys); + esmHasDefault = "default" in ns; + if (keys.length === 0 && !esmHasDefault) { + errors.push(`ESM bundle (${esmTarget}) exposes no exports`); + } + } catch (error) { + errors.push(`failed to import ESM bundle (${esmTarget}): ${error.message}`); + } + } else { + errors.push("no ESM entry could be resolved from exports"); + } + + if (cjsTarget) { + try { + const mod = require(path.resolve(pkgDir, cjsTarget)); + const keys = Object.keys(mod).filter((k) => k !== "default"); + const cjsHasDefault = "default" in mod; + // A CJS module that re-exports a default may surface it as the + // module itself; treat a non-plain export object as "has default". + if (keys.length === 0 && !cjsHasDefault && typeof mod !== "object") { + errors.push(`CJS bundle (${cjsTarget}) exposes no exports`); + } + + // Parity: named exports must match between ESM and CJS. + if (esmKeys) { + const cjsKeys = new Set(keys); + const missingInCjs = [...esmKeys].filter((k) => !cjsKeys.has(k)); + const missingInEsm = [...cjsKeys].filter((k) => !esmKeys.has(k)); + if (missingInCjs.length > 0) { + errors.push(`named exports present in ESM but missing in CJS: ${missingInCjs.join(", ")}`); + } + + if (missingInEsm.length > 0) { + errors.push(`named exports present in CJS but missing in ESM: ${missingInEsm.join(", ")}`); + } + + if (esmHasDefault !== cjsHasDefault) { + errors.push(`default export mismatch: ESM ${esmHasDefault ? "has" : "lacks"} default, CJS ${cjsHasDefault ? "has" : "lacks"} default`); + } + } + } catch (error) { + errors.push(`failed to require CJS bundle (${cjsTarget}): ${error.message}`); + } + } + // No CJS target → ESM-only package (e.g. cacheable-request); nothing to compare. + + return errors; +} + +async function checkPublint(pkgDir) { + const { messages } = await publint({ pkgDir, level: "error" }); + const pkg = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf8")); + return messages + .filter((m) => m.type === "error") + .map((m) => formatMessage(m, pkg)) + .filter(Boolean); +} + +const attwPkg = require("@arethetypeswrong/cli/package.json"); +const attwBin = path.resolve( + path.dirname(require.resolve("@arethetypeswrong/cli/package.json")), + attwPkg.bin.attw, +); + +function checkAttw(pkgDir, pkg) { + // Packages with no `require`/CJS entry are intentionally ESM-only (e.g. + // cacheable-request); the esm-only profile avoids false "CJS resolves to + // ESM" failures while still validating type resolution. + const isEsmOnly = !resolveConditionEntry(pkg, "require"); + const args = [attwBin, "--pack", pkgDir, "--format", "table", "--no-color"]; + if (isEsmOnly) { + args.push("--profile", "esm-only"); + } + + const result = spawnSync(process.execPath, args, { cwd: pkgDir, encoding: "utf8" }); + + if (result.status === 0) { + return []; + } + + const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(); + return [output || `attw exited with status ${result.status}`]; +} + +async function main() { + const pkgDirs = readdirSync(packagesDir) + .map((name) => path.join(packagesDir, name)) + .filter((dir) => existsSync(path.join(dir, "package.json"))); + + const failures = []; + + for (const pkgDir of pkgDirs.sort()) { + const pkg = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf8")); + + // Skip non-published packages and internal tools that produce no build + // output (e.g. website is private, benchmark has a no-op build). + if (pkg.private === true || !existsSync(path.join(pkgDir, "dist"))) { + console.log(`\n${pkg.name}\n - skipped (no published dist output)`); + continue; + } + + const checks = { + "export paths": checkExportPaths(pkgDir, pkg), + runtime: await checkRuntime(pkgDir, pkg), + publint: await checkPublint(pkgDir), + attw: checkAttw(pkgDir, pkg), + }; + + console.log(`\n${pkg.name}`); + for (const [label, errors] of Object.entries(checks)) { + if (errors.length === 0) { + console.log(` ✓ ${label}`); + } else { + console.log(` ✗ ${label}`); + for (const error of errors) { + console.log(` ${error.split("\n").join("\n ")}`); + } + + failures.push(`${pkg.name} → ${label}`); + } + } + } + + console.log(""); + if (failures.length > 0) { + console.error(`Build validation failed (${failures.length}):`); + for (const failure of failures) { + console.error(` - ${failure}`); + } + + process.exit(1); + } + + console.log("Build validation passed for all packages."); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); From e80a9da09b966ea00948b80585a25c321df6e822 Mon Sep 17 00:00:00 2001 From: Jared Wray Date: Wed, 3 Jun 2026 10:41:19 -0700 Subject: [PATCH 2/2] fix(scripts): harden CJS export introspection in build validation Guard the CJS runtime check against modules that resolve to null, undefined, or a primitive (avoiding a TypeError on `Object.keys`/`in`), and correctly treat a bare function export (`module.exports = fn`) as a valid export rather than flagging "exposes no exports". Addresses review feedback on PR #1651. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/test-build.mjs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/test-build.mjs b/scripts/test-build.mjs index f0fff63f..b6750437 100644 --- a/scripts/test-build.mjs +++ b/scripts/test-build.mjs @@ -130,11 +130,17 @@ async function checkRuntime(pkgDir, pkg) { if (cjsTarget) { try { const mod = require(path.resolve(pkgDir, cjsTarget)); - const keys = Object.keys(mod).filter((k) => k !== "default"); - const cjsHasDefault = "default" in mod; - // A CJS module that re-exports a default may surface it as the - // module itself; treat a non-plain export object as "has default". - if (keys.length === 0 && !cjsHasDefault && typeof mod !== "object") { + // A CJS module may export an object, a function (module.exports = + // fn), or — for a broken bundle — null/undefined/a primitive. + // Guard before introspecting so we never throw on the `in` operator + // or Object.keys, and so a bare function still counts as an export. + const isObjectOrFunction = + mod !== null && (typeof mod === "object" || typeof mod === "function"); + const keys = isObjectOrFunction + ? Object.keys(mod).filter((k) => k !== "default") + : []; + const cjsHasDefault = isObjectOrFunction && "default" in mod; + if (keys.length === 0 && !cjsHasDefault && !isObjectOrFunction) { errors.push(`CJS bundle (${cjsTarget}) exposes no exports`); }