bleam 0.0.4 → 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 (114) 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 +918 -68
  19. package/dist/cli.js +917 -71
  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 +48 -13
  79. package/templates/basic/app/index.tsx +136 -0
  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
  114. package/templates/basic/app.tsx +0 -18
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,12 +145,139 @@ 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
+ }
250
+ function parseInternalMetroOptions(argv) {
251
+ let workspaceRoot;
252
+ let port = 8081;
253
+ for (let index = 0; index < argv.length; index += 1) {
254
+ const arg = argv[index];
255
+ if (arg === "--workspace-root") {
256
+ workspaceRoot = path.resolve(invocationRoot(), argv[++index] ?? "");
257
+ continue;
258
+ }
259
+ if (arg === "--port") {
260
+ port = Number(argv[++index]);
261
+ continue;
262
+ }
263
+ if (arg.startsWith("--port=")) {
264
+ port = Number(arg.slice(7));
265
+ continue;
266
+ }
267
+ throw new Error(`Unknown internal option: ${arg}`);
268
+ }
269
+ if (!workspaceRoot) throw new Error("Missing --workspace-root");
270
+ if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
271
+ return {
272
+ workspaceRoot,
273
+ port
274
+ };
275
+ }
79
276
  function isBleamProjectRoot(projectRoot) {
80
- return existsSync(path.join(projectRoot, "app.config.ts")) && existsSync(path.join(projectRoot, "app.tsx"));
277
+ return existsSync(path.join(projectRoot, "app.config.ts")) && existsSync(path.join(projectRoot, "app", "index.tsx"));
278
+ }
279
+ function resolvedDependencyRoot() {
280
+ return path.dirname(path.dirname(require.resolve("react-native/package.json")));
81
281
  }
82
282
  function discoverBleamProjectRoot(searchRoot) {
83
283
  const ignoredNames = new Set([
@@ -110,6 +310,23 @@ function assertStringField(value, field) {
110
310
  if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
111
311
  return value;
112
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
+ }
113
330
  function loadAppConfig(projectRoot) {
114
331
  const configPath = path.join(projectRoot, "app.config.ts");
115
332
  const loader = `
@@ -129,7 +346,7 @@ function loadAppConfig(projectRoot) {
129
346
  ...process.env,
130
347
  NODE_PATH: [
131
348
  path.join(projectRoot, "node_modules"),
132
- path.join(packageRoot, "node_modules"),
349
+ resolvedDependencyRoot(),
133
350
  process.env.NODE_PATH
134
351
  ].filter(Boolean).join(path.delimiter)
135
352
  }
@@ -139,14 +356,21 @@ function loadAppConfig(projectRoot) {
139
356
  return {
140
357
  name: assertStringField(value.name, "name"),
141
358
  version: typeof value.version === "string" ? value.version : void 0,
359
+ buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
142
360
  bundleIdentifier: typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0,
143
- teamId: typeof value.teamId === "string" ? value.teamId : void 0
361
+ teamId: typeof value.teamId === "string" ? value.teamId : void 0,
362
+ appearance: normalizeAppearance(value.appearance)
144
363
  };
145
364
  }
146
365
  function assertBleamConfigFiles(projectRoot) {
147
366
  if (existsSync(path.join(projectRoot, "app.json"))) throw new Error("Bleam projects use app.config.ts; app.json is not supported.");
148
367
  if (!existsSync(path.join(projectRoot, "app.config.ts"))) throw new Error("Missing app.config.ts. Bleam projects must define app.config.ts.");
149
- if (!existsSync(path.join(projectRoot, "app.tsx"))) throw new Error("Missing app.tsx. Bleam projects must define app.tsx.");
368
+ if (!existsSync(path.join(projectRoot, "app", "index.tsx"))) throw new Error("Missing app/index.tsx. Bleam projects must define app/index.tsx.");
369
+ }
370
+ function ensureProjectTsconfig(projectRoot) {
371
+ const tsconfigPath = path.join(projectRoot, "tsconfig.json");
372
+ if (existsSync(tsconfigPath)) return;
373
+ writeFileSync(tsconfigPath, `${JSON.stringify({ extends: "./.bleam/dev/tsconfig.json" }, null, 2)}\n`);
150
374
  }
151
375
  function readProjectConfig(options) {
152
376
  assertBleamConfigFiles(options.projectRoot);
@@ -154,8 +378,10 @@ function readProjectConfig(options) {
154
378
  return {
155
379
  name: config.name,
156
380
  version: config.version,
381
+ buildNumber: config.buildNumber ?? "1",
157
382
  bundleIdentifier: config.bundleIdentifier,
158
383
  teamId: config.teamId,
384
+ appearance: config.appearance ?? normalizeAppearance(void 0),
159
385
  runtimeVersion,
160
386
  sdkVersion: runtimeVersion
161
387
  };
@@ -168,27 +394,51 @@ function nameFromDirectory(projectRoot) {
168
394
  if (!name) return "Bleam App";
169
395
  return name.replace(/\b\w/g, (letter) => letter.toUpperCase());
170
396
  }
397
+ function singleQuotedString(value) {
398
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
399
+ }
171
400
  function createNewProject(options) {
172
401
  const templateRoot = path.join(packageRoot, "templates", "basic");
173
402
  if (!existsSync(templateRoot)) throw new Error(`Missing basic template: ${templateRoot}`);
174
403
  if (existsSync(options.projectRoot) && readdirSync(options.projectRoot).length > 0) throw new Error(`Directory is not empty: ${options.projectRoot}`);
175
404
  mkdirSync(options.projectRoot, { recursive: true });
176
405
  cpSync(templateRoot, options.projectRoot, { recursive: true });
177
- writeFileSync(path.join(options.projectRoot, "app.config.ts"), `export default {\n name: ${JSON.stringify(nameFromDirectory(options.projectRoot))},\n}\n`);
406
+ const name = nameFromDirectory(options.projectRoot);
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`);
409
+ createDevWorkspace(options.projectRoot, {
410
+ name,
411
+ buildNumber: "1",
412
+ appearance: normalizeAppearance(void 0),
413
+ runtimeVersion,
414
+ sdkVersion: runtimeVersion
415
+ });
178
416
  console.log(`Created Bleam project at ${options.projectRoot}`);
179
417
  }
180
418
  function createDevWorkspace(projectRoot, config) {
181
419
  const workspaceRoot = path.join(projectRoot, ".bleam", "dev");
182
420
  mkdirSync(workspaceRoot, { recursive: true });
183
421
  const workspaceNodeModules = path.join(workspaceRoot, "node_modules");
184
- if (!existsSync(workspaceNodeModules)) symlinkSync(path.join(packageRoot, "node_modules"), workspaceNodeModules);
422
+ const dependencyRoot = resolvedDependencyRoot();
423
+ rmSync(workspaceNodeModules, {
424
+ recursive: true,
425
+ force: true
426
+ });
427
+ symlinkSync(dependencyRoot, workspaceNodeModules);
428
+ ensureProjectTsconfig(projectRoot);
185
429
  writeFileSync(path.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: {
186
430
  name: config.name,
187
431
  slug: slugFromName(config.name),
188
432
  version: config.version ?? "0.0.0",
189
433
  orientation: "default",
190
434
  platforms: ["ios"],
191
- 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
+ } }
192
442
  } }, null, 2)}\n`);
193
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`);
194
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`);
@@ -199,6 +449,7 @@ function createDevWorkspace(projectRoot, config) {
199
449
  main: "index.js",
200
450
  dependencies: {
201
451
  "@types/react": packageJson.dependencies?.["@types/react"],
452
+ "@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
202
453
  expo: packageJson.dependencies?.expo,
203
454
  react: packageJson.dependencies?.react,
204
455
  "react-native": packageJson.dependencies?.["react-native"],
@@ -208,10 +459,49 @@ function createDevWorkspace(projectRoot, config) {
208
459
  }, null, 2)}\n`);
209
460
  writeFileSync(path.join(workspaceRoot, "tsconfig.json"), `${JSON.stringify({
210
461
  extends: "bleam/tsconfig",
211
- include: [path.relative(workspaceRoot, path.join(projectRoot, "app.tsx")), path.relative(workspaceRoot, path.join(projectRoot, "app.config.ts"))]
462
+ compilerOptions: { paths: {
463
+ bleam: ["./node_modules/bleam/dist/index"],
464
+ "bleam/*": ["./node_modules/bleam/dist/*"],
465
+ expo: ["./node_modules/expo"],
466
+ "expo/*": ["./node_modules/expo/*"],
467
+ react: ["./node_modules/@types/react"],
468
+ "react/jsx-runtime": ["./node_modules/@types/react/jsx-runtime"],
469
+ "react/jsx-dev-runtime": ["./node_modules/@types/react/jsx-dev-runtime"],
470
+ "react-native": ["./node_modules/react-native"],
471
+ "react-native/*": ["./node_modules/react-native/*"]
472
+ } },
473
+ include: [path.relative(workspaceRoot, path.join(projectRoot, "app")), path.relative(workspaceRoot, path.join(projectRoot, "app.config.ts"))]
212
474
  }, null, 2)}\n`);
213
475
  return workspaceRoot;
214
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
+ }
215
505
  function writeProjectRuntimeConfig(projectRoot, config, origin) {
216
506
  const projectConfigPath = path.join(projectRoot, ".bleam", "project.json");
217
507
  mkdirSync(path.dirname(projectConfigPath), { recursive: true });
@@ -220,28 +510,220 @@ function writeProjectRuntimeConfig(projectRoot, config, origin) {
220
510
  version: config.version,
221
511
  bundleIdentifier: config.bundleIdentifier,
222
512
  teamId: config.teamId,
513
+ appearance: config.appearance,
223
514
  devServer: origin,
224
515
  runtimeVersion: config.runtimeVersion,
225
516
  sdkVersion: config.sdkVersion
226
517
  }, null, 2)}\n`);
227
518
  return projectConfigPath;
228
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
+ }
229
696
  function runtimeCacheRoot() {
230
- return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, runtimePlatform);
697
+ return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
231
698
  }
232
699
  function localDevelopmentRuntimeAppPath() {
233
- 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");
234
706
  }
235
707
  function runtimeReleaseBaseUrl() {
236
708
  return process.env.BLEAM_RUNTIME_RELEASE_BASE_URL ?? "https://github.com/bajedev/bleam-app/releases/download";
237
709
  }
238
710
  function runtimeArchiveName() {
239
- return `bleam-runtime-v${runtimeVersion}-${runtimePlatform}.zip`;
711
+ return `bleam-runtime-v${runtimeVersion}-${artifactPlatform()}.zip`;
240
712
  }
241
713
  function runtimeArchiveUrl() {
242
714
  const tag = encodeURIComponent(`bleam@${runtimeVersion}`);
243
715
  return `${runtimeReleaseBaseUrl()}/${tag}/${runtimeArchiveName()}`;
244
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
+ }
245
727
  function sha256(filePath) {
246
728
  return createHash("sha256").update(readFileSync(filePath)).digest("hex");
247
729
  }
@@ -256,7 +738,8 @@ async function downloadExpectedSha256(url) {
256
738
  return (await response.text()).trim().split(/\s+/)[0];
257
739
  }
258
740
  async function downloadRuntimeArtifact() {
259
- 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();
260
743
  const cacheRoot = runtimeCacheRoot();
261
744
  const cachedAppPath = path.join(cacheRoot, "Bleam.app");
262
745
  if (existsSync(cachedAppPath)) return {
@@ -303,10 +786,11 @@ async function downloadRuntimeArtifact() {
303
786
  };
304
787
  }
305
788
  async function resolveRuntimeArtifact() {
789
+ const runtimePlatform = artifactPlatform();
306
790
  const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
307
791
  if (overrideAppPath) {
308
792
  const appPath = path.resolve(overrideAppPath);
309
- 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()}`);
310
794
  return {
311
795
  appPath,
312
796
  version: runtimeVersion,
@@ -319,7 +803,11 @@ async function resolveRuntimeArtifact() {
319
803
  version: runtimeVersion,
320
804
  platform: runtimePlatform
321
805
  };
322
- 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
+ }
323
811
  }
324
812
  function copyRuntimeClient(projectRoot, runtime) {
325
813
  const clientAppPath = path.join(projectRoot, ".bleam", "client", "Bleam.app");
@@ -331,39 +819,6 @@ function copyRuntimeClient(projectRoot, runtime) {
331
819
  cpSync(runtime.appPath, clientAppPath, { recursive: true });
332
820
  return clientAppPath;
333
821
  }
334
- function waitForDevServer(port, timeoutMs = 12e4) {
335
- return new Promise((resolve, reject) => {
336
- const startedAt = Date.now();
337
- const poll = () => {
338
- const req = http.get(`http://localhost:${port}/status`, (res) => {
339
- let data = "";
340
- res.on("data", (chunk) => {
341
- data += chunk.toString();
342
- });
343
- res.on("end", () => {
344
- if (data.includes("packager-status:running")) {
345
- resolve();
346
- return;
347
- }
348
- retry();
349
- });
350
- });
351
- req.on("error", retry);
352
- req.setTimeout(2e3, () => {
353
- req.destroy();
354
- retry();
355
- });
356
- };
357
- const retry = () => {
358
- if (Date.now() - startedAt > timeoutMs) {
359
- reject(/* @__PURE__ */ new Error(`Bleam dev server did not start within ${timeoutMs / 1e3}s`));
360
- return;
361
- }
362
- setTimeout(poll, 500);
363
- };
364
- poll();
365
- });
366
- }
367
822
  function isBleamManifest(value) {
368
823
  const manifest = value && typeof value === "object" ? value : {};
369
824
  return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
@@ -371,18 +826,138 @@ function isBleamManifest(value) {
371
826
  async function readDevManifest(origin, timeoutMs = 3e4) {
372
827
  const manifestUrl = `${origin}/bleam/manifest.json`;
373
828
  const startedAt = Date.now();
829
+ const spinner = startSpinner("Waiting for Bleam dev server...");
374
830
  while (Date.now() - startedAt <= timeoutMs) {
375
831
  try {
376
832
  const response = await fetch(manifestUrl, { cache: "no-store" });
377
833
  const value = await response.json();
378
- 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
+ }
379
838
  } catch {}
380
839
  await new Promise((resolve) => setTimeout(resolve, 500));
381
840
  }
841
+ spinner?.error("Bleam dev server did not become available");
382
842
  throw new Error(`Bleam manifest did not become available: ${manifestUrl}`);
383
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
+ }
920
+ function formatDuration(ms) {
921
+ return `${(ms / 1e3).toFixed(1)}s`;
922
+ }
923
+ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
924
+ const startedAt = Date.now();
925
+ const requestTimeoutMs = 3e4;
926
+ const spinner = startSpinner("Bundling app...");
927
+ while (Date.now() - startedAt <= timeoutMs) {
928
+ const requestStartedAt = Date.now();
929
+ try {
930
+ const response = await fetch(bundleUrl, {
931
+ cache: "no-store",
932
+ signal: AbortSignal.timeout(requestTimeoutMs)
933
+ });
934
+ await response.body?.cancel();
935
+ if (response.ok) {
936
+ spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
937
+ if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
938
+ return;
939
+ }
940
+ if (spinner) spinner.text = `Bundle request returned ${response.status}, retrying...`;
941
+ else console.log(`Bundle request returned ${response.status}, retrying...`);
942
+ } catch (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
+ }
952
+ }
953
+ await new Promise((resolve) => setTimeout(resolve, 500));
954
+ }
955
+ spinner?.error("Bundle did not complete");
956
+ throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}.\nBundle URL: ${bundleUrl}\n${doctorHint()}`);
957
+ }
384
958
  function openRuntimeClient(appPath, projectConfigPath, manifest) {
385
- spawn("open", [
959
+ const child = spawn("open", [
960
+ "-n",
386
961
  appPath,
387
962
  "--args",
388
963
  "--bleam-project-config",
@@ -391,10 +966,220 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
391
966
  manifest.bundleUrl,
392
967
  "--bleam-title",
393
968
  manifest.name
394
- ], {
395
- detached: true,
396
- stdio: "ignore"
397
- }).unref();
969
+ ], { stdio: "ignore" });
970
+ child.on("error", (error) => {
971
+ console.error(`Failed to open Bleam runtime at ${appPath}: ${error.message}`);
972
+ });
973
+ child.on("exit", (code) => {
974
+ if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
975
+ });
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
+ }
1079
+ async function startInternalMetroServer(options) {
1080
+ process.env.BLEAM_DEV_PLATFORM ??= "macos";
1081
+ const metro = require("@expo/metro/metro");
1082
+ const { loadConfig } = require("@expo/metro/metro-config");
1083
+ const config = await loadConfig({
1084
+ config: path.join(options.workspaceRoot, "metro.config.js"),
1085
+ cwd: options.workspaceRoot,
1086
+ port: options.port
1087
+ });
1088
+ const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
1089
+ watch: true,
1090
+ waitForBundler: true
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
+ });
1111
+ const server = http.createServer((req, res) => {
1112
+ if (req.url?.split("?")[0] === "/status") {
1113
+ res.writeHead(200, {
1114
+ "Cache-Control": "no-store",
1115
+ "Content-Type": "text/plain; charset=utf-8",
1116
+ "X-React-Native-Project-Root": encodeURI(config.projectRoot)
1117
+ });
1118
+ res.end("packager-status:running");
1119
+ return;
1120
+ }
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
+ });
1150
+ });
1151
+ server.timeout = 0;
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
+ });
1164
+ await new Promise((resolve, reject) => {
1165
+ server.on("error", reject);
1166
+ server.listen(options.port, "localhost", () => resolve());
1167
+ });
1168
+ server.on("error", (error) => {
1169
+ console.error(error instanceof Error ? error.message : String(error));
1170
+ });
1171
+ console.log(`Bleam dev server running at http://localhost:${options.port}`);
1172
+ const close = () => {
1173
+ setTimeout(() => process.exit(0), 1e3).unref();
1174
+ messageServer.close();
1175
+ for (const client of messageClients) client.terminate();
1176
+ server.close(() => {
1177
+ end();
1178
+ process.exit(0);
1179
+ });
1180
+ };
1181
+ process.on("SIGINT", close);
1182
+ process.on("SIGTERM", close);
398
1183
  }
399
1184
  async function startDevServer(options) {
400
1185
  const config = readProjectConfig(options);
@@ -404,10 +1189,10 @@ async function startDevServer(options) {
404
1189
  const origin = `http://localhost:${options.port}`;
405
1190
  const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
406
1191
  const child = spawn(process.execPath, [
407
- require.resolve("expo/bin/cli"),
408
- "start",
1192
+ fileURLToPath(import.meta.url),
1193
+ "internal:metro",
1194
+ "--workspace-root",
409
1195
  workspaceRoot,
410
- "--localhost",
411
1196
  "--port",
412
1197
  String(options.port)
413
1198
  ], {
@@ -419,11 +1204,12 @@ async function startDevServer(options) {
419
1204
  BLEAM_DEV_NAME: config.name,
420
1205
  BLEAM_DEV_ORIGIN: origin,
421
1206
  BLEAM_DEV_PORT: String(options.port),
1207
+ BLEAM_DEV_PLATFORM: "macos",
422
1208
  BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
423
1209
  BLEAM_DEV_SDK_VERSION: config.sdkVersion,
424
1210
  NODE_PATH: [
425
1211
  path.join(options.projectRoot, "node_modules"),
426
- path.join(packageRoot, "node_modules"),
1212
+ resolvedDependencyRoot(),
427
1213
  path.dirname(path.dirname(require.resolve("expo/package.json"))),
428
1214
  path.dirname(path.dirname(require.resolve("react/package.json"))),
429
1215
  path.dirname(path.dirname(require.resolve("react-native/package.json"))),
@@ -431,15 +1217,51 @@ async function startDevServer(options) {
431
1217
  ].filter(Boolean).join(path.delimiter)
432
1218
  }
433
1219
  });
1220
+ let cleanupInteractiveControls = () => {};
1221
+ let stopping = false;
434
1222
  const stop = (signal) => {
1223
+ if (stopping) return;
1224
+ stopping = true;
1225
+ cleanupInteractiveControls();
435
1226
  child.kill(signal);
1227
+ setTimeout(() => process.exit(0), 2e3).unref();
436
1228
  };
437
1229
  process.on("SIGINT", () => stop("SIGINT"));
438
1230
  process.on("SIGTERM", () => stop("SIGTERM"));
439
- child.on("exit", (code) => process.exit(code ?? 0));
440
- await waitForDevServer(options.port);
441
- const manifest = await readDevManifest(origin);
442
- if (process.env.BLEAM_NO_OPEN !== "1") openRuntimeClient(clientAppPath, projectConfigPath, manifest);
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
+ });
1241
+ try {
1242
+ const manifest = await readDevManifest(origin);
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 {
1251
+ await waitForDevBundle(manifest.bundleUrl);
1252
+ console.log("Opening Bleam app...");
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.");
1259
+ }
1260
+ } catch (error) {
1261
+ cleanupInteractiveControls();
1262
+ child.kill("SIGTERM");
1263
+ throw error;
1264
+ }
443
1265
  }
444
1266
  async function main(argv = process.argv.slice(2)) {
445
1267
  const [command, ...args] = argv;
@@ -452,7 +1274,8 @@ async function main(argv = process.argv.slice(2)) {
452
1274
  createNewProject(options);
453
1275
  if (options.run) await startDevServer({
454
1276
  projectRoot: options.projectRoot,
455
- port: 8081
1277
+ port: 8081,
1278
+ run: true
456
1279
  });
457
1280
  return;
458
1281
  }
@@ -460,6 +1283,30 @@ async function main(argv = process.argv.slice(2)) {
460
1283
  await startDevServer(parseDevOptions(args));
461
1284
  return;
462
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
+ }
1306
+ if (command === "internal:metro") {
1307
+ await startInternalMetroServer(parseInternalMetroOptions(args));
1308
+ return;
1309
+ }
463
1310
  throw new Error(`Unknown command: ${command}`);
464
1311
  }
465
1312
  main().catch((error) => {
@@ -468,5 +1315,4 @@ main().catch((error) => {
468
1315
  });
469
1316
 
470
1317
  //#endregion
471
- export { };
472
- //# sourceMappingURL=cli.js.map
1318
+ export { };