lecodes-cli 0.19.0 → 0.20.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.
Files changed (270) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +1069 -396
  3. package/package.json +14 -13
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk-types.json +1 -1
  6. package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
  7. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  8. package/runtime/web/assets/basis-C64VHDVD.js +1 -0
  9. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  10. package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
  11. package/runtime/web/assets/draco-BiISTFcR.js +118 -0
  12. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  13. package/runtime/web/assets/index-CfNCCBjN.js +2 -0
  14. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
  15. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
  16. package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
  17. package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
  18. package/runtime/web/embed.html +32 -0
  19. package/runtime/web/embed.js +171 -0
  20. package/src/api.ts +302 -0
  21. package/src/browserAuth.ts +87 -0
  22. package/src/cmgenTool.ts +104 -0
  23. package/src/commands/app.ts +896 -0
  24. package/src/commands/appAndroid.ts +603 -0
  25. package/src/commands/appDesktop.ts +339 -0
  26. package/src/commands/appDesktopMac.ts +470 -0
  27. package/src/commands/appIcon.ts +187 -0
  28. package/src/commands/appShared.ts +542 -0
  29. package/src/commands/appTemplates.ts +907 -0
  30. package/src/commands/appTemplatesAndroid.ts +599 -0
  31. package/src/commands/appTemplatesGradlew.ts +9 -0
  32. package/src/commands/assets.ts +28 -0
  33. package/src/commands/clone.ts +60 -0
  34. package/src/commands/compile.ts +253 -0
  35. package/src/commands/create.ts +59 -0
  36. package/src/commands/design.ts +863 -0
  37. package/src/commands/designTemplates.ts +11 -0
  38. package/src/commands/desktop.ts +127 -0
  39. package/src/commands/dev.ts +236 -0
  40. package/src/commands/diff.ts +59 -0
  41. package/src/commands/init.ts +191 -0
  42. package/src/commands/install.ts +198 -0
  43. package/src/commands/lightmap.ts +290 -0
  44. package/src/commands/link.ts +147 -0
  45. package/src/commands/login.ts +24 -0
  46. package/src/commands/navmesh.ts +225 -0
  47. package/src/commands/pn.ts +305 -0
  48. package/src/commands/projectTemplates.ts +130 -0
  49. package/src/commands/pull.ts +109 -0
  50. package/src/commands/push.ts +173 -0
  51. package/src/commands/render.ts +416 -0
  52. package/src/commands/scene.ts +148 -0
  53. package/src/commands/shaders.ts +157 -0
  54. package/src/commands/status.ts +29 -0
  55. package/src/commands/test.ts +371 -0
  56. package/src/commands/thumbs.ts +178 -0
  57. package/src/commands/types.ts +87 -0
  58. package/src/commands/update.ts +190 -0
  59. package/src/compile/assetIcons.ts +214 -0
  60. package/src/compile/collect.ts +46 -0
  61. package/src/compile/collectLocal.ts +37 -0
  62. package/src/compile/designCompile.ts +104 -0
  63. package/src/compile/fonts.ts +156 -0
  64. package/src/compile/headlessBundle.ts +129 -0
  65. package/src/compile/nativeStack.ts +21 -0
  66. package/src/compile/projectCompile.ts +109 -0
  67. package/src/compile/sceneCompile.ts +113 -0
  68. package/src/compile/screenEntry.ts +127 -0
  69. package/src/compile/shaders.ts +245 -0
  70. package/src/config.ts +42 -0
  71. package/src/designMeta.ts +35 -0
  72. package/src/desktopRenderer.ts +545 -0
  73. package/src/desktopScript.ts +276 -0
  74. package/src/dev/clientTemplates.ts +160 -0
  75. package/src/dev/devServer.ts +333 -0
  76. package/src/dev/webRunner.ts +22 -0
  77. package/src/dev/wsServer.ts +144 -0
  78. package/src/distRoot.ts +20 -0
  79. package/src/ignore.ts +163 -0
  80. package/src/index.ts +494 -0
  81. package/src/lecodes-3d-editor.d.ts +41 -0
  82. package/src/lecodes-assets.d.ts +7 -0
  83. package/src/lecodes-design.d.ts +191 -0
  84. package/src/lecodes-renderer.d.ts +131 -0
  85. package/src/localFiles.ts +144 -0
  86. package/src/manifest.ts +46 -0
  87. package/src/matcTool.ts +137 -0
  88. package/src/peers.ts +40 -0
  89. package/src/project.ts +61 -0
  90. package/src/projectEnv.ts +94 -0
  91. package/src/qrcode-terminal.d.ts +9 -0
  92. package/src/releases.ts +125 -0
  93. package/src/serverDiff.ts +78 -0
  94. package/src/textDiff.ts +103 -0
  95. package/src/types.ts +0 -0
  96. package/src/util.ts +146 -0
  97. package/runtime/sdk/animate/animate.ts +0 -238
  98. package/runtime/sdk/animate/bezier.ts +0 -138
  99. package/runtime/sdk/animate/easings.ts +0 -126
  100. package/runtime/sdk/canvas/Canvas.ts +0 -305
  101. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  102. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  103. package/runtime/sdk/compile/assetMacro.ts +0 -146
  104. package/runtime/sdk/compile/assetName.ts +0 -50
  105. package/runtime/sdk/compile/bundler.ts +0 -287
  106. package/runtime/sdk/compile/compileProject.ts +0 -129
  107. package/runtime/sdk/compile/detectEntry.ts +0 -128
  108. package/runtime/sdk/compile/fontMacro.ts +0 -459
  109. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  110. package/runtime/sdk/compile/header.ts +0 -67
  111. package/runtime/sdk/compile/index.ts +0 -100
  112. package/runtime/sdk/compile/libraryImports.ts +0 -52
  113. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  114. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  115. package/runtime/sdk/compile/serverSplit.ts +0 -233
  116. package/runtime/sdk/compile/serverTypes.ts +0 -227
  117. package/runtime/sdk/compile/sfnt.ts +0 -98
  118. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  119. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  120. package/runtime/sdk/compile/sourcemap.ts +0 -25
  121. package/runtime/sdk/core/Aspect.ts +0 -568
  122. package/runtime/sdk/core/InspectorUI.ts +0 -212
  123. package/runtime/sdk/core/StateMachine.ts +0 -308
  124. package/runtime/sdk/core/color.ts +0 -66
  125. package/runtime/sdk/core/compWrite.ts +0 -42
  126. package/runtime/sdk/core/events.ts +0 -38
  127. package/runtime/sdk/core/fields.ts +0 -120
  128. package/runtime/sdk/core/registry.ts +0 -23
  129. package/runtime/sdk/core/signals.ts +0 -277
  130. package/runtime/sdk/core/time.ts +0 -84
  131. package/runtime/sdk/g2/Camera2D.ts +0 -40
  132. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  133. package/runtime/sdk/g2/Node2D.ts +0 -267
  134. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  135. package/runtime/sdk/g2/Physics2D.ts +0 -346
  136. package/runtime/sdk/g2/Scene2D.ts +0 -209
  137. package/runtime/sdk/g2/Shape2D.ts +0 -259
  138. package/runtime/sdk/g2/Sprite.ts +0 -89
  139. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  140. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  141. package/runtime/sdk/g2/Texture2D.ts +0 -47
  142. package/runtime/sdk/g2/Tilemap.ts +0 -41
  143. package/runtime/sdk/g2/Tileset.ts +0 -71
  144. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  145. package/runtime/sdk/g2/autotile.ts +0 -433
  146. package/runtime/sdk/g2/cells.ts +0 -91
  147. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  148. package/runtime/sdk/g2/groups2d.ts +0 -106
  149. package/runtime/sdk/g2/loop.ts +0 -50
  150. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  151. package/runtime/sdk/g2/touch.ts +0 -83
  152. package/runtime/sdk/gl/Camera.ts +0 -160
  153. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  154. package/runtime/sdk/gl/CharacterController.ts +0 -238
  155. package/runtime/sdk/gl/Gearbox.ts +0 -212
  156. package/runtime/sdk/gl/Geometry.ts +0 -279
  157. package/runtime/sdk/gl/IK.ts +0 -193
  158. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  159. package/runtime/sdk/gl/Light.ts +0 -107
  160. package/runtime/sdk/gl/Lightmap.ts +0 -256
  161. package/runtime/sdk/gl/Material.ts +0 -260
  162. package/runtime/sdk/gl/Mesh.ts +0 -83
  163. package/runtime/sdk/gl/Model.ts +0 -124
  164. package/runtime/sdk/gl/NavAgent.ts +0 -337
  165. package/runtime/sdk/gl/NavMesh.ts +0 -397
  166. package/runtime/sdk/gl/Node.ts +0 -350
  167. package/runtime/sdk/gl/Noise.ts +0 -30
  168. package/runtime/sdk/gl/Particles.ts +0 -676
  169. package/runtime/sdk/gl/Physics.ts +0 -222
  170. package/runtime/sdk/gl/Plane.ts +0 -53
  171. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  172. package/runtime/sdk/gl/Ray.ts +0 -16
  173. package/runtime/sdk/gl/Scene.ts +0 -486
  174. package/runtime/sdk/gl/Shape.ts +0 -409
  175. package/runtime/sdk/gl/Terrain.ts +0 -1092
  176. package/runtime/sdk/gl/Texture.ts +0 -63
  177. package/runtime/sdk/gl/Trigger.ts +0 -45
  178. package/runtime/sdk/gl/Vehicle.ts +0 -473
  179. package/runtime/sdk/gl/Wheel.ts +0 -240
  180. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  181. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  182. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  183. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  184. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  185. package/runtime/sdk/gl/animation/core.ts +0 -336
  186. package/runtime/sdk/gl/controls.ts +0 -95
  187. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  188. package/runtime/sdk/gl/scenarios.ts +0 -291
  189. package/runtime/sdk/gl/state.ts +0 -6
  190. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  191. package/runtime/sdk/gl/touch.ts +0 -68
  192. package/runtime/sdk/inject.ts +0 -197
  193. package/runtime/sdk/math/Mathf.ts +0 -118
  194. package/runtime/sdk/math/mat4.ts +0 -278
  195. package/runtime/sdk/math/quat.ts +0 -232
  196. package/runtime/sdk/math/vec.ts +0 -255
  197. package/runtime/sdk/net/codec.ts +0 -119
  198. package/runtime/sdk/net/core.ts +0 -384
  199. package/runtime/sdk/net/index.ts +0 -181
  200. package/runtime/sdk/net/replication.ts +0 -622
  201. package/runtime/sdk/plugins/camera.ts +0 -81
  202. package/runtime/sdk/plugins/geolocation.ts +0 -123
  203. package/runtime/sdk/plugins/oauth.ts +0 -61
  204. package/runtime/sdk/plugins/permission.ts +0 -7
  205. package/runtime/sdk/plugins/push.ts +0 -132
  206. package/runtime/sdk/plugins/qr.ts +0 -73
  207. package/runtime/sdk/plugins/service.ts +0 -47
  208. package/runtime/sdk/runtime/app.ts +0 -101
  209. package/runtime/sdk/runtime/appEvents.ts +0 -54
  210. package/runtime/sdk/runtime/channel.ts +0 -61
  211. package/runtime/sdk/runtime/clipboard.ts +0 -20
  212. package/runtime/sdk/runtime/datetime.ts +0 -329
  213. package/runtime/sdk/runtime/device.ts +0 -293
  214. package/runtime/sdk/runtime/fetch.ts +0 -77
  215. package/runtime/sdk/runtime/files.ts +0 -23
  216. package/runtime/sdk/runtime/input.ts +0 -175
  217. package/runtime/sdk/runtime/media.ts +0 -111
  218. package/runtime/sdk/runtime/misc.ts +0 -16
  219. package/runtime/sdk/runtime/net.ts +0 -36
  220. package/runtime/sdk/runtime/rpc.ts +0 -218
  221. package/runtime/sdk/runtime/service.ts +0 -83
  222. package/runtime/sdk/runtime/share.ts +0 -9
  223. package/runtime/sdk/runtime/storage.ts +0 -13
  224. package/runtime/sdk/runtime/touch.ts +0 -76
  225. package/runtime/sdk/scene/defineScene.ts +0 -1353
  226. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  227. package/runtime/sdk/scene/gizmos.ts +0 -148
  228. package/runtime/sdk/scene/grammar.ts +0 -120
  229. package/runtime/sdk/scene/material.ts +0 -188
  230. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  231. package/runtime/sdk/server/auth/global.ts +0 -80
  232. package/runtime/sdk/server/auth/host.ts +0 -318
  233. package/runtime/sdk/server/auth/models.ts +0 -83
  234. package/runtime/sdk/server/auth/types.ts +0 -50
  235. package/runtime/sdk/server/channel.ts +0 -56
  236. package/runtime/sdk/server/context.ts +0 -36
  237. package/runtime/sdk/server/db/defineDb.ts +0 -237
  238. package/runtime/sdk/server/db/fields.ts +0 -132
  239. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  240. package/runtime/sdk/server/db/index.ts +0 -7
  241. package/runtime/sdk/server/db/marci/query.ts +0 -412
  242. package/runtime/sdk/server/db/types.ts +0 -202
  243. package/runtime/sdk/server/errors.ts +0 -12
  244. package/runtime/sdk/server/host.ts +0 -74
  245. package/runtime/sdk/server/inject.ts +0 -13
  246. package/runtime/sdk/server/runtime.ts +0 -133
  247. package/runtime/sdk/server/validate.ts +0 -87
  248. package/runtime/sdk/ui/NativeView.ts +0 -142
  249. package/runtime/sdk/ui/UI.ts +0 -39
  250. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  251. package/runtime/sdk/ui/UIButton.ts +0 -101
  252. package/runtime/sdk/ui/UIContainer.ts +0 -60
  253. package/runtime/sdk/ui/UIImage.ts +0 -83
  254. package/runtime/sdk/ui/UIInput.ts +0 -185
  255. package/runtime/sdk/ui/UIModal.ts +0 -139
  256. package/runtime/sdk/ui/UINode.ts +0 -830
  257. package/runtime/sdk/ui/UIPager.ts +0 -362
  258. package/runtime/sdk/ui/UIPopover.ts +0 -100
  259. package/runtime/sdk/ui/UIScreen.ts +0 -123
  260. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  261. package/runtime/sdk/ui/UISpacer.ts +0 -14
  262. package/runtime/sdk/ui/UITabs.ts +0 -236
  263. package/runtime/sdk/ui/UIText.ts +0 -51
  264. package/runtime/sdk/ui/UIVideo.ts +0 -88
  265. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  266. package/runtime/sdk/ui/UIWidget.ts +0 -127
  267. package/runtime/sdk/ui/fonts.ts +0 -13
  268. package/runtime/sdk/ui/presentable.ts +0 -117
  269. package/runtime/sdk/ui/router.ts +0 -132
  270. package/runtime/sdk/ui/theme.ts +0 -84
@@ -0,0 +1,599 @@
1
+ /* Templates for `lecodes app init android` / `lecodes app sync` — the Android mirror of
2
+ * appTemplates.ts, distilled from the LetaryCreatorViewer host (lecodes-android :app). The
3
+ * same three ownership tiers: app/ is USER-owned after generation (init writes once, sync
4
+ * never touches it — except app/src/main/assets/, the Resources/ analog, and values-only
5
+ * versionName/versionCode restamps in app/build.gradle.kts); settings/root gradle files,
6
+ * lecodes-runtime/ and plugins/ are CLI-owned (sync regenerates); the gradle wrapper is
7
+ * written once at init. Unlike the pbxproj there is no structural-edit problem — every
8
+ * CLI-owned file is plain text and fully regenerated. */
9
+
10
+ export const GRADLE_VERSION = "8.13"
11
+ export const AGP_VERSION = "8.13.2"
12
+ export const KOTLIN_VERSION = "2.1.0"
13
+ export const COMPILE_SDK = 36
14
+ export const MIN_SDK = 23
15
+ export const TARGET_SDK = 36
16
+
17
+ /** The fat AAR ships no POM (the ivy repository uses metadataSources { artifact() }), so the
18
+ * SDK's own dependencies are declared explicitly in the generated build files. Keep in sync
19
+ * with lecodes-android lecodes/build.gradle.kts per SDK release. */
20
+ export const SDK_RUNTIME_DEPS = [
21
+ "androidx.core:core-ktx:1.18.0",
22
+ "androidx.appcompat:appcompat:1.7.1",
23
+ "com.squareup.okhttp3:okhttp:4.12.0",
24
+ ]
25
+
26
+ /** Known app-level Gradle plugins a plugin manifest may request by bare id ("id:version" in
27
+ * the manifest pins explicitly; an unknown bare id is a hard error at sync). */
28
+ export const GRADLE_PLUGIN_VERSIONS: Record<string, string> = {
29
+ "com.google.gms.google-services": "4.5.0",
30
+ }
31
+
32
+ export interface AndroidShellIdentity {
33
+ displayName: string
34
+ applicationId: string
35
+ version: string
36
+ versionCode: number
37
+ }
38
+
39
+ export interface AndroidRuntimePlugin { id: string, module: string, register: string }
40
+
41
+ /* ------------------------------ CLI-owned (regenerated) ------------------------------ */
42
+
43
+ export const settingsGradleTemplate = (slug: string, pluginModules: string[], aarWrapperPaths: string[], sdkRepoUrl: string): string => {
44
+ const insecure = sdkRepoUrl.startsWith("http://")
45
+ ? "\n isAllowInsecureProtocol = true // LECODES_ANDROID_SDK_URL dev override"
46
+ : ""
47
+ const includes = pluginModules.map(m => `\ninclude(":plugins:${m}")`).join("")
48
+ + aarWrapperPaths.map(p => `\ninclude("${p}")`).join("")
49
+ return `// Generated by \`lecodes app sync\` — regenerated on every sync, do not edit.
50
+ pluginManagement {
51
+ repositories {
52
+ google()
53
+ mavenCentral()
54
+ gradlePluginPortal()
55
+ }
56
+ }
57
+ dependencyResolutionManagement {
58
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
59
+ repositories {
60
+ google()
61
+ mavenCentral()
62
+ // The LeCodes Android SDK: a fat AAR attached to GitHub Releases of the dist repo,
63
+ // resolved straight off the release-download URL pattern (no Maven registry). The
64
+ // content filter keeps Gradle from probing GitHub for every other coordinate.
65
+ ivy {
66
+ url = uri("${sdkRepoUrl}")
67
+ patternLayout { artifact("[revision]/[artifact]-[revision].[ext]") }
68
+ metadataSources { artifact() }${insecure}
69
+ content { includeGroup("io.letary") }
70
+ }
71
+ }
72
+ }
73
+ rootProject.name = "${slug}"
74
+ include(":app")
75
+ include(":lecodes-runtime")${includes}
76
+ `
77
+ }
78
+
79
+ export const rootBuildGradleTemplate = (extraPlugins: { id: string, version: string }[]): string => {
80
+ const extras = extraPlugins.map(p => `\n id("${p.id}") version "${p.version}" apply false`).join("")
81
+ return `// Generated by \`lecodes app sync\` — regenerated on every sync, do not edit.
82
+ plugins {
83
+ id("com.android.application") version "${AGP_VERSION}" apply false
84
+ id("com.android.library") version "${AGP_VERSION}" apply false
85
+ id("org.jetbrains.kotlin.android") version "${KOTLIN_VERSION}" apply false${extras}
86
+ }
87
+ `
88
+ }
89
+
90
+ export const GRADLE_PROPERTIES = `# Generated by \`lecodes app sync\` — regenerated on every sync, do not edit.
91
+ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
92
+ android.useAndroidX=true
93
+ android.nonTransitiveRClass=true
94
+ kotlin.code.style=official
95
+ `
96
+
97
+ export const gradleWrapperProperties = (): string => `distributionBase=GRADLE_USER_HOME
98
+ distributionPath=wrapper/dists
99
+ distributionUrl=https\\://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip
100
+ networkTimeout=10000
101
+ validateDistributionUrl=true
102
+ zipStoreBase=GRADLE_USER_HOME
103
+ zipStorePath=wrapper/dists
104
+ `
105
+
106
+ export const runtimeBuildGradleTemplate = (sdkVersion: string, pluginModules: string[], sdkArtifact = "lecodes-sdk"): string => {
107
+ const deps = SDK_RUNTIME_DEPS.map(d => `\n api("${d}")`).join("")
108
+ const plugins = pluginModules.map(m => `\n api(project(":plugins:${m}"))`).join("")
109
+ return `// Generated by \`lecodes app sync\` — regenerated on every sync, do not edit.
110
+ // The SDK pin lives here (\`lecodes app update\` moves it); everything is \`api\` so app code
111
+ // sees the SDK and plugin classes through the single :lecodes-runtime dependency.
112
+ plugins {
113
+ id("com.android.library")
114
+ id("org.jetbrains.kotlin.android")
115
+ }
116
+
117
+ android {
118
+ namespace = "io.letary.lecodes.runtime"
119
+ compileSdk = ${COMPILE_SDK}
120
+ defaultConfig {
121
+ minSdk = ${MIN_SDK}
122
+ }
123
+ compileOptions {
124
+ sourceCompatibility = JavaVersion.VERSION_11
125
+ targetCompatibility = JavaVersion.VERSION_11
126
+ }
127
+ }
128
+
129
+ kotlin {
130
+ compilerOptions {
131
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
132
+ }
133
+ }
134
+
135
+ dependencies {
136
+ // Exact pin — the release asset URL is derived from this artifact + version (see
137
+ // settings.gradle.kts). The artifact is the SDK variant sync picked (lecodes-sdk = full,
138
+ // -3d / -2d / -core = smaller engine builds); every variant vends the same Kotlin API.
139
+ api("io.letary:${sdkArtifact}:${sdkVersion}@aar")
140
+ // The fat AAR carries no POM, so its own dependencies ride here explicitly.${deps}${plugins}
141
+ }
142
+ `
143
+ }
144
+
145
+ /** lecodes-runtime/src/main/kotlin/io/letary/lecodes/runtime/LeCodesRuntime.kt — the generated
146
+ * registration + host-hook surface. Plugins are called by FQN (no import collisions); the
147
+ * activity hooks are always present and inert without a consuming plugin (the Android analog
148
+ * of iOS's buffered LeCodesAppHooks — here the only launch-time state, a notification tap's
149
+ * intent extra, survives until the host forwards it, so no buffering is needed). */
150
+ export const runtimeKotlinTemplate = (plugins: AndroidRuntimePlugin[], projectUuid?: string): string => {
151
+ const identity = projectUuid ? ` engine.bootProjectUuid = "${projectUuid}"\n` : ""
152
+ const calls = plugins.map(p => ` ${p.register}.register(engine, context)\n`).join("")
153
+ const body = identity + calls
154
+ const push = plugins.find(p => p.id === "push")
155
+ const pushNs = push ? push.register.split(".").slice(0, -1).join(".") : null
156
+ return `// Generated by \`lecodes app sync\` — regenerated on every sync, do not edit.
157
+ package io.letary.lecodes.runtime
158
+
159
+ import android.content.Context
160
+ import android.content.Intent
161
+ import io.letary.lecodes.LecodesEngine
162
+
163
+ object LeCodesRuntime {
164
+
165
+ /** Register the plugins installed via app.json. Call from Activity.onCreate BEFORE the
166
+ * engine runs the bundle — app code resolves plugin factories at synchronous top level. */
167
+ fun registerBundledPlugins(engine: LecodesEngine, context: Context) {
168
+ ${body === "" ? " // no plugins installed\n" : body} }
169
+
170
+ /** Cold create only (savedInstanceState == null): forward the launch intent so a plugin
171
+ * can park launch-time state (a push notification tap) before the engine boots. */
172
+ fun onActivityLaunch(intent: Intent) {
173
+ ${push ? ` ${pushNs}.PushPlugin.parkLaunchPayload(intent)\n` : " // no plugin consumes launch intents\n"} }
174
+
175
+ /** Warm intent (Activity.onNewIntent). @return true when a plugin consumed it — the
176
+ * caller must then SKIP its generic deep-link handling. */
177
+ fun onNewIntent(intent: Intent): Boolean {
178
+ ${push ? ` return ${pushNs}.PushPlugin.handleTapIntent(intent)\n` : " return false\n"} }
179
+
180
+ /** Activity.onResume(true) / onPause(false). */
181
+ fun setForeground(foreground: Boolean) {
182
+ ${push ? ` ${pushNs}.PushPlugin.setForeground(foreground)\n` : " // no plugin tracks foreground state\n"} }
183
+ }
184
+ `
185
+ }
186
+
187
+ export const pluginBuildGradleTemplate = (
188
+ id: string, module: string, namespace: string, sdkVersion: string,
189
+ dependencies: string[], aarNames: string[], hasConsumerRules: boolean,
190
+ sdkArtifact = "lecodes-sdk",
191
+ ): string => {
192
+ const consumer = hasConsumerRules ? `\n consumerProguardFiles("consumer-rules.pro")` : ""
193
+ // AGP forbids direct local .aar file deps in library modules — each rides in its own
194
+ // generated wrapper module (libs/<name>/) consumed as a project dependency.
195
+ const aars = aarNames.map(n => `\n implementation(project(":plugins:${module}:libs:${n.replace(/\.aar$/, "")}"))`).join("")
196
+ const sdkDeps = SDK_RUNTIME_DEPS.map(d => `\n implementation("${d}")`).join("")
197
+ const manifestDeps = dependencies.map(d => d.startsWith("platform:")
198
+ ? `\n implementation(platform("${d.slice("platform:".length)}"))`
199
+ : `\n implementation("${d}")`).join("")
200
+ return `// Generated by \`lecodes app sync\` for plugin "${id}" — regenerated on every sync, do not edit.
201
+ plugins {
202
+ id("com.android.library")
203
+ id("org.jetbrains.kotlin.android")
204
+ }
205
+
206
+ android {
207
+ namespace = "${namespace}"
208
+ compileSdk = ${COMPILE_SDK}
209
+ defaultConfig {
210
+ minSdk = ${MIN_SDK}${consumer}
211
+ }
212
+ compileOptions {
213
+ sourceCompatibility = JavaVersion.VERSION_11
214
+ targetCompatibility = JavaVersion.VERSION_11
215
+ }
216
+ }
217
+
218
+ kotlin {
219
+ compilerOptions {
220
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
221
+ }
222
+ }
223
+
224
+ dependencies {
225
+ // Same artifact (SDK variant) as lecodes-runtime — two different variant coordinates in one
226
+ // shell would put two copies of the SDK classes on the runtime classpath.
227
+ implementation("io.letary:${sdkArtifact}:${sdkVersion}@aar")${sdkDeps}${aars}${manifestDeps}
228
+ }
229
+ `
230
+ }
231
+
232
+ export const ANDROID_README = `# Android shell
233
+
234
+ Generated by \`lecodes app init android\`. Daily workflow:
235
+
236
+ \`\`\`
237
+ lecodes app sync # embed the compiled bundle + plugins into this shell
238
+ lecodes app open android # open in Android Studio
239
+ lecodes app build android # signed release APK + AAB (gradlew assembleRelease bundleRelease)
240
+ \`\`\`
241
+
242
+ ## Ownership
243
+
244
+ | Path | Owner |
245
+ | --- | --- |
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/src/main/assets/\` | CLI-owned — wiped and regenerated by every sync (the compiled \`app.js\` + its \`// preload:\` assets) |
248
+ | \`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
+ | \`settings.gradle.kts\`, \`build.gradle.kts\` (root), \`gradle.properties\`, \`lecodes-runtime/\`, \`plugins/\` | CLI-owned — regenerated by every sync |
250
+ | \`gradle/\`, \`gradlew\`, \`gradlew.bat\` | Written once at init |
251
+
252
+ ## The SDK
253
+
254
+ \`lecodes-runtime/build.gradle.kts\` pins the exact \`io.letary:lecodes-sdk\` release, resolved
255
+ from the GitHub Releases of \`letary/lecodes-android-sdk\` through the ivy repository declared
256
+ in \`settings.gradle.kts\`. \`lecodes app update\` moves the pin (or set \`app.json\`
257
+ \`android.sdk\`). The SDK AAR ships no POM, so its own dependencies are declared explicitly in
258
+ the generated runtime build file.
259
+
260
+ ## SDK variant
261
+
262
+ Sync links the smallest SDK artifact covering the engines the compiled bundle actually uses —
263
+ \`lecodes-sdk\` (full: 3D+2D), \`lecodes-sdk-3d\`, \`lecodes-sdk-2d\` or \`lecodes-sdk-core\`
264
+ (UI only) — so an app without 3D scenes doesn't ship Filament. If the project starts using an
265
+ engine, sync switches automatically on the next run; set \`android.variant\` in \`../app.json\`
266
+ to force one (needs lecodes-android-sdk >= 1.3.0; older releases ship only the full AAR).
267
+
268
+ Physics, navigation meshes and multiplayer ship in \`lecodes-sdk\` (full) ONLY —
269
+ \`lecodes-sdk-3d\` is 3D rendering without the game stack. Auto-selection reads which RENDERING
270
+ engines the bundle uses and cannot see those, so a project using \`Physics\`,
271
+ \`CharacterController\`, \`NavMesh\` or networking must set \`android.variant: "full"\` in
272
+ \`../app.json\` — otherwise those aspects silently do nothing on device.
273
+
274
+ ## App-local native code
275
+
276
+ Drop Kotlin files into \`app/src/main/java/\` and register them in \`MainActivity\` after the
277
+ \`LeCodesRuntime.registerBundledPlugins(...)\` call — \`engine.registerCallback\` for sync
278
+ globals, \`engine.registerService\`/\`registerView\` for channels (see the SDK docs). Add any
279
+ dependencies they need to \`app/build.gradle.kts\` — that file is yours.
280
+
281
+ ## Release signing
282
+
283
+ Create \`android/keystore.properties\` (gitignored):
284
+
285
+ \`\`\`
286
+ storeFile=release.keystore
287
+ storePassword=…
288
+ keyAlias=…
289
+ keyPassword=…
290
+ \`\`\`
291
+
292
+ or export \`LECODES_ANDROID_KEYSTORE_FILE\` / \`LECODES_ANDROID_KEYSTORE_PASSWORD\` /
293
+ \`LECODES_ANDROID_KEY_ALIAS\` / \`LECODES_ANDROID_KEY_PASSWORD\`. Never put passwords in app.json.
294
+
295
+ ## Icons
296
+
297
+ The easy path: set \`app.json\` \`"icon"\` to a square ≥1024px PNG/JPEG in the project (e.g.
298
+ \`"icon": "icon.png"\`, optional \`"iconBackground": "#RRGGBB"\` — the adaptive-icon backdrop,
299
+ default white). Every \`lecodes app sync\` then renders the full launcher set: legacy density
300
+ PNGs, the API 26+ adaptive icon (your image at 72dp on the 108dp canvas), and an Android 13
301
+ themed-icon (monochrome) layer that reuses the foreground silhouette.
302
+
303
+ Prefer hand-made icons? Leave \`"icon"\` unset and use Android Studio (right-click \`res\` →
304
+ New → Image Asset) — sync never touches icon files then. \`drawable/ic_launcher.xml\` is only
305
+ the init-time placeholder.
306
+
307
+ ## Notes
308
+
309
+ - No native OTA on Android yet — app.json \`"update"\` is ignored for this shell (iOS-only).
310
+ - Push: the notification service needs \`app/google-services.json\` from the le.codes Firebase
311
+ project; \`lecodes app sync\` applies the google-services Gradle plugin only when that file
312
+ exists (and warns otherwise).
313
+ `
314
+
315
+ /* ---------------------------- USER-owned (written once) ---------------------------- */
316
+
317
+ export const appBuildGradleTemplate = ({ applicationId, version, versionCode }: AndroidShellIdentity): string => `import java.util.Properties
318
+
319
+ // Generated by \`lecodes app init\` — yours to edit. \`lecodes app sync\` only restamps the
320
+ // versionName/versionCode VALUES and manages \`// lecodes:plugin\`-tagged lines in the plugins
321
+ // block below; everything else is left alone.
322
+ plugins {
323
+ id("com.android.application")
324
+ id("org.jetbrains.kotlin.android")
325
+ }
326
+
327
+ // Release signing: android/keystore.properties (gitignored) or LECODES_ANDROID_* env vars —
328
+ // see README.md. Without either, release builds come out unsigned.
329
+ val keystoreProps = Properties().apply {
330
+ val f = rootProject.file("keystore.properties")
331
+ if (f.exists()) f.inputStream().use { load(it) }
332
+ }
333
+ fun signing(key: String, env: String): String? = keystoreProps.getProperty(key) ?: System.getenv(env)
334
+
335
+ android {
336
+ namespace = "${applicationId}"
337
+ compileSdk = ${COMPILE_SDK}
338
+
339
+ defaultConfig {
340
+ applicationId = "${applicationId}"
341
+ minSdk = ${MIN_SDK}
342
+ targetSdk = ${TARGET_SDK}
343
+ versionCode = ${versionCode}
344
+ versionName = "${version}"
345
+ }
346
+
347
+ val storeFilePath = signing("storeFile", "LECODES_ANDROID_KEYSTORE_FILE")
348
+ if (storeFilePath != null) {
349
+ signingConfigs.create("release") {
350
+ storeFile = rootProject.file(storeFilePath)
351
+ storePassword = signing("storePassword", "LECODES_ANDROID_KEYSTORE_PASSWORD")
352
+ keyAlias = signing("keyAlias", "LECODES_ANDROID_KEY_ALIAS")
353
+ keyPassword = signing("keyPassword", "LECODES_ANDROID_KEY_PASSWORD")
354
+ }
355
+ }
356
+
357
+ buildTypes {
358
+ release {
359
+ isMinifyEnabled = false
360
+ signingConfig = signingConfigs.findByName("release")
361
+ }
362
+ }
363
+ compileOptions {
364
+ sourceCompatibility = JavaVersion.VERSION_11
365
+ targetCompatibility = JavaVersion.VERSION_11
366
+ }
367
+ }
368
+
369
+ kotlin {
370
+ compilerOptions {
371
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
372
+ }
373
+ }
374
+
375
+ dependencies {
376
+ implementation(project(":lecodes-runtime"))
377
+ // App-local native code deps go here — this file is yours after init.
378
+ }
379
+ `
380
+
381
+ export const appManifestTemplate = (): string => `<?xml version="1.0" encoding="utf-8"?>
382
+ <!-- Generated by \`lecodes app init\` — yours to edit. Sync leaves it alone, except that with
383
+ app.json "icon" set it restamps the android:icon VALUE to the generated @mipmap set.
384
+ Plugin permissions/services arrive through the plugin modules' own manifests via the AGP
385
+ manifest merger, so installing/removing plugins needs no edits here. -->
386
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
387
+
388
+ <uses-permission android:name="android.permission.INTERNET" />
389
+
390
+ <application
391
+ android:allowBackup="true"
392
+ android:icon="@drawable/ic_launcher"
393
+ android:label="@string/app_name"
394
+ android:supportsRtl="true"
395
+ android:theme="@style/Theme.LeCodesApp">
396
+ <!-- singleTask: a warm deep link re-enters the SAME instance via onNewIntent (the "url"
397
+ app event) instead of stacking a second activity with its own engine. -->
398
+ <activity
399
+ android:name=".MainActivity"
400
+ android:exported="true"
401
+ android:launchMode="singleTask"
402
+ android:windowSoftInputMode="adjustResize">
403
+ <intent-filter>
404
+ <action android:name="android.intent.action.MAIN" />
405
+ <category android:name="android.intent.category.LAUNCHER" />
406
+ </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
+ </activity>
420
+ </application>
421
+
422
+ </manifest>
423
+ `
424
+
425
+ export const mainActivityTemplate = (pkg: string): string => `//
426
+ // MainActivity.kt
427
+ // Generated by lecodes app init — yours to edit.
428
+ //
429
+ // Runs the bundled app.js (produced by \`lecodes app sync\`) offline: everything the script
430
+ // needs is provided by the SDK core plus the plugins registered via LeCodesRuntime.
431
+ //
432
+
433
+ package ${pkg}
434
+
435
+ import android.content.Intent
436
+ import android.os.Bundle
437
+ import android.view.KeyEvent
438
+ import androidx.appcompat.app.AppCompatActivity
439
+ import androidx.lifecycle.ViewModelProvider
440
+ import io.letary.lecodes.LeCodesView
441
+ import io.letary.lecodes.runtime.LeCodesRuntime
442
+
443
+ class MainActivity : AppCompatActivity() {
444
+
445
+ lateinit var viewModel: AppViewModel
446
+
447
+ override fun onCreate(savedInstanceState: Bundle?) {
448
+ super.onCreate(savedInstanceState)
449
+
450
+ viewModel = ViewModelProvider(this)[AppViewModel::class.java]
451
+ setContentView(LeCodesView(engine = viewModel.engine, context = applicationContext))
452
+
453
+ viewModel.engine.setLanguage(resources.configuration.locales[0]?.language ?: "en")
454
+
455
+ // Plugins BEFORE the engine runs the bundle — app code resolves plugin factories at
456
+ // synchronous top level.
457
+ LeCodesRuntime.registerBundledPlugins(viewModel.engine, applicationContext)
458
+
459
+ // A cold start from a notification tap parks its payload BEFORE the engine boots (inert
460
+ // without the push plugin). Only on a fresh creation — a recreation must not re-park.
461
+ if (savedInstanceState == null) {
462
+ LeCodesRuntime.onActivityLaunch(intent)
463
+ }
464
+
465
+ // BEFORE onActivityCreate: the bundle reads the launch link synchronously at boot.
466
+ viewModel.engine.setLaunchUrl(intent.data?.toString())
467
+
468
+ viewModel.onActivityCreate(this)
469
+ // System back is wired inside engine.attach() (SDK >= 1.2.0): widget → destination →
470
+ // pager → router; an unhandled press is swallowed. To leave the app on unhandled back
471
+ // instead: viewModel.engine.onUnhandledBackPressed = { moveTaskToBack(true) }
472
+ }
473
+
474
+ // Physical-key state for Input.key(). Record, never consume.
475
+ override fun dispatchKeyEvent(event: KeyEvent): Boolean {
476
+ viewModel.engine.handleKeyEvent(event)
477
+ return super.dispatchKeyEvent(event)
478
+ }
479
+
480
+ override fun onResume() {
481
+ super.onResume()
482
+ LeCodesRuntime.setForeground(true)
483
+ viewModel.engine.resume()
484
+ }
485
+
486
+ override fun onPause() {
487
+ super.onPause()
488
+ LeCodesRuntime.setForeground(false)
489
+ viewModel.engine.pause()
490
+ }
491
+
492
+ // Warm deep link (the activity is singleTask). A consumed intent (a notification tap the
493
+ // push plugin routed) must SKIP the generic path — a same-project tap must not reload the
494
+ // running app through its own link.
495
+ override fun onNewIntent(intent: Intent) {
496
+ super.onNewIntent(intent)
497
+ if (LeCodesRuntime.onNewIntent(intent)) return
498
+ val url = intent.data?.toString() ?: return
499
+ viewModel.engine.emitUrl(url)
500
+ }
501
+ }
502
+ `
503
+
504
+ export const appViewModelTemplate = (pkg: string): string => `//
505
+ // AppViewModel.kt
506
+ // Generated by lecodes app init — yours to edit.
507
+ //
508
+ // The engine lives here so it survives configuration changes (rotation recreates the
509
+ // Activity, not the ViewModel); onCleared is the one "never coming back" signal where the
510
+ // engine's system registrations are released.
511
+ //
512
+
513
+ package ${pkg}
514
+
515
+ import android.app.Activity
516
+ import android.app.Application
517
+ import androidx.lifecycle.AndroidViewModel
518
+ import io.letary.lecodes.LecodesEngine
519
+
520
+ class AppViewModel(application: Application) : AndroidViewModel(application) {
521
+
522
+ val engine: LecodesEngine = LecodesEngine(application.applicationContext)
523
+
524
+ init {
525
+ // \`// preload:\` assets (images, fonts) resolve out of app/src/main/assets/ —
526
+ // \`lecodes app sync\` copies them there flat by name.
527
+ engine.onFetchLocal = { path ->
528
+ getApplication<Application>().assets.open(path).use { it.readBytes() }
529
+ }
530
+ }
531
+
532
+ override fun onCleared() {
533
+ engine.dispose()
534
+ super.onCleared()
535
+ }
536
+
537
+ private var isCreated = false
538
+ fun onActivityCreate(activity: Activity) {
539
+ engine.attach(activity)
540
+ if (!isCreated) {
541
+ val code = getApplication<Application>().assets.open("app.js").bufferedReader().use { it.readText() }
542
+ engine.run(code)
543
+ isCreated = true
544
+ }
545
+ }
546
+ }
547
+ `
548
+
549
+ export const themesXmlTemplate = (): string => `<resources>
550
+ <style name="Theme.LeCodesApp" parent="Theme.AppCompat.Light.NoActionBar">
551
+ <item name="android:windowTranslucentNavigation">true</item>
552
+ <item name="android:statusBarColor">@android:color/transparent</item>
553
+ <item name="android:windowNoTitle">true</item>
554
+ <item name="android:windowTranslucentStatus">true</item>
555
+ <item name="android:windowBackground">@color/black</item>
556
+ </style>
557
+ </resources>
558
+ `
559
+
560
+ export const COLORS_XML = `<resources>
561
+ <color name="black">#FF000000</color>
562
+ </resources>
563
+ `
564
+
565
+ export const stringsXmlTemplate = (name: string): string => `<resources>
566
+ <string name="app_name">${name.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "\\'")}</string>
567
+ </resources>
568
+ `
569
+
570
+ /** Placeholder launcher icon — a plain vector (valid from API 21, no adaptive/mipmap set, no
571
+ * binary assets). README points at Android Studio's Image Asset tool for the real one. */
572
+ export const IC_LAUNCHER_XML = `<?xml version="1.0" encoding="utf-8"?>
573
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
574
+ android:width="108dp"
575
+ android:height="108dp"
576
+ android:viewportWidth="108"
577
+ android:viewportHeight="108">
578
+ <path
579
+ android:fillColor="#FF000000"
580
+ android:pathData="M0,0h108v108h-108z"/>
581
+ <path
582
+ android:fillColor="#FFFFFFFF"
583
+ android:pathData="M34,38 h40 a4,4 0 0 1 4,4 v24 a4,4 0 0 1 -4,4 h-40 a4,4 0 0 1 -4,-4 v-24 a4,4 0 0 1 4,-4 z M38,44 v20 h32 v-20 z"/>
584
+ </vector>
585
+ `
586
+
587
+ export const ANDROID_GITIGNORE = `.gradle/
588
+ build/
589
+ local.properties
590
+ keystore.properties
591
+ *.keystore
592
+ `
593
+
594
+ /** libs/<name>/build.gradle.kts — wraps a plugin's local AAR as a consumable artifact
595
+ * (the AGP-supported alternative to direct local .aar file deps in library modules). */
596
+ export const aarWrapperBuildGradle = (aarName: string): string => `// Generated by \`lecodes app sync\` — regenerated on every sync, do not edit.
597
+ configurations.maybeCreate("default")
598
+ artifacts.add("default", file("${aarName}"))
599
+ `
@@ -0,0 +1,9 @@
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 appAndroid.ts). JSON-escaped mechanically —
3
+ * regenerate from the tag rather than editing. gradlew must be written with LF and
4
+ * gradlew.bat with CRLF (initAndroid handles the conversion); both are version-agnostic
5
+ * launchers that read gradle/wrapper/gradle-wrapper.properties for the real distribution. */
6
+
7
+ export const GRADLEW_SH: string = "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n# Gradle start up script for POSIX generated by Gradle.\n#\n# Important for running:\n#\n# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n# noncompliant, but you have some other compliant shell such as ksh or\n# bash, then to run this script, type that shell name before the whole\n# command line, like:\n#\n# ksh Gradle\n#\n# Busybox and similar reduced shells will NOT work, because this script\n# requires all of these POSIX shell features:\n# * functions;\n# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n# «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n# * compound commands having a testable exit status, especially «case»;\n# * various built-in commands including «command», «set», and «ulimit».\n#\n# Important for patching:\n#\n# (2) This script targets any POSIX shell, so it avoids extensions provided\n# by Bash, Ksh, etc; in particular arrays are avoided.\n#\n# The \"traditional\" practice of packing multiple parameters into a\n# space-separated string is a well documented source of bugs and security\n# problems, so this is (mostly) avoided, by progressively accumulating\n# options in \"$@\", and eventually passing that to Java.\n#\n# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n# see the in-line comments for details.\n#\n# There are tweaks for specific operating systems such as AIX, CygWin,\n# Darwin, MinGW, and NonStop.\n#\n# (3) This script is generated from the Groovy template\n# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n# within the Gradle project.\n#\n# You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n APP_HOME=${app_path%\"${app_path##*/}\"} # leaves a trailing /; empty if no leading path\n [ -h \"$app_path\" ]\ndo\n ls=$( ls -ld \"$app_path\" )\n link=${ls#*' -> '}\n case $link in #(\n /*) app_path=$link ;; #(\n *) app_path=$APP_HOME$link ;;\n esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n echo \"$*\"\n} >&2\n\ndie () {\n echo\n echo \"$*\"\n echo\n exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in #(\n CYGWIN* ) cygwin=true ;; #(\n Darwin* ) darwin=true ;; #(\n MSYS* | MINGW* ) msys=true ;; #(\n NONSTOP* ) nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n # IBM's JDK on AIX uses strange locations for the executables\n JAVACMD=$JAVA_HOME/jre/sh/java\n else\n JAVACMD=$JAVA_HOME/bin/java\n fi\n if [ ! -x \"$JAVACMD\" ] ; then\n die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n fi\nelse\n JAVACMD=java\n if ! command -v java >/dev/null 2>&1\n then\n die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n case $MAX_FD in #(\n max*)\n # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n # shellcheck disable=SC2039,SC3045\n MAX_FD=$( ulimit -H -n ) ||\n warn \"Could not query maximum file descriptor limit\"\n esac\n case $MAX_FD in #(\n '' | soft) :;; #(\n *)\n # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n # shellcheck disable=SC2039,SC3045\n ulimit -n \"$MAX_FD\" ||\n warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n# * args from the command line\n# * the main class name\n# * -classpath\n# * -D...appname settings\n# * --module-path (only if needed)\n# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n # Now convert the arguments - kludge to limit ourselves to /bin/sh\n for arg do\n if\n case $arg in #(\n -*) false ;; # don't mess with options #(\n /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath\n [ -e \"$t\" ] ;; #(\n *) false ;;\n esac\n then\n arg=$( cygpath --path --ignore --mixed \"$arg\" )\n fi\n # Roll the args list around exactly as many times as the number of\n # args, so each arg winds up back in the position where it started, but\n # possibly modified.\n #\n # NB: a `for` loop captures its iteration list before it begins, so\n # changing the positional parameters here affects neither the number of\n # iterations, nor the values presented in `arg`.\n shift # remove old arg\n set -- \"$@\" \"$arg\" # push replacement arg\n done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 \"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n# and any embedded shellness will be escaped.\n# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n# treated as '${Hostname}' itself on the command line.\n\nset -- \\\n \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n -classpath \"$CLASSPATH\" \\\n org.gradle.wrapper.GradleWrapperMain \\\n \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n# readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n# set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n xargs -n1 |\n sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n tr '\\n' ' '\n )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
8
+
9
+ export const GRADLEW_BAT: string = "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 \"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
@@ -0,0 +1,28 @@
1
+ import { CliError, type Args } from "../util"
2
+ import { resolveCmgenExe } from "../cmgenTool"
3
+
4
+ /*
5
+ * `lecodes assets <convert|probe|doctor|sky> …` — the LeCodes asset pipeline (packages/lecodes-assets):
6
+ * FBX → GLB (ufbx wasm + glTF-Transform), `--clips` merged by bone name, textures embedded, GLB
7
+ * doctor (bone cap / texture normalize / KTX2). The package is an optional peer (like the design
8
+ * board and the renderer); its own argv parser handles the sub-command flags, so the raw argv is
9
+ * forwarded untouched (`assets` gets `_raw` from main). See docs/animation-plan.md §2.9.
10
+ */
11
+ export const assets = async (args: Args & { _raw?: string[] }) => {
12
+ let mod: typeof import("lecodes-assets/cli")
13
+ try {
14
+ mod = await import("lecodes-assets/cli")
15
+ } catch {
16
+ throw new CliError("The asset pipeline isn't installed. Run: npm install -g lecodes-assets")
17
+ }
18
+ const raw = args._raw ?? args._
19
+ // `sky` shells out to filament's cmgen, which the asset package deliberately never downloads
20
+ // itself (it stays network-free and usable standalone). The CLI owns release artifacts, so it
21
+ // resolves the binary — cache or first-use download — and hands it over through the env var the
22
+ // package already honours. An explicit LECODES_CMGEN wins, so a local filament build still rules.
23
+ if (raw[0] === "sky" && !process.env.LECODES_CMGEN) {
24
+ process.env.LECODES_CMGEN = await resolveCmgenExe()
25
+ }
26
+ const code = await mod.runAssetsCli(raw)
27
+ if (code !== 0) process.exitCode = code
28
+ }