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