lecodes-cli 2.2.6 → 2.2.9

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
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "lecodes-cli",
55
- version: "2.2.6",
55
+ version: "2.2.9",
56
56
  dependencies: {
57
57
  "@letary/chisel": "^0.14.0",
58
58
  jimp: "^1.6.1",
@@ -76,7 +76,7 @@ var init_package = __esm(() => {
76
76
  },
77
77
  peerDependencies: {
78
78
  "lecodes-design": "^0.11.0",
79
- "lecodes-headless": "^2.2.6",
79
+ "lecodes-headless": "^2.2.9",
80
80
  "lecodes-3d-editor": "^0.9.0",
81
81
  "lecodes-assets": "^0.2.1",
82
82
  "heic-decode": "^2.1.0",
@@ -146,7 +146,7 @@ var init_package = __esm(() => {
146
146
  });
147
147
 
148
148
  // ../../sdk/src/version.ts
149
- var SDK_VERSION = "2.2.6", sdkMajor = (version) => {
149
+ var SDK_VERSION = "2.2.9", sdkMajor = (version) => {
150
150
  const m = version?.match(/^(\d+)\.\d+\.\d+/);
151
151
  return m ? Number(m[1]) : null;
152
152
  }, 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);
@@ -1703,7 +1703,9 @@ var init_chisel = __esm(() => {
1703
1703
  "NativeView",
1704
1704
  "CameraView",
1705
1705
  "MapView",
1706
- "QRScanner"
1706
+ "QRScanner",
1707
+ "WebView",
1708
+ "RTCVideoView"
1707
1709
  ],
1708
1710
  containers: ["UIColumn", "UIRow", "UIBox", "UIButton", "UIScrollable", "UIScreen", "UIWidget"],
1709
1711
  text: "UIText",
@@ -217957,13 +217959,101 @@ var readManifest2 = (dir, id) => {
217957
217959
  };
217958
217960
  var readManifestIfAny = (dir, id) => existsSync14(join17(dir, MANIFEST_FILE)) ? readManifest2(dir, id) : null;
217959
217961
 
217962
+ // src/plugin/emitPackage.ts
217963
+ class PackageError extends Error {
217964
+ }
217965
+ var swiftPackageName = (url) => (url.replace(/\/+$/, "").split("/").pop() ?? url).replace(/\.git$/, "");
217966
+ var iosModule = (id, manifest) => manifest.ios?.module ?? `LeCodes${pascal(id)}`;
217967
+ var PACKAGE_FILE = "Package.swift";
217968
+ var DEFAULT_SDK_PATH = "../lecodes";
217969
+ var emitPackageSwift = (plugins, sdkPath = DEFAULT_SDK_PATH) => {
217970
+ const ios = plugins.filter((p) => p.manifest.ios !== null);
217971
+ const packages = new Map;
217972
+ for (const p of ios) {
217973
+ for (const pkg of p.manifest.ios.packages ?? []) {
217974
+ const prior = packages.get(pkg.url);
217975
+ if (prior && (prior.exact !== pkg.exact || prior.from !== pkg.from)) {
217976
+ throw new PackageError(`"${p.id}" pins ${pkg.url} at ${pkg.exact ?? `from ${pkg.from}`} but "${prior.by}" pins ${prior.exact ?? `from ${prior.from}`} — one package resolves once`);
217977
+ }
217978
+ if (!prior)
217979
+ packages.set(pkg.url, { ...pkg, by: p.id });
217980
+ }
217981
+ }
217982
+ const moduleOf = (p) => iosModule(p.id, p.manifest);
217983
+ const products = ios.map((p) => ` .library(name: "${moduleOf(p)}", targets: ["${moduleOf(p)}"]),`);
217984
+ const dependencies = [
217985
+ ` .package(name: "lecodes", path: sdk),`,
217986
+ ...[...packages.values()].map((pkg) => ` .package(url: "${pkg.url}", ${pkg.exact ? `exact: "${pkg.exact}"` : `from: "${pkg.from}"`}),`)
217987
+ ];
217988
+ const targets = ios.map((p) => {
217989
+ const deps = [
217990
+ `.product(name: "LeCodes", package: "lecodes")`,
217991
+ ...(p.manifest.ios.packages ?? []).flatMap((pkg) => pkg.products.map((name) => `.product(name: "${name}", package: "${swiftPackageName(pkg.url)}")`))
217992
+ ];
217993
+ return [
217994
+ ` // ${p.id}`,
217995
+ ` .target(`,
217996
+ ` name: "${moduleOf(p)}",`,
217997
+ ` dependencies: [${deps.join(", ")}],`,
217998
+ ` path: "plugins/${p.id}/ios"`,
217999
+ ` ),`
218000
+ ].join(`
218001
+ `);
218002
+ });
218003
+ return `// swift-tools-version:5.9
218004
+ // GENERATED by \`lecodes plugin gen\` from the plugins' manifests (lecodes-plugin.json) — do not edit.
218005
+ //
218006
+ // The plugins with an iOS half as ONE SwiftPM package: a target per plugin (its sources in
218007
+ // plugins/<id>/ios, the packages its manifest names) and a product per plugin. Each plugin is its
218008
+ // own module: an Xcode project links a plugin as a product instead of compiling its folder, and two
218009
+ // plugins may name a type alike.
218010
+ //
218011
+ // The SDK is the LeCodes package on disk: LECODES_IOS_SDK_PATH, else the path below (the repo's
218012
+ // package.json \`lecodes.iosSdkPath\`, or the sibling ../lecodes). A shell made by \`lecodes app sync\`
218013
+ // does not use this package: it vendors the sources into targets of its own, pinned by
218014
+ // plugins.lock.json.
218015
+ import PackageDescription
218016
+
218017
+ let sdk = Context.environment["LECODES_IOS_SDK_PATH"] ?? ${JSON.stringify(sdkPath)}
218018
+
218019
+ let package = Package(
218020
+ name: "lecodes-plugins",
218021
+ platforms: [.iOS(.v15)],
218022
+ products: [
218023
+ ${products.join(`
218024
+ `)}
218025
+ ],
218026
+ dependencies: [
218027
+ ${dependencies.join(`
218028
+ `)}
218029
+ ],
218030
+ targets: [
218031
+ ${targets.join(`
218032
+ `)}
218033
+ ]
218034
+ )
218035
+ `;
218036
+ };
218037
+
217960
218038
  // src/commands/app/shared.ts
217961
218039
  var FIRST_PARTY_PLUGINS = {
217962
218040
  camera: "letary/lecodes-plugins",
217963
218041
  "qr-scanner": "letary/lecodes-plugins",
217964
218042
  map: "letary/lecodes-plugins",
217965
218043
  geolocation: "letary/lecodes-plugins",
217966
- push: "letary/lecodes-plugins"
218044
+ push: "letary/lecodes-plugins",
218045
+ browser: "letary/lecodes-plugins"
218046
+ };
218047
+ var urlSchemeOf = (config) => {
218048
+ const scheme = config?.urlScheme;
218049
+ if (scheme === undefined)
218050
+ return;
218051
+ if (typeof scheme !== "string" || !/^[a-z][a-z0-9+.-]*$/.test(scheme)) {
218052
+ throw new CliError(`app.json "urlScheme" must be lowercase letters, digits, "+", "-" or ".", starting with a letter — got ${JSON.stringify(scheme)}.`);
218053
+ }
218054
+ if (scheme === "http" || scheme === "https")
218055
+ throw new CliError(`app.json "urlScheme" is the app's OWN scheme ("myshop" → myshop://…), not ${scheme}.`);
218056
+ return scheme;
217967
218057
  };
217968
218058
  var readAppConfigAt = (root) => readJson2(join18(root, "app.json"));
217969
218059
  var DESKTOP_DEFAULT_WINDOW = { width: 1280, height: 720 };
@@ -217992,7 +218082,6 @@ var desktopDefaultViewport = (root) => {
217992
218082
  height: cfg.desktop.window?.height ?? DESKTOP_DEFAULT_WINDOW.height
217993
218083
  };
217994
218084
  };
217995
- var swiftPackageName = (url) => (url.replace(/\/+$/, "").split("/").pop() ?? url).replace(/\.git$/, "");
217996
218085
  var isLocalPluginSpec = (spec) => spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || spec.startsWith("file:");
217997
218086
  var resolveUpdateUrl = (update, root, sdkVersion) => {
217998
218087
  if (update === false)
@@ -218887,11 +218976,30 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
218887
218976
  LeCodesAppHooks.coldStartNotification(response)
218888
218977
  }
218889
218978
 
218979
+ let controller = ViewController()
218980
+ // Cold-started by a link (app.json "urlScheme", or a universal link): the engine reads it as
218981
+ // app.launchUrl — set BEFORE the view loads, which boots the JS world.
218982
+ if let url = connectionOptions.urlContexts.first?.url
218983
+ ?? connectionOptions.userActivities.first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb })?.webpageURL {
218984
+ controller.engine.setLaunchUrl(url.absoluteString)
218985
+ }
218890
218986
  let window = UIWindow(windowScene: windowScene)
218891
- window.rootViewController = ViewController()
218987
+ window.rootViewController = controller
218892
218988
  self.window = window
218893
218989
  window.makeKeyAndVisible()
218894
218990
  }
218991
+
218992
+ // A link while the app runs: the "url" app event (an OAuth redirect back into the app, a
218993
+ // checkout's return — what Browser.open(url, { returnUrl }) resolves on).
218994
+ func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
218995
+ guard let url = URLContexts.first?.url else { return }
218996
+ (window?.rootViewController as? ViewController)?.engine.emitUrl(url.absoluteString)
218997
+ }
218998
+
218999
+ func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
219000
+ guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else { return }
219001
+ (window?.rootViewController as? ViewController)?.engine.emitUrl(url.absoluteString)
219002
+ }
218895
219003
  }
218896
219004
  `;
218897
219005
  var VIEW_CONTROLLER = `//
@@ -218952,9 +219060,22 @@ ${body}</dict>
218952
219060
  </plist>
218953
219061
  `;
218954
219062
  };
218955
- var infoPlistTemplate = (pluginKeys, updateUrl) => {
219063
+ var infoPlistTemplate = (pluginKeys, updateUrl, urlScheme) => {
218956
219064
  const xml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
218957
219065
  const keys = Object.keys(pluginKeys).sort();
219066
+ const schemeBlock = !urlScheme ? "" : `
219067
+ <!-- lecodes:urlScheme — app.json "urlScheme": links the app opens on (the "url" app event) -->
219068
+ <key>CFBundleURLTypes</key>
219069
+ <array>
219070
+ <dict>
219071
+ <key>CFBundleURLName</key>
219072
+ <string>${xml(urlScheme)}</string>
219073
+ <key>CFBundleURLSchemes</key>
219074
+ <array>
219075
+ <string>${xml(urlScheme)}</string>
219076
+ </array>
219077
+ </dict>
219078
+ </array>`;
218958
219079
  const pluginBlock = keys.length === 0 ? "" : `
218959
219080
  <!-- lecodes:plugins — merged from installed plugin manifests by \`lecodes app sync\` -->
218960
219081
  ${keys.map((k) => ` <key>${k}</key>
@@ -218972,7 +219093,7 @@ ${keys.map((k) => ` <key>${k}</key>
218972
219093
  so App Store Connect skips the "Missing Compliance" prompt on every build.
218973
219094
  Flip to <true/> only if the app adds its own non-exempt cryptography. -->
218974
219095
  <key>ITSAppUsesNonExemptEncryption</key>
218975
- <false/>
219096
+ <false/>${schemeBlock}
218976
219097
  <key>UIApplicationSceneManifest</key>
218977
219098
  <dict>
218978
219099
  <key>UIApplicationSupportsMultipleScenes</key>
@@ -220359,7 +220480,16 @@ var APP_PROGUARD_RULES = `# R8 rules for app-local code (yours — \`lecodes app
220359
220480
  # reflection, e.g.
220360
220481
  # -keep class com.example.myapp.MyBridge { *; }
220361
220482
  `;
220362
- var appManifestTemplate = () => `<?xml version="1.0" encoding="utf-8"?>
220483
+ var urlSchemeFilter = (urlScheme) => !urlScheme ? "" : `
220484
+ <!-- lecodes:urlScheme — app.json "urlScheme": links the app opens on (the "url" app event) -->
220485
+ <intent-filter>
220486
+ <action android:name="android.intent.action.VIEW" />
220487
+ <category android:name="android.intent.category.DEFAULT" />
220488
+ <category android:name="android.intent.category.BROWSABLE" />
220489
+ <data android:scheme="${urlScheme}" />
220490
+ </intent-filter>
220491
+ <!-- /lecodes:urlScheme -->`;
220492
+ var appManifestTemplate = (urlScheme) => `<?xml version="1.0" encoding="utf-8"?>
220363
220493
  <!-- Generated by \`lecodes app init\` — yours to edit. Sync leaves it alone, except that with
220364
220494
  app.json "icon" set it restamps the android:icon VALUE to the generated @mipmap set.
220365
220495
  Plugin permissions/services arrive through the plugin modules' own manifests via the AGP
@@ -220384,7 +220514,7 @@ var appManifestTemplate = () => `<?xml version="1.0" encoding="utf-8"?>
220384
220514
  <intent-filter>
220385
220515
  <action android:name="android.intent.action.MAIN" />
220386
220516
  <category android:name="android.intent.category.LAUNCHER" />
220387
- </intent-filter>
220517
+ </intent-filter>${urlSchemeFilter(urlScheme)}
220388
220518
  </activity>
220389
220519
  </application>
220390
220520
 
@@ -221010,7 +221140,7 @@ var initAndroid = async (opts) => {
221010
221140
  write("gradle/wrapper/gradle-wrapper.jar", wrapperJar);
221011
221141
  write("app/build.gradle.kts", appBuildGradleTemplate(identity, config.android.minify ?? true));
221012
221142
  write("app/proguard-rules.pro", APP_PROGUARD_RULES);
221013
- write("app/src/main/AndroidManifest.xml", appManifestTemplate());
221143
+ write("app/src/main/AndroidManifest.xml", appManifestTemplate(urlSchemeOf(config)));
221014
221144
  write(`app/src/main/java/${pkgPath}/MainActivity.kt`, mainActivityTemplate(identity.applicationId));
221015
221145
  write(`app/src/main/java/${pkgPath}/AppViewModel.kt`, appViewModelTemplate(identity.applicationId));
221016
221146
  write("app/src/main/res/values/themes.xml", themesXmlTemplate());
@@ -221188,10 +221318,36 @@ var syncAndroid = async (root, config, prior) => {
221188
221318
  await writeAndroidIcons(androidDir, await loadIconMaster(root, config));
221189
221319
  restampManifestIcon(androidDir);
221190
221320
  }
221321
+ restampManifestUrlScheme(androidDir, urlSchemeOf(config));
221191
221322
  log("");
221192
221323
  success(`Android shell synced: app.js + ${runtimePlugins.length} plugin(s), SDK ${sdkVersion}, v${config.version ?? "1.0.0"} (${config.android?.versionCode ?? 1}).`);
221193
221324
  note("Build & run from Android Studio (`lecodes app open android`) — user files in android/app/ were not touched.");
221194
221325
  };
221326
+ var restampManifestUrlScheme = (androidDir, urlScheme) => {
221327
+ const path2 = join21(androidDir, "app", "src", "main", "AndroidManifest.xml");
221328
+ if (!existsSync19(path2))
221329
+ return;
221330
+ const content = readFileSync18(path2, "utf8");
221331
+ const tagged = /\n[ \t]*<!-- lecodes:urlScheme[\s\S]*?<!-- \/lecodes:urlScheme -->/;
221332
+ const block = urlSchemeFilter(urlScheme);
221333
+ let next;
221334
+ if (tagged.test(content))
221335
+ next = content.replace(tagged, block);
221336
+ else if (!urlScheme)
221337
+ return;
221338
+ else {
221339
+ const launcher = /(<category android:name="android\.intent\.category\.LAUNCHER" \/>\s*<\/intent-filter>)/;
221340
+ if (!launcher.test(content)) {
221341
+ warn(`couldn't find the launcher activity in app/src/main/AndroidManifest.xml — add an intent-filter for the "${urlScheme}" scheme yourself.`);
221342
+ return;
221343
+ }
221344
+ next = content.replace(launcher, `$1${block}`);
221345
+ }
221346
+ if (next === content)
221347
+ return;
221348
+ writeFileSync14(path2, next);
221349
+ log(`${c.green("updated")} android/app/src/main/AndroidManifest.xml: urlScheme ${urlScheme ? `→ ${urlScheme}://` : "removed"}`);
221350
+ };
221195
221351
  var restampAppBuildGradle = (androidDir, config, gradlePlugins) => {
221196
221352
  const path2 = join21(androidDir, "app", "build.gradle.kts");
221197
221353
  if (!existsSync19(path2)) {
@@ -222692,7 +222848,7 @@ var buildDesktop = async (flags) => {
222692
222848
  localShaders: true,
222693
222849
  desktopRenderer: renderer
222694
222850
  });
222695
- const wanted = new Set([...js.matchAll(/fetchLocal\("([^"]+)"\)/g)].map((m) => m[1]));
222851
+ const wanted = new Set([...js.matchAll(/(?:fetchLocal|_creatorFetch\.local)\("([^"]+)"\)/g)].map((m) => m[1]));
222696
222852
  const shipped = resources.filter((r2) => !r2.remote && (r2.shader || wanted.has(r2.name)));
222697
222853
  const byName = new Map;
222698
222854
  for (const r2 of resources) {
@@ -222991,7 +223147,7 @@ var initIos = async (opts) => {
222991
223147
  write("App/AppDelegate.swift", APP_DELEGATE);
222992
223148
  write("App/SceneDelegate.swift", SCENE_DELEGATE);
222993
223149
  write("App/ViewController.swift", VIEW_CONTROLLER);
222994
- write("App/Info.plist", infoPlistTemplate({}));
223150
+ write("App/Info.plist", infoPlistTemplate({}, undefined, urlSchemeOf(config)));
222995
223151
  write("App/App.entitlements", entitlementsTemplate({}));
222996
223152
  write("App/Base.lproj/LaunchScreen.storyboard", LAUNCH_STORYBOARD);
222997
223153
  write("App/Assets.xcassets/Contents.json", ASSETS_ROOT_JSON);
@@ -223178,7 +223334,7 @@ var syncIos = async (root, config) => {
223178
223334
  for (const [key, values] of Object.entries(infoValues))
223179
223335
  mergedKeys[key] = [...new Set(values)].join(" ");
223180
223336
  const updateUrl = resolveUpdateUrl(config.update, root, sdkVersion);
223181
- writeFileSync18(join25(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl));
223337
+ writeFileSync18(join25(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl, urlSchemeOf(config)));
223182
223338
  if (updateUrl)
223183
223339
  log(`${c.green("update")} OTA source ${updateUrl} — checked natively at launch, new code applies next launch${config.update === undefined ? c.dim(' ("update": false in app.json turns it off)') : ""}`);
223184
223340
  else if (config.update === false)
@@ -223240,7 +223396,7 @@ var sync2 = defineCommand({
223240
223396
  usage: "[ios|android]",
223241
223397
  description: `Compiles the bundle and embeds the assets it references (ios/App/Resources/ resp. the android assets/; offline by default, also for server projects — app.json "resources": {"remote": [globs]} keeps heavy assets streaming), picks the SDK variant from the bundle's engine use, vendors the app.json plugins (pinned in plugins.lock.json) and regenerates the CLI-owned shell files. User files (ios/App/*.swift, android/app/) are never touched.
223242
223398
 
223243
- Native OTA JS updates at launch (both shells; the current launch runs the newest LOCAL bundle, a download applies on the next): on by default for a pushed project (its bundle on the platform, the launcher's own URL), app.json "update": {"url": …} for custom hosting, "update": false turns it off. A download runs only when it is of the shell's SDK major and not older than the bundle running. app.json "icon" (a square ≥1024px image) renders the app icons every sync ("iconBackground": the adaptive / flatten backdrop). No platform = every shell that exists; a dual-shell project compiles once.`,
223399
+ Native OTA JS updates at launch (both shells; the current launch runs the newest LOCAL bundle, a download applies on the next): on by default for a pushed project (its bundle on the platform, the launcher's own URL), app.json "update": {"url": …} for custom hosting, "update": false turns it off. A download runs only when it is of the shell's SDK major and not older than the bundle running. app.json "icon" (a square ≥1024px image) renders the app icons every sync ("iconBackground": the adaptive / flatten backdrop). app.json "urlScheme" ("myshop") is the scheme the shells open on: iOS CFBundleURLTypes, Android a BROWSABLE intent-filter — a link arrives as app.launchUrl / the "url" event (the return of Browser.open with a returnUrl). No platform = every shell that exists; a dual-shell project compiles once.`,
223244
223400
  examples: ["lecodes app sync", "lecodes app sync android"],
223245
223401
  run: ({ args }) => runSync(args[0])
223246
223402
  });
@@ -231098,7 +231254,7 @@ var loadTypescript = (dir) => {
231098
231254
  var generatePlugin = (ts, plugin2) => {
231099
231255
  const file = join46(plugin2.dir, CONTRACT_FILE);
231100
231256
  const contract = readContract(ts, file, readFileSync33(file, "utf8"));
231101
- readManifestIfAny(plugin2.dir, plugin2.id);
231257
+ const manifest = readManifestIfAny(plugin2.dir, plugin2.id);
231102
231258
  const handWritten = existsSync41(join46(plugin2.dir, "sdk", `${plugin2.id}.ts`));
231103
231259
  const single = contract.channels.length === 1;
231104
231260
  const files = contract.channels.map((channel) => ({
@@ -231122,7 +231278,7 @@ var generatePlugin = (ts, plugin2) => {
231122
231278
  ${foreign.map((f5) => ` ${f5}`).join(`
231123
231279
  `)}`);
231124
231280
  }
231125
- return { contract, files };
231281
+ return { contract, manifest, files };
231126
231282
  };
231127
231283
  var hostImports = (web) => {
231128
231284
  const found = [];
@@ -231162,32 +231318,53 @@ var kotlinPackage = (android) => {
231162
231318
  }
231163
231319
  return { dir, name: relative8(root, dir).split(/[\\/]/).join(".") };
231164
231320
  };
231321
+ var repoSdkPath = (root) => {
231322
+ const file = join46(root, "package.json");
231323
+ if (!existsSync41(file))
231324
+ return DEFAULT_SDK_PATH;
231325
+ try {
231326
+ const pkg = JSON.parse(readFileSync33(file, "utf8"));
231327
+ const path2 = pkg.lecodes?.iosSdkPath;
231328
+ if (path2 !== undefined && typeof path2 !== "string")
231329
+ throw new PackageError(`package.json lecodes.iosSdkPath: a path, as a string`);
231330
+ return path2 ?? DEFAULT_SDK_PATH;
231331
+ } catch (e2) {
231332
+ if (e2 instanceof PackageError)
231333
+ throw e2;
231334
+ throw new PackageError(`package.json does not parse: ${e2 instanceof Error ? e2.message : String(e2)}`);
231335
+ }
231336
+ };
231165
231337
  var generate = (path2, opts = {}) => {
231166
231338
  const plugins = findPlugins(path2);
231167
231339
  if (plugins.length === 0)
231168
231340
  throw new CliError(`No plugin under ${resolve23(path2)}: a plugin is a directory with a ${CONTRACT_FILE} (one, or plugins/<id>/ of a repo).`);
231169
231341
  const ts = loadTypescript(plugins[0].dir);
231170
231342
  const root = resolve23(path2);
231171
- const report = { plugins: [], failures: [] };
231343
+ const report = { plugins: [], repo: { written: [], stale: [], fresh: [] }, failures: [] };
231344
+ const place = (f5, entry) => {
231345
+ const name = shown(root, f5.file) || basename23(f5.file);
231346
+ const current = existsSync41(f5.file) ? readFileSync33(f5.file, "utf8") : null;
231347
+ if (current === f5.text) {
231348
+ entry.fresh.push(name);
231349
+ return;
231350
+ }
231351
+ if (opts.check) {
231352
+ entry.stale.push(name);
231353
+ return;
231354
+ }
231355
+ mkdirSync26(join46(f5.file, ".."), { recursive: true });
231356
+ writeFileSync28(f5.file, f5.text);
231357
+ entry.written.push(name);
231358
+ };
231359
+ const manifests = [];
231172
231360
  for (const plugin2 of plugins) {
231173
231361
  try {
231174
- const { contract, files } = generatePlugin(ts, plugin2);
231362
+ const { contract, manifest, files } = generatePlugin(ts, plugin2);
231363
+ if (manifest)
231364
+ manifests.push({ id: plugin2.id, manifest });
231175
231365
  const entry = { id: plugin2.id, contract, written: [], stale: [], fresh: [] };
231176
- for (const f5 of files) {
231177
- const name = shown(root, f5.file) || basename23(f5.file);
231178
- const current = existsSync41(f5.file) ? readFileSync33(f5.file, "utf8") : null;
231179
- if (current === f5.text) {
231180
- entry.fresh.push(name);
231181
- continue;
231182
- }
231183
- if (opts.check) {
231184
- entry.stale.push(name);
231185
- continue;
231186
- }
231187
- mkdirSync26(join46(f5.file, ".."), { recursive: true });
231188
- writeFileSync28(f5.file, f5.text);
231189
- entry.written.push(name);
231190
- }
231366
+ for (const f5 of files)
231367
+ place(f5, entry);
231191
231368
  report.plugins.push(entry);
231192
231369
  } catch (e2) {
231193
231370
  if (e2 instanceof ContractError)
@@ -231203,6 +231380,17 @@ ${e2.problems.map((p2) => ` ${p2}`).join(`
231203
231380
  throw e2;
231204
231381
  }
231205
231382
  }
231383
+ const isRepo = !existsSync41(join46(root, CONTRACT_FILE));
231384
+ if (isRepo && report.failures.length === 0 && manifests.some((m) => m.manifest.ios)) {
231385
+ try {
231386
+ place({ file: join46(root, PACKAGE_FILE), text: emitPackageSwift(manifests, repoSdkPath(root)) }, report.repo);
231387
+ } catch (e2) {
231388
+ if (e2 instanceof PackageError)
231389
+ report.failures.push(`${PACKAGE_FILE}: ${e2.message}`);
231390
+ else
231391
+ throw e2;
231392
+ }
231393
+ }
231206
231394
  return report;
231207
231395
  };
231208
231396
 
@@ -231213,7 +231401,7 @@ var gen = defineCommand({
231213
231401
  usage: "[path]",
231214
231402
  description: `The path is one plugin (a directory with a contract.d.ts) or a repo that keeps its plugins in plugins/<id>/; the current directory when omitted.
231215
231403
 
231216
- Writes sdk/<id>.gen.ts — the wrapper an app calls: the wire call by call, the class with the wire's methods as its own, and the public export when the plugin has no hand-written sdk/<id>.ts. Where the plugin has an ios/ folder, writes ios/<Name>.gen.swift — the contract's types, the protocol the plugin implements and the glue that reads a call off the wire. Where it has an android/ folder, writes the same as <Name>.gen.kt into the folder of the plugin's Kotlin package (android/ is laid out as a Gradle module: the package is the folder the path under android/src/main/java narrows down to). Where it has a web/ folder, writes web/<id>.gen.ts — the same for the web half, in a file that imports nothing; a web half that imports a LeCodes package is refused, because it would run on that host only. A contract the generator does not understand is an error with its line, and every problem of a file is reported at once.
231404
+ Writes sdk/<id>.gen.ts — the wrapper an app calls: the wire call by call, the class with the wire's methods as its own, and the public export when the plugin has no hand-written sdk/<id>.ts. Where the plugin has an ios/ folder, writes ios/<Name>.gen.swift — the contract's types, the protocol the plugin implements and the glue that reads a call off the wire. Where it has an android/ folder, writes the same as <Name>.gen.kt into the folder of the plugin's Kotlin package (android/ is laid out as a Gradle module: the package is the folder the path under android/src/main/java narrows down to). Where it has a web/ folder, writes web/<id>.gen.ts — the same for the web half, in a file that imports nothing; a web half that imports a LeCodes package is refused, because it would run on that host only. A repo of plugins also gets its Package.swift from the manifests: the iOS halves as one SwiftPM package, a target and a product per plugin, the SDK as a package on disk (LECODES_IOS_SDK_PATH, else the repo's package.json lecodes.iosSdkPath, else ../lecodes) — what an Xcode project links the halves as (a shell vendors the sources instead). A contract the generator does not understand is an error with its line, and every problem of a file is reported at once.
231217
231405
 
231218
231406
  Generated files are committed. Reading a contract needs TypeScript in the plugin's repo.`,
231219
231407
  flags: {
@@ -231232,9 +231420,18 @@ Generated files are committed. Reading a contract needs TypeScript in the plugin
231232
231420
  for (const f5 of p2.fresh)
231233
231421
  log(c.dim(` fresh ${f5}`));
231234
231422
  }
231423
+ if (report.repo.written.length + report.repo.stale.length + report.repo.fresh.length > 0) {
231424
+ info(`${c.bold("the repo")} ${c.dim("Package.swift from the manifests")}`);
231425
+ for (const f5 of report.repo.written)
231426
+ log(c.green(` written ${f5}`));
231427
+ for (const f5 of report.repo.stale)
231428
+ log(c.red(` stale ${f5}`));
231429
+ for (const f5 of report.repo.fresh)
231430
+ log(c.dim(` fresh ${f5}`));
231431
+ }
231235
231432
  for (const failure2 of report.failures)
231236
231433
  log(c.red(failure2));
231237
- const stale = report.plugins.reduce((n2, p2) => n2 + p2.stale.length, 0);
231434
+ const stale = report.plugins.reduce((n2, p2) => n2 + p2.stale.length, 0) + report.repo.stale.length;
231238
231435
  if (report.failures.length)
231239
231436
  throw new CliError(`${report.failures.length} contract${report.failures.length === 1 ? "" : "s"} did not generate.`);
231240
231437
  if (stale)
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "lecodes-cli",
3
- "version": "2.2.6",
3
+ "version": "2.2.9",
4
4
  "dependencies": {
5
5
  "@letary/chisel": "^0.14.0",
6
6
  "jimp": "^1.6.1",
7
- "lecodes-sdk": "2.2.6"
7
+ "lecodes-sdk": "2.2.9"
8
8
  },
9
9
  "devDependencies": {
10
10
  "lecodes-design": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-design/lecodes-design-0.11.0.tgz",
11
- "lecodes-headless": "2.2.6",
11
+ "lecodes-headless": "2.2.9",
12
12
  "qrcode-terminal": "^0.12.0",
13
13
  "lecodes-3d-editor": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-3d-editor/lecodes-3d-editor-0.9.0.tgz",
14
14
  "lecodes-assets": "0.2.1",
15
15
  "lecodes-creator-nav": "0.19.0",
16
- "lecodes-gl-lite": "2.2.6",
17
- "lecodes-web-frame": "2.2.6",
18
- "lecodes-web-player": "2.2.6",
19
- "lecodes-web-shared": "2.2.6",
16
+ "lecodes-gl-lite": "2.2.9",
17
+ "lecodes-web-frame": "2.2.9",
18
+ "lecodes-web-player": "2.2.9",
19
+ "lecodes-web-shared": "2.2.9",
20
20
  "lecodes-web-lite": "2.0.2",
21
21
  "heic-decode": "^2.1.0",
22
22
  "marcidb-embedded": "^0.15.0",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "peerDependencies": {
26
26
  "lecodes-design": "^0.11.0",
27
- "lecodes-headless": "^2.2.6",
27
+ "lecodes-headless": "^2.2.9",
28
28
  "lecodes-3d-editor": "^0.9.0",
29
29
  "lecodes-assets": "^0.2.1",
30
30
  "heic-decode": "^2.1.0",