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 +234 -37
- package/package.json +8 -8
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +3 -3
- package/runtime/web/assets/browser-BpFb50ko.js +1 -0
- package/runtime/web/assets/webrtc-CbCltXvl.js +1 -0
- package/runtime/web/player.js +6 -6
- package/runtime/web-lite/lite.js +1 -1
- package/runtime/web-lite/plugins/browser/index-BpFb50ko.js +1 -0
- package/runtime/web-lite/plugins/webrtc/index-CbCltXvl.js +1 -0
- package/runtime/web-lite/plugins.json +3 -1
- package/src/commands/app/android.ts +30 -3
- package/src/commands/app/desktop.ts +3 -2
- package/src/commands/app/index.ts +4 -4
- package/src/commands/app/shared.ts +19 -2
- package/src/commands/app/templates/android.ts +14 -2
- package/src/commands/app/templates/ios.ts +35 -3
- package/src/commands/plugin.ts +8 -2
- package/src/plugin/emitPackage.ts +98 -0
- package/src/plugin/generate.ts +51 -16
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
17
|
-
"lecodes-web-frame": "2.2.
|
|
18
|
-
"lecodes-web-player": "2.2.
|
|
19
|
-
"lecodes-web-shared": "2.2.
|
|
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.
|
|
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",
|