bleam 0.0.5 → 0.0.7

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 (141) hide show
  1. package/dist/ai.cjs +4146 -0
  2. package/dist/ai.d.cts +535 -0
  3. package/dist/ai.d.ts +535 -0
  4. package/dist/ai.js +4102 -0
  5. package/dist/animated.cjs +286 -0
  6. package/dist/animated.d.cts +47 -0
  7. package/dist/animated.d.ts +47 -0
  8. package/dist/animated.js +281 -0
  9. package/dist/app.cjs +92 -0
  10. package/dist/app.d.cts +1 -2
  11. package/dist/app.d.ts +1 -2
  12. package/dist/app.js +93 -2
  13. package/dist/bundler.d.cts +1 -2
  14. package/dist/bundler.d.ts +1 -2
  15. package/dist/cli.cjs +1375 -71
  16. package/dist/cli.d.cts +89 -1
  17. package/dist/cli.d.ts +89 -1
  18. package/dist/cli.js +1373 -74
  19. package/dist/config-Bb6SULnR.cjs +46 -0
  20. package/dist/config-CfWa3q-k.js +28 -0
  21. package/dist/config-Cms0rvqg.d.ts +40 -0
  22. package/dist/config-CufOVJV3.d.cts +40 -0
  23. package/dist/config.cjs +4 -25
  24. package/dist/config.d.cts +2 -28
  25. package/dist/config.d.ts +2 -28
  26. package/dist/config.js +2 -24
  27. package/dist/crypto-BB92-Upx.js +7 -0
  28. package/dist/crypto-CT3VZ7lF.cjs +13 -0
  29. package/dist/crypto.cjs +2 -2
  30. package/dist/crypto.d.cts +6 -1
  31. package/dist/crypto.d.ts +8 -1
  32. package/dist/crypto.js +2 -2
  33. package/dist/dev-protocol.d.cts +16 -3
  34. package/dist/dev-protocol.d.ts +16 -3
  35. package/dist/dev-protocol.js +1 -2
  36. package/dist/files-Bo7h9fik.cjs +468 -0
  37. package/dist/files-Ds1wT8C2.js +445 -0
  38. package/dist/files-DwA7pzr3.d.cts +99 -0
  39. package/dist/files-VrkQlKIT.d.ts +99 -0
  40. package/dist/files.cjs +7 -0
  41. package/dist/files.d.cts +2 -0
  42. package/dist/files.d.ts +2 -0
  43. package/dist/files.js +4 -0
  44. package/dist/index.cjs +4 -5
  45. package/dist/index.d.cts +2 -3
  46. package/dist/index.d.ts +2 -3
  47. package/dist/index.js +2 -4
  48. package/dist/native-runtime-C85Nuc4F.js +10 -0
  49. package/dist/{native-runtime-CdeT0JyQ.cjs → native-runtime-CsXnXkQn.cjs} +1 -14
  50. package/dist/native-sqlite-xcGdamRD.js +64 -0
  51. package/dist/native-sqlite-yQLD5s9i.cjs +66 -0
  52. package/dist/platform.cjs +27 -0
  53. package/dist/platform.d.cts +39 -0
  54. package/dist/platform.d.ts +39 -0
  55. package/dist/platform.js +21 -0
  56. package/dist/runtime.d.cts +1 -7
  57. package/dist/runtime.d.ts +1 -7
  58. package/dist/schema-Bo5Jvqus.js +90 -0
  59. package/dist/schema-DOOjfXvs.d.ts +58 -0
  60. package/dist/schema-ENSMX_1t.d.cts +58 -0
  61. package/dist/schema-rQ13mrpD.cjs +102 -0
  62. package/dist/schema.cjs +4 -0
  63. package/dist/schema.d.cts +2 -0
  64. package/dist/schema.d.ts +2 -0
  65. package/dist/schema.js +3 -0
  66. package/dist/state-Bx0VlTlO.cjs +852 -0
  67. package/dist/state-CAwe-Vw1.js +767 -0
  68. package/dist/state.cjs +19 -0
  69. package/dist/state.d.cts +159 -0
  70. package/dist/state.d.ts +159 -0
  71. package/dist/state.js +6 -0
  72. package/dist/styles.cjs +742 -0
  73. package/dist/styles.d.cts +701 -0
  74. package/dist/styles.d.ts +701 -0
  75. package/dist/styles.js +739 -0
  76. package/dist/ui-1WepaMS4.d.cts +92 -0
  77. package/dist/ui-234Plg7Z.d.cts +92 -0
  78. package/dist/ui-BJmXhz9Q.d.ts +92 -0
  79. package/dist/ui-Dd7SXdbg.d.cts +92 -0
  80. package/dist/ui-Ds88eETu.d.ts +92 -0
  81. package/dist/ui-TaqnB5SP.d.ts +92 -0
  82. package/dist/ui-WQvnXL0W.d.ts +92 -0
  83. package/dist/ui.cjs +364 -0
  84. package/dist/ui.d.cts +2 -0
  85. package/dist/ui.d.ts +2 -0
  86. package/dist/ui.js +357 -0
  87. package/dist/window.cjs +596 -0
  88. package/dist/window.d.cts +63 -0
  89. package/dist/window.d.ts +63 -0
  90. package/dist/window.js +589 -0
  91. package/metro-config.cjs +18 -13
  92. package/package.json +57 -21
  93. package/templates/basic/app/index.tsx +133 -12
  94. package/templates/basic/app.config.ts +4 -2
  95. package/templates/foundation-models/app/index.tsx +32 -0
  96. package/templates/foundation-models/app.config.ts +5 -0
  97. package/templates/image-generation/app/index.tsx +39 -0
  98. package/templates/image-generation/app.config.ts +5 -0
  99. package/templates/state/app/index.tsx +30 -0
  100. package/templates/state/app.config.ts +5 -0
  101. package/dist/app.d.cts.map +0 -1
  102. package/dist/app.d.ts.map +0 -1
  103. package/dist/app.js.map +0 -1
  104. package/dist/bundler.d.cts.map +0 -1
  105. package/dist/bundler.d.ts.map +0 -1
  106. package/dist/cli.js.map +0 -1
  107. package/dist/config.d.cts.map +0 -1
  108. package/dist/config.d.ts.map +0 -1
  109. package/dist/config.js.map +0 -1
  110. package/dist/crypto-BNhWeXMj.cjs +0 -15
  111. package/dist/crypto-Bg8cD8wX.d.cts +0 -8
  112. package/dist/crypto-Bg8cD8wX.d.cts.map +0 -1
  113. package/dist/crypto-CCA_hvao.js +0 -11
  114. package/dist/crypto-CCA_hvao.js.map +0 -1
  115. package/dist/crypto-IOzbNoJD.d.ts +0 -8
  116. package/dist/crypto-IOzbNoJD.d.ts.map +0 -1
  117. package/dist/dev-protocol.d.cts.map +0 -1
  118. package/dist/dev-protocol.d.ts.map +0 -1
  119. package/dist/dev-protocol.js.map +0 -1
  120. package/dist/fs-BK7joD1g.js +0 -13
  121. package/dist/fs-BK7joD1g.js.map +0 -1
  122. package/dist/fs-CEhcjuN6.d.ts +0 -9
  123. package/dist/fs-CEhcjuN6.d.ts.map +0 -1
  124. package/dist/fs-CfE8CHf_.d.cts +0 -9
  125. package/dist/fs-CfE8CHf_.d.cts.map +0 -1
  126. package/dist/fs-DKHYTPua.cjs +0 -18
  127. package/dist/fs.cjs +0 -3
  128. package/dist/fs.d.cts +0 -2
  129. package/dist/fs.d.ts +0 -2
  130. package/dist/fs.js +0 -3
  131. package/dist/native-runtime-BoqybBBf.js +0 -18
  132. package/dist/native-runtime-BoqybBBf.js.map +0 -1
  133. package/dist/native.cjs +0 -132
  134. package/dist/native.d.cts +0 -64
  135. package/dist/native.d.cts.map +0 -1
  136. package/dist/native.d.ts +0 -64
  137. package/dist/native.d.ts.map +0 -1
  138. package/dist/native.js +0 -133
  139. package/dist/native.js.map +0 -1
  140. package/dist/runtime.d.cts.map +0 -1
  141. package/dist/runtime.d.ts.map +0 -1
package/dist/cli.js CHANGED
@@ -1,33 +1,139 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import { execFileSync, spawn, spawnSync } from "node:child_process";
4
- import { createHash } from "node:crypto";
5
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
6
6
  import http from "node:http";
7
7
  import os from "node:os";
8
8
  import path from "node:path";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { WebSocket, WebSocketServer } from "ws";
11
+ import yoctoSpinner from "yocto-spinner";
12
+ import { bold, dim, green, red, yellow } from "yoctocolors";
10
13
 
11
14
  //#region src/cli.ts
12
15
  const require = createRequire(import.meta.url);
13
16
  const packageRoot = path.dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
14
17
  const packageJson = require("../package.json");
15
18
  const runtimeVersion = packageJson.version;
16
- const runtimePlatform = `macos-${process.arch}`;
19
+ const starterAppName = "Bleam Basic";
20
+ function artifactPlatform() {
21
+ return `macos-${process.arch}`;
22
+ }
23
+ function prettyPath(value) {
24
+ const home = os.homedir();
25
+ return value === home || value.startsWith(`${home}${path.sep}`) ? `~${value.slice(home.length)}` : value;
26
+ }
27
+ function commandSucceeds(command, args) {
28
+ const result = spawnSync(command, args, { stdio: "ignore" });
29
+ return !result.error && result.status === 0;
30
+ }
31
+ function commandText(command, args) {
32
+ return [command, ...args].join(" ");
33
+ }
34
+ function doctorHint(projectRoot) {
35
+ return projectRoot ? `Run ${bold(`bleam doctor ${projectRoot}`)} for diagnostics.` : `Run ${bold("bleam doctor")} for diagnostics.`;
36
+ }
37
+ function appConfigBuildExample() {
38
+ return [
39
+ "Example:",
40
+ "import { appConfig } from 'bleam'",
41
+ "",
42
+ "export default appConfig({",
43
+ " name: 'My App',",
44
+ " bundleIdentifier: 'com.example.myapp',",
45
+ " teamId: 'ABCDE12345',",
46
+ "})"
47
+ ].join("\n");
48
+ }
49
+ function missingBuildFieldError(field) {
50
+ return /* @__PURE__ */ new Error(`app.config.ts must define ${field} for bleam build.\n\n${appConfigBuildExample()}`);
51
+ }
52
+ function heading(text) {
53
+ return bold(text);
54
+ }
55
+ function muted(text) {
56
+ return dim(text);
57
+ }
58
+ function statusLabel(status) {
59
+ if (status === "pass") return green("[ok]");
60
+ if (status === "warn") return yellow("[warn]");
61
+ return red("[fail]");
62
+ }
63
+ function startSpinner(text) {
64
+ if (!process.stderr.isTTY) {
65
+ console.log(text);
66
+ return;
67
+ }
68
+ return yoctoSpinner({
69
+ text,
70
+ handleSignals: false
71
+ }).start();
72
+ }
17
73
  function printUsage() {
18
74
  console.log(`Usage: bleam <command>
19
75
 
20
76
  Commands:
21
- new [dir] [--run] Create a new Bleam project
22
- dev [dir] [--port <port>] Start a Bleam dev server`);
77
+ new <dir> [--template <name>]
78
+ Create and run a Bleam project
79
+ dev [dir] [--port <port>] [--no-run] [--clean]
80
+ Start a Bleam dev server
81
+ build [dir] [--output <dir>] [--clean]
82
+ Export standalone native archives
83
+ install [dir] [--output <dir>] [--clean]
84
+ Build and install a standalone macOS app
85
+ publish ota [dir] [--service-url <url>]
86
+ Publish an OTA update to the local update service
87
+ publish platform [dir] [--service-url <url>]
88
+ Build and publish a platform update locally
89
+ doctor [dir] Check the local Bleam environment
90
+ typecheck [dir] Typecheck project files
91
+ check [dir] [--write] Format, lint, and organize imports
92
+ format [dir] [--check] Format project files
93
+ lint [dir] Lint project files`);
94
+ }
95
+ function parsePublishOtaOptions(argv) {
96
+ const root = invocationRoot();
97
+ let projectRoot = root;
98
+ let hasProjectRootArg = false;
99
+ let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? "http://127.0.0.1:8787";
100
+ for (let index = 0; index < argv.length; index += 1) {
101
+ const arg = argv[index];
102
+ if (arg === "--service-url") {
103
+ serviceUrl = argv[++index] ?? "";
104
+ continue;
105
+ }
106
+ if (arg.startsWith("--service-url=")) {
107
+ serviceUrl = arg.slice(14);
108
+ continue;
109
+ }
110
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
111
+ if (hasProjectRootArg) throw new Error("Only one project directory may be specified");
112
+ projectRoot = path.resolve(root, arg);
113
+ hasProjectRootArg = true;
114
+ }
115
+ const url = new URL(serviceUrl);
116
+ if (url.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Local publishing requires an http://localhost service URL");
117
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
118
+ return {
119
+ projectRoot: canonicalPath(projectRoot),
120
+ serviceUrl: url.origin
121
+ };
23
122
  }
24
123
  function invocationRoot() {
25
- return path.isAbsolute(process.env.PWD ?? "") ? process.env.PWD : process.cwd();
124
+ const pwd = process.env.PWD;
125
+ return pwd && path.isAbsolute(pwd) ? pwd : process.cwd();
126
+ }
127
+ function canonicalPath(value) {
128
+ const resolved = path.resolve(value);
129
+ return existsSync(resolved) ? realpathSync.native(resolved) : resolved;
26
130
  }
27
131
  function parseDevOptions(argv) {
28
132
  const root = invocationRoot();
29
133
  let projectRoot = root;
30
134
  let port = 8081;
135
+ let run = true;
136
+ let clean = false;
31
137
  let hasProjectRootArg = false;
32
138
  for (let index = 0; index < argv.length; index += 1) {
33
139
  const arg = argv[index];
@@ -40,6 +146,18 @@ function parseDevOptions(argv) {
40
146
  port = Number(arg.slice(7));
41
147
  continue;
42
148
  }
149
+ if (arg === "--run") {
150
+ run = true;
151
+ continue;
152
+ }
153
+ if (arg === "--no-run") {
154
+ run = false;
155
+ continue;
156
+ }
157
+ if (arg === "--clean" || arg === "-c") {
158
+ clean = true;
159
+ continue;
160
+ }
43
161
  if (arg === "--help" || arg === "-h") {
44
162
  printUsage();
45
163
  process.exit(0);
@@ -51,17 +169,121 @@ function parseDevOptions(argv) {
51
169
  if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
52
170
  if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
53
171
  return {
54
- projectRoot: path.resolve(projectRoot),
55
- port
172
+ projectRoot: canonicalPath(projectRoot),
173
+ port,
174
+ run,
175
+ clean
56
176
  };
57
177
  }
58
178
  function parseNewOptions(argv) {
179
+ const root = invocationRoot();
180
+ let projectRoot;
181
+ let template = "basic";
182
+ for (let index = 0; index < argv.length; index += 1) {
183
+ const arg = argv[index];
184
+ if (arg === "--help" || arg === "-h") {
185
+ printUsage();
186
+ process.exit(0);
187
+ }
188
+ if (arg === "--template") {
189
+ template = projectTemplate(argv[++index] ?? "");
190
+ continue;
191
+ }
192
+ if (arg.startsWith("--template=")) {
193
+ template = projectTemplate(arg.slice(11));
194
+ continue;
195
+ }
196
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
197
+ if (projectRoot) throw new Error("Only one project directory may be specified");
198
+ projectRoot = path.resolve(root, arg);
199
+ }
200
+ if (!projectRoot) throw new Error("Usage: bleam new <dir>");
201
+ return {
202
+ projectRoot: canonicalPath(projectRoot),
203
+ template
204
+ };
205
+ }
206
+ const projectTemplates = [
207
+ "basic",
208
+ "state",
209
+ "foundation-models",
210
+ "image-generation"
211
+ ];
212
+ function projectTemplate(value) {
213
+ if (projectTemplates.includes(value)) return value;
214
+ throw new Error(`Unknown template: ${value}. Expected ${projectTemplates.join(", ")}`);
215
+ }
216
+ function parseBuildOptions(argv) {
217
+ const root = invocationRoot();
218
+ let projectRoot = root;
219
+ let hasProjectRootArg = false;
220
+ let output;
221
+ let configuration = "Release";
222
+ let clean = false;
223
+ let exportOptionsPlist;
224
+ for (let index = 0; index < argv.length; index += 1) {
225
+ const arg = argv[index];
226
+ if (arg === "--output" || arg === "-o") {
227
+ output = argv[++index];
228
+ continue;
229
+ }
230
+ if (arg.startsWith("--output=")) {
231
+ output = arg.slice(9);
232
+ continue;
233
+ }
234
+ if (arg === "--configuration") {
235
+ const value = argv[++index];
236
+ if (!value) throw new Error("Missing --configuration value");
237
+ configuration = value;
238
+ continue;
239
+ }
240
+ if (arg.startsWith("--configuration=")) {
241
+ configuration = arg.slice(16);
242
+ continue;
243
+ }
244
+ if (arg === "--clean") {
245
+ clean = true;
246
+ continue;
247
+ }
248
+ if (arg === "--export-options-plist") {
249
+ exportOptionsPlist = argv[++index];
250
+ continue;
251
+ }
252
+ if (arg.startsWith("--export-options-plist=")) {
253
+ exportOptionsPlist = arg.slice(23);
254
+ continue;
255
+ }
256
+ if (arg === "--help" || arg === "-h") {
257
+ printUsage();
258
+ process.exit(0);
259
+ }
260
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
261
+ projectRoot = path.resolve(root, arg);
262
+ hasProjectRootArg = true;
263
+ }
264
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
265
+ const resolvedProjectRoot = canonicalPath(projectRoot);
266
+ return {
267
+ projectRoot: resolvedProjectRoot,
268
+ output: path.resolve(resolvedProjectRoot, output ?? "dist"),
269
+ configuration,
270
+ clean,
271
+ exportOptionsPlist: exportOptionsPlist ? path.resolve(root, exportOptionsPlist) : void 0
272
+ };
273
+ }
274
+ function parseProjectToolOptions(argv) {
59
275
  const root = invocationRoot();
60
276
  let projectRoot = root;
61
- let run = false;
277
+ let hasProjectRootArg = false;
278
+ let write = false;
279
+ let formatCheck = false;
62
280
  for (const arg of argv) {
63
- if (arg === "--run") {
64
- run = true;
281
+ if (arg === "--write") {
282
+ write = true;
283
+ continue;
284
+ }
285
+ if (arg === "--check") {
286
+ formatCheck = true;
65
287
  continue;
66
288
  }
67
289
  if (arg === "--help" || arg === "-h") {
@@ -70,15 +292,35 @@ function parseNewOptions(argv) {
70
292
  }
71
293
  if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
72
294
  projectRoot = path.resolve(root, arg);
295
+ hasProjectRootArg = true;
73
296
  }
297
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
74
298
  return {
75
- projectRoot,
76
- run
299
+ projectRoot: canonicalPath(projectRoot),
300
+ write,
301
+ formatCheck
77
302
  };
78
303
  }
304
+ function parseDoctorOptions(argv) {
305
+ const root = invocationRoot();
306
+ let projectRoot = root;
307
+ let hasProjectRootArg = false;
308
+ for (const arg of argv) {
309
+ if (arg === "--help" || arg === "-h") {
310
+ printUsage();
311
+ process.exit(0);
312
+ }
313
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
314
+ projectRoot = path.resolve(root, arg);
315
+ hasProjectRootArg = true;
316
+ }
317
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
318
+ return { projectRoot: canonicalPath(projectRoot) };
319
+ }
79
320
  function parseInternalMetroOptions(argv) {
80
321
  let workspaceRoot;
81
322
  let port = 8081;
323
+ let resetCache = false;
82
324
  for (let index = 0; index < argv.length; index += 1) {
83
325
  const arg = argv[index];
84
326
  if (arg === "--workspace-root") {
@@ -93,13 +335,18 @@ function parseInternalMetroOptions(argv) {
93
335
  port = Number(arg.slice(7));
94
336
  continue;
95
337
  }
338
+ if (arg === "--reset-cache") {
339
+ resetCache = true;
340
+ continue;
341
+ }
96
342
  throw new Error(`Unknown internal option: ${arg}`);
97
343
  }
98
344
  if (!workspaceRoot) throw new Error("Missing --workspace-root");
99
345
  if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
100
346
  return {
101
347
  workspaceRoot,
102
- port
348
+ port,
349
+ resetCache
103
350
  };
104
351
  }
105
352
  function isBleamProjectRoot(projectRoot) {
@@ -139,6 +386,28 @@ function assertStringField(value, field) {
139
386
  if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
140
387
  return value;
141
388
  }
389
+ function normalizeAppGroups(value, bundleIdentifier) {
390
+ const configured = Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
391
+ if (configured.length > 0) return configured;
392
+ return bundleIdentifier ? [`group.${bundleIdentifier}`] : [];
393
+ }
394
+ function isHexColor(value) {
395
+ return typeof value === "string" && /^#[\da-f]{6}$/i.test(value);
396
+ }
397
+ function normalizeAppearance(value) {
398
+ const input = value && typeof value === "object" ? value : {};
399
+ const window = input.window && typeof input.window === "object" ? input.window : {};
400
+ const appearance = {};
401
+ if (window.backgroundColor !== void 0) {
402
+ if (!isHexColor(window.backgroundColor)) throw new Error("app.config.ts appearance.window.backgroundColor must be a #rrggbb color");
403
+ appearance.window = { backgroundColor: window.backgroundColor };
404
+ }
405
+ if (input.userInterfaceStyle !== void 0) {
406
+ if (input.userInterfaceStyle !== "automatic" && input.userInterfaceStyle !== "light" && input.userInterfaceStyle !== "dark") throw new Error("app.config.ts appearance.userInterfaceStyle must be automatic, light, or dark");
407
+ appearance.userInterfaceStyle = input.userInterfaceStyle;
408
+ }
409
+ return appearance;
410
+ }
142
411
  function loadAppConfig(projectRoot) {
143
412
  const configPath = path.join(projectRoot, "app.config.ts");
144
413
  const loader = `
@@ -165,11 +434,15 @@ function loadAppConfig(projectRoot) {
165
434
  });
166
435
  if (result.status !== 0) throw new Error(result.stderr.trim() || "Failed to load app.config.ts");
167
436
  const value = JSON.parse(result.stdout);
437
+ const bundleIdentifier = typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0;
168
438
  return {
169
439
  name: assertStringField(value.name, "name"),
170
440
  version: typeof value.version === "string" ? value.version : void 0,
171
- bundleIdentifier: typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0,
172
- teamId: typeof value.teamId === "string" ? value.teamId : void 0
441
+ buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
442
+ bundleIdentifier,
443
+ appGroups: normalizeAppGroups(value.appGroups, bundleIdentifier),
444
+ teamId: typeof value.teamId === "string" ? value.teamId : void 0,
445
+ appearance: normalizeAppearance(value.appearance)
173
446
  };
174
447
  }
175
448
  function assertBleamConfigFiles(projectRoot) {
@@ -188,8 +461,11 @@ function readProjectConfig(options) {
188
461
  return {
189
462
  name: config.name,
190
463
  version: config.version,
464
+ buildNumber: config.buildNumber ?? "1",
191
465
  bundleIdentifier: config.bundleIdentifier,
466
+ appGroups: config.appGroups ?? [],
192
467
  teamId: config.teamId,
468
+ appearance: config.appearance ?? normalizeAppearance(void 0),
193
469
  runtimeVersion,
194
470
  sdkVersion: runtimeVersion
195
471
  };
@@ -202,20 +478,43 @@ function nameFromDirectory(projectRoot) {
202
478
  if (!name) return "Bleam App";
203
479
  return name.replace(/\b\w/g, (letter) => letter.toUpperCase());
204
480
  }
205
- function createNewProject(options) {
206
- const templateRoot = path.join(packageRoot, "templates", "basic");
207
- if (!existsSync(templateRoot)) throw new Error(`Missing basic template: ${templateRoot}`);
208
- if (existsSync(options.projectRoot) && readdirSync(options.projectRoot).length > 0) throw new Error(`Directory is not empty: ${options.projectRoot}`);
209
- mkdirSync(options.projectRoot, { recursive: true });
210
- cpSync(templateRoot, options.projectRoot, { recursive: true });
481
+ function singleQuotedString(value) {
482
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
483
+ }
484
+ function updateStarterAppName(projectRoot, name) {
485
+ const appPath = path.join(projectRoot, "app", "index.tsx");
486
+ const source = readFileSync(appPath, "utf8");
487
+ const placeholder = `const appName = ${singleQuotedString(starterAppName)}`;
488
+ if (!source.includes(placeholder)) throw new Error(`Missing starter app name placeholder: ${appPath}`);
489
+ writeFileSync(appPath, source.replace(placeholder, `const appName = ${singleQuotedString(name)}`));
490
+ }
491
+ function createProject(options) {
492
+ const template = options.template ?? "basic";
493
+ const templateRoot = path.join(packageRoot, "templates", template);
494
+ if (!existsSync(templateRoot)) throw new Error(`Missing ${template} template: ${templateRoot}`);
495
+ if (existsSync(options.projectRoot)) throw new Error(`Destination already exists: ${options.projectRoot}`);
211
496
  const name = nameFromDirectory(options.projectRoot);
212
- writeFileSync(path.join(options.projectRoot, "app.config.ts"), `export default {\n name: ${JSON.stringify(name)},\n}\n`);
213
- writeFileSync(path.join(options.projectRoot, ".gitignore"), `.bleam\n`);
214
- createDevWorkspace(options.projectRoot, {
215
- name,
216
- runtimeVersion,
217
- sdkVersion: runtimeVersion
218
- });
497
+ mkdirSync(options.projectRoot, { recursive: true });
498
+ try {
499
+ cpSync(templateRoot, options.projectRoot, { recursive: true });
500
+ updateStarterAppName(options.projectRoot, name);
501
+ writeFileSync(path.join(options.projectRoot, "app.config.ts"), `import { appConfig } from 'bleam'\n\nexport default appConfig({\n name: ${singleQuotedString(name)},\n})\n`);
502
+ writeFileSync(path.join(options.projectRoot, ".gitignore"), `.bleam\nbiome.json\n.DS_Store\n`);
503
+ createDevWorkspace(options.projectRoot, {
504
+ name,
505
+ buildNumber: "1",
506
+ appGroups: [],
507
+ appearance: normalizeAppearance(void 0),
508
+ runtimeVersion,
509
+ sdkVersion: runtimeVersion
510
+ });
511
+ } catch (error) {
512
+ rmSync(options.projectRoot, {
513
+ recursive: true,
514
+ force: true
515
+ });
516
+ throw error;
517
+ }
219
518
  console.log(`Created Bleam project at ${options.projectRoot}`);
220
519
  }
221
520
  function createDevWorkspace(projectRoot, config) {
@@ -229,14 +528,23 @@ function createDevWorkspace(projectRoot, config) {
229
528
  });
230
529
  symlinkSync(dependencyRoot, workspaceNodeModules);
231
530
  ensureProjectTsconfig(projectRoot);
232
- writeFileSync(path.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: {
531
+ const expoConfig = {
233
532
  name: config.name,
234
533
  slug: slugFromName(config.name),
235
534
  version: config.version ?? "0.0.0",
236
535
  orientation: "default",
237
536
  platforms: ["ios"],
238
- newArchEnabled: true
239
- } }, null, 2)}\n`);
537
+ newArchEnabled: true,
538
+ extra: { bleam: {
539
+ appearance: config.appearance,
540
+ buildNumber: config.buildNumber,
541
+ runtimeVersion: config.runtimeVersion,
542
+ sdkVersion: config.sdkVersion
543
+ } }
544
+ };
545
+ if (existsSync(path.join(projectRoot, "icon.png"))) expoConfig.icon = "../../icon.png";
546
+ if (existsSync(path.join(projectRoot, "splash.png"))) expoConfig.plugins = [["expo-splash-screen", { image: "../../splash.png" }]];
547
+ writeFileSync(path.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: expoConfig }, null, 2)}\n`);
240
548
  writeFileSync(path.join(workspaceRoot, "index.js"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(path.join(projectRoot, "app"))}\n\nregisterApp(App)\n`);
241
549
  writeFileSync(path.join(workspaceRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
242
550
  writeFileSync(path.join(workspaceRoot, "package.json"), `${JSON.stringify({
@@ -246,7 +554,9 @@ function createDevWorkspace(projectRoot, config) {
246
554
  main: "index.js",
247
555
  dependencies: {
248
556
  "@types/react": packageJson.dependencies?.["@types/react"],
557
+ "@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
249
558
  expo: packageJson.dependencies?.expo,
559
+ "expo-splash-screen": packageJson.dependencies?.["expo-splash-screen"],
250
560
  react: packageJson.dependencies?.react,
251
561
  "react-native": packageJson.dependencies?.["react-native"],
252
562
  typescript: packageJson.dependencies?.typescript
@@ -256,8 +566,8 @@ function createDevWorkspace(projectRoot, config) {
256
566
  writeFileSync(path.join(workspaceRoot, "tsconfig.json"), `${JSON.stringify({
257
567
  extends: "bleam/tsconfig",
258
568
  compilerOptions: { paths: {
259
- bleam: ["./node_modules/bleam"],
260
- "bleam/*": ["./node_modules/bleam/*"],
569
+ bleam: ["./node_modules/bleam/dist/index"],
570
+ "bleam/*": ["./node_modules/bleam/dist/*"],
261
571
  expo: ["./node_modules/expo"],
262
572
  "expo/*": ["./node_modules/expo/*"],
263
573
  react: ["./node_modules/@types/react"],
@@ -270,6 +580,34 @@ function createDevWorkspace(projectRoot, config) {
270
580
  }, null, 2)}\n`);
271
581
  return workspaceRoot;
272
582
  }
583
+ function writeBiomeConfig(configPath) {
584
+ writeFileSync(configPath, `${JSON.stringify({
585
+ $schema: "https://biomejs.dev/schemas/2.5.2/schema.json",
586
+ files: { includes: [
587
+ "app/**/*",
588
+ "app.config.ts",
589
+ "tsconfig.json"
590
+ ] },
591
+ formatter: {
592
+ enabled: true,
593
+ indentStyle: "space",
594
+ indentWidth: 2,
595
+ lineWidth: 80
596
+ },
597
+ javascript: { formatter: {
598
+ quoteStyle: "single",
599
+ semicolons: "asNeeded",
600
+ trailingCommas: "all"
601
+ } },
602
+ linter: {
603
+ enabled: true,
604
+ rules: {
605
+ preset: "recommended",
606
+ suspicious: { noExplicitAny: "off" }
607
+ }
608
+ }
609
+ }, null, 2)}\n`);
610
+ }
273
611
  function writeProjectRuntimeConfig(projectRoot, config, origin) {
274
612
  const projectConfigPath = path.join(projectRoot, ".bleam", "project.json");
275
613
  mkdirSync(path.dirname(projectConfigPath), { recursive: true });
@@ -278,28 +616,463 @@ function writeProjectRuntimeConfig(projectRoot, config, origin) {
278
616
  version: config.version,
279
617
  bundleIdentifier: config.bundleIdentifier,
280
618
  teamId: config.teamId,
619
+ appearance: config.appearance,
281
620
  devServer: origin,
282
621
  runtimeVersion: config.runtimeVersion,
283
622
  sdkVersion: config.sdkVersion
284
623
  }, null, 2)}\n`);
285
624
  return projectConfigPath;
286
625
  }
626
+ function nativeRuntimeTemplateRoot() {
627
+ return path.resolve(packageRoot, "..", "..", "app", "bleam");
628
+ }
629
+ function safeArtifactName(name) {
630
+ return name.trim().replace(/[/:\\]/g, "-").replace(/\s+/g, " ") || "App";
631
+ }
632
+ function xcodeSettingValue(value) {
633
+ if (/^[A-Za-z0-9_.$-]+$/.test(value)) return value;
634
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
635
+ }
636
+ function plistString(value) {
637
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
638
+ }
639
+ function replacePlistString(plist, key, value) {
640
+ const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<string>)([^<]*)(</string>)`);
641
+ if (!pattern.test(plist)) return plist;
642
+ return plist.replace(pattern, `$1${plistString(value)}$3`);
643
+ }
644
+ function plistStringArray(values) {
645
+ return values.map((value) => `\t\t<string>${plistString(value)}</string>`).join("\n");
646
+ }
647
+ function replaceOrInsertPlistString(plist, key, value) {
648
+ const replaced = replacePlistString(plist, key, value);
649
+ if (replaced !== plist) return replaced;
650
+ const closingDictionary = plist.lastIndexOf("</dict>");
651
+ if (closingDictionary < 0) return plist;
652
+ return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<string>${plistString(value)}</string>\n${plist.slice(closingDictionary)}`;
653
+ }
654
+ function replaceOrInsertPlistStringArray(plist, key, values) {
655
+ const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<array>)([\\s\\S]*?)(</array>)`);
656
+ const replacement = `$1\n${plistStringArray(values)}\n\t$3`;
657
+ if (pattern.test(plist)) return plist.replace(pattern, replacement);
658
+ const closingDictionary = plist.lastIndexOf("</dict>");
659
+ if (closingDictionary < 0) return plist;
660
+ return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<array>\n${plistStringArray(values)}\n\t</array>\n${plist.slice(closingDictionary)}`;
661
+ }
662
+ function writeGeneratedAppJson(nativeRoot, config) {
663
+ writeFileSync(path.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
664
+ name: config.name,
665
+ slug: slugFromName(config.name),
666
+ version: config.version ?? "1.0.0",
667
+ platforms: ["ios"],
668
+ ios: {
669
+ appleTeamId: config.teamId,
670
+ buildNumber: config.buildNumber,
671
+ bundleIdentifier: config.bundleIdentifier
672
+ }
673
+ } }, null, 2)}\n`);
674
+ }
675
+ function patchNativeProject(nativeRoot, config) {
676
+ const projectPath = path.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
677
+ const infoPlistPath = path.join(nativeRoot, "ios", "Bleam", "Info.plist");
678
+ const generationInfoPlistPath = path.join(nativeRoot, "ios", "GenerationService", "Info.plist");
679
+ const platformInfoPlistPath = path.join(nativeRoot, "ios", "PlatformHelper", "Info.plist");
680
+ const appEntitlementsPath = path.join(nativeRoot, "ios", "Bleam", "bleam.entitlements");
681
+ const generationEntitlementsPath = path.join(nativeRoot, "ios", "GenerationService", "GenerationService.entitlements");
682
+ const productName = xcodeSettingValue(config.name);
683
+ const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
684
+ const generationBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.GenerationService`);
685
+ const platformBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.PlatformHelper`);
686
+ const teamId = xcodeSettingValue(config.teamId ?? "");
687
+ const version = xcodeSettingValue(config.version ?? "1.0.0");
688
+ const buildNumber = xcodeSettingValue(config.buildNumber);
689
+ const primaryAppGroup = config.appGroups[0];
690
+ let project = readFileSync(projectPath, "utf8");
691
+ project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g, (_, current) => {
692
+ return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : current.endsWith(".PlatformHelper") ? platformBundleIdentifier : bundleIdentifier};`;
693
+ }).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
694
+ return `DEVELOPMENT_TEAM = ${teamId};`;
695
+ }).replace(/MARKETING_VERSION = [^;]+;/g, () => {
696
+ return `MARKETING_VERSION = ${version};`;
697
+ }).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, () => {
698
+ return `CURRENT_PROJECT_VERSION = ${buildNumber};`;
699
+ }).replace(/PRODUCT_NAME = bleam;/g, () => {
700
+ return `PRODUCT_NAME = ${productName};`;
701
+ });
702
+ writeFileSync(projectPath, project);
703
+ let infoPlist = readFileSync(infoPlistPath, "utf8");
704
+ infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
705
+ infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
706
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamRuntimeVersion", config.runtimeVersion);
707
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamTeamIdentifier", config.teamId ?? "");
708
+ const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL;
709
+ if (updateServiceUrl) infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
710
+ infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
711
+ if (primaryAppGroup) infoPlist = replaceOrInsertPlistString(infoPlist, "AppGroupIdentifier", primaryAppGroup);
712
+ writeFileSync(infoPlistPath, infoPlist);
713
+ if (primaryAppGroup) {
714
+ let generationInfoPlist = readFileSync(generationInfoPlistPath, "utf8");
715
+ generationInfoPlist = replaceOrInsertPlistString(generationInfoPlist, "AppGroupIdentifier", primaryAppGroup);
716
+ writeFileSync(generationInfoPlistPath, generationInfoPlist);
717
+ writeFileSync(platformInfoPlistPath, replaceOrInsertPlistString(readFileSync(platformInfoPlistPath, "utf8"), "BleamPlatformVersion", config.runtimeVersion));
718
+ for (const entitlementsPath of [appEntitlementsPath, generationEntitlementsPath]) writeFileSync(entitlementsPath, replaceOrInsertPlistStringArray(readFileSync(entitlementsPath, "utf8"), "com.apple.security.application-groups", config.appGroups));
719
+ }
720
+ }
721
+ function createBuildNativeWorkspace(options, config) {
722
+ const nativeRoot = path.join(options.projectRoot, ".bleam", "build", "native");
723
+ const templateRoot = nativeRuntimeTemplateRoot();
724
+ if (options.clean) rmSync(nativeRoot, {
725
+ recursive: true,
726
+ force: true
727
+ });
728
+ mkdirSync(path.dirname(nativeRoot), { recursive: true });
729
+ cpSync(templateRoot, nativeRoot, {
730
+ recursive: true,
731
+ verbatimSymlinks: true,
732
+ filter(source) {
733
+ return !path.relative(templateRoot, source).split(path.sep).some((part) => {
734
+ return part === "build" || part === "dist" || part === "node_modules" || part === ".expo" || part === "Pods" || part === ".xcode.env.local" || part === ".DS_Store" || part.endsWith(".xcworkspace");
735
+ });
736
+ }
737
+ });
738
+ const nodeModules = path.join(nativeRoot, "node_modules");
739
+ rmSync(nodeModules, {
740
+ recursive: true,
741
+ force: true
742
+ });
743
+ symlinkSync(resolvedDependencyRoot(), nodeModules);
744
+ writeGeneratedAppJson(nativeRoot, config);
745
+ writeFileSync(path.join(nativeRoot, "index.ts"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(path.join(options.projectRoot, "app"))}\n\nregisterApp(App)\n`);
746
+ writeFileSync(path.join(nativeRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
747
+ writeFileSync(path.join(nativeRoot, "ios", ".xcode.env"), `export NODE_BINARY=$(command -v node)\nexport BLEAM_PROJECT_ROOT=${singleQuotedString(options.projectRoot)}\nexport BLEAM_DEV_PLATFORM=macos\n`);
748
+ patchNativeProject(nativeRoot, config);
749
+ return nativeRoot;
750
+ }
751
+ function preflightBuild(options, config) {
752
+ if (process.platform !== "darwin") throw new Error(`bleam build requires macOS and Xcode. Current platform: ${process.platform}.\n${doctorHint(options.projectRoot)}`);
753
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
754
+ if (!config.teamId) throw missingBuildFieldError("teamId");
755
+ const templateRoot = nativeRuntimeTemplateRoot();
756
+ if (!existsSync(templateRoot)) throw new Error(`Missing native runtime template: ${templateRoot}\n${doctorHint(options.projectRoot)}`);
757
+ if (!commandSucceeds("pod", ["--version"])) throw new Error(`CocoaPods is required for bleam build, but ${bold("pod")} is unavailable.\nInstall CocoaPods, then run bleam build again.\n${doctorHint(options.projectRoot)}`);
758
+ if (!commandSucceeds("xcodebuild", ["-version"])) throw new Error(`Xcode command line tools are required for bleam build, but ${bold("xcodebuild")} is unavailable.\nInstall Xcode or run ${bold("xcode-select --install")}, then run bleam build again.\n${doctorHint(options.projectRoot)}`);
759
+ }
760
+ function runInheritedCommand(command, args, cwd) {
761
+ const text = commandText(command, args);
762
+ const result = spawnSync(command, args, {
763
+ cwd,
764
+ stdio: "inherit",
765
+ env: process.env
766
+ });
767
+ if (result.error) throw new Error(`${text} could not start in ${cwd}: ${result.error.message}\n${doctorHint()}`);
768
+ if (result.status !== 0) throw new Error(`${text} failed in ${cwd} with exit code ${result.status ?? "unknown"}.\nNative command output above may contain the underlying Xcode/CocoaPods error.\n${doctorHint()}`);
769
+ }
770
+ function installPods(nativeRoot) {
771
+ const iosRoot = path.join(nativeRoot, "ios");
772
+ const expectedStamp = podInstallInputStamp(iosRoot);
773
+ if (hasCurrentPodInstall(iosRoot, expectedStamp)) {
774
+ console.log("Using cached native dependencies");
775
+ return;
776
+ }
777
+ console.log("Installing native dependencies...");
778
+ runInheritedCommand("pod", ["install"], iosRoot);
779
+ writeFileSync(podInstallStampPath(iosRoot), `${expectedStamp}\n`);
780
+ }
781
+ function podInstallInputStamp(iosRoot) {
782
+ const inputHash = createHash("sha256");
783
+ for (const relative of [
784
+ "Podfile",
785
+ "Podfile.lock",
786
+ "../package.json"
787
+ ]) {
788
+ const input = path.resolve(iosRoot, relative);
789
+ if (existsSync(input)) inputHash.update(readFileSync(input));
790
+ }
791
+ return inputHash.digest("hex");
792
+ }
793
+ function podInstallStampPath(iosRoot) {
794
+ return path.join(iosRoot, ".bleam-pods-input");
795
+ }
796
+ function hasCurrentPodInstall(iosRoot, expectedStamp) {
797
+ const stampPath = podInstallStampPath(iosRoot);
798
+ return existsSync(path.join(iosRoot, "Bleam.xcworkspace")) && existsSync(path.join(iosRoot, "Pods", "Manifest.lock")) && existsSync(stampPath) && readFileSync(stampPath, "utf8").trim() === expectedStamp;
799
+ }
800
+ function nativeArchiveArgs(options) {
801
+ return [
802
+ "-allowProvisioningUpdates",
803
+ "-workspace",
804
+ path.join(options.nativeRoot, "ios", "Bleam.xcworkspace"),
805
+ "-scheme",
806
+ "Bleam",
807
+ "-configuration",
808
+ options.configuration,
809
+ "-destination",
810
+ "platform=macOS,arch=arm64,variant=Mac Catalyst",
811
+ "-archivePath",
812
+ options.archivePath,
813
+ "ARCHS=arm64",
814
+ "archive"
815
+ ];
816
+ }
817
+ function archiveNativeApp(options) {
818
+ const platformOutput = path.join(options.outputRoot, "macos");
819
+ const archivePath = path.join(platformOutput, `${safeArtifactName(options.config.name)}.xcarchive`);
820
+ mkdirSync(platformOutput, { recursive: true });
821
+ rmSync(archivePath, {
822
+ recursive: true,
823
+ force: true
824
+ });
825
+ console.log(`Archiving ${options.config.name} for macOS...`);
826
+ runInheritedCommand("xcodebuild", nativeArchiveArgs({
827
+ nativeRoot: options.nativeRoot,
828
+ archivePath,
829
+ configuration: options.configuration
830
+ }), options.nativeRoot);
831
+ return archivePath;
832
+ }
833
+ function exportNativeArchive(options) {
834
+ const exportPath = path.join(options.outputRoot, "macos", "export");
835
+ rmSync(exportPath, {
836
+ recursive: true,
837
+ force: true
838
+ });
839
+ mkdirSync(exportPath, { recursive: true });
840
+ console.log("Exporting macOS archive...");
841
+ runInheritedCommand("xcodebuild", [
842
+ "-allowProvisioningUpdates",
843
+ "-exportArchive",
844
+ "-archivePath",
845
+ options.archivePath,
846
+ "-exportPath",
847
+ exportPath,
848
+ "-exportOptionsPlist",
849
+ options.exportOptionsPlist
850
+ ], path.dirname(options.archivePath));
851
+ return exportPath;
852
+ }
853
+ async function buildNativeArchives(options) {
854
+ const config = readProjectConfig(options);
855
+ preflightBuild(options, config);
856
+ const nativeRoot = createBuildNativeWorkspace(options, config);
857
+ installPods(nativeRoot);
858
+ const archivePath = archiveNativeApp({
859
+ nativeRoot,
860
+ outputRoot: options.output,
861
+ configuration: options.configuration,
862
+ config
863
+ });
864
+ console.log(`Created ${archivePath}`);
865
+ if (options.exportOptionsPlist) {
866
+ const exportPath = exportNativeArchive({
867
+ archivePath,
868
+ outputRoot: options.output,
869
+ exportOptionsPlist: options.exportOptionsPlist
870
+ });
871
+ console.log(`Exported ${exportPath}`);
872
+ }
873
+ return archivePath;
874
+ }
875
+ async function installNativeApp(options) {
876
+ const destination = installArchivedApp(await buildNativeArchives(options));
877
+ console.log(`Installed ${destination}`);
878
+ }
879
+ function installArchivedApp(archivePath, applications = path.join(os.homedir(), "Applications")) {
880
+ const products = path.join(archivePath, "Products", "Applications");
881
+ const appName = readdirSync(products).find((name) => name.endsWith(".app"));
882
+ if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
883
+ const source = path.join(products, appName);
884
+ const destination = applications === path.join(os.homedir(), "Applications") ? installedAppPath(appName) : path.join(applications, appName);
885
+ mkdirSync(applications, { recursive: true });
886
+ rmSync(destination, {
887
+ recursive: true,
888
+ force: true
889
+ });
890
+ execFileSync("ditto", [source, destination], { stdio: "inherit" });
891
+ return destination;
892
+ }
893
+ function installedAppPath(appName) {
894
+ if (path.basename(appName) !== appName || !appName.endsWith(".app")) throw new Error(`Invalid macOS app name: ${appName}`);
895
+ return path.join(os.homedir(), "Applications", appName);
896
+ }
897
+ async function publishOta(options) {
898
+ const config = readProjectConfig(options);
899
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
900
+ if (!config.teamId) throw missingBuildFieldError("teamId");
901
+ const workspaceRoot = createBuildNativeWorkspace({
902
+ projectRoot: options.projectRoot,
903
+ output: path.join(options.projectRoot, "dist"),
904
+ configuration: "Release",
905
+ clean: false
906
+ }, config);
907
+ const outputRoot = path.join(options.projectRoot, ".bleam", "publish", "ota");
908
+ const bundleRoot = path.join(outputRoot, "bundle");
909
+ const bundlePath = path.join(bundleRoot, "bundle.js");
910
+ const archivePath = path.join(outputRoot, "ota.zip");
911
+ rmSync(outputRoot, {
912
+ recursive: true,
913
+ force: true
914
+ });
915
+ mkdirSync(bundleRoot, { recursive: true });
916
+ console.log(`Bundling OTA update for runtime ${runtimeVersion}...`);
917
+ runInheritedCommand(process.execPath, [
918
+ require.resolve("@expo/cli"),
919
+ "export:embed",
920
+ "--entry-file",
921
+ path.join(workspaceRoot, "index.ts"),
922
+ "--platform",
923
+ "ios",
924
+ "--dev",
925
+ "false",
926
+ "--bundle-output",
927
+ bundlePath,
928
+ "--assets-dest",
929
+ path.join(bundleRoot, "assets"),
930
+ "--minify",
931
+ "true"
932
+ ], workspaceRoot);
933
+ runInheritedCommand("ditto", [
934
+ "-c",
935
+ "-k",
936
+ "--keepParent",
937
+ bundleRoot,
938
+ archivePath
939
+ ], outputRoot);
940
+ const sizeBytes = readFileSync(archivePath).byteLength;
941
+ const digest = sha256(archivePath);
942
+ const releaseId = `ota_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
943
+ const release = {
944
+ schemaVersion: 1,
945
+ kind: "ota",
946
+ bundleIdentifier: config.bundleIdentifier,
947
+ teamIdentifier: config.teamId,
948
+ channel: "stable",
949
+ releaseId,
950
+ bundleVersion: `${config.version ?? "1.0.0"}+${config.buildNumber}`,
951
+ runtimeVersion,
952
+ artifact: {
953
+ format: "bleam-ota-zip-v1",
954
+ downloadId: releaseId,
955
+ sizeBytes,
956
+ sha256: digest
957
+ },
958
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
959
+ };
960
+ const response = await fetch(`${options.serviceUrl}/v1/local/releases/ota`, {
961
+ method: "POST",
962
+ headers: { "content-type": "application/json" },
963
+ body: JSON.stringify({
964
+ release,
965
+ artifactBase64: readFileSync(archivePath).toString("base64")
966
+ })
967
+ });
968
+ if (!response.ok) throw new Error(`Local OTA publication failed: ${response.status} ${await response.text()}`);
969
+ console.log(`Published OTA update ${releaseId}`);
970
+ console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/ota/${runtimeVersion}`);
971
+ }
972
+ async function publishPlatform(options) {
973
+ const config = readProjectConfig(options);
974
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
975
+ if (!config.teamId) throw missingBuildFieldError("teamId");
976
+ const archivePath = await buildNativeArchives({
977
+ projectRoot: options.projectRoot,
978
+ output: path.join(options.projectRoot, "dist"),
979
+ configuration: "Release",
980
+ clean: false
981
+ });
982
+ const products = path.join(archivePath, "Products", "Applications");
983
+ const appName = readdirSync(products).find((name) => name.endsWith(".app"));
984
+ if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
985
+ const outputRoot = path.join(options.projectRoot, ".bleam", "publish", "platform");
986
+ const stagingRoot = path.join(outputRoot, "app");
987
+ const artifactPath = path.join(outputRoot, "platform.zip");
988
+ rmSync(outputRoot, {
989
+ recursive: true,
990
+ force: true
991
+ });
992
+ mkdirSync(stagingRoot, { recursive: true });
993
+ execFileSync("ditto", [path.join(products, appName), path.join(stagingRoot, appName)]);
994
+ runInheritedCommand("zip", [
995
+ "-q",
996
+ "-r",
997
+ "-y",
998
+ artifactPath,
999
+ "app"
1000
+ ], outputRoot);
1001
+ const sizeBytes = readFileSync(artifactPath).byteLength;
1002
+ const digest = sha256(artifactPath);
1003
+ const releaseId = `platform_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
1004
+ const release = platformRelease(config, releaseId, sizeBytes, digest);
1005
+ const response = await fetch(`${options.serviceUrl}/v1/local/releases/platform`, {
1006
+ method: "POST",
1007
+ headers: { "content-type": "application/json" },
1008
+ body: JSON.stringify({
1009
+ release,
1010
+ artifactBase64: readFileSync(artifactPath).toString("base64")
1011
+ })
1012
+ });
1013
+ if (!response.ok) throw new Error(`Local platform publication failed: ${response.status} ${await response.text()}`);
1014
+ console.log(`Published platform update ${releaseId}`);
1015
+ console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/platform/macos-arm64`);
1016
+ }
1017
+ function platformRelease(config, releaseId, sizeBytes, digest) {
1018
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
1019
+ if (!config.teamId) throw missingBuildFieldError("teamId");
1020
+ return {
1021
+ schemaVersion: 1,
1022
+ kind: "platform",
1023
+ bundleIdentifier: config.bundleIdentifier,
1024
+ teamIdentifier: config.teamId,
1025
+ channel: "stable",
1026
+ releaseId,
1027
+ appVersion: config.version ?? "1.0.0",
1028
+ buildNumber: config.buildNumber,
1029
+ bleamPlatformVersion: runtimeVersion,
1030
+ target: {
1031
+ os: "macos",
1032
+ architecture: "arm64",
1033
+ installLocation: "user-applications",
1034
+ minimumMacOSVersion: "14.0"
1035
+ },
1036
+ artifact: {
1037
+ format: "bleam-macos-app-zip-v1",
1038
+ downloadId: releaseId,
1039
+ sizeBytes,
1040
+ sha256: digest
1041
+ },
1042
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
1043
+ };
1044
+ }
287
1045
  function runtimeCacheRoot() {
288
- return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, runtimePlatform);
1046
+ return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
289
1047
  }
290
1048
  function localDevelopmentRuntimeAppPath() {
291
- return path.resolve(packageRoot, "..", "..", "app", "bleam", "build", "Build", "Products", "Debug-maccatalyst", "Bleam.app");
1049
+ const productsRoot = path.resolve(packageRoot, "..", "..", "app", "bleam", "build", "Build", "Products", "Debug-maccatalyst");
1050
+ for (const appName of ["Bleam.app", "bleam.app"]) {
1051
+ const appPath = path.join(productsRoot, appName);
1052
+ if (existsSync(appPath)) return appPath;
1053
+ }
1054
+ return path.join(productsRoot, "Bleam.app");
292
1055
  }
293
1056
  function runtimeReleaseBaseUrl() {
294
1057
  return process.env.BLEAM_RUNTIME_RELEASE_BASE_URL ?? "https://github.com/bajedev/bleam-app/releases/download";
295
1058
  }
296
1059
  function runtimeArchiveName() {
297
- return `bleam-runtime-v${runtimeVersion}-${runtimePlatform}.zip`;
1060
+ return `bleam-runtime-v${runtimeVersion}-${artifactPlatform()}.zip`;
298
1061
  }
299
1062
  function runtimeArchiveUrl() {
300
1063
  const tag = encodeURIComponent(`bleam@${runtimeVersion}`);
301
1064
  return `${runtimeReleaseBaseUrl()}/${tag}/${runtimeArchiveName()}`;
302
1065
  }
1066
+ function runtimeResolutionDetails() {
1067
+ return [
1068
+ `Runtime version: ${runtimeVersion}`,
1069
+ `Runtime platform: ${artifactPlatform()}`,
1070
+ `Runtime cache: ${runtimeCacheRoot()}`,
1071
+ `Release URL: ${runtimeArchiveUrl()}`,
1072
+ `Override with BLEAM_RUNTIME_APP=/path/to/Bleam.app for local testing.`,
1073
+ doctorHint()
1074
+ ].join("\n");
1075
+ }
303
1076
  function sha256(filePath) {
304
1077
  return createHash("sha256").update(readFileSync(filePath)).digest("hex");
305
1078
  }
@@ -314,19 +1087,26 @@ async function downloadExpectedSha256(url) {
314
1087
  return (await response.text()).trim().split(/\s+/)[0];
315
1088
  }
316
1089
  async function downloadRuntimeArtifact() {
317
- if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available on macOS.");
1090
+ if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available from macOS hosts.");
1091
+ const runtimePlatform = artifactPlatform();
318
1092
  const cacheRoot = runtimeCacheRoot();
319
1093
  const cachedAppPath = path.join(cacheRoot, "Bleam.app");
320
- if (existsSync(cachedAppPath)) return {
321
- appPath: cachedAppPath,
322
- version: runtimeVersion,
323
- platform: runtimePlatform
324
- };
1094
+ if (existsSync(cachedAppPath)) {
1095
+ console.log(`Using cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
1096
+ return {
1097
+ appPath: cachedAppPath,
1098
+ version: runtimeVersion,
1099
+ platform: runtimePlatform
1100
+ };
1101
+ }
325
1102
  mkdirSync(cacheRoot, { recursive: true });
326
1103
  const archiveUrl = runtimeArchiveUrl();
327
1104
  const archivePath = path.join(cacheRoot, runtimeArchiveName());
328
1105
  const extractRoot = path.join(cacheRoot, "extract");
1106
+ console.log(`Downloading Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
1107
+ console.log(`Runtime archive: ${archiveUrl}`);
329
1108
  await downloadFile(archiveUrl, archivePath);
1109
+ console.log("Verifying Bleam runtime archive");
330
1110
  const expectedSha256 = await downloadExpectedSha256(archiveUrl);
331
1111
  if (expectedSha256 && sha256(archivePath) !== expectedSha256) {
332
1112
  rmSync(archivePath, { force: true });
@@ -337,6 +1117,7 @@ async function downloadRuntimeArtifact() {
337
1117
  force: true
338
1118
  });
339
1119
  mkdirSync(extractRoot, { recursive: true });
1120
+ console.log("Extracting Bleam runtime archive");
340
1121
  execFileSync("ditto", [
341
1122
  "-x",
342
1123
  "-k",
@@ -349,11 +1130,15 @@ async function downloadRuntimeArtifact() {
349
1130
  recursive: true,
350
1131
  force: true
351
1132
  });
352
- cpSync(extractedAppPath, cachedAppPath, { recursive: true });
1133
+ cpSync(extractedAppPath, cachedAppPath, {
1134
+ recursive: true,
1135
+ verbatimSymlinks: true
1136
+ });
353
1137
  rmSync(extractRoot, {
354
1138
  recursive: true,
355
1139
  force: true
356
1140
  });
1141
+ console.log(`Cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
357
1142
  return {
358
1143
  appPath: cachedAppPath,
359
1144
  version: runtimeVersion,
@@ -361,10 +1146,13 @@ async function downloadRuntimeArtifact() {
361
1146
  };
362
1147
  }
363
1148
  async function resolveRuntimeArtifact() {
1149
+ const runtimePlatform = artifactPlatform();
1150
+ console.log(`Resolving Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
364
1151
  const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
365
1152
  if (overrideAppPath) {
366
1153
  const appPath = path.resolve(overrideAppPath);
367
- if (!existsSync(appPath)) throw new Error(`BLEAM_RUNTIME_APP does not exist: ${appPath}`);
1154
+ if (!existsSync(appPath)) throw new Error(`BLEAM_RUNTIME_APP points to a missing app bundle: ${appPath}\nUnset BLEAM_RUNTIME_APP or point it to a valid Bleam.app.\n${doctorHint()}`);
1155
+ console.log(`Using BLEAM_RUNTIME_APP runtime at ${prettyPath(appPath)}`);
368
1156
  return {
369
1157
  appPath,
370
1158
  version: runtimeVersion,
@@ -372,23 +1160,47 @@ async function resolveRuntimeArtifact() {
372
1160
  };
373
1161
  }
374
1162
  const localAppPath = localDevelopmentRuntimeAppPath();
375
- if (existsSync(localAppPath)) return {
376
- appPath: localAppPath,
377
- version: runtimeVersion,
378
- platform: runtimePlatform
379
- };
380
- return downloadRuntimeArtifact();
1163
+ if (existsSync(localAppPath)) {
1164
+ console.log(`Using local development runtime at ${prettyPath(localAppPath)}`);
1165
+ return {
1166
+ appPath: localAppPath,
1167
+ version: runtimeVersion,
1168
+ platform: runtimePlatform
1169
+ };
1170
+ }
1171
+ try {
1172
+ return await downloadRuntimeArtifact();
1173
+ } catch (error) {
1174
+ throw new Error(`Failed to resolve the Bleam runtime.\n${runtimeResolutionDetails()}\n\nCause: ${error instanceof Error ? error.message : String(error)}`);
1175
+ }
381
1176
  }
382
1177
  function copyRuntimeClient(projectRoot, runtime) {
383
1178
  const clientAppPath = path.join(projectRoot, ".bleam", "client", "Bleam.app");
1179
+ console.log(`Preparing Bleam app at ${prettyPath(clientAppPath)}`);
384
1180
  rmSync(clientAppPath, {
385
1181
  recursive: true,
386
1182
  force: true
387
1183
  });
388
1184
  mkdirSync(path.dirname(clientAppPath), { recursive: true });
389
- cpSync(runtime.appPath, clientAppPath, { recursive: true });
1185
+ cpSync(runtime.appPath, clientAppPath, {
1186
+ recursive: true,
1187
+ verbatimSymlinks: true
1188
+ });
390
1189
  return clientAppPath;
391
1190
  }
1191
+ function cleanDevArtifacts(projectRoot) {
1192
+ const bleamRoot = path.join(projectRoot, ".bleam");
1193
+ const targets = [
1194
+ "client",
1195
+ "dev",
1196
+ "project.json"
1197
+ ].map((name) => path.join(bleamRoot, name));
1198
+ console.log("Cleaning Bleam dev artifacts");
1199
+ for (const target of targets) rmSync(target, {
1200
+ recursive: true,
1201
+ force: true
1202
+ });
1203
+ }
392
1204
  function isBleamManifest(value) {
393
1205
  const manifest = value && typeof value === "object" ? value : {};
394
1206
  return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
@@ -396,24 +1208,113 @@ function isBleamManifest(value) {
396
1208
  async function readDevManifest(origin, timeoutMs = 3e4) {
397
1209
  const manifestUrl = `${origin}/bleam/manifest.json`;
398
1210
  const startedAt = Date.now();
399
- console.log("Waiting for Bleam dev server...");
1211
+ const spinner = startSpinner("Waiting for Bleam dev server...");
400
1212
  while (Date.now() - startedAt <= timeoutMs) {
401
1213
  try {
402
1214
  const response = await fetch(manifestUrl, { cache: "no-store" });
403
1215
  const value = await response.json();
404
- if (response.ok && isBleamManifest(value)) return value;
1216
+ if (response.ok && isBleamManifest(value)) {
1217
+ spinner?.success("Bleam dev server is ready");
1218
+ return value;
1219
+ }
405
1220
  } catch {}
406
1221
  await new Promise((resolve) => setTimeout(resolve, 500));
407
1222
  }
1223
+ spinner?.error("Bleam dev server did not become available");
408
1224
  throw new Error(`Bleam manifest did not become available: ${manifestUrl}`);
409
1225
  }
1226
+ function pushDoctorCheck(checks, status, label, detail) {
1227
+ checks.push({
1228
+ status,
1229
+ label,
1230
+ detail
1231
+ });
1232
+ }
1233
+ function printDoctorSection(title, checks) {
1234
+ if (checks.length === 0) return;
1235
+ console.log(`\n${heading(title)}`);
1236
+ for (const check of checks) {
1237
+ const detail = check.detail ? muted(`: ${check.detail}`) : "";
1238
+ console.log(` ${statusLabel(check.status)} ${check.label}${detail}`);
1239
+ }
1240
+ }
1241
+ function runDoctor(options) {
1242
+ const projectChecks = [];
1243
+ const runtimeChecks = [];
1244
+ const environmentChecks = [];
1245
+ const appConfigPath = path.join(options.projectRoot, "app.config.ts");
1246
+ const appEntryPath = path.join(options.projectRoot, "app", "index.tsx");
1247
+ if (existsSync(appConfigPath)) pushDoctorCheck(projectChecks, "pass", "app.config.ts found");
1248
+ else pushDoctorCheck(projectChecks, "fail", "app.config.ts missing");
1249
+ if (existsSync(appEntryPath)) pushDoctorCheck(projectChecks, "pass", "app/index.tsx found");
1250
+ else pushDoctorCheck(projectChecks, "fail", "app/index.tsx missing");
1251
+ try {
1252
+ const config = readProjectConfig(options);
1253
+ pushDoctorCheck(projectChecks, "pass", "config loaded");
1254
+ pushDoctorCheck(projectChecks, "pass", "name", config.name);
1255
+ if (config.bundleIdentifier) pushDoctorCheck(projectChecks, "pass", "bundleIdentifier", config.bundleIdentifier);
1256
+ else pushDoctorCheck(projectChecks, "warn", "bundleIdentifier not configured", "required for bleam build");
1257
+ if (config.teamId) pushDoctorCheck(projectChecks, "pass", "teamId", config.teamId);
1258
+ else pushDoctorCheck(projectChecks, "warn", "teamId not configured", "required for bleam build");
1259
+ } catch (error) {
1260
+ pushDoctorCheck(projectChecks, "fail", "config failed to load", error instanceof Error ? error.message : String(error));
1261
+ }
1262
+ const cacheRoot = runtimeCacheRoot();
1263
+ const cachedAppPath = path.join(cacheRoot, "Bleam.app");
1264
+ const localAppPath = localDevelopmentRuntimeAppPath();
1265
+ pushDoctorCheck(runtimeChecks, "pass", "version", runtimeVersion);
1266
+ pushDoctorCheck(runtimeChecks, "pass", "platform", artifactPlatform());
1267
+ pushDoctorCheck(runtimeChecks, "pass", "cache", prettyPath(cacheRoot));
1268
+ if (existsSync(cachedAppPath)) pushDoctorCheck(runtimeChecks, "pass", "cached runtime found");
1269
+ else pushDoctorCheck(runtimeChecks, "warn", "runtime not cached yet", "bleam dev can download it on demand");
1270
+ if (existsSync(localAppPath)) pushDoctorCheck(runtimeChecks, "pass", "local development runtime found", prettyPath(localAppPath));
1271
+ else pushDoctorCheck(runtimeChecks, "warn", "local development runtime not found", prettyPath(localAppPath));
1272
+ const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
1273
+ if (overrideAppPath) {
1274
+ const appPath = path.resolve(overrideAppPath);
1275
+ pushDoctorCheck(runtimeChecks, existsSync(appPath) ? "pass" : "fail", "BLEAM_RUNTIME_APP", prettyPath(appPath));
1276
+ }
1277
+ pushDoctorCheck(runtimeChecks, "pass", "release artifact URL", runtimeArchiveUrl());
1278
+ pushDoctorCheck(environmentChecks, process.platform === "darwin" ? "pass" : "fail", "macOS host", process.platform);
1279
+ pushDoctorCheck(environmentChecks, "pass", "node", prettyPath(process.execPath));
1280
+ const hasXcodebuild = commandSucceeds("xcodebuild", ["-version"]);
1281
+ pushDoctorCheck(environmentChecks, hasXcodebuild ? "pass" : "warn", "xcodebuild", hasXcodebuild ? "available" : "unavailable");
1282
+ const hasPod = commandSucceeds("pod", ["--version"]);
1283
+ pushDoctorCheck(environmentChecks, hasPod ? "pass" : "warn", "pod", hasPod ? "available" : "unavailable");
1284
+ const allChecks = [
1285
+ ...projectChecks,
1286
+ ...runtimeChecks,
1287
+ ...environmentChecks
1288
+ ];
1289
+ const counts = {
1290
+ pass: allChecks.filter((check) => check.status === "pass").length,
1291
+ warn: allChecks.filter((check) => check.status === "warn").length,
1292
+ fail: allChecks.filter((check) => check.status === "fail").length
1293
+ };
1294
+ console.log(heading("Bleam Doctor"));
1295
+ printDoctorSection("Project", projectChecks);
1296
+ printDoctorSection("Runtime", runtimeChecks);
1297
+ printDoctorSection("Environment", environmentChecks);
1298
+ console.log(`\n${heading("Summary")}`);
1299
+ console.log(` ${green(`${counts.pass} passed`)}, ${yellow(`${counts.warn} warnings`)}, ${counts.fail > 0 ? red(`${counts.fail} failed`) : green("0 failed")}`);
1300
+ if (counts.fail > 0) process.exit(1);
1301
+ }
410
1302
  function formatDuration(ms) {
411
1303
  return `${(ms / 1e3).toFixed(1)}s`;
412
1304
  }
1305
+ async function bundleErrorPreview(response) {
1306
+ const contentType = response.headers.get("content-type") ?? "";
1307
+ if (!contentType.includes("json") && !contentType.includes("text")) return "";
1308
+ try {
1309
+ return (await response.text()).trim().slice(0, 1e3);
1310
+ } catch {
1311
+ return "";
1312
+ }
1313
+ }
413
1314
  async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
414
1315
  const startedAt = Date.now();
415
1316
  const requestTimeoutMs = 3e4;
416
- console.log("Bundling app...");
1317
+ const spinner = startSpinner("Bundling app...");
417
1318
  while (Date.now() - startedAt <= timeoutMs) {
418
1319
  const requestStartedAt = Date.now();
419
1320
  try {
@@ -421,19 +1322,32 @@ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
421
1322
  cache: "no-store",
422
1323
  signal: AbortSignal.timeout(requestTimeoutMs)
423
1324
  });
424
- await response.body?.cancel();
425
1325
  if (response.ok) {
426
- console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
1326
+ await response.body?.cancel();
1327
+ spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
1328
+ if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
427
1329
  return;
428
1330
  }
429
- console.log(`Bundle request returned ${response.status}, retrying...`);
1331
+ const preview = await bundleErrorPreview(response);
1332
+ const detail = preview ? `\n${preview}` : "";
1333
+ const message = `Bundle request returned ${response.status}, retrying...${detail}`;
1334
+ if (spinner) spinner.text = message;
1335
+ else console.log(message);
430
1336
  } catch (error) {
431
- if (error instanceof Error && error.name === "TimeoutError") console.log(`Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`);
432
- else console.log(`Bundle request failed: ${error instanceof Error ? error.message : String(error)}`);
1337
+ if (error instanceof Error && error.name === "TimeoutError") {
1338
+ const message = `Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`;
1339
+ if (spinner) spinner.text = message;
1340
+ else console.log(message);
1341
+ } else {
1342
+ const message = `Bundle request failed: ${error instanceof Error ? error.message : String(error)}`;
1343
+ if (spinner) spinner.text = `${message}, retrying...`;
1344
+ else console.log(message);
1345
+ }
433
1346
  }
434
1347
  await new Promise((resolve) => setTimeout(resolve, 500));
435
1348
  }
436
- throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}: ${bundleUrl}`);
1349
+ spinner?.error("Bundle did not complete");
1350
+ throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}.\nBundle URL: ${bundleUrl}\n${doctorHint()}`);
437
1351
  }
438
1352
  function openRuntimeClient(appPath, projectConfigPath, manifest) {
439
1353
  const child = spawn("open", [
@@ -454,18 +1368,234 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
454
1368
  if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
455
1369
  });
456
1370
  }
1371
+ function quitRuntimeClient() {
1372
+ spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to quit"], { stdio: "ignore" });
1373
+ }
1374
+ async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
1375
+ quitRuntimeClient();
1376
+ openRuntimeClient(appPath, projectConfigPath, manifest);
1377
+ }
1378
+ function focusRuntimeClient() {
1379
+ return spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to activate"], { stdio: "ignore" }).status === 0;
1380
+ }
1381
+ function sendJson(res, status, value) {
1382
+ res.writeHead(status, {
1383
+ "Cache-Control": "no-store",
1384
+ "Content-Type": "application/json; charset=utf-8"
1385
+ });
1386
+ res.end(JSON.stringify(value));
1387
+ }
1388
+ function readRequestBody(req, limit = 1e6) {
1389
+ return new Promise((resolve, reject) => {
1390
+ const chunks = [];
1391
+ let size = 0;
1392
+ req.on("data", (chunk) => {
1393
+ size += chunk.length;
1394
+ if (size > limit) {
1395
+ reject(/* @__PURE__ */ new Error("Request body too large"));
1396
+ req.destroy();
1397
+ return;
1398
+ }
1399
+ chunks.push(chunk);
1400
+ });
1401
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1402
+ req.on("error", reject);
1403
+ });
1404
+ }
1405
+ function isLogLevel(value) {
1406
+ return value === "debug" || value === "info" || value === "warn" || value === "error";
1407
+ }
1408
+ function isLogSource(value) {
1409
+ return value === "cli" || value === "metro" || value === "runtime" || value === "js" || value === "native" || value === "xpc";
1410
+ }
1411
+ function normalizeDevLogEvent(value) {
1412
+ const input = value && typeof value === "object" ? value : {};
1413
+ const message = typeof input.message === "string" ? input.message : input.message === void 0 ? "" : JSON.stringify(input.message);
1414
+ return {
1415
+ type: "log",
1416
+ id: typeof input.id === "string" && input.id ? input.id : randomUUID(),
1417
+ time: typeof input.time === "string" && input.time ? input.time : (/* @__PURE__ */ new Date()).toISOString(),
1418
+ source: isLogSource(input.source) ? input.source : "runtime",
1419
+ level: isLogLevel(input.level) ? input.level : "info",
1420
+ category: typeof input.category === "string" && input.category ? input.category : void 0,
1421
+ message,
1422
+ details: input.details,
1423
+ jobId: typeof input.jobId === "string" && input.jobId ? input.jobId : void 0
1424
+ };
1425
+ }
1426
+ function formatDevLogEvent(event) {
1427
+ const category = event.category ? `:${event.category}` : "";
1428
+ const job = event.jobId ? ` ${event.jobId.slice(0, 8)}` : "";
1429
+ return `[${event.source}${category}:${event.level}${job}] ${event.message}`;
1430
+ }
1431
+ async function requestRuntimeReload(origin) {
1432
+ const response = await fetch(`${origin}/bleam/reload`, {
1433
+ method: "POST",
1434
+ cache: "no-store"
1435
+ });
1436
+ const value = await response.json();
1437
+ if (!response.ok) throw new Error(`Reload request failed: ${response.status}`);
1438
+ const reload = value;
1439
+ return typeof reload.clients === "number" ? reload.clients : 0;
1440
+ }
1441
+ function biomeBinPath() {
1442
+ const biomePackageJsonPath = require.resolve("@biomejs/biome/package.json");
1443
+ const biomePackageRoot = path.dirname(biomePackageJsonPath);
1444
+ const biomePackage = require("@biomejs/biome/package.json");
1445
+ const bin = typeof biomePackage.bin === "string" ? biomePackage.bin : biomePackage.bin?.biome;
1446
+ if (!bin) throw new Error("Could not resolve @biomejs/biome binary");
1447
+ return path.join(biomePackageRoot, bin);
1448
+ }
1449
+ function typescriptBinPath() {
1450
+ const typescriptPackageJsonPath = require.resolve("typescript/package.json");
1451
+ const typescriptPackageRoot = path.dirname(typescriptPackageJsonPath);
1452
+ const bin = require("typescript/package.json").bin?.tsc;
1453
+ if (!bin) throw new Error("Could not resolve TypeScript compiler binary");
1454
+ return path.join(typescriptPackageRoot, bin);
1455
+ }
1456
+ function ensureProjectToolWorkspace(options) {
1457
+ const config = readProjectConfig({ projectRoot: options.projectRoot });
1458
+ return createDevWorkspace(options.projectRoot, config);
1459
+ }
1460
+ function runBiomeCommand(command, options) {
1461
+ assertBleamConfigFiles(options.projectRoot);
1462
+ ensureProjectToolWorkspace(options);
1463
+ const configPath = path.join(options.projectRoot, "biome.json");
1464
+ const existingConfig = existsSync(configPath) ? readFileSync(configPath, "utf8") : void 0;
1465
+ const args = [
1466
+ command,
1467
+ "--config-path",
1468
+ "biome.json"
1469
+ ];
1470
+ if (command === "check" && options.write) args.push("--write");
1471
+ if (command === "format" && !options.formatCheck) args.push("--write");
1472
+ args.push("app", "app.config.ts", "tsconfig.json");
1473
+ writeBiomeConfig(configPath);
1474
+ const result = spawnSync(biomeBinPath(), args, {
1475
+ cwd: options.projectRoot,
1476
+ stdio: "inherit"
1477
+ });
1478
+ if (existingConfig === void 0) rmSync(configPath, { force: true });
1479
+ else writeFileSync(configPath, existingConfig);
1480
+ if (result.error) throw result.error;
1481
+ process.exit(result.status ?? 1);
1482
+ }
1483
+ function runTypecheckCommand(options) {
1484
+ assertBleamConfigFiles(options.projectRoot);
1485
+ ensureProjectToolWorkspace(options);
1486
+ const result = spawnSync(typescriptBinPath(), [
1487
+ "--noEmit",
1488
+ "-p",
1489
+ path.join(options.projectRoot, "tsconfig.json")
1490
+ ], {
1491
+ cwd: options.projectRoot,
1492
+ stdio: "inherit"
1493
+ });
1494
+ if (result.error) throw result.error;
1495
+ process.exit(result.status ?? 1);
1496
+ }
1497
+ function setupInteractiveControls(options) {
1498
+ if (!process.stdin.isTTY) return () => {};
1499
+ const wasRaw = process.stdin.isRaw;
1500
+ let closed = false;
1501
+ const cleanup = () => {
1502
+ if (closed) return;
1503
+ closed = true;
1504
+ process.stdin.off("data", onData);
1505
+ process.stdin.setRawMode(wasRaw);
1506
+ };
1507
+ const onData = (data) => {
1508
+ for (const byte of data) {
1509
+ if (byte === 3) {
1510
+ cleanup();
1511
+ options.stop();
1512
+ return;
1513
+ }
1514
+ const key = String.fromCharCode(byte).toLowerCase();
1515
+ if (key === "o") {
1516
+ (async () => {
1517
+ try {
1518
+ if (focusRuntimeClient()) {
1519
+ console.log("Focused Bleam app");
1520
+ return;
1521
+ }
1522
+ console.log(`Opening Bleam app at ${prettyPath(options.clientAppPath)}`);
1523
+ await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
1524
+ } catch (error) {
1525
+ console.error(error instanceof Error ? error.message : String(error));
1526
+ }
1527
+ })();
1528
+ continue;
1529
+ }
1530
+ if (key === "r") {
1531
+ console.log("Requesting app reload");
1532
+ requestRuntimeReload(options.origin).then((clients) => {
1533
+ console.log(`Reloaded app (${clients} client${clients === 1 ? "" : "s"})`);
1534
+ }).catch((error) => {
1535
+ console.error(error instanceof Error ? error.message : String(error));
1536
+ });
1537
+ }
1538
+ }
1539
+ };
1540
+ process.stdin.setRawMode(true);
1541
+ process.stdin.resume();
1542
+ process.stdin.on("data", onData);
1543
+ console.log("Press o to open, r to reload, Ctrl+C to stop.");
1544
+ return cleanup;
1545
+ }
457
1546
  async function startInternalMetroServer(options) {
1547
+ process.env.BLEAM_DEV_PLATFORM ??= "macos";
1548
+ const projectRoot = process.env.BLEAM_PROJECT_ROOT ?? options.workspaceRoot;
1549
+ const logPath = path.join(projectRoot, ".bleam", "dev", "logs", "dev.log");
1550
+ const recentLogs = [];
1551
+ const logClients = /* @__PURE__ */ new Set();
1552
+ const maxRecentLogs = 1e3;
458
1553
  const metro = require("@expo/metro/metro");
459
1554
  const { loadConfig } = require("@expo/metro/metro-config");
460
1555
  const config = await loadConfig({
461
1556
  config: path.join(options.workspaceRoot, "metro.config.js"),
462
1557
  cwd: options.workspaceRoot,
463
- port: options.port
1558
+ port: options.port,
1559
+ resetCache: options.resetCache
464
1560
  });
465
1561
  const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
466
1562
  watch: true,
467
1563
  waitForBundler: true
468
1564
  });
1565
+ const messageClients = /* @__PURE__ */ new Set();
1566
+ const messageServer = new WebSocketServer({ noServer: true });
1567
+ const logServer = new WebSocketServer({ noServer: true });
1568
+ const recordLog = (event) => {
1569
+ recentLogs.push(event);
1570
+ if (recentLogs.length > maxRecentLogs) recentLogs.splice(0, recentLogs.length - maxRecentLogs);
1571
+ mkdirSync(path.dirname(logPath), { recursive: true });
1572
+ appendFileSync(logPath, `${JSON.stringify(event)}\n`, "utf8");
1573
+ const payload = JSON.stringify(event);
1574
+ for (const client of logClients) if (client.readyState === WebSocket.OPEN) client.send(payload);
1575
+ console.log(formatDevLogEvent(event));
1576
+ };
1577
+ const broadcastReload = () => {
1578
+ const message = JSON.stringify({
1579
+ version: 2,
1580
+ method: "reload",
1581
+ params: {}
1582
+ });
1583
+ let clients = 0;
1584
+ for (const client of messageClients) if (client.readyState === WebSocket.OPEN) {
1585
+ client.send(message);
1586
+ clients += 1;
1587
+ }
1588
+ return clients;
1589
+ };
1590
+ messageServer.on("connection", (client) => {
1591
+ messageClients.add(client);
1592
+ client.on("close", () => messageClients.delete(client));
1593
+ });
1594
+ logServer.on("connection", (client) => {
1595
+ logClients.add(client);
1596
+ for (const event of recentLogs) client.send(JSON.stringify(event));
1597
+ client.on("close", () => logClients.delete(client));
1598
+ });
469
1599
  const server = http.createServer((req, res) => {
470
1600
  if (req.url?.split("?")[0] === "/status") {
471
1601
  res.writeHead(200, {
@@ -476,10 +1606,78 @@ async function startInternalMetroServer(options) {
476
1606
  res.end("packager-status:running");
477
1607
  return;
478
1608
  }
479
- middleware(req, res);
1609
+ if (req.url?.split("?")[0] === "/bleam/reload") {
1610
+ if (req.method !== "POST") {
1611
+ res.writeHead(405, { Allow: "POST" });
1612
+ res.end();
1613
+ return;
1614
+ }
1615
+ sendJson(res, 200, {
1616
+ ok: true,
1617
+ clients: broadcastReload()
1618
+ });
1619
+ return;
1620
+ }
1621
+ if (req.url?.split("?")[0] === "/bleam/logs") {
1622
+ if (req.method === "GET") {
1623
+ sendJson(res, 200, { logs: recentLogs });
1624
+ return;
1625
+ }
1626
+ if (req.method !== "POST") {
1627
+ res.writeHead(405, { Allow: "GET, POST" });
1628
+ res.end();
1629
+ return;
1630
+ }
1631
+ readRequestBody(req).then((body) => {
1632
+ const parsed = body ? JSON.parse(body) : {};
1633
+ const events = Array.isArray(parsed) ? parsed : [parsed];
1634
+ for (const value of events) recordLog(normalizeDevLogEvent(value));
1635
+ sendJson(res, 200, { ok: true });
1636
+ }).catch((error) => {
1637
+ sendJson(res, 400, {
1638
+ ok: false,
1639
+ error: error instanceof Error ? error.message : String(error)
1640
+ });
1641
+ });
1642
+ return;
1643
+ }
1644
+ middleware(req, res, (error) => {
1645
+ if (res.headersSent) return;
1646
+ if (error) {
1647
+ console.error(error.message);
1648
+ res.writeHead(500, {
1649
+ "Cache-Control": "no-store",
1650
+ "Content-Type": "text/plain; charset=utf-8"
1651
+ });
1652
+ res.end(error.message);
1653
+ return;
1654
+ }
1655
+ res.writeHead(404, {
1656
+ "Cache-Control": "no-store",
1657
+ "Content-Type": "text/plain; charset=utf-8"
1658
+ });
1659
+ res.end("Not found");
1660
+ });
480
1661
  });
481
1662
  server.timeout = 0;
482
1663
  attachHmrServer(server);
1664
+ const metroUpgradeListeners = server.listeners("upgrade");
1665
+ server.removeAllListeners("upgrade");
1666
+ server.on("upgrade", (req, socket, head) => {
1667
+ if (req.url?.split("?")[0] === "/message") {
1668
+ messageServer.handleUpgrade(req, socket, head, (client) => {
1669
+ messageServer.emit("connection", client, req);
1670
+ });
1671
+ return;
1672
+ }
1673
+ if (req.url?.split("?")[0] === "/bleam/logs/stream") {
1674
+ logServer.handleUpgrade(req, socket, head, (client) => {
1675
+ logServer.emit("connection", client, req);
1676
+ });
1677
+ return;
1678
+ }
1679
+ for (const listener of metroUpgradeListeners) listener.call(server, req, socket, head);
1680
+ });
483
1681
  await new Promise((resolve, reject) => {
484
1682
  server.on("error", reject);
485
1683
  server.listen(options.port, "localhost", () => resolve());
@@ -489,6 +1687,11 @@ async function startInternalMetroServer(options) {
489
1687
  });
490
1688
  console.log(`Bleam dev server running at http://localhost:${options.port}`);
491
1689
  const close = () => {
1690
+ setTimeout(() => process.exit(0), 1e3).unref();
1691
+ messageServer.close();
1692
+ logServer.close();
1693
+ for (const client of messageClients) client.terminate();
1694
+ for (const client of logClients) client.terminate();
492
1695
  server.close(() => {
493
1696
  end();
494
1697
  process.exit(0);
@@ -499,8 +1702,10 @@ async function startInternalMetroServer(options) {
499
1702
  }
500
1703
  async function startDevServer(options) {
501
1704
  const config = readProjectConfig(options);
1705
+ if (options.clean) cleanDevArtifacts(options.projectRoot);
502
1706
  const runtime = await resolveRuntimeArtifact();
503
1707
  const clientAppPath = copyRuntimeClient(options.projectRoot, runtime);
1708
+ console.log("Preparing Bleam dev workspace");
504
1709
  const workspaceRoot = createDevWorkspace(options.projectRoot, config);
505
1710
  const origin = `http://localhost:${options.port}`;
506
1711
  const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
@@ -510,7 +1715,8 @@ async function startDevServer(options) {
510
1715
  "--workspace-root",
511
1716
  workspaceRoot,
512
1717
  "--port",
513
- String(options.port)
1718
+ String(options.port),
1719
+ ...options.clean ? ["--reset-cache"] : []
514
1720
  ], {
515
1721
  stdio: "inherit",
516
1722
  env: {
@@ -520,6 +1726,7 @@ async function startDevServer(options) {
520
1726
  BLEAM_DEV_NAME: config.name,
521
1727
  BLEAM_DEV_ORIGIN: origin,
522
1728
  BLEAM_DEV_PORT: String(options.port),
1729
+ BLEAM_DEV_PLATFORM: "macos",
523
1730
  BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
524
1731
  BLEAM_DEV_SDK_VERSION: config.sdkVersion,
525
1732
  NODE_PATH: [
@@ -532,20 +1739,57 @@ async function startDevServer(options) {
532
1739
  ].filter(Boolean).join(path.delimiter)
533
1740
  }
534
1741
  });
1742
+ let cleanupInteractiveControls = () => {};
1743
+ let didLaunchRuntimeClient = false;
1744
+ let didCleanupDevSession = false;
1745
+ let stopping = false;
1746
+ const cleanupDevSession = () => {
1747
+ if (didCleanupDevSession) return;
1748
+ didCleanupDevSession = true;
1749
+ cleanupInteractiveControls();
1750
+ if (didLaunchRuntimeClient) quitRuntimeClient();
1751
+ };
535
1752
  const stop = (signal) => {
1753
+ if (stopping) return;
1754
+ stopping = true;
1755
+ cleanupDevSession();
536
1756
  child.kill(signal);
1757
+ setTimeout(() => process.exit(0), 2e3).unref();
537
1758
  };
538
1759
  process.on("SIGINT", () => stop("SIGINT"));
539
1760
  process.on("SIGTERM", () => stop("SIGTERM"));
540
- child.on("exit", (code) => process.exit(code ?? 0));
1761
+ child.on("error", (error) => {
1762
+ cleanupDevSession();
1763
+ console.error(`Bleam dev server process failed: ${error instanceof Error ? error.message : String(error)}`);
1764
+ console.error(doctorHint(options.projectRoot));
1765
+ process.exit(1);
1766
+ });
1767
+ child.on("exit", (code) => {
1768
+ cleanupDevSession();
1769
+ process.exit(code ?? 0);
1770
+ });
541
1771
  try {
542
1772
  const manifest = await readDevManifest(origin);
543
- if (process.env.BLEAM_NO_OPEN !== "1") {
1773
+ cleanupInteractiveControls = setupInteractiveControls({
1774
+ origin,
1775
+ clientAppPath,
1776
+ projectConfigPath,
1777
+ manifest,
1778
+ stop: () => stop("SIGINT")
1779
+ });
1780
+ if (options.run && process.env.BLEAM_NO_OPEN !== "1") try {
544
1781
  await waitForDevBundle(manifest.bundleUrl);
545
- console.log("Opening Bleam app...");
546
- openRuntimeClient(clientAppPath, projectConfigPath, manifest);
1782
+ console.log(`Opening Bleam app at ${prettyPath(clientAppPath)}`);
1783
+ await launchRuntimeClient(clientAppPath, projectConfigPath, manifest);
1784
+ didLaunchRuntimeClient = true;
1785
+ } catch (error) {
1786
+ console.error(`Initial app launch failed: ${error instanceof Error ? error.message : String(error)}`);
1787
+ console.error(doctorHint(options.projectRoot));
1788
+ console.error("Check BLEAM_RUNTIME_APP if you are using a runtime override.");
1789
+ console.error("Bleam dev server is still running.");
547
1790
  }
548
1791
  } catch (error) {
1792
+ cleanupDevSession();
549
1793
  child.kill("SIGTERM");
550
1794
  throw error;
551
1795
  }
@@ -558,10 +1802,12 @@ async function main(argv = process.argv.slice(2)) {
558
1802
  }
559
1803
  if (command === "new") {
560
1804
  const options = parseNewOptions(args);
561
- createNewProject(options);
562
- if (options.run) await startDevServer({
1805
+ createProject(options);
1806
+ await startDevServer({
563
1807
  projectRoot: options.projectRoot,
564
- port: 8081
1808
+ port: 8081,
1809
+ run: true,
1810
+ clean: false
565
1811
  });
566
1812
  return;
567
1813
  }
@@ -569,17 +1815,70 @@ async function main(argv = process.argv.slice(2)) {
569
1815
  await startDevServer(parseDevOptions(args));
570
1816
  return;
571
1817
  }
1818
+ if (command === "install") {
1819
+ await installNativeApp(parseBuildOptions(args));
1820
+ return;
1821
+ }
1822
+ if (command === "build") {
1823
+ await buildNativeArchives(parseBuildOptions(args));
1824
+ return;
1825
+ }
1826
+ if (command === "publish") {
1827
+ const [kind, ...publishArgs] = args;
1828
+ if (kind === "ota") {
1829
+ await publishOta(parsePublishOtaOptions(publishArgs));
1830
+ return;
1831
+ }
1832
+ if (kind === "platform") {
1833
+ await publishPlatform(parsePublishOtaOptions(publishArgs));
1834
+ return;
1835
+ }
1836
+ throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>]");
1837
+ }
1838
+ if (command === "doctor") {
1839
+ runDoctor(parseDoctorOptions(args));
1840
+ return;
1841
+ }
1842
+ if (command === "typecheck") {
1843
+ runTypecheckCommand(parseProjectToolOptions(args));
1844
+ return;
1845
+ }
1846
+ if (command === "check") {
1847
+ runBiomeCommand("check", parseProjectToolOptions(args));
1848
+ return;
1849
+ }
1850
+ if (command === "format") {
1851
+ runBiomeCommand("format", parseProjectToolOptions(args));
1852
+ return;
1853
+ }
1854
+ if (command === "lint") {
1855
+ runBiomeCommand("lint", parseProjectToolOptions(args));
1856
+ return;
1857
+ }
572
1858
  if (command === "internal:metro") {
573
1859
  await startInternalMetroServer(parseInternalMetroOptions(args));
574
1860
  return;
575
1861
  }
576
1862
  throw new Error(`Unknown command: ${command}`);
577
1863
  }
578
- main().catch((error) => {
1864
+ if (process.env.VITEST !== "true") main().catch((error) => {
579
1865
  console.error(error instanceof Error ? error.message : String(error));
580
1866
  process.exit(1);
581
1867
  });
1868
+ const __test = {
1869
+ createProject,
1870
+ createBuildNativeWorkspace,
1871
+ hasCurrentPodInstall,
1872
+ installedAppPath,
1873
+ installArchivedApp,
1874
+ nativeArchiveArgs,
1875
+ parseBuildOptions,
1876
+ parseNewOptions,
1877
+ parsePublishOtaOptions,
1878
+ platformRelease,
1879
+ podInstallInputStamp,
1880
+ replaceOrInsertPlistString
1881
+ };
582
1882
 
583
1883
  //#endregion
584
- export { };
585
- //# sourceMappingURL=cli.js.map
1884
+ export { __test };