lecodes-cli 0.20.1 → 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.
Files changed (107) hide show
  1. package/README.md +56 -57
  2. package/dist/index.js +5920 -5022
  3. package/package.json +9 -7
  4. package/runtime/materials/decal-relief.mat +12 -1
  5. package/runtime/materials/decal.mat +17 -3
  6. package/runtime/materials/lightmap-baked-lite.mat +6 -1
  7. package/runtime/materials/lightmap-baked.mat +6 -1
  8. package/runtime/materials/lightmap.mat +3 -0
  9. package/runtime/materials/particles-quad.mat +17 -9
  10. package/runtime/materials/particles.mat +17 -9
  11. package/runtime/scene-harness.json +1 -1
  12. package/runtime/sdk-types.json +1 -1
  13. package/runtime/web/assets/createViewerLite-DHqTFhWB.js +866 -0
  14. package/runtime/web/assets/{index-BTB5KV9x.js → index-Q06c6oHx.js} +1 -1
  15. package/runtime/web/embed.js +2 -2
  16. package/src/api.ts +1 -302
  17. package/src/cli/args.ts +37 -0
  18. package/src/cli/command.ts +113 -0
  19. package/src/cli/errors.ts +13 -0
  20. package/src/cli/help.ts +133 -0
  21. package/src/cli/index.ts +7 -0
  22. package/src/cli/output.ts +102 -0
  23. package/src/cli/run.ts +106 -0
  24. package/src/commands/{appAndroid.ts → app/android.ts} +451 -42
  25. package/src/commands/{appDesktop.ts → app/desktop.ts} +63 -32
  26. package/src/commands/{appDesktopMac.ts → app/desktopMac.ts} +24 -18
  27. package/src/commands/{appIcon.ts → app/icon.ts} +2 -2
  28. package/src/commands/{app.ts → app/index.ts} +397 -289
  29. package/src/commands/{appShared.ts → app/shared.ts} +30 -39
  30. package/src/commands/{appTemplatesAndroid.ts → app/templates/android.ts} +62 -28
  31. package/src/commands/{appTemplatesGradlew.ts → app/templates/gradlew.ts} +1 -1
  32. package/src/commands/{appTemplates.ts → app/templates/ios.ts} +1 -1
  33. package/src/commands/assets.ts +23 -21
  34. package/src/commands/clone.ts +48 -51
  35. package/src/commands/compile.ts +62 -88
  36. package/src/commands/create.ts +46 -47
  37. package/src/commands/design/comments.ts +361 -0
  38. package/src/commands/design/context.ts +101 -0
  39. package/src/commands/design/index.ts +391 -0
  40. package/src/commands/design/snapshot.ts +134 -0
  41. package/src/commands/{designTemplates.ts → design/templates.ts} +4 -3
  42. package/src/commands/desktop.ts +90 -82
  43. package/src/commands/dev.ts +201 -176
  44. package/src/commands/diff.ts +39 -51
  45. package/src/commands/index.ts +60 -0
  46. package/src/commands/{init.ts → init/index.ts} +199 -191
  47. package/src/commands/install.ts +80 -67
  48. package/src/commands/lightmap.ts +203 -171
  49. package/src/commands/link.ts +87 -94
  50. package/src/commands/login.ts +25 -23
  51. package/src/commands/navmesh.ts +168 -138
  52. package/src/commands/pn.ts +201 -244
  53. package/src/commands/pull.ts +49 -56
  54. package/src/commands/push.ts +48 -54
  55. package/src/commands/render.ts +158 -99
  56. package/src/commands/scene.ts +58 -55
  57. package/src/commands/{shaders.ts → shaders/index.ts} +61 -42
  58. package/src/commands/{shadersNew.ts → shaders/new.ts} +2 -2
  59. package/src/commands/shared.ts +79 -0
  60. package/src/commands/status.ts +22 -26
  61. package/src/commands/test.ts +398 -371
  62. package/src/commands/thumbs.ts +185 -175
  63. package/src/commands/update/index.ts +226 -0
  64. package/src/commands/{types.ts → update/types.ts} +31 -22
  65. package/src/compile/collect.ts +5 -5
  66. package/src/compile/collectLocal.ts +3 -3
  67. package/src/compile/designCompile.ts +3 -3
  68. package/src/compile/headlessBundle.ts +133 -129
  69. package/src/compile/projectCompile.ts +2 -2
  70. package/src/compile/sceneCompile.ts +10 -8
  71. package/src/compile/screenEntry.ts +123 -127
  72. package/src/compile/shaders.ts +3 -3
  73. package/src/dev/androidDev.ts +1 -1
  74. package/src/dev/devServer.ts +3 -3
  75. package/src/dev/webRunner.ts +1 -1
  76. package/src/{cmgenTool.ts → hosts/cmgenTool.ts} +1 -1
  77. package/src/{desktopRenderer.ts → hosts/desktopRenderer.ts} +6 -5
  78. package/src/{desktopScript.ts → hosts/desktopScript.ts} +1 -1
  79. package/src/{distRoot.ts → hosts/distRoot.ts} +9 -5
  80. package/src/{matcTool.ts → hosts/matcTool.ts} +1 -1
  81. package/src/{peerInstall.ts → hosts/peerInstall.ts} +2 -2
  82. package/src/{releases.ts → hosts/releases.ts} +1 -1
  83. package/src/index.ts +32 -480
  84. package/src/platform/api.ts +302 -0
  85. package/src/{browserAuth.ts → platform/browserAuth.ts} +1 -1
  86. package/src/{config.ts → platform/config.ts} +1 -1
  87. package/src/{serverDiff.ts → platform/serverDiff.ts} +3 -3
  88. package/src/{projectEnv.ts → project/env.ts} +33 -2
  89. package/src/{localFiles.ts → project/localFiles.ts} +1 -1
  90. package/src/{manifest.ts → project/manifest.ts} +1 -1
  91. package/src/{project.ts → project/materialize.ts} +2 -2
  92. package/src/project/paths.ts +20 -0
  93. package/src/{textDiff.ts → project/textDiff.ts} +1 -1
  94. package/src/{types.ts → project/types.ts} +0 -0
  95. package/runtime/web/assets/createViewerLite-C_bkKXLg.js +0 -852
  96. package/src/commands/design.ts +0 -846
  97. package/src/commands/update.ts +0 -211
  98. package/src/util.ts +0 -146
  99. /package/src/commands/{projectTemplates.ts → init/templates.ts} +0 -0
  100. /package/src/{lecodes-3d-editor.d.ts → declarations/lecodes-3d-editor.d.ts} +0 -0
  101. /package/src/{lecodes-assets.d.ts → declarations/lecodes-assets.d.ts} +0 -0
  102. /package/src/{lecodes-design.d.ts → declarations/lecodes-design.d.ts} +0 -0
  103. /package/src/{lecodes-renderer.d.ts → declarations/lecodes-renderer.d.ts} +0 -0
  104. /package/src/{qrcode-terminal.d.ts → declarations/qrcode-terminal.d.ts} +0 -0
  105. /package/src/{peers.ts → hosts/peers.ts} +0 -0
  106. /package/src/{designMeta.ts → project/designMeta.ts} +0 -0
  107. /package/src/{ignore.ts → project/ignore.ts} +0 -0
@@ -1,12 +1,13 @@
1
1
  import { execFileSync, spawn } from "node:child_process"
2
2
  import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs"
3
3
  import { basename, dirname, join, resolve } from "node:path"
4
- import { buildBundle } from "./compile"
5
- import { resolveDesktopExe, selectedDesktopVersion } from "../desktopRenderer"
6
- import { CliError, c, flagBool, flagStr, log, note, success, warn, type Args } from "../util"
7
- import { assertRendererSupportsBundle, desktopPluginLibraries, projectRoot, readJson, resolveDesktopRenderer, writeJson, type AppConfig } from "./appShared"
8
- import { loadIconMaster } from "./appIcon"
9
- import { buildMacApp, macHostProvidedNames } from "./appDesktopMac"
4
+ import { buildBundle } from "../compile"
5
+ import { resolveDesktopExe, selectedDesktopVersion } from "../../hosts/desktopRenderer"
6
+ import { CliError, bool, c, defineCommand, log, note, num, str, success, warn, type FlagValues } from "../../cli"
7
+ import { bundleFlags, bundleOptions } from "../shared"
8
+ import { desktopPluginLibraries, projectRoot, readJson, resolveDesktopRenderer, writeJson, type AppConfig } from "./shared"
9
+ import { loadIconMaster } from "./icon"
10
+ import { buildMacApp, macHostProvidedNames } from "./desktopMac"
10
11
 
11
12
  /*
12
13
  * `lecodes app init|build desktop` — the desktop arm of the native-app commands
@@ -14,7 +15,8 @@ import { buildMacApp, macHostProvidedNames } from "./appDesktopMac"
14
15
  * `build` stages the verified cached host + the compiled bundle + its assets into one portable
15
16
  * folder that runs on a double-click. On macOS the same command produces a signed (and, with a
16
17
  * notarytool profile, notarized) .app instead — the staging half is shared, the bundle half lives
17
- * in appDesktopMac.ts.
18
+ * in desktopMac.ts. The `build` node is defined here and mounted twice: `lecodes desktop build`
19
+ * (desktop.ts) and `lecodes app build desktop` (index.ts) are the same command.
18
20
  *
19
21
  * The staged folder is SELF-DESCRIBING: a pruned app.json (name + desktop.window) ships next to
20
22
  * app.js, and the host (≥1.3.0) reads it for title/size/min-size/resizable/fullscreen — no
@@ -32,16 +34,19 @@ const dirSize = (dir: string): number =>
32
34
 
33
35
  /* --------------------------------------- init ---------------------------------------- */
34
36
 
37
+ /** The `app init desktop` flags (index.ts declares the shared init node). */
38
+ export interface DesktopInitOptions { name?: string, fullscreen?: boolean }
39
+
35
40
  /** `lecodes app init desktop` — declare the desktop target in app.json (mirrors
36
41
  * `lecodes init --desktop` for existing projects). Purely additive; re-running keeps edits. */
37
- export const initDesktop = async (args: Args) => {
42
+ export const initDesktop = async (opts: DesktopInitOptions): Promise<void> => {
38
43
  const root = projectRoot()
39
44
  const existing = readJson<AppConfig>(join(root, "app.json")) ?? {}
40
- const name = flagStr(args, "name") ?? existing.name ?? basename(root)
45
+ const name = opts.name ?? existing.name ?? basename(root)
41
46
  const window = {
42
47
  width: 1280, height: 800, minWidth: 640, minHeight: 480,
43
48
  ...existing.desktop?.window,
44
- ...(flagBool(args, "fullscreen") ? { fullscreen: true } : {}),
49
+ ...(opts.fullscreen ? { fullscreen: true } : {}),
45
50
  }
46
51
  const config: AppConfig = { ...existing, name, desktop: { ...existing.desktop, window } }
47
52
  writeJson(join(root, "app.json"), config)
@@ -60,12 +65,27 @@ const cleanFileName = (name: string) =>
60
65
  const ASSETS_DIR = "assets" // the project's own files
61
66
  const RUNTIME_DIR = "runtime" // the host's engine files (initial.js, built-in materials, IBL)
62
67
 
63
- export const buildDesktop = async (args: Args) => {
68
+ const desktopBuildFlags = {
69
+ out: str("output folder (default dist/<name>)", { alias: "o", value: "<dir>" }),
70
+ slug: str("folder / zip name override (Latin-only)", { value: "<name>" }),
71
+ renderer: str("host + shader variant: gl | vulkan (default: app.json desktop.renderer, else gl)", { value: "<gl|vulkan>" }),
72
+ probe: bool("headless-render the staged folder to a PNG next to it (macOS: a brief windowed run + snapshot)"),
73
+ frames: num("(--probe) frames to render before the snapshot", { default: 90 }),
74
+ after: num("(--probe, macOS) seconds the app runs before the snapshot", { default: 8 }),
75
+ zip: bool("also pack the result for sending: <out>.zip with UTF-8 / forward-slash entries (macOS: a ditto archive of the .app)"),
76
+ dmg: bool("macOS: also build a drag-to-Applications disk image (signed + notarized too)"),
77
+ sign: str("macOS codesign identity — a \"Developer ID Application: …\" cert, or adhoc (local only)", { value: "<identity|adhoc>" }),
78
+ notarize: str("macOS: notarize + staple with this `xcrun notarytool store-credentials` keychain profile", { value: "<profile>" }),
79
+ "no-notarize": bool("macOS: skip notarization even when app.json / the environment names a profile"),
80
+ ...bundleFlags,
81
+ }
82
+
83
+ const buildDesktop = async (flags: FlagValues<typeof desktopBuildFlags>): Promise<void> => {
64
84
  const root = projectRoot()
65
85
  const config = readJson<AppConfig>(join(root, "app.json")) ?? {}
66
86
  const name = (config.name ?? basename(root)).trim() || "app"
67
- const slug = flagStr(args, "slug") ?? cleanFileName(name)
68
- const out = resolve(root, flagStr(args, "o", "out") ?? join(root, "dist", slug))
87
+ const slug = flags.slug ?? cleanFileName(name)
88
+ const out = resolve(root, flags.out ?? join(root, "dist", slug))
69
89
 
70
90
  // Only a previous build output (or an empty/absent dir) may be replaced — never wipe a
71
91
  // directory this command didn't produce.
@@ -78,24 +98,21 @@ export const buildDesktop = async (args: Args) => {
78
98
  }
79
99
 
80
100
  // Per-target entry (app.json desktop.entry); an explicit --entry wins.
81
- if (config.desktop?.entry && flagStr(args, "entry") === undefined) args.flags.entry = config.desktop.entry
101
+ const bundle = bundleOptions(flags)
102
+ if (config.desktop?.entry && bundle.entry === undefined) bundle.entry = config.desktop.entry
82
103
 
83
104
  // ONE renderer decision: it selects the host binary staged into the folder AND the single
84
105
  // .filamat variant compiled beside the bundle. Two independent reads of the config is how a
85
106
  // build ships a Vulkan exe next to an OpenGL shader (a Filament abort at the first material).
86
- const renderer = resolveDesktopRenderer(root, args)
107
+ const renderer = resolveDesktopRenderer(root, flags.renderer)
87
108
 
88
109
  const mac = process.platform === "darwin"
89
110
  note(`Building "${name}" for ${mac ? "macOS" : "desktop"}${renderer === "vulkan" ? " (Vulkan)" : ""}…`)
90
- const { js, resources } = await buildBundle(
91
- args,
92
- { remotePatterns: config.resources?.remote ?? [] },
93
- { localShaders: true, desktopRenderer: renderer },
94
- )
95
-
96
- // The Vulkan host has no 2D engine — refuse now rather than shipping a folder that opens a blank
97
- // window (see assertRendererSupportsBundle).
98
- assertRendererSupportsBundle(renderer, js)
111
+ const { js, resources } = await buildBundle(bundle, {
112
+ shell: { remotePatterns: config.resources?.remote ?? [] },
113
+ localShaders: true,
114
+ desktopRenderer: renderer,
115
+ })
99
116
 
100
117
  // Only what the bundle actually references ships — the compile inventory lists EVERY project
101
118
  // resource (design/ references included), which would multiply the build size. asset() compiles
@@ -145,9 +162,14 @@ export const buildDesktop = async (args: Args) => {
145
162
  throw new CliError(`The bundle references assets with no local file: ${missing.join(", ")}`)
146
163
  }
147
164
 
148
- // macOS: one signed .app rather than a folder — see appDesktopMac.ts.
149
- if (mac) return buildMacApp({ root, config, name, slug, out, js, shipped, hostExe: exe, args })
150
- if (flagBool(args, "dmg")) warn("--dmg is a macOS format — ignored on this platform.")
165
+ // macOS: one signed .app rather than a folder — see desktopMac.ts.
166
+ if (mac) {
167
+ return buildMacApp({
168
+ root, config, name, slug, out, js, shipped, hostExe: exe,
169
+ opts: { sign: flags.sign, notarize: flags.notarize, noNotarize: flags["no-notarize"], dmg: flags.dmg, zip: flags.zip, probe: flags.probe, after: flags.after },
170
+ })
171
+ }
172
+ if (flags.dmg) warn("--dmg is a macOS format — ignored on this platform.")
151
173
 
152
174
  const version = process.env.LECODES_DESKTOP_EXE ? "local build" : selectedDesktopVersion()
153
175
 
@@ -236,11 +258,21 @@ export const buildDesktop = async (args: Args) => {
236
258
 
237
259
  success(`Portable build: ${out} (${mb(dirSize(out))})`)
238
260
 
239
- if (flagBool(args, "probe")) await probeBuild(out, exeName, config, args)
240
- if (flagBool(args, "zip")) zipBuild(out)
241
- if (!flagBool(args, "probe")) note(`Tip: --probe renders the staged folder headless to verify exactly what ships.`)
261
+ if (flags.probe) await probeBuild(out, exeName, config, flags.frames)
262
+ if (flags.zip) zipBuild(out)
263
+ if (!flags.probe) note(`Tip: --probe renders the staged folder headless to verify exactly what ships.`)
242
264
  }
243
265
 
266
+ /** `lecodes desktop build` and `lecodes app build desktop` — the one node, mounted in both trees. */
267
+ export const desktopBuild = defineCommand({
268
+ name: "build",
269
+ summary: "Stage a portable desktop app folder (dist/<name>/): host exe + app.js + assets + runtime",
270
+ description: "The cached host renamed to <Name>.exe + app.js + assets/ (the project's files) + runtime/ (the engine's) + app.json (the host reads title/size/fullscreen from it) + app.ico from app.json \"icon\". Duplicate asset basenames are a build error (they silently alias in the bundle). Also spelled `lecodes app build desktop`.\n\nOn macOS: a signed .app instead (the downloaded LeCodes Desktop.app + the project inside it); app.json desktop.macos = bundleId / buildNumber / category / signing.identity / notarize.profile (a notarytool keychain profile — notarized + stapled, so it opens on any Mac without \"Open Anyway\"). --sign / --notarize / --no-notarize override it per build; LECODES_MACOS_SIGN_ID / LECODES_NOTARY_PROFILE sit between the flags and app.json.",
271
+ flags: desktopBuildFlags,
272
+ examples: ["lecodes desktop build", "lecodes app build desktop --zip --probe", "lecodes desktop build -o out/kiosk --renderer vulkan", "lecodes app build desktop --dmg --notarize MyProfile"],
273
+ run: ({ flags }) => buildDesktop(flags),
274
+ })
275
+
244
276
  /** app.ico from the app.json icon master: PNG-compressed frames (fine on the Win10+ the
245
277
  * distribution requires) at the sizes Windows asks for — no image tooling needed. */
246
278
  const writeIco = async (root: string, config: AppConfig, dest: string) => {
@@ -272,9 +304,8 @@ const writeIco = async (root: string, config: AppConfig, dest: string) => {
272
304
 
273
305
  /** `--probe`: run the STAGED folder hidden and dump a PNG next to it — verifies exactly what
274
306
  * ships (the staged host, its assets, the bundle), not the dev environment. */
275
- const probeBuild = async (out: string, exeName: string, config: AppConfig, args: Args) => {
307
+ const probeBuild = async (out: string, exeName: string, config: AppConfig, frames: number) => {
276
308
  const win = config.desktop?.window
277
- const frames = Number(flagStr(args, "frames")) || 90
278
309
  const shot = join(dirname(out), `${basename(out)}-probe.png`)
279
310
  note(`Probe: hidden render of the staged folder (${frames} frames)…`)
280
311
  await new Promise<void>((done, fail) => {
@@ -2,12 +2,12 @@ import { execFileSync, spawn, spawnSync } from "node:child_process"
2
2
  import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs"
3
3
  import { tmpdir } from "node:os"
4
4
  import { basename, join, resolve } from "node:path"
5
- import { CliError, c, flagBool, flagStr, log, note, success, warn, type Args } from "../util"
6
- import { writeJson, type AppConfig, type MacDesktopConfig } from "./appShared"
7
- import { loadIconMaster } from "./appIcon"
5
+ import { CliError, c, log, note, success, warn } from "../../cli"
6
+ import { writeJson, type AppConfig, type MacDesktopConfig } from "./shared"
7
+ import { loadIconMaster } from "./icon"
8
8
 
9
9
  /*
10
- * `lecodes app build desktop` on macOS — the .app arm of appDesktop.ts.
10
+ * `lecodes app build desktop` on macOS — the .app arm of desktop.ts.
11
11
  *
12
12
  * Windows/Linux stage a portable FOLDER (exe + app.js + assets/ + runtime/). macOS has no such
13
13
  * thing: Gatekeeper judges one signed bundle as a whole, and a Finder launch runs with cwd "/", so
@@ -320,8 +320,7 @@ const writeIcns = async (root: string, config: AppConfig, dest: string, tmp: str
320
320
 
321
321
  /** `--probe`: launch the built app for a few seconds and grab the presented 3D frame through the
322
322
  * host's LECODES_SNAPSHOT hook. The window shows briefly — the macOS host has no hidden mode. */
323
- const probeMacApp = async (app: string, out: string, args: Args) => {
324
- const after = Number(flagStr(args, "after")) || 8
323
+ const probeMacApp = async (app: string, out: string, after: number) => {
325
324
  const shot = join(out, `${basename(app, ".app")}-probe.png`)
326
325
  rmSync(shot, { force: true })
327
326
  note(`Probe: launching the built app for ${after + 2}s (the window will show)…`)
@@ -348,6 +347,18 @@ const probeMacApp = async (app: string, out: string, args: Args) => {
348
347
 
349
348
  /* ---------------------------------------- build ---------------------------------------- */
350
349
 
350
+ /** The macOS-relevant `desktop build` flags (desktop.ts declares them; this is the typed subset). */
351
+ export interface MacBuildOptions {
352
+ sign?: string
353
+ notarize?: string
354
+ noNotarize: boolean
355
+ dmg: boolean
356
+ zip: boolean
357
+ probe: boolean
358
+ /** `--probe`: seconds the app runs before the snapshot. */
359
+ after: number
360
+ }
361
+
351
362
  export interface MacBuildInput {
352
363
  root: string
353
364
  config: AppConfig
@@ -357,11 +368,11 @@ export interface MacBuildInput {
357
368
  js: string
358
369
  shipped: { name: string, path: string, absPath?: string }[]
359
370
  hostExe: string
360
- args: Args
371
+ opts: MacBuildOptions
361
372
  }
362
373
 
363
374
  export const buildMacApp = async (input: MacBuildInput) => {
364
- const { root, config, name, slug, out, js, shipped, hostExe, args } = input
375
+ const { root, config, name, slug, out, js, shipped, hostExe, opts } = input
365
376
  const hostApp = macHostAppOf(hostExe)
366
377
  assertMacHostCanBundle(hostExe)
367
378
  const mac = config.desktop?.macos ?? {}
@@ -416,12 +427,7 @@ export const buildMacApp = async (input: MacBuildInput) => {
416
427
  note(` bundle: ${bundleId} ${config.version ?? "1.0"} (${mac.buildNumber ?? 1})`)
417
428
 
418
429
  // 4) Sign the whole bundle (the project files are now sealed too), then notarize + staple.
419
- const signing = resolveMacSigning(mac, {
420
- sign: flagStr(args, "sign"),
421
- notarize: flagStr(args, "notarize"),
422
- noNotarize: flagBool(args, "no-notarize"),
423
- env: process.env,
424
- }, developerIdInKeychain)
430
+ const signing = resolveMacSigning(mac, { sign: opts.sign, notarize: opts.notarize, noNotarize: opts.noNotarize, env: process.env }, developerIdInKeychain)
425
431
  await signApp(app, signing, hostApp, tmp)
426
432
  if (signing.notaryProfile) {
427
433
  notarize(app, signing.notaryProfile, tmp, "app")
@@ -439,16 +445,16 @@ export const buildMacApp = async (input: MacBuildInput) => {
439
445
  success(`App: ${app} (${mb(dirSize(app))})`)
440
446
 
441
447
  // The DMG goes before --probe/--zip so a failure there costs nothing already earned.
442
- if (flagBool(args, "dmg")) await buildDmg(app, out, appName, signing, tmp)
443
- if (flagBool(args, "probe")) await probeMacApp(app, out, args)
444
- if (flagBool(args, "zip")) {
448
+ if (opts.dmg) await buildDmg(app, out, appName, signing, tmp)
449
+ if (opts.probe) await probeMacApp(app, out, opts.after)
450
+ if (opts.zip) {
445
451
  // ditto, not zip(1): it keeps the bundle's structure/xattrs (the stapled ticket rides in
446
452
  // Contents/CodeResources) exactly as Finder's "Compress" would.
447
453
  const zip = join(out, `${appName}.zip`)
448
454
  rmSync(zip, { force: true })
449
455
  execFileSync("ditto", ["-c", "-k", "--keepParent", app, zip])
450
456
  success(`Zip: ${zip} (${mb(statSync(zip).size)})`)
451
- } else if (!flagBool(args, "dmg")) {
457
+ } else if (!opts.dmg) {
452
458
  note("Tip: --zip packs the .app for sending; --dmg wraps it in a drag-to-Applications image.")
453
459
  }
454
460
  } finally {
@@ -1,8 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
2
2
  import { dirname, join, resolve } from "node:path"
3
3
  import type { JimpInstance } from "jimp"
4
- import { CliError, c, log, warn } from "../util"
5
- import type { AppConfig } from "./appShared"
4
+ import { CliError, c, log, warn } from "../../cli"
5
+ import type { AppConfig } from "./shared"
6
6
 
7
7
  /* App icons rendered from the one master image app.json "icon" names (a square ≥1024px
8
8
  * PNG/JPEG, project-root-relative). Strictly additive: without the key, sync never touches an