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.cjs CHANGED
@@ -28,6 +28,7 @@ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__
28
28
  const packageRoot = node_path.default.dirname((0, node_url.fileURLToPath)(new URL("../package.json", require("url").pathToFileURL(__filename).href)));
29
29
  const packageJson = require$1("../package.json");
30
30
  const runtimeVersion = packageJson.version;
31
+ const starterAppName = "Bleam Basic";
31
32
  function artifactPlatform() {
32
33
  return `macos-${process.arch}`;
33
34
  }
@@ -85,16 +86,52 @@ function printUsage() {
85
86
  console.log(`Usage: bleam <command>
86
87
 
87
88
  Commands:
88
- new [dir] [--run] Create a new Bleam project
89
- dev [dir] [--port <port>] [--run]
89
+ new <dir> [--template <name>]
90
+ Create and run a Bleam project
91
+ dev [dir] [--port <port>] [--no-run] [--clean]
90
92
  Start a Bleam dev server
91
- build [dir] [--output <dir>]
92
- Build App Store-ready native archives
93
+ build [dir] [--output <dir>] [--clean]
94
+ Export standalone native archives
95
+ install [dir] [--output <dir>] [--clean]
96
+ Build and install a standalone macOS app
97
+ publish ota [dir] [--service-url <url>]
98
+ Publish an OTA update to the local update service
99
+ publish platform [dir] [--service-url <url>]
100
+ Build and publish a platform update locally
93
101
  doctor [dir] Check the local Bleam environment
102
+ typecheck [dir] Typecheck project files
94
103
  check [dir] [--write] Format, lint, and organize imports
95
104
  format [dir] [--check] Format project files
96
105
  lint [dir] Lint project files`);
97
106
  }
107
+ function parsePublishOtaOptions(argv) {
108
+ const root = invocationRoot();
109
+ let projectRoot = root;
110
+ let hasProjectRootArg = false;
111
+ let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? "http://127.0.0.1:8787";
112
+ for (let index = 0; index < argv.length; index += 1) {
113
+ const arg = argv[index];
114
+ if (arg === "--service-url") {
115
+ serviceUrl = argv[++index] ?? "";
116
+ continue;
117
+ }
118
+ if (arg.startsWith("--service-url=")) {
119
+ serviceUrl = arg.slice(14);
120
+ continue;
121
+ }
122
+ if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
123
+ if (hasProjectRootArg) throw new Error("Only one project directory may be specified");
124
+ projectRoot = node_path.default.resolve(root, arg);
125
+ hasProjectRootArg = true;
126
+ }
127
+ const url = new URL(serviceUrl);
128
+ if (url.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Local publishing requires an http://localhost service URL");
129
+ if (!hasProjectRootArg && !isBleamProjectRoot(projectRoot)) projectRoot = discoverBleamProjectRoot(projectRoot) ?? projectRoot;
130
+ return {
131
+ projectRoot: canonicalPath(projectRoot),
132
+ serviceUrl: url.origin
133
+ };
134
+ }
98
135
  function invocationRoot() {
99
136
  const pwd = process.env.PWD;
100
137
  return pwd && node_path.default.isAbsolute(pwd) ? pwd : process.cwd();
@@ -107,7 +144,8 @@ function parseDevOptions(argv) {
107
144
  const root = invocationRoot();
108
145
  let projectRoot = root;
109
146
  let port = 8081;
110
- let run = false;
147
+ let run = true;
148
+ let clean = false;
111
149
  let hasProjectRootArg = false;
112
150
  for (let index = 0; index < argv.length; index += 1) {
113
151
  const arg = argv[index];
@@ -124,6 +162,14 @@ function parseDevOptions(argv) {
124
162
  run = true;
125
163
  continue;
126
164
  }
165
+ if (arg === "--no-run") {
166
+ run = false;
167
+ continue;
168
+ }
169
+ if (arg === "--clean" || arg === "-c") {
170
+ clean = true;
171
+ continue;
172
+ }
127
173
  if (arg === "--help" || arg === "-h") {
128
174
  printUsage();
129
175
  process.exit(0);
@@ -137,36 +183,55 @@ function parseDevOptions(argv) {
137
183
  return {
138
184
  projectRoot: canonicalPath(projectRoot),
139
185
  port,
140
- run
186
+ run,
187
+ clean
141
188
  };
142
189
  }
143
190
  function parseNewOptions(argv) {
144
191
  const root = invocationRoot();
145
- let projectRoot = root;
146
- let run = false;
147
- for (const arg of argv) {
148
- if (arg === "--run") {
149
- run = true;
150
- continue;
151
- }
192
+ let projectRoot;
193
+ let template = "basic";
194
+ for (let index = 0; index < argv.length; index += 1) {
195
+ const arg = argv[index];
152
196
  if (arg === "--help" || arg === "-h") {
153
197
  printUsage();
154
198
  process.exit(0);
155
199
  }
200
+ if (arg === "--template") {
201
+ template = projectTemplate(argv[++index] ?? "");
202
+ continue;
203
+ }
204
+ if (arg.startsWith("--template=")) {
205
+ template = projectTemplate(arg.slice(11));
206
+ continue;
207
+ }
156
208
  if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
209
+ if (projectRoot) throw new Error("Only one project directory may be specified");
157
210
  projectRoot = node_path.default.resolve(root, arg);
158
211
  }
212
+ if (!projectRoot) throw new Error("Usage: bleam new <dir>");
159
213
  return {
160
214
  projectRoot: canonicalPath(projectRoot),
161
- run
215
+ template
162
216
  };
163
217
  }
218
+ const projectTemplates = [
219
+ "basic",
220
+ "state",
221
+ "foundation-models",
222
+ "image-generation"
223
+ ];
224
+ function projectTemplate(value) {
225
+ if (projectTemplates.includes(value)) return value;
226
+ throw new Error(`Unknown template: ${value}. Expected ${projectTemplates.join(", ")}`);
227
+ }
164
228
  function parseBuildOptions(argv) {
165
229
  const root = invocationRoot();
166
230
  let projectRoot = root;
167
231
  let hasProjectRootArg = false;
168
232
  let output;
169
233
  let configuration = "Release";
234
+ let clean = false;
170
235
  let exportOptionsPlist;
171
236
  for (let index = 0; index < argv.length; index += 1) {
172
237
  const arg = argv[index];
@@ -188,6 +253,10 @@ function parseBuildOptions(argv) {
188
253
  configuration = arg.slice(16);
189
254
  continue;
190
255
  }
256
+ if (arg === "--clean") {
257
+ clean = true;
258
+ continue;
259
+ }
191
260
  if (arg === "--export-options-plist") {
192
261
  exportOptionsPlist = argv[++index];
193
262
  continue;
@@ -210,6 +279,7 @@ function parseBuildOptions(argv) {
210
279
  projectRoot: resolvedProjectRoot,
211
280
  output: node_path.default.resolve(resolvedProjectRoot, output ?? "dist"),
212
281
  configuration,
282
+ clean,
213
283
  exportOptionsPlist: exportOptionsPlist ? node_path.default.resolve(root, exportOptionsPlist) : void 0
214
284
  };
215
285
  }
@@ -262,6 +332,7 @@ function parseDoctorOptions(argv) {
262
332
  function parseInternalMetroOptions(argv) {
263
333
  let workspaceRoot;
264
334
  let port = 8081;
335
+ let resetCache = false;
265
336
  for (let index = 0; index < argv.length; index += 1) {
266
337
  const arg = argv[index];
267
338
  if (arg === "--workspace-root") {
@@ -276,13 +347,18 @@ function parseInternalMetroOptions(argv) {
276
347
  port = Number(arg.slice(7));
277
348
  continue;
278
349
  }
350
+ if (arg === "--reset-cache") {
351
+ resetCache = true;
352
+ continue;
353
+ }
279
354
  throw new Error(`Unknown internal option: ${arg}`);
280
355
  }
281
356
  if (!workspaceRoot) throw new Error("Missing --workspace-root");
282
357
  if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid port: ${port}`);
283
358
  return {
284
359
  workspaceRoot,
285
- port
360
+ port,
361
+ resetCache
286
362
  };
287
363
  }
288
364
  function isBleamProjectRoot(projectRoot) {
@@ -322,6 +398,11 @@ function assertStringField(value, field) {
322
398
  if (typeof value !== "string" || value.trim() === "") throw new Error(`app.config.ts must define ${field}`);
323
399
  return value;
324
400
  }
401
+ function normalizeAppGroups(value, bundleIdentifier) {
402
+ const configured = Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
403
+ if (configured.length > 0) return configured;
404
+ return bundleIdentifier ? [`group.${bundleIdentifier}`] : [];
405
+ }
325
406
  function isHexColor(value) {
326
407
  return typeof value === "string" && /^#[\da-f]{6}$/i.test(value);
327
408
  }
@@ -365,11 +446,13 @@ function loadAppConfig(projectRoot) {
365
446
  });
366
447
  if (result.status !== 0) throw new Error(result.stderr.trim() || "Failed to load app.config.ts");
367
448
  const value = JSON.parse(result.stdout);
449
+ const bundleIdentifier = typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0;
368
450
  return {
369
451
  name: assertStringField(value.name, "name"),
370
452
  version: typeof value.version === "string" ? value.version : void 0,
371
453
  buildNumber: typeof value.buildNumber === "string" ? value.buildNumber : void 0,
372
- bundleIdentifier: typeof value.bundleIdentifier === "string" ? value.bundleIdentifier : void 0,
454
+ bundleIdentifier,
455
+ appGroups: normalizeAppGroups(value.appGroups, bundleIdentifier),
373
456
  teamId: typeof value.teamId === "string" ? value.teamId : void 0,
374
457
  appearance: normalizeAppearance(value.appearance)
375
458
  };
@@ -392,6 +475,7 @@ function readProjectConfig(options) {
392
475
  version: config.version,
393
476
  buildNumber: config.buildNumber ?? "1",
394
477
  bundleIdentifier: config.bundleIdentifier,
478
+ appGroups: config.appGroups ?? [],
395
479
  teamId: config.teamId,
396
480
  appearance: config.appearance ?? normalizeAppearance(void 0),
397
481
  runtimeVersion,
@@ -409,22 +493,40 @@ function nameFromDirectory(projectRoot) {
409
493
  function singleQuotedString(value) {
410
494
  return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n")}'`;
411
495
  }
412
- function createNewProject(options) {
413
- const templateRoot = node_path.default.join(packageRoot, "templates", "basic");
414
- if (!(0, node_fs.existsSync)(templateRoot)) throw new Error(`Missing basic template: ${templateRoot}`);
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}`);
416
- (0, node_fs.mkdirSync)(options.projectRoot, { recursive: true });
417
- (0, node_fs.cpSync)(templateRoot, options.projectRoot, { recursive: true });
496
+ function updateStarterAppName(projectRoot, name) {
497
+ const appPath = node_path.default.join(projectRoot, "app", "index.tsx");
498
+ const source = (0, node_fs.readFileSync)(appPath, "utf8");
499
+ const placeholder = `const appName = ${singleQuotedString(starterAppName)}`;
500
+ if (!source.includes(placeholder)) throw new Error(`Missing starter app name placeholder: ${appPath}`);
501
+ (0, node_fs.writeFileSync)(appPath, source.replace(placeholder, `const appName = ${singleQuotedString(name)}`));
502
+ }
503
+ function createProject(options) {
504
+ const template = options.template ?? "basic";
505
+ const templateRoot = node_path.default.join(packageRoot, "templates", template);
506
+ if (!(0, node_fs.existsSync)(templateRoot)) throw new Error(`Missing ${template} template: ${templateRoot}`);
507
+ if ((0, node_fs.existsSync)(options.projectRoot)) throw new Error(`Destination already exists: ${options.projectRoot}`);
418
508
  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
- });
509
+ (0, node_fs.mkdirSync)(options.projectRoot, { recursive: true });
510
+ try {
511
+ (0, node_fs.cpSync)(templateRoot, options.projectRoot, { recursive: true });
512
+ updateStarterAppName(options.projectRoot, name);
513
+ (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`);
514
+ (0, node_fs.writeFileSync)(node_path.default.join(options.projectRoot, ".gitignore"), `.bleam\nbiome.json\n.DS_Store\n`);
515
+ createDevWorkspace(options.projectRoot, {
516
+ name,
517
+ buildNumber: "1",
518
+ appGroups: [],
519
+ appearance: normalizeAppearance(void 0),
520
+ runtimeVersion,
521
+ sdkVersion: runtimeVersion
522
+ });
523
+ } catch (error) {
524
+ (0, node_fs.rmSync)(options.projectRoot, {
525
+ recursive: true,
526
+ force: true
527
+ });
528
+ throw error;
529
+ }
428
530
  console.log(`Created Bleam project at ${options.projectRoot}`);
429
531
  }
430
532
  function createDevWorkspace(projectRoot, config) {
@@ -438,7 +540,7 @@ function createDevWorkspace(projectRoot, config) {
438
540
  });
439
541
  (0, node_fs.symlinkSync)(dependencyRoot, workspaceNodeModules);
440
542
  ensureProjectTsconfig(projectRoot);
441
- (0, node_fs.writeFileSync)(node_path.default.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: {
543
+ const expoConfig = {
442
544
  name: config.name,
443
545
  slug: slugFromName(config.name),
444
546
  version: config.version ?? "0.0.0",
@@ -451,7 +553,10 @@ function createDevWorkspace(projectRoot, config) {
451
553
  runtimeVersion: config.runtimeVersion,
452
554
  sdkVersion: config.sdkVersion
453
555
  } }
454
- } }, null, 2)}\n`);
556
+ };
557
+ if ((0, node_fs.existsSync)(node_path.default.join(projectRoot, "icon.png"))) expoConfig.icon = "../../icon.png";
558
+ if ((0, node_fs.existsSync)(node_path.default.join(projectRoot, "splash.png"))) expoConfig.plugins = [["expo-splash-screen", { image: "../../splash.png" }]];
559
+ (0, node_fs.writeFileSync)(node_path.default.join(workspaceRoot, "app.config.js"), `module.exports = ${JSON.stringify({ expo: expoConfig }, null, 2)}\n`);
455
560
  (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`);
456
561
  (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`);
457
562
  (0, node_fs.writeFileSync)(node_path.default.join(workspaceRoot, "package.json"), `${JSON.stringify({
@@ -463,6 +568,7 @@ function createDevWorkspace(projectRoot, config) {
463
568
  "@types/react": packageJson.dependencies?.["@types/react"],
464
569
  "@biomejs/biome": packageJson.dependencies?.["@biomejs/biome"],
465
570
  expo: packageJson.dependencies?.expo,
571
+ "expo-splash-screen": packageJson.dependencies?.["expo-splash-screen"],
466
572
  react: packageJson.dependencies?.react,
467
573
  "react-native": packageJson.dependencies?.["react-native"],
468
574
  typescript: packageJson.dependencies?.typescript
@@ -547,6 +653,24 @@ function replacePlistString(plist, key, value) {
547
653
  if (!pattern.test(plist)) return plist;
548
654
  return plist.replace(pattern, `$1${plistString(value)}$3`);
549
655
  }
656
+ function plistStringArray(values) {
657
+ return values.map((value) => `\t\t<string>${plistString(value)}</string>`).join("\n");
658
+ }
659
+ function replaceOrInsertPlistString(plist, key, value) {
660
+ const replaced = replacePlistString(plist, key, value);
661
+ if (replaced !== plist) return replaced;
662
+ const closingDictionary = plist.lastIndexOf("</dict>");
663
+ if (closingDictionary < 0) return plist;
664
+ return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<string>${plistString(value)}</string>\n${plist.slice(closingDictionary)}`;
665
+ }
666
+ function replaceOrInsertPlistStringArray(plist, key, values) {
667
+ const pattern = /* @__PURE__ */ new RegExp(`(<key>${key}</key>\\s*<array>)([\\s\\S]*?)(</array>)`);
668
+ const replacement = `$1\n${plistStringArray(values)}\n\t$3`;
669
+ if (pattern.test(plist)) return plist.replace(pattern, replacement);
670
+ const closingDictionary = plist.lastIndexOf("</dict>");
671
+ if (closingDictionary < 0) return plist;
672
+ return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<array>\n${plistStringArray(values)}\n\t</array>\n${plist.slice(closingDictionary)}`;
673
+ }
550
674
  function writeGeneratedAppJson(nativeRoot, config) {
551
675
  (0, node_fs.writeFileSync)(node_path.default.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
552
676
  name: config.name,
@@ -563,40 +687,60 @@ function writeGeneratedAppJson(nativeRoot, config) {
563
687
  function patchNativeProject(nativeRoot, config) {
564
688
  const projectPath = node_path.default.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
565
689
  const infoPlistPath = node_path.default.join(nativeRoot, "ios", "Bleam", "Info.plist");
690
+ const generationInfoPlistPath = node_path.default.join(nativeRoot, "ios", "GenerationService", "Info.plist");
691
+ const platformInfoPlistPath = node_path.default.join(nativeRoot, "ios", "PlatformHelper", "Info.plist");
692
+ const appEntitlementsPath = node_path.default.join(nativeRoot, "ios", "Bleam", "bleam.entitlements");
693
+ const generationEntitlementsPath = node_path.default.join(nativeRoot, "ios", "GenerationService", "GenerationService.entitlements");
566
694
  const productName = xcodeSettingValue(config.name);
567
695
  const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
696
+ const generationBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.GenerationService`);
697
+ const platformBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.PlatformHelper`);
568
698
  const teamId = xcodeSettingValue(config.teamId ?? "");
569
699
  const version = xcodeSettingValue(config.version ?? "1.0.0");
570
700
  const buildNumber = xcodeSettingValue(config.buildNumber);
701
+ const primaryAppGroup = config.appGroups[0];
571
702
  let project = (0, node_fs.readFileSync)(projectPath, "utf8");
572
- project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = [^;]+;/g, () => {
573
- return `PRODUCT_BUNDLE_IDENTIFIER = ${bundleIdentifier};`;
703
+ project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g, (_, current) => {
704
+ return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : current.endsWith(".PlatformHelper") ? platformBundleIdentifier : bundleIdentifier};`;
574
705
  }).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
575
706
  return `DEVELOPMENT_TEAM = ${teamId};`;
576
707
  }).replace(/MARKETING_VERSION = [^;]+;/g, () => {
577
708
  return `MARKETING_VERSION = ${version};`;
578
709
  }).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, () => {
579
710
  return `CURRENT_PROJECT_VERSION = ${buildNumber};`;
580
- }).replace(/PRODUCT_NAME = [^;]+;/g, () => {
711
+ }).replace(/PRODUCT_NAME = bleam;/g, () => {
581
712
  return `PRODUCT_NAME = ${productName};`;
582
713
  });
583
714
  (0, node_fs.writeFileSync)(projectPath, project);
584
715
  let infoPlist = (0, node_fs.readFileSync)(infoPlistPath, "utf8");
585
716
  infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
586
717
  infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
718
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamRuntimeVersion", config.runtimeVersion);
719
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamTeamIdentifier", config.teamId ?? "");
720
+ const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL;
721
+ if (updateServiceUrl) infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
587
722
  infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
723
+ if (primaryAppGroup) infoPlist = replaceOrInsertPlistString(infoPlist, "AppGroupIdentifier", primaryAppGroup);
588
724
  (0, node_fs.writeFileSync)(infoPlistPath, infoPlist);
725
+ if (primaryAppGroup) {
726
+ let generationInfoPlist = (0, node_fs.readFileSync)(generationInfoPlistPath, "utf8");
727
+ generationInfoPlist = replaceOrInsertPlistString(generationInfoPlist, "AppGroupIdentifier", primaryAppGroup);
728
+ (0, node_fs.writeFileSync)(generationInfoPlistPath, generationInfoPlist);
729
+ (0, node_fs.writeFileSync)(platformInfoPlistPath, replaceOrInsertPlistString((0, node_fs.readFileSync)(platformInfoPlistPath, "utf8"), "BleamPlatformVersion", config.runtimeVersion));
730
+ for (const entitlementsPath of [appEntitlementsPath, generationEntitlementsPath]) (0, node_fs.writeFileSync)(entitlementsPath, replaceOrInsertPlistStringArray((0, node_fs.readFileSync)(entitlementsPath, "utf8"), "com.apple.security.application-groups", config.appGroups));
731
+ }
589
732
  }
590
733
  function createBuildNativeWorkspace(options, config) {
591
734
  const nativeRoot = node_path.default.join(options.projectRoot, ".bleam", "build", "native");
592
735
  const templateRoot = nativeRuntimeTemplateRoot();
593
- (0, node_fs.rmSync)(nativeRoot, {
736
+ if (options.clean) (0, node_fs.rmSync)(nativeRoot, {
594
737
  recursive: true,
595
738
  force: true
596
739
  });
597
740
  (0, node_fs.mkdirSync)(node_path.default.dirname(nativeRoot), { recursive: true });
598
741
  (0, node_fs.cpSync)(templateRoot, nativeRoot, {
599
742
  recursive: true,
743
+ verbatimSymlinks: true,
600
744
  filter(source) {
601
745
  return !node_path.default.relative(templateRoot, source).split(node_path.default.sep).some((part) => {
602
746
  return part === "build" || part === "dist" || part === "node_modules" || part === ".expo" || part === "Pods" || part === ".xcode.env.local" || part === ".DS_Store" || part.endsWith(".xcworkspace");
@@ -636,32 +780,66 @@ function runInheritedCommand(command, args, cwd) {
636
780
  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
781
  }
638
782
  function installPods(nativeRoot) {
783
+ const iosRoot = node_path.default.join(nativeRoot, "ios");
784
+ const expectedStamp = podInstallInputStamp(iosRoot);
785
+ if (hasCurrentPodInstall(iosRoot, expectedStamp)) {
786
+ console.log("Using cached native dependencies");
787
+ return;
788
+ }
639
789
  console.log("Installing native dependencies...");
640
- runInheritedCommand("pod", ["install"], node_path.default.join(nativeRoot, "ios"));
790
+ runInheritedCommand("pod", ["install"], iosRoot);
791
+ (0, node_fs.writeFileSync)(podInstallStampPath(iosRoot), `${expectedStamp}\n`);
792
+ }
793
+ function podInstallInputStamp(iosRoot) {
794
+ const inputHash = (0, node_crypto.createHash)("sha256");
795
+ for (const relative of [
796
+ "Podfile",
797
+ "Podfile.lock",
798
+ "../package.json"
799
+ ]) {
800
+ const input = node_path.default.resolve(iosRoot, relative);
801
+ if ((0, node_fs.existsSync)(input)) inputHash.update((0, node_fs.readFileSync)(input));
802
+ }
803
+ return inputHash.digest("hex");
641
804
  }
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", [
805
+ function podInstallStampPath(iosRoot) {
806
+ return node_path.default.join(iosRoot, ".bleam-pods-input");
807
+ }
808
+ function hasCurrentPodInstall(iosRoot, expectedStamp) {
809
+ const stampPath = podInstallStampPath(iosRoot);
810
+ return (0, node_fs.existsSync)(node_path.default.join(iosRoot, "Bleam.xcworkspace")) && (0, node_fs.existsSync)(node_path.default.join(iosRoot, "Pods", "Manifest.lock")) && (0, node_fs.existsSync)(stampPath) && (0, node_fs.readFileSync)(stampPath, "utf8").trim() === expectedStamp;
811
+ }
812
+ function nativeArchiveArgs(options) {
813
+ return [
652
814
  "-allowProvisioningUpdates",
653
815
  "-workspace",
654
816
  node_path.default.join(options.nativeRoot, "ios", "Bleam.xcworkspace"),
655
817
  "-scheme",
656
- "bleam",
818
+ "Bleam",
657
819
  "-configuration",
658
820
  options.configuration,
659
821
  "-destination",
660
822
  "platform=macOS,arch=arm64,variant=Mac Catalyst",
661
823
  "-archivePath",
662
- archivePath,
824
+ options.archivePath,
825
+ "ARCHS=arm64",
663
826
  "archive"
664
- ], options.nativeRoot);
827
+ ];
828
+ }
829
+ function archiveNativeApp(options) {
830
+ const platformOutput = node_path.default.join(options.outputRoot, "macos");
831
+ const archivePath = node_path.default.join(platformOutput, `${safeArtifactName(options.config.name)}.xcarchive`);
832
+ (0, node_fs.mkdirSync)(platformOutput, { recursive: true });
833
+ (0, node_fs.rmSync)(archivePath, {
834
+ recursive: true,
835
+ force: true
836
+ });
837
+ console.log(`Archiving ${options.config.name} for macOS...`);
838
+ runInheritedCommand("xcodebuild", nativeArchiveArgs({
839
+ nativeRoot: options.nativeRoot,
840
+ archivePath,
841
+ configuration: options.configuration
842
+ }), options.nativeRoot);
665
843
  return archivePath;
666
844
  }
667
845
  function exportNativeArchive(options) {
@@ -704,6 +882,177 @@ async function buildNativeArchives(options) {
704
882
  });
705
883
  console.log(`Exported ${exportPath}`);
706
884
  }
885
+ return archivePath;
886
+ }
887
+ async function installNativeApp(options) {
888
+ const destination = installArchivedApp(await buildNativeArchives(options));
889
+ console.log(`Installed ${destination}`);
890
+ }
891
+ function installArchivedApp(archivePath, applications = node_path.default.join(node_os.default.homedir(), "Applications")) {
892
+ const products = node_path.default.join(archivePath, "Products", "Applications");
893
+ const appName = (0, node_fs.readdirSync)(products).find((name) => name.endsWith(".app"));
894
+ if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
895
+ const source = node_path.default.join(products, appName);
896
+ const destination = applications === node_path.default.join(node_os.default.homedir(), "Applications") ? installedAppPath(appName) : node_path.default.join(applications, appName);
897
+ (0, node_fs.mkdirSync)(applications, { recursive: true });
898
+ (0, node_fs.rmSync)(destination, {
899
+ recursive: true,
900
+ force: true
901
+ });
902
+ (0, node_child_process.execFileSync)("ditto", [source, destination], { stdio: "inherit" });
903
+ return destination;
904
+ }
905
+ function installedAppPath(appName) {
906
+ if (node_path.default.basename(appName) !== appName || !appName.endsWith(".app")) throw new Error(`Invalid macOS app name: ${appName}`);
907
+ return node_path.default.join(node_os.default.homedir(), "Applications", appName);
908
+ }
909
+ async function publishOta(options) {
910
+ const config = readProjectConfig(options);
911
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
912
+ if (!config.teamId) throw missingBuildFieldError("teamId");
913
+ const workspaceRoot = createBuildNativeWorkspace({
914
+ projectRoot: options.projectRoot,
915
+ output: node_path.default.join(options.projectRoot, "dist"),
916
+ configuration: "Release",
917
+ clean: false
918
+ }, config);
919
+ const outputRoot = node_path.default.join(options.projectRoot, ".bleam", "publish", "ota");
920
+ const bundleRoot = node_path.default.join(outputRoot, "bundle");
921
+ const bundlePath = node_path.default.join(bundleRoot, "bundle.js");
922
+ const archivePath = node_path.default.join(outputRoot, "ota.zip");
923
+ (0, node_fs.rmSync)(outputRoot, {
924
+ recursive: true,
925
+ force: true
926
+ });
927
+ (0, node_fs.mkdirSync)(bundleRoot, { recursive: true });
928
+ console.log(`Bundling OTA update for runtime ${runtimeVersion}...`);
929
+ runInheritedCommand(process.execPath, [
930
+ require$1.resolve("@expo/cli"),
931
+ "export:embed",
932
+ "--entry-file",
933
+ node_path.default.join(workspaceRoot, "index.ts"),
934
+ "--platform",
935
+ "ios",
936
+ "--dev",
937
+ "false",
938
+ "--bundle-output",
939
+ bundlePath,
940
+ "--assets-dest",
941
+ node_path.default.join(bundleRoot, "assets"),
942
+ "--minify",
943
+ "true"
944
+ ], workspaceRoot);
945
+ runInheritedCommand("ditto", [
946
+ "-c",
947
+ "-k",
948
+ "--keepParent",
949
+ bundleRoot,
950
+ archivePath
951
+ ], outputRoot);
952
+ const sizeBytes = (0, node_fs.readFileSync)(archivePath).byteLength;
953
+ const digest = sha256(archivePath);
954
+ const releaseId = `ota_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
955
+ const release = {
956
+ schemaVersion: 1,
957
+ kind: "ota",
958
+ bundleIdentifier: config.bundleIdentifier,
959
+ teamIdentifier: config.teamId,
960
+ channel: "stable",
961
+ releaseId,
962
+ bundleVersion: `${config.version ?? "1.0.0"}+${config.buildNumber}`,
963
+ runtimeVersion,
964
+ artifact: {
965
+ format: "bleam-ota-zip-v1",
966
+ downloadId: releaseId,
967
+ sizeBytes,
968
+ sha256: digest
969
+ },
970
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
971
+ };
972
+ const response = await fetch(`${options.serviceUrl}/v1/local/releases/ota`, {
973
+ method: "POST",
974
+ headers: { "content-type": "application/json" },
975
+ body: JSON.stringify({
976
+ release,
977
+ artifactBase64: (0, node_fs.readFileSync)(archivePath).toString("base64")
978
+ })
979
+ });
980
+ if (!response.ok) throw new Error(`Local OTA publication failed: ${response.status} ${await response.text()}`);
981
+ console.log(`Published OTA update ${releaseId}`);
982
+ console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/ota/${runtimeVersion}`);
983
+ }
984
+ async function publishPlatform(options) {
985
+ const config = readProjectConfig(options);
986
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
987
+ if (!config.teamId) throw missingBuildFieldError("teamId");
988
+ const archivePath = await buildNativeArchives({
989
+ projectRoot: options.projectRoot,
990
+ output: node_path.default.join(options.projectRoot, "dist"),
991
+ configuration: "Release",
992
+ clean: false
993
+ });
994
+ const products = node_path.default.join(archivePath, "Products", "Applications");
995
+ const appName = (0, node_fs.readdirSync)(products).find((name) => name.endsWith(".app"));
996
+ if (!appName) throw new Error(`Archive does not contain a macOS app: ${archivePath}`);
997
+ const outputRoot = node_path.default.join(options.projectRoot, ".bleam", "publish", "platform");
998
+ const stagingRoot = node_path.default.join(outputRoot, "app");
999
+ const artifactPath = node_path.default.join(outputRoot, "platform.zip");
1000
+ (0, node_fs.rmSync)(outputRoot, {
1001
+ recursive: true,
1002
+ force: true
1003
+ });
1004
+ (0, node_fs.mkdirSync)(stagingRoot, { recursive: true });
1005
+ (0, node_child_process.execFileSync)("ditto", [node_path.default.join(products, appName), node_path.default.join(stagingRoot, appName)]);
1006
+ runInheritedCommand("zip", [
1007
+ "-q",
1008
+ "-r",
1009
+ "-y",
1010
+ artifactPath,
1011
+ "app"
1012
+ ], outputRoot);
1013
+ const sizeBytes = (0, node_fs.readFileSync)(artifactPath).byteLength;
1014
+ const digest = sha256(artifactPath);
1015
+ const releaseId = `platform_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
1016
+ const release = platformRelease(config, releaseId, sizeBytes, digest);
1017
+ const response = await fetch(`${options.serviceUrl}/v1/local/releases/platform`, {
1018
+ method: "POST",
1019
+ headers: { "content-type": "application/json" },
1020
+ body: JSON.stringify({
1021
+ release,
1022
+ artifactBase64: (0, node_fs.readFileSync)(artifactPath).toString("base64")
1023
+ })
1024
+ });
1025
+ if (!response.ok) throw new Error(`Local platform publication failed: ${response.status} ${await response.text()}`);
1026
+ console.log(`Published platform update ${releaseId}`);
1027
+ console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/platform/macos-arm64`);
1028
+ }
1029
+ function platformRelease(config, releaseId, sizeBytes, digest) {
1030
+ if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
1031
+ if (!config.teamId) throw missingBuildFieldError("teamId");
1032
+ return {
1033
+ schemaVersion: 1,
1034
+ kind: "platform",
1035
+ bundleIdentifier: config.bundleIdentifier,
1036
+ teamIdentifier: config.teamId,
1037
+ channel: "stable",
1038
+ releaseId,
1039
+ appVersion: config.version ?? "1.0.0",
1040
+ buildNumber: config.buildNumber,
1041
+ bleamPlatformVersion: runtimeVersion,
1042
+ target: {
1043
+ os: "macos",
1044
+ architecture: "arm64",
1045
+ installLocation: "user-applications",
1046
+ minimumMacOSVersion: "14.0"
1047
+ },
1048
+ artifact: {
1049
+ format: "bleam-macos-app-zip-v1",
1050
+ downloadId: releaseId,
1051
+ sizeBytes,
1052
+ sha256: digest
1053
+ },
1054
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString()
1055
+ };
707
1056
  }
708
1057
  function runtimeCacheRoot() {
709
1058
  return node_path.default.join(node_os.default.homedir(), "Library", "Caches", "bleam", "runtimes", runtimeVersion, artifactPlatform());
@@ -754,16 +1103,22 @@ async function downloadRuntimeArtifact() {
754
1103
  const runtimePlatform = artifactPlatform();
755
1104
  const cacheRoot = runtimeCacheRoot();
756
1105
  const cachedAppPath = node_path.default.join(cacheRoot, "Bleam.app");
757
- if ((0, node_fs.existsSync)(cachedAppPath)) return {
758
- appPath: cachedAppPath,
759
- version: runtimeVersion,
760
- platform: runtimePlatform
761
- };
1106
+ if ((0, node_fs.existsSync)(cachedAppPath)) {
1107
+ console.log(`Using cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
1108
+ return {
1109
+ appPath: cachedAppPath,
1110
+ version: runtimeVersion,
1111
+ platform: runtimePlatform
1112
+ };
1113
+ }
762
1114
  (0, node_fs.mkdirSync)(cacheRoot, { recursive: true });
763
1115
  const archiveUrl = runtimeArchiveUrl();
764
1116
  const archivePath = node_path.default.join(cacheRoot, runtimeArchiveName());
765
1117
  const extractRoot = node_path.default.join(cacheRoot, "extract");
1118
+ console.log(`Downloading Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
1119
+ console.log(`Runtime archive: ${archiveUrl}`);
766
1120
  await downloadFile(archiveUrl, archivePath);
1121
+ console.log("Verifying Bleam runtime archive");
767
1122
  const expectedSha256 = await downloadExpectedSha256(archiveUrl);
768
1123
  if (expectedSha256 && sha256(archivePath) !== expectedSha256) {
769
1124
  (0, node_fs.rmSync)(archivePath, { force: true });
@@ -774,6 +1129,7 @@ async function downloadRuntimeArtifact() {
774
1129
  force: true
775
1130
  });
776
1131
  (0, node_fs.mkdirSync)(extractRoot, { recursive: true });
1132
+ console.log("Extracting Bleam runtime archive");
777
1133
  (0, node_child_process.execFileSync)("ditto", [
778
1134
  "-x",
779
1135
  "-k",
@@ -786,11 +1142,15 @@ async function downloadRuntimeArtifact() {
786
1142
  recursive: true,
787
1143
  force: true
788
1144
  });
789
- (0, node_fs.cpSync)(extractedAppPath, cachedAppPath, { recursive: true });
1145
+ (0, node_fs.cpSync)(extractedAppPath, cachedAppPath, {
1146
+ recursive: true,
1147
+ verbatimSymlinks: true
1148
+ });
790
1149
  (0, node_fs.rmSync)(extractRoot, {
791
1150
  recursive: true,
792
1151
  force: true
793
1152
  });
1153
+ console.log(`Cached Bleam runtime at ${prettyPath(cachedAppPath)}`);
794
1154
  return {
795
1155
  appPath: cachedAppPath,
796
1156
  version: runtimeVersion,
@@ -799,10 +1159,12 @@ async function downloadRuntimeArtifact() {
799
1159
  }
800
1160
  async function resolveRuntimeArtifact() {
801
1161
  const runtimePlatform = artifactPlatform();
1162
+ console.log(`Resolving Bleam runtime ${runtimeVersion} for ${runtimePlatform}`);
802
1163
  const overrideAppPath = process.env.BLEAM_RUNTIME_APP;
803
1164
  if (overrideAppPath) {
804
1165
  const appPath = node_path.default.resolve(overrideAppPath);
805
1166
  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()}`);
1167
+ console.log(`Using BLEAM_RUNTIME_APP runtime at ${prettyPath(appPath)}`);
806
1168
  return {
807
1169
  appPath,
808
1170
  version: runtimeVersion,
@@ -810,11 +1172,14 @@ async function resolveRuntimeArtifact() {
810
1172
  };
811
1173
  }
812
1174
  const localAppPath = localDevelopmentRuntimeAppPath();
813
- if ((0, node_fs.existsSync)(localAppPath)) return {
814
- appPath: localAppPath,
815
- version: runtimeVersion,
816
- platform: runtimePlatform
817
- };
1175
+ if ((0, node_fs.existsSync)(localAppPath)) {
1176
+ console.log(`Using local development runtime at ${prettyPath(localAppPath)}`);
1177
+ return {
1178
+ appPath: localAppPath,
1179
+ version: runtimeVersion,
1180
+ platform: runtimePlatform
1181
+ };
1182
+ }
818
1183
  try {
819
1184
  return await downloadRuntimeArtifact();
820
1185
  } catch (error) {
@@ -823,14 +1188,31 @@ async function resolveRuntimeArtifact() {
823
1188
  }
824
1189
  function copyRuntimeClient(projectRoot, runtime) {
825
1190
  const clientAppPath = node_path.default.join(projectRoot, ".bleam", "client", "Bleam.app");
1191
+ console.log(`Preparing Bleam app at ${prettyPath(clientAppPath)}`);
826
1192
  (0, node_fs.rmSync)(clientAppPath, {
827
1193
  recursive: true,
828
1194
  force: true
829
1195
  });
830
1196
  (0, node_fs.mkdirSync)(node_path.default.dirname(clientAppPath), { recursive: true });
831
- (0, node_fs.cpSync)(runtime.appPath, clientAppPath, { recursive: true });
1197
+ (0, node_fs.cpSync)(runtime.appPath, clientAppPath, {
1198
+ recursive: true,
1199
+ verbatimSymlinks: true
1200
+ });
832
1201
  return clientAppPath;
833
1202
  }
1203
+ function cleanDevArtifacts(projectRoot) {
1204
+ const bleamRoot = node_path.default.join(projectRoot, ".bleam");
1205
+ const targets = [
1206
+ "client",
1207
+ "dev",
1208
+ "project.json"
1209
+ ].map((name) => node_path.default.join(bleamRoot, name));
1210
+ console.log("Cleaning Bleam dev artifacts");
1211
+ for (const target of targets) (0, node_fs.rmSync)(target, {
1212
+ recursive: true,
1213
+ force: true
1214
+ });
1215
+ }
834
1216
  function isBleamManifest(value) {
835
1217
  const manifest = value && typeof value === "object" ? value : {};
836
1218
  return typeof manifest.name === "string" && typeof manifest.bundleUrl === "string" && manifest.platform === "macos" && manifest.dev === true;
@@ -932,6 +1314,15 @@ function runDoctor(options) {
932
1314
  function formatDuration(ms) {
933
1315
  return `${(ms / 1e3).toFixed(1)}s`;
934
1316
  }
1317
+ async function bundleErrorPreview(response) {
1318
+ const contentType = response.headers.get("content-type") ?? "";
1319
+ if (!contentType.includes("json") && !contentType.includes("text")) return "";
1320
+ try {
1321
+ return (await response.text()).trim().slice(0, 1e3);
1322
+ } catch {
1323
+ return "";
1324
+ }
1325
+ }
935
1326
  async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
936
1327
  const startedAt = Date.now();
937
1328
  const requestTimeoutMs = 3e4;
@@ -943,14 +1334,17 @@ async function waitForDevBundle(bundleUrl, timeoutMs = 6e4) {
943
1334
  cache: "no-store",
944
1335
  signal: AbortSignal.timeout(requestTimeoutMs)
945
1336
  });
946
- await response.body?.cancel();
947
1337
  if (response.ok) {
1338
+ await response.body?.cancel();
948
1339
  spinner?.success(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
949
1340
  if (!spinner) console.log(`Bundled app in ${formatDuration(Date.now() - startedAt)}`);
950
1341
  return;
951
1342
  }
952
- if (spinner) spinner.text = `Bundle request returned ${response.status}, retrying...`;
953
- else console.log(`Bundle request returned ${response.status}, retrying...`);
1343
+ const preview = await bundleErrorPreview(response);
1344
+ const detail = preview ? `\n${preview}` : "";
1345
+ const message = `Bundle request returned ${response.status}, retrying...${detail}`;
1346
+ if (spinner) spinner.text = message;
1347
+ else console.log(message);
954
1348
  } catch (error) {
955
1349
  if (error instanceof Error && error.name === "TimeoutError") {
956
1350
  const message = `Bundle request timed out after ${formatDuration(Date.now() - requestStartedAt)}, retrying...`;
@@ -986,7 +1380,11 @@ function openRuntimeClient(appPath, projectConfigPath, manifest) {
986
1380
  if (code && code !== 0) console.error(`Failed to open Bleam runtime at ${appPath}`);
987
1381
  });
988
1382
  }
1383
+ function quitRuntimeClient() {
1384
+ (0, node_child_process.spawnSync)("osascript", ["-e", "tell application id \"dev.bleam.app\" to quit"], { stdio: "ignore" });
1385
+ }
989
1386
  async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
1387
+ quitRuntimeClient();
990
1388
  openRuntimeClient(appPath, projectConfigPath, manifest);
991
1389
  }
992
1390
  function focusRuntimeClient() {
@@ -999,6 +1397,49 @@ function sendJson(res, status, value) {
999
1397
  });
1000
1398
  res.end(JSON.stringify(value));
1001
1399
  }
1400
+ function readRequestBody(req, limit = 1e6) {
1401
+ return new Promise((resolve, reject) => {
1402
+ const chunks = [];
1403
+ let size = 0;
1404
+ req.on("data", (chunk) => {
1405
+ size += chunk.length;
1406
+ if (size > limit) {
1407
+ reject(/* @__PURE__ */ new Error("Request body too large"));
1408
+ req.destroy();
1409
+ return;
1410
+ }
1411
+ chunks.push(chunk);
1412
+ });
1413
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1414
+ req.on("error", reject);
1415
+ });
1416
+ }
1417
+ function isLogLevel(value) {
1418
+ return value === "debug" || value === "info" || value === "warn" || value === "error";
1419
+ }
1420
+ function isLogSource(value) {
1421
+ return value === "cli" || value === "metro" || value === "runtime" || value === "js" || value === "native" || value === "xpc";
1422
+ }
1423
+ function normalizeDevLogEvent(value) {
1424
+ const input = value && typeof value === "object" ? value : {};
1425
+ const message = typeof input.message === "string" ? input.message : input.message === void 0 ? "" : JSON.stringify(input.message);
1426
+ return {
1427
+ type: "log",
1428
+ id: typeof input.id === "string" && input.id ? input.id : (0, node_crypto.randomUUID)(),
1429
+ time: typeof input.time === "string" && input.time ? input.time : (/* @__PURE__ */ new Date()).toISOString(),
1430
+ source: isLogSource(input.source) ? input.source : "runtime",
1431
+ level: isLogLevel(input.level) ? input.level : "info",
1432
+ category: typeof input.category === "string" && input.category ? input.category : void 0,
1433
+ message,
1434
+ details: input.details,
1435
+ jobId: typeof input.jobId === "string" && input.jobId ? input.jobId : void 0
1436
+ };
1437
+ }
1438
+ function formatDevLogEvent(event) {
1439
+ const category = event.category ? `:${event.category}` : "";
1440
+ const job = event.jobId ? ` ${event.jobId.slice(0, 8)}` : "";
1441
+ return `[${event.source}${category}:${event.level}${job}] ${event.message}`;
1442
+ }
1002
1443
  async function requestRuntimeReload(origin) {
1003
1444
  const response = await fetch(`${origin}/bleam/reload`, {
1004
1445
  method: "POST",
@@ -1017,6 +1458,13 @@ function biomeBinPath() {
1017
1458
  if (!bin) throw new Error("Could not resolve @biomejs/biome binary");
1018
1459
  return node_path.default.join(biomePackageRoot, bin);
1019
1460
  }
1461
+ function typescriptBinPath() {
1462
+ const typescriptPackageJsonPath = require$1.resolve("typescript/package.json");
1463
+ const typescriptPackageRoot = node_path.default.dirname(typescriptPackageJsonPath);
1464
+ const bin = require$1("typescript/package.json").bin?.tsc;
1465
+ if (!bin) throw new Error("Could not resolve TypeScript compiler binary");
1466
+ return node_path.default.join(typescriptPackageRoot, bin);
1467
+ }
1020
1468
  function ensureProjectToolWorkspace(options) {
1021
1469
  const config = readProjectConfig({ projectRoot: options.projectRoot });
1022
1470
  return createDevWorkspace(options.projectRoot, config);
@@ -1044,6 +1492,20 @@ function runBiomeCommand(command, options) {
1044
1492
  if (result.error) throw result.error;
1045
1493
  process.exit(result.status ?? 1);
1046
1494
  }
1495
+ function runTypecheckCommand(options) {
1496
+ assertBleamConfigFiles(options.projectRoot);
1497
+ ensureProjectToolWorkspace(options);
1498
+ const result = (0, node_child_process.spawnSync)(typescriptBinPath(), [
1499
+ "--noEmit",
1500
+ "-p",
1501
+ node_path.default.join(options.projectRoot, "tsconfig.json")
1502
+ ], {
1503
+ cwd: options.projectRoot,
1504
+ stdio: "inherit"
1505
+ });
1506
+ if (result.error) throw result.error;
1507
+ process.exit(result.status ?? 1);
1508
+ }
1047
1509
  function setupInteractiveControls(options) {
1048
1510
  if (!process.stdin.isTTY) return () => {};
1049
1511
  const wasRaw = process.stdin.isRaw;
@@ -1065,21 +1527,26 @@ function setupInteractiveControls(options) {
1065
1527
  if (key === "o") {
1066
1528
  (async () => {
1067
1529
  try {
1068
- if (!focusRuntimeClient()) {
1069
- console.log("Opening Bleam app...");
1070
- await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
1530
+ if (focusRuntimeClient()) {
1531
+ console.log("Focused Bleam app");
1532
+ return;
1071
1533
  }
1534
+ console.log(`Opening Bleam app at ${prettyPath(options.clientAppPath)}`);
1535
+ await launchRuntimeClient(options.clientAppPath, options.projectConfigPath, options.manifest);
1072
1536
  } catch (error) {
1073
1537
  console.error(error instanceof Error ? error.message : String(error));
1074
1538
  }
1075
1539
  })();
1076
1540
  continue;
1077
1541
  }
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
- });
1542
+ if (key === "r") {
1543
+ console.log("Requesting app reload");
1544
+ requestRuntimeReload(options.origin).then((clients) => {
1545
+ console.log(`Reloaded app (${clients} client${clients === 1 ? "" : "s"})`);
1546
+ }).catch((error) => {
1547
+ console.error(error instanceof Error ? error.message : String(error));
1548
+ });
1549
+ }
1083
1550
  }
1084
1551
  };
1085
1552
  process.stdin.setRawMode(true);
@@ -1090,12 +1557,18 @@ function setupInteractiveControls(options) {
1090
1557
  }
1091
1558
  async function startInternalMetroServer(options) {
1092
1559
  process.env.BLEAM_DEV_PLATFORM ??= "macos";
1560
+ const projectRoot = process.env.BLEAM_PROJECT_ROOT ?? options.workspaceRoot;
1561
+ const logPath = node_path.default.join(projectRoot, ".bleam", "dev", "logs", "dev.log");
1562
+ const recentLogs = [];
1563
+ const logClients = /* @__PURE__ */ new Set();
1564
+ const maxRecentLogs = 1e3;
1093
1565
  const metro = require$1("@expo/metro/metro");
1094
1566
  const { loadConfig } = require$1("@expo/metro/metro-config");
1095
1567
  const config = await loadConfig({
1096
1568
  config: node_path.default.join(options.workspaceRoot, "metro.config.js"),
1097
1569
  cwd: options.workspaceRoot,
1098
- port: options.port
1570
+ port: options.port,
1571
+ resetCache: options.resetCache
1099
1572
  });
1100
1573
  const { middleware, end, attachHmrServer } = await metro.createConnectMiddleware(config, {
1101
1574
  watch: true,
@@ -1103,6 +1576,16 @@ async function startInternalMetroServer(options) {
1103
1576
  });
1104
1577
  const messageClients = /* @__PURE__ */ new Set();
1105
1578
  const messageServer = new ws.WebSocketServer({ noServer: true });
1579
+ const logServer = new ws.WebSocketServer({ noServer: true });
1580
+ const recordLog = (event) => {
1581
+ recentLogs.push(event);
1582
+ if (recentLogs.length > maxRecentLogs) recentLogs.splice(0, recentLogs.length - maxRecentLogs);
1583
+ (0, node_fs.mkdirSync)(node_path.default.dirname(logPath), { recursive: true });
1584
+ (0, node_fs.appendFileSync)(logPath, `${JSON.stringify(event)}\n`, "utf8");
1585
+ const payload = JSON.stringify(event);
1586
+ for (const client of logClients) if (client.readyState === ws.WebSocket.OPEN) client.send(payload);
1587
+ console.log(formatDevLogEvent(event));
1588
+ };
1106
1589
  const broadcastReload = () => {
1107
1590
  const message = JSON.stringify({
1108
1591
  version: 2,
@@ -1120,6 +1603,11 @@ async function startInternalMetroServer(options) {
1120
1603
  messageClients.add(client);
1121
1604
  client.on("close", () => messageClients.delete(client));
1122
1605
  });
1606
+ logServer.on("connection", (client) => {
1607
+ logClients.add(client);
1608
+ for (const event of recentLogs) client.send(JSON.stringify(event));
1609
+ client.on("close", () => logClients.delete(client));
1610
+ });
1123
1611
  const server = node_http.default.createServer((req, res) => {
1124
1612
  if (req.url?.split("?")[0] === "/status") {
1125
1613
  res.writeHead(200, {
@@ -1142,6 +1630,29 @@ async function startInternalMetroServer(options) {
1142
1630
  });
1143
1631
  return;
1144
1632
  }
1633
+ if (req.url?.split("?")[0] === "/bleam/logs") {
1634
+ if (req.method === "GET") {
1635
+ sendJson(res, 200, { logs: recentLogs });
1636
+ return;
1637
+ }
1638
+ if (req.method !== "POST") {
1639
+ res.writeHead(405, { Allow: "GET, POST" });
1640
+ res.end();
1641
+ return;
1642
+ }
1643
+ readRequestBody(req).then((body) => {
1644
+ const parsed = body ? JSON.parse(body) : {};
1645
+ const events = Array.isArray(parsed) ? parsed : [parsed];
1646
+ for (const value of events) recordLog(normalizeDevLogEvent(value));
1647
+ sendJson(res, 200, { ok: true });
1648
+ }).catch((error) => {
1649
+ sendJson(res, 400, {
1650
+ ok: false,
1651
+ error: error instanceof Error ? error.message : String(error)
1652
+ });
1653
+ });
1654
+ return;
1655
+ }
1145
1656
  middleware(req, res, (error) => {
1146
1657
  if (res.headersSent) return;
1147
1658
  if (error) {
@@ -1171,6 +1682,12 @@ async function startInternalMetroServer(options) {
1171
1682
  });
1172
1683
  return;
1173
1684
  }
1685
+ if (req.url?.split("?")[0] === "/bleam/logs/stream") {
1686
+ logServer.handleUpgrade(req, socket, head, (client) => {
1687
+ logServer.emit("connection", client, req);
1688
+ });
1689
+ return;
1690
+ }
1174
1691
  for (const listener of metroUpgradeListeners) listener.call(server, req, socket, head);
1175
1692
  });
1176
1693
  await new Promise((resolve, reject) => {
@@ -1184,7 +1701,9 @@ async function startInternalMetroServer(options) {
1184
1701
  const close = () => {
1185
1702
  setTimeout(() => process.exit(0), 1e3).unref();
1186
1703
  messageServer.close();
1704
+ logServer.close();
1187
1705
  for (const client of messageClients) client.terminate();
1706
+ for (const client of logClients) client.terminate();
1188
1707
  server.close(() => {
1189
1708
  end();
1190
1709
  process.exit(0);
@@ -1195,8 +1714,10 @@ async function startInternalMetroServer(options) {
1195
1714
  }
1196
1715
  async function startDevServer(options) {
1197
1716
  const config = readProjectConfig(options);
1717
+ if (options.clean) cleanDevArtifacts(options.projectRoot);
1198
1718
  const runtime = await resolveRuntimeArtifact();
1199
1719
  const clientAppPath = copyRuntimeClient(options.projectRoot, runtime);
1720
+ console.log("Preparing Bleam dev workspace");
1200
1721
  const workspaceRoot = createDevWorkspace(options.projectRoot, config);
1201
1722
  const origin = `http://localhost:${options.port}`;
1202
1723
  const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
@@ -1206,7 +1727,8 @@ async function startDevServer(options) {
1206
1727
  "--workspace-root",
1207
1728
  workspaceRoot,
1208
1729
  "--port",
1209
- String(options.port)
1730
+ String(options.port),
1731
+ ...options.clean ? ["--reset-cache"] : []
1210
1732
  ], {
1211
1733
  stdio: "inherit",
1212
1734
  env: {
@@ -1230,24 +1752,32 @@ async function startDevServer(options) {
1230
1752
  }
1231
1753
  });
1232
1754
  let cleanupInteractiveControls = () => {};
1755
+ let didLaunchRuntimeClient = false;
1756
+ let didCleanupDevSession = false;
1233
1757
  let stopping = false;
1758
+ const cleanupDevSession = () => {
1759
+ if (didCleanupDevSession) return;
1760
+ didCleanupDevSession = true;
1761
+ cleanupInteractiveControls();
1762
+ if (didLaunchRuntimeClient) quitRuntimeClient();
1763
+ };
1234
1764
  const stop = (signal) => {
1235
1765
  if (stopping) return;
1236
1766
  stopping = true;
1237
- cleanupInteractiveControls();
1767
+ cleanupDevSession();
1238
1768
  child.kill(signal);
1239
1769
  setTimeout(() => process.exit(0), 2e3).unref();
1240
1770
  };
1241
1771
  process.on("SIGINT", () => stop("SIGINT"));
1242
1772
  process.on("SIGTERM", () => stop("SIGTERM"));
1243
1773
  child.on("error", (error) => {
1244
- cleanupInteractiveControls();
1774
+ cleanupDevSession();
1245
1775
  console.error(`Bleam dev server process failed: ${error instanceof Error ? error.message : String(error)}`);
1246
1776
  console.error(doctorHint(options.projectRoot));
1247
1777
  process.exit(1);
1248
1778
  });
1249
1779
  child.on("exit", (code) => {
1250
- cleanupInteractiveControls();
1780
+ cleanupDevSession();
1251
1781
  process.exit(code ?? 0);
1252
1782
  });
1253
1783
  try {
@@ -1261,8 +1791,9 @@ async function startDevServer(options) {
1261
1791
  });
1262
1792
  if (options.run && process.env.BLEAM_NO_OPEN !== "1") try {
1263
1793
  await waitForDevBundle(manifest.bundleUrl);
1264
- console.log("Opening Bleam app...");
1794
+ console.log(`Opening Bleam app at ${prettyPath(clientAppPath)}`);
1265
1795
  await launchRuntimeClient(clientAppPath, projectConfigPath, manifest);
1796
+ didLaunchRuntimeClient = true;
1266
1797
  } catch (error) {
1267
1798
  console.error(`Initial app launch failed: ${error instanceof Error ? error.message : String(error)}`);
1268
1799
  console.error(doctorHint(options.projectRoot));
@@ -1270,7 +1801,7 @@ async function startDevServer(options) {
1270
1801
  console.error("Bleam dev server is still running.");
1271
1802
  }
1272
1803
  } catch (error) {
1273
- cleanupInteractiveControls();
1804
+ cleanupDevSession();
1274
1805
  child.kill("SIGTERM");
1275
1806
  throw error;
1276
1807
  }
@@ -1283,11 +1814,12 @@ async function main(argv = process.argv.slice(2)) {
1283
1814
  }
1284
1815
  if (command === "new") {
1285
1816
  const options = parseNewOptions(args);
1286
- createNewProject(options);
1287
- if (options.run) await startDevServer({
1817
+ createProject(options);
1818
+ await startDevServer({
1288
1819
  projectRoot: options.projectRoot,
1289
1820
  port: 8081,
1290
- run: true
1821
+ run: true,
1822
+ clean: false
1291
1823
  });
1292
1824
  return;
1293
1825
  }
@@ -1295,14 +1827,34 @@ async function main(argv = process.argv.slice(2)) {
1295
1827
  await startDevServer(parseDevOptions(args));
1296
1828
  return;
1297
1829
  }
1830
+ if (command === "install") {
1831
+ await installNativeApp(parseBuildOptions(args));
1832
+ return;
1833
+ }
1298
1834
  if (command === "build") {
1299
1835
  await buildNativeArchives(parseBuildOptions(args));
1300
1836
  return;
1301
1837
  }
1838
+ if (command === "publish") {
1839
+ const [kind, ...publishArgs] = args;
1840
+ if (kind === "ota") {
1841
+ await publishOta(parsePublishOtaOptions(publishArgs));
1842
+ return;
1843
+ }
1844
+ if (kind === "platform") {
1845
+ await publishPlatform(parsePublishOtaOptions(publishArgs));
1846
+ return;
1847
+ }
1848
+ throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>]");
1849
+ }
1302
1850
  if (command === "doctor") {
1303
1851
  runDoctor(parseDoctorOptions(args));
1304
1852
  return;
1305
1853
  }
1854
+ if (command === "typecheck") {
1855
+ runTypecheckCommand(parseProjectToolOptions(args));
1856
+ return;
1857
+ }
1306
1858
  if (command === "check") {
1307
1859
  runBiomeCommand("check", parseProjectToolOptions(args));
1308
1860
  return;
@@ -1321,9 +1873,24 @@ async function main(argv = process.argv.slice(2)) {
1321
1873
  }
1322
1874
  throw new Error(`Unknown command: ${command}`);
1323
1875
  }
1324
- main().catch((error) => {
1876
+ if (process.env.VITEST !== "true") main().catch((error) => {
1325
1877
  console.error(error instanceof Error ? error.message : String(error));
1326
1878
  process.exit(1);
1327
1879
  });
1880
+ const __test = {
1881
+ createProject,
1882
+ createBuildNativeWorkspace,
1883
+ hasCurrentPodInstall,
1884
+ installedAppPath,
1885
+ installArchivedApp,
1886
+ nativeArchiveArgs,
1887
+ parseBuildOptions,
1888
+ parseNewOptions,
1889
+ parsePublishOtaOptions,
1890
+ platformRelease,
1891
+ podInstallInputStamp,
1892
+ replaceOrInsertPlistString
1893
+ };
1328
1894
 
1329
- //#endregion
1895
+ //#endregion
1896
+ exports.__test = __test;