lecodes-cli 2.2.11 → 2.2.13
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/README.md +4 -2
- package/dist/index.js +1168 -870
- package/package.json +9 -9
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +3 -3
- package/runtime/web/player.js +2 -2
- package/runtime/web-lite/lite.js +9 -9
- package/src/cli/args.ts +10 -3
- package/src/cli/command.ts +7 -0
- package/src/cli/run.ts +2 -1
- package/src/commands/app/android.ts +7 -10
- package/src/commands/app/desktop.ts +3 -3
- package/src/commands/app/index.ts +10 -13
- package/src/commands/app/shared.ts +43 -17
- package/src/commands/app/templates/android.ts +1 -1
- package/src/commands/index.ts +3 -1
- package/src/commands/init/templates.ts +4 -3
- package/src/commands/publish.ts +38 -0
- package/src/commands/push.ts +28 -15
- package/src/commands/releases.ts +168 -0
- package/src/hosts/desktopScript.ts +3 -3
- package/src/platform/api.ts +114 -8
- package/src/plugin/generate.ts +9 -6
- package/src/plugin/permissions.ts +42 -10
package/dist/index.js
CHANGED
|
@@ -64,9 +64,9 @@ var package_default;
|
|
|
64
64
|
var init_package = __esm(() => {
|
|
65
65
|
package_default = {
|
|
66
66
|
name: "lecodes-cli",
|
|
67
|
-
version: "2.2.
|
|
67
|
+
version: "2.2.13",
|
|
68
68
|
dependencies: {
|
|
69
|
-
"@letary/chisel": "^0.
|
|
69
|
+
"@letary/chisel": "^0.15.0",
|
|
70
70
|
jimp: "^1.6.1",
|
|
71
71
|
"lecodes-sdk": "workspace:*"
|
|
72
72
|
},
|
|
@@ -88,7 +88,7 @@ var init_package = __esm(() => {
|
|
|
88
88
|
},
|
|
89
89
|
peerDependencies: {
|
|
90
90
|
"lecodes-design": "^0.11.0",
|
|
91
|
-
"lecodes-headless": "^2.2.
|
|
91
|
+
"lecodes-headless": "^2.2.13",
|
|
92
92
|
"lecodes-3d-editor": "^0.9.0",
|
|
93
93
|
"lecodes-assets": "^0.2.1",
|
|
94
94
|
"heic-decode": "^2.1.0",
|
|
@@ -158,10 +158,10 @@ var init_package = __esm(() => {
|
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
// ../../sdk/src/version.ts
|
|
161
|
-
var SDK_VERSION = "2.2.
|
|
161
|
+
var SDK_VERSION = "2.2.13";
|
|
162
162
|
|
|
163
163
|
// src/cli/args.ts
|
|
164
|
-
var isNegativeNumber = (s) => /^-(\d|\.\d)/.test(s), isValue = (s) => !s.startsWith("-") || isNegativeNumber(s), parseArgv = (argv) => {
|
|
164
|
+
var isNegativeNumber = (s) => /^-(\d|\.\d)/.test(s), isValue = (s) => !s.startsWith("-") || isNegativeNumber(s), parseArgv = (argv, booleans = new Set) => {
|
|
165
165
|
const out = { positional: [], flags: {} };
|
|
166
166
|
for (let i = 0;i < argv.length; i++) {
|
|
167
167
|
const arg = argv[i];
|
|
@@ -183,7 +183,13 @@ var isNegativeNumber = (s) => /^-(\d|\.\d)/.test(s), isValue = (s) => !s.startsW
|
|
|
183
183
|
}
|
|
184
184
|
const key = arg.slice(long ? 2 : 1);
|
|
185
185
|
const next = argv[i + 1];
|
|
186
|
-
if (
|
|
186
|
+
if (booleans.has(key)) {
|
|
187
|
+
if (next === "true" || next === "false") {
|
|
188
|
+
out.flags[key] = next;
|
|
189
|
+
i++;
|
|
190
|
+
} else
|
|
191
|
+
out.flags[key] = true;
|
|
192
|
+
} else if (next !== undefined && isValue(next)) {
|
|
187
193
|
out.flags[key] = next;
|
|
188
194
|
i++;
|
|
189
195
|
} else
|
|
@@ -243,6 +249,15 @@ var str = (help, opts) => ({ type: "string", help, ...opts }), num = (help, opts
|
|
|
243
249
|
out[name] = present;
|
|
244
250
|
}
|
|
245
251
|
return out;
|
|
252
|
+
}, booleanFlagNames = (spec) => {
|
|
253
|
+
const names = new Set;
|
|
254
|
+
for (const [name, s] of Object.entries(spec))
|
|
255
|
+
if (s.type === "boolean") {
|
|
256
|
+
names.add(name);
|
|
257
|
+
if (s.alias)
|
|
258
|
+
names.add(s.alias);
|
|
259
|
+
}
|
|
260
|
+
return names;
|
|
246
261
|
}, unknownFlags = (spec, raw) => {
|
|
247
262
|
const known = new Set;
|
|
248
263
|
for (const [name, s] of Object.entries(spec)) {
|
|
@@ -498,8 +513,8 @@ var all = (groups) => groups.flatMap((g) => g.commands), closest = (name, cmds)
|
|
|
498
513
|
process.exitCode = 1;
|
|
499
514
|
return;
|
|
500
515
|
}
|
|
501
|
-
const parsed = parseArgv(rest);
|
|
502
516
|
const spec = node.flags ?? {};
|
|
517
|
+
const parsed = parseArgv(rest, booleanFlagNames(spec));
|
|
503
518
|
if (!node.passthrough) {
|
|
504
519
|
const unknown = unknownFlags(spec, parsed.flags);
|
|
505
520
|
if (unknown.length)
|
|
@@ -525,6 +540,7 @@ var all = (groups) => groups.flatMap((g) => g.commands), closest = (name, cmds)
|
|
|
525
540
|
process.exitCode = 1;
|
|
526
541
|
};
|
|
527
542
|
var init_run = __esm(() => {
|
|
543
|
+
init_command();
|
|
528
544
|
init_args();
|
|
529
545
|
init_command();
|
|
530
546
|
init_errors();
|
|
@@ -590,7 +606,7 @@ var errorMessage = (body, status) => {
|
|
|
590
606
|
return b.message;
|
|
591
607
|
}
|
|
592
608
|
return `Request failed with status ${status}`;
|
|
593
|
-
}, apiRequest = async (apiUrl, path, opts = {}) => {
|
|
609
|
+
}, ApiError, apiRequest = async (apiUrl, path, opts = {}) => {
|
|
594
610
|
const headers = {};
|
|
595
611
|
const isForm = opts.body instanceof FormData;
|
|
596
612
|
if (opts.token)
|
|
@@ -610,13 +626,64 @@ var errorMessage = (body, status) => {
|
|
|
610
626
|
const contentType = resp.headers.get("content-type") ?? "";
|
|
611
627
|
const parsed = contentType.startsWith("application/json") ? await resp.json() : contentType.startsWith("text/") ? await resp.text() : null;
|
|
612
628
|
if (resp.status >= 400)
|
|
613
|
-
throw new
|
|
629
|
+
throw new ApiError(errorMessage(parsed, resp.status), resp.status, parsed);
|
|
614
630
|
return parsed;
|
|
615
631
|
}, getAccount = (apiUrl, token) => apiRequest(apiUrl, "/account", { token }), getProject = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}`, { token }), createProject = (apiUrl, token, name, teamUuid) => apiRequest(apiUrl, "/projects", { token, body: { name, teamUuid } }), getCommits = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}/commits`, { token }), getTypes = (apiUrl) => apiRequest(apiUrl, "/types"), uploadBlob = (apiUrl, token, uuid, name, bytes) => {
|
|
616
632
|
const form = new FormData;
|
|
617
633
|
form.append("file", new Blob([bytes]), name);
|
|
618
634
|
return apiRequest(apiUrl, `/projects/${uuid}/blobs`, { token, body: form });
|
|
619
|
-
}, sync = (apiUrl, token, uuid, payload) => apiRequest(apiUrl, `/projects/${uuid}/sync`, { token, body: payload }),
|
|
635
|
+
}, sync = (apiUrl, token, uuid, payload) => apiRequest(apiUrl, `/projects/${uuid}/sync`, { token, body: payload }), SchemaChangeError, opText = (op) => {
|
|
636
|
+
if (typeof op === "string")
|
|
637
|
+
return op;
|
|
638
|
+
if (op && typeof op === "object" && typeof op.text === "string")
|
|
639
|
+
return op.text;
|
|
640
|
+
return JSON.stringify(op);
|
|
641
|
+
}, schemaChangeOf = (e) => {
|
|
642
|
+
if (!(e instanceof ApiError) || e.status !== 409 || !e.body || typeof e.body !== "object")
|
|
643
|
+
return e;
|
|
644
|
+
const body = e.body;
|
|
645
|
+
const destructive = Array.isArray(body.destructive) ? body.destructive.map(opText) : null;
|
|
646
|
+
const incompatible = typeof body.incompatible === "string" ? body.incompatible : body.incompatible === true ? "" : null;
|
|
647
|
+
if (!destructive && incompatible === null)
|
|
648
|
+
return e;
|
|
649
|
+
return new SchemaChangeError(typeof body.error === "string" ? body.error : "The schema change needs a confirmation", destructive ?? [], incompatible);
|
|
650
|
+
}, releaseFailure = (e, opts) => {
|
|
651
|
+
const schema = schemaChangeOf(e);
|
|
652
|
+
if (schema !== e || !(e instanceof ApiError))
|
|
653
|
+
return schema;
|
|
654
|
+
if (opts.build && e.status === 400)
|
|
655
|
+
return new CliError(`Build failed:
|
|
656
|
+
${e.message}`, 400);
|
|
657
|
+
if (e.status === 502 || e.status === 503) {
|
|
658
|
+
const detail = e.message.trimStart().startsWith("<") ? "" : `: ${e.message.trim()}`;
|
|
659
|
+
const service = opts.build ? "The platform's build service is unavailable" : "The platform could not reach the app backend";
|
|
660
|
+
return new CliError(`${service} (${e.status})${detail}. Nothing changed — try again in a minute.`, e.status);
|
|
661
|
+
}
|
|
662
|
+
return e;
|
|
663
|
+
}, publishProject = async (apiUrl, token, uuid, opts = {}) => {
|
|
664
|
+
try {
|
|
665
|
+
const body = { ...opts.allowDestructive ? { allowDestructive: true } : {}, ...opts.force ? { force: true } : {} };
|
|
666
|
+
return await apiRequest(apiUrl, `/projects/${uuid}/publish`, { token, body });
|
|
667
|
+
} catch (e) {
|
|
668
|
+
throw releaseFailure(e, { build: true });
|
|
669
|
+
}
|
|
670
|
+
}, listReleases = async (apiUrl, token, uuid) => (await apiRequest(apiUrl, `/projects/${uuid}/publish`, { token })).releases, useRelease = async (apiUrl, token, uuid, version, opts = {}) => {
|
|
671
|
+
try {
|
|
672
|
+
return await apiRequest(apiUrl, `/projects/${uuid}/releases/${encodeURIComponent(version)}/use`, { token, body: opts.allowDestructive ? { allowDestructive: true } : {} });
|
|
673
|
+
} catch (e) {
|
|
674
|
+
throw releaseFailure(e, { build: false });
|
|
675
|
+
}
|
|
676
|
+
}, uploadReleaseBundle = async (apiUrl, token, uuid, bundle) => {
|
|
677
|
+
const form = new FormData;
|
|
678
|
+
form.append("app", new Blob([bundle.code], { type: "text/javascript" }), "app.js");
|
|
679
|
+
if (bundle.map !== null)
|
|
680
|
+
form.append("map", new Blob([bundle.map], { type: "application/json" }), "app.js.map");
|
|
681
|
+
try {
|
|
682
|
+
return await apiRequest(apiUrl, `/projects/${uuid}/releases/upload`, { token, body: form });
|
|
683
|
+
} catch (e) {
|
|
684
|
+
throw releaseFailure(e, { build: false });
|
|
685
|
+
}
|
|
686
|
+
}, enableDesignShare = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}/design/share`, { token, body: {} }), disableDesignShare = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}/design/share`, { token, method: "DELETE" }), setDesignCommentsOpen = (apiUrl, token, uuid, open) => apiRequest(apiUrl, `/projects/${uuid}/design/comments/open`, { token, body: { open } }), listDesignComments = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}/design/comments`, { token }), createDesignComment = (apiUrl, token, uuid, input) => apiRequest(apiUrl, `/projects/${uuid}/design/comments`, { token, body: input }), replyDesignComment = (apiUrl, token, uuid, threadId, body, origin) => apiRequest(apiUrl, `/projects/${uuid}/design/comments/${threadId}/messages`, { token, body: { body, origin } }), resolveDesignComment = (apiUrl, token, uuid, threadId, resolved) => apiRequest(apiUrl, `/projects/${uuid}/design/comments/${threadId}`, { token, method: "PATCH", body: { resolved } }), sendPush = (apiUrl, token, uuid, payload) => apiRequest(apiUrl, `/projects/${uuid}/push/send`, { token, body: payload }), listPushDevices = (apiUrl, token, uuid, user) => apiRequest(apiUrl, `/projects/${uuid}/push/registrations${user ? `?user=${encodeURIComponent(user)}` : ""}`, { token }), forgetPushDevice = (apiUrl, token, uuid, address) => apiRequest(apiUrl, `/projects/${uuid}/push/registrations/${encodeURIComponent(address)}`, { token, method: "DELETE" }), listPushKeys = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}/push/keys`, { token }), createPushKey = (apiUrl, token, uuid, name) => apiRequest(apiUrl, `/projects/${uuid}/push/keys`, { token, body: { name } }), revokePushKey = (apiUrl, token, uuid, id) => apiRequest(apiUrl, `/projects/${uuid}/push/keys/${id}`, { token, method: "DELETE" }), downloadBlob = async (apiUrl, src) => {
|
|
620
687
|
const resp = await fetch(`${apiUrl}${src}`);
|
|
621
688
|
if (!resp.ok)
|
|
622
689
|
throw new CliError(`Failed to download ${src} (status ${resp.status})`);
|
|
@@ -624,6 +691,22 @@ var errorMessage = (body, status) => {
|
|
|
624
691
|
};
|
|
625
692
|
var init_api = __esm(() => {
|
|
626
693
|
init_cli();
|
|
694
|
+
ApiError = class ApiError extends CliError {
|
|
695
|
+
body;
|
|
696
|
+
constructor(message, status, body) {
|
|
697
|
+
super(message, status);
|
|
698
|
+
this.body = body;
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
SchemaChangeError = class SchemaChangeError extends CliError {
|
|
702
|
+
destructive;
|
|
703
|
+
incompatible;
|
|
704
|
+
constructor(message, destructive, incompatible) {
|
|
705
|
+
super(message, 409);
|
|
706
|
+
this.destructive = destructive;
|
|
707
|
+
this.incompatible = incompatible;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
627
710
|
});
|
|
628
711
|
|
|
629
712
|
// ../../sdk/src/compile/assetName.ts
|
|
@@ -171163,7 +171246,7 @@ var init_shaderSchema = __esm(() => {
|
|
|
171163
171246
|
});
|
|
171164
171247
|
|
|
171165
171248
|
// ../../sdk/src/compile/header.ts
|
|
171166
|
-
var buildHeader = (name, code, opts = {}) => {
|
|
171249
|
+
var ENGINE_SIGNALS, buildHeader = (name, code, opts = {}) => {
|
|
171167
171250
|
let header = `// sdk: ${SDK_VERSION}
|
|
171168
171251
|
`;
|
|
171169
171252
|
header += `// name: ${name.slice(0, 20)}
|
|
@@ -171181,6 +171264,10 @@ var buildHeader = (name, code, opts = {}) => {
|
|
|
171181
171264
|
`;
|
|
171182
171265
|
if (code.includes("_creator2d.createScene"))
|
|
171183
171266
|
header += `// 2d: enable
|
|
171267
|
+
`;
|
|
171268
|
+
for (const [name, re] of ENGINE_SIGNALS)
|
|
171269
|
+
if (re.test(code))
|
|
171270
|
+
header += `// ${name}: enable
|
|
171184
171271
|
`;
|
|
171185
171272
|
if (usesAR(code, opts.plugins))
|
|
171186
171273
|
header += `// ar: enable
|
|
@@ -171214,7 +171301,14 @@ var buildHeader = (name, code, opts = {}) => {
|
|
|
171214
171301
|
`;
|
|
171215
171302
|
return header;
|
|
171216
171303
|
}, usesAR = (code, plugins) => !!plugins?.includes("ar") && code.includes("_creator.createScene");
|
|
171217
|
-
var init_header = () => {
|
|
171304
|
+
var init_header = __esm(() => {
|
|
171305
|
+
ENGINE_SIGNALS = [
|
|
171306
|
+
["physics", /_creator\.(?:physicsCreateBody|physicsConfigure|physicsRaycast|characterCreate|vehicleCreate|ragdollCreate)\(/],
|
|
171307
|
+
["net", /\b_creatorNet\b/],
|
|
171308
|
+
["nav", /_creatorNav\.\w+\(/],
|
|
171309
|
+
["audio", /_creatorAudio\.(?:loadClip|play)\(/]
|
|
171310
|
+
];
|
|
171311
|
+
});
|
|
171218
171312
|
|
|
171219
171313
|
// ../../sdk/src/compile/sourcemap.ts
|
|
171220
171314
|
var offsetSourceMap = (mapJson, lines) => {
|
|
@@ -171228,295 +171322,8 @@ var offsetSourceMap = (mapJson, lines) => {
|
|
|
171228
171322
|
return `//# lecodesMap=data:application/json;charset=utf-8;base64,${b64}`;
|
|
171229
171323
|
};
|
|
171230
171324
|
|
|
171231
|
-
// ../../sdk/src/compile/
|
|
171232
|
-
var
|
|
171233
|
-
const seen = new Map;
|
|
171234
|
-
for (const e of entries)
|
|
171235
|
-
if (e.type === "resource") {
|
|
171236
|
-
const b = localAssetBase(e.path);
|
|
171237
|
-
seen.set(b, (seen.get(b) ?? 0) + 1);
|
|
171238
|
-
}
|
|
171239
|
-
const names = new Map;
|
|
171240
|
-
for (const e of entries)
|
|
171241
|
-
if (e.type === "resource") {
|
|
171242
|
-
const b = localAssetBase(e.path);
|
|
171243
|
-
names.set(e.path, (seen.get(b) ?? 0) > 1 ? e.path.replace(/^[./]+/, "").replace(/[\\/]/g, "__") : b);
|
|
171244
|
-
}
|
|
171245
|
-
return names;
|
|
171246
|
-
}, entriesToFiles = (entries, opts) => {
|
|
171247
|
-
const files = {};
|
|
171248
|
-
const localNames = localAssetNames(entries);
|
|
171249
|
-
const localAssetId = (nameExpr) => `_creatorFetch.local(${nameExpr})`;
|
|
171250
|
-
for (const e of entries) {
|
|
171251
|
-
if (e.type === "resource") {
|
|
171252
|
-
if (opts.localAssets && !e.remote) {
|
|
171253
|
-
const name = localNames.get(e.path) ?? localAssetBase(e.path);
|
|
171254
|
-
files[e.path] = `export default ("id:" + ${localAssetId(JSON.stringify(name))})`;
|
|
171255
|
-
} else {
|
|
171256
|
-
files[e.path] = `export default "${opts.publicUrl}${e.fileSrc ?? ""}"`;
|
|
171257
|
-
}
|
|
171258
|
-
} else if (e.type === "shader") {
|
|
171259
|
-
const shaders = e.shaders ?? [];
|
|
171260
|
-
if (e.shadersLocal && shaders.length > 0) {
|
|
171261
|
-
const name = shaders[0].src.split(/[\/]/).pop() ?? "";
|
|
171262
|
-
const [prefix, suffix = ".filamat"] = name.split("_opengl");
|
|
171263
|
-
files[e.path] = `export default ("id:" + ${localAssetId(`${JSON.stringify(prefix + "_")} + _creator.backend + ${JSON.stringify(suffix)}`)})`;
|
|
171264
|
-
} else if (shaders.length > 0) {
|
|
171265
|
-
files[e.path] = `export default \`${opts.publicUrl}${shaders[0].src.replace("_opengl", "_${_creator.backend}")}\``;
|
|
171266
|
-
} else {
|
|
171267
|
-
files[e.path] = `export default ""`;
|
|
171268
|
-
}
|
|
171269
|
-
} else if (e.path.toLowerCase().endsWith(".svg")) {
|
|
171270
|
-
files[e.path] = `export default SvgSource(\`${e.text ?? ""}\`)`;
|
|
171271
|
-
} else if (e.path.toLowerCase().endsWith(".json")) {
|
|
171272
|
-
files[e.path] = `export default ${e.text?.trim() || "null"}`;
|
|
171273
|
-
} else {
|
|
171274
|
-
files[e.path] = e.text ?? "";
|
|
171275
|
-
}
|
|
171276
|
-
}
|
|
171277
|
-
return files;
|
|
171278
|
-
}, pluginsOfAppJson = (entries) => {
|
|
171279
|
-
const app = entries.find((e) => e.path.replace(/^[./\\]+/, "") === "app.json");
|
|
171280
|
-
if (!app?.text)
|
|
171281
|
-
return [];
|
|
171282
|
-
try {
|
|
171283
|
-
const plugins = JSON.parse(app.text).plugins;
|
|
171284
|
-
return plugins && typeof plugins === "object" && !Array.isArray(plugins) ? Object.keys(plugins) : [];
|
|
171285
|
-
} catch {
|
|
171286
|
-
return [];
|
|
171287
|
-
}
|
|
171288
|
-
}, pluginsOfBundle = (mapJson) => {
|
|
171289
|
-
const ids = new Set;
|
|
171290
|
-
try {
|
|
171291
|
-
for (const source of JSON.parse(mapJson).sources ?? []) {
|
|
171292
|
-
const m = /\/plugins\/gen\/([^/]+)\/sdk\//.exec(source);
|
|
171293
|
-
if (m)
|
|
171294
|
-
ids.add(m[1]);
|
|
171295
|
-
}
|
|
171296
|
-
} catch {}
|
|
171297
|
-
return [...ids].sort();
|
|
171298
|
-
}, compileProjectWithMap = async (opts) => {
|
|
171299
|
-
const published = clientPublishes(opts.entries);
|
|
171300
|
-
if (published.length)
|
|
171301
|
-
throw new ClientPublishError(published);
|
|
171302
|
-
const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "");
|
|
171303
|
-
const entry = opts.entryOverride ?? detectEntry(entries, scanModulesChisel).entry;
|
|
171304
|
-
if (!entry) {
|
|
171305
|
-
throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
|
|
171306
|
-
}
|
|
171307
|
-
const files = entriesToFiles(entries, opts);
|
|
171308
|
-
const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true);
|
|
171309
|
-
const plugins = opts.plugins ?? pluginsOfAppJson(entries);
|
|
171310
|
-
if (map && opts.onWarning) {
|
|
171311
|
-
const missing = pluginsOfBundle(map).filter((id) => !plugins.includes(id));
|
|
171312
|
-
if (missing.length) {
|
|
171313
|
-
opts.onWarning(`The app uses ${missing.map((id) => `"${id}"`).join(", ")} and app.json does not list ${missing.length === 1 ? "it" : "them"}: a plugin that is not listed is not there — in a shell built from this project and in the browser alike. Add to app.json: "plugins": { ${missing.map((id) => `"${id}": "*"`).join(", ")} }`);
|
|
171314
|
-
}
|
|
171315
|
-
}
|
|
171316
|
-
if (opts.header === false)
|
|
171317
|
-
return { code, map, entry };
|
|
171318
|
-
const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader, plugins });
|
|
171319
|
-
const headerLines = (header.match(/\n/g) ?? []).length;
|
|
171320
|
-
return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry };
|
|
171321
|
-
}, compileProject = async (opts) => {
|
|
171322
|
-
const { code, map } = await compileProjectWithMap(opts);
|
|
171323
|
-
return map ? `${code}
|
|
171324
|
-
${inlineSourceMapComment(map)}` : code;
|
|
171325
|
-
};
|
|
171326
|
-
var init_compileProject = __esm(() => {
|
|
171327
|
-
init_bundler();
|
|
171328
|
-
init_detectEntry();
|
|
171329
|
-
init_header();
|
|
171330
|
-
init_serverSplit();
|
|
171331
|
-
});
|
|
171332
|
-
|
|
171333
|
-
// ../../sdk/src/compile/sceneEditor.ts
|
|
171334
|
-
var SCENE_HARNESS_DIR = "./.scene-editor", SCENE_ENTRY_PATH = "./.scene-entry.ts", resolveRelative = (fromPath, spec) => {
|
|
171335
|
-
const segs = (p) => p.split("/").filter((s) => s !== "" && s !== ".");
|
|
171336
|
-
const out = segs(fromPath).slice(0, -1);
|
|
171337
|
-
for (const seg of segs(spec)) {
|
|
171338
|
-
if (seg === "..")
|
|
171339
|
-
out.pop();
|
|
171340
|
-
else
|
|
171341
|
-
out.push(seg);
|
|
171342
|
-
}
|
|
171343
|
-
return "./" + out.join("/");
|
|
171344
|
-
}, sceneImportsOf = (sceneText, scenePath, projectPaths) => {
|
|
171345
|
-
const known = new Set(projectPaths);
|
|
171346
|
-
const out = [];
|
|
171347
|
-
const re = /^\s*import\s+[A-Za-z_$][\w$]*\s+from\s+["']([^"']+\.scene(?:\.ts)?)["']/gm;
|
|
171348
|
-
for (const m of sceneText.matchAll(re)) {
|
|
171349
|
-
const spec = m[1];
|
|
171350
|
-
if (!spec.startsWith("."))
|
|
171351
|
-
continue;
|
|
171352
|
-
let p = resolveRelative(scenePath, spec);
|
|
171353
|
-
if (!p.endsWith(".ts"))
|
|
171354
|
-
p += ".ts";
|
|
171355
|
-
if (known.has(p) && !out.includes(p))
|
|
171356
|
-
out.push(p);
|
|
171357
|
-
}
|
|
171358
|
-
return out;
|
|
171359
|
-
}, sceneEditorEntries = (scenePath, harness, projectPaths = [], sceneImports = []) => {
|
|
171360
|
-
const entries = Object.entries(harness).map(([name, text]) => ({
|
|
171361
|
-
path: `${SCENE_HARNESS_DIR}/${name}`,
|
|
171362
|
-
type: "text",
|
|
171363
|
-
text
|
|
171364
|
-
}));
|
|
171365
|
-
entries.push({
|
|
171366
|
-
path: `${SCENE_HARNESS_DIR}/.edit-mode.ts`,
|
|
171367
|
-
type: "text",
|
|
171368
|
-
text: `;(globalThis as any).__lecodesSceneEdit = true
|
|
171369
|
-
export {}
|
|
171370
|
-
`
|
|
171371
|
-
});
|
|
171372
|
-
const editorImports = projectPaths.filter((p) => p.endsWith(".editor.ts")).map((p) => `import "${p}"
|
|
171373
|
-
`).join("");
|
|
171374
|
-
const materialPaths = projectPaths.filter((p) => p.endsWith(".material.ts"));
|
|
171375
|
-
const materialImports = materialPaths.map((p, i) => `import __mat${i} from "${p}"
|
|
171376
|
-
`).join("");
|
|
171377
|
-
const materialRegister = materialPaths.length === 0 ? "" : `;(globalThis as any).__lecodesMaterials = { ${materialPaths.map((p, i) => `${JSON.stringify(p)}: __mat${i}`).join(", ")} }
|
|
171378
|
-
`;
|
|
171379
|
-
const sceneImportLines = sceneImports.map((p, i) => `import __scn${i} from "${p}"
|
|
171380
|
-
`).join("");
|
|
171381
|
-
const sceneRegister = sceneImports.length === 0 ? "" : `;(globalThis as any).__lecodesSceneFiles = { ${sceneImports.map((p, i) => `${JSON.stringify(p)}: __scn${i}`).join(", ")} }
|
|
171382
|
-
`;
|
|
171383
|
-
entries.push({
|
|
171384
|
-
path: SCENE_ENTRY_PATH,
|
|
171385
|
-
type: "text",
|
|
171386
|
-
text: `import "${SCENE_HARNESS_DIR}/.edit-mode.ts"
|
|
171387
|
-
` + materialImports + sceneImportLines + `import "${scenePath}"
|
|
171388
|
-
` + editorImports + `import "${SCENE_HARNESS_DIR}/main.ts"
|
|
171389
|
-
` + materialRegister + sceneRegister
|
|
171390
|
-
});
|
|
171391
|
-
return entries;
|
|
171392
|
-
};
|
|
171393
|
-
|
|
171394
|
-
// ../../sdk/src/compile/fontRegistry.ts
|
|
171395
|
-
var DEFAULT_FONT_REGISTRY_BASE = "https://cdn.le.codes/fonts/v1", fontRegistryBase = () => typeof process !== "undefined" && process.env?.LECODES_FONT_REGISTRY?.trim() || DEFAULT_FONT_REGISTRY_BASE, faceFileName = (id, weight, italic, ext) => `${id}-${weight}${italic ? "-italic" : ""}.${ext}`, LATIN, CYR, OFL = "OFL-1.1", f = (family, category, scripts, weights, italics, defaultWeights) => ({ family, category, scripts, weights, italics, defaultWeights, license: OFL }), FONT_REGISTRY;
|
|
171396
|
-
var init_fontRegistry = __esm(() => {
|
|
171397
|
-
LATIN = ["latin"];
|
|
171398
|
-
CYR = ["latin", "cyrillic"];
|
|
171399
|
-
FONT_REGISTRY = {
|
|
171400
|
-
inter: f("Inter", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [], [400, 500, 700]),
|
|
171401
|
-
manrope: f("Manrope", "sans-serif", CYR, [200, 300, 400, 500, 600, 700, 800], [], [400, 500, 700]),
|
|
171402
|
-
"golos-text": f("Golos Text", "sans-serif", CYR, [400, 500, 600, 700, 800, 900], [], [400, 500, 700]),
|
|
171403
|
-
onest: f("Onest", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [], [400, 500, 700]),
|
|
171404
|
-
rubik: f("Rubik", "sans-serif", CYR, [300, 400, 500, 600, 700, 800, 900], [300, 400, 500, 600, 700, 800, 900], [400, 500, 700]),
|
|
171405
|
-
nunito: f("Nunito", "sans-serif", CYR, [200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 600, 700]),
|
|
171406
|
-
montserrat: f("Montserrat", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 500, 700]),
|
|
171407
|
-
"ibm-plex-sans": f("IBM Plex Sans", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700], [400, 700], [400, 500, 700]),
|
|
171408
|
-
"pt-sans": f("PT Sans", "sans-serif", CYR, [400, 700], [400, 700], [400, 700]),
|
|
171409
|
-
jost: f("Jost", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 500, 700]),
|
|
171410
|
-
"exo-2": f("Exo 2", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 500, 700]),
|
|
171411
|
-
unbounded: f("Unbounded", "display", CYR, [200, 300, 400, 500, 600, 700, 800, 900], [], [400, 700]),
|
|
171412
|
-
"russo-one": f("Russo One", "display", CYR, [400], [], [400]),
|
|
171413
|
-
oswald: f("Oswald", "display", CYR, [200, 300, 400, 500, 600, 700], [], [400, 700]),
|
|
171414
|
-
comfortaa: f("Comfortaa", "display", CYR, [300, 400, 500, 600, 700], [], [400, 700]),
|
|
171415
|
-
"space-grotesk": f("Space Grotesk", "display", LATIN, [300, 400, 500, 600, 700], [], [400, 700]),
|
|
171416
|
-
"playfair-display": f("Playfair Display", "serif", CYR, [400, 500, 600, 700, 800, 900], [400, 700], [400, 700]),
|
|
171417
|
-
lora: f("Lora", "serif", CYR, [400, 500, 600, 700], [400, 700], [400, 700]),
|
|
171418
|
-
"pt-serif": f("PT Serif", "serif", CYR, [400, 700], [400, 700], [400, 700]),
|
|
171419
|
-
cormorant: f("Cormorant", "serif", CYR, [300, 400, 500, 600, 700], [400, 700], [400, 700]),
|
|
171420
|
-
literata: f("Literata", "serif", CYR, [200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 700]),
|
|
171421
|
-
"jetbrains-mono": f("JetBrains Mono", "monospace", CYR, [100, 200, 300, 400, 500, 600, 700, 800], [400, 700], [400, 700]),
|
|
171422
|
-
"fira-code": f("Fira Code", "monospace", CYR, [300, 400, 500, 600, 700], [], [400, 700]),
|
|
171423
|
-
"ibm-plex-mono": f("IBM Plex Mono", "monospace", CYR, [100, 200, 300, 400, 500, 600, 700], [400, 700], [400, 700]),
|
|
171424
|
-
caveat: f("Caveat", "handwriting", CYR, [400, 500, 600, 700], [], [400, 700]),
|
|
171425
|
-
pacifico: f("Pacifico", "handwriting", CYR, [400], [], [400])
|
|
171426
|
-
};
|
|
171427
|
-
});
|
|
171428
|
-
|
|
171429
|
-
// ../../sdk/src/compile/sfnt.ts
|
|
171430
|
-
var TAG_TTF = 65536, TAG_OTTO = 1330926671, TAG_TTCF = 1953784678, TAG_WOFF = 2001684038, TAG_WOF2 = 2001684018, parseSfnt = (bytes) => {
|
|
171431
|
-
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
171432
|
-
if (bytes.byteLength < 12)
|
|
171433
|
-
throw new Error("not a font file (truncated)");
|
|
171434
|
-
const version = dv.getUint32(0);
|
|
171435
|
-
if (version === TAG_TTCF)
|
|
171436
|
-
throw new Error("font collections (.ttc) aren't supported — export a single face");
|
|
171437
|
-
if (version === TAG_WOFF || version === TAG_WOF2)
|
|
171438
|
-
throw new Error("WOFF/WOFF2 project fonts aren't supported — use the .ttf/.otf");
|
|
171439
|
-
if (version !== TAG_TTF && version !== TAG_OTTO)
|
|
171440
|
-
throw new Error("not a TTF/OTF font file");
|
|
171441
|
-
const numTables = dv.getUint16(4);
|
|
171442
|
-
const tables = new Map;
|
|
171443
|
-
for (let i = 0;i < numTables; i++) {
|
|
171444
|
-
const rec = 12 + i * 16;
|
|
171445
|
-
if (rec + 16 > bytes.byteLength)
|
|
171446
|
-
throw new Error("not a font file (truncated table directory)");
|
|
171447
|
-
const tag = String.fromCharCode(bytes[rec], bytes[rec + 1], bytes[rec + 2], bytes[rec + 3]);
|
|
171448
|
-
tables.set(tag, { offset: dv.getUint32(rec + 8), length: dv.getUint32(rec + 12) });
|
|
171449
|
-
}
|
|
171450
|
-
const family = readFamily(dv, bytes, tables.get("name"));
|
|
171451
|
-
if (!family)
|
|
171452
|
-
throw new Error("no family name in the font's `name` table");
|
|
171453
|
-
let weight = 400;
|
|
171454
|
-
let italic = false;
|
|
171455
|
-
const os2 = tables.get("OS/2");
|
|
171456
|
-
if (os2 && os2.offset + 64 <= bytes.byteLength) {
|
|
171457
|
-
weight = dv.getUint16(os2.offset + 4);
|
|
171458
|
-
italic = (dv.getUint16(os2.offset + 62) & 1) === 1;
|
|
171459
|
-
} else {
|
|
171460
|
-
const head = tables.get("head");
|
|
171461
|
-
if (head && head.offset + 46 <= bytes.byteLength) {
|
|
171462
|
-
const macStyle = dv.getUint16(head.offset + 44);
|
|
171463
|
-
if (macStyle & 1)
|
|
171464
|
-
weight = 700;
|
|
171465
|
-
italic = (macStyle & 2) === 2;
|
|
171466
|
-
}
|
|
171467
|
-
}
|
|
171468
|
-
if (weight < 1 || weight > 1000)
|
|
171469
|
-
weight = 400;
|
|
171470
|
-
return { family, weight, italic };
|
|
171471
|
-
}, readFamily = (dv, bytes, name) => {
|
|
171472
|
-
if (!name || name.offset + 6 > bytes.byteLength)
|
|
171473
|
-
return "";
|
|
171474
|
-
const base = name.offset;
|
|
171475
|
-
const count = dv.getUint16(base + 2);
|
|
171476
|
-
const stringOffset = base + dv.getUint16(base + 4);
|
|
171477
|
-
let best = "";
|
|
171478
|
-
let bestScore = -1;
|
|
171479
|
-
for (let i = 0;i < count; i++) {
|
|
171480
|
-
const rec = base + 6 + i * 12;
|
|
171481
|
-
if (rec + 12 > bytes.byteLength)
|
|
171482
|
-
break;
|
|
171483
|
-
const platformID = dv.getUint16(rec);
|
|
171484
|
-
const languageID = dv.getUint16(rec + 4);
|
|
171485
|
-
const nameID = dv.getUint16(rec + 6);
|
|
171486
|
-
const length = dv.getUint16(rec + 8);
|
|
171487
|
-
const offset = stringOffset + dv.getUint16(rec + 10);
|
|
171488
|
-
if (nameID !== 16 && nameID !== 1)
|
|
171489
|
-
continue;
|
|
171490
|
-
if (offset + length > bytes.byteLength)
|
|
171491
|
-
continue;
|
|
171492
|
-
let score = nameID === 16 ? 100 : 0;
|
|
171493
|
-
if (platformID === 3)
|
|
171494
|
-
score += languageID === 1033 ? 30 : 20;
|
|
171495
|
-
else if (platformID === 0)
|
|
171496
|
-
score += 10;
|
|
171497
|
-
if (score <= bestScore)
|
|
171498
|
-
continue;
|
|
171499
|
-
const text = platformID === 1 ? decodeLatin1(bytes, offset, length) : decodeUtf16be(bytes, offset, length);
|
|
171500
|
-
if (!text.trim())
|
|
171501
|
-
continue;
|
|
171502
|
-
best = text.trim();
|
|
171503
|
-
bestScore = score;
|
|
171504
|
-
}
|
|
171505
|
-
return best;
|
|
171506
|
-
}, decodeUtf16be = (bytes, offset, length) => {
|
|
171507
|
-
let out = "";
|
|
171508
|
-
for (let i = 0;i + 1 < length; i += 2)
|
|
171509
|
-
out += String.fromCharCode(bytes[offset + i] << 8 | bytes[offset + i + 1]);
|
|
171510
|
-
return out;
|
|
171511
|
-
}, decodeLatin1 = (bytes, offset, length) => {
|
|
171512
|
-
let out = "";
|
|
171513
|
-
for (let i = 0;i < length; i++)
|
|
171514
|
-
out += String.fromCharCode(bytes[offset + i]);
|
|
171515
|
-
return out;
|
|
171516
|
-
};
|
|
171517
|
-
|
|
171518
|
-
// ../../sdk/src/compile/fontMacro.ts
|
|
171519
|
-
var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$", isIdentPart = (c) => isIdentStart(c) || c >= "0" && c <= "9", skipString2 = (src, start) => {
|
|
171325
|
+
// ../../sdk/src/compile/assetIconMacro.ts
|
|
171326
|
+
var ICON_ID, isIdentStart = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$", isIdentPart = (c) => isIdentStart(c) || c >= "0" && c <= "9", skipString2 = (src, start) => {
|
|
171520
171327
|
const quote = src[start];
|
|
171521
171328
|
const n = src.length;
|
|
171522
171329
|
let i = start + 1;
|
|
@@ -171626,12 +171433,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c) => c >= "A" && c <= "Z
|
|
|
171626
171433
|
if (inner.includes(q) && !inner.includes("\\" + q))
|
|
171627
171434
|
return null;
|
|
171628
171435
|
return inner.replace(/\\(.)/g, "$1");
|
|
171629
|
-
},
|
|
171630
|
-
let k = i - 1;
|
|
171631
|
-
while (k >= 0 && /\s/.test(src[k]))
|
|
171632
|
-
k--;
|
|
171633
|
-
return k >= 0 ? src[k] : "";
|
|
171634
|
-
}, scanFontCalls = (source) => {
|
|
171436
|
+
}, scanAssetIconCalls = (source) => {
|
|
171635
171437
|
const calls = [];
|
|
171636
171438
|
const n = source.length;
|
|
171637
171439
|
let i = 0;
|
|
@@ -171659,7 +171461,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c) => c >= "A" && c <= "Z
|
|
|
171659
171461
|
let j = i + 1;
|
|
171660
171462
|
while (j < n && isIdentPart(source[j]))
|
|
171661
171463
|
j++;
|
|
171662
|
-
if (source.slice(i, j) === "
|
|
171464
|
+
if (source.slice(i, j) === "assetIcon" && prevNonSpace(source, i) !== ".") {
|
|
171663
171465
|
const call = parseCall(source, i, j);
|
|
171664
171466
|
if (call) {
|
|
171665
171467
|
calls.push(call);
|
|
@@ -171673,6 +171475,11 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c) => c >= "A" && c <= "Z
|
|
|
171673
171475
|
i++;
|
|
171674
171476
|
}
|
|
171675
171477
|
return calls;
|
|
171478
|
+
}, prevNonSpace = (src, i) => {
|
|
171479
|
+
let k = i - 1;
|
|
171480
|
+
while (k >= 0 && /\s/.test(src[k]))
|
|
171481
|
+
k--;
|
|
171482
|
+
return k >= 0 ? src[k] : "";
|
|
171676
171483
|
}, parseCall = (src, start, identEnd) => {
|
|
171677
171484
|
const n = src.length;
|
|
171678
171485
|
let i = identEnd;
|
|
@@ -171683,9 +171490,621 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c) => c >= "A" && c <= "Z
|
|
|
171683
171490
|
const close = matchParen(src, i);
|
|
171684
171491
|
if (close === -1)
|
|
171685
171492
|
return null;
|
|
171686
|
-
const call = { start, end: close + 1,
|
|
171493
|
+
const call = { start, end: close + 1, id: "", pack: "", name: "" };
|
|
171687
171494
|
const args = splitTopLevel(src.slice(i + 1, close), ",");
|
|
171688
|
-
const
|
|
171495
|
+
const id = stringLiteralValue(args[0] ?? "");
|
|
171496
|
+
if (id === null) {
|
|
171497
|
+
call.error = `assetIcon(...) needs a string-literal id like "lucide:bell" (got \`${(args[0] ?? "").trim() || "()"}\`).`;
|
|
171498
|
+
return call;
|
|
171499
|
+
}
|
|
171500
|
+
const m = ICON_ID.exec(id);
|
|
171501
|
+
if (!m) {
|
|
171502
|
+
call.error = `assetIcon("${id}"): id must be "pack:name", lowercase [a-z0-9-] (e.g. "lucide:bell").`;
|
|
171503
|
+
return call;
|
|
171504
|
+
}
|
|
171505
|
+
call.id = id;
|
|
171506
|
+
call.pack = m[1];
|
|
171507
|
+
call.name = m[2];
|
|
171508
|
+
const optsText = args[1]?.trim();
|
|
171509
|
+
if (optsText) {
|
|
171510
|
+
const color = extractColor(optsText);
|
|
171511
|
+
if (color.error) {
|
|
171512
|
+
call.error = color.error;
|
|
171513
|
+
return call;
|
|
171514
|
+
}
|
|
171515
|
+
if (color.bake !== undefined)
|
|
171516
|
+
call.bake = color.bake;
|
|
171517
|
+
else if (color.tint !== undefined)
|
|
171518
|
+
call.tint = color.tint;
|
|
171519
|
+
}
|
|
171520
|
+
return call;
|
|
171521
|
+
}, extractColor = (optsText) => {
|
|
171522
|
+
if (!optsText.startsWith("{") || !optsText.endsWith("}")) {
|
|
171523
|
+
return { error: `assetIcon(id, opts): opts must be an object literal like { color: "#5b8cff" }.` };
|
|
171524
|
+
}
|
|
171525
|
+
for (const prop of splitTopLevel(optsText.slice(1, -1), ",")) {
|
|
171526
|
+
const kv = splitTopLevel(prop, ":");
|
|
171527
|
+
if (kv.length < 2)
|
|
171528
|
+
continue;
|
|
171529
|
+
const key = kv[0].trim().replace(/^["'`]|["'`]$/g, "");
|
|
171530
|
+
if (key !== "color")
|
|
171531
|
+
continue;
|
|
171532
|
+
const raw = kv.slice(1).join(":").trim();
|
|
171533
|
+
if (!raw)
|
|
171534
|
+
continue;
|
|
171535
|
+
const literal = stringLiteralValue(raw);
|
|
171536
|
+
return literal !== null ? { bake: literal } : { tint: raw };
|
|
171537
|
+
}
|
|
171538
|
+
return {};
|
|
171539
|
+
}, rewriteAssetIcons = (source, calls, svgById) => {
|
|
171540
|
+
let out = source;
|
|
171541
|
+
for (const call of [...calls].sort((a, b) => b.start - a.start)) {
|
|
171542
|
+
const svg = svgById.get(call.id);
|
|
171543
|
+
if (svg === undefined)
|
|
171544
|
+
continue;
|
|
171545
|
+
out = out.slice(0, call.start) + iconExpr(svg, call) + out.slice(call.end);
|
|
171546
|
+
}
|
|
171547
|
+
return out;
|
|
171548
|
+
}, iconExpr = (svg, call) => {
|
|
171549
|
+
const body = call.bake ? svg.split("currentColor").join(call.bake) : svg;
|
|
171550
|
+
const src = `SvgSource(\`${escapeTemplate(body)}\`)`;
|
|
171551
|
+
return call.tint ? `Object.assign(${src}, { tintColor: (${call.tint}) })` : src;
|
|
171552
|
+
}, escapeTemplate = (s) => s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"), applyAssetIconMacro = async (entries, resolveSvgs) => {
|
|
171553
|
+
const scanned = entries.filter((e) => e.type === "text" && typeof e.text === "string" && e.text.includes("assetIcon")).map((e) => ({ entry: e, calls: scanAssetIconCalls(e.text) })).filter((s) => s.calls.length > 0);
|
|
171554
|
+
if (scanned.length === 0)
|
|
171555
|
+
return [];
|
|
171556
|
+
const errors = scanned.flatMap((s) => s.calls.filter((c) => c.error).map((c) => c.error));
|
|
171557
|
+
if (errors.length)
|
|
171558
|
+
throw new Error([...new Set(errors)].join(`
|
|
171559
|
+
`));
|
|
171560
|
+
const ids = [...new Set(scanned.flatMap((s) => s.calls.map((c) => c.id)))];
|
|
171561
|
+
const svgById = await resolveSvgs(ids);
|
|
171562
|
+
const missing = ids.filter((id) => !svgById.has(id));
|
|
171563
|
+
if (missing.length)
|
|
171564
|
+
throw new Error(`assetIcon: could not resolve icon(s): ${missing.join(", ")}.`);
|
|
171565
|
+
for (const { entry, calls } of scanned)
|
|
171566
|
+
entry.text = rewriteAssetIcons(entry.text, calls, svgById);
|
|
171567
|
+
return ids;
|
|
171568
|
+
}, buildIconsHeaderValue = (ids) => [...new Set(ids.map((id) => id.split(":")[0]))].sort().join(";"), editDistance = (a, b, cap) => {
|
|
171569
|
+
if (Math.abs(a.length - b.length) > cap)
|
|
171570
|
+
return cap + 1;
|
|
171571
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
171572
|
+
for (let i = 1;i <= a.length; i++) {
|
|
171573
|
+
const cur = [i];
|
|
171574
|
+
let rowMin = i;
|
|
171575
|
+
for (let j = 1;j <= b.length; j++) {
|
|
171576
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
171577
|
+
if (cur[j] < rowMin)
|
|
171578
|
+
rowMin = cur[j];
|
|
171579
|
+
}
|
|
171580
|
+
if (rowMin > cap)
|
|
171581
|
+
return cap + 1;
|
|
171582
|
+
prev = cur;
|
|
171583
|
+
}
|
|
171584
|
+
return prev[b.length];
|
|
171585
|
+
}, suggestIconNames = (want, icons) => {
|
|
171586
|
+
const wantParts = new Set(want.split("-").filter(Boolean));
|
|
171587
|
+
const scored = [];
|
|
171588
|
+
for (const [name, info] of Object.entries(icons)) {
|
|
171589
|
+
const lenDiff = Math.abs(name.length - want.length) / 100;
|
|
171590
|
+
let rank;
|
|
171591
|
+
if (name.includes(want) || want.includes(name))
|
|
171592
|
+
rank = lenDiff;
|
|
171593
|
+
else {
|
|
171594
|
+
const d = editDistance(want, name, 2);
|
|
171595
|
+
if (d <= 2)
|
|
171596
|
+
rank = 1 + d;
|
|
171597
|
+
else if (info.kw?.some((k) => wantParts.has(k)))
|
|
171598
|
+
rank = 4 + lenDiff;
|
|
171599
|
+
else
|
|
171600
|
+
continue;
|
|
171601
|
+
}
|
|
171602
|
+
scored.push({ name, rank });
|
|
171603
|
+
}
|
|
171604
|
+
return scored.sort((a, b) => a.rank - b.rank || (a.name < b.name ? -1 : 1)).slice(0, 5).map((s) => s.name);
|
|
171605
|
+
}, iconNotFoundError = async (pack, name, read, registry) => {
|
|
171606
|
+
const metaRaw = await read(`${pack}/meta.json`);
|
|
171607
|
+
if (metaRaw !== null) {
|
|
171608
|
+
let icons = {};
|
|
171609
|
+
try {
|
|
171610
|
+
icons = JSON.parse(metaRaw).icons ?? {};
|
|
171611
|
+
} catch {}
|
|
171612
|
+
const similar = suggestIconNames(name, icons);
|
|
171613
|
+
const hint = similar.length ? ` Did you mean ${similar.map((s) => `"${pack}:${s}"`).join(", ")}?` : ` No similar name in the "${pack}" pack either.`;
|
|
171614
|
+
return new Error(`assetIcon: icon "${pack}:${name}" not found.${hint}`);
|
|
171615
|
+
}
|
|
171616
|
+
const licRaw = await read("licenses.json");
|
|
171617
|
+
if (licRaw !== null) {
|
|
171618
|
+
try {
|
|
171619
|
+
const packs = Object.keys(JSON.parse(licRaw)).sort();
|
|
171620
|
+
return new Error(`assetIcon: unknown icon pack "${pack}" (in "${pack}:${name}"). Available packs: ${packs.join(", ")}.`);
|
|
171621
|
+
} catch {}
|
|
171622
|
+
}
|
|
171623
|
+
return Object.assign(new Error(`assetIcon: can't reach the icon registry${registry ? ` (${registry})` : ""} to resolve "${pack}:${name}". ` + `Check your connection, or set LECODES_ICON_REGISTRY.`), { transient: true });
|
|
171624
|
+
}, VENDORED_ICON_PATH, collectVendoredIcons = (entries) => {
|
|
171625
|
+
const map = new Map;
|
|
171626
|
+
for (const e of entries) {
|
|
171627
|
+
if (e.type !== "text" || typeof e.text !== "string")
|
|
171628
|
+
continue;
|
|
171629
|
+
const m = VENDORED_ICON_PATH.exec(e.path);
|
|
171630
|
+
if (m)
|
|
171631
|
+
map.set(`${m[1]}:${m[2]}`, e.text);
|
|
171632
|
+
}
|
|
171633
|
+
return map;
|
|
171634
|
+
};
|
|
171635
|
+
var init_assetIconMacro = __esm(() => {
|
|
171636
|
+
ICON_ID = /^([a-z0-9]+(?:-[a-z0-9]+)*):([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
171637
|
+
VENDORED_ICON_PATH = /(?:^|\/)assets\/icons\/([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)\.svg$/;
|
|
171638
|
+
});
|
|
171639
|
+
|
|
171640
|
+
// ../../sdk/src/compile/compileProject.ts
|
|
171641
|
+
var localAssetBase = (path) => path.split(/[\\/]/).pop() ?? path, localAssetNames = (entries) => {
|
|
171642
|
+
const seen = new Map;
|
|
171643
|
+
for (const e of entries)
|
|
171644
|
+
if (e.type === "resource") {
|
|
171645
|
+
const b = localAssetBase(e.path);
|
|
171646
|
+
seen.set(b, (seen.get(b) ?? 0) + 1);
|
|
171647
|
+
}
|
|
171648
|
+
const names = new Map;
|
|
171649
|
+
for (const e of entries)
|
|
171650
|
+
if (e.type === "resource") {
|
|
171651
|
+
const b = localAssetBase(e.path);
|
|
171652
|
+
names.set(e.path, (seen.get(b) ?? 0) > 1 ? e.path.replace(/^[./]+/, "").replace(/[\\/]/g, "__") : b);
|
|
171653
|
+
}
|
|
171654
|
+
return names;
|
|
171655
|
+
}, entriesToFiles = (entries, opts) => {
|
|
171656
|
+
const files = {};
|
|
171657
|
+
const localNames = localAssetNames(entries);
|
|
171658
|
+
const localAssetId = (nameExpr) => `_creatorFetch.local(${nameExpr})`;
|
|
171659
|
+
for (const e of entries) {
|
|
171660
|
+
if (e.type === "resource") {
|
|
171661
|
+
if (opts.localAssets && !e.remote) {
|
|
171662
|
+
const name = localNames.get(e.path) ?? localAssetBase(e.path);
|
|
171663
|
+
files[e.path] = `export default ("id:" + ${localAssetId(JSON.stringify(name))})`;
|
|
171664
|
+
} else {
|
|
171665
|
+
files[e.path] = `export default "${opts.publicUrl}${e.fileSrc ?? ""}"`;
|
|
171666
|
+
}
|
|
171667
|
+
} else if (e.type === "shader") {
|
|
171668
|
+
const shaders = e.shaders ?? [];
|
|
171669
|
+
if (e.shadersLocal && shaders.length > 0) {
|
|
171670
|
+
const name = shaders[0].src.split(/[\/]/).pop() ?? "";
|
|
171671
|
+
const [prefix, suffix = ".filamat"] = name.split("_opengl");
|
|
171672
|
+
files[e.path] = `export default ("id:" + ${localAssetId(`${JSON.stringify(prefix + "_")} + _creator.backend + ${JSON.stringify(suffix)}`)})`;
|
|
171673
|
+
} else if (shaders.length > 0) {
|
|
171674
|
+
files[e.path] = `export default \`${opts.publicUrl}${shaders[0].src.replace("_opengl", "_${_creator.backend}")}\``;
|
|
171675
|
+
} else {
|
|
171676
|
+
files[e.path] = `export default ""`;
|
|
171677
|
+
}
|
|
171678
|
+
} else if (e.path.toLowerCase().endsWith(".svg")) {
|
|
171679
|
+
files[e.path] = `export default SvgSource(\`${escapeTemplate(e.text ?? "")}\`)`;
|
|
171680
|
+
} else if (e.path.toLowerCase().endsWith(".json")) {
|
|
171681
|
+
files[e.path] = `export default ${e.text?.trim() || "null"}`;
|
|
171682
|
+
} else if (!CODE_EXT.test(e.path)) {
|
|
171683
|
+
files[e.path] = `export default ${JSON.stringify(e.text ?? "")}`;
|
|
171684
|
+
} else {
|
|
171685
|
+
files[e.path] = e.text ?? "";
|
|
171686
|
+
}
|
|
171687
|
+
}
|
|
171688
|
+
return files;
|
|
171689
|
+
}, pluginSpecsOf = (plugins) => {
|
|
171690
|
+
if (Array.isArray(plugins))
|
|
171691
|
+
return Object.fromEntries(plugins.filter((p) => typeof p === "string").map((id) => [id, "*"]));
|
|
171692
|
+
if (plugins && typeof plugins === "object") {
|
|
171693
|
+
return Object.fromEntries(Object.entries(plugins).map(([id, v]) => [id, typeof v === "string" ? v : "*"]));
|
|
171694
|
+
}
|
|
171695
|
+
return {};
|
|
171696
|
+
}, pluginIdsOf = (plugins) => Object.keys(pluginSpecsOf(plugins)), pluginsOfAppJson = (entries) => {
|
|
171697
|
+
const app = entries.find((e) => e.path.replace(/^[./\\]+/, "") === "app.json");
|
|
171698
|
+
if (!app?.text)
|
|
171699
|
+
return [];
|
|
171700
|
+
try {
|
|
171701
|
+
return pluginIdsOf(JSON.parse(app.text).plugins);
|
|
171702
|
+
} catch {
|
|
171703
|
+
return [];
|
|
171704
|
+
}
|
|
171705
|
+
}, pluginsOfBundle = (mapJson) => {
|
|
171706
|
+
const ids = new Set;
|
|
171707
|
+
try {
|
|
171708
|
+
for (const source of JSON.parse(mapJson).sources ?? []) {
|
|
171709
|
+
const m = /\/plugins\/gen\/([^/]+)\/sdk\//.exec(source);
|
|
171710
|
+
if (m)
|
|
171711
|
+
ids.add(m[1]);
|
|
171712
|
+
}
|
|
171713
|
+
} catch {}
|
|
171714
|
+
return [...ids].sort();
|
|
171715
|
+
}, compileProjectWithMap = async (opts) => {
|
|
171716
|
+
const published = clientPublishes(opts.entries);
|
|
171717
|
+
if (published.length)
|
|
171718
|
+
throw new ClientPublishError(published);
|
|
171719
|
+
const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "");
|
|
171720
|
+
const entry = opts.entryOverride ?? detectEntry(entries, scanModulesChisel).entry;
|
|
171721
|
+
if (!entry) {
|
|
171722
|
+
throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
|
|
171723
|
+
}
|
|
171724
|
+
const files = entriesToFiles(entries, opts);
|
|
171725
|
+
const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true);
|
|
171726
|
+
const plugins = opts.plugins ?? pluginsOfAppJson(entries);
|
|
171727
|
+
if (map && opts.onWarning) {
|
|
171728
|
+
const missing = pluginsOfBundle(map).filter((id) => !plugins.includes(id));
|
|
171729
|
+
if (missing.length) {
|
|
171730
|
+
opts.onWarning(`The app uses ${missing.map((id) => `"${id}"`).join(", ")} and app.json does not list ${missing.length === 1 ? "it" : "them"}: a plugin that is not listed is not there — in a shell built from this project and in the browser alike. Add to app.json: "plugins": { ${missing.map((id) => `"${id}": "*"`).join(", ")} }`);
|
|
171731
|
+
}
|
|
171732
|
+
}
|
|
171733
|
+
if (opts.header === false)
|
|
171734
|
+
return { code, map, entry };
|
|
171735
|
+
const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader, plugins });
|
|
171736
|
+
const headerLines = (header.match(/\n/g) ?? []).length;
|
|
171737
|
+
return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry };
|
|
171738
|
+
}, compileProject = async (opts) => {
|
|
171739
|
+
const { code, map } = await compileProjectWithMap(opts);
|
|
171740
|
+
return map ? `${code}
|
|
171741
|
+
${inlineSourceMapComment(map)}` : code;
|
|
171742
|
+
};
|
|
171743
|
+
var init_compileProject = __esm(() => {
|
|
171744
|
+
init_bundler();
|
|
171745
|
+
init_detectEntry();
|
|
171746
|
+
init_header();
|
|
171747
|
+
init_serverSplit();
|
|
171748
|
+
init_assetMacro();
|
|
171749
|
+
init_assetIconMacro();
|
|
171750
|
+
});
|
|
171751
|
+
|
|
171752
|
+
// ../../sdk/src/compile/sceneEditor.ts
|
|
171753
|
+
var SCENE_HARNESS_DIR = "./.scene-editor", SCENE_ENTRY_PATH = "./.scene-entry.ts", resolveRelative = (fromPath, spec) => {
|
|
171754
|
+
const segs = (p) => p.split("/").filter((s) => s !== "" && s !== ".");
|
|
171755
|
+
const out = segs(fromPath).slice(0, -1);
|
|
171756
|
+
for (const seg of segs(spec)) {
|
|
171757
|
+
if (seg === "..")
|
|
171758
|
+
out.pop();
|
|
171759
|
+
else
|
|
171760
|
+
out.push(seg);
|
|
171761
|
+
}
|
|
171762
|
+
return "./" + out.join("/");
|
|
171763
|
+
}, sceneImportsOf = (sceneText, scenePath, projectPaths) => {
|
|
171764
|
+
const known = new Set(projectPaths);
|
|
171765
|
+
const out = [];
|
|
171766
|
+
const re = /^\s*import\s+[A-Za-z_$][\w$]*\s+from\s+["']([^"']+\.scene(?:\.ts)?)["']/gm;
|
|
171767
|
+
for (const m of sceneText.matchAll(re)) {
|
|
171768
|
+
const spec = m[1];
|
|
171769
|
+
if (!spec.startsWith("."))
|
|
171770
|
+
continue;
|
|
171771
|
+
let p = resolveRelative(scenePath, spec);
|
|
171772
|
+
if (!p.endsWith(".ts"))
|
|
171773
|
+
p += ".ts";
|
|
171774
|
+
if (known.has(p) && !out.includes(p))
|
|
171775
|
+
out.push(p);
|
|
171776
|
+
}
|
|
171777
|
+
return out;
|
|
171778
|
+
}, sceneEditorEntries = (scenePath, harness, projectPaths = [], sceneImports = []) => {
|
|
171779
|
+
const entries = Object.entries(harness).map(([name, text]) => ({
|
|
171780
|
+
path: `${SCENE_HARNESS_DIR}/${name}`,
|
|
171781
|
+
type: "text",
|
|
171782
|
+
text
|
|
171783
|
+
}));
|
|
171784
|
+
entries.push({
|
|
171785
|
+
path: `${SCENE_HARNESS_DIR}/.edit-mode.ts`,
|
|
171786
|
+
type: "text",
|
|
171787
|
+
text: `;(globalThis as any).__lecodesSceneEdit = true
|
|
171788
|
+
export {}
|
|
171789
|
+
`
|
|
171790
|
+
});
|
|
171791
|
+
const editorImports = projectPaths.filter((p) => p.endsWith(".editor.ts")).map((p) => `import "${p}"
|
|
171792
|
+
`).join("");
|
|
171793
|
+
const materialPaths = projectPaths.filter((p) => p.endsWith(".material.ts"));
|
|
171794
|
+
const materialImports = materialPaths.map((p, i) => `import __mat${i} from "${p}"
|
|
171795
|
+
`).join("");
|
|
171796
|
+
const materialRegister = materialPaths.length === 0 ? "" : `;(globalThis as any).__lecodesMaterials = { ${materialPaths.map((p, i) => `${JSON.stringify(p)}: __mat${i}`).join(", ")} }
|
|
171797
|
+
`;
|
|
171798
|
+
const sceneImportLines = sceneImports.map((p, i) => `import __scn${i} from "${p}"
|
|
171799
|
+
`).join("");
|
|
171800
|
+
const sceneRegister = sceneImports.length === 0 ? "" : `;(globalThis as any).__lecodesSceneFiles = { ${sceneImports.map((p, i) => `${JSON.stringify(p)}: __scn${i}`).join(", ")} }
|
|
171801
|
+
`;
|
|
171802
|
+
entries.push({
|
|
171803
|
+
path: SCENE_ENTRY_PATH,
|
|
171804
|
+
type: "text",
|
|
171805
|
+
text: `import "${SCENE_HARNESS_DIR}/.edit-mode.ts"
|
|
171806
|
+
` + materialImports + sceneImportLines + `import "${scenePath}"
|
|
171807
|
+
` + editorImports + `import "${SCENE_HARNESS_DIR}/main.ts"
|
|
171808
|
+
` + materialRegister + sceneRegister
|
|
171809
|
+
});
|
|
171810
|
+
return entries;
|
|
171811
|
+
};
|
|
171812
|
+
|
|
171813
|
+
// ../../sdk/src/compile/fontRegistry.ts
|
|
171814
|
+
var DEFAULT_FONT_REGISTRY_BASE = "https://cdn.le.codes/fonts/v1", fontRegistryBase = () => typeof process !== "undefined" && process.env?.LECODES_FONT_REGISTRY?.trim() || DEFAULT_FONT_REGISTRY_BASE, faceFileName = (id, weight, italic, ext) => `${id}-${weight}${italic ? "-italic" : ""}.${ext}`, LATIN, CYR, OFL = "OFL-1.1", f = (family, category, scripts, weights, italics, defaultWeights) => ({ family, category, scripts, weights, italics, defaultWeights, license: OFL }), FONT_REGISTRY;
|
|
171815
|
+
var init_fontRegistry = __esm(() => {
|
|
171816
|
+
LATIN = ["latin"];
|
|
171817
|
+
CYR = ["latin", "cyrillic"];
|
|
171818
|
+
FONT_REGISTRY = {
|
|
171819
|
+
inter: f("Inter", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [], [400, 500, 700]),
|
|
171820
|
+
manrope: f("Manrope", "sans-serif", CYR, [200, 300, 400, 500, 600, 700, 800], [], [400, 500, 700]),
|
|
171821
|
+
"golos-text": f("Golos Text", "sans-serif", CYR, [400, 500, 600, 700, 800, 900], [], [400, 500, 700]),
|
|
171822
|
+
onest: f("Onest", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [], [400, 500, 700]),
|
|
171823
|
+
rubik: f("Rubik", "sans-serif", CYR, [300, 400, 500, 600, 700, 800, 900], [300, 400, 500, 600, 700, 800, 900], [400, 500, 700]),
|
|
171824
|
+
nunito: f("Nunito", "sans-serif", CYR, [200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 600, 700]),
|
|
171825
|
+
montserrat: f("Montserrat", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 500, 700]),
|
|
171826
|
+
"ibm-plex-sans": f("IBM Plex Sans", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700], [400, 700], [400, 500, 700]),
|
|
171827
|
+
"pt-sans": f("PT Sans", "sans-serif", CYR, [400, 700], [400, 700], [400, 700]),
|
|
171828
|
+
jost: f("Jost", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 500, 700]),
|
|
171829
|
+
"exo-2": f("Exo 2", "sans-serif", CYR, [100, 200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 500, 700]),
|
|
171830
|
+
unbounded: f("Unbounded", "display", CYR, [200, 300, 400, 500, 600, 700, 800, 900], [], [400, 700]),
|
|
171831
|
+
"russo-one": f("Russo One", "display", CYR, [400], [], [400]),
|
|
171832
|
+
oswald: f("Oswald", "display", CYR, [200, 300, 400, 500, 600, 700], [], [400, 700]),
|
|
171833
|
+
comfortaa: f("Comfortaa", "display", CYR, [300, 400, 500, 600, 700], [], [400, 700]),
|
|
171834
|
+
"space-grotesk": f("Space Grotesk", "display", LATIN, [300, 400, 500, 600, 700], [], [400, 700]),
|
|
171835
|
+
"playfair-display": f("Playfair Display", "serif", CYR, [400, 500, 600, 700, 800, 900], [400, 700], [400, 700]),
|
|
171836
|
+
lora: f("Lora", "serif", CYR, [400, 500, 600, 700], [400, 700], [400, 700]),
|
|
171837
|
+
"pt-serif": f("PT Serif", "serif", CYR, [400, 700], [400, 700], [400, 700]),
|
|
171838
|
+
cormorant: f("Cormorant", "serif", CYR, [300, 400, 500, 600, 700], [400, 700], [400, 700]),
|
|
171839
|
+
literata: f("Literata", "serif", CYR, [200, 300, 400, 500, 600, 700, 800, 900], [400, 700], [400, 700]),
|
|
171840
|
+
"jetbrains-mono": f("JetBrains Mono", "monospace", CYR, [100, 200, 300, 400, 500, 600, 700, 800], [400, 700], [400, 700]),
|
|
171841
|
+
"fira-code": f("Fira Code", "monospace", CYR, [300, 400, 500, 600, 700], [], [400, 700]),
|
|
171842
|
+
"ibm-plex-mono": f("IBM Plex Mono", "monospace", CYR, [100, 200, 300, 400, 500, 600, 700], [400, 700], [400, 700]),
|
|
171843
|
+
caveat: f("Caveat", "handwriting", CYR, [400, 500, 600, 700], [], [400, 700]),
|
|
171844
|
+
pacifico: f("Pacifico", "handwriting", CYR, [400], [], [400])
|
|
171845
|
+
};
|
|
171846
|
+
});
|
|
171847
|
+
|
|
171848
|
+
// ../../sdk/src/compile/sfnt.ts
|
|
171849
|
+
var TAG_TTF = 65536, TAG_OTTO = 1330926671, TAG_TTCF = 1953784678, TAG_WOFF = 2001684038, TAG_WOF2 = 2001684018, parseSfnt = (bytes) => {
|
|
171850
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
171851
|
+
if (bytes.byteLength < 12)
|
|
171852
|
+
throw new Error("not a font file (truncated)");
|
|
171853
|
+
const version = dv.getUint32(0);
|
|
171854
|
+
if (version === TAG_TTCF)
|
|
171855
|
+
throw new Error("font collections (.ttc) aren't supported — export a single face");
|
|
171856
|
+
if (version === TAG_WOFF || version === TAG_WOF2)
|
|
171857
|
+
throw new Error("WOFF/WOFF2 project fonts aren't supported — use the .ttf/.otf");
|
|
171858
|
+
if (version !== TAG_TTF && version !== TAG_OTTO)
|
|
171859
|
+
throw new Error("not a TTF/OTF font file");
|
|
171860
|
+
const numTables = dv.getUint16(4);
|
|
171861
|
+
const tables = new Map;
|
|
171862
|
+
for (let i = 0;i < numTables; i++) {
|
|
171863
|
+
const rec = 12 + i * 16;
|
|
171864
|
+
if (rec + 16 > bytes.byteLength)
|
|
171865
|
+
throw new Error("not a font file (truncated table directory)");
|
|
171866
|
+
const tag = String.fromCharCode(bytes[rec], bytes[rec + 1], bytes[rec + 2], bytes[rec + 3]);
|
|
171867
|
+
tables.set(tag, { offset: dv.getUint32(rec + 8), length: dv.getUint32(rec + 12) });
|
|
171868
|
+
}
|
|
171869
|
+
const family = readFamily(dv, bytes, tables.get("name"));
|
|
171870
|
+
if (!family)
|
|
171871
|
+
throw new Error("no family name in the font's `name` table");
|
|
171872
|
+
let weight = 400;
|
|
171873
|
+
let italic = false;
|
|
171874
|
+
const os2 = tables.get("OS/2");
|
|
171875
|
+
if (os2 && os2.offset + 64 <= bytes.byteLength) {
|
|
171876
|
+
weight = dv.getUint16(os2.offset + 4);
|
|
171877
|
+
italic = (dv.getUint16(os2.offset + 62) & 1) === 1;
|
|
171878
|
+
} else {
|
|
171879
|
+
const head = tables.get("head");
|
|
171880
|
+
if (head && head.offset + 46 <= bytes.byteLength) {
|
|
171881
|
+
const macStyle = dv.getUint16(head.offset + 44);
|
|
171882
|
+
if (macStyle & 1)
|
|
171883
|
+
weight = 700;
|
|
171884
|
+
italic = (macStyle & 2) === 2;
|
|
171885
|
+
}
|
|
171886
|
+
}
|
|
171887
|
+
if (weight < 1 || weight > 1000)
|
|
171888
|
+
weight = 400;
|
|
171889
|
+
return { family, weight, italic };
|
|
171890
|
+
}, readFamily = (dv, bytes, name) => {
|
|
171891
|
+
if (!name || name.offset + 6 > bytes.byteLength)
|
|
171892
|
+
return "";
|
|
171893
|
+
const base = name.offset;
|
|
171894
|
+
const count = dv.getUint16(base + 2);
|
|
171895
|
+
const stringOffset = base + dv.getUint16(base + 4);
|
|
171896
|
+
let best = "";
|
|
171897
|
+
let bestScore = -1;
|
|
171898
|
+
for (let i = 0;i < count; i++) {
|
|
171899
|
+
const rec = base + 6 + i * 12;
|
|
171900
|
+
if (rec + 12 > bytes.byteLength)
|
|
171901
|
+
break;
|
|
171902
|
+
const platformID = dv.getUint16(rec);
|
|
171903
|
+
const languageID = dv.getUint16(rec + 4);
|
|
171904
|
+
const nameID = dv.getUint16(rec + 6);
|
|
171905
|
+
const length = dv.getUint16(rec + 8);
|
|
171906
|
+
const offset = stringOffset + dv.getUint16(rec + 10);
|
|
171907
|
+
if (nameID !== 16 && nameID !== 1)
|
|
171908
|
+
continue;
|
|
171909
|
+
if (offset + length > bytes.byteLength)
|
|
171910
|
+
continue;
|
|
171911
|
+
let score = nameID === 16 ? 100 : 0;
|
|
171912
|
+
if (platformID === 3)
|
|
171913
|
+
score += languageID === 1033 ? 30 : 20;
|
|
171914
|
+
else if (platformID === 0)
|
|
171915
|
+
score += 10;
|
|
171916
|
+
if (score <= bestScore)
|
|
171917
|
+
continue;
|
|
171918
|
+
const text = platformID === 1 ? decodeLatin1(bytes, offset, length) : decodeUtf16be(bytes, offset, length);
|
|
171919
|
+
if (!text.trim())
|
|
171920
|
+
continue;
|
|
171921
|
+
best = text.trim();
|
|
171922
|
+
bestScore = score;
|
|
171923
|
+
}
|
|
171924
|
+
return best;
|
|
171925
|
+
}, decodeUtf16be = (bytes, offset, length) => {
|
|
171926
|
+
let out = "";
|
|
171927
|
+
for (let i = 0;i + 1 < length; i += 2)
|
|
171928
|
+
out += String.fromCharCode(bytes[offset + i] << 8 | bytes[offset + i + 1]);
|
|
171929
|
+
return out;
|
|
171930
|
+
}, decodeLatin1 = (bytes, offset, length) => {
|
|
171931
|
+
let out = "";
|
|
171932
|
+
for (let i = 0;i < length; i++)
|
|
171933
|
+
out += String.fromCharCode(bytes[offset + i]);
|
|
171934
|
+
return out;
|
|
171935
|
+
};
|
|
171936
|
+
|
|
171937
|
+
// ../../sdk/src/compile/fontMacro.ts
|
|
171938
|
+
var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart2 = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$", isIdentPart2 = (c) => isIdentStart2(c) || c >= "0" && c <= "9", skipString3 = (src, start) => {
|
|
171939
|
+
const quote = src[start];
|
|
171940
|
+
const n = src.length;
|
|
171941
|
+
let i = start + 1;
|
|
171942
|
+
while (i < n) {
|
|
171943
|
+
const c = src[i];
|
|
171944
|
+
if (c === "\\") {
|
|
171945
|
+
i += 2;
|
|
171946
|
+
continue;
|
|
171947
|
+
}
|
|
171948
|
+
if (quote === "`" && c === "$" && src[i + 1] === "{") {
|
|
171949
|
+
i = skipBraces2(src, i + 1);
|
|
171950
|
+
continue;
|
|
171951
|
+
}
|
|
171952
|
+
if (c === quote)
|
|
171953
|
+
return i + 1;
|
|
171954
|
+
i++;
|
|
171955
|
+
}
|
|
171956
|
+
return n;
|
|
171957
|
+
}, skipBraces2 = (src, open) => {
|
|
171958
|
+
const n = src.length;
|
|
171959
|
+
let depth = 0;
|
|
171960
|
+
let i = open;
|
|
171961
|
+
while (i < n) {
|
|
171962
|
+
const c = src[i];
|
|
171963
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
171964
|
+
i = skipString3(src, i);
|
|
171965
|
+
continue;
|
|
171966
|
+
}
|
|
171967
|
+
if (c === "{")
|
|
171968
|
+
depth++;
|
|
171969
|
+
else if (c === "}") {
|
|
171970
|
+
depth--;
|
|
171971
|
+
if (depth === 0)
|
|
171972
|
+
return i + 1;
|
|
171973
|
+
}
|
|
171974
|
+
i++;
|
|
171975
|
+
}
|
|
171976
|
+
return n;
|
|
171977
|
+
}, matchParen2 = (src, open) => {
|
|
171978
|
+
const n = src.length;
|
|
171979
|
+
let depth = 0;
|
|
171980
|
+
let i = open;
|
|
171981
|
+
while (i < n) {
|
|
171982
|
+
const c = src[i];
|
|
171983
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
171984
|
+
i = skipString3(src, i);
|
|
171985
|
+
continue;
|
|
171986
|
+
}
|
|
171987
|
+
if (c === "/" && src[i + 1] === "/") {
|
|
171988
|
+
i += 2;
|
|
171989
|
+
while (i < n && src[i] !== `
|
|
171990
|
+
`)
|
|
171991
|
+
i++;
|
|
171992
|
+
continue;
|
|
171993
|
+
}
|
|
171994
|
+
if (c === "/" && src[i + 1] === "*") {
|
|
171995
|
+
i += 2;
|
|
171996
|
+
while (i < n && !(src[i] === "*" && src[i + 1] === "/"))
|
|
171997
|
+
i++;
|
|
171998
|
+
i += 2;
|
|
171999
|
+
continue;
|
|
172000
|
+
}
|
|
172001
|
+
if (c === "(")
|
|
172002
|
+
depth++;
|
|
172003
|
+
else if (c === ")") {
|
|
172004
|
+
depth--;
|
|
172005
|
+
if (depth === 0)
|
|
172006
|
+
return i;
|
|
172007
|
+
}
|
|
172008
|
+
i++;
|
|
172009
|
+
}
|
|
172010
|
+
return -1;
|
|
172011
|
+
}, splitTopLevel2 = (text, delim) => {
|
|
172012
|
+
const parts = [];
|
|
172013
|
+
const n = text.length;
|
|
172014
|
+
let depth = 0;
|
|
172015
|
+
let last = 0;
|
|
172016
|
+
let i = 0;
|
|
172017
|
+
while (i < n) {
|
|
172018
|
+
const c = text[i];
|
|
172019
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
172020
|
+
i = skipString3(text, i);
|
|
172021
|
+
continue;
|
|
172022
|
+
}
|
|
172023
|
+
if (c === "(" || c === "[" || c === "{")
|
|
172024
|
+
depth++;
|
|
172025
|
+
else if (c === ")" || c === "]" || c === "}")
|
|
172026
|
+
depth = Math.max(0, depth - 1);
|
|
172027
|
+
else if (depth === 0 && c === delim) {
|
|
172028
|
+
parts.push(text.slice(last, i));
|
|
172029
|
+
last = i + 1;
|
|
172030
|
+
}
|
|
172031
|
+
i++;
|
|
172032
|
+
}
|
|
172033
|
+
parts.push(text.slice(last));
|
|
172034
|
+
return parts;
|
|
172035
|
+
}, stringLiteralValue2 = (text) => {
|
|
172036
|
+
const t = text.trim();
|
|
172037
|
+
if (t.length < 2)
|
|
172038
|
+
return null;
|
|
172039
|
+
const q = t[0];
|
|
172040
|
+
if (q !== '"' && q !== "'" && q !== "`" || t[t.length - 1] !== q)
|
|
172041
|
+
return null;
|
|
172042
|
+
const inner = t.slice(1, -1);
|
|
172043
|
+
if (q === "`" && inner.includes("${"))
|
|
172044
|
+
return null;
|
|
172045
|
+
if (inner.includes(q) && !inner.includes("\\" + q))
|
|
172046
|
+
return null;
|
|
172047
|
+
return inner.replace(/\\(.)/g, "$1");
|
|
172048
|
+
}, prevNonSpace2 = (src, i) => {
|
|
172049
|
+
let k = i - 1;
|
|
172050
|
+
while (k >= 0 && /\s/.test(src[k]))
|
|
172051
|
+
k--;
|
|
172052
|
+
return k >= 0 ? src[k] : "";
|
|
172053
|
+
}, scanFontCalls = (source) => {
|
|
172054
|
+
const calls = [];
|
|
172055
|
+
const n = source.length;
|
|
172056
|
+
let i = 0;
|
|
172057
|
+
while (i < n) {
|
|
172058
|
+
const c = source[i];
|
|
172059
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
172060
|
+
i = skipString3(source, i);
|
|
172061
|
+
continue;
|
|
172062
|
+
}
|
|
172063
|
+
if (c === "/" && source[i + 1] === "/") {
|
|
172064
|
+
i += 2;
|
|
172065
|
+
while (i < n && source[i] !== `
|
|
172066
|
+
`)
|
|
172067
|
+
i++;
|
|
172068
|
+
continue;
|
|
172069
|
+
}
|
|
172070
|
+
if (c === "/" && source[i + 1] === "*") {
|
|
172071
|
+
i += 2;
|
|
172072
|
+
while (i < n && !(source[i] === "*" && source[i + 1] === "/"))
|
|
172073
|
+
i++;
|
|
172074
|
+
i += 2;
|
|
172075
|
+
continue;
|
|
172076
|
+
}
|
|
172077
|
+
if (isIdentStart2(c)) {
|
|
172078
|
+
let j = i + 1;
|
|
172079
|
+
while (j < n && isIdentPart2(source[j]))
|
|
172080
|
+
j++;
|
|
172081
|
+
if (source.slice(i, j) === "font" && prevNonSpace2(source, i) !== ".") {
|
|
172082
|
+
const call = parseCall2(source, i, j);
|
|
172083
|
+
if (call) {
|
|
172084
|
+
calls.push(call);
|
|
172085
|
+
i = call.end;
|
|
172086
|
+
continue;
|
|
172087
|
+
}
|
|
172088
|
+
}
|
|
172089
|
+
i = j;
|
|
172090
|
+
continue;
|
|
172091
|
+
}
|
|
172092
|
+
i++;
|
|
172093
|
+
}
|
|
172094
|
+
return calls;
|
|
172095
|
+
}, parseCall2 = (src, start, identEnd) => {
|
|
172096
|
+
const n = src.length;
|
|
172097
|
+
let i = identEnd;
|
|
172098
|
+
while (i < n && /\s/.test(src[i]))
|
|
172099
|
+
i++;
|
|
172100
|
+
if (src[i] !== "(")
|
|
172101
|
+
return null;
|
|
172102
|
+
const close = matchParen2(src, i);
|
|
172103
|
+
if (close === -1)
|
|
172104
|
+
return null;
|
|
172105
|
+
const call = { start, end: close + 1, arg: "", kind: "registry" };
|
|
172106
|
+
const args = splitTopLevel2(src.slice(i + 1, close), ",");
|
|
172107
|
+
const arg = stringLiteralValue2(args[0] ?? "");
|
|
171689
172108
|
if (arg === null) {
|
|
171690
172109
|
call.error = `font(...) needs a string-literal argument — a registry id ("manrope") or a project path ("./fonts/Brand.ttf"). Got \`${(args[0] ?? "").trim() || "()"}\`.`;
|
|
171691
172110
|
return call;
|
|
@@ -171726,10 +172145,10 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c) => c >= "A" && c <= "Z
|
|
|
171726
172145
|
if (!optsText.startsWith("{") || !optsText.endsWith("}")) {
|
|
171727
172146
|
return `font("${call.arg}", opts): opts must be an object literal like { weights: [400, 700] }.`;
|
|
171728
172147
|
}
|
|
171729
|
-
for (const prop of
|
|
172148
|
+
for (const prop of splitTopLevel2(optsText.slice(1, -1), ",")) {
|
|
171730
172149
|
if (!prop.trim())
|
|
171731
172150
|
continue;
|
|
171732
|
-
const kv =
|
|
172151
|
+
const kv = splitTopLevel2(prop, ":");
|
|
171733
172152
|
if (kv.length < 2)
|
|
171734
172153
|
return `font("${call.arg}"): couldn't parse option \`${prop.trim()}\`.`;
|
|
171735
172154
|
const key = kv[0].trim().replace(/^["'`]|["'`]$/g, "");
|
|
@@ -171924,321 +172343,6 @@ var init_fontMacro = __esm(() => {
|
|
|
171924
172343
|
DECLARES_FONT = /\b(?:function|const|let|var|class)\s+font\b|\bimport\s+(?:\{[^}]*\bfont\b[^}]*\}|font)\s+from\b/;
|
|
171925
172344
|
});
|
|
171926
172345
|
|
|
171927
|
-
// ../../sdk/src/compile/assetIconMacro.ts
|
|
171928
|
-
var ICON_ID, isIdentStart2 = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$", isIdentPart2 = (c) => isIdentStart2(c) || c >= "0" && c <= "9", skipString3 = (src, start) => {
|
|
171929
|
-
const quote = src[start];
|
|
171930
|
-
const n = src.length;
|
|
171931
|
-
let i = start + 1;
|
|
171932
|
-
while (i < n) {
|
|
171933
|
-
const c = src[i];
|
|
171934
|
-
if (c === "\\") {
|
|
171935
|
-
i += 2;
|
|
171936
|
-
continue;
|
|
171937
|
-
}
|
|
171938
|
-
if (quote === "`" && c === "$" && src[i + 1] === "{") {
|
|
171939
|
-
i = skipBraces2(src, i + 1);
|
|
171940
|
-
continue;
|
|
171941
|
-
}
|
|
171942
|
-
if (c === quote)
|
|
171943
|
-
return i + 1;
|
|
171944
|
-
i++;
|
|
171945
|
-
}
|
|
171946
|
-
return n;
|
|
171947
|
-
}, skipBraces2 = (src, open) => {
|
|
171948
|
-
const n = src.length;
|
|
171949
|
-
let depth = 0;
|
|
171950
|
-
let i = open;
|
|
171951
|
-
while (i < n) {
|
|
171952
|
-
const c = src[i];
|
|
171953
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
171954
|
-
i = skipString3(src, i);
|
|
171955
|
-
continue;
|
|
171956
|
-
}
|
|
171957
|
-
if (c === "{")
|
|
171958
|
-
depth++;
|
|
171959
|
-
else if (c === "}") {
|
|
171960
|
-
depth--;
|
|
171961
|
-
if (depth === 0)
|
|
171962
|
-
return i + 1;
|
|
171963
|
-
}
|
|
171964
|
-
i++;
|
|
171965
|
-
}
|
|
171966
|
-
return n;
|
|
171967
|
-
}, matchParen2 = (src, open) => {
|
|
171968
|
-
const n = src.length;
|
|
171969
|
-
let depth = 0;
|
|
171970
|
-
let i = open;
|
|
171971
|
-
while (i < n) {
|
|
171972
|
-
const c = src[i];
|
|
171973
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
171974
|
-
i = skipString3(src, i);
|
|
171975
|
-
continue;
|
|
171976
|
-
}
|
|
171977
|
-
if (c === "/" && src[i + 1] === "/") {
|
|
171978
|
-
i += 2;
|
|
171979
|
-
while (i < n && src[i] !== `
|
|
171980
|
-
`)
|
|
171981
|
-
i++;
|
|
171982
|
-
continue;
|
|
171983
|
-
}
|
|
171984
|
-
if (c === "/" && src[i + 1] === "*") {
|
|
171985
|
-
i += 2;
|
|
171986
|
-
while (i < n && !(src[i] === "*" && src[i + 1] === "/"))
|
|
171987
|
-
i++;
|
|
171988
|
-
i += 2;
|
|
171989
|
-
continue;
|
|
171990
|
-
}
|
|
171991
|
-
if (c === "(")
|
|
171992
|
-
depth++;
|
|
171993
|
-
else if (c === ")") {
|
|
171994
|
-
depth--;
|
|
171995
|
-
if (depth === 0)
|
|
171996
|
-
return i;
|
|
171997
|
-
}
|
|
171998
|
-
i++;
|
|
171999
|
-
}
|
|
172000
|
-
return -1;
|
|
172001
|
-
}, splitTopLevel2 = (text, delim) => {
|
|
172002
|
-
const parts = [];
|
|
172003
|
-
const n = text.length;
|
|
172004
|
-
let depth = 0;
|
|
172005
|
-
let last = 0;
|
|
172006
|
-
let i = 0;
|
|
172007
|
-
while (i < n) {
|
|
172008
|
-
const c = text[i];
|
|
172009
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
172010
|
-
i = skipString3(text, i);
|
|
172011
|
-
continue;
|
|
172012
|
-
}
|
|
172013
|
-
if (c === "(" || c === "[" || c === "{")
|
|
172014
|
-
depth++;
|
|
172015
|
-
else if (c === ")" || c === "]" || c === "}")
|
|
172016
|
-
depth = Math.max(0, depth - 1);
|
|
172017
|
-
else if (depth === 0 && c === delim) {
|
|
172018
|
-
parts.push(text.slice(last, i));
|
|
172019
|
-
last = i + 1;
|
|
172020
|
-
}
|
|
172021
|
-
i++;
|
|
172022
|
-
}
|
|
172023
|
-
parts.push(text.slice(last));
|
|
172024
|
-
return parts;
|
|
172025
|
-
}, stringLiteralValue2 = (text) => {
|
|
172026
|
-
const t = text.trim();
|
|
172027
|
-
if (t.length < 2)
|
|
172028
|
-
return null;
|
|
172029
|
-
const q = t[0];
|
|
172030
|
-
if (q !== '"' && q !== "'" && q !== "`" || t[t.length - 1] !== q)
|
|
172031
|
-
return null;
|
|
172032
|
-
const inner = t.slice(1, -1);
|
|
172033
|
-
if (q === "`" && inner.includes("${"))
|
|
172034
|
-
return null;
|
|
172035
|
-
if (inner.includes(q) && !inner.includes("\\" + q))
|
|
172036
|
-
return null;
|
|
172037
|
-
return inner.replace(/\\(.)/g, "$1");
|
|
172038
|
-
}, scanAssetIconCalls = (source) => {
|
|
172039
|
-
const calls = [];
|
|
172040
|
-
const n = source.length;
|
|
172041
|
-
let i = 0;
|
|
172042
|
-
while (i < n) {
|
|
172043
|
-
const c = source[i];
|
|
172044
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
172045
|
-
i = skipString3(source, i);
|
|
172046
|
-
continue;
|
|
172047
|
-
}
|
|
172048
|
-
if (c === "/" && source[i + 1] === "/") {
|
|
172049
|
-
i += 2;
|
|
172050
|
-
while (i < n && source[i] !== `
|
|
172051
|
-
`)
|
|
172052
|
-
i++;
|
|
172053
|
-
continue;
|
|
172054
|
-
}
|
|
172055
|
-
if (c === "/" && source[i + 1] === "*") {
|
|
172056
|
-
i += 2;
|
|
172057
|
-
while (i < n && !(source[i] === "*" && source[i + 1] === "/"))
|
|
172058
|
-
i++;
|
|
172059
|
-
i += 2;
|
|
172060
|
-
continue;
|
|
172061
|
-
}
|
|
172062
|
-
if (isIdentStart2(c)) {
|
|
172063
|
-
let j = i + 1;
|
|
172064
|
-
while (j < n && isIdentPart2(source[j]))
|
|
172065
|
-
j++;
|
|
172066
|
-
if (source.slice(i, j) === "assetIcon" && prevNonSpace2(source, i) !== ".") {
|
|
172067
|
-
const call = parseCall2(source, i, j);
|
|
172068
|
-
if (call) {
|
|
172069
|
-
calls.push(call);
|
|
172070
|
-
i = call.end;
|
|
172071
|
-
continue;
|
|
172072
|
-
}
|
|
172073
|
-
}
|
|
172074
|
-
i = j;
|
|
172075
|
-
continue;
|
|
172076
|
-
}
|
|
172077
|
-
i++;
|
|
172078
|
-
}
|
|
172079
|
-
return calls;
|
|
172080
|
-
}, prevNonSpace2 = (src, i) => {
|
|
172081
|
-
let k = i - 1;
|
|
172082
|
-
while (k >= 0 && /\s/.test(src[k]))
|
|
172083
|
-
k--;
|
|
172084
|
-
return k >= 0 ? src[k] : "";
|
|
172085
|
-
}, parseCall2 = (src, start, identEnd) => {
|
|
172086
|
-
const n = src.length;
|
|
172087
|
-
let i = identEnd;
|
|
172088
|
-
while (i < n && /\s/.test(src[i]))
|
|
172089
|
-
i++;
|
|
172090
|
-
if (src[i] !== "(")
|
|
172091
|
-
return null;
|
|
172092
|
-
const close = matchParen2(src, i);
|
|
172093
|
-
if (close === -1)
|
|
172094
|
-
return null;
|
|
172095
|
-
const call = { start, end: close + 1, id: "", pack: "", name: "" };
|
|
172096
|
-
const args = splitTopLevel2(src.slice(i + 1, close), ",");
|
|
172097
|
-
const id = stringLiteralValue2(args[0] ?? "");
|
|
172098
|
-
if (id === null) {
|
|
172099
|
-
call.error = `assetIcon(...) needs a string-literal id like "lucide:bell" (got \`${(args[0] ?? "").trim() || "()"}\`).`;
|
|
172100
|
-
return call;
|
|
172101
|
-
}
|
|
172102
|
-
const m = ICON_ID.exec(id);
|
|
172103
|
-
if (!m) {
|
|
172104
|
-
call.error = `assetIcon("${id}"): id must be "pack:name", lowercase [a-z0-9-] (e.g. "lucide:bell").`;
|
|
172105
|
-
return call;
|
|
172106
|
-
}
|
|
172107
|
-
call.id = id;
|
|
172108
|
-
call.pack = m[1];
|
|
172109
|
-
call.name = m[2];
|
|
172110
|
-
const optsText = args[1]?.trim();
|
|
172111
|
-
if (optsText) {
|
|
172112
|
-
const color = extractColor(optsText);
|
|
172113
|
-
if (color.error) {
|
|
172114
|
-
call.error = color.error;
|
|
172115
|
-
return call;
|
|
172116
|
-
}
|
|
172117
|
-
if (color.bake !== undefined)
|
|
172118
|
-
call.bake = color.bake;
|
|
172119
|
-
else if (color.tint !== undefined)
|
|
172120
|
-
call.tint = color.tint;
|
|
172121
|
-
}
|
|
172122
|
-
return call;
|
|
172123
|
-
}, extractColor = (optsText) => {
|
|
172124
|
-
if (!optsText.startsWith("{") || !optsText.endsWith("}")) {
|
|
172125
|
-
return { error: `assetIcon(id, opts): opts must be an object literal like { color: "#5b8cff" }.` };
|
|
172126
|
-
}
|
|
172127
|
-
for (const prop of splitTopLevel2(optsText.slice(1, -1), ",")) {
|
|
172128
|
-
const kv = splitTopLevel2(prop, ":");
|
|
172129
|
-
if (kv.length < 2)
|
|
172130
|
-
continue;
|
|
172131
|
-
const key = kv[0].trim().replace(/^["'`]|["'`]$/g, "");
|
|
172132
|
-
if (key !== "color")
|
|
172133
|
-
continue;
|
|
172134
|
-
const raw = kv.slice(1).join(":").trim();
|
|
172135
|
-
if (!raw)
|
|
172136
|
-
continue;
|
|
172137
|
-
const literal = stringLiteralValue2(raw);
|
|
172138
|
-
return literal !== null ? { bake: literal } : { tint: raw };
|
|
172139
|
-
}
|
|
172140
|
-
return {};
|
|
172141
|
-
}, rewriteAssetIcons = (source, calls, svgById) => {
|
|
172142
|
-
let out = source;
|
|
172143
|
-
for (const call of [...calls].sort((a, b) => b.start - a.start)) {
|
|
172144
|
-
const svg = svgById.get(call.id);
|
|
172145
|
-
if (svg === undefined)
|
|
172146
|
-
continue;
|
|
172147
|
-
out = out.slice(0, call.start) + iconExpr(svg, call) + out.slice(call.end);
|
|
172148
|
-
}
|
|
172149
|
-
return out;
|
|
172150
|
-
}, iconExpr = (svg, call) => {
|
|
172151
|
-
const body = call.bake ? svg.split("currentColor").join(call.bake) : svg;
|
|
172152
|
-
const src = `SvgSource(\`${escapeTemplate(body)}\`)`;
|
|
172153
|
-
return call.tint ? `Object.assign(${src}, { tintColor: (${call.tint}) })` : src;
|
|
172154
|
-
}, escapeTemplate = (s) => s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"), applyAssetIconMacro = async (entries, resolveSvgs) => {
|
|
172155
|
-
const scanned = entries.filter((e) => e.type === "text" && typeof e.text === "string" && e.text.includes("assetIcon")).map((e) => ({ entry: e, calls: scanAssetIconCalls(e.text) })).filter((s) => s.calls.length > 0);
|
|
172156
|
-
if (scanned.length === 0)
|
|
172157
|
-
return [];
|
|
172158
|
-
const errors = scanned.flatMap((s) => s.calls.filter((c) => c.error).map((c) => c.error));
|
|
172159
|
-
if (errors.length)
|
|
172160
|
-
throw new Error([...new Set(errors)].join(`
|
|
172161
|
-
`));
|
|
172162
|
-
const ids = [...new Set(scanned.flatMap((s) => s.calls.map((c) => c.id)))];
|
|
172163
|
-
const svgById = await resolveSvgs(ids);
|
|
172164
|
-
const missing = ids.filter((id) => !svgById.has(id));
|
|
172165
|
-
if (missing.length)
|
|
172166
|
-
throw new Error(`assetIcon: could not resolve icon(s): ${missing.join(", ")}.`);
|
|
172167
|
-
for (const { entry, calls } of scanned)
|
|
172168
|
-
entry.text = rewriteAssetIcons(entry.text, calls, svgById);
|
|
172169
|
-
return ids;
|
|
172170
|
-
}, buildIconsHeaderValue = (ids) => [...new Set(ids.map((id) => id.split(":")[0]))].sort().join(";"), editDistance = (a, b, cap) => {
|
|
172171
|
-
if (Math.abs(a.length - b.length) > cap)
|
|
172172
|
-
return cap + 1;
|
|
172173
|
-
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
172174
|
-
for (let i = 1;i <= a.length; i++) {
|
|
172175
|
-
const cur = [i];
|
|
172176
|
-
let rowMin = i;
|
|
172177
|
-
for (let j = 1;j <= b.length; j++) {
|
|
172178
|
-
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
172179
|
-
if (cur[j] < rowMin)
|
|
172180
|
-
rowMin = cur[j];
|
|
172181
|
-
}
|
|
172182
|
-
if (rowMin > cap)
|
|
172183
|
-
return cap + 1;
|
|
172184
|
-
prev = cur;
|
|
172185
|
-
}
|
|
172186
|
-
return prev[b.length];
|
|
172187
|
-
}, suggestIconNames = (want, icons) => {
|
|
172188
|
-
const wantParts = new Set(want.split("-").filter(Boolean));
|
|
172189
|
-
const scored = [];
|
|
172190
|
-
for (const [name, info] of Object.entries(icons)) {
|
|
172191
|
-
const lenDiff = Math.abs(name.length - want.length) / 100;
|
|
172192
|
-
let rank;
|
|
172193
|
-
if (name.includes(want) || want.includes(name))
|
|
172194
|
-
rank = lenDiff;
|
|
172195
|
-
else {
|
|
172196
|
-
const d = editDistance(want, name, 2);
|
|
172197
|
-
if (d <= 2)
|
|
172198
|
-
rank = 1 + d;
|
|
172199
|
-
else if (info.kw?.some((k) => wantParts.has(k)))
|
|
172200
|
-
rank = 4 + lenDiff;
|
|
172201
|
-
else
|
|
172202
|
-
continue;
|
|
172203
|
-
}
|
|
172204
|
-
scored.push({ name, rank });
|
|
172205
|
-
}
|
|
172206
|
-
return scored.sort((a, b) => a.rank - b.rank || (a.name < b.name ? -1 : 1)).slice(0, 5).map((s) => s.name);
|
|
172207
|
-
}, iconNotFoundError = async (pack, name, read, registry) => {
|
|
172208
|
-
const metaRaw = await read(`${pack}/meta.json`);
|
|
172209
|
-
if (metaRaw !== null) {
|
|
172210
|
-
let icons = {};
|
|
172211
|
-
try {
|
|
172212
|
-
icons = JSON.parse(metaRaw).icons ?? {};
|
|
172213
|
-
} catch {}
|
|
172214
|
-
const similar = suggestIconNames(name, icons);
|
|
172215
|
-
const hint = similar.length ? ` Did you mean ${similar.map((s) => `"${pack}:${s}"`).join(", ")}?` : ` No similar name in the "${pack}" pack either.`;
|
|
172216
|
-
return new Error(`assetIcon: icon "${pack}:${name}" not found.${hint}`);
|
|
172217
|
-
}
|
|
172218
|
-
const licRaw = await read("licenses.json");
|
|
172219
|
-
if (licRaw !== null) {
|
|
172220
|
-
try {
|
|
172221
|
-
const packs = Object.keys(JSON.parse(licRaw)).sort();
|
|
172222
|
-
return new Error(`assetIcon: unknown icon pack "${pack}" (in "${pack}:${name}"). Available packs: ${packs.join(", ")}.`);
|
|
172223
|
-
} catch {}
|
|
172224
|
-
}
|
|
172225
|
-
return Object.assign(new Error(`assetIcon: can't reach the icon registry${registry ? ` (${registry})` : ""} to resolve "${pack}:${name}". ` + `Check your connection, or set LECODES_ICON_REGISTRY.`), { transient: true });
|
|
172226
|
-
}, VENDORED_ICON_PATH, collectVendoredIcons = (entries) => {
|
|
172227
|
-
const map = new Map;
|
|
172228
|
-
for (const e of entries) {
|
|
172229
|
-
if (e.type !== "text" || typeof e.text !== "string")
|
|
172230
|
-
continue;
|
|
172231
|
-
const m = VENDORED_ICON_PATH.exec(e.path);
|
|
172232
|
-
if (m)
|
|
172233
|
-
map.set(`${m[1]}:${m[2]}`, e.text);
|
|
172234
|
-
}
|
|
172235
|
-
return map;
|
|
172236
|
-
};
|
|
172237
|
-
var init_assetIconMacro = __esm(() => {
|
|
172238
|
-
ICON_ID = /^([a-z0-9]+(?:-[a-z0-9]+)*):([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
172239
|
-
VENDORED_ICON_PATH = /(?:^|\/)assets\/icons\/([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)\.svg$/;
|
|
172240
|
-
});
|
|
172241
|
-
|
|
172242
172346
|
// ../../sdk/src/compile/index.ts
|
|
172243
172347
|
var init_compile = __esm(() => {
|
|
172244
172348
|
init_bundler();
|
|
@@ -207654,9 +207758,9 @@ var init_fields = __esm(() => {
|
|
|
207654
207758
|
});
|
|
207655
207759
|
|
|
207656
207760
|
// ../../sdk/src/server/errors.ts
|
|
207657
|
-
var
|
|
207761
|
+
var ApiError2;
|
|
207658
207762
|
var init_errors3 = __esm(() => {
|
|
207659
|
-
|
|
207763
|
+
ApiError2 = class ApiError2 extends Error {
|
|
207660
207764
|
status;
|
|
207661
207765
|
constructor(status, message = "") {
|
|
207662
207766
|
super(message);
|
|
@@ -208111,21 +208215,21 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208111
208215
|
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
208112
208216
|
}, checkPassword = (password) => {
|
|
208113
208217
|
if (typeof password !== "string" || password.length < PASSWORD_MIN)
|
|
208114
|
-
throw new
|
|
208218
|
+
throw new ApiError2(400, `Password must be at least ${PASSWORD_MIN} characters`);
|
|
208115
208219
|
if (password.length > PASSWORD_MAX)
|
|
208116
|
-
throw new
|
|
208220
|
+
throw new ApiError2(400, `Password must be at most ${PASSWORD_MAX} characters`);
|
|
208117
208221
|
return password;
|
|
208118
208222
|
}, EMAIL_RE, normalizeEmail = (email) => {
|
|
208119
208223
|
const e = typeof email === "string" ? email.trim().toLowerCase() : "";
|
|
208120
208224
|
if (!e || e.length > 254 || !EMAIL_RE.test(e))
|
|
208121
|
-
throw new
|
|
208225
|
+
throw new ApiError2(400, "Invalid email");
|
|
208122
208226
|
return e;
|
|
208123
208227
|
}, LOGIN_RE, parseLogin = (login) => {
|
|
208124
208228
|
const l = typeof login === "string" ? login.trim() : "";
|
|
208125
208229
|
if (l.includes("@"))
|
|
208126
208230
|
return { provider: "email", subject: normalizeEmail(l) };
|
|
208127
208231
|
if (!LOGIN_RE.test(l))
|
|
208128
|
-
throw new
|
|
208232
|
+
throw new ApiError2(400, "Login must be 3–32 letters, digits, dots, dashes or underscores");
|
|
208129
208233
|
return { provider: "login", subject: l.toLowerCase() };
|
|
208130
208234
|
}, identityKey = (provider, subject) => `${provider}:${subject}`, window2 = (map, key, now, span) => {
|
|
208131
208235
|
const list = (map.get(key) ?? []).filter((x) => now - x < span);
|
|
@@ -208153,7 +208257,7 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208153
208257
|
const requireSignedIn = (ctx) => {
|
|
208154
208258
|
const id = stateOf(ctx).userId;
|
|
208155
208259
|
if (id === null)
|
|
208156
|
-
throw new
|
|
208260
|
+
throw new ApiError2(401, "Sign in required");
|
|
208157
208261
|
return id;
|
|
208158
208262
|
};
|
|
208159
208263
|
const mint = async (ctx) => {
|
|
@@ -208210,7 +208314,7 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208210
208314
|
const ident = await findIdentity("email", email);
|
|
208211
208315
|
if (ident) {
|
|
208212
208316
|
if (ident.user.id !== userId)
|
|
208213
|
-
throw new
|
|
208317
|
+
throw new ApiError2(409, "This email belongs to another account");
|
|
208214
208318
|
if (!ident.verifiedAt)
|
|
208215
208319
|
await identities.update(ident.id, { verifiedAt: now(), hash: null });
|
|
208216
208320
|
} else if (create) {
|
|
@@ -208223,7 +208327,7 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208223
208327
|
const { provider, subject } = parseLogin(rawLogin);
|
|
208224
208328
|
const password = checkPassword(rawPassword);
|
|
208225
208329
|
if (await findIdentity(provider, subject))
|
|
208226
|
-
throw new
|
|
208330
|
+
throw new ApiError2(409, provider === "email" ? "An account with this email already exists" : "This login is taken");
|
|
208227
208331
|
const userId = await createUser(data);
|
|
208228
208332
|
await createIdentity(userId, provider, subject, { hash: await hashPassword(password) });
|
|
208229
208333
|
await bind(ctx, userId, { rotate: true });
|
|
@@ -208238,13 +208342,13 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208238
208342
|
const byLogin = window2(passwordFails, `l:${who ? identityKey(who.provider, who.subject) : ""}`, t, 10 * 60000);
|
|
208239
208343
|
const byIp = ctx.ip ? window2(passwordFails, `ip:${ctx.ip}`, t, 10 * 60000) : [];
|
|
208240
208344
|
if (byLogin.length >= PASSWORD_FAILS_PER_LOGIN || byIp.length >= PASSWORD_FAILS_PER_IP)
|
|
208241
|
-
throw new
|
|
208345
|
+
throw new ApiError2(429, "Too many attempts — try again later");
|
|
208242
208346
|
const ident = who ? await findIdentity(who.provider, who.subject) : null;
|
|
208243
208347
|
const ok = !!ident?.hash && typeof rawPassword === "string" && await verifyPassword(rawPassword, ident.hash);
|
|
208244
208348
|
if (!ok) {
|
|
208245
208349
|
byLogin.push(t);
|
|
208246
208350
|
byIp.push(t);
|
|
208247
|
-
throw new
|
|
208351
|
+
throw new ApiError2(401, "Invalid login or password");
|
|
208248
208352
|
}
|
|
208249
208353
|
await bind(ctx, ident.user.id, { rotate: true });
|
|
208250
208354
|
};
|
|
@@ -208255,7 +208359,7 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208255
208359
|
const mine = await identities.where({ user: { id: userId } }).select({ id: true, provider: true });
|
|
208256
208360
|
const typed = mine.filter((i) => i.provider === "email" || i.provider === "login");
|
|
208257
208361
|
if (!typed.length)
|
|
208258
|
-
throw new
|
|
208362
|
+
throw new ApiError2(400, "Your account has no email or login to sign in with a password — sign in with a code first");
|
|
208259
208363
|
const hash = await hashPassword(password);
|
|
208260
208364
|
for (const i of typed)
|
|
208261
208365
|
await identities.update(i.id, { hash });
|
|
@@ -208272,9 +208376,9 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208272
208376
|
const t = now();
|
|
208273
208377
|
const recent = window2(sends, email, t, 10 * 60000);
|
|
208274
208378
|
if (recent.length && t - recent[recent.length - 1] < SEND_MIN_GAP_MS)
|
|
208275
|
-
throw new
|
|
208379
|
+
throw new ApiError2(429, `Try again in ${Math.ceil((SEND_MIN_GAP_MS - (t - recent[recent.length - 1])) / 1000)} s`);
|
|
208276
208380
|
if (recent.length >= SEND_MAX_PER_10MIN)
|
|
208277
|
-
throw new
|
|
208381
|
+
throw new ApiError2(429, "Too many codes requested — try again later");
|
|
208278
208382
|
const code = randomCode();
|
|
208279
208383
|
await sessions.update(s.sessionId, { pendingEmail: email, codeHash: await codeHash(s.sessionId, code), codeExpires: t + CODE_TTL_MS, codeAttempts: 0 });
|
|
208280
208384
|
recent.push(t);
|
|
@@ -208303,12 +208407,12 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208303
208407
|
if (!isDevCode) {
|
|
208304
208408
|
const row = await sessions.where({ id: s.sessionId }).select({ pendingEmail: true, codeHash: true, codeExpires: true, codeAttempts: true }).first();
|
|
208305
208409
|
if (!row?.codeHash || row.pendingEmail !== email || !row.codeExpires || row.codeExpires < now())
|
|
208306
|
-
throw new
|
|
208410
|
+
throw new ApiError2(400, "Code expired — request a new one");
|
|
208307
208411
|
if (row.codeAttempts >= CODE_MAX_ATTEMPTS)
|
|
208308
|
-
throw new
|
|
208412
|
+
throw new ApiError2(429, "Too many attempts — request a new code");
|
|
208309
208413
|
if (typeof code !== "string" || await codeHash(s.sessionId, code.trim()) !== row.codeHash) {
|
|
208310
208414
|
await sessions.update(s.sessionId, { codeAttempts: { $increment: 1 } });
|
|
208311
|
-
throw new
|
|
208415
|
+
throw new ApiError2(400, "Invalid code");
|
|
208312
208416
|
}
|
|
208313
208417
|
}
|
|
208314
208418
|
const userId = await userOfProvenEmail(email);
|
|
@@ -208343,7 +208447,7 @@ var CODE_TTL_MS, CODE_MAX_ATTEMPTS = 5, SEND_MIN_GAP_MS = 30000, SEND_MAX_PER_10
|
|
|
208343
208447
|
signIn: async (ctx, userId) => {
|
|
208344
208448
|
requireAuth();
|
|
208345
208449
|
if (!await users.where({ id: userId }).select({ id: true }).first())
|
|
208346
|
-
throw new
|
|
208450
|
+
throw new ApiError2(404, "No such user");
|
|
208347
208451
|
await bind(ctx, userId, { rotate: true });
|
|
208348
208452
|
},
|
|
208349
208453
|
signOut: async (ctx) => {
|
|
@@ -209891,7 +209995,7 @@ var require_main = __commonJS(function(exports, module) {
|
|
|
209891
209995
|
});
|
|
209892
209996
|
|
|
209893
209997
|
// src/dev/backendWorker.ts
|
|
209894
|
-
import { existsSync as
|
|
209998
|
+
import { existsSync as existsSync55, mkdirSync as mkdirSync35, mkdtempSync as mkdtempSync8, renameSync as renameSync3, rmSync as rmSync18 } from "node:fs";
|
|
209895
209999
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
209896
210000
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
209897
210001
|
import { join as join58 } from "node:path";
|
|
@@ -209935,7 +210039,7 @@ var post = (m) => {
|
|
|
209935
210039
|
rmSync18(aside, { recursive: true, force: true });
|
|
209936
210040
|
renameSync3(dir, aside);
|
|
209937
210041
|
rmSync18(`${filesDir}-old`, { recursive: true, force: true });
|
|
209938
|
-
if (
|
|
210042
|
+
if (existsSync55(filesDir))
|
|
209939
210043
|
renameSync3(filesDir, `${filesDir}-old`);
|
|
209940
210044
|
mkdirSync35(dir, { recursive: true });
|
|
209941
210045
|
db = openDatabase(dir);
|
|
@@ -210028,7 +210132,7 @@ var init_backendWorker = __esm(() => {
|
|
|
210028
210132
|
// src/index.ts
|
|
210029
210133
|
init_package();
|
|
210030
210134
|
init_cli();
|
|
210031
|
-
import { existsSync as
|
|
210135
|
+
import { existsSync as existsSync56, realpathSync as realpathSync2 } from "node:fs";
|
|
210032
210136
|
import { delimiter as delimiter3, dirname as dirname28, join as join59 } from "node:path";
|
|
210033
210137
|
|
|
210034
210138
|
// src/commands/app/index.ts
|
|
@@ -210635,6 +210739,9 @@ var readManifest2 = (dir, id) => {
|
|
|
210635
210739
|
};
|
|
210636
210740
|
var readManifestIfAny = (dir, id) => existsSync14(join17(dir, MANIFEST_FILE)) ? readManifest2(dir, id) : null;
|
|
210637
210741
|
|
|
210742
|
+
// src/commands/app/shared.ts
|
|
210743
|
+
init_compile();
|
|
210744
|
+
|
|
210638
210745
|
// src/plugin/emitPackage.ts
|
|
210639
210746
|
class PackageError extends Error {
|
|
210640
210747
|
}
|
|
@@ -210731,7 +210838,12 @@ var urlSchemeOf = (config) => {
|
|
|
210731
210838
|
throw new CliError(`app.json "urlScheme" is the app's OWN scheme ("myshop" → myshop://…), not ${scheme}.`);
|
|
210732
210839
|
return scheme;
|
|
210733
210840
|
};
|
|
210734
|
-
var readAppConfigAt = (root) =>
|
|
210841
|
+
var readAppConfigAt = (root) => {
|
|
210842
|
+
const config = readJson2(join18(root, "app.json"));
|
|
210843
|
+
if (config && "plugins" in config)
|
|
210844
|
+
config.plugins = pluginSpecsOf(config.plugins);
|
|
210845
|
+
return config;
|
|
210846
|
+
};
|
|
210735
210847
|
var DESKTOP_DEFAULT_WINDOW = { width: 1280, height: 720 };
|
|
210736
210848
|
var resolveDesktopRenderer = (root, renderer) => {
|
|
210737
210849
|
const raw = renderer ?? process.env.LECODES_DESKTOP_RENDERER ?? readAppConfigAt(root)?.desktop?.renderer;
|
|
@@ -210851,25 +210963,33 @@ var detectEngines = (bundleJs) => {
|
|
|
210851
210963
|
}
|
|
210852
210964
|
const header = lines.join(`
|
|
210853
210965
|
`);
|
|
210854
|
-
|
|
210966
|
+
const on = (name) => new RegExp(`^// ${name}: enable$`, "m").test(header);
|
|
210967
|
+
return { gl: !/^\/\/ gl: disable$/m.test(header), g2: on("2d"), physics: on("physics"), net: on("net"), nav: on("nav"), audio: on("audio") };
|
|
210855
210968
|
};
|
|
210969
|
+
var VARIANT_LACKS = {
|
|
210970
|
+
full: [],
|
|
210971
|
+
"3d": ["g2", "physics", "net", "nav", "audio"],
|
|
210972
|
+
"2d": ["gl", "physics", "net", "nav"],
|
|
210973
|
+
core: ["gl", "g2", "physics", "net", "nav"]
|
|
210974
|
+
};
|
|
210975
|
+
var ENGINE_WORDS = { gl: "3D", g2: "2D", physics: "physics", net: "multiplayer", nav: "navigation", audio: "game audio" };
|
|
210976
|
+
var lacks = (variant, uses) => VARIANT_LACKS[variant].filter((e) => uses[e]).map((e) => ENGINE_WORDS[e]);
|
|
210856
210977
|
var resolveVariant = (requested, bundleJs, configKey = "ios.variant") => {
|
|
210857
210978
|
const uses = detectEngines(bundleJs);
|
|
210858
|
-
const detected =
|
|
210979
|
+
const detected = SDK_VARIANTS.find((v) => lacks(v, uses).length === 0) ?? "full";
|
|
210980
|
+
const inUse = Object.keys(ENGINE_WORDS).filter((e) => uses[e]).map((e) => ENGINE_WORDS[e]);
|
|
210981
|
+
const why = inUse.length > 0 ? `uses ${inUse.join(" + ")}` : "UI only, no engine use detected";
|
|
210859
210982
|
if (requested === "auto")
|
|
210860
|
-
return { variant: detected, detected };
|
|
210983
|
+
return { variant: detected, detected, why };
|
|
210861
210984
|
const variant = requested;
|
|
210862
|
-
const missing =
|
|
210863
|
-
uses.gl && variant !== "3d" && variant !== "full" ? "3D" : null,
|
|
210864
|
-
uses.g2 && variant !== "2d" && variant !== "full" ? "2D" : null
|
|
210865
|
-
].filter(Boolean);
|
|
210985
|
+
const missing = lacks(variant, uses);
|
|
210866
210986
|
if (missing.length > 0) {
|
|
210867
|
-
warn(`project uses
|
|
210987
|
+
warn(`project uses ${missing.join(" + ")} but ${configKey} forces "${variant}", which has no such engine — that part won't run on device; remove ${configKey} to auto-select "${detected}".`);
|
|
210868
210988
|
}
|
|
210869
|
-
return { variant, detected };
|
|
210989
|
+
return { variant, detected, why };
|
|
210870
210990
|
};
|
|
210871
210991
|
var ANDROID_SDK_VARIANTS = [...SDK_VARIANTS, "vulkan"];
|
|
210872
|
-
var ANDROID_SDK_VULKAN_MIN = "2.
|
|
210992
|
+
var ANDROID_SDK_VULKAN_MIN = "2.2.11";
|
|
210873
210993
|
var androidSdkArtifact = (variant, sdkVersion) => {
|
|
210874
210994
|
if (variant === "vulkan" && sdkVersion && cmpVersion(sdkVersion, ANDROID_SDK_VULKAN_MIN) < 0) {
|
|
210875
210995
|
throw new CliError(`android.variant "vulkan" needs lecodes-android-sdk >= ${ANDROID_SDK_VULKAN_MIN} (pinned: ${sdkVersion}) — run \`lecodes app update\` or set android.sdk.`);
|
|
@@ -212992,7 +213112,7 @@ engines the bundle uses and cannot see those, so a project using \`Physics\`,
|
|
|
212992
213112
|
|
|
212993
213113
|
\`android.variant: "vulkan"\` links \`lecodes-sdk-vulkan\`: the full engine set with Filament on
|
|
212994
213114
|
Vulkan (OpenGL ES where the device has no Vulkan 1.1), \`arm64-v8a\` only — for 3D games. Never
|
|
212995
|
-
auto-selected; needs lecodes-android-sdk >= 2.
|
|
213115
|
+
auto-selected; needs lecodes-android-sdk >= 2.2.11.
|
|
212996
213116
|
|
|
212997
213117
|
## App-local native code
|
|
212998
213118
|
|
|
@@ -213757,7 +213877,7 @@ var initAndroid = async (opts) => {
|
|
|
213757
213877
|
if (existsSync19(androidDir)) {
|
|
213758
213878
|
throw new CliError("android/ already exists — run `lecodes app sync` to update it.");
|
|
213759
213879
|
}
|
|
213760
|
-
const existing =
|
|
213880
|
+
const existing = readAppConfigAt(root) ?? {};
|
|
213761
213881
|
const variant = opts.variant ?? existing.android?.variant ?? "auto";
|
|
213762
213882
|
if (variant !== "auto" && !ANDROID_SDK_VARIANTS.includes(variant)) {
|
|
213763
213883
|
throw new CliError(`--variant must be auto, ${ANDROID_SDK_VARIANTS.join(", ")} (got "${variant}")`);
|
|
@@ -213868,20 +213988,14 @@ var syncAndroid = async (root, config, prior) => {
|
|
|
213868
213988
|
if (requestedVariant !== "auto" && !ANDROID_SDK_VARIANTS.includes(requestedVariant)) {
|
|
213869
213989
|
throw new CliError(`app.json android.variant must be "auto" or one of ${ANDROID_SDK_VARIANTS.join(", ")} (got "${requestedVariant}").`);
|
|
213870
213990
|
}
|
|
213871
|
-
const { variant: covered,
|
|
213991
|
+
const { variant: covered, why } = resolveVariant(requestedVariant === "auto" ? "auto" : androidVariantEngines(requestedVariant), bundleJs, "android.variant");
|
|
213872
213992
|
const variant = requestedVariant === "vulkan" ? "vulkan" : covered;
|
|
213873
213993
|
let sdkArtifact = androidSdkArtifact(variant, sdkVersion);
|
|
213874
213994
|
if (sdkArtifact !== "lecodes-sdk" && cmpVersion(sdkVersion, ANDROID_SDK_VARIANTS_MIN) < 0) {
|
|
213875
213995
|
warn(`lecodes-android-sdk ${sdkVersion} predates the variant matrix (${ANDROID_SDK_VARIANTS_MIN}) — linking the full lecodes-sdk AAR; run \`lecodes app update\` to pick up variant builds.`);
|
|
213876
213996
|
sdkArtifact = "lecodes-sdk";
|
|
213877
213997
|
}
|
|
213878
|
-
|
|
213879
|
-
full: "3D + 2D engines in use",
|
|
213880
|
-
"3d": "3D in use, no 2D",
|
|
213881
|
-
"2d": "2D in use, no 3D",
|
|
213882
|
-
core: "no 3D/2D engine use detected"
|
|
213883
|
-
};
|
|
213884
|
-
log(`${c.green("variant")} ${variant} → ${sdkArtifact}${requestedVariant === "auto" ? c.dim(` (auto: ${WHY[detected]})`) : c.dim(" (forced by android.variant)")}`);
|
|
213998
|
+
log(`${c.green("variant")} ${variant} → ${sdkArtifact}${requestedVariant === "auto" ? c.dim(` (auto: ${why})`) : c.dim(" (forced by android.variant)")}`);
|
|
213885
213999
|
const lockPath = join21(root, "plugins.lock.json");
|
|
213886
214000
|
const oldLock = readJson2(lockPath)?.plugins ?? {};
|
|
213887
214001
|
const pluginSpecs = Object.entries(config.plugins ?? {});
|
|
@@ -214114,7 +214228,7 @@ var androidBuildFlags = {
|
|
|
214114
214228
|
};
|
|
214115
214229
|
var buildAndroid = async (flags) => {
|
|
214116
214230
|
const root = projectRoot();
|
|
214117
|
-
const config =
|
|
214231
|
+
const config = readAppConfigAt(root);
|
|
214118
214232
|
const androidDir = join21(root, "android");
|
|
214119
214233
|
if (!config?.android || !existsSync19(androidDir)) {
|
|
214120
214234
|
throw new CliError("No Android shell here — run `lecodes app init android` first.");
|
|
@@ -214318,7 +214432,7 @@ var writeSigningConfig = (root, config, storeFile, alias, storePassword, keyPass
|
|
|
214318
214432
|
}
|
|
214319
214433
|
}
|
|
214320
214434
|
const configPath = join21(root, "app.json");
|
|
214321
|
-
const fresh =
|
|
214435
|
+
const fresh = readAppConfigAt(root) ?? config;
|
|
214322
214436
|
fresh.android = { ...fresh.android ?? {}, signing: { storeFile: envPath, keyAlias: alias } };
|
|
214323
214437
|
writeJson(configPath, fresh);
|
|
214324
214438
|
log(`${c.green("written")} app.json (android.signing — metadata only, no passwords)`);
|
|
@@ -214334,7 +214448,7 @@ var printCertificate = (storeFile, alias, storePassword) => {
|
|
|
214334
214448
|
var PLAY_APP_SIGNING_NOTE = "With Play App Signing (default for new Play apps) this is the UPLOAD key — Google keeps the app signing key, and a lost upload key can be reset through Play support. Without it, losing this file means the app can never be updated. Back it up either way.";
|
|
214335
214449
|
var keystoreProject = () => {
|
|
214336
214450
|
const root = projectRoot();
|
|
214337
|
-
return { root, config:
|
|
214451
|
+
return { root, config: readAppConfigAt(root) ?? {} };
|
|
214338
214452
|
};
|
|
214339
214453
|
var passwordFlag = { password: str("keystore password (else asked on the terminal)", { value: "<pw>" }) };
|
|
214340
214454
|
var keystoreCreateFlags = {
|
|
@@ -215465,7 +215579,7 @@ var dirSize2 = (dir) => readdirSync8(dir, { withFileTypes: true }).reduce((sum,
|
|
|
215465
215579
|
}, 0);
|
|
215466
215580
|
var initDesktop = async (opts) => {
|
|
215467
215581
|
const root = projectRoot();
|
|
215468
|
-
const existing =
|
|
215582
|
+
const existing = readAppConfigAt(root) ?? {};
|
|
215469
215583
|
const name = opts.name ?? existing.name ?? basename8(root);
|
|
215470
215584
|
const window2 = {
|
|
215471
215585
|
width: 1280,
|
|
@@ -215499,7 +215613,7 @@ var desktopBuildFlags = {
|
|
|
215499
215613
|
};
|
|
215500
215614
|
var buildDesktop = async (flags) => {
|
|
215501
215615
|
const root = projectRoot();
|
|
215502
|
-
const config =
|
|
215616
|
+
const config = readAppConfigAt(root) ?? {};
|
|
215503
215617
|
const name = (config.name ?? basename8(root)).trim() || "app";
|
|
215504
215618
|
const slug = flags.slug ?? cleanFileName(name);
|
|
215505
215619
|
const out = resolve11(root, flags.out ?? join24(root, "dist", slug));
|
|
@@ -215776,7 +215890,7 @@ var initIos = async (opts) => {
|
|
|
215776
215890
|
if (existsSync23(iosDir)) {
|
|
215777
215891
|
throw new CliError("ios/ already exists — run `lecodes app sync` to update it.");
|
|
215778
215892
|
}
|
|
215779
|
-
const existing =
|
|
215893
|
+
const existing = readAppConfigAt(root) ?? {};
|
|
215780
215894
|
const variant = opts.variant ?? existing.ios?.variant ?? "auto";
|
|
215781
215895
|
if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
|
|
215782
215896
|
throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
|
|
@@ -215865,7 +215979,7 @@ var init = defineCommand({
|
|
|
215865
215979
|
});
|
|
215866
215980
|
var runSync = async (platformArg) => {
|
|
215867
215981
|
const root = projectRoot();
|
|
215868
|
-
const config =
|
|
215982
|
+
const config = readAppConfigAt(root);
|
|
215869
215983
|
const platform = mobilePlatform(platformArg);
|
|
215870
215984
|
const { hasIos, hasAndroid } = shellsOf(root, config);
|
|
215871
215985
|
if (platform === "ios" && !hasIos)
|
|
@@ -215946,14 +216060,8 @@ var syncIos = async (root, config) => {
|
|
|
215946
216060
|
mkdirSync15(resourcesDir, { recursive: true });
|
|
215947
216061
|
const resources = await compileShell(join25(resourcesDir, "app.js"), { remotePatterns });
|
|
215948
216062
|
const bundleJs = readFileSync20(join25(resourcesDir, "app.js"), "utf8");
|
|
215949
|
-
const { variant,
|
|
215950
|
-
|
|
215951
|
-
core: "UI only",
|
|
215952
|
-
"2d": "uses the 2D engine",
|
|
215953
|
-
"3d": "uses the 3D engine",
|
|
215954
|
-
full: "uses the 3D + 2D engines"
|
|
215955
|
-
};
|
|
215956
|
-
log(`${c.green("variant")} ${variant}${requestedVariant === "auto" ? ` (auto: ${WHY[detected]})` : " (forced by ios.variant)"}`);
|
|
216063
|
+
const { variant, why } = resolveVariant(requestedVariant, bundleJs);
|
|
216064
|
+
log(`${c.green("variant")} ${variant}${requestedVariant === "auto" ? ` (auto: ${why})` : " (forced by ios.variant)"}`);
|
|
215957
216065
|
const sdkWhy = sdkPin === "latest" ? "latest" : config.ios.sdk ? "pinned by ios.sdk" : "from the shell — set ios.sdk or run `lecodes app update`";
|
|
215958
216066
|
log(`${c.green("sdk")} lecodes-ios-sdk ${sdkVersion}${c.dim(` (${sdkWhy})`)}`);
|
|
215959
216067
|
const preload = /^\/\/ preload: (.*)$/m.exec(bundleJs)?.[1].split(";").map((s) => s.trim()).filter(Boolean) ?? [];
|
|
@@ -216076,7 +216184,7 @@ Native OTA JS updates at launch (both shells; the current launch runs the newest
|
|
|
216076
216184
|
var pluginConfig = () => {
|
|
216077
216185
|
const root = projectRoot();
|
|
216078
216186
|
const configPath = join25(root, "app.json");
|
|
216079
|
-
const config =
|
|
216187
|
+
const config = readAppConfigAt(root);
|
|
216080
216188
|
if (!config)
|
|
216081
216189
|
throw new CliError("No app.json here — run `lecodes app init <ios|android>` first.");
|
|
216082
216190
|
config.plugins ??= {};
|
|
@@ -216181,7 +216289,7 @@ var updateSdkPin = async (root, config, wanted, checkOnly, platform) => {
|
|
|
216181
216289
|
};
|
|
216182
216290
|
var runAppUpdate = async (opts = {}) => {
|
|
216183
216291
|
const root = projectRoot();
|
|
216184
|
-
const config =
|
|
216292
|
+
const config = readAppConfigAt(root);
|
|
216185
216293
|
const platform = mobilePlatform(opts.platform);
|
|
216186
216294
|
const { hasIos, hasAndroid } = shellsOf(root, config);
|
|
216187
216295
|
if (platform === "ios" && !hasIos)
|
|
@@ -216339,7 +216447,7 @@ var doctor = defineCommand({
|
|
|
216339
216447
|
examples: ["lecodes app doctor"],
|
|
216340
216448
|
run: async () => {
|
|
216341
216449
|
const root = projectRoot();
|
|
216342
|
-
const config =
|
|
216450
|
+
const config = readAppConfigAt(root);
|
|
216343
216451
|
const { hasIos, hasAndroid } = shellsOf(root, config);
|
|
216344
216452
|
if (!hasIos && !hasAndroid)
|
|
216345
216453
|
throw new CliError(NO_IOS_SHELL);
|
|
@@ -216386,7 +216494,7 @@ var exportOptionsPlist = (method, teamId, destination) => `<?xml version="1.0" e
|
|
|
216386
216494
|
`;
|
|
216387
216495
|
var requireShell = () => {
|
|
216388
216496
|
const root = projectRoot();
|
|
216389
|
-
const config =
|
|
216497
|
+
const config = readAppConfigAt(root);
|
|
216390
216498
|
if (!config?.ios || !existsSync23(join25(root, "ios")))
|
|
216391
216499
|
throw new CliError(NO_IOS_SHELL);
|
|
216392
216500
|
return { root, config };
|
|
@@ -216499,7 +216607,7 @@ var iosUpload = defineCommand({
|
|
|
216499
216607
|
"-allowProvisioningUpdates"
|
|
216500
216608
|
]);
|
|
216501
216609
|
const configPath = join25(root, "app.json");
|
|
216502
|
-
const fresh =
|
|
216610
|
+
const fresh = readAppConfigAt(root);
|
|
216503
216611
|
fresh.ios.buildNumber = (fresh.ios.buildNumber ?? 1) + 1;
|
|
216504
216612
|
writeJson(configPath, fresh);
|
|
216505
216613
|
success(`Uploaded build ${(fresh.ios.buildNumber ?? 2) - 1} — check App Store Connect / TestFlight once processing finishes.`);
|
|
@@ -216745,7 +216853,7 @@ lecodes render --desktop --view iso --png iso.png # 3D: inspection camera fitt
|
|
|
216745
216853
|
# { "view": "iso", "screenshot": "iso.png" } then { "view": "off" }
|
|
216746
216854
|
lecodes render --desktop --script <file> # the same scenario on the NATIVE host (real GPU, the
|
|
216747
216855
|
# real hit-test): the same steps, targets and expects as
|
|
216748
|
-
# headless (only expect.audio / the json output are headless-only)
|
|
216856
|
+
# headless (only expect.audio / expect.toast / the json output are headless-only)
|
|
216749
216857
|
lecodes render --sim --script <file> # the WINDOWLESS native host (lecodes-server): the real
|
|
216750
216858
|
# engines, no GPU, no pixels, no UI hit-test — expect.log /
|
|
216751
216859
|
# expect.paint only; runs on CI and beside an open desktop window
|
|
@@ -216791,11 +216899,12 @@ Three hosts run the same file, at a fixed 16 ms clock on all of them:
|
|
|
216791
216899
|
|---|---|---|---|---|
|
|
216792
216900
|
| headless | (none) | Bun: the runtime + UI renderer + a recording 3D core (no GPU) | names or \`{ x, y }\` | all |
|
|
216793
216901
|
| sim | \`--sim\` | the windowless native server: real engines, no pixels, no UI | \`{ x, y }\`, straight to the scene | \`log\`, \`paint\` |
|
|
216794
|
-
| desktop | \`--desktop\` | the hidden desktop window: pixels, GL materials, the real hit-test | names or \`{ x, y }\` | all but \`audio\` |
|
|
216902
|
+
| desktop | \`--desktop\` | the hidden desktop window: pixels, GL materials, the real hit-test | names or \`{ x, y }\` | all but \`audio\` / \`toast\` |
|
|
216795
216903
|
|
|
216796
216904
|
One flow file, the same verdict on headless and desktop: names resolve the same way (\`name\` →
|
|
216797
216905
|
exact text → substring → placeholder), every step runs (\`hover\`, \`type\`, \`fill\`, \`scroll\`,
|
|
216798
|
-
\`clip\` included) and the semantic expects read the same tree. Only \`expect.audio
|
|
216906
|
+
\`clip\` included) and the semantic expects read the same tree. Only \`expect.audio\`,
|
|
216907
|
+
\`expect.toast\` (the last \`toast()\` message — a toast is not in the tree) and the
|
|
216799
216908
|
\`json\` output are the headless renderer's. On the desktop \`type\` taps the input and types into
|
|
216800
216909
|
it (appends); the sim has no UI, so it takes coordinates and \`expect.log\` / \`expect.paint\` only.
|
|
216801
216910
|
|
|
@@ -219979,8 +220088,8 @@ var lightmap_default = defineCommand({
|
|
|
219979
220088
|
// src/commands/link.ts
|
|
219980
220089
|
init_cli();
|
|
219981
220090
|
init_api();
|
|
219982
|
-
import { existsSync as
|
|
219983
|
-
import { basename as basename21, join as join44, resolve as
|
|
220091
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
220092
|
+
import { basename as basename21, join as join44, resolve as resolve22 } from "node:path";
|
|
219984
220093
|
init_ignore();
|
|
219985
220094
|
init_localFiles();
|
|
219986
220095
|
init_manifest();
|
|
@@ -219990,14 +220099,148 @@ init_types4();
|
|
|
219990
220099
|
// src/commands/push.ts
|
|
219991
220100
|
init_cli();
|
|
219992
220101
|
init_api();
|
|
219993
|
-
import { readFileSync as
|
|
220102
|
+
import { readFileSync as readFileSync32 } from "node:fs";
|
|
219994
220103
|
import { basename as basename20 } from "node:path";
|
|
219995
220104
|
init_localFiles();
|
|
219996
220105
|
init_manifest();
|
|
220106
|
+
|
|
220107
|
+
// src/commands/releases.ts
|
|
220108
|
+
init_cli();
|
|
220109
|
+
init_api();
|
|
220110
|
+
init_localFiles();
|
|
220111
|
+
import { existsSync as existsSync39, readFileSync as readFileSync31 } from "node:fs";
|
|
220112
|
+
import { resolve as resolve21 } from "node:path";
|
|
220113
|
+
init_shared();
|
|
220114
|
+
var compareVersions = (a, b) => {
|
|
220115
|
+
const pa = a.split("."), pb = b.split(".");
|
|
220116
|
+
for (let i = 0;i < Math.max(pa.length, pb.length); i++) {
|
|
220117
|
+
const d = (parseInt(pa[i] ?? "0", 10) || 0) - (parseInt(pb[i] ?? "0", 10) || 0);
|
|
220118
|
+
if (d)
|
|
220119
|
+
return d;
|
|
220120
|
+
}
|
|
220121
|
+
return 0;
|
|
220122
|
+
};
|
|
220123
|
+
var formatSize = (bytes) => bytes < 1024 ? `${bytes} B` : bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
220124
|
+
var qrUrl = (apiUrl, uuid) => `${apiUrl}/qr/${uuid}`;
|
|
220125
|
+
var reportRelease = (release, qr, verb = "Published") => {
|
|
220126
|
+
const parts = [release.unchanged ? `Nothing changed — ${release.version} is current` : `${verb} ${release.version}`, release.url.replace(/\/+$/, "")];
|
|
220127
|
+
if (!release.unchanged) {
|
|
220128
|
+
if (typeof release.size === "number")
|
|
220129
|
+
parts.push(formatSize(release.size));
|
|
220130
|
+
if (release.sdk)
|
|
220131
|
+
parts.push(`SDK ${release.sdk}`);
|
|
220132
|
+
}
|
|
220133
|
+
success(parts.join(" · "));
|
|
220134
|
+
info(` ${c.dim("QR")} ${qr}`);
|
|
220135
|
+
};
|
|
220136
|
+
var withSchemaConfirm = async (call, opts) => {
|
|
220137
|
+
try {
|
|
220138
|
+
return await call(false);
|
|
220139
|
+
} catch (e) {
|
|
220140
|
+
if (!(e instanceof SchemaChangeError))
|
|
220141
|
+
throw e;
|
|
220142
|
+
if (e.incompatible !== null) {
|
|
220143
|
+
throw new CliError(`The production database cannot migrate to this schema in place${e.incompatible ? `: ${e.incompatible}` : ""}. Nothing changed.`, 409);
|
|
220144
|
+
}
|
|
220145
|
+
warn("The schema change deletes data in the production database:");
|
|
220146
|
+
for (const op of e.destructive)
|
|
220147
|
+
log(c.red(` ${op}`));
|
|
220148
|
+
if (!opts.allowDestructive) {
|
|
220149
|
+
if (!process.stdin.isTTY)
|
|
220150
|
+
throw new CliError(`Nothing changed. Run \`${opts.retry} --allow-destructive\` to apply it.`, 409);
|
|
220151
|
+
if (!await confirm("Delete that data and go on?", false)) {
|
|
220152
|
+
info(c.dim("Cancelled — nothing changed."));
|
|
220153
|
+
return null;
|
|
220154
|
+
}
|
|
220155
|
+
}
|
|
220156
|
+
return await call(true);
|
|
220157
|
+
}
|
|
220158
|
+
};
|
|
220159
|
+
var allowDestructiveFlag = { "allow-destructive": bool("apply a schema change that deletes production data without asking") };
|
|
220160
|
+
var use = defineCommand({
|
|
220161
|
+
name: "use",
|
|
220162
|
+
usage: "<version>",
|
|
220163
|
+
summary: "Make a release current again — a rollback, no compile",
|
|
220164
|
+
description: "The release's bundle becomes what the project's address, the viewers and the store run, and its server is deployed to production again (the database migrates to that release's schema: a change that deletes data is shown and asked about, like a publish).",
|
|
220165
|
+
flags: allowDestructiveFlag,
|
|
220166
|
+
examples: ["lecodes releases use 1.2.3"],
|
|
220167
|
+
run: async ({ args, flags }) => {
|
|
220168
|
+
const version = args[0]?.replace(/^v/, "");
|
|
220169
|
+
if (!version)
|
|
220170
|
+
throw new UsageError("Which release? `lecodes releases` lists them.");
|
|
220171
|
+
const { apiUrl, token, manifest } = linkedProject();
|
|
220172
|
+
const releases = await listReleases(apiUrl, token, manifest.uuid);
|
|
220173
|
+
const target = releases.find((r) => r.version === version);
|
|
220174
|
+
if (!target)
|
|
220175
|
+
throw new CliError(`${manifest.name} has no release ${version} — \`lecodes releases\` lists them.`);
|
|
220176
|
+
const previous = releases.find((r) => r.current);
|
|
220177
|
+
const res = await withSchemaConfirm((allowDestructive) => useRelease(apiUrl, token, manifest.uuid, version, { allowDestructive }), { allowDestructive: flags["allow-destructive"], retry: `lecodes releases use ${version}` });
|
|
220178
|
+
if (!res)
|
|
220179
|
+
return;
|
|
220180
|
+
const back = previous !== undefined && compareVersions(res.version, previous.version) < 0;
|
|
220181
|
+
reportRelease({ ...target, ...res }, qrUrl(apiUrl, manifest.uuid), back ? "Rolled back to" : "Published");
|
|
220182
|
+
}
|
|
220183
|
+
});
|
|
220184
|
+
var upload2 = defineCommand({
|
|
220185
|
+
name: "upload",
|
|
220186
|
+
usage: "<app.js>",
|
|
220187
|
+
summary: "Make a bundle compiled here the current release — no build on the platform",
|
|
220188
|
+
description: "For a bundle the platform's build service cannot make any more: compiled with an older SDK's CLI (`compile --remote-assets`, so it names the project's uploaded resources), it becomes the current release with its own `// sdk:` — what the viewers of that SDK line run. Local changes are pushed first as the release's checkpoint. A project with server code cannot take one (its server is built with the client).",
|
|
220189
|
+
flags: { message: str('checkpoint message when there are local changes (default "Release")', { alias: "m", value: "<text>" }) },
|
|
220190
|
+
examples: ["lecodes releases upload app.js", 'lecodes releases upload build/app.js -m "Kungur for the 2.1 app"'],
|
|
220191
|
+
run: async ({ args, flags }) => {
|
|
220192
|
+
const file = args[0];
|
|
220193
|
+
if (!file)
|
|
220194
|
+
throw new UsageError("Which bundle? `lecodes releases upload <app.js>`.");
|
|
220195
|
+
const path = resolve21(file);
|
|
220196
|
+
if (!existsSync39(path))
|
|
220197
|
+
throw new CliError(`No such file: ${file}`);
|
|
220198
|
+
const code = readFileSync31(path, "utf8");
|
|
220199
|
+
const sdk = /^\/\/ sdk: (\S+)/m.exec(code)?.[1];
|
|
220200
|
+
if (!sdk)
|
|
220201
|
+
throw new CliError(`${file} is not a LeCodes bundle: no \`// sdk:\` header line. \`lecodes compile --remote-assets -o app.js\` writes one.`);
|
|
220202
|
+
const map = existsSync39(`${path}.map`) ? readFileSync31(`${path}.map`, "utf8") : null;
|
|
220203
|
+
const { root, manifest, apiUrl, token } = linkedProject();
|
|
220204
|
+
if (hasChanges(diffLocal(manifest, scanProject(root, manifest))))
|
|
220205
|
+
await runPush({ message: flags.message || "Release" });
|
|
220206
|
+
const release = await uploadReleaseBundle(apiUrl, token, manifest.uuid, { code, map });
|
|
220207
|
+
reportRelease(release, qrUrl(apiUrl, manifest.uuid), "Uploaded");
|
|
220208
|
+
}
|
|
220209
|
+
});
|
|
220210
|
+
var releases_default = defineCommand({
|
|
220211
|
+
name: "releases",
|
|
220212
|
+
summary: "List the project's releases (● = the current one); `releases use <version>` rolls back, `releases upload <app.js>` releases a bundle compiled here",
|
|
220213
|
+
description: "Every `lecodes publish` (or the editor's Publish button) makes a release: an immutable build with its own version. The current one is what https://<handle>.le.codes, the viewers and the store run.",
|
|
220214
|
+
commands: [use, upload2],
|
|
220215
|
+
examples: ["lecodes releases", "lecodes releases use 1.2.3", "lecodes releases upload app.js"],
|
|
220216
|
+
run: async () => {
|
|
220217
|
+
const { apiUrl, token, manifest } = linkedProject();
|
|
220218
|
+
const releases = await listReleases(apiUrl, token, manifest.uuid);
|
|
220219
|
+
if (releases.length === 0) {
|
|
220220
|
+
info(`${manifest.name} has no releases yet — \`lecodes publish\` makes the first.`);
|
|
220221
|
+
return;
|
|
220222
|
+
}
|
|
220223
|
+
table([
|
|
220224
|
+
["", c.dim("VERSION"), c.dim("DATE"), c.dim("SIZE"), c.dim("SDK")],
|
|
220225
|
+
...releases.map((r) => [
|
|
220226
|
+
r.current ? c.green("●") : "",
|
|
220227
|
+
r.current ? c.bold(r.version) : r.version,
|
|
220228
|
+
relativeTime(r.createdAt),
|
|
220229
|
+
formatSize(r.size),
|
|
220230
|
+
r.sdk
|
|
220231
|
+
])
|
|
220232
|
+
]);
|
|
220233
|
+
if (!releases.some((r) => r.current))
|
|
220234
|
+
info(c.dim(" Unpublished: no release is current."));
|
|
220235
|
+
}
|
|
220236
|
+
});
|
|
220237
|
+
|
|
220238
|
+
// src/commands/push.ts
|
|
219997
220239
|
init_shared();
|
|
219998
220240
|
var pushFlags = {
|
|
219999
220241
|
message: str("checkpoint message (asked for when omitted)", { alias: "m", value: "<text>" }),
|
|
220000
|
-
publish: bool("also
|
|
220242
|
+
publish: bool("also release the project with the push (= `lecodes publish`)"),
|
|
220243
|
+
"force-publish": bool("--publish, and release even when nothing changed — a new SDK on the build service (= `lecodes publish --force`)"),
|
|
220001
220244
|
force: bool("push even if the server moved on (shows + confirms which server files get overwritten)"),
|
|
220002
220245
|
yes: bool("skip the --force overwrite confirmation", { alias: "y" })
|
|
220003
220246
|
};
|
|
@@ -220005,18 +220248,21 @@ var push_default = defineCommand({
|
|
|
220005
220248
|
name: "push",
|
|
220006
220249
|
summary: "Push local changes as one new checkpoint",
|
|
220007
220250
|
flags: pushFlags,
|
|
220008
|
-
examples: ['lecodes push -m "Tweak the menu"', "lecodes push --publish"],
|
|
220251
|
+
examples: ['lecodes push -m "Tweak the menu"', "lecodes push --publish", "lecodes push --force-publish"],
|
|
220009
220252
|
run: ({ flags }) => runPush(flags)
|
|
220010
220253
|
});
|
|
220011
220254
|
var runPush = async (opts) => {
|
|
220012
220255
|
const { root, manifest, apiUrl, token } = linkedProject();
|
|
220013
|
-
const
|
|
220256
|
+
const forcePublish = !!opts["force-publish"];
|
|
220257
|
+
const publish = !!opts.publish || forcePublish;
|
|
220014
220258
|
const scan = scanProject(root, manifest);
|
|
220015
220259
|
const diff = diffLocal(manifest, scan);
|
|
220016
220260
|
if (!hasChanges(diff)) {
|
|
220017
220261
|
if (publish) {
|
|
220018
|
-
|
|
220019
|
-
|
|
220262
|
+
info(c.dim("Working tree clean — publishing the project as it is."));
|
|
220263
|
+
const release = await withSchemaConfirm((allowDestructive) => publishProject(apiUrl, token, manifest.uuid, { allowDestructive, force: forcePublish }), { allowDestructive: false, retry: "lecodes publish" });
|
|
220264
|
+
if (release)
|
|
220265
|
+
reportRelease(release, qrUrl(apiUrl, manifest.uuid));
|
|
220020
220266
|
} else
|
|
220021
220267
|
info(c.dim("Working tree clean — nothing to push."));
|
|
220022
220268
|
await flushComments();
|
|
@@ -220058,9 +220304,9 @@ var runPush = async (opts) => {
|
|
|
220058
220304
|
if (source)
|
|
220059
220305
|
entry.assetId = source.id;
|
|
220060
220306
|
if (isTextKind(kind))
|
|
220061
|
-
entry.text =
|
|
220307
|
+
entry.text = readFileSync32(file.absPath, "utf8");
|
|
220062
220308
|
else if (!source || source.sha !== file.sha) {
|
|
220063
|
-
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename20(file.path),
|
|
220309
|
+
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename20(file.path), readFileSync32(file.absPath));
|
|
220064
220310
|
entry.blobFileId = blob.id;
|
|
220065
220311
|
uploaded++;
|
|
220066
220312
|
}
|
|
@@ -220069,11 +220315,11 @@ var runPush = async (opts) => {
|
|
|
220069
220315
|
const folders = scan.dirs.map((path) => ({ path }));
|
|
220070
220316
|
if (uploaded > 0)
|
|
220071
220317
|
info(c.dim(`Uploaded ${uploaded} binary file${uploaded === 1 ? "" : "s"}.`));
|
|
220072
|
-
const res = await sync(apiUrl, token, manifest.uuid, { message, files, folders, publish });
|
|
220318
|
+
const res = await sync(apiUrl, token, manifest.uuid, { message, files, folders, publish, ...forcePublish ? { force: true } : {} });
|
|
220073
220319
|
if (!res.created) {
|
|
220074
220320
|
info(c.dim("Server reported no changes."));
|
|
220075
220321
|
if (publish)
|
|
220076
|
-
reportPublish(res);
|
|
220322
|
+
reportPublish(res, qrUrl(apiUrl, manifest.uuid));
|
|
220077
220323
|
return;
|
|
220078
220324
|
}
|
|
220079
220325
|
const shaByPath = new Map(scan.files.map((f) => [f.path, f.sha]));
|
|
@@ -220089,7 +220335,7 @@ var runPush = async (opts) => {
|
|
|
220089
220335
|
].filter(Boolean).join(", ");
|
|
220090
220336
|
success(`Pushed checkpoint${res.seq ? ` #${res.seq}` : ""}${counts ? ` (${counts})` : ""}: ${message}`);
|
|
220091
220337
|
if (publish)
|
|
220092
|
-
reportPublish(res);
|
|
220338
|
+
reportPublish(res, qrUrl(apiUrl, manifest.uuid));
|
|
220093
220339
|
await flushComments();
|
|
220094
220340
|
};
|
|
220095
220341
|
var flushComments = async () => {
|
|
@@ -220112,11 +220358,11 @@ var serverPathDrift = (project, manifest) => {
|
|
|
220112
220358
|
const parts = [added.length ? describe("added", added) : "", removed.length ? describe("removed", removed) : ""].filter(Boolean);
|
|
220113
220359
|
return parts.length ? parts.join("; ") : null;
|
|
220114
220360
|
};
|
|
220115
|
-
var reportPublish = (res) => {
|
|
220116
|
-
if (res.published)
|
|
220117
|
-
|
|
220361
|
+
var reportPublish = (res, qr) => {
|
|
220362
|
+
if (res.published && res.release)
|
|
220363
|
+
reportRelease(res.release, qr);
|
|
220118
220364
|
else
|
|
220119
|
-
warn(`Push landed, but publish failed: ${res.publishError ?? "unknown error"}
|
|
220365
|
+
warn(`Push landed, but publish failed: ${res.publishError ?? "unknown error"} — \`lecodes publish\` tries again.`);
|
|
220120
220366
|
};
|
|
220121
220367
|
|
|
220122
220368
|
// src/commands/link.ts
|
|
@@ -220171,10 +220417,10 @@ var link_default = defineCommand({
|
|
|
220171
220417
|
run: async ({ args, flags }) => {
|
|
220172
220418
|
const { apiUrl, token } = session();
|
|
220173
220419
|
const dirArg = args[0];
|
|
220174
|
-
const root = dirArg ?
|
|
220175
|
-
if (!
|
|
220420
|
+
const root = dirArg ? resolve22(dirArg) : process.cwd();
|
|
220421
|
+
if (!existsSync40(root))
|
|
220176
220422
|
throw new CliError(`No such directory: ${root}`);
|
|
220177
|
-
if (
|
|
220423
|
+
if (existsSync40(join44(root, LECODES_DIR, "manifest.json")) && !flags.force) {
|
|
220178
220424
|
throw new CliError("This folder is already linked (.lecodes/manifest.json exists) — use `lecodes push`, or `lecodes link --force` to re-link it to another project.");
|
|
220179
220425
|
}
|
|
220180
220426
|
let enclosing = null;
|
|
@@ -220260,9 +220506,9 @@ var login_default = defineCommand({
|
|
|
220260
220506
|
});
|
|
220261
220507
|
|
|
220262
220508
|
// src/commands/navmesh.ts
|
|
220263
|
-
import { existsSync as
|
|
220509
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync25, mkdtempSync as mkdtempSync5, readdirSync as readdirSync17, readFileSync as readFileSync33, rmSync as rmSync12, statSync as statSync15, unlinkSync as unlinkSync2, writeFileSync as writeFileSync27 } from "node:fs";
|
|
220264
220510
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
220265
|
-
import { basename as basename22, join as join45, relative as relative7, resolve as
|
|
220511
|
+
import { basename as basename22, join as join45, relative as relative7, resolve as resolve23 } from "node:path";
|
|
220266
220512
|
init_cli();
|
|
220267
220513
|
init_shared();
|
|
220268
220514
|
|
|
@@ -221059,7 +221305,7 @@ var range2 = (name, value, fallback, min, max) => {
|
|
|
221059
221305
|
var guessName = (root) => {
|
|
221060
221306
|
const dirs = [join45(root, "src"), root];
|
|
221061
221307
|
for (const dir of dirs) {
|
|
221062
|
-
if (!
|
|
221308
|
+
if (!existsSync41(dir))
|
|
221063
221309
|
continue;
|
|
221064
221310
|
let entries;
|
|
221065
221311
|
try {
|
|
@@ -221071,7 +221317,7 @@ var guessName = (root) => {
|
|
|
221071
221317
|
if (!f.endsWith(".scene.ts"))
|
|
221072
221318
|
continue;
|
|
221073
221319
|
try {
|
|
221074
|
-
if (/\bnavmesh\s*:/.test(
|
|
221320
|
+
if (/\bnavmesh\s*:/.test(readFileSync33(join45(dir, f), "utf8")))
|
|
221075
221321
|
return f.slice(0, -".scene.ts".length);
|
|
221076
221322
|
} catch {}
|
|
221077
221323
|
}
|
|
@@ -221137,9 +221383,9 @@ var bake2 = defineCommand({
|
|
|
221137
221383
|
examples: ["lecodes navmesh bake", "lecodes navmesh bake --name yard --cell 0.2 --obj --logs"],
|
|
221138
221384
|
run: async ({ flags }) => {
|
|
221139
221385
|
const { root } = localProject();
|
|
221140
|
-
const outDir =
|
|
221386
|
+
const outDir = resolve23(root, flags.out);
|
|
221141
221387
|
const entryFile = flags.entry;
|
|
221142
|
-
if (!
|
|
221388
|
+
if (!existsSync41(resolve23(root, entryFile))) {
|
|
221143
221389
|
throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the level: a scene file with env.navmesh, or code calling NavMesh.load).`);
|
|
221144
221390
|
}
|
|
221145
221391
|
const name = flags.name ?? guessName(root) ?? "scene";
|
|
@@ -221150,7 +221396,7 @@ var bake2 = defineCommand({
|
|
|
221150
221396
|
const logs = flags.logs;
|
|
221151
221397
|
mkdirSync25(outDir, { recursive: true });
|
|
221152
221398
|
const navmeshPath = join45(outDir, `${name}.navmesh`);
|
|
221153
|
-
if (!
|
|
221399
|
+
if (!existsSync41(navmeshPath))
|
|
221154
221400
|
writeFileSync27(navmeshPath, placeholderLnav());
|
|
221155
221401
|
const importPath = "./" + entryFile.replace(/\\/g, "/").replace(/\.tsx?$/, "");
|
|
221156
221402
|
const entryPath = "/.navmesh-entry.ts";
|
|
@@ -221219,9 +221465,9 @@ import ${JSON.stringify(importPath)}
|
|
|
221219
221465
|
if (!config)
|
|
221220
221466
|
throw new CliError("The host reported a dump but no `[navmesh] config` line — SDK/host mismatch?");
|
|
221221
221467
|
const geoPath = join45(outDir, `${name}.navgeo`);
|
|
221222
|
-
if (!
|
|
221468
|
+
if (!existsSync41(geoPath))
|
|
221223
221469
|
throw new CliError(`The host reported a dump but ${geoPath} is missing.`);
|
|
221224
|
-
const geo = readNgeo(new Uint8Array(
|
|
221470
|
+
const geo = readNgeo(new Uint8Array(readFileSync33(geoPath)));
|
|
221225
221471
|
const params = {
|
|
221226
221472
|
agentRadius: range2("radius", flags.radius, config.agent.radius, 0.05, 10),
|
|
221227
221473
|
agentHeight: range2("height", flags.height, config.agent.height, 0.1, 20),
|
|
@@ -221295,25 +221541,44 @@ init_cli();
|
|
|
221295
221541
|
|
|
221296
221542
|
// src/plugin/generate.ts
|
|
221297
221543
|
init_cli();
|
|
221298
|
-
import { existsSync as
|
|
221544
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync26, readFileSync as readFileSync34, readdirSync as readdirSync18, statSync as statSync16, writeFileSync as writeFileSync28 } from "node:fs";
|
|
221299
221545
|
import { createRequire as createRequire7 } from "node:module";
|
|
221300
|
-
import { basename as basename23, join as join46, relative as relative8, resolve as
|
|
221546
|
+
import { basename as basename23, join as join46, relative as relative8, resolve as resolve24, sep as sep3 } from "node:path";
|
|
221301
221547
|
|
|
221302
221548
|
// src/plugin/permissions.ts
|
|
221303
221549
|
var PERMISSION_KINDS = ["camera", "microphone", "location", "speech"];
|
|
221304
221550
|
var isPermissionKind = (s) => PERMISSION_KINDS.includes(s);
|
|
221305
221551
|
var PERMISSION_NEEDS = {
|
|
221306
|
-
camera: { infoPlist: ["NSCameraUsageDescription"], android: [["android.permission.CAMERA"]] },
|
|
221307
|
-
microphone: {
|
|
221552
|
+
camera: { infoPlist: ["NSCameraUsageDescription"], android: [["android.permission.CAMERA"]], androidFeatures: ["android.hardware.camera"] },
|
|
221553
|
+
microphone: {
|
|
221554
|
+
infoPlist: ["NSMicrophoneUsageDescription"],
|
|
221555
|
+
android: [["android.permission.RECORD_AUDIO"]],
|
|
221556
|
+
androidFeatures: ["android.hardware.microphone"]
|
|
221557
|
+
},
|
|
221308
221558
|
location: {
|
|
221309
221559
|
infoPlist: ["NSLocationWhenInUseUsageDescription"],
|
|
221310
|
-
android: [["android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"]]
|
|
221560
|
+
android: [["android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"]],
|
|
221561
|
+
androidFeatures: ["android.hardware.location"]
|
|
221311
221562
|
},
|
|
221312
|
-
speech: {
|
|
221563
|
+
speech: {
|
|
221564
|
+
infoPlist: ["NSSpeechRecognitionUsageDescription"],
|
|
221565
|
+
android: [["android.permission.RECORD_AUDIO"]],
|
|
221566
|
+
androidFeatures: ["android.hardware.microphone"]
|
|
221567
|
+
}
|
|
221313
221568
|
};
|
|
221314
221569
|
var usesPermissions = (xml) => [...xml.matchAll(/<uses-permission(?:-sdk-23)?\b[^>]*\bandroid:name\s*=\s*"([^"]+)"/g)].map((m) => m[1]);
|
|
221315
|
-
var
|
|
221316
|
-
const out =
|
|
221570
|
+
var usesFeatures = (xml) => {
|
|
221571
|
+
const out = [];
|
|
221572
|
+
for (const m of xml.replace(/<!--[\s\S]*?-->/g, "").matchAll(/<uses-feature\b[^>]*>/g)) {
|
|
221573
|
+
const attr = (re) => re.exec(m[0])?.[2];
|
|
221574
|
+
const name = attr(/\bandroid:name\s*=\s*(["'])(.*?)\1/);
|
|
221575
|
+
if (name)
|
|
221576
|
+
out.push({ name, required: attr(/\bandroid:required\s*=\s*(["'])(.*?)\1/)?.trim() !== "false" });
|
|
221577
|
+
}
|
|
221578
|
+
return out;
|
|
221579
|
+
};
|
|
221580
|
+
var missingDeclarations = (kinds, infoPlist, androidNames, androidFeatures) => {
|
|
221581
|
+
const out = { infoPlist: [], android: [], androidFeatures: [] };
|
|
221317
221582
|
for (const kind of kinds) {
|
|
221318
221583
|
const needs = PERMISSION_NEEDS[kind];
|
|
221319
221584
|
if (infoPlist)
|
|
@@ -221326,8 +221591,14 @@ var missingDeclarations = (kinds, infoPlist, androidNames) => {
|
|
|
221326
221591
|
if (!group.some((name) => androidNames.includes(name)))
|
|
221327
221592
|
out.android.push(`${group.join(" or ")} (${kind})`);
|
|
221328
221593
|
}
|
|
221594
|
+
if (androidFeatures)
|
|
221595
|
+
for (const feature of needs.androidFeatures) {
|
|
221596
|
+
const declared = androidFeatures.filter((f) => f.name === feature);
|
|
221597
|
+
if (declared.length === 0 || declared.some((f) => f.required))
|
|
221598
|
+
out.androidFeatures.push(`${feature} (${kind})`);
|
|
221599
|
+
}
|
|
221329
221600
|
}
|
|
221330
|
-
return { infoPlist: [...new Set(out.infoPlist)], android: [...new Set(out.android)] };
|
|
221601
|
+
return { infoPlist: [...new Set(out.infoPlist)], android: [...new Set(out.android)], androidFeatures: [...new Set(out.androidFeatures)] };
|
|
221331
221602
|
};
|
|
221332
221603
|
|
|
221333
221604
|
// src/plugin/contract.ts
|
|
@@ -224135,13 +224406,13 @@ const wire = {
|
|
|
224135
224406
|
var shown = (from, to) => relative8(from, to).split(sep3).join("/");
|
|
224136
224407
|
var CONTRACT_FILE = "contract.d.ts";
|
|
224137
224408
|
var findPlugins = (path) => {
|
|
224138
|
-
const root =
|
|
224139
|
-
if (
|
|
224409
|
+
const root = resolve24(path);
|
|
224410
|
+
if (existsSync42(join46(root, CONTRACT_FILE)))
|
|
224140
224411
|
return [{ id: basename23(root), dir: root }];
|
|
224141
224412
|
const holder = join46(root, "plugins");
|
|
224142
|
-
if (!
|
|
224413
|
+
if (!existsSync42(holder))
|
|
224143
224414
|
return [];
|
|
224144
|
-
return readdirSync18(holder).sort().map((id) => ({ id, dir: join46(holder, id) })).filter((p) => statSync16(p.dir).isDirectory() &&
|
|
224415
|
+
return readdirSync18(holder).sort().map((id) => ({ id, dir: join46(holder, id) })).filter((p) => statSync16(p.dir).isDirectory() && existsSync42(join46(p.dir, CONTRACT_FILE)));
|
|
224145
224416
|
};
|
|
224146
224417
|
var loadTypescript = (dir) => {
|
|
224147
224418
|
for (const from of [join46(dir, "package.json"), import.meta.url]) {
|
|
@@ -224156,25 +224427,25 @@ var loadTypescript = (dir) => {
|
|
|
224156
224427
|
};
|
|
224157
224428
|
var generatePlugin = (ts, plugin) => {
|
|
224158
224429
|
const file = join46(plugin.dir, CONTRACT_FILE);
|
|
224159
|
-
const contract = readContract(ts, file,
|
|
224430
|
+
const contract = readContract(ts, file, readFileSync34(file, "utf8"));
|
|
224160
224431
|
const manifest = readManifestIfAny(plugin.dir, plugin.id);
|
|
224161
224432
|
checkPermissions(plugin, contract, manifest);
|
|
224162
|
-
const handWritten =
|
|
224433
|
+
const handWritten = existsSync42(join46(plugin.dir, "sdk", `${plugin.id}.ts`));
|
|
224163
224434
|
const single = contract.channels.length === 1;
|
|
224164
224435
|
const files = contract.channels.map((channel) => ({
|
|
224165
224436
|
file: join46(plugin.dir, "sdk", single ? `${plugin.id}.gen.ts` : `${plugin.id}.${channel.iface}.gen.ts`),
|
|
224166
224437
|
text: emitSdk(contract, channel, { id: plugin.id, handWritten })
|
|
224167
224438
|
}));
|
|
224168
|
-
if (
|
|
224439
|
+
if (existsSync42(join46(plugin.dir, "ios"))) {
|
|
224169
224440
|
const name = single ? contract.channels[0].iface : contract.channels[0].iface + "Plugin";
|
|
224170
224441
|
files.push({ file: join46(plugin.dir, "ios", `${name}.gen.swift`), text: emitSwift(lower3(contract), { id: plugin.id }) });
|
|
224171
224442
|
}
|
|
224172
|
-
if (
|
|
224443
|
+
if (existsSync42(join46(plugin.dir, "android"))) {
|
|
224173
224444
|
const name = single ? contract.channels[0].iface : contract.channels[0].iface + "Plugin";
|
|
224174
224445
|
const pkg = kotlinPackage(join46(plugin.dir, "android"));
|
|
224175
224446
|
files.push({ file: join46(pkg.dir, `${name}.gen.kt`), text: emitKotlin(lower3(contract), { id: plugin.id, package: pkg.name }) });
|
|
224176
224447
|
}
|
|
224177
|
-
if (
|
|
224448
|
+
if (existsSync42(join46(plugin.dir, "web"))) {
|
|
224178
224449
|
files.push({ file: join46(plugin.dir, "web", `${plugin.id}.gen.ts`), text: emitWeb(lower3(contract), { id: plugin.id }) });
|
|
224179
224450
|
const foreign = hostImports(join46(plugin.dir, "web"));
|
|
224180
224451
|
if (foreign.length)
|
|
@@ -224196,13 +224467,15 @@ var checkPermissions = (plugin, contract, manifest) => {
|
|
|
224196
224467
|
if (kinds.length === 0)
|
|
224197
224468
|
return;
|
|
224198
224469
|
const androidManifest = join46(plugin.dir, "android", "src", "main", "AndroidManifest.xml");
|
|
224199
|
-
const
|
|
224200
|
-
const missing = missingDeclarations(kinds, manifest.ios ? manifest.ios.infoPlist ?? {} : null,
|
|
224470
|
+
const androidXml = manifest.android ? existsSync42(androidManifest) ? readFileSync34(androidManifest, "utf8") : "" : null;
|
|
224471
|
+
const missing = missingDeclarations(kinds, manifest.ios ? manifest.ios.infoPlist ?? {} : null, androidXml === null ? null : usesPermissions(androidXml), androidXml === null ? null : usesFeatures(androidXml));
|
|
224201
224472
|
const problems = [];
|
|
224202
224473
|
if (missing.infoPlist.length)
|
|
224203
224474
|
problems.push(`ios.infoPlist: the contract's @permission needs ${missing.infoPlist.join(", ")} — the usage string the dialog shows`);
|
|
224204
224475
|
if (missing.android.length)
|
|
224205
224476
|
problems.push(`android/src/main/AndroidManifest.xml: the contract's @permission needs <uses-permission android:name="…"/> for ${missing.android.join(", ")}`);
|
|
224477
|
+
if (missing.androidFeatures.length)
|
|
224478
|
+
problems.push(`android/src/main/AndroidManifest.xml: the contract's @permission implies <uses-feature android:name="…" android:required="false"/> for ${missing.androidFeatures.join(", ")} — without it Play requires the hardware`);
|
|
224206
224479
|
if (problems.length)
|
|
224207
224480
|
throw new ManifestError(join46(plugin.dir, MANIFEST_FILE), problems);
|
|
224208
224481
|
};
|
|
@@ -224218,7 +224491,7 @@ var hostImports = (web) => {
|
|
|
224218
224491
|
}
|
|
224219
224492
|
if (!/\.(ts|tsx|js|mjs)$/.test(entry))
|
|
224220
224493
|
continue;
|
|
224221
|
-
|
|
224494
|
+
readFileSync34(path, "utf8").split(`
|
|
224222
224495
|
`).forEach((line, i) => {
|
|
224223
224496
|
const m = /(?:from\s+|import\s*\(\s*|import\s+)["'](lecodes-[^"']*)["']/.exec(line);
|
|
224224
224497
|
if (m && !/^\s*\/\//.test(line))
|
|
@@ -224232,7 +224505,7 @@ var hostImports = (web) => {
|
|
|
224232
224505
|
var kotlinPackage = (android) => {
|
|
224233
224506
|
const root = join46(android, "src", "main", "java");
|
|
224234
224507
|
let dir = root;
|
|
224235
|
-
while (
|
|
224508
|
+
while (existsSync42(dir)) {
|
|
224236
224509
|
const entries = readdirSync18(dir).filter((e) => !e.startsWith("."));
|
|
224237
224510
|
const dirs = entries.filter((e) => statSync16(join46(dir, e)).isDirectory());
|
|
224238
224511
|
if (dirs.length !== 1 || entries.length !== 1)
|
|
@@ -224246,10 +224519,10 @@ var kotlinPackage = (android) => {
|
|
|
224246
224519
|
};
|
|
224247
224520
|
var repoSdkPath = (root) => {
|
|
224248
224521
|
const file = join46(root, "package.json");
|
|
224249
|
-
if (!
|
|
224522
|
+
if (!existsSync42(file))
|
|
224250
224523
|
return DEFAULT_SDK_PATH;
|
|
224251
224524
|
try {
|
|
224252
|
-
const pkg = JSON.parse(
|
|
224525
|
+
const pkg = JSON.parse(readFileSync34(file, "utf8"));
|
|
224253
224526
|
const path = pkg.lecodes?.iosSdkPath;
|
|
224254
224527
|
if (path !== undefined && typeof path !== "string")
|
|
224255
224528
|
throw new PackageError(`package.json lecodes.iosSdkPath: a path, as a string`);
|
|
@@ -224263,13 +224536,13 @@ var repoSdkPath = (root) => {
|
|
|
224263
224536
|
var generate = (path, opts = {}) => {
|
|
224264
224537
|
const plugins = findPlugins(path);
|
|
224265
224538
|
if (plugins.length === 0)
|
|
224266
|
-
throw new CliError(`No plugin under ${
|
|
224539
|
+
throw new CliError(`No plugin under ${resolve24(path)}: a plugin is a directory with a ${CONTRACT_FILE} (one, or plugins/<id>/ of a repo).`);
|
|
224267
224540
|
const ts = loadTypescript(plugins[0].dir);
|
|
224268
|
-
const root =
|
|
224541
|
+
const root = resolve24(path);
|
|
224269
224542
|
const report = { plugins: [], repo: { written: [], stale: [], fresh: [] }, failures: [] };
|
|
224270
224543
|
const place = (f, entry) => {
|
|
224271
224544
|
const name = shown(root, f.file) || basename23(f.file);
|
|
224272
|
-
const current =
|
|
224545
|
+
const current = existsSync42(f.file) ? readFileSync34(f.file, "utf8") : null;
|
|
224273
224546
|
if (current === f.text) {
|
|
224274
224547
|
entry.fresh.push(name);
|
|
224275
224548
|
return;
|
|
@@ -224306,7 +224579,7 @@ ${e.problems.map((p) => ` ${p}`).join(`
|
|
|
224306
224579
|
throw e;
|
|
224307
224580
|
}
|
|
224308
224581
|
}
|
|
224309
|
-
const isRepo = !
|
|
224582
|
+
const isRepo = !existsSync42(join46(root, CONTRACT_FILE));
|
|
224310
224583
|
if (isRepo && report.failures.length === 0 && manifests.some((m) => m.manifest.ios)) {
|
|
224311
224584
|
try {
|
|
224312
224585
|
place({ file: join46(root, PACKAGE_FILE), text: emitPackageSwift(manifests, repoSdkPath(root)) }, report.repo);
|
|
@@ -224582,12 +224855,37 @@ var pn_default = defineCommand({
|
|
|
224582
224855
|
commands: [send, devices, keys]
|
|
224583
224856
|
});
|
|
224584
224857
|
|
|
224858
|
+
// src/commands/publish.ts
|
|
224859
|
+
init_cli();
|
|
224860
|
+
init_api();
|
|
224861
|
+
init_localFiles();
|
|
224862
|
+
init_shared();
|
|
224863
|
+
var publish_default = defineCommand({
|
|
224864
|
+
name: "publish",
|
|
224865
|
+
summary: "Release the project: push local changes, build, make it the live version",
|
|
224866
|
+
description: 'The editor\'s Publish button. Local changes are pushed first as a checkpoint ("Release", or -m). The platform builds the project, deploys its server to production and makes the build the current release at https://<handle>.le.codes — what the viewers and the store run. A project that builds to what is live already makes no release: "Nothing changed — <version> is current" (--force releases it all the same). A schema change that deletes production data is shown and asked about; without a terminal it needs --allow-destructive. `lecodes releases` lists the releases, `lecodes releases use <version>` rolls back.',
|
|
224867
|
+
flags: {
|
|
224868
|
+
message: str('checkpoint message when there are local changes (default "Release")', { alias: "m", value: "<text>" }),
|
|
224869
|
+
force: bool("rebuild even when nothing changed — a new SDK on the build service"),
|
|
224870
|
+
"allow-destructive": bool("apply a schema change that deletes production data without asking")
|
|
224871
|
+
},
|
|
224872
|
+
examples: ["lecodes publish", 'lecodes publish -m "Checkout v2"', "lecodes publish --force", "lecodes publish --allow-destructive"],
|
|
224873
|
+
run: async ({ flags }) => {
|
|
224874
|
+
const { root, manifest, apiUrl, token } = linkedProject();
|
|
224875
|
+
if (hasChanges(diffLocal(manifest, scanProject(root, manifest))))
|
|
224876
|
+
await runPush({ message: flags.message || "Release" });
|
|
224877
|
+
const release = await withSchemaConfirm((allowDestructive) => publishProject(apiUrl, token, manifest.uuid, { allowDestructive, force: flags.force }), { allowDestructive: flags["allow-destructive"], retry: flags.force ? "lecodes publish --force" : "lecodes publish" });
|
|
224878
|
+
if (release)
|
|
224879
|
+
reportRelease(release, qrUrl(apiUrl, manifest.uuid));
|
|
224880
|
+
}
|
|
224881
|
+
});
|
|
224882
|
+
|
|
224585
224883
|
// src/commands/pull.ts
|
|
224586
224884
|
init_cli();
|
|
224587
224885
|
init_api();
|
|
224588
224886
|
init_localFiles();
|
|
224589
224887
|
init_manifest();
|
|
224590
|
-
import { existsSync as
|
|
224888
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync27, rmSync as rmSync13, writeFileSync as writeFileSync29 } from "node:fs";
|
|
224591
224889
|
import { dirname as dirname21, join as join47 } from "node:path";
|
|
224592
224890
|
init_paths();
|
|
224593
224891
|
init_types4();
|
|
@@ -224618,7 +224916,7 @@ var pull_default = defineCommand({
|
|
|
224618
224916
|
if (remotePaths.has(path))
|
|
224619
224917
|
continue;
|
|
224620
224918
|
const abs = join47(root, ...path.replace(/^\//, "").split("/"));
|
|
224621
|
-
if (
|
|
224919
|
+
if (existsSync43(abs))
|
|
224622
224920
|
rmSync13(abs);
|
|
224623
224921
|
}
|
|
224624
224922
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
@@ -224679,8 +224977,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
224679
224977
|
};
|
|
224680
224978
|
|
|
224681
224979
|
// src/commands/render.ts
|
|
224682
|
-
import { mkdirSync as mkdirSync29, readFileSync as
|
|
224683
|
-
import { dirname as dirname22, resolve as
|
|
224980
|
+
import { mkdirSync as mkdirSync29, readFileSync as readFileSync36, writeFileSync as writeFileSync31 } from "node:fs";
|
|
224981
|
+
import { dirname as dirname22, resolve as resolve28 } from "node:path";
|
|
224684
224982
|
|
|
224685
224983
|
// src/compile/headlessBundle.ts
|
|
224686
224984
|
init_api();
|
|
@@ -224767,22 +225065,22 @@ var compileHeadlessBundle = async (opts = {}) => {
|
|
|
224767
225065
|
init_designMeta();
|
|
224768
225066
|
init_cli();
|
|
224769
225067
|
init_paths();
|
|
224770
|
-
import { existsSync as
|
|
224771
|
-
import { isAbsolute, join as join48, relative as relative9, resolve as
|
|
225068
|
+
import { existsSync as existsSync44, readFileSync as readFileSync35, statSync as statSync17 } from "node:fs";
|
|
225069
|
+
import { isAbsolute, join as join48, relative as relative9, resolve as resolve25 } from "node:path";
|
|
224772
225070
|
var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
224773
225071
|
var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
|
|
224774
225072
|
var DEFAULT_DESIGN_DIR = "design";
|
|
224775
225073
|
var hasSeparator = (file) => file.includes("/") || file.includes("\\");
|
|
224776
225074
|
var findScreenFile = (root, file, dirFlag) => {
|
|
224777
|
-
const candidates = [
|
|
225075
|
+
const candidates = [resolve25(process.cwd(), file)];
|
|
224778
225076
|
if (!hasSeparator(file)) {
|
|
224779
225077
|
candidates.push(join48(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
|
|
224780
225078
|
}
|
|
224781
225079
|
for (const base of candidates) {
|
|
224782
|
-
if (
|
|
225080
|
+
if (existsSync44(base) && statSync17(base).isFile())
|
|
224783
225081
|
return base;
|
|
224784
225082
|
for (const ext of SCREEN_EXTS)
|
|
224785
|
-
if (
|
|
225083
|
+
if (existsSync44(base + ext))
|
|
224786
225084
|
return base + ext;
|
|
224787
225085
|
}
|
|
224788
225086
|
throw new CliError(`No screen file at "${file}".` + (hasSeparator(file) ? "" : ` Pass a path, or a screen id that exists in ${dirFlag ?? DEFAULT_DESIGN_DIR}/screens/.`));
|
|
@@ -224792,7 +225090,7 @@ var designDirOf = (root, key) => {
|
|
|
224792
225090
|
if (!m)
|
|
224793
225091
|
return null;
|
|
224794
225092
|
const dir = join48(root, m[1]);
|
|
224795
|
-
return
|
|
225093
|
+
return existsSync44(join48(dir, "meta.json")) ? { dir, id: m[2] } : null;
|
|
224796
225094
|
};
|
|
224797
225095
|
var resolveScreenTarget = (root, file, opts = {}) => {
|
|
224798
225096
|
const abs = findScreenFile(root, file, opts.dir);
|
|
@@ -224801,7 +225099,7 @@ var resolveScreenTarget = (root, file, opts = {}) => {
|
|
|
224801
225099
|
throw new CliError(`"${file}" is outside the project (${root}) — a screen renders as part of its project, so its ` + `imports and asset() files resolve from the same tree.`);
|
|
224802
225100
|
}
|
|
224803
225101
|
const key = toKey(rel);
|
|
224804
|
-
if (!/^\s*export\s+default\b/m.test(
|
|
225102
|
+
if (!/^\s*export\s+default\b/m.test(readFileSync35(abs, "utf8"))) {
|
|
224805
225103
|
throw new CliError(`${key.slice(1)} has no default export. A screen file ends with ` + `\`export default UIScreen([...])\` — or \`export default (state) => UIScreen([...])\` for screen states.`);
|
|
224806
225104
|
}
|
|
224807
225105
|
const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
|
|
@@ -224837,9 +225135,9 @@ init_manifest();
|
|
|
224837
225135
|
|
|
224838
225136
|
// src/hosts/flowPick.ts
|
|
224839
225137
|
init_cli();
|
|
224840
|
-
import { existsSync as
|
|
225138
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync6, rmSync as rmSync14, writeFileSync as writeFileSync30 } from "node:fs";
|
|
224841
225139
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
224842
|
-
import { join as join49, resolve as
|
|
225140
|
+
import { join as join49, resolve as resolve26 } from "node:path";
|
|
224843
225141
|
import { deflateSync as deflateSync2 } from "node:zlib";
|
|
224844
225142
|
var MAX_SIDE = 4096;
|
|
224845
225143
|
var crcTable = (() => {
|
|
@@ -224908,8 +225206,8 @@ var write = (name, bytes) => {
|
|
|
224908
225206
|
};
|
|
224909
225207
|
var fileOf = (at, value, baseDir) => {
|
|
224910
225208
|
if (typeof value === "string") {
|
|
224911
|
-
const path =
|
|
224912
|
-
if (!
|
|
225209
|
+
const path = resolve26(baseDir, value);
|
|
225210
|
+
if (!existsSync45(path))
|
|
224913
225211
|
throw new CliError(`${at}: "pick": no file "${value}" (looked for ${path}).`);
|
|
224914
225212
|
return path;
|
|
224915
225213
|
}
|
|
@@ -224949,7 +225247,7 @@ var resolvePicks = (steps, baseDir) => {
|
|
|
224949
225247
|
|
|
224950
225248
|
// src/hosts/desktopScript.ts
|
|
224951
225249
|
init_cli();
|
|
224952
|
-
import { resolve as
|
|
225250
|
+
import { resolve as resolve27 } from "node:path";
|
|
224953
225251
|
|
|
224954
225252
|
// ../../runtime/headless/src/headless/paintDump.ts
|
|
224955
225253
|
var FLAG_INTERACTIVE = 1;
|
|
@@ -225132,7 +225430,7 @@ var FRAME_MS = 16;
|
|
|
225132
225430
|
var SETTLE_FRAMES = 640;
|
|
225133
225431
|
var ACTIONS = ["tap", "type", "fill", "scroll", "wait", "frames", "drag", "hold", "hover", "key", "keyDown", "keyUp", "look", "mouseTo", "wheel", "stick", "gamepad", "camera", "view"];
|
|
225134
225432
|
var SEMANTIC_EXPECT = ["text", "notText", "node", "notNode", "screen", "value"];
|
|
225135
|
-
var UNSUPPORTED_EXPECT = ["audio"];
|
|
225433
|
+
var UNSUPPORTED_EXPECT = ["audio", "toast"];
|
|
225136
225434
|
var VIEW_NAMES = ["iso", "top", "front", "side", "back"];
|
|
225137
225435
|
var isVec3 = (v) => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === "number");
|
|
225138
225436
|
var pt = (p) => typeof p === "string" ? p : `(${p.x},${p.y})`;
|
|
@@ -225394,7 +225692,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
225394
225692
|
}
|
|
225395
225693
|
}
|
|
225396
225694
|
if (needDump) {
|
|
225397
|
-
plan.paintDump =
|
|
225695
|
+
plan.paintDump = resolve27(opts.outDir, `.dump-${i}.json`);
|
|
225398
225696
|
plan.lines.push(emit(`frames 1`));
|
|
225399
225697
|
plan.lines.push(emit(`dump ${plan.paintDump}`));
|
|
225400
225698
|
budgetMs += 2 * FRAME_MS;
|
|
@@ -225409,7 +225707,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
225409
225707
|
throw new CliError(`${at}: "screenshot" must be a path.`);
|
|
225410
225708
|
if (opts.sim)
|
|
225411
225709
|
throw new CliError(`${at}: "screenshot" isn't available with --sim (the windowless host paints nothing) — judge the run by expect.log, or run it with --desktop for pixels.`);
|
|
225412
|
-
plan.screenshot =
|
|
225710
|
+
plan.screenshot = resolve27(opts.outDir, s.screenshot);
|
|
225413
225711
|
const clip = s.clip;
|
|
225414
225712
|
if (clip !== undefined) {
|
|
225415
225713
|
if (typeof clip === "string") {
|
|
@@ -225435,7 +225733,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
225435
225733
|
if (s.dump !== undefined) {
|
|
225436
225734
|
if (typeof s.dump !== "string")
|
|
225437
225735
|
throw new CliError(`${at}: "dump" must be a path.`);
|
|
225438
|
-
plan.dump =
|
|
225736
|
+
plan.dump = resolve27(opts.outDir, s.dump);
|
|
225439
225737
|
plan.lines.push(emit(`frames 1`));
|
|
225440
225738
|
plan.lines.push(emit(`dump ${plan.dump}`));
|
|
225441
225739
|
budgetMs += 2 * FRAME_MS;
|
|
@@ -225480,7 +225778,7 @@ var evaluateDesktopRun = (plan, output, readFile = () => null) => {
|
|
|
225480
225778
|
let ok = true;
|
|
225481
225779
|
for (const st of plan.steps) {
|
|
225482
225780
|
const skipped = [
|
|
225483
|
-
...st.skippedExpect.map((k) => `expect.${k} (the headless
|
|
225781
|
+
...st.skippedExpect.map((k) => `expect.${k} (the headless renderer's record — not on a native host)`),
|
|
225484
225782
|
...st.skippedOutputs.map((k) => `${k} output (the headless renderer's shape — use "dump" on a native host)`)
|
|
225485
225783
|
];
|
|
225486
225784
|
let error;
|
|
@@ -225586,7 +225884,7 @@ var parseNativeViews = (raw) => {
|
|
|
225586
225884
|
var requireRenderer = async () => loadPeer("lecodes-headless", "headless", { for: "lecodes render" });
|
|
225587
225885
|
var fileScenarioIO = (dir) => ({
|
|
225588
225886
|
write: (relPath, data) => {
|
|
225589
|
-
const abs =
|
|
225887
|
+
const abs = resolve28(dir, relPath);
|
|
225590
225888
|
mkdirSync29(dirname22(abs), { recursive: true });
|
|
225591
225889
|
writeFileSync31(abs, data);
|
|
225592
225890
|
}
|
|
@@ -225596,9 +225894,9 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
225596
225894
|
return;
|
|
225597
225895
|
let map = value;
|
|
225598
225896
|
if (typeof value === "string") {
|
|
225599
|
-
const abs =
|
|
225897
|
+
const abs = resolve28(baseDir, value);
|
|
225600
225898
|
try {
|
|
225601
|
-
map = JSON.parse(
|
|
225899
|
+
map = JSON.parse(readFileSync36(abs, "utf8"));
|
|
225602
225900
|
} catch (e) {
|
|
225603
225901
|
throw new CliError(`Can't read fixtures "${value}": ${e instanceof Error ? e.message : String(e)}`);
|
|
225604
225902
|
}
|
|
@@ -225611,18 +225909,18 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
225611
225909
|
var readScenarioFile = (path) => {
|
|
225612
225910
|
let raw;
|
|
225613
225911
|
try {
|
|
225614
|
-
raw = JSON.parse(
|
|
225912
|
+
raw = JSON.parse(readFileSync36(resolve28(path), "utf8"));
|
|
225615
225913
|
} catch (e) {
|
|
225616
225914
|
throw new CliError(`Can't read scenario "${path}": ${e instanceof Error ? e.message : String(e)}`);
|
|
225617
225915
|
}
|
|
225618
225916
|
if (Array.isArray(raw))
|
|
225619
|
-
return { steps: resolvePicks(raw, dirname22(
|
|
225917
|
+
return { steps: resolvePicks(raw, dirname22(resolve28(path))) };
|
|
225620
225918
|
if (typeof raw !== "object" || raw === null)
|
|
225621
225919
|
throw new CliError(`Scenario "${path}" must be a JSON object with "steps" (or a bare step array).`);
|
|
225622
225920
|
const file = raw;
|
|
225623
225921
|
if (file.steps === undefined)
|
|
225624
225922
|
throw new CliError(`Scenario "${path}" has no "steps".`);
|
|
225625
|
-
return { ...file, steps: resolvePicks(file.steps, dirname22(
|
|
225923
|
+
return { ...file, steps: resolvePicks(file.steps, dirname22(resolve28(path))) };
|
|
225626
225924
|
};
|
|
225627
225925
|
var parseClip = (raw) => {
|
|
225628
225926
|
if (raw === undefined)
|
|
@@ -225747,7 +226045,7 @@ var renderDesktop = async (flags, file) => {
|
|
|
225747
226045
|
entryOverride: screen?.entryPath
|
|
225748
226046
|
});
|
|
225749
226047
|
if (!sim)
|
|
225750
|
-
mkdirSync29(dirname22(
|
|
226048
|
+
mkdirSync29(dirname22(resolve28(pngFlag)), { recursive: true });
|
|
225751
226049
|
if (scenario) {
|
|
225752
226050
|
const outDir = flags["out-dir"];
|
|
225753
226051
|
const plan = translateScenario(scenario.steps, { outDir, headroomFrames: frames, sim });
|
|
@@ -225843,7 +226141,7 @@ var renderHeadless = async (flags, file) => {
|
|
|
225843
226141
|
const width = flags.width || scenario?.width || screen?.frame?.[0] || projectViewport?.width || 390;
|
|
225844
226142
|
const height = flags.height || scenario?.height || screen?.frame?.[1] || projectViewport?.height || 844;
|
|
225845
226143
|
const onConsole = flags.logs ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
225846
|
-
const envelopeFixtures = resolveFixtures(scenario?.fixtures, flags.script !== undefined ? dirname22(
|
|
226144
|
+
const envelopeFixtures = resolveFixtures(scenario?.fixtures, flags.script !== undefined ? dirname22(resolve28(flags.script)) : ".");
|
|
225847
226145
|
const flagFixtures = resolveFixtures(flags.fixtures, ".");
|
|
225848
226146
|
const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
|
|
225849
226147
|
if (fixtures) {
|
|
@@ -225884,7 +226182,7 @@ var renderHeadless = async (flags, file) => {
|
|
|
225884
226182
|
${c.red(st.error ?? "failed")}`);
|
|
225885
226183
|
}
|
|
225886
226184
|
if (!result.ok && result.failureArtifacts.length > 0) {
|
|
225887
|
-
logErr(c.dim(` failing frame: ${result.failureArtifacts.map((p) =>
|
|
226185
|
+
logErr(c.dim(` failing frame: ${result.failureArtifacts.map((p) => resolve28(outDir, p)).join(", ")}`));
|
|
225888
226186
|
}
|
|
225889
226187
|
logErr(result.ok ? c.green(`✓ ${result.steps.length}/${steps.length} steps passed`) : c.red(`✗ failed at step ${result.steps.length} of ${steps.length}`));
|
|
225890
226188
|
return done((cb) => cb(), result.ok ? 0 : 1);
|
|
@@ -225905,10 +226203,10 @@ var renderHeadless = async (flags, file) => {
|
|
|
225905
226203
|
for (const w of [...warnings, ...mapWarnings])
|
|
225906
226204
|
warnErr(w);
|
|
225907
226205
|
const pngPath = mapFlag.endsWith(".png") ? mapFlag : mapFlag + ".png";
|
|
225908
|
-
mkdirSync29(dirname22(
|
|
225909
|
-
writeFileSync31(
|
|
226206
|
+
mkdirSync29(dirname22(resolve28(pngPath)), { recursive: true });
|
|
226207
|
+
writeFileSync31(resolve28(pngPath), png);
|
|
225910
226208
|
const jsonPath = pngPath.slice(0, -4) + ".json";
|
|
225911
|
-
writeFileSync31(
|
|
226209
|
+
writeFileSync31(resolve28(jsonPath), JSON.stringify(data, null, 2));
|
|
225912
226210
|
note(`Wrote ${pngPath} + ${jsonPath}`);
|
|
225913
226211
|
return done((cb) => cb());
|
|
225914
226212
|
}
|
|
@@ -225928,8 +226226,8 @@ var renderHeadless = async (flags, file) => {
|
|
|
225928
226226
|
if (isNullSink(pngFlag)) {
|
|
225929
226227
|
note(`Discarded PNG (${pngFlag})`);
|
|
225930
226228
|
} else {
|
|
225931
|
-
mkdirSync29(dirname22(
|
|
225932
|
-
writeFileSync31(
|
|
226229
|
+
mkdirSync29(dirname22(resolve28(pngFlag)), { recursive: true });
|
|
226230
|
+
writeFileSync31(resolve28(pngFlag), png);
|
|
225933
226231
|
note(`Wrote ${pngFlag} (${png.length} bytes, ${outW}x${outH}${clip !== undefined ? ", clipped" : ""})`);
|
|
225934
226232
|
}
|
|
225935
226233
|
return done((cb) => cb());
|
|
@@ -225944,7 +226242,7 @@ var renderHeadless = async (flags, file) => {
|
|
|
225944
226242
|
return done((cb) => cb());
|
|
225945
226243
|
}
|
|
225946
226244
|
if (out) {
|
|
225947
|
-
writeFileSync31(
|
|
226245
|
+
writeFileSync31(resolve28(out), result.text);
|
|
225948
226246
|
note(`Wrote ${out} (${result.text.length} bytes)`);
|
|
225949
226247
|
return done((cb) => cb());
|
|
225950
226248
|
}
|
|
@@ -225956,8 +226254,8 @@ var renderHeadless = async (flags, file) => {
|
|
|
225956
226254
|
|
|
225957
226255
|
// src/commands/scene.ts
|
|
225958
226256
|
init_cli();
|
|
225959
|
-
import { existsSync as
|
|
225960
|
-
import { basename as basename24, dirname as dirname23, join as join51, relative as relative10, resolve as
|
|
226257
|
+
import { existsSync as existsSync47, readFileSync as readFileSync38, readdirSync as readdirSync19, statSync as statSync18, writeFileSync as writeFileSync33 } from "node:fs";
|
|
226258
|
+
import { basename as basename24, dirname as dirname23, join as join51, relative as relative10, resolve as resolve29 } from "node:path";
|
|
225961
226259
|
|
|
225962
226260
|
// src/compile/sceneCompile.ts
|
|
225963
226261
|
init_localFiles();
|
|
@@ -225965,7 +226263,7 @@ init_ignore();
|
|
|
225965
226263
|
init_cli();
|
|
225966
226264
|
init_projectCompile();
|
|
225967
226265
|
import { createHash as createHash8 } from "node:crypto";
|
|
225968
|
-
import { existsSync as
|
|
226266
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync30, readFileSync as readFileSync37, writeFileSync as writeFileSync32 } from "node:fs";
|
|
225969
226267
|
import { join as join50 } from "node:path";
|
|
225970
226268
|
var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
|
|
225971
226269
|
var rel = (key) => key.replace(/^\//, "");
|
|
@@ -225977,9 +226275,9 @@ var compileSceneEditor = async (opts) => {
|
|
|
225977
226275
|
for (const file of scan.files) {
|
|
225978
226276
|
const path = "./" + rel(file.path);
|
|
225979
226277
|
if (TEXT_EXT.test(path)) {
|
|
225980
|
-
entries.push({ path, type: "text", text:
|
|
226278
|
+
entries.push({ path, type: "text", text: readFileSync37(file.absPath, "utf8") });
|
|
225981
226279
|
} else if (/\.mat$/i.test(path)) {
|
|
225982
|
-
entries.push({ path, type: "shader", text:
|
|
226280
|
+
entries.push({ path, type: "shader", text: readFileSync37(file.absPath, "utf8"), absPath: file.absPath });
|
|
225983
226281
|
} else {
|
|
225984
226282
|
entries.push({ path, type: "resource", fileSrc: "/project/" + rel(file.path) });
|
|
225985
226283
|
}
|
|
@@ -225997,7 +226295,7 @@ var compileSceneEditor = async (opts) => {
|
|
|
225997
226295
|
const dir = shadersCacheDir(root);
|
|
225998
226296
|
mkdirSync30(dir, { recursive: true });
|
|
225999
226297
|
const liteName = `${hash}_webgl-lite.filamat`;
|
|
226000
|
-
if (!
|
|
226298
|
+
if (!existsSync46(join50(dir, liteName)))
|
|
226001
226299
|
writeFileSync32(join50(dir, liteName), JSON.stringify(buildLiteMaterialArtifact(e.text ?? "")));
|
|
226002
226300
|
const src = `/project/.lecodes/shaders/${hash}_opengl.filamat`;
|
|
226003
226301
|
e.shaders = ["opengl", "webgl-lite"].map((platform) => ({ platform, src }));
|
|
@@ -226035,7 +226333,7 @@ var DEFAULT_PORT3 = 4488;
|
|
|
226035
226333
|
var findAppRoot = (start) => {
|
|
226036
226334
|
let dir = start;
|
|
226037
226335
|
while (true) {
|
|
226038
|
-
if (
|
|
226336
|
+
if (existsSync47(join51(dir, "app.json")))
|
|
226039
226337
|
return dir;
|
|
226040
226338
|
const parent = dirname23(dir);
|
|
226041
226339
|
if (parent === dir)
|
|
@@ -226060,10 +226358,10 @@ var getContext2 = (pathArg) => {
|
|
|
226060
226358
|
root = process.cwd();
|
|
226061
226359
|
manifest = null;
|
|
226062
226360
|
if (pathArg) {
|
|
226063
|
-
const abs =
|
|
226064
|
-
if (
|
|
226361
|
+
const abs = resolve29(process.cwd(), pathArg);
|
|
226362
|
+
if (existsSync47(abs) && statSync18(abs).isDirectory())
|
|
226065
226363
|
root = abs;
|
|
226066
|
-
else if (abs.endsWith(".scene.ts") &&
|
|
226364
|
+
else if (abs.endsWith(".scene.ts") && existsSync47(abs))
|
|
226067
226365
|
root = findAppRoot(dirname23(abs)) ?? dirname23(abs);
|
|
226068
226366
|
else
|
|
226069
226367
|
throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
|
|
@@ -226073,9 +226371,9 @@ var getContext2 = (pathArg) => {
|
|
|
226073
226371
|
}
|
|
226074
226372
|
let scene = null;
|
|
226075
226373
|
if (pathArg?.endsWith(".scene.ts")) {
|
|
226076
|
-
const abs =
|
|
226374
|
+
const abs = resolve29(process.cwd(), pathArg);
|
|
226077
226375
|
const rel = relative10(root, abs).replace(/\\/g, "/");
|
|
226078
|
-
if (
|
|
226376
|
+
if (existsSync47(abs) && !rel.startsWith(".."))
|
|
226079
226377
|
scene = "./" + rel;
|
|
226080
226378
|
}
|
|
226081
226379
|
return { root, manifest, name: manifest?.name ?? basename24(root), scene };
|
|
@@ -226084,9 +226382,9 @@ var writeMcpConfig2 = (root, port) => {
|
|
|
226084
226382
|
const path = join51(root, ".mcp.json");
|
|
226085
226383
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
226086
226384
|
let config = {};
|
|
226087
|
-
if (
|
|
226385
|
+
if (existsSync47(path)) {
|
|
226088
226386
|
try {
|
|
226089
|
-
config = JSON.parse(
|
|
226387
|
+
config = JSON.parse(readFileSync38(path, "utf8"));
|
|
226090
226388
|
} catch {
|
|
226091
226389
|
warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
|
|
226092
226390
|
return;
|
|
@@ -226201,7 +226499,7 @@ var serve_default = defineCommand({
|
|
|
226201
226499
|
});
|
|
226202
226500
|
|
|
226203
226501
|
// src/commands/shaders/index.ts
|
|
226204
|
-
import { readFileSync as
|
|
226502
|
+
import { readFileSync as readFileSync40 } from "node:fs";
|
|
226205
226503
|
import { relative as relative12 } from "node:path";
|
|
226206
226504
|
init_cli();
|
|
226207
226505
|
init_shared();
|
|
@@ -226210,15 +226508,15 @@ init_shared();
|
|
|
226210
226508
|
init_compile();
|
|
226211
226509
|
init_distRoot();
|
|
226212
226510
|
init_cli();
|
|
226213
|
-
import { existsSync as
|
|
226214
|
-
import { basename as basename26, join as join52, relative as relative11, resolve as
|
|
226511
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync31, readdirSync as readdirSync20, readFileSync as readFileSync39, writeFileSync as writeFileSync34 } from "node:fs";
|
|
226512
|
+
import { basename as basename26, join as join52, relative as relative11, resolve as resolve30 } from "node:path";
|
|
226215
226513
|
var engineMaterialsDir = () => {
|
|
226216
226514
|
const candidates = [
|
|
226217
226515
|
join52(distRoot, "runtime", "materials"),
|
|
226218
226516
|
join52(distRoot, "..", "..", "engines", "gl", "materials", "src")
|
|
226219
226517
|
];
|
|
226220
226518
|
for (const c of candidates)
|
|
226221
|
-
if (
|
|
226519
|
+
if (existsSync48(join52(c, "unlit.mat")))
|
|
226222
226520
|
return c;
|
|
226223
226521
|
return null;
|
|
226224
226522
|
};
|
|
@@ -226300,11 +226598,11 @@ var writeNewShader = (o) => {
|
|
|
226300
226598
|
const name = o.name.endsWith(".mat") ? o.name.slice(0, -4) : o.name;
|
|
226301
226599
|
if (!NAME_RE.test(name))
|
|
226302
226600
|
throw new CliError(`"${o.name}" is not a shader name — letters, digits, - and _ (starting with a letter), e.g. hologram or rim-light`);
|
|
226303
|
-
const dir =
|
|
226601
|
+
const dir = resolve30(o.cwd ?? process.cwd(), o.dir ?? ".");
|
|
226304
226602
|
const matPath = join52(dir, `${name}.mat`), assetPath = join52(dir, `${name}.material.ts`);
|
|
226305
226603
|
if (!o.force) {
|
|
226306
226604
|
for (const p of [matPath, assetPath])
|
|
226307
|
-
if (
|
|
226605
|
+
if (existsSync48(p))
|
|
226308
226606
|
throw new CliError(`${relative11(o.cwd ?? process.cwd(), p) || basename26(p)} exists — pick another name or pass --force to overwrite`);
|
|
226309
226607
|
}
|
|
226310
226608
|
let mat;
|
|
@@ -226317,7 +226615,7 @@ var writeNewShader = (o) => {
|
|
|
226317
226615
|
const known = engineMaterialNames();
|
|
226318
226616
|
if (!known.includes(ref))
|
|
226319
226617
|
throw new CliError(`no engine material "${ref}". Known: ${known.join(", ")}`);
|
|
226320
|
-
mat = refMat(name, ref,
|
|
226618
|
+
mat = refMat(name, ref, readFileSync39(join52(srcDir, `${ref}.mat`), "utf8"));
|
|
226321
226619
|
} else
|
|
226322
226620
|
mat = starterMat(name);
|
|
226323
226621
|
mkdirSync31(dir, { recursive: true });
|
|
@@ -226349,7 +226647,7 @@ var status2 = async () => {
|
|
|
226349
226647
|
if (matcId === null)
|
|
226350
226648
|
state = c.dim("unknown (matc not downloaded)");
|
|
226351
226649
|
else {
|
|
226352
|
-
const text =
|
|
226650
|
+
const text = readFileSync40(shader.absPath, "utf8");
|
|
226353
226651
|
if (shaderIsCompiled(root, text, matcId))
|
|
226354
226652
|
state = "compiled";
|
|
226355
226653
|
else {
|
|
@@ -226358,7 +226656,7 @@ var status2 = async () => {
|
|
|
226358
226656
|
}
|
|
226359
226657
|
}
|
|
226360
226658
|
log(` ${shader.path.padEnd(width)} ${state}`);
|
|
226361
|
-
const schema = parseShaderSchema(
|
|
226659
|
+
const schema = parseShaderSchema(readFileSync40(shader.absPath, "utf8"));
|
|
226362
226660
|
for (const p of schema.params) {
|
|
226363
226661
|
const range = p.min !== undefined ? ` [${p.min}..${p.max}]` : "";
|
|
226364
226662
|
const def = p.default !== null && p.default !== undefined ? ` = ${JSON.stringify(p.default)}` : "";
|
|
@@ -226399,7 +226697,7 @@ var compileShaders = async (platforms) => {
|
|
|
226399
226697
|
}
|
|
226400
226698
|
const matcExe = await resolveMatcExe();
|
|
226401
226699
|
const matcId = matcIdentity(matcExe);
|
|
226402
|
-
const wasCompiled = new Map(shaders.map((s) => [s.path, shaderIsCompiled(root,
|
|
226700
|
+
const wasCompiled = new Map(shaders.map((s) => [s.path, shaderIsCompiled(root, readFileSync40(s.absPath, "utf8"), matcId)]));
|
|
226403
226701
|
const errors = [];
|
|
226404
226702
|
for (const shader of shaders) {
|
|
226405
226703
|
try {
|
|
@@ -226501,14 +226799,14 @@ var status_default = defineCommand({
|
|
|
226501
226799
|
});
|
|
226502
226800
|
|
|
226503
226801
|
// src/commands/test.ts
|
|
226504
|
-
import { existsSync as
|
|
226505
|
-
import { dirname as dirname25, resolve as
|
|
226802
|
+
import { existsSync as existsSync50, mkdirSync as mkdirSync33, readFileSync as readFileSync41, readdirSync as readdirSync21 } from "node:fs";
|
|
226803
|
+
import { dirname as dirname25, resolve as resolve31 } from "node:path";
|
|
226506
226804
|
init_manifest();
|
|
226507
226805
|
|
|
226508
226806
|
// src/hosts/liteRunner.ts
|
|
226509
226807
|
init_cli();
|
|
226510
226808
|
import { spawn as spawn7 } from "node:child_process";
|
|
226511
|
-
import { existsSync as
|
|
226809
|
+
import { existsSync as existsSync49, mkdirSync as mkdirSync32, rmSync as rmSync15, writeFileSync as writeFileSync35 } from "node:fs";
|
|
226512
226810
|
import { createServer as createServer3 } from "node:net";
|
|
226513
226811
|
import { dirname as dirname24, join as join53 } from "node:path";
|
|
226514
226812
|
import { createInterface as createInterface2 } from "node:readline";
|
|
@@ -226521,7 +226819,7 @@ var NO_NODE = "--lite needs Node on the PATH: the browser is driven from a Node
|
|
|
226521
226819
|
var NO_PLAYWRIGHT = "--lite runs the flows in a browser and needs Playwright: `npm install -D playwright` in the project, then `npx playwright install chromium` once.";
|
|
226522
226820
|
var startDriver = async () => {
|
|
226523
226821
|
const script = join53(distRoot, "src", "hosts", "liteBrowser.mjs");
|
|
226524
|
-
if (!
|
|
226822
|
+
if (!existsSync49(script))
|
|
226525
226823
|
throw new CliError("--lite is not available in this build of the CLI (its browser driver ships with the npm package).");
|
|
226526
226824
|
const child = spawn7("node", [script], { stdio: ["pipe", "pipe", "inherit"] });
|
|
226527
226825
|
const waiting = new Map;
|
|
@@ -227018,7 +227316,7 @@ var slug = (s) => s.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").sli
|
|
|
227018
227316
|
var readFlowFile = (abs, displayName) => {
|
|
227019
227317
|
let raw;
|
|
227020
227318
|
try {
|
|
227021
|
-
raw = JSON.parse(
|
|
227319
|
+
raw = JSON.parse(readFileSync41(abs, "utf8"));
|
|
227022
227320
|
} catch (e) {
|
|
227023
227321
|
throw new CliError(`Can't read "${displayName}": ${e instanceof Error ? e.message : String(e)}`);
|
|
227024
227322
|
}
|
|
@@ -227112,39 +227410,39 @@ var runTests = async (args, flags) => {
|
|
|
227112
227410
|
if (nativeFixedDt < 0 || nativeFixedDt > 1000)
|
|
227113
227411
|
throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${nativeFixedDt}".`);
|
|
227114
227412
|
const projectViewport = desktopDefaultViewport(root);
|
|
227115
|
-
const testsDir =
|
|
227116
|
-
const designMetaPath =
|
|
227413
|
+
const testsDir = resolve31(root, flags.dir);
|
|
227414
|
+
const designMetaPath = resolve31(root, flags["design-dir"], "meta.json");
|
|
227117
227415
|
const asJson = flags.json;
|
|
227118
227416
|
const out = asJson ? logErr : log;
|
|
227119
227417
|
const cases = [];
|
|
227120
227418
|
if (args.length > 0) {
|
|
227121
227419
|
for (const given of args) {
|
|
227122
|
-
const candidates = [
|
|
227123
|
-
const abs = candidates.find((p) =>
|
|
227420
|
+
const candidates = [resolve31(given), resolve31(testsDir, given), resolve31(testsDir, `${given}.flow.json`)];
|
|
227421
|
+
const abs = candidates.find((p) => existsSync50(p));
|
|
227124
227422
|
if (!abs)
|
|
227125
227423
|
throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
|
|
227126
227424
|
cases.push(readFlowFile(abs, given));
|
|
227127
227425
|
}
|
|
227128
|
-
} else if (
|
|
227426
|
+
} else if (existsSync50(testsDir)) {
|
|
227129
227427
|
for (const f of readdirSync21(testsDir).filter((f) => f.endsWith(".flow.json")).sort()) {
|
|
227130
|
-
cases.push(readFlowFile(
|
|
227428
|
+
cases.push(readFlowFile(resolve31(testsDir, f), f));
|
|
227131
227429
|
}
|
|
227132
227430
|
}
|
|
227133
|
-
const defaultPack =
|
|
227134
|
-
const flowFixtures = flags.fixtures !== undefined ? resolveFixtures(flags.fixtures, ".") :
|
|
227431
|
+
const defaultPack = resolve31(testsDir, "fixtures.json");
|
|
227432
|
+
const flowFixtures = flags.fixtures !== undefined ? resolveFixtures(flags.fixtures, ".") : existsSync50(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
|
|
227135
227433
|
const flowsOnly = flags.flows;
|
|
227136
227434
|
const wantFlows = !flags["no-flows"] && args.length === 0;
|
|
227137
227435
|
if (flowsOnly)
|
|
227138
227436
|
cases.length = 0;
|
|
227139
227437
|
let skipped = [];
|
|
227140
227438
|
let entryScreen = null;
|
|
227141
|
-
if ((desktop || lite) && wantFlows &&
|
|
227439
|
+
if ((desktop || lite) && wantFlows && existsSync50(designMetaPath)) {
|
|
227142
227440
|
note("Design-derived flows skipped with --desktop / --lite (they read expect.screen and fill inputs; run `lecodes test` headless for them).");
|
|
227143
227441
|
}
|
|
227144
|
-
if (!desktop && !lite && (wantFlows || flowsOnly) &&
|
|
227442
|
+
if (!desktop && !lite && (wantFlows || flowsOnly) && existsSync50(designMetaPath)) {
|
|
227145
227443
|
let meta;
|
|
227146
227444
|
try {
|
|
227147
|
-
meta = JSON.parse(
|
|
227445
|
+
meta = JSON.parse(readFileSync41(designMetaPath, "utf8"));
|
|
227148
227446
|
} catch (e) {
|
|
227149
227447
|
throw new CliError(`Can't read ${designMetaPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
227150
227448
|
}
|
|
@@ -227177,7 +227475,7 @@ var runTests = async (args, flags) => {
|
|
|
227177
227475
|
}, timeoutMs);
|
|
227178
227476
|
killer.unref?.();
|
|
227179
227477
|
const onConsole = flags.logs ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
227180
|
-
const artifactsRoot =
|
|
227478
|
+
const artifactsRoot = resolve31(root, flags.artifacts ?? resolve31(testsDir, ".artifacts"));
|
|
227181
227479
|
const results = [];
|
|
227182
227480
|
const reportCase = (result) => {
|
|
227183
227481
|
const skippedCount = result.steps.reduce((n, s) => n + (s.skipped?.length ?? 0), 0);
|
|
@@ -227197,13 +227495,13 @@ var runTests = async (args, flags) => {
|
|
|
227197
227495
|
}
|
|
227198
227496
|
};
|
|
227199
227497
|
const runNativeCase = async (kase) => {
|
|
227200
|
-
const caseDir =
|
|
227498
|
+
const caseDir = resolve31(artifactsRoot, slug(kase.name));
|
|
227201
227499
|
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames, sim });
|
|
227202
227500
|
for (const st of plan.steps)
|
|
227203
227501
|
if (st.screenshot)
|
|
227204
227502
|
mkdirSync33(dirname25(st.screenshot), { recursive: true });
|
|
227205
227503
|
mkdirSync33(caseDir, { recursive: true });
|
|
227206
|
-
const finalPng =
|
|
227504
|
+
const finalPng = resolve31(caseDir, "final.png");
|
|
227207
227505
|
const { output } = await runDesktopRender(native.js, native.resources, {
|
|
227208
227506
|
renderer: desktopRenderer,
|
|
227209
227507
|
host: sim ? "server" : "desktop",
|
|
@@ -227217,7 +227515,7 @@ var runTests = async (args, flags) => {
|
|
|
227217
227515
|
script: plan.text,
|
|
227218
227516
|
fixedDtMs: nativeFixedDt
|
|
227219
227517
|
});
|
|
227220
|
-
const r = evaluateDesktopRun(plan, output, (path) =>
|
|
227518
|
+
const r = evaluateDesktopRun(plan, output, (path) => existsSync50(path) ? readFileSync41(path, "utf8") : null);
|
|
227221
227519
|
if (!r.loaded)
|
|
227222
227520
|
throw new CliError("This desktop host doesn't support scripted input (CREATOR_SCRIPT). Update it: lecodes desktop update (or point LECODES_DESKTOP_EXE at a newer build).");
|
|
227223
227521
|
return {
|
|
@@ -227248,7 +227546,7 @@ var runTests = async (args, flags) => {
|
|
|
227248
227546
|
}
|
|
227249
227547
|
}
|
|
227250
227548
|
const runLiteCase = async (kase) => {
|
|
227251
|
-
const caseDir =
|
|
227549
|
+
const caseDir = resolve31(artifactsRoot, slug(kase.name));
|
|
227252
227550
|
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: 0 });
|
|
227253
227551
|
mkdirSync33(caseDir, { recursive: true });
|
|
227254
227552
|
const output = await liteSession.run(plan, {
|
|
@@ -227256,7 +227554,7 @@ var runTests = async (args, flags) => {
|
|
|
227256
227554
|
height: flags.height || kase.height || projectViewport?.height || 844,
|
|
227257
227555
|
onConsole
|
|
227258
227556
|
});
|
|
227259
|
-
const r = evaluateDesktopRun(plan, output, (path) =>
|
|
227557
|
+
const r = evaluateDesktopRun(plan, output, (path) => existsSync50(path) ? readFileSync41(path, "utf8") : null);
|
|
227260
227558
|
return {
|
|
227261
227559
|
name: kase.name,
|
|
227262
227560
|
kind: kase.kind,
|
|
@@ -227307,14 +227605,14 @@ var runTests = async (args, flags) => {
|
|
|
227307
227605
|
onConsole
|
|
227308
227606
|
});
|
|
227309
227607
|
try {
|
|
227310
|
-
const io = fileScenarioIO(
|
|
227608
|
+
const io = fileScenarioIO(resolve31(artifactsRoot, slug(kase.name)));
|
|
227311
227609
|
const outcome = await renderer.runScenario(session, steps, io);
|
|
227312
227610
|
result = {
|
|
227313
227611
|
name: kase.name,
|
|
227314
227612
|
kind: kase.kind,
|
|
227315
227613
|
ok: outcome.ok,
|
|
227316
227614
|
steps: outcome.steps.map((s) => ({ desc: s.desc, ok: s.ok, error: s.error })),
|
|
227317
|
-
artifacts: outcome.failureArtifacts.map((p) =>
|
|
227615
|
+
artifacts: outcome.failureArtifacts.map((p) => resolve31(artifactsRoot, slug(kase.name), p))
|
|
227318
227616
|
};
|
|
227319
227617
|
} finally {
|
|
227320
227618
|
session.dispose();
|
|
@@ -227365,9 +227663,9 @@ var runTests = async (args, flags) => {
|
|
|
227365
227663
|
init_peerInstall();
|
|
227366
227664
|
import { createRequire as createRequire8 } from "node:module";
|
|
227367
227665
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
227368
|
-
import { existsSync as
|
|
227666
|
+
import { existsSync as existsSync51, mkdtempSync as mkdtempSync7, readdirSync as readdirSync22, rmSync as rmSync16, statSync as statSync19 } from "node:fs";
|
|
227369
227667
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
227370
|
-
import { extname, join as join54, relative as relative13, resolve as
|
|
227668
|
+
import { extname, join as join54, relative as relative13, resolve as resolve32 } from "node:path";
|
|
227371
227669
|
init_cli();
|
|
227372
227670
|
init_paths();
|
|
227373
227671
|
var DEFAULT_SIZE = 256;
|
|
@@ -227385,7 +227683,7 @@ var findKtx = () => {
|
|
|
227385
227683
|
"/opt/homebrew/bin/ktx"
|
|
227386
227684
|
].filter((p) => !!p);
|
|
227387
227685
|
for (const path of candidates) {
|
|
227388
|
-
if (
|
|
227686
|
+
if (existsSync51(path))
|
|
227389
227687
|
return path;
|
|
227390
227688
|
}
|
|
227391
227689
|
try {
|
|
@@ -227417,7 +227715,7 @@ var ktxToPng = (ktx, file, out) => {
|
|
|
227417
227715
|
run(["extract", "--transcode", "rgba8", "--level", "0", file, out]);
|
|
227418
227716
|
} catch (e) {
|
|
227419
227717
|
run(["extract", "--level", "0", file, out]);
|
|
227420
|
-
if (!
|
|
227718
|
+
if (!existsSync51(out))
|
|
227421
227719
|
throw e;
|
|
227422
227720
|
}
|
|
227423
227721
|
};
|
|
@@ -227452,8 +227750,8 @@ var thumbs = async (targets, size, force) => {
|
|
|
227452
227750
|
const rendered = [];
|
|
227453
227751
|
if (targets.length > 0) {
|
|
227454
227752
|
for (const arg of targets) {
|
|
227455
|
-
const abs =
|
|
227456
|
-
if (!
|
|
227753
|
+
const abs = resolve32(process.cwd(), arg);
|
|
227754
|
+
if (!existsSync51(abs))
|
|
227457
227755
|
throw new CliError(`No such file or folder: ${arg}`);
|
|
227458
227756
|
if (statSync19(abs).isDirectory())
|
|
227459
227757
|
files.push(...collect(abs));
|
|
@@ -227532,9 +227830,9 @@ init_peerInstall();
|
|
|
227532
227830
|
init_archives();
|
|
227533
227831
|
init_packageManager();
|
|
227534
227832
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
227535
|
-
import { existsSync as
|
|
227833
|
+
import { existsSync as existsSync52, readdirSync as readdirSync23 } from "node:fs";
|
|
227536
227834
|
import { homedir as homedir14 } from "node:os";
|
|
227537
|
-
import { dirname as dirname26, join as join55, resolve as
|
|
227835
|
+
import { dirname as dirname26, join as join55, resolve as resolve34 } from "node:path";
|
|
227538
227836
|
|
|
227539
227837
|
// src/commands/update/types.ts
|
|
227540
227838
|
init_api();
|
|
@@ -227542,10 +227840,10 @@ init_config();
|
|
|
227542
227840
|
init_manifest();
|
|
227543
227841
|
init_types4();
|
|
227544
227842
|
init_cli();
|
|
227545
|
-
import { resolve as
|
|
227843
|
+
import { resolve as resolve33 } from "node:path";
|
|
227546
227844
|
var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
|
|
227547
227845
|
var refreshTypes = async (opts) => {
|
|
227548
|
-
const root = opts.dir ?
|
|
227846
|
+
const root = opts.dir ? resolve33(opts.dir) : (() => {
|
|
227549
227847
|
try {
|
|
227550
227848
|
return findProjectRoot(process.cwd());
|
|
227551
227849
|
} catch {
|
|
@@ -227603,10 +227901,10 @@ var refreshTypes = async (opts) => {
|
|
|
227603
227901
|
|
|
227604
227902
|
// src/commands/update/index.ts
|
|
227605
227903
|
var findProjectDir = (start) => {
|
|
227606
|
-
const home =
|
|
227607
|
-
let dir =
|
|
227904
|
+
const home = resolve34(homedir14());
|
|
227905
|
+
let dir = resolve34(start);
|
|
227608
227906
|
while (true) {
|
|
227609
|
-
if (dir !== home && (
|
|
227907
|
+
if (dir !== home && (existsSync52(join55(dir, "app.json")) || existsSync52(join55(dir, ".lecodes", "manifest.json")) || existsSync52(join55(dir, ".lecodes", "types"))))
|
|
227610
227908
|
return dir;
|
|
227611
227909
|
const parent = dirname26(dir);
|
|
227612
227910
|
if (parent === dir)
|
|
@@ -227643,7 +227941,7 @@ var semverNewer2 = (a, b) => {
|
|
|
227643
227941
|
return 0;
|
|
227644
227942
|
};
|
|
227645
227943
|
var FROM_SOURCE = /[\\/]src[\\/]commands[\\/]/.test(import.meta.url);
|
|
227646
|
-
var STANDALONE =
|
|
227944
|
+
var STANDALONE = existsSync52(join55(dirname26(process.execPath), "runtime")) && existsSync52(join55(dirname26(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
|
|
227647
227945
|
var update_default = defineCommand({
|
|
227648
227946
|
name: "update",
|
|
227649
227947
|
summary: "Move everything the project depends on to its newest published state",
|
|
@@ -227703,7 +228001,7 @@ ${c.bold(name)} ${c.dim(what)}`);
|
|
|
227703
228001
|
return ["skipped", "not inside a project"];
|
|
227704
228002
|
if (flags["no-shells"])
|
|
227705
228003
|
return ["skipped", "--no-shells"];
|
|
227706
|
-
const present = ["ios", "android"].filter((d) =>
|
|
228004
|
+
const present = ["ios", "android"].filter((d) => existsSync52(join55(root, d)) && readdirSync23(join55(root, d)).length > 0);
|
|
227707
228005
|
if (present.length === 0)
|
|
227708
228006
|
return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
|
|
227709
228007
|
await runSubcommand(app_default, "update");
|
|
@@ -227781,8 +228079,8 @@ init_cli();
|
|
|
227781
228079
|
|
|
227782
228080
|
// src/web/build.ts
|
|
227783
228081
|
import { createHash as createHash9 } from "node:crypto";
|
|
227784
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as
|
|
227785
|
-
import { dirname as dirname27, extname as extname2, join as join56, resolve as
|
|
228082
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as existsSync53, mkdirSync as mkdirSync34, readFileSync as readFileSync42, readdirSync as readdirSync24, rmSync as rmSync17, statSync as statSync20, writeFileSync as writeFileSync36 } from "node:fs";
|
|
228083
|
+
import { dirname as dirname27, extname as extname2, join as join56, resolve as resolve35 } from "node:path";
|
|
227786
228084
|
init_fonts();
|
|
227787
228085
|
init_assetIcons();
|
|
227788
228086
|
init_projectCompile();
|
|
@@ -227806,8 +228104,8 @@ var walk = (dir, prefix = "") => {
|
|
|
227806
228104
|
};
|
|
227807
228105
|
var buildWeb = async (options) => {
|
|
227808
228106
|
const { root, manifest, name, lite } = options;
|
|
227809
|
-
const out =
|
|
227810
|
-
if (out ===
|
|
228107
|
+
const out = resolve35(root, options.out);
|
|
228108
|
+
if (out === resolve35(root) || resolve35(root).startsWith(out + "\\") || resolve35(root).startsWith(out + "/")) {
|
|
227811
228109
|
throw new CliError(`The output folder is cleaned before a build: "${options.out}" holds the project itself.`);
|
|
227812
228110
|
}
|
|
227813
228111
|
const config = readAppConfigAt(root)?.web ?? {};
|
|
@@ -227817,7 +228115,7 @@ var buildWeb = async (options) => {
|
|
|
227817
228115
|
const assetUrl = (path) => "assets" + encodeURI(path);
|
|
227818
228116
|
const collected = collectLocalEntries(root, manifest, "assets");
|
|
227819
228117
|
const entries = collected.entries.filter((e) => {
|
|
227820
|
-
const abs = e.absPath ?
|
|
228118
|
+
const abs = e.absPath ? resolve35(e.absPath) : null;
|
|
227821
228119
|
return !(abs && (abs === out || abs.startsWith(out + "\\") || abs.startsWith(out + "/")));
|
|
227822
228120
|
});
|
|
227823
228121
|
for (const w of collected.warnings)
|
|
@@ -227859,8 +228157,8 @@ var buildWeb = async (options) => {
|
|
|
227859
228157
|
mkdirSync34(out, { recursive: true });
|
|
227860
228158
|
cpSync6(lite, out, { recursive: true });
|
|
227861
228159
|
const table = join56(out, "features.json");
|
|
227862
|
-
if (
|
|
227863
|
-
const { features, fonts } = JSON.parse(
|
|
228160
|
+
if (existsSync53(table)) {
|
|
228161
|
+
const { features, fonts } = JSON.parse(readFileSync42(table, "utf8"));
|
|
227864
228162
|
if (config.font === "system")
|
|
227865
228163
|
for (const file of fonts ?? [])
|
|
227866
228164
|
rmSync17(join56(out, file), { force: true });
|
|
@@ -227873,9 +228171,9 @@ var buildWeb = async (options) => {
|
|
|
227873
228171
|
rmSync17(table);
|
|
227874
228172
|
}
|
|
227875
228173
|
const pluginsTable = join56(out, "plugins.json");
|
|
227876
|
-
if (
|
|
228174
|
+
if (existsSync53(pluginsTable)) {
|
|
227877
228175
|
const listed = new Set((/^\/\/ plugins: (.+)$/m.exec(header)?.[1] ?? "").split(";").map((id) => id.trim()).filter(Boolean));
|
|
227878
|
-
const { plugins } = JSON.parse(
|
|
228176
|
+
const { plugins } = JSON.parse(readFileSync42(pluginsTable, "utf8"));
|
|
227879
228177
|
const kept = {};
|
|
227880
228178
|
for (const id of Object.keys(plugins)) {
|
|
227881
228179
|
if (listed.has(id))
|
|
@@ -227901,7 +228199,7 @@ var buildWeb = async (options) => {
|
|
|
227901
228199
|
if (!js.includes(entry.fileSrc))
|
|
227902
228200
|
continue;
|
|
227903
228201
|
const to = join56(out, "assets", ...entry.path.split("/").filter(Boolean));
|
|
227904
|
-
if (
|
|
228202
|
+
if (existsSync53(to)) {
|
|
227905
228203
|
warnings.push(`${entry.path}: a file of the page has this name in the built folder — the project's file was not copied`);
|
|
227906
228204
|
continue;
|
|
227907
228205
|
}
|
|
@@ -227909,12 +228207,12 @@ var buildWeb = async (options) => {
|
|
|
227909
228207
|
copyFileSync2(entry.absPath, to);
|
|
227910
228208
|
}
|
|
227911
228209
|
const app = readAppConfigAt(root);
|
|
227912
|
-
let html =
|
|
228210
|
+
let html = readFileSync42(join56(lite, "index.html"), "utf8");
|
|
227913
228211
|
const head = [];
|
|
227914
228212
|
head.push(`<meta name="theme-color" content="${escapeHtml(config.themeColor ?? "#000000")}" />`);
|
|
227915
228213
|
if (app?.icon) {
|
|
227916
|
-
const icon =
|
|
227917
|
-
if (
|
|
228214
|
+
const icon = resolve35(root, app.icon);
|
|
228215
|
+
if (existsSync53(icon)) {
|
|
227918
228216
|
const file = `icon${extname2(icon).toLowerCase() || ".png"}`;
|
|
227919
228217
|
copyFileSync2(icon, join56(out, file));
|
|
227920
228218
|
head.push(`<link rel="icon" href="${file}" />`, `<link rel="apple-touch-icon" href="${file}" />`);
|
|
@@ -227949,12 +228247,12 @@ var buildWeb = async (options) => {
|
|
|
227949
228247
|
};
|
|
227950
228248
|
|
|
227951
228249
|
// src/web/preview.ts
|
|
227952
|
-
import { createReadStream as createReadStream3, existsSync as
|
|
228250
|
+
import { createReadStream as createReadStream3, existsSync as existsSync54, statSync as statSync21 } from "node:fs";
|
|
227953
228251
|
import { createServer as createServer4 } from "node:http";
|
|
227954
|
-
import { join as join57, resolve as
|
|
228252
|
+
import { join as join57, resolve as resolve36 } from "node:path";
|
|
227955
228253
|
var startPreview = async (dir, port) => {
|
|
227956
|
-
const root =
|
|
227957
|
-
const fallback =
|
|
228254
|
+
const root = resolve36(dir);
|
|
228255
|
+
const fallback = existsSync54(join57(root, "404.html")) ? join57(root, "index.html") : null;
|
|
227958
228256
|
const server2 = createServer4((req, res) => {
|
|
227959
228257
|
let path;
|
|
227960
228258
|
try {
|
|
@@ -227971,10 +228269,10 @@ var startPreview = async (dir, port) => {
|
|
|
227971
228269
|
return;
|
|
227972
228270
|
}
|
|
227973
228271
|
let file = join57(root, ...parts);
|
|
227974
|
-
if (
|
|
228272
|
+
if (existsSync54(file) && statSync21(file).isDirectory())
|
|
227975
228273
|
file = join57(file, "index.html");
|
|
227976
228274
|
let status = 200;
|
|
227977
|
-
if (!
|
|
228275
|
+
if (!existsSync54(file) || !statSync21(file).isFile()) {
|
|
227978
228276
|
if (!fallback || /\.[a-z0-9]+$/i.test(parts[parts.length - 1] ?? "")) {
|
|
227979
228277
|
res.writeHead(404, { "content-type": "text/plain" });
|
|
227980
228278
|
res.end("Not found");
|
|
@@ -228164,7 +228462,7 @@ var web_default = defineCommand({
|
|
|
228164
228462
|
|
|
228165
228463
|
// src/commands/index.ts
|
|
228166
228464
|
var commandGroups = (opts) => [
|
|
228167
|
-
{ title: "Project", commands: [init_default, create_default, clone_default, link_default, status_default, diff_default, pull_default, push_default, login_default] },
|
|
228465
|
+
{ title: "Project", commands: [init_default, create_default, clone_default, link_default, status_default, diff_default, pull_default, push_default, publish_default, releases_default, login_default] },
|
|
228168
228466
|
{ title: "Run & test", commands: [
|
|
228169
228467
|
dev_default,
|
|
228170
228468
|
web_default,
|
|
@@ -228201,7 +228499,7 @@ init_peers();
|
|
|
228201
228499
|
var STANDALONE2 = (() => {
|
|
228202
228500
|
const dir = dirname28(process.execPath);
|
|
228203
228501
|
const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
|
|
228204
|
-
return
|
|
228502
|
+
return existsSync56(join59(dir, "runtime")) && existsSync56(join59(dir, chisel));
|
|
228205
228503
|
})();
|
|
228206
228504
|
var INSTALLED = STANDALONE2 && (() => {
|
|
228207
228505
|
const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
|
|
@@ -228217,7 +228515,7 @@ var INSTALLED = STANDALONE2 && (() => {
|
|
|
228217
228515
|
return false;
|
|
228218
228516
|
try {
|
|
228219
228517
|
const cand = join59(d, exe);
|
|
228220
|
-
return
|
|
228518
|
+
return existsSync56(cand) && realpathSync2(cand) === self2;
|
|
228221
228519
|
} catch {
|
|
228222
228520
|
return false;
|
|
228223
228521
|
}
|