diff --git a/.github/scripts/manifests/README.md b/.github/scripts/manifests/README.md new file mode 100644 index 00000000..615b5f5b --- /dev/null +++ b/.github/scripts/manifests/README.md @@ -0,0 +1,109 @@ +# Demo Manifests + +Builds one searchable **Demo Manifest** per demo, so the JointJS MCP +demo search ranks catalog entries (title, summary, keywords, packages) +instead of raw source chunks. Each manifest carries a section per +**Variant**. Vocabulary (Manifest, Variant, Demo Snapshot, Edition) +follows `CONTEXT.md` in the +[joint-mcp](https://github.com/clientIO/joint-mcp) repo; the spec is +joint-mcp issue #27 (v3 layout: #39). + +## Usage + +```bash +# from the repo root +npm run manifests:build -- --version 4.3 # write .manifests/ +npm run manifests:test # golden-fixture + unit tests +``` + +`--version` is required: this repo is unversioned, so the value labels the +Demo Snapshot the manifests describe (the JointJS version the demos target). +Manifests exist from snapshot 4.3 forward. + +## Output + +``` +.manifests/ + manifests/ + version-4.3/ + .md # one Manifest per demo, YAML frontmatter + per-variant sections + manifests-index/ + version-4.3.json # slim runtime index: bare array of { demo, variant_dir, variant } +``` + +One Manifest per demo, flat under `manifests/version-X.Y/`. Frontmatter is +`demo`, `version`, `edition`, `title`. A shared header (title + summary, +**Edition**, curated **Keywords**) precedes one `## Variant: ` section +per variant, each carrying a `demo_id` / `**Packages:**` / `**Uses:**` block +followed by its `### Source files`. `demo_id` +(`version-X.Y//`) matches the R2 `versioned_demos/` +layout, so `get_demo_code` resolves it unchanged. + +The `manifests-index/version-X.Y.json` is a bare array of +`{ demo, variant_dir, variant }` (canonical `variant`: `react-ts` → +`react`, `vue-ts` → `vue`) in generation order — the worker's framework +pre-filter source. It is deliberately kept **outside** the `manifests/` +prefix so the AutoRAG index never chunks it as searchable noise. + +Rules of thumb: + +- **Title & summary** come from the **demo-root `README.md` only**; variant + READMEs are no longer parsed (they still appear under Source files). A demo + with no root README is skipped entirely (`:: Skipping (no root + README.md)`) — no manifest, no index entries. +- **Keywords** come solely from `demo-keywords.json` at the repo root, keyed + by demo name (shared across variants). Authoring rule: keywords are the + *synonym/expansion channel* — terms an agent might search for that the + title and summary do **not** already contain. Prefer the established + jointjs.com/demos vocabulary (application categories such as + "Project management", "Data modeling"; feature tags such as "Drag & Drop", + "Automatic layout"); free terms (diagram-type synonyms like "swimlane", + "ERD") are allowed. Casing and order are preserved as authored. A demo + missing from the overlay gets a build warning and no Keywords line; a + keyword containing a comma is warned about and skipped (the body line is + comma-joined). +- **Uses** lists collapsed runtime Joint API surfaces extracted from imports + of Joint packages: named imports directly (`GraphProvider`), namespace + bindings resolved through member access and collapsed after the first + capitalized segment (`ui.Stencil`, `shapes.standard.Rectangle`, + `dia.Paper.Options` → `dia.Paper`). Aliased imports are recorded under + their original names, star imports without their local prefix; + imported-but-unused bindings and the `jsx`/`env` tooling bindings are + dropped. +- **Source files** is a curated view for orientation, not the full + inventory (`get_demo_code` lists the real files live): lockfiles + (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`) and binary assets + (png, jpg, jpeg, gif, ico, woff, woff2, ttf, eot, mp3, mp4) are excluded; + `.svg` and config files are kept deliberately. +- **Packages** come from the variant's `package.json` `dependencies` + (`@joint/*`, `jointjs`, `rappid`, `@clientio/rappid`). **Edition** is + shared across the demo: `commercial` when any variant depends on + `@joint/plus` or `@joint/react-plus` (or `rappid`/`@clientio/rappid`); + demos with no joint dependency (CDN-loaded) fall back to a `JointJS+` + title check. +- **Imperative-react advisory.** A `react` variant whose packages include no + `@joint/react*` package gets a single advisory line after its + `**Packages:**` line, steering agents to `get_started(framework="react")` + and `@joint/react` (or `@joint/react-plus` under a JointJS+ license). +- Every variant directory with a `package.json` gets a section — + `demos.config.json` `skip` flags are build-only and not honored here. + +## R2 destination + +Manifests are uploaded (by the sync step, joint-mcp#30) to the demos bucket +under two top-level prefixes, siblings of the demo source: + +``` +versioned_demos/version-4.3///… # demo source (existing) +manifests/version-4.3/.md # search documents (this script) +manifests-index/version-4.3.json # framework pre-filter index (this script) +``` + +The upload is scripted: `npm run sync -- --version X.Y` (dev bucket) / +`npm run sync:prod -- --version X.Y` (prod) syncs all three prefixes — +see `.github/scripts/sync.mjs`. It is sync-only and fails preflight +unless this script's build output for that version exists. + +The AutoRAG demos index points at `manifests/` only; the slim +`manifests-index/` sits outside it and `get_demo_code` keeps reading full +source from `versioned_demos/`. diff --git a/.github/scripts/manifests/build.mjs b/.github/scripts/manifests/build.mjs new file mode 100644 index 00000000..9a2c2bc0 --- /dev/null +++ b/.github/scripts/manifests/build.mjs @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +// Builds Demo Manifests into .manifests/: one markdown Manifest per demo at +// manifests/version-X.Y/{demo}.md plus a slim index at +// manifests-index/version-X.Y.json, ready to upload to the demos R2 bucket +// under the matching prefixes — see README.md here. +// +// Usage: npm run manifests:build -- --version 4.3 + +import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { parseArgs } from 'node:util'; +import { generateManifests } from './generate.mjs'; + +const USAGE = 'Usage: npm run manifests:build -- --version X.Y (e.g. 4.3)'; + +let values = {}; +try { + ({ values } = parseArgs({ options: { version: { type: 'string' } } })); +} catch { + console.warn(USAGE); + process.exit(1); +} +if (!values.version || !/^\d+\.\d+$/.test(values.version)) { + console.warn(USAGE); + process.exit(1); +} + +const ROOT = resolve(import.meta.dirname, '..', '..', '..'); +const OUT_DIR = join(ROOT, '.manifests'); + +rmSync(OUT_DIR, { recursive: true, force: true }); +const outputs = generateManifests(ROOT, values.version); +for (const [relPath, content] of outputs) { + const destination = join(OUT_DIR, relPath); + mkdirSync(dirname(destination), { recursive: true }); + writeFileSync(destination, content); +} +const demoCount = [...outputs.keys()].filter((key) => key.startsWith('manifests/version-')).length; +const variantCount = JSON.parse(outputs.get(`manifests-index/version-${values.version}.json`)).length; +console.log( + `Wrote ${outputs.size} files to .manifests/ (${demoCount} demos, ${variantCount} variants, Demo Snapshot version ${values.version})`, +); diff --git a/.github/scripts/manifests/fixtures/expected/manifests-index/version-9.9.json b/.github/scripts/manifests/fixtures/expected/manifests-index/version-9.9.json new file mode 100644 index 00000000..10867b9d --- /dev/null +++ b/.github/scripts/manifests/fixtures/expected/manifests-index/version-9.9.json @@ -0,0 +1,32 @@ +[ + { + "demo": "flow-tool", + "variant_dir": "ts", + "variant": "ts" + }, + { + "demo": "flow-tool", + "variant_dir": "vue-ts", + "variant": "vue" + }, + { + "demo": "legacy-embed", + "variant_dir": "js", + "variant": "js" + }, + { + "demo": "widget-board", + "variant_dir": "js", + "variant": "js" + }, + { + "demo": "widget-board", + "variant_dir": "react-js", + "variant": "react" + }, + { + "demo": "widget-board", + "variant_dir": "react-ts", + "variant": "react" + } +] diff --git a/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/flow-tool.md b/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/flow-tool.md new file mode 100644 index 00000000..9a90d1f7 --- /dev/null +++ b/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/flow-tool.md @@ -0,0 +1,38 @@ +--- +demo: "flow-tool" +version: "9.9" +edition: "open-source" +title: "Flow Tool" +--- + +# Flow Tool + +Flow Tool is an open-source JointJS demo showing a minimal process editor. + +**Edition:** open-source + +**Keywords:** Process modeling, Automatic layout, swimlane + +## Variant: ts + +**demo_id:** version-9.9/flow-tool/ts +**Packages:** @joint/core +**Uses:** dia.Paper, shapes.standard.Rectangle + +### Source files + +- README.md +- package.json +- src/main.ts + +## Variant: vue-ts + +**demo_id:** version-9.9/flow-tool/vue-ts +**Packages:** @joint/core +**Uses:** dia.Graph, shapes.standard.Rectangle + +### Source files + +- README.md +- package.json +- src/main.ts diff --git a/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/legacy-embed.md b/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/legacy-embed.md new file mode 100644 index 00000000..bff2ba22 --- /dev/null +++ b/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/legacy-embed.md @@ -0,0 +1,23 @@ +--- +demo: "legacy-embed" +version: "9.9" +edition: "commercial" +title: "JointJS+: Legacy Embed" +--- + +# JointJS+: Legacy Embed + +Legacy Embed loads JointJS+ from a CDN bundle, so its package.json declares no Joint dependency. + +**Edition:** commercial + +## Variant: js + +**demo_id:** version-9.9/legacy-embed/js +**Packages:** none +**Uses:** none + +### Source files + +- index.html +- package.json diff --git a/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/widget-board.md b/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/widget-board.md new file mode 100644 index 00000000..65c1552b --- /dev/null +++ b/.github/scripts/manifests/fixtures/expected/manifests/version-9.9/widget-board.md @@ -0,0 +1,49 @@ +--- +demo: "widget-board" +version: "9.9" +edition: "commercial" +title: "JointJS+: Widget Board" +--- + +# JointJS+: Widget Board + +The Widget Board demo lets users arrange dashboard widgets on a grid and connect them with links. + +**Edition:** commercial + +**Keywords:** Dashboard, Drag & Drop, widget grid + +## Variant: js + +**demo_id:** version-9.9/widget-board/js +**Packages:** @joint/plus +**Uses:** dia.Graph, dia.Paper, highlighters.addClass, ui.Stencil + +### Source files + +- index.html +- package.json +- src/index.js + +## Variant: react-js + +**demo_id:** version-9.9/widget-board/react-js +**Packages:** @joint/plus +⚠ **API note:** this variant uses the imperative `@joint/plus` API inside React, not the declarative React package. For new React apps call `get_started(framework="react")` and prefer `@joint/react` — or `@joint/react-plus` if the project has a JointJS+ license (this demo relies on JointJS+ features). Adapt this demo's ideas, not its integration pattern. +**Uses:** dia.Paper + +### Source files + +- package.json +- src/App.jsx + +## Variant: react-ts + +**demo_id:** version-9.9/widget-board/react-ts +**Packages:** @joint/react-plus +**Uses:** GraphProvider, Paper + +### Source files + +- package.json +- src/App.tsx diff --git a/.github/scripts/manifests/fixtures/sample-repo/demo-keywords.json b/.github/scripts/manifests/fixtures/sample-repo/demo-keywords.json new file mode 100644 index 00000000..09fc565c --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/demo-keywords.json @@ -0,0 +1,4 @@ +{ + "flow-tool": ["Process modeling", "Automatic layout", "flowchart, diagram", "swimlane"], + "widget-board": ["Dashboard", "Drag & Drop", "widget grid"] +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/flow-tool/README.md b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/README.md new file mode 100644 index 00000000..14adfed2 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/README.md @@ -0,0 +1,17 @@ +# Flow Tool + +Flow Tool is an open-source JointJS demo showing a minimal process editor. + + + Open in StackBlitz + + +This demo is also available online at [jointjs.com](https://jointjs.com/demos/flow-tool). + +## Available Versions + +- [TypeScript](./ts/) +- [Vue](./vue-ts/) diff --git a/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/README.md b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/README.md new file mode 100644 index 00000000..8d64e4bf --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/README.md @@ -0,0 +1,15 @@ +# Flow Tool (TypeScript) + +Flow Tool is an open-source JointJS demo showing a minimal process editor. + + + Open in StackBlitz + + +## What this demo shows + +- **Custom shapes** — rectangles with ports +- **Link routing** — orthogonal links between ports diff --git a/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/package.json b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/package.json new file mode 100644 index 00000000..475c7605 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/package.json @@ -0,0 +1,7 @@ +{ + "name": "flow-tool-ts", + "private": true, + "dependencies": { + "@joint/core": "4.3.0" + } +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/src/main.ts b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/src/main.ts new file mode 100644 index 00000000..33786d00 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/ts/src/main.ts @@ -0,0 +1,6 @@ +import * as joint from '@joint/core'; + +const options: joint.dia.Paper.Options = { gridSize: 10 }; +const paper = new joint.dia.Paper(options); +const rect = new joint.shapes.standard.Rectangle(); +paper.model.addCell(rect); diff --git a/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/README.md b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/README.md new file mode 100644 index 00000000..4137c755 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/README.md @@ -0,0 +1,3 @@ +# Flow Tool (Vue) + +Flow Tool is an open-source JointJS demo showing a minimal process editor, built with Vue. diff --git a/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/package.json b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/package.json new file mode 100644 index 00000000..48206fe5 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/package.json @@ -0,0 +1,7 @@ +{ + "name": "flow-tool-vue", + "private": true, + "dependencies": { + "@joint/core": "4.3.0" + } +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/src/main.ts b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/src/main.ts new file mode 100644 index 00000000..8573548f --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/flow-tool/vue-ts/src/main.ts @@ -0,0 +1,5 @@ +import { dia, shapes as defaultShapes } from '@joint/core'; + +const graph = new dia.Graph(); +const rect = new defaultShapes.standard.Rectangle(); +graph.addCell(rect); diff --git a/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/README.md b/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/README.md new file mode 100644 index 00000000..e2fc86d0 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/README.md @@ -0,0 +1,3 @@ +# JointJS+: Legacy Embed + +Legacy Embed loads JointJS+ from a CDN bundle, so its package.json declares no Joint dependency. diff --git a/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/js/index.html b/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/js/index.html new file mode 100644 index 00000000..3a757cb1 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/js/index.html @@ -0,0 +1,2 @@ + +
diff --git a/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/js/package.json b/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/js/package.json new file mode 100644 index 00000000..1f794c40 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/legacy-embed/js/package.json @@ -0,0 +1,4 @@ +{ + "name": "legacy-embed-js", + "private": true +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/README.md b/.github/scripts/manifests/fixtures/sample-repo/widget-board/README.md new file mode 100644 index 00000000..abf16b59 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/README.md @@ -0,0 +1,10 @@ +# JointJS+: Widget Board JointJS+ + +The Widget Board demo lets users arrange dashboard widgets on a grid and connect them with links. + +This demo is also available online at [jointjs.com](https://jointjs.com/demos/widget-board). + +## Available Versions + +- [JavaScript](./js/) +- [React](./react-ts/) diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/assets/logo.png b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/assets/logo.png new file mode 100644 index 00000000..072a0152 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/assets/logo.png @@ -0,0 +1 @@ +png-placeholder diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/index.html b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/index.html new file mode 100644 index 00000000..3a757cb1 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/index.html @@ -0,0 +1,2 @@ + +
diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/package-lock.json b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/package-lock.json new file mode 100644 index 00000000..2a28ba8d --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/package-lock.json @@ -0,0 +1,4 @@ +{ + "name": "widget-board-js", + "lockfileVersion": 3 +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/package.json b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/package.json new file mode 100644 index 00000000..92b60a94 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/package.json @@ -0,0 +1,10 @@ +{ + "name": "widget-board-js", + "private": true, + "dependencies": { + "@joint/plus": "4.3.0" + }, + "devDependencies": { + "webpack": "5.99.5" + } +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/src/index.js b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/src/index.js new file mode 100644 index 00000000..2042a5ea --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/js/src/index.js @@ -0,0 +1,7 @@ +import { dia, ui, highlighters, util } from '@joint/plus'; + +const graph = new dia.Graph(); +const paper = new dia.Paper({ model: graph }); +const stencil = new ui.Stencil({ paper }); +highlighters.addClass.add(stencil.el, 'body', 'active'); +console.log(stencil); diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-js/package.json b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-js/package.json new file mode 100644 index 00000000..7e9e95a3 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-js/package.json @@ -0,0 +1,8 @@ +{ + "name": "widget-board-react-js", + "private": true, + "dependencies": { + "@joint/plus": "4.3.0", + "react": "19.0.0" + } +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-js/src/App.jsx b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-js/src/App.jsx new file mode 100644 index 00000000..95a07d86 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-js/src/App.jsx @@ -0,0 +1,6 @@ +import { dia } from '@joint/plus'; + +export function App() { + const paper = new dia.Paper({}); + return paper; +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-ts/package.json b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-ts/package.json new file mode 100644 index 00000000..73a44e31 --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-ts/package.json @@ -0,0 +1,8 @@ +{ + "name": "widget-board-react", + "private": true, + "dependencies": { + "@joint/react-plus": "4.3.0", + "react": "19.0.0" + } +} diff --git a/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-ts/src/App.tsx b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-ts/src/App.tsx new file mode 100644 index 00000000..db4b560f --- /dev/null +++ b/.github/scripts/manifests/fixtures/sample-repo/widget-board/react-ts/src/App.tsx @@ -0,0 +1,10 @@ +import { jsx } from '@joint/react-plus/jsx-runtime'; +import { GraphProvider, Paper } from '@joint/react-plus'; + +export function App() { + return ( + + + + ); +} diff --git a/.github/scripts/manifests/generate.mjs b/.github/scripts/manifests/generate.mjs new file mode 100644 index 00000000..c8e66e71 --- /dev/null +++ b/.github/scripts/manifests/generate.mjs @@ -0,0 +1,99 @@ +// Walks a joint-demos checkout and produces one Demo Manifest per demo +// (a demo is any top-level dir with a root README.md; its variants are the +// direct subdirs containing a package.json). Enumeration mirrors +// build-demos.sh: skip dot-dirs, node_modules, _site. demos.config.json +// skip flags are build-only and intentionally not honored. + +import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from 'node:fs'; +import { join } from 'node:path'; +import { buildDemoManifest, renderIndex } from './transform.mjs'; + +const SKIP_TOP_LEVEL = new Set(['node_modules', '_site']); +const SKIP_VARIANT_ENTRIES = new Set(['node_modules', 'dist', 'build', 'coverage', '.git', '.angular', '.DS_Store']); +const CODE_FILE_RE = /\.(?:js|mjs|ts|mts|jsx|tsx)$/; + +// Overlay entries are validated here (warn + skip on commas, warn + omit on +// missing demos) so the transform stays pure. Exported for its unit test. +export function resolveKeywords(demoName, overlay) { + const entry = overlay[demoName]; + if (entry === undefined) { + console.warn(`:: No demo-keywords.json entry for ${demoName}; Keywords line omitted`); + return []; + } + return entry.filter((keyword) => { + if (keyword.includes(',')) { + console.warn(`:: ${demoName}: skipping keyword with comma: ${JSON.stringify(keyword)}`); + return false; + } + return true; + }); +} + +function listFiles(dir, prefix = '') { + const files = []; + for (const name of readdirSync(dir).sort()) { + if (SKIP_VARIANT_ENTRIES.has(name)) continue; + const path = join(dir, name); + const rel = prefix ? `${prefix}/${name}` : name; + const stats = lstatSync(path); + if (stats.isSymbolicLink()) continue; + if (stats.isDirectory()) { + if (name.startsWith('.')) continue; + files.push(...listFiles(path, rel)); + } else { + files.push(rel); + } + } + return files; +} + +export function generateManifests(rootDir, version) { + const outputs = new Map(); + const indexEntries = []; + const keywordsPath = join(rootDir, 'demo-keywords.json'); + const keywordOverlay = existsSync(keywordsPath) + ? JSON.parse(readFileSync(keywordsPath, 'utf8')) + : {}; + for (const demoName of readdirSync(rootDir).sort()) { + if (demoName.startsWith('.') || SKIP_TOP_LEVEL.has(demoName)) continue; + const demoDir = join(rootDir, demoName); + if (!statSync(demoDir).isDirectory()) continue; + // Title/summary come from the demo-root README only; no root README + // means no manifest and no index entries for the demo. + const readmePath = join(demoDir, 'README.md'); + if (!existsSync(readmePath)) { + console.warn(`:: Skipping ${demoName} (no root README.md)`); + continue; + } + const variants = []; + for (const variantDir of readdirSync(demoDir).sort()) { + const variantPath = join(demoDir, variantDir); + if (variantDir.startsWith('.') || !statSync(variantPath).isDirectory()) continue; + if (!existsSync(join(variantPath, 'package.json'))) continue; + const files = listFiles(variantPath); + variants.push({ + variantDir, + packageJson: JSON.parse(readFileSync(join(variantPath, 'package.json'), 'utf8')), + files, + sources: files + .filter((file) => CODE_FILE_RE.test(file)) + .map((file) => readFileSync(join(variantPath, file), 'utf8')), + }); + } + // A demo with a root README but no variants yields no manifest; + // resolve keywords only past this guard so it never warns about a + // missing entry. + if (variants.length === 0) continue; + const manifest = buildDemoManifest({ + demoName, + version, + readme: readFileSync(readmePath, 'utf8'), + keywords: resolveKeywords(demoName, keywordOverlay), + variants, + }); + outputs.set(manifest.path, manifest.content); + indexEntries.push(...manifest.indexEntries); + } + outputs.set(`manifests-index/version-${version}.json`, renderIndex(indexEntries)); + return outputs; +} diff --git a/.github/scripts/manifests/manifests.test.mjs b/.github/scripts/manifests/manifests.test.mjs new file mode 100644 index 00000000..dfc4d39c --- /dev/null +++ b/.github/scripts/manifests/manifests.test.mjs @@ -0,0 +1,152 @@ +import assert from 'node:assert/strict'; +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join } from 'node:path'; +import { test } from 'node:test'; +import { canonicalVariant, edition, extractUses, parseReadme } from './transform.mjs'; +import { generateManifests, resolveKeywords } from './generate.mjs'; + +const FIXTURES = join(import.meta.dirname, 'fixtures'); + +function walk(dir, prefix = '') { + const files = []; + for (const name of readdirSync(dir).sort()) { + const path = join(dir, name); + const rel = prefix ? `${prefix}/${name}` : name; + if (statSync(path).isDirectory()) { + files.push(...walk(path, rel)); + } else { + files.push(rel); + } + } + return files; +} + +test('canonicalVariant maps directory names to Variants', () => { + assert.equal(canonicalVariant('js'), 'js'); + assert.equal(canonicalVariant('ts'), 'ts'); + assert.equal(canonicalVariant('angular'), 'angular'); + assert.equal(canonicalVariant('react'), 'react'); + assert.equal(canonicalVariant('react-ts'), 'react'); + assert.equal(canonicalVariant('react-js'), 'react'); + assert.equal(canonicalVariant('react-redux-ts'), 'react'); + assert.equal(canonicalVariant('vue'), 'vue'); + assert.equal(canonicalVariant('vue-ts'), 'vue'); + assert.equal(canonicalVariant('vue-js'), 'vue'); + assert.equal(canonicalVariant('svelte'), 'svelte'); +}); + +test('edition distinguishes commercial from open-source', () => { + assert.equal(edition(['@joint/plus'], 'anything'), 'commercial'); + assert.equal(edition(['@joint/core', '@joint/react-plus'], 'anything'), 'commercial'); + assert.equal(edition(['@clientio/rappid'], 'anything'), 'commercial'); + assert.equal(edition(['rappid'], 'anything'), 'commercial'); + assert.equal(edition(['@joint/core'], 'anything'), 'open-source'); + assert.equal(edition([], 'JointJS+: CDN Demo'), 'commercial'); + assert.equal(edition([], 'Plain Demo'), 'open-source'); +}); + +test('extractUses resolves namespace members and named symbols', () => { + const source = [ + 'import { dia, ui } from \'@joint/plus\';', + 'import { GraphProvider } from \'@joint/react-plus\';', + 'new dia.Paper({});', + 'new ui.Stencil({});', + 'new shapes.standard.Rectangle();', + 'render(GraphProvider);', + ].join('\n'); + // shapes is never imported from a Joint package, so it is not recorded; + // GraphProvider has no member access, so it is recorded bare. + assert.deepEqual(extractUses([source]), ['GraphProvider', 'dia.Paper', 'ui.Stencil']); +}); + +test('extractUses collapses chains after the first capitalized segment', () => { + const source = [ + 'import { dia, shapes, util, highlighters } from \'@joint/core\';', + 'new dia.Paper({} as dia.Paper.Options);', + 'const id: dia.Cell.ID = id2;', + 'new shapes.standard.Rectangle();', + 'util.breakText(\'x\');', + 'highlighters.addClass.add(view, \'body\', \'hl\');', + ].join('\n'); + assert.deepEqual(extractUses([source]), [ + 'dia.Cell', + 'dia.Paper', + 'highlighters.addClass', + 'shapes.standard.Rectangle', + 'util.breakText', + ]); +}); + +test('extractUses canonicalizes aliased and star imports', () => { + const aliased = [ + 'import { shapes as defaultShapes } from \'@joint/core\';', + 'new defaultShapes.standard.Rectangle();', + ].join('\n'); + assert.deepEqual(extractUses([aliased]), ['shapes.standard.Rectangle']); + const bareAlias = [ + 'import { shapes as defaultShapes } from \'@joint/core\';', + 'register(defaultShapes);', + ].join('\n'); + assert.deepEqual(extractUses([bareAlias]), ['shapes']); + const star = [ + 'import * as joint from \'@joint/core\';', + 'new joint.shapes.standard.Rectangle();', + 'joint.util.breakText(\'x\');', + ].join('\n'); + assert.deepEqual(extractUses([star]), ['shapes.standard.Rectangle', 'util.breakText']); +}); + +test('extractUses drops tooling and unused imports', () => { + const source = [ + 'import { jsx } from \'@joint/react-plus/jsx-runtime\';', + 'import { env } from \'@joint/core\';', + 'import { dia, ui } from \'@joint/plus\';', + 'if (env.test) {}', + 'new dia.Paper({});', + ].join('\n'); + // jsx and env are tooling bindings; ui is imported but never referenced. + assert.deepEqual(extractUses([source]), ['dia.Paper']); +}); + +test('parseReadme strips multi-line HTML and keeps prose angle brackets', () => { + const markdown = [ + '# Demo', + '', + 'Summary paragraph.', + '', + '', + ' ', + '', + '', + 'Costs a < b and emphasis works.', + '', + '## Next', + ].join('\n'); + const { summary } = parseReadme(markdown); + assert.equal(summary, 'Summary paragraph.\n\nCosts a < b and emphasis works.'); +}); + +test('resolveKeywords omits missing demos and skips comma keywords', () => { + // Both paths warn on stderr; assertions cover the returned value only. + assert.deepEqual(resolveKeywords('kanban', {}), []); + assert.deepEqual( + resolveKeywords('kanban', { kanban: ['task board', 'flowchart, diagram', 'swimlane'] }), + ['task board', 'swimlane'], + ); +}); + +test('golden: sample repo produces the expected manifests and index', () => { + const outputs = generateManifests(join(FIXTURES, 'sample-repo'), '9.9'); + const expectedRoot = join(FIXTURES, 'expected'); + const expectedFiles = walk(expectedRoot); + // Sort both sides: the walk descends per-directory, but the flat key sort + // orders the `manifests/` and `manifests-index/` prefixes differently + // ('-' < '/'), so compare the key sets order-insensitively. + assert.deepEqual([...outputs.keys()].sort(), [...expectedFiles].sort()); + for (const relPath of expectedFiles) { + assert.equal(outputs.get(relPath), readFileSync(join(expectedRoot, relPath), 'utf8'), relPath); + } +}); diff --git a/.github/scripts/manifests/transform.mjs b/.github/scripts/manifests/transform.mjs new file mode 100644 index 00000000..21a2a445 --- /dev/null +++ b/.github/scripts/manifests/transform.mjs @@ -0,0 +1,229 @@ +// Pure transform seam for Demo Manifests (joint-mcp#27, v3): one demo's +// variants in, a single per-demo Manifest markdown + its index entries out. +// No filesystem access — the golden-fixture test drives this via +// generateManifests(). + +const COMMERCIAL_PACKAGES = new Set(['@joint/plus', '@joint/react-plus', '@clientio/rappid', 'rappid']); +const JOINT_PACKAGE_RE = /^(@joint\/|@clientio\/rappid$|jointjs$|rappid$)/; + +// 'react-redux-ts' -> 'react', 'vue-ts' -> 'vue', 'js' -> 'js' +export function canonicalVariant(variantDir) { + return variantDir.split('-')[0]; +} + +export function jointPackages(packageJson) { + return Object.keys(packageJson.dependencies ?? {}) + .filter((name) => JOINT_PACKAGE_RE.test(name)) + .sort(); +} + +// Edition is keyed off package.json dependencies; the title check covers +// demos that load Joint from a CDN bundle and declare no joint dependency. +export function edition(packages, title) { + if (packages.length > 0) { + return packages.some((name) => COMMERCIAL_PACKAGES.has(name)) ? 'commercial' : 'open-source'; + } + return title.includes('JointJS+') ? 'commercial' : 'open-source'; +} + +// [^;]+? spans multi-line named imports (which contain no ';') but cannot +// leak across a preceding statement such as a side-effect CSS import. +const JOINT_IMPORT_RE = /import\s+([^;]+?)\s+from\s+['"](?:@joint\/[^'"]+|jointjs|@clientio\/rappid|rappid)['"]/g; + +const TOOLING_BINDINGS = new Set(['jsx', 'env']); + +// Binary assets and lockfiles are excluded from the manifest's Source files +// section only — the Demo Snapshot upload and get_demo_code are unaffected. +const BINARY_EXT_RE = /\.(?:png|jpe?g|gif|ico|woff2?|ttf|eot|mp3|mp4)$/i; +const LOCKFILES = new Set(['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']); + +function isSourceFile(path) { + const base = path.split('/').pop(); + return !BINARY_EXT_RE.test(base) && !LOCKFILES.has(base); +} + +function importedBindings(source) { + const bindings = []; + for (const match of source.matchAll(JOINT_IMPORT_RE)) { + const clause = match[1].trim(); + const star = clause.match(/^\*\s+as\s+(\w+)$/); + if (star) { + bindings.push({ local: star[1], original: null, star: true }); + continue; + } + const named = clause.match(/\{([^}]*)\}/); + if (named) { + for (const part of named[1].split(',')) { + const name = part.trim().replace(/^type\s+/, ''); + if (!name) continue; + const alias = name.match(/^(\w+)\s+as\s+(\w+)$/); + if (alias) { + bindings.push({ local: alias[2], original: alias[1], star: false }); + } else { + const bare = name.split(/\s+/)[0]; + bindings.push({ local: bare, original: bare, star: false }); + } + } + } + const defaultImport = clause.match(/^(\w+)\s*(?:,|$)/); + if (defaultImport) { + bindings.push({ local: defaultImport[1], original: defaultImport[1], star: false }); + } + } + return bindings; +} + +// dia.Paper.Options -> dia.Paper: keep segments while they are all-lowercase +// namespaces; the first segment containing a capital ends the chain. Chains +// with no capitalized segment (util.breakText) are kept whole. +function collapseChain(segments) { + const kept = []; + for (const segment of segments) { + kept.push(segment); + if (/[A-Z]/.test(segment)) break; + } + return kept.join('.'); +} + +// Joint API symbols the variant's code uses, from its imports of Joint +// packages. Aliased bindings are recorded under their original names; star +// imports emit members without the local namespace prefix; bindings never +// referenced outside their import declaration are dropped, as are the +// jsx/env tooling bindings. Member chains collapse after the first +// capitalized segment (dia.Paper.Options -> dia.Paper). +export function extractUses(sources) { + const uses = new Set(); + for (const source of sources) { + const bindings = importedBindings(source); + if (bindings.length === 0) continue; + const body = source.replace(JOINT_IMPORT_RE, ''); + for (const { local, original, star } of bindings) { + if (TOOLING_BINDINGS.has(local) || TOOLING_BINDINGS.has(original ?? '')) continue; + const memberRe = new RegExp(`\\b${local}\\.(\\w+(?:\\.\\w+)*)`, 'g'); + let hasMember = false; + for (const member of body.matchAll(memberRe)) { + hasMember = true; + const chain = member[1].split('.'); + uses.add(collapseChain(star ? chain : [original, ...chain])); + } + if (!hasMember && !star && new RegExp(`\\b${local}\\b`).test(body)) { + uses.add(original); + } + } + } + return [...uses].sort(); +} + +const ONLINE_NOTE_RE = /^This demo is also available online at /; + +// Tags must start with a letter or '/', so a prose '<' (e.g. "a < b") +// never swallows text. [^>] matches newlines, so a single pass over a +// joined block also removes tags that span lines (the StackBlitz badge). +const HTML_TAG_RE = /<\/?[a-zA-Z][^>]*>/g; + +function cleanInline(text) { + return text + .replace(/!\[[^\]]*\]\([^)]*\)/g, '') + .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(HTML_TAG_RE, '') + .trim(); +} + +export function parseReadme(markdown) { + const lines = markdown.split('\n'); + let title = ''; + let seenTitle = false; + const summaryLines = []; + for (const line of lines) { + if (!seenTitle) { + const heading = line.match(/^#\s+(.*)$/); + if (heading) { + title = cleanInline(heading[1]); + seenTitle = true; + } + continue; + } + if (/^##\s/.test(line)) break; + summaryLines.push(line); + } + const summary = summaryLines + .join('\n') + .replace(HTML_TAG_RE, '') + .split('\n') + .map(cleanInline) + .filter((line) => !ONLINE_NOTE_RE.test(line)) + .join('\n') + .replace(/\n{3,}/g, '\n\n') + .trim(); + return { title, summary }; +} + +function yaml(value) { + return JSON.stringify(value); +} + +// Verbatim from joint-mcp#39 — do not rephrase. Rendered as a single line +// directly after the **Packages:** line of an imperative-react variant. +const IMPERATIVE_REACT_ADVISORY = '⚠ **API note:** this variant uses the imperative `@joint/plus` API inside React, not the declarative React package. For new React apps call `get_started(framework="react")` and prefer `@joint/react` — or `@joint/react-plus` if the project has a JointJS+ license (this demo relies on JointJS+ features). Adapt this demo\'s ideas, not its integration pattern.'; + +// A react variant that pulls in an imperative Joint package instead of a +// declarative @joint/react* one earns the advisory. +function needsAdvisory(variantDir, packages) { + return canonicalVariant(variantDir) === 'react' + && !packages.some((name) => name.startsWith('@joint/react')); +} + +// One Manifest per demo. `variants` is pre-grouped and sorted by the walker; +// each carries { variantDir, packageJson, files, sources }. Title/summary +// come from the demo-root README only; edition is keyed off the union of +// every variant's joint packages (title fallback covers CDN-only demos). +export function buildDemoManifest({ demoName, version, readme, keywords, variants }) { + const { title, summary } = parseReadme(readme); + const allPackages = [...new Set(variants.flatMap((variant) => jointPackages(variant.packageJson)))].sort(); + const demoEdition = edition(allPackages, title); + const frontmatter = [ + '---', + `demo: ${yaml(demoName)}`, + `version: ${yaml(version)}`, + `edition: ${yaml(demoEdition)}`, + `title: ${yaml(title)}`, + '---', + ].join('\n'); + // Every element is separated from the next by exactly one blank line. + const sections = [ + frontmatter, + `# ${title}`, + summary, + `**Edition:** ${demoEdition}`, + ...(keywords?.length ? [`**Keywords:** ${keywords.join(', ')}`] : []), + ]; + const indexEntries = []; + for (const { variantDir, packageJson, files, sources } of variants) { + const packages = jointPackages(packageJson); + const uses = extractUses(sources); + // demo_id/Packages/(advisory/)Uses are one consecutive block. + const block = [ + `**demo_id:** version-${version}/${demoName}/${variantDir}`, + `**Packages:** ${packages.join(', ') || 'none'}`, + ...(needsAdvisory(variantDir, packages) ? [IMPERATIVE_REACT_ADVISORY] : []), + `**Uses:** ${uses.join(', ') || 'none'}`, + ].join('\n'); + sections.push( + `## Variant: ${variantDir}`, + block, + '### Source files', + files.filter(isSourceFile).map((file) => `- ${file}`).join('\n'), + ); + indexEntries.push({ demo: demoName, variant_dir: variantDir, variant: canonicalVariant(variantDir) }); + } + return { + path: `manifests/version-${version}/${demoName}.md`, + content: sections.join('\n\n') + '\n', + indexEntries, + }; +} + +// Slim bare-array index; the Demo Snapshot version lives in the filename. +export function renderIndex(entries) { + return JSON.stringify(entries, null, 2) + '\n'; +} diff --git a/.github/scripts/sync.mjs b/.github/scripts/sync.mjs new file mode 100644 index 00000000..154c498b --- /dev/null +++ b/.github/scripts/sync.mjs @@ -0,0 +1,99 @@ +#!/usr/bin/env node + +// Syncs one Demo Snapshot version and its Demo Manifests to the demos R2 +// bucket over the rclone remote `cf-r2` (dev bucket by default, --prod for +// production). Sync-only: it never builds manifests — run +// `npm run manifests:build -- --version X.Y` first. Every destination is +// version-scoped so other versions' objects in the bucket are never touched. +// +// Usage: npm run sync -- --version 4.3 [--allow-dirty] +// npm run sync:prod -- --version 4.3 + +import { parseArgs } from 'node:util'; +import { execFileSync, spawnSync } from 'node:child_process'; +import { existsSync, readdirSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export const USAGE = 'Usage: npm run sync -- --version X.Y [--allow-dirty] (sync:prod for production)'; + +// Verbatim from joint-mcp#30 (grill decision 2026-07-17): every top-level +// non-hidden directory is a demo, demo content lives at depth >= 2 inside it, +// anything new at the root fails closed. First match wins. +export const SNAPSHOT_FILTERS = [ + '--filter', '- .DS_Store', + '--filter', '- node_modules/**', + '--filter', '- _site/**', + '--filter', '- /.*/**', + '--filter', '+ /*/*/**', + '--filter', '- *', +]; + +export function parseSyncArgs(args) { + let values; + try { + ({ values } = parseArgs({ + args, + options: { + 'version': { type: 'string' }, + 'prod': { type: 'boolean', default: false }, + 'allow-dirty': { type: 'boolean', default: false }, + }, + })); + } catch { + return null; + } + if (!values.version || !/^\d+\.\d+$/.test(values.version)) { + return null; + } + return { version: values.version, prod: values.prod, allowDirty: values['allow-dirty'] }; +} + +export function planCommands({ version, bucket }) { + return [ + ['sync', './', `cf-r2:${bucket}/versioned_demos/version-${version}`, ...SNAPSHOT_FILTERS], + ['sync', `.manifests/manifests/version-${version}`, `cf-r2:${bucket}/manifests/version-${version}`], + ['copyto', `.manifests/manifests-index/version-${version}.json`, `cf-r2:${bucket}/manifests-index/version-${version}.json`], + ]; +} + +const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; +if (isMain) { + const parsed = parseSyncArgs(process.argv.slice(2)); + if (!parsed) { + console.warn(USAGE); + process.exit(1); + } + const { version, prod, allowDirty } = parsed; + const ROOT = resolve(import.meta.dirname, '..', '..'); + const bucket = prod ? 'jointjs-demos' : 'jointjs-demos-dev'; + + // Preflight 1: manifests:build output for this version must exist (sync-only script). + const manifestsDir = join(ROOT, '.manifests', 'manifests', `version-${version}`); + const indexFile = join(ROOT, '.manifests', 'manifests-index', `version-${version}.json`); + if (!existsSync(manifestsDir) || readdirSync(manifestsDir).length === 0 || !existsSync(indexFile)) { + console.warn(`No manifest build output for version ${version} in .manifests/.`); + console.warn(`run: npm run manifests:build -- --version ${version}`); + process.exit(1); + } + + // Preflight 2: clean tree. The snapshot filters admit every file at depth >= 2 + // inside demo directories, so untracked local files would upload into the + // Demo Snapshot. A fresh clone passes trivially; ignored files (.manifests/, + // node_modules/) don't trip porcelain. + const dirty = execFileSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf8' }).trim(); + if (dirty && !allowDirty) { + console.warn('Working tree is not clean — untracked files inside demo directories would upload into the Demo Snapshot.'); + console.warn('Sync from a fresh clone, or pass --allow-dirty to override.'); + process.exit(1); + } + + for (const args of planCommands({ version, bucket })) { + console.log(`rclone ${args.join(' ')}`); + const { status } = spawnSync('rclone', args, { cwd: ROOT, stdio: 'inherit' }); + if (status !== 0) { + process.exit(status ?? 1); + } + } + console.log(`Synced Demo Snapshot + Manifests version ${version} to ${bucket}`); +} diff --git a/.github/scripts/sync.test.mjs b/.github/scripts/sync.test.mjs new file mode 100644 index 00000000..1552891f --- /dev/null +++ b/.github/scripts/sync.test.mjs @@ -0,0 +1,65 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { parseSyncArgs, planCommands, SNAPSHOT_FILTERS } from './sync.mjs'; + +test('parseSyncArgs parses a plain dev sync', () => { + assert.deepEqual(parseSyncArgs(['--version', '4.3']), { + version: '4.3', + prod: false, + allowDirty: false, + }); +}); + +test('parseSyncArgs parses --prod and --allow-dirty', () => { + assert.deepEqual(parseSyncArgs(['--prod', '--version', '4.3', '--allow-dirty']), { + version: '4.3', + prod: true, + allowDirty: true, + }); +}); + +test('parseSyncArgs rejects bad input', () => { + assert.equal(parseSyncArgs([]), null); // version required + assert.equal(parseSyncArgs(['--version', '4']), null); // not X.Y + assert.equal(parseSyncArgs(['--version', 'v4.3']), null); // not X.Y + assert.equal(parseSyncArgs(['--version', '4.3', '--nope']), null); // unknown flag +}); + +test('planCommands emits the three version-scoped rclone commands', () => { + assert.deepEqual(planCommands({ version: '4.3', bucket: 'jointjs-demos-dev' }), [ + [ + 'sync', './', 'cf-r2:jointjs-demos-dev/versioned_demos/version-4.3', + '--filter', '- .DS_Store', + '--filter', '- node_modules/**', + '--filter', '- _site/**', + '--filter', '- /.*/**', + '--filter', '+ /*/*/**', + '--filter', '- *', + ], + [ + 'sync', '.manifests/manifests/version-4.3', + 'cf-r2:jointjs-demos-dev/manifests/version-4.3', + ], + [ + 'copyto', '.manifests/manifests-index/version-4.3.json', + 'cf-r2:jointjs-demos-dev/manifests-index/version-4.3.json', + ], + ]); +}); + +test('planCommands targets the prod bucket when asked', () => { + const destinations = planCommands({ version: '4.3', bucket: 'jointjs-demos' }) + .map((args) => args.find((arg) => arg.startsWith('cf-r2:'))); + assert.deepEqual(destinations, [ + 'cf-r2:jointjs-demos/versioned_demos/version-4.3', + 'cf-r2:jointjs-demos/manifests/version-4.3', + 'cf-r2:jointjs-demos/manifests-index/version-4.3.json', + ]); +}); + +test('SNAPSHOT_FILTERS is the verbatim #30 ruleset', () => { + assert.deepEqual( + SNAPSHOT_FILTERS.filter((arg) => arg !== '--filter'), + ['- .DS_Store', '- node_modules/**', '- _site/**', '- /.*/**', '+ /*/*/**', '- *'], + ); +}); diff --git a/.gitignore b/.gitignore index 5d854db9..50c3e99f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build _site .angular .claude/settings.local.json +.manifests/ diff --git a/README.md b/README.md index 1261bf68..604b7b46 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,8 @@ The following scripts are then available from the repository root: | `lint` | `npm run lint` | Lint all JS/TS files using the root ESLint config | | `build` | `npm run build` | Build all demos into `_site/` (stops on first failure) | | `screenshot` | `npm run screenshot` | Capture screenshots for demos that don't have one yet | +| `manifests:build` | `npm run manifests:build -- --version X.Y` | Build Demo Manifests for the MCP demo search into `.manifests/` | +| `manifests:test` | `npm run manifests:test` | Run the manifest golden-fixture tests | > [!NOTE] > `build` require Bash. On Windows, run them from Git Bash or WSL. diff --git a/demo-keywords.json b/demo-keywords.json new file mode 100644 index 00000000..93ef533c --- /dev/null +++ b/demo-keywords.json @@ -0,0 +1,2031 @@ +{ + "absolute-port-layout-dynamic-port-sizes": [ + "Custom shapes", + "Content-driven shapes", + "Anchors" + ], + "abstract-syntax-tree": [ + "Automatic layout", + "Integration", + "Collapse & Expand", + "Custom shapes", + "Zoom & Pan", + "Export to PNG/JPEG", + "Export to SVG", + "Import/Export", + "Events", + "Graph API", + "code structure explorer", + "JavaScript Abstract Syntax Tree", + "source code analyzer" + ], + "activity-diagram": [ + "Anchors", + "Custom shapes", + "Constraints on movement", + "Collapse & Expand", + "Link labels", + "Embedding", + "workflow modeling", + "process flow", + "state transitions", + "process automation", + "flowchart", + "event-driven process" + ], + "ai-agent-builder": [ + "AI workflow builders", + "Zoom & Pan", + "Toolbar", + "Minimap", + "Property editor & viewer", + "Popups", + "Element palette", + "Undo/Redo" + ], + "ai-workflow-builder": [ + "AI workflow builders", + "Zoom & Pan", + "Toolbar", + "Minimap", + "Property editor & viewer", + "Popups", + "Element palette", + "Undo/Redo", + "Import/Export", + "Export to JSON", + "Export to PNG/JPEG", + "Link labels", + "Ports", + "Tooltips", + "Animation", + "Element tools", + "Content-driven shapes", + "Copy & Paste", + "Keyboard", + "Selection", + "Validation", + "Simulation", + "Graph API", + "Integration", + "Menus", + "Responsive", + "Guidelines", + "Save/Load", + "Shape transformation", + "Text content", + "Context Toolbar" + ], + "alignment-and-distance-based-position-guides": [ + "Element palette", + "Built-in shapes", + "Highlighters", + "Vectorizer", + "Geometry", + "Measurement", + "Guidelines" + ], + "angles": [ + "Built-in shapes", + "Custom shapes", + "Link tools", + "Measurement", + "Guidelines", + "Shape transformation" + ], + "animated-process-flow-diagram": [ + "Selection", + "Built-in shapes", + "CSS styling", + "Graph API", + "Highlighters", + "Routers", + "Animation", + "flowchart", + "process diagram" + ], + "bandwidth": [ + "Constraints on movement", + "Vectorizer", + "Geometry", + "CSS styling", + "Custom shapes", + "Events", + "Adjustable interactivity", + "network bandwidth", + "data traffic control", + "network performance", + "bandwidth allocation", + "internet traffic management", + "ISP management" + ], + "bootstrap-tabs": [ + "Integration", + "tabbed interface", + "framework integration" + ], + "bpmn-camunda-integration": [ + "BPMN", + "workflow automation", + "process orchestration", + "business process automation", + "workflow engine", + "process execution" + ], + "bpmn-editor": [ + "BPMN", + "Element palette", + "Print", + "Import/Export", + "Export to JSON", + "Selection", + "Tooltips", + "Built-in shapes", + "Element tools", + "Link tools", + "Shape transformation", + "Zoom & Pan", + "Toolbar", + "Keyboard", + "Undo/Redo", + "Validation", + "Events", + "Property editor & viewer", + "Embedding", + "business process model and notation", + "business process modeling", + "process diagram", + "workflow management", + "business process notation", + "process automation" + ], + "bpmn-pools-swimlanes-milestones": [ + "BPMN", + "Built-in shapes", + "Element palette", + "Text content", + "Highlighters", + "Keyboard", + "Shape transformation", + "Undo/Redo", + "Toolbar", + "Constraints on movement", + "Import/Export", + "business process model and notation", + "business process modeling", + "process diagram", + "workflow management", + "business process notation", + "process automation" + ], + "cables": [ + "Electronic design", + "Custom shapes", + "Element palette", + "Ports", + "Events", + "Tooltips", + "Element tools", + "Highlighters", + "CSS styling", + "Link tools", + "Animation", + "Embedding", + "Connectors", + "Anchors", + "cable management", + "electrical wiring", + "cable routing", + "industrial wiring", + "power distribution", + "network cabling" + ], + "callouts": [ + "Element tools", + "Geometry", + "Embedding", + "Built-in shapes", + "bubbles" + ], + "change-element-text-alignment-in-inspector": [ + "Element palette", + "Property editor & viewer", + "Undo/Redo", + "Toolbar", + "Selection", + "Events", + "Custom shapes" + ], + "charts": [ + "Built-in shapes", + "Tooltips", + "Events", + "data visualization", + "graphs", + "line chart", + "bar chart", + "pie chart", + "area chart" + ], + "chatbot": [ + "AI workflow builders", + "Integration", + "Element palette", + "Tooltips", + "Selection", + "Keyboard", + "Export to PNG/JPEG", + "Import/Export", + "Export to JSON", + "Custom shapes", + "Virtual rendering", + "Element tools", + "Link tools", + "Zoom & Pan", + "Toolbar", + "Undo/Redo", + "Events", + "Property editor & viewer", + "Ports", + "conversational bot", + "virtual assistant", + "automated customer support", + "dialogue system", + "messaging bot", + "interactive agent" + ], + "chatgpt-timeline": [ + "Timelines", + "Custom shapes", + "Automatic layout", + "Vectorizer", + "Connectors", + "Responsive", + "milestones" + ], + "chess": [ + "Integration", + "Events", + "Animation", + "Geometry", + "Custom shapes" + ], + "circular-layout": [ + "Automatic layout", + "Built-in shapes", + "Geometry", + "circle layout", + "circular diagram", + "cyclic relationship", + "radial layout", + "network visualization", + "cycle chart" + ], + "clipboard-api-integration": [ + "Selection", + "Element palette", + "Built-in shapes", + "Link tools", + "Context Toolbar", + "flowchart" + ], + "collapsible-minimap": [ + "Built-in shapes", + "Zoom & Pan", + "Tooltips", + "Toolbar", + "Custom shapes", + "navigation" + ], + "comment-view": [ + "Adjustable interactivity", + "Element tools", + "Link tools", + "Export to PNG/JPEG", + "Custom shapes", + "Built-in shapes", + "Import/Export", + "Zoom & Pan", + "Text content", + "Events", + "Anchors", + "Toggle visibility" + ], + "confirmation-dialogs": [ + "Popups", + "Undo/Redo", + "Events", + "Link tools" + ], + "connecting-nodes-by-dragging-and-dropping": [ + "Drag & Drop", + "Element palette", + "Undo/Redo", + "Highlighters", + "Popups", + "Geometry", + "flowchart" + ], + "connector-arrows": [ + "Highlighters", + "Zoom & Pan", + "Responsive", + "Animation", + "Events" + ], + "constellation": [ + "Animation", + "External images", + "Custom shapes", + "Zoom & Pan", + "Graph API", + "Events", + "constellation mapping", + "star chart", + "astronomy", + "celestial navigation", + "night sky map", + "star clusters" + ], + "container-collapse-expand": [ + "Custom shapes", + "Events", + "Highlighters" + ], + "container-distance-guides": [ + "alignment guides", + "smart guides", + "snapping", + "spacing guides", + "positioning guides" + ], + "container-layout": [ + "Automatic layout", + "Drag & Drop", + "Events", + "Embedding", + "Graph API", + "Custom shapes", + "graph layout", + "network visualization", + "data visualization", + "organizational chart", + "hierarchy diagram", + "flowchart" + ], + "content-driven-shapes": [ + "Custom shapes", + "auto-resize", + "dynamic sizing", + "self-sizing elements" + ], + "context-toolbar": [ + "Menus", + "Built-in shapes", + "Events" + ], + "control-tool": [ + "Element tools", + "Built-in shapes", + "reshape element", + "transform handle", + "geometry editing" + ], + "convex-hull-algorithm": [ + "Built-in shapes", + "Geometry", + "Vectorizer", + "Events" + ], + "copy-paste": [ + "Element palette", + "Selection", + "Built-in shapes", + "Events", + "Element tools", + "Shape transformation" + ], + "corporate-organizational-chart": [ + "Org charts", + "Element palette", + "Zoom & Pan", + "Events", + "Popups", + "Minimap", + "Highlighters", + "Automatic layout", + "Element tools", + "Custom properties", + "orgchart", + "org chart", + "company hierarchy", + "corporate structure", + "management chart", + "business organization" + ], + "counters": [ + "Custom shapes", + "Performance", + "Events", + "Geometry", + "Vectorizer", + "Content-driven shapes", + "Simulation", + "Collapse & Expand", + "Virtual rendering" + ], + "curve-connector": [ + "Connectors", + "Connection points", + "Built-in shapes" + ], + "curves": [ + "Highlighters", + "Link tools", + "Geometry", + "Custom shapes", + "Element tools", + "Anchors" + ], + "data-mapping": [ + "Data modeling", + "Built-in shapes", + "Custom shapes", + "Link tools", + "Element tools", + "Shape transformation", + "Zoom & Pan", + "Toolbar", + "Undo/Redo", + "Validation", + "Routers", + "Anchors", + "Highlighters", + "Events", + "Menus", + "Popups", + "Property editor & viewer", + "Content-driven shapes", + "Collapse & Expand", + "Context Toolbar", + "data integration", + "ETL", + "data transformation", + "metadata mapping", + "database mapping", + "data schema mapping" + ], + "data-pipeline": [ + "ETL", + "data flow", + "data processing", + "DAG", + "node-based editor", + "orthogonal routing" + ], + "database": [ + "Data modeling", + "Property editor & viewer", + "Built-in shapes", + "Custom shapes", + "Routers", + "Highlighters", + "Zoom & Pan", + "Events", + "Link tools", + "Popups", + "Content-driven shapes", + "database diagram", + "entity-relationship diagram", + "ER diagram", + "database schema", + "relational database", + "tables and relationships" + ], + "decision-tree": [ + "Automatic layout", + "Vectorizer", + "Custom shapes", + "Graph API", + "Menus", + "Popups", + "flowchart", + "decision analysis", + "decision making", + "process flow", + "business logic", + "conditional branching" + ], + "decision-tree-analysis": [ + "Automatic layout", + "Custom shapes", + "Zoom & Pan", + "Tooltips", + "Graph API", + "Link labels", + "decision analysis", + "classification", + "machine learning", + "data mining", + "predictive modeling", + "flowchart" + ], + "diagram-generation-from-external-data": [ + "Automatic layout", + "Import/Export", + "Highlighters", + "Zoom & Pan", + "Built-in shapes", + "Anchors", + "REST API diagram", + "data visualization", + "tree diagram", + "data-driven diagram", + "data mapping", + "JSON-to-diagram" + ], + "diagram-index": [ + "Integration", + "Built-in shapes", + "Highlighters", + "Events", + "Zoom & Pan", + "Selection", + "flowchart" + ], + "dialog-generator": [ + "Text content", + "Custom shapes", + "Content-driven shapes", + "Selection", + "Vectorizer", + "Validation", + "Events", + "Popups", + "Ports", + "Tooltips", + "Graph API" + ], + "different-views-of-the-same-graph": [ + "Built-in shapes", + "Toggle visibility", + "multiple views", + "filtered view", + "partial rendering" + ], + "directed-graph-layout": [ + "Automatic layout", + "Integration", + "Link labels", + "Custom shapes", + "Highlighters", + "layout directed graph", + "dependency graph", + "flowchart", + "network visualization", + "hierarchical layout", + "node-link diagram" + ], + "discrete-event-system-specification": [ + "Built-in shapes", + "Embedding", + "Highlighters", + "Validation", + "Adjustable interactivity", + "Events", + "Ports", + "discrete-event abstraction", + "discrete event simulation", + "event-driven modeling", + "process simulation", + "system dynamics", + "queuing systems" + ], + "distances": [ + "Built-in shapes", + "Custom shapes", + "Shape transformation", + "Measurement", + "Guidelines", + "distance calculation" + ], + "drop-image-as-shape": [ + "External images", + "Built-in shapes", + "Events" + ], + "drop-stencil-element-as-shape-icon": [ + "External images", + "Element palette", + "Element tools", + "Highlighters", + "Built-in shapes", + "Events", + "Validation" + ], + "dwdm-circuit": [ + "Simulation", + "Collapse & Expand", + "Automatic layout", + "Highlighters", + "Anchors", + "Custom shapes", + "Events", + "optical networking", + "fiber optic communication", + "telecom networking", + "wavelength multiplexing", + "optical transport network", + "network infrastructure" + ], + "dynamic-font-size": [ + "Element tools", + "Built-in shapes", + "responsive text", + "adaptive font" + ], + "dynamic-port-list": [ + "Ports", + "Content-driven shapes", + "Link tools", + "Custom shapes", + "Events", + "Validation", + "Routers" + ], + "dynamic-status-icons": [ + "Simulation", + "status indicators", + "state badges", + "condition icons" + ], + "dynamic-stencil": [ + "Element palette", + "Graph API", + "Export to PNG/JPEG", + "Built-in shapes", + "Element tools", + "Selection", + "Events" + ], + "dynamic-tooltip-content": [ + "Tooltips", + "Ports", + "Built-in shapes", + "External images", + "Validation", + "Link labels", + "Events", + "flowchart", + "process diagram", + "workflow visualization", + "business process mapping", + "decision flow", + "task sequence" + ], + "electric-generator": [ + "SCADA & HMI", + "Energy networks", + "Highlighters", + "Custom shapes", + "CSS styling", + "Simulation", + "power generation", + "electric power plant", + "generator system", + "energy production", + "electrical engineering", + "industrial equipment" + ], + "element-connect-tool": [ + "Element tools", + "Built-in shapes", + "Anchors", + "Geometry" + ], + "element-grouping": [ + "Element palette", + "Selection", + "Embedding", + "Built-in shapes", + "Adjustable interactivity" + ], + "element-neighborhood-dialog-window": [ + "Zoom & Pan", + "Popups", + "Graph API", + "Automatic layout", + "Built-in shapes", + "Minimap", + "Menus", + "tree graph", + "tree diagram" + ], + "element-palette-tooltips": [ + "CSS styling", + "hover hints", + "informational popups" + ], + "element-port-and-link-label-markup": [ + "Custom shapes", + "port styling", + "link label styling" + ], + "element-tags-and-badges": [ + "Content-driven shapes", + "Text content", + "Highlighters", + "Element tools", + "shapes" + ], + "elk-layout": [ + "Automatic layout", + "Integration", + "Embedding", + "Zoom & Pan", + "Custom shapes", + "Link labels" + ], + "elk-layout-with-ports-and-clusters": [ + "Automatic layout", + "Integration", + "Embedding", + "Zoom & Pan", + "Custom shapes", + "graph visualization", + "hierarchical graph", + "flowchart", + "directed acyclic graph", + "software architecture diagram", + "dependency graph" + ], + "enitity-relationship-diagram": [ + "Data modeling", + "Built-in shapes", + "Highlighters", + "Connection points", + "Link labels", + "ERD", + "database modeling", + "database schema", + "relational database", + "entity relationship model", + "database design" + ], + "external-svg-images": [ + "External images", + "Custom shapes", + "Built-in shapes", + "Highlighters", + "Connection points", + "Electrical diagram", + "wiring diagram", + "electrical circuit", + "circuit design", + "electrical schematics", + "residential wiring" + ], + "fault-tree-analysis": [ + "Custom shapes", + "Element tools", + "Events", + "Automatic layout", + "Graph API", + "Collapse & Expand", + "FTA", + "failure analysis", + "risk assessment", + "reliability engineering", + "safety engineering" + ], + "fills": [ + "Built-in shapes", + "gradients", + "fill styles", + "pattern fill" + ], + "find-all-cells-between-2-elements": [ + "Animation", + "Automatic layout", + "Selection", + "CSS styling", + "Built-in shapes" + ], + "finite-object-pool": [ + "Built-in shapes", + "Element palette", + "Element tools" + ], + "finite-state-machines": [ + "Events", + "Built-in shapes", + "Connectors", + "FSA", + "state machine", + "state diagram", + "automata", + "computational model", + "state transitions" + ], + "fishbone": [ + "Automatic layout", + "Undo/Redo", + "Property editor & viewer", + "CSS styling", + "Zoom & Pan", + "Toolbar", + "Popups", + "Custom shapes", + "Highlighters" + ], + "fixed-connection-points": [ + "Highlighters", + "Geometry", + "dark theme", + "dark mode" + ], + "flowchart": [ + "Built-in shapes", + "CSS styling", + "Highlighters", + "Link tools", + "Connectors", + "Routers", + "Link labels", + "Events", + "Anchors", + "Responsive", + "process flow", + "workflow diagram", + "process mapping", + "business process", + "decision tree", + "step-by-step process" + ], + "font-awesome": [ + "Integration", + "Export to PNG/JPEG", + "Highlighters", + "Element tools", + "Built-in shapes", + "Link labels", + "Popups", + "Shape transformation", + "icon set", + "graphical icons", + "vector icons", + "UI icons", + "web icons", + "iconography" + ], + "force-directed-interaction": [ + "Drag & Drop", + "Automatic layout", + "Custom shapes" + ], + "force-directed-layout": [ + "Automatic layout", + "Drag & Drop", + "Built-in shapes", + "ERD", + "entity relationship diagram" + ], + "force-directed-radial-force": [ + "Automatic layout", + "Drag & Drop", + "Built-in shapes", + "force directed layout" + ], + "framework-element-view": [ + "Integration", + "Custom shapes", + "Angular components" + ], + "genogram": [ + "Org charts", + "Automatic layout", + "Custom shapes", + "Highlighters", + "Graph API", + "Responsive", + "Toggle visibility", + "Family relationship diagram", + "Pedigree chart" + ], + "hexagonal-grid": [ + "Integration", + "Element tools", + "Highlighters", + "Vectorizer", + "Constraints on movement", + "Built-in shapes", + "Events", + "hex map", + "strategy game map", + "tactical game board", + "board game layout", + "game design grid", + "hexagonal tiling" + ], + "hierarchical-diagrams": [ + "Built-in shapes", + "Events", + "Highlighters", + "Automatic layout", + "Validation" + ], + "highlighter-view-update-attribute": [ + "Highlighters", + "dynamic styling", + "data-driven styling", + "conditional formatting" + ], + "hover-element-connect-tool": [ + "Element tools", + "link creation", + "drag to connect" + ], + "hover-link-connect-tool": [ + "Link tools", + "link-to-link connection", + "drag to connect" + ], + "html-form-ports": [ + "Data modeling", + "HTML shapes", + "Custom shapes", + "Graph API" + ], + "icons": [ + "Custom shapes", + "External images", + "Events", + "icon library", + "symbol set", + "user interface symbols", + "pictograms", + "graphical symbols", + "UI icons" + ], + "image-processor": [ + "Property editor & viewer", + "Element palette", + "Toolbar", + "CSS styling", + "Connection points", + "Copy & Paste", + "Custom shapes", + "Drag & Drop", + "Export to JSON", + "Minimap", + "Ports", + "Save/Load", + "Selection", + "Undo/Redo", + "Zoom & Pan", + "Context Toolbar", + "computer vision", + "digital image analysis", + "image filtering", + "photo editing", + "pixel manipulation", + "image transformation" + ], + "infinite-paper-vs-sheets": [ + "Zoom & Pan", + "Toolbar", + "Grid", + "Element palette", + "Built-in shapes", + "flowchart" + ], + "inspector-dynamic-element-type": [ + "Events", + "Element palette", + "Property editor & viewer", + "Undo/Redo" + ], + "inspector-for-selection": [ + "Element palette", + "Property editor & viewer", + "Undo/Redo", + "CSS styling" + ], + "isometric-diagram": [ + "Custom shapes", + "Constraints on movement", + "Geometry", + "Vectorizer", + "Shape transformation", + "3D diagram", + "architectural drawing", + "building plan", + "construction design", + "spatial layout", + "engineering visualization" + ], + "json-visualizer": [ + "Zoom & Pan", + "Automatic layout", + "Built-in shapes", + "Import/Export", + "JSON viewer", + "data visualization", + "structured data", + "hierarchical data", + "data explorer", + "nested data" + ], + "kanban": [ + "Project management", + "Automatic layout", + "Import/Export", + "Custom shapes", + "Text content", + "Drag & Drop", + "Link tools", + "Element tools", + "Selection", + "Undo/Redo", + "Validation", + "Highlighters", + "Events", + "task management", + "workflow visualization", + "scrum board", + "agile project management", + "to-do list", + "project tracking" + ], + "kitchen-sink": [ + "Integration", + "Element palette", + "Import/Export", + "Export to SVG", + "Export to PNG/JPEG", + "Print", + "Automatic layout", + "Selection", + "Minimap", + "Measurement", + "Built-in shapes", + "Custom shapes", + "Element tools", + "Link tools", + "Shape transformation", + "Zoom & Pan", + "Toolbar", + "Keyboard", + "Undo/Redo", + "Tooltips", + "Connection points", + "Events", + "Popups", + "Menus", + "Property editor & viewer", + "Ports", + "Copy & Paste", + "Link labels", + "Guidelines", + "generic diagramming", + "flowchart", + "process mapping", + "generic graph", + "workflow visualization", + "multi-domain diagrams" + ], + "libavoid-standalone-link-routing": [ + "Integration", + "Ports", + "Anchors", + "Routers", + "Automatic layout", + "Shape transformation", + "Link tools", + "Element tools", + "Events" + ], + "link-connect-tool": [ + "Link tools", + "Built-in shapes", + "Anchors" + ], + "link-teleports": [ + "Connectors", + "Link tools", + "Zoom & Pan" + ], + "links": [ + "Built-in shapes", + "Animation", + "Link labels" + ], + "list-of-links-in-inspector": [ + "Toolbar", + "Undo/Redo", + "Element palette", + "Selection", + "Property editor & viewer", + "Tooltips", + "Highlighters", + "Menus", + "Events", + "Custom properties" + ], + "logic-circuits": [ + "Electronic design", + "Routers", + "Validation", + "Simulation", + "Events", + "Custom shapes", + "Animation", + "digital logic", + "combinational logic", + "sequential logic", + "boolean algebra", + "logic gates", + "AND gate" + ], + "marey-chart": [ + "Timelines", + "Zoom & Pan", + "Link tools", + "Vectorizer", + "Geometry", + "CSS styling", + "Events", + "train schedules", + "railway timetables", + "transportation planning", + "traffic flow", + "time-distance diagram", + "transit visualization" + ], + "marketing-automation": [ + "AI workflow builders", + "Zoom & Pan", + "Automatic layout", + "Custom shapes", + "Toolbar", + "Minimap", + "Property editor & viewer", + "Popups", + "Element palette", + "Undo/Redo", + "Animation", + "Simulation" + ], + "microservices-architecture": [ + "Automatic layout", + "Connection points", + "Element palette", + "Embedding", + "Guidelines", + "Keyboard", + "Undo/Redo", + "Minimap", + "Routers", + "Selection", + "Shape transformation", + "Text content", + "Zoom & Pan" + ], + "microsoft-adaptive-cards": [ + "HTML shapes", + "Integration", + "Custom shapes", + "Collapse & Expand", + "Content-driven shapes" + ], + "mind-map": [ + "Toolbar", + "Automatic layout", + "Zoom & Pan", + "Keyboard", + "Drag & Drop", + "Selection", + "Undo/Redo", + "Text content", + "Content-driven shapes", + "Graph API", + "Highlighters", + "Popups", + "External images", + "Custom shapes", + "Anchors", + "Connectors", + "mind map", + "brainstorming", + "idea mapping", + "concept mapping", + "visual thinking", + "information organization" + ], + "mix-bus": [ + "Link-to-link", + "Anchors", + "Connection points", + "Events", + "Graph API", + "Embedding" + ], + "move-elements-via-keyboard": [ + "Undo/Redo", + "Element palette", + "Selection", + "Events", + "Built-in shapes", + "Toolbar" + ], + "msagl-layout": [ + "Adjustable interactivity", + "Animation", + "Custom shapes", + "Link labels", + "Integration" + ], + "multidirectional-tree": [ + "Automatic layout", + "Built-in shapes", + "Selection", + "Zoom & Pan", + "Events", + "Animation", + "tree diagram", + "hierarchical graph", + "organizational chart", + "family tree", + "genealogy", + "decision tree" + ], + "nodejs-milestones-timeline": [ + "Timelines", + "Custom shapes", + "Adjustable interactivity", + "Events", + "Constraints on movement", + "Responsive", + "dark theme", + "dark mode" + ], + "optional-ports": [ + "Built-in shapes", + "Property editor & viewer", + "toggle ports", + "port management", + "dynamic ports" + ], + "organizational-chart": [ + "Org charts", + "Automatic layout", + "External images", + "Custom shapes", + "Drag & Drop", + "Events", + "Popups", + "Property editor & viewer", + "orgchart", + "org chart", + "company hierarchy", + "corporate structure", + "management chart", + "business organization" + ], + "paper-attributes": [ + "Built-in shapes", + "Zoom & Pan", + "Vectorizer", + "Events" + ], + "parametric-diagram": [ + "UML & software modeling", + "Adjustable interactivity", + "Automatic layout", + "Custom shapes", + "Constraints on movement", + "Element palette", + "Embedding", + "Events", + "Export to PNG/JPEG", + "Highlighters", + "Text content", + "Ports", + "Toolbar", + "systems modeling language", + "engineering design", + "CAD modeling", + "technical drawing", + "geometric constraints", + "parametric modeling" + ], + "pdf-export": [ + "Import/Export", + "Export to PDF", + "Export to PNG/JPEG", + "Export to SVG", + "Export to JSON", + "Zoom & Pan", + "Toolbar", + "Integration", + "Graph API", + "Events" + ], + "pert-chart": [ + "Timelines", + "Project management", + "Built-in shapes", + "Toolbar", + "project scheduling", + "task dependencies", + "critical path method", + "activity on node", + "project planning", + "timeline visualization" + ], + "petri-nets": [ + "Animation", + "Simulation", + "Custom shapes", + "workflow modeling", + "process flow", + "discrete event systems", + "concurrency modeling", + "system state transitions", + "process automation" + ], + "planogram": [ + "Element palette", + "Custom shapes", + "Validation", + "Events", + "Embedding", + "Shape transformation", + "Element tools", + "Selection", + "Constraints on movement", + "Grid", + "retail layout", + "shelf layout", + "merchandise display", + "retail planning", + "store shelf arrangement", + "product placement" + ], + "port-drag-drop": [ + "Element palette", + "Events", + "Built-in shapes", + "Highlighters", + "Element tools" + ], + "port-inspector": [ + "Property editor & viewer", + "Custom shapes", + "Events" + ], + "port-reordering-tool": [ + "Element tools", + "Built-in shapes", + "Geometry", + "Vectorizer" + ], + "presentation-mode": [ + "Zoom & Pan", + "Property editor & viewer", + "Selection", + "Highlighters", + "Events", + "Custom properties", + "Animation" + ], + "preserve-spaces-in-text-wrap": [ + "Text content", + "text wrapping", + "whitespace handling", + "text layout" + ], + "puzzle": [ + "Custom shapes", + "Events", + "Vectorizer", + "Animation", + "External images", + "Highlighters", + "Geometry" + ], + "rectangular-layout": [ + "Automatic layout", + "Built-in shapes", + "Geometry" + ], + "resize-control-tool": [ + "Element tools", + "Built-in shapes", + "Shape transformation" + ], + "rich-text-editor": [ + "Text content", + "Zoom & Pan", + "Content-driven shapes", + "Toolbar", + "Selection", + "Events", + "text formatting", + "word processor", + "document editing", + "content creation", + "typing", + "text styling" + ], + "roi-calculator": [ + "Custom shapes", + "HTML shapes", + "Embedding", + "Events", + "return on investment", + "financial analysis", + "investment planning", + "cost-benefit analysis", + "profitability", + "business finance" + ], + "rough-shapes": [ + "Geometry", + "Vectorizer", + "Element tools", + "Link tools", + "Events", + "Custom shapes", + "Adjustable interactivity", + "diagram sketch" + ], + "sankey-diagram": [ + "SCADA & HMI", + "Energy networks", + "Anchors", + "Custom shapes", + "Drag & Drop", + "Tooltips", + "Graph API", + "Automatic layout", + "flow diagram", + "stream graph", + "user flow", + "energy flow", + "material flow", + "resource allocation" + ], + "saving-and-loading-using-file-system-access-api": [ + "Export to JSON", + "Toolbar", + "Save/Load", + "Element palette", + "Undo/Redo", + "file manager" + ], + "scada": [ + "SCADA & HMI", + "Simulation", + "Animation", + "CSS styling", + "Custom properties", + "Custom shapes", + "Highlighters", + "Link-to-link", + "HTML shapes", + "Ports", + "Toolbar", + "HMI", + "human-machine interface", + "industrial automation", + "process monitoring", + "industrial control systems", + "P&ID" + ], + "scale-option-for-tools": [ + "Element tools", + "tool scaling", + "zoom-independent tools", + "tool size" + ], + "scale-svgmarker": [ + "arrowhead sizing", + "marker scaling", + "proportional scaling", + "stroke width", + "line thickness" + ], + "searchable-sitemap": [ + "Automatic layout", + "Virtual rendering", + "Collapse & Expand", + "Custom shapes", + "Zoom & Pan", + "External images", + "Highlighters", + "Embedding" + ], + "selection-alignment": [ + "Element palette", + "Undo/Redo", + "Built-in shapes", + "Events", + "Vectorizer", + "Menus", + "Guidelines" + ], + "sequence-diagram": [ + "UML & software modeling", + "Link-to-link", + "Link labels", + "Link tools", + "Element tools", + "Highlighters", + "Custom shapes", + "Embedding", + "Events", + "Constraints on movement", + "message flow", + "object interaction", + "process flow", + "software development", + "interaction diagram", + "communication diagram" + ], + "serpentine-layout": [ + "Automatic layout", + "Built-in shapes", + "Responsive", + "serpentine process", + "linear manufacturing", + "assembly line flow", + "sequential production", + "process flowchart", + "industrial workflow" + ], + "shape-builder": [ + "Anchors", + "Highlighters", + "Content-driven shapes", + "Custom shapes", + "Custom properties", + "Element palette", + "HTML shapes", + "Keyboard", + "Popups", + "Property editor & viewer", + "Ports" + ], + "shapes-drawing": [ + "Events", + "Adjustable interactivity", + "Built-in shapes", + "Geometry", + "Vectorizer" + ], + "sheet-cutting": [ + "Shape transformation", + "Toolbar", + "Validation", + "Measurement", + "Highlighters", + "Guidelines", + "Grid", + "Geometry", + "Custom shapes", + "Element palette", + "Constraints on movement", + "Print", + "sheet metal fabrication", + "metal processing", + "material optimization", + "cutting layout", + "cutting pattern", + "manufacturing" + ], + "shortest-path-algorithm": [ + "Animation", + "CSS styling", + "Adjustable interactivity", + "Graph API", + "Automatic layout", + "Built-in shapes", + "Link tools", + "Element tools", + "Validation", + "Highlighters", + "Events", + "graph algorithm", + "Dijkstra" + ], + "smart-routing": [ + "Routers", + "Connectors", + "Events", + "Toolbar" + ], + "snap-links-to-themselves": [ + "Link tools", + "self-loop", + "loop link", + "self-connection" + ], + "spreadsheet-shapes-with-handsontable": [ + "Content-driven shapes", + "Custom shapes", + "Events", + "Link labels", + "Integration", + "Graph API", + "grid layout" + ], + "stencil-favorite-in-use-groups": [ + "Element palette", + "Drag & Drop", + "Built-in shapes", + "Automatic layout", + "flowchart" + ], + "stencil-theme-picker": [ + "Popups", + "Element palette", + "Menus", + "CSS styling", + "theme selection", + "UI themes", + "user interface customization", + "appearance settings", + "visual styles", + "color schemes" + ], + "stencil-vs-diagram-elements": [ + "Element palette", + "Built-in shapes", + "Custom properties" + ], + "tabs": [ + "BPMN", + "Integration", + "Built-in shapes", + "Zoom & Pan", + "Tooltips", + "Popups", + "Highlighters", + "Events" + ], + "tasks": [ + "Zoom & Pan", + "Custom shapes", + "HTML shapes", + "Toolbar" + ], + "team-order": [ + "Automatic layout", + "Toolbar", + "Custom shapes", + "Anchors", + "Animation", + "stack layout" + ], + "text-position-based-on-space-availability": [ + "Element palette", + "Property editor & viewer", + "Custom shapes", + "Selection", + "Undo/Redo", + "Events", + "Toolbar", + "Graph API" + ], + "the-archimate-enterprise-architecture-modeling-language": [ + "UML & software modeling", + "Custom shapes", + "Context Toolbar", + "Embedding", + "External images", + "Highlighters", + "Menus", + "Shape transformation", + "Undo/Redo", + "Toolbar", + "Popups", + "Guidelines", + "enterprise modeling", + "business architecture", + "application architecture", + "technology architecture", + "architecture framework", + "IT strategy" + ], + "theory-of-change": [ + "Project management", + "CSS styling", + "Automatic layout", + "Collapse & Expand", + "Anchors", + "Custom shapes", + "Drag & Drop", + "Element tools", + "Embedding", + "Link labels", + "Popups", + "Validation", + "change management", + "strategic planning", + "program evaluation", + "logic model", + "outcomes mapping", + "impact assessment" + ], + "timeline": [ + "Timelines", + "Property editor & viewer", + "Toolbar", + "Routers", + "event timeline", + "project timeline", + "schedule visualization", + "chronological events", + "time tracking", + "history chart" + ], + "tokens": [ + "Animation", + "Simulation", + "Geometry", + "Custom shapes", + "Integration", + "Zoom & Pan", + "Tooltips", + "Keyboard", + "Performance", + "Automatic layout" + ], + "touch-gestures": [ + "Integration", + "Zoom & Pan", + "Built-in shapes", + "flowchart" + ], + "tournament-brackets": [ + "Automatic layout", + "Routers", + "Toolbar", + "Custom shapes", + "Undo/Redo", + "playoff bracket", + "competition bracket", + "elimination tournament", + "single elimination", + "championship bracket", + "sports scheduling" + ], + "translate-connected-links-in-selection": [ + "Built-in shapes", + "Menus", + "move connected links", + "group move", + "selection dragging" + ], + "tree-builder": [ + "Org charts", + "Automatic layout", + "Drag & Drop", + "Events", + "Graph API", + "org chart", + "organizational chart", + "orgchart" + ], + "tree-collapse-expand": [ + "Org charts", + "Automatic layout", + "Performance", + "Export to PNG/JPEG", + "Custom shapes", + "Virtual rendering", + "Zoom & Pan", + "Toolbar", + "Events", + "collapsible tree", + "hierarchical data", + "organizational chart", + "data visualization", + "expandable tree", + "tree diagram" + ], + "tree-designer": [ + "Automatic layout", + "Property editor & viewer", + "Toolbar", + "Undo/Redo", + "tree structure", + "hierarchical diagram", + "decision tree", + "classification", + "taxonomy", + "data hierarchy" + ], + "tree-graph-and-cycles": [ + "Org charts", + "Graph API", + "Anchors", + "Element tools", + "Events", + "Drag & Drop", + "orgchart", + "Organizational chart", + "org chart", + "hierarchy diagram", + "corporate structure", + "company hierarchy" + ], + "tree-of-life": [ + "Link-to-link", + "Custom shapes", + "External images", + "Integration", + "Connectors", + "dark mode", + "dark theme" + ], + "tree-stencil": [ + "Element palette", + "Automatic layout", + "Collapse & Expand", + "Custom shapes" + ], + "ui-popup": [ + "Events", + "Toolbar", + "Menus" + ], + "uml-class-diagrams": [ + "UML & software modeling", + "Routers", + "Custom shapes", + "Built-in shapes", + "Anchors", + "Link tools", + "Ports", + "Link labels", + "object-oriented design", + "software modeling", + "software architecture", + "system design", + "software development", + "model-driven engineering" + ], + "uml-class-shape-inspector": [ + "Property editor & viewer", + "Custom shapes", + "Built-in shapes", + "Content-driven shapes", + "dark theme", + "dark mode" + ], + "uml-statechart-diagram": [ + "UML & software modeling", + "Custom shapes", + "Embedding", + "state diagram", + "Unified Modeling Language", + "software modeling", + "software architecture", + "object-oriented design" + ], + "upload-image-to-stencil": [ + "External images", + "Element palette", + "Popups", + "Built-in shapes", + "Events" + ], + "use-case-diagram": [ + "UML & software modeling", + "Custom shapes", + "Events", + "Constraints on movement", + "Link labels", + "Embedding", + "object-oriented design", + "system modeling", + "actors", + "system boundary", + "include and extend relationships" + ], + "vector-editor": [ + "Custom shapes", + "Shape transformation", + "Zoom & Pan", + "Vectorizer", + "Undo/Redo", + "Events", + "Property editor & viewer", + "Guidelines" + ], + "view-edit-mode": [ + "Adjustable interactivity", + "Element palette", + "Highlighters", + "Element tools", + "Link tools", + "Zoom & Pan", + "Undo/Redo", + "Toolbar", + "Built-in shapes", + "dark mode", + "dark theme" + ], + "visio-bpmn-export": [ + "BPMN", + "Import/Export", + "Built-in shapes", + "business process model and notation", + "business process modeling", + "process diagram", + "workflow management", + "business process notation", + "process automation" + ], + "visio-bpmn-import": [ + "BPMN", + "Import/Export", + "Built-in shapes", + "business process model and notation", + "business process modeling", + "process diagram", + "workflow management", + "business process notation", + "process automation" + ], + "visio-default-import": [ + "diagram interoperability", + "diagram migration", + "file conversion" + ], + "visio-flowchart-import": [ + "Import/Export", + "Embedding", + "Link tools", + "Built-in shapes", + "process flow", + "business process modeling", + "workflow diagram", + "process mapping", + "activity diagram", + "operation flow" + ], + "visio-org-chart-import": [ + "Org charts", + "Import/Export", + "Popups", + "Events", + "Built-in shapes", + "orgchart", + "company hierarchy", + "corporate structure", + "employee hierarchy", + "management structure", + "workforce visualization" + ], + "workflow-builder": [ + "AI workflow builders", + "Zoom & Pan", + "Automatic layout", + "Custom shapes", + "Toolbar", + "Minimap", + "Property editor & viewer", + "Popups", + "Element palette", + "Undo/Redo", + "Ports" + ], + "working-with-ports": [ + "Custom shapes", + "Connectors", + "Anchors", + "Validation", + "Highlighters" + ], + "yamazumi-3d": [ + "Automatic layout", + "Drag & Drop", + "Custom shapes", + "Events", + "Element tools", + "Geometry", + "Vectorizer", + "Animation", + "Undo/Redo", + "Toolbar", + "production balancing", + "line balancing", + "takt time chart", + "assembly line optimization", + "cycle time analysis", + "manufacturing workflow" + ] +} diff --git a/flowchart/README.md b/flowchart/README.md index ef988d4d..5368a431 100644 --- a/flowchart/README.md +++ b/flowchart/README.md @@ -1,6 +1,6 @@ # JointJS: Flowchart -This flowchart created using our JavaScript/Typescript flowchart library shows a simple checkout process. Use it as a boilerplate for your application and let your users design the sequence of movements or actions. This demo application can be easily integrated into TypeScript, React, Angular, Vue, Svelte, and LightningJS. In addition, you can learn how to style the diagram in CSS and switch between light and dark modes. +This flowchart created using our flowchart library shows a simple checkout process. Use it as a boilerplate for your application and let your users design the sequence of movements or actions. In addition, you can learn how to style the diagram in CSS and switch between light and dark modes. This demo is also available online at [jointjs.com](https://jointjs.com/demos/flowchart). diff --git a/kanban/README.md b/kanban/README.md index d6358fe6..b7b2f2a6 100644 --- a/kanban/README.md +++ b/kanban/README.md @@ -1,6 +1,6 @@ # JointJS+: Kanban JointJS+ -The demo displays the popular Kanban board diagram type. It's written in JavaScript, but can be easily integrated with TypeScript, React, Vue, Angular, Svelte, or LightningJS. +The demo displays the popular Kanban board diagram type. This demo is also available online at [jointjs.com](https://jointjs.com/demos/kanban). diff --git a/mind-map/README.md b/mind-map/README.md index aea7ffb1..c9ad02b6 100644 --- a/mind-map/README.md +++ b/mind-map/README.md @@ -1,6 +1,6 @@ # JointJS+: MindMap JointJS+ -The MindMap demo shows a diagram used to visually organize information into a hierarchy and display the relationships between them. This demo is written in JavaScript, but can be easily integrated with TypeScript, React, Vue, Angular, Svelte, or LightningJS. The source code of this demo is available as part of the JointJS+ license. +The MindMap demo shows a diagram used to visually organize information into a hierarchy and display the relationships between them. The source code of this demo is available as part of the JointJS+ license. This demo is also available online at [jointjs.com](https://jointjs.com/demos/mind-map). diff --git a/package.json b/package.json index af5f6e60..deabbe65 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,12 @@ "scripts": { "lint": "eslint .", "build": "bash .github/scripts/build-demos.sh", - "screenshot": "node .github/scripts/screenshot-demos.mjs" + "screenshot": "node .github/scripts/screenshot-demos.mjs", + "manifests:build": "node .github/scripts/manifests/build.mjs", + "manifests:test": "node --test .github/scripts/manifests/manifests.test.mjs", + "sync": "node .github/scripts/sync.mjs", + "sync:prod": "node .github/scripts/sync.mjs --prod", + "sync:test": "node --test .github/scripts/sync.test.mjs" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "8.53.0",