@flatkit/compiler 0.30.0 → 0.30.2
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/analysis.d.ts +24 -2
- package/dist/analysis.js +7 -3
- package/dist/{chunk-K67U2OKN.js → chunk-P2XIV5B3.js} +32 -46
- package/dist/chunk-P2XIV5B3.js.map +1 -0
- package/dist/{chunk-KS3NW7BP.js → chunk-UO7ZHNQT.js} +57 -8
- package/dist/chunk-UO7ZHNQT.js.map +1 -0
- package/dist/cli/flatc.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/docs/tooling.md +10 -4
- package/package.json +4 -4
- package/dist/chunk-K67U2OKN.js.map +0 -1
- package/dist/chunk-KS3NW7BP.js.map +0 -1
package/dist/analysis.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Doc, EditFrame } from '@flatkit/types';
|
|
1
|
+
import { Doc, EditFrame, Text } from '@flatkit/types';
|
|
2
2
|
import { TextEdit, Diagnostic, ScriptUnit } from '@flatkit/engine/dsl';
|
|
3
3
|
import { MediaMap } from './compile.js';
|
|
4
4
|
|
|
@@ -61,6 +61,21 @@ declare function applyFixes(src: string, diagnostics: CheckDiagnostic[]): {
|
|
|
61
61
|
text: string;
|
|
62
62
|
applied: number;
|
|
63
63
|
};
|
|
64
|
+
/** Applies the mechanical repairs until nothing is left to apply, and returns the FINAL text. Pure: it
|
|
65
|
+
* takes a `recheck` and never touches the filesystem, which is the whole guarantee -- the caller writes
|
|
66
|
+
* once, at the end, a text this loop has already re-checked. Writing each pass and reverting on failure
|
|
67
|
+
* would leave a window where the author's file holds a version we already know we do not want.
|
|
68
|
+
*
|
|
69
|
+
* It ITERATES because repairing one error unmasks the next (a run-on interactor line swallows the
|
|
70
|
+
* statements under it), and it accepts a pass only if the error count strictly DROPS -- except that the
|
|
71
|
+
* count legitimately RISES on the first pass of a source that did not parse at all, where a single error
|
|
72
|
+
* was all that was visible. That case never reaches here: it is handled before the Doc exists. */
|
|
73
|
+
declare function repairLoop(src: string, diagnostics: CheckDiagnostic[], recheck: (candidate: string) => CheckDiagnostic[], maxPasses?: number): {
|
|
74
|
+
text: string;
|
|
75
|
+
applied: number;
|
|
76
|
+
first: number;
|
|
77
|
+
errors: number;
|
|
78
|
+
};
|
|
64
79
|
|
|
65
80
|
type LintContext = {
|
|
66
81
|
/** Known variables in addition to those declared/assigned in the source. */
|
|
@@ -122,6 +137,13 @@ declare function docPaintParamWarnings(doc: Doc): {
|
|
|
122
137
|
scope: string;
|
|
123
138
|
diag: Diagnostic;
|
|
124
139
|
}[];
|
|
140
|
+
/** Greedy word-wrap of a `wrap` text at its box width — the renderer's own rule (break at spaces, and
|
|
141
|
+
* only at spaces). Returns the resulting line count and the widest single word, which is what decides
|
|
142
|
+
* whether anything spills sideways: a word wider than the box has no break point and runs past it. */
|
|
143
|
+
declare function wrapMetrics(t: Text): {
|
|
144
|
+
lines: number;
|
|
145
|
+
widestWord: number;
|
|
146
|
+
};
|
|
125
147
|
/** LAYOUT warnings (non-blocking): canvas overflow, text too wide, overlapping hitboxes.
|
|
126
148
|
* Approximate but without canvas — catches "blind" positioning bugs before the render. */
|
|
127
149
|
declare function docLayoutWarnings(doc: Doc): {
|
|
@@ -174,4 +196,4 @@ declare function joinScopeProgram(rest: string, objects: {
|
|
|
174
196
|
body: string;
|
|
175
197
|
}[]): string;
|
|
176
198
|
|
|
177
|
-
export { type CheckDiagnostic, type CheckOptions, type CheckResult, type LintContext, allScopeVariables, applyFixes, checkProgram, docCelLayerWarnings, docHasErrors, docLayoutWarnings, docLintContext, docModifierWarnings, docPaintParamWarnings, docStructureWarnings, formatDiagnostics, formatObjectBlock, joinScopeProgram, lint, lintDoc, lintDocReport, lintReport, localVariables, programDiagnostics, scopeProgram, scopeRegions, splitScopeProgram };
|
|
199
|
+
export { type CheckDiagnostic, type CheckOptions, type CheckResult, type LintContext, allScopeVariables, applyFixes, checkProgram, docCelLayerWarnings, docHasErrors, docLayoutWarnings, docLintContext, docModifierWarnings, docPaintParamWarnings, docStructureWarnings, formatDiagnostics, formatObjectBlock, joinScopeProgram, lint, lintDoc, lintDocReport, lintReport, localVariables, programDiagnostics, repairLoop, scopeProgram, scopeRegions, splitScopeProgram, wrapMetrics };
|
package/dist/analysis.js
CHANGED
|
@@ -18,10 +18,12 @@ import {
|
|
|
18
18
|
lintReport,
|
|
19
19
|
localVariables,
|
|
20
20
|
programDiagnostics,
|
|
21
|
+
repairLoop,
|
|
21
22
|
scopeProgram,
|
|
22
23
|
scopeRegions,
|
|
23
|
-
splitScopeProgram
|
|
24
|
-
|
|
24
|
+
splitScopeProgram,
|
|
25
|
+
wrapMetrics
|
|
26
|
+
} from "./chunk-UO7ZHNQT.js";
|
|
25
27
|
import "./chunk-KXKX2RMX.js";
|
|
26
28
|
export {
|
|
27
29
|
allScopeVariables,
|
|
@@ -43,8 +45,10 @@ export {
|
|
|
43
45
|
lintReport,
|
|
44
46
|
localVariables,
|
|
45
47
|
programDiagnostics,
|
|
48
|
+
repairLoop,
|
|
46
49
|
scopeProgram,
|
|
47
50
|
scopeRegions,
|
|
48
|
-
splitScopeProgram
|
|
51
|
+
splitScopeProgram,
|
|
52
|
+
wrapMetrics
|
|
49
53
|
};
|
|
50
54
|
//# sourceMappingURL=analysis.js.map
|
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
docHasErrors,
|
|
4
4
|
formatDiagnostics,
|
|
5
5
|
lintDocReport,
|
|
6
|
-
programDiagnostics
|
|
7
|
-
|
|
6
|
+
programDiagnostics,
|
|
7
|
+
repairLoop
|
|
8
|
+
} from "./chunk-UO7ZHNQT.js";
|
|
8
9
|
import {
|
|
9
10
|
compileFlatpack,
|
|
10
11
|
packToJSON
|
|
@@ -67,7 +68,8 @@ Usage:
|
|
|
67
68
|
--check semantic lint only (no .flatpack); exits \u22600 on ERROR (warnings do not stop). Lints a
|
|
68
69
|
program .flatink OR an asset library .flat (per-symbol; several .flat are merged)
|
|
69
70
|
--fix apply the MECHANICAL repairs the diagnostics carry (missing separator, run-on line)
|
|
70
|
-
and re-check
|
|
71
|
+
and re-check, iterating. Writes the file AT MOST ONCE, and never a text it has not
|
|
72
|
+
re-checked; a file it cannot repair is left untouched, mtime included. Implies --check.
|
|
71
73
|
--no-libs do NOT auto-discover the .flat files sitting next to the program (use it in a working
|
|
72
74
|
folder, where a neighbouring scratch file is not a dependency)
|
|
73
75
|
--watch recompile on every change in the folder (agent \u2192 player loop)
|
|
@@ -102,9 +104,9 @@ Usage:
|
|
|
102
104
|
Media referenced by 'asset "id" "path" kind' are embedded (paths relative to the program).
|
|
103
105
|
`);
|
|
104
106
|
}
|
|
105
|
-
function buildDocFromProgram(programPath, explicitFlats = [], assetMode = "inline", assetsDir = "", noLibs = false) {
|
|
107
|
+
function buildDocFromProgram(programPath, explicitFlats = [], assetMode = "inline", assetsDir = "", noLibs = false, srcOverride) {
|
|
106
108
|
const baseDir = dirname(programPath);
|
|
107
|
-
const programSrc = readFileSync(programPath, "utf8");
|
|
109
|
+
const programSrc = srcOverride ?? readFileSync(programPath, "utf8");
|
|
108
110
|
const prog = parseProgramFull(programSrc);
|
|
109
111
|
const flatPaths = new Set(explicitFlats.map((p) => resolve(p)));
|
|
110
112
|
if (!flatPaths.size && !noLibs) for (const f of readdirSync(baseDir).filter((f2) => f2.endsWith(".flat"))) flatPaths.add(join(baseDir, f));
|
|
@@ -172,23 +174,32 @@ function buildDocFromProgram(programPath, explicitFlats = [], assetMode = "inlin
|
|
|
172
174
|
doc = { ...doc, imports: stdImports.length ? stdImports : void 0 };
|
|
173
175
|
return { doc, flatLibs: flatPaths.size, packages: localResolved.size, media: Object.keys(media).length, mediaCopies, src: programSrc };
|
|
174
176
|
}
|
|
175
|
-
function compileOnce(programPath, explicitFlats, out, checkOnly, assetMode = "inline", noLibs = false, doFix = false
|
|
177
|
+
function compileOnce(programPath, explicitFlats, out, checkOnly, assetMode = "inline", noLibs = false, doFix = false) {
|
|
176
178
|
const outPath = out ? resolve(out) : join(dirname(programPath), basename(programPath, extname(programPath)) + ".flatpack");
|
|
177
179
|
const assetsDir = assetMode === "external" ? basename(outPath, extname(outPath)) + ".assets" : "";
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
180
|
+
const build = (src) => buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src);
|
|
181
|
+
let parseRepaired = "";
|
|
182
|
+
if (doFix) {
|
|
183
|
+
let candidate = readFileSync(programPath, "utf8");
|
|
184
|
+
for (let pass = 0; pass < 10; pass++) {
|
|
185
|
+
try {
|
|
186
|
+
build(candidate);
|
|
187
|
+
break;
|
|
188
|
+
} catch (e) {
|
|
189
|
+
if (!(e instanceof FlatSyntaxError) || !e.fix) break;
|
|
190
|
+
const { text, applied } = applyFixes(candidate, [{ scope: "scene", line: e.line, col: e.col, severity: "error", message: e.message, fix: e.fix }]);
|
|
191
|
+
if (!applied) break;
|
|
192
|
+
candidate = text;
|
|
193
|
+
parseRepaired = candidate;
|
|
187
194
|
process.stderr.write(`flatc: --fix: repaired \`${e.message.split(":")[0]}\` in ${basename(programPath)}
|
|
188
195
|
`);
|
|
189
|
-
return compileOnce(programPath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix, fixPass + 1);
|
|
190
196
|
}
|
|
191
197
|
}
|
|
198
|
+
}
|
|
199
|
+
let built;
|
|
200
|
+
try {
|
|
201
|
+
built = build(parseRepaired || void 0);
|
|
202
|
+
} catch (e) {
|
|
192
203
|
process.stderr.write(`flatc: compile error: ${e.message}
|
|
193
204
|
`);
|
|
194
205
|
return 1;
|
|
@@ -198,37 +209,12 @@ function compileOnce(programPath, explicitFlats, out, checkOnly, assetMode = "in
|
|
|
198
209
|
const report = formatDiagnostics(diagnostics);
|
|
199
210
|
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
200
211
|
if (doFix) {
|
|
201
|
-
const original = readFileSync(programPath, "utf8");
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
let total = 0;
|
|
205
|
-
let before = errs(diagnostics);
|
|
206
|
-
const first = before;
|
|
207
|
-
let current = diagnostics;
|
|
208
|
-
for (let pass = 0; pass < 5; pass++) {
|
|
209
|
-
const step = applyFixes(text, current);
|
|
210
|
-
if (!step.applied) break;
|
|
211
|
-
writeFileSync(programPath, step.text);
|
|
212
|
-
let after;
|
|
213
|
-
try {
|
|
214
|
-
after = programDiagnostics(buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs).doc, step.text);
|
|
215
|
-
} catch {
|
|
216
|
-
writeFileSync(programPath, text);
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
if (errs(after) >= before) {
|
|
220
|
-
writeFileSync(programPath, text);
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
text = step.text;
|
|
224
|
-
total += step.applied;
|
|
225
|
-
before = errs(after);
|
|
226
|
-
current = after;
|
|
227
|
-
}
|
|
228
|
-
if (!total) {
|
|
229
|
-
writeFileSync(programPath, original);
|
|
212
|
+
const original = parseRepaired || readFileSync(programPath, "utf8");
|
|
213
|
+
const { text, applied: total, first, errors: before } = repairLoop(original, diagnostics, (src) => programDiagnostics(buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src).doc, src));
|
|
214
|
+
if (!total && !parseRepaired) {
|
|
230
215
|
process.stderr.write(`flatc: --fix: nothing to repair mechanically${hasErrors ? " \u2014 the remaining errors need a decision, not a separator\n" : "\n"}`);
|
|
231
216
|
} else {
|
|
217
|
+
writeFileSync(programPath, text);
|
|
232
218
|
process.stderr.write(`flatc: --fix: ${total} repair(s) applied to ${basename(programPath)} \xB7 errors ${first} \u2192 ${before}
|
|
233
219
|
`);
|
|
234
220
|
return compileOnce(programPath, explicitFlats, out, checkOnly, assetMode, noLibs, false);
|
|
@@ -579,4 +565,4 @@ function run(argv) {
|
|
|
579
565
|
export {
|
|
580
566
|
run
|
|
581
567
|
};
|
|
582
|
-
//# sourceMappingURL=chunk-
|
|
568
|
+
//# sourceMappingURL=chunk-P2XIV5B3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/flatc.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// flatc — the \"modern SWF\" command-line compiler.\n//\n// The VSCode-first flow: you write a `.flatink` program (composition + logic, DSL)\n// + `.flat` asset libs (visuals, exported by the editor) + media, then:\n//\n// flatc game.flatink hero.flat decor.flat -o game.flatpack\n//\n// → a single `.flatpack` (the baked Doc as JSON) that the player runs. Media referenced\n// by `asset \"id\" \"path\" kind` are EMBEDDED (paths relative to the program).\n// ─────────────────────────────────────────────────────────────────────────────\nimport { readFileSync, writeFileSync, existsSync, readdirSync, watch, mkdirSync, copyFileSync } from 'node:fs'\nimport { resolve, dirname, basename, extname, join, relative, isAbsolute } from 'node:path'\nimport { compileFlatpack, packToJSON, type MediaMap } from '../compile'\nimport { parseProgramFull, parseFlatLib } from '@flatkit/engine/flatFormat'\nimport { hasPackage } from '@flatkit/engine/stdlib'\nimport { parseUnits } from '@flatkit/engine/dsl'\nimport { sanitizeDoc } from '@flatkit/engine/validateDoc'\nimport { unitsToFunctions } from '@flatkit/engine/scriptDoc'\nimport { containerBBox, containerBBoxUnion } from '@flatkit/engine/groups'\nimport { isInstance, isGroup } from '@flatkit/engine/layers'\nimport { IDENTITY } from '@flatkit/engine/transform'\nimport { lintDocReport, docHasErrors } from '../programDoc'\nimport { applyFixes, formatDiagnostics, programDiagnostics, repairLoop } from '../check'\nimport { FlatSyntaxError } from '@flatkit/engine/flatFormat'\nimport { playHeadless, type Gesture } from '@flatkit/player/debug'\nimport type { FuncDef } from '@flatkit/engine/actions'\nimport type { Doc, Instance, Item, Layer, SymbolDef } from '@flatkit/types'\n\nconst MIME: Record<string, string> = {\n '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',\n '.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif',\n '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4',\n // Fonts (RFC 8081 media types) — matches the editor's import (`font/woff2` default, FontFace API).\n '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf', '.otf': 'font/otf',\n // NB: video (.mp4/.webm/…) intentionally omitted — no video runtime in the editor or player yet.\n}\nconst mimeFor = (path: string): string => MIME[extname(path).toLowerCase()] ?? 'application/octet-stream'\n\n/**\n * True if `target` resolves to a file inside `baseDir` (or baseDir itself). Guards against a hostile\n * program escaping its folder via `../` in a media path or a `use` package name — compiling an untrusted\n * `.flatink` must never read (and embed) arbitrary host files.\n */\nfunction isWithin(baseDir: string, target: string): boolean {\n const rel = relative(baseDir, target)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n\nfunction printHelp(): void {\n process.stdout.write(`flatc — compile a FlatInk program into a .flatpack\n\nUsage:\n flatc <program.flatink> [assets.flat …] [-o output.flatpack]\n flatc <program.flatink> --watch\n flatc --play <program.flatink | scene.flatpack> --script <gestures.json>\n flatc --render <program.flatink | scene.flatpack> -o out.png [--frame N] [--at k=v[,k2=v2]] [--scale S]\n flatc --preview <library.flat> [--symbol NAME] [-o out.flatpack | --render -o out.png]\n flatc <library.flat> [more.flat …] --check\n\n program.flatink the program (composition + logic, text DSL)\n assets.flat visual asset libs (default: every .flat in the program's folder)\n -o, --out output file (default: <program>.flatpack — the CANONICAL name, JSON inside)\n --assets MODE media baking: 'inline' (default, base64 in the .flatpack) or 'external'\n (sidecar <out>.assets/ folder; asset.data = relative key — serve the folder and\n play with sameOriginAssetResolver(<flatpackUrl>))\n --check semantic lint only (no .flatpack); exits ≠0 on ERROR (warnings do not stop). Lints a\n program .flatink OR an asset library .flat (per-symbol; several .flat are merged)\n --fix apply the MECHANICAL repairs the diagnostics carry (missing separator, run-on line)\n and re-check, iterating. Writes the file AT MOST ONCE, and never a text it has not\n re-checked; a file it cannot repair is left untouched, mtime included. Implies --check.\n --no-libs do NOT auto-discover the .flat files sitting next to the program (use it in a working\n folder, where a neighbouring scratch file is not a dependency)\n --watch recompile on every change in the folder (agent → player loop)\n --play run the file WITHOUT a canvas, replay --script and print { sends, vars } (JSON)\n --trace (with --play) HUMAN-READABLE log per gesture: emitted sends + variable diff (debug)\n --script <f> JSON gesture script: [{ \"type\": \"down|move|up|cancel\", \"x\", \"y\" }, { \"type\": \"set\", \"name\", \"value\" }, { \"type\": \"wait\", \"frames\": N }, { \"type\": \"wheel\", \"dy\": N }]\n semantic (by NAME, the engine resolves coords): { \"type\": \"drag\", \"source\", \"target\" } · { \"type\": \"tap\", \"target\" }\n · { \"type\": \"scratch\", \"target\" } (sweeps a reveal zone) · { \"type\": \"connect\", \"source\", \"target\" } (pulls a link wire)\n { \"type\": \"key\", \"name\": \"ArrowRight\", \"frames\": N } holds a key (keys.<name> = 1) N steps, then releases it\n \"wait\" lets the simulation run N fixed steps (60 Hz): \"every frame\" + playhead advance like in real playback\n \"expect\" self-verifies: { \"type\": \"expect\", \"sends\": [\"done\"], \"vars\": { \"score\": 3 } } → exits ≠0 on mismatch\n (sends = sequence of names emitted SINCE the last expect; vars = current state). Great in CI.\n --render render a PNG IMAGE (headless skia): see what we draw (positioning)\n --preview wrap ONE symbol of a <library.flat> into a playable Doc (a single centered instance on an\n auto-sized stage) → a .flatpack to drop in the browser player, or a PNG with --render.\n No wrapper .flatink to author by hand. Output defaults to <library>.<symbol>.flatpack/.png\n --symbol NAME (with --preview) which symbol to wrap (default: the first in the lib)\n --bbox all|frame0 (with --preview) auto-size to the UNION over all frames (default 'all', no clipping of\n drifting/rotating/growing motion) or just frame 0 ('frame0', the old behavior)\n --pad N (with --preview) padding in px around the symbol's bounds (default 24)\n --set p=v[,p2=v2] set a SYMBOL's exposed params (works with --preview AND --render); a state param\n takes a state NAME\n or a number (e.g. --set door=open), others take a number → baked into the preview\n --frame N (with --render) target frame (default 0)\n --at k=v[,k2=v2] (with --render) force variables → capture a given state (e.g. a step of an escape)\n --steps N (with --render) run N fixed sim steps (60 Hz, every-frame) BEFORE capture → see a\n stateful act unfold without forcing every derived variable by hand in --at\n --scale S|auto (with --render) resolution factor (default 2); 'auto' picks one from the canvas size\n (enlarges small/thin assets so fine filaments stay legible, large assets stay 1x)\n -h, --help this help\n\nMedia referenced by 'asset \"id\" \"path\" kind' are embedded (paths relative to the program).\n`)\n}\n\n/** How media is baked: `inline` = base64 data-URI in the .flatpack; `external` = relative key + sidecar files. */\ntype AssetMode = 'inline' | 'external'\ntype MediaCopy = { src: string; key: string } // external mode: source file → relative key (forward slashes)\ntype BuildResult = { doc: Doc; flatLibs: number; packages: number; media: number; mediaCopies: MediaCopy[]; src: string }\n\n/**\n * Reads a `.flatink`, resolves libs/packages/media, compiles → standalone Doc. Throws on compile error.\n * `assetMode`: `inline` embeds each media as a base64 data-URI (default); `external` keeps `asset.data` as a\n * relative key (`<assetsDir>/<path>`) and returns the files to copy beside the .flatpack (no base64 bloat).\n */\nfunction buildDocFromProgram(programPath: string, explicitFlats: string[] = [], assetMode: AssetMode = 'inline', assetsDir = '', noLibs = false, srcOverride?: string): BuildResult {\n const baseDir = dirname(programPath)\n // `srcOverride` compiles a CANDIDATE text as if it were the file, without writing it. `--fix` needs it:\n // it must re-check each repair before committing anything, and writing-then-reverting leaves a window\n // where the author's file holds a version we already know we do not want.\n const programSrc = srcOverride ?? readFileSync(programPath, 'utf8')\n const prog = parseProgramFull(programSrc)\n\n // .flat libs: explicit if provided, otherwise auto-discover the .flat files in the program's folder\n // (`--no-libs` opts out — in a working folder, a neighbouring scratch file is not a dependency).\n const flatPaths = new Set<string>(explicitFlats.map((p) => resolve(p)))\n if (!flatPaths.size && !noLibs) for (const f of readdirSync(baseDir).filter((f) => f.endsWith('.flat'))) flatPaths.add(join(baseDir, f))\n\n // PACKAGES: non-stdlib `use \"x\"` → local files inlined (x.flatink = functions, x.flat = symbols).\n const pkgFunctions: FuncDef[] = []\n const localResolved = new Set<string>()\n for (const name of prog.imports ?? []) {\n if (hasPackage(name)) continue // stdlib → left as a reference\n const fink = join(baseDir, name + '.flatink')\n const fflat = join(baseDir, name + '.flat')\n if (!isWithin(baseDir, fink) || !isWithin(baseDir, fflat)) { process.stderr.write(`flatc: package outside the program folder (ignored): \"${name}\"\\n`); continue }\n let found = false\n if (existsSync(fink)) { for (const f of unitsToFunctions(parseUnits(readFileSync(fink, 'utf8')).units)) pkgFunctions.push(f, { ...f, name: `${name}.${f.name}` }); found = true }\n if (existsSync(fflat)) { flatPaths.add(fflat); found = true }\n if (found) localResolved.add(name)\n else process.stderr.write(`flatc: package not found: \"${name}\" (neither stdlib, nor ${name}.flatink / ${name}.flat)\\n`)\n }\n // Parse each lib HERE so a failure names the FILE. Libs are auto-discovered from the folder, so the one\n // that fails is often a neighbour the author never mentioned — a bare \"compile error: …\" sent them hunting\n // through their own program for a fault that was never in it.\n const assetSrcs = [...flatPaths].map((p) => {\n const src = readFileSync(p, 'utf8')\n try { parseFlatLib(src) } catch (e) { throw new Error(`${basename(p)}: ${(e as Error).message} (pass --no-libs to ignore the .flat files sitting next to the program)`, { cause: e }) }\n if (programConstructsIn(src).length) process.stderr.write(`flatc: ${basename(p)}: looks like a PROGRAM saved as .flat — loaded as a symbol library, so its scene/object blocks are ignored. Rename it to .flatink, or pass --no-libs.\\n`)\n return src\n })\n\n // Media: each `asset … \"path\" …` resolved relative to the program. `inline` → base64 data-URI baked in;\n // `external` → `asset.data` becomes a relative key and the file is copied next to the .flatpack.\n const media: MediaMap = {}\n const mediaCopies: MediaCopy[] = []\n for (const a of prog.assets ?? []) {\n const mp = resolve(baseDir, a.data)\n if (!isWithin(baseDir, mp)) { process.stderr.write(`flatc: media outside the program folder (ignored): ${a.data}\\n`); continue }\n if (!existsSync(mp)) { process.stderr.write(`flatc: missing media (ignored): ${a.data}\\n`); continue }\n const mime = mimeFor(mp)\n if (assetMode === 'external') {\n const key = `${assetsDir}/${a.data.replace(/\\\\/g, '/')}` // URL-form relative key (resolved by the host)\n media[a.data] = { mime, data: key }\n mediaCopies.push({ src: mp, key })\n } else {\n media[a.data] = { mime, data: `data:${mime};base64,${readFileSync(mp).toString('base64')}` }\n }\n }\n\n let doc = compileFlatpack(programSrc, assetSrcs, media)\n if (pkgFunctions.length) doc = { ...doc, functions: [...(doc.functions ?? []), ...pkgFunctions] }\n const stdImports = (doc.imports ?? []).filter(hasPackage)\n doc = { ...doc, imports: stdImports.length ? stdImports : undefined }\n // The source travels with the Doc: the source-level passes (`programDiagnostics`) need the author's text,\n // since a parse-level drop and an `object` block binding to nothing leave no trace in the compiled Doc.\n return { doc, flatLibs: flatPaths.size, packages: localResolved.size, media: Object.keys(media).length, mediaCopies, src: programSrc }\n}\n\n/** Compile once (write or --check). Returns the exit code. */\nfunction compileOnce(programPath: string, explicitFlats: string[], out: string, checkOnly: boolean, assetMode: AssetMode = 'inline', noLibs = false, doFix = false): number {\n const outPath = out ? resolve(out) : join(dirname(programPath), basename(programPath, extname(programPath)) + '.flatpack')\n // External mode: sidecar folder next to the .flatpack, e.g. `game.flatpack` → `game.assets/`.\n const assetsDir = assetMode === 'external' ? basename(outPath, extname(outPath)) + '.assets' : ''\n const build = (src?: string) => buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src)\n // `--fix` on a source the parser REJECTS. That case never reaches the diagnostics pass below, and it is\n // exactly where a mechanical repair earns its keep: there is no partial result to work from. Repaired\n // IN MEMORY, one syntax error per turn, so the whole of `--fix` still writes the file at most once.\n let parseRepaired = ''\n if (doFix) {\n let candidate = readFileSync(programPath, 'utf8')\n for (let pass = 0; pass < 10; pass++) {\n try { build(candidate); break } catch (e) {\n if (!(e instanceof FlatSyntaxError) || !e.fix) break\n const { text, applied } = applyFixes(candidate, [{ scope: 'scene', line: e.line, col: e.col, severity: 'error', message: e.message, fix: e.fix }])\n if (!applied) break\n candidate = text\n parseRepaired = candidate\n process.stderr.write(`flatc: --fix: repaired \\`${e.message.split(':')[0]}\\` in ${basename(programPath)}\\n`)\n }\n }\n }\n let built: BuildResult\n try { built = build(parseRepaired || undefined) }\n catch (e) {\n process.stderr.write(`flatc: compile error: ${(e as Error).message}\\n`)\n return 1\n }\n const { doc } = built\n // The SAME pass the `checkProgram` API runs — source-level diagnostics (statements the parser dropped,\n // `object` blocks binding to nothing) merged with the semantic lint of the Doc. Shared on purpose: a\n // check that lives only in the CLI is a check an integrator has to shell out for.\n const diagnostics = programDiagnostics(doc, built.src)\n const report = formatDiagnostics(diagnostics)\n const hasErrors = diagnostics.some((d) => d.severity === 'error')\n // `--fix` applies the MECHANICAL repairs the diagnostics carry (a missing separator, a run-on line),\n // then re-checks. Two rules make it safe to run unattended:\n // • it ITERATES, because repairing one error unmasks the next — a run-on interactor line swallows the\n // statements under it, and their own diagnostics only appear once it is split;\n // • it writes only if the error count strictly DROPS at each pass. An auto-fix that makes things worse\n // must not survive, and the honest way to know is to run the same check again on the result.\n if (doFix) {\n // START from the text the diagnostics were computed on. Falling back to the file would apply edits\n // whose line/col were measured against the parse-repaired text -- a comma insertion survives that,\n // a multi-line replacement does not.\n const original = parseRepaired || readFileSync(programPath, 'utf8')\n const { text, applied: total, first, errors: before } = repairLoop(original, diagnostics, (src) =>\n programDiagnostics(buildDocFromProgram(programPath, explicitFlats, assetMode, assetsDir, noLibs, src).doc, src))\n if (!total && !parseRepaired) {\n // NOT even a rewrite of the identical bytes: a new mtime wakes every watcher pointed at the folder,\n // and a write fails outright on a read-only checkout we had no reason to touch.\n process.stderr.write(`flatc: --fix: nothing to repair mechanically${hasErrors ? ' — the remaining errors need a decision, not a separator\\n' : '\\n'}`)\n } else {\n writeFileSync(programPath, text) // ONE write, of a text this loop has already re-checked\n process.stderr.write(`flatc: --fix: ${total} repair(s) applied to ${basename(programPath)} · errors ${first} → ${before}\\n`)\n return compileOnce(programPath, explicitFlats, out, checkOnly, assetMode, noLibs, false)\n }\n }\n if (checkOnly) {\n if (report) process.stderr.write(report + '\\n')\n if (hasErrors) return 1\n // Success line deliberately avoids the word \"error\" (the report on a FAILURE already prints \"error\" to\n // stderr + exits ≠0) so a `grep error` over the output can't false-positive — the real signal is the exit\n // code; on success, only warnings remain (non-blocking), surfaced as a count.\n const warnings = diagnostics.length\n process.stdout.write(`flatc: check passed ✓${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n }\n if (report) process.stderr.write(report + '\\n') // compile anyway: diagnostics as warnings\n writeFileSync(outPath, packToJSON(doc))\n const outDir = dirname(outPath)\n for (const c of built.mediaCopies) {\n const dest = join(outDir, ...c.key.split('/'))\n if (!isWithin(outDir, dest)) continue // defensive: never write outside the output folder\n mkdirSync(dirname(dest), { recursive: true })\n copyFileSync(c.src, dest)\n }\n const where = assetMode === 'external' ? ` (external → ${assetsDir}/)` : ''\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.symbols.length} symbol(s) · ${built.flatLibs} lib(s) · ${built.packages} package(s) · ${built.media} media${where}\\n`)\n return 0\n}\n\n/** PROGRAM-only constructs found in what is supposed to be a `.flat` symbol library. `parseFlatLib` reads a\n * `.flat` as a bag of symbols and silently ignores everything else, so a whole program saved under the wrong\n * extension parsed to \"0 symbol(s)\" and passed `--check` with nothing verified — the worst possible answer\n * for tooling, since a check written against the wrong extension ALWAYS passes and looks like a safety net.\n * Both constructs are anchored at line start: only a real block matches, not a mention inside a string. */\nfunction programConstructsIn(src: string): string[] {\n const found: string[] = []\n if (/^[ \\t]*scene\\s*\\{/m.test(src)) found.push('a `scene { … }` block')\n if (/^[ \\t]*object\\s+\"/m.test(src)) found.push('`object \"…\" { … }` blocks')\n return found\n}\n\n/**\n * `--check` for a `.flat` asset LIBRARY (one or several): parse via `parseFlatLib` (NOT the program parser,\n * which would choke on `symbol`/`params`/`layer`), merge the symbols into an empty-scene Doc, and run the SAME\n * Doc lint as a program -> per-symbol diagnostics (`[<Symbol>] line:col: …`). Reuses `lintDocReport`/\n * `docHasErrors`, so every existing check (params-in-expr, undeclared color param in a paint, unknown\n * functions/objects…) applies for free, with the same `[scope] line:col: level: msg` format and exit code.\n * No `behaviorDiagnostics` (those are scene-level — a pure lib has no scene).\n */\nfunction checkFlatLibs(flatPaths: string[]): number {\n const symbols: SymbolDef[] = []\n for (const p of flatPaths) {\n let src: string\n try { src = readFileSync(p, 'utf8') } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n const disguised = programConstructsIn(src)\n if (disguised.length) { // a PROGRAM saved as `.flat` — see programConstructsIn\n process.stderr.write(`flatc: ${basename(p)}: this is a PROGRAM, not a symbol library — it contains ${disguised.join(' and ')}, which a .flat never carries and which --check therefore does NOT verify. Rename it to .flatink.\\n`)\n return 1\n }\n try { symbols.push(...parseFlatLib(src).symbols) } // ids are uid-unique across calls -> safe to merge libs\n catch (e) { process.stderr.write(`flatc: ${basename(p)}: ${(e as Error).message}\\n`); return 1 } // a malformed lib -> a clean parse error (not \"[scene] …\")\n }\n const doc: Doc = { width: 1, height: 1, timeline: { fps: 24, durationFrames: 1, tracks: [] }, variables: {}, layers: [], symbols }\n const report = lintDocReport(doc) // scene scope is empty -> only per-symbol diagnostics\n if (report) process.stderr.write(report + '\\n')\n if (docHasErrors(doc)) return 1\n // \"check passed\", NOT \"no errors\" — the word \"error\" must not appear on success (a `grep error` trap); the\n // exit code is the signal, and any remaining lines are warnings (non-blocking), reported as a count.\n const warnings = report ? report.split('\\n').filter(Boolean).length : 0\n process.stdout.write(`flatc: check passed ✓ ${symbols.length} symbol(s)${warnings ? ` · ${warnings} warning(s)` : ''}\\n`)\n return 0\n}\n\n/** Loads a Doc from a `.flatink` (compiled) or a `.flatpack`/`.flatpack.json` (already-baked JSON). */\nfunction loadDoc(filePath: string, noLibs = false): Doc {\n if (filePath.endsWith('.flatink')) return buildDocFromProgram(filePath, [], 'inline', '', noLibs).doc\n // .flatpack (canonical) or .flatpack.json (alias): untrusted JSON → normalize before use.\n return sanitizeDoc(JSON.parse(readFileSync(filePath, 'utf8')))\n}\n\n/** --play: runs the file headless, replays --script, prints { sends, vars }. */\nfunction playOnce(filePath: string, scriptPath: string, trace: boolean, noLibs = false): number {\n if (!scriptPath) { process.stderr.write('flatc: --play requires --script <gestures.json>\\n'); return 1 }\n if (!existsSync(scriptPath)) { process.stderr.write(`flatc: script not found: ${scriptPath}\\n`); return 1 }\n let doc: Doc, gestures: Gesture[]\n try { doc = loadDoc(filePath, noLibs) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n try { gestures = JSON.parse(readFileSync(scriptPath, 'utf8')) as Gesture[] } catch (e) { process.stderr.write(`flatc: invalid JSON script: ${(e as Error).message}\\n`); return 1 }\n if (!Array.isArray(gestures)) { process.stderr.write('flatc: the script must be an array of gestures\\n'); return 1 }\n const res = playHeadless(doc, gestures, { trace })\n if (!trace) process.stdout.write(JSON.stringify(res, null, 2) + '\\n')\n else // --trace: readable log (one gesture per line) → inspection / debug-player.\n for (const s of res.steps ?? []) {\n // `name` · `name=value` · `name{a=1, b=2}` (record payload) — the three shapes the host receives.\n const send = (e: { name: string; value?: number | string; fields?: Record<string, number> }): string =>\n e.fields ? `${e.name}{${Object.entries(e.fields).map(([k, v]) => `${k}=${v}`).join(', ')}}`\n : e.value !== undefined ? `${e.name}=${e.value}` : e.name\n const sends = s.sends.length ? ' sends:[' + s.sends.map(send).join(', ') + ']' : ''\n const vars = Object.entries(s.changed).map(([k, [a, b]]) => `${k}:${JSON.stringify(a)}→${JSON.stringify(b)}`)\n process.stdout.write(`${s.gesture.padEnd(28)}${sends}${vars.length ? ' vars{' + vars.join(' ') + '}' : ''}\\n`)\n }\n // `expect`: any detected mismatch → stderr log + exit code ≠0 (CI self-verification).\n if (res.expectFailures?.length) {\n for (const f of res.expectFailures) process.stderr.write(`flatc: ✗ ${f}\\n`)\n return 1\n }\n return 0\n}\n\n/** Parses `--at`: \"k=v[,k2=v2]\" → variable table (state override for rendering). */\nfunction parseVars(spec: string, into: Record<string, number>): void {\n for (const pair of spec.split(',')) {\n const i = pair.indexOf('=')\n if (i < 0) continue\n const k = pair.slice(0, i).trim(); const v = Number(pair.slice(i + 1).trim())\n if (k && Number.isFinite(v)) into[k] = v\n }\n}\n\n/** `--scale auto`: pick a resolution factor from the canvas size — enlarge small/thin content (so fine\n * filaments stay legible) while leaving large content at 1×. `max(160/long, 48/short)`, clamped [1, 8]. */\nfunction autoScale(w: number, h: number): number {\n const maxSide = Math.max(1, w, h), minSide = Math.max(1, Math.min(w, h))\n return Math.max(1, Math.min(8, Math.round(Math.max(160 / maxSide, 48 / minSide))))\n}\n\n/** Renders a Doc to a PNG file (headless skia). Async (SVG decode + raster). Shared by --render and --preview. */\nasync function renderDocToFile(doc: Doc, outPath: string, frame: number, vars: Record<string, number>, scale: number, steps: number, params: Record<string, string> = {}): Promise<number> {\n try {\n const { renderDocToPng } = await import('./render')\n const png = await renderDocToPng(doc, { frame, vars: Object.keys(vars).length ? vars : undefined, scale, steps: steps || undefined, params: Object.keys(params).length ? params : undefined })\n writeFileSync(outPath, png)\n } catch (e) {\n process.stderr.write(`flatc: render failed: ${(e as Error).message}\\n`)\n if (process.env.FLATC_DEBUG && (e as Error).stack) process.stderr.write(`${(e as Error).stack}\\n`) // FLATC_DEBUG=1 → full stack for diagnosis\n return 1\n }\n process.stdout.write(`flatc: ${basename(outPath)} ✓ ${doc.width}×${doc.height} ×${scale}${frame ? ` · frame ${frame}` : ''}${steps ? ` · ${steps} step(s)` : ''}${Object.keys(vars).length ? ` · ${Object.entries(vars).map(([k, v]) => `${k}=${v}`).join(' ')}` : ''}\\n`)\n return 0\n}\n\n/** --render: renders the file to PNG (headless skia). Async (SVG decode + raster). */\nasync function renderOnce(filePath: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, scaleAuto: boolean, noLibs = false, params: Record<string, string> = {}): Promise<number> {\n let doc: Doc\n // `--no-libs` was parsed and then not passed on, so `--render` still auto-discovered the neighbouring\n // .flat files -- and failed on one, advising the very flag that had been given. Same hole in `--play`.\n try { doc = loadDoc(filePath, noLibs) } catch (e) { process.stderr.write(`flatc: cannot read: ${(e as Error).message}\\n`); return 1 }\n // A frame past the end WRAPS silently, so a render can show frame 20 while the caller asked for 200\n // and reads the result as a renderer bug. It cost exactly that once.\n const dur = doc.timeline?.durationFrames ?? 0\n if (dur > 0 && frame >= dur) process.stderr.write(`flatc: --frame ${frame} is past the timeline (durationFrames ${dur}); the playhead WRAPS, so this renders frame ${frame % dur}\\n`)\n const outPath = out ? resolve(out) : join(dirname(filePath), basename(filePath, extname(filePath)) + '.png')\n return renderDocToFile(doc, outPath, frame, vars, scaleAuto ? autoScale(doc.width, doc.height) : scale, steps, params)\n}\n\nconst gcd = (a: number, b: number): number => { a = Math.abs(a); b = Math.abs(b); while (b) { const t = a % b; a = b; b = t } return a || 1 }\nconst lcm = (a: number, b: number): number => Math.max(1, Math.round((a / gcd(a, b)) * b))\n\n/** Every instance in a symbol's subtree (descends into local groups; does NOT cross into other symbols). */\nfunction* instancesOf(sym: SymbolDef): Generator<Instance> {\n const stack: Item[] = []\n for (const l of sym.layers) stack.push(...l.items)\n for (let it = stack.pop(); it; it = stack.pop()) {\n if (isInstance(it)) yield it\n else if (isGroup(it)) for (const l of it.layers) stack.push(...l.items)\n }\n}\n\n/**\n * Preview window length (frames). A `synced` instance loops on the PARENT's frame, so the symbol's own\n * duration suffices — that's the historical behavior, kept for every symbol with no MovieClip descendant.\n * But an `independent` (`loop`) descendant runs on its OWN duration, immune to the parent's wrap: to SHOW it\n * loop CLEANLY the window must be a common multiple of every such clip's duration (and the root's) — its\n * \"loop seam\". `once` clips only need the window to REACH their last frame (they hold afterwards). We walk\n * the subtree, fold each independent clip's duration into the seam (LCM), and extend past the longest clip,\n * keeping the window a whole number of seams. Capped, so coprime durations can't blow the window up.\n */\nfunction previewDuration(root: SymbolDef, symbols: SymbolDef[]): number {\n const byId = new Map(symbols.map((s) => [s.id, s]))\n const seen = new Set<string>()\n let seam = Math.max(1, root.timeline?.durationFrames ?? 1) // clean-loop period (multiple of every loop clip)\n let longest = seam // window must reach the last frame of any clip\n const walk = (sym: SymbolDef | undefined): void => {\n if (!sym || seen.has(sym.id)) return\n seen.add(sym.id)\n for (const it of instancesOf(sym)) {\n const child = byId.get(it.symbolId)\n const cdur = Math.max(1, child?.timeline?.durationFrames ?? 1)\n const mode = it.playback?.mode\n if (mode === 'independent') { seam = lcm(seam, cdur); longest = Math.max(longest, cdur) }\n else if (mode === 'once') longest = Math.max(longest, cdur)\n walk(child)\n }\n }\n walk(root)\n let dur = seam\n while (dur < longest) dur += seam // keep a clean loop seam while covering the longest MovieClip\n return Math.min(dur, 6000) // hard cap (~4 min @24fps) against a pathological coprime-duration blow-up\n}\n\n/**\n * Wraps ONE symbol from a `.flat` library into a minimal, playable Doc: a single centered instance on a\n * canvas auto-sized to the symbol's frame-0 bounds (+ padding). Lets you preview a library asset without\n * hand-authoring a wrapper `.flatink`. The root timeline borrows the symbol's own fps and a duration long\n * enough to show every nested MovieClip (`loop`/`once`) loop cleanly (`previewDuration`). Throws if the lib\n * is empty or the named symbol is missing.\n */\nfunction buildPreviewDoc(flatPath: string, symbolName: string, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string> = {}): { doc: Doc; symbol: SymbolDef; others: string[] } {\n const { symbols } = parseFlatLib(readFileSync(flatPath, 'utf8'))\n if (!symbols.length) throw new Error('no symbols found in this .flat library')\n let symbol = symbols[0]\n if (symbolName) {\n const found = symbols.find((s) => s.name === symbolName)\n if (!found) throw new Error(`symbol \"${symbolName}\" not found — available: ${symbols.map((s) => s.name).join(', ')}`)\n symbol = found\n }\n\n const instance: Instance = { id: 'preview_instance', kind: 'instance', name: symbol.name, transform: { ...IDENTITY }, symbolId: symbol.id }\n const layer: Layer = { id: 'preview_layer', name: 'preview', visible: true, locked: false, opacity: 1, items: [instance] }\n const tl = symbol.timeline\n const dur = previewDuration(symbol, symbols)\n const timeline = { fps: tl?.fps ?? 24, durationFrames: dur, tracks: [] }\n // ALL symbols stay in the library (the chosen one may instance the others) — only `instance` is on stage.\n let doc: Doc = { width: 1, height: 1, layers: [layer], symbols, timeline }\n\n // Auto-size & center. Default `--bbox all`: UNION over every frame of the symbol (sub-timelines NOT\n // frozen) so motion that drifts/rotates/grows is never clipped. `--bbox frame0` keeps the old frame-0\n // measure (+`--pad` to absorb motion).\n // Sample at most ~240 evenly-spaced frames (+ the last) so a long/abusive `durationFrames` can't blow\n // up memory/time; a sparse union is virtually as tight as every-frame for real motion.\n const sampleFrames = (n: number): number[] => {\n const step = Math.max(1, Math.ceil(n / 240))\n const fs: number[] = []\n for (let f = 0; f < n; f += step) fs.push(f)\n if (fs[fs.length - 1] !== n - 1) fs.push(n - 1)\n return fs\n }\n const bb = bboxMode === 'all' ? containerBBoxUnion(doc, instance, sampleFrames(dur)) : containerBBox(doc, instance, 0)\n if (bb) {\n doc = { ...doc, width: Math.max(1, Math.ceil(bb.maxX - bb.minX) + pad * 2), height: Math.max(1, Math.ceil(bb.maxY - bb.minY) + pad * 2) }\n instance.transform = { ...IDENTITY, e: pad - bb.minX, f: pad - bb.minY }\n } else {\n doc = { ...doc, width: 512, height: 512 } // empty/degenerate symbol: fall back to a fixed centered stage\n instance.transform = { ...IDENTITY, e: 256, f: 256 }\n }\n\n // `--set param=value`: set the preview instance's exposed params (call-site values, raw literals). The\n // renderer resolves them per the symbol's ParamDef / state machines (color → fill, number/bool → scope,\n // state name → driven frame). A light validation warns on names the symbol does not expose.\n const entries = Object.entries(setSpec)\n if (entries.length) {\n const known = new Set([...(symbol.states ?? []).map((s) => s.param), ...(symbol.params ?? []).map((p) => p.name)])\n for (const [k, v] of entries) {\n if (!known.has(k)) process.stderr.write(`flatc: --set ${k}: \"${symbol.name}\" exposes no such param (${[...known].join(', ') || 'none'})\\n`)\n else (instance.params ??= {})[k] = v\n }\n }\n return { doc, symbol, others: symbols.filter((s) => s !== symbol).map((s) => s.name) }\n}\n\n/** --preview: wraps one symbol of a `.flat` into a playable Doc, then writes a .flatpack (default) or a PNG (with --render). */\nasync function previewOnce(flatPath: string, symbolName: string, out: string, frame: number, vars: Record<string, number>, scale: number, steps: number, doRender: boolean, pad: number, bboxMode: 'all' | 'frame0', setSpec: Record<string, string>, scaleAuto: boolean): Promise<number> {\n let built: { doc: Doc; symbol: SymbolDef; others: string[] }\n try { built = buildPreviewDoc(flatPath, symbolName, pad, bboxMode, setSpec) }\n catch (e) { process.stderr.write(`flatc: ${(e as Error).message}\\n`); return 1 }\n const { doc, symbol, others } = built\n const stem = `${basename(flatPath, extname(flatPath))}.${symbol.name}`\n let code: number\n if (doRender) {\n // auto scale from the CONTENT size (minus the padding), so thin filaments still trigger an upscale.\n const sc = scaleAuto ? autoScale(doc.width - 2 * pad, doc.height - 2 * pad) : scale\n code = await renderDocToFile(doc, out ? resolve(out) : join(dirname(flatPath), stem + '.png'), frame, vars, sc, steps)\n } else {\n const outPath = out ? resolve(out) : join(dirname(flatPath), stem + '.flatpack')\n writeFileSync(outPath, packToJSON(doc))\n process.stdout.write(`flatc: ${basename(outPath)} ✓ preview of \"${symbol.name}\" · ${doc.width}×${doc.height} · play it in the browser player\\n`)\n code = 0\n }\n if (!symbolName && others.length) process.stderr.write(`flatc: previewed \"${symbol.name}\"; other symbols in this lib: ${others.join(', ')} (pick with --symbol <name>)\\n`)\n return code\n}\n\nexport function run(argv: string[]): number | Promise<number> {\n const args = argv.slice(2)\n let out = '', scriptPath = '', symbolName = ''\n let checkOnly = false, doFix = false, doWatch = false, doPlay = false, doRender = false, doTrace = false, doPreview = false, noLibs = false\n let frame = 0, scale = 2, steps = 0, pad = 24\n let scaleAuto = false\n let bboxMode: 'all' | 'frame0' = 'all'\n let assetMode: AssetMode = 'inline'\n const vars: Record<string, number> = {}\n const setSpec: Record<string, string> = {} // `--set param=value` (state name or number) for --preview\n const positional: string[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i]\n if (a === '-o' || a === '--out') out = args[++i] ?? ''\n else if (a === '--script') scriptPath = resolve(args[++i] ?? '')\n else if (a === '--assets') assetMode = args[++i] === 'external' ? 'external' : 'inline'\n else if (a === '--check') checkOnly = true\n else if (a === '--fix') { doFix = true; checkOnly = true }\n else if (a === '--no-libs') noLibs = true\n else if (a === '--watch') doWatch = true\n else if (a === '--play') doPlay = true\n else if (a === '--trace') doTrace = true\n else if (a === '--render') doRender = true\n else if (a === '--preview') doPreview = true\n else if (a === '--symbol') symbolName = args[++i] ?? ''\n else if (a === '--pad') pad = Math.max(0, Number(args[++i] ?? '24') || 0)\n else if (a === '--bbox') bboxMode = args[++i] === 'frame0' ? 'frame0' : 'all'\n else if (a === '--frame') frame = Number(args[++i] ?? '0') || 0\n else if (a === '--steps') steps = Math.max(0, Number(args[++i] ?? '0') || 0)\n else if (a === '--scale') { const v = args[++i] ?? '2'; if (v === 'auto') scaleAuto = true; else scale = Number(v) || 2 }\n else if (a === '--at') parseVars(args[++i] ?? '', vars)\n else if (a === '--set') for (const pair of (args[++i] ?? '').split(',')) { const j = pair.indexOf('='); if (j > 0) setSpec[pair.slice(0, j).trim()] = pair.slice(j + 1).trim() }\n else if (a === '-h' || a === '--help') { printHelp(); return 0 }\n else positional.push(a)\n }\n if (!positional.length) { printHelp(); return 1 }\n\n const filePath = resolve(positional[0])\n if (!existsSync(filePath)) { process.stderr.write(`flatc: not found: ${filePath}\\n`); return 1 }\n\n const explicitFlats = positional.slice(1)\n if (doPreview) return previewOnce(filePath, symbolName, out, frame, vars, scale, steps, doRender, pad, bboxMode, setSpec, scaleAuto)\n if (doRender) return renderOnce(filePath, out, frame, vars, scale, steps, scaleAuto, noLibs, setSpec)\n if (doPlay) return playOnce(filePath, scriptPath, doTrace, noLibs)\n // `--check <library>.flat`: a `.flat` first positional is an asset LIB, not a program → lint via parseFlatLib\n // (the following positionals are more `.flat` libs to merge). Every other path is unchanged.\n const action: () => number = checkOnly && filePath.endsWith('.flat')\n ? () => checkFlatLibs([filePath, ...explicitFlats])\n : () => compileOnce(filePath, explicitFlats, out, checkOnly, assetMode, noLibs, doFix)\n if (doWatch) {\n const code = action()\n const baseDir = dirname(filePath)\n let timer: ReturnType<typeof setTimeout> | undefined\n // We ignore changes to the OUTPUT (.flatpack) — otherwise writing it would re-trigger the compile in a loop.\n watch(baseDir, { recursive: false }, (_e, filename) => {\n if (filename && (filename.endsWith('.flatpack') || filename.endsWith('.flatpack.json'))) return\n clearTimeout(timer); timer = setTimeout(action, 80)\n })\n process.stdout.write(`flatc: watching ${baseDir} … (Ctrl+C to stop)\\n`)\n return code\n }\n return action()\n}\n"],"mappings":"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","names":["f"]}
|
|
@@ -539,6 +539,14 @@ function estTextWidth(t) {
|
|
|
539
539
|
for (const line of t.content.split("\n")) longest = Math.max(longest, line.length);
|
|
540
540
|
return longest * advance;
|
|
541
541
|
}
|
|
542
|
+
function inkBBox(doc, it) {
|
|
543
|
+
const box = itemBBox(doc, it);
|
|
544
|
+
if (!box || !isText(it) || it.wrap || it.textPath) return box;
|
|
545
|
+
const estW = estTextWidth(it);
|
|
546
|
+
if (estW >= it.box.w) return box;
|
|
547
|
+
const left = it.align === "left" ? 0 : it.align === "right" ? it.box.w - estW : it.box.w / 2 - estW / 2;
|
|
548
|
+
return transformBBox({ minX: left, minY: 0, maxX: left + estW, maxY: it.box.h }, it.transform);
|
|
549
|
+
}
|
|
542
550
|
function wrapMetrics(t) {
|
|
543
551
|
const advance = glyphAdvance(t);
|
|
544
552
|
const width = (s) => s.length * advance;
|
|
@@ -574,7 +582,7 @@ function docLayoutWarnings(doc) {
|
|
|
574
582
|
collect(doc.layers, IDENTITY, false);
|
|
575
583
|
for (const { it, parent, dynamic } of placed) {
|
|
576
584
|
if (dynamic || isText(it) && it.textPath) continue;
|
|
577
|
-
const local =
|
|
585
|
+
const local = inkBBox(doc, it);
|
|
578
586
|
if (!local) continue;
|
|
579
587
|
const b = transformBBox(local, parent);
|
|
580
588
|
const visible = b.maxX > 0 && b.minX < W && b.maxY > 0 && b.minY < H;
|
|
@@ -591,15 +599,31 @@ function docLayoutWarnings(doc) {
|
|
|
591
599
|
const p0 = apply(m, { x: left, y: 0 }), p1 = apply(m, { x: left + estW, y: 0 });
|
|
592
600
|
const wl = Math.min(p0.x, p1.x), wr = Math.max(p0.x, p1.x);
|
|
593
601
|
if (wr > W + TOL || wl < -TOL) {
|
|
594
|
-
|
|
602
|
+
const oneWord = !/\s/.test(it.content.trim());
|
|
603
|
+
const advice = oneWord ? " \u2014 `wrap` cannot help: it breaks at spaces, and this is a single word. Intentional if the bleed is the point; otherwise reduce the size or move it in" : ' \u2014 add "wrap"';
|
|
604
|
+
out.push(warn(`text "${it.content.slice(0, 24)}${it.content.length > 24 ? "\u2026" : ""}" overflows the canvas (estimated ~${r0(estW)} px, edge ${r0(wl)}->${r0(wr)} outside 0->${W})${advice}`));
|
|
595
605
|
}
|
|
596
606
|
}
|
|
597
607
|
const parked = (b) => b.maxX < 0 && b.maxY < 0;
|
|
598
608
|
const offCanvas = (b) => (b.maxX < -TOL || b.minX > W + TOL || b.maxY < -TOL || b.minY > H + TOL) && !parked(b);
|
|
609
|
+
const moving = /* @__PURE__ */ new Set();
|
|
610
|
+
const markMoving = (items) => {
|
|
611
|
+
let any = false;
|
|
612
|
+
for (const it of items) {
|
|
613
|
+
const inside = isGroup2(it) && it.layers.some((l) => markMoving(l.items));
|
|
614
|
+
if (dynamicPos(it) || inside) {
|
|
615
|
+
moving.add(it.id);
|
|
616
|
+
any = true;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return any;
|
|
620
|
+
};
|
|
621
|
+
for (const l of doc.layers) markMoving(l.items);
|
|
622
|
+
const movesADescendant = (it) => moving.has(it.id);
|
|
599
623
|
const reportOffCanvas = (items, matrix, dynamic) => {
|
|
600
624
|
for (const it of items) {
|
|
601
625
|
const isDynamic = dynamic || dynamicPos(it);
|
|
602
|
-
if (!isDynamic && !(isText(it) && it.textPath)) {
|
|
626
|
+
if (!isDynamic && !movesADescendant(it) && !(isText(it) && it.textPath)) {
|
|
603
627
|
const local = itemBBox(doc, it);
|
|
604
628
|
const b = local && transformBBox(local, matrix);
|
|
605
629
|
if (b && offCanvas(b)) {
|
|
@@ -615,9 +639,9 @@ function docLayoutWarnings(doc) {
|
|
|
615
639
|
if (!isText(it) || !it.wrap || it.textPath) continue;
|
|
616
640
|
const { lines, widestWord } = wrapMetrics(it);
|
|
617
641
|
const label = `${it.content.slice(0, 24).replace(/\n/g, " ")}${it.content.length > 24 ? "\u2026" : ""}`;
|
|
618
|
-
const tall = lines * it.size * it.lineHeight;
|
|
642
|
+
const tall = (lines - 1) * it.size * it.lineHeight;
|
|
619
643
|
if (tall > it.box.h + TOL) {
|
|
620
|
-
out.push(warn(`text "${label}" overflows its box: ~${lines} wrapped line(s) \u2248 ${r0(
|
|
644
|
+
out.push(warn(`text "${label}" overflows its box: ~${lines} wrapped line(s) \u2248 ${r0(lines * it.size * it.lineHeight)} px for a box ${r0(it.box.h)} px tall \u2014 raise the box height, shorten the text, or reduce the size`));
|
|
621
645
|
}
|
|
622
646
|
if (widestWord > it.box.w + TOL) {
|
|
623
647
|
out.push(warn(`text "${label}" has a word too wide for its box (~${r0(widestWord)} px > ${r0(it.box.w)} px) \u2014 a single word cannot be broken, so it spills past the frame whatever "wrap" says`));
|
|
@@ -776,7 +800,9 @@ function applyFixes(src, diagnostics) {
|
|
|
776
800
|
let applied = 0;
|
|
777
801
|
const touched = /* @__PURE__ */ new Set();
|
|
778
802
|
for (const e of edits) {
|
|
779
|
-
if (e.line
|
|
803
|
+
if (!Number.isInteger(e.line) || !Number.isInteger(e.endLine) || !Number.isInteger(e.col) || !Number.isInteger(e.endCol)) continue;
|
|
804
|
+
if (e.line < 1 || e.col < 1 || e.endCol < 1 || e.endLine > lines.length) continue;
|
|
805
|
+
if (e.endLine < e.line || e.endLine === e.line && e.endCol < e.col) continue;
|
|
780
806
|
let overlaps = false;
|
|
781
807
|
for (let l = e.line; l <= e.endLine; l++) if (touched.has(l)) overlaps = true;
|
|
782
808
|
if (overlaps) continue;
|
|
@@ -790,6 +816,27 @@ function applyFixes(src, diagnostics) {
|
|
|
790
816
|
}
|
|
791
817
|
return { text: lines.join("\n"), applied };
|
|
792
818
|
}
|
|
819
|
+
function repairLoop(src, diagnostics, recheck, maxPasses = 5) {
|
|
820
|
+
const errs = (ds) => ds.filter((d) => d.severity === "error").length;
|
|
821
|
+
const first = errs(diagnostics);
|
|
822
|
+
let text = src, applied = 0, errors = first, current = diagnostics;
|
|
823
|
+
for (let pass = 0; pass < maxPasses; pass++) {
|
|
824
|
+
const step = applyFixes(text, current);
|
|
825
|
+
if (!step.applied) break;
|
|
826
|
+
let after;
|
|
827
|
+
try {
|
|
828
|
+
after = recheck(step.text);
|
|
829
|
+
} catch {
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
if (errs(after) >= errors) break;
|
|
833
|
+
text = step.text;
|
|
834
|
+
applied += step.applied;
|
|
835
|
+
errors = errs(after);
|
|
836
|
+
current = after;
|
|
837
|
+
}
|
|
838
|
+
return { text, applied, first, errors };
|
|
839
|
+
}
|
|
793
840
|
|
|
794
841
|
export {
|
|
795
842
|
splitScopeProgram,
|
|
@@ -808,6 +855,7 @@ export {
|
|
|
808
855
|
docStructureWarnings,
|
|
809
856
|
docCelLayerWarnings,
|
|
810
857
|
docPaintParamWarnings,
|
|
858
|
+
wrapMetrics,
|
|
811
859
|
docLayoutWarnings,
|
|
812
860
|
lintDoc,
|
|
813
861
|
docModifierWarnings,
|
|
@@ -816,6 +864,7 @@ export {
|
|
|
816
864
|
formatDiagnostics,
|
|
817
865
|
programDiagnostics,
|
|
818
866
|
checkProgram,
|
|
819
|
-
applyFixes
|
|
867
|
+
applyFixes,
|
|
868
|
+
repairLoop
|
|
820
869
|
};
|
|
821
|
-
//# sourceMappingURL=chunk-
|
|
870
|
+
//# sourceMappingURL=chunk-UO7ZHNQT.js.map
|