From 5f25d300b8bd3c353ddd3c233ecda41dce7422a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:41:04 +0000 Subject: [PATCH 1/5] ci: add typecheck and lint gates, refresh node test matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI never ran tsc or eslint, so type errors and lint violations (including an untypechecked packages/cli) could land silently. Add a `typecheck` script to every package (tsc --noEmit, or the frontend's existing `tsc -b` project setup) plus a root script that runs them all, wire both typecheck and `lint --workspaces --if-present` into the Build job, and bump the pinned Node 20.x (EOL) to a 22.x/24.x matrix for the Test job. Fixing packages/cli's typecheck required skipLibCheck (an @types/node version bump introduced generic TypedArrays that conflict with this tsconfig's ES2018 lib under this TypeScript version — the frontend tsconfig already carries the same flag) and a narrow `as any` cast in one test assertion where the library's own return type is untyped `{}`. Making frontend lint pass in CI required replacing a handful of `any`-typed showSaveFilePicker/webkitdirectory workarounds with narrow structural types, and scoping the lint script to src/ (tests aren't typechecked by this project either, so this mirrors existing convention) with a small --max-warnings allowance for the pre-existing react-hooks/ react-refresh warnings. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QMjKvi9vHT3LfikGfW84Kg --- .github/workflows/ci.yml | 9 ++- .github/workflows/release.yml | 1 + package-lock.json | 66 +++++++-------------- package.json | 5 ++ packages/cli/package.json | 9 ++- packages/cli/tests/data.test.ts | 2 +- packages/cli/tsconfig.json | 1 + packages/frontend/package.json | 5 +- packages/frontend/src/global.d.ts | 2 +- packages/frontend/src/pages/DataUpload.tsx | 7 ++- packages/frontend/src/pages/Review.tsx | 8 ++- packages/frontend/src/staging/datasetZip.ts | 8 ++- packages/metadata/package.json | 6 +- 13 files changed, 70 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dab852..0273c1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,21 +15,26 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: npm - run: npm ci + - run: npm run typecheck + - run: npm run lint --workspaces --if-present - run: npm run build test: name: Test runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22.x, 24.x] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: ${{ matrix.node-version }} cache: npm - run: npm ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a0acb5..a45e14f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,6 +19,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20.x + cache: npm - name: Install Dependencies run: npm ci diff --git a/package-lock.json b/package-lock.json index f079caf..66a6ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "metadata", "license": "MIT", "workspaces": [ "packages/*" @@ -15,6 +16,9 @@ "@changesets/cli": "^2.31.0", "esbuild": "^0.25.0", "jest": "^29.7.0" + }, + "engines": { + "node": ">=20" } }, "node_modules/@adobe/css-tools": { @@ -4788,20 +4792,20 @@ } }, "node_modules/@types/node": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", - "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", - "dev": true, + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/node/node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/picomatch": { @@ -8781,21 +8785,6 @@ "node": ">=10.17.0" } }, - "node_modules/husky": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", - "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", - "dev": true, - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -15613,7 +15602,7 @@ "version": "0.1.1", "dependencies": { "@inquirer/prompts": "^8.5.2", - "@jspsych/metadata": "*", + "@jspsych/metadata": "^0.0.3 || ^0.1.0", "psychds-validator": "^1.5.1", "yargs": "^17.7.2" }, @@ -15625,7 +15614,11 @@ "@types/jest": "^29.5.12", "@types/node": "^20.0.0", "jest": "^29.7.0", - "node-pty": "^1.1.0" + "node-pty": "^1.1.0", + "typescript": "^5.5.4" + }, + "engines": { + "node": ">=20" } }, "packages/cli/node_modules/@inquirer/checkbox": { @@ -15947,16 +15940,6 @@ } } }, - "packages/cli/node_modules/@types/node": { - "version": "20.19.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", - "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, "packages/cli/node_modules/chardet": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", @@ -16000,13 +15983,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "packages/cli/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, - "license": "MIT" - }, "packages/frontend": { "version": "0.0.2", "dependencies": { @@ -16080,9 +16056,11 @@ "devDependencies": { "@jspsych/config": "^3.3.3", "@types/jest": "^29.5.12", - "husky": "^9.0.11", "ts-jest": "^29.1.4", "typescript": "^5.5.4" + }, + "engines": { + "node": ">=20" } } } diff --git a/package.json b/package.json index f82300b..eb6b4c9 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,17 @@ { + "name": "metadata", "private": true, "type": "module", "workspaces": [ "packages/*" ], + "engines": { + "node": ">=20" + }, "scripts": { "test": "jest", "build": "npm run build --workspaces --platform=node", + "typecheck": "npm run typecheck --workspaces --if-present", "changeset": "changeset", "changeset:version": "changeset version", "changeset:publish": "npm run build && changeset publish" diff --git a/packages/cli/package.json b/packages/cli/package.json index 47bf9ad..b37f85b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -12,6 +12,9 @@ "repository": { "url": "https://github.com/jspsych/metadata" }, + "engines": { + "node": ">=20" + }, "bin": { "jspsych-metadata-cli": "./dist/cjs/index.cjs" }, @@ -25,14 +28,16 @@ "build:esm": "esbuild src/index.ts --bundle --format=esm --platform=node --external:psychds-validator --outfile=dist/esm/index.js", "build:cjs": "esbuild src/index.ts --bundle --format=cjs --platform=node --external:psychds-validator --outfile=dist/cjs/index.cjs", "cli": "node dist/cjs/index.cjs", - "test": "jest" + "test": "jest", + "typecheck": "tsc --noEmit" }, "devDependencies": { "@sucrase/jest-plugin": "^3.0.0", "@types/jest": "^29.5.12", "@types/node": "^20.0.0", "jest": "^29.7.0", - "node-pty": "^1.1.0" + "node-pty": "^1.1.0", + "typescript": "^5.5.4" }, "dependencies": { "@inquirer/prompts": "^8.5.2", diff --git a/packages/cli/tests/data.test.ts b/packages/cli/tests/data.test.ts index bf12909..1f5ed9e 100644 --- a/packages/cli/tests/data.test.ts +++ b/packages/cli/tests/data.test.ts @@ -366,7 +366,7 @@ describe("processDirectory output-directory creation (#118)", () => { expect(written.startsWith("Participant_ID,")).toBe(true); // The metadata variable name matches it exactly. - const names = (metadata.getMetadata().variableMeasured as any[]).map((v) => v.name); + const names = ((metadata.getMetadata() as any).variableMeasured as any[]).map((v) => v.name); expect(names).toContain("Participant_ID"); expect(names).not.toContain("Participant_ID"); }); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 7c689f9..ccb5046 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node", "esModuleInterop": true, "resolveJsonModule": true, + "skipLibCheck": true, "types": ["jest", "node"] }, "include": ["src", "tests"] diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 7398031..f0a323c 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -6,9 +6,10 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 10", "preview": "vite preview", - "test": "jest" + "test": "jest", + "typecheck": "tsc -b" }, "dependencies": { "@jspsych/metadata": "^0.0.3", diff --git a/packages/frontend/src/global.d.ts b/packages/frontend/src/global.d.ts index 0a958ad..823f5da 100644 --- a/packages/frontend/src/global.d.ts +++ b/packages/frontend/src/global.d.ts @@ -5,7 +5,7 @@ declare global { } interface JsPsychMetadataInstance { - generate: (data: any, metadataOptions?: object, csv?: boolean) => void; + generate: (data: unknown[] | string, metadataOptions?: object, csv?: boolean) => void; getMetadata: () => object; } diff --git a/packages/frontend/src/pages/DataUpload.tsx b/packages/frontend/src/pages/DataUpload.tsx index 604e093..d15f721 100644 --- a/packages/frontend/src/pages/DataUpload.tsx +++ b/packages/frontend/src/pages/DataUpload.tsx @@ -133,7 +133,8 @@ const DataUpload: React.FC = ({ }, [files, convertedStore, joinKeyCandidates, joinKeyProblemFile, committedKeys, fileStatuses]); useEffect(() => { - if (inputRef.current) (inputRef.current as any).webkitdirectory = true; // not in TS lib + // webkitdirectory is a nonstandard HTMLInputElement attribute not in TS lib. + if (inputRef.current) (inputRef.current as HTMLInputElement & { webkitdirectory: boolean }).webkitdirectory = true; }, []); const handleFolderChange = (e: React.ChangeEvent) => { @@ -210,7 +211,7 @@ const DataUpload: React.FC = ({ // flagged: trial_index alone repeats across records, but the identifier column (a // synthesized source_record_id, else a real participant_id) makes (id, trial_index) unique. const idColumn = (['source_record_id', 'participant_id'] as const).find((col) => - parsed.some((row: any) => row && typeof row === 'object' && col in row)); + parsed.some((row) => row && typeof row === 'object' && col in row)); const keys = idColumn ? [idColumn, 'trial_index'] : ['trial_index']; const analysis = analyzeJoinKeys(parsed, keys); if (!analysis.isUnique) { @@ -294,7 +295,7 @@ const DataUpload: React.FC = ({ // happen immediately: getExtracted* reflect only the most recent generate() call. JSON // arrays are serialised to CSV; CSV is written verbatim. Non-array JSON is skipped (it // isn't a jsPsych trial table) before generate() runs — matching the CLI. - let mainRows: Array> = []; + let mainRows: Array> = []; let mainContent: string | undefined; if (type === 'json') { // Tag a per-line source_record_id for JSON-Lines (a no-op for a single array) so the diff --git a/packages/frontend/src/pages/Review.tsx b/packages/frontend/src/pages/Review.tsx index 16380b1..4e444c3 100644 --- a/packages/frontend/src/pages/Review.tsx +++ b/packages/frontend/src/pages/Review.tsx @@ -49,7 +49,13 @@ const Review: React.FC = ({ jsPsychMetadata, dataFiles }) => { const handleDownload = async () => { if ('showSaveFilePicker' in window) { try { - const fileHandle = await (window as any).showSaveFilePicker({ + const fileHandle = await ( + window as unknown as { + showSaveFilePicker: ( + opts: unknown, + ) => Promise<{ createWritable: () => Promise<{ write(d: string): Promise; close(): Promise }> }>; + } + ).showSaveFilePicker({ suggestedName: FILENAME, types: [{ description: 'JSON file', accept: { 'application/json': ['.json'] } }], }); diff --git a/packages/frontend/src/staging/datasetZip.ts b/packages/frontend/src/staging/datasetZip.ts index 5ea0a35..921be08 100644 --- a/packages/frontend/src/staging/datasetZip.ts +++ b/packages/frontend/src/staging/datasetZip.ts @@ -125,11 +125,15 @@ export async function downloadDatasetZip( // falling back to blob, which would also fail if the disk is full). let sink: ZipSink | undefined; try { - const fileHandle = await (window as any).showSaveFilePicker({ + const fileHandle = await ( + window as unknown as { + showSaveFilePicker: (opts: unknown) => Promise<{ createWritable: () => Promise }>; + } + ).showSaveFilePicker({ suggestedName: filename, types: [{ description: 'ZIP file', accept: { 'application/zip': ['.zip'] } }], }); - sink = await fileHandle.createWritable() as ZipSink; + sink = await fileHandle.createWritable(); } catch (err) { if ((err as DOMException).name === 'AbortError') return false; // Picker or handle creation failed — fall through to blob download. diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 355201b..d0f83f9 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -4,6 +4,9 @@ "description": "jsPsych package for creating and customizing metadata according to Psych-DS standards.", "type": "module", "main": "dist/index.js", + "engines": { + "node": ">=20" + }, "exports": { ".": { "import": { @@ -27,7 +30,8 @@ "test:watch": "npm test -- --watch", "build": "node esbuild.config.mjs && npx tsc", "build:watch": "npm run build -- --watch", - "build:types": "tsc --emitDeclarationOnly --declaration --project tsconfig.build.json" + "build:types": "tsc --emitDeclarationOnly --declaration --project tsconfig.build.json", + "typecheck": "tsc --noEmit" }, "repository": { "type": "git", From a5426b4b69b85c334303233adea23e0cee6c616d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:41:18 +0000 Subject: [PATCH 2/5] fix(metadata,cli): correct package metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit packages/metadata's repository/bugs URLs pointed at jspsych/jsPsych (the wrong repo) and would leak that to npm on publish; point them at jspsych/metadata instead. Reorder the exports map so the "types" condition comes first — TS condition matching is order-sensitive, so a trailing "types" entry can be skipped. Drop the dead build:types script (references a nonexistent tsconfig.build.json and isn't part of the build chain) and the vestigial husky devDependency (no .husky/ directory in this repo). packages/cli depended on "@jspsych/metadata": "*", which would happily resolve to an incompatible future major. Pin it to the 0.0.x/0.1.x line (0.1.0 is the pending release in PR #47) rather than a bare "^0.1.0": until that release ships, the local workspace package is still at 0.0.3, and a range that excludes it breaks `npm install` for local dev and CI alike since npm workspaces only symlinks a dependency when the workspace version satisfies the declared range. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QMjKvi9vHT3LfikGfW84Kg --- packages/cli/package.json | 2 +- packages/metadata/package.json | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index b37f85b..eee8024 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,7 +41,7 @@ }, "dependencies": { "@inquirer/prompts": "^8.5.2", - "@jspsych/metadata": "*", + "@jspsych/metadata": "^0.0.3 || ^0.1.0", "psychds-validator": "^1.5.1", "yargs": "^17.7.2" } diff --git a/packages/metadata/package.json b/packages/metadata/package.json index d0f83f9..dd8b643 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -9,12 +9,12 @@ }, "exports": { ".": { + "types": "./dist/index.d.ts", "import": { "node": "./dist/index.js", "default": "./dist/index.esm.js" }, - "require": "./dist/index.cjs", - "types": "./dist/index.d.ts" + "require": "./dist/index.cjs" } }, "typings": "dist/index.d.ts", @@ -30,24 +30,22 @@ "test:watch": "npm test -- --watch", "build": "node esbuild.config.mjs && npx tsc", "build:watch": "npm run build -- --watch", - "build:types": "tsc --emitDeclarationOnly --declaration --project tsconfig.build.json", "typecheck": "tsc --noEmit" }, "repository": { "type": "git", - "url": "git+https://github.com/jspsych/jsPsych.git", + "url": "git+https://github.com/jspsych/metadata.git", "directory": "packages/metadata" }, "author": "Victor Zhang vzhang@vassar.edu", "license": "MIT", "bugs": { - "url": "https://github.com/jspsych/jsPsych/issues" + "url": "https://github.com/jspsych/metadata/issues" }, "homepage": "https://www.jspsych.org/latest/metadata", "devDependencies": { "@jspsych/config": "^3.3.3", "@types/jest": "^29.5.12", - "husky": "^9.0.11", "ts-jest": "^29.1.4", "typescript": "^5.5.4" }, From 28f7a16a56dbc16b24cd2faa83cab7c83955e59c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:41:39 +0000 Subject: [PATCH 3/5] docs: expand root README, drop ignored build flag The root README was two lines; expand it into a proper overview (what the project is, the monorepo layout, a CLI quick-start, links to the docs/ guides, and dev/contributing commands) without turning it into a duplicate of the package-level READMEs. Also drop --platform=node from the root `build` script: npm silently ignores flags passed after `--workspaces` that aren't recognized by `npm run`, so it had no effect (each package's own build script already sets --platform=node where esbuild needs it). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QMjKvi9vHT3LfikGfW84Kg --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bc55acb..e8a7ac3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ # metadata -Library and CLI tool to generate Psych-DS compliant metadata for jsPsych experiments + +Tools for generating [Psych-DS](https://psych-ds.github.io/) compliant metadata for +[jsPsych](https://www.jspsych.org/) experiments. Point them at a folder of jsPsych data files +and they produce a `dataset_description.json` (plus, for the CLI, a ready-to-share +Psych-DS project) describing your experiment and its variables — no more hand-writing the +description file. + +This is a monorepo (npm workspaces) with three packages: + +| Package | Description | +| --------------------------------------- | ---------------------------------------------------------------------- | +| [`@jspsych/metadata`](packages/metadata) | Core library: reads jsPsych data and builds Psych-DS metadata. | +| [`@jspsych/metadata-cli`](packages/cli) | Terminal tool for local folders and scripted/automated pipelines. | +| `frontend` | Browser wizard — upload data, fill in a few fields, download a project. | + +## Quick start (CLI) + +Requires Node.js 20 or later. + +``` +npx @jspsych/metadata-cli +``` + +Running it with no flags launches interactive mode, which walks you through pointing it at a +folder of jsPsych data files (`.csv`, `.json`, or `.jsonl`) and writes a self-contained +Psych-DS project alongside them (`data/`, `dataset_description.json`, `README.md`, `CHANGES.md`). + +Prefer a browser? The web wizard needs no install — see +[Using the Web Wizard](docs/using-the-frontend.md). + +## Documentation + +- **[Getting Started](docs/getting-started.md)** — overview and which tool to use (CLI vs. web wizard). +- **[CLI Guide](docs/cli-guide.md)** — step-by-step walkthrough, accepted data formats, renaming strategies. +- **[CLI Reference](docs/cli-reference.md)** — flags, exit codes, filename rules, non-interactive usage. +- **[Using the Web Wizard](docs/using-the-frontend.md)** — the browser-based alternative to the CLI. +- **[Metadata Options](docs/metadata-options.md)** — the optional JSON file for authors, descriptions, etc. +- **[What is Psych-DS?](docs/what-is-psych-ds.md)** — background on the standard. + +## Development + +``` +npm install # from the repo root +npm run build # build all packages +npm test # run the full test suite (jest, all workspaces) +npm run typecheck # tsc --noEmit / tsc -b across all packages +``` + +Each package also has its own `README.md` with package-specific instructions +([metadata](packages/metadata/README.md), [cli](packages/cli/README.md), +[frontend](packages/frontend/README.md)). + +## Contributing + +Issues and pull requests are welcome at +[github.com/jspsych/metadata](https://github.com/jspsych/metadata). Please make sure +`npm test` and `npm run typecheck` pass before opening a PR. + +## License + +MIT diff --git a/package.json b/package.json index eb6b4c9..04db81b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "scripts": { "test": "jest", - "build": "npm run build --workspaces --platform=node", + "build": "npm run build --workspaces", "typecheck": "npm run typecheck --workspaces --if-present", "changeset": "changeset", "changeset:version": "changeset version", From fb11210e66092af5d3b82cbe35bc830e9a237e02 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 21:12:40 +0000 Subject: [PATCH 4/5] chore: add changeset Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QMjKvi9vHT3LfikGfW84Kg --- .changeset/infra-package-metadata.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/infra-package-metadata.md diff --git a/.changeset/infra-package-metadata.md b/.changeset/infra-package-metadata.md new file mode 100644 index 0000000..2efe625 --- /dev/null +++ b/.changeset/infra-package-metadata.md @@ -0,0 +1,6 @@ +--- +"@jspsych/metadata": patch +"@jspsych/metadata-cli": patch +--- + +Package metadata fixes: correct `repository`/`bugs` URLs (previously pointed at jspsych/jsPsych), put the `types` condition first in the exports map, declare `engines`, and pin the CLI's dependency on `@jspsych/metadata` to a real range instead of `*`. From 4941b055920b272c4eb4004e5a36d697e62a4fae Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Mon, 20 Jul 2026 17:53:10 -0400 Subject: [PATCH 5/5] =?UTF-8?q?ci,docs:=20post-merge=20fixes=20=E2=80=94?= =?UTF-8?q?=20drop=20Node=2020,=20widen=20metadata=20range,=20sync=20docs-?= =?UTF-8?q?site=20CLI=20reference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reconcile README with the merged Docusaurus docs site (#139): keep the monorepo intro/quick-start/development sections, point Documentation at metadata.jspsych.org and website/, drop the legacy docs/ links. - Drop Node 20 support (maintainer decision): engines >=22 in all three packages, release workflow bumped 20.x -> 22.x to match the CI matrix (22/24) that npm test actually runs on. - Widen the CLI's @jspsych/metadata range to '>=0.0.3 <0.2.0' so a future 0.0.x or 0.1.x patch release cannot re-break npm ci (caret ranges on 0.0.x match only that exact version). - Sync website/docs/reference/cli-reference.md with the exit-code scheme and flag-validation behavior that landed in #135 (the docs/ copy was updated there; the site copy predated it). - Resolve merge conflicts from main: take #136's DataUpload busy-state effect (webkitdirectory is now set via JSX spread, making #137's typed effect obsolete) and accept #136's deletion of global.d.ts. Co-Authored-By: Claude Fable 5 --- .github/workflows/release.yml | 4 ++-- package-lock.json | 8 ++++---- package.json | 2 +- packages/cli/package.json | 4 ++-- packages/metadata/package.json | 2 +- website/docs/reference/cli-reference.md | 24 +++++++++++++++++------- 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a45e14f..0b0e28b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,10 +15,10 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4 - - name: Setup Node.js 20.x + - name: Setup Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: npm - name: Install Dependencies diff --git a/package-lock.json b/package-lock.json index 66a6ba0..c0a3c6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "jest": "^29.7.0" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "node_modules/@adobe/css-tools": { @@ -15602,7 +15602,7 @@ "version": "0.1.1", "dependencies": { "@inquirer/prompts": "^8.5.2", - "@jspsych/metadata": "^0.0.3 || ^0.1.0", + "@jspsych/metadata": ">=0.0.3 <0.2.0", "psychds-validator": "^1.5.1", "yargs": "^17.7.2" }, @@ -15618,7 +15618,7 @@ "typescript": "^5.5.4" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "packages/cli/node_modules/@inquirer/checkbox": { @@ -16060,7 +16060,7 @@ "typescript": "^5.5.4" }, "engines": { - "node": ">=20" + "node": ">=22" } } } diff --git a/package.json b/package.json index 04db81b..bbc26a1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "packages/*" ], "engines": { - "node": ">=20" + "node": ">=22" }, "scripts": { "test": "jest", diff --git a/packages/cli/package.json b/packages/cli/package.json index eee8024..61e30d6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/jspsych/metadata" }, "engines": { - "node": ">=20" + "node": ">=22" }, "bin": { "jspsych-metadata-cli": "./dist/cjs/index.cjs" @@ -41,7 +41,7 @@ }, "dependencies": { "@inquirer/prompts": "^8.5.2", - "@jspsych/metadata": "^0.0.3 || ^0.1.0", + "@jspsych/metadata": ">=0.0.3 <0.2.0", "psychds-validator": "^1.5.1", "yargs": "^17.7.2" } diff --git a/packages/metadata/package.json b/packages/metadata/package.json index dd8b643..c846062 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -5,7 +5,7 @@ "type": "module", "main": "dist/index.js", "engines": { - "node": ">=20" + "node": ">=22" }, "exports": { ".": { diff --git a/website/docs/reference/cli-reference.md b/website/docs/reference/cli-reference.md index efbe2de..3ac2f1d 100644 --- a/website/docs/reference/cli-reference.md +++ b/website/docs/reference/cli-reference.md @@ -24,11 +24,12 @@ With no flags, the tool runs interactively and prompts you for everything it nee | `--data-dir` | `-d` | path | Path to the folder containing your raw jsPsych data files (`.csv`, `.json`, or `.jsonl`). | | `--metadata-options` | `-m` | path | Path to a metadata options `.json` file. See [Customizing the output](../guides/customizing-output.md). | | `--verbose` | `-v` | boolean | Print detailed output at each processing step. Shows plugin fetching, variable resolution, and full validation warnings. | +| `--version` | | boolean | Print the CLI version and exit. | ### Notes on flag behaviour - Paths can use `~` for your home directory (e.g. `--data-dir=~/experiments/raw`). -- If a flag is provided but the path is invalid, the tool falls back to prompting for that step interactively. +- If a flag is provided but the path is invalid (or the `--metadata-options` file is not valid JSON), the tool exits immediately with a usage error (exit code `2`). It does **not** fall back to an interactive prompt — a flag you passed explicitly failing silently would hide mistakes in scripts. - `--psych-ds-dir` implies **update mode** — the tool loads the existing `dataset_description.json` before processing new data. Without this flag, the tool asks whether to create or update. ## Non-interactive mode @@ -44,7 +45,7 @@ npx @jspsych/metadata-cli \ Non-interactive mode enforces stricter rules than interactive mode: -- **Non-compliant filenames are a hard error.** In interactive mode, the tool offers a menu of renaming strategies to bring non-compliant filenames into the Psych-DS naming pattern. In non-interactive mode, a non-compliant filename causes the tool to exit immediately with an error message and exit code 1. Rename your files before running (see [Data file naming](#data-file-naming) below). +- **Non-compliant filenames are a hard error.** In interactive mode, the tool offers a menu of renaming strategies to bring non-compliant filenames into the Psych-DS naming pattern. In non-interactive mode, a non-compliant filename causes the tool to exit immediately with an error message and exit code `2` (usage error). Rename your files before running (see [Data file naming](#data-file-naming) below). - **Unknown variable descriptions are not prompted.** Variables the tool cannot automatically describe are left as `"unknown"` in the output. - **Join keys are resolved automatically.** When nested-array rows aren't uniquely identified by `trial_index`, the tool picks the keys deterministically instead of prompting, and reports the choice (see [Nested arrays and join keys](#nested-arrays-and-join-keys)). @@ -55,15 +56,24 @@ The tool also runs without prompting whenever it isn't attached to an interactiv | Code | Meaning | |------|---------| | `0` | Completed successfully. Psych-DS validation passed (warnings are allowed). | -| `1` | Psych-DS validation failed with one or more errors, or a non-compliant filename was found in non-interactive mode. | +| `1` | Unexpected internal error, or an interactive prompt was aborted (e.g. Ctrl+C). | +| `2` | Usage error: bad flags, a flag pointing at a missing directory, an invalid or malformed JSON input (`--metadata-options`, `dataset_description.json`), or a non-compliant data filename in non-interactive mode. | +| `3` | The generated dataset failed Psych-DS validation with one or more errors. | +| `4` | Partial success: some data files could not be ingested. The metadata written covers only the files that succeeded. | -You can use the exit code in a shell script to handle failures: +If a run both fails validation and has ingestion failures, validation takes precedence: the tool exits `3`. Any non-zero code means the output should not be trusted as a complete, valid Psych-DS dataset. + +You can use the exit code in a shell script to distinguish failures: ```bash npx @jspsych/metadata-cli --psych-ds-dir=./project --data-dir=./data --metadata-options=./options.json -if [ $? -ne 0 ]; then - echo "Metadata generation failed — check the output above for errors." -fi +case $? in + 0) echo "Success." ;; + 2) echo "Bad flags or inputs — check the command line." ;; + 3) echo "Psych-DS validation failed — check the errors above." ;; + 4) echo "Some data files could not be ingested — metadata is incomplete." ;; + *) echo "Metadata generation failed — check the output above for errors." ;; +esac ``` ## Data file requirements