lecodes-cli 0.8.1 → 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 +93 -47
- 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
|
`;
|
|
@@ -3579,6 +3588,16 @@ Sync links the smallest SDK product that covers what the compiled bundle actuall
|
|
|
3579
3588
|
project switches it automatically on the next sync; set \`ios.variant\` in \`../app.json\`
|
|
3580
3589
|
to pin one instead.
|
|
3581
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
|
+
|
|
3582
3601
|
## Config
|
|
3583
3602
|
|
|
3584
3603
|
Identity lives in \`../app.json\` (name, version, plugins, ios.bundleId / teamId / variant /
|
|
@@ -3587,7 +3606,7 @@ buildNumber) — edit it there, then re-run \`lecodes app sync\`. Plugins are ma
|
|
|
3587
3606
|
`;
|
|
3588
3607
|
|
|
3589
3608
|
// src/commands/app.ts
|
|
3590
|
-
var SDK_PACKAGE_VERSION = "1.
|
|
3609
|
+
var SDK_PACKAGE_VERSION = "1.2.0";
|
|
3591
3610
|
var SDK_VARIANTS = ["core", "2d", "3d", "full"];
|
|
3592
3611
|
var FIRST_PARTY_PLUGINS = {
|
|
3593
3612
|
camera: "letary/lecodes-plugins",
|
|
@@ -3809,6 +3828,25 @@ var ensureSdkResourcesBundle = async () => {
|
|
|
3809
3828
|
throw new CliError(`No LeCodesSDKResources.bundle inside the release asset ${url}`);
|
|
3810
3829
|
return bundle;
|
|
3811
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
|
+
};
|
|
3812
3850
|
var appSync = async (_args) => {
|
|
3813
3851
|
const root = projectRoot();
|
|
3814
3852
|
const config = readJson(join11(root, "app.json"));
|
|
@@ -3916,7 +3954,10 @@ var appSync = async (_args) => {
|
|
|
3916
3954
|
const mergedKeys = {};
|
|
3917
3955
|
for (const [key, values] of Object.entries(infoValues))
|
|
3918
3956
|
mergedKeys[key] = [...new Set(values)].join(" ");
|
|
3919
|
-
|
|
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`);
|
|
3920
3961
|
if (pluginSpecs.length > 0 || existsSync8(lockPath))
|
|
3921
3962
|
writeJson(lockPath, { plugins: newLock });
|
|
3922
3963
|
const pbxPath = join11(iosDir, "App.xcodeproj", "project.pbxproj");
|
|
@@ -4046,6 +4087,10 @@ var appDoctor = async (_args) => {
|
|
|
4046
4087
|
const wanted = Object.keys(config.plugins ?? {});
|
|
4047
4088
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
4048
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
|
+
}
|
|
4049
4094
|
const asc = readJson(join11(homedir2(), ".lecodes", "asc.json"));
|
|
4050
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);
|
|
4051
4096
|
log("");
|
|
@@ -7313,7 +7358,8 @@ ${c.bold("Commands:")}
|
|
|
7313
7358
|
keeps heavy assets streaming from the server), pick the SDK
|
|
7314
7359
|
variant from the bundle's engine use, vendor app.json
|
|
7315
7360
|
plugins (pinned in plugins.lock.json), regenerate the
|
|
7316
|
-
CLI-owned shell files
|
|
7361
|
+
CLI-owned shell files. app.json "update": true (or
|
|
7362
|
+
{"url": …}) enables native OTA JS updates at launch
|
|
7317
7363
|
app plugin <list|add|remove> Manage native plugins in app.json (then run 'lecodes app sync')
|
|
7318
7364
|
add takes a known id (camera, qr-scanner) or github:owner/repo[@tag]
|
|
7319
7365
|
app open [ios] Open the native shell in its IDE (Xcode; Android Studio later)
|