@caddis/cli 0.4.43 → 0.4.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/bundles/antigravity-plugin/plugin.json +1 -1
  2. package/bundles/antigravity-plugin/scripts/caddis_file_lock.py +48 -7
  3. package/bundles/antigravity-plugin/scripts/caddis_lanes.py +27 -0
  4. package/bundles/antigravity-plugin/scripts/caddis_todo.py +7 -5
  5. package/bundles/antigravity-plugin/scripts/claude-oss.ps1 +15 -3
  6. package/bundles/antigravity-plugin/scripts/claude-oss.sh +12 -1
  7. package/bundles/antigravity-plugin/skills/_registry.md +5 -4
  8. package/bundles/antigravity-plugin/skills/adopt-app/SKILL.md +6 -1
  9. package/bundles/antigravity-plugin/skills/advisory-hub/SKILL.md +7 -1
  10. package/bundles/antigravity-plugin/skills/decide/SKILL.md +1 -1
  11. package/bundles/antigravity-plugin/skills/flow-studio/README.md +53 -0
  12. package/bundles/antigravity-plugin/skills/flow-studio/SKILL.md +103 -0
  13. package/bundles/antigravity-plugin/skills/flow-studio/examples/README.md +11 -0
  14. package/bundles/antigravity-plugin/skills/flow-studio/examples/hostile.json +1783 -0
  15. package/bundles/antigravity-plugin/skills/flow-studio/examples/lineage.json +1778 -0
  16. package/bundles/antigravity-plugin/skills/flow-studio/model.schema.json +123 -0
  17. package/bundles/antigravity-plugin/skills/flow-studio/package.json +11 -0
  18. package/bundles/antigravity-plugin/skills/flow-studio/references/patterns.md +79 -0
  19. package/bundles/antigravity-plugin/skills/flow-studio/scripts/build-engine.mjs +55 -0
  20. package/bundles/antigravity-plugin/skills/flow-studio/scripts/build.mjs +37 -0
  21. package/bundles/antigravity-plugin/skills/flow-studio/scripts/check.mjs +388 -0
  22. package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/build-engine.test.mjs +26 -0
  23. package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/check-selftest.test.mjs +75 -0
  24. package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/cli.test.mjs +64 -0
  25. package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/embed.mjs +24 -0
  26. package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/embed.test.mjs +29 -0
  27. package/bundles/antigravity-plugin/skills/flow-studio/scripts/validate.mjs +38 -0
  28. package/bundles/antigravity-plugin/skills/flow-studio/src/chrome.ts +171 -0
  29. package/bundles/antigravity-plugin/skills/flow-studio/src/controls.ts +231 -0
  30. package/bundles/antigravity-plugin/skills/flow-studio/src/ctx.ts +118 -0
  31. package/bundles/antigravity-plugin/skills/flow-studio/src/engine.ts +142 -0
  32. package/bundles/antigravity-plugin/skills/flow-studio/src/inspector.ts +108 -0
  33. package/bundles/antigravity-plugin/skills/flow-studio/src/interact.ts +240 -0
  34. package/bundles/antigravity-plugin/skills/flow-studio/src/layout.test.ts +69 -0
  35. package/bundles/antigravity-plugin/skills/flow-studio/src/layout.ts +57 -0
  36. package/bundles/antigravity-plugin/skills/flow-studio/src/model.test.ts +107 -0
  37. package/bundles/antigravity-plugin/skills/flow-studio/src/model.ts +165 -0
  38. package/bundles/antigravity-plugin/skills/flow-studio/src/notes.ts +92 -0
  39. package/bundles/antigravity-plugin/skills/flow-studio/src/render.ts +155 -0
  40. package/bundles/antigravity-plugin/skills/flow-studio/src/routing.test.ts +13 -0
  41. package/bundles/antigravity-plugin/skills/flow-studio/src/routing.ts +11 -0
  42. package/bundles/antigravity-plugin/skills/flow-studio/src/rules.test.ts +88 -0
  43. package/bundles/antigravity-plugin/skills/flow-studio/src/rules.ts +35 -0
  44. package/bundles/antigravity-plugin/skills/flow-studio/src/stateCss.test.ts +66 -0
  45. package/bundles/antigravity-plugin/skills/flow-studio/src/stateCss.ts +44 -0
  46. package/bundles/antigravity-plugin/skills/flow-studio/src/styles.css +157 -0
  47. package/bundles/antigravity-plugin/skills/flow-studio/src/trace.test.ts +20 -0
  48. package/bundles/antigravity-plugin/skills/flow-studio/src/trace.ts +22 -0
  49. package/bundles/antigravity-plugin/skills/flow-studio/src/viewport.test.ts +55 -0
  50. package/bundles/antigravity-plugin/skills/flow-studio/src/viewport.ts +45 -0
  51. package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.html +1744 -0
  52. package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.shell.html +61 -0
  53. package/bundles/antigravity-plugin/skills/flow-studio/tsconfig.json +17 -0
  54. package/bundles/antigravity-plugin/skills/implement/SKILL.md +8 -0
  55. package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +6 -1
  56. package/bundles/antigravity-plugin/skills/port-to-github/SKILL.md +6 -1
  57. package/bundles/antigravity-plugin/skills/tech-explainer/SKILL.md +5 -6
  58. package/bundles/antigravity-plugin-extras/plugin.json +1 -1
  59. package/bundles/antigravity-plugin-extras/skills/_registry.md +2 -0
  60. package/bundles/antigravity-plugin-extras/skills/adopt-rbac/SKILL.md +26 -0
  61. package/bundles/antigravity-plugin-extras/skills/case-management/SKILL.md +24 -0
  62. package/bundles/codex/.codex/skills/devops/adopt-app/SKILL.md +6 -1
  63. package/bundles/codex/.codex/skills/devops/port-to-github/SKILL.md +6 -1
  64. package/bundles/codex/.codex/skills/docs/tech-explainer/SKILL.md +5 -6
  65. package/bundles/codex/.codex/skills/frontend/flow-studio/README.md +53 -0
  66. package/bundles/codex/.codex/skills/frontend/flow-studio/SKILL.md +103 -0
  67. package/bundles/codex/.codex/skills/frontend/flow-studio/examples/README.md +11 -0
  68. package/bundles/codex/.codex/skills/frontend/flow-studio/examples/hostile.json +1783 -0
  69. package/bundles/codex/.codex/skills/frontend/flow-studio/examples/lineage.json +1778 -0
  70. package/bundles/codex/.codex/skills/frontend/flow-studio/model.schema.json +123 -0
  71. package/bundles/codex/.codex/skills/frontend/flow-studio/package.json +11 -0
  72. package/bundles/codex/.codex/skills/frontend/flow-studio/references/patterns.md +79 -0
  73. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/build-engine.mjs +55 -0
  74. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/build.mjs +37 -0
  75. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/check.mjs +388 -0
  76. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/build-engine.test.mjs +26 -0
  77. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/check-selftest.test.mjs +75 -0
  78. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/cli.test.mjs +64 -0
  79. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/embed.mjs +24 -0
  80. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/embed.test.mjs +29 -0
  81. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/validate.mjs +38 -0
  82. package/bundles/codex/.codex/skills/frontend/flow-studio/src/chrome.ts +171 -0
  83. package/bundles/codex/.codex/skills/frontend/flow-studio/src/controls.ts +231 -0
  84. package/bundles/codex/.codex/skills/frontend/flow-studio/src/ctx.ts +118 -0
  85. package/bundles/codex/.codex/skills/frontend/flow-studio/src/engine.ts +142 -0
  86. package/bundles/codex/.codex/skills/frontend/flow-studio/src/inspector.ts +108 -0
  87. package/bundles/codex/.codex/skills/frontend/flow-studio/src/interact.ts +240 -0
  88. package/bundles/codex/.codex/skills/frontend/flow-studio/src/layout.test.ts +69 -0
  89. package/bundles/codex/.codex/skills/frontend/flow-studio/src/layout.ts +57 -0
  90. package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.test.ts +107 -0
  91. package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.ts +165 -0
  92. package/bundles/codex/.codex/skills/frontend/flow-studio/src/notes.ts +92 -0
  93. package/bundles/codex/.codex/skills/frontend/flow-studio/src/render.ts +155 -0
  94. package/bundles/codex/.codex/skills/frontend/flow-studio/src/routing.test.ts +13 -0
  95. package/bundles/codex/.codex/skills/frontend/flow-studio/src/routing.ts +11 -0
  96. package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.test.ts +88 -0
  97. package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.ts +35 -0
  98. package/bundles/codex/.codex/skills/frontend/flow-studio/src/stateCss.test.ts +66 -0
  99. package/bundles/codex/.codex/skills/frontend/flow-studio/src/stateCss.ts +44 -0
  100. package/bundles/codex/.codex/skills/frontend/flow-studio/src/styles.css +157 -0
  101. package/bundles/codex/.codex/skills/frontend/flow-studio/src/trace.test.ts +20 -0
  102. package/bundles/codex/.codex/skills/frontend/flow-studio/src/trace.ts +22 -0
  103. package/bundles/codex/.codex/skills/frontend/flow-studio/src/viewport.test.ts +55 -0
  104. package/bundles/codex/.codex/skills/frontend/flow-studio/src/viewport.ts +45 -0
  105. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.html +1744 -0
  106. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.shell.html +61 -0
  107. package/bundles/codex/.codex/skills/frontend/flow-studio/tsconfig.json +17 -0
  108. package/bundles/codex/.codex/skills/media/particle-art/SKILL.md +6 -1
  109. package/bundles/codex/.codex/skills/workflow/advisory-hub/SKILL.md +7 -1
  110. package/bundles/manifest.json +3 -3
  111. package/package.json +1 -1
@@ -0,0 +1,123 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "flow-studio model",
4
+ "description": "What an author writes. The engine renders it. validate.mjs also enforces rules a schema cannot (unique ids, links that point at real nodes, no overlap, no orphan).",
5
+ "type": "object",
6
+ "required": ["version", "meta", "layout", "columns", "lanes", "states", "nodes", "links"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "meta": {
11
+ "type": "object",
12
+ "required": ["title", "help", "asOf", "source"],
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "title": { "type": "string" },
16
+ "help": { "type": "string" },
17
+ "asOf": { "type": "string" },
18
+ "source": { "type": "string" },
19
+ "labels": { "type": "object", "additionalProperties": { "type": "string" } }
20
+ }
21
+ },
22
+ "layout": { "const": "columns-lanes" },
23
+ "columns": {
24
+ "type": "array", "minItems": 1,
25
+ "items": {
26
+ "type": "object", "required": ["id", "title", "width"], "additionalProperties": false,
27
+ "properties": {
28
+ "id": { "type": "string" }, "title": { "type": "string" }, "short": { "type": "string" },
29
+ "width": { "type": "number", "minimum": 60 }, "phase": { "type": "string" }
30
+ }
31
+ }
32
+ },
33
+ "lanes": {
34
+ "type": "array", "minItems": 1,
35
+ "items": {
36
+ "type": "object", "required": ["id", "title"], "additionalProperties": false,
37
+ "properties": {
38
+ "id": { "type": "string" }, "title": { "type": "string" }, "badge": { "type": "string" },
39
+ "badgeTone": { "enum": ["ok", "warn", "muted"] }, "note": { "type": "string" }, "gap": { "type": "boolean" }
40
+ }
41
+ }
42
+ },
43
+ "states": {
44
+ "type": "object", "minProperties": 1,
45
+ "additionalProperties": {
46
+ "type": "object", "required": ["word", "tone", "border", "edge", "motion", "bucket"], "additionalProperties": false,
47
+ "properties": {
48
+ "word": { "type": "string" },
49
+ "tone": { "enum": ["ok", "warn", "crit", "info", "muted", "accent"] },
50
+ "border": { "enum": ["solid", "dashed", "dotted"] },
51
+ "edge": { "enum": ["flow", "dash", "dash-short", "dot"] },
52
+ "motion": { "enum": ["flow", "flow-slow", "breathe", "none"] },
53
+ "bucket": { "enum": ["good", "neutral", "gap"] }
54
+ }
55
+ }
56
+ },
57
+ "nodes": {
58
+ "type": "array",
59
+ "items": {
60
+ "type": "object", "required": ["id", "kind", "lane", "col", "row", "state", "title"], "additionalProperties": false,
61
+ "properties": {
62
+ "id": { "type": "string" },
63
+ "kind": { "enum": ["card", "group", "chip", "gateway", "start", "end", "event", "placeholder", "note"] },
64
+ "lane": { "type": "string" }, "col": { "type": "integer", "minimum": 0 }, "colSpan": { "type": "integer", "minimum": 1 },
65
+ "row": { "type": "number" }, "state": { "type": "string" },
66
+ "stateIn": { "type": "object", "additionalProperties": { "type": "string" } },
67
+ "visibleIn": { "type": "array", "items": { "type": "string" } },
68
+ "title": { "type": "string" }, "subtitle": { "type": "string" },
69
+ "members": { "type": "array", "items": { "type": "string" } },
70
+ "weight": { "type": "number", "minimum": 0 }, "ref": { "type": "string" },
71
+ "upgrade": { "type": "string" }, "allowIsolated": { "type": "boolean" }
72
+ }
73
+ }
74
+ },
75
+ "links": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "object", "required": ["id", "from", "to", "state"], "additionalProperties": false,
79
+ "properties": {
80
+ "id": { "type": "string" }, "from": { "type": "string" }, "to": { "type": "string" }, "state": { "type": "string" },
81
+ "stateIn": { "type": "object", "additionalProperties": { "type": "string" } },
82
+ "visibleIn": { "type": "array", "items": { "type": "string" } }, "label": { "type": "string" }
83
+ }
84
+ }
85
+ },
86
+ "views": {
87
+ "type": "array",
88
+ "items": {
89
+ "type": "object", "required": ["id", "label"], "additionalProperties": false,
90
+ "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "default": { "type": "boolean" } }
91
+ }
92
+ },
93
+ "inspector": {
94
+ "type": "object", "additionalProperties": false,
95
+ "properties": {
96
+ "relations": {
97
+ "type": "array", "maxItems": 3,
98
+ "items": {
99
+ "type": "object", "required": ["id", "label", "dir"], "additionalProperties": false,
100
+ "properties": {
101
+ "id": { "type": "string" }, "label": { "type": "string" }, "dir": { "enum": ["up", "down"] },
102
+ "filter": { "type": "object", "additionalProperties": false, "properties": { "col": { "type": "integer" }, "kind": { "type": "string" } } }
103
+ }
104
+ }
105
+ },
106
+ "actions": {
107
+ "type": "array",
108
+ "items": {
109
+ "type": "object", "required": ["id", "label", "dir", "staleText"], "additionalProperties": false,
110
+ "properties": { "id": { "type": "string" }, "label": { "type": "string" }, "dir": { "const": "down" }, "staleText": { "type": "string" } }
111
+ }
112
+ }
113
+ }
114
+ },
115
+ "notes": {
116
+ "type": "array",
117
+ "items": {
118
+ "type": "object", "required": ["anchor", "title", "text"], "additionalProperties": false,
119
+ "properties": { "anchor": { "type": "string" }, "title": { "type": "string" }, "text": { "type": "string" } }
120
+ }
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "flow-studio",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "test": "node --experimental-strip-types --test src/*.test.ts scripts/lib/*.test.mjs",
7
+ "typecheck": "tsc -p tsconfig.json",
8
+ "build:engine": "node --experimental-strip-types scripts/build-engine.mjs",
9
+ "check:engine": "node --experimental-strip-types scripts/build-engine.mjs --check"
10
+ }
11
+ }
@@ -0,0 +1,79 @@
1
+ # Scenario recipes
2
+
3
+ Each recipe says how to express a scenario in a flow-studio model. **Proven** means an example model passes the
4
+ check harness. **Candidate** means the recipe is written but not built yet: say so when you use it.
5
+ Version 0.1 draws left-to-right flows only (see `SKILL.md`); where a scenario needs a loop or a branch, the
6
+ recipe says what to do today.
7
+
8
+ Common states to start from (change the words to fit the scenario):
9
+
10
+ | State id | Word | tone | border | edge | motion | bucket |
11
+ |---|---|---|---|---|---|---|
12
+ | `ok` | OK | ok | solid | flow | flow | good |
13
+ | `planned` | PLANNED | muted | dashed | dash | flow-slow | neutral |
14
+ | `late` | LATE | warn | solid | flow | flow | neutral |
15
+ | `breached` | BREACHED | crit | solid | flow | flow | gap |
16
+ | `unknown` | NOT RECORDED | warn | dotted | dot | breathe | gap |
17
+
18
+ ## 1. Data lineage and dependency impact (proven: `examples/lineage.json`)
19
+
20
+ - **Columns:** source, host, database, jobs, tables, refreshers, report tables, apps, pages.
21
+ - **Lanes:** one per app or system; a lane with no document gets `gap: true` and a note saying what is missing.
22
+ - **States:** measured, declared, inferred, not recorded. Views: Today (what is measured) and Complete (adds the
23
+ declared layer).
24
+ - **Inspector:** Feeds this, Downstream, Pages (`filter: {col: <pages column>}`). **Action:** "Simulate: this stops
25
+ updating".
26
+ - **Questions:** which layers matter, which sources are measured and which are declared, who owns each document.
27
+ - **Traps:** guessing a link (use a placeholder), counting a declared snapshot as current (carry its date).
28
+
29
+ ## 2. Ticket, case or order lifecycle (candidate)
30
+
31
+ - **Columns:** the stages (New, Triage, In progress, Waiting, Resolved, Closed). **Lanes:** the owning team or
32
+ system. **States:** on time, at risk, breached. **Nodes:** one card per stage and team; `weight` = open count.
33
+ - **Loops:** "reopened" and "back to triage" are loops: not drawn yet. Add a "Reopened" node in a later column.
34
+ - **Inspector:** Came from, Goes to, Closed. **Action:** "Simulate: SLA breach".
35
+ - **Traps:** drawing every status as a column when only some have owners.
36
+
37
+ ## 3. Agent, user or customer journey (candidate)
38
+
39
+ - **Columns:** phases (Discover, Try, Buy, Use, Renew) or time steps. **Lanes:** actors (customer, agent, system).
40
+ **Nodes:** touchpoints. **States:** smooth, friction, failure. **Metrics:** put the figure in `subtitle`.
41
+ - **Traps:** mixing actors in one lane; no states (a journey with no pain points hides the point).
42
+
43
+ ## 4. Incident timeline and root cause (candidate)
44
+
45
+ - **Columns:** time buckets (T-30m, T-10m, T0, T+10m, T+1h). **Lanes:** systems. **Nodes:** events.
46
+ **States:** normal, degraded, failed. **Action:** "Simulate: this failed" shows the blast radius.
47
+
48
+ ## 5. CI/CD pipeline and release train (candidate)
49
+
50
+ - **Columns:** stages (commit, build, test, scan, deploy, verify). **Lanes:** environments or services.
51
+ **States:** passed, running, failed, skipped. **Inspector:** Upstream, Downstream, Environments.
52
+
53
+ ## 6. Approval or business process (candidate)
54
+
55
+ - **Columns:** steps; **lanes:** roles. Branches ("approved" and "rejected") are two nodes in the next column.
56
+ Gateway diamonds are not drawn yet: use a card titled "Decision: ...".
57
+
58
+ ## 7. Service or architecture dependency map (candidate)
59
+
60
+ - **Columns:** tiers (client, edge, service, data store). **Lanes:** domains. **States:** healthy, degraded, down.
61
+ **Action:** "Simulate: this is down" ripples to the dependents.
62
+
63
+ ## 8. State machine (candidate; needs loops)
64
+
65
+ - Not a good fit until loop-back links exist. Draw the forward path only and list the returns in the inspector
66
+ (`members`).
67
+
68
+ ## 9 to 16. Further candidates
69
+
70
+ Multi-agent or LLM orchestration trace, job schedule and batch dependency, decision tree or runbook, data model or
71
+ entity explorer, access and permission graph, supply chain, learning path or roadmap, network topology. Start from
72
+ recipe 1 or 5 and say they are unproven. When one passes the checks, add its model under `examples/` and change
73
+ "candidate" to "proven" here.
74
+
75
+ ## Choosing the lanes (the usual mistake)
76
+
77
+ If every node sits in one lane, the page has no structure: a lane answers "who or what owns this row". If there
78
+ are more than about 10 lanes, group them (a lane per team, not per person). If a lane has no source of truth,
79
+ mark it `gap: true` so the gap is visible instead of looking like a forgotten node.
@@ -0,0 +1,55 @@
1
+ // Build templates/engine.html from the TypeScript sources.
2
+ // node --experimental-strip-types scripts/build-engine.mjs write templates/engine.html
3
+ // node --experimental-strip-types scripts/build-engine.mjs --check exit 1 if the committed file is stale
4
+ // esbuild is a dev-time tool. It is looked up in this folder, in the caddis cli folder, then in the
5
+ // current directory. The built page has no dependency on it.
6
+
7
+ import { createRequire } from "node:module";
8
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
9
+ import { dirname, resolve, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ const root = resolve(here, "..");
14
+
15
+ function loadEsbuild() {
16
+ const candidates = [join(root, "package.json"), resolve(root, "../../../../cli/package.json"), join(process.cwd(), "package.json")];
17
+ for (const c of candidates) {
18
+ if (!existsSync(dirname(c))) continue;
19
+ try { return createRequire(c)("esbuild"); } catch { /* try the next place */ }
20
+ }
21
+ throw new Error("esbuild was not found. Run `npm i -D esbuild` in the skill folder, or run this from the caddis checkout (cli/node_modules has it).");
22
+ }
23
+
24
+ const normalise = text => text.replace(/\r\n/g, "\n");
25
+
26
+ export async function buildEngine() {
27
+ const esbuild = loadEsbuild();
28
+ const out = await esbuild.build({
29
+ entryPoints: [join(root, "src/engine.ts")],
30
+ bundle: true, format: "iife", target: "es2022", minify: false, write: false, legalComments: "none", logLevel: "silent",
31
+ });
32
+ const js = out.outputFiles[0].text;
33
+ if (/<\/script/i.test(js)) throw new Error("The bundle contains a closing script tag; it would end the page's script early.");
34
+ const css = readFileSync(join(root, "src/styles.css"), "utf8");
35
+ if (/<\/style/i.test(css)) throw new Error("The stylesheet contains a closing style tag; it would end the page style early.");
36
+ if (css.includes("/*__ENGINE__*/") || css.includes("/*__MODEL__*/") || css.includes("/*__NOTES__*/")) throw new Error("The stylesheet contains a template slot marker.");
37
+ const shell = readFileSync(join(root, "templates/engine.shell.html"), "utf8");
38
+ for (const slot of ["/*__CSS__*/", "/*__ENGINE__*/", "/*__MODEL__*/{}", "/*__NOTES__*/[]"]) {
39
+ if (!shell.includes(slot)) throw new Error(`The shell is missing the slot ${slot}`);
40
+ }
41
+ return normalise(shell.replace("/*__CSS__*/", () => css).replace("/*__ENGINE__*/", () => js));
42
+ }
43
+
44
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
45
+ const target = join(root, "templates/engine.html");
46
+ const html = await buildEngine();
47
+ if (process.argv.includes("--check")) {
48
+ const have = existsSync(target) ? normalise(readFileSync(target, "utf8")) : "";
49
+ if (have !== html) { console.error("templates/engine.html is stale. Run: node --experimental-strip-types scripts/build-engine.mjs"); process.exit(1); }
50
+ console.log("templates/engine.html is up to date");
51
+ } else {
52
+ writeFileSync(target, html, "utf8");
53
+ console.log(`wrote templates/engine.html (${html.length} bytes)`);
54
+ }
55
+ }
@@ -0,0 +1,37 @@
1
+ // Build one self-contained HTML page from a model.
2
+ // node --experimental-strip-types scripts/build.mjs <model.json> <out.html> [--notes notes.json]
3
+ // The model is validated first. Any error stops the build (exit 1). Warnings are printed.
4
+
5
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
6
+ import { dirname, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { validateModel } from "../src/model.ts";
9
+ import { embedModel } from "./lib/embed.mjs";
10
+ import { loadModel, report } from "./validate.mjs";
11
+
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ const args = process.argv.slice(2);
14
+ const notesAt = args.indexOf("--notes");
15
+ const notesPath = notesAt >= 0 ? args.splice(notesAt, 2)[1] : null;
16
+ const [modelPath, outPath] = args;
17
+ if (!modelPath || !outPath) {
18
+ console.error("usage: node --experimental-strip-types scripts/build.mjs <model.json> <out.html> [--notes notes.json]");
19
+ process.exit(2);
20
+ }
21
+
22
+ const model = loadModel(modelPath);
23
+ const errors = report(validateModel(model));
24
+ if (errors) {
25
+ console.error(`${errors} error(s): not building.`);
26
+ process.exit(1);
27
+ }
28
+ const extra = notesPath ? loadModel(notesPath) : [];
29
+ if (notesPath && !Array.isArray(extra)) {
30
+ console.error(`${notesPath} must be a JSON array of notes.`);
31
+ process.exit(2);
32
+ }
33
+ const notes = [...(Array.isArray(model.notes) ? model.notes : []), ...(Array.isArray(extra) ? extra : [])].map((n, i) => ({ ...n, id: i + 1 }));
34
+ const shell = readFileSync(resolve(here, "../templates/engine.html"), "utf8");
35
+ mkdirSync(dirname(resolve(outPath)), { recursive: true });
36
+ writeFileSync(outPath, embedModel(shell, model, notes), "utf8");
37
+ console.log(`wrote ${outPath}`);