lecodes-cli 0.8.0 → 0.8.2
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 +211 -69
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2007,7 +2007,7 @@ var require_main = __commonJS((exports, module) => {
|
|
|
2007
2007
|
// package.json
|
|
2008
2008
|
var package_default = {
|
|
2009
2009
|
name: "lecodes-cli",
|
|
2010
|
-
version: "0.8.
|
|
2010
|
+
version: "0.8.2",
|
|
2011
2011
|
description: "Clone, edit and push LeCodes projects from your machine.",
|
|
2012
2012
|
type: "module",
|
|
2013
2013
|
license: "MIT",
|
|
@@ -2203,9 +2203,42 @@ var writeManifest = (root, manifest) => {
|
|
|
2203
2203
|
writeFileSync(manifestPath(root), JSON.stringify(manifest, null, 2));
|
|
2204
2204
|
};
|
|
2205
2205
|
|
|
2206
|
-
// src/
|
|
2207
|
-
import {
|
|
2206
|
+
// src/config.ts
|
|
2207
|
+
import { homedir } from "node:os";
|
|
2208
2208
|
import { join as join2 } from "node:path";
|
|
2209
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2210
|
+
var DEFAULT_API = "https://le.codes";
|
|
2211
|
+
var CONFIG_DIR = join2(homedir(), ".lecodes");
|
|
2212
|
+
var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
|
|
2213
|
+
var loadConfig = () => {
|
|
2214
|
+
let stored = {};
|
|
2215
|
+
try {
|
|
2216
|
+
stored = JSON.parse(readFileSync2(CONFIG_FILE, "utf8"));
|
|
2217
|
+
} catch {}
|
|
2218
|
+
return {
|
|
2219
|
+
apiUrl: process.env.LECODES_API ?? stored.apiUrl,
|
|
2220
|
+
token: process.env.LECODES_TOKEN ?? stored.token
|
|
2221
|
+
};
|
|
2222
|
+
};
|
|
2223
|
+
var saveConfig = (config) => {
|
|
2224
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
2225
|
+
writeFileSync2(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
2226
|
+
};
|
|
2227
|
+
var normalizeApiUrl = (url) => url.trim().replace(/\/+$/, "");
|
|
2228
|
+
var requireToken = (config) => {
|
|
2229
|
+
if (!config.token)
|
|
2230
|
+
throw new CliError("Not logged in. Run `lecodes login` first.");
|
|
2231
|
+
return config.token;
|
|
2232
|
+
};
|
|
2233
|
+
var requireApiUrl = (config) => {
|
|
2234
|
+
if (!config.apiUrl)
|
|
2235
|
+
throw new CliError("No server URL configured. Run `lecodes login` first.");
|
|
2236
|
+
return normalizeApiUrl(config.apiUrl);
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
// src/ignore.ts
|
|
2240
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2241
|
+
import { join as join3 } from "node:path";
|
|
2209
2242
|
var BUILTIN_PATTERNS = ["node_modules/", ".git/", "_*", "*.log", ".DS_Store"];
|
|
2210
2243
|
var DEFAULT_IGNORE_FILE = `# Files matched here are NOT pushed to LeCodes.
|
|
2211
2244
|
# Only untracked files are affected — assets already in the project stay tracked
|
|
@@ -2286,19 +2319,19 @@ var buildGlobMatcher = (patterns) => {
|
|
|
2286
2319
|
};
|
|
2287
2320
|
var loadIgnore = (root) => {
|
|
2288
2321
|
const lines = [...BUILTIN_PATTERNS];
|
|
2289
|
-
const file =
|
|
2322
|
+
const file = join3(root, ".lecodesignore");
|
|
2290
2323
|
if (existsSync2(file)) {
|
|
2291
2324
|
try {
|
|
2292
|
-
lines.push(...
|
|
2325
|
+
lines.push(...readFileSync3(file, "utf8").split(/\r?\n/));
|
|
2293
2326
|
} catch {}
|
|
2294
2327
|
}
|
|
2295
2328
|
const rules = lines.map(compile).filter((r) => r !== null);
|
|
2296
2329
|
return buildMatcher(rules);
|
|
2297
2330
|
};
|
|
2298
2331
|
var writeDefaultIgnore = (root) => {
|
|
2299
|
-
const file =
|
|
2332
|
+
const file = join3(root, ".lecodesignore");
|
|
2300
2333
|
if (!existsSync2(file))
|
|
2301
|
-
|
|
2334
|
+
writeFileSync3(file, DEFAULT_IGNORE_FILE);
|
|
2302
2335
|
};
|
|
2303
2336
|
|
|
2304
2337
|
// src/commands/compile.ts
|
|
@@ -2370,39 +2403,6 @@ var downloadBlob = async (apiUrl, src) => {
|
|
|
2370
2403
|
return new Uint8Array(await resp.arrayBuffer());
|
|
2371
2404
|
};
|
|
2372
2405
|
|
|
2373
|
-
// src/config.ts
|
|
2374
|
-
import { homedir } from "node:os";
|
|
2375
|
-
import { join as join3 } from "node:path";
|
|
2376
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2377
|
-
var DEFAULT_API = "https://le.codes";
|
|
2378
|
-
var CONFIG_DIR = join3(homedir(), ".lecodes");
|
|
2379
|
-
var CONFIG_FILE = join3(CONFIG_DIR, "config.json");
|
|
2380
|
-
var loadConfig = () => {
|
|
2381
|
-
let stored = {};
|
|
2382
|
-
try {
|
|
2383
|
-
stored = JSON.parse(readFileSync3(CONFIG_FILE, "utf8"));
|
|
2384
|
-
} catch {}
|
|
2385
|
-
return {
|
|
2386
|
-
apiUrl: process.env.LECODES_API ?? stored.apiUrl,
|
|
2387
|
-
token: process.env.LECODES_TOKEN ?? stored.token
|
|
2388
|
-
};
|
|
2389
|
-
};
|
|
2390
|
-
var saveConfig = (config) => {
|
|
2391
|
-
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
2392
|
-
writeFileSync3(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
2393
|
-
};
|
|
2394
|
-
var normalizeApiUrl = (url) => url.trim().replace(/\/+$/, "");
|
|
2395
|
-
var requireToken = (config) => {
|
|
2396
|
-
if (!config.token)
|
|
2397
|
-
throw new CliError("Not logged in. Run `lecodes login` first.");
|
|
2398
|
-
return config.token;
|
|
2399
|
-
};
|
|
2400
|
-
var requireApiUrl = (config) => {
|
|
2401
|
-
if (!config.apiUrl)
|
|
2402
|
-
throw new CliError("No server URL configured. Run `lecodes login` first.");
|
|
2403
|
-
return normalizeApiUrl(config.apiUrl);
|
|
2404
|
-
};
|
|
2405
|
-
|
|
2406
2406
|
// src/compile/collect.ts
|
|
2407
2407
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
2408
2408
|
import { basename } from "node:path";
|
|
@@ -3349,8 +3349,12 @@ class ViewController: LeCodesViewController {
|
|
|
3349
3349
|
|
|
3350
3350
|
registerBundledPlugins(in: engine)
|
|
3351
3351
|
|
|
3352
|
-
|
|
3353
|
-
|
|
3352
|
+
// OTA (native, no JS involved): start the check FIRST — even a bundle that crashes
|
|
3353
|
+
// on boot can never block its own fix — then run the newest local bundle (a
|
|
3354
|
+
// downloaded update, or the embedded app.js). New code applies on the next launch.
|
|
3355
|
+
// Configured via app.json "update" (→ Info.plist LeCodesUpdateURL); offline: no-op.
|
|
3356
|
+
LeCodesUpdater.checkForUpdate()
|
|
3357
|
+
guard let code = LeCodesUpdater.activeBundle() else {
|
|
3354
3358
|
print("LeCodes: bundled app.js is missing — run \`lecodes app sync\`")
|
|
3355
3359
|
return
|
|
3356
3360
|
}
|
|
@@ -3358,13 +3362,18 @@ class ViewController: LeCodesViewController {
|
|
|
3358
3362
|
}
|
|
3359
3363
|
}
|
|
3360
3364
|
`;
|
|
3361
|
-
var infoPlistTemplate = (pluginKeys) => {
|
|
3365
|
+
var infoPlistTemplate = (pluginKeys, updateUrl) => {
|
|
3366
|
+
const xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3362
3367
|
const keys = Object.keys(pluginKeys).sort();
|
|
3363
3368
|
const pluginBlock = keys.length === 0 ? "" : `
|
|
3364
3369
|
<!-- lecodes:plugins — merged from installed plugin manifests by \`lecodes app sync\` -->
|
|
3365
3370
|
${keys.map((k) => ` <key>${k}</key>
|
|
3366
3371
|
<string>${pluginKeys[k]}</string>`).join(`
|
|
3367
3372
|
`)}`;
|
|
3373
|
+
const updateBlock = !updateUrl ? "" : `
|
|
3374
|
+
<!-- lecodes:update — native OTA source (app.json "update"); LeCodesUpdater checks it at launch -->
|
|
3375
|
+
<key>LeCodesUpdateURL</key>
|
|
3376
|
+
<string>${xml(updateUrl)}</string>`;
|
|
3368
3377
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3369
3378
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3370
3379
|
<plist version="1.0">
|
|
@@ -3385,7 +3394,7 @@ ${keys.map((k) => ` <key>${k}</key>
|
|
|
3385
3394
|
</dict>
|
|
3386
3395
|
</array>
|
|
3387
3396
|
</dict>
|
|
3388
|
-
</dict>${pluginBlock}
|
|
3397
|
+
</dict>${pluginBlock}${updateBlock}
|
|
3389
3398
|
</dict>
|
|
3390
3399
|
</plist>
|
|
3391
3400
|
`;
|
|
@@ -3484,8 +3493,9 @@ var runtimePackageTemplate = (sdkVersion, variant, plugins) => {
|
|
|
3484
3493
|
return `// swift-tools-version:5.9
|
|
3485
3494
|
//
|
|
3486
3495
|
// GENERATED by lecodes — regenerated by \`lecodes app sync\`; do not edit.
|
|
3487
|
-
// Owns the app's native dependency graph: the LeCodesSDK variant (
|
|
3488
|
-
//
|
|
3496
|
+
// Owns the app's native dependency graph: the LeCodesSDK variant (auto-picked from what
|
|
3497
|
+
// the compiled bundle uses, or forced via app.json ios.variant) and one vendored target
|
|
3498
|
+
// per installed plugin (see plugins.lock.json).
|
|
3489
3499
|
|
|
3490
3500
|
import PackageDescription
|
|
3491
3501
|
|
|
@@ -3570,6 +3580,24 @@ CLI-owned — regenerated on every sync, hand edits are overwritten:
|
|
|
3570
3580
|
- the version values inside \`App.xcodeproj/project.pbxproj\` (from app.json)
|
|
3571
3581
|
- this README
|
|
3572
3582
|
|
|
3583
|
+
## SDK variant
|
|
3584
|
+
|
|
3585
|
+
Sync links the smallest SDK product that covers what the compiled bundle actually uses:
|
|
3586
|
+
\`core\` (UI only), \`2d\` (+ 2D engine with physics), \`3d\` (+ 3D/AR engine with physics,
|
|
3587
|
+
~17 MB heavier), or \`full\` (everything). Adding your first 3D scene or 2D game to the
|
|
3588
|
+
project switches it automatically on the next sync; set \`ios.variant\` in \`../app.json\`
|
|
3589
|
+
to pin one instead.
|
|
3590
|
+
|
|
3591
|
+
## Over-the-air updates
|
|
3592
|
+
|
|
3593
|
+
With \`"update": true\` in \`../app.json\` (server-backed projects; or
|
|
3594
|
+
\`"update": {"url": "https://…/app.js"}\` for any static hosting), the app checks that URL
|
|
3595
|
+
at launch — natively, before any JS runs, so a broken bundle can never block its own fix.
|
|
3596
|
+
Unchanged code costs a ~0-byte 304; new code downloads in the background and runs on the
|
|
3597
|
+
NEXT launch. Offline it's a silent no-op — the app always launches from a local bundle.
|
|
3598
|
+
An App Store update always beats a cached OTA bundle. JS only: adding/removing native
|
|
3599
|
+
plugins still needs a store release.
|
|
3600
|
+
|
|
3573
3601
|
## Config
|
|
3574
3602
|
|
|
3575
3603
|
Identity lives in \`../app.json\` (name, version, plugins, ios.bundleId / teamId / variant /
|
|
@@ -3578,7 +3606,8 @@ buildNumber) — edit it there, then re-run \`lecodes app sync\`. Plugins are ma
|
|
|
3578
3606
|
`;
|
|
3579
3607
|
|
|
3580
3608
|
// src/commands/app.ts
|
|
3581
|
-
var SDK_PACKAGE_VERSION = "1.0
|
|
3609
|
+
var SDK_PACKAGE_VERSION = "1.2.0";
|
|
3610
|
+
var SDK_VARIANTS = ["core", "2d", "3d", "full"];
|
|
3582
3611
|
var FIRST_PARTY_PLUGINS = {
|
|
3583
3612
|
camera: "letary/lecodes-plugins",
|
|
3584
3613
|
"qr-scanner": "letary/lecodes-plugins"
|
|
@@ -3679,7 +3708,7 @@ var slugify = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+
|
|
|
3679
3708
|
var appInit = async (args) => {
|
|
3680
3709
|
const platform = args._[1];
|
|
3681
3710
|
if (platform !== "ios") {
|
|
3682
|
-
throw new CliError(platform ? `Unsupported platform "${platform}" — only "ios" for now.` : "Usage: lecodes app init ios [--name <display name>] [--bundle-id <id>] [--team <team id>] [--variant core|full]");
|
|
3711
|
+
throw new CliError(platform ? `Unsupported platform "${platform}" — only "ios" for now.` : "Usage: lecodes app init ios [--name <display name>] [--bundle-id <id>] [--team <team id>] [--variant auto|core|2d|3d|full]");
|
|
3683
3712
|
}
|
|
3684
3713
|
const root = projectRoot();
|
|
3685
3714
|
const iosDir = join11(root, "ios");
|
|
@@ -3687,9 +3716,10 @@ var appInit = async (args) => {
|
|
|
3687
3716
|
throw new CliError("ios/ already exists — run `lecodes app sync` to update it.");
|
|
3688
3717
|
}
|
|
3689
3718
|
const existing = readJson(join11(root, "app.json")) ?? {};
|
|
3690
|
-
const variant = flagStr(args, "variant") ?? existing.ios?.variant ?? "
|
|
3691
|
-
if (variant !== "
|
|
3692
|
-
throw new CliError(`--variant must be "
|
|
3719
|
+
const variant = flagStr(args, "variant") ?? existing.ios?.variant ?? "auto";
|
|
3720
|
+
if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
|
|
3721
|
+
throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
|
|
3722
|
+
}
|
|
3693
3723
|
const name = flagStr(args, "name") ?? existing.name ?? basename4(root);
|
|
3694
3724
|
const config = {
|
|
3695
3725
|
...existing,
|
|
@@ -3739,7 +3769,7 @@ ios/
|
|
|
3739
3769
|
write("App/Assets.xcassets/Contents.json", ASSETS_ROOT_JSON);
|
|
3740
3770
|
write("App/Assets.xcassets/AccentColor.colorset/Contents.json", ACCENT_COLOR_JSON);
|
|
3741
3771
|
write("App/Assets.xcassets/AppIcon.appiconset/Contents.json", APP_ICON_JSON);
|
|
3742
|
-
write("LeCodesRuntime/Package.swift", runtimePackageTemplate(SDK_PACKAGE_VERSION, variant, []));
|
|
3772
|
+
write("LeCodesRuntime/Package.swift", runtimePackageTemplate(SDK_PACKAGE_VERSION, variant === "auto" ? "core" : variant, []));
|
|
3743
3773
|
write("LeCodesRuntime/Sources/LeCodesRuntime/LeCodesRuntime.swift", runtimeSwiftTemplate([]));
|
|
3744
3774
|
log("");
|
|
3745
3775
|
success(`iOS shell for ${c.bold(name)} ready.`);
|
|
@@ -3747,6 +3777,76 @@ ios/
|
|
|
3747
3777
|
log(` ${c.bold("open ios/App.xcodeproj")} then run on your device from Xcode`);
|
|
3748
3778
|
log(` App icon: drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/`);
|
|
3749
3779
|
};
|
|
3780
|
+
var detectEngines = (bundleJs) => {
|
|
3781
|
+
const lines = [];
|
|
3782
|
+
for (const line of bundleJs.split(`
|
|
3783
|
+
`)) {
|
|
3784
|
+
if (!line.startsWith("//"))
|
|
3785
|
+
break;
|
|
3786
|
+
lines.push(line);
|
|
3787
|
+
}
|
|
3788
|
+
const header = lines.join(`
|
|
3789
|
+
`);
|
|
3790
|
+
return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
|
|
3791
|
+
};
|
|
3792
|
+
var resolveVariant = (requested, bundleJs) => {
|
|
3793
|
+
const uses = detectEngines(bundleJs);
|
|
3794
|
+
const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
|
|
3795
|
+
if (requested === "auto")
|
|
3796
|
+
return { variant: detected, detected };
|
|
3797
|
+
const variant = requested;
|
|
3798
|
+
const missing = [
|
|
3799
|
+
uses.gl && variant !== "3d" && variant !== "full" ? "3D" : null,
|
|
3800
|
+
uses.g2 && variant !== "2d" && variant !== "full" ? "2D" : null
|
|
3801
|
+
].filter(Boolean);
|
|
3802
|
+
if (missing.length > 0) {
|
|
3803
|
+
warn(`project uses the ${missing.join(" + ")} engine but ios.variant forces "${variant}" — those scenes won't run on device; remove ios.variant to auto-select "${detected}".`);
|
|
3804
|
+
}
|
|
3805
|
+
return { variant, detected };
|
|
3806
|
+
};
|
|
3807
|
+
var ensureSdkResourcesBundle = async () => {
|
|
3808
|
+
const override = process.env.LECODES_SDK_RESOURCES;
|
|
3809
|
+
if (override) {
|
|
3810
|
+
note(`Using local resources bundle ${override} (LECODES_SDK_RESOURCES)`);
|
|
3811
|
+
return resolve2(override);
|
|
3812
|
+
}
|
|
3813
|
+
const dir = join11(homedir2(), ".lecodes", "sdk-resources", SDK_PACKAGE_VERSION);
|
|
3814
|
+
const bundle = join11(dir, "LeCodesSDKResources.bundle");
|
|
3815
|
+
if (existsSync8(bundle))
|
|
3816
|
+
return bundle;
|
|
3817
|
+
const url = `https://github.com/letary/lecodes-ios-sdk/releases/download/${SDK_PACKAGE_VERSION}/LeCodesSDKResources.bundle.zip`;
|
|
3818
|
+
note(`Downloading LeCodesSDKResources.bundle (SDK ${SDK_PACKAGE_VERSION})…`);
|
|
3819
|
+
const res = await fetch(url);
|
|
3820
|
+
if (!res.ok)
|
|
3821
|
+
throw new CliError(`Resources bundle download failed (HTTP ${res.status}) — ${url}`);
|
|
3822
|
+
mkdirSync5(dir, { recursive: true });
|
|
3823
|
+
const zip = join11(dir, "LeCodesSDKResources.bundle.zip");
|
|
3824
|
+
writeFileSync7(zip, new Uint8Array(await res.arrayBuffer()));
|
|
3825
|
+
execFileSync("ditto", ["-x", "-k", zip, dir]);
|
|
3826
|
+
rmSync(zip);
|
|
3827
|
+
if (!existsSync8(bundle))
|
|
3828
|
+
throw new CliError(`No LeCodesSDKResources.bundle inside the release asset ${url}`);
|
|
3829
|
+
return bundle;
|
|
3830
|
+
};
|
|
3831
|
+
var resolveUpdateUrl = (update, root) => {
|
|
3832
|
+
if (!update)
|
|
3833
|
+
return;
|
|
3834
|
+
let url = typeof update === "string" ? update : typeof update === "object" ? update.url : undefined;
|
|
3835
|
+
if (!url) {
|
|
3836
|
+
let manifest = null;
|
|
3837
|
+
try {
|
|
3838
|
+
manifest = readManifest(root);
|
|
3839
|
+
} catch {}
|
|
3840
|
+
if (!manifest) {
|
|
3841
|
+
warn(`app.json "update": true needs a server-backed project — use "update": {"url": "https://…/app.js"} for custom hosting. OTA left unconfigured.`);
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3844
|
+
url = `${requireApiUrl(loadConfig())}/code/${manifest.uuid}.js`;
|
|
3845
|
+
}
|
|
3846
|
+
if (!url.startsWith("https://"))
|
|
3847
|
+
warn(`update URL is not https — iOS blocks it on device (ATS): ${url}`);
|
|
3848
|
+
return url;
|
|
3849
|
+
};
|
|
3750
3850
|
var appSync = async (_args) => {
|
|
3751
3851
|
const root = projectRoot();
|
|
3752
3852
|
const config = readJson(join11(root, "app.json"));
|
|
@@ -3754,7 +3854,10 @@ var appSync = async (_args) => {
|
|
|
3754
3854
|
if (!config?.ios || !existsSync8(iosDir)) {
|
|
3755
3855
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
3756
3856
|
}
|
|
3757
|
-
const
|
|
3857
|
+
const requestedVariant = config.ios.variant ?? "auto";
|
|
3858
|
+
if (requestedVariant !== "auto" && !SDK_VARIANTS.includes(requestedVariant)) {
|
|
3859
|
+
throw new CliError(`app.json ios.variant must be "auto" or one of ${SDK_VARIANTS.join(", ")} (got "${requestedVariant}").`);
|
|
3860
|
+
}
|
|
3758
3861
|
const lockPath = join11(root, "plugins.lock.json");
|
|
3759
3862
|
const oldLock = readJson(lockPath)?.plugins ?? {};
|
|
3760
3863
|
const pluginSpecs = Object.entries(config.plugins ?? {});
|
|
@@ -3789,18 +3892,6 @@ var appSync = async (_args) => {
|
|
|
3789
3892
|
log(`${c.yellow("removed")} ios/LeCodesRuntime/Sources/${entry} (plugin uninstalled)`);
|
|
3790
3893
|
}
|
|
3791
3894
|
}
|
|
3792
|
-
writeFileSync7(join11(iosDir, "README.md"), IOS_README);
|
|
3793
|
-
writeFileSync7(join11(iosDir, "LeCodesRuntime", "Package.swift"), runtimePackageTemplate(SDK_PACKAGE_VERSION, variant, runtimePlugins));
|
|
3794
|
-
writeFileSync7(join11(sourcesDir, "LeCodesRuntime", "LeCodesRuntime.swift"), runtimeSwiftTemplate(runtimePlugins));
|
|
3795
|
-
const mergedKeys = {};
|
|
3796
|
-
for (const [key, values] of Object.entries(infoValues))
|
|
3797
|
-
mergedKeys[key] = [...new Set(values)].join(" ");
|
|
3798
|
-
writeFileSync7(join11(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys));
|
|
3799
|
-
if (pluginSpecs.length > 0 || existsSync8(lockPath))
|
|
3800
|
-
writeJson(lockPath, { plugins: newLock });
|
|
3801
|
-
const pbxPath = join11(iosDir, "App.xcodeproj", "project.pbxproj");
|
|
3802
|
-
const pbx = readFileSync10(pbxPath, "utf8").replace(/MARKETING_VERSION = [^;]+;/g, `MARKETING_VERSION = ${config.version ?? "1.0.0"};`).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${config.ios.buildNumber ?? 1};`);
|
|
3803
|
-
writeFileSync7(pbxPath, pbx);
|
|
3804
3895
|
const remotePatterns = config.resources?.remote ?? [];
|
|
3805
3896
|
if (!Array.isArray(remotePatterns) || remotePatterns.some((p) => typeof p !== "string")) {
|
|
3806
3897
|
throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
|
|
@@ -3810,6 +3901,23 @@ var appSync = async (_args) => {
|
|
|
3810
3901
|
mkdirSync5(resourcesDir, { recursive: true });
|
|
3811
3902
|
const resources = await compileShell({ _: [], flags: { o: join11(resourcesDir, "app.js") } }, { remotePatterns });
|
|
3812
3903
|
const bundleJs = readFileSync10(join11(resourcesDir, "app.js"), "utf8");
|
|
3904
|
+
const { variant, detected } = resolveVariant(requestedVariant, bundleJs);
|
|
3905
|
+
const WHY = {
|
|
3906
|
+
core: "UI only",
|
|
3907
|
+
"2d": "uses the 2D engine",
|
|
3908
|
+
"3d": "uses the 3D engine",
|
|
3909
|
+
full: "uses the 3D + 2D engines"
|
|
3910
|
+
};
|
|
3911
|
+
log(`${c.green("variant")} ${variant}${requestedVariant === "auto" ? ` (auto: ${WHY[detected]})` : " (forced by ios.variant)"}`);
|
|
3912
|
+
const sdkProvided = new Set;
|
|
3913
|
+
if (variant === "3d" || variant === "full") {
|
|
3914
|
+
const sdkResources = await ensureSdkResourcesBundle();
|
|
3915
|
+
cpSync(sdkResources, join11(resourcesDir, "LeCodesSDKResources.bundle"), { recursive: true });
|
|
3916
|
+
for (const f2 of readdirSync4(sdkResources))
|
|
3917
|
+
if (f2 !== "Info.plist")
|
|
3918
|
+
sdkProvided.add(f2);
|
|
3919
|
+
log(`${c.green("resources")} LeCodesSDKResources.bundle (filament) → ios/App/Resources/`);
|
|
3920
|
+
}
|
|
3813
3921
|
const preload = /^\/\/ preload: (.*)$/m.exec(bundleJs)?.[1].split(";").map((s) => s.trim()).filter(Boolean) ?? [];
|
|
3814
3922
|
const IMPLICIT_PRELOADS = new Set(["uberarchive", "capture"]);
|
|
3815
3923
|
const byName = new Map;
|
|
@@ -3826,7 +3934,8 @@ var appSync = async (_args) => {
|
|
|
3826
3934
|
continue;
|
|
3827
3935
|
const candidates = byName.get(name) ?? [];
|
|
3828
3936
|
if (candidates.length === 0) {
|
|
3829
|
-
|
|
3937
|
+
if (!sdkProvided.has(name))
|
|
3938
|
+
warn(`bundle needs local asset "${name}" but no project file provides it — it won't load on device`);
|
|
3830
3939
|
continue;
|
|
3831
3940
|
}
|
|
3832
3941
|
if (candidates.length > 1)
|
|
@@ -3839,6 +3948,21 @@ var appSync = async (_args) => {
|
|
|
3839
3948
|
const streamed = resources.filter((r) => r.remote);
|
|
3840
3949
|
if (streamed.length > 0)
|
|
3841
3950
|
log(`${c.green("remote")} ${streamed.length} resource(s) stay on the server (resources.remote): ${streamed.map((r) => r.path).join(", ")}`);
|
|
3951
|
+
writeFileSync7(join11(iosDir, "README.md"), IOS_README);
|
|
3952
|
+
writeFileSync7(join11(iosDir, "LeCodesRuntime", "Package.swift"), runtimePackageTemplate(SDK_PACKAGE_VERSION, variant, runtimePlugins));
|
|
3953
|
+
writeFileSync7(join11(sourcesDir, "LeCodesRuntime", "LeCodesRuntime.swift"), runtimeSwiftTemplate(runtimePlugins));
|
|
3954
|
+
const mergedKeys = {};
|
|
3955
|
+
for (const [key, values] of Object.entries(infoValues))
|
|
3956
|
+
mergedKeys[key] = [...new Set(values)].join(" ");
|
|
3957
|
+
const updateUrl = resolveUpdateUrl(config.update, root);
|
|
3958
|
+
writeFileSync7(join11(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl));
|
|
3959
|
+
if (updateUrl)
|
|
3960
|
+
log(`${c.green("update")} OTA source ${updateUrl} — checked natively at launch, new code applies next launch`);
|
|
3961
|
+
if (pluginSpecs.length > 0 || existsSync8(lockPath))
|
|
3962
|
+
writeJson(lockPath, { plugins: newLock });
|
|
3963
|
+
const pbxPath = join11(iosDir, "App.xcodeproj", "project.pbxproj");
|
|
3964
|
+
const pbx = readFileSync10(pbxPath, "utf8").replace(/MARKETING_VERSION = [^;]+;/g, `MARKETING_VERSION = ${config.version ?? "1.0.0"};`).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${config.ios.buildNumber ?? 1};`);
|
|
3965
|
+
writeFileSync7(pbxPath, pbx);
|
|
3842
3966
|
log("");
|
|
3843
3967
|
success(`Shell synced: app.js + ${runtimePlugins.length} plugin(s), variant ${variant}, v${config.version ?? "1.0.0"} (${config.ios.buildNumber ?? 1}).`);
|
|
3844
3968
|
note("Build & run from Xcode (open ios/App.xcodeproj) — user files in ios/App/*.swift were not touched.");
|
|
@@ -3949,10 +4073,24 @@ var appDoctor = async (_args) => {
|
|
|
3949
4073
|
check(hasIcon, "App icon present", "drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/ (App Store requires it)", false);
|
|
3950
4074
|
const bundle = join11(root, "ios", "App", "Resources", "app.js");
|
|
3951
4075
|
check(existsSync8(bundle), "Bundled app.js", "run `lecodes app sync`");
|
|
4076
|
+
const requestedVariant = config.ios.variant ?? "auto";
|
|
4077
|
+
const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
|
|
4078
|
+
check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
|
|
4079
|
+
if (existsSync8(bundle) && validVariant) {
|
|
4080
|
+
const { variant } = resolveVariant(requestedVariant, readFileSync10(bundle, "utf8"));
|
|
4081
|
+
const pkg = readFileSync10(join11(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
|
|
4082
|
+
const needsResources = variant === "3d" || variant === "full";
|
|
4083
|
+
const hasResources = existsSync8(join11(root, "ios", "App", "Resources", "LeCodesSDKResources.bundle"));
|
|
4084
|
+
check(pkg.includes(`"LeCodesSDK-${variant}"`) && (!needsResources || hasResources), `SDK variant ${variant}${needsResources ? " (+ filament resources)" : ""}`, "run `lecodes app sync`");
|
|
4085
|
+
}
|
|
3952
4086
|
const lock = readJson(join11(root, "plugins.lock.json"))?.plugins ?? {};
|
|
3953
4087
|
const wanted = Object.keys(config.plugins ?? {});
|
|
3954
4088
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
3955
4089
|
check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
|
|
4090
|
+
if (config.update) {
|
|
4091
|
+
const plist = readFileSync10(join11(root, "ios", "App", "Info.plist"), "utf8");
|
|
4092
|
+
check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
|
|
4093
|
+
}
|
|
3956
4094
|
const asc = readJson(join11(homedir2(), ".lecodes", "asc.json"));
|
|
3957
4095
|
check(!!asc?.keyPath, "App Store Connect API key configured", "pass --key-path/--key-id/--issuer-id to `lecodes app upload` once to store it", false);
|
|
3958
4096
|
log("");
|
|
@@ -7211,13 +7349,17 @@ ${c.bold("Commands:")}
|
|
|
7211
7349
|
--name <s> display name (default: app.json / folder name)
|
|
7212
7350
|
--bundle-id <id> iOS bundle identifier
|
|
7213
7351
|
--team <id> Apple development team id
|
|
7214
|
-
--variant <v> SDK variant:
|
|
7352
|
+
--variant <v> SDK variant: auto (default — sync picks the
|
|
7353
|
+
smallest of core/2d/3d/full covering what
|
|
7354
|
+
the bundle uses), or one of those to pin
|
|
7215
7355
|
app sync Make the shell match the project: compile the bundle + embed
|
|
7216
7356
|
its assets into ios/App/Resources/ (offline by default, also
|
|
7217
7357
|
for server projects; app.json "resources": {"remote": [globs]}
|
|
7218
|
-
keeps heavy assets streaming from the server),
|
|
7358
|
+
keeps heavy assets streaming from the server), pick the SDK
|
|
7359
|
+
variant from the bundle's engine use, vendor app.json
|
|
7219
7360
|
plugins (pinned in plugins.lock.json), regenerate the
|
|
7220
|
-
CLI-owned shell files
|
|
7361
|
+
CLI-owned shell files. app.json "update": true (or
|
|
7362
|
+
{"url": …}) enables native OTA JS updates at launch
|
|
7221
7363
|
app plugin <list|add|remove> Manage native plugins in app.json (then run 'lecodes app sync')
|
|
7222
7364
|
add takes a known id (camera, qr-scanner) or github:owner/repo[@tag]
|
|
7223
7365
|
app open [ios] Open the native shell in its IDE (Xcode; Android Studio later)
|