bleam 0.0.5 → 0.0.6

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 (113) hide show
  1. package/dist/ai.cjs +187 -0
  2. package/dist/ai.d.cts +52 -0
  3. package/dist/ai.d.ts +52 -0
  4. package/dist/ai.js +182 -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.d.cts +1 -2
  10. package/dist/app.d.ts +1 -2
  11. package/dist/app.js +1 -2
  12. package/dist/bundler.d.cts +1 -2
  13. package/dist/bundler.d.ts +1 -2
  14. package/dist/chrome.cjs +265 -0
  15. package/dist/chrome.d.cts +19 -0
  16. package/dist/chrome.d.ts +19 -0
  17. package/dist/chrome.js +262 -0
  18. package/dist/cli.cjs +768 -31
  19. package/dist/cli.js +767 -34
  20. package/dist/config-Bb6SULnR.cjs +46 -0
  21. package/dist/config-CfWa3q-k.js +28 -0
  22. package/dist/config-ClDGHNb3.d.ts +39 -0
  23. package/dist/config-D3gPaVMb.d.cts +39 -0
  24. package/dist/config.cjs +4 -25
  25. package/dist/config.d.cts +2 -28
  26. package/dist/config.d.ts +2 -28
  27. package/dist/config.js +2 -24
  28. package/dist/crypto-BB92-Upx.js +7 -0
  29. package/dist/crypto-CT3VZ7lF.cjs +13 -0
  30. package/dist/crypto.cjs +2 -2
  31. package/dist/crypto.d.cts +8 -1
  32. package/dist/crypto.d.ts +8 -1
  33. package/dist/crypto.js +2 -2
  34. package/dist/dev-protocol.d.cts +3 -3
  35. package/dist/dev-protocol.d.ts +3 -3
  36. package/dist/dev-protocol.js +1 -2
  37. package/dist/fs.cjs +12 -2
  38. package/dist/fs.d.cts +7 -1
  39. package/dist/fs.d.ts +7 -1
  40. package/dist/fs.js +10 -1
  41. package/dist/index.cjs +4 -5
  42. package/dist/index.d.cts +2 -3
  43. package/dist/index.d.ts +2 -3
  44. package/dist/index.js +2 -4
  45. package/dist/native-macos-D6p2SmUj.js +20 -0
  46. package/dist/native-macos-DLwo_ii9.cjs +31 -0
  47. package/dist/native-runtime-C85Nuc4F.js +10 -0
  48. package/dist/native-runtime-CbPzrs8j.d.cts +33 -0
  49. package/dist/{native-runtime-CdeT0JyQ.cjs → native-runtime-CsXnXkQn.cjs} +1 -14
  50. package/dist/native-runtime-DxHSrsTX.d.ts +33 -0
  51. package/dist/native.cjs +35 -32
  52. package/dist/native.d.cts +4 -34
  53. package/dist/native.d.ts +3 -33
  54. package/dist/native.js +36 -34
  55. package/dist/runtime.d.cts +1 -2
  56. package/dist/runtime.d.ts +1 -2
  57. package/dist/schema-BOuUHcqo.d.cts +58 -0
  58. package/dist/schema-DVmoD9JL.js +90 -0
  59. package/dist/schema-bxeeCyLn.cjs +102 -0
  60. package/dist/schema-ipsECAso.d.ts +58 -0
  61. package/dist/schema.cjs +4 -0
  62. package/dist/schema.d.cts +2 -0
  63. package/dist/schema.d.ts +2 -0
  64. package/dist/schema.js +3 -0
  65. package/dist/state.cjs +765 -0
  66. package/dist/state.d.cts +129 -0
  67. package/dist/state.d.ts +129 -0
  68. package/dist/state.js +747 -0
  69. package/dist/styles.cjs +742 -0
  70. package/dist/styles.d.cts +701 -0
  71. package/dist/styles.d.ts +701 -0
  72. package/dist/styles.js +739 -0
  73. package/dist/ui.cjs +113 -0
  74. package/dist/ui.d.cts +30 -0
  75. package/dist/ui.d.ts +30 -0
  76. package/dist/ui.js +110 -0
  77. package/metro-config.cjs +18 -13
  78. package/package.json +47 -13
  79. package/templates/basic/app/index.tsx +130 -12
  80. package/templates/basic/app.config.ts +4 -2
  81. package/dist/app.d.cts.map +0 -1
  82. package/dist/app.d.ts.map +0 -1
  83. package/dist/app.js.map +0 -1
  84. package/dist/bundler.d.cts.map +0 -1
  85. package/dist/bundler.d.ts.map +0 -1
  86. package/dist/cli.js.map +0 -1
  87. package/dist/config.d.cts.map +0 -1
  88. package/dist/config.d.ts.map +0 -1
  89. package/dist/config.js.map +0 -1
  90. package/dist/crypto-BNhWeXMj.cjs +0 -15
  91. package/dist/crypto-Bg8cD8wX.d.cts +0 -8
  92. package/dist/crypto-Bg8cD8wX.d.cts.map +0 -1
  93. package/dist/crypto-CCA_hvao.js +0 -11
  94. package/dist/crypto-CCA_hvao.js.map +0 -1
  95. package/dist/crypto-IOzbNoJD.d.ts +0 -8
  96. package/dist/crypto-IOzbNoJD.d.ts.map +0 -1
  97. package/dist/dev-protocol.d.cts.map +0 -1
  98. package/dist/dev-protocol.d.ts.map +0 -1
  99. package/dist/dev-protocol.js.map +0 -1
  100. package/dist/fs-BK7joD1g.js +0 -13
  101. package/dist/fs-BK7joD1g.js.map +0 -1
  102. package/dist/fs-CEhcjuN6.d.ts +0 -9
  103. package/dist/fs-CEhcjuN6.d.ts.map +0 -1
  104. package/dist/fs-CfE8CHf_.d.cts +0 -9
  105. package/dist/fs-CfE8CHf_.d.cts.map +0 -1
  106. package/dist/fs-DKHYTPua.cjs +0 -18
  107. package/dist/native-runtime-BoqybBBf.js +0 -18
  108. package/dist/native-runtime-BoqybBBf.js.map +0 -1
  109. package/dist/native.d.cts.map +0 -1
  110. package/dist/native.d.ts.map +0 -1
  111. package/dist/native.js.map +0 -1
  112. package/dist/runtime.d.cts.map +0 -1
  113. package/dist/runtime.d.ts.map +0 -1
package/dist/cli.js CHANGED
@@ -2,32 +2,100 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { execFileSync, spawn, spawnSync } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
5
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
5
+ import { 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
+ function artifactPlatform() {
20
+ return `macos-${process.arch}`;
21
+ }
22
+ function prettyPath(value) {
23
+ const home = os.homedir();
24
+ return value === home || value.startsWith(`${home}${path.sep}`) ? `~${value.slice(home.length)}` : value;
25
+ }
26
+ function commandSucceeds(command, args) {
27
+ const result = spawnSync(command, args, { stdio: "ignore" });
28
+ return !result.error && result.status === 0;
29
+ }
30
+ function commandText(command, args) {
31
+ return [command, ...args].join(" ");
32
+ }
33
+ function doctorHint(projectRoot) {
34
+ return projectRoot ? `Run ${bold(`bleam doctor ${projectRoot}`)} for diagnostics.` : `Run ${bold("bleam doctor")} for diagnostics.`;
35
+ }
36
+ function appConfigBuildExample() {
37
+ return [
38
+ "Example:",
39
+ "import { appConfig } from 'bleam'",
40
+ "",
41
+ "export default appConfig({",
42
+ " name: 'My App',",
43
+ " bundleIdentifier: 'com.example.myapp',",
44
+ " teamId: 'ABCDE12345',",
45
+ "})"
46
+ ].join("\n");
47
+ }
48
+ function missingBuildFieldError(field) {
49
+ return /* @__PURE__ */ new Error(`app.config.ts must define ${field} for bleam build.\n\n${appConfigBuildExample()}`);
50
+ }
51
+ function heading(text) {
52
+ return bold(text);
53
+ }
54
+ function muted(text) {
55
+ return dim(text);
56
+ }
57
+ function statusLabel(status) {
58
+ if (status === "pass") return green("[ok]");
59
+ if (status === "warn") return yellow("[warn]");
60
+ return red("[fail]");
61
+ }
62
+ function startSpinner(text) {
63
+ if (!process.stderr.isTTY) {
64
+ console.log(text);
65
+ return;
66
+ }
67
+ return yoctoSpinner({
68
+ text,
69
+ handleSignals: false
70
+ }).start();
71
+ }
17
72
  function printUsage() {
18
73
  console.log(`Usage: bleam <command>
19
74
 
20
75
  Commands:
21
76
  new [dir] [--run] Create a new Bleam project
22
- dev [dir] [--port <port>] Start a Bleam dev server`);
77
+ dev [dir] [--port <port>] [--run]
78
+ Start a Bleam dev server
79
+ build [dir] [--output <dir>]
80
+ Build App Store-ready native archives
81
+ doctor [dir] Check the local Bleam environment
82
+ check [dir] [--write] Format, lint, and organize imports
83
+ format [dir] [--check] Format project files
84
+ lint [dir] Lint project files`);
23
85
  }
24
86
  function invocationRoot() {
25
- return path.isAbsolute(process.env.PWD ?? "") ? process.env.PWD : process.cwd();
87
+ const pwd = process.env.PWD;
88
+ return pwd && path.isAbsolute(pwd) ? pwd : process.cwd();
89
+ }
90
+ function canonicalPath(value) {
91
+ const resolved = path.resolve(value);
92
+ return existsSync(resolved) ? realpathSync.native(resolved) : resolved;
26
93
  }
27
94
  function parseDevOptions(argv) {
28
95
  const root = invocationRoot();
29
96
  let projectRoot = root;
30
97
  let port = 8081;
98
+ let run = false;
31
99
  let hasProjectRootArg = false;
32
100
  for (let index = 0; index < argv.length; index += 1) {
33
101
  const arg = argv[index];
@@ -40,6 +108,10 @@ function parseDevOptions(argv) {
40
108
  port = Number(arg.slice(7));
41
109
  continue;
42
110
  }
111
+ if (arg === "--run") {
112
+ run = true;
113
+ continue;
114
+ }
43
115
  if (arg === "--help" || arg === "-h") {
44
116
  printUsage();
45
117
  process.exit(0);
@@ -51,8 +123,9 @@ function parseDevOptions(argv) {
51
123
  if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
52
124
  if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
53
125
  return {
54
- projectRoot: path.resolve(projectRoot),
55
- port
126
+ projectRoot: canonicalPath(projectRoot),
127
+ port,
128
+ run
56
129
  };
57
130
  }
58
131
  function parseNewOptions(argv) {
@@ -72,10 +145,108 @@ function parseNewOptions(argv) {
72
145
  projectRoot = path.resolve(root, arg);
73
146
  }
74
147
  return {
75
- projectRoot,
148
+ projectRoot: canonicalPath(projectRoot),
76
149
  run
77
150
  };
78
151
  }
152
+ function parseBuildOptions(argv) {
153
+ const root = invocationRoot();
154
+ let projectRoot = root;
155
+ let hasProjectRootArg = false;
156
+ let output;
157
+ let configuration = "Release";
158
+ let exportOptionsPlist;
159
+ for (let index = 0; index < argv.length; index += 1) {
160
+ const arg = argv[index];
161
+ if (arg === "--output" || arg === "-o") {
162
+ output = argv[++index];
163
+ continue;
164
+ }
165
+ if (arg.startsWith("--output=")) {
166
+ output = arg.slice(9);
167
+ continue;
168
+ }
169
+ if (arg === "--configuration") {
170
+ const value = argv[++index];
171
+ if (!value) throw new Error("Missing --configuration value");
172
+ configuration = value;
173
+ continue;
174
+ }
175
+ if (arg.startsWith("--configuration=")) {
176
+ configuration = arg.slice(16);
177
+ continue;
178
+ }
179
+ if (arg === "--export-options-plist") {
180
+ exportOptionsPlist = argv[++index];
181
+ continue;
182
+ }
183
+ if (arg.startsWith("--export-options-plist=")) {
184
+ exportOptionsPlist = arg.slice(23);
185
+ continue;
186
+ }
187
+ if (arg === "--help" || arg === "-h") {
188
+ printUsage();
189
+ process.exit(0);
190
+ }
191
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
192
+ projectRoot = path.resolve(root, arg);
193
+ hasProjectRootArg = true;
194
+ }
195
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
196
+ const resolvedProjectRoot = canonicalPath(projectRoot);
197
+ return {
198
+ projectRoot: resolvedProjectRoot,
199
+ output: path.resolve(resolvedProjectRoot, output ?? "dist"),
200
+ configuration,
201
+ exportOptionsPlist: exportOptionsPlist ? path.resolve(root, exportOptionsPlist) : void 0
202
+ };
203
+ }
204
+ function parseProjectToolOptions(argv) {
205
+ const root = invocationRoot();
206
+ let projectRoot = root;
207
+ let hasProjectRootArg = false;
208
+ let write = false;
209
+ let formatCheck = false;
210
+ for (const arg of argv) {
211
+ if (arg === "--write") {
212
+ write = true;
213
+ continue;
214
+ }
215
+ if (arg === "--check") {
216
+ formatCheck = true;
217
+ continue;
218
+ }
219
+ if (arg === "--help" || arg === "-h") {
220
+ printUsage();
221
+ process.exit(0);
222
+ }
223
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
224
+ projectRoot = path.resolve(root, arg);
225
+ hasProjectRootArg = true;
226
+ }
227
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
228
+ return {
229
+ projectRoot: canonicalPath(projectRoot),
230
+ write,
231
+ formatCheck
232
+ };
233
+ }
234
+ function parseDoctorOptions(argv) {
235
+ const root = invocationRoot();
236
+ let projectRoot = root;
237
+ let hasProjectRootArg = false;
238
+ for (const arg of argv) {
239
+ if (arg === "--help" || arg === "-h") {
240
+ printUsage();
241
+ process.exit(0);
242
+ }
243
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
244
+ projectRoot = path.resolve(root, arg);
245
+ hasProjectRootArg = true;
246
+ }
247
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
248
+ return { projectRoot: canonicalPath(projectRoot) };
249
+ }
79
250
  function parseInternalMetroOptions(argv) {
80
251
  let workspaceRoot;
81
252
  let port = 8081;
@@ -139,6 +310,23 @@ function assertStringField(value, field) {
139
310
  if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
140
311
  return value;
141
312
  }
313
+ function isHexColor(value) {
314
+ return typeof value === "string" && /^#[\da-f]{6}$/i.test(value);
315
+ }
316
+ function normalizeAppearance(value) {
317
+ const input = value && typeof value === "object" ? value : {};
318
+ const window = input.window && typeof input.window === "object" ? input.window : {};
319
+ const appearance = {};
320
+ if (window.backgroundColor !== void 0) {
321
+ if (!isHexColor(window.backgroundColor)) throw new Error("app.config.ts appearance.window.backgroundColor must be a #rrggbb color");
322
+ appearance.window = { backgroundColor: window.backgroundColor };
323
+ }
324
+ if (input.userInterfaceStyle !== void 0) {
325
+ if (input.userInterfaceStyle !== "automatic" && input.userInterfaceStyle !== "light" && input.userInterfaceStyle !== "dark") throw new Error("app.config.ts appearance.userInterfaceStyle must be automatic, light, or dark");
326
+ appearance.userInterfaceStyle = input.userInterfaceStyle;
327
+ }
328
+ return appearance;
329
+ }
142
330
  function loadAppConfig(projectRoot) {
143
331
  const configPath = path.join(projectRoot, "app.config.ts");
144
332
  const loader = `
@@ -168,8 +356,10 @@ function loadAppConfig(projectRoot) {
168
356
  return {
169
357
  name: assertStringField(value.name, "name"),
170
358
  version: typeof value.version === "string" ? value.version : void 0,
359
+ buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
171
360
  bundleIdentifier: typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0,
172
- teamId: typeof value.teamId === "string" ? value.teamId : void 0
361
+ teamId: typeof value.teamId === "string" ? value.teamId : void 0,
362
+ appearance: normalizeAppearance(value.appearance)
173
363
  };
174
364
  }
175
365
  function assertBleamConfigFiles(projectRoot) {
@@ -188,8 +378,10 @@ function readProjectConfig(options) {
188
378
  return {
189
379
  name: config.name,
190
380
  version: config.version,
381
+ buildNumber: config.buildNumber ?? "1",
191
382
  bundleIdentifier: config.bundleIdentifier,
192
383
  teamId: config.teamId,
384
+ appearance: config.appearance ?? normalizeAppearance(void 0),
193
385
  runtimeVersion,
194
386
  sdkVersion: runtimeVersion
195
387
  };
@@ -202,6 +394,9 @@ function nameFromDirectory(projectRoot) {
202
394
  if (!name) return "Bleam App";
203
395
  return name.replace(/\b\w/g, (letter) => letter.toUpperCase());
204
396
  }
397
+ function singleQuotedString(value) {
398
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
399
+ }
205
400
  function createNewProject(options) {
206
401
  const templateRoot = path.join(packageRoot, "templates", "basic");
207
402
  if (!existsSync(templateRoot)) throw new Error(`Missing basic template: ${templateRoot}`);
@@ -209,10 +404,12 @@ function createNewProject(options) {
209
404
  mkdirSync(options.projectRoot, { recursive: true });
210
405
  cpSync(templateRoot, options.projectRoot, { recursive: true });
211
406
  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`);
407
+ writeFileSync(path.join(options.projectRoot, "app.config.ts"), `import { appConfig } from 'bleam'\n\nexport default appConfig({\n name: ${singleQuotedString(name)},\n})\n`);
408
+ writeFileSync(path.join(options.projectRoot, ".gitignore"), `.bleam\nbiome.json\n.DS_Store\n`);
214
409
  createDevWorkspace(options.projectRoot, {
215
410
  name,
411
+ buildNumber: "1",
412
+ appearance: normalizeAppearance(void 0),
216
413
  runtimeVersion,
217
414
  sdkVersion: runtimeVersion
218
415
  });
@@ -235,7 +432,13 @@ function createDevWorkspace(projectRoot, config) {
235
432
  version: config.version ?? "0.0.0",
236
433
  orientation: "default",
237
434
  platforms: ["ios"],
238
- newArchEnabled: true
435
+ newArchEnabled: true,
436
+ extra: { bleam: {
437
+ appearance: config.appearance,
438
+ buildNumber: config.buildNumber,
439
+ runtimeVersion: config.runtimeVersion,
440
+ sdkVersion: config.sdkVersion
441
+ } }
239
442
  } }, null, 2)}\n`);
240
443
  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
444
  writeFileSync(path.join(workspaceRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
@@ -246,6 +449,7 @@ function createDevWorkspace(projectRoot, config) {
246
449
  main: "index.js",
247
450
  dependencies: {
248
451
  "@types/react": packageJson.dependencies?.["@types/react"],
452
+ "@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
249
453
  expo: packageJson.dependencies?.expo,
250
454
  react: packageJson.dependencies?.react,
251
455
  "react-native": packageJson.dependencies?.["react-native"],
@@ -256,8 +460,8 @@ function createDevWorkspace(projectRoot, config) {
256
460
  writeFileSync(path.join(workspaceRoot, "tsconfig.json"), `${JSON.stringify({
257
461
  extends: "bleam/tsconfig",
258
462
  compilerOptions: { paths: {
259
- bleam: ["./node_modules/bleam"],
260
- "bleam/*": ["./node_modules/bleam/*"],
463
+ bleam: ["./node_modules/bleam/dist/index"],
464
+ "bleam/*": ["./node_modules/bleam/dist/*"],
261
465
  expo: ["./node_modules/expo"],
262
466
  "expo/*": ["./node_modules/expo/*"],
263
467
  react: ["./node_modules/@types/react"],
@@ -270,6 +474,34 @@ function createDevWorkspace(projectRoot, config) {
270
474
  }, null, 2)}\n`);
271
475
  return workspaceRoot;
272
476
  }
477
+ function writeBiomeConfig(configPath) {
478
+ writeFileSync(configPath, `${JSON.stringify({
479
+ $schema: "https://biomejs.dev/schemas/2.5.2/schema.json",
480
+ files: { includes: [
481
+ "app/**/*",
482
+ "app.config.ts",
483
+ "tsconfig.json"
484
+ ] },
485
+ formatter: {
486
+ enabled: true,
487
+ indentStyle: "space",
488
+ indentWidth: 2,
489
+ lineWidth: 80
490
+ },
491
+ javascript: { formatter: {
492
+ quoteStyle: "single",
493
+ semicolons: "asNeeded",
494
+ trailingCommas: "all"
495
+ } },
496
+ linter: {
497
+ enabled: true,
498
+ rules: {
499
+ preset: "recommended",
500
+ suspicious: { noExplicitAny: "off" }
501
+ }
502
+ }
503
+ }, null, 2)}\n`);
504
+ }
273
505
  function writeProjectRuntimeConfig(projectRoot, config, origin) {
274
506
  const projectConfigPath = path.join(projectRoot, ".bleam", "project.json");
275
507
  mkdirSync(path.dirname(projectConfigPath), { recursive: true });
@@ -278,28 +510,220 @@ function writeProjectRuntimeConfig(projectRoot, config, origin) {
278
510
  version: config.version,
279
511
  bundleIdentifier: config.bundleIdentifier,
280
512
  teamId: config.teamId,
513
+ appearance: config.appearance,
281
514
  devServer: origin,
282
515
  runtimeVersion: config.runtimeVersion,
283
516
  sdkVersion: config.sdkVersion
284
517
  }, null, 2)}\n`);
285
518
  return projectConfigPath;
286
519
  }
520
+ function nativeRuntimeTemplateRoot() {
521
+ return path.resolve(packageRoot, "..", "..", "app", "bleam");
522
+ }
523
+ function safeArtifactName(name) {
524
+ return name.trim().replace(/[/:\\]/g, "-").replace(/\s+/g, " ") || "App";
525
+ }
526
+ function xcodeSettingValue(value) {
527
+ if (/^[A-Za-z0-9_.$-]+$/.test(value)) return value;
528
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
529
+ }
530
+ function plistString(value) {
531
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
532
+ }
533
+ function replacePlistString(plist, key, value) {
534
+ const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<string>)([^<]*)(</string>)`);
535
+ if (!pattern.test(plist)) return plist;
536
+ return plist.replace(pattern, `$1${plistString(value)}$3`);
537
+ }
538
+ function writeGeneratedAppJson(nativeRoot, config) {
539
+ writeFileSync(path.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
540
+ name: config.name,
541
+ slug: slugFromName(config.name),
542
+ version: config.version ?? "1.0.0",
543
+ platforms: ["ios"],
544
+ ios: {
545
+ appleTeamId: config.teamId,
546
+ buildNumber: config.buildNumber,
547
+ bundleIdentifier: config.bundleIdentifier
548
+ }
549
+ } }, null, 2)}\n`);
550
+ }
551
+ function patchNativeProject(nativeRoot, config) {
552
+ const projectPath = path.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
553
+ const infoPlistPath = path.join(nativeRoot, "ios", "Bleam", "Info.plist");
554
+ const productName = xcodeSettingValue(config.name);
555
+ const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
556
+ const teamId = xcodeSettingValue(config.teamId ?? "");
557
+ const version = xcodeSettingValue(config.version ?? "1.0.0");
558
+ const buildNumber = xcodeSettingValue(config.buildNumber);
559
+ let project = readFileSync(projectPath, "utf8");
560
+ project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = [^;]+;/g, () => {
561
+ return `PRODUCT_BUNDLE_IDENTIFIER = ${bundleIdentifier};`;
562
+ }).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
563
+ return `DEVELOPMENT_TEAM = ${teamId};`;
564
+ }).replace(/MARKETING_VERSION = [^;]+;/g, () => {
565
+ return `MARKETING_VERSION = ${version};`;
566
+ }).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, () => {
567
+ return `CURRENT_PROJECT_VERSION = ${buildNumber};`;
568
+ }).replace(/PRODUCT_NAME = [^;]+;/g, () => {
569
+ return `PRODUCT_NAME = ${productName};`;
570
+ });
571
+ writeFileSync(projectPath, project);
572
+ let infoPlist = readFileSync(infoPlistPath, "utf8");
573
+ infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
574
+ infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
575
+ infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
576
+ writeFileSync(infoPlistPath, infoPlist);
577
+ }
578
+ function createBuildNativeWorkspace(options, config) {
579
+ const nativeRoot = path.join(options.projectRoot, ".bleam", "build", "native");
580
+ const templateRoot = nativeRuntimeTemplateRoot();
581
+ rmSync(nativeRoot, {
582
+ recursive: true,
583
+ force: true
584
+ });
585
+ mkdirSync(path.dirname(nativeRoot), { recursive: true });
586
+ cpSync(templateRoot, nativeRoot, {
587
+ recursive: true,
588
+ filter(source) {
589
+ return !path.relative(templateRoot, source).split(path.sep).some((part) => {
590
+ return part === "build" || part === "dist" || part === "node_modules" || part === ".expo" || part === "Pods" || part === ".xcode.env.local" || part === ".DS_Store" || part.endsWith(".xcworkspace");
591
+ });
592
+ }
593
+ });
594
+ const nodeModules = path.join(nativeRoot, "node_modules");
595
+ rmSync(nodeModules, {
596
+ recursive: true,
597
+ force: true
598
+ });
599
+ symlinkSync(resolvedDependencyRoot(), nodeModules);
600
+ writeGeneratedAppJson(nativeRoot, config);
601
+ 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`);
602
+ writeFileSync(path.join(nativeRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
603
+ 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`);
604
+ patchNativeProject(nativeRoot, config);
605
+ return nativeRoot;
606
+ }
607
+ function preflightBuild(options, config) {
608
+ if (process.platform !== "darwin") throw new Error(`bleam build requires macOS and Xcode. Current platform: ${process.platform}.\n${doctorHint(options.projectRoot)}`);
609
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
610
+ if (!config.teamId) throw missingBuildFieldError("teamId");
611
+ const templateRoot = nativeRuntimeTemplateRoot();
612
+ if (!existsSync(templateRoot)) throw new Error(`Missing native runtime template: ${templateRoot}\n${doctorHint(options.projectRoot)}`);
613
+ 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)}`);
614
+ 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)}`);
615
+ }
616
+ function runInheritedCommand(command, args, cwd) {
617
+ const text = commandText(command, args);
618
+ const result = spawnSync(command, args, {
619
+ cwd,
620
+ stdio: "inherit",
621
+ env: process.env
622
+ });
623
+ if (result.error) throw new Error(`${text} could not start in ${cwd}: ${result.error.message}\n${doctorHint()}`);
624
+ 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()}`);
625
+ }
626
+ function installPods(nativeRoot) {
627
+ console.log("Installing native dependencies...");
628
+ runInheritedCommand("pod", ["install"], path.join(nativeRoot, "ios"));
629
+ }
630
+ function archiveNativeApp(options) {
631
+ const platformOutput = path.join(options.outputRoot, "macos");
632
+ const archivePath = path.join(platformOutput, `${safeArtifactName(options.config.name)}.xcarchive`);
633
+ mkdirSync(platformOutput, { recursive: true });
634
+ rmSync(archivePath, {
635
+ recursive: true,
636
+ force: true
637
+ });
638
+ console.log(`Archiving ${options.config.name} for macOS...`);
639
+ runInheritedCommand("xcodebuild", [
640
+ "-allowProvisioningUpdates",
641
+ "-workspace",
642
+ path.join(options.nativeRoot, "ios", "Bleam.xcworkspace"),
643
+ "-scheme",
644
+ "bleam",
645
+ "-configuration",
646
+ options.configuration,
647
+ "-destination",
648
+ "platform=macOS,arch=arm64,variant=Mac Catalyst",
649
+ "-archivePath",
650
+ archivePath,
651
+ "archive"
652
+ ], options.nativeRoot);
653
+ return archivePath;
654
+ }
655
+ function exportNativeArchive(options) {
656
+ const exportPath = path.join(options.outputRoot, "macos", "export");
657
+ rmSync(exportPath, {
658
+ recursive: true,
659
+ force: true
660
+ });
661
+ mkdirSync(exportPath, { recursive: true });
662
+ console.log("Exporting macOS archive...");
663
+ runInheritedCommand("xcodebuild", [
664
+ "-allowProvisioningUpdates",
665
+ "-exportArchive",
666
+ "-archivePath",
667
+ options.archivePath,
668
+ "-exportPath",
669
+ exportPath,
670
+ "-exportOptionsPlist",
671
+ options.exportOptionsPlist
672
+ ], path.dirname(options.archivePath));
673
+ return exportPath;
674
+ }
675
+ async function buildNativeArchives(options) {
676
+ const config = readProjectConfig(options);
677
+ preflightBuild(options, config);
678
+ const nativeRoot = createBuildNativeWorkspace(options, config);
679
+ installPods(nativeRoot);
680
+ const archivePath = archiveNativeApp({
681
+ nativeRoot,
682
+ outputRoot: options.output,
683
+ configuration: options.configuration,
684
+ config
685
+ });
686
+ console.log(`Created ${archivePath}`);
687
+ if (options.exportOptionsPlist) {
688
+ const exportPath = exportNativeArchive({
689
+ archivePath,
690
+ outputRoot: options.output,
691
+ exportOptionsPlist: options.exportOptionsPlist
692
+ });
693
+ console.log(`Exported ${exportPath}`);
694
+ }
695
+ }
287
696
  function runtimeCacheRoot() {
288
- return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, runtimePlatform);
697
+ return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
289
698
  }
290
699
  function localDevelopmentRuntimeAppPath() {
291
- return path.resolve(packageRoot, "..", "..", "app", "bleam", "build", "Build", "Products", "Debug-maccatalyst", "Bleam.app");
700
+ const productsRoot = path.resolve(packageRoot, "..", "..", "app", "bleam", "build", "Build", "Products", "Debug-maccatalyst");
701
+ for (const appName of ["Bleam.app", "bleam.app"]) {
702
+ const appPath = path.join(productsRoot, appName);
703
+ if (existsSync(appPath)) return appPath;
704
+ }
705
+ return path.join(productsRoot, "Bleam.app");
292
706
  }
293
707
  function runtimeReleaseBaseUrl() {
294
708
  return process.env.BLEAM_RUNTIME_RELEASE_BASE_URL ?? "https://github.com/bajedev/bleam-app/releases/download";
295
709
  }
296
710
  function runtimeArchiveName() {
297
- return `bleam-runtime-v${runtimeVersion}-${runtimePlatform}.zip`;
711
+ return `bleam-runtime-v${runtimeVersion}-${artifactPlatform()}.zip`;
298
712
  }
299
713
  function runtimeArchiveUrl() {
300
714
  const tag = encodeURIComponent(`bleam@${runtimeVersion}`);
301
715
  return `${runtimeReleaseBaseUrl()}/${tag}/${runtimeArchiveName()}`;
302
716
  }
717
+ function runtimeResolutionDetails() {
718
+ return [
719
+ `Runtime version: ${runtimeVersion}`,
720
+ `Runtime platform: ${artifactPlatform()}`,
721
+ `Runtime cache: ${runtimeCacheRoot()}`,
722
+ `Release URL: ${runtimeArchiveUrl()}`,
723
+ `Override with BLEAM_RUNTIME_APP=/path/to/Bleam.app for local testing.`,
724
+ doctorHint()
725
+ ].join("\n");
726
+ }
303
727
  function sha256(filePath) {
304
728
  return createHash("sha256").update(readFileSync(filePath)).digest("hex");
305
729
  }
@@ -314,7 +738,8 @@ async function downloadExpectedSha256(url) {
314
738
  return (await response.text()).trim().split(/\s+/)[0];
315
739
  }
316
740
  async function downloadRuntimeArtifact() {
317
- if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available on macOS.");
741
+ if (process.platform !== "darwin") throw new Error("Bleam runtime artifacts are currently only available from macOS hosts.");
742
+ const runtimePlatform = artifactPlatform();
318
743
  const cacheRoot = runtimeCacheRoot();
319
744
  const cachedAppPath = path.join(cacheRoot, "Bleam.app");
320
745
  if (existsSync(cachedAppPath)) return {
@@ -361,10 +786,11 @@ async function downloadRuntimeArtifact() {
361
786
  };
362
787
  }
363
788
  async function resolveRuntimeArtifact() {
789
+ const runtimePlatform = artifactPlatform();
364
790
  const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
365
791
  if (overrideAppPath) {
366
792
  const appPath = path.resolve(overrideAppPath);
367
- if (!existsSync(appPath)) throw new Error(`BLEAM_RUNTIME_APP does not exist: ${appPath}`);
793
+ 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()}`);
368
794
  return {
369
795
  appPath,
370
796
  version: runtimeVersion,
@@ -377,7 +803,11 @@ async function resolveRuntimeArtifact() {
377
803
  version: runtimeVersion,
378
804
  platform: runtimePlatform
379
805
  };
380
- return downloadRuntimeArtifact();
806
+ try {
807
+ return await downloadRuntimeArtifact();
808
+ } catch (error) {
809
+ throw new Error(`Failed to resolve the Bleam runtime.\n${runtimeResolutionDetails()}\n\nCause: ${error instanceof Error ? error.message : String(error)}`);
810
+ }
381
811
  }
382
812
  function copyRuntimeClient(projectRoot, runtime) {
383
813
  const clientAppPath = path.join(projectRoot, ".bleam", "client", "Bleam.app");
@@ -396,24 +826,104 @@ function isBleamManifest(value) {
396
826
  async function readDevManifest(origin, timeoutMs = 3e4) {
397
827
  const manifestUrl = `${origin}/bleam/manifest.json`;
398
828
  const startedAt = Date.now();
399
- console.log("Waiting for Bleam dev server...");
829
+ const spinner = startSpinner("Waiting for Bleam dev server...");
400
830
  while (Date.now() - startedAt <= timeoutMs) {
401
831
  try {
402
832
  const response = await fetch(manifestUrl, { cache: "no-store" });
403
833
  const value = await response.json();
404
- if (response.ok && isBleamManifest(value)) return value;
834
+ if (response.ok && isBleamManifest(value)) {
835
+ spinner?.success("Bleam dev server is ready");
836
+ return value;
837
+ }
405
838
  } catch {}
406
839
  await new Promise((resolve) => setTimeout(resolve, 500));
407
840
  }
841
+ spinner?.error("Bleam dev server did not become available");
408
842
  throw new Error(`Bleam manifest did not become available: ${manifestUrl}`);
409
843
  }
844
+ function pushDoctorCheck(checks, status, label, detail) {
845
+ checks.push({
846
+ status,
847
+ label,
848
+ detail
849
+ });
850
+ }
851
+ function printDoctorSection(title, checks) {
852
+ if (checks.length === 0) return;
853
+ console.log(`\n${heading(title)}`);
854
+ for (const check of checks) {
855
+ const detail = check.detail ? muted(`: ${check.detail}`) : "";
856
+ console.log(` ${statusLabel(check.status)} ${check.label}${detail}`);
857
+ }
858
+ }
859
+ function runDoctor(options) {
860
+ const projectChecks = [];
861
+ const runtimeChecks = [];
862
+ const environmentChecks = [];
863
+ const appConfigPath = path.join(options.projectRoot, "app.config.ts");
864
+ const appEntryPath = path.join(options.projectRoot, "app", "index.tsx");
865
+ if (existsSync(appConfigPath)) pushDoctorCheck(projectChecks, "pass", "app.config.ts found");
866
+ else pushDoctorCheck(projectChecks, "fail", "app.config.ts missing");
867
+ if (existsSync(appEntryPath)) pushDoctorCheck(projectChecks, "pass", "app/index.tsx found");
868
+ else pushDoctorCheck(projectChecks, "fail", "app/index.tsx missing");
869
+ try {
870
+ const config = readProjectConfig(options);
871
+ pushDoctorCheck(projectChecks, "pass", "config loaded");
872
+ pushDoctorCheck(projectChecks, "pass", "name", config.name);
873
+ if (config.bundleIdentifier) pushDoctorCheck(projectChecks, "pass", "bundleIdentifier", config.bundleIdentifier);
874
+ else pushDoctorCheck(projectChecks, "warn", "bundleIdentifier not configured", "required for bleam build");
875
+ if (config.teamId) pushDoctorCheck(projectChecks, "pass", "teamId", config.teamId);
876
+ else pushDoctorCheck(projectChecks, "warn", "teamId not configured", "required for bleam build");
877
+ } catch (error) {
878
+ pushDoctorCheck(projectChecks, "fail", "config failed to load", error instanceof Error ? error.message : String(error));
879
+ }
880
+ const cacheRoot = runtimeCacheRoot();
881
+ const cachedAppPath = path.join(cacheRoot, "Bleam.app");
882
+ const localAppPath = localDevelopmentRuntimeAppPath();
883
+ pushDoctorCheck(runtimeChecks, "pass", "version", runtimeVersion);
884
+ pushDoctorCheck(runtimeChecks, "pass", "platform", artifactPlatform());
885
+ pushDoctorCheck(runtimeChecks, "pass", "cache", prettyPath(cacheRoot));
886
+ if (existsSync(cachedAppPath)) pushDoctorCheck(runtimeChecks, "pass", "cached runtime found");
887
+ else pushDoctorCheck(runtimeChecks, "warn", "runtime not cached yet", "bleam dev can download it on demand");
888
+ if (existsSync(localAppPath)) pushDoctorCheck(runtimeChecks, "pass", "local development runtime found", prettyPath(localAppPath));
889
+ else pushDoctorCheck(runtimeChecks, "warn", "local development runtime not found", prettyPath(localAppPath));
890
+ const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
891
+ if (overrideAppPath) {
892
+ const appPath = path.resolve(overrideAppPath);
893
+ pushDoctorCheck(runtimeChecks, existsSync(appPath) ? "pass" : "fail", "BLEAM_RUNTIME_APP", prettyPath(appPath));
894
+ }
895
+ pushDoctorCheck(runtimeChecks, "pass", "release artifact URL", runtimeArchiveUrl());
896
+ pushDoctorCheck(environmentChecks, process.platform === "darwin" ? "pass" : "fail", "macOS host", process.platform);
897
+ pushDoctorCheck(environmentChecks, "pass", "node", prettyPath(process.execPath));
898
+ const hasXcodebuild = commandSucceeds("xcodebuild", ["-version"]);
899
+ pushDoctorCheck(environmentChecks, hasXcodebuild ? "pass" : "warn", "xcodebuild", hasXcodebuild ? "available" : "unavailable");
900
+ const hasPod = commandSucceeds("pod", ["--version"]);
901
+ pushDoctorCheck(environmentChecks, hasPod ? "pass" : "warn", "pod", hasPod ? "available" : "unavailable");
902
+ const allChecks = [
903
+ ...projectChecks,
904
+ ...runtimeChecks,
905
+ ...environmentChecks
906
+ ];
907
+ const counts = {
908
+ pass: allChecks.filter((check) => check.status === "pass").length,
909
+ warn: allChecks.filter((check) => check.status === "warn").length,
910
+ fail: allChecks.filter((check) => check.status === "fail").length
911
+ };
912
+ console.log(heading("Bleam Doctor"));
913
+ printDoctorSection("Project", projectChecks);
914
+ printDoctorSection("Runtime", runtimeChecks);
915
+ printDoctorSection("Environment", environmentChecks);
916
+ console.log(`\n${heading("Summary")}`);
917
+ console.log(` ${green(`${counts.pass} passed`)}, ${yellow(`${counts.warn} warnings`)}, ${counts.fail > 0 ? red(`${counts.fail} failed`) : green("0 failed")}`);
918
+ if (counts.fail > 0) process.exit(1);
919
+ }
410
920
  function formatDuration(ms) {
411
921
  return `${(ms / 1e3).toFixed(1)}s`;
412
922
  }
413
923
  async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
414
924
  const startedAt = Date.now();
415
925
  const requestTimeoutMs = 3e4;
416
- console.log("Bundling app...");
926
+ const spinner = startSpinner("Bundling app...");
417
927
  while (Date.now() - startedAt <= timeoutMs) {
418
928
  const requestStartedAt = Date.now();
419
929
  try {
@@ -423,17 +933,27 @@ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
423
933
  });
424
934
  await response.body?.cancel();
425
935
  if (response.ok) {
426
- console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
936
+ spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
937
+ if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
427
938
  return;
428
939
  }
429
- console.log(`Bundle request returned ${response.status}, retrying...`);
940
+ if (spinner) spinner.text = `Bundle request returned ${response.status}, retrying...`;
941
+ else console.log(`Bundle request returned ${response.status}, retrying...`);
430
942
  } 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)}`);
943
+ if (error instanceof Error && error.name === "TimeoutError") {
944
+ const message = `Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`;
945
+ if (spinner) spinner.text = message;
946
+ else console.log(message);
947
+ } else {
948
+ const message = `Bundle request failed: ${error instanceof Error ? error.message : String(error)}`;
949
+ if (spinner) spinner.text = `${message}, retrying...`;
950
+ else console.log(message);
951
+ }
433
952
  }
434
953
  await new Promise((resolve) => setTimeout(resolve, 500));
435
954
  }
436
- throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}: ${bundleUrl}`);
955
+ spinner?.error("Bundle did not complete");
956
+ throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}.\nBundle URL: ${bundleUrl}\n${doctorHint()}`);
437
957
  }
438
958
  function openRuntimeClient(appPath, projectConfigPath, manifest) {
439
959
  const child = spawn("open", [
@@ -454,7 +974,110 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
454
974
  if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
455
975
  });
456
976
  }
977
+ async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
978
+ openRuntimeClient(appPath, projectConfigPath, manifest);
979
+ }
980
+ function focusRuntimeClient() {
981
+ return spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to activate"], { stdio: "ignore" }).status === 0;
982
+ }
983
+ function sendJson(res, status, value) {
984
+ res.writeHead(status, {
985
+ "Cache-Control": "no-store",
986
+ "Content-Type": "application/json; charset=utf-8"
987
+ });
988
+ res.end(JSON.stringify(value));
989
+ }
990
+ async function requestRuntimeReload(origin) {
991
+ const response = await fetch(`${origin}/bleam/reload`, {
992
+ method: "POST",
993
+ cache: "no-store"
994
+ });
995
+ const value = await response.json();
996
+ if (!response.ok) throw new Error(`Reload request failed: ${response.status}`);
997
+ const reload = value;
998
+ return typeof reload.clients === "number" ? reload.clients : 0;
999
+ }
1000
+ function biomeBinPath() {
1001
+ const biomePackageJsonPath = require.resolve("@biomejs/biome/package.json");
1002
+ const biomePackageRoot = path.dirname(biomePackageJsonPath);
1003
+ const biomePackage = require("@biomejs/biome/package.json");
1004
+ const bin = typeof biomePackage.bin === "string" ? biomePackage.bin : biomePackage.bin?.biome;
1005
+ if (!bin) throw new Error("Could not resolve @biomejs/biome binary");
1006
+ return path.join(biomePackageRoot, bin);
1007
+ }
1008
+ function ensureProjectToolWorkspace(options) {
1009
+ const config = readProjectConfig({ projectRoot: options.projectRoot });
1010
+ return createDevWorkspace(options.projectRoot, config);
1011
+ }
1012
+ function runBiomeCommand(command, options) {
1013
+ assertBleamConfigFiles(options.projectRoot);
1014
+ ensureProjectToolWorkspace(options);
1015
+ const configPath = path.join(options.projectRoot, "biome.json");
1016
+ const existingConfig = existsSync(configPath) ? readFileSync(configPath, "utf8") : void 0;
1017
+ const args = [
1018
+ command,
1019
+ "--config-path",
1020
+ "biome.json"
1021
+ ];
1022
+ if (command === "check" && options.write) args.push("--write");
1023
+ if (command === "format" && !options.formatCheck) args.push("--write");
1024
+ args.push("app", "app.config.ts", "tsconfig.json");
1025
+ writeBiomeConfig(configPath);
1026
+ const result = spawnSync(biomeBinPath(), args, {
1027
+ cwd: options.projectRoot,
1028
+ stdio: "inherit"
1029
+ });
1030
+ if (existingConfig === void 0) rmSync(configPath, { force: true });
1031
+ else writeFileSync(configPath, existingConfig);
1032
+ if (result.error) throw result.error;
1033
+ process.exit(result.status ?? 1);
1034
+ }
1035
+ function setupInteractiveControls(options) {
1036
+ if (!process.stdin.isTTY) return () => {};
1037
+ const wasRaw = process.stdin.isRaw;
1038
+ let closed = false;
1039
+ const cleanup = () => {
1040
+ if (closed) return;
1041
+ closed = true;
1042
+ process.stdin.off("data", onData);
1043
+ process.stdin.setRawMode(wasRaw);
1044
+ };
1045
+ const onData = (data) => {
1046
+ for (const byte of data) {
1047
+ if (byte === 3) {
1048
+ cleanup();
1049
+ options.stop();
1050
+ return;
1051
+ }
1052
+ const key = String.fromCharCode(byte).toLowerCase();
1053
+ if (key === "o") {
1054
+ (async () => {
1055
+ try {
1056
+ if (!focusRuntimeClient()) {
1057
+ console.log("Opening Bleam app...");
1058
+ await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
1059
+ }
1060
+ } catch (error) {
1061
+ console.error(error instanceof Error ? error.message : String(error));
1062
+ }
1063
+ })();
1064
+ continue;
1065
+ }
1066
+ if (key === "r") requestRuntimeReload(options.origin).then((clients) => {
1067
+ console.log(`Reloaded app (${clients} client${clients === 1 ? "" : "s"})`);
1068
+ }).catch((error) => {
1069
+ console.error(error instanceof Error ? error.message : String(error));
1070
+ });
1071
+ }
1072
+ };
1073
+ process.stdin.setRawMode(true);
1074
+ process.stdin.resume();
1075
+ process.stdin.on("data", onData);
1076
+ console.log("Press o to open, r to reload, Ctrl+C to stop.");
1077
+ return cleanup;
1078
+ }
457
1079
  async function startInternalMetroServer(options) {
1080
+ process.env.BLEAM_DEV_PLATFORM ??= "macos";
458
1081
  const metro = require("@expo/metro/metro");
459
1082
  const { loadConfig } = require("@expo/metro/metro-config");
460
1083
  const config = await loadConfig({
@@ -466,6 +1089,25 @@ async function startInternalMetroServer(options) {
466
1089
  watch: true,
467
1090
  waitForBundler: true
468
1091
  });
1092
+ const messageClients = /* @__PURE__ */ new Set();
1093
+ const messageServer = new WebSocketServer({ noServer: true });
1094
+ const broadcastReload = () => {
1095
+ const message = JSON.stringify({
1096
+ version: 2,
1097
+ method: "reload",
1098
+ params: {}
1099
+ });
1100
+ let clients = 0;
1101
+ for (const client of messageClients) if (client.readyState === WebSocket.OPEN) {
1102
+ client.send(message);
1103
+ clients += 1;
1104
+ }
1105
+ return clients;
1106
+ };
1107
+ messageServer.on("connection", (client) => {
1108
+ messageClients.add(client);
1109
+ client.on("close", () => messageClients.delete(client));
1110
+ });
469
1111
  const server = http.createServer((req, res) => {
470
1112
  if (req.url?.split("?")[0] === "/status") {
471
1113
  res.writeHead(200, {
@@ -476,10 +1118,49 @@ async function startInternalMetroServer(options) {
476
1118
  res.end("packager-status:running");
477
1119
  return;
478
1120
  }
479
- middleware(req, res);
1121
+ if (req.url?.split("?")[0] === "/bleam/reload") {
1122
+ if (req.method !== "POST") {
1123
+ res.writeHead(405, { Allow: "POST" });
1124
+ res.end();
1125
+ return;
1126
+ }
1127
+ sendJson(res, 200, {
1128
+ ok: true,
1129
+ clients: broadcastReload()
1130
+ });
1131
+ return;
1132
+ }
1133
+ middleware(req, res, (error) => {
1134
+ if (res.headersSent) return;
1135
+ if (error) {
1136
+ console.error(error.message);
1137
+ res.writeHead(500, {
1138
+ "Cache-Control": "no-store",
1139
+ "Content-Type": "text/plain; charset=utf-8"
1140
+ });
1141
+ res.end(error.message);
1142
+ return;
1143
+ }
1144
+ res.writeHead(404, {
1145
+ "Cache-Control": "no-store",
1146
+ "Content-Type": "text/plain; charset=utf-8"
1147
+ });
1148
+ res.end("Not found");
1149
+ });
480
1150
  });
481
1151
  server.timeout = 0;
482
1152
  attachHmrServer(server);
1153
+ const metroUpgradeListeners = server.listeners("upgrade");
1154
+ server.removeAllListeners("upgrade");
1155
+ server.on("upgrade", (req, socket, head) => {
1156
+ if (req.url?.split("?")[0] === "/message") {
1157
+ messageServer.handleUpgrade(req, socket, head, (client) => {
1158
+ messageServer.emit("connection", client, req);
1159
+ });
1160
+ return;
1161
+ }
1162
+ for (const listener of metroUpgradeListeners) listener.call(server, req, socket, head);
1163
+ });
483
1164
  await new Promise((resolve, reject) => {
484
1165
  server.on("error", reject);
485
1166
  server.listen(options.port, "localhost", () => resolve());
@@ -489,6 +1170,9 @@ async function startInternalMetroServer(options) {
489
1170
  });
490
1171
  console.log(`Bleam dev server running at http://localhost:${options.port}`);
491
1172
  const close = () => {
1173
+ setTimeout(() => process.exit(0), 1e3).unref();
1174
+ messageServer.close();
1175
+ for (const client of messageClients) client.terminate();
492
1176
  server.close(() => {
493
1177
  end();
494
1178
  process.exit(0);
@@ -520,6 +1204,7 @@ async function startDevServer(options) {
520
1204
  BLEAM_DEV_NAME: config.name,
521
1205
  BLEAM_DEV_ORIGIN: origin,
522
1206
  BLEAM_DEV_PORT: String(options.port),
1207
+ BLEAM_DEV_PLATFORM: "macos",
523
1208
  BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
524
1209
  BLEAM_DEV_SDK_VERSION: config.sdkVersion,
525
1210
  NODE_PATH: [
@@ -532,20 +1217,48 @@ async function startDevServer(options) {
532
1217
  ].filter(Boolean).join(path.delimiter)
533
1218
  }
534
1219
  });
1220
+ let cleanupInteractiveControls = () => {};
1221
+ let stopping = false;
535
1222
  const stop = (signal) => {
1223
+ if (stopping) return;
1224
+ stopping = true;
1225
+ cleanupInteractiveControls();
536
1226
  child.kill(signal);
1227
+ setTimeout(() => process.exit(0), 2e3).unref();
537
1228
  };
538
1229
  process.on("SIGINT", () => stop("SIGINT"));
539
1230
  process.on("SIGTERM", () => stop("SIGTERM"));
540
- child.on("exit", (code) => process.exit(code ?? 0));
1231
+ child.on("error", (error) => {
1232
+ cleanupInteractiveControls();
1233
+ console.error(`Bleam dev server process failed: ${error instanceof Error ? error.message : String(error)}`);
1234
+ console.error(doctorHint(options.projectRoot));
1235
+ process.exit(1);
1236
+ });
1237
+ child.on("exit", (code) => {
1238
+ cleanupInteractiveControls();
1239
+ process.exit(code ?? 0);
1240
+ });
541
1241
  try {
542
1242
  const manifest = await readDevManifest(origin);
543
- if (process.env.BLEAM_NO_OPEN !== "1") {
1243
+ cleanupInteractiveControls = setupInteractiveControls({
1244
+ origin,
1245
+ clientAppPath,
1246
+ projectConfigPath,
1247
+ manifest,
1248
+ stop: () => stop("SIGINT")
1249
+ });
1250
+ if (options.run && process.env.BLEAM_NO_OPEN !== "1") try {
544
1251
  await waitForDevBundle(manifest.bundleUrl);
545
1252
  console.log("Opening Bleam app...");
546
- openRuntimeClient(clientAppPath, projectConfigPath, manifest);
1253
+ await launchRuntimeClient(clientAppPath, projectConfigPath, manifest);
1254
+ } catch (error) {
1255
+ console.error(`Initial app launch failed: ${error instanceof Error ? error.message : String(error)}`);
1256
+ console.error(doctorHint(options.projectRoot));
1257
+ console.error("Check BLEAM_RUNTIME_APP if you are using a runtime override.");
1258
+ console.error("Bleam dev server is still running.");
547
1259
  }
548
1260
  } catch (error) {
1261
+ cleanupInteractiveControls();
549
1262
  child.kill("SIGTERM");
550
1263
  throw error;
551
1264
  }
@@ -561,7 +1274,8 @@ async function main(argv = process.argv.slice(2)) {
561
1274
  createNewProject(options);
562
1275
  if (options.run) await startDevServer({
563
1276
  projectRoot: options.projectRoot,
564
- port: 8081
1277
+ port: 8081,
1278
+ run: true
565
1279
  });
566
1280
  return;
567
1281
  }
@@ -569,6 +1283,26 @@ async function main(argv = process.argv.slice(2)) {
569
1283
  await startDevServer(parseDevOptions(args));
570
1284
  return;
571
1285
  }
1286
+ if (command === "build") {
1287
+ await buildNativeArchives(parseBuildOptions(args));
1288
+ return;
1289
+ }
1290
+ if (command === "doctor") {
1291
+ runDoctor(parseDoctorOptions(args));
1292
+ return;
1293
+ }
1294
+ if (command === "check") {
1295
+ runBiomeCommand("check", parseProjectToolOptions(args));
1296
+ return;
1297
+ }
1298
+ if (command === "format") {
1299
+ runBiomeCommand("format", parseProjectToolOptions(args));
1300
+ return;
1301
+ }
1302
+ if (command === "lint") {
1303
+ runBiomeCommand("lint", parseProjectToolOptions(args));
1304
+ return;
1305
+ }
572
1306
  if (command === "internal:metro") {
573
1307
  await startInternalMetroServer(parseInternalMetroOptions(args));
574
1308
  return;
@@ -581,5 +1315,4 @@ main().catch((error) => {
581
1315
  });
582
1316
 
583
1317
  //#endregion
584
- export { };
585
- //# sourceMappingURL=cli.js.map
1318
+ export { };