lecodes-cli 0.20.2 → 1.0.0
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/README.md +56 -57
- package/dist/index.js +5918 -5020
- package/package.json +7 -5
- package/runtime/materials/decal-relief.mat +12 -1
- package/runtime/materials/decal.mat +17 -3
- package/runtime/materials/lightmap-baked-lite.mat +6 -1
- package/runtime/materials/lightmap-baked.mat +6 -1
- package/runtime/materials/lightmap.mat +3 -0
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/assets/createViewerLite-DHqTFhWB.js +866 -0
- package/runtime/web/assets/{index-BMt7AnC5.js → index-Q06c6oHx.js} +1 -1
- package/runtime/web/embed.js +1 -1
- package/src/api.ts +1 -302
- package/src/cli/args.ts +37 -0
- package/src/cli/command.ts +113 -0
- package/src/cli/errors.ts +13 -0
- package/src/cli/help.ts +133 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/output.ts +102 -0
- package/src/cli/run.ts +106 -0
- package/src/commands/{appAndroid.ts → app/android.ts} +451 -42
- package/src/commands/{appDesktop.ts → app/desktop.ts} +63 -32
- package/src/commands/{appDesktopMac.ts → app/desktopMac.ts} +24 -18
- package/src/commands/{appIcon.ts → app/icon.ts} +2 -2
- package/src/commands/{app.ts → app/index.ts} +397 -289
- package/src/commands/{appShared.ts → app/shared.ts} +30 -39
- package/src/commands/{appTemplatesAndroid.ts → app/templates/android.ts} +62 -28
- package/src/commands/{appTemplatesGradlew.ts → app/templates/gradlew.ts} +1 -1
- package/src/commands/{appTemplates.ts → app/templates/ios.ts} +1 -1
- package/src/commands/assets.ts +23 -21
- package/src/commands/clone.ts +48 -51
- package/src/commands/compile.ts +62 -88
- package/src/commands/create.ts +46 -47
- package/src/commands/design/comments.ts +361 -0
- package/src/commands/design/context.ts +101 -0
- package/src/commands/design/index.ts +391 -0
- package/src/commands/design/snapshot.ts +134 -0
- package/src/commands/{designTemplates.ts → design/templates.ts} +4 -3
- package/src/commands/desktop.ts +90 -82
- package/src/commands/dev.ts +201 -176
- package/src/commands/diff.ts +39 -51
- package/src/commands/index.ts +60 -0
- package/src/commands/{init.ts → init/index.ts} +199 -191
- package/src/commands/install.ts +80 -67
- package/src/commands/lightmap.ts +203 -171
- package/src/commands/link.ts +87 -94
- package/src/commands/login.ts +25 -23
- package/src/commands/navmesh.ts +168 -138
- package/src/commands/pn.ts +201 -244
- package/src/commands/pull.ts +49 -56
- package/src/commands/push.ts +48 -54
- package/src/commands/render.ts +158 -99
- package/src/commands/scene.ts +58 -55
- package/src/commands/{shaders.ts → shaders/index.ts} +61 -42
- package/src/commands/{shadersNew.ts → shaders/new.ts} +2 -2
- package/src/commands/shared.ts +79 -0
- package/src/commands/status.ts +22 -26
- package/src/commands/test.ts +398 -371
- package/src/commands/thumbs.ts +185 -175
- package/src/commands/update/index.ts +226 -0
- package/src/commands/{types.ts → update/types.ts} +31 -22
- package/src/compile/collect.ts +5 -5
- package/src/compile/collectLocal.ts +3 -3
- package/src/compile/designCompile.ts +3 -3
- package/src/compile/headlessBundle.ts +133 -129
- package/src/compile/projectCompile.ts +2 -2
- package/src/compile/sceneCompile.ts +10 -8
- package/src/compile/screenEntry.ts +123 -127
- package/src/compile/shaders.ts +3 -3
- package/src/dev/androidDev.ts +1 -1
- package/src/dev/devServer.ts +3 -3
- package/src/dev/webRunner.ts +1 -1
- package/src/{cmgenTool.ts → hosts/cmgenTool.ts} +1 -1
- package/src/{desktopRenderer.ts → hosts/desktopRenderer.ts} +6 -5
- package/src/{desktopScript.ts → hosts/desktopScript.ts} +1 -1
- package/src/{distRoot.ts → hosts/distRoot.ts} +9 -5
- package/src/{matcTool.ts → hosts/matcTool.ts} +1 -1
- package/src/{peerInstall.ts → hosts/peerInstall.ts} +2 -2
- package/src/{releases.ts → hosts/releases.ts} +1 -1
- package/src/index.ts +32 -480
- package/src/platform/api.ts +302 -0
- package/src/{browserAuth.ts → platform/browserAuth.ts} +1 -1
- package/src/{config.ts → platform/config.ts} +1 -1
- package/src/{serverDiff.ts → platform/serverDiff.ts} +3 -3
- package/src/{projectEnv.ts → project/env.ts} +33 -2
- package/src/{localFiles.ts → project/localFiles.ts} +1 -1
- package/src/{manifest.ts → project/manifest.ts} +1 -1
- package/src/{project.ts → project/materialize.ts} +2 -2
- package/src/project/paths.ts +20 -0
- package/src/{textDiff.ts → project/textDiff.ts} +1 -1
- package/src/{types.ts → project/types.ts} +0 -0
- package/runtime/web/assets/createViewerLite-Ct_PZdof.js +0 -852
- package/src/commands/design.ts +0 -846
- package/src/commands/update.ts +0 -211
- package/src/util.ts +0 -146
- /package/src/commands/{projectTemplates.ts → init/templates.ts} +0 -0
- /package/src/{lecodes-3d-editor.d.ts → declarations/lecodes-3d-editor.d.ts} +0 -0
- /package/src/{lecodes-assets.d.ts → declarations/lecodes-assets.d.ts} +0 -0
- /package/src/{lecodes-design.d.ts → declarations/lecodes-design.d.ts} +0 -0
- /package/src/{lecodes-renderer.d.ts → declarations/lecodes-renderer.d.ts} +0 -0
- /package/src/{qrcode-terminal.d.ts → declarations/qrcode-terminal.d.ts} +0 -0
- /package/src/{peers.ts → hosts/peers.ts} +0 -0
- /package/src/{designMeta.ts → project/designMeta.ts} +0 -0
- /package/src/{ignore.ts → project/ignore.ts} +0 -0
|
@@ -2,14 +2,14 @@ import { execFileSync } from "node:child_process"
|
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
3
3
|
import { homedir } from "node:os"
|
|
4
4
|
import { dirname, join, resolve } from "node:path"
|
|
5
|
-
import { findProjectRoot } from "
|
|
6
|
-
import { DEFAULT_IGNORE_FILE } from "
|
|
7
|
-
import { CliError, c,
|
|
5
|
+
import { findProjectRoot } from "../../project/manifest"
|
|
6
|
+
import { DEFAULT_IGNORE_FILE } from "../../project/ignore"
|
|
7
|
+
import { CliError, c, log, note, warn } from "../../cli"
|
|
8
8
|
|
|
9
|
-
/* Shared plumbing for the `lecodes app` platform arms (
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* (lecodes-ios-sdk vs lecodes-android-sdk) and resolvePlugin a platform one. */
|
|
9
|
+
/* Shared plumbing for the `lecodes app` platform arms (index.ts = iOS + the command tree,
|
|
10
|
+
* android.ts = Android, desktop.ts = the portable desktop build) and for every other command that
|
|
11
|
+
* reads app.json (the desktop window, the renderer, native plugin libraries). The SDK helpers take
|
|
12
|
+
* a repo parameter (lecodes-ios-sdk vs lecodes-android-sdk) and resolvePlugin a platform one. */
|
|
13
13
|
|
|
14
14
|
/** First-party plugin registry: id → GitHub repo holding plugins/<id>/. Third-party plugins
|
|
15
15
|
* use a `github:owner/repo[@tag]` spec in app.json instead (manifest at the repo root). */
|
|
@@ -53,11 +53,14 @@ export interface AppConfig {
|
|
|
53
53
|
* `variant`: SDK artifact — "auto" (default: sync picks the smallest one covering the engines
|
|
54
54
|
* the compiled bundle actually uses) or core|2d|3d|full to force one, like ios.variant
|
|
55
55
|
* (needs lecodes-android-sdk >= 1.3.0 — older releases ship only the full AAR).
|
|
56
|
-
* `signing`: keystore METADATA only
|
|
57
|
-
*
|
|
56
|
+
* `signing`: keystore METADATA only (written by `lecodes app keystore create|use`) — the
|
|
57
|
+
* path + passwords live in the project .env as LECODES_ANDROID_* (or android/
|
|
58
|
+
* keystore.properties for Studio-only builds), never in app.json.
|
|
59
|
+
* `minify`: R8 on release builds (default true; sync restamps the values in app/build.gradle.kts). */
|
|
58
60
|
android?: {
|
|
59
61
|
applicationId?: string, versionCode?: number, sdk?: string, variant?: string,
|
|
60
62
|
signing?: { storeFile?: string, keyAlias?: string },
|
|
63
|
+
minify?: boolean,
|
|
61
64
|
}
|
|
62
65
|
/** Desktop host (packages/desktop). `window` sizes are LOGICAL units — the host multiplies by
|
|
63
66
|
* the display scale. `entry` picks a per-target entry file (default: the detected project
|
|
@@ -67,8 +70,8 @@ export interface AppConfig {
|
|
|
67
70
|
window?: DesktopWindowConfig,
|
|
68
71
|
/** Which desktop host binary to run: "gl" (default) or "vulkan" (Windows only, host >= 1.4.0).
|
|
69
72
|
* A BUILD/DEV-TIME host selection, not a runtime switch — the two are separate binaries with
|
|
70
|
-
* their own shaders and different features (the Vulkan one has no
|
|
71
|
-
*
|
|
73
|
+
* their own shaders and different features (the Vulkan one has no native-view plugins).
|
|
74
|
+
* Overridable per run with `--renderer` / LECODES_DESKTOP_RENDERER. */
|
|
72
75
|
renderer?: "gl" | "vulkan",
|
|
73
76
|
/** 3D buffer scale vs the window (0.25–1, host ≥1.4.0). The UI always renders at native
|
|
74
77
|
* pixels; only the 3D scene is rendered smaller and upscaled by the compositor. Multiplies
|
|
@@ -88,7 +91,7 @@ export interface AppConfig {
|
|
|
88
91
|
* a built folder's host reads it from the staged app.json. Headless render/test ignore it. */
|
|
89
92
|
virtualKeyboard?: boolean | DesktopVirtualKeyboard,
|
|
90
93
|
/** The macOS .app build (`lecodes app build desktop` on a Mac) — identity + signing +
|
|
91
|
-
* notarization. See
|
|
94
|
+
* notarization. See desktopMac.ts for the whole story. */
|
|
92
95
|
macos?: MacDesktopConfig,
|
|
93
96
|
}
|
|
94
97
|
}
|
|
@@ -181,13 +184,14 @@ export const readAppConfigAt = (root: string): AppConfig | null =>
|
|
|
181
184
|
export const DESKTOP_DEFAULT_WINDOW = { width: 1280, height: 720 }
|
|
182
185
|
|
|
183
186
|
/**
|
|
184
|
-
* The renderer for this run: `--renderer` > LECODES_DESKTOP_RENDERER > app.json
|
|
185
|
-
* > "gl". Resolve it ONCE per command and pass the result to both
|
|
186
|
-
* shader staging — two independent reads of the config is how a build
|
|
187
|
-
* next to an OpenGL .filamat, which fails at the first material with a
|
|
187
|
+
* The renderer for this run: the `--renderer` flag > LECODES_DESKTOP_RENDERER > app.json
|
|
188
|
+
* `desktop.renderer` > "gl". Resolve it ONCE per command and pass the result to both
|
|
189
|
+
* `resolveDesktopExe` and the shader staging — two independent reads of the config is how a build
|
|
190
|
+
* ends up with a Vulkan exe next to an OpenGL .filamat, which fails at the first material with a
|
|
191
|
+
* Filament abort.
|
|
188
192
|
*/
|
|
189
|
-
export const resolveDesktopRenderer = (root: string,
|
|
190
|
-
const raw =
|
|
193
|
+
export const resolveDesktopRenderer = (root: string, renderer?: string): "gl" | "vulkan" => {
|
|
194
|
+
const raw = renderer
|
|
191
195
|
?? process.env.LECODES_DESKTOP_RENDERER
|
|
192
196
|
?? readAppConfigAt(root)?.desktop?.renderer
|
|
193
197
|
if (raw === undefined) return "gl"
|
|
@@ -303,10 +307,16 @@ export const git = (cwd: string, ...args: string[]): string =>
|
|
|
303
307
|
|
|
304
308
|
export const slugify = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "app"
|
|
305
309
|
|
|
310
|
+
/** The environment for a child process. Spelled out on every spawn on purpose: Bun hands an
|
|
311
|
+
* inherited child the environment SNAPSHOT taken at startup, so anything the project .env
|
|
312
|
+
* contributed (projectEnv.ts — keystore passwords, CREATOR_* tracing) would never arrive. */
|
|
313
|
+
export const childEnv = (extra: Record<string, string> = {}): Record<string, string | undefined> =>
|
|
314
|
+
({ ...process.env, ...extra })
|
|
315
|
+
|
|
306
316
|
/** Run a tool, capturing output; on failure surface the tail (xcodebuild/gradle logs are huge). */
|
|
307
317
|
export const runTool = (cwd: string, cmd: string, args: string[]): string => {
|
|
308
318
|
try {
|
|
309
|
-
return execFileSync(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 128 * 1024 * 1024 })
|
|
319
|
+
return execFileSync(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 128 * 1024 * 1024, env: childEnv() })
|
|
310
320
|
} catch (e) {
|
|
311
321
|
const err = e as { stdout?: string, stderr?: string }
|
|
312
322
|
const tail = ((err.stdout ?? "") + "\n" + (err.stderr ?? "")).split("\n").filter(l => l.trim()).slice(-25).join("\n")
|
|
@@ -329,7 +339,7 @@ export const ensureShellIgnored = (root: string, dir: string) => {
|
|
|
329
339
|
/* ----------------------------------- SDK variants ----------------------------------- */
|
|
330
340
|
|
|
331
341
|
// Both native SDKs ship the same variant matrix (full / 3d / 2d / core), so the detection and
|
|
332
|
-
// resolution logic is shared between the iOS (
|
|
342
|
+
// resolution logic is shared between the iOS (index.ts) and Android (android.ts) syncs.
|
|
333
343
|
export const SDK_VARIANTS = ["core", "2d", "3d", "full"] as const
|
|
334
344
|
type SdkVariantShared = (typeof SDK_VARIANTS)[number]
|
|
335
345
|
|
|
@@ -346,25 +356,6 @@ export const detectEngines = (bundleJs: string): { gl: boolean, g2: boolean } =>
|
|
|
346
356
|
return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) }
|
|
347
357
|
}
|
|
348
358
|
|
|
349
|
-
/**
|
|
350
|
-
* Refuse a Vulkan desktop build for a project that uses the 2D engine. The Vulkan host is built with
|
|
351
|
-
* CREATOR_PKG_2D=OFF (creator-2d is sokol-on-GL, with no Vulkan twin), so those scenes don't render
|
|
352
|
-
* — a blank window at runtime with nothing in the log to explain it. Fail at build time instead,
|
|
353
|
-
* where the fix ("use the default renderer") is one line away.
|
|
354
|
-
*
|
|
355
|
-
* Detection is the same `// 2d: enable` bundle header `lecodes app sync` picks its SDK variant from,
|
|
356
|
-
* so it can't drift from what the engine actually needs.
|
|
357
|
-
*/
|
|
358
|
-
export const assertRendererSupportsBundle = (renderer: "gl" | "vulkan", bundleJs: string): void => {
|
|
359
|
-
if (renderer !== "vulkan") return
|
|
360
|
-
if (!detectEngines(bundleJs).g2) return
|
|
361
|
-
throw new CliError(
|
|
362
|
-
"This project uses the 2D engine, which the Vulkan desktop host doesn't include " +
|
|
363
|
-
"(creator-2d renders through OpenGL and has no Vulkan path yet).\n" +
|
|
364
|
-
" Build with the default renderer: drop `desktop.renderer` from app.json, or pass `--renderer gl`.",
|
|
365
|
-
)
|
|
366
|
-
}
|
|
367
|
-
|
|
368
359
|
/** The SDK product for a shell: the smallest variant covering the engines the compiled bundle
|
|
369
360
|
* actually uses, unless <configKey> (ios.variant / android.variant) forces one — then warn when
|
|
370
361
|
* the forced product can't run the project (missing engine = those scenes silently no-op on
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Templates for `lecodes app init android` / `lecodes app sync` — the Android mirror of
|
|
2
|
-
*
|
|
2
|
+
* ios.ts, distilled from the LetaryCreatorViewer host (lecodes-android :app). The
|
|
3
3
|
* same three ownership tiers: app/ is USER-owned after generation (init writes once, sync
|
|
4
4
|
* never touches it — except app/src/main/assets/, the Resources/ analog, and values-only
|
|
5
5
|
* versionName/versionCode restamps in app/build.gradle.kts); settings/root gradle files,
|
|
@@ -234,16 +234,17 @@ export const ANDROID_README = `# Android shell
|
|
|
234
234
|
Generated by \`lecodes app init android\`. Daily workflow:
|
|
235
235
|
|
|
236
236
|
\`\`\`
|
|
237
|
-
lecodes app sync
|
|
238
|
-
lecodes app open android
|
|
239
|
-
lecodes app
|
|
237
|
+
lecodes app sync # embed the compiled bundle + plugins into this shell
|
|
238
|
+
lecodes app open android # open in Android Studio
|
|
239
|
+
lecodes app keystore create # once: the release signing key (or \`keystore use <file>\`)
|
|
240
|
+
lecodes app build android # signed release .apk + .aab → ../dist/
|
|
240
241
|
\`\`\`
|
|
241
242
|
|
|
242
243
|
## Ownership
|
|
243
244
|
|
|
244
245
|
| Path | Owner |
|
|
245
246
|
| --- | --- |
|
|
246
|
-
| \`app/\` (sources, manifest, res, build.gradle.kts) | **Yours** after init — sync never rewrites them (it only restamps \`versionName\`/\`versionCode\` values, manages \`// lecodes:plugin\`-tagged lines in the plugins block, and — when \`app.json\` sets \`"icon"\` — the icon files below) |
|
|
247
|
+
| \`app/\` (sources, manifest, res, build.gradle.kts, proguard-rules.pro) | **Yours** after init — sync never rewrites them (it only restamps the \`versionName\`/\`versionCode\` and \`isMinifyEnabled\`/\`isShrinkResources\` values, manages \`// lecodes:plugin\`-tagged lines in the plugins block, and — when \`app.json\` sets \`"icon"\` — the icon files below) |
|
|
247
248
|
| \`app/src/main/assets/\` | CLI-owned — wiped and regenerated by every sync (the compiled \`app.js\` + its \`// preload:\` assets) |
|
|
248
249
|
| \`res/mipmap-*/ic_launcher*\`, \`res/values/lecodes_icon.xml\` | CLI-owned **only when** \`app.json\` sets \`"icon"\` — regenerated by every sync (plus a value-only \`android:icon\` restamp in the manifest); without the key sync never touches icons |
|
|
249
250
|
| \`settings.gradle.kts\`, \`build.gradle.kts\` (root), \`gradle.properties\`, \`lecodes-runtime/\`, \`plugins/\` | CLI-owned — regenerated by every sync |
|
|
@@ -280,17 +281,42 @@ dependencies they need to \`app/build.gradle.kts\` — that file is yours.
|
|
|
280
281
|
|
|
281
282
|
## Release signing
|
|
282
283
|
|
|
283
|
-
|
|
284
|
+
The key lives in the project's \`.env\` (next to \`app.json\`; the CLI loads it for every command
|
|
285
|
+
and never pushes it):
|
|
284
286
|
|
|
285
287
|
\`\`\`
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
LECODES_ANDROID_KEYSTORE_FILE=./android/release.keystore # ./ = the project root; ~/ and absolute paths work too
|
|
289
|
+
LECODES_ANDROID_KEYSTORE_PASSWORD=…
|
|
290
|
+
LECODES_ANDROID_KEY_ALIAS=upload
|
|
291
|
+
LECODES_ANDROID_KEY_PASSWORD=…
|
|
290
292
|
\`\`\`
|
|
291
293
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
+
\`lecodes app keystore create\` makes a new key and writes those lines; \`lecodes app keystore use
|
|
295
|
+
<file>\` attaches an existing one (a keystore outside the project is just a path); \`lecodes app
|
|
296
|
+
keystore info\` prints the SHA-1 / SHA-256 fingerprints the Play Console, Firebase and Google
|
|
297
|
+
Sign-In ask for. The same four names work as real environment variables (CI). Building from
|
|
298
|
+
Android Studio without the CLI? \`android/keystore.properties\` (gitignored, \`storeFile\` /
|
|
299
|
+
\`storePassword\` / \`keyAlias\` / \`keyPassword\`) is still honoured and wins over the env.
|
|
300
|
+
Never put passwords in app.json — \`android.signing\` there is metadata only.
|
|
301
|
+
|
|
302
|
+
With Play App Signing (the default for new Play apps) this key is the UPLOAD key — Google holds
|
|
303
|
+
the app signing key and a lost upload key can be reset through Play support. Without it, losing
|
|
304
|
+
the keystore means the app can never be updated. Back it up either way.
|
|
305
|
+
|
|
306
|
+
## Building
|
|
307
|
+
|
|
308
|
+
\`lecodes app build android\` syncs, runs \`gradlew assembleRelease bundleRelease\` and copies the
|
|
309
|
+
results to \`../dist/<name>-<version>-<versionCode>.apk|.aab\` (\`-o <dir>\` elsewhere). A release
|
|
310
|
+
without a signing key is refused (\`--allow-unsigned\` for a throwaway build); \`--configuration
|
|
311
|
+
Debug\` signs with the debug keystore. \`--apk\` / \`--aab\` pick one artifact.
|
|
312
|
+
|
|
313
|
+
Release builds run R8 (\`isMinifyEnabled\` + \`isShrinkResources\`): the SDK AAR ships the keep
|
|
314
|
+
rules its JNI bindings need, plugins ship theirs, \`app/proguard-rules.pro\` is for your own
|
|
315
|
+
classes. \`app.json\` \`"android": { "minify": false }\` turns it off (sync restamps the values).
|
|
316
|
+
R8 only touches the dex — the engine's native libraries and \`app.js\` are not shrunk.
|
|
317
|
+
|
|
318
|
+
\`abiFilters\` in \`app/build.gradle.kts\` keeps the package to the two ABIs the SDK ships
|
|
319
|
+
(arm64-v8a, armeabi-v7a).
|
|
294
320
|
|
|
295
321
|
## Icons
|
|
296
322
|
|
|
@@ -314,11 +340,11 @@ the init-time placeholder.
|
|
|
314
340
|
|
|
315
341
|
/* ---------------------------- USER-owned (written once) ---------------------------- */
|
|
316
342
|
|
|
317
|
-
export const appBuildGradleTemplate = ({ applicationId, version, versionCode }: AndroidShellIdentity): string => `import java.util.Properties
|
|
343
|
+
export const appBuildGradleTemplate = ({ applicationId, version, versionCode }: AndroidShellIdentity, minify = true): string => `import java.util.Properties
|
|
318
344
|
|
|
319
345
|
// Generated by \`lecodes app init\` — yours to edit. \`lecodes app sync\` only restamps the
|
|
320
|
-
// versionName/versionCode VALUES and manages
|
|
321
|
-
// block below; everything else is left alone.
|
|
346
|
+
// versionName/versionCode and isMinifyEnabled/isShrinkResources VALUES and manages
|
|
347
|
+
// \`// lecodes:plugin\`-tagged lines in the plugins block below; everything else is left alone.
|
|
322
348
|
plugins {
|
|
323
349
|
id("com.android.application")
|
|
324
350
|
id("org.jetbrains.kotlin.android")
|
|
@@ -342,6 +368,9 @@ android {
|
|
|
342
368
|
targetSdk = ${TARGET_SDK}
|
|
343
369
|
versionCode = ${versionCode}
|
|
344
370
|
versionName = "${version}"
|
|
371
|
+
// The LeCodes SDK ships arm64-v8a + armeabi-v7a natives. A transitive dependency adds a
|
|
372
|
+
// lone x86_64 library, which would make the package claim an ABI the engine can't load on.
|
|
373
|
+
ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") }
|
|
345
374
|
}
|
|
346
375
|
|
|
347
376
|
val storeFilePath = signing("storeFile", "LECODES_ANDROID_KEYSTORE_FILE")
|
|
@@ -356,7 +385,12 @@ android {
|
|
|
356
385
|
|
|
357
386
|
buildTypes {
|
|
358
387
|
release {
|
|
359
|
-
|
|
388
|
+
// R8 (shrink + optimize + obfuscate). The SDK AAR carries the keep rules its JNI
|
|
389
|
+
// bindings need; app-local rules go in proguard-rules.pro. app.json
|
|
390
|
+
// android.minify: false turns it off (sync restamps these two values).
|
|
391
|
+
isMinifyEnabled = ${minify}
|
|
392
|
+
isShrinkResources = ${minify}
|
|
393
|
+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
|
360
394
|
signingConfig = signingConfigs.findByName("release")
|
|
361
395
|
}
|
|
362
396
|
}
|
|
@@ -378,6 +412,18 @@ dependencies {
|
|
|
378
412
|
}
|
|
379
413
|
`
|
|
380
414
|
|
|
415
|
+
/** app/proguard-rules.pro — user-owned R8 rules for app-local code. The SDK's own keep rules
|
|
416
|
+
* (its JNI-bound classes) ride inside the AAR as consumer rules, and plugin modules ship theirs
|
|
417
|
+
* through consumerProguardFiles, so this file starts empty. */
|
|
418
|
+
export const APP_PROGUARD_RULES = `# R8 rules for app-local code (yours — \`lecodes app sync\` never touches this file).
|
|
419
|
+
#
|
|
420
|
+
# The LeCodes SDK keeps its own JNI-bound classes through the consumer rules inside the AAR, and
|
|
421
|
+
# plugin modules bring their own. Add rules here only for classes YOUR Kotlin registers with the
|
|
422
|
+
# engine by name (engine.registerCallback / registerService / registerView) or reaches through
|
|
423
|
+
# reflection, e.g.
|
|
424
|
+
# -keep class com.example.myapp.MyBridge { *; }
|
|
425
|
+
`
|
|
426
|
+
|
|
381
427
|
export const appManifestTemplate = (): string => `<?xml version="1.0" encoding="utf-8"?>
|
|
382
428
|
<!-- Generated by \`lecodes app init\` — yours to edit. Sync leaves it alone, except that with
|
|
383
429
|
app.json "icon" set it restamps the android:icon VALUE to the generated @mipmap set.
|
|
@@ -404,18 +450,6 @@ export const appManifestTemplate = (): string => `<?xml version="1.0" encoding="
|
|
|
404
450
|
<action android:name="android.intent.action.MAIN" />
|
|
405
451
|
<category android:name="android.intent.category.LAUNCHER" />
|
|
406
452
|
</intent-filter>
|
|
407
|
-
|
|
408
|
-
<!-- le.codes/qr deep links (QR scans, notification taps). Editable — remove if the
|
|
409
|
-
app should not claim them. -->
|
|
410
|
-
<intent-filter android:autoVerify="true">
|
|
411
|
-
<action android:name="android.intent.action.VIEW" />
|
|
412
|
-
<category android:name="android.intent.category.DEFAULT" />
|
|
413
|
-
<category android:name="android.intent.category.BROWSABLE" />
|
|
414
|
-
<data android:scheme="https" />
|
|
415
|
-
<data android:scheme="http" />
|
|
416
|
-
<data android:host="le.codes" />
|
|
417
|
-
<data android:pathPrefix="/qr" />
|
|
418
|
-
</intent-filter>
|
|
419
453
|
</activity>
|
|
420
454
|
</application>
|
|
421
455
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Gradle wrapper launcher scripts, verbatim from the gradle/gradle v8.13.0 tag (the same
|
|
2
|
-
* pinned source as the wrapper jar download in
|
|
2
|
+
* pinned source as the wrapper jar download in ../android.ts). JSON-escaped mechanically —
|
|
3
3
|
* regenerate from the tag rather than editing. gradlew must be written with LF and
|
|
4
4
|
* gradlew.bat with CRLF (initAndroid handles the conversion); both are version-agnostic
|
|
5
5
|
* launchers that read gradle/wrapper/gradle-wrapper.properties for the real distribution. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { swiftPackageName, type IosPackage } from "
|
|
1
|
+
import { swiftPackageName, type IosPackage } from "../shared"
|
|
2
2
|
|
|
3
3
|
/* Templates for `lecodes app init ios` / `lecodes app sync` — distilled 1:1 from the
|
|
4
4
|
* hand-built, device-verified reference shell (docs/xcode-app-plan.md). Three ownership
|
package/src/commands/assets.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolveCmgenExe } from "../cmgenTool"
|
|
3
|
-
import { loadPeer } from "../peerInstall"
|
|
1
|
+
import { defineCommand } from "../cli"
|
|
2
|
+
import { resolveCmgenExe } from "../hosts/cmgenTool"
|
|
3
|
+
import { loadPeer } from "../hosts/peerInstall"
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
|
-
* `lecodes assets
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* board and the renderer); its own argv parser handles the sub-command flags, so the raw argv is
|
|
10
|
-
* forwarded untouched (`assets` gets `_raw` from main). See docs/animation-plan.md §2.9.
|
|
6
|
+
* `lecodes assets …` — the asset pipeline (packages/lecodes-assets): FBX → GLB, the GLB doctor,
|
|
7
|
+
* retargeting, Unity packs, skies. The package is an optional peer with its OWN argv parser and
|
|
8
|
+
* help, so the argv is forwarded untouched (`passthrough`) — `lecodes assets --help` is theirs.
|
|
11
9
|
*/
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
export default defineCommand({
|
|
11
|
+
name: "assets",
|
|
12
|
+
summary: "The asset pipeline: convert / probe / doctor / retarget / unpack / scene / sky / terrain-pack …",
|
|
13
|
+
usage: "<command> [args…]",
|
|
14
|
+
passthrough: true,
|
|
15
|
+
examples: ["lecodes assets convert hero.fbx --clips anims/*.fbx -o hero.glb", "lecodes assets doctor hero.glb --fix --ktx2", "lecodes assets --help"],
|
|
16
|
+
run: async ({ argv }) => {
|
|
17
|
+
const mod = await loadPeer<typeof import("lecodes-assets/cli")>("lecodes-assets", "cli", { for: "lecodes assets" })
|
|
18
|
+
// `sky` shells out to filament's cmgen, which the asset package deliberately never downloads
|
|
19
|
+
// itself (it stays network-free and usable standalone). The CLI owns release artifacts, so it
|
|
20
|
+
// resolves the binary — cache or first-use download — and hands it over through the env var the
|
|
21
|
+
// package already honours. An explicit LECODES_CMGEN wins, so a local filament build still rules.
|
|
22
|
+
if (argv[0] === "sky" && !process.env.LECODES_CMGEN) process.env.LECODES_CMGEN = await resolveCmgenExe()
|
|
23
|
+
const code = await mod.runAssetsCli(argv)
|
|
24
|
+
if (code !== 0) process.exitCode = code
|
|
25
|
+
},
|
|
26
|
+
})
|
package/src/commands/clone.ts
CHANGED
|
@@ -1,60 +1,57 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync } from "node:fs"
|
|
2
2
|
import { resolve } from "node:path"
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
/* `lecodes clone <uuid|url> [dir]` —
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* (https://le.codes/app/projects/<uuid>) and viewer links (.../view/<uuid>), with any
|
|
16
|
-
* query/hash or trailing slash; falls back to the last path segment.
|
|
17
|
-
*/
|
|
3
|
+
import { CliError, UsageError, bool, c, defineCommand, endProgress, info, progress, success } from "../cli"
|
|
4
|
+
import { getCommits, getProject } from "../platform/api"
|
|
5
|
+
import { ensureGitignore, writeDefaultIgnore } from "../project/ignore"
|
|
6
|
+
import { writeManifest } from "../project/manifest"
|
|
7
|
+
import { materializeAssets } from "../project/materialize"
|
|
8
|
+
import { materializeTypes, writeTsconfig } from "../project/types"
|
|
9
|
+
import { session, slugOf } from "./shared"
|
|
10
|
+
|
|
11
|
+
/* `lecodes clone <uuid|url> [dir]` — a project's files + types into a new folder. */
|
|
12
|
+
|
|
13
|
+
/** A bare uuid or a project link (editor `/app/projects/<uuid>`, viewer `/view/<uuid>`, any query /
|
|
14
|
+
* hash / trailing slash) → the uuid; falls back to the last path segment. */
|
|
18
15
|
export const parseUuid = (input: string): string => {
|
|
19
|
-
if (!input) throw new
|
|
16
|
+
if (!input) throw new UsageError("a project uuid or url is required")
|
|
20
17
|
if (!input.includes("/")) return input
|
|
21
18
|
const clean = input.split(/[?#]/)[0].replace(/\/+$/, "")
|
|
22
19
|
const match = clean.match(/\/(?:projects|view)\/([^/]+)/)
|
|
23
20
|
return match ? match[1] : clean.slice(clean.lastIndexOf("/") + 1)
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
throw new CliError(`Directory "${dir}" already exists and is not empty.`)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
23
|
+
export default defineCommand({
|
|
24
|
+
name: "clone",
|
|
25
|
+
summary: "Download a project's files + types into a new folder",
|
|
26
|
+
usage: "<uuid|url> [dir]",
|
|
27
|
+
flags: { "no-types": bool("skip the .d.ts / tsconfig generation") },
|
|
28
|
+
examples: ["lecodes clone https://le.codes/app/projects/<uuid> myapp"],
|
|
29
|
+
run: async ({ args, flags }) => {
|
|
30
|
+
const { apiUrl, token } = session()
|
|
31
|
+
const uuid = parseUuid(args[0])
|
|
32
|
+
const project = await getProject(apiUrl, token, uuid)
|
|
33
|
+
|
|
34
|
+
const dir = args[1] ?? slugOf(project.name)
|
|
35
|
+
const root = resolve(dir)
|
|
36
|
+
if (existsSync(root) && readdirSync(root).length > 0) throw new CliError(`Directory "${dir}" already exists and is not empty.`)
|
|
37
|
+
mkdirSync(root, { recursive: true })
|
|
38
|
+
|
|
39
|
+
info(c.dim(`Fetching project ${c.bold(project.name)}…`))
|
|
40
|
+
const files = await materializeAssets(root, project, apiUrl, token, progress)
|
|
41
|
+
endProgress()
|
|
42
|
+
|
|
43
|
+
if (!flags["no-types"]) {
|
|
44
|
+
info(c.dim("Fetching type definitions…"))
|
|
45
|
+
await materializeTypes(root, apiUrl)
|
|
46
|
+
writeTsconfig(root)
|
|
47
|
+
}
|
|
48
|
+
writeDefaultIgnore(root)
|
|
49
|
+
ensureGitignore(root) // .lecodes/ is per-checkout state — keep it out of the project's git repo
|
|
50
|
+
|
|
51
|
+
const commits = await getCommits(apiUrl, token, uuid)
|
|
52
|
+
writeManifest(root, { uuid, apiUrl, name: project.name, baseHeadId: commits.headId, files })
|
|
53
|
+
|
|
54
|
+
success(`Cloned ${c.bold(project.name)} into ${c.bold(dir)}/ (${Object.keys(files).length} files)`)
|
|
55
|
+
info(c.dim(` cd ${dir} — edit, then \`lecodes push -m "your message"\``))
|
|
56
|
+
},
|
|
57
|
+
})
|