bleam 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/ai.cjs +3984 -25
  2. package/dist/ai.d.cts +503 -20
  3. package/dist/ai.d.ts +503 -20
  4. package/dist/ai.js +3946 -26
  5. package/dist/animated.cjs +1 -1
  6. package/dist/animated.js +1 -1
  7. package/dist/app.cjs +92 -0
  8. package/dist/app.js +92 -0
  9. package/dist/cli.cjs +653 -86
  10. package/dist/cli.d.cts +89 -1
  11. package/dist/cli.d.ts +89 -1
  12. package/dist/cli.js +654 -88
  13. package/dist/{config-ClDGHNb3.d.ts → config-Cms0rvqg.d.ts} +1 -0
  14. package/dist/{config-D3gPaVMb.d.cts → config-CufOVJV3.d.cts} +1 -0
  15. package/dist/config.d.cts +1 -1
  16. package/dist/config.d.ts +1 -1
  17. package/dist/crypto.d.cts +0 -2
  18. package/dist/crypto.d.ts +1 -1
  19. package/dist/dev-protocol.d.cts +14 -1
  20. package/dist/dev-protocol.d.ts +14 -1
  21. package/dist/files-Bo7h9fik.cjs +468 -0
  22. package/dist/files-Ds1wT8C2.js +445 -0
  23. package/dist/files-DwA7pzr3.d.cts +99 -0
  24. package/dist/files-VrkQlKIT.d.ts +99 -0
  25. package/dist/files.cjs +7 -0
  26. package/dist/files.d.cts +2 -0
  27. package/dist/files.d.ts +2 -0
  28. package/dist/files.js +4 -0
  29. package/dist/index.d.cts +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/native-sqlite-xcGdamRD.js +64 -0
  32. package/dist/native-sqlite-yQLD5s9i.cjs +66 -0
  33. package/dist/platform.cjs +27 -0
  34. package/dist/platform.d.cts +39 -0
  35. package/dist/platform.d.ts +39 -0
  36. package/dist/platform.js +21 -0
  37. package/dist/runtime.d.cts +1 -6
  38. package/dist/runtime.d.ts +1 -6
  39. package/dist/{schema-ipsECAso.d.ts → schema-DOOjfXvs.d.ts} +1 -1
  40. package/dist/{schema-BOuUHcqo.d.cts → schema-ENSMX_1t.d.cts} +1 -1
  41. package/dist/schema.cjs +1 -1
  42. package/dist/schema.d.cts +1 -1
  43. package/dist/schema.d.ts +1 -1
  44. package/dist/schema.js +1 -1
  45. package/dist/state-Bx0VlTlO.cjs +852 -0
  46. package/dist/state-CAwe-Vw1.js +767 -0
  47. package/dist/state.cjs +17 -763
  48. package/dist/state.d.cts +107 -77
  49. package/dist/state.d.ts +107 -77
  50. package/dist/state.js +4 -745
  51. package/dist/ui-1WepaMS4.d.cts +92 -0
  52. package/dist/ui-234Plg7Z.d.cts +92 -0
  53. package/dist/ui-BJmXhz9Q.d.ts +92 -0
  54. package/dist/ui-Dd7SXdbg.d.cts +92 -0
  55. package/dist/ui-Ds88eETu.d.ts +92 -0
  56. package/dist/ui-TaqnB5SP.d.ts +92 -0
  57. package/dist/ui-WQvnXL0W.d.ts +92 -0
  58. package/dist/ui.cjs +286 -35
  59. package/dist/ui.d.cts +2 -30
  60. package/dist/ui.d.ts +2 -30
  61. package/dist/ui.js +283 -36
  62. package/dist/window.cjs +596 -0
  63. package/dist/window.d.cts +63 -0
  64. package/dist/window.d.ts +63 -0
  65. package/dist/window.js +589 -0
  66. package/package.json +16 -14
  67. package/templates/basic/app/index.tsx +8 -5
  68. package/templates/foundation-models/app/index.tsx +32 -0
  69. package/templates/foundation-models/app.config.ts +5 -0
  70. package/templates/image-generation/app/index.tsx +39 -0
  71. package/templates/image-generation/app.config.ts +5 -0
  72. package/templates/state/app/index.tsx +30 -0
  73. package/templates/state/app.config.ts +5 -0
  74. package/dist/chrome.cjs +0 -265
  75. package/dist/chrome.d.cts +0 -19
  76. package/dist/chrome.d.ts +0 -19
  77. package/dist/chrome.js +0 -262
  78. package/dist/fs.cjs +0 -13
  79. package/dist/fs.d.cts +0 -8
  80. package/dist/fs.d.ts +0 -8
  81. package/dist/fs.js +0 -12
  82. package/dist/native-macos-D6p2SmUj.js +0 -20
  83. package/dist/native-macos-DLwo_ii9.cjs +0 -31
  84. package/dist/native-runtime-CbPzrs8j.d.cts +0 -33
  85. package/dist/native-runtime-DxHSrsTX.d.ts +0 -33
  86. package/dist/native.cjs +0 -135
  87. package/dist/native.d.cts +0 -34
  88. package/dist/native.d.ts +0 -34
  89. package/dist/native.js +0 -135
  90. /package/dist/{schema-DVmoD9JL.js → schema-Bo5Jvqus.js} +0 -0
  91. /package/dist/{schema-bxeeCyLn.cjs → schema-rQ13mrpD.cjs} +0 -0
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import { execFileSync, spawn, spawnSync } from "node:child_process";
4
- import { createHash } from "node:crypto";
5
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { appendFileSync, 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";
@@ -16,6 +16,7 @@ const require = createRequire(import.meta.url);
16
16
  const packageRoot = path.dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
17
17
  const packageJson = require("../package.json");
18
18
  const runtimeVersion = packageJson.version;
19
+ const starterAppName = "Bleam Basic";
19
20
  function artifactPlatform() {
20
21
  return `macos-${process.arch}`;
21
22
  }
@@ -73,16 +74,52 @@ function printUsage() {
73
74
  console.log(`Usage: bleam <command>
74
75
 
75
76
  Commands:
76
- new [dir] [--run] Create a new Bleam project
77
- dev [dir] [--port <port>] [--run]
77
+ new <dir> [--template <name>]
78
+ Create and run a Bleam project
79
+ dev [dir] [--port <port>] [--no-run] [--clean]
78
80
  Start a Bleam dev server
79
- build [dir] [--output <dir>]
80
- Build App Store-ready native archives
81
+ build [dir] [--output <dir>] [--clean]
82
+ Export standalone native archives
83
+ install [dir] [--output <dir>] [--clean]
84
+ Build and install a standalone macOS app
85
+ publish ota [dir] [--service-url <url>]
86
+ Publish an OTA update to the local update service
87
+ publish platform [dir] [--service-url <url>]
88
+ Build and publish a platform update locally
81
89
  doctor [dir] Check the local Bleam environment
90
+ typecheck [dir] Typecheck project files
82
91
  check [dir] [--write] Format, lint, and organize imports
83
92
  format [dir] [--check] Format project files
84
93
  lint [dir] Lint project files`);
85
94
  }
95
+ function parsePublishOtaOptions(argv) {
96
+ const root = invocationRoot();
97
+ let projectRoot = root;
98
+ let hasProjectRootArg = false;
99
+ let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? "http://127.0.0.1:8787";
100
+ for (let index = 0; index < argv.length; index += 1) {
101
+ const arg = argv[index];
102
+ if (arg === "--service-url") {
103
+ serviceUrl = argv[++index] ?? "";
104
+ continue;
105
+ }
106
+ if (arg.startsWith("--service-url=")) {
107
+ serviceUrl = arg.slice(14);
108
+ continue;
109
+ }
110
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
111
+ if (hasProjectRootArg) throw new Error("Only one project directory may be specified");
112
+ projectRoot = path.resolve(root, arg);
113
+ hasProjectRootArg = true;
114
+ }
115
+ const url = new URL(serviceUrl);
116
+ if (url.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Local publishing requires an http://localhost service URL");
117
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
118
+ return {
119
+ projectRoot: canonicalPath(projectRoot),
120
+ serviceUrl: url.origin
121
+ };
122
+ }
86
123
  function invocationRoot() {
87
124
  const pwd = process.env.PWD;
88
125
  return pwd && path.isAbsolute(pwd) ? pwd : process.cwd();
@@ -95,7 +132,8 @@ function parseDevOptions(argv) {
95
132
  const root = invocationRoot();
96
133
  let projectRoot = root;
97
134
  let port = 8081;
98
- let run = false;
135
+ let run = true;
136
+ let clean = false;
99
137
  let hasProjectRootArg = false;
100
138
  for (let index = 0; index < argv.length; index += 1) {
101
139
  const arg = argv[index];
@@ -112,6 +150,14 @@ function parseDevOptions(argv) {
112
150
  run = true;
113
151
  continue;
114
152
  }
153
+ if (arg === "--no-run") {
154
+ run = false;
155
+ continue;
156
+ }
157
+ if (arg === "--clean" || arg === "-c") {
158
+ clean = true;
159
+ continue;
160
+ }
115
161
  if (arg === "--help" || arg === "-h") {
116
162
  printUsage();
117
163
  process.exit(0);
@@ -125,36 +171,55 @@ function parseDevOptions(argv) {
125
171
  return {
126
172
  projectRoot: canonicalPath(projectRoot),
127
173
  port,
128
- run
174
+ run,
175
+ clean
129
176
  };
130
177
  }
131
178
  function parseNewOptions(argv) {
132
179
  const root = invocationRoot();
133
- let projectRoot = root;
134
- let run = false;
135
- for (const arg of argv) {
136
- if (arg === "--run") {
137
- run = true;
138
- continue;
139
- }
180
+ let projectRoot;
181
+ let template = "basic";
182
+ for (let index = 0; index < argv.length; index += 1) {
183
+ const arg = argv[index];
140
184
  if (arg === "--help" || arg === "-h") {
141
185
  printUsage();
142
186
  process.exit(0);
143
187
  }
188
+ if (arg === "--template") {
189
+ template = projectTemplate(argv[++index] ?? "");
190
+ continue;
191
+ }
192
+ if (arg.startsWith("--template=")) {
193
+ template = projectTemplate(arg.slice(11));
194
+ continue;
195
+ }
144
196
  if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
197
+ if (projectRoot) throw new Error("Only one project directory may be specified");
145
198
  projectRoot = path.resolve(root, arg);
146
199
  }
200
+ if (!projectRoot) throw new Error("Usage: bleam new <dir>");
147
201
  return {
148
202
  projectRoot: canonicalPath(projectRoot),
149
- run
203
+ template
150
204
  };
151
205
  }
206
+ const projectTemplates = [
207
+ "basic",
208
+ "state",
209
+ "foundation-models",
210
+ "image-generation"
211
+ ];
212
+ function projectTemplate(value) {
213
+ if (projectTemplates.includes(value)) return value;
214
+ throw new Error(`Unknown template: ${value}. Expected ${projectTemplates.join(", ")}`);
215
+ }
152
216
  function parseBuildOptions(argv) {
153
217
  const root = invocationRoot();
154
218
  let projectRoot = root;
155
219
  let hasProjectRootArg = false;
156
220
  let output;
157
221
  let configuration = "Release";
222
+ let clean = false;
158
223
  let exportOptionsPlist;
159
224
  for (let index = 0; index < argv.length; index += 1) {
160
225
  const arg = argv[index];
@@ -176,6 +241,10 @@ function parseBuildOptions(argv) {
176
241
  configuration = arg.slice(16);
177
242
  continue;
178
243
  }
244
+ if (arg === "--clean") {
245
+ clean = true;
246
+ continue;
247
+ }
179
248
  if (arg === "--export-options-plist") {
180
249
  exportOptionsPlist = argv[++index];
181
250
  continue;
@@ -198,6 +267,7 @@ function parseBuildOptions(argv) {
198
267
  projectRoot: resolvedProjectRoot,
199
268
  output: path.resolve(resolvedProjectRoot, output ?? "dist"),
200
269
  configuration,
270
+ clean,
201
271
  exportOptionsPlist: exportOptionsPlist ? path.resolve(root, exportOptionsPlist) : void 0
202
272
  };
203
273
  }
@@ -250,6 +320,7 @@ function parseDoctorOptions(argv) {
250
320
  function parseInternalMetroOptions(argv) {
251
321
  let workspaceRoot;
252
322
  let port = 8081;
323
+ let resetCache = false;
253
324
  for (let index = 0; index < argv.length; index += 1) {
254
325
  const arg = argv[index];
255
326
  if (arg === "--workspace-root") {
@@ -264,13 +335,18 @@ function parseInternalMetroOptions(argv) {
264
335
  port = Number(arg.slice(7));
265
336
  continue;
266
337
  }
338
+ if (arg === "--reset-cache") {
339
+ resetCache = true;
340
+ continue;
341
+ }
267
342
  throw new Error(`Unknown internal option: ${arg}`);
268
343
  }
269
344
  if (!workspaceRoot) throw new Error("Missing --workspace-root");
270
345
  if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
271
346
  return {
272
347
  workspaceRoot,
273
- port
348
+ port,
349
+ resetCache
274
350
  };
275
351
  }
276
352
  function isBleamProjectRoot(projectRoot) {
@@ -310,6 +386,11 @@ function assertStringField(value, field) {
310
386
  if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
311
387
  return value;
312
388
  }
389
+ function normalizeAppGroups(value, bundleIdentifier) {
390
+ const configured = Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
391
+ if (configured.length > 0) return configured;
392
+ return bundleIdentifier ? [`group.${bundleIdentifier}`] : [];
393
+ }
313
394
  function isHexColor(value) {
314
395
  return typeof value === "string" && /^#[\da-f]{6}$/i.test(value);
315
396
  }
@@ -353,11 +434,13 @@ function loadAppConfig(projectRoot) {
353
434
  });
354
435
  if (result.status !== 0) throw new Error(result.stderr.trim() || "Failed to load app.config.ts");
355
436
  const value = JSON.parse(result.stdout);
437
+ const bundleIdentifier = typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0;
356
438
  return {
357
439
  name: assertStringField(value.name, "name"),
358
440
  version: typeof value.version === "string" ? value.version : void 0,
359
441
  buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
360
- bundleIdentifier: typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0,
442
+ bundleIdentifier,
443
+ appGroups: normalizeAppGroups(value.appGroups, bundleIdentifier),
361
444
  teamId: typeof value.teamId === "string" ? value.teamId : void 0,
362
445
  appearance: normalizeAppearance(value.appearance)
363
446
  };
@@ -380,6 +463,7 @@ function readProjectConfig(options) {
380
463
  version: config.version,
381
464
  buildNumber: config.buildNumber ?? "1",
382
465
  bundleIdentifier: config.bundleIdentifier,
466
+ appGroups: config.appGroups ?? [],
383
467
  teamId: config.teamId,
384
468
  appearance: config.appearance ?? normalizeAppearance(void 0),
385
469
  runtimeVersion,
@@ -397,22 +481,40 @@ function nameFromDirectory(projectRoot) {
397
481
  function singleQuotedString(value) {
398
482
  return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
399
483
  }
400
- function createNewProject(options) {
401
- const templateRoot = path.join(packageRoot, "templates", "basic");
402
- if (!existsSync(templateRoot)) throw new Error(`Missing basic template: ${templateRoot}`);
403
- if (existsSync(options.projectRoot) && readdirSync(options.projectRoot).length > 0) throw new Error(`Directory is not empty: ${options.projectRoot}`);
404
- mkdirSync(options.projectRoot, { recursive: true });
405
- cpSync(templateRoot, options.projectRoot, { recursive: true });
484
+ function updateStarterAppName(projectRoot, name) {
485
+ const appPath = path.join(projectRoot, "app", "index.tsx");
486
+ const source = readFileSync(appPath, "utf8");
487
+ const placeholder = `const appName = ${singleQuotedString(starterAppName)}`;
488
+ if (!source.includes(placeholder)) throw new Error(`Missing starter app name placeholder: ${appPath}`);
489
+ writeFileSync(appPath, source.replace(placeholder, `const appName = ${singleQuotedString(name)}`));
490
+ }
491
+ function createProject(options) {
492
+ const template = options.template ?? "basic";
493
+ const templateRoot = path.join(packageRoot, "templates", template);
494
+ if (!existsSync(templateRoot)) throw new Error(`Missing ${template} template: ${templateRoot}`);
495
+ if (existsSync(options.projectRoot)) throw new Error(`Destination already exists: ${options.projectRoot}`);
406
496
  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
- });
497
+ mkdirSync(options.projectRoot, { recursive: true });
498
+ try {
499
+ cpSync(templateRoot, options.projectRoot, { recursive: true });
500
+ updateStarterAppName(options.projectRoot, name);
501
+ writeFileSync(path.join(options.projectRoot, "app.config.ts"), `import { appConfig } from 'bleam'\n\nexport default appConfig({\n name: ${singleQuotedString(name)},\n})\n`);
502
+ writeFileSync(path.join(options.projectRoot, ".gitignore"), `.bleam\nbiome.json\n.DS_Store\n`);
503
+ createDevWorkspace(options.projectRoot, {
504
+ name,
505
+ buildNumber: "1",
506
+ appGroups: [],
507
+ appearance: normalizeAppearance(void 0),
508
+ runtimeVersion,
509
+ sdkVersion: runtimeVersion
510
+ });
511
+ } catch (error) {
512
+ rmSync(options.projectRoot, {
513
+ recursive: true,
514
+ force: true
515
+ });
516
+ throw error;
517
+ }
416
518
  console.log(`Created Bleam project at ${options.projectRoot}`);
417
519
  }
418
520
  function createDevWorkspace(projectRoot, config) {
@@ -426,7 +528,7 @@ function createDevWorkspace(projectRoot, config) {
426
528
  });
427
529
  symlinkSync(dependencyRoot, workspaceNodeModules);
428
530
  ensureProjectTsconfig(projectRoot);
429
- writeFileSync(path.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: {
531
+ const expoConfig = {
430
532
  name: config.name,
431
533
  slug: slugFromName(config.name),
432
534
  version: config.version ?? "0.0.0",
@@ -439,7 +541,10 @@ function createDevWorkspace(projectRoot, config) {
439
541
  runtimeVersion: config.runtimeVersion,
440
542
  sdkVersion: config.sdkVersion
441
543
  } }
442
- } }, null, 2)}\n`);
544
+ };
545
+ if (existsSync(path.join(projectRoot, "icon.png"))) expoConfig.icon = "../../icon.png";
546
+ if (existsSync(path.join(projectRoot, "splash.png"))) expoConfig.plugins = [["expo-splash-screen", { image: "../../splash.png" }]];
547
+ writeFileSync(path.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: expoConfig }, null, 2)}\n`);
443
548
  writeFileSync(path.join(workspaceRoot, "index.js"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(path.join(projectRoot, "app"))}\n\nregisterApp(App)\n`);
444
549
  writeFileSync(path.join(workspaceRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
445
550
  writeFileSync(path.join(workspaceRoot, "package.json"), `${JSON.stringify({
@@ -451,6 +556,7 @@ function createDevWorkspace(projectRoot, config) {
451
556
  "@types/react": packageJson.dependencies?.["@types/react"],
452
557
  "@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
453
558
  expo: packageJson.dependencies?.expo,
559
+ "expo-splash-screen": packageJson.dependencies?.["expo-splash-screen"],
454
560
  react: packageJson.dependencies?.react,
455
561
  "react-native": packageJson.dependencies?.["react-native"],
456
562
  typescript: packageJson.dependencies?.typescript
@@ -535,6 +641,24 @@ function replacePlistString(plist, key, value) {
535
641
  if (!pattern.test(plist)) return plist;
536
642
  return plist.replace(pattern, `$1${plistString(value)}$3`);
537
643
  }
644
+ function plistStringArray(values) {
645
+ return values.map((value) => `\t\t<string>${plistString(value)}</string>`).join("\n");
646
+ }
647
+ function replaceOrInsertPlistString(plist, key, value) {
648
+ const replaced = replacePlistString(plist, key, value);
649
+ if (replaced !== plist) return replaced;
650
+ const closingDictionary = plist.lastIndexOf("</dict>");
651
+ if (closingDictionary < 0) return plist;
652
+ return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<string>${plistString(value)}</string>\n${plist.slice(closingDictionary)}`;
653
+ }
654
+ function replaceOrInsertPlistStringArray(plist, key, values) {
655
+ const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<array>)([\\s\\S]*?)(</array>)`);
656
+ const replacement = `$1\n${plistStringArray(values)}\n\t$3`;
657
+ if (pattern.test(plist)) return plist.replace(pattern, replacement);
658
+ const closingDictionary = plist.lastIndexOf("</dict>");
659
+ if (closingDictionary < 0) return plist;
660
+ return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<array>\n${plistStringArray(values)}\n\t</array>\n${plist.slice(closingDictionary)}`;
661
+ }
538
662
  function writeGeneratedAppJson(nativeRoot, config) {
539
663
  writeFileSync(path.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
540
664
  name: config.name,
@@ -551,40 +675,60 @@ function writeGeneratedAppJson(nativeRoot, config) {
551
675
  function patchNativeProject(nativeRoot, config) {
552
676
  const projectPath = path.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
553
677
  const infoPlistPath = path.join(nativeRoot, "ios", "Bleam", "Info.plist");
678
+ const generationInfoPlistPath = path.join(nativeRoot, "ios", "GenerationService", "Info.plist");
679
+ const platformInfoPlistPath = path.join(nativeRoot, "ios", "PlatformHelper", "Info.plist");
680
+ const appEntitlementsPath = path.join(nativeRoot, "ios", "Bleam", "bleam.entitlements");
681
+ const generationEntitlementsPath = path.join(nativeRoot, "ios", "GenerationService", "GenerationService.entitlements");
554
682
  const productName = xcodeSettingValue(config.name);
555
683
  const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
684
+ const generationBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.GenerationService`);
685
+ const platformBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.PlatformHelper`);
556
686
  const teamId = xcodeSettingValue(config.teamId ?? "");
557
687
  const version = xcodeSettingValue(config.version ?? "1.0.0");
558
688
  const buildNumber = xcodeSettingValue(config.buildNumber);
689
+ const primaryAppGroup = config.appGroups[0];
559
690
  let project = readFileSync(projectPath, "utf8");
560
- project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = [^;]+;/g, () => {
561
- return `PRODUCT_BUNDLE_IDENTIFIER = ${bundleIdentifier};`;
691
+ project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g, (_, current) => {
692
+ return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : current.endsWith(".PlatformHelper") ? platformBundleIdentifier : bundleIdentifier};`;
562
693
  }).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
563
694
  return `DEVELOPMENT_TEAM = ${teamId};`;
564
695
  }).replace(/MARKETING_VERSION = [^;]+;/g, () => {
565
696
  return `MARKETING_VERSION = ${version};`;
566
697
  }).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, () => {
567
698
  return `CURRENT_PROJECT_VERSION = ${buildNumber};`;
568
- }).replace(/PRODUCT_NAME = [^;]+;/g, () => {
699
+ }).replace(/PRODUCT_NAME = bleam;/g, () => {
569
700
  return `PRODUCT_NAME = ${productName};`;
570
701
  });
571
702
  writeFileSync(projectPath, project);
572
703
  let infoPlist = readFileSync(infoPlistPath, "utf8");
573
704
  infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
574
705
  infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
706
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamRuntimeVersion", config.runtimeVersion);
707
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamTeamIdentifier", config.teamId ?? "");
708
+ const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL;
709
+ if (updateServiceUrl) infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
575
710
  infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
711
+ if (primaryAppGroup) infoPlist = replaceOrInsertPlistString(infoPlist, "AppGroupIdentifier", primaryAppGroup);
576
712
  writeFileSync(infoPlistPath, infoPlist);
713
+ if (primaryAppGroup) {
714
+ let generationInfoPlist = readFileSync(generationInfoPlistPath, "utf8");
715
+ generationInfoPlist = replaceOrInsertPlistString(generationInfoPlist, "AppGroupIdentifier", primaryAppGroup);
716
+ writeFileSync(generationInfoPlistPath, generationInfoPlist);
717
+ writeFileSync(platformInfoPlistPath, replaceOrInsertPlistString(readFileSync(platformInfoPlistPath, "utf8"), "BleamPlatformVersion", config.runtimeVersion));
718
+ for (const entitlementsPath of [appEntitlementsPath, generationEntitlementsPath]) writeFileSync(entitlementsPath, replaceOrInsertPlistStringArray(readFileSync(entitlementsPath, "utf8"), "com.apple.security.application-groups", config.appGroups));
719
+ }
577
720
  }
578
721
  function createBuildNativeWorkspace(options, config) {
579
722
  const nativeRoot = path.join(options.projectRoot, ".bleam", "build", "native");
580
723
  const templateRoot = nativeRuntimeTemplateRoot();
581
- rmSync(nativeRoot, {
724
+ if (options.clean) rmSync(nativeRoot, {
582
725
  recursive: true,
583
726
  force: true
584
727
  });
585
728
  mkdirSync(path.dirname(nativeRoot), { recursive: true });
586
729
  cpSync(templateRoot, nativeRoot, {
587
730
  recursive: true,
731
+ verbatimSymlinks: true,
588
732
  filter(source) {
589
733
  return !path.relative(templateRoot, source).split(path.sep).some((part) => {
590
734
  return part === "build" || part === "dist" || part === "node_modules" || part === ".expo" || part === "Pods" || part === ".xcode.env.local" || part === ".DS_Store" || part.endsWith(".xcworkspace");
@@ -624,32 +768,66 @@ function runInheritedCommand(command, args, cwd) {
624
768
  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
769
  }
626
770
  function installPods(nativeRoot) {
771
+ const iosRoot = path.join(nativeRoot, "ios");
772
+ const expectedStamp = podInstallInputStamp(iosRoot);
773
+ if (hasCurrentPodInstall(iosRoot, expectedStamp)) {
774
+ console.log("Using cached native dependencies");
775
+ return;
776
+ }
627
777
  console.log("Installing native dependencies...");
628
- runInheritedCommand("pod", ["install"], path.join(nativeRoot, "ios"));
778
+ runInheritedCommand("pod", ["install"], iosRoot);
779
+ writeFileSync(podInstallStampPath(iosRoot), `${expectedStamp}\n`);
780
+ }
781
+ function podInstallInputStamp(iosRoot) {
782
+ const inputHash = createHash("sha256");
783
+ for (const relative of [
784
+ "Podfile",
785
+ "Podfile.lock",
786
+ "../package.json"
787
+ ]) {
788
+ const input = path.resolve(iosRoot, relative);
789
+ if (existsSync(input)) inputHash.update(readFileSync(input));
790
+ }
791
+ return inputHash.digest("hex");
629
792
  }
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", [
793
+ function podInstallStampPath(iosRoot) {
794
+ return path.join(iosRoot, ".bleam-pods-input");
795
+ }
796
+ function hasCurrentPodInstall(iosRoot, expectedStamp) {
797
+ const stampPath = podInstallStampPath(iosRoot);
798
+ return existsSync(path.join(iosRoot, "Bleam.xcworkspace")) && existsSync(path.join(iosRoot, "Pods", "Manifest.lock")) && existsSync(stampPath) && readFileSync(stampPath, "utf8").trim() === expectedStamp;
799
+ }
800
+ function nativeArchiveArgs(options) {
801
+ return [
640
802
  "-allowProvisioningUpdates",
641
803
  "-workspace",
642
804
  path.join(options.nativeRoot, "ios", "Bleam.xcworkspace"),
643
805
  "-scheme",
644
- "bleam",
806
+ "Bleam",
645
807
  "-configuration",
646
808
  options.configuration,
647
809
  "-destination",
648
810
  "platform=macOS,arch=arm64,variant=Mac Catalyst",
649
811
  "-archivePath",
650
- archivePath,
812
+ options.archivePath,
813
+ "ARCHS=arm64",
651
814
  "archive"
652
- ], options.nativeRoot);
815
+ ];
816
+ }
817
+ function archiveNativeApp(options) {
818
+ const platformOutput = path.join(options.outputRoot, "macos");
819
+ const archivePath = path.join(platformOutput, `${safeArtifactName(options.config.name)}.xcarchive`);
820
+ mkdirSync(platformOutput, { recursive: true });
821
+ rmSync(archivePath, {
822
+ recursive: true,
823
+ force: true
824
+ });
825
+ console.log(`Archiving ${options.config.name} for macOS...`);
826
+ runInheritedCommand("xcodebuild", nativeArchiveArgs({
827
+ nativeRoot: options.nativeRoot,
828
+ archivePath,
829
+ configuration: options.configuration
830
+ }), options.nativeRoot);
653
831
  return archivePath;
654
832
  }
655
833
  function exportNativeArchive(options) {
@@ -692,6 +870,177 @@ async function buildNativeArchives(options) {
692
870
  });
693
871
  console.log(`Exported ${exportPath}`);
694
872
  }
873
+ return archivePath;
874
+ }
875
+ async function installNativeApp(options) {
876
+ const destination = installArchivedApp(await buildNativeArchives(options));
877
+ console.log(`Installed ${destination}`);
878
+ }
879
+ function installArchivedApp(archivePath, applications = path.join(os.homedir(), "Applications")) {
880
+ const products = path.join(archivePath, "Products", "Applications");
881
+ const appName = readdirSync(products).find((name) => name.endsWith(".app"));
882
+ if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
883
+ const source = path.join(products, appName);
884
+ const destination = applications === path.join(os.homedir(), "Applications") ? installedAppPath(appName) : path.join(applications, appName);
885
+ mkdirSync(applications, { recursive: true });
886
+ rmSync(destination, {
887
+ recursive: true,
888
+ force: true
889
+ });
890
+ execFileSync("ditto", [source, destination], { stdio: "inherit" });
891
+ return destination;
892
+ }
893
+ function installedAppPath(appName) {
894
+ if (path.basename(appName) !== appName || !appName.endsWith(".app")) throw new Error(`Invalid macOS app name: ${appName}`);
895
+ return path.join(os.homedir(), "Applications", appName);
896
+ }
897
+ async function publishOta(options) {
898
+ const config = readProjectConfig(options);
899
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
900
+ if (!config.teamId) throw missingBuildFieldError("teamId");
901
+ const workspaceRoot = createBuildNativeWorkspace({
902
+ projectRoot: options.projectRoot,
903
+ output: path.join(options.projectRoot, "dist"),
904
+ configuration: "Release",
905
+ clean: false
906
+ }, config);
907
+ const outputRoot = path.join(options.projectRoot, ".bleam", "publish", "ota");
908
+ const bundleRoot = path.join(outputRoot, "bundle");
909
+ const bundlePath = path.join(bundleRoot, "bundle.js");
910
+ const archivePath = path.join(outputRoot, "ota.zip");
911
+ rmSync(outputRoot, {
912
+ recursive: true,
913
+ force: true
914
+ });
915
+ mkdirSync(bundleRoot, { recursive: true });
916
+ console.log(`Bundling OTA update for runtime ${runtimeVersion}...`);
917
+ runInheritedCommand(process.execPath, [
918
+ require.resolve("@expo/cli"),
919
+ "export:embed",
920
+ "--entry-file",
921
+ path.join(workspaceRoot, "index.ts"),
922
+ "--platform",
923
+ "ios",
924
+ "--dev",
925
+ "false",
926
+ "--bundle-output",
927
+ bundlePath,
928
+ "--assets-dest",
929
+ path.join(bundleRoot, "assets"),
930
+ "--minify",
931
+ "true"
932
+ ], workspaceRoot);
933
+ runInheritedCommand("ditto", [
934
+ "-c",
935
+ "-k",
936
+ "--keepParent",
937
+ bundleRoot,
938
+ archivePath
939
+ ], outputRoot);
940
+ const sizeBytes = readFileSync(archivePath).byteLength;
941
+ const digest = sha256(archivePath);
942
+ const releaseId = `ota_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
943
+ const release = {
944
+ schemaVersion: 1,
945
+ kind: "ota",
946
+ bundleIdentifier: config.bundleIdentifier,
947
+ teamIdentifier: config.teamId,
948
+ channel: "stable",
949
+ releaseId,
950
+ bundleVersion: `${config.version ?? "1.0.0"}+${config.buildNumber}`,
951
+ runtimeVersion,
952
+ artifact: {
953
+ format: "bleam-ota-zip-v1",
954
+ downloadId: releaseId,
955
+ sizeBytes,
956
+ sha256: digest
957
+ },
958
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
959
+ };
960
+ const response = await fetch(`${options.serviceUrl}/v1/local/releases/ota`, {
961
+ method: "POST",
962
+ headers: { "content-type": "application/json" },
963
+ body: JSON.stringify({
964
+ release,
965
+ artifactBase64: readFileSync(archivePath).toString("base64")
966
+ })
967
+ });
968
+ if (!response.ok) throw new Error(`Local OTA publication failed: ${response.status} ${await response.text()}`);
969
+ console.log(`Published OTA update ${releaseId}`);
970
+ console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/ota/${runtimeVersion}`);
971
+ }
972
+ async function publishPlatform(options) {
973
+ const config = readProjectConfig(options);
974
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
975
+ if (!config.teamId) throw missingBuildFieldError("teamId");
976
+ const archivePath = await buildNativeArchives({
977
+ projectRoot: options.projectRoot,
978
+ output: path.join(options.projectRoot, "dist"),
979
+ configuration: "Release",
980
+ clean: false
981
+ });
982
+ const products = path.join(archivePath, "Products", "Applications");
983
+ const appName = readdirSync(products).find((name) => name.endsWith(".app"));
984
+ if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
985
+ const outputRoot = path.join(options.projectRoot, ".bleam", "publish", "platform");
986
+ const stagingRoot = path.join(outputRoot, "app");
987
+ const artifactPath = path.join(outputRoot, "platform.zip");
988
+ rmSync(outputRoot, {
989
+ recursive: true,
990
+ force: true
991
+ });
992
+ mkdirSync(stagingRoot, { recursive: true });
993
+ execFileSync("ditto", [path.join(products, appName), path.join(stagingRoot, appName)]);
994
+ runInheritedCommand("zip", [
995
+ "-q",
996
+ "-r",
997
+ "-y",
998
+ artifactPath,
999
+ "app"
1000
+ ], outputRoot);
1001
+ const sizeBytes = readFileSync(artifactPath).byteLength;
1002
+ const digest = sha256(artifactPath);
1003
+ const releaseId = `platform_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
1004
+ const release = platformRelease(config, releaseId, sizeBytes, digest);
1005
+ const response = await fetch(`${options.serviceUrl}/v1/local/releases/platform`, {
1006
+ method: "POST",
1007
+ headers: { "content-type": "application/json" },
1008
+ body: JSON.stringify({
1009
+ release,
1010
+ artifactBase64: readFileSync(artifactPath).toString("base64")
1011
+ })
1012
+ });
1013
+ if (!response.ok) throw new Error(`Local platform publication failed: ${response.status} ${await response.text()}`);
1014
+ console.log(`Published platform update ${releaseId}`);
1015
+ console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/platform/macos-arm64`);
1016
+ }
1017
+ function platformRelease(config, releaseId, sizeBytes, digest) {
1018
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
1019
+ if (!config.teamId) throw missingBuildFieldError("teamId");
1020
+ return {
1021
+ schemaVersion: 1,
1022
+ kind: "platform",
1023
+ bundleIdentifier: config.bundleIdentifier,
1024
+ teamIdentifier: config.teamId,
1025
+ channel: "stable",
1026
+ releaseId,
1027
+ appVersion: config.version ?? "1.0.0",
1028
+ buildNumber: config.buildNumber,
1029
+ bleamPlatformVersion: runtimeVersion,
1030
+ target: {
1031
+ os: "macos",
1032
+ architecture: "arm64",
1033
+ installLocation: "user-applications",
1034
+ minimumMacOSVersion: "14.0"
1035
+ },
1036
+ artifact: {
1037
+ format: "bleam-macos-app-zip-v1",
1038
+ downloadId: releaseId,
1039
+ sizeBytes,
1040
+ sha256: digest
1041
+ },
1042
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
1043
+ };
695
1044
  }
696
1045
  function runtimeCacheRoot() {
697
1046
  return path.join(os.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
@@ -742,16 +1091,22 @@ async function downloadRuntimeArtifact() {
742
1091
  const runtimePlatform = artifactPlatform();
743
1092
  const cacheRoot = runtimeCacheRoot();
744
1093
  const cachedAppPath = path.join(cacheRoot, "Bleam.app");
745
- if (existsSync(cachedAppPath)) return {
746
- appPath: cachedAppPath,
747
- version: runtimeVersion,
748
- platform: runtimePlatform
749
- };
1094
+ if (existsSync(cachedAppPath)) {
1095
+ console.log(`Using cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
1096
+ return {
1097
+ appPath: cachedAppPath,
1098
+ version: runtimeVersion,
1099
+ platform: runtimePlatform
1100
+ };
1101
+ }
750
1102
  mkdirSync(cacheRoot, { recursive: true });
751
1103
  const archiveUrl = runtimeArchiveUrl();
752
1104
  const archivePath = path.join(cacheRoot, runtimeArchiveName());
753
1105
  const extractRoot = path.join(cacheRoot, "extract");
1106
+ console.log(`Downloading Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
1107
+ console.log(`Runtime archive: ${archiveUrl}`);
754
1108
  await downloadFile(archiveUrl, archivePath);
1109
+ console.log("Verifying Bleam runtime archive");
755
1110
  const expectedSha256 = await downloadExpectedSha256(archiveUrl);
756
1111
  if (expectedSha256 && sha256(archivePath) !== expectedSha256) {
757
1112
  rmSync(archivePath, { force: true });
@@ -762,6 +1117,7 @@ async function downloadRuntimeArtifact() {
762
1117
  force: true
763
1118
  });
764
1119
  mkdirSync(extractRoot, { recursive: true });
1120
+ console.log("Extracting Bleam runtime archive");
765
1121
  execFileSync("ditto", [
766
1122
  "-x",
767
1123
  "-k",
@@ -774,11 +1130,15 @@ async function downloadRuntimeArtifact() {
774
1130
  recursive: true,
775
1131
  force: true
776
1132
  });
777
- cpSync(extractedAppPath, cachedAppPath, { recursive: true });
1133
+ cpSync(extractedAppPath, cachedAppPath, {
1134
+ recursive: true,
1135
+ verbatimSymlinks: true
1136
+ });
778
1137
  rmSync(extractRoot, {
779
1138
  recursive: true,
780
1139
  force: true
781
1140
  });
1141
+ console.log(`Cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
782
1142
  return {
783
1143
  appPath: cachedAppPath,
784
1144
  version: runtimeVersion,
@@ -787,10 +1147,12 @@ async function downloadRuntimeArtifact() {
787
1147
  }
788
1148
  async function resolveRuntimeArtifact() {
789
1149
  const runtimePlatform = artifactPlatform();
1150
+ console.log(`Resolving Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
790
1151
  const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
791
1152
  if (overrideAppPath) {
792
1153
  const appPath = path.resolve(overrideAppPath);
793
1154
  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()}`);
1155
+ console.log(`Using BLEAM_RUNTIME_APP runtime at ${prettyPath(appPath)}`);
794
1156
  return {
795
1157
  appPath,
796
1158
  version: runtimeVersion,
@@ -798,11 +1160,14 @@ async function resolveRuntimeArtifact() {
798
1160
  };
799
1161
  }
800
1162
  const localAppPath = localDevelopmentRuntimeAppPath();
801
- if (existsSync(localAppPath)) return {
802
- appPath: localAppPath,
803
- version: runtimeVersion,
804
- platform: runtimePlatform
805
- };
1163
+ if (existsSync(localAppPath)) {
1164
+ console.log(`Using local development runtime at ${prettyPath(localAppPath)}`);
1165
+ return {
1166
+ appPath: localAppPath,
1167
+ version: runtimeVersion,
1168
+ platform: runtimePlatform
1169
+ };
1170
+ }
806
1171
  try {
807
1172
  return await downloadRuntimeArtifact();
808
1173
  } catch (error) {
@@ -811,14 +1176,31 @@ async function resolveRuntimeArtifact() {
811
1176
  }
812
1177
  function copyRuntimeClient(projectRoot, runtime) {
813
1178
  const clientAppPath = path.join(projectRoot, ".bleam", "client", "Bleam.app");
1179
+ console.log(`Preparing Bleam app at ${prettyPath(clientAppPath)}`);
814
1180
  rmSync(clientAppPath, {
815
1181
  recursive: true,
816
1182
  force: true
817
1183
  });
818
1184
  mkdirSync(path.dirname(clientAppPath), { recursive: true });
819
- cpSync(runtime.appPath, clientAppPath, { recursive: true });
1185
+ cpSync(runtime.appPath, clientAppPath, {
1186
+ recursive: true,
1187
+ verbatimSymlinks: true
1188
+ });
820
1189
  return clientAppPath;
821
1190
  }
1191
+ function cleanDevArtifacts(projectRoot) {
1192
+ const bleamRoot = path.join(projectRoot, ".bleam");
1193
+ const targets = [
1194
+ "client",
1195
+ "dev",
1196
+ "project.json"
1197
+ ].map((name) => path.join(bleamRoot, name));
1198
+ console.log("Cleaning Bleam dev artifacts");
1199
+ for (const target of targets) rmSync(target, {
1200
+ recursive: true,
1201
+ force: true
1202
+ });
1203
+ }
822
1204
  function isBleamManifest(value) {
823
1205
  const manifest = value && typeof value === "object" ? value : {};
824
1206
  return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
@@ -920,6 +1302,15 @@ function runDoctor(options) {
920
1302
  function formatDuration(ms) {
921
1303
  return `${(ms / 1e3).toFixed(1)}s`;
922
1304
  }
1305
+ async function bundleErrorPreview(response) {
1306
+ const contentType = response.headers.get("content-type") ?? "";
1307
+ if (!contentType.includes("json") && !contentType.includes("text")) return "";
1308
+ try {
1309
+ return (await response.text()).trim().slice(0, 1e3);
1310
+ } catch {
1311
+ return "";
1312
+ }
1313
+ }
923
1314
  async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
924
1315
  const startedAt = Date.now();
925
1316
  const requestTimeoutMs = 3e4;
@@ -931,14 +1322,17 @@ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
931
1322
  cache: "no-store",
932
1323
  signal: AbortSignal.timeout(requestTimeoutMs)
933
1324
  });
934
- await response.body?.cancel();
935
1325
  if (response.ok) {
1326
+ await response.body?.cancel();
936
1327
  spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
937
1328
  if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
938
1329
  return;
939
1330
  }
940
- if (spinner) spinner.text = `Bundle request returned ${response.status}, retrying...`;
941
- else console.log(`Bundle request returned ${response.status}, retrying...`);
1331
+ const preview = await bundleErrorPreview(response);
1332
+ const detail = preview ? `\n${preview}` : "";
1333
+ const message = `Bundle request returned ${response.status}, retrying...${detail}`;
1334
+ if (spinner) spinner.text = message;
1335
+ else console.log(message);
942
1336
  } catch (error) {
943
1337
  if (error instanceof Error && error.name === "TimeoutError") {
944
1338
  const message = `Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`;
@@ -974,7 +1368,11 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
974
1368
  if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
975
1369
  });
976
1370
  }
1371
+ function quitRuntimeClient() {
1372
+ spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to quit"], { stdio: "ignore" });
1373
+ }
977
1374
  async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
1375
+ quitRuntimeClient();
978
1376
  openRuntimeClient(appPath, projectConfigPath, manifest);
979
1377
  }
980
1378
  function focusRuntimeClient() {
@@ -987,6 +1385,49 @@ function sendJson(res, status, value) {
987
1385
  });
988
1386
  res.end(JSON.stringify(value));
989
1387
  }
1388
+ function readRequestBody(req, limit = 1e6) {
1389
+ return new Promise((resolve, reject) => {
1390
+ const chunks = [];
1391
+ let size = 0;
1392
+ req.on("data", (chunk) => {
1393
+ size += chunk.length;
1394
+ if (size > limit) {
1395
+ reject(/* @__PURE__ */ new Error("Request body too large"));
1396
+ req.destroy();
1397
+ return;
1398
+ }
1399
+ chunks.push(chunk);
1400
+ });
1401
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1402
+ req.on("error", reject);
1403
+ });
1404
+ }
1405
+ function isLogLevel(value) {
1406
+ return value === "debug" || value === "info" || value === "warn" || value === "error";
1407
+ }
1408
+ function isLogSource(value) {
1409
+ return value === "cli" || value === "metro" || value === "runtime" || value === "js" || value === "native" || value === "xpc";
1410
+ }
1411
+ function normalizeDevLogEvent(value) {
1412
+ const input = value && typeof value === "object" ? value : {};
1413
+ const message = typeof input.message === "string" ? input.message : input.message === void 0 ? "" : JSON.stringify(input.message);
1414
+ return {
1415
+ type: "log",
1416
+ id: typeof input.id === "string" && input.id ? input.id : randomUUID(),
1417
+ time: typeof input.time === "string" && input.time ? input.time : (/* @__PURE__ */ new Date()).toISOString(),
1418
+ source: isLogSource(input.source) ? input.source : "runtime",
1419
+ level: isLogLevel(input.level) ? input.level : "info",
1420
+ category: typeof input.category === "string" && input.category ? input.category : void 0,
1421
+ message,
1422
+ details: input.details,
1423
+ jobId: typeof input.jobId === "string" && input.jobId ? input.jobId : void 0
1424
+ };
1425
+ }
1426
+ function formatDevLogEvent(event) {
1427
+ const category = event.category ? `:${event.category}` : "";
1428
+ const job = event.jobId ? ` ${event.jobId.slice(0, 8)}` : "";
1429
+ return `[${event.source}${category}:${event.level}${job}] ${event.message}`;
1430
+ }
990
1431
  async function requestRuntimeReload(origin) {
991
1432
  const response = await fetch(`${origin}/bleam/reload`, {
992
1433
  method: "POST",
@@ -1005,6 +1446,13 @@ function biomeBinPath() {
1005
1446
  if (!bin) throw new Error("Could not resolve @biomejs/biome binary");
1006
1447
  return path.join(biomePackageRoot, bin);
1007
1448
  }
1449
+ function typescriptBinPath() {
1450
+ const typescriptPackageJsonPath = require.resolve("typescript/package.json");
1451
+ const typescriptPackageRoot = path.dirname(typescriptPackageJsonPath);
1452
+ const bin = require("typescript/package.json").bin?.tsc;
1453
+ if (!bin) throw new Error("Could not resolve TypeScript compiler binary");
1454
+ return path.join(typescriptPackageRoot, bin);
1455
+ }
1008
1456
  function ensureProjectToolWorkspace(options) {
1009
1457
  const config = readProjectConfig({ projectRoot: options.projectRoot });
1010
1458
  return createDevWorkspace(options.projectRoot, config);
@@ -1032,6 +1480,20 @@ function runBiomeCommand(command, options) {
1032
1480
  if (result.error) throw result.error;
1033
1481
  process.exit(result.status ?? 1);
1034
1482
  }
1483
+ function runTypecheckCommand(options) {
1484
+ assertBleamConfigFiles(options.projectRoot);
1485
+ ensureProjectToolWorkspace(options);
1486
+ const result = spawnSync(typescriptBinPath(), [
1487
+ "--noEmit",
1488
+ "-p",
1489
+ path.join(options.projectRoot, "tsconfig.json")
1490
+ ], {
1491
+ cwd: options.projectRoot,
1492
+ stdio: "inherit"
1493
+ });
1494
+ if (result.error) throw result.error;
1495
+ process.exit(result.status ?? 1);
1496
+ }
1035
1497
  function setupInteractiveControls(options) {
1036
1498
  if (!process.stdin.isTTY) return () => {};
1037
1499
  const wasRaw = process.stdin.isRaw;
@@ -1053,21 +1515,26 @@ function setupInteractiveControls(options) {
1053
1515
  if (key === "o") {
1054
1516
  (async () => {
1055
1517
  try {
1056
- if (!focusRuntimeClient()) {
1057
- console.log("Opening Bleam app...");
1058
- await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
1518
+ if (focusRuntimeClient()) {
1519
+ console.log("Focused Bleam app");
1520
+ return;
1059
1521
  }
1522
+ console.log(`Opening Bleam app at ${prettyPath(options.clientAppPath)}`);
1523
+ await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
1060
1524
  } catch (error) {
1061
1525
  console.error(error instanceof Error ? error.message : String(error));
1062
1526
  }
1063
1527
  })();
1064
1528
  continue;
1065
1529
  }
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
- });
1530
+ if (key === "r") {
1531
+ console.log("Requesting app reload");
1532
+ requestRuntimeReload(options.origin).then((clients) => {
1533
+ console.log(`Reloaded app (${clients} client${clients === 1 ? "" : "s"})`);
1534
+ }).catch((error) => {
1535
+ console.error(error instanceof Error ? error.message : String(error));
1536
+ });
1537
+ }
1071
1538
  }
1072
1539
  };
1073
1540
  process.stdin.setRawMode(true);
@@ -1078,12 +1545,18 @@ function setupInteractiveControls(options) {
1078
1545
  }
1079
1546
  async function startInternalMetroServer(options) {
1080
1547
  process.env.BLEAM_DEV_PLATFORM ??= "macos";
1548
+ const projectRoot = process.env.BLEAM_PROJECT_ROOT ?? options.workspaceRoot;
1549
+ const logPath = path.join(projectRoot, ".bleam", "dev", "logs", "dev.log");
1550
+ const recentLogs = [];
1551
+ const logClients = /* @__PURE__ */ new Set();
1552
+ const maxRecentLogs = 1e3;
1081
1553
  const metro = require("@expo/metro/metro");
1082
1554
  const { loadConfig } = require("@expo/metro/metro-config");
1083
1555
  const config = await loadConfig({
1084
1556
  config: path.join(options.workspaceRoot, "metro.config.js"),
1085
1557
  cwd: options.workspaceRoot,
1086
- port: options.port
1558
+ port: options.port,
1559
+ resetCache: options.resetCache
1087
1560
  });
1088
1561
  const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
1089
1562
  watch: true,
@@ -1091,6 +1564,16 @@ async function startInternalMetroServer(options) {
1091
1564
  });
1092
1565
  const messageClients = /* @__PURE__ */ new Set();
1093
1566
  const messageServer = new WebSocketServer({ noServer: true });
1567
+ const logServer = new WebSocketServer({ noServer: true });
1568
+ const recordLog = (event) => {
1569
+ recentLogs.push(event);
1570
+ if (recentLogs.length > maxRecentLogs) recentLogs.splice(0, recentLogs.length - maxRecentLogs);
1571
+ mkdirSync(path.dirname(logPath), { recursive: true });
1572
+ appendFileSync(logPath, `${JSON.stringify(event)}\n`, "utf8");
1573
+ const payload = JSON.stringify(event);
1574
+ for (const client of logClients) if (client.readyState === WebSocket.OPEN) client.send(payload);
1575
+ console.log(formatDevLogEvent(event));
1576
+ };
1094
1577
  const broadcastReload = () => {
1095
1578
  const message = JSON.stringify({
1096
1579
  version: 2,
@@ -1108,6 +1591,11 @@ async function startInternalMetroServer(options) {
1108
1591
  messageClients.add(client);
1109
1592
  client.on("close", () => messageClients.delete(client));
1110
1593
  });
1594
+ logServer.on("connection", (client) => {
1595
+ logClients.add(client);
1596
+ for (const event of recentLogs) client.send(JSON.stringify(event));
1597
+ client.on("close", () => logClients.delete(client));
1598
+ });
1111
1599
  const server = http.createServer((req, res) => {
1112
1600
  if (req.url?.split("?")[0] === "/status") {
1113
1601
  res.writeHead(200, {
@@ -1130,6 +1618,29 @@ async function startInternalMetroServer(options) {
1130
1618
  });
1131
1619
  return;
1132
1620
  }
1621
+ if (req.url?.split("?")[0] === "/bleam/logs") {
1622
+ if (req.method === "GET") {
1623
+ sendJson(res, 200, { logs: recentLogs });
1624
+ return;
1625
+ }
1626
+ if (req.method !== "POST") {
1627
+ res.writeHead(405, { Allow: "GET, POST" });
1628
+ res.end();
1629
+ return;
1630
+ }
1631
+ readRequestBody(req).then((body) => {
1632
+ const parsed = body ? JSON.parse(body) : {};
1633
+ const events = Array.isArray(parsed) ? parsed : [parsed];
1634
+ for (const value of events) recordLog(normalizeDevLogEvent(value));
1635
+ sendJson(res, 200, { ok: true });
1636
+ }).catch((error) => {
1637
+ sendJson(res, 400, {
1638
+ ok: false,
1639
+ error: error instanceof Error ? error.message : String(error)
1640
+ });
1641
+ });
1642
+ return;
1643
+ }
1133
1644
  middleware(req, res, (error) => {
1134
1645
  if (res.headersSent) return;
1135
1646
  if (error) {
@@ -1159,6 +1670,12 @@ async function startInternalMetroServer(options) {
1159
1670
  });
1160
1671
  return;
1161
1672
  }
1673
+ if (req.url?.split("?")[0] === "/bleam/logs/stream") {
1674
+ logServer.handleUpgrade(req, socket, head, (client) => {
1675
+ logServer.emit("connection", client, req);
1676
+ });
1677
+ return;
1678
+ }
1162
1679
  for (const listener of metroUpgradeListeners) listener.call(server, req, socket, head);
1163
1680
  });
1164
1681
  await new Promise((resolve, reject) => {
@@ -1172,7 +1689,9 @@ async function startInternalMetroServer(options) {
1172
1689
  const close = () => {
1173
1690
  setTimeout(() => process.exit(0), 1e3).unref();
1174
1691
  messageServer.close();
1692
+ logServer.close();
1175
1693
  for (const client of messageClients) client.terminate();
1694
+ for (const client of logClients) client.terminate();
1176
1695
  server.close(() => {
1177
1696
  end();
1178
1697
  process.exit(0);
@@ -1183,8 +1702,10 @@ async function startInternalMetroServer(options) {
1183
1702
  }
1184
1703
  async function startDevServer(options) {
1185
1704
  const config = readProjectConfig(options);
1705
+ if (options.clean) cleanDevArtifacts(options.projectRoot);
1186
1706
  const runtime = await resolveRuntimeArtifact();
1187
1707
  const clientAppPath = copyRuntimeClient(options.projectRoot, runtime);
1708
+ console.log("Preparing Bleam dev workspace");
1188
1709
  const workspaceRoot = createDevWorkspace(options.projectRoot, config);
1189
1710
  const origin = `http://localhost:${options.port}`;
1190
1711
  const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
@@ -1194,7 +1715,8 @@ async function startDevServer(options) {
1194
1715
  "--workspace-root",
1195
1716
  workspaceRoot,
1196
1717
  "--port",
1197
- String(options.port)
1718
+ String(options.port),
1719
+ ...options.clean ? ["--reset-cache"] : []
1198
1720
  ], {
1199
1721
  stdio: "inherit",
1200
1722
  env: {
@@ -1218,24 +1740,32 @@ async function startDevServer(options) {
1218
1740
  }
1219
1741
  });
1220
1742
  let cleanupInteractiveControls = () => {};
1743
+ let didLaunchRuntimeClient = false;
1744
+ let didCleanupDevSession = false;
1221
1745
  let stopping = false;
1746
+ const cleanupDevSession = () => {
1747
+ if (didCleanupDevSession) return;
1748
+ didCleanupDevSession = true;
1749
+ cleanupInteractiveControls();
1750
+ if (didLaunchRuntimeClient) quitRuntimeClient();
1751
+ };
1222
1752
  const stop = (signal) => {
1223
1753
  if (stopping) return;
1224
1754
  stopping = true;
1225
- cleanupInteractiveControls();
1755
+ cleanupDevSession();
1226
1756
  child.kill(signal);
1227
1757
  setTimeout(() => process.exit(0), 2e3).unref();
1228
1758
  };
1229
1759
  process.on("SIGINT", () => stop("SIGINT"));
1230
1760
  process.on("SIGTERM", () => stop("SIGTERM"));
1231
1761
  child.on("error", (error) => {
1232
- cleanupInteractiveControls();
1762
+ cleanupDevSession();
1233
1763
  console.error(`Bleam dev server process failed: ${error instanceof Error ? error.message : String(error)}`);
1234
1764
  console.error(doctorHint(options.projectRoot));
1235
1765
  process.exit(1);
1236
1766
  });
1237
1767
  child.on("exit", (code) => {
1238
- cleanupInteractiveControls();
1768
+ cleanupDevSession();
1239
1769
  process.exit(code ?? 0);
1240
1770
  });
1241
1771
  try {
@@ -1249,8 +1779,9 @@ async function startDevServer(options) {
1249
1779
  });
1250
1780
  if (options.run && process.env.BLEAM_NO_OPEN !== "1") try {
1251
1781
  await waitForDevBundle(manifest.bundleUrl);
1252
- console.log("Opening Bleam app...");
1782
+ console.log(`Opening Bleam app at ${prettyPath(clientAppPath)}`);
1253
1783
  await launchRuntimeClient(clientAppPath, projectConfigPath, manifest);
1784
+ didLaunchRuntimeClient = true;
1254
1785
  } catch (error) {
1255
1786
  console.error(`Initial app launch failed: ${error instanceof Error ? error.message : String(error)}`);
1256
1787
  console.error(doctorHint(options.projectRoot));
@@ -1258,7 +1789,7 @@ async function startDevServer(options) {
1258
1789
  console.error("Bleam dev server is still running.");
1259
1790
  }
1260
1791
  } catch (error) {
1261
- cleanupInteractiveControls();
1792
+ cleanupDevSession();
1262
1793
  child.kill("SIGTERM");
1263
1794
  throw error;
1264
1795
  }
@@ -1271,11 +1802,12 @@ async function main(argv = process.argv.slice(2)) {
1271
1802
  }
1272
1803
  if (command === "new") {
1273
1804
  const options = parseNewOptions(args);
1274
- createNewProject(options);
1275
- if (options.run) await startDevServer({
1805
+ createProject(options);
1806
+ await startDevServer({
1276
1807
  projectRoot: options.projectRoot,
1277
1808
  port: 8081,
1278
- run: true
1809
+ run: true,
1810
+ clean: false
1279
1811
  });
1280
1812
  return;
1281
1813
  }
@@ -1283,14 +1815,34 @@ async function main(argv = process.argv.slice(2)) {
1283
1815
  await startDevServer(parseDevOptions(args));
1284
1816
  return;
1285
1817
  }
1818
+ if (command === "install") {
1819
+ await installNativeApp(parseBuildOptions(args));
1820
+ return;
1821
+ }
1286
1822
  if (command === "build") {
1287
1823
  await buildNativeArchives(parseBuildOptions(args));
1288
1824
  return;
1289
1825
  }
1826
+ if (command === "publish") {
1827
+ const [kind, ...publishArgs] = args;
1828
+ if (kind === "ota") {
1829
+ await publishOta(parsePublishOtaOptions(publishArgs));
1830
+ return;
1831
+ }
1832
+ if (kind === "platform") {
1833
+ await publishPlatform(parsePublishOtaOptions(publishArgs));
1834
+ return;
1835
+ }
1836
+ throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>]");
1837
+ }
1290
1838
  if (command === "doctor") {
1291
1839
  runDoctor(parseDoctorOptions(args));
1292
1840
  return;
1293
1841
  }
1842
+ if (command === "typecheck") {
1843
+ runTypecheckCommand(parseProjectToolOptions(args));
1844
+ return;
1845
+ }
1294
1846
  if (command === "check") {
1295
1847
  runBiomeCommand("check", parseProjectToolOptions(args));
1296
1848
  return;
@@ -1309,10 +1861,24 @@ async function main(argv = process.argv.slice(2)) {
1309
1861
  }
1310
1862
  throw new Error(`Unknown command: ${command}`);
1311
1863
  }
1312
- main().catch((error) => {
1864
+ if (process.env.VITEST !== "true") main().catch((error) => {
1313
1865
  console.error(error instanceof Error ? error.message : String(error));
1314
1866
  process.exit(1);
1315
1867
  });
1868
+ const __test = {
1869
+ createProject,
1870
+ createBuildNativeWorkspace,
1871
+ hasCurrentPodInstall,
1872
+ installedAppPath,
1873
+ installArchivedApp,
1874
+ nativeArchiveArgs,
1875
+ parseBuildOptions,
1876
+ parseNewOptions,
1877
+ parsePublishOtaOptions,
1878
+ platformRelease,
1879
+ podInstallInputStamp,
1880
+ replaceOrInsertPlistString
1881
+ };
1316
1882
 
1317
1883
  //#endregion
1318
- export { };
1884
+ export { __test };