lecodes-cli 0.8.0 → 0.8.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.
Files changed (2) hide show
  1. package/dist/index.js +121 -25
  2. 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.0",
2010
+ version: "0.8.1",
2011
2011
  description: "Clone, edit and push LeCodes projects from your machine.",
2012
2012
  type: "module",
2013
2013
  license: "MIT",
@@ -3484,8 +3484,9 @@ var runtimePackageTemplate = (sdkVersion, variant, plugins) => {
3484
3484
  return `// swift-tools-version:5.9
3485
3485
  //
3486
3486
  // GENERATED by lecodes — regenerated by \`lecodes app sync\`; do not edit.
3487
- // Owns the app's native dependency graph: the LeCodesSDK variant (app.json ios.variant)
3488
- // and one vendored target per installed plugin (see plugins.lock.json).
3487
+ // Owns the app's native dependency graph: the LeCodesSDK variant (auto-picked from what
3488
+ // the compiled bundle uses, or forced via app.json ios.variant) and one vendored target
3489
+ // per installed plugin (see plugins.lock.json).
3489
3490
 
3490
3491
  import PackageDescription
3491
3492
 
@@ -3570,6 +3571,14 @@ CLI-owned — regenerated on every sync, hand edits are overwritten:
3570
3571
  - the version values inside \`App.xcodeproj/project.pbxproj\` (from app.json)
3571
3572
  - this README
3572
3573
 
3574
+ ## SDK variant
3575
+
3576
+ Sync links the smallest SDK product that covers what the compiled bundle actually uses:
3577
+ \`core\` (UI only), \`2d\` (+ 2D engine with physics), \`3d\` (+ 3D/AR engine with physics,
3578
+ ~17 MB heavier), or \`full\` (everything). Adding your first 3D scene or 2D game to the
3579
+ project switches it automatically on the next sync; set \`ios.variant\` in \`../app.json\`
3580
+ to pin one instead.
3581
+
3573
3582
  ## Config
3574
3583
 
3575
3584
  Identity lives in \`../app.json\` (name, version, plugins, ios.bundleId / teamId / variant /
@@ -3578,7 +3587,8 @@ buildNumber) — edit it there, then re-run \`lecodes app sync\`. Plugins are ma
3578
3587
  `;
3579
3588
 
3580
3589
  // src/commands/app.ts
3581
- var SDK_PACKAGE_VERSION = "1.0.4";
3590
+ var SDK_PACKAGE_VERSION = "1.1.0";
3591
+ var SDK_VARIANTS = ["core", "2d", "3d", "full"];
3582
3592
  var FIRST_PARTY_PLUGINS = {
3583
3593
  camera: "letary/lecodes-plugins",
3584
3594
  "qr-scanner": "letary/lecodes-plugins"
@@ -3679,7 +3689,7 @@ var slugify = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+
3679
3689
  var appInit = async (args) => {
3680
3690
  const platform = args._[1];
3681
3691
  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]");
3692
+ 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
3693
  }
3684
3694
  const root = projectRoot();
3685
3695
  const iosDir = join11(root, "ios");
@@ -3687,9 +3697,10 @@ var appInit = async (args) => {
3687
3697
  throw new CliError("ios/ already exists — run `lecodes app sync` to update it.");
3688
3698
  }
3689
3699
  const existing = readJson(join11(root, "app.json")) ?? {};
3690
- const variant = flagStr(args, "variant") ?? existing.ios?.variant ?? "core";
3691
- if (variant !== "core" && variant !== "full")
3692
- throw new CliError(`--variant must be "core" or "full" (got "${variant}")`);
3700
+ const variant = flagStr(args, "variant") ?? existing.ios?.variant ?? "auto";
3701
+ if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
3702
+ throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
3703
+ }
3693
3704
  const name = flagStr(args, "name") ?? existing.name ?? basename4(root);
3694
3705
  const config = {
3695
3706
  ...existing,
@@ -3739,7 +3750,7 @@ ios/
3739
3750
  write("App/Assets.xcassets/Contents.json", ASSETS_ROOT_JSON);
3740
3751
  write("App/Assets.xcassets/AccentColor.colorset/Contents.json", ACCENT_COLOR_JSON);
3741
3752
  write("App/Assets.xcassets/AppIcon.appiconset/Contents.json", APP_ICON_JSON);
3742
- write("LeCodesRuntime/Package.swift", runtimePackageTemplate(SDK_PACKAGE_VERSION, variant, []));
3753
+ write("LeCodesRuntime/Package.swift", runtimePackageTemplate(SDK_PACKAGE_VERSION, variant === "auto" ? "core" : variant, []));
3743
3754
  write("LeCodesRuntime/Sources/LeCodesRuntime/LeCodesRuntime.swift", runtimeSwiftTemplate([]));
3744
3755
  log("");
3745
3756
  success(`iOS shell for ${c.bold(name)} ready.`);
@@ -3747,6 +3758,57 @@ ios/
3747
3758
  log(` ${c.bold("open ios/App.xcodeproj")} then run on your device from Xcode`);
3748
3759
  log(` App icon: drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/`);
3749
3760
  };
3761
+ var detectEngines = (bundleJs) => {
3762
+ const lines = [];
3763
+ for (const line of bundleJs.split(`
3764
+ `)) {
3765
+ if (!line.startsWith("//"))
3766
+ break;
3767
+ lines.push(line);
3768
+ }
3769
+ const header = lines.join(`
3770
+ `);
3771
+ return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
3772
+ };
3773
+ var resolveVariant = (requested, bundleJs) => {
3774
+ const uses = detectEngines(bundleJs);
3775
+ const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
3776
+ if (requested === "auto")
3777
+ return { variant: detected, detected };
3778
+ const variant = requested;
3779
+ const missing = [
3780
+ uses.gl && variant !== "3d" && variant !== "full" ? "3D" : null,
3781
+ uses.g2 && variant !== "2d" && variant !== "full" ? "2D" : null
3782
+ ].filter(Boolean);
3783
+ if (missing.length > 0) {
3784
+ 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}".`);
3785
+ }
3786
+ return { variant, detected };
3787
+ };
3788
+ var ensureSdkResourcesBundle = async () => {
3789
+ const override = process.env.LECODES_SDK_RESOURCES;
3790
+ if (override) {
3791
+ note(`Using local resources bundle ${override} (LECODES_SDK_RESOURCES)`);
3792
+ return resolve2(override);
3793
+ }
3794
+ const dir = join11(homedir2(), ".lecodes", "sdk-resources", SDK_PACKAGE_VERSION);
3795
+ const bundle = join11(dir, "LeCodesSDKResources.bundle");
3796
+ if (existsSync8(bundle))
3797
+ return bundle;
3798
+ const url = `https://github.com/letary/lecodes-ios-sdk/releases/download/${SDK_PACKAGE_VERSION}/LeCodesSDKResources.bundle.zip`;
3799
+ note(`Downloading LeCodesSDKResources.bundle (SDK ${SDK_PACKAGE_VERSION})…`);
3800
+ const res = await fetch(url);
3801
+ if (!res.ok)
3802
+ throw new CliError(`Resources bundle download failed (HTTP ${res.status}) — ${url}`);
3803
+ mkdirSync5(dir, { recursive: true });
3804
+ const zip = join11(dir, "LeCodesSDKResources.bundle.zip");
3805
+ writeFileSync7(zip, new Uint8Array(await res.arrayBuffer()));
3806
+ execFileSync("ditto", ["-x", "-k", zip, dir]);
3807
+ rmSync(zip);
3808
+ if (!existsSync8(bundle))
3809
+ throw new CliError(`No LeCodesSDKResources.bundle inside the release asset ${url}`);
3810
+ return bundle;
3811
+ };
3750
3812
  var appSync = async (_args) => {
3751
3813
  const root = projectRoot();
3752
3814
  const config = readJson(join11(root, "app.json"));
@@ -3754,7 +3816,10 @@ var appSync = async (_args) => {
3754
3816
  if (!config?.ios || !existsSync8(iosDir)) {
3755
3817
  throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
3756
3818
  }
3757
- const variant = config.ios.variant === "full" ? "full" : "core";
3819
+ const requestedVariant = config.ios.variant ?? "auto";
3820
+ if (requestedVariant !== "auto" && !SDK_VARIANTS.includes(requestedVariant)) {
3821
+ throw new CliError(`app.json ios.variant must be "auto" or one of ${SDK_VARIANTS.join(", ")} (got "${requestedVariant}").`);
3822
+ }
3758
3823
  const lockPath = join11(root, "plugins.lock.json");
3759
3824
  const oldLock = readJson(lockPath)?.plugins ?? {};
3760
3825
  const pluginSpecs = Object.entries(config.plugins ?? {});
@@ -3789,18 +3854,6 @@ var appSync = async (_args) => {
3789
3854
  log(`${c.yellow("removed")} ios/LeCodesRuntime/Sources/${entry} (plugin uninstalled)`);
3790
3855
  }
3791
3856
  }
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
3857
  const remotePatterns = config.resources?.remote ?? [];
3805
3858
  if (!Array.isArray(remotePatterns) || remotePatterns.some((p) => typeof p !== "string")) {
3806
3859
  throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
@@ -3810,6 +3863,23 @@ var appSync = async (_args) => {
3810
3863
  mkdirSync5(resourcesDir, { recursive: true });
3811
3864
  const resources = await compileShell({ _: [], flags: { o: join11(resourcesDir, "app.js") } }, { remotePatterns });
3812
3865
  const bundleJs = readFileSync10(join11(resourcesDir, "app.js"), "utf8");
3866
+ const { variant, detected } = resolveVariant(requestedVariant, bundleJs);
3867
+ const WHY = {
3868
+ core: "UI only",
3869
+ "2d": "uses the 2D engine",
3870
+ "3d": "uses the 3D engine",
3871
+ full: "uses the 3D + 2D engines"
3872
+ };
3873
+ log(`${c.green("variant")} ${variant}${requestedVariant === "auto" ? ` (auto: ${WHY[detected]})` : " (forced by ios.variant)"}`);
3874
+ const sdkProvided = new Set;
3875
+ if (variant === "3d" || variant === "full") {
3876
+ const sdkResources = await ensureSdkResourcesBundle();
3877
+ cpSync(sdkResources, join11(resourcesDir, "LeCodesSDKResources.bundle"), { recursive: true });
3878
+ for (const f2 of readdirSync4(sdkResources))
3879
+ if (f2 !== "Info.plist")
3880
+ sdkProvided.add(f2);
3881
+ log(`${c.green("resources")} LeCodesSDKResources.bundle (filament) → ios/App/Resources/`);
3882
+ }
3813
3883
  const preload = /^\/\/ preload: (.*)$/m.exec(bundleJs)?.[1].split(";").map((s) => s.trim()).filter(Boolean) ?? [];
3814
3884
  const IMPLICIT_PRELOADS = new Set(["uberarchive", "capture"]);
3815
3885
  const byName = new Map;
@@ -3826,7 +3896,8 @@ var appSync = async (_args) => {
3826
3896
  continue;
3827
3897
  const candidates = byName.get(name) ?? [];
3828
3898
  if (candidates.length === 0) {
3829
- warn(`bundle needs local asset "${name}" but no project file provides it — it won't load on device`);
3899
+ if (!sdkProvided.has(name))
3900
+ warn(`bundle needs local asset "${name}" but no project file provides it — it won't load on device`);
3830
3901
  continue;
3831
3902
  }
3832
3903
  if (candidates.length > 1)
@@ -3839,6 +3910,18 @@ var appSync = async (_args) => {
3839
3910
  const streamed = resources.filter((r) => r.remote);
3840
3911
  if (streamed.length > 0)
3841
3912
  log(`${c.green("remote")} ${streamed.length} resource(s) stay on the server (resources.remote): ${streamed.map((r) => r.path).join(", ")}`);
3913
+ writeFileSync7(join11(iosDir, "README.md"), IOS_README);
3914
+ writeFileSync7(join11(iosDir, "LeCodesRuntime", "Package.swift"), runtimePackageTemplate(SDK_PACKAGE_VERSION, variant, runtimePlugins));
3915
+ writeFileSync7(join11(sourcesDir, "LeCodesRuntime", "LeCodesRuntime.swift"), runtimeSwiftTemplate(runtimePlugins));
3916
+ const mergedKeys = {};
3917
+ for (const [key, values] of Object.entries(infoValues))
3918
+ mergedKeys[key] = [...new Set(values)].join(" ");
3919
+ writeFileSync7(join11(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys));
3920
+ if (pluginSpecs.length > 0 || existsSync8(lockPath))
3921
+ writeJson(lockPath, { plugins: newLock });
3922
+ const pbxPath = join11(iosDir, "App.xcodeproj", "project.pbxproj");
3923
+ 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};`);
3924
+ writeFileSync7(pbxPath, pbx);
3842
3925
  log("");
3843
3926
  success(`Shell synced: app.js + ${runtimePlugins.length} plugin(s), variant ${variant}, v${config.version ?? "1.0.0"} (${config.ios.buildNumber ?? 1}).`);
3844
3927
  note("Build & run from Xcode (open ios/App.xcodeproj) — user files in ios/App/*.swift were not touched.");
@@ -3949,6 +4032,16 @@ var appDoctor = async (_args) => {
3949
4032
  check(hasIcon, "App icon present", "drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/ (App Store requires it)", false);
3950
4033
  const bundle = join11(root, "ios", "App", "Resources", "app.js");
3951
4034
  check(existsSync8(bundle), "Bundled app.js", "run `lecodes app sync`");
4035
+ const requestedVariant = config.ios.variant ?? "auto";
4036
+ const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
4037
+ check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
4038
+ if (existsSync8(bundle) && validVariant) {
4039
+ const { variant } = resolveVariant(requestedVariant, readFileSync10(bundle, "utf8"));
4040
+ const pkg = readFileSync10(join11(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
4041
+ const needsResources = variant === "3d" || variant === "full";
4042
+ const hasResources = existsSync8(join11(root, "ios", "App", "Resources", "LeCodesSDKResources.bundle"));
4043
+ check(pkg.includes(`"LeCodesSDK-${variant}"`) && (!needsResources || hasResources), `SDK variant ${variant}${needsResources ? " (+ filament resources)" : ""}`, "run `lecodes app sync`");
4044
+ }
3952
4045
  const lock = readJson(join11(root, "plugins.lock.json"))?.plugins ?? {};
3953
4046
  const wanted = Object.keys(config.plugins ?? {});
3954
4047
  const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
@@ -7211,11 +7304,14 @@ ${c.bold("Commands:")}
7211
7304
  --name <s> display name (default: app.json / folder name)
7212
7305
  --bundle-id <id> iOS bundle identifier
7213
7306
  --team <id> Apple development team id
7214
- --variant <v> SDK variant: core (default) or full (3D/AR)
7307
+ --variant <v> SDK variant: auto (default — sync picks the
7308
+ smallest of core/2d/3d/full covering what
7309
+ the bundle uses), or one of those to pin
7215
7310
  app sync Make the shell match the project: compile the bundle + embed
7216
7311
  its assets into ios/App/Resources/ (offline by default, also
7217
7312
  for server projects; app.json "resources": {"remote": [globs]}
7218
- keeps heavy assets streaming from the server), vendor app.json
7313
+ keeps heavy assets streaming from the server), pick the SDK
7314
+ variant from the bundle's engine use, vendor app.json
7219
7315
  plugins (pinned in plugins.lock.json), regenerate the
7220
7316
  CLI-owned shell files
7221
7317
  app plugin <list|add|remove> Manage native plugins in app.json (then run 'lecodes app sync')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lecodes-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Clone, edit and push LeCodes projects from your machine.",
5
5
  "type": "module",
6
6
  "license": "MIT",