Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/infra-package-metadata.md
Original file line number Diff line number Diff line change
@@ -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 `*`.
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ 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
run: npm ci
Expand Down
69 changes: 59 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
# 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.

## Documentation
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`](packages/frontend) | Browser wizard — upload data, fill in a few fields, download a project. |

## Quick start (CLI)

The docs site lives in [`website/`](./website) and is built with [Docusaurus](https://docusaurus.io/) on the shared [jsPsych docs theme](https://github.com/jspsych/jspsych-docs-theme). Once deployed it will be available at **https://metadata.jspsych.org**. The homepage is a landing/overview page, and the metadata **web wizard** (built from [`packages/frontend`](./packages/frontend)) is embedded at [`/wizard`](https://metadata.jspsych.org/wizard), one click away in the top nav.
Requires Node.js 22 or later.

It covers:
```
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 — use it at
[metadata.jspsych.org/wizard](https://metadata.jspsych.org/wizard).

## Documentation

- **What is Psych-DS?** — [what the Psych-DS standard is and what the tools generate](./website/docs/introduction.md).
The docs site lives in [`website/`](./website) and is built with
[Docusaurus](https://docusaurus.io/) on the shared
[jsPsych docs theme](https://github.com/jspsych/jspsych-docs-theme). Once deployed it is
available at **https://metadata.jspsych.org**, with the web wizard (built from
[`packages/frontend`](./packages/frontend)) embedded at
[`/wizard`](https://metadata.jspsych.org/wizard). It covers:

- **What is Psych-DS?** — [what the standard is and what the tools generate](./website/docs/introduction.md).
- **Guides** — [using the wizard](./website/docs/guides/using-the-wizard.mdx), [using the CLI](./website/docs/guides/using-the-cli.mdx), and [customizing the output](./website/docs/guides/customizing-output.md) with an options file.
- **Reference** — the [CLI reference](./website/docs/reference/cli-reference.md) (flags, exit codes, filename rules).

Expand All @@ -26,10 +56,29 @@ npm start # dev server with hot reload
npm run build # production build into website/build
```

`npm start` and `npm run build` automatically run `npm run build:wizard` first, which builds the wizard from `packages/frontend` into `website/static/wizard-app/` (gitignored) so the `/wizard` page can embed it.
`npm start` and `npm run build` automatically run `npm run build:wizard` first, which builds
the wizard from `packages/frontend` into `website/static/wizard-app/` (gitignored) so the
`/wizard` page can embed it.

## 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.

## Packages
## License

- [`packages/metadata`](./packages/metadata) (`@jspsych/metadata`) — the core library.
- [`packages/cli`](./packages/cli) (`@jspsych/metadata-cli`) — the interactive CLI.
- [`packages/frontend`](./packages/frontend) — the browser-based web wizard.
MIT
66 changes: 22 additions & 44 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
{
"name": "metadata",
"private": true,
"type": "module",
"workspaces": [
"packages/*"
],
"engines": {
"node": ">=22"
},
"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",
"changeset:publish": "npm run build && changeset publish"
Expand Down
11 changes: 8 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"repository": {
"url": "https://github.com/jspsych/metadata"
},
"engines": {
"node": ">=22"
},
"bin": {
"jspsych-metadata-cli": "./dist/cjs/index.cjs"
},
Expand All @@ -25,18 +28,20 @@
"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",
"@jspsych/metadata": "*",
"@jspsych/metadata": ">=0.0.3 <0.2.0",
"psychds-validator": "^1.5.1",
"yargs": "^17.7.2"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/tests/data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,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");
});
Expand Down
1 change: 1 addition & 0 deletions packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"moduleResolution": "node",
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"types": ["jest", "node"]
},
"include": ["src", "tests"]
Expand Down
5 changes: 3 additions & 2 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/pages/DataUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
fileStatuses: FileStatus[];
};

export const emptyDataSession: DataSession = {

Check warning on line 42 in packages/frontend/src/pages/DataUpload.tsx

View workflow job for this annotation

GitHub Actions / Build

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
files: [],
sourceName: '',
convertedStore: null,
Expand Down Expand Up @@ -97,7 +97,7 @@
* it into a single subject-<stem> value, mirroring the CLI's non-rename path. JSON uploads are
* never compliant data filenames, so they always fall through to deriveFallbackBase.
*/
export const compliantBase = (name: string): string | null => {

Check warning on line 100 in packages/frontend/src/pages/DataUpload.tsx

View workflow job for this annotation

GitHub Actions / Build

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
const m = /^(.*)_data\.(csv|tsv)$/.exec(name);
return m && isValidPsychDSDataFilename(name) ? m[1] : null;
};
Expand Down Expand Up @@ -315,7 +315,7 @@
// 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) {
Expand Down Expand Up @@ -405,7 +405,7 @@
// 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<Record<string, any>> = [];
let mainRows: Array<Record<string, unknown>> = [];
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
Expand Down
8 changes: 7 additions & 1 deletion packages/frontend/src/pages/Review.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@
const [valError, setValError] = useState<string | null>(null);

// Review is unmounted whenever the user navigates away, so each visit gets a fresh snapshot.
const metadataObj = useMemo(() => jsPsychMetadata.getMetadata(), []);

Check warning on line 49 in packages/frontend/src/pages/Review.tsx

View workflow job for this annotation

GitHub Actions / Build

React Hook useMemo has a missing dependency: 'jsPsychMetadata'. Either include it or remove the dependency array
const metadataJson = useMemo(() => JSON.stringify(metadataObj, null, 2), [metadataObj]);

const projectName = useMemo(() => {
const name = jsPsychMetadata.getMetadataField('name') as string | undefined;
return name?.trim() || 'dataset';
}, []);

Check warning on line 55 in packages/frontend/src/pages/Review.tsx

View workflow job for this annotation

GitHub Actions / Build

React Hook useMemo has a missing dependency: 'jsPsychMetadata'. Either include it or remove the dependency array

// Staged Psych-DS data/ payload (paths already include `data/`); drives validation + zip.
const hasDataFiles = useMemo(() => (dataFiles?.paths().length ?? 0) > 0, [dataFiles]);
Expand All @@ -60,7 +60,13 @@
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<void>; close(): Promise<void> }> }>;
}
).showSaveFilePicker({
suggestedName: FILENAME,
types: [{ description: 'JSON file', accept: { 'application/json': ['.json'] } }],
});
Expand Down
Loading
Loading