@glissade/cli 0.43.1 → 0.44.0-pre.1

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.
package/dist/cli.js CHANGED
@@ -91,6 +91,7 @@ const USAGE = `usage:
91
91
  gs mcp <scene-module> start an MCP stdio server for this scene: describe / list_targets / apply_patch / undo / render_frame (the AI-native write layer)
92
92
  gs build [filter...] [--config <glissade.config.ts>] [--affected <git-ref>] [--explain] content-graph DAG runner: narrate→sfx→loudness→render per scene, runs ONLY the stale subtree. --affected <ref> pre-filters to scenes a git diff since <ref> touched (rebuild only what a change set touched; composed with the per-step content-hash staleness)
93
93
  gs describe [--out <api.json>] [--examples] snapshot THIS engine's describe() API manifest (stdout, or --out to a file) — the input to gs migrate
94
+ gs types [--out <file.ts>] [--from <api.json>] [--check] codegen a type-checked track() SDK from the describe() manifest: only registered animatable paths + their value types compile, so a typo'd path or wrong value-type id is a COMPILE error (import track from the generated file). --check fails if --out is stale. Zero-runtime (types + a re-typed re-export of the real track)
94
95
  gs migrate <baseline-api.json> [--json] [--check] diff a saved API manifest against the current engine: moved imports / removed / added / changed, with a suggested fix per breaking item (advisory; --check exits non-zero on any breaking change for CI gating)
95
96
  gs repin <scene-module> --golden <dir> [--name <p>] [--frames a,b,..] [--fps <n>] [--since <ref>] [--write] [--only a,b] [--heatmap <dir>] [--floor <ssim>] [--force] narration-aware golden reviewer: render current vs committed goldens, report perceptual delta + the re-narration cause, re-pin only frames you allow (default dry-run; --floor refuses a bigger-than-expected drop)
96
97
  gs localize <scene-module> --to <locale> [--from <locale>] [--write] [--strict] [--keep-voice] [--json] fork a narration into a new locale (clone segment/pause structure, PRESERVING beat ids so .start() anchors survive) + stub messages.<locale>.json from the scene's t() ids, running the render path's parity + localize checks BEFORE any TTS. Default dry-run (exits non-zero on drift); --write emits <base>.<locale>.narration.json + messages.<locale>.json (re-localize CARRIES existing translations over — never clobbers); --strict refuses to write on a preflight failure
@@ -195,7 +196,7 @@ async function main() {
195
196
  process.stdout.write(`${describe().version}\n`);
196
197
  return;
197
198
  }
198
- if (command !== "render" && command !== "diff" && command !== "verify-determinism" && command !== "dev" && command !== "import" && command !== "narrate" && command !== "narration-lint" && command !== "sfx" && command !== "prepare" && command !== "measure-loudness" && command !== "fonts" && command !== "cache" && command !== "mcp" && command !== "build" && command !== "describe" && command !== "migrate" && command !== "repin" && command !== "master" && command !== "localize") {
199
+ if (command !== "render" && command !== "diff" && command !== "verify-determinism" && command !== "dev" && command !== "import" && command !== "narrate" && command !== "narration-lint" && command !== "sfx" && command !== "prepare" && command !== "measure-loudness" && command !== "fonts" && command !== "cache" && command !== "mcp" && command !== "build" && command !== "describe" && command !== "migrate" && command !== "repin" && command !== "master" && command !== "localize" && command !== "types") {
199
200
  console.error(USAGE);
200
201
  process.exit(command === void 0 || command === "help" || command === "--help" ? 0 : 1);
201
202
  }
@@ -307,6 +308,40 @@ async function main() {
307
308
  } else process.stdout.write(json);
308
309
  return;
309
310
  }
311
+ if (command === "types") {
312
+ const { flags: tf } = parseArgs(rest);
313
+ const { generateTypedSdk } = await import("./typedSdk.js");
314
+ const { readFileSync, writeFileSync, existsSync } = await import("node:fs");
315
+ const from = tf.get("from");
316
+ let manifest;
317
+ if (from) {
318
+ try {
319
+ manifest = JSON.parse(readFileSync(from, "utf8"));
320
+ } catch (err) {
321
+ fail(`gs types: could not read manifest '${from}': ${err instanceof Error ? err.message : String(err)}`);
322
+ }
323
+ if (typeof manifest.version !== "string" || manifest.nodes === void 0) fail(`'${from}' is not a describe() API manifest (missing version/nodes)`);
324
+ } else {
325
+ const { describe } = await import("@glissade/scene/describe");
326
+ manifest = describe();
327
+ }
328
+ const src = generateTypedSdk(manifest);
329
+ const outPath = tf.get("out");
330
+ if (tf.has("check")) {
331
+ if (!outPath) fail("gs types --check needs --out <file> (the committed typed-SDK file to verify)");
332
+ if ((existsSync(outPath) ? readFileSync(outPath, "utf8") : "") !== src) {
333
+ process.stderr.write(`gs types: ${outPath} is STALE — run \`gs types --out ${outPath}\` to regenerate\n`);
334
+ process.exit(1);
335
+ }
336
+ process.stderr.write(`gs types: ${outPath} is up to date\n`);
337
+ return;
338
+ }
339
+ if (outPath) {
340
+ writeFileSync(outPath, src);
341
+ process.stderr.write(`gs types: wrote a typed track() SDK (${Object.keys(manifest.nodes).length} node types) → ${outPath}\n`);
342
+ } else process.stdout.write(src);
343
+ return;
344
+ }
310
345
  if (command === "migrate") {
311
346
  const { positional: mp, flags: mf } = parseArgs(rest);
312
347
  const baselinePath = mp[0];
@@ -0,0 +1,104 @@
1
+ //#region src/typedSdk.ts
2
+ /** value-type id → the TS type its keyframes carry. Unmapped ids fall back to `unknown`. */
3
+ const TS_VALUE_TYPE = {
4
+ number: "number",
5
+ vec2: "readonly [number, number]",
6
+ "vec2-arc": "readonly [number, number]",
7
+ color: "string",
8
+ string: "string",
9
+ boolean: "boolean",
10
+ paint: "Paint",
11
+ fontAxes: "FontAxes",
12
+ path: "PathValue"
13
+ };
14
+ /** core types the generated file must import when a mapped value type references them. */
15
+ const CORE_TYPE_IMPORTS = {
16
+ paint: "Paint",
17
+ fontAxes: "FontAxes",
18
+ path: "PathValue"
19
+ };
20
+ /**
21
+ * Collect every animatable prop path across the manifest's node taxonomy (component
22
+ * props are construction-only → excluded). Value types are UNIONED per path — the
23
+ * manifest encodes a polymorphic prop as a pipe-joined id (`'color|paint'`), and a
24
+ * path can carry different types on different node types; both cases become the union
25
+ * of members, so passing ANY valid member type-checks (a wrong-type-for-a-specific-node
26
+ * still fails loud at bind — the honest best an instance-free SDK can do). `polymorphic`
27
+ * lists the >1-member paths for the header note.
28
+ */
29
+ function collectTargets(manifest) {
30
+ const byPath = /* @__PURE__ */ new Map();
31
+ for (const node of Object.values(manifest.nodes)) for (const [path, prop] of Object.entries(node.props)) {
32
+ if (!prop.animatable || prop.target === void 0) continue;
33
+ const set = byPath.get(path) ?? /* @__PURE__ */ new Set();
34
+ for (const member of prop.type.split("|")) set.add(member.trim());
35
+ byPath.set(path, set);
36
+ }
37
+ const targets = [...byPath.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([path, set]) => ({
38
+ path,
39
+ valueTypes: [...set].sort()
40
+ }));
41
+ return {
42
+ targets,
43
+ polymorphic: targets.filter((t) => t.valueTypes.length > 1).map((t) => `${t.path}: ${t.valueTypes.join(" | ")}`)
44
+ };
45
+ }
46
+ const tsValueOf = (valueType) => TS_VALUE_TYPE[valueType] ?? "unknown";
47
+ /**
48
+ * Generate the typed-SDK source (a `.ts` module). Emits a `KnownTrackPath` union, a
49
+ * `TrackTarget` template (`` `${string}/${KnownTrackPath}` ``), per-path `TypeIdOf` /
50
+ * `ValueOf` conditional maps, and a re-typed `track` re-export whose runtime IS
51
+ * `@glissade/core`'s `track` (only the types are narrowed). Deterministic: paths are
52
+ * sorted, so the same manifest → byte-identical output (drift-guardable with `--check`).
53
+ */
54
+ function generateTypedSdk(manifest) {
55
+ const { targets, polymorphic } = collectTargets(manifest);
56
+ const usedCoreTypes = [...new Set(targets.flatMap((t) => t.valueTypes.map((v) => CORE_TYPE_IMPORTS[v])).filter((n) => n !== void 0))].sort();
57
+ /** union of value-type id literals for a target, e.g. `'color' | 'paint'`. */
58
+ const typeIdUnion = (t) => t.valueTypes.map((v) => `'${v}'`).join(" | ");
59
+ /** union of TS value types for a target, e.g. `string | Paint`. */
60
+ const valueUnion = (t) => [...new Set(t.valueTypes.map(tsValueOf))].join(" | ");
61
+ const L = [];
62
+ L.push(`// GENERATED by \`gs types\` from the describe() API manifest v${manifest.version} — DO NOT EDIT.`);
63
+ L.push("// Regenerate after a glissade upgrade (or when you add a defineComponent target):");
64
+ L.push("// gs types --out glissade-targets.ts");
65
+ L.push("// Then import `track` from here instead of @glissade/core to type-check your targets.");
66
+ if (polymorphic.length) L.push(`// Polymorphic paths (accept a value-type UNION): ${polymorphic.join(", ")}`);
67
+ L.push("");
68
+ L.push(`import { track as _track, type Track, type Key } from '@glissade/core';`);
69
+ if (usedCoreTypes.length) L.push(`import type { ${usedCoreTypes.join(", ")} } from '@glissade/core';`);
70
+ L.push("");
71
+ L.push("/** Every animatable prop path in the describe() taxonomy — a typo'd path is a type error. */");
72
+ L.push(`export type KnownTrackPath =`);
73
+ for (const t of targets) L.push(` | '${t.path}'`);
74
+ L.push(` ;`);
75
+ L.push("");
76
+ L.push("/** A track target: any node id + a KNOWN animatable path. */");
77
+ L.push("export type TrackTarget = `${string}/${KnownTrackPath}`;");
78
+ L.push("");
79
+ L.push("/** The value-type id each path expects (a wrong `type` arg is a type error;");
80
+ L.push(" * a polymorphic prop like `fill: color|paint` accepts either member). */");
81
+ L.push("type TypeIdOf<P extends TrackTarget> =");
82
+ for (const t of targets) L.push(` P extends \`\${string}/${t.path}\` ? ${typeIdUnion(t)} :`);
83
+ L.push(" never;");
84
+ L.push("");
85
+ L.push("/** The value each path animates (the keyframe value type). */");
86
+ L.push("type ValueOf<P extends TrackTarget> =");
87
+ for (const t of targets) L.push(` P extends \`\${string}/${t.path}\` ? ${valueUnion(t)} :`);
88
+ L.push(" never;");
89
+ L.push("");
90
+ L.push("/**");
91
+ L.push(" * Type-checked `track` — only a KNOWN path + its correct value type compile.");
92
+ L.push(" * Runtime is `@glissade/core`'s `track` verbatim (only the types are narrowed).");
93
+ L.push(" */");
94
+ L.push("export const track = _track as <P extends TrackTarget>(");
95
+ L.push(" target: P,");
96
+ L.push(" type: TypeIdOf<P>,");
97
+ L.push(" keys: Key<ValueOf<P>>[],");
98
+ L.push(" opts?: { editable?: boolean },");
99
+ L.push(") => Track<ValueOf<P>>;");
100
+ L.push("");
101
+ return L.join("\n");
102
+ }
103
+ //#endregion
104
+ export { generateTypedSdk };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/cli",
3
- "version": "0.43.1",
3
+ "version": "0.44.0-pre.1",
4
4
  "description": "glissade CLI: headless rendering via backend-skia (+ FFmpeg mux).",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -28,15 +28,15 @@
28
28
  "@napi-rs/canvas": "^0.1.65",
29
29
  "esbuild": "0.28.0",
30
30
  "jiti": "^2.4.2",
31
- "@glissade/backend-skia": "0.43.1",
32
- "@glissade/core": "0.43.1",
33
- "@glissade/interact": "0.43.1",
34
- "@glissade/lottie": "0.43.1",
35
- "@glissade/narrate": "0.43.1",
36
- "@glissade/player": "0.43.1",
37
- "@glissade/scene": "0.43.1",
38
- "@glissade/sfx": "0.43.1",
39
- "@glissade/svg": "0.43.1"
31
+ "@glissade/backend-skia": "0.44.0-pre.1",
32
+ "@glissade/core": "0.44.0-pre.1",
33
+ "@glissade/interact": "0.44.0-pre.1",
34
+ "@glissade/lottie": "0.44.0-pre.1",
35
+ "@glissade/narrate": "0.44.0-pre.1",
36
+ "@glissade/player": "0.44.0-pre.1",
37
+ "@glissade/scene": "0.44.0-pre.1",
38
+ "@glissade/sfx": "0.44.0-pre.1",
39
+ "@glissade/svg": "0.44.0-pre.1"
40
40
  },
41
41
  "repository": {
42
42
  "type": "git",