@cs-object/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +195 -0
- package/dist/chunk-7XCJ6AF3.js +607 -0
- package/dist/chunk-KYWIVB2V.js +341 -0
- package/dist/chunk-MOM6Q2VG.js +44 -0
- package/dist/chunk-OYQDZWT7.js +145 -0
- package/dist/chunk-UCPKZ67Z.js +88 -0
- package/dist/cli.js +262 -0
- package/dist/dev-server-BVY4Q6R6.js +732 -0
- package/dist/development.js +166 -0
- package/dist/html-AAIMIB2S.js +9 -0
- package/dist/pdf-WXWGZ6NC.js +9 -0
- package/package.json +60 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stringifyJson
|
|
3
|
+
} from "./chunk-MOM6Q2VG.js";
|
|
4
|
+
|
|
5
|
+
// src/reports.ts
|
|
6
|
+
import { CommandReportSchema } from "@cs-object/core";
|
|
7
|
+
|
|
8
|
+
// package.json
|
|
9
|
+
var version = "0.1.0";
|
|
10
|
+
|
|
11
|
+
// src/reports.ts
|
|
12
|
+
function initialReport(command) {
|
|
13
|
+
return {
|
|
14
|
+
reportVersion: "1",
|
|
15
|
+
command,
|
|
16
|
+
toolVersion: version,
|
|
17
|
+
ok: false,
|
|
18
|
+
provenance: {},
|
|
19
|
+
numericPolicy: {
|
|
20
|
+
absoluteTolerance: 1e-9,
|
|
21
|
+
relativeTolerance: 1e-12,
|
|
22
|
+
numberDomain: "finite-real-typed-safe-integer"
|
|
23
|
+
},
|
|
24
|
+
checks: {
|
|
25
|
+
executionValidity: { status: "not_applicable" },
|
|
26
|
+
inputConsistency: { status: "not_applicable" },
|
|
27
|
+
constantConsistency: { status: "not_applicable" },
|
|
28
|
+
formulaConsistency: { status: "not_applicable" },
|
|
29
|
+
outputConsistency: {
|
|
30
|
+
status: "not_applicable",
|
|
31
|
+
counts: { checked: 0, passed: 0, failed: 0 }
|
|
32
|
+
},
|
|
33
|
+
sourceToDocumentConsistency: { status: "not_applicable" },
|
|
34
|
+
independentReferenceAgreement: { status: "not_applicable" },
|
|
35
|
+
documentContent: { status: "not_applicable" },
|
|
36
|
+
rendering: { status: "not_applicable" },
|
|
37
|
+
visualInspection: { status: "not_applicable" }
|
|
38
|
+
},
|
|
39
|
+
diagnostics: []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function usageReport(failure) {
|
|
43
|
+
return CommandReportSchema.parse({
|
|
44
|
+
reportVersion: "1",
|
|
45
|
+
command: failure.kind === "unavailable" ? failure.command : void 0,
|
|
46
|
+
toolVersion: version,
|
|
47
|
+
ok: false,
|
|
48
|
+
provenance: {},
|
|
49
|
+
numericPolicy: {
|
|
50
|
+
absoluteTolerance: 1e-9,
|
|
51
|
+
relativeTolerance: 1e-12,
|
|
52
|
+
numberDomain: "finite-real-typed-safe-integer"
|
|
53
|
+
},
|
|
54
|
+
checks: Object.fromEntries(
|
|
55
|
+
[
|
|
56
|
+
"executionValidity",
|
|
57
|
+
"inputConsistency",
|
|
58
|
+
"constantConsistency",
|
|
59
|
+
"formulaConsistency",
|
|
60
|
+
"outputConsistency",
|
|
61
|
+
"sourceToDocumentConsistency",
|
|
62
|
+
"independentReferenceAgreement",
|
|
63
|
+
"documentContent",
|
|
64
|
+
"rendering",
|
|
65
|
+
"visualInspection"
|
|
66
|
+
].map((name) => [
|
|
67
|
+
name,
|
|
68
|
+
{
|
|
69
|
+
status: "not_applicable",
|
|
70
|
+
...name === "outputConsistency" ? { counts: { checked: 0, passed: 0, failed: 0 } } : {}
|
|
71
|
+
}
|
|
72
|
+
])
|
|
73
|
+
),
|
|
74
|
+
diagnostics: [
|
|
75
|
+
{
|
|
76
|
+
code: failure.kind === "unavailable" ? "COMMAND_UNAVAILABLE" : "UNKNOWN_COMMAND",
|
|
77
|
+
stage: "usage",
|
|
78
|
+
message: failure.kind === "unavailable" ? "Verified commands are unavailable until ticket11 integration. No execution, verification, rendering or inspection ran." : `Unknown command ${failure.command}. Use cso --help for supported commands. No checks ran.`
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/strict-json.ts
|
|
85
|
+
var structureTokens = /("(?:[^"\\]|\\.)*")(\s*:)?|[{}[\]]/g;
|
|
86
|
+
function parseStrictJson(text) {
|
|
87
|
+
const parsed = JSON.parse(text);
|
|
88
|
+
const containers = [];
|
|
89
|
+
for (const token of text.matchAll(structureTokens)) {
|
|
90
|
+
if (token[0] === "{") {
|
|
91
|
+
containers.push(/* @__PURE__ */ new Set());
|
|
92
|
+
} else if (token[0] === "[") {
|
|
93
|
+
containers.push(null);
|
|
94
|
+
} else if (token[0] === "}" || token[0] === "]") {
|
|
95
|
+
containers.pop();
|
|
96
|
+
} else if (token[1] && token[2]) {
|
|
97
|
+
const key = JSON.parse(token[1]);
|
|
98
|
+
if (typeof key !== "string") {
|
|
99
|
+
throw new Error("Expected JSON string key");
|
|
100
|
+
}
|
|
101
|
+
const keys = containers.at(-1);
|
|
102
|
+
if (keys?.has(key)) {
|
|
103
|
+
throw new Error(`Duplicate JSON key ${JSON.stringify(key)}`);
|
|
104
|
+
}
|
|
105
|
+
keys?.add(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return parsed;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/verification.ts
|
|
112
|
+
import {
|
|
113
|
+
executionBindingFrom as executionBindingFrom2,
|
|
114
|
+
CommandReportSchema as CommandReportSchema2,
|
|
115
|
+
verifyExecution
|
|
116
|
+
} from "@cs-object/core";
|
|
117
|
+
|
|
118
|
+
// src/reference.ts
|
|
119
|
+
import { readFileSync } from "fs";
|
|
120
|
+
import { createHash } from "crypto";
|
|
121
|
+
import {
|
|
122
|
+
executionBindingFrom,
|
|
123
|
+
ReferenceFileSchema,
|
|
124
|
+
contractIssuesToDiagnostics,
|
|
125
|
+
referenceBindingKey
|
|
126
|
+
} from "@cs-object/core";
|
|
127
|
+
function captureReference(path, execution) {
|
|
128
|
+
let provenance;
|
|
129
|
+
try {
|
|
130
|
+
const bytes = readFileSync(path);
|
|
131
|
+
provenance = {
|
|
132
|
+
path,
|
|
133
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
134
|
+
revisions: []
|
|
135
|
+
};
|
|
136
|
+
const raw = parseStrictJson(
|
|
137
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes)
|
|
138
|
+
);
|
|
139
|
+
const parsed = ReferenceFileSchema.safeParse(raw);
|
|
140
|
+
if (!parsed.success) {
|
|
141
|
+
return {
|
|
142
|
+
kind: "failed",
|
|
143
|
+
provenance,
|
|
144
|
+
diagnostics: contractIssuesToDiagnostics(parsed.error.issues).map(
|
|
145
|
+
(diagnostic) => ({ ...diagnostic, stage: "reference" })
|
|
146
|
+
)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const binding = referenceBindingKey(executionBindingFrom(execution));
|
|
150
|
+
provenance.revisions = parsed.data.cases.filter((item) => referenceBindingKey(item.binding) === binding).map((item) => item.revision);
|
|
151
|
+
return { kind: "parsed", cases: parsed.data.cases, bytes, provenance };
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
kind: "failed",
|
|
155
|
+
provenance,
|
|
156
|
+
diagnostics: [
|
|
157
|
+
{
|
|
158
|
+
code: "REFERENCE_CAPTURE_FAILED",
|
|
159
|
+
stage: "reference",
|
|
160
|
+
message: error instanceof Error ? error.message : String(error)
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/execution.ts
|
|
168
|
+
import { spawnSync } from "child_process";
|
|
169
|
+
import {
|
|
170
|
+
ExecutionResponseSchema,
|
|
171
|
+
contractIssuesToDiagnostics as contractIssuesToDiagnostics2
|
|
172
|
+
} from "@cs-object/core";
|
|
173
|
+
var message = (error) => error instanceof Error ? error.message : String(error);
|
|
174
|
+
function runPythonExecution(options, timeoutMs) {
|
|
175
|
+
const environment = { ...process.env };
|
|
176
|
+
for (const key of ["PYTHONPATH", "PYTHONHOME", "NODE_PATH"]) {
|
|
177
|
+
Reflect.deleteProperty(environment, key);
|
|
178
|
+
}
|
|
179
|
+
const result = spawnSync(
|
|
180
|
+
process.env.PYTHON ?? "python3",
|
|
181
|
+
[
|
|
182
|
+
"-I",
|
|
183
|
+
"-m",
|
|
184
|
+
"cso_python",
|
|
185
|
+
"execute",
|
|
186
|
+
options.sourcePath,
|
|
187
|
+
"--function",
|
|
188
|
+
options.functionName,
|
|
189
|
+
"--inputs-json",
|
|
190
|
+
stringifyJson(options.inputs)
|
|
191
|
+
],
|
|
192
|
+
{
|
|
193
|
+
env: environment,
|
|
194
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
195
|
+
timeout: timeoutMs,
|
|
196
|
+
killSignal: "SIGKILL"
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
if (result.stderr) {
|
|
200
|
+
process.stderr.write(result.stderr);
|
|
201
|
+
}
|
|
202
|
+
if (result.error || result.signal) {
|
|
203
|
+
return {
|
|
204
|
+
kind: "failed",
|
|
205
|
+
diagnostics: [
|
|
206
|
+
{
|
|
207
|
+
code: "PYTHON_PROCESS_FAILED",
|
|
208
|
+
stage: "execution",
|
|
209
|
+
message: result.error ? message(result.error) : `Python terminated by ${result.signal}`
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
let raw;
|
|
215
|
+
try {
|
|
216
|
+
raw = parseStrictJson(
|
|
217
|
+
new TextDecoder("utf-8", { fatal: true }).decode(result.stdout)
|
|
218
|
+
);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
kind: "failed",
|
|
222
|
+
diagnostics: [
|
|
223
|
+
{
|
|
224
|
+
code: "INVALID_EXECUTION_JSON",
|
|
225
|
+
stage: "contract",
|
|
226
|
+
message: message(error)
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const parsed = ExecutionResponseSchema.safeParse(raw);
|
|
232
|
+
if (!parsed.success) {
|
|
233
|
+
return {
|
|
234
|
+
kind: "failed",
|
|
235
|
+
diagnostics: contractIssuesToDiagnostics2(parsed.error.issues)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
kind: "response",
|
|
240
|
+
response: parsed.data,
|
|
241
|
+
bytes: result.stdout,
|
|
242
|
+
exitCode: result.status
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/verification.ts
|
|
247
|
+
function executeAndVerify(options, timeoutMs) {
|
|
248
|
+
const report = initialReport("verify");
|
|
249
|
+
report.provenance.function = options.functionName;
|
|
250
|
+
const finish = (exitCode) => ({
|
|
251
|
+
kind: "failed",
|
|
252
|
+
outcome: {
|
|
253
|
+
report: CommandReportSchema2.parse({
|
|
254
|
+
...report,
|
|
255
|
+
command: options.command
|
|
256
|
+
}),
|
|
257
|
+
exitCode
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const attempt = runPythonExecution(options, timeoutMs);
|
|
261
|
+
if (attempt.kind === "failed") {
|
|
262
|
+
report.diagnostics.push(...attempt.diagnostics);
|
|
263
|
+
report.checks.executionValidity = { status: "failed" };
|
|
264
|
+
report.checks.sourceToDocumentConsistency = { status: "failed" };
|
|
265
|
+
return finish(1);
|
|
266
|
+
}
|
|
267
|
+
const response = attempt.response;
|
|
268
|
+
report.diagnostics.push(...response.diagnostics);
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
report.provenance = response.provenance ?? {};
|
|
271
|
+
const usage = attempt.exitCode === 2 && response.diagnostics.every((diagnostic) => diagnostic.stage === "usage");
|
|
272
|
+
if (!usage) {
|
|
273
|
+
report.checks.executionValidity = { status: "failed" };
|
|
274
|
+
report.checks.sourceToDocumentConsistency = { status: "failed" };
|
|
275
|
+
if (!response.diagnostics.some(
|
|
276
|
+
(diagnostic) => ["source", "execution", "contract"].includes(diagnostic.stage)
|
|
277
|
+
)) {
|
|
278
|
+
report.diagnostics.push({
|
|
279
|
+
code: "PYTHON_PROCESS_FAILED",
|
|
280
|
+
stage: "execution",
|
|
281
|
+
message: `Python failed with exit ${attempt.exitCode}`
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return finish(usage ? 2 : 1);
|
|
286
|
+
}
|
|
287
|
+
if (attempt.exitCode !== 0) {
|
|
288
|
+
report.diagnostics.push({
|
|
289
|
+
code: "EXECUTION_EXIT_MISMATCH",
|
|
290
|
+
stage: "execution",
|
|
291
|
+
message: `Python returned success with exit ${attempt.exitCode}`
|
|
292
|
+
});
|
|
293
|
+
report.checks.executionValidity = { status: "failed" };
|
|
294
|
+
report.checks.sourceToDocumentConsistency = { status: "failed" };
|
|
295
|
+
return finish(1);
|
|
296
|
+
}
|
|
297
|
+
const execution = response.execution;
|
|
298
|
+
report.provenance = {
|
|
299
|
+
...executionBindingFrom2(execution),
|
|
300
|
+
sourceManifest: execution.sourceManifest,
|
|
301
|
+
versions: execution.versions
|
|
302
|
+
};
|
|
303
|
+
const reference = options.referencePath ? captureReference(options.referencePath, execution) : void 0;
|
|
304
|
+
if (reference?.provenance) {
|
|
305
|
+
report.provenance.reference = reference.provenance;
|
|
306
|
+
}
|
|
307
|
+
const verified = verifyExecution({
|
|
308
|
+
execution,
|
|
309
|
+
referenceCases: reference?.kind === "parsed" ? reference.cases : void 0
|
|
310
|
+
});
|
|
311
|
+
report.numericPolicy = verified.numericPolicy;
|
|
312
|
+
Object.assign(report.checks, verified.checks);
|
|
313
|
+
report.diagnostics.push(...verified.diagnostics);
|
|
314
|
+
if (reference?.kind === "failed") {
|
|
315
|
+
report.diagnostics.push(...reference.diagnostics);
|
|
316
|
+
}
|
|
317
|
+
report.ok = verified.ok && reference?.kind !== "failed";
|
|
318
|
+
if (!report.ok) return finish(1);
|
|
319
|
+
return {
|
|
320
|
+
kind: "verified",
|
|
321
|
+
report: CommandReportSchema2.parse(report),
|
|
322
|
+
capture: {
|
|
323
|
+
execution,
|
|
324
|
+
executionBytes: attempt.bytes,
|
|
325
|
+
verification: verified,
|
|
326
|
+
referenceBytes: reference?.kind === "parsed" ? reference.bytes : void 0
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function verifyCommand(options) {
|
|
331
|
+
const result = executeAndVerify(options);
|
|
332
|
+
return result.kind === "failed" ? result.outcome : { report: result.report, exitCode: 0 };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export {
|
|
336
|
+
initialReport,
|
|
337
|
+
usageReport,
|
|
338
|
+
parseStrictJson,
|
|
339
|
+
executeAndVerify,
|
|
340
|
+
verifyCommand
|
|
341
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/json.ts
|
|
2
|
+
function stringifyJson(value) {
|
|
3
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
4
|
+
const encode = (item) => {
|
|
5
|
+
if (Object.is(item, -0)) {
|
|
6
|
+
return "-0.0";
|
|
7
|
+
}
|
|
8
|
+
if (typeof item === "number" && Number.isFinite(item) && Number.isInteger(item) && !Number.isSafeInteger(item)) {
|
|
9
|
+
return item.toExponential();
|
|
10
|
+
}
|
|
11
|
+
if (item === null || typeof item !== "object") {
|
|
12
|
+
return JSON.stringify(item);
|
|
13
|
+
}
|
|
14
|
+
if (ancestors.has(item)) {
|
|
15
|
+
throw new TypeError("Cannot serialize cyclic JSON");
|
|
16
|
+
}
|
|
17
|
+
ancestors.add(item);
|
|
18
|
+
try {
|
|
19
|
+
if (Array.isArray(item)) {
|
|
20
|
+
return `[${Array.from(item, (child) => encode(child) ?? "null").join(",")}]`;
|
|
21
|
+
}
|
|
22
|
+
const prototype = Object.getPrototypeOf(item);
|
|
23
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
24
|
+
throw new TypeError("Expected a plain JSON object");
|
|
25
|
+
}
|
|
26
|
+
const fields = Object.entries(item).flatMap(([key, child]) => {
|
|
27
|
+
const encoded2 = encode(child);
|
|
28
|
+
return encoded2 === void 0 ? [] : [`${JSON.stringify(key)}:${encoded2}`];
|
|
29
|
+
});
|
|
30
|
+
return `{${fields.join(",")}}`;
|
|
31
|
+
} finally {
|
|
32
|
+
ancestors.delete(item);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const encoded = encode(value);
|
|
36
|
+
if (encoded === void 0) {
|
|
37
|
+
throw new TypeError("Expected a JSON value");
|
|
38
|
+
}
|
|
39
|
+
return encoded;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
stringifyJson
|
|
44
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// src/arguments.ts
|
|
2
|
+
import { PythonIdentifierSchema } from "@cs-object/core";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
var UsageError = class extends Error {
|
|
5
|
+
};
|
|
6
|
+
var numericPattern = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
7
|
+
function inputNumber(text) {
|
|
8
|
+
const value = Number(text);
|
|
9
|
+
if (!numericPattern.test(text) || !Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
10
|
+
throw new UsageError(
|
|
11
|
+
"Inputs must be finite JSON numbers in the safe integer range"
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function jsonInputs(text) {
|
|
17
|
+
const entries = [];
|
|
18
|
+
const body = text.trim();
|
|
19
|
+
try {
|
|
20
|
+
JSON.parse(body);
|
|
21
|
+
} catch {
|
|
22
|
+
throw new UsageError("--inputs-json must be valid JSON");
|
|
23
|
+
}
|
|
24
|
+
if (!body.startsWith("{") || !body.endsWith("}")) {
|
|
25
|
+
throw new UsageError("--inputs-json must be a JSON object of numbers");
|
|
26
|
+
}
|
|
27
|
+
let rest = body.slice(1, -1).trim();
|
|
28
|
+
while (rest.length > 0) {
|
|
29
|
+
const match = /^("(?:[^"\\]|\\(?:["\\/bfnrt]|u[\da-fA-F]{4}))*")\s*:\s*([^,]+)(,|$)/.exec(
|
|
30
|
+
rest
|
|
31
|
+
);
|
|
32
|
+
if (!match || !match[1] || !match[2]) {
|
|
33
|
+
throw new UsageError("--inputs-json must be a JSON object of numbers");
|
|
34
|
+
}
|
|
35
|
+
const key = JSON.parse(match[1]);
|
|
36
|
+
if (typeof key !== "string") throw new UsageError("Invalid input name");
|
|
37
|
+
entries.push([key, inputNumber(match[2].trim())]);
|
|
38
|
+
rest = rest.slice(match[0].length).trim();
|
|
39
|
+
if (match[3] === "," && rest.length === 0) {
|
|
40
|
+
throw new UsageError("--inputs-json cannot have a trailing comma");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return entries;
|
|
44
|
+
}
|
|
45
|
+
function parseCliArgs(args) {
|
|
46
|
+
const values = /* @__PURE__ */ new Map();
|
|
47
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
48
|
+
const setInput = (spelling, value) => {
|
|
49
|
+
if (!PythonIdentifierSchema.safeParse(spelling).success) {
|
|
50
|
+
throw new UsageError(`Input ${spelling} is not a valid Python parameter`);
|
|
51
|
+
}
|
|
52
|
+
const name = spelling.normalize("NFKC");
|
|
53
|
+
if (!PythonIdentifierSchema.safeParse(name).success) {
|
|
54
|
+
throw new UsageError(
|
|
55
|
+
`Input ${spelling} does not normalize to a valid Python parameter`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (inputs.has(name))
|
|
59
|
+
throw new UsageError(`Input ${name} was provided more than once`);
|
|
60
|
+
inputs.set(name, value);
|
|
61
|
+
};
|
|
62
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
63
|
+
"--source",
|
|
64
|
+
"--function",
|
|
65
|
+
"--out",
|
|
66
|
+
"--inputs-json",
|
|
67
|
+
"--input"
|
|
68
|
+
]);
|
|
69
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
70
|
+
const option = args[index];
|
|
71
|
+
const value = args[index + 1];
|
|
72
|
+
if (!option || !allowed.has(option))
|
|
73
|
+
throw new UsageError(`Unknown option ${option}`);
|
|
74
|
+
if (!value || value.startsWith("--"))
|
|
75
|
+
throw new UsageError(`${option} requires a value`);
|
|
76
|
+
if (option !== "--input") {
|
|
77
|
+
if (values.has(option))
|
|
78
|
+
throw new UsageError(`${option} was provided more than once`);
|
|
79
|
+
values.set(option, value);
|
|
80
|
+
}
|
|
81
|
+
if (option === "--input") {
|
|
82
|
+
const separator = value.indexOf("=");
|
|
83
|
+
if (separator < 1)
|
|
84
|
+
throw new UsageError("--input must use name=value format");
|
|
85
|
+
setInput(
|
|
86
|
+
value.slice(0, separator),
|
|
87
|
+
inputNumber(value.slice(separator + 1))
|
|
88
|
+
);
|
|
89
|
+
} else if (option === "--inputs-json") {
|
|
90
|
+
for (const [name, number] of jsonInputs(value)) setInput(name, number);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const source = values.get("--source");
|
|
94
|
+
const functionName = values.get("--function");
|
|
95
|
+
const out = values.get("--out");
|
|
96
|
+
if (!source) throw new UsageError("--source is required");
|
|
97
|
+
if (!functionName || !PythonIdentifierSchema.safeParse(functionName).success)
|
|
98
|
+
throw new UsageError("--function requires a valid Python identifier");
|
|
99
|
+
const normalizedFunction = functionName.normalize("NFKC");
|
|
100
|
+
if (!PythonIdentifierSchema.safeParse(normalizedFunction).success) {
|
|
101
|
+
throw new UsageError(
|
|
102
|
+
"--function does not normalize to a valid Python identifier"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!out) throw new UsageError("--out is required");
|
|
106
|
+
return {
|
|
107
|
+
sourcePath: resolve(source),
|
|
108
|
+
functionName: normalizedFunction,
|
|
109
|
+
outPath: resolve(out),
|
|
110
|
+
inputs: Object.fromEntries(inputs)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
var help = `Usage: cso <verify|html|pdf|bindings|dev|dev-export|dev-render> [arguments]
|
|
114
|
+
Use cso <command> --help for usage. dev-export and dev-render require
|
|
115
|
+
--source <file.cso.py> --function <name> --out <path>.
|
|
116
|
+
|
|
117
|
+
verify evaluates documented formulas and optional independent references.
|
|
118
|
+
Development export/render establishes no numerical verification or independent
|
|
119
|
+
reference agreement. Inspect every PDF page before delivery.
|
|
120
|
+
|
|
121
|
+
Commands:
|
|
122
|
+
dev Serve a verified calculation, editable inputs and report on localhost.
|
|
123
|
+
bindings Generate typed calculation handles; --check reports stale files without writing.
|
|
124
|
+
dev-export Export CSO JSON using the selected installed Python module.
|
|
125
|
+
dev-render Export once and render a development PDF with installed React/CSS.
|
|
126
|
+
verify Execute once and verify formulas; use cso verify --help.
|
|
127
|
+
html Verify once and publish standalone HTML; --check-layout adds browser checks.
|
|
128
|
+
pdf Verify once, retain evidence unless --no-evidence, and atomically publish a PDF.
|
|
129
|
+
|
|
130
|
+
Options:
|
|
131
|
+
--input <name=number> Repeat for distinct Python function parameters.
|
|
132
|
+
--inputs-json <object> Flat JSON object of numeric parameters; duplicates rejected.
|
|
133
|
+
--help Show usage without executing Python or launching Chromium.
|
|
134
|
+
|
|
135
|
+
Paths resolve from the caller's directory. Set PYTHON to the interpreter with the
|
|
136
|
+
cso-python wheel installed. dev-render requires Playwright Chromium.
|
|
137
|
+
Development exit codes: 0 exported/rendered, 1 execution/render/write failed,
|
|
138
|
+
2 invalid usage. verify/html/pdf --format json emit one public CommandReport.
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
UsageError,
|
|
143
|
+
parseCliArgs,
|
|
144
|
+
help
|
|
145
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LayoutInspectionError,
|
|
3
|
+
buildPreparedHtml,
|
|
4
|
+
captureAssets,
|
|
5
|
+
inspectPreparedHtml,
|
|
6
|
+
jsonBytes,
|
|
7
|
+
publishDocumentEvidence,
|
|
8
|
+
renderPreparedPdf,
|
|
9
|
+
retainSource,
|
|
10
|
+
sha256
|
|
11
|
+
} from "./chunk-7XCJ6AF3.js";
|
|
12
|
+
import {
|
|
13
|
+
executeAndVerify
|
|
14
|
+
} from "./chunk-KYWIVB2V.js";
|
|
15
|
+
|
|
16
|
+
// src/document.ts
|
|
17
|
+
import { CommandReportSchema } from "@cs-object/core";
|
|
18
|
+
import {
|
|
19
|
+
prepareExecutionDocument,
|
|
20
|
+
DocumentPreparationError
|
|
21
|
+
} from "@cs-object/react";
|
|
22
|
+
async function documentCommand(options) {
|
|
23
|
+
const result = executeAndVerify(options);
|
|
24
|
+
if (result.kind === "failed") return result.outcome;
|
|
25
|
+
const report = { ...result.report, command: options.command, ok: false };
|
|
26
|
+
let stage = "document";
|
|
27
|
+
try {
|
|
28
|
+
const document = prepareExecutionDocument({
|
|
29
|
+
execution: result.capture.execution,
|
|
30
|
+
assets: captureAssets(options.sourcePath, result.capture.execution)
|
|
31
|
+
});
|
|
32
|
+
const retained = retainSource(result.capture, document);
|
|
33
|
+
report.checks.documentContent = { status: "passed" };
|
|
34
|
+
let bytes;
|
|
35
|
+
let presentation = [];
|
|
36
|
+
if (options.command === "pdf") {
|
|
37
|
+
stage = "rendering";
|
|
38
|
+
const rendered = await renderPreparedPdf(document);
|
|
39
|
+
bytes = rendered.pdf;
|
|
40
|
+
presentation = rendered.presentation;
|
|
41
|
+
report.checks.rendering = { status: "passed" };
|
|
42
|
+
} else {
|
|
43
|
+
const html = buildPreparedHtml(document);
|
|
44
|
+
bytes = Buffer.from(html);
|
|
45
|
+
if (options.checkLayout) {
|
|
46
|
+
stage = "rendering";
|
|
47
|
+
presentation = await inspectPreparedHtml(document, html);
|
|
48
|
+
report.checks.rendering = { status: "passed" };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
stage = "write";
|
|
52
|
+
const successful = CommandReportSchema.parse({
|
|
53
|
+
...report,
|
|
54
|
+
ok: true,
|
|
55
|
+
checks: { ...report.checks, visualInspection: { status: "pending" } },
|
|
56
|
+
output: { path: options.outPath, sha256: sha256(bytes) }
|
|
57
|
+
});
|
|
58
|
+
const stdoutBytes = jsonBytes(successful);
|
|
59
|
+
const evidence = publishDocumentEvidence({
|
|
60
|
+
outPath: options.outPath,
|
|
61
|
+
artifact: { kind: options.command, bytes },
|
|
62
|
+
presentationCheck: options.command === "pdf" || options.checkLayout ? "passed" : "not_applicable",
|
|
63
|
+
reportBytes: stdoutBytes,
|
|
64
|
+
retained,
|
|
65
|
+
presentation,
|
|
66
|
+
retainEvidence: options.retainEvidence
|
|
67
|
+
});
|
|
68
|
+
return { report: successful, exitCode: 0, stdoutBytes, evidence };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (stage === "document")
|
|
71
|
+
report.checks.documentContent = { status: "failed" };
|
|
72
|
+
if (stage === "rendering") report.checks.rendering = { status: "failed" };
|
|
73
|
+
report.diagnostics.push(
|
|
74
|
+
...error instanceof DocumentPreparationError || error instanceof LayoutInspectionError ? error.diagnostics : [
|
|
75
|
+
{
|
|
76
|
+
code: stage === "document" ? "DOCUMENT_PREPARATION_FAILED" : stage === "rendering" ? `${options.command.toUpperCase()}_RENDERING_FAILED` : `${options.command.toUpperCase()}_PUBLICATION_FAILED`,
|
|
77
|
+
stage,
|
|
78
|
+
message: error instanceof Error ? error.message : String(error)
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
);
|
|
82
|
+
return { report: CommandReportSchema.parse(report), exitCode: 1 };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
documentCommand
|
|
88
|
+
};
|