From 774e9a9adc6f15f15e9730bddb61875322faf642 Mon Sep 17 00:00:00 2001 From: woksin Date: Thu, 1 Oct 2026 00:29:41 +0200 Subject: [PATCH 1/2] Keep specifications out of the published Scene packages --- .github/workflows/javascript-build.yml | 3 + .../blueprint.components/package.json | 3 +- .../JavaScript/blueprint.default/package.json | 3 +- Source/JavaScript/components/package.json | 3 +- Source/JavaScript/engine/package.json | 3 +- Source/JavaScript/model/package.json | 3 +- Source/JavaScript/primereact/package.json | 3 +- Source/JavaScript/react/package.json | 3 +- Source/JavaScript/tailwind/package.json | 3 +- package.json | 1 + verify-package-contents.js | 76 +++++++++++++++++++ 11 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 verify-package-contents.js diff --git a/.github/workflows/javascript-build.yml b/.github/workflows/javascript-build.yml index ef1c401a..8543125f 100644 --- a/.github/workflows/javascript-build.yml +++ b/.github/workflows/javascript-build.yml @@ -55,5 +55,8 @@ jobs: export NODE_OPTIONS="--max-old-space-size=4096" yarn ci + - name: Verify packed packages hold no specifications + run: yarn verify-packages + - name: Build Storybook run: yarn build-storybook diff --git a/Source/JavaScript/blueprint.components/package.json b/Source/JavaScript/blueprint.components/package.json index 20e95b5f..ac2127ca 100644 --- a/Source/JavaScript/blueprint.components/package.json +++ b/Source/JavaScript/blueprint.components/package.json @@ -24,7 +24,8 @@ "files": [ "dist", "**/*.ts", - "**/*.tsx" + "**/*.tsx", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/Source/JavaScript/blueprint.default/package.json b/Source/JavaScript/blueprint.default/package.json index d2bdd9a1..fc3f5ed4 100644 --- a/Source/JavaScript/blueprint.default/package.json +++ b/Source/JavaScript/blueprint.default/package.json @@ -25,7 +25,8 @@ "dist", "**/*.ts", "**/*.tsx", - "shell/layout.css" + "shell/layout.css", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/Source/JavaScript/components/package.json b/Source/JavaScript/components/package.json index e43cef92..e2559902 100644 --- a/Source/JavaScript/components/package.json +++ b/Source/JavaScript/components/package.json @@ -26,7 +26,8 @@ "dist", "**/*.ts", "**/*.tsx", - "**/*.css" + "**/*.css", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/Source/JavaScript/engine/package.json b/Source/JavaScript/engine/package.json index 837d4a2e..42d414a9 100644 --- a/Source/JavaScript/engine/package.json +++ b/Source/JavaScript/engine/package.json @@ -23,7 +23,8 @@ }, "files": [ "dist", - "**/*.ts" + "**/*.ts", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/Source/JavaScript/model/package.json b/Source/JavaScript/model/package.json index 63ee7636..e79c32b2 100644 --- a/Source/JavaScript/model/package.json +++ b/Source/JavaScript/model/package.json @@ -23,7 +23,8 @@ }, "files": [ "dist", - "**/*.ts" + "**/*.ts", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/Source/JavaScript/primereact/package.json b/Source/JavaScript/primereact/package.json index e32d6168..bc6c3cf7 100644 --- a/Source/JavaScript/primereact/package.json +++ b/Source/JavaScript/primereact/package.json @@ -25,7 +25,8 @@ "dist", "**/*.ts", "**/*.tsx", - "**/*.css" + "**/*.css", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/Source/JavaScript/react/package.json b/Source/JavaScript/react/package.json index 76237d92..f47175d2 100644 --- a/Source/JavaScript/react/package.json +++ b/Source/JavaScript/react/package.json @@ -24,7 +24,8 @@ "files": [ "dist", "**/*.ts", - "**/*.tsx" + "**/*.tsx", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/Source/JavaScript/tailwind/package.json b/Source/JavaScript/tailwind/package.json index d1845e4f..c233c901 100644 --- a/Source/JavaScript/tailwind/package.json +++ b/Source/JavaScript/tailwind/package.json @@ -23,7 +23,8 @@ "files": [ "dist", "**/*.ts", - "**/*.tsx" + "**/*.tsx", + "!**/for_*/**" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/package.json b/package.json index 995b0481..6d50cc68 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "test": "node ./run-task-on-workspaces.js test", "ci": "node ./run-task-on-workspaces.js ci", "build-storybook": "node ./run-task-on-workspaces.js build-storybook", + "verify-packages": "node ./verify-package-contents.js", "publish-version": "node ./run-task-on-workspaces.js publish-version $*", "up": "node ./run-task-on-workspaces.js up", "postinstall": "node ./link-eslint-typescript.js" diff --git a/verify-package-contents.js b/verify-package-contents.js new file mode 100644 index 00000000..ddd41b30 --- /dev/null +++ b/verify-package-contents.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +/* eslint-disable header/header */ + +// Packs every public workspace package (dry run) and fails when the archive would carry specifications. +// +// Specifications live next to the code they describe, in `for_*` folders. `tsc -b` type-checks and emits +// them with everything else, so the build output contains them and only the `files` list in each +// package.json keeps them out of the archive. This checks the real packed file list, so a package that +// drops or loosens that exclusion fails here instead of shipping compiled specifications. +// +// Run it after `yarn build`, from the repository root: +// +// node ./verify-package-contents.js + +const path = require('path'); +const fs = require('fs'); +const spawn = require('child_process').spawnSync; +const glob = require('glob').sync; + +const specificationFolder = /(^|\/)for_[^/]+\//; +const testRunnerImport = /(?:\bfrom\s*|\brequire\(\s*|\bimport\(\s*)['"](?:vitest|chai|chai-as-promised|sinon|sinon-chai|@testing-library\/[^'"]+)['"]/; +const runtimeScript = /\.(?:js|cjs|mjs)$/; + +function packedFiles(packageDirectory) { + const result = spawn('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], { cwd: packageDirectory, encoding: 'utf8' }); + if (result.status !== 0) { + throw new Error(`npm pack failed in '${packageDirectory}':\n${result.stderr}`); + } + + // npm reports an array of packed packages; newer versions key the same entries by package name. + const report = JSON.parse(result.stdout); + const entry = Array.isArray(report) ? report[0] : Object.values(report)[0]; + return entry.files.map(_ => _.path); +} + +const packageFiles = glob('Source/JavaScript/*/package.json', { cwd: process.cwd(), ignore: ['**/dist/**', '**/node_modules/**'] }); +const problems = []; +let packages = 0; + +for (const packageFile of packageFiles.sort()) { + const packageDirectory = path.dirname(packageFile); + const packageJson = JSON.parse(fs.readFileSync(packageFile, 'utf8')); + if (packageJson.private === true) { + continue; + } + + packages++; + const files = packedFiles(packageDirectory); + if (!files.some(_ => _.startsWith('dist/'))) { + problems.push(`${packageJson.name}: the archive has no 'dist/' output - run the build before verifying`); + continue; + } + + for (const file of files) { + if (specificationFolder.test(file)) { + problems.push(`${packageJson.name}: '${file}' is a specification and must not be packed`); + } else if (runtimeScript.test(file) && testRunnerImport.test(fs.readFileSync(path.join(packageDirectory, file), 'utf8'))) { + problems.push(`${packageJson.name}: '${file}' imports a test runner`); + } + } + + console.log(`Verified '${packageJson.name}': ${files.length} packed files`); +} + +if (packages === 0) { + console.log('No public packages found under Source/JavaScript'); + process.exit(1); +} + +if (problems.length > 0) { + console.log(`\n${problems.length} problem(s) in packed packages:`); + problems.forEach(_ => console.log(` ${_}`)); + process.exit(1); +} + +console.log(`\nNo specifications or test-runner imports in ${packages} packed package(s)`); From 6637dbde3a32f0d161b743f484ceaa8d2ac8c329 Mon Sep 17 00:00:00 2001 From: woksin Date: Thu, 1 Oct 2026 00:33:26 +0200 Subject: [PATCH 2/2] Parse the npm pack report when npm 10 runs prepare output ahead of it --- verify-package-contents.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/verify-package-contents.js b/verify-package-contents.js index ddd41b30..9972939d 100644 --- a/verify-package-contents.js +++ b/verify-package-contents.js @@ -27,8 +27,15 @@ function packedFiles(packageDirectory) { throw new Error(`npm pack failed in '${packageDirectory}':\n${result.stderr}`); } + // npm 10 runs the package's `prepare` script even with --ignore-scripts, and that script writes its own + // progress to stdout ahead of the JSON report, so parse from the line where the report starts. // npm reports an array of packed packages; newer versions key the same entries by package name. - const report = JSON.parse(result.stdout); + const start = result.stdout.search(/^[[{]\s*$/m); + if (start < 0) { + throw new Error(`npm pack reported no packed files in '${packageDirectory}':\n${result.stdout}`); + } + + const report = JSON.parse(result.stdout.slice(start)); const entry = Array.isArray(report) ? report[0] : Object.values(report)[0]; return entry.files.map(_ => _.path); }