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
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
"perf:react-hook-form": "npm run perf -w @interactive-os/json-document-react-hook-form",
"perf:ajv": "npm run perf -w @interactive-os/json-document-ajv",
"perf:rich-text": "npm run build -w @interactive-os/json-document-rich-text && node packages/json-document-rich-text/benchmarks/editor.mjs",
"release:check": "npm run verify -w @interactive-os/json-document && npm run verify:companions && npm run standard:check && npm run docs:evaluate && npm run perf:core && npm run pack:library && npm run pack:companions",
"release:check": "npm run workspace:check && npm run verify:libraries && npm run standard:check && npm run docs:evaluate && npm run perf:core && npm run pack:libraries",
"standard:check": "node standards/json-document-v3/evaluate.mjs && node standards/json-document-rich-text-v1/evaluate.mjs && node standards/json-document-rich-text-v1/evaluate-corpora.mjs && npm test -w @interactive-os/json-document -- conformance",
"verify": "npm run verify -w @interactive-os/json-document && npm run verify:companions && npm run docs:evaluate && npm run typecheck -w @interactive-os/json-document-site && npm test -w @interactive-os/json-document-site && npm run site:verify:pages && npm run browser:test",
"verify:companions": "npm run verify -w @interactive-os/json-document-selection && npm run verify -w @interactive-os/json-document-editing && npm run verify -w @interactive-os/json-document-react && npm run verify -w @interactive-os/json-document-react-hook-form && npm run verify -w @interactive-os/json-document-ajv && npm run verify -w @interactive-os/json-document-zod && npm run verify -w @interactive-os/json-document-tanstack-table && npm run verify -w @interactive-os/json-document-web && npm run verify -w @interactive-os/json-document-contenteditable && npm run verify -w @interactive-os/json-document-rich-text && npm run verify -w @interactive-os/json-document-rich-text-web && npm run verify -w @interactive-os/json-document-rich-text-react && npm run verify -w @interactive-os/json-document-collaboration && npm run verify -w @interactive-os/json-document-contenteditable-collaboration",
"pack:library": "npm pack -w @interactive-os/json-document --dry-run --cache ./.npm-cache",
"pack:companions": "npm pack -w @interactive-os/json-document-selection --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-editing --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-react --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-react-hook-form --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-ajv --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-zod --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-tanstack-table --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-web --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-contenteditable --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-rich-text --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-rich-text-web --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-rich-text-react --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-collaboration --dry-run --cache ./.npm-cache && npm pack -w @interactive-os/json-document-contenteditable-collaboration --dry-run --cache ./.npm-cache",
"verify": "npm run workspace:check && npm run verify:libraries && npm run docs:evaluate && npm run typecheck -w @interactive-os/json-document-site && npm test -w @interactive-os/json-document-site && npm run site:verify:pages && npm run browser:test",
"workspace:check": "node scripts/workspace-tasks.mjs check",
"verify:libraries": "node scripts/workspace-tasks.mjs verify",
"pack:libraries": "node scripts/workspace-tasks.mjs pack",
"browser:test": "playwright test --config site/playwright.config.ts",
"browser:test:headed": "playwright test --config site/playwright.config.ts --headed",
"browser:install": "playwright install chromium",
Expand Down
5 changes: 2 additions & 3 deletions packages/contenteditable-collaboration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document-collaboration",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"prepack": "npm run build",
"test": "vitest run --config vitest.config.ts",
"pretypecheck": "npm run build -w @interactive-os/json-document-collaboration",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-ajv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"perf": "npm run build && node benchmarks/validation.mjs",
"pretypecheck": "npm run build -w @interactive-os/json-document",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
3 changes: 1 addition & 2 deletions packages/json-document-collaboration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"prepack": "npm run build",
"test": "vitest run --config vitest.config.ts",
"test:soak": "JSON_DOCUMENT_COLLABORATION_SOAK_CASES=24 JSON_DOCUMENT_COLLABORATION_SOAK_STEPS=96 vitest run --config vitest.config.ts tests/convergence/soak.test.ts",
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-contenteditable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"pretypecheck": "npm run build -w @interactive-os/json-document",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-editing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document && npm run build -w @interactive-os/json-document-selection",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"check:structure": "node scripts/check-responsibility-layout.mjs",
"pretypecheck": "npm run build -w @interactive-os/json-document",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"test": "vitest run --config vitest.config.ts",
"perf": "npm run build && node benchmarks/editors.mjs",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-react-hook-form/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document-selection && npm run build -w @interactive-os/json-document-editing && npm run build -w @interactive-os/json-document-react",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"perf": "npm run build && node benchmarks/form.mjs",
"pretypecheck": "npm run build -w @interactive-os/json-document-selection && npm run build -w @interactive-os/json-document-editing && npm run build -w @interactive-os/json-document-react && npm run build -w @interactive-os/json-document-zod",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document && npm run build -w @interactive-os/json-document-editing",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"pretypecheck": "npm run build -w @interactive-os/json-document && npm run build -w @interactive-os/json-document-editing",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-rich-text-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document-rich-text && npm run build -w @interactive-os/json-document-rich-text-web && npm run build -w @interactive-os/json-document-react",
"build": "npm run clean && tsc -p tsconfig.json",
"pretypecheck": "npm run prebuild",
"build": "npm run clean && tsc -b tsconfig.json",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"test": "vitest run --config vitest.config.ts",
"verify": "npm run typecheck && npm test && npm run build"
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-rich-text-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document-rich-text && npm run build -w @interactive-os/json-document-web",
"build": "npm run clean && tsc -p tsconfig.json",
"pretypecheck": "npm run prebuild",
"build": "npm run clean && tsc -b tsconfig.json",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"test": "vitest run --config vitest.config.ts",
"verify": "npm run typecheck && npm test && npm run build"
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-rich-text/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document && npm run build -w @interactive-os/json-document-selection && npm run build -w @interactive-os/json-document-editing",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"check:structure": "node scripts/check-editor-responsibilities.mjs",
"pretypecheck": "npm run build -w @interactive-os/json-document && npm run build -w @interactive-os/json-document-selection && npm run build -w @interactive-os/json-document-editing",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"test": "vitest run --config vitest.config.ts",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"perf": "npm run build && node benchmarks/editor.mjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/json-document-selection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
},
"scripts": {
"clean": "rm -rf dist",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"typecheck": "tsc -p tsconfig.test.json --noEmit && tsc -p tests/dom-free/tsconfig.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-tanstack-table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document-editing",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"pretypecheck": "npm run build -w @interactive-os/json-document-editing",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document-selection && npm run build -w @interactive-os/json-document-editing",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"pretypecheck": "npm run build -w @interactive-os/json-document-selection && npm run build -w @interactive-os/json-document-editing",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
5 changes: 2 additions & 3 deletions packages/json-document-zod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run build -w @interactive-os/json-document",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"test": "vitest run --config vitest.config.ts",
"pretypecheck": "npm run build -w @interactive-os/json-document",
"pretypecheck": "node ../../scripts/workspace-tasks.mjs build-dependencies",
"typecheck": "tsc -p tsconfig.test.json --noEmit",
"verify": "npm run typecheck && npm test && npm run build"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/json-document/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"scripts": {
"clean": "rm -rf dist",
"layers:check": "node ./scripts/evaluate-layers.mjs",
"build": "npm run clean && tsc -p tsconfig.json",
"build": "npm run clean && tsc -b tsconfig.json",
"prepack": "npm run build",
"prepublishOnly": "npm --prefix ../.. run release:check",
"smoke:package": "node ./tests/package/smoke.mjs",
Expand Down
168 changes: 168 additions & 0 deletions scripts/workspace-tasks.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { dirname, join, normalize } from "node:path";
import { fileURLToPath } from "node:url";

const repositoryRoot = dirname(dirname(fileURLToPath(import.meta.url)));

function readJson(path) {
return JSON.parse(readFileSync(join(repositoryRoot, path), "utf8"));
}

const rootPackage = readJson("package.json");
const libraryDirectories = rootPackage.workspaces.filter((directory) =>
directory.startsWith("packages/"),
);
const libraries = libraryDirectories.map((directory) => ({
directory,
manifest: readJson(`${directory}/package.json`),
tsconfig: readJson(`${directory}/tsconfig.json`),
}));
const libraryByName = new Map(libraries.map((library) => [library.manifest.name, library]));

function internalDependencies(library) {
const manifest = library.manifest;
const names = Object.keys({
...manifest.dependencies,
...manifest.optionalDependencies,
...manifest.peerDependencies,
...manifest.devDependencies,
});
return names.flatMap((name) => {
const dependency = libraryByName.get(name);
return dependency === undefined ? [] : [dependency];
});
}

function topologicalLibraries() {
const result = [];
const visiting = new Set();
const visited = new Set();
function visit(library) {
const name = library.manifest.name;
if (visited.has(name)) return;
if (visiting.has(name)) throw new Error(`workspace dependency cycle: ${name}`);
visiting.add(name);
for (const dependency of internalDependencies(library)) visit(dependency);
visiting.delete(name);
visited.add(name);
result.push(library);
}
for (const library of libraries) visit(library);
return result;
}

function run(command, args) {
const result = spawnSync(command, args, {
cwd: repositoryRoot,
env: process.env,
stdio: "inherit",
});
if (result.error !== undefined) throw result.error;
if (result.status !== 0) process.exit(result.status ?? 1);
}

function runCaptured(command, args) {
const result = spawnSync(command, args, {
cwd: repositoryRoot,
env: process.env,
encoding: "utf8",
});
if (result.stderr) process.stderr.write(result.stderr);
if (result.error !== undefined) throw result.error;
if (result.status !== 0) {
if (result.stdout) process.stdout.write(result.stdout);
process.exit(result.status ?? 1);
}
return result.stdout;
}

function runOptionalScript(script) {
for (const library of topologicalLibraries()) {
if (library.manifest.scripts?.[script] === undefined) {
console.log(`[workspace:${script}] skip ${library.manifest.name}: no script`);
continue;
}
console.log(`[workspace:${script}] ${library.manifest.name}`);
run("npm", ["run", script, "--workspace", library.manifest.name]);
}
}

function packLibraries() {
run("npm", ["run", "build"]);
for (const library of topologicalLibraries()) {
if (library.manifest.private === true) {
console.log(`[workspace:pack] skip ${library.manifest.name}: private`);
continue;
}
console.log(`[workspace:pack] ${library.manifest.name}`);
const output = runCaptured("npm", [
"pack",
"--workspace",
library.manifest.name,
"--dry-run",
"--json",
"--cache",
"./.npm-cache",
]);
const packed = JSON.parse(output)[0];
if (packed.files.some(({ path }) => path.endsWith(".tsbuildinfo"))) {
throw new Error(`${library.manifest.name}: TypeScript build metadata leaked into package`);
}
console.log(`[workspace:pack] ok ${packed.name}: ${packed.entryCount} files`);
}
}

function buildDependencies() {
const currentDirectory = normalize(process.cwd());
const library = libraries.find(({ directory }) =>
currentDirectory === normalize(join(repositoryRoot, directory)),
);
if (library === undefined) throw new Error("build-dependencies must run from a library workspace");
const dependencies = new Set(internalDependencies(library).map(({ manifest }) => manifest.name));
const configs = topologicalLibraries()
.filter(({ manifest }) => dependencies.has(manifest.name))
.map(({ directory }) => `${directory}/tsconfig.json`);
if (configs.length === 0) return;
run("npm", ["exec", "tsc", "--", "-b", ...configs]);
}

function checkWorkspaceContract() {
const solution = readJson("tsconfig.build.json");
const solutionDirectories = solution.references.map(({ path }) => normalize(path.replace(/^\.\//, "")));
if (JSON.stringify(solutionDirectories) !== JSON.stringify(libraryDirectories)) {
throw new Error("tsconfig.build.json references must match library workspaces");
}
for (const library of libraries) {
if (library.manifest.private === true) throw new Error(`library workspace must be publishable: ${library.directory}`);
for (const script of ["build", "verify"]) {
if (library.manifest.scripts?.[script] === undefined) throw new Error(`${library.directory}: missing ${script} script`);
}
const manifest = library.manifest;
const expected = internalDependencies(library)
.filter(({ manifest: dependency }) =>
manifest.dependencies?.[dependency.name] !== undefined ||
manifest.optionalDependencies?.[dependency.name] !== undefined ||
manifest.peerDependencies?.[dependency.name] !== undefined)
.map(({ directory }) => directory)
.sort();
const actual = (library.tsconfig.references ?? [])
.map(({ path }) => normalize(join(library.directory, path)))
.sort();
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(`${library.directory}: project references do not match runtime dependencies`);
}
}
console.log(`workspace task contract ok: ${libraries.length} libraries`);
}

const command = process.argv[2];
if (command === "check") checkWorkspaceContract();
else if (command === "verify") runOptionalScript("verify");
else if (command === "pack") packLibraries();
else if (command === "build-dependencies") buildDependencies();
else if (command === "list") console.log(topologicalLibraries().map(({ manifest }) => manifest.name).join("\n"));
else {
console.error("usage: node scripts/workspace-tasks.mjs <build-dependencies|check|list|verify|pack>");
process.exit(2);
}