lecodes-cli 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +958 -119
- package/package.json +13 -12
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +3 -3
- package/runtime/web/assets/{inspect-C7x78PNs.js → inspect-DdJ50Kun.js} +2 -2
- package/runtime/web/player.js +18 -18
- package/runtime/web-lite/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
- package/runtime/web-lite/assets/creatorTween-PF8B6T8V.wasm +0 -0
- package/runtime/web-lite/index.html +23 -0
- package/runtime/web-lite/lite.js +201 -0
- package/src/commands/design/comments.ts +1 -1
- package/src/commands/design/context.ts +1 -1
- package/src/commands/design/index.ts +1 -1
- package/src/commands/dev.ts +17 -53
- package/src/commands/index.ts +2 -0
- package/src/commands/test.ts +55 -6
- package/src/commands/update/index.ts +6 -4
- package/src/commands/web.ts +171 -0
- package/src/compile/fonts.ts +5 -2
- package/src/dev/devServer.ts +20 -1
- package/src/dev/watchProject.ts +53 -0
- package/src/dev/webRunner.ts +10 -0
- package/src/hosts/archives.ts +32 -0
- package/src/hosts/liteBrowser.mjs +86 -0
- package/src/hosts/liteRunner.ts +386 -0
- package/src/hosts/peerInstall.ts +8 -4
- package/src/web/build.ts +171 -0
- package/src/web/preview.ts +50 -0
package/dist/index.js
CHANGED
|
@@ -52,28 +52,29 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "lecodes-cli",
|
|
55
|
-
version: "2.0.
|
|
55
|
+
version: "2.0.3",
|
|
56
56
|
dependencies: {
|
|
57
57
|
"@letary/chisel": "^0.13.0",
|
|
58
58
|
jimp: "^1.6.1",
|
|
59
59
|
"lecodes-sdk": "workspace:*"
|
|
60
60
|
},
|
|
61
61
|
devDependencies: {
|
|
62
|
-
"lecodes-design": "
|
|
62
|
+
"lecodes-design": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-design/lecodes-design-0.11.0.tgz",
|
|
63
63
|
"lecodes-headless": "workspace:*",
|
|
64
64
|
"qrcode-terminal": "^0.12.0",
|
|
65
|
-
"lecodes-3d-editor": "
|
|
65
|
+
"lecodes-3d-editor": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-3d-editor/lecodes-3d-editor-0.9.0.tgz",
|
|
66
66
|
"lecodes-assets": "workspace:*",
|
|
67
67
|
"lecodes-creator-nav": "workspace:*",
|
|
68
68
|
"lecodes-gl-lite": "workspace:*",
|
|
69
69
|
"lecodes-web-frame": "workspace:*",
|
|
70
70
|
"lecodes-web-player": "workspace:*",
|
|
71
|
-
"lecodes-web-shared": "workspace:*"
|
|
71
|
+
"lecodes-web-shared": "workspace:*",
|
|
72
|
+
"lecodes-web-lite": "workspace:*"
|
|
72
73
|
},
|
|
73
74
|
peerDependencies: {
|
|
74
|
-
"lecodes-design": "^0.
|
|
75
|
-
"lecodes-headless": "^2.0.
|
|
76
|
-
"lecodes-3d-editor": "^0.
|
|
75
|
+
"lecodes-design": "^0.11.0",
|
|
76
|
+
"lecodes-headless": "^2.0.3",
|
|
77
|
+
"lecodes-3d-editor": "^0.9.0",
|
|
77
78
|
"lecodes-assets": "^0.2.1"
|
|
78
79
|
},
|
|
79
80
|
bin: {
|
|
@@ -126,7 +127,7 @@ var init_package = __esm(() => {
|
|
|
126
127
|
});
|
|
127
128
|
|
|
128
129
|
// ../../sdk/src/version.ts
|
|
129
|
-
var SDK_VERSION = "2.0.
|
|
130
|
+
var SDK_VERSION = "2.0.3", sdkMajor = (version) => {
|
|
130
131
|
const m = version?.match(/^(\d+)\.\d+\.\d+/);
|
|
131
132
|
return m ? Number(m[1]) : null;
|
|
132
133
|
}, bundleSdkVersion = (head) => head.slice(0, 2000).match(/^\/\/ sdk: (\d+\.\d+\.\d+\S*)/m)?.[1] ?? null, bundleRunsOn = (bundleVersion, hostVersion) => sdkMajor(bundleVersion) !== null && sdkMajor(bundleVersion) === sdkMajor(hostVersion);
|
|
@@ -1349,6 +1350,7 @@ var init_chisel = __esm(() => {
|
|
|
1349
1350
|
"remove",
|
|
1350
1351
|
"setContent",
|
|
1351
1352
|
"keepAlive",
|
|
1353
|
+
"url",
|
|
1352
1354
|
"attachTo",
|
|
1353
1355
|
"detents",
|
|
1354
1356
|
"dismissible",
|
|
@@ -171787,10 +171789,10 @@ var applyProjectFonts = async (entries, opts) => {
|
|
|
171787
171789
|
if (faces.length === 0)
|
|
171788
171790
|
return;
|
|
171789
171791
|
return buildFontsHeaderValue(faces, (path) => {
|
|
171790
|
-
if (!opts.publicUrl || !/^https?:/i.test(opts.publicUrl))
|
|
171792
|
+
if (!opts.relative && (!opts.publicUrl || !/^https?:/i.test(opts.publicUrl)))
|
|
171791
171793
|
return null;
|
|
171792
171794
|
const fileSrc = byPath().get(path.replace(/^\//, ""))?.fileSrc;
|
|
171793
|
-
return fileSrc ? opts.publicUrl + fileSrc : null;
|
|
171795
|
+
return fileSrc ? (opts.publicUrl ?? "") + fileSrc : null;
|
|
171794
171796
|
}) || undefined;
|
|
171795
171797
|
}, isHttp = (s) => /^https?:/i.test(s), sha12 = (b) => createHash2("sha256").update(b).digest("hex").slice(0, 12), registryHome = (registry) => registry.replace(/\/v\d+$/, ""), readRegistry = async (registry, rel) => {
|
|
171796
171798
|
if (isHttp(registry)) {
|
|
@@ -212716,6 +212718,24 @@ var init_peers = __esm(() => {
|
|
|
212716
212718
|
];
|
|
212717
212719
|
});
|
|
212718
212720
|
|
|
212721
|
+
// src/hosts/archives.ts
|
|
212722
|
+
var ARCHIVED_PEERS, BASE = "https://storage.yandexcloud.net/le-codes/releases/packages", packagesBase = () => (process.env.LECODES_PACKAGES_URL || BASE).replace(/\/$/, ""), archiveUrl = (name, version) => `${packagesBase()}/${name}/${name}-${version}.tgz`, archivedVersion = (name) => {
|
|
212723
|
+
if (!ARCHIVED_PEERS.has(name))
|
|
212724
|
+
return null;
|
|
212725
|
+
if (name === "lecodes-headless")
|
|
212726
|
+
return package_default.version;
|
|
212727
|
+
const range = package_default.peerDependencies?.[name];
|
|
212728
|
+
const version = range?.replace(/^[\^~>=\s]+/, "");
|
|
212729
|
+
return version && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
212730
|
+
}, archiveSpec = (name) => {
|
|
212731
|
+
const version = archivedVersion(name);
|
|
212732
|
+
return version ? archiveUrl(name, version) : null;
|
|
212733
|
+
};
|
|
212734
|
+
var init_archives = __esm(() => {
|
|
212735
|
+
init_package();
|
|
212736
|
+
ARCHIVED_PEERS = new Set(["lecodes-headless", "lecodes-design", "lecodes-3d-editor"]);
|
|
212737
|
+
});
|
|
212738
|
+
|
|
212719
212739
|
// src/hosts/peerInstall.ts
|
|
212720
212740
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
212721
212741
|
import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync19, writeFileSync as writeFileSync18 } from "node:fs";
|
|
@@ -212746,8 +212766,9 @@ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson2 = (path2) => {
|
|
|
212746
212766
|
return null;
|
|
212747
212767
|
}
|
|
212748
212768
|
}, peerSpec = (name) => {
|
|
212749
|
-
|
|
212750
|
-
|
|
212769
|
+
const archive = archiveSpec(name);
|
|
212770
|
+
if (archive)
|
|
212771
|
+
return archive;
|
|
212751
212772
|
if (name === "@napi-rs/canvas") {
|
|
212752
212773
|
const renderer = readJson2(join24(peerDirInPrefix("lecodes-headless"), "package.json"));
|
|
212753
212774
|
const range2 = renderer?.peerDependencies?.["@napi-rs/canvas"] ?? renderer?.optionalDependencies?.["@napi-rs/canvas"] ?? CANVAS_RANGE_FALLBACK;
|
|
@@ -212840,6 +212861,7 @@ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson2 = (path2) => {
|
|
|
212840
212861
|
var init_peerInstall = __esm(() => {
|
|
212841
212862
|
init_package();
|
|
212842
212863
|
init_peers();
|
|
212864
|
+
init_archives();
|
|
212843
212865
|
init_cli();
|
|
212844
212866
|
});
|
|
212845
212867
|
|
|
@@ -213140,7 +213162,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, dirFlag, getContext = (dir) =>
|
|
|
213140
213162
|
const mod = await loadPeer("lecodes-design", "server", { for: "lecodes design" });
|
|
213141
213163
|
const missing = needs.filter((name) => typeof mod[name] !== "function");
|
|
213142
213164
|
if (missing.length) {
|
|
213143
|
-
throw new CliError(`This needs a newer lecodes-design — the installed one has no \`${missing[0]}\`. Run:
|
|
213165
|
+
throw new CliError(`This needs a newer lecodes-design — the installed one has no \`${missing[0]}\`. Run: lecodes update`);
|
|
213144
213166
|
}
|
|
213145
213167
|
return mod;
|
|
213146
213168
|
}, requirePlatform = (ctx, verb) => {
|
|
@@ -213256,7 +213278,7 @@ var aiFlag, cliAuthor = (ai) => ai || process.env.CLAUDECODE ? { author: "Claude
|
|
|
213256
213278
|
if (!platform)
|
|
213257
213279
|
return null;
|
|
213258
213280
|
if (typeof designServer.mirrorThreads !== "function") {
|
|
213259
|
-
note("Comment sync needs a newer lecodes-design (
|
|
213281
|
+
note("Comment sync needs a newer lecodes-design (run: lecodes update) — serving without it.");
|
|
213260
213282
|
return null;
|
|
213261
213283
|
}
|
|
213262
213284
|
let failures = 0;
|
|
@@ -213570,7 +213592,7 @@ var init_snapshot = __esm(() => {
|
|
|
213570
213592
|
});
|
|
213571
213593
|
});
|
|
213572
213594
|
|
|
213573
|
-
// ../../node_modules/.bun/lecodes-design@0.
|
|
213595
|
+
// ../../node_modules/.bun/lecodes-design@https+++storage.yandexcloud.net+le-codes+releases+packages+lecodes-design+lecodes-design-0.11.0.tgz+a0cdf6553a4c6042/node_modules/lecodes-design/src/core/concept.ts
|
|
213574
213596
|
var SPEC_TEMPLATE = `# Concept
|
|
213575
213597
|
|
|
213576
213598
|
The narrative half of this design. \`meta.json\` + the canvas hold the *visual/flow* half (screens,
|
|
@@ -213606,7 +213628,7 @@ Behaviour that isn't visible in a single screen — validation, permissions, not
|
|
|
213606
213628
|
cases — and the decisions you've made and why, so they aren't re-litigated later.
|
|
213607
213629
|
`;
|
|
213608
213630
|
|
|
213609
|
-
// ../../node_modules/.bun/lecodes-design@0.
|
|
213631
|
+
// ../../node_modules/.bun/lecodes-design@https+++storage.yandexcloud.net+le-codes+releases+packages+lecodes-design+lecodes-design-0.11.0.tgz+a0cdf6553a4c6042/node_modules/lecodes-design/src/templates.ts
|
|
213610
213632
|
var SPEC_MD, META_JSON = `{
|
|
213611
213633
|
"screens": {
|
|
213612
213634
|
"home": { "pos": [0, 0], "desc": "Example screen — replace me", "role": "list" }
|
|
@@ -213703,9 +213725,9 @@ export default UIScreen([
|
|
|
213703
213725
|
mb: spacing.lg,
|
|
213704
213726
|
}),
|
|
213705
213727
|
]).style({ bgColor: colors.bg, p: spacing.md, pt: "safe-top" })
|
|
213706
|
-
`, CLAUDE_MD = `#
|
|
213728
|
+
`, CLAUDE_MD = `# le.codes Design — screen prototyping canvas
|
|
213707
213729
|
|
|
213708
|
-
This folder is a **design canvas** for a
|
|
213730
|
+
This folder is a **design canvas** for a le.codes app: each file in \`screens/\` is one live,
|
|
213709
213731
|
executable screen shown on a spatial canvas in the browser (\`lecodes design\` runs the dev server).
|
|
213710
213732
|
You (Claude) write the screens; the developer views them on the canvas and gives feedback.
|
|
213711
213733
|
This is an intent-capture stage — screens run on inline mock data, before any backend exists.
|
|
@@ -213811,7 +213833,7 @@ keep in the chat.
|
|
|
213811
213833
|
(entities + fields ARE the future schema — make them meaningful, not "Lorem ipsum").
|
|
213812
213834
|
- **Reuse via shared/.** Design tokens and reusable components live in \`shared/\`. Use them in
|
|
213813
213835
|
every screen. Promoting a repeated pattern into \`shared/\` is an explicit, deliberate step.
|
|
213814
|
-
- The
|
|
213836
|
+
- The le.codes UI API (\`UIScreen\`, \`UIColumn\`, \`UIRow\`, \`UIBox\`, \`UIText\`, \`UIImage\`,
|
|
213815
213837
|
\`UIButton\`, \`UIInput\`, \`UISpacer\`, \`UIScrollable\`, …) is **global** — no imports needed.
|
|
213816
213838
|
Colors must be hex strings. This folder ships a \`tsconfig.json\` + bundled SDK types
|
|
213817
213839
|
(\`.lecodes/types/\`), so the editor resolves those globals — and \`UINode\` / \`UINodeChild\` for
|
|
@@ -214033,7 +214055,7 @@ declare function assetIcon(id: string, opts?: { color?: string }): { readonly sv
|
|
|
214033
214055
|
declare function font(id: string, opts?: { weights?: number[], italic?: boolean }): string
|
|
214034
214056
|
`, README_HINT = `# design/
|
|
214035
214057
|
|
|
214036
|
-
|
|
214058
|
+
le.codes Design canvas — run \`lecodes design\` here and open the printed URL.
|
|
214037
214059
|
See CLAUDE.md for the conventions (it's written for AI assistants, but it's the spec).
|
|
214038
214060
|
`;
|
|
214039
214061
|
var init_templates = __esm(() => {
|
|
@@ -214114,7 +214136,7 @@ var firstLine = (body, max = 64) => {
|
|
|
214114
214136
|
const sync3 = flags["no-sync"] ? null : startCommentSync(ctx, designServer);
|
|
214115
214137
|
log(`${c.bold("LeCodes Design")} — ${ctx.name}`);
|
|
214116
214138
|
log(`Canvas: ${c.bold(server2.url)}`);
|
|
214117
|
-
log(`MCP: ${c.bold(server2.url + "/mcp")}${renderer ? "" : c.dim(" (render_screen off —
|
|
214139
|
+
log(`MCP: ${c.bold(server2.url + "/mcp")}${renderer ? "" : c.dim(" (render_screen off — lecodes-headless is not installed: `lecodes render` installs it)")}`);
|
|
214118
214140
|
log(`Watching: ${ctx.dirName}/ (screens hot-reload on save; Ctrl+C to stop)`);
|
|
214119
214141
|
if (sync3)
|
|
214120
214142
|
log(`Comments: ${c.dim("in sync with the platform — replies and resolves go back automatically")}`);
|
|
@@ -215391,8 +215413,8 @@ var require_main = __commonJS((exports, module) => {
|
|
|
215391
215413
|
// src/index.ts
|
|
215392
215414
|
init_package();
|
|
215393
215415
|
init_cli();
|
|
215394
|
-
import { existsSync as
|
|
215395
|
-
import { delimiter as delimiter3, dirname as
|
|
215416
|
+
import { existsSync as existsSync53, realpathSync as realpathSync2 } from "node:fs";
|
|
215417
|
+
import { delimiter as delimiter3, dirname as dirname27, join as join53 } from "node:path";
|
|
215396
215418
|
|
|
215397
215419
|
// src/commands/app/index.ts
|
|
215398
215420
|
init_manifest();
|
|
@@ -222047,8 +222069,7 @@ var desktop_default = defineCommand({
|
|
|
222047
222069
|
|
|
222048
222070
|
// src/commands/dev.ts
|
|
222049
222071
|
init_manifest();
|
|
222050
|
-
|
|
222051
|
-
import { existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync26, watch, writeFileSync as writeFileSync24 } from "node:fs";
|
|
222072
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync26, writeFileSync as writeFileSync24 } from "node:fs";
|
|
222052
222073
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
222053
222074
|
import { networkInterfaces } from "node:os";
|
|
222054
222075
|
import { basename as basename13, join as join35 } from "node:path";
|
|
@@ -222457,6 +222478,7 @@ var startDevServer = async (options) => {
|
|
|
222457
222478
|
` + renderPrelude({ origin: reqOrigin, base, transport, v: v2 });
|
|
222458
222479
|
};
|
|
222459
222480
|
const web = options.web ?? null;
|
|
222481
|
+
const lite = options.lite ?? null;
|
|
222460
222482
|
const sendBuilt = (res, dir, rel) => {
|
|
222461
222483
|
if (rel.split("/").some((seg) => seg === ".." || seg === "" || seg === "."))
|
|
222462
222484
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
@@ -222518,6 +222540,18 @@ var startDevServer = async (options) => {
|
|
|
222518
222540
|
return redirect(`${base}/web/${query()}`);
|
|
222519
222541
|
return sendBuilt(res, web.page, rel === "/" ? "index.html" : rel.slice(1));
|
|
222520
222542
|
}
|
|
222543
|
+
if (route === "/lite" || route.startsWith("/lite/")) {
|
|
222544
|
+
if (!lite)
|
|
222545
|
+
return sendText(res, 404, "text/plain", "No Web Lite page with this CLI (lecodes-web-lite's dist).");
|
|
222546
|
+
const rel = route.slice("/lite".length);
|
|
222547
|
+
if (rel === "" || rel === "/" && !url.searchParams.has("dev")) {
|
|
222548
|
+
url.searchParams.set("dev", "..");
|
|
222549
|
+
res.writeHead(302, { location: `${base}/lite/?${url.searchParams.toString()}${url.hash}` });
|
|
222550
|
+
res.end();
|
|
222551
|
+
return;
|
|
222552
|
+
}
|
|
222553
|
+
return sendBuilt(res, lite, rel === "/" ? "index.html" : rel.slice(1));
|
|
222554
|
+
}
|
|
222521
222555
|
if (route === "/version") {
|
|
222522
222556
|
return sendJson(res, 200, { v });
|
|
222523
222557
|
}
|
|
@@ -222601,6 +222635,7 @@ var startDevServer = async (options) => {
|
|
|
222601
222635
|
return {
|
|
222602
222636
|
url: `${origin}${base}/dev.js`,
|
|
222603
222637
|
webUrl: web ? `${origin}${base}/web/` : null,
|
|
222638
|
+
liteUrl: lite ? `${origin}${base}/lite/` : null,
|
|
222604
222639
|
base,
|
|
222605
222640
|
port: boundPort,
|
|
222606
222641
|
v: () => v,
|
|
@@ -222636,6 +222671,15 @@ var PAGE = {
|
|
|
222636
222671
|
build: ["-c", "vite.page.config.ts"],
|
|
222637
222672
|
sources: ["hosts/web-frame/src", "hosts/web-frame/page", "hosts/web-player/src/protocol.ts"]
|
|
222638
222673
|
};
|
|
222674
|
+
var LITE = {
|
|
222675
|
+
pkg: "lecodes-web-lite",
|
|
222676
|
+
dist: "dist",
|
|
222677
|
+
entry: "lite.js",
|
|
222678
|
+
vendored: "web-lite",
|
|
222679
|
+
build: [],
|
|
222680
|
+
sources: ["hosts/web-lite/src", "hosts/web-lite/index.html", "renderers/web-lite/src", "renderers/web-shared/src", "hosts/web-player/src/dev.ts"]
|
|
222681
|
+
};
|
|
222682
|
+
var resolveLiteRunner = (log2 = () => {}) => resolve16(LITE, log2);
|
|
222639
222683
|
var resolveWebRunner = (log2 = () => {}) => {
|
|
222640
222684
|
const player = resolve16(PLAYER, log2);
|
|
222641
222685
|
return player ? { player, page: resolve16(PAGE, log2) } : null;
|
|
@@ -222689,6 +222733,59 @@ var newest = (paths) => {
|
|
|
222689
222733
|
return latest;
|
|
222690
222734
|
};
|
|
222691
222735
|
|
|
222736
|
+
// src/dev/watchProject.ts
|
|
222737
|
+
init_ignore();
|
|
222738
|
+
init_paths();
|
|
222739
|
+
import { watch } from "node:fs";
|
|
222740
|
+
var DEBOUNCE_MS = 120;
|
|
222741
|
+
var SKIP_DIRS2 = new Set([".lecodes", ".git", "node_modules", "dist"]);
|
|
222742
|
+
var SKIP_FILES2 = new Set(["tsconfig.json", "jsconfig.json", ".gitignore", ".lecodesignore", ".DS_Store"]);
|
|
222743
|
+
var watchProject = (root, onChange, log2) => {
|
|
222744
|
+
const ignore = loadIgnore(root);
|
|
222745
|
+
const counts = (file) => {
|
|
222746
|
+
if (file === null)
|
|
222747
|
+
return true;
|
|
222748
|
+
const parts = file.replace(/\\/g, "/").split("/");
|
|
222749
|
+
if (parts.some((p2) => SKIP_DIRS2.has(p2)))
|
|
222750
|
+
return false;
|
|
222751
|
+
if (SKIP_FILES2.has(parts[parts.length - 1]))
|
|
222752
|
+
return false;
|
|
222753
|
+
try {
|
|
222754
|
+
if (ignore(toKey(file), false))
|
|
222755
|
+
return false;
|
|
222756
|
+
} catch {
|
|
222757
|
+
return false;
|
|
222758
|
+
}
|
|
222759
|
+
return true;
|
|
222760
|
+
};
|
|
222761
|
+
let timer = null;
|
|
222762
|
+
let watcher = null;
|
|
222763
|
+
try {
|
|
222764
|
+
watcher = watch(root, { recursive: true }, (_type, file) => {
|
|
222765
|
+
const name = file === null ? null : String(file);
|
|
222766
|
+
log2.debug?.(`[watch] event: ${name ?? "(null)"} → ${counts(name) ? "recompile" : "skip"}`);
|
|
222767
|
+
if (!counts(name))
|
|
222768
|
+
return;
|
|
222769
|
+
if (timer)
|
|
222770
|
+
clearTimeout(timer);
|
|
222771
|
+
timer = setTimeout(() => {
|
|
222772
|
+
timer = null;
|
|
222773
|
+
onChange();
|
|
222774
|
+
}, DEBOUNCE_MS);
|
|
222775
|
+
});
|
|
222776
|
+
} catch {
|
|
222777
|
+
log2.warn("fs.watch(recursive) unavailable on this platform — hot reload disabled (restart to pick up changes).");
|
|
222778
|
+
}
|
|
222779
|
+
watcher?.on("error", (e2) => log2.warn(`Watcher error — hot reload may have stopped (restart the command): ${e2.message}`));
|
|
222780
|
+
return {
|
|
222781
|
+
close() {
|
|
222782
|
+
if (timer)
|
|
222783
|
+
clearTimeout(timer);
|
|
222784
|
+
watcher?.close();
|
|
222785
|
+
}
|
|
222786
|
+
};
|
|
222787
|
+
};
|
|
222788
|
+
|
|
222692
222789
|
// src/commands/dev.ts
|
|
222693
222790
|
init_browserAuth();
|
|
222694
222791
|
|
|
@@ -222982,11 +223079,7 @@ var launchOnIos = (opts) => {
|
|
|
222982
223079
|
// src/commands/dev.ts
|
|
222983
223080
|
init_cli();
|
|
222984
223081
|
init_shared();
|
|
222985
|
-
init_paths();
|
|
222986
223082
|
var DEFAULT_PORT2 = 4499;
|
|
222987
|
-
var DEBOUNCE_MS = 120;
|
|
222988
|
-
var WATCH_SKIP_DIRS = new Set([".lecodes", ".git", "node_modules"]);
|
|
222989
|
-
var WATCH_SKIP_FILES = new Set(["tsconfig.json", "jsconfig.json", ".gitignore", ".lecodesignore", ".DS_Store"]);
|
|
222990
223083
|
var VIRTUAL_IFACE = /wsl|docker|hyper-v|vethernet|virtualbox|vmware|tap|tun|zerotier|tailscale|loopback/i;
|
|
222991
223084
|
var lanCandidates = () => {
|
|
222992
223085
|
const nets = networkInterfaces();
|
|
@@ -223106,54 +223199,18 @@ var dev_default = defineCommand({
|
|
|
223106
223199
|
warnErr(`Compile failed — fix it and save; devices connect once it compiles:
|
|
223107
223200
|
${first.error}`);
|
|
223108
223201
|
}
|
|
223109
|
-
const
|
|
223110
|
-
|
|
223111
|
-
if (
|
|
223112
|
-
|
|
223113
|
-
const parts = file.replace(/\\/g, "/").split("/");
|
|
223114
|
-
if (parts.some((p2) => WATCH_SKIP_DIRS.has(p2)))
|
|
223115
|
-
return false;
|
|
223116
|
-
if (WATCH_SKIP_FILES.has(parts[parts.length - 1]))
|
|
223117
|
-
return false;
|
|
223118
|
-
try {
|
|
223119
|
-
const key = toKey(file);
|
|
223120
|
-
if (ignore(key, false))
|
|
223121
|
-
return false;
|
|
223122
|
-
} catch {
|
|
223123
|
-
return false;
|
|
223124
|
-
}
|
|
223125
|
-
return true;
|
|
223126
|
-
};
|
|
223127
|
-
const debug2 = process.env.LECODES_DEV_DEBUG === "1";
|
|
223128
|
-
let timer = null;
|
|
223129
|
-
const onChange = (file) => {
|
|
223130
|
-
if (debug2)
|
|
223131
|
-
note(`[watch] event: ${file ?? "(null)"} → ${shouldRecompile(file) ? "recompile" : "skip"}`);
|
|
223132
|
-
if (!shouldRecompile(file))
|
|
223133
|
-
return;
|
|
223134
|
-
if (timer)
|
|
223135
|
-
clearTimeout(timer);
|
|
223136
|
-
timer = setTimeout(async () => {
|
|
223137
|
-
timer = null;
|
|
223138
|
-
const result = await server2.recompile();
|
|
223139
|
-
if (!result.ok) {
|
|
223140
|
-
warnErr(`Compile failed (devices keep the last good version):
|
|
223202
|
+
const watcher = watchProject(root, async () => {
|
|
223203
|
+
const result = await server2.recompile();
|
|
223204
|
+
if (!result.ok) {
|
|
223205
|
+
warnErr(`Compile failed (devices keep the last good version):
|
|
223141
223206
|
${result.error}`);
|
|
223142
|
-
|
|
223143
|
-
|
|
223144
|
-
|
|
223145
|
-
|
|
223146
|
-
|
|
223147
|
-
|
|
223148
|
-
|
|
223149
|
-
};
|
|
223150
|
-
let watcher = null;
|
|
223151
|
-
try {
|
|
223152
|
-
watcher = watch(root, { recursive: true }, (_type, file) => onChange(file));
|
|
223153
|
-
} catch {
|
|
223154
|
-
warnErr("fs.watch(recursive) unavailable on this platform — hot reload disabled (restart to pick up changes).");
|
|
223155
|
-
}
|
|
223156
|
-
watcher?.on("error", (e2) => warnErr(`Watcher error — hot reload may have stopped (restart lecodes dev): ${e2.message}`));
|
|
223207
|
+
} else if (result.changed) {
|
|
223208
|
+
const devices = server2.clientCount();
|
|
223209
|
+
note(`✓ v${result.v} → ${devices} device${devices === 1 ? "" : "s"}`);
|
|
223210
|
+
for (const w of result.warnings)
|
|
223211
|
+
warnErr(w);
|
|
223212
|
+
}
|
|
223213
|
+
}, { warn: warnErr, debug: process.env.LECODES_DEV_DEBUG === "1" ? note : undefined });
|
|
223157
223214
|
log("");
|
|
223158
223215
|
log(`${c.bold("LeCodes Dev")} — ${name}`);
|
|
223159
223216
|
log("");
|
|
@@ -223186,14 +223243,14 @@ ${result.error}`);
|
|
|
223186
223243
|
session2 = launchOnAndroid({ port, url: androidUrl, serial: flags.serial, apk: flags.apk, packageName: flags.package });
|
|
223187
223244
|
} catch (e2) {
|
|
223188
223245
|
server2.close();
|
|
223189
|
-
watcher
|
|
223246
|
+
watcher.close();
|
|
223190
223247
|
throw e2;
|
|
223191
223248
|
}
|
|
223192
223249
|
note(`Android: ${session2.serial} → ${androidUrl} (adb reverse; device logs stream here as [device:log])`);
|
|
223193
223250
|
const finish = (code) => {
|
|
223194
223251
|
session2.stop();
|
|
223195
223252
|
server2.close();
|
|
223196
|
-
watcher
|
|
223253
|
+
watcher.close();
|
|
223197
223254
|
process.exit(code);
|
|
223198
223255
|
};
|
|
223199
223256
|
process.on("SIGINT", () => {
|
|
@@ -223228,14 +223285,14 @@ ${crashes.join(`
|
|
|
223228
223285
|
session2 = launchOnIos({ simulatorUrl: `http://127.0.0.1:${port}${path2}`, deviceUrl, udid: flags.udid, app: flags.app, bundleId: flags.bundle });
|
|
223229
223286
|
} catch (e2) {
|
|
223230
223287
|
server2.close();
|
|
223231
|
-
watcher
|
|
223288
|
+
watcher.close();
|
|
223232
223289
|
throw e2;
|
|
223233
223290
|
}
|
|
223234
223291
|
note(`iOS: ${session2.name} → ${session2.kind === "simulator" ? `http://127.0.0.1:${port}${path2}` : deviceUrl} (device logs stream here as [device:log])`);
|
|
223235
223292
|
const finish = (code) => {
|
|
223236
223293
|
session2.stop();
|
|
223237
223294
|
server2.close();
|
|
223238
|
-
watcher
|
|
223295
|
+
watcher.close();
|
|
223239
223296
|
process.exit(code);
|
|
223240
223297
|
};
|
|
223241
223298
|
process.on("SIGINT", () => {
|
|
@@ -230703,8 +230760,391 @@ var status_default = defineCommand({
|
|
|
230703
230760
|
});
|
|
230704
230761
|
|
|
230705
230762
|
// src/commands/test.ts
|
|
230706
|
-
import { existsSync as
|
|
230707
|
-
import { dirname as
|
|
230763
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync32, readFileSync as readFileSync40, readdirSync as readdirSync21 } from "node:fs";
|
|
230764
|
+
import { dirname as dirname24, resolve as resolve28 } from "node:path";
|
|
230765
|
+
init_manifest();
|
|
230766
|
+
|
|
230767
|
+
// src/hosts/liteRunner.ts
|
|
230768
|
+
init_cli();
|
|
230769
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
230770
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync31, writeFileSync as writeFileSync34 } from "node:fs";
|
|
230771
|
+
import { createServer as createServer3 } from "node:net";
|
|
230772
|
+
import { dirname as dirname23, join as join48 } from "node:path";
|
|
230773
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
230774
|
+
init_distRoot();
|
|
230775
|
+
init_peers();
|
|
230776
|
+
var FRAME_MS2 = 16;
|
|
230777
|
+
var LOG_SLACK_MS = 3000;
|
|
230778
|
+
var NO_NODE = "--lite needs Node on the PATH: the browser is driven from a Node process.";
|
|
230779
|
+
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.";
|
|
230780
|
+
var startDriver = async () => {
|
|
230781
|
+
const script = join48(distRoot, "src", "hosts", "liteBrowser.mjs");
|
|
230782
|
+
if (!existsSync47(script))
|
|
230783
|
+
throw new CliError("--lite is not available in this build of the CLI (its browser driver ships with the npm package).");
|
|
230784
|
+
const child = spawn6("node", [script], { stdio: ["pipe", "pipe", "inherit"] });
|
|
230785
|
+
const waiting = new Map;
|
|
230786
|
+
let onConsole = () => {};
|
|
230787
|
+
let next = 1;
|
|
230788
|
+
let dead = null;
|
|
230789
|
+
const fail = (error) => {
|
|
230790
|
+
dead = dead ?? error;
|
|
230791
|
+
for (const w of waiting.values())
|
|
230792
|
+
w.reject(dead);
|
|
230793
|
+
waiting.clear();
|
|
230794
|
+
};
|
|
230795
|
+
child.on("error", () => fail(new CliError(NO_NODE)));
|
|
230796
|
+
child.on("exit", () => fail(new Error("the browser's process ended")));
|
|
230797
|
+
createInterface2({ input: child.stdout }).on("line", (line) => {
|
|
230798
|
+
let message;
|
|
230799
|
+
try {
|
|
230800
|
+
message = JSON.parse(line);
|
|
230801
|
+
} catch {
|
|
230802
|
+
return;
|
|
230803
|
+
}
|
|
230804
|
+
if (message.event === "console") {
|
|
230805
|
+
onConsole(message.level ?? "log", message.text ?? "");
|
|
230806
|
+
return;
|
|
230807
|
+
}
|
|
230808
|
+
const w = message.id !== undefined ? waiting.get(message.id) : undefined;
|
|
230809
|
+
if (!w)
|
|
230810
|
+
return;
|
|
230811
|
+
waiting.delete(message.id);
|
|
230812
|
+
if (message.ok)
|
|
230813
|
+
w.resolve(message.value);
|
|
230814
|
+
else
|
|
230815
|
+
w.reject(new Error(message.error ?? "failed"));
|
|
230816
|
+
});
|
|
230817
|
+
const call = (op, args = {}) => new Promise((resolve28, reject) => {
|
|
230818
|
+
if (dead) {
|
|
230819
|
+
reject(dead);
|
|
230820
|
+
return;
|
|
230821
|
+
}
|
|
230822
|
+
const id = next++;
|
|
230823
|
+
waiting.set(id, { resolve: resolve28, reject });
|
|
230824
|
+
child.stdin.write(JSON.stringify({ id, op, args }) + `
|
|
230825
|
+
`);
|
|
230826
|
+
});
|
|
230827
|
+
const from = [
|
|
230828
|
+
join48(process.cwd(), "package.json"),
|
|
230829
|
+
join48(distRoot, "package.json"),
|
|
230830
|
+
join48(peersDir(), "package.json"),
|
|
230831
|
+
join48(distRoot, "..", "..", "engines", "gl-lite", "package.json")
|
|
230832
|
+
];
|
|
230833
|
+
try {
|
|
230834
|
+
await call("launch", { from });
|
|
230835
|
+
} catch (e2) {
|
|
230836
|
+
child.kill();
|
|
230837
|
+
if (e2 instanceof CliError)
|
|
230838
|
+
throw e2;
|
|
230839
|
+
const message = e2 instanceof Error ? e2.message : String(e2);
|
|
230840
|
+
throw new CliError(message === "NO_PLAYWRIGHT" ? NO_PLAYWRIGHT : `Chromium did not start (${message}) — run "npx playwright install chromium" once.`);
|
|
230841
|
+
}
|
|
230842
|
+
return {
|
|
230843
|
+
open(width, height, listener) {
|
|
230844
|
+
onConsole = listener;
|
|
230845
|
+
return call("open", { width, height });
|
|
230846
|
+
},
|
|
230847
|
+
goto: (url) => call("goto", { url }),
|
|
230848
|
+
waitFor: (expression, timeout) => call("waitFor", { expression, timeout }),
|
|
230849
|
+
evaluate: (expression) => call("evaluate", { expression }),
|
|
230850
|
+
timeout: (ms) => call("timeout", { ms }),
|
|
230851
|
+
mouse: (action, x2, y2) => call("mouse", { action, x: x2, y: y2 }),
|
|
230852
|
+
keyboard: (action, text2) => call("keyboard", { action, text: text2 }),
|
|
230853
|
+
screenshot: (path2, clip) => call("screenshot", { path: path2, clip }),
|
|
230854
|
+
async close() {
|
|
230855
|
+
await call("close").catch(() => {});
|
|
230856
|
+
child.kill();
|
|
230857
|
+
}
|
|
230858
|
+
};
|
|
230859
|
+
};
|
|
230860
|
+
var freePort = () => new Promise((resolve28, reject) => {
|
|
230861
|
+
const probe = createServer3();
|
|
230862
|
+
probe.once("error", reject);
|
|
230863
|
+
probe.listen(0, "127.0.0.1", () => {
|
|
230864
|
+
const address = probe.address();
|
|
230865
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
230866
|
+
probe.close(() => resolve28(port));
|
|
230867
|
+
});
|
|
230868
|
+
});
|
|
230869
|
+
var startLiteSession = async (options) => {
|
|
230870
|
+
const port = await freePort();
|
|
230871
|
+
const server2 = await startDevServer({
|
|
230872
|
+
root: options.root,
|
|
230873
|
+
manifest: options.manifest,
|
|
230874
|
+
name: options.name,
|
|
230875
|
+
lite: options.lite,
|
|
230876
|
+
origin: `http://127.0.0.1:${port}`,
|
|
230877
|
+
bind: "127.0.0.1",
|
|
230878
|
+
port,
|
|
230879
|
+
token: "test",
|
|
230880
|
+
entryOverride: options.entry
|
|
230881
|
+
});
|
|
230882
|
+
const compiled = await server2.recompile();
|
|
230883
|
+
if (!compiled.ok) {
|
|
230884
|
+
server2.close();
|
|
230885
|
+
throw new CliError(`Compile failed:
|
|
230886
|
+
${compiled.error}`);
|
|
230887
|
+
}
|
|
230888
|
+
let driver;
|
|
230889
|
+
try {
|
|
230890
|
+
driver = await startDriver();
|
|
230891
|
+
} catch (e2) {
|
|
230892
|
+
server2.close();
|
|
230893
|
+
throw e2;
|
|
230894
|
+
}
|
|
230895
|
+
return {
|
|
230896
|
+
run: (plan, runOptions) => runPlan(driver, `${server2.liteUrl}?dev=..&test`, plan, runOptions),
|
|
230897
|
+
async close() {
|
|
230898
|
+
await driver.close();
|
|
230899
|
+
server2.close();
|
|
230900
|
+
}
|
|
230901
|
+
};
|
|
230902
|
+
};
|
|
230903
|
+
var parseArgs2 = (rest) => {
|
|
230904
|
+
const out = [];
|
|
230905
|
+
let i2 = 0;
|
|
230906
|
+
while (i2 < rest.length) {
|
|
230907
|
+
if (rest[i2] === " ") {
|
|
230908
|
+
i2++;
|
|
230909
|
+
continue;
|
|
230910
|
+
}
|
|
230911
|
+
if (rest[i2] === '"') {
|
|
230912
|
+
let text2 = "";
|
|
230913
|
+
i2++;
|
|
230914
|
+
while (i2 < rest.length && rest[i2] !== '"') {
|
|
230915
|
+
if (rest[i2] === "\\")
|
|
230916
|
+
i2++;
|
|
230917
|
+
text2 += rest[i2] ?? "";
|
|
230918
|
+
i2++;
|
|
230919
|
+
}
|
|
230920
|
+
i2++;
|
|
230921
|
+
out.push(text2);
|
|
230922
|
+
continue;
|
|
230923
|
+
}
|
|
230924
|
+
const end = rest.indexOf(" ", i2);
|
|
230925
|
+
const token = rest.slice(i2, end < 0 ? rest.length : end);
|
|
230926
|
+
const n2 = Number(token);
|
|
230927
|
+
out.push(Number.isFinite(n2) && token !== "" ? n2 : token);
|
|
230928
|
+
i2 = end < 0 ? rest.length : end;
|
|
230929
|
+
}
|
|
230930
|
+
return out;
|
|
230931
|
+
};
|
|
230932
|
+
var unwire = (text2) => text2.replace(/\\(n|\\)/g, (_, c4) => c4 === "n" ? `
|
|
230933
|
+
` : "\\");
|
|
230934
|
+
var LEVEL = { log: "DEBUG", debug: "DEBUG", info: "INFO", warning: "WARN", warn: "WARN", error: "ERROR" };
|
|
230935
|
+
var ON_SHOW = ".lc-root > .lc:not(.lc-covered)";
|
|
230936
|
+
var FRAME = "new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(null))))";
|
|
230937
|
+
var FILL_VALUE = `(f) => f.type === "email" ? "test@example.com" : f.type === "tel" ? "5550100" : f.inputMode === "numeric" || f.inputMode === "decimal" ? "1" : f.type === "date" ? "2026-01-01" : f.type === "time" ? "12:00" : "test"`;
|
|
230938
|
+
var runPlan = async (driver, url, plan, options) => {
|
|
230939
|
+
const output = [];
|
|
230940
|
+
const seen = [];
|
|
230941
|
+
const say = (line) => {
|
|
230942
|
+
output.push(line);
|
|
230943
|
+
};
|
|
230944
|
+
await driver.open(options.width, options.height, (level, text2) => {
|
|
230945
|
+
seen.push(text2);
|
|
230946
|
+
say(`[${LEVEL[level] ?? "DEBUG"}] ${text2}`);
|
|
230947
|
+
options.onConsole?.(level, text2);
|
|
230948
|
+
});
|
|
230949
|
+
const expectOf = new Map;
|
|
230950
|
+
for (const step of plan.steps)
|
|
230951
|
+
if (step.expectLog !== undefined)
|
|
230952
|
+
for (const line of step.lines)
|
|
230953
|
+
expectOf.set(line, step.expectLog);
|
|
230954
|
+
const dump = () => driver.evaluate("window.lecodesTest.dump()");
|
|
230955
|
+
const rectOf = (id) => driver.evaluate(`window.lecodesTest.rect(${id})`);
|
|
230956
|
+
const nodeOf = async (name) => {
|
|
230957
|
+
const node = findDumpTarget(parseTreeDump(await dump()), name);
|
|
230958
|
+
if (!node)
|
|
230959
|
+
throw new Error(`no element "${name}" on the screen`);
|
|
230960
|
+
return node;
|
|
230961
|
+
};
|
|
230962
|
+
const pointOf = async (args, at2) => {
|
|
230963
|
+
const first = args[at2];
|
|
230964
|
+
if (typeof first === "number")
|
|
230965
|
+
return { point: [first, Number(args[at2 + 1])], next: at2 + 2 };
|
|
230966
|
+
const rect = await rectOf((await nodeOf(String(first))).id);
|
|
230967
|
+
if (!rect || rect[2] === 0 || rect[3] === 0)
|
|
230968
|
+
throw new Error(`"${first}" is not laid out`);
|
|
230969
|
+
return { point: [rect[0] + rect[2] / 2, rect[1] + rect[3] / 2], next: at2 + 1 };
|
|
230970
|
+
};
|
|
230971
|
+
const frame = () => driver.evaluate(FRAME);
|
|
230972
|
+
const waitFor = async (ms, line) => {
|
|
230973
|
+
const expected = expectOf.get(line);
|
|
230974
|
+
if (expected === undefined) {
|
|
230975
|
+
await driver.timeout(ms);
|
|
230976
|
+
return;
|
|
230977
|
+
}
|
|
230978
|
+
const until = Date.now() + ms + LOG_SLACK_MS;
|
|
230979
|
+
while (Date.now() < until) {
|
|
230980
|
+
if (seen.some((text2) => text2.includes(expected)))
|
|
230981
|
+
return;
|
|
230982
|
+
await driver.timeout(FRAME_MS2);
|
|
230983
|
+
}
|
|
230984
|
+
};
|
|
230985
|
+
let clip = null;
|
|
230986
|
+
const play = async (op, rest, line) => {
|
|
230987
|
+
const args = parseArgs2(rest);
|
|
230988
|
+
switch (op) {
|
|
230989
|
+
case "frames":
|
|
230990
|
+
await waitFor(Math.max(0, Number(args[0])) * FRAME_MS2, line);
|
|
230991
|
+
return;
|
|
230992
|
+
case "wait":
|
|
230993
|
+
await waitFor(Math.max(0, Number(args[0])), line);
|
|
230994
|
+
return;
|
|
230995
|
+
case "tap": {
|
|
230996
|
+
const { point } = await pointOf(args, 0);
|
|
230997
|
+
await driver.mouse("move", point[0], point[1]);
|
|
230998
|
+
await driver.mouse("down");
|
|
230999
|
+
await driver.mouse("up");
|
|
231000
|
+
await frame();
|
|
231001
|
+
return;
|
|
231002
|
+
}
|
|
231003
|
+
case "hold": {
|
|
231004
|
+
const { point, next } = await pointOf(args, 0);
|
|
231005
|
+
await driver.mouse("move", point[0], point[1]);
|
|
231006
|
+
await driver.mouse("down");
|
|
231007
|
+
await driver.timeout(Number(args[next]) || 0);
|
|
231008
|
+
await driver.mouse("up");
|
|
231009
|
+
await frame();
|
|
231010
|
+
return;
|
|
231011
|
+
}
|
|
231012
|
+
case "drag": {
|
|
231013
|
+
const from = await pointOf(args, 0);
|
|
231014
|
+
const to2 = await pointOf(args, from.next);
|
|
231015
|
+
const ms = Number(args[to2.next]) || 300;
|
|
231016
|
+
const steps = Math.max(2, Math.round(ms / FRAME_MS2));
|
|
231017
|
+
await driver.mouse("move", from.point[0], from.point[1]);
|
|
231018
|
+
await driver.mouse("down");
|
|
231019
|
+
for (let i2 = 1;i2 <= steps; i2++) {
|
|
231020
|
+
const t2 = i2 / steps;
|
|
231021
|
+
await driver.mouse("move", from.point[0] + (to2.point[0] - from.point[0]) * t2, from.point[1] + (to2.point[1] - from.point[1]) * t2);
|
|
231022
|
+
await driver.timeout(ms / steps);
|
|
231023
|
+
}
|
|
231024
|
+
await driver.mouse("up");
|
|
231025
|
+
await frame();
|
|
231026
|
+
return;
|
|
231027
|
+
}
|
|
231028
|
+
case "mouseto": {
|
|
231029
|
+
const { point } = await pointOf(args, 0);
|
|
231030
|
+
await driver.mouse("move", point[0], point[1]);
|
|
231031
|
+
await frame();
|
|
231032
|
+
return;
|
|
231033
|
+
}
|
|
231034
|
+
case "type":
|
|
231035
|
+
await driver.keyboard("type", unwire(rest));
|
|
231036
|
+
await frame();
|
|
231037
|
+
return;
|
|
231038
|
+
case "fill": {
|
|
231039
|
+
const value = rest === "" ? undefined : unwire(rest);
|
|
231040
|
+
const count = await driver.evaluate(`(() => {
|
|
231041
|
+
window.__lecodesFill = [...document.querySelectorAll("${ON_SHOW} input.lc, ${ON_SHOW} textarea.lc")].filter((f) => f.value === "")
|
|
231042
|
+
return window.__lecodesFill.length
|
|
231043
|
+
})()`);
|
|
231044
|
+
for (let i2 = 0;i2 < count; i2++) {
|
|
231045
|
+
const text2 = value ?? await driver.evaluate(`(${FILL_VALUE})(window.__lecodesFill[${i2}])`);
|
|
231046
|
+
await driver.evaluate(`window.__lecodesFill[${i2}].focus()`);
|
|
231047
|
+
await driver.keyboard("type", text2);
|
|
231048
|
+
}
|
|
231049
|
+
await frame();
|
|
231050
|
+
return;
|
|
231051
|
+
}
|
|
231052
|
+
case "scroll": {
|
|
231053
|
+
const by = Number(args[0]);
|
|
231054
|
+
const name = typeof args[1] === "string" ? args[1] : null;
|
|
231055
|
+
const target = name === null ? `document.querySelector("${ON_SHOW} .lc-scrollable, ${ON_SHOW} .lc-vlist")` : `document.querySelector('[data-node="${(await nodeOf(name)).id}"]')`;
|
|
231056
|
+
const found = await driver.evaluate(`(() => {
|
|
231057
|
+
const el = ${target}
|
|
231058
|
+
if (!el) return false
|
|
231059
|
+
const style = getComputedStyle(el)
|
|
231060
|
+
if (style.overflowX !== "hidden" && style.overflowY === "hidden") el.scrollLeft += ${by}
|
|
231061
|
+
else el.scrollTop += ${by}
|
|
231062
|
+
return true
|
|
231063
|
+
})()`);
|
|
231064
|
+
if (!found)
|
|
231065
|
+
throw new Error(name === null ? "no scrollable on the screen" : `"${name}" is not there`);
|
|
231066
|
+
await frame();
|
|
231067
|
+
return;
|
|
231068
|
+
}
|
|
231069
|
+
case "key": {
|
|
231070
|
+
await driver.keyboard("down", String(args[0]));
|
|
231071
|
+
await driver.timeout(Number(args[1]) || 0);
|
|
231072
|
+
await driver.keyboard("up", String(args[0]));
|
|
231073
|
+
return;
|
|
231074
|
+
}
|
|
231075
|
+
case "keydown":
|
|
231076
|
+
await driver.keyboard("down", String(args[0]));
|
|
231077
|
+
return;
|
|
231078
|
+
case "keyup":
|
|
231079
|
+
await driver.keyboard("up", String(args[0]));
|
|
231080
|
+
return;
|
|
231081
|
+
case "wheel":
|
|
231082
|
+
await driver.mouse("wheel", Number(args[0]) * 100, Number(args[1]) * 100);
|
|
231083
|
+
await frame();
|
|
231084
|
+
return;
|
|
231085
|
+
case "dump": {
|
|
231086
|
+
const file = rest.trim();
|
|
231087
|
+
mkdirSync31(dirname23(file), { recursive: true });
|
|
231088
|
+
writeFileSync34(file, await dump());
|
|
231089
|
+
return;
|
|
231090
|
+
}
|
|
231091
|
+
case "clip": {
|
|
231092
|
+
if (rest.trim() === "off") {
|
|
231093
|
+
clip = null;
|
|
231094
|
+
return;
|
|
231095
|
+
}
|
|
231096
|
+
if (typeof args[0] === "number") {
|
|
231097
|
+
clip = { x: args[0], y: Number(args[1]), width: Number(args[2]), height: Number(args[3]) };
|
|
231098
|
+
return;
|
|
231099
|
+
}
|
|
231100
|
+
const rect = await rectOf((await nodeOf(String(args[0]))).id);
|
|
231101
|
+
if (!rect)
|
|
231102
|
+
throw new Error(`"${args[0]}" is not laid out`);
|
|
231103
|
+
clip = { x: rect[0], y: rect[1], width: rect[2], height: rect[3] };
|
|
231104
|
+
return;
|
|
231105
|
+
}
|
|
231106
|
+
case "shot": {
|
|
231107
|
+
const file = rest.trim();
|
|
231108
|
+
mkdirSync31(dirname23(file), { recursive: true });
|
|
231109
|
+
await driver.screenshot(file, clip);
|
|
231110
|
+
return;
|
|
231111
|
+
}
|
|
231112
|
+
default:
|
|
231113
|
+
throw new Error(`"${op}" is not available on Web Lite (it has no 3D scene, no gamepad)`);
|
|
231114
|
+
}
|
|
231115
|
+
};
|
|
231116
|
+
try {
|
|
231117
|
+
await driver.goto(url);
|
|
231118
|
+
await driver.waitFor("window.lecodesTest && window.lecodesTest.ready === true", 20000);
|
|
231119
|
+
say("[script] loaded");
|
|
231120
|
+
const lines = plan.text.split(`
|
|
231121
|
+
`);
|
|
231122
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
231123
|
+
const text2 = lines[i2].trim();
|
|
231124
|
+
if (text2 === "" || text2.startsWith("#"))
|
|
231125
|
+
continue;
|
|
231126
|
+
const space = text2.indexOf(" ");
|
|
231127
|
+
const op = space < 0 ? text2 : text2.slice(0, space);
|
|
231128
|
+
const rest = space < 0 ? "" : text2.slice(space + 1);
|
|
231129
|
+
try {
|
|
231130
|
+
await play(op, rest, i2 + 1);
|
|
231131
|
+
say(`[script] #${i2 + 1} done ${op}`);
|
|
231132
|
+
} catch (e2) {
|
|
231133
|
+
say(`[script] #${i2 + 1} error ${op}: ${e2 instanceof Error ? e2.message.split(`
|
|
231134
|
+
`)[0] : String(e2)}`);
|
|
231135
|
+
break;
|
|
231136
|
+
}
|
|
231137
|
+
}
|
|
231138
|
+
say("[script] end");
|
|
231139
|
+
} catch (e2) {
|
|
231140
|
+
say(`[ERROR] ${e2 instanceof Error ? e2.message.split(`
|
|
231141
|
+
`)[0] : String(e2)}`);
|
|
231142
|
+
}
|
|
231143
|
+
return output.join(`
|
|
231144
|
+
`);
|
|
231145
|
+
};
|
|
231146
|
+
|
|
231147
|
+
// src/commands/test.ts
|
|
230708
231148
|
init_manifest();
|
|
230709
231149
|
init_cli();
|
|
230710
231150
|
init_shared();
|
|
@@ -230782,7 +231222,7 @@ var readFlowFile = (abs, displayName) => {
|
|
|
230782
231222
|
name: file.name ?? displayName,
|
|
230783
231223
|
kind: "file",
|
|
230784
231224
|
steps: file.steps,
|
|
230785
|
-
fixtures: resolveFixtures(file.fixtures,
|
|
231225
|
+
fixtures: resolveFixtures(file.fixtures, dirname24(abs)),
|
|
230786
231226
|
width: file.width,
|
|
230787
231227
|
height: file.height,
|
|
230788
231228
|
settle: file.settle,
|
|
@@ -230802,6 +231242,7 @@ var testFlags = {
|
|
|
230802
231242
|
...viewportFlags,
|
|
230803
231243
|
...bundleFlags,
|
|
230804
231244
|
...desktopRenderFlags,
|
|
231245
|
+
lite: bool("run the file scenarios on Web Lite, in a browser (needs Playwright): the same steps, targets and expects; the clock is the browser's, so a step that expects a log line waits for it; design flows stay headless"),
|
|
230805
231246
|
desktop: bool("run the file scenarios through the NATIVE host: the same steps, targets and expects as headless (expect.audio and the json output excepted); design flows stay headless. --frames / --fixed-dt as in render --desktop")
|
|
230806
231247
|
};
|
|
230807
231248
|
var test_default = defineCommand({
|
|
@@ -230817,6 +231258,11 @@ Design flows read tests/fixtures.json automatically (canned fetch responses) so
|
|
|
230817
231258
|
});
|
|
230818
231259
|
var runTests = async (args, flags) => {
|
|
230819
231260
|
const sim = flags.sim;
|
|
231261
|
+
const lite = flags.lite;
|
|
231262
|
+
if (lite && (flags.desktop || sim))
|
|
231263
|
+
throw new CliError("--lite is a host of its own — drop --desktop / --sim.");
|
|
231264
|
+
if (lite && flags.flows)
|
|
231265
|
+
throw new CliError("--flows needs the headless renderer (design flows read expect.screen and fill inputs) — drop --lite.");
|
|
230820
231266
|
const desktop = flags.desktop || sim;
|
|
230821
231267
|
if (desktop && flags.flows)
|
|
230822
231268
|
throw new CliError(`--flows needs the headless renderer (design flows read expect.screen and fill inputs) — drop --${sim ? "sim" : "desktop"}.`);
|
|
@@ -230828,7 +231274,7 @@ var runTests = async (args, flags) => {
|
|
|
230828
231274
|
}
|
|
230829
231275
|
const desktopRenderer = desktop ? resolveDesktopRenderer(desktopRoot, flags.renderer) : undefined;
|
|
230830
231276
|
const native = desktop ? await buildBundle(bundleOptions(flags), { localAssets: true, desktopRenderer }) : null;
|
|
230831
|
-
const headless = desktop ? null : await compileHeadlessBundle({ entry: flags.entry, publicUrl: flags["public-url"], remoteAssets: flags["remote-assets"], waitNetwork: flags["wait-network"] });
|
|
231277
|
+
const headless = desktop || lite ? null : await compileHeadlessBundle({ entry: flags.entry, publicUrl: flags["public-url"], remoteAssets: flags["remote-assets"], waitNetwork: flags["wait-network"] });
|
|
230832
231278
|
const warnings = headless?.warnings ?? [];
|
|
230833
231279
|
const useLocalAssets = headless?.useLocalAssets ?? false;
|
|
230834
231280
|
const js = headless?.js ?? "";
|
|
@@ -230842,7 +231288,7 @@ var runTests = async (args, flags) => {
|
|
|
230842
231288
|
root = process.cwd();
|
|
230843
231289
|
}
|
|
230844
231290
|
}
|
|
230845
|
-
const renderer = desktop ? null : await requireRenderer();
|
|
231291
|
+
const renderer = desktop || lite ? null : await requireRenderer();
|
|
230846
231292
|
const nativeFrames = flags.frames || 60;
|
|
230847
231293
|
const nativeFixedDt = flags["fixed-dt"] ?? 16;
|
|
230848
231294
|
if (nativeFixedDt < 0 || nativeFixedDt > 1000)
|
|
@@ -230856,28 +231302,28 @@ var runTests = async (args, flags) => {
|
|
|
230856
231302
|
if (args.length > 0) {
|
|
230857
231303
|
for (const given of args) {
|
|
230858
231304
|
const candidates = [resolve28(given), resolve28(testsDir, given), resolve28(testsDir, `${given}.flow.json`)];
|
|
230859
|
-
const abs = candidates.find((p2) =>
|
|
231305
|
+
const abs = candidates.find((p2) => existsSync48(p2));
|
|
230860
231306
|
if (!abs)
|
|
230861
231307
|
throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
|
|
230862
231308
|
cases.push(readFlowFile(abs, given));
|
|
230863
231309
|
}
|
|
230864
|
-
} else if (
|
|
231310
|
+
} else if (existsSync48(testsDir)) {
|
|
230865
231311
|
for (const f5 of readdirSync21(testsDir).filter((f6) => f6.endsWith(".flow.json")).sort()) {
|
|
230866
231312
|
cases.push(readFlowFile(resolve28(testsDir, f5), f5));
|
|
230867
231313
|
}
|
|
230868
231314
|
}
|
|
230869
231315
|
const defaultPack = resolve28(testsDir, "fixtures.json");
|
|
230870
|
-
const flowFixtures = flags.fixtures !== undefined ? resolveFixtures(flags.fixtures, ".") :
|
|
231316
|
+
const flowFixtures = flags.fixtures !== undefined ? resolveFixtures(flags.fixtures, ".") : existsSync48(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
|
|
230871
231317
|
const flowsOnly = flags.flows;
|
|
230872
231318
|
const wantFlows = !flags["no-flows"] && args.length === 0;
|
|
230873
231319
|
if (flowsOnly)
|
|
230874
231320
|
cases.length = 0;
|
|
230875
231321
|
let skipped = [];
|
|
230876
231322
|
let entryScreen = null;
|
|
230877
|
-
if (desktop && wantFlows &&
|
|
230878
|
-
note("Design-derived flows skipped with --desktop (they read expect.screen and fill inputs; run `lecodes test` headless for them).");
|
|
231323
|
+
if ((desktop || lite) && wantFlows && existsSync48(designMetaPath)) {
|
|
231324
|
+
note("Design-derived flows skipped with --desktop / --lite (they read expect.screen and fill inputs; run `lecodes test` headless for them).");
|
|
230879
231325
|
}
|
|
230880
|
-
if (!desktop && (wantFlows || flowsOnly) &&
|
|
231326
|
+
if (!desktop && !lite && (wantFlows || flowsOnly) && existsSync48(designMetaPath)) {
|
|
230881
231327
|
let meta;
|
|
230882
231328
|
try {
|
|
230883
231329
|
meta = JSON.parse(readFileSync40(designMetaPath, "utf8"));
|
|
@@ -230917,7 +231363,7 @@ var runTests = async (args, flags) => {
|
|
|
230917
231363
|
const reportCase = (result) => {
|
|
230918
231364
|
const skippedCount = result.steps.reduce((n2, s2) => n2 + (s2.skipped?.length ?? 0), 0);
|
|
230919
231365
|
if (result.ok) {
|
|
230920
|
-
out(`${c.green("✓")} ${result.name}${skippedCount > 0 ? c.dim(` (${skippedCount}
|
|
231366
|
+
out(`${c.green("✓")} ${result.name}${skippedCount > 0 ? c.dim(` (${skippedCount} check${skippedCount === 1 ? "" : "s"} skipped on this host)`) : ""}`);
|
|
230921
231367
|
} else {
|
|
230922
231368
|
out(`${c.red("✗")} ${result.name}`);
|
|
230923
231369
|
if (result.error)
|
|
@@ -230936,8 +231382,8 @@ var runTests = async (args, flags) => {
|
|
|
230936
231382
|
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames, sim });
|
|
230937
231383
|
for (const st of plan.steps)
|
|
230938
231384
|
if (st.screenshot)
|
|
230939
|
-
|
|
230940
|
-
|
|
231385
|
+
mkdirSync32(dirname24(st.screenshot), { recursive: true });
|
|
231386
|
+
mkdirSync32(caseDir, { recursive: true });
|
|
230941
231387
|
const finalPng = resolve28(caseDir, "final.png");
|
|
230942
231388
|
const { output } = await runDesktopRender(native.js, native.resources, {
|
|
230943
231389
|
renderer: desktopRenderer,
|
|
@@ -230952,7 +231398,7 @@ var runTests = async (args, flags) => {
|
|
|
230952
231398
|
script: plan.text,
|
|
230953
231399
|
fixedDtMs: nativeFixedDt
|
|
230954
231400
|
});
|
|
230955
|
-
const r2 = evaluateDesktopRun(plan, output, (path2) =>
|
|
231401
|
+
const r2 = evaluateDesktopRun(plan, output, (path2) => existsSync48(path2) ? readFileSync40(path2, "utf8") : null);
|
|
230956
231402
|
if (!r2.loaded)
|
|
230957
231403
|
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).");
|
|
230958
231404
|
return {
|
|
@@ -230964,8 +231410,48 @@ var runTests = async (args, flags) => {
|
|
|
230964
231410
|
artifacts: r2.ok ? [] : [finalPng]
|
|
230965
231411
|
};
|
|
230966
231412
|
};
|
|
231413
|
+
let liteSession = null;
|
|
231414
|
+
if (lite) {
|
|
231415
|
+
const page = resolveLiteRunner(note);
|
|
231416
|
+
if (!page)
|
|
231417
|
+
throw new CliError("No Web Lite page found: this CLI has no runtime/web-lite (a monorepo checkout needs `bun run build` in hosts/web-lite).");
|
|
231418
|
+
let manifest = null;
|
|
231419
|
+
try {
|
|
231420
|
+
manifest = readManifest(root);
|
|
231421
|
+
} catch {}
|
|
231422
|
+
liteSession = await startLiteSession({ root, manifest, name: manifest?.name ?? "app", lite: page, entry: flags.entry });
|
|
231423
|
+
}
|
|
231424
|
+
const runLiteCase = async (kase) => {
|
|
231425
|
+
const caseDir = resolve28(artifactsRoot, slug(kase.name));
|
|
231426
|
+
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: 0 });
|
|
231427
|
+
mkdirSync32(caseDir, { recursive: true });
|
|
231428
|
+
const output = await liteSession.run(plan, {
|
|
231429
|
+
width: flags.width || kase.width || projectViewport?.width || 390,
|
|
231430
|
+
height: flags.height || kase.height || projectViewport?.height || 844,
|
|
231431
|
+
onConsole
|
|
231432
|
+
});
|
|
231433
|
+
const r2 = evaluateDesktopRun(plan, output, (path2) => existsSync48(path2) ? readFileSync40(path2, "utf8") : null);
|
|
231434
|
+
return {
|
|
231435
|
+
name: kase.name,
|
|
231436
|
+
kind: kase.kind,
|
|
231437
|
+
ok: r2.ok,
|
|
231438
|
+
steps: r2.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error, skipped: s2.skipped.length > 0 ? s2.skipped : undefined })),
|
|
231439
|
+
error: !r2.loaded ? "the page did not load the app" : r2.ended ? undefined : "the page stopped before the script finished",
|
|
231440
|
+
artifacts: []
|
|
231441
|
+
};
|
|
231442
|
+
};
|
|
230967
231443
|
for (const kase of cases) {
|
|
230968
231444
|
let result;
|
|
231445
|
+
if (lite) {
|
|
231446
|
+
try {
|
|
231447
|
+
result = await runLiteCase(kase);
|
|
231448
|
+
} catch (e2) {
|
|
231449
|
+
result = { name: kase.name, kind: kase.kind, ok: false, steps: [], error: e2 instanceof Error ? e2.message : String(e2), artifacts: [] };
|
|
231450
|
+
}
|
|
231451
|
+
results.push(result);
|
|
231452
|
+
reportCase(result);
|
|
231453
|
+
continue;
|
|
231454
|
+
}
|
|
230969
231455
|
if (desktop) {
|
|
230970
231456
|
try {
|
|
230971
231457
|
result = await runNativeCase(kase);
|
|
@@ -231039,6 +231525,7 @@ var runTests = async (args, flags) => {
|
|
|
231039
231525
|
}, null, 2) + `
|
|
231040
231526
|
`);
|
|
231041
231527
|
}
|
|
231528
|
+
await liteSession?.close();
|
|
231042
231529
|
clearTimeout(killer);
|
|
231043
231530
|
process.exit(failed > 0 ? 1 : 0);
|
|
231044
231531
|
};
|
|
@@ -231047,9 +231534,9 @@ var runTests = async (args, flags) => {
|
|
|
231047
231534
|
init_peerInstall();
|
|
231048
231535
|
import { createRequire as createRequire8 } from "node:module";
|
|
231049
231536
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
231050
|
-
import { existsSync as
|
|
231537
|
+
import { existsSync as existsSync49, mkdtempSync as mkdtempSync6, readdirSync as readdirSync22, rmSync as rmSync13, statSync as statSync19 } from "node:fs";
|
|
231051
231538
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
231052
|
-
import { extname, join as
|
|
231539
|
+
import { extname, join as join49, relative as relative12, resolve as resolve29 } from "node:path";
|
|
231053
231540
|
init_cli();
|
|
231054
231541
|
init_paths();
|
|
231055
231542
|
var DEFAULT_SIZE = 256;
|
|
@@ -231061,13 +231548,13 @@ var findKtx = () => {
|
|
|
231061
231548
|
process.env.LECODES_KTX,
|
|
231062
231549
|
process.env.KTX_CLI,
|
|
231063
231550
|
process.env.KTX_TOKTX?.replace(/toktx(\.exe)?$/i, exe),
|
|
231064
|
-
process.platform === "win32" ?
|
|
231065
|
-
process.platform === "win32" ?
|
|
231551
|
+
process.platform === "win32" ? join49(process.env.LOCALAPPDATA ?? "", "Programs", "KTX-Software", "bin", exe) : null,
|
|
231552
|
+
process.platform === "win32" ? join49(process.env.ProgramFiles ?? "", "KTX-Software", "bin", exe) : null,
|
|
231066
231553
|
"/usr/local/bin/ktx",
|
|
231067
231554
|
"/opt/homebrew/bin/ktx"
|
|
231068
231555
|
].filter((p2) => !!p2);
|
|
231069
231556
|
for (const path2 of candidates) {
|
|
231070
|
-
if (
|
|
231557
|
+
if (existsSync49(path2))
|
|
231071
231558
|
return path2;
|
|
231072
231559
|
}
|
|
231073
231560
|
try {
|
|
@@ -231099,7 +231586,7 @@ var ktxToPng = (ktx, file, out) => {
|
|
|
231099
231586
|
run4(["extract", "--transcode", "rgba8", "--level", "0", file, out]);
|
|
231100
231587
|
} catch (e2) {
|
|
231101
231588
|
run4(["extract", "--level", "0", file, out]);
|
|
231102
|
-
if (!
|
|
231589
|
+
if (!existsSync49(out))
|
|
231103
231590
|
throw e2;
|
|
231104
231591
|
}
|
|
231105
231592
|
};
|
|
@@ -231108,7 +231595,7 @@ var collect = (dir) => {
|
|
|
231108
231595
|
for (const name of readdirSync22(dir)) {
|
|
231109
231596
|
if (name === "node_modules" || name.startsWith("."))
|
|
231110
231597
|
continue;
|
|
231111
|
-
const full =
|
|
231598
|
+
const full = join49(dir, name);
|
|
231112
231599
|
if (statSync19(full).isDirectory())
|
|
231113
231600
|
out.push(...collect(full));
|
|
231114
231601
|
else if (IMAGE_EXT.test(name))
|
|
@@ -231135,7 +231622,7 @@ var thumbs = async (targets, size, force) => {
|
|
|
231135
231622
|
if (targets.length > 0) {
|
|
231136
231623
|
for (const arg of targets) {
|
|
231137
231624
|
const abs = resolve29(process.cwd(), arg);
|
|
231138
|
-
if (!
|
|
231625
|
+
if (!existsSync49(abs))
|
|
231139
231626
|
throw new CliError(`No such file or folder: ${arg}`);
|
|
231140
231627
|
if (statSync19(abs).isDirectory())
|
|
231141
231628
|
files.push(...collect(abs));
|
|
@@ -231157,7 +231644,7 @@ var thumbs = async (targets, size, force) => {
|
|
|
231157
231644
|
const ktx = files.some((f5) => f5.toLowerCase().endsWith(".ktx2")) ? findKtx() : null;
|
|
231158
231645
|
const sharp = sharpResizer();
|
|
231159
231646
|
const resize = sharp ?? jimpResizer();
|
|
231160
|
-
const tmp = mkdtempSync6(
|
|
231647
|
+
const tmp = mkdtempSync6(join49(tmpdir6(), "lecodes-thumbs-"));
|
|
231161
231648
|
let made = 0, fresh = 0;
|
|
231162
231649
|
const skipped = [];
|
|
231163
231650
|
try {
|
|
@@ -231183,7 +231670,7 @@ var thumbs = async (targets, size, force) => {
|
|
|
231183
231670
|
skipped.push(`${asset} (KTX2 needs KTX-Software's \`ktx\` — set LECODES_KTX or put it on PATH)`);
|
|
231184
231671
|
continue;
|
|
231185
231672
|
}
|
|
231186
|
-
source =
|
|
231673
|
+
source = join49(tmp, `${i2}.png`);
|
|
231187
231674
|
ktxToPng(ktx, file, source);
|
|
231188
231675
|
}
|
|
231189
231676
|
sceneServer.writePreviewFile(root, asset, await resize(source, size));
|
|
@@ -231203,7 +231690,7 @@ var thumbs = async (targets, size, force) => {
|
|
|
231203
231690
|
if (rendered.length > 0) {
|
|
231204
231691
|
warnErr(`Models, materials and prefabs are RENDERED, not decoded — the scene editor does those: ${rendered.join(", ")}`);
|
|
231205
231692
|
}
|
|
231206
|
-
note(`Cache: ${
|
|
231693
|
+
note(`Cache: ${join49(root, ".lecodes", "preview")} (re-runs skip everything unchanged)`);
|
|
231207
231694
|
};
|
|
231208
231695
|
|
|
231209
231696
|
// src/commands/update/index.ts
|
|
@@ -231211,10 +231698,11 @@ init_package();
|
|
|
231211
231698
|
init_cli();
|
|
231212
231699
|
init_peers();
|
|
231213
231700
|
init_peerInstall();
|
|
231701
|
+
init_archives();
|
|
231214
231702
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
231215
|
-
import { existsSync as
|
|
231703
|
+
import { existsSync as existsSync50, readdirSync as readdirSync23 } from "node:fs";
|
|
231216
231704
|
import { homedir as homedir13 } from "node:os";
|
|
231217
|
-
import { dirname as
|
|
231705
|
+
import { dirname as dirname25, join as join50, resolve as resolve31 } from "node:path";
|
|
231218
231706
|
|
|
231219
231707
|
// src/commands/update/types.ts
|
|
231220
231708
|
init_api();
|
|
@@ -231286,9 +231774,9 @@ var findProjectDir = (start) => {
|
|
|
231286
231774
|
const home = resolve31(homedir13());
|
|
231287
231775
|
let dir = resolve31(start);
|
|
231288
231776
|
while (true) {
|
|
231289
|
-
if (dir !== home && (
|
|
231777
|
+
if (dir !== home && (existsSync50(join50(dir, "app.json")) || existsSync50(join50(dir, ".lecodes", "manifest.json")) || existsSync50(join50(dir, ".lecodes", "types"))))
|
|
231290
231778
|
return dir;
|
|
231291
|
-
const parent =
|
|
231779
|
+
const parent = dirname25(dir);
|
|
231292
231780
|
if (parent === dir)
|
|
231293
231781
|
return null;
|
|
231294
231782
|
dir = parent;
|
|
@@ -231323,7 +231811,7 @@ var semverNewer2 = (a2, b) => {
|
|
|
231323
231811
|
return 0;
|
|
231324
231812
|
};
|
|
231325
231813
|
var FROM_SOURCE = /[\\/]src[\\/]commands[\\/]/.test(import.meta.url);
|
|
231326
|
-
var STANDALONE =
|
|
231814
|
+
var STANDALONE = existsSync50(join50(dirname25(process.execPath), "runtime")) && existsSync50(join50(dirname25(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
|
|
231327
231815
|
var update_default = defineCommand({
|
|
231328
231816
|
name: "update",
|
|
231329
231817
|
summary: "Move everything the project depends on to its newest published state",
|
|
@@ -231383,7 +231871,7 @@ ${c.bold(name)} ${c.dim(what)}`);
|
|
|
231383
231871
|
return ["skipped", "not inside a project"];
|
|
231384
231872
|
if (flags["no-shells"])
|
|
231385
231873
|
return ["skipped", "--no-shells"];
|
|
231386
|
-
const present = ["ios", "android"].filter((d) =>
|
|
231874
|
+
const present = ["ios", "android"].filter((d) => existsSync50(join50(root, d)) && readdirSync23(join50(root, d)).length > 0);
|
|
231387
231875
|
if (present.length === 0)
|
|
231388
231876
|
return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
|
|
231389
231877
|
await runSubcommand(app_default, "update");
|
|
@@ -231398,7 +231886,7 @@ ${c.bold(name)} ${c.dim(what)}`);
|
|
|
231398
231886
|
for (const [name, probe] of OPTIONAL_PEERS) {
|
|
231399
231887
|
const where = peerSource(name, probe);
|
|
231400
231888
|
const installed = peerVersion(name, probe);
|
|
231401
|
-
const latest = !installed ? null :
|
|
231889
|
+
const latest = !installed ? null : archivedVersion(name) ?? await npmLatest(name);
|
|
231402
231890
|
rows.push({ name, installed, latest, where });
|
|
231403
231891
|
}
|
|
231404
231892
|
rows.push({ name: "lecodes-cli", installed: package_default.version, latest: await npmLatest("lecodes-cli"), where: "global" });
|
|
@@ -231412,7 +231900,7 @@ ${c.bold(name)} ${c.dim(what)}`);
|
|
|
231412
231900
|
if (behind.length === 0)
|
|
231413
231901
|
return "ok";
|
|
231414
231902
|
const prefixBehind = behind.filter((r2) => r2.where === "prefix");
|
|
231415
|
-
const globalSpecs = behind.filter((r2) => r2.where !== "prefix").map((r2) => r2.name + "@latest");
|
|
231903
|
+
const globalSpecs = behind.filter((r2) => r2.where !== "prefix").map((r2) => archiveSpec(r2.name) ?? r2.name + "@latest");
|
|
231416
231904
|
if (flags.check) {
|
|
231417
231905
|
return ["updated", behind.length + " behind — run: lecodes update" + (globalSpecs.length ? " (global: npm install -g " + globalSpecs.join(" ") + ")" : "")];
|
|
231418
231906
|
}
|
|
@@ -231430,7 +231918,7 @@ ${c.bold(name)} ${c.dim(what)}`);
|
|
|
231430
231918
|
const r2 = spawnSync10("npm", ["install", "-g", ...toInstall], { stdio: "inherit", shell: true });
|
|
231431
231919
|
if (r2.status !== 0)
|
|
231432
231920
|
throw new CliError("npm install -g exited with " + (r2.status ?? "a signal"));
|
|
231433
|
-
done.push(...
|
|
231921
|
+
done.push(...behind.filter((r3) => r3.where !== "prefix" && (!STANDALONE || r3.name !== "lecodes-cli")).map((r3) => r3.name));
|
|
231434
231922
|
}
|
|
231435
231923
|
if (done.length === 0)
|
|
231436
231924
|
return ["skipped", "only the standalone CLI itself is behind — download the release"];
|
|
@@ -231449,11 +231937,362 @@ ${c.bold("Summary")}`);
|
|
|
231449
231937
|
}
|
|
231450
231938
|
});
|
|
231451
231939
|
|
|
231940
|
+
// src/commands/web.ts
|
|
231941
|
+
init_manifest();
|
|
231942
|
+
import { basename as basename25, relative as relative13 } from "node:path";
|
|
231943
|
+
init_browserAuth();
|
|
231944
|
+
init_cli();
|
|
231945
|
+
|
|
231946
|
+
// src/web/build.ts
|
|
231947
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
231948
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as existsSync51, mkdirSync as mkdirSync33, readFileSync as readFileSync41, readdirSync as readdirSync24, rmSync as rmSync14, statSync as statSync20, writeFileSync as writeFileSync35 } from "node:fs";
|
|
231949
|
+
import { dirname as dirname26, extname as extname2, join as join51, resolve as resolve32 } from "node:path";
|
|
231950
|
+
init_fonts();
|
|
231951
|
+
init_assetIcons();
|
|
231952
|
+
init_projectCompile();
|
|
231953
|
+
init_cli();
|
|
231954
|
+
var escapeHtml = (text2) => text2.replace(/[&<>"]/g, (c4) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c4]);
|
|
231955
|
+
var normalBase = (base) => {
|
|
231956
|
+
const b = (base ?? "/").trim() || "/";
|
|
231957
|
+
return (b.startsWith("/") ? b : `/${b}`).replace(/\/*$/, "/");
|
|
231958
|
+
};
|
|
231959
|
+
var walk = (dir, prefix = "") => {
|
|
231960
|
+
const out = [];
|
|
231961
|
+
for (const name of readdirSync24(dir)) {
|
|
231962
|
+
const abs = join51(dir, name);
|
|
231963
|
+
const stat = statSync20(abs);
|
|
231964
|
+
if (stat.isDirectory())
|
|
231965
|
+
out.push(...walk(abs, `${prefix}${name}/`));
|
|
231966
|
+
else
|
|
231967
|
+
out.push({ path: prefix + name, bytes: stat.size });
|
|
231968
|
+
}
|
|
231969
|
+
return out;
|
|
231970
|
+
};
|
|
231971
|
+
var buildWeb = async (options) => {
|
|
231972
|
+
const { root, manifest, name, lite } = options;
|
|
231973
|
+
const out = resolve32(root, options.out);
|
|
231974
|
+
if (out === resolve32(root) || resolve32(root).startsWith(out + "\\") || resolve32(root).startsWith(out + "/")) {
|
|
231975
|
+
throw new CliError(`The output folder is cleaned before a build: "${options.out}" holds the project itself.`);
|
|
231976
|
+
}
|
|
231977
|
+
const config = readAppConfigAt(root)?.web ?? {};
|
|
231978
|
+
const mode = config.urls === "path" ? "path" : "hash";
|
|
231979
|
+
const base = normalBase(config.base);
|
|
231980
|
+
const warnings = [];
|
|
231981
|
+
const assetUrl = (path2) => "assets" + encodeURI(path2);
|
|
231982
|
+
const collected = collectLocalEntries(root, manifest, "assets");
|
|
231983
|
+
const entries = collected.entries.filter((e2) => {
|
|
231984
|
+
const abs = e2.absPath ? resolve32(e2.absPath) : null;
|
|
231985
|
+
return !(abs && (abs === out || abs.startsWith(out + "\\") || abs.startsWith(out + "/")));
|
|
231986
|
+
});
|
|
231987
|
+
for (const w of collected.warnings)
|
|
231988
|
+
if (!/shaders compile on the server/.test(w))
|
|
231989
|
+
warnings.push(w);
|
|
231990
|
+
const fontsHeader = await applyProjectFonts(entries, {
|
|
231991
|
+
mode: "vendor",
|
|
231992
|
+
root,
|
|
231993
|
+
vendorDir: root,
|
|
231994
|
+
vendorPrefix: "",
|
|
231995
|
+
publicUrl: "",
|
|
231996
|
+
relative: true,
|
|
231997
|
+
makeResourceEntry: (path2, absPath) => ({ path: path2, type: "resource", fileSrc: assetUrl(path2), absPath })
|
|
231998
|
+
});
|
|
231999
|
+
const iconsHeader = await applyAppAssetIcons(entries);
|
|
232000
|
+
if (!options.entry) {
|
|
232001
|
+
const ambiguous = ambiguousEntryWarning(entries);
|
|
232002
|
+
if (ambiguous)
|
|
232003
|
+
warnings.push(ambiguous);
|
|
232004
|
+
}
|
|
232005
|
+
const compiled = await compileProject2({
|
|
232006
|
+
entries,
|
|
232007
|
+
name,
|
|
232008
|
+
publicUrl: "",
|
|
232009
|
+
entryOverride: options.entry,
|
|
232010
|
+
header: true,
|
|
232011
|
+
fontsHeader,
|
|
232012
|
+
iconsHeader,
|
|
232013
|
+
sourcemap: options.sourcemap === true,
|
|
232014
|
+
minify: options.minify,
|
|
232015
|
+
onWarning: (w) => warnings.push(w)
|
|
232016
|
+
});
|
|
232017
|
+
const js = compiled.replace(/^\/\/ last-updated: .*$/m, "// last-updated: -");
|
|
232018
|
+
const header = js.slice(0, 2000);
|
|
232019
|
+
if (!/^\/\/ gl: disable/m.test(header) || /^\/\/ 2d: enable/m.test(header)) {
|
|
232020
|
+
warnings.push("This project uses 3D or 2D scenes, which Web Lite does not show yet: the page runs its UI, a scene's calls fail. (`lecodes dev --web` runs the whole app on the web player.)");
|
|
232021
|
+
}
|
|
232022
|
+
rmSync14(out, { recursive: true, force: true });
|
|
232023
|
+
mkdirSync33(out, { recursive: true });
|
|
232024
|
+
cpSync6(lite, out, { recursive: true });
|
|
232025
|
+
const bundle = `app.${createHash9("sha256").update(js).digest("hex").slice(0, 10)}.js`;
|
|
232026
|
+
writeFileSync35(join51(out, bundle), js);
|
|
232027
|
+
for (const entry of entries) {
|
|
232028
|
+
if (entry.type !== "resource" || !entry.absPath || !entry.fileSrc)
|
|
232029
|
+
continue;
|
|
232030
|
+
if (!js.includes(entry.fileSrc))
|
|
232031
|
+
continue;
|
|
232032
|
+
const to2 = join51(out, "assets", ...entry.path.split("/").filter(Boolean));
|
|
232033
|
+
if (existsSync51(to2)) {
|
|
232034
|
+
warnings.push(`${entry.path}: a file of the page has this name in the built folder — the project's file was not copied`);
|
|
232035
|
+
continue;
|
|
232036
|
+
}
|
|
232037
|
+
mkdirSync33(dirname26(to2), { recursive: true });
|
|
232038
|
+
copyFileSync2(entry.absPath, to2);
|
|
232039
|
+
}
|
|
232040
|
+
const app = readAppConfigAt(root);
|
|
232041
|
+
let html = readFileSync41(join51(lite, "index.html"), "utf8");
|
|
232042
|
+
const head = [];
|
|
232043
|
+
head.push(`<meta name="theme-color" content="${escapeHtml(config.themeColor ?? "#000000")}" />`);
|
|
232044
|
+
if (app?.icon) {
|
|
232045
|
+
const icon = resolve32(root, app.icon);
|
|
232046
|
+
if (existsSync51(icon)) {
|
|
232047
|
+
const file = `icon${extname2(icon).toLowerCase() || ".png"}`;
|
|
232048
|
+
copyFileSync2(icon, join51(out, file));
|
|
232049
|
+
head.push(`<link rel="icon" href="${file}" />`, `<link rel="apple-touch-icon" href="${file}" />`);
|
|
232050
|
+
} else {
|
|
232051
|
+
warnings.push(`app.json icon "${app.icon}" is not there — the page has no icon`);
|
|
232052
|
+
}
|
|
232053
|
+
}
|
|
232054
|
+
if (mode === "path")
|
|
232055
|
+
head.unshift(`<base href="${escapeHtml(base)}" />`);
|
|
232056
|
+
const attrs = [`data-src="${bundle}"`, ...mode === "path" ? [`data-urls="path"`, `data-base="${escapeHtml(base)}"`] : []].join(" ");
|
|
232057
|
+
const replace = (from, to2) => {
|
|
232058
|
+
const next = html.replace(from, to2);
|
|
232059
|
+
if (next === html)
|
|
232060
|
+
throw new CliError(`The page of Web Lite has no ${String(from)} to fill — this CLI and its page are of different versions.`);
|
|
232061
|
+
html = next;
|
|
232062
|
+
};
|
|
232063
|
+
replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(config.title ?? app?.name ?? name)}</title>`);
|
|
232064
|
+
replace(`<div id="app"></div>`, `<div id="app" ${attrs}></div>`);
|
|
232065
|
+
replace("</head>", ` ${head.join(`
|
|
232066
|
+
`)}
|
|
232067
|
+
</head>`);
|
|
232068
|
+
if (config.lang)
|
|
232069
|
+
replace(/<html lang="[^"]*">/, `<html lang="${escapeHtml(config.lang)}">`);
|
|
232070
|
+
writeFileSync35(join51(out, "index.html"), html);
|
|
232071
|
+
if (mode === "path")
|
|
232072
|
+
writeFileSync35(join51(out, "404.html"), html);
|
|
232073
|
+
return { out, bundle, files: walk(out), warnings };
|
|
232074
|
+
};
|
|
232075
|
+
|
|
232076
|
+
// src/web/preview.ts
|
|
232077
|
+
import { createReadStream as createReadStream2, existsSync as existsSync52, statSync as statSync21 } from "node:fs";
|
|
232078
|
+
import { createServer as createServer4 } from "node:http";
|
|
232079
|
+
import { join as join52, resolve as resolve33 } from "node:path";
|
|
232080
|
+
var startPreview = async (dir, port) => {
|
|
232081
|
+
const root = resolve33(dir);
|
|
232082
|
+
const fallback = existsSync52(join52(root, "404.html")) ? join52(root, "index.html") : null;
|
|
232083
|
+
const server2 = createServer4((req, res) => {
|
|
232084
|
+
let path2;
|
|
232085
|
+
try {
|
|
232086
|
+
path2 = decodeURIComponent(new URL(req.url ?? "/", "http://localhost").pathname);
|
|
232087
|
+
} catch {
|
|
232088
|
+
res.writeHead(400);
|
|
232089
|
+
res.end();
|
|
232090
|
+
return;
|
|
232091
|
+
}
|
|
232092
|
+
const parts = path2.split("/").filter(Boolean);
|
|
232093
|
+
if (parts.some((p2) => p2 === ".." || p2 === ".")) {
|
|
232094
|
+
res.writeHead(400);
|
|
232095
|
+
res.end();
|
|
232096
|
+
return;
|
|
232097
|
+
}
|
|
232098
|
+
let file = join52(root, ...parts);
|
|
232099
|
+
if (existsSync52(file) && statSync21(file).isDirectory())
|
|
232100
|
+
file = join52(file, "index.html");
|
|
232101
|
+
let status3 = 200;
|
|
232102
|
+
if (!existsSync52(file) || !statSync21(file).isFile()) {
|
|
232103
|
+
if (!fallback || /\.[a-z0-9]+$/i.test(parts[parts.length - 1] ?? "")) {
|
|
232104
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
232105
|
+
res.end("Not found");
|
|
232106
|
+
return;
|
|
232107
|
+
}
|
|
232108
|
+
file = fallback;
|
|
232109
|
+
}
|
|
232110
|
+
res.writeHead(status3, {
|
|
232111
|
+
"content-type": contentType(file),
|
|
232112
|
+
"cache-control": /[.-][0-9a-f]{8,}\.[a-z0-9]+$/i.test(file) ? "max-age=31536000, immutable" : "no-cache"
|
|
232113
|
+
});
|
|
232114
|
+
createReadStream2(file).pipe(res);
|
|
232115
|
+
});
|
|
232116
|
+
await new Promise((done, reject) => {
|
|
232117
|
+
server2.once("error", reject);
|
|
232118
|
+
server2.listen(port, "0.0.0.0", () => {
|
|
232119
|
+
server2.removeListener("error", reject);
|
|
232120
|
+
done();
|
|
232121
|
+
});
|
|
232122
|
+
});
|
|
232123
|
+
const address = server2.address();
|
|
232124
|
+
const bound = typeof address === "object" && address !== null ? address.port : port;
|
|
232125
|
+
return { url: `http://localhost:${bound}/`, port: bound, close: () => server2.close() };
|
|
232126
|
+
};
|
|
232127
|
+
|
|
232128
|
+
// src/commands/web.ts
|
|
232129
|
+
init_shared();
|
|
232130
|
+
var DEFAULT_PORT4 = 4488;
|
|
232131
|
+
var DEFAULT_OUT = "dist/web";
|
|
232132
|
+
var project = () => {
|
|
232133
|
+
let root;
|
|
232134
|
+
let manifest;
|
|
232135
|
+
try {
|
|
232136
|
+
root = findProjectRoot(process.cwd());
|
|
232137
|
+
manifest = readManifest(root);
|
|
232138
|
+
} catch {
|
|
232139
|
+
root = process.cwd();
|
|
232140
|
+
manifest = null;
|
|
232141
|
+
}
|
|
232142
|
+
return { root, manifest, name: manifest?.name ?? basename25(root) };
|
|
232143
|
+
};
|
|
232144
|
+
var litePage = () => {
|
|
232145
|
+
const lite = resolveLiteRunner(note);
|
|
232146
|
+
if (!lite)
|
|
232147
|
+
throw new CliError("No Web Lite page found: this CLI has no runtime/web-lite (a monorepo checkout needs `bun run build` in hosts/web-lite).");
|
|
232148
|
+
return lite;
|
|
232149
|
+
};
|
|
232150
|
+
var kb = (bytes) => bytes < 1024 ? `${bytes} B` : bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
232151
|
+
var dev = defineCommand({
|
|
232152
|
+
name: "dev",
|
|
232153
|
+
summary: "Open the project as a web page, reloaded on every save",
|
|
232154
|
+
description: "Serves the project's bundle and assets with the page of Web Lite around them; a save recompiles and runs the app again, the page's console output and errors stream back here. The address's hash is the route the app is opened with (#/product/42 → app.launchUrl).",
|
|
232155
|
+
flags: {
|
|
232156
|
+
port: num("server port", { default: DEFAULT_PORT4 }),
|
|
232157
|
+
entry: bundleFlags.entry,
|
|
232158
|
+
"no-open": bool("don't open the browser")
|
|
232159
|
+
},
|
|
232160
|
+
examples: ["lecodes web dev", "lecodes web dev --port 8080 --no-open"],
|
|
232161
|
+
run: async ({ flags }) => {
|
|
232162
|
+
const { root, manifest, name } = project();
|
|
232163
|
+
const lite = litePage();
|
|
232164
|
+
const port = flags.port || DEFAULT_PORT4;
|
|
232165
|
+
const lan = lanCandidates()[0]?.address;
|
|
232166
|
+
const origin = `http://localhost:${port}`;
|
|
232167
|
+
const server2 = await startDevServer({
|
|
232168
|
+
root,
|
|
232169
|
+
manifest,
|
|
232170
|
+
name,
|
|
232171
|
+
origin,
|
|
232172
|
+
port,
|
|
232173
|
+
token: loadDevToken(root),
|
|
232174
|
+
entryOverride: flags.entry,
|
|
232175
|
+
lite,
|
|
232176
|
+
log: note
|
|
232177
|
+
}).catch((e2) => {
|
|
232178
|
+
const msg = e2 instanceof Error ? e2.message : String(e2);
|
|
232179
|
+
throw new CliError(msg.includes("EADDRINUSE") ? `Port ${port} is already in use — pass --port <n>.` : msg);
|
|
232180
|
+
});
|
|
232181
|
+
note("Compiling…");
|
|
232182
|
+
const first = await server2.recompile();
|
|
232183
|
+
if (first.ok)
|
|
232184
|
+
for (const w of first.warnings)
|
|
232185
|
+
warnErr(w);
|
|
232186
|
+
else
|
|
232187
|
+
warnErr(`Compile failed — fix it and save; the page loads once it compiles:
|
|
232188
|
+
${first.error}`);
|
|
232189
|
+
const watcher = watchProject(root, async () => {
|
|
232190
|
+
const result = await server2.recompile();
|
|
232191
|
+
if (!result.ok)
|
|
232192
|
+
warnErr(`Compile failed (the page keeps the last good version):
|
|
232193
|
+
${result.error}`);
|
|
232194
|
+
else if (result.changed) {
|
|
232195
|
+
note(`✓ v${result.v}`);
|
|
232196
|
+
for (const w of result.warnings)
|
|
232197
|
+
warnErr(w);
|
|
232198
|
+
}
|
|
232199
|
+
}, { warn: warnErr, debug: process.env.LECODES_DEV_DEBUG === "1" ? note : undefined });
|
|
232200
|
+
const url = server2.liteUrl;
|
|
232201
|
+
log("");
|
|
232202
|
+
log(`${c.bold("LeCodes Web")} — ${name}`);
|
|
232203
|
+
log("");
|
|
232204
|
+
log(`In the browser: ${c.bold(url)}`);
|
|
232205
|
+
if (lan)
|
|
232206
|
+
note(`On this network: ${url.replace("http://localhost:", `http://${lan}:`)}`);
|
|
232207
|
+
log("");
|
|
232208
|
+
log(`Watching ${root} — save a file to reload. Ctrl+C to stop.`);
|
|
232209
|
+
if (!flags["no-open"])
|
|
232210
|
+
openBrowser(url);
|
|
232211
|
+
process.on("SIGINT", () => {
|
|
232212
|
+
watcher.close();
|
|
232213
|
+
server2.close();
|
|
232214
|
+
process.exit(0);
|
|
232215
|
+
});
|
|
232216
|
+
}
|
|
232217
|
+
});
|
|
232218
|
+
var build2 = defineCommand({
|
|
232219
|
+
name: "build",
|
|
232220
|
+
summary: "Build the project into a folder any static hosting serves",
|
|
232221
|
+
description: 'Writes the page, the bundle, the page\'s own files and the assets the bundle names into one folder (dist/web). Every url in it is relative: the folder works at a domain\'s root and inside a folder of one.\n\napp.json `web` sets the page: `title`, `themeColor`, `lang`, and `urls` — "hash" (default: a route is `#/product/42`, which any hosting serves) or "path" (`/product/42` under `base`: the hosting has to answer every path with the page; a 404.html is written for the ones that do it that way).',
|
|
232222
|
+
flags: {
|
|
232223
|
+
out: str("the folder to write (cleaned first)", { alias: "o", value: "<dir>", default: DEFAULT_OUT }),
|
|
232224
|
+
entry: bundleFlags.entry,
|
|
232225
|
+
sourcemap: bool("inline the source map (errors name the app's own lines; the bundle grows by its sources)"),
|
|
232226
|
+
"no-minify": bundleFlags["no-minify"]
|
|
232227
|
+
},
|
|
232228
|
+
examples: ["lecodes web build", "lecodes web build -o public"],
|
|
232229
|
+
run: async ({ flags }) => {
|
|
232230
|
+
const { root, manifest, name } = project();
|
|
232231
|
+
const lite = litePage();
|
|
232232
|
+
note("Building…");
|
|
232233
|
+
const result = await buildWeb({
|
|
232234
|
+
root,
|
|
232235
|
+
manifest,
|
|
232236
|
+
name,
|
|
232237
|
+
lite,
|
|
232238
|
+
out: flags.out,
|
|
232239
|
+
entry: flags.entry,
|
|
232240
|
+
sourcemap: flags.sourcemap,
|
|
232241
|
+
minify: flags["no-minify"] ? false : undefined
|
|
232242
|
+
});
|
|
232243
|
+
for (const w of result.warnings)
|
|
232244
|
+
warnErr(w);
|
|
232245
|
+
const total = result.files.reduce((sum, f5) => sum + f5.bytes, 0);
|
|
232246
|
+
const shown = relative13(process.cwd(), result.out) || ".";
|
|
232247
|
+
log("");
|
|
232248
|
+
for (const f5 of [...result.files].sort((a2, b) => b.bytes - a2.bytes).slice(0, 8))
|
|
232249
|
+
log(` ${kb(f5.bytes).padStart(10)} ${f5.path}`);
|
|
232250
|
+
if (result.files.length > 8)
|
|
232251
|
+
log(c.dim(` …and ${result.files.length - 8} more`));
|
|
232252
|
+
log("");
|
|
232253
|
+
log(`${c.green("✓")} ${c.bold(shown)} — ${result.files.length} files, ${kb(total)}`);
|
|
232254
|
+
note(`Look at it: lecodes web preview${flags.out === DEFAULT_OUT ? "" : ` ${flags.out}`}`);
|
|
232255
|
+
}
|
|
232256
|
+
});
|
|
232257
|
+
var preview = defineCommand({
|
|
232258
|
+
name: "preview",
|
|
232259
|
+
summary: "Serve a built folder, as a static hosting would",
|
|
232260
|
+
usage: "[dir]",
|
|
232261
|
+
flags: {
|
|
232262
|
+
port: num("server port", { default: DEFAULT_PORT4 + 1 }),
|
|
232263
|
+
"no-open": bool("don't open the browser")
|
|
232264
|
+
},
|
|
232265
|
+
examples: ["lecodes web preview", "lecodes web preview public --port 8080"],
|
|
232266
|
+
run: async ({ args, flags }) => {
|
|
232267
|
+
const dir = args[0] ?? DEFAULT_OUT;
|
|
232268
|
+
const server2 = await startPreview(dir, flags.port || DEFAULT_PORT4 + 1).catch((e2) => {
|
|
232269
|
+
const msg = e2 instanceof Error ? e2.message : String(e2);
|
|
232270
|
+
throw new CliError(msg.includes("EADDRINUSE") ? `Port ${flags.port} is already in use — pass --port <n>.` : msg);
|
|
232271
|
+
});
|
|
232272
|
+
log(`${c.bold("LeCodes Web")} — ${dir}`);
|
|
232273
|
+
log(`In the browser: ${c.bold(server2.url)}`);
|
|
232274
|
+
log("Ctrl+C to stop.");
|
|
232275
|
+
if (!flags["no-open"])
|
|
232276
|
+
openBrowser(server2.url);
|
|
232277
|
+
process.on("SIGINT", () => {
|
|
232278
|
+
server2.close();
|
|
232279
|
+
process.exit(0);
|
|
232280
|
+
});
|
|
232281
|
+
}
|
|
232282
|
+
});
|
|
232283
|
+
var web_default = defineCommand({
|
|
232284
|
+
name: "web",
|
|
232285
|
+
summary: "The project as a web page (Web Lite): serve it with reload on save, build it for a hosting",
|
|
232286
|
+
description: "Web Lite shows the app with the browser's own layout, text and scroll: what a site ships. UI apps for now — a project's 3D and 2D scenes are not shown yet.",
|
|
232287
|
+
commands: [dev, build2, preview]
|
|
232288
|
+
});
|
|
232289
|
+
|
|
231452
232290
|
// src/commands/index.ts
|
|
231453
232291
|
var commandGroups = (opts) => [
|
|
231454
232292
|
{ title: "Project", commands: [init_default, create_default, clone_default, link_default, status_default, diff_default, pull_default, push_default, login_default] },
|
|
231455
232293
|
{ title: "Run & test", commands: [
|
|
231456
232294
|
dev_default,
|
|
232295
|
+
web_default,
|
|
231457
232296
|
compile_default,
|
|
231458
232297
|
render_default,
|
|
231459
232298
|
test_default,
|
|
@@ -231485,9 +232324,9 @@ var commandGroups = (opts) => [
|
|
|
231485
232324
|
// src/index.ts
|
|
231486
232325
|
init_peers();
|
|
231487
232326
|
var STANDALONE2 = (() => {
|
|
231488
|
-
const dir =
|
|
232327
|
+
const dir = dirname27(process.execPath);
|
|
231489
232328
|
const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
|
|
231490
|
-
return
|
|
232329
|
+
return existsSync53(join53(dir, "runtime")) && existsSync53(join53(dir, chisel));
|
|
231491
232330
|
})();
|
|
231492
232331
|
var INSTALLED = STANDALONE2 && (() => {
|
|
231493
232332
|
const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
|
|
@@ -231502,8 +232341,8 @@ var INSTALLED = STANDALONE2 && (() => {
|
|
|
231502
232341
|
if (!d)
|
|
231503
232342
|
return false;
|
|
231504
232343
|
try {
|
|
231505
|
-
const cand =
|
|
231506
|
-
return
|
|
232344
|
+
const cand = join53(d, exe);
|
|
232345
|
+
return existsSync53(cand) && realpathSync2(cand) === self2;
|
|
231507
232346
|
} catch {
|
|
231508
232347
|
return false;
|
|
231509
232348
|
}
|