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.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,12 +157,139 @@ 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
+ }
262
+ function parseInternalMetroOptions(argv) {
263
+ let workspaceRoot;
264
+ let port = 8081;
265
+ for (let index = 0; index < argv.length; index += 1) {
266
+ const arg = argv[index];
267
+ if (arg === "--workspace-root") {
268
+ workspaceRoot = node_path.default.resolve(invocationRoot(), argv[++index] ?? "");
269
+ continue;
270
+ }
271
+ if (arg === "--port") {
272
+ port = Number(argv[++index]);
273
+ continue;
274
+ }
275
+ if (arg.startsWith("--port=")) {
276
+ port = Number(arg.slice(7));
277
+ continue;
278
+ }
279
+ throw new Error(`Unknown internal option: ${arg}`);
280
+ }
281
+ if (!workspaceRoot) throw new Error("Missing --workspace-root");
282
+ if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
283
+ return {
284
+ workspaceRoot,
285
+ port
286
+ };
287
+ }
88
288
  function isBleamProjectRoot(projectRoot) {
89
- return (0, node_fs.existsSync)(node_path.default.join(projectRoot, "app.config.ts")) && (0, node_fs.existsSync)(node_path.default.join(projectRoot, "app.tsx"));
289
+ return (0, node_fs.existsSync)(node_path.default.join(projectRoot, "app.config.ts")) && (0, node_fs.existsSync)(node_path.default.join(projectRoot, "app", "index.tsx"));
290
+ }
291
+ function resolvedDependencyRoot() {
292
+ return node_path.default.dirname(node_path.default.dirname(require$1.resolve("react-native/package.json")));
90
293
  }
91
294
  function discoverBleamProjectRoot(searchRoot) {
92
295
  const ignoredNames = new Set([
@@ -119,6 +322,23 @@ function assertStringField(value, field) {
119
322
  if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
120
323
  return value;
121
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
+ }
122
342
  function loadAppConfig(projectRoot) {
123
343
  const configPath = node_path.default.join(projectRoot, "app.config.ts");
124
344
  const loader = `
@@ -138,7 +358,7 @@ function loadAppConfig(projectRoot) {
138
358
  ...process.env,
139
359
  NODE_PATH: [
140
360
  node_path.default.join(projectRoot, "node_modules"),
141
- node_path.default.join(packageRoot, "node_modules"),
361
+ resolvedDependencyRoot(),
142
362
  process.env.NODE_PATH
143
363
  ].filter(Boolean).join(node_path.default.delimiter)
144
364
  }
@@ -148,14 +368,21 @@ function loadAppConfig(projectRoot) {
148
368
  return {
149
369
  name: assertStringField(value.name, "name"),
150
370
  version: typeof value.version === "string" ? value.version : void 0,
371
+ buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
151
372
  bundleIdentifier: typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0,
152
- teamId: typeof value.teamId === "string" ? value.teamId : void 0
373
+ teamId: typeof value.teamId === "string" ? value.teamId : void 0,
374
+ appearance: normalizeAppearance(value.appearance)
153
375
  };
154
376
  }
155
377
  function assertBleamConfigFiles(projectRoot) {
156
378
  if ((0, node_fs.existsSync)(node_path.default.join(projectRoot, "app.json"))) throw new Error("Bleam projects use app.config.ts; app.json is not supported.");
157
379
  if (!(0, node_fs.existsSync)(node_path.default.join(projectRoot, "app.config.ts"))) throw new Error("Missing app.config.ts. Bleam projects must define app.config.ts.");
158
- if (!(0, node_fs.existsSync)(node_path.default.join(projectRoot, "app.tsx"))) throw new Error("Missing app.tsx. Bleam projects must define app.tsx.");
380
+ if (!(0, node_fs.existsSync)(node_path.default.join(projectRoot, "app", "index.tsx"))) throw new Error("Missing app/index.tsx. Bleam projects must define app/index.tsx.");
381
+ }
382
+ function ensureProjectTsconfig(projectRoot) {
383
+ const tsconfigPath = node_path.default.join(projectRoot, "tsconfig.json");
384
+ if ((0, node_fs.existsSync)(tsconfigPath)) return;
385
+ (0, node_fs.writeFileSync)(tsconfigPath, `${JSON.stringify({ extends: "./.bleam/dev/tsconfig.json" }, null, 2)}\n`);
159
386
  }
160
387
  function readProjectConfig(options) {
161
388
  assertBleamConfigFiles(options.projectRoot);
@@ -163,8 +390,10 @@ function readProjectConfig(options) {
163
390
  return {
164
391
  name: config.name,
165
392
  version: config.version,
393
+ buildNumber: config.buildNumber ?? "1",
166
394
  bundleIdentifier: config.bundleIdentifier,
167
395
  teamId: config.teamId,
396
+ appearance: config.appearance ?? normalizeAppearance(void 0),
168
397
  runtimeVersion,
169
398
  sdkVersion: runtimeVersion
170
399
  };
@@ -177,27 +406,51 @@ function nameFromDirectory(projectRoot) {
177
406
  if (!name) return "Bleam App";
178
407
  return name.replace(/\b\w/g, (letter) => letter.toUpperCase());
179
408
  }
409
+ function singleQuotedString(value) {
410
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
411
+ }
180
412
  function createNewProject(options) {
181
413
  const templateRoot = node_path.default.join(packageRoot, "templates", "basic");
182
414
  if (!(0, node_fs.existsSync)(templateRoot)) throw new Error(`Missing basic template: ${templateRoot}`);
183
415
  if ((0, node_fs.existsSync)(options.projectRoot) && (0, node_fs.readdirSync)(options.projectRoot).length > 0) throw new Error(`Directory is not empty: ${options.projectRoot}`);
184
416
  (0, node_fs.mkdirSync)(options.projectRoot, { recursive: true });
185
417
  (0, node_fs.cpSync)(templateRoot, options.projectRoot, { recursive: true });
186
- (0, node_fs.writeFileSync)(node_path.default.join(options.projectRoot, "app.config.ts"), `export default {\n name: ${JSON.stringify(nameFromDirectory(options.projectRoot))},\n}\n`);
418
+ const name = nameFromDirectory(options.projectRoot);
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`);
421
+ createDevWorkspace(options.projectRoot, {
422
+ name,
423
+ buildNumber: "1",
424
+ appearance: normalizeAppearance(void 0),
425
+ runtimeVersion,
426
+ sdkVersion: runtimeVersion
427
+ });
187
428
  console.log(`Created Bleam project at ${options.projectRoot}`);
188
429
  }
189
430
  function createDevWorkspace(projectRoot, config) {
190
431
  const workspaceRoot = node_path.default.join(projectRoot, ".bleam", "dev");
191
432
  (0, node_fs.mkdirSync)(workspaceRoot, { recursive: true });
192
433
  const workspaceNodeModules = node_path.default.join(workspaceRoot, "node_modules");
193
- if (!(0, node_fs.existsSync)(workspaceNodeModules)) (0, node_fs.symlinkSync)(node_path.default.join(packageRoot, "node_modules"), workspaceNodeModules);
434
+ const dependencyRoot = resolvedDependencyRoot();
435
+ (0, node_fs.rmSync)(workspaceNodeModules, {
436
+ recursive: true,
437
+ force: true
438
+ });
439
+ (0, node_fs.symlinkSync)(dependencyRoot, workspaceNodeModules);
440
+ ensureProjectTsconfig(projectRoot);
194
441
  (0, node_fs.writeFileSync)(node_path.default.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: {
195
442
  name: config.name,
196
443
  slug: slugFromName(config.name),
197
444
  version: config.version ?? "0.0.0",
198
445
  orientation: "default",
199
446
  platforms: ["ios"],
200
- 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
+ } }
201
454
  } }, null, 2)}\n`);
202
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`);
203
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`);
@@ -208,6 +461,7 @@ function createDevWorkspace(projectRoot, config) {
208
461
  main: "index.js",
209
462
  dependencies: {
210
463
  "@types/react": packageJson.dependencies?.["@types/react"],
464
+ "@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
211
465
  expo: packageJson.dependencies?.expo,
212
466
  react: packageJson.dependencies?.react,
213
467
  "react-native": packageJson.dependencies?.["react-native"],
@@ -217,10 +471,49 @@ function createDevWorkspace(projectRoot, config) {
217
471
  }, null, 2)}\n`);
218
472
  (0, node_fs.writeFileSync)(node_path.default.join(workspaceRoot, "tsconfig.json"), `${JSON.stringify({
219
473
  extends: "bleam/tsconfig",
220
- include: [node_path.default.relative(workspaceRoot, node_path.default.join(projectRoot, "app.tsx")), node_path.default.relative(workspaceRoot, node_path.default.join(projectRoot, "app.config.ts"))]
474
+ compilerOptions: { paths: {
475
+ bleam: ["./node_modules/bleam/dist/index"],
476
+ "bleam/*": ["./node_modules/bleam/dist/*"],
477
+ expo: ["./node_modules/expo"],
478
+ "expo/*": ["./node_modules/expo/*"],
479
+ react: ["./node_modules/@types/react"],
480
+ "react/jsx-runtime": ["./node_modules/@types/react/jsx-runtime"],
481
+ "react/jsx-dev-runtime": ["./node_modules/@types/react/jsx-dev-runtime"],
482
+ "react-native": ["./node_modules/react-native"],
483
+ "react-native/*": ["./node_modules/react-native/*"]
484
+ } },
485
+ include: [node_path.default.relative(workspaceRoot, node_path.default.join(projectRoot, "app")), node_path.default.relative(workspaceRoot, node_path.default.join(projectRoot, "app.config.ts"))]
221
486
  }, null, 2)}\n`);
222
487
  return workspaceRoot;
223
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
+ }
224
517
  function writeProjectRuntimeConfig(projectRoot, config, origin) {
225
518
  const projectConfigPath = node_path.default.join(projectRoot, ".bleam", "project.json");
226
519
  (0, node_fs.mkdirSync)(node_path.default.dirname(projectConfigPath), { recursive: true });
@@ -229,28 +522,220 @@ function writeProjectRuntimeConfig(projectRoot, config, origin) {
229
522
  version: config.version,
230
523
  bundleIdentifier: config.bundleIdentifier,
231
524
  teamId: config.teamId,
525
+ appearance: config.appearance,
232
526
  devServer: origin,
233
527
  runtimeVersion: config.runtimeVersion,
234
528
  sdkVersion: config.sdkVersion
235
529
  }, null, 2)}\n`);
236
530
  return projectConfigPath;
237
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
+ }
238
708
  function runtimeCacheRoot() {
239
- 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());
240
710
  }
241
711
  function localDevelopmentRuntimeAppPath() {
242
- 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");
243
718
  }
244
719
  function runtimeReleaseBaseUrl() {
245
720
  return process.env.BLEAM_RUNTIME_RELEASE_BASE_URL ?? "https://github.com/bajedev/bleam-app/releases/download";
246
721
  }
247
722
  function runtimeArchiveName() {
248
- return `bleam-runtime-v${runtimeVersion}-${runtimePlatform}.zip`;
723
+ return `bleam-runtime-v${runtimeVersion}-${artifactPlatform()}.zip`;
249
724
  }
250
725
  function runtimeArchiveUrl() {
251
726
  const tag = encodeURIComponent(`bleam@${runtimeVersion}`);
252
727
  return `${runtimeReleaseBaseUrl()}/${tag}/${runtimeArchiveName()}`;
253
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
+ }
254
739
  function sha256(filePath) {
255
740
  return (0, node_crypto.createHash)("sha256").update((0, node_fs.readFileSync)(filePath)).digest("hex");
256
741
  }
@@ -265,7 +750,8 @@ async function downloadExpectedSha256(url) {
265
750
  return (await response.text()).trim().split(/\s+/)[0];
266
751
  }
267
752
  async function downloadRuntimeArtifact() {
268
- 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();
269
755
  const cacheRoot = runtimeCacheRoot();
270
756
  const cachedAppPath = node_path.default.join(cacheRoot, "Bleam.app");
271
757
  if ((0, node_fs.existsSync)(cachedAppPath)) return {
@@ -312,10 +798,11 @@ async function downloadRuntimeArtifact() {
312
798
  };
313
799
  }
314
800
  async function resolveRuntimeArtifact() {
801
+ const runtimePlatform = artifactPlatform();
315
802
  const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
316
803
  if (overrideAppPath) {
317
804
  const appPath = node_path.default.resolve(overrideAppPath);
318
- 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()}`);
319
806
  return {
320
807
  appPath,
321
808
  version: runtimeVersion,
@@ -328,7 +815,11 @@ async function resolveRuntimeArtifact() {
328
815
  version: runtimeVersion,
329
816
  platform: runtimePlatform
330
817
  };
331
- 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
+ }
332
823
  }
333
824
  function copyRuntimeClient(projectRoot, runtime) {
334
825
  const clientAppPath = node_path.default.join(projectRoot, ".bleam", "client", "Bleam.app");
@@ -340,39 +831,6 @@ function copyRuntimeClient(projectRoot, runtime) {
340
831
  (0, node_fs.cpSync)(runtime.appPath, clientAppPath, { recursive: true });
341
832
  return clientAppPath;
342
833
  }
343
- function waitForDevServer(port, timeoutMs = 12e4) {
344
- return new Promise((resolve, reject) => {
345
- const startedAt = Date.now();
346
- const poll = () => {
347
- const req = node_http.default.get(`http://localhost:${port}/status`, (res) => {
348
- let data = "";
349
- res.on("data", (chunk) => {
350
- data += chunk.toString();
351
- });
352
- res.on("end", () => {
353
- if (data.includes("packager-status:running")) {
354
- resolve();
355
- return;
356
- }
357
- retry();
358
- });
359
- });
360
- req.on("error", retry);
361
- req.setTimeout(2e3, () => {
362
- req.destroy();
363
- retry();
364
- });
365
- };
366
- const retry = () => {
367
- if (Date.now() - startedAt > timeoutMs) {
368
- reject(/* @__PURE__ */ new Error(`Bleam dev server did not start within ${timeoutMs / 1e3}s`));
369
- return;
370
- }
371
- setTimeout(poll, 500);
372
- };
373
- poll();
374
- });
375
- }
376
834
  function isBleamManifest(value) {
377
835
  const manifest = value && typeof value === "object" ? value : {};
378
836
  return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
@@ -380,18 +838,138 @@ function isBleamManifest(value) {
380
838
  async function readDevManifest(origin, timeoutMs = 3e4) {
381
839
  const manifestUrl = `${origin}/bleam/manifest.json`;
382
840
  const startedAt = Date.now();
841
+ const spinner = startSpinner("Waiting for Bleam dev server...");
383
842
  while (Date.now() - startedAt <= timeoutMs) {
384
843
  try {
385
844
  const response = await fetch(manifestUrl, { cache: "no-store" });
386
845
  const value = await response.json();
387
- 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
+ }
388
850
  } catch {}
389
851
  await new Promise((resolve) => setTimeout(resolve, 500));
390
852
  }
853
+ spinner?.error("Bleam dev server did not become available");
391
854
  throw new Error(`Bleam manifest did not become available: ${manifestUrl}`);
392
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
+ }
932
+ function formatDuration(ms) {
933
+ return `${(ms / 1e3).toFixed(1)}s`;
934
+ }
935
+ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
936
+ const startedAt = Date.now();
937
+ const requestTimeoutMs = 3e4;
938
+ const spinner = startSpinner("Bundling app...");
939
+ while (Date.now() - startedAt <= timeoutMs) {
940
+ const requestStartedAt = Date.now();
941
+ try {
942
+ const response = await fetch(bundleUrl, {
943
+ cache: "no-store",
944
+ signal: AbortSignal.timeout(requestTimeoutMs)
945
+ });
946
+ await response.body?.cancel();
947
+ if (response.ok) {
948
+ spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
949
+ if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
950
+ return;
951
+ }
952
+ if (spinner) spinner.text = `Bundle request returned ${response.status}, retrying...`;
953
+ else console.log(`Bundle request returned ${response.status}, retrying...`);
954
+ } catch (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
+ }
964
+ }
965
+ await new Promise((resolve) => setTimeout(resolve, 500));
966
+ }
967
+ spinner?.error("Bundle did not complete");
968
+ throw new Error(`Bleam bundle did not complete within ${formatDuration(timeoutMs)}.\nBundle URL: ${bundleUrl}\n${doctorHint()}`);
969
+ }
393
970
  function openRuntimeClient(appPath, projectConfigPath, manifest) {
394
- (0, node_child_process.spawn)("open", [
971
+ const child = (0, node_child_process.spawn)("open", [
972
+ "-n",
395
973
  appPath,
396
974
  "--args",
397
975
  "--bleam-project-config",
@@ -400,10 +978,220 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
400
978
  manifest.bundleUrl,
401
979
  "--bleam-title",
402
980
  manifest.name
403
- ], {
404
- detached: true,
405
- stdio: "ignore"
406
- }).unref();
981
+ ], { stdio: "ignore" });
982
+ child.on("error", (error) => {
983
+ console.error(`Failed to open Bleam runtime at ${appPath}: ${error.message}`);
984
+ });
985
+ child.on("exit", (code) => {
986
+ if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
987
+ });
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
+ }
1091
+ async function startInternalMetroServer(options) {
1092
+ process.env.BLEAM_DEV_PLATFORM ??= "macos";
1093
+ const metro = require$1("@expo/metro/metro");
1094
+ const { loadConfig } = require$1("@expo/metro/metro-config");
1095
+ const config = await loadConfig({
1096
+ config: node_path.default.join(options.workspaceRoot, "metro.config.js"),
1097
+ cwd: options.workspaceRoot,
1098
+ port: options.port
1099
+ });
1100
+ const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
1101
+ watch: true,
1102
+ waitForBundler: true
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
+ });
1123
+ const server = node_http.default.createServer((req, res) => {
1124
+ if (req.url?.split("?")[0] === "/status") {
1125
+ res.writeHead(200, {
1126
+ "Cache-Control": "no-store",
1127
+ "Content-Type": "text/plain; charset=utf-8",
1128
+ "X-React-Native-Project-Root": encodeURI(config.projectRoot)
1129
+ });
1130
+ res.end("packager-status:running");
1131
+ return;
1132
+ }
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
+ });
1162
+ });
1163
+ server.timeout = 0;
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
+ });
1176
+ await new Promise((resolve, reject) => {
1177
+ server.on("error", reject);
1178
+ server.listen(options.port, "localhost", () => resolve());
1179
+ });
1180
+ server.on("error", (error) => {
1181
+ console.error(error instanceof Error ? error.message : String(error));
1182
+ });
1183
+ console.log(`Bleam dev server running at http://localhost:${options.port}`);
1184
+ const close = () => {
1185
+ setTimeout(() => process.exit(0), 1e3).unref();
1186
+ messageServer.close();
1187
+ for (const client of messageClients) client.terminate();
1188
+ server.close(() => {
1189
+ end();
1190
+ process.exit(0);
1191
+ });
1192
+ };
1193
+ process.on("SIGINT", close);
1194
+ process.on("SIGTERM", close);
407
1195
  }
408
1196
  async function startDevServer(options) {
409
1197
  const config = readProjectConfig(options);
@@ -413,10 +1201,10 @@ async function startDevServer(options) {
413
1201
  const origin = `http://localhost:${options.port}`;
414
1202
  const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
415
1203
  const child = (0, node_child_process.spawn)(process.execPath, [
416
- require$1.resolve("expo/bin/cli"),
417
- "start",
1204
+ (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href),
1205
+ "internal:metro",
1206
+ "--workspace-root",
418
1207
  workspaceRoot,
419
- "--localhost",
420
1208
  "--port",
421
1209
  String(options.port)
422
1210
  ], {
@@ -428,11 +1216,12 @@ async function startDevServer(options) {
428
1216
  BLEAM_DEV_NAME: config.name,
429
1217
  BLEAM_DEV_ORIGIN: origin,
430
1218
  BLEAM_DEV_PORT: String(options.port),
1219
+ BLEAM_DEV_PLATFORM: "macos",
431
1220
  BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
432
1221
  BLEAM_DEV_SDK_VERSION: config.sdkVersion,
433
1222
  NODE_PATH: [
434
1223
  node_path.default.join(options.projectRoot, "node_modules"),
435
- node_path.default.join(packageRoot, "node_modules"),
1224
+ resolvedDependencyRoot(),
436
1225
  node_path.default.dirname(node_path.default.dirname(require$1.resolve("expo/package.json"))),
437
1226
  node_path.default.dirname(node_path.default.dirname(require$1.resolve("react/package.json"))),
438
1227
  node_path.default.dirname(node_path.default.dirname(require$1.resolve("react-native/package.json"))),
@@ -440,15 +1229,51 @@ async function startDevServer(options) {
440
1229
  ].filter(Boolean).join(node_path.default.delimiter)
441
1230
  }
442
1231
  });
1232
+ let cleanupInteractiveControls = () => {};
1233
+ let stopping = false;
443
1234
  const stop = (signal) => {
1235
+ if (stopping) return;
1236
+ stopping = true;
1237
+ cleanupInteractiveControls();
444
1238
  child.kill(signal);
1239
+ setTimeout(() => process.exit(0), 2e3).unref();
445
1240
  };
446
1241
  process.on("SIGINT", () => stop("SIGINT"));
447
1242
  process.on("SIGTERM", () => stop("SIGTERM"));
448
- child.on("exit", (code) => process.exit(code ?? 0));
449
- await waitForDevServer(options.port);
450
- const manifest = await readDevManifest(origin);
451
- if (process.env.BLEAM_NO_OPEN !== "1") openRuntimeClient(clientAppPath, projectConfigPath, manifest);
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
+ });
1253
+ try {
1254
+ const manifest = await readDevManifest(origin);
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 {
1263
+ await waitForDevBundle(manifest.bundleUrl);
1264
+ console.log("Opening Bleam app...");
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.");
1271
+ }
1272
+ } catch (error) {
1273
+ cleanupInteractiveControls();
1274
+ child.kill("SIGTERM");
1275
+ throw error;
1276
+ }
452
1277
  }
453
1278
  async function main(argv = process.argv.slice(2)) {
454
1279
  const [command, ...args] = argv;
@@ -461,7 +1286,8 @@ async function main(argv = process.argv.slice(2)) {
461
1286
  createNewProject(options);
462
1287
  if (options.run) await startDevServer({
463
1288
  projectRoot: options.projectRoot,
464
- port: 8081
1289
+ port: 8081,
1290
+ run: true
465
1291
  });
466
1292
  return;
467
1293
  }
@@ -469,6 +1295,30 @@ async function main(argv = process.argv.slice(2)) {
469
1295
  await startDevServer(parseDevOptions(args));
470
1296
  return;
471
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
+ }
1318
+ if (command === "internal:metro") {
1319
+ await startInternalMetroServer(parseInternalMetroOptions(args));
1320
+ return;
1321
+ }
472
1322
  throw new Error(`Unknown command: ${command}`);
473
1323
  }
474
1324
  main().catch((error) => {