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