lecodes-cli 0.15.0 → 0.16.1

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 CHANGED
@@ -45212,7 +45212,7 @@ var require_main = __commonJS((exports, module) => {
45212
45212
  // package.json
45213
45213
  var package_default = {
45214
45214
  name: "lecodes-cli",
45215
- version: "0.15.0",
45215
+ version: "0.16.1",
45216
45216
  dependencies: {
45217
45217
  "@letary/chisel": "^0.6.0",
45218
45218
  jimp: "^1.6.1"
@@ -45226,8 +45226,8 @@ var package_default = {
45226
45226
  },
45227
45227
  peerDependencies: {
45228
45228
  "lecodes-design": "^0.8.0",
45229
- "lecodes-renderer": "^0.8.2",
45230
- "lecodes-3d-editor": "^0.2.0"
45229
+ "lecodes-renderer": "^0.8.3",
45230
+ "lecodes-3d-editor": "^0.3.0"
45231
45231
  },
45232
45232
  bin: {
45233
45233
  lecodes: "dist/index.js"
@@ -46324,6 +46324,23 @@ NEXT launch. Offline it's a silent no-op — the app always launches from a loca
46324
46324
  An App Store update always beats a cached OTA bundle. JS only: adding/removing native
46325
46325
  plugins still needs a store release.
46326
46326
 
46327
+ Shipping an update is just \`lecodes push --publish\` — no rebuild, no store review:
46328
+ running apps download the new code on their next launch and run it on the launch after.
46329
+
46330
+ Shells generated before OTA support have a \`ViewController.swift\` that reads app.js from
46331
+ \`Bundle.main\` directly, so downloaded updates would never run — and since \`App/*.swift\`
46332
+ is yours, sync won't rewrite it (\`lecodes app doctor\` flags this). Replace the
46333
+ \`Bundle.main\` load with:
46334
+
46335
+ \`\`\`swift
46336
+ LeCodesUpdater.checkForUpdate()
46337
+ guard let code = LeCodesUpdater.activeBundle() else {
46338
+ print("LeCodes: bundled app.js is missing — run \`lecodes app sync\`")
46339
+ return
46340
+ }
46341
+ engine.run(code)
46342
+ \`\`\`
46343
+
46327
46344
  ## Config
46328
46345
 
46329
46346
  Identity lives in \`../app.json\` (name, version, plugins, ios.bundleId / teamId / variant /
@@ -46401,6 +46418,34 @@ var ensureShellIgnored = (root, dir) => {
46401
46418
  `);
46402
46419
  log(`${c.green("updated")} .lecodesignore (+ ${dir}/)`);
46403
46420
  };
46421
+ var SDK_VARIANTS = ["core", "2d", "3d", "full"];
46422
+ var detectEngines = (bundleJs) => {
46423
+ const lines = [];
46424
+ for (const line of bundleJs.split(`
46425
+ `)) {
46426
+ if (!line.startsWith("//"))
46427
+ break;
46428
+ lines.push(line);
46429
+ }
46430
+ const header = lines.join(`
46431
+ `);
46432
+ return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
46433
+ };
46434
+ var resolveVariant = (requested, bundleJs, configKey = "ios.variant") => {
46435
+ const uses = detectEngines(bundleJs);
46436
+ const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
46437
+ if (requested === "auto")
46438
+ return { variant: detected, detected };
46439
+ const variant = requested;
46440
+ const missing = [
46441
+ uses.gl && variant !== "3d" && variant !== "full" ? "3D" : null,
46442
+ uses.g2 && variant !== "2d" && variant !== "full" ? "2D" : null
46443
+ ].filter(Boolean);
46444
+ if (missing.length > 0) {
46445
+ warn(`project uses the ${missing.join(" + ")} engine but ${configKey} forces "${variant}" — those scenes won't run on device; remove ${configKey} to auto-select "${detected}".`);
46446
+ }
46447
+ return { variant, detected };
46448
+ };
46404
46449
  var VERSION_RE = /^\d+\.\d+\.\d+$/;
46405
46450
  var cmpVersion = (a, b) => {
46406
46451
  const pa = a.split(".").map(Number);
@@ -46748,7 +46793,7 @@ validateDistributionUrl=true
46748
46793
  zipStoreBase=GRADLE_USER_HOME
46749
46794
  zipStorePath=wrapper/dists
46750
46795
  `;
46751
- var runtimeBuildGradleTemplate = (sdkVersion, pluginModules) => {
46796
+ var runtimeBuildGradleTemplate = (sdkVersion, pluginModules, sdkArtifact = "lecodes-sdk") => {
46752
46797
  const deps = SDK_RUNTIME_DEPS.map((d) => `
46753
46798
  api("${d}")`).join("");
46754
46799
  const plugins = pluginModules.map((m) => `
@@ -46780,8 +46825,10 @@ kotlin {
46780
46825
  }
46781
46826
 
46782
46827
  dependencies {
46783
- // Exact pin — the release asset URL is derived from this version (see settings.gradle.kts).
46784
- api("io.letary:lecodes-sdk:${sdkVersion}@aar")
46828
+ // Exact pin — the release asset URL is derived from this artifact + version (see
46829
+ // settings.gradle.kts). The artifact is the SDK variant sync picked (lecodes-sdk = full,
46830
+ // -3d / -2d / -core = smaller engine builds); every variant vends the same Kotlin API.
46831
+ api("io.letary:${sdkArtifact}:${sdkVersion}@aar")
46785
46832
  // The fat AAR carries no POM, so its own dependencies ride here explicitly.${deps}${plugins}
46786
46833
  }
46787
46834
  `;
@@ -46831,7 +46878,7 @@ ${push ? ` ${pushNs}.PushPlugin.setForeground(foreground)
46831
46878
  }
46832
46879
  `;
46833
46880
  };
46834
- var pluginBuildGradleTemplate = (id, module, namespace, sdkVersion, dependencies, aarNames, hasConsumerRules) => {
46881
+ var pluginBuildGradleTemplate = (id, module, namespace, sdkVersion, dependencies, aarNames, hasConsumerRules, sdkArtifact = "lecodes-sdk") => {
46835
46882
  const consumer = hasConsumerRules ? `
46836
46883
  consumerProguardFiles("consumer-rules.pro")` : "";
46837
46884
  const aars = aarNames.map((n2) => `
@@ -46866,7 +46913,9 @@ kotlin {
46866
46913
  }
46867
46914
 
46868
46915
  dependencies {
46869
- implementation("io.letary:lecodes-sdk:${sdkVersion}@aar")${sdkDeps}${aars}${manifestDeps}
46916
+ // Same artifact (SDK variant) as lecodes-runtime — two different variant coordinates in one
46917
+ // shell would put two copies of the SDK classes on the runtime classpath.
46918
+ implementation("io.letary:${sdkArtifact}:${sdkVersion}@aar")${sdkDeps}${aars}${manifestDeps}
46870
46919
  }
46871
46920
  `;
46872
46921
  };
@@ -46898,6 +46947,14 @@ in \`settings.gradle.kts\`. \`lecodes app update\` moves the pin (or set \`app.j
46898
46947
  \`android.sdk\`). The SDK AAR ships no POM, so its own dependencies are declared explicitly in
46899
46948
  the generated runtime build file.
46900
46949
 
46950
+ ## SDK variant
46951
+
46952
+ Sync links the smallest SDK artifact covering the engines the compiled bundle actually uses —
46953
+ \`lecodes-sdk\` (full: 3D+2D), \`lecodes-sdk-3d\`, \`lecodes-sdk-2d\` or \`lecodes-sdk-core\`
46954
+ (UI only) — so an app without 3D scenes doesn't ship Filament. If the project starts using an
46955
+ engine, sync switches automatically on the next run; set \`android.variant\` in \`../app.json\`
46956
+ to force one (needs lecodes-android-sdk >= 1.3.0; older releases ship only the full AAR).
46957
+
46901
46958
  ## App-local native code
46902
46959
 
46903
46960
  Drop Kotlin files into \`app/src/main/java/\` and register them in \`MainActivity\` after the
@@ -47561,6 +47618,8 @@ if "%OS%"=="Windows_NT" endlocal
47561
47618
  // src/commands/appAndroid.ts
47562
47619
  var ANDROID_SDK_REPO = "letary/lecodes-android-sdk";
47563
47620
  var ANDROID_SDK_MIN_VERSION = "1.2.0";
47621
+ var ANDROID_SDK_VARIANTS_MIN = "1.3.0";
47622
+ var variantArtifact = (variant) => variant === "full" ? "lecodes-sdk" : `lecodes-sdk-${variant}`;
47564
47623
  var ANDROID_SDK_URL_DEFAULT = `https://github.com/${ANDROID_SDK_REPO}/releases/download`;
47565
47624
  var sdkRepoUrl = () => process.env.LECODES_ANDROID_SDK_URL ?? ANDROID_SDK_URL_DEFAULT;
47566
47625
  var ANDROID_SDK_OPTS = { repo: ANDROID_SDK_REPO, minVersion: ANDROID_SDK_MIN_VERSION, configKey: "android.sdk" };
@@ -47568,7 +47627,7 @@ var shellSdkVersionAndroid = (androidDir) => {
47568
47627
  const path2 = join14(androidDir, "lecodes-runtime", "build.gradle.kts");
47569
47628
  if (!existsSync13(path2))
47570
47629
  return null;
47571
- const m = /io\.letary:lecodes-sdk:(\d+\.\d+\.\d+)@aar/.exec(readFileSync13(path2, "utf8"));
47630
+ const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(readFileSync13(path2, "utf8"));
47572
47631
  return m ? m[1] : null;
47573
47632
  };
47574
47633
  var GRADLE_WRAPPER_JAR_URL = "https://raw.githubusercontent.com/gradle/gradle/v8.13.0/gradle/wrapper/gradle-wrapper.jar";
@@ -47601,6 +47660,10 @@ var initAndroid = async (args) => {
47601
47660
  throw new CliError("android/ already exists — run `lecodes app sync` to update it.");
47602
47661
  }
47603
47662
  const existing = readJson(join14(root, "app.json")) ?? {};
47663
+ const variant = flagStr(args, "variant") ?? existing.android?.variant ?? "auto";
47664
+ if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
47665
+ throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
47666
+ }
47604
47667
  const sdkFlag = flagStr(args, "sdk") ?? existing.android?.sdk;
47605
47668
  const sdkVersion = await resolveSdkVersion(process.env.LECODES_ANDROID_SDK ?? sdkFlag ?? "latest", null, ANDROID_SDK_OPTS);
47606
47669
  const sdk = sdkFlag === "latest" ? "latest" : sdkVersion;
@@ -47615,6 +47678,7 @@ var initAndroid = async (args) => {
47615
47678
  applicationId: flagStr(args, "app-id") ?? existing.android?.applicationId ?? defaultAppId,
47616
47679
  versionCode: existing.android?.versionCode ?? 1,
47617
47680
  sdk,
47681
+ variant,
47618
47682
  ...existing.android?.signing ? { signing: existing.android.signing } : {}
47619
47683
  }
47620
47684
  };
@@ -47656,7 +47720,9 @@ var initAndroid = async (args) => {
47656
47720
  write("app/src/main/res/values/colors.xml", COLORS_XML);
47657
47721
  write("app/src/main/res/values/strings.xml", stringsXmlTemplate(name));
47658
47722
  write("app/src/main/res/drawable/ic_launcher.xml", IC_LAUNCHER_XML);
47659
- write("lecodes-runtime/build.gradle.kts", runtimeBuildGradleTemplate(sdkVersion, []));
47723
+ const initVariant = variant === "auto" ? "core" : variant;
47724
+ const initArtifact = cmpVersion(sdkVersion, ANDROID_SDK_VARIANTS_MIN) >= 0 ? variantArtifact(initVariant) : "lecodes-sdk";
47725
+ write("lecodes-runtime/build.gradle.kts", runtimeBuildGradleTemplate(sdkVersion, [], initArtifact));
47660
47726
  write("lecodes-runtime/src/main/kotlin/io/letary/lecodes/runtime/LeCodesRuntime.kt", runtimeKotlinTemplate([]));
47661
47727
  log("");
47662
47728
  log(`${c.green("sdk")} lecodes-android-sdk ${sdkVersion}${sdk === "latest" ? c.dim(" (android.sdk: latest — re-resolved on every sync)") : c.dim(" (pinned in app.json android.sdk — `lecodes app update` moves it)")}`);
@@ -47681,6 +47747,40 @@ var syncAndroid = async (root, config, prior) => {
47681
47747
  const linkedSdk = shellSdkVersionAndroid(androidDir);
47682
47748
  const sdkPin = process.env.LECODES_ANDROID_SDK ?? config.android?.sdk ?? linkedSdk ?? "latest";
47683
47749
  const sdkVersion = await resolveSdkVersion(sdkPin, linkedSdk, ANDROID_SDK_OPTS);
47750
+ const remotePatterns = config.resources?.remote ?? [];
47751
+ if (!Array.isArray(remotePatterns) || remotePatterns.some((p2) => typeof p2 !== "string")) {
47752
+ throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
47753
+ }
47754
+ const assetsDir = join14(androidDir, "app", "src", "main", "assets");
47755
+ rmrf(assetsDir);
47756
+ mkdirSync8(assetsDir, { recursive: true });
47757
+ let bundleJs;
47758
+ let resources;
47759
+ if (prior) {
47760
+ writeFileSync10(join14(assetsDir, "app.js"), prior.bundleJs);
47761
+ bundleJs = prior.bundleJs;
47762
+ resources = prior.resources;
47763
+ } else {
47764
+ resources = await compileShell({ _: [], flags: { o: join14(assetsDir, "app.js") } }, { remotePatterns });
47765
+ bundleJs = readFileSync13(join14(assetsDir, "app.js"), "utf8");
47766
+ }
47767
+ const requestedVariant = config.android?.variant ?? "auto";
47768
+ if (requestedVariant !== "auto" && !SDK_VARIANTS.includes(requestedVariant)) {
47769
+ throw new CliError(`app.json android.variant must be "auto" or one of ${SDK_VARIANTS.join(", ")} (got "${requestedVariant}").`);
47770
+ }
47771
+ const { variant, detected } = resolveVariant(requestedVariant, bundleJs, "android.variant");
47772
+ let sdkArtifact = variantArtifact(variant);
47773
+ if (sdkArtifact !== "lecodes-sdk" && cmpVersion(sdkVersion, ANDROID_SDK_VARIANTS_MIN) < 0) {
47774
+ warn(`lecodes-android-sdk ${sdkVersion} predates the variant matrix (${ANDROID_SDK_VARIANTS_MIN}) — linking the full lecodes-sdk AAR; run \`lecodes app update\` to pick up variant builds.`);
47775
+ sdkArtifact = "lecodes-sdk";
47776
+ }
47777
+ const WHY = {
47778
+ full: "3D + 2D engines in use",
47779
+ "3d": "3D in use, no 2D",
47780
+ "2d": "2D in use, no 3D",
47781
+ core: "no 3D/2D engine use detected"
47782
+ };
47783
+ log(`${c.green("variant")} ${variant} → ${sdkArtifact}${requestedVariant === "auto" ? c.dim(` (auto: ${WHY[detected]})`) : c.dim(" (forced by android.variant)")}`);
47684
47784
  const lockPath = join14(root, "plugins.lock.json");
47685
47785
  const oldLock = readJson(lockPath)?.plugins ?? {};
47686
47786
  const pluginSpecs = Object.entries(config.plugins ?? {});
@@ -47721,7 +47821,7 @@ var syncAndroid = async (root, config, prior) => {
47721
47821
  aarWrapperPaths.push(`:plugins:${module}:libs:${base}`);
47722
47822
  }
47723
47823
  const hasConsumerRules = existsSync13(join14(dest, "consumer-rules.pro"));
47724
- writeFileSync10(join14(dest, "build.gradle.kts"), pluginBuildGradleTemplate(id, module, namespace, sdkVersion, android.dependencies ?? [], aarNames, hasConsumerRules));
47824
+ writeFileSync10(join14(dest, "build.gradle.kts"), pluginBuildGradleTemplate(id, module, namespace, sdkVersion, android.dependencies ?? [], aarNames, hasConsumerRules, sdkArtifact));
47725
47825
  runtimePlugins.push({ id, module, register });
47726
47826
  newLock[id] = plugin.lock;
47727
47827
  log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → plugins/${module})`);
@@ -47733,23 +47833,6 @@ var syncAndroid = async (root, config, prior) => {
47733
47833
  log(`${c.yellow("removed")} android/plugins/${entry} (plugin uninstalled)`);
47734
47834
  }
47735
47835
  }
47736
- const remotePatterns = config.resources?.remote ?? [];
47737
- if (!Array.isArray(remotePatterns) || remotePatterns.some((p2) => typeof p2 !== "string")) {
47738
- throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
47739
- }
47740
- const assetsDir = join14(androidDir, "app", "src", "main", "assets");
47741
- rmrf(assetsDir);
47742
- mkdirSync8(assetsDir, { recursive: true });
47743
- let bundleJs;
47744
- let resources;
47745
- if (prior) {
47746
- writeFileSync10(join14(assetsDir, "app.js"), prior.bundleJs);
47747
- bundleJs = prior.bundleJs;
47748
- resources = prior.resources;
47749
- } else {
47750
- resources = await compileShell({ _: [], flags: { o: join14(assetsDir, "app.js") } }, { remotePatterns });
47751
- bundleJs = readFileSync13(join14(assetsDir, "app.js"), "utf8");
47752
- }
47753
47836
  const sdkWhy = sdkPin === "latest" ? "latest" : config.android?.sdk ? "pinned by android.sdk" : "from the shell — set android.sdk or run `lecodes app update`";
47754
47837
  log(`${c.green("sdk")} lecodes-android-sdk ${sdkVersion}${c.dim(` (${sdkWhy})`)}`);
47755
47838
  const preload = /^\/\/ preload: (.*)$/m.exec(bundleJs)?.[1].split(";").map((s2) => s2.trim()).filter(Boolean) ?? [];
@@ -47792,7 +47875,7 @@ var syncAndroid = async (root, config, prior) => {
47792
47875
  writeFileSync10(join14(androidDir, "settings.gradle.kts"), settingsGradleTemplate(slugify(config.name ?? basename4(root)), modules, aarWrapperPaths, sdkRepoUrl()));
47793
47876
  writeFileSync10(join14(androidDir, "build.gradle.kts"), rootBuildGradleTemplate([...gradlePlugins].map(([id, version]) => ({ id, version }))));
47794
47877
  writeFileSync10(join14(androidDir, "gradle.properties"), GRADLE_PROPERTIES);
47795
- writeFileSync10(join14(androidDir, "lecodes-runtime", "build.gradle.kts"), runtimeBuildGradleTemplate(sdkVersion, modules));
47878
+ writeFileSync10(join14(androidDir, "lecodes-runtime", "build.gradle.kts"), runtimeBuildGradleTemplate(sdkVersion, modules, sdkArtifact));
47796
47879
  const runtimeKt = join14(androidDir, "lecodes-runtime", "src", "main", "kotlin", "io", "letary", "lecodes", "runtime", "LeCodesRuntime.kt");
47797
47880
  mkdirSync8(dirname10(runtimeKt), { recursive: true });
47798
47881
  writeFileSync10(runtimeKt, runtimeKotlinTemplate(runtimePlugins, shellUuid));
@@ -47963,6 +48046,9 @@ var doctorAndroidChecks = async (root, config, check) => {
47963
48046
  check(javaMajor >= 17, `JDK ${javaMajor || "not found"}`, "install JDK 17+ (Android Studio ships one)");
47964
48047
  const hasSdkHome = !!(process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT) || existsSync13(join14(androidDir, "local.properties"));
47965
48048
  check(hasSdkHome, "Android SDK location", "set ANDROID_HOME or create android/local.properties");
48049
+ const requestedVariant = config.android?.variant ?? "auto";
48050
+ const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
48051
+ check(validVariant, `android.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
47966
48052
  const wrapperJar = join14(androidDir, "gradle", "wrapper", "gradle-wrapper.jar");
47967
48053
  check(existsSync13(wrapperJar) && statSync2(wrapperJar).size > 0, "Gradle wrapper jar", `run \`gradle wrapper --gradle-version ${GRADLE_VERSION}\` in android/ (or let Android Studio repair it)`);
47968
48054
  check(existsSync13(join14(androidDir, "app", "src", "main", "assets", "app.js")), "Bundled app.js", "run `lecodes app sync`");
@@ -48464,7 +48550,6 @@ Built with LeCodes Desktop ${version}.
48464
48550
  // src/commands/app.ts
48465
48551
  var SDK_REPO = "letary/lecodes-ios-sdk";
48466
48552
  var SDK_MIN_VERSION = "1.2.0";
48467
- var SDK_VARIANTS = ["core", "2d", "3d", "full"];
48468
48553
  var SDK_IDENTITY_VERSION = "1.6.0";
48469
48554
  var shellSdkVersion = (iosDir) => {
48470
48555
  const path2 = join17(iosDir, "LeCodesRuntime", "Package.swift");
@@ -48547,33 +48632,6 @@ var appInit = async (args) => {
48547
48632
  log(` ${c.bold("open ios/App.xcodeproj")} then run on your device from Xcode`);
48548
48633
  log(` App icon: set app.json "icon" to a square ≥1024px image — sync renders every size (or drop a PNG into App/Assets.xcassets/AppIcon.appiconset/)`);
48549
48634
  };
48550
- var detectEngines = (bundleJs) => {
48551
- const lines = [];
48552
- for (const line of bundleJs.split(`
48553
- `)) {
48554
- if (!line.startsWith("//"))
48555
- break;
48556
- lines.push(line);
48557
- }
48558
- const header = lines.join(`
48559
- `);
48560
- return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
48561
- };
48562
- var resolveVariant = (requested, bundleJs) => {
48563
- const uses = detectEngines(bundleJs);
48564
- const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
48565
- if (requested === "auto")
48566
- return { variant: detected, detected };
48567
- const variant = requested;
48568
- const missing = [
48569
- uses.gl && variant !== "3d" && variant !== "full" ? "3D" : null,
48570
- uses.g2 && variant !== "2d" && variant !== "full" ? "2D" : null
48571
- ].filter(Boolean);
48572
- if (missing.length > 0) {
48573
- 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}".`);
48574
- }
48575
- return { variant, detected };
48576
- };
48577
48635
  var ensureSdkResourcesBundle = async (sdkVersion) => {
48578
48636
  const override = process.env.LECODES_SDK_RESOURCES;
48579
48637
  if (override) {
@@ -48771,6 +48829,14 @@ var syncIos = async (root, config) => {
48771
48829
  writeFileSync13(join17(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl));
48772
48830
  if (updateUrl)
48773
48831
  log(`${c.green("update")} OTA source ${updateUrl} — checked natively at launch, new code applies next launch`);
48832
+ else if (!config.update) {
48833
+ let manifest = null;
48834
+ try {
48835
+ manifest = readManifest(root);
48836
+ } catch {}
48837
+ if (manifest)
48838
+ note(`tip: add "update": true to app.json for over-the-air JS updates — ship with \`lecodes push --publish\`, no store release`);
48839
+ }
48774
48840
  if (pluginSpecs.length > 0 || existsSync16(lockPath))
48775
48841
  writeJson(lockPath, { plugins: newLock });
48776
48842
  const pbxPath = join17(iosDir, "App.xcodeproj", "project.pbxproj");
@@ -49086,6 +49152,9 @@ var doctorIosChecks = async (root, config, check) => {
49086
49152
  if (config.update) {
49087
49153
  const plist = readFileSync15(join17(root, "ios", "App", "Info.plist"), "utf8");
49088
49154
  check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
49155
+ const appDir = join17(root, "ios", "App");
49156
+ const callsUpdater = readdirSync7(appDir).some((f3) => f3.endsWith(".swift") && readFileSync15(join17(appDir, f3), "utf8").includes("LeCodesUpdater.activeBundle"));
49157
+ check(callsUpdater, "App code launches via LeCodesUpdater", `no App/*.swift calls LeCodesUpdater.activeBundle() — downloaded updates would never run; see "Over-the-air updates" in ios/README.md for the ViewController snippet`, false);
49089
49158
  }
49090
49159
  const asc = readJson(join17(homedir6(), ".lecodes", "asc.json"));
49091
49160
  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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lecodes-cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "dependencies": {
5
5
  "@letary/chisel": "^0.6.0",
6
6
  "jimp": "^1.6.1"
@@ -14,8 +14,8 @@
14
14
  },
15
15
  "peerDependencies": {
16
16
  "lecodes-design": "^0.8.0",
17
- "lecodes-renderer": "^0.8.2",
18
- "lecodes-3d-editor": "^0.2.0"
17
+ "lecodes-renderer": "^0.8.3",
18
+ "lecodes-3d-editor": "^0.3.0"
19
19
  },
20
20
  "bin": {
21
21
  "lecodes": "dist/index.js"