@horizon36596/zenith-cli 0.1.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/LICENSE +21 -0
- package/NOTICE +18 -0
- package/README.md +207 -0
- package/dist/autoLoad.d.ts +54 -0
- package/dist/autoLoad.d.ts.map +1 -0
- package/dist/autoLoad.js +139 -0
- package/dist/autoLoad.js.map +1 -0
- package/dist/calibrateFit.d.ts +38 -0
- package/dist/calibrateFit.d.ts.map +1 -0
- package/dist/calibrateFit.js +69 -0
- package/dist/calibrateFit.js.map +1 -0
- package/dist/commandLibrary.d.ts +19 -0
- package/dist/commandLibrary.d.ts.map +1 -0
- package/dist/commandLibrary.js +27 -0
- package/dist/commandLibrary.js.map +1 -0
- package/dist/commands/calibrate.d.ts +18 -0
- package/dist/commands/calibrate.d.ts.map +1 -0
- package/dist/commands/calibrate.js +116 -0
- package/dist/commands/calibrate.js.map +1 -0
- package/dist/commands/codegen.d.ts +12 -0
- package/dist/commands/codegen.d.ts.map +1 -0
- package/dist/commands/codegen.js +63 -0
- package/dist/commands/codegen.js.map +1 -0
- package/dist/commands/deploy.d.ts +63 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +181 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/diff.d.ts +11 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +68 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/estimate.d.ts +9 -0
- package/dist/commands/estimate.d.ts.map +1 -0
- package/dist/commands/estimate.js +81 -0
- package/dist/commands/estimate.js.map +1 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +67 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/new.d.ts +10 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +28 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/propose.d.ts +15 -0
- package/dist/commands/propose.d.ts.map +1 -0
- package/dist/commands/propose.js +67 -0
- package/dist/commands/propose.js.map +1 -0
- package/dist/commands/render.d.ts +16 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +90 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/commands/sim.d.ts +31 -0
- package/dist/commands/sim.d.ts.map +1 -0
- package/dist/commands/sim.js +213 -0
- package/dist/commands/sim.js.map +1 -0
- package/dist/commands/validate.d.ts +19 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +97 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/coreGate.d.ts +19 -0
- package/dist/coreGate.d.ts.map +1 -0
- package/dist/coreGate.js +23 -0
- package/dist/coreGate.js.map +1 -0
- package/dist/fieldImage.d.ts +19 -0
- package/dist/fieldImage.d.ts.map +1 -0
- package/dist/fieldImage.js +63 -0
- package/dist/fieldImage.js.map +1 -0
- package/dist/findings.d.ts +20 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +57 -0
- package/dist/findings.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +194 -0
- package/dist/index.js.map +1 -0
- package/dist/javaCodegen.d.ts +44 -0
- package/dist/javaCodegen.d.ts.map +1 -0
- package/dist/javaCodegen.js +537 -0
- package/dist/javaCodegen.js.map +1 -0
- package/dist/launcher.d.ts +39 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +153 -0
- package/dist/launcher.js.map +1 -0
- package/dist/lib.d.ts +13 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +13 -0
- package/dist/lib.js.map +1 -0
- package/dist/project.d.ts +33 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +104 -0
- package/dist/project.js.map +1 -0
- package/dist/skeletons.d.ts +16 -0
- package/dist/skeletons.d.ts.map +1 -0
- package/dist/skeletons.js +120 -0
- package/dist/skeletons.js.map +1 -0
- package/dist/traceReport.d.ts +75 -0
- package/dist/traceReport.d.ts.map +1 -0
- package/dist/traceReport.js +91 -0
- package/dist/traceReport.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { estimate as coreEstimate, ledger as coreLedger, render as coreRender } from "@horizon36596/zenith-core";
|
|
5
|
+
import { resolveSeason } from "@horizon36596/zenith-seasons";
|
|
6
|
+
import { loadAutoAndPlan, resolveAutoPath } from "../autoLoad.js";
|
|
7
|
+
import { tryCore } from "../coreGate.js";
|
|
8
|
+
import { formatFindingsTable } from "../findings.js";
|
|
9
|
+
import { hostPlatform, resolveLauncher } from "../launcher.js";
|
|
10
|
+
import { confinePath, loadProject, ProjectError } from "../project.js";
|
|
11
|
+
import { buildSimReport, parseTrace, TraceParseError } from "../traceReport.js";
|
|
12
|
+
/**
|
|
13
|
+
* Splits a command line into argv words the way a POSIX shell would, without ever handing the
|
|
14
|
+
* string to a shell: single quotes are fully literal, double quotes allow `\"`, `\\`, `` \` `` and
|
|
15
|
+
* `\$` to escape, and an unquoted backslash escapes the next character. `sim.command` in
|
|
16
|
+
* `zenith.json` is the team's own string (not attacker input), so this only needs to parse it the
|
|
17
|
+
* way a person reading it would, not defend against a hostile one; the actual security boundary is
|
|
18
|
+
* that `{auto}` is substituted in only after `auto.name` has already been validated against
|
|
19
|
+
* `AUTO_NAME_PATTERN`, so no token it lands in can ever contain a quote, space or shell
|
|
20
|
+
* metacharacter, and the whole argv array is then spawned with `shell: false`.
|
|
21
|
+
*/
|
|
22
|
+
export function splitShellWords(command) {
|
|
23
|
+
const words = [];
|
|
24
|
+
let current = "";
|
|
25
|
+
let has = false;
|
|
26
|
+
let i = 0;
|
|
27
|
+
const push = () => {
|
|
28
|
+
if (has)
|
|
29
|
+
words.push(current);
|
|
30
|
+
current = "";
|
|
31
|
+
has = false;
|
|
32
|
+
};
|
|
33
|
+
while (i < command.length) {
|
|
34
|
+
const ch = command[i];
|
|
35
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
|
|
36
|
+
push();
|
|
37
|
+
i += 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === "'") {
|
|
41
|
+
has = true;
|
|
42
|
+
i += 1;
|
|
43
|
+
while (i < command.length && command[i] !== "'") {
|
|
44
|
+
current += command[i];
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
47
|
+
i += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (ch === '"') {
|
|
51
|
+
has = true;
|
|
52
|
+
i += 1;
|
|
53
|
+
while (i < command.length && command[i] !== '"') {
|
|
54
|
+
const next = command[i];
|
|
55
|
+
if (next === "\\" && i + 1 < command.length && '"\\$`'.includes(command[i + 1])) {
|
|
56
|
+
current += command[i + 1];
|
|
57
|
+
i += 2;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
current += next;
|
|
61
|
+
i += 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
i += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (ch === "\\" && i + 1 < command.length) {
|
|
68
|
+
current += command[i + 1];
|
|
69
|
+
i += 2;
|
|
70
|
+
has = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
current += ch;
|
|
74
|
+
has = true;
|
|
75
|
+
i += 1;
|
|
76
|
+
}
|
|
77
|
+
push();
|
|
78
|
+
return words;
|
|
79
|
+
}
|
|
80
|
+
const pad = (text, width) => (text.length >= width ? text : text + " ".repeat(width - text.length));
|
|
81
|
+
function formatReport(report) {
|
|
82
|
+
const lines = [];
|
|
83
|
+
const idWidth = Math.max(2, ...report.steps.map((step) => step.id.length));
|
|
84
|
+
lines.push(`${pad("step", idWidth)} nominal s actual s delta s max cross-track in`);
|
|
85
|
+
for (const step of report.steps) {
|
|
86
|
+
const nominal = step.estimateNominalS === null ? "–" : step.estimateNominalS.toFixed(2);
|
|
87
|
+
const delta = step.deltaS === null ? "–" : (step.deltaS >= 0 ? "+" : "") + step.deltaS.toFixed(2);
|
|
88
|
+
const cross = step.maxCrossTrackIn === null ? "–" : step.maxCrossTrackIn.toFixed(2);
|
|
89
|
+
const interrupted = step.interrupted ? " (interrupted)" : "";
|
|
90
|
+
lines.push(`${pad(step.id, idWidth)} ${pad(nominal, 9)} ${pad(step.actualS.toFixed(2), 8)} ${pad(delta, 7)} ${cross}${interrupted}`);
|
|
91
|
+
}
|
|
92
|
+
lines.push("");
|
|
93
|
+
lines.push(`total: estimate ${report.totalEstimateS === null ? "unknown" : report.totalEstimateS.toFixed(2)} s, actual ${report.totalActualS.toFixed(2)} s`);
|
|
94
|
+
lines.push(`structure contacts: ${String(report.structureContacts)}, launches: ${String(report.launches)}, held at end: ${report.heldAtEnd === null ? "unknown" : String(report.heldAtEnd)}`);
|
|
95
|
+
if (Object.keys(report.tips).length > 0) {
|
|
96
|
+
lines.push(`tips: ${Object.entries(report.tips).map(([alliance, count]) => `${alliance} ${String(count)}`).join(", ")}`);
|
|
97
|
+
}
|
|
98
|
+
if (report.inert.length > 0) {
|
|
99
|
+
lines.push(`not measured by this sim build: ${report.inert.join(", ")} (https://libraries.horizon36596.org/zenith/simulation/)`);
|
|
100
|
+
}
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
/** Runs the sim command and resolves with its exit code and the interleaved output it printed. */
|
|
104
|
+
function runCommand(spawnImpl, launch, cwd, timeoutS) {
|
|
105
|
+
return new Promise((resolvePromise, reject) => {
|
|
106
|
+
// No shell: the argv array is spawned directly, so nothing in `command` (or in `{auto}`, which
|
|
107
|
+
// is substituted in only after auto.name has already been validated) is ever interpreted for
|
|
108
|
+
// metacharacters, quoting or redirection (finding 1). `resolveLauncher` has already turned a
|
|
109
|
+
// Gradle wrapper or a Windows batch file into something that starts without one.
|
|
110
|
+
const child = spawnImpl(launch.file, launch.args, { cwd, shell: false, windowsHide: true });
|
|
111
|
+
const chunks = [];
|
|
112
|
+
let timedOut = false;
|
|
113
|
+
const timer = timeoutS === undefined
|
|
114
|
+
? undefined
|
|
115
|
+
: setTimeout(() => {
|
|
116
|
+
timedOut = true;
|
|
117
|
+
child.kill();
|
|
118
|
+
}, timeoutS * 1000);
|
|
119
|
+
child.stdout?.on("data", (chunk) => {
|
|
120
|
+
process.stdout.write(chunk);
|
|
121
|
+
chunks.push(chunk.toString());
|
|
122
|
+
});
|
|
123
|
+
child.stderr?.on("data", (chunk) => {
|
|
124
|
+
process.stderr.write(chunk);
|
|
125
|
+
chunks.push(chunk.toString());
|
|
126
|
+
});
|
|
127
|
+
child.on("error", (error) => {
|
|
128
|
+
if (timer !== undefined)
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
reject(error);
|
|
131
|
+
});
|
|
132
|
+
child.on("close", (code) => {
|
|
133
|
+
if (timer !== undefined)
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
resolvePromise({ code: code ?? 1, output: chunks.join(""), timedOut });
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* `zenith sim <auto> [--open] [--timeout <s>]`: runs `zenith.json.sim.command`, reads the trace it
|
|
141
|
+
* writes, and prints estimate vs actual per step (site/docs/simulation.md).
|
|
142
|
+
*/
|
|
143
|
+
export async function runSim(autoArg, options) {
|
|
144
|
+
const cwd = options.cwd ?? process.cwd();
|
|
145
|
+
const project = loadProject(cwd, options.project, autoArg);
|
|
146
|
+
const path = resolveAutoPath(cwd, project, autoArg);
|
|
147
|
+
const result = loadAutoAndPlan(path, project);
|
|
148
|
+
if (!result.ok) {
|
|
149
|
+
const files = [{ path: autoArg, findings: result.findings }];
|
|
150
|
+
process.stdout.write(options.json === true ? `${JSON.stringify({ files }, null, 2)}\n` : `${formatFindingsTable(files)}\n`);
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
// loadAutoAndPlan (result.ok) already validated auto.name against AUTO_NAME_PATTERN, so every
|
|
154
|
+
// {auto} substitution below is guaranteed to contain only [A-Za-z0-9._-] — no path separator, no
|
|
155
|
+
// ".." segment, and (for the sim command) no shell metacharacter of any kind.
|
|
156
|
+
const { auto, plan, robot, field, seasonWarnings } = result.loaded;
|
|
157
|
+
const simConfig = project.link.sim;
|
|
158
|
+
if (simConfig === undefined) {
|
|
159
|
+
throw new ProjectError('zenith.json has no "sim" section (command and trace); add one before running zenith sim.');
|
|
160
|
+
}
|
|
161
|
+
const argv = splitShellWords(simConfig.command).map((word) => word.split("{auto}").join(auto.name));
|
|
162
|
+
const tracePath = confinePath(project.root, simConfig.trace.replaceAll("{auto}", auto.name));
|
|
163
|
+
const launch = resolveLauncher(argv, project.root, options.host ?? hostPlatform());
|
|
164
|
+
const { code, output, timedOut } = await runCommand(options.spawnFn ?? spawn, launch, project.root, options.timeout);
|
|
165
|
+
if (code !== 0) {
|
|
166
|
+
const tail = output.split("\n").slice(-40).join("\n");
|
|
167
|
+
const reason = timedOut ? `timed out after ${String(options.timeout)} s` : `exit code ${String(code)}`;
|
|
168
|
+
process.stderr.write(`sim command failed (${reason}):\n${tail}\n`);
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
171
|
+
if (!existsSync(tracePath)) {
|
|
172
|
+
process.stderr.write(`the sim command exited 0 but did not write a trace at ${tracePath}\n`);
|
|
173
|
+
return 2;
|
|
174
|
+
}
|
|
175
|
+
let trace;
|
|
176
|
+
try {
|
|
177
|
+
trace = parseTrace(JSON.parse(readFileSync(tracePath, "utf8")));
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const message = error instanceof TraceParseError || error instanceof Error ? error.message : String(error);
|
|
181
|
+
process.stderr.write(`could not read the trace at ${tracePath}: ${message}\n`);
|
|
182
|
+
return 2;
|
|
183
|
+
}
|
|
184
|
+
const estimateOutcome = tryCore(() => coreEstimate(plan, robot));
|
|
185
|
+
const report = buildSimReport(trace, plan, estimateOutcome.ok ? estimateOutcome.value : null);
|
|
186
|
+
let renderedPath = null;
|
|
187
|
+
if (options.open === true) {
|
|
188
|
+
const ledgerOutcome = tryCore(() => coreLedger(plan, field, resolveSeason(field).rules));
|
|
189
|
+
const renderOutcome = tryCore(() => coreRender(plan, estimateOutcome.ok ? estimateOutcome.value : null, result.loaded.findings, ledgerOutcome.ok ? ledgerOutcome.value : [], {}));
|
|
190
|
+
if (renderOutcome.ok) {
|
|
191
|
+
const relative = `${project.link.autosDir}/.renders/${auto.name}.sim.svg`;
|
|
192
|
+
const full = confinePath(project.root, relative);
|
|
193
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
194
|
+
writeFileSync(full, renderOutcome.value, "utf8");
|
|
195
|
+
renderedPath = relative;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (options.json === true) {
|
|
199
|
+
process.stdout.write(`${JSON.stringify({ report, estimateAvailable: estimateOutcome.ok, render: renderedPath, seasonWarnings }, null, 2)}\n`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
for (const warning of seasonWarnings)
|
|
203
|
+
process.stderr.write(`warning: ${autoArg}: ${warning}\n`);
|
|
204
|
+
process.stdout.write(`${formatReport(report)}\n`);
|
|
205
|
+
if (!estimateOutcome.ok)
|
|
206
|
+
process.stdout.write(`\n${estimateOutcome.message}\n`);
|
|
207
|
+
if (options.open === true) {
|
|
208
|
+
process.stdout.write(renderedPath === null ? "\n--open: render is not available yet (core M1 not landed).\n" : `\nopen: ${renderedPath}\n`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=sim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sim.js","sourceRoot":"","sources":["../../src/commands/sim.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Finding } from "@horizon36596/zenith-core";
|
|
2
|
+
import { type Project } from "../project.js";
|
|
3
|
+
export interface ValidateOptions {
|
|
4
|
+
json?: boolean;
|
|
5
|
+
/** Say what each finding code means and how to fix it (`FINDING_HELP`). */
|
|
6
|
+
explain?: boolean;
|
|
7
|
+
project?: string;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ValidateResult {
|
|
11
|
+
findings: Finding[];
|
|
12
|
+
/** Full sentences from `@horizon36596/zenith-seasons` when the field names no plugin this build carries. */
|
|
13
|
+
seasonWarnings: string[];
|
|
14
|
+
}
|
|
15
|
+
/** One auto's findings, including the schema errors that stop it from being planned at all. */
|
|
16
|
+
export declare function validateAuto(path: string, project: Project): ValidateResult;
|
|
17
|
+
/** `zenith validate <auto...>`: schema plus checks, exit 1 on any error. */
|
|
18
|
+
export declare function runValidate(autos: readonly string[], options: ValidateOptions): number;
|
|
19
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,OAAO,EACb,MAAM,2BAA2B,CAAC;AAWnC,OAAO,EAA6B,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAgCD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,4GAA4G;IAC5G,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,+FAA+F;AAC/F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,cAAc,CAmC3E;AAID,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CA+BtF"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
2
|
+
import { check, estimate, loadAuto, loadField, loadRobot, plan, resolve, } from "@horizon36596/zenith-core";
|
|
3
|
+
import { resolveSeason } from "@horizon36596/zenith-seasons";
|
|
4
|
+
import { SchemaError } from "@horizon36596/zenith-schema";
|
|
5
|
+
import { assertValidAutoName, nearestStepId, resolveAutoPath } from "../autoLoad.js";
|
|
6
|
+
import { countErrors, formatExplanations, formatFindingsTable, helpFor, } from "../findings.js";
|
|
7
|
+
import { loadProject, readJsonFile } from "../project.js";
|
|
8
|
+
const schemaFinding = (stepId, message) => ({
|
|
9
|
+
severity: "error",
|
|
10
|
+
stepId,
|
|
11
|
+
code: "SCHEMA",
|
|
12
|
+
message,
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* An auto may name its own robot or field file; when it does, that file wins over the link file's,
|
|
16
|
+
* and a broken override is a finding rather than a crash.
|
|
17
|
+
*/
|
|
18
|
+
function override(project, path, load, fallback, findings) {
|
|
19
|
+
if (path === undefined)
|
|
20
|
+
return fallback;
|
|
21
|
+
const full = isAbsolute(path) ? path : join(project.root, path);
|
|
22
|
+
try {
|
|
23
|
+
return load(readJsonFile(full));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
findings.push(schemaFinding("(file)", `Cannot use ${path}: ${error.message.split("\n")[0] ?? ""}`));
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** One auto's findings, including the schema errors that stop it from being planned at all. */
|
|
31
|
+
export function validateAuto(path, project) {
|
|
32
|
+
let json;
|
|
33
|
+
try {
|
|
34
|
+
json = readJsonFile(path);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return { findings: [schemaFinding("(file)", error.message)], seasonWarnings: [] };
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const auto = loadAuto(json);
|
|
41
|
+
assertValidAutoName(auto.name);
|
|
42
|
+
const findings = [];
|
|
43
|
+
const robot = override(project, auto.robot, loadRobot, project.robot, findings);
|
|
44
|
+
const field = override(project, auto.field, loadField, project.field, findings);
|
|
45
|
+
const planned = plan(resolve(auto, project.waypoints), robot, field);
|
|
46
|
+
const season = resolveSeason(field);
|
|
47
|
+
findings.push(...check(planned, estimate(planned, robot), robot, field, season.rules));
|
|
48
|
+
return { findings, seasonWarnings: [...season.warnings] };
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof SchemaError) {
|
|
52
|
+
return {
|
|
53
|
+
findings: error.issues.map((issue) => schemaFinding(issue.path.length === 0 ? "(file)" : nearestStepId(json, issue.path), `${issue.path.join(".")}: ${issue.message}`)),
|
|
54
|
+
seasonWarnings: [],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
findings: [schemaFinding("(file)", error instanceof Error ? error.message : String(error))],
|
|
59
|
+
seasonWarnings: [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const slash = (path) => path.split("\\").join("/");
|
|
64
|
+
/** `zenith validate <auto...>`: schema plus checks, exit 1 on any error. */
|
|
65
|
+
export function runValidate(autos, options) {
|
|
66
|
+
const cwd = options.cwd ?? process.cwd();
|
|
67
|
+
// `loadProject` itself now walks up from the first auto argument when there is no zenith.json
|
|
68
|
+
// above the working directory (finding 27) — every verb gets that fallback, not just this one.
|
|
69
|
+
const project = loadProject(cwd, options.project, autos[0]);
|
|
70
|
+
const files = autos.map((autoArg) => {
|
|
71
|
+
const full = resolveAutoPath(cwd, project, autoArg);
|
|
72
|
+
const result = validateAuto(full, project);
|
|
73
|
+
return { path: slash(relative(cwd, full)), findings: result.findings, seasonWarnings: result.seasonWarnings };
|
|
74
|
+
});
|
|
75
|
+
const errors = countErrors(files);
|
|
76
|
+
if (options.json === true) {
|
|
77
|
+
const body = options.explain === true
|
|
78
|
+
? { project: slash(project.root), files, errors, help: helpFor(files) }
|
|
79
|
+
: { project: slash(project.root), files, errors };
|
|
80
|
+
process.stdout.write(`${JSON.stringify(body, null, 2)}\n`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
for (const warning of file.seasonWarnings ?? []) {
|
|
85
|
+
process.stderr.write(`warning: ${file.path}: ${warning}\n`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
process.stdout.write(`${formatFindingsTable(files)}\n`);
|
|
89
|
+
if (options.explain === true) {
|
|
90
|
+
const explained = formatExplanations(files);
|
|
91
|
+
if (explained.length > 0)
|
|
92
|
+
process.stdout.write(`${explained}\n`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return errors === 0 ? 0 : 1;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,IAAI,EACJ,OAAO,GAER,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAA0B,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACnB,OAAO,GAER,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAgB,MAAM,eAAe,CAAC;AAUxE,MAAM,aAAa,GAAG,CAAC,MAAc,EAAE,OAAe,EAAW,EAAE,CAAC,CAAC;IACnE,QAAQ,EAAE,OAAO;IACjB,MAAM;IACN,IAAI,EAAE,QAAQ;IACd,OAAO;CACR,CAAC,CAAC;AAEH;;;GAGG;AACH,SAAS,QAAQ,CACf,OAAgB,EAChB,IAAwB,EACxB,IAA0B,EAC1B,QAAW,EACX,QAAmB;IAEnB,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACxC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,QAAQ,EAAE,cAAc,IAAI,KAAM,KAAe,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAChG,CAAC;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAQD,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAgB;IACzD,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,QAAQ,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAG,KAAe,CAAC,OAAO,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;IAC/F,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5B,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAU,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACvF,MAAM,KAAK,GAAU,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACvF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACvF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,OAAO;gBACL,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACnC,aAAa,CACX,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EACpE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAC5C,CACF;gBACD,cAAc,EAAE,EAAE;aACnB,CAAC;QACJ,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3F,cAAc,EAAE,EAAE;SACnB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEnE,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,KAAwB,EAAE,OAAwB;IAC5E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,8FAA8F;IAC9F,+FAA+F;IAC/F,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAmB,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;IAChH,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC1B,MAAM,IAAI,GACR,OAAO,CAAC,OAAO,KAAK,IAAI;YACtB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE;YACvE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,cAAc,IAAI,EAAE,EAAE,CAAC;gBAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC7B,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC5C,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,IAAI,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IACD,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NotImplementedError } from "@horizon36596/zenith-core";
|
|
2
|
+
/**
|
|
3
|
+
* `core.estimate`, `core.ledger`, `core.render` and `core.diff` throw `NotImplementedError` until
|
|
4
|
+
* M1 lands. Every verb that calls one of them runs the call through
|
|
5
|
+
* here so the CLI prints a clear message and exits 2 (usage/IO, since the file itself is fine; the
|
|
6
|
+
* tool just cannot finish the job yet) instead of a stack trace.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CORE_NOT_LANDED_EXIT_CODE = 2;
|
|
9
|
+
export declare function coreNotLandedMessage(error: NotImplementedError): string;
|
|
10
|
+
export type CoreResult<T> = {
|
|
11
|
+
ok: true;
|
|
12
|
+
value: T;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
/** Calls `fn`; catches only `NotImplementedError` and turns it into a `CoreResult`. */
|
|
18
|
+
export declare function tryCore<T>(fn: () => T): CoreResult<T>;
|
|
19
|
+
//# sourceMappingURL=coreGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coreGate.d.ts","sourceRoot":"","sources":["../src/coreGate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAEvE;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpF,uFAAuF;AACvF,wBAAgB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAOrD"}
|
package/dist/coreGate.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NotImplementedError } from "@horizon36596/zenith-core";
|
|
2
|
+
/**
|
|
3
|
+
* `core.estimate`, `core.ledger`, `core.render` and `core.diff` throw `NotImplementedError` until
|
|
4
|
+
* M1 lands. Every verb that calls one of them runs the call through
|
|
5
|
+
* here so the CLI prints a clear message and exits 2 (usage/IO, since the file itself is fine; the
|
|
6
|
+
* tool just cannot finish the job yet) instead of a stack trace.
|
|
7
|
+
*/
|
|
8
|
+
export const CORE_NOT_LANDED_EXIT_CODE = 2;
|
|
9
|
+
export function coreNotLandedMessage(error) {
|
|
10
|
+
return `core M1 not landed yet: ${error.message}`;
|
|
11
|
+
}
|
|
12
|
+
/** Calls `fn`; catches only `NotImplementedError` and turns it into a `CoreResult`. */
|
|
13
|
+
export function tryCore(fn) {
|
|
14
|
+
try {
|
|
15
|
+
return { ok: true, value: fn() };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof NotImplementedError)
|
|
19
|
+
return { ok: false, message: coreNotLandedMessage(error) };
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=coreGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coreGate.js","sourceRoot":"","sources":["../src/coreGate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAE3C,MAAM,UAAU,oBAAoB,CAAC,KAA0B;IAC7D,OAAO,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAAC;AACpD,CAAC;AAID,uFAAuF;AACvF,MAAM,UAAU,OAAO,CAAI,EAAW;IACpC,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,mBAAmB;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;QACrG,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RenderOptions } from "@horizon36596/zenith-core";
|
|
2
|
+
import type { Field } from "@horizon36596/zenith-schema";
|
|
3
|
+
import { type Project } from "./project.js";
|
|
4
|
+
/**
|
|
5
|
+
* The stored size of a PNG or WebP image in pixels, read from its header, or null for any other
|
|
6
|
+
* format or a header this does not recognise. Only a `pxBoundsIn` crop needs it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function imageSizePx(bytes: Uint8Array): {
|
|
9
|
+
widthPx: number;
|
|
10
|
+
heightPx: number;
|
|
11
|
+
} | null;
|
|
12
|
+
/**
|
|
13
|
+
* The picture `field.image` names, read for `core.render`, when it is a file inside the project:
|
|
14
|
+
* `src` relative to the field file. An `app:` image ships with the editor rather than the robot
|
|
15
|
+
* repository, and a missing file is not an error, so both give `undefined` and the render falls
|
|
16
|
+
* back to vector (site/docs/editor.md).
|
|
17
|
+
*/
|
|
18
|
+
export declare function fieldImageFor(project: Project, field: Field, fieldPath: string): RenderOptions["fieldImage"] | undefined;
|
|
19
|
+
//# sourceMappingURL=fieldImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fieldImage.d.ts","sourceRoot":"","sources":["../src/fieldImage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAUzD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAqB3F;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,GAChB,aAAa,CAAC,YAAY,CAAC,GAAG,SAAS,CAiBzC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, extname, join } from "node:path";
|
|
3
|
+
import { confinePath } from "./project.js";
|
|
4
|
+
const MIME = {
|
|
5
|
+
".png": "image/png",
|
|
6
|
+
".webp": "image/webp",
|
|
7
|
+
".jpg": "image/jpeg",
|
|
8
|
+
".jpeg": "image/jpeg",
|
|
9
|
+
".svg": "image/svg+xml",
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The stored size of a PNG or WebP image in pixels, read from its header, or null for any other
|
|
13
|
+
* format or a header this does not recognise. Only a `pxBoundsIn` crop needs it.
|
|
14
|
+
*/
|
|
15
|
+
export function imageSizePx(bytes) {
|
|
16
|
+
const ascii = (from, length) => String.fromCharCode(...bytes.subarray(from, from + length));
|
|
17
|
+
const u16le = (at) => (bytes[at] ?? 0) | ((bytes[at + 1] ?? 0) << 8);
|
|
18
|
+
const u24le = (at) => u16le(at) | ((bytes[at + 2] ?? 0) << 16);
|
|
19
|
+
const u32be = (at) => (((bytes[at] ?? 0) << 24) | ((bytes[at + 1] ?? 0) << 16) | ((bytes[at + 2] ?? 0) << 8) | (bytes[at + 3] ?? 0)) >>> 0;
|
|
20
|
+
if (ascii(1, 3) === "PNG" && ascii(12, 4) === "IHDR") {
|
|
21
|
+
return { widthPx: u32be(16), heightPx: u32be(20) };
|
|
22
|
+
}
|
|
23
|
+
if (ascii(0, 4) === "RIFF" && ascii(8, 4) === "WEBP") {
|
|
24
|
+
const chunk = ascii(12, 4);
|
|
25
|
+
if (chunk === "VP8X")
|
|
26
|
+
return { widthPx: u24le(24) + 1, heightPx: u24le(27) + 1 };
|
|
27
|
+
if (chunk === "VP8 ")
|
|
28
|
+
return { widthPx: u16le(26) & 0x3fff, heightPx: u16le(28) & 0x3fff };
|
|
29
|
+
if (chunk === "VP8L") {
|
|
30
|
+
const bits = (bytes[21] ?? 0) | ((bytes[22] ?? 0) << 8) | ((bytes[23] ?? 0) << 16) | ((bytes[24] ?? 0) << 24);
|
|
31
|
+
return { widthPx: (bits & 0x3fff) + 1, heightPx: ((bits >>> 14) & 0x3fff) + 1 };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The picture `field.image` names, read for `core.render`, when it is a file inside the project:
|
|
38
|
+
* `src` relative to the field file. An `app:` image ships with the editor rather than the robot
|
|
39
|
+
* repository, and a missing file is not an error, so both give `undefined` and the render falls
|
|
40
|
+
* back to vector (site/docs/editor.md).
|
|
41
|
+
*/
|
|
42
|
+
export function fieldImageFor(project, field, fieldPath) {
|
|
43
|
+
const src = field.image?.src;
|
|
44
|
+
if (src === undefined || src.startsWith("app:"))
|
|
45
|
+
return undefined;
|
|
46
|
+
let full;
|
|
47
|
+
try {
|
|
48
|
+
full = confinePath(project.root, join(dirname(fieldPath), src));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const mime = MIME[extname(full).toLowerCase()];
|
|
54
|
+
if (mime === undefined || !existsSync(full))
|
|
55
|
+
return undefined;
|
|
56
|
+
const bytes = readFileSync(full);
|
|
57
|
+
const size = imageSizePx(bytes);
|
|
58
|
+
return {
|
|
59
|
+
href: `data:${mime};base64,${bytes.toString("base64")}`,
|
|
60
|
+
...(size === null ? {} : size),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=fieldImage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fieldImage.js","sourceRoot":"","sources":["../src/fieldImage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Finding, type FindingCode, type FindingHelp } from "@horizon36596/zenith-core";
|
|
2
|
+
export interface FileFindings {
|
|
3
|
+
path: string;
|
|
4
|
+
findings: Finding[];
|
|
5
|
+
/** Full sentences from `@horizon36596/zenith-seasons` when the file's season plugin is missing or unknown. */
|
|
6
|
+
seasonWarnings?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
/** A table a person reads. Every verb also has --json, so an agent never parses this. */
|
|
9
|
+
export declare function formatFindingsTable(files: readonly FileFindings[]): string;
|
|
10
|
+
/** The distinct codes these files raise, in the order they first appear. */
|
|
11
|
+
export declare function codesIn(files: readonly FileFindings[]): FindingCode[];
|
|
12
|
+
/** `FINDING_HELP` for just the codes these files raise, for `validate --explain --json`. */
|
|
13
|
+
export declare const helpFor: (files: readonly FileFindings[]) => Partial<Record<FindingCode, FindingHelp>>;
|
|
14
|
+
/**
|
|
15
|
+
* `validate --explain`: what each code in the table means and how to fix it, once per code, in
|
|
16
|
+
* the words the editor shows behind the code chip.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatExplanations(files: readonly FileFindings[]): string;
|
|
19
|
+
export declare const countErrors: (files: readonly FileFindings[]) => number;
|
|
20
|
+
//# sourceMappingURL=findings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE3G,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,8GAA8G;IAC9G,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAKD,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CA6B1E;AAED,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,WAAW,EAAE,CAQrE;AAED,4FAA4F;AAC5F,eAAO,MAAM,OAAO,GAAI,OAAO,SAAS,YAAY,EAAE,KAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CACnB,CAAC;AAE/E;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAWzE;AAED,eAAO,MAAM,WAAW,GAAI,OAAO,SAAS,YAAY,EAAE,KAAG,MAI1D,CAAC"}
|
package/dist/findings.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { FINDING_HELP } from "@horizon36596/zenith-core";
|
|
2
|
+
const pad = (text, width) => text.length >= width ? text : text + " ".repeat(width - text.length);
|
|
3
|
+
/** A table a person reads. Every verb also has --json, so an agent never parses this. */
|
|
4
|
+
export function formatFindingsTable(files) {
|
|
5
|
+
const lines = [];
|
|
6
|
+
for (const file of files) {
|
|
7
|
+
lines.push(file.path);
|
|
8
|
+
if (file.findings.length === 0) {
|
|
9
|
+
lines.push(" no findings");
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
const severityWidth = Math.max(...file.findings.map((finding) => finding.severity.length));
|
|
13
|
+
const codeWidth = Math.max(...file.findings.map((finding) => finding.code.length));
|
|
14
|
+
const stepWidth = Math.max(...file.findings.map((finding) => finding.stepId.length));
|
|
15
|
+
for (const finding of file.findings) {
|
|
16
|
+
lines.push(` ${pad(finding.severity, severityWidth)} ${pad(finding.code, codeWidth)} ${pad(finding.stepId, stepWidth)} ${finding.message}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const errors = files.reduce((sum, file) => sum + file.findings.filter((finding) => finding.severity === "error").length, 0);
|
|
21
|
+
const warnings = files.reduce((sum, file) => sum + file.findings.filter((finding) => finding.severity === "warning").length, 0);
|
|
22
|
+
const plural = (count, word) => `${String(count)} ${word}${count === 1 ? "" : "s"}`;
|
|
23
|
+
lines.push(`${plural(errors, "error")}, ${plural(warnings, "warning")}`);
|
|
24
|
+
return lines.join("\n");
|
|
25
|
+
}
|
|
26
|
+
/** The distinct codes these files raise, in the order they first appear. */
|
|
27
|
+
export function codesIn(files) {
|
|
28
|
+
const codes = [];
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
for (const finding of file.findings) {
|
|
31
|
+
if (!codes.includes(finding.code))
|
|
32
|
+
codes.push(finding.code);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return codes;
|
|
36
|
+
}
|
|
37
|
+
/** `FINDING_HELP` for just the codes these files raise, for `validate --explain --json`. */
|
|
38
|
+
export const helpFor = (files) => Object.fromEntries(codesIn(files).map((code) => [code, FINDING_HELP[code]]));
|
|
39
|
+
/**
|
|
40
|
+
* `validate --explain`: what each code in the table means and how to fix it, once per code, in
|
|
41
|
+
* the words the editor shows behind the code chip.
|
|
42
|
+
*/
|
|
43
|
+
export function formatExplanations(files) {
|
|
44
|
+
const codes = codesIn(files);
|
|
45
|
+
if (codes.length === 0)
|
|
46
|
+
return "";
|
|
47
|
+
const lines = ["", "What these findings mean"];
|
|
48
|
+
for (const code of codes) {
|
|
49
|
+
const help = FINDING_HELP[code];
|
|
50
|
+
lines.push(` ${code}: ${help.title}`);
|
|
51
|
+
lines.push(` ${help.means}`);
|
|
52
|
+
lines.push(` To fix it: ${help.fix}`);
|
|
53
|
+
}
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
export const countErrors = (files) => files.reduce((sum, file) => sum + file.findings.filter((finding) => finding.severity === "error").length, 0);
|
|
57
|
+
//# sourceMappingURL=findings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"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"}
|