@glissade/cli 0.46.0-pre.0 → 0.47.0-pre.0
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 +26 -8
- package/dist/describeLint.js +105 -0
- package/dist/mcp.js +1 -1
- package/dist/typedSdk.js +182 -1
- package/package.json +10 -10
package/dist/cli.js
CHANGED
|
@@ -26,7 +26,10 @@ const KNOWN_BOOLEAN_FLAGS = new Set([
|
|
|
26
26
|
"write",
|
|
27
27
|
"keep-voice",
|
|
28
28
|
"help",
|
|
29
|
-
"lottie"
|
|
29
|
+
"lottie",
|
|
30
|
+
"lint",
|
|
31
|
+
"global",
|
|
32
|
+
"iife"
|
|
30
33
|
]);
|
|
31
34
|
function parseArgs(argv) {
|
|
32
35
|
const positional = [];
|
|
@@ -92,8 +95,8 @@ const USAGE = `usage:
|
|
|
92
95
|
gs cache verify <scene-module> [--range a..b] [--sample <n>] assert cache hits == cold renders (§3.5)
|
|
93
96
|
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)
|
|
94
97
|
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)
|
|
95
|
-
gs describe [--out <api.json>] [--examples] snapshot THIS engine's describe() API manifest (stdout, or --out to a file) — the input to gs migrate
|
|
96
|
-
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)
|
|
98
|
+
gs describe [--out <api.json>] [--examples] [--lint] snapshot THIS engine's describe() API manifest (stdout, or --out to a file) — the input to gs migrate. --lint reconciles the manifest against the window.glissade runtime surface (every helper/node/surface member resolves, no type surfaced as a value, no arity drift) and exits non-zero on drift
|
|
99
|
+
gs types [--out <file.ts>] [--from <api.json>] [--check] [--global] 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). --global (alias --iife) instead emits a SELF-CONTAINED ambient window.glissade .d.ts for the no-build <script src> author (typed IIFE surface — a typo'd window.glissade member is a compile error)
|
|
97
100
|
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)
|
|
98
101
|
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)
|
|
99
102
|
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
|
|
@@ -310,6 +313,20 @@ async function main() {
|
|
|
310
313
|
const { describe } = await import("@glissade/scene/describe");
|
|
311
314
|
if (df.has("examples")) await import("@glissade/scene/examples");
|
|
312
315
|
const manifest = describe(df.has("examples") ? { examples: true } : {});
|
|
316
|
+
if (df.has("lint")) {
|
|
317
|
+
const { describeLint, collectRuntimeSurface, exemptFromUnreachable } = await import("./describeLint.js");
|
|
318
|
+
const { surface, unreachable } = await collectRuntimeSurface(manifest);
|
|
319
|
+
const exempt = exemptFromUnreachable(manifest, unreachable);
|
|
320
|
+
const violations = describeLint(manifest, surface, { exempt });
|
|
321
|
+
if (violations.length > 0) {
|
|
322
|
+
process.stderr.write(`gs describe --lint: ${violations.length} violation(s) — describe() drifted from the window.glissade surface:\n`);
|
|
323
|
+
for (const v of violations) process.stderr.write(` ✗ [${v.kind}] ${v.name}: ${v.detail}\n`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
const skipped = exempt.size > 0 ? ` (${exempt.size} browser-only helper(s) not verifiable headlessly — the check:describe CI gate covers them against the built @glissade/browser bundle)` : "";
|
|
327
|
+
process.stderr.write(`gs describe --lint: OK — every described node/helper/surface member resolves on the runtime bundle${skipped}\n`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
313
330
|
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
314
331
|
const outPath = df.get("out");
|
|
315
332
|
if (outPath) {
|
|
@@ -321,7 +338,8 @@ async function main() {
|
|
|
321
338
|
}
|
|
322
339
|
if (command === "types") {
|
|
323
340
|
const { flags: tf } = parseArgs(rest);
|
|
324
|
-
const
|
|
341
|
+
const global = tf.has("global") || tf.has("iife");
|
|
342
|
+
const { generateTypedSdk, generateAmbientDts } = await import("./typedSdk.js");
|
|
325
343
|
const { readFileSync, writeFileSync, existsSync } = await import("node:fs");
|
|
326
344
|
const from = tf.get("from");
|
|
327
345
|
let manifest;
|
|
@@ -336,12 +354,12 @@ async function main() {
|
|
|
336
354
|
const { describe } = await import("@glissade/scene/describe");
|
|
337
355
|
manifest = describe();
|
|
338
356
|
}
|
|
339
|
-
const src = generateTypedSdk(manifest);
|
|
357
|
+
const src = global ? generateAmbientDts(manifest) : generateTypedSdk(manifest);
|
|
340
358
|
const outPath = tf.get("out");
|
|
341
359
|
if (tf.has("check")) {
|
|
342
|
-
if (!outPath) fail(
|
|
360
|
+
if (!outPath) fail(`gs types ${global ? "--global " : ""}--check needs --out <file> (the committed ${global ? "ambient .d.ts" : "typed-SDK"} file to verify)`);
|
|
343
361
|
if ((existsSync(outPath) ? readFileSync(outPath, "utf8") : "") !== src) {
|
|
344
|
-
process.stderr.write(`gs types: ${outPath} is STALE — run \`gs types --out ${outPath}\` to regenerate\n`);
|
|
362
|
+
process.stderr.write(`gs types: ${outPath} is STALE — run \`gs types ${global ? "--global " : ""}--out ${outPath}\` to regenerate\n`);
|
|
345
363
|
process.exit(1);
|
|
346
364
|
}
|
|
347
365
|
process.stderr.write(`gs types: ${outPath} is up to date\n`);
|
|
@@ -349,7 +367,7 @@ async function main() {
|
|
|
349
367
|
}
|
|
350
368
|
if (outPath) {
|
|
351
369
|
writeFileSync(outPath, src);
|
|
352
|
-
process.stderr.write(`gs types: wrote a typed track() SDK (${Object.keys(manifest.nodes).length} node types) → ${outPath}\n`);
|
|
370
|
+
process.stderr.write(global ? `gs types --global: wrote the self-contained ambient window.glissade .d.ts → ${outPath}\n` : `gs types: wrote a typed track() SDK (${Object.keys(manifest.nodes).length} node types) → ${outPath}\n`);
|
|
353
371
|
} else process.stdout.write(src);
|
|
354
372
|
return;
|
|
355
373
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { usageArity } from "@glissade/scene/describe";
|
|
2
|
+
//#region src/describeLint.ts
|
|
3
|
+
/**
|
|
4
|
+
* Type-only surface names that ARE legitimately a runtime class/value on the
|
|
5
|
+
* bundle (a driver class exported under the same name as its type). Empty today —
|
|
6
|
+
* the current type-only surface (Paint/PathValue/FontAxes) erases entirely — but a
|
|
7
|
+
* seam so a future `FollowPath`-style class-and-type name doesn't false-positive.
|
|
8
|
+
*/
|
|
9
|
+
const RUNTIME_TYPE_ALLOWLIST = /* @__PURE__ */ new Set([]);
|
|
10
|
+
/**
|
|
11
|
+
* Reconcile a describe() manifest against a runtime surface record. Returns every
|
|
12
|
+
* violation (empty = clean); the CLI prints them and exits non-zero.
|
|
13
|
+
*
|
|
14
|
+
* Assertions:
|
|
15
|
+
* (a) every callable the manifest advertises (node constructor, helper, surface
|
|
16
|
+
* value function/constructor) resolves to a `function` on the bundle;
|
|
17
|
+
* (b) every surface `kind:'type'` name is truly type-only (erases at runtime) —
|
|
18
|
+
* a type surfaced as a runtime value is the drift this catches;
|
|
19
|
+
* (c) arity: a documented callable must not REQUIRE materially more positional
|
|
20
|
+
* args than its usage advertises (`Function.length > documented total + 1`;
|
|
21
|
+
* tolerant by one — a trailing optional like `measurer` legitimately lifts
|
|
22
|
+
* `Function.length`).
|
|
23
|
+
*/
|
|
24
|
+
function describeLint(manifest, surface, opts = {}) {
|
|
25
|
+
const exempt = opts.exempt ?? /* @__PURE__ */ new Set();
|
|
26
|
+
const out = [];
|
|
27
|
+
const isFn = (n) => typeof surface[n] === "function";
|
|
28
|
+
const present = (n) => surface[n] !== void 0;
|
|
29
|
+
const callables = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const name of Object.keys(manifest.nodes)) callables.add(name);
|
|
31
|
+
for (const h of manifest.helpers) callables.add(h.name);
|
|
32
|
+
for (const e of manifest.surface ?? []) if (e.kind === "value" && (e.form === "constructor" || e.form === "function")) callables.add(e.name);
|
|
33
|
+
for (const name of [...callables].sort()) {
|
|
34
|
+
if (exempt.has(name) || isFn(name)) continue;
|
|
35
|
+
out.push(present(name) ? {
|
|
36
|
+
kind: "not-callable",
|
|
37
|
+
name,
|
|
38
|
+
detail: `window.glissade.${name} is a ${typeof surface[name]}, expected a function`
|
|
39
|
+
} : {
|
|
40
|
+
kind: "missing",
|
|
41
|
+
name,
|
|
42
|
+
detail: `window.glissade.${name} is documented but MISSING from the runtime bundle`
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
for (const e of manifest.surface ?? []) if (e.kind === "value" && e.form === "object" && !exempt.has(e.name) && !present(e.name)) out.push({
|
|
46
|
+
kind: "missing",
|
|
47
|
+
name: e.name,
|
|
48
|
+
detail: `window.glissade.${e.name} (value export) is MISSING from the runtime bundle`
|
|
49
|
+
});
|
|
50
|
+
for (const e of manifest.surface ?? []) if (e.kind === "type" && !exempt.has(e.name) && present(e.name) && !RUNTIME_TYPE_ALLOWLIST.has(e.name)) out.push({
|
|
51
|
+
kind: "type-as-value",
|
|
52
|
+
name: e.name,
|
|
53
|
+
detail: `'${e.name}' is tagged kind:'type' but resolves to a runtime ${typeof surface[e.name]} — surface it as kind:'value' or remove it`
|
|
54
|
+
});
|
|
55
|
+
for (const h of manifest.helpers) {
|
|
56
|
+
if (exempt.has(h.name)) continue;
|
|
57
|
+
const fn = surface[h.name];
|
|
58
|
+
if (typeof fn !== "function") continue;
|
|
59
|
+
const total = usageArity(h.usage);
|
|
60
|
+
if (total !== void 0 && fn.length > total + 1) out.push({
|
|
61
|
+
kind: "arity",
|
|
62
|
+
name: h.name,
|
|
63
|
+
detail: `window.glissade.${h.name} takes ${fn.length} params but its usage documents ${total} — update describe()'s usage or the signature`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Assemble the runtime surface a HEADLESS `gs describe --lint` can reach: the
|
|
70
|
+
* embed authoring packages the CLI legitimately depends on (core + scene + their
|
|
71
|
+
* tree-shakeable subpaths + player), merged into one record. Browser-only modules
|
|
72
|
+
* (`@glissade/backend-canvas2d/snapshot`, `@glissade/element`) are NOT CLI deps —
|
|
73
|
+
* importing them throws, so those specifiers are collected into `unreachable` and
|
|
74
|
+
* the caller exempts the helpers they'd have provided. (The `check:describe` CI
|
|
75
|
+
* gate injects the built `@glissade/browser` bundle instead, so it verifies those
|
|
76
|
+
* too.)
|
|
77
|
+
*/
|
|
78
|
+
async function collectRuntimeSurface(manifest) {
|
|
79
|
+
const modules = new Set([
|
|
80
|
+
"@glissade/core",
|
|
81
|
+
"@glissade/scene",
|
|
82
|
+
"@glissade/scene/describe",
|
|
83
|
+
"@glissade/scene/layout-ctors"
|
|
84
|
+
]);
|
|
85
|
+
for (const h of manifest.helpers) modules.add(h.import);
|
|
86
|
+
const surface = {};
|
|
87
|
+
const unreachable = /* @__PURE__ */ new Set();
|
|
88
|
+
for (const spec of modules) try {
|
|
89
|
+
Object.assign(surface, await import(spec));
|
|
90
|
+
} catch {
|
|
91
|
+
unreachable.add(spec);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
surface,
|
|
95
|
+
unreachable
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** The exempt name-set for a headless lint: helper names whose import module was unreachable. */
|
|
99
|
+
function exemptFromUnreachable(manifest, unreachable) {
|
|
100
|
+
const exempt = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const h of manifest.helpers) if (unreachable.has(h.import)) exempt.add(h.name);
|
|
102
|
+
return exempt;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { collectRuntimeSurface, describeLint, exemptFromUnreachable };
|
package/dist/mcp.js
CHANGED
|
@@ -6,12 +6,12 @@ import { join } from "node:path";
|
|
|
6
6
|
import { validateTrack } from "@glissade/core";
|
|
7
7
|
import { evaluate } from "@glissade/scene";
|
|
8
8
|
import { SkiaBackend } from "@glissade/backend-skia";
|
|
9
|
+
import { describe } from "@glissade/scene/describe";
|
|
9
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
12
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
12
13
|
import { emptySidecar, mergeSidecar } from "@glissade/core/sidecar";
|
|
13
14
|
import { applyPatches } from "@glissade/core/studio-host";
|
|
14
|
-
import { describe } from "@glissade/scene/describe";
|
|
15
15
|
//#region src/mcpSession.ts
|
|
16
16
|
/**
|
|
17
17
|
* gs mcp session (0.28): the stateful core behind the AI-native WRITE layer. It
|
package/dist/typedSdk.js
CHANGED
|
@@ -100,5 +100,186 @@ function generateTypedSdk(manifest) {
|
|
|
100
100
|
L.push("");
|
|
101
101
|
return L.join("\n");
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* value-type id → the SELF-CONTAINED TS type used in the ambient d.ts. Opaque core
|
|
105
|
+
* types (`paint`/`fontAxes`/`path`) map to local `Gs*` aliases instead of the real
|
|
106
|
+
* `@glissade/core` names, so the emitted file needs no imports (the no-build author
|
|
107
|
+
* has no node_modules to resolve `Paint` from).
|
|
108
|
+
*/
|
|
109
|
+
const AMBIENT_VALUE_TYPE = {
|
|
110
|
+
number: "number",
|
|
111
|
+
vec2: "readonly [number, number]",
|
|
112
|
+
"vec2-arc": "readonly [number, number]",
|
|
113
|
+
color: "string",
|
|
114
|
+
string: "string",
|
|
115
|
+
boolean: "boolean",
|
|
116
|
+
paint: "GsPaint",
|
|
117
|
+
fontAxes: "GsFontAxes",
|
|
118
|
+
path: "GsPathValue"
|
|
119
|
+
};
|
|
120
|
+
/** The node constructors that get a real per-node props interface (form:'constructor'). */
|
|
121
|
+
const CONSTRUCTOR_NODES = new Set([
|
|
122
|
+
"Group",
|
|
123
|
+
"Rect",
|
|
124
|
+
"Circle",
|
|
125
|
+
"Path",
|
|
126
|
+
"Text",
|
|
127
|
+
"Image",
|
|
128
|
+
"Video",
|
|
129
|
+
"Layout"
|
|
130
|
+
]);
|
|
131
|
+
/**
|
|
132
|
+
* Map a manifest prop `type` string to a SELF-CONTAINED TS type for the ambient
|
|
133
|
+
* d.ts. Precise for the known value-type ids (incl. a `|`-union of them, e.g.
|
|
134
|
+
* `color|paint` → `string | GsPaint`); best-effort `unknown` for opaque construction
|
|
135
|
+
* types (`SketchStyle`, `BlendMode`, the `anchor` preset union, …) whose real shapes
|
|
136
|
+
* aren't self-containedly expressible — permissive, so authoring never fights it.
|
|
137
|
+
*/
|
|
138
|
+
function ambientPropType(manifestType) {
|
|
139
|
+
const members = manifestType.split("|").map((m) => m.trim());
|
|
140
|
+
if (members.every((m) => m in AMBIENT_VALUE_TYPE)) return [...new Set(members.map((m) => AMBIENT_VALUE_TYPE[m]))].join(" | ");
|
|
141
|
+
if (manifestType === "Node[]") return "GsNode[]";
|
|
142
|
+
return "unknown";
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Derive a surface taxonomy when the manifest predates 0.47 (no `surface`): node
|
|
146
|
+
* names that are known constructors → constructors, helpers + core callables →
|
|
147
|
+
* functions, `easings` → object, plus the opaque type-only names. Keeps
|
|
148
|
+
* `gs types --global --from <old-api.json>` working (best-effort).
|
|
149
|
+
*/
|
|
150
|
+
function surfaceOf(manifest) {
|
|
151
|
+
if (manifest.surface && manifest.surface.length > 0) return manifest.surface;
|
|
152
|
+
const out = [];
|
|
153
|
+
for (const name of Object.keys(manifest.nodes)) if (CONSTRUCTOR_NODES.has(name)) out.push({
|
|
154
|
+
name,
|
|
155
|
+
kind: "value",
|
|
156
|
+
iife: true,
|
|
157
|
+
form: "constructor"
|
|
158
|
+
});
|
|
159
|
+
else out.push({
|
|
160
|
+
name,
|
|
161
|
+
kind: "value",
|
|
162
|
+
iife: true,
|
|
163
|
+
form: "function"
|
|
164
|
+
});
|
|
165
|
+
for (const h of manifest.helpers) out.push({
|
|
166
|
+
name: h.name,
|
|
167
|
+
kind: "value",
|
|
168
|
+
iife: true,
|
|
169
|
+
form: "function"
|
|
170
|
+
});
|
|
171
|
+
for (const name of [
|
|
172
|
+
"timeline",
|
|
173
|
+
"createScene",
|
|
174
|
+
"track",
|
|
175
|
+
"evaluate",
|
|
176
|
+
"stagger",
|
|
177
|
+
"describe"
|
|
178
|
+
]) out.push({
|
|
179
|
+
name,
|
|
180
|
+
kind: "value",
|
|
181
|
+
iife: true,
|
|
182
|
+
form: "function"
|
|
183
|
+
});
|
|
184
|
+
out.push({
|
|
185
|
+
name: "easings",
|
|
186
|
+
kind: "value",
|
|
187
|
+
iife: true,
|
|
188
|
+
form: "object"
|
|
189
|
+
});
|
|
190
|
+
for (const name of [
|
|
191
|
+
"Paint",
|
|
192
|
+
"PathValue",
|
|
193
|
+
"FontAxes"
|
|
194
|
+
]) out.push({
|
|
195
|
+
name,
|
|
196
|
+
kind: "type",
|
|
197
|
+
iife: false,
|
|
198
|
+
form: "type"
|
|
199
|
+
});
|
|
200
|
+
const seen = /* @__PURE__ */ new Set();
|
|
201
|
+
return out.filter((e) => seen.has(e.name) ? false : (seen.add(e.name), true)).sort((a, b) => a.name.localeCompare(b.name));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Generate the SELF-CONTAINED ambient `window.glissade` `.d.ts` from the manifest's
|
|
205
|
+
* `surface` taxonomy. Emits opaque `Gs*` aliases for the complex types, a real
|
|
206
|
+
* per-node construction-props interface for each node constructor (value types
|
|
207
|
+
* reused from the manifest), a best-effort signature per callable, and the global
|
|
208
|
+
* augmentation (`declare global { interface Window { glissade }; const glissade }`).
|
|
209
|
+
* Deterministic (everything sorted) + pure, so it's `--check`-gateable.
|
|
210
|
+
*
|
|
211
|
+
* HONEST about approximation: node instances / Paint / FontAxes / PathValue / the
|
|
212
|
+
* timeline document are OPAQUE brands (the real shapes need the published types);
|
|
213
|
+
* helper signatures beyond the headline authoring calls are `(...args) => any`.
|
|
214
|
+
* The value it delivers is member-name + node-construction-prop type-checking — a
|
|
215
|
+
* typo'd `window.glissade.Reet` or an unknown surface member is a compile error.
|
|
216
|
+
*/
|
|
217
|
+
function generateAmbientDts(manifest) {
|
|
218
|
+
const surface = surfaceOf(manifest);
|
|
219
|
+
new Map(manifest.surface ? manifest.surface.map((e) => [e.name, e]) : []);
|
|
220
|
+
const builderMethods = [...manifest.builder.methods.map((m) => m.name)].sort();
|
|
221
|
+
const L = [];
|
|
222
|
+
L.push(`// GENERATED by \`gs types --global\` from the describe() API manifest v${manifest.version} — DO NOT EDIT.`);
|
|
223
|
+
L.push("// Self-contained ambient types for the NO-BUILD `window.glissade` IIFE (<script src>).");
|
|
224
|
+
L.push("// Regenerate after a glissade upgrade: gs types --global --out glissade.d.ts");
|
|
225
|
+
L.push("// Reference it from your tsconfig `include` (or a /// <reference path> ) — no imports needed.");
|
|
226
|
+
L.push("//");
|
|
227
|
+
L.push("// APPROXIMATE: node instances, Paint, FontAxes, PathValue and the Timeline document are");
|
|
228
|
+
L.push("// OPAQUE brands, and helper signatures beyond the headline authoring calls are `(...args) => any`.");
|
|
229
|
+
L.push("// What IS checked: every surface member name + each node constructor's construction props.");
|
|
230
|
+
L.push("");
|
|
231
|
+
L.push("export {};");
|
|
232
|
+
L.push("");
|
|
233
|
+
L.push("/** Opaque best-effort types — the real shapes live in @glissade/core|scene. */");
|
|
234
|
+
L.push("type GsPaint = string | { readonly [key: string]: unknown };");
|
|
235
|
+
L.push("type GsFontAxes = { readonly [axis: string]: number };");
|
|
236
|
+
L.push("type GsPathValue = readonly unknown[];");
|
|
237
|
+
L.push("type GsNode = { readonly __glissadeNode: 'node' };");
|
|
238
|
+
L.push("type GsScene = { readonly __glissadeScene: 'scene' };");
|
|
239
|
+
L.push("type GsTimeline = { readonly __glissadeTimeline: 'timeline' };");
|
|
240
|
+
L.push("");
|
|
241
|
+
L.push("/** The `timeline(tl => …)` builder surface (methods from describe().builder). */");
|
|
242
|
+
L.push("interface GsTimelineBuilder {");
|
|
243
|
+
for (const name of builderMethods) L.push(` ${name}(...args: any[]): GsTimelineBuilder;`);
|
|
244
|
+
L.push("}");
|
|
245
|
+
L.push("");
|
|
246
|
+
const ctorNodes = surface.filter((e) => e.form === "constructor" && manifest.nodes[e.name]).map((e) => e.name).sort();
|
|
247
|
+
const propsInterfaceName = (node) => `Gs${node}Props`;
|
|
248
|
+
for (const node of ctorNodes) {
|
|
249
|
+
const props = manifest.nodes[node].props;
|
|
250
|
+
L.push(`/** Construction props for \`new glissade.${node}({ … })\` (best-effort; opaque prop types → \`unknown\`). */`);
|
|
251
|
+
L.push(`interface ${propsInterfaceName(node)} {`);
|
|
252
|
+
for (const prop of Object.keys(props).sort()) L.push(` ${JSON.stringify(prop)}?: ${ambientPropType(props[prop].type)};`);
|
|
253
|
+
L.push("}");
|
|
254
|
+
L.push("");
|
|
255
|
+
}
|
|
256
|
+
L.push("/** Every `window.glissade.<name>` export (a typo'd member is a compile error). */");
|
|
257
|
+
L.push("interface GlissadeGlobal {");
|
|
258
|
+
for (const e of surface) {
|
|
259
|
+
if (e.kind !== "value" || !e.iife) continue;
|
|
260
|
+
L.push(` ${JSON.stringify(e.name)}: ${ambientMemberType(e, ctorNodes)};`);
|
|
261
|
+
}
|
|
262
|
+
L.push("}");
|
|
263
|
+
L.push("");
|
|
264
|
+
L.push("declare global {");
|
|
265
|
+
L.push(" interface Window {");
|
|
266
|
+
L.push(" glissade: GlissadeGlobal;");
|
|
267
|
+
L.push(" }");
|
|
268
|
+
L.push(" const glissade: GlissadeGlobal;");
|
|
269
|
+
L.push("}");
|
|
270
|
+
L.push("");
|
|
271
|
+
return L.join("\n");
|
|
272
|
+
}
|
|
273
|
+
/** The TS type for one window.glissade value member (constructor / function / object). */
|
|
274
|
+
function ambientMemberType(e, ctorNodes) {
|
|
275
|
+
if (e.form === "object") return "Record<string, unknown>";
|
|
276
|
+
if (e.form === "constructor") return `new (props?: ${ctorNodes.includes(e.name) ? `Gs${e.name}Props` : "Record<string, unknown>"}) => GsNode`;
|
|
277
|
+
switch (e.name) {
|
|
278
|
+
case "timeline": return "(build: (tl: GsTimelineBuilder) => void) => GsTimeline";
|
|
279
|
+
case "createScene": return "(spec: { size: { w: number; h: number }; children: GsNode[] }) => GsScene";
|
|
280
|
+
case "track": return "(target: string, type: string, keys: readonly unknown[], opts?: { editable?: boolean }) => unknown";
|
|
281
|
+
default: return "(...args: any[]) => any";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
103
284
|
//#endregion
|
|
104
|
-
export { generateTypedSdk };
|
|
285
|
+
export { generateAmbientDts, generateTypedSdk };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0-pre.0",
|
|
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.
|
|
32
|
-
"@glissade/core": "0.
|
|
33
|
-
"@glissade/interact": "0.
|
|
34
|
-
"@glissade/lottie": "0.
|
|
35
|
-
"@glissade/narrate": "0.
|
|
36
|
-
"@glissade/player": "0.
|
|
37
|
-
"@glissade/scene": "0.
|
|
38
|
-
"@glissade/sfx": "0.
|
|
39
|
-
"@glissade/svg": "0.
|
|
31
|
+
"@glissade/backend-skia": "0.47.0-pre.0",
|
|
32
|
+
"@glissade/core": "0.47.0-pre.0",
|
|
33
|
+
"@glissade/interact": "0.47.0-pre.0",
|
|
34
|
+
"@glissade/lottie": "0.47.0-pre.0",
|
|
35
|
+
"@glissade/narrate": "0.47.0-pre.0",
|
|
36
|
+
"@glissade/player": "0.47.0-pre.0",
|
|
37
|
+
"@glissade/scene": "0.47.0-pre.0",
|
|
38
|
+
"@glissade/sfx": "0.47.0-pre.0",
|
|
39
|
+
"@glissade/svg": "0.47.0-pre.0"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|