@cimplify/cli 0.15.3 → 0.16.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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/add-SXC6RUPQ.mjs +140 -0
  4. package/dist/{add-KRIIKAS4.mjs → add-XQVQLD3F.mjs} +4 -4
  5. package/dist/ai-NQH5L4I6.mjs +17 -0
  6. package/dist/{assets-CWB72PZG.mjs → assets-FZGVOQXH.mjs} +12 -7
  7. package/dist/{auth-step-up-Z6KOTTV7.mjs → auth-step-up-67LCPDNL.mjs} +5 -5
  8. package/dist/build-J4ZUIOPX.mjs +625 -0
  9. package/dist/{cancel-ZHVVILOU.mjs → cancel-VGJHZJ4J.mjs} +4 -4
  10. package/dist/capture-RAJ2B3BH.mjs +69 -0
  11. package/dist/chunk-25ZPFHLF.mjs +51 -0
  12. package/dist/chunk-47ZXSY47.mjs +52 -0
  13. package/dist/{chunk-O2TO2J4I.mjs → chunk-54J3XR4T.mjs} +2 -2
  14. package/dist/chunk-5HP2INMY.mjs +10631 -0
  15. package/dist/chunk-7JR6JCFF.mjs +32 -0
  16. package/dist/chunk-7PQL7E7K.mjs +68 -0
  17. package/dist/chunk-AKACUJ3W.mjs +81 -0
  18. package/dist/chunk-BYNE553A.mjs +2945 -0
  19. package/dist/{chunk-NYXA6X56.mjs → chunk-CLQYPTJ2.mjs} +31 -11
  20. package/dist/chunk-CNZAOVSK.mjs +104 -0
  21. package/dist/chunk-CPBESPWK.mjs +5576 -0
  22. package/dist/{chunk-3RPR33MO.mjs → chunk-DYWUPHV3.mjs} +11 -7
  23. package/dist/chunk-EAV2P4N3.mjs +1941 -0
  24. package/dist/{chunk-UTD34C2H.mjs → chunk-EDZSOL5O.mjs} +2 -4
  25. package/dist/chunk-EGQIPYUN.mjs +696 -0
  26. package/dist/{chunk-DMLQBAGC.mjs → chunk-EOO4UOQD.mjs} +1 -1
  27. package/dist/chunk-EYEMPIMP.mjs +74 -0
  28. package/dist/chunk-G2ZEFSQJ.mjs +46 -0
  29. package/dist/{chunk-TH4XENKH.mjs → chunk-I4HNHPEM.mjs} +1 -1
  30. package/dist/chunk-JEVSFNPT.mjs +363 -0
  31. package/dist/chunk-JKECUAAV.mjs +2169 -0
  32. package/dist/chunk-KMHORI4Y.mjs +638 -0
  33. package/dist/chunk-KNPYBZ6B.mjs +502 -0
  34. package/dist/{chunk-3QUK5BPY.mjs → chunk-KOD4GBQ5.mjs} +8 -1
  35. package/dist/chunk-KQNZKV6E.mjs +535 -0
  36. package/dist/{chunk-2MKZKUVH.mjs → chunk-LLRUOIMU.mjs} +1 -2
  37. package/dist/{chunk-OLZKNUMK.mjs → chunk-LNMITJ4Z.mjs} +6 -6
  38. package/dist/chunk-MEM3MDV4.mjs +6463 -0
  39. package/dist/chunk-NY4YT7O6.mjs +84 -0
  40. package/dist/{chunk-LZEG3FEY.mjs → chunk-Q6CF4C3E.mjs} +7 -7
  41. package/dist/chunk-RP66A52P.mjs +86 -0
  42. package/dist/{chunk-I2UP7YTG.mjs → chunk-SAXGWS2K.mjs} +1 -1
  43. package/dist/chunk-SCKQ4XSD.mjs +41 -0
  44. package/dist/chunk-SF44L2OD.mjs +1152 -0
  45. package/dist/chunk-SGT5IJVW.mjs +28 -0
  46. package/dist/{chunk-LJEKKFC7.mjs → chunk-SS4JTMIV.mjs} +8 -8
  47. package/dist/chunk-T4MW3PD4.mjs +1456 -0
  48. package/dist/chunk-THATLMEX.mjs +1227 -0
  49. package/dist/{chunk-MLE4MVYC.mjs → chunk-TT4YI33G.mjs} +2 -2
  50. package/dist/chunk-TYV2SVFN.mjs +101 -0
  51. package/dist/chunk-VHK7WBO3.mjs +189 -0
  52. package/dist/chunk-VSA44UKR.mjs +391 -0
  53. package/dist/chunk-XL54SQJ3.mjs +42 -0
  54. package/dist/{chunk-5KYR6WJB.mjs → chunk-XQAHREKO.mjs} +17 -1
  55. package/dist/chunk-ZUCDXRFI.mjs +2011 -0
  56. package/dist/create-4S2EAUR3.mjs +10 -0
  57. package/dist/credits-NJOAOPPW.mjs +33 -0
  58. package/dist/{deploy-STWZ3OSM.mjs → deploy-26Y4UGZZ.mjs} +11 -11
  59. package/dist/dev-MOYURRWQ.mjs +401 -0
  60. package/dist/dev-WL43Q7VD.mjs +8 -0
  61. package/dist/dispatcher.mjs +131 -30
  62. package/dist/{doctor-3RW3OGZJ.mjs → doctor-E6ETD2GJ.mjs} +12 -20
  63. package/dist/{domains-FTNK4IKY.mjs → domains-LOTQMVOR.mjs} +6 -6
  64. package/dist/engine-runtime-fetch-CIE7S7PB.mjs +10 -0
  65. package/dist/{env-TNCYUAMT.mjs → env-3PDFC5XF.mjs} +7 -7
  66. package/dist/env-MOTYOPM5.mjs +19 -0
  67. package/dist/explain-K5QEVWCD.mjs +251 -0
  68. package/dist/{inspect-X3M7WSTL.mjs → inspect-Q3E2DHYY.mjs} +3 -3
  69. package/dist/inspect-SJ4WBAXO.mjs +86 -0
  70. package/dist/introspect-7F34YN5V.mjs +9 -0
  71. package/dist/kernel-DEGWMHFL.mjs +662 -0
  72. package/dist/kernel-plugin.js +6862 -0
  73. package/dist/keys-W5SN3DPK.mjs +71 -0
  74. package/dist/lines-XGJYTQ7P.mjs +56 -0
  75. package/dist/{link-76Z6V6CJ.mjs → link-NKUAVNM2.mjs} +6 -6
  76. package/dist/{lint-DDZ7HUQN.mjs → lint-G4YPQSSO.mjs} +2 -2
  77. package/dist/{list-2MFOQKKQ.mjs → list-TDR7K2JB.mjs} +3 -3
  78. package/dist/lock-EJPW2LM3.mjs +57 -0
  79. package/dist/{login-RLMDEWAL.mjs → login-AYLU7JU7.mjs} +10 -8
  80. package/dist/{logout-Z57X6HPE.mjs → logout-3M6VMHLH.mjs} +2 -2
  81. package/dist/{logs-TNDM4RFE.mjs → logs-4EL5SOXN.mjs} +5 -5
  82. package/dist/logs-4QQGG2XY.mjs +44 -0
  83. package/dist/mcp-4R2R6E5M.mjs +1077 -0
  84. package/dist/{mock-EVER34IX.mjs → mock-AK24GSP2.mjs} +2 -2
  85. package/dist/outdated-JEKCVLTG.mjs +58 -0
  86. package/dist/port-SBZ7X3EH.mjs +2408 -0
  87. package/dist/{projects-YL4T24VV.mjs → projects-7JKTC5WJ.mjs} +6 -6
  88. package/dist/publish-QQCX47HK.mjs +20 -0
  89. package/dist/remix-NLLYD3BI.mjs +125 -0
  90. package/dist/repo-Z4G4EW3N.mjs +9 -0
  91. package/dist/report-M7F4ZYP7.mjs +2 -0
  92. package/dist/rollback-2WQ2CW7T.mjs +43 -0
  93. package/dist/{rollback-47VQ4HJK.mjs → rollback-ZGU32HGT.mjs} +6 -6
  94. package/dist/scene-HXARHUM7.mjs +324 -0
  95. package/dist/{status-TWPOMWNA.mjs → status-C3ZSLZ7Q.mjs} +5 -5
  96. package/dist/status-WEQOC25K.mjs +18 -0
  97. package/dist/test-ZPBWV4VX.mjs +233 -0
  98. package/dist/testers-A3LMXOJJ.mjs +49 -0
  99. package/dist/{unlink-H3BO3DOQ.mjs → unlink-4G4FE4RJ.mjs} +2 -2
  100. package/dist/{update-FLY46XI3.mjs → update-GHKKM5FS.mjs} +4 -4
  101. package/dist/{whoami-CQPPARRS.mjs → whoami-3QQN6G2A.mjs} +3 -3
  102. package/dist/workshop-CD74D2LK.mjs +1703 -0
  103. package/package.json +11 -7
  104. package/templates/storefront-auto/app/products/[slug]/page.tsx +1 -1
  105. package/templates/storefront-auto/package.json +1 -1
  106. package/templates/storefront-bakery/package.json +1 -1
  107. package/templates/storefront-fashion/app/products/[slug]/page.tsx +1 -1
  108. package/templates/storefront-fashion/package.json +1 -1
  109. package/templates/storefront-grocery/package.json +1 -1
  110. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +1 -1
  111. package/templates/storefront-pharmacy/package.json +1 -1
  112. package/templates/storefront-restaurant/package.json +1 -1
  113. package/templates/storefront-retail/app/products/[slug]/page.tsx +1 -1
  114. package/templates/storefront-retail/package.json +1 -1
  115. package/templates/storefront-services/package.json +1 -1
  116. package/dist/chunk-ARIDYKLO.mjs +0 -6463
  117. package/dist/dev-7O65HY6C.mjs +0 -8
  118. package/dist/explain-DLRUUCNS.mjs +0 -251
  119. package/dist/introspect-RC265ACF.mjs +0 -9
  120. package/dist/repo-UUT3AZ3L.mjs +0 -9
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { CliError, CLI_ERROR_CODE } from './chunk-3QUK5BPY.mjs';
2
+ import { CliError, CLI_ERROR_CODE } from './chunk-KOD4GBQ5.mjs';
3
3
  import { promises } from 'fs';
4
4
  import os from 'os';
5
5
  import path from 'path';
@@ -137,4 +137,4 @@ ${STATE_GITIGNORE_LINE}
137
137
  await promises.writeFile(gitignore, next, ENCODING_UTF8);
138
138
  }
139
139
 
140
- export { clearAuth, clearProjectLink, readAuth, readAuthOrNull, readProjectLink, readProjectLinkOrNull, readProjectState, writeAuth, writeProjectLink, writeProjectState, writeStepUp };
140
+ export { authConfigDir, clearAuth, clearProjectLink, readAuth, readAuthOrNull, readProjectLink, readProjectLinkOrNull, readProjectState, writeAuth, writeProjectLink, writeProjectState, writeStepUp };
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ import { SessionError, CaptureSession, FRAME_MS, DEFAULT_EPOCH_MS } from './chunk-KNPYBZ6B.mjs';
3
+ import { buildLocal, workPath } from './chunk-CPBESPWK.mjs';
4
+ import { writeLoopProfile, LOOP_PROFILE_FILE } from './chunk-5HP2INMY.mjs';
5
+ import { locateToolchain } from './chunk-ZUCDXRFI.mjs';
6
+ import { formatDiagnostic } from './chunk-SGT5IJVW.mjs';
7
+ import { createHash } from 'crypto';
8
+ import { promises } from 'fs';
9
+ import path from 'path';
10
+
11
+ var CAPTURE_SCHEMA = "cimplify.capture/1";
12
+ var FINAL_CAPTURE = "final";
13
+ async function prepareSession(root) {
14
+ const toolchain = locateToolchain(root);
15
+ if (toolchain === null) throw new SessionError("the pinned SDK is not installed, so the game cannot be served; run your package manager's install");
16
+ const build = await buildLocal(root, { uiSites: true, loops: { probe: true } });
17
+ const errors = build.result.diagnostics.filter((d) => d.severity === "error");
18
+ if (errors.length > 0) throw new SessionError(`the build failed:
19
+ ${errors.map(formatDiagnostic).join("\n")}`);
20
+ return { root, build, toolchain };
21
+ }
22
+ function digest(bytes) {
23
+ return createHash("sha256").update(bytes).digest("hex");
24
+ }
25
+ async function runSession(setup, request) {
26
+ const session = await CaptureSession.open(setup, { device: request.device, backend: request.backend, seed: request.seed, scene: request.scene });
27
+ const out = [];
28
+ const take = async (name) => {
29
+ const png = request.screenshots || request.inspect ? await session.screenshot() : new Uint8Array(0);
30
+ const inspection = request.inspect ? await session.inspect(png) : null;
31
+ const snapshot = inspection === null ? await session.snapshot() : null;
32
+ const report = inspection?.report ?? null;
33
+ const readiness = await session.readiness();
34
+ const device = request.device;
35
+ out.push({
36
+ png,
37
+ inspect: report,
38
+ sidecar: {
39
+ schema: CAPTURE_SCHEMA,
40
+ name,
41
+ png: "",
42
+ sha256: png.length === 0 ? "" : digest(png),
43
+ frame: session.frame,
44
+ time_ms: Math.round(session.frame * FRAME_MS * 1e3) / 1e3,
45
+ seed: request.seed,
46
+ epoch_ms: DEFAULT_EPOCH_MS,
47
+ device: { name: device.name, width: device.width, height: device.height, scale: device.scale, insets: device.insets },
48
+ backend: request.backend,
49
+ css_safe_area: session.cssSafeArea,
50
+ scene: report?.scene ?? snapshot?.scene ?? { requested: request.scene, applied: null, current: null },
51
+ script: request.scriptPath,
52
+ ui: report === null ? null : { nodes: report.nodes.length, findings: report.checks.findings.length },
53
+ readiness
54
+ }
55
+ });
56
+ };
57
+ try {
58
+ if (request.script !== null) await session.run(request.script, take);
59
+ await session.advanceTo(request.frame);
60
+ await take(FINAL_CAPTURE);
61
+ await recordLoopProfile(setup.root, session);
62
+ return out;
63
+ } finally {
64
+ await session.close();
65
+ }
66
+ }
67
+ async function recordLoopProfile(root, session) {
68
+ const samples = await session.loopSamples();
69
+ const keys = Object.keys(samples);
70
+ if (keys.length === 0 || session.frame <= 0) return null;
71
+ const loops = {};
72
+ for (const key of keys) {
73
+ const [ms, calls, rows] = samples[key];
74
+ loops[key] = { ms, calls, rows };
75
+ }
76
+ return writeLoopProfile(workPath(root, LOOP_PROFILE_FILE), session.frame, loops);
77
+ }
78
+ async function writeCapture(frame, pngPath) {
79
+ await promises.mkdir(path.dirname(pngPath), { recursive: true });
80
+ await promises.writeFile(pngPath, frame.png);
81
+ const sidecar = { ...frame.sidecar, png: path.basename(pngPath) };
82
+ await promises.writeFile(`${pngPath.slice(0, -path.extname(pngPath).length)}.json`, `${JSON.stringify({ ...sidecar, inspect: frame.inspect }, null, 2)}
83
+ `);
84
+ return sidecar;
85
+ }
86
+ function framesFor(time, frame, fallback) {
87
+ if (time !== void 0 && frame !== void 0) return "pass --frame or --time, not both";
88
+ if (frame !== void 0) {
89
+ const n = Number(frame);
90
+ return Number.isInteger(n) && n >= 0 ? n : `--frame takes a whole number; got "${frame}"`;
91
+ }
92
+ if (time !== void 0) {
93
+ const match = /^(\d+(?:\.\d+)?)(ms|s)?$/.exec(time);
94
+ if (match === null) return `--time takes seconds or milliseconds, such as 2.5s or 2500ms; got "${time}"`;
95
+ const ms = Number(match[1]) * (match[2] === "ms" ? 1 : 1e3);
96
+ return Math.round(ms / FRAME_MS);
97
+ }
98
+ return fallback;
99
+ }
100
+
101
+ export { CAPTURE_SCHEMA, FINAL_CAPTURE, framesFor, prepareSession, runSession, writeCapture };
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ import { diagnostic } from './chunk-SGT5IJVW.mjs';
3
+ import ts from 'typescript';
4
+
5
+ var SCENES_FLAG = "CIMPLIFY_GENERATIVE_SCENES";
6
+ var SCENE_MODULES = ["@cimplify/sdk/game", "@cimplify/sdk"];
7
+ var SCENE_FUNCTION = "scene";
8
+ var SITE_HINT = /\bscene\s*\(/;
9
+ var NOT_STATIC = "scene.not_static";
10
+ function scenesEnabled(env = process.env) {
11
+ const v = env[SCENES_FLAG];
12
+ return v !== "0" && v !== "false";
13
+ }
14
+ async function sceneCompilerFromFlag(env = process.env) {
15
+ if (!scenesEnabled(env)) return null;
16
+ const sdk = await import('@cimplify/sdk/game');
17
+ return (def) => sdk.compileScene(def);
18
+ }
19
+ function mentionsScenes(text) {
20
+ return SCENE_MODULES.some((m) => text.includes(m)) && SITE_HINT.test(text);
21
+ }
22
+ var NotStatic = class {
23
+ constructor(node) {
24
+ this.node = node;
25
+ }
26
+ node;
27
+ };
28
+ function unwrap(expr) {
29
+ let e = expr;
30
+ while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e) || ts.isNonNullExpression(e) || ts.isTypeAssertionExpression(e)) e = e.expression;
31
+ return e;
32
+ }
33
+ function keyOf(name) {
34
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)) return name.text;
35
+ return null;
36
+ }
37
+ function evaluate(node) {
38
+ const e = unwrap(node);
39
+ if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) return e.text;
40
+ if (ts.isNumericLiteral(e)) return Number(e.text);
41
+ if (e.kind === ts.SyntaxKind.TrueKeyword) return true;
42
+ if (e.kind === ts.SyntaxKind.FalseKeyword) return false;
43
+ if (e.kind === ts.SyntaxKind.NullKeyword) return null;
44
+ if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.MinusToken) {
45
+ const v = evaluate(e.operand);
46
+ if (typeof v === "number") return -v;
47
+ throw new NotStatic(e);
48
+ }
49
+ if (ts.isArrayLiteralExpression(e)) return e.elements.map((x) => ts.isSpreadElement(x) ? fail(x) : evaluate(x));
50
+ if (ts.isObjectLiteralExpression(e)) {
51
+ const out = {};
52
+ for (const p of e.properties) {
53
+ if (!ts.isPropertyAssignment(p)) throw new NotStatic(p);
54
+ const key = keyOf(p.name);
55
+ if (key === null) throw new NotStatic(p.name);
56
+ out[key] = evaluate(p.initializer);
57
+ }
58
+ return out;
59
+ }
60
+ throw new NotStatic(e);
61
+ }
62
+ function fail(node) {
63
+ throw new NotStatic(node);
64
+ }
65
+ function sceneImports(sf) {
66
+ const names = /* @__PURE__ */ new Set();
67
+ for (const stmt of sf.statements) {
68
+ if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier) || !SCENE_MODULES.includes(stmt.moduleSpecifier.text)) continue;
69
+ const bindings = stmt.importClause?.namedBindings;
70
+ if (bindings === void 0 || !ts.isNamedImports(bindings)) continue;
71
+ for (const el of bindings.elements) {
72
+ if ((el.propertyName ?? el.name).text === SCENE_FUNCTION) names.add(el.name.text);
73
+ }
74
+ }
75
+ return names;
76
+ }
77
+ function property(obj, key) {
78
+ for (const p of obj.properties) if (ts.isPropertyAssignment(p) && keyOf(p.name) === key) return p;
79
+ return null;
80
+ }
81
+ function bindingOf(call) {
82
+ const parent = call.parent;
83
+ return ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name) ? parent.name.text : null;
84
+ }
85
+ function position(sf, node) {
86
+ const at = sf.getLineAndCharacterOfPosition(node.getStart(sf));
87
+ return { line: at.line + 1, column: at.character + 1 };
88
+ }
89
+ function findSceneSites(path, text) {
90
+ const sf = ts.createSourceFile(path, text, ts.ScriptTarget.ES2022, true, ts.ScriptKind.TS);
91
+ const callees = sceneImports(sf);
92
+ const sites = [];
93
+ const diagnostics = [];
94
+ if (callees.size === 0) return { sites, diagnostics };
95
+ const visit = (node) => {
96
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callees.has(node.expression.text)) {
97
+ const [nameArg, specArg] = node.arguments;
98
+ const objects = /* @__PURE__ */ new Map();
99
+ const fields = /* @__PURE__ */ new Map();
100
+ const spec = specArg === void 0 ? null : unwrap(specArg);
101
+ if (spec !== null && ts.isObjectLiteralExpression(spec)) {
102
+ const objectsProp = property(spec, "objects");
103
+ if (objectsProp !== null && ts.isObjectLiteralExpression(unwrap(objectsProp.initializer))) {
104
+ for (const p of unwrap(objectsProp.initializer).properties) {
105
+ if (!ts.isPropertyAssignment(p)) continue;
106
+ const key = keyOf(p.name);
107
+ if (key === null) continue;
108
+ objects.set(key, p);
109
+ const body = unwrap(p.initializer);
110
+ if (!ts.isObjectLiteralExpression(body)) continue;
111
+ for (const f of body.properties) {
112
+ const fk = ts.isPropertyAssignment(f) ? keyOf(f.name) : null;
113
+ if (fk !== null && ts.isPropertyAssignment(f)) fields.set(`${key}.${fk}`, f);
114
+ }
115
+ }
116
+ }
117
+ for (const top of ["area", "seed", "quality", "style", "agent"]) {
118
+ const p = property(spec, top);
119
+ if (p !== null) fields.set(top, p);
120
+ }
121
+ }
122
+ let definition = null;
123
+ const label = nameArg !== void 0 && (ts.isStringLiteral(nameArg) || ts.isNoSubstitutionTemplateLiteral(nameArg)) ? nameArg.text : bindingOf(node) ?? "scene";
124
+ try {
125
+ if (nameArg === void 0 || specArg === void 0) throw new NotStatic(node);
126
+ const name = evaluate(nameArg);
127
+ if (typeof name !== "string") throw new NotStatic(nameArg);
128
+ definition = { kind: "scene", name, spec: evaluate(specArg) };
129
+ } catch (error) {
130
+ if (!(error instanceof NotStatic)) throw error;
131
+ diagnostics.push({
132
+ code: NOT_STATIC,
133
+ severity: "error",
134
+ message: "scene arguments must be literal (strings, numbers, booleans, arrays and objects) so the build can check them",
135
+ object: null,
136
+ field: null,
137
+ fix: "inline the value instead of computing it",
138
+ path,
139
+ scene: label,
140
+ ...position(sf, error.node)
141
+ });
142
+ }
143
+ sites.push({ path, binding: bindingOf(node), definition, call: node, objects, fields, source: sf });
144
+ }
145
+ ts.forEachChild(node, visit);
146
+ };
147
+ visit(sf);
148
+ return { sites, diagnostics };
149
+ }
150
+ function locate(site, d) {
151
+ const fieldNode = d.object !== null && d.field !== null ? site.fields.get(`${d.object}.${d.field}`) : d.field !== null ? site.fields.get(d.field.split(".")[0]) : void 0;
152
+ const objectNode = d.object !== null ? site.objects.get(d.object) : void 0;
153
+ const node = fieldNode ?? objectNode ?? site.call;
154
+ return { ...d, path: site.path, scene: site.definition?.name ?? site.binding ?? "scene", ...position(site.source, node) };
155
+ }
156
+ function compileSites(path, text, compile) {
157
+ const found = findSceneSites(path, text);
158
+ const diagnostics = [...found.diagnostics];
159
+ const compiled = found.sites.map((site) => {
160
+ if (site.definition === null) return null;
161
+ const result = compile(site.definition);
162
+ for (const d of result.diagnostics) diagnostics.push(locate(site, d));
163
+ return result;
164
+ });
165
+ return { sites: found.sites, compiled, diagnostics };
166
+ }
167
+ function formatSceneDiagnostic(d) {
168
+ const who = d.object === null ? `scene ${d.scene}` : `${d.scene}.${d.object}`;
169
+ const fix = d.fix === null ? "" : ` (fix: ${d.fix})`;
170
+ return `${d.path}:${d.line}:${d.column}: ${d.severity} ${d.code}: ${who}: ${d.message}${fix}`;
171
+ }
172
+ function toBuildDiagnostic(d) {
173
+ const who = d.object === null ? `scene ${d.scene}` : `${d.scene}.${d.object}`;
174
+ const fix = d.fix === null ? "" : `; fix: ${d.fix}`;
175
+ return diagnostic(d.code, `${who}: ${d.message}${fix}`, d.path, { line: d.line, column: d.column }, d.severity);
176
+ }
177
+ function buildScenes(files, compile, out) {
178
+ let sites = 0;
179
+ for (const file of files) {
180
+ const text = file.bytes.toString("utf8");
181
+ if (!mentionsScenes(text)) continue;
182
+ const result = compileSites(file.path, text, compile);
183
+ sites += result.sites.length;
184
+ for (const d of result.diagnostics) out.push(toBuildDiagnostic(d));
185
+ }
186
+ return sites;
187
+ }
188
+
189
+ export { SCENES_FLAG, buildScenes, compileSites, formatSceneDiagnostic, sceneCompilerFromFlag, scenesEnabled };
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env node
2
+ import { loadOrCreateSigningKey, registrationFor, withRegistration, writeSigningKey, signSource, signingKeyDir, remoteFromEnv } from './chunk-EGQIPYUN.mjs';
3
+ import { CHANNELS, remoteCall, RemoteError } from './chunk-SCKQ4XSD.mjs';
4
+ import { buildLocal, workPath, formatBytes, VERIFIED_MAX_SOURCE_SET_BYTES, requireInteractiveProject, UPLOAD_PART_BYTES } from './chunk-CPBESPWK.mjs';
5
+ import { MANIFEST_FILE, assignInteractiveId } from './chunk-T4MW3PD4.mjs';
6
+ import { refreshEngineRuntime } from './chunk-G2ZEFSQJ.mjs';
7
+ import { sha256 } from './chunk-25ZPFHLF.mjs';
8
+ import { formatDiagnostic } from './chunk-SGT5IJVW.mjs';
9
+ import { parseArgs, flagString } from './chunk-XQAHREKO.mjs';
10
+ import { CliError, CLI_ERROR_CODE, info, dim, step, failure, isJsonMode, warn, success, bold, result } from './chunk-KOD4GBQ5.mjs';
11
+ import { promises } from 'fs';
12
+ import os from 'os';
13
+ import path from 'path';
14
+
15
+ var UPLOAD_JOURNAL_FILE = "upload-journal.json";
16
+ var JOURNAL_VERSION = 1;
17
+ function sameKey(state, key) {
18
+ return state.version === JOURNAL_VERSION && state.origin === key.origin && state.interactiveId === key.interactiveId && state.channel === key.channel && state.sourceDigest === key.sourceDigest;
19
+ }
20
+ var UploadJournal = class _UploadJournal {
21
+ constructor(file, state, resumed) {
22
+ this.file = file;
23
+ this.state = state;
24
+ this.resumed = resumed;
25
+ }
26
+ file;
27
+ state;
28
+ resumed;
29
+ writing = Promise.resolve();
30
+ static memory(key) {
31
+ return new _UploadJournal(null, { ...key, version: JOURNAL_VERSION, revision: null, files: {} }, false);
32
+ }
33
+ static async open(file, key) {
34
+ try {
35
+ const state = JSON.parse(await promises.readFile(file, "utf8"));
36
+ if (sameKey(state, key)) return new _UploadJournal(file, state, state.revision !== null);
37
+ } catch (err) {
38
+ if (err.code !== "ENOENT" && !(err instanceof SyntaxError)) throw err;
39
+ }
40
+ return new _UploadJournal(file, { ...key, version: JOURNAL_VERSION, revision: null, files: {} }, false);
41
+ }
42
+ get revision() {
43
+ return this.state.revision;
44
+ }
45
+ setRevision(revision) {
46
+ this.state.revision = revision;
47
+ if (revision === null) this.state.files = {};
48
+ return this.flush();
49
+ }
50
+ partsFor(digest, size, partBytes) {
51
+ const known = this.state.files[digest];
52
+ if (known === void 0 || known.size !== size || known.partBytes !== partBytes) return null;
53
+ return known.parts;
54
+ }
55
+ donePartsOf(digest) {
56
+ return this.state.files[digest]?.done ?? [];
57
+ }
58
+ isComplete(digest) {
59
+ return this.state.files[digest]?.complete === true;
60
+ }
61
+ recordParts(digest, size, partBytes, parts) {
62
+ const known = this.state.files[digest];
63
+ const keep = known !== void 0 && known.size === size && known.partBytes === partBytes;
64
+ this.state.files[digest] = { size, partBytes, parts: [...parts], done: keep ? known.done : [], complete: keep ? known.complete : false };
65
+ return this.flush();
66
+ }
67
+ markPart(digest, part) {
68
+ const known = this.state.files[digest];
69
+ if (known === void 0 || known.done.includes(part)) return this.writing;
70
+ known.done.push(part);
71
+ known.done.sort((a, b) => a - b);
72
+ return this.flush();
73
+ }
74
+ markFile(digest, size) {
75
+ const known = this.state.files[digest];
76
+ if (known === void 0) this.state.files[digest] = { size, partBytes: 0, parts: [], done: [], complete: true };
77
+ else known.complete = true;
78
+ return this.flush();
79
+ }
80
+ flush() {
81
+ const file = this.file;
82
+ if (file === null) return this.writing;
83
+ const snapshot = `${JSON.stringify(this.state)}
84
+ `;
85
+ this.writing = this.writing.then(async () => {
86
+ await promises.mkdir(path.dirname(file), { recursive: true });
87
+ const staged = `${file}.tmp`;
88
+ await promises.writeFile(staged, snapshot);
89
+ await promises.rename(staged, file);
90
+ });
91
+ return this.writing;
92
+ }
93
+ async remove() {
94
+ await this.writing;
95
+ if (this.file !== null) await promises.rm(this.file, { force: true });
96
+ }
97
+ };
98
+
99
+ // src/interactives/remote/upload.ts
100
+ var UPLOADS_IN_FLIGHT = 4;
101
+ function bufferSource(path3, digest, bytes) {
102
+ return { path: path3, digest, size: bytes.length, read: async (offset, length) => bytes.subarray(offset, offset + length) };
103
+ }
104
+ function partSizes(size, partBytes) {
105
+ const out = [];
106
+ for (let offset = 0; offset < size; offset += partBytes) out.push(Math.min(partBytes, size - offset));
107
+ return out;
108
+ }
109
+ async function partDigests(source, partBytes) {
110
+ const out = [];
111
+ let offset = 0;
112
+ for (const size of partSizes(source.size, partBytes)) {
113
+ out.push(sha256(await source.read(offset, size)));
114
+ offset += size;
115
+ }
116
+ return out;
117
+ }
118
+ async function drain(tasks, concurrency) {
119
+ let next = 0;
120
+ let failure2 = null;
121
+ const worker = async () => {
122
+ while (failure2 === null && next < tasks.length) {
123
+ const task = tasks[next++];
124
+ try {
125
+ await task();
126
+ } catch (err) {
127
+ if (failure2 === null) failure2 = err;
128
+ }
129
+ }
130
+ };
131
+ await Promise.all(Array.from({ length: Math.max(1, Math.min(concurrency, tasks.length)) }, worker));
132
+ if (failure2 !== null) throw failure2;
133
+ }
134
+ async function uploadSources(remote, sources, missing, options) {
135
+ const partBytes = options.partBytes ?? UPLOAD_PART_BYTES;
136
+ const concurrency = options.concurrency ?? UPLOADS_IN_FLIGHT;
137
+ const journal = options.journal;
138
+ const unique = /* @__PURE__ */ new Map();
139
+ for (const source of sources) if (!unique.has(source.digest)) unique.set(source.digest, source);
140
+ let totalBytes = 0;
141
+ for (const source of unique.values()) totalBytes += source.size;
142
+ let sentBytes = 0;
143
+ let skippedBytes = 0;
144
+ let uploadedFiles = 0;
145
+ let skippedFiles = 0;
146
+ let uploadedParts = 0;
147
+ let skippedParts = 0;
148
+ const report = (path3) => options.onProgress?.({ totalBytes, sentBytes, skippedBytes, path: path3 });
149
+ const skip = (bytes) => {
150
+ skippedBytes += bytes;
151
+ };
152
+ const whole = [];
153
+ const parted = [];
154
+ for (const source of unique.values()) {
155
+ if (!missing.has(source.digest)) {
156
+ skippedFiles++;
157
+ skip(source.size);
158
+ } else if (source.size > partBytes) parted.push(source);
159
+ else whole.push(source);
160
+ }
161
+ report(null);
162
+ const tasks = [];
163
+ for (const source of whole) {
164
+ tasks.push(async () => {
165
+ await remote.uploadFile(options.sessionId, source.digest, await source.read(0, source.size));
166
+ await journal.markFile(source.digest, source.size);
167
+ uploadedFiles++;
168
+ sentBytes += source.size;
169
+ report(source.path);
170
+ });
171
+ }
172
+ const planned = /* @__PURE__ */ new Map();
173
+ await drain(
174
+ parted.map((source) => async () => {
175
+ const digests = journal.partsFor(source.digest, source.size, partBytes) ?? await partDigests(source, partBytes);
176
+ await journal.recordParts(source.digest, source.size, partBytes, digests);
177
+ let offset = 0;
178
+ planned.set(
179
+ source.digest,
180
+ partSizes(source.size, partBytes).map((size, index) => {
181
+ const part = { number: index + 1, offset, size, digest: digests[index] };
182
+ offset += size;
183
+ return part;
184
+ })
185
+ );
186
+ }),
187
+ concurrency
188
+ );
189
+ for (const source of parted) {
190
+ const parts = planned.get(source.digest);
191
+ const opened = await remote.openUpload(
192
+ source.digest,
193
+ source.size,
194
+ parts.map((part) => part.digest)
195
+ );
196
+ if (opened.partBytes !== partBytes || opened.parts !== parts.length) {
197
+ throw new RemoteError("invalid", `the server splits ${source.path} into ${opened.parts} parts of ${opened.partBytes} bytes, not ${parts.length} of ${partBytes}`);
198
+ }
199
+ if (opened.stored) {
200
+ skippedFiles++;
201
+ skippedParts += parts.length;
202
+ skip(source.size);
203
+ await journal.markFile(source.digest, source.size);
204
+ continue;
205
+ }
206
+ const received = new Set(opened.received);
207
+ const pending = parts.filter((part) => !received.has(part.number));
208
+ for (const part of parts) {
209
+ if (!received.has(part.number)) continue;
210
+ skippedParts++;
211
+ skip(part.size);
212
+ await journal.markPart(source.digest, part.number);
213
+ }
214
+ let remaining = pending.length;
215
+ const finish = async () => {
216
+ await remote.completeUpload(source.digest);
217
+ await journal.markFile(source.digest, source.size);
218
+ uploadedFiles++;
219
+ report(source.path);
220
+ };
221
+ if (remaining === 0) {
222
+ tasks.push(finish);
223
+ continue;
224
+ }
225
+ for (const part of pending) {
226
+ tasks.push(async () => {
227
+ const bytes = await source.read(part.offset, part.size);
228
+ if (sha256(bytes) !== part.digest) {
229
+ throw new RemoteError("invalid", `${source.path} changed while it was uploading; publish again`);
230
+ }
231
+ await remote.uploadPart(source.digest, part.number, bytes);
232
+ await journal.markPart(source.digest, part.number);
233
+ uploadedParts++;
234
+ sentBytes += part.size;
235
+ report(source.path);
236
+ remaining--;
237
+ if (remaining === 0) await finish();
238
+ });
239
+ }
240
+ }
241
+ await drain(tasks, concurrency);
242
+ return { uploadedFiles, skippedFiles, uploadedParts, skippedParts, sentBytes, skippedBytes };
243
+ }
244
+
245
+ // src/interactives/commands/publish.ts
246
+ var PROGRESS_STEP_PERCENT = 10;
247
+ function parseChannel(value) {
248
+ if (value === void 0) return "live";
249
+ if (CHANNELS.indexOf(value) < 0) {
250
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `unknown channel '${value}'; expected ${CHANNELS.join(" or ")}`);
251
+ }
252
+ return value;
253
+ }
254
+ function keyLabel() {
255
+ return os.hostname();
256
+ }
257
+ async function registeredSigningKey(remote, keyDir) {
258
+ const key = await loadOrCreateSigningKey(keyDir);
259
+ if (registrationFor(key, remote.origin) !== null) return key;
260
+ const registered = await remote.registerSigningKey(key.rawPublicKey, keyLabel());
261
+ const next = withRegistration(key, { origin: remote.origin, keyId: registered.keyId });
262
+ await writeSigningKey(keyDir, next);
263
+ info(dim(`Registered signing key ${registered.keyId} with your account`));
264
+ return next;
265
+ }
266
+ async function ensureInteractiveId(root, remote, identity) {
267
+ if (identity.id !== null) return identity.id;
268
+ const created = await remote.createInteractive({ name: identity.name, description: identity.description, category: identity.category });
269
+ const manifestPath = path.join(root, MANIFEST_FILE);
270
+ await promises.writeFile(manifestPath, assignInteractiveId(await promises.readFile(manifestPath, "utf8"), created));
271
+ info(dim(`Assigned Interactive id ${created} and wrote it to ${MANIFEST_FILE}`));
272
+ return created;
273
+ }
274
+ async function publishProject(root, channel, deps) {
275
+ step("Building locally before upload");
276
+ let local = await buildLocal(root);
277
+ if (!local.result.ok || local.result.manifest === null) {
278
+ for (const d of local.result.diagnostics) failure(formatDiagnostic(d));
279
+ throw new CliError(CLI_ERROR_CODE.BUILD_FAILED, "the local build failed, so nothing was uploaded");
280
+ }
281
+ const identity = local.result.manifest.identity;
282
+ const key = await remoteCall(() => registeredSigningKey(deps.remote, deps.keyDir));
283
+ const interactiveId = await remoteCall(() => ensureInteractiveId(root, deps.remote, identity));
284
+ if (identity.id === null) local = await buildLocal(root);
285
+ const set = local.set;
286
+ const allowance = await remoteCall(() => deps.remote.sourceLimits());
287
+ if (allowance !== null) checkAllowance(set.files, allowance);
288
+ const journal = await UploadJournal.open(workPath(root, UPLOAD_JOURNAL_FILE), { origin: deps.remote.origin, interactiveId, channel, sourceDigest: set.digest });
289
+ const session = await openSession(deps.remote, journal, {
290
+ interactiveId,
291
+ channel,
292
+ sourceDigest: set.digest,
293
+ files: set.files.map((f) => ({ path: f.path, digest: f.digest, size: f.size })),
294
+ signature: signSource(key, set.digest)
295
+ });
296
+ const resumed = session.sessionId === journal.revision && journal.resumed;
297
+ step(resumed ? `Resuming the upload of ${set.files.length} files to the ${channel} channel` : `Uploading ${set.files.length} files to the ${channel} channel`);
298
+ const sent = await remoteCall(
299
+ () => uploadSources(
300
+ deps.remote,
301
+ set.files.map((f) => bufferSource(f.path, f.digest, f.bytes)),
302
+ new Set(session.missing),
303
+ { sessionId: session.sessionId, journal, partBytes: allowance?.partBytes, concurrency: deps.concurrency, onProgress: deps.onProgress ?? progressPrinter() }
304
+ )
305
+ );
306
+ step("Building and admitting on Cimplify");
307
+ const outcome = await remoteCall(() => deps.remote.commitPublish(session.sessionId));
308
+ await journal.remove();
309
+ return {
310
+ interactiveId,
311
+ channel,
312
+ files: set.files.length,
313
+ uploaded: sent.uploadedFiles,
314
+ skipped: set.files.length - sent.uploadedFiles,
315
+ sentBytes: sent.sentBytes,
316
+ skippedBytes: sent.skippedBytes,
317
+ resumed,
318
+ outcome
319
+ };
320
+ }
321
+ async function openSession(remote, journal, request) {
322
+ const revision = journal.revision;
323
+ if (revision !== null) {
324
+ const pending = await remoteCall(() => remote.pendingPublish(revision));
325
+ if (pending !== null) return { sessionId: revision, missing: pending.receiving ? pending.missing : [] };
326
+ }
327
+ const session = await remoteCall(() => remote.beginPublish(request));
328
+ await journal.setRevision(session.sessionId);
329
+ return session;
330
+ }
331
+ function checkAllowance(files, allowance) {
332
+ let total = 0;
333
+ for (const file of files) total += file.size;
334
+ if (total > allowance.sourceSetBytes) {
335
+ const remediation = allowance.verified ? `Trim the source set below ${formatBytes(allowance.sourceSetBytes)}.` : `Verify your creator account to publish up to ${formatBytes(VERIFIED_MAX_SOURCE_SET_BYTES)}, or trim the source set below ${formatBytes(allowance.sourceSetBytes)}.`;
336
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `the source set is ${formatBytes(total)}; your creator account publishes source sets up to ${formatBytes(allowance.sourceSetBytes)}`, { remediation });
337
+ }
338
+ }
339
+ function progressPrinter() {
340
+ const live = !isJsonMode() && Boolean(process.stdout.isTTY);
341
+ let shown = -1;
342
+ return (progress) => {
343
+ if (isJsonMode() || progress.totalBytes === 0) return;
344
+ const done = progress.sentBytes + progress.skippedBytes;
345
+ const percent = Math.floor(done * 100 / progress.totalBytes);
346
+ const line = ` ${percent}% ${formatBytes(done)} of ${formatBytes(progress.totalBytes)}${progress.skippedBytes > 0 ? `, ${formatBytes(progress.skippedBytes)} already on Cimplify` : ""}`;
347
+ if (live) {
348
+ process.stdout.write(`\r${line}${percent === 100 ? "\n" : ""}`);
349
+ return;
350
+ }
351
+ if (percent === shown || percent < 100 && percent - shown < PROGRESS_STEP_PERCENT) return;
352
+ shown = percent;
353
+ info(dim(line));
354
+ };
355
+ }
356
+ async function run(argv) {
357
+ const args = parseArgs(argv);
358
+ const root = process.cwd();
359
+ requireInteractiveProject(root, "publish");
360
+ const channel = parseChannel(flagString(args, "channel"));
361
+ await refreshEngineRuntime(root);
362
+ const summary = await publishProject(root, channel, { remote: await remoteFromEnv(), keyDir: signingKeyDir() });
363
+ info(dim(` ${summary.uploaded} uploaded (${formatBytes(summary.sentBytes)}), ${summary.skipped} unchanged and skipped (${formatBytes(summary.skippedBytes)})${summary.resumed ? ", resumed" : ""}`));
364
+ for (const log of summary.outcome.logs) {
365
+ const line = ` ${dim(log.at)} ${log.message}`;
366
+ if (log.level === "error") failure(line);
367
+ else if (log.level === "warning") warn(line);
368
+ else info(line);
369
+ }
370
+ const rev = summary.outcome.revision;
371
+ if (rev.state === "refused") {
372
+ throw new CliError(CLI_ERROR_CODE.BUILD_FAILED, `revision ${rev.revision} was refused at admission`);
373
+ }
374
+ if (rev.state === "in_review") success(`Revision ${bold(rev.revision)} passed admission checks and waits for review before release to ${channel}`);
375
+ else success(`Published ${bold(rev.revision)} to ${channel} (${rev.packageDigest})`);
376
+ result({
377
+ interactive_id: summary.interactiveId,
378
+ channel,
379
+ revision: rev.revision,
380
+ state: rev.state,
381
+ package_digest: rev.packageDigest,
382
+ source_digest: rev.sourceDigest,
383
+ uploaded: summary.uploaded,
384
+ skipped: summary.skipped,
385
+ sent_bytes: summary.sentBytes,
386
+ skipped_bytes: summary.skippedBytes,
387
+ resumed: summary.resumed
388
+ });
389
+ }
390
+
391
+ export { checkAllowance, keyLabel, parseChannel, progressPrinter, publishProject, registeredSigningKey, run };