bleam 0.0.9 → 0.0.10

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.
package/dist/cli.cjs CHANGED
@@ -24,6 +24,7 @@ let yoctocolors = require("yoctocolors");
24
24
  yoctocolors = require_chunk.__toESM(yoctocolors);
25
25
 
26
26
  //#region src/cli.ts
27
+ const localUpdateServiceUrl = "http://127.0.0.1:8787";
27
28
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
28
29
  const packageRoot = node_path.default.dirname((0, node_url.fileURLToPath)(new URL("../package.json", require("url").pathToFileURL(__filename).href)));
29
30
  const packageJson = require$1("../package.json");
@@ -96,7 +97,7 @@ Commands:
96
97
  Build and install a standalone macOS app
97
98
  publish ota [dir] [--service-url <url>]
98
99
  Publish an OTA update to the local update service
99
- publish platform [dir] [--service-url <url>]
100
+ publish platform [dir] [--service-url <url>] [--redeploy]
100
101
  Build and publish a platform update locally
101
102
  doctor [dir] Check the local Bleam environment
102
103
  typecheck [dir] Typecheck project files
@@ -108,7 +109,7 @@ function parsePublishOtaOptions(argv) {
108
109
  const root = invocationRoot();
109
110
  let projectRoot = root;
110
111
  let hasProjectRootArg = false;
111
- let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? "http://127.0.0.1:8787";
112
+ let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl;
112
113
  for (let index = 0; index < argv.length; index += 1) {
113
114
  const arg = argv[index];
114
115
  if (arg === "--service-url") {
@@ -132,6 +133,13 @@ function parsePublishOtaOptions(argv) {
132
133
  serviceUrl: url.origin
133
134
  };
134
135
  }
136
+ function parsePublishPlatformOptions(argv) {
137
+ const redeploy = argv.includes("--redeploy");
138
+ return {
139
+ ...parsePublishOtaOptions(argv.filter((argument) => argument !== "--redeploy")),
140
+ redeploy
141
+ };
142
+ }
135
143
  function invocationRoot() {
136
144
  const pwd = process.env.PWD;
137
145
  return pwd && node_path.default.isAbsolute(pwd) ? pwd : process.cwd();
@@ -479,7 +487,8 @@ function readProjectConfig(options) {
479
487
  teamId: config.teamId,
480
488
  appearance: config.appearance ?? normalizeAppearance(void 0),
481
489
  runtimeVersion,
482
- sdkVersion: runtimeVersion
490
+ sdkVersion: runtimeVersion,
491
+ iconPath: (0, node_fs.existsSync)(node_path.default.join(options.projectRoot, "icon.png")) ? node_path.default.join(options.projectRoot, "icon.png") : void 0
483
492
  };
484
493
  }
485
494
  function slugFromName(name) {
@@ -639,6 +648,18 @@ function nativeRuntimeTemplateRoot(root = packageRoot) {
639
648
  const bundledTemplate = node_path.default.join(root, "templates", "native");
640
649
  return (0, node_fs.existsSync)(bundledTemplate) ? bundledTemplate : node_path.default.resolve(root, "..", "..", "app", "bleam");
641
650
  }
651
+ function localUpdateServiceRoot(root = packageRoot) {
652
+ const bundledService = node_path.default.join(root, "templates", "updates");
653
+ return (0, node_fs.existsSync)(bundledService) ? bundledService : node_path.default.resolve(root, "..", "..", "services", "updates");
654
+ }
655
+ function localUpdateServicePaths(home = node_os.default.homedir()) {
656
+ const root = node_path.default.join(home, "Library", "Caches", "bleam", "updates");
657
+ return {
658
+ root,
659
+ state: node_path.default.join(root, "state"),
660
+ log: node_path.default.join(root, "worker.log")
661
+ };
662
+ }
642
663
  function safeArtifactName(name) {
643
664
  return name.trim().replace(/[/:\\]/g, "-").replace(/\s+/g, " ") || "App";
644
665
  }
@@ -673,11 +694,13 @@ function replaceOrInsertPlistStringArray(plist, key, values) {
673
694
  return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<array>\n${plistStringArray(values)}\n\t</array>\n${plist.slice(closingDictionary)}`;
674
695
  }
675
696
  function writeGeneratedAppJson(nativeRoot, config) {
697
+ const icon = config.iconPath ? "./icon.png" : void 0;
676
698
  (0, node_fs.writeFileSync)(node_path.default.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
677
699
  name: config.name,
678
700
  slug: slugFromName(config.name),
679
701
  version: config.version ?? "1.0.0",
680
702
  platforms: ["ios"],
703
+ icon,
681
704
  ios: {
682
705
  appleTeamId: config.teamId,
683
706
  buildNumber: config.buildNumber,
@@ -685,24 +708,65 @@ function writeGeneratedAppJson(nativeRoot, config) {
685
708
  }
686
709
  } }, null, 2)}\n`);
687
710
  }
711
+ function prepareNativeIcon(nativeRoot, iconPath) {
712
+ const generatedIcon = node_path.default.join(nativeRoot, "icon.png");
713
+ const appIconRoot = node_path.default.join(nativeRoot, "ios", "Bleam", "Images.xcassets", "AppIcon.appiconset");
714
+ (0, node_fs.rmSync)(generatedIcon, { force: true });
715
+ (0, node_fs.rmSync)(node_path.default.join(appIconRoot, "AppIcon.png"), { force: true });
716
+ if (!iconPath) return;
717
+ (0, node_fs.cpSync)(iconPath, generatedIcon);
718
+ (0, node_fs.cpSync)(iconPath, node_path.default.join(appIconRoot, "AppIcon.png"));
719
+ (0, node_fs.writeFileSync)(node_path.default.join(appIconRoot, "Contents.json"), `${JSON.stringify({
720
+ images: [{
721
+ filename: "AppIcon.png",
722
+ idiom: "universal",
723
+ platform: "ios",
724
+ size: "1024x1024"
725
+ }],
726
+ info: {
727
+ version: 1,
728
+ author: "bleam"
729
+ }
730
+ }, null, 2)}\n`);
731
+ }
732
+ function platformInputFingerprint(config) {
733
+ const iconSha256 = config.iconPath ? sha256(config.iconPath) : null;
734
+ return (0, node_crypto.createHash)("sha256").update(JSON.stringify({
735
+ schemaVersion: 1,
736
+ bleamPlatformVersion: config.runtimeVersion,
737
+ name: config.name,
738
+ appVersion: config.version ?? "1.0.0",
739
+ buildNumber: config.buildNumber,
740
+ bundleIdentifier: config.bundleIdentifier,
741
+ teamIdentifier: config.teamId,
742
+ appGroups: config.appGroups,
743
+ appearance: config.appearance,
744
+ iconSha256,
745
+ updateServiceUrl: process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl,
746
+ target: {
747
+ os: "macos",
748
+ architecture: "arm64",
749
+ minimumMacOSVersion: "14.0"
750
+ }
751
+ })).digest("hex");
752
+ }
688
753
  function patchNativeProject(nativeRoot, config) {
689
754
  const projectPath = node_path.default.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
690
755
  const infoPlistPath = node_path.default.join(nativeRoot, "ios", "Bleam", "Info.plist");
691
756
  const generationInfoPlistPath = node_path.default.join(nativeRoot, "ios", "GenerationService", "Info.plist");
692
- const platformInfoPlistPath = node_path.default.join(nativeRoot, "ios", "PlatformHelper", "Info.plist");
693
757
  const appEntitlementsPath = node_path.default.join(nativeRoot, "ios", "Bleam", "bleam.entitlements");
694
758
  const generationEntitlementsPath = node_path.default.join(nativeRoot, "ios", "GenerationService", "GenerationService.entitlements");
695
759
  const productName = xcodeSettingValue(config.name);
696
760
  const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
697
761
  const generationBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.GenerationService`);
698
- const platformBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.PlatformHelper`);
699
762
  const teamId = xcodeSettingValue(config.teamId ?? "");
700
763
  const version = xcodeSettingValue(config.version ?? "1.0.0");
701
764
  const buildNumber = xcodeSettingValue(config.buildNumber);
702
765
  const primaryAppGroup = config.appGroups[0];
766
+ const fingerprint = platformInputFingerprint(config);
703
767
  let project = (0, node_fs.readFileSync)(projectPath, "utf8");
704
768
  project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g, (_, current) => {
705
- return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : current.endsWith(".PlatformHelper") ? platformBundleIdentifier : bundleIdentifier};`;
769
+ return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : bundleIdentifier};`;
706
770
  }).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
707
771
  return `DEVELOPMENT_TEAM = ${teamId};`;
708
772
  }).replace(/MARKETING_VERSION = [^;]+;/g, () => {
@@ -717,9 +781,10 @@ function patchNativeProject(nativeRoot, config) {
717
781
  infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
718
782
  infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
719
783
  infoPlist = replaceOrInsertPlistString(infoPlist, "BleamRuntimeVersion", config.runtimeVersion);
784
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamPlatformFingerprint", fingerprint);
720
785
  infoPlist = replaceOrInsertPlistString(infoPlist, "BleamTeamIdentifier", config.teamId ?? "");
721
- const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL;
722
- if (updateServiceUrl) infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
786
+ const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl;
787
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
723
788
  infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
724
789
  if (primaryAppGroup) infoPlist = replaceOrInsertPlistString(infoPlist, "AppGroupIdentifier", primaryAppGroup);
725
790
  (0, node_fs.writeFileSync)(infoPlistPath, infoPlist);
@@ -727,7 +792,6 @@ function patchNativeProject(nativeRoot, config) {
727
792
  let generationInfoPlist = (0, node_fs.readFileSync)(generationInfoPlistPath, "utf8");
728
793
  generationInfoPlist = replaceOrInsertPlistString(generationInfoPlist, "AppGroupIdentifier", primaryAppGroup);
729
794
  (0, node_fs.writeFileSync)(generationInfoPlistPath, generationInfoPlist);
730
- (0, node_fs.writeFileSync)(platformInfoPlistPath, replaceOrInsertPlistString((0, node_fs.readFileSync)(platformInfoPlistPath, "utf8"), "BleamPlatformVersion", config.runtimeVersion));
731
795
  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));
732
796
  }
733
797
  }
@@ -760,6 +824,7 @@ function createBuildNativeWorkspace(options, config) {
760
824
  (0, node_fs.writeFileSync)(node_path.default.join(nativeRoot, "index.ts"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(node_path.default.join(options.projectRoot, "app"))}\n\nregisterApp(App)\n`);
761
825
  (0, node_fs.writeFileSync)(node_path.default.join(nativeRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(node_path.default.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
762
826
  (0, node_fs.writeFileSync)(node_path.default.join(nativeRoot, "ios", ".xcode.env"), `export NODE_BINARY=$(command -v node)\nexport BLEAM_PROJECT_ROOT=${singleQuotedString(options.projectRoot)}\nexport BLEAM_DEV_PLATFORM=macos\n`);
827
+ prepareNativeIcon(nativeRoot, config.iconPath);
763
828
  patchNativeProject(nativeRoot, config);
764
829
  return nativeRoot;
765
830
  }
@@ -784,14 +849,13 @@ function runInheritedCommand(command, args, cwd) {
784
849
  }
785
850
  function installPods(nativeRoot) {
786
851
  const iosRoot = node_path.default.join(nativeRoot, "ios");
787
- const expectedStamp = podInstallInputStamp(iosRoot);
788
- if (hasCurrentPodInstall(iosRoot, expectedStamp)) {
852
+ if (hasCurrentPodInstall(iosRoot, podInstallInputStamp(iosRoot))) {
789
853
  console.log("Using cached native dependencies");
790
854
  return;
791
855
  }
792
856
  console.log("Installing native dependencies...");
793
857
  runInheritedCommand("pod", ["install"], iosRoot);
794
- (0, node_fs.writeFileSync)(podInstallStampPath(iosRoot), `${expectedStamp}\n`);
858
+ (0, node_fs.writeFileSync)(podInstallStampPath(iosRoot), `${podInstallInputStamp(iosRoot)}\n`);
795
859
  }
796
860
  function ensureNativeFrameworks(nativeRoot) {
797
861
  const frameworksRoot = node_path.default.join(nativeRoot, "ios", "Vendor", "MLXSwift", "0.31.6-macos");
@@ -824,7 +888,9 @@ function podInstallStampPath(iosRoot) {
824
888
  }
825
889
  function hasCurrentPodInstall(iosRoot, expectedStamp) {
826
890
  const stampPath = podInstallStampPath(iosRoot);
827
- 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;
891
+ const lockPath = node_path.default.join(iosRoot, "Podfile.lock");
892
+ const manifestPath = node_path.default.join(iosRoot, "Pods", "Manifest.lock");
893
+ return (0, node_fs.existsSync)(node_path.default.join(iosRoot, "Bleam.xcworkspace")) && (0, node_fs.existsSync)(lockPath) && (0, node_fs.existsSync)(manifestPath) && (0, node_fs.readFileSync)(lockPath, "utf8") === (0, node_fs.readFileSync)(manifestPath, "utf8") && (0, node_fs.existsSync)(stampPath) && (0, node_fs.readFileSync)(stampPath, "utf8").trim() === expectedStamp;
828
894
  }
829
895
  function nativeArchiveArgs(options) {
830
896
  return [
@@ -999,10 +1065,76 @@ async function publishOta(options) {
999
1065
  console.log(`Published OTA update ${releaseId}`);
1000
1066
  console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/ota/${runtimeVersion}`);
1001
1067
  }
1068
+ function compareVersions(left, right) {
1069
+ const parse = (value) => {
1070
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+.*)?$/.exec(value);
1071
+ if (!match) throw new Error(`Invalid Bleam platform version: ${value}`);
1072
+ return {
1073
+ core: match.slice(1, 4).map(Number),
1074
+ prerelease: match[4]?.split(".")
1075
+ };
1076
+ };
1077
+ const a = parse(left);
1078
+ const b = parse(right);
1079
+ for (let index = 0; index < a.core.length; index += 1) if (a.core[index] !== b.core[index]) return (a.core[index] ?? 0) < (b.core[index] ?? 0) ? -1 : 1;
1080
+ if (!a.prerelease && !b.prerelease) return 0;
1081
+ if (!a.prerelease) return 1;
1082
+ if (!b.prerelease) return -1;
1083
+ const length = Math.max(a.prerelease.length, b.prerelease.length);
1084
+ for (let index = 0; index < length; index += 1) {
1085
+ const aPart = a.prerelease[index];
1086
+ const bPart = b.prerelease[index];
1087
+ if (aPart === void 0) return -1;
1088
+ if (bPart === void 0) return 1;
1089
+ if (aPart === bPart) continue;
1090
+ const aNumber = /^\d+$/.test(aPart) ? Number(aPart) : void 0;
1091
+ const bNumber = /^\d+$/.test(bPart) ? Number(bPart) : void 0;
1092
+ if (aNumber !== void 0 && bNumber !== void 0) return aNumber < bNumber ? -1 : 1;
1093
+ if (aNumber !== void 0) return -1;
1094
+ if (bNumber !== void 0) return 1;
1095
+ return aPart < bPart ? -1 : 1;
1096
+ }
1097
+ return 0;
1098
+ }
1099
+ function platformPublicationReason(active, target, redeploy) {
1100
+ if (active && compareVersions(target.bleamPlatformVersion, active.bleamPlatformVersion) < 0) throw new Error(`Cannot publish older Bleam platform ${target.bleamPlatformVersion} over ${active.bleamPlatformVersion}. Rollback requires a separate release operation.`);
1101
+ const unchanged = active?.platformFingerprint === target.platformFingerprint;
1102
+ if (unchanged && !redeploy) throw new Error("Platform inputs are unchanged. Developer code and ordinary assets must be published with bleam publish ota. Use --redeploy only to intentionally republish identical platform inputs.");
1103
+ if (!unchanged && redeploy) throw new Error("--redeploy requires an existing release with the same platform fingerprint; publish normally because platform inputs changed.");
1104
+ if (unchanged) return "redeploy";
1105
+ if (!active) return "first-release";
1106
+ if (!active.platformFingerprint) return "legacy-migration";
1107
+ if (active.bleamPlatformVersion !== target.bleamPlatformVersion) return "runtime-upgrade";
1108
+ return "signed-input-change";
1109
+ }
1110
+ async function activePlatformRelease(serviceUrl, config) {
1111
+ const response = await fetch(`${serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier ?? "")}/platform/macos-arm64`, { cache: "no-store" });
1112
+ if (response.status === 404) return void 0;
1113
+ if (!response.ok) throw new Error(`Could not resolve the active platform release: ${response.status} ${await response.text()}`);
1114
+ const release = await response.json();
1115
+ if (release.kind !== "platform" || release.bundleIdentifier !== config.bundleIdentifier || release.teamIdentifier !== config.teamId || typeof release.bleamPlatformVersion !== "string" || release.platformFingerprint !== void 0 && (typeof release.platformFingerprint !== "string" || !/^[a-f0-9]{64}$/.test(release.platformFingerprint))) throw new Error("The active platform release is invalid for this app");
1116
+ return {
1117
+ bleamPlatformVersion: release.bleamPlatformVersion,
1118
+ platformFingerprint: release.platformFingerprint
1119
+ };
1120
+ }
1002
1121
  async function publishPlatform(options) {
1003
1122
  const config = readProjectConfig(options);
1004
1123
  if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
1005
1124
  if (!config.teamId) throw missingBuildFieldError("teamId");
1125
+ const fingerprint = platformInputFingerprint(config);
1126
+ const active = await activePlatformRelease(options.serviceUrl, config);
1127
+ console.log(`Bleam platform: ${active?.bleamPlatformVersion ?? "none"} -> ${config.runtimeVersion}`);
1128
+ console.log(`Platform fingerprint: ${active?.platformFingerprint ?? "none"} -> ${fingerprint}`);
1129
+ const reason = platformPublicationReason(active, {
1130
+ bleamPlatformVersion: config.runtimeVersion,
1131
+ platformFingerprint: fingerprint
1132
+ }, options.redeploy);
1133
+ if (reason === "redeploy") console.log("Redeploying unchanged platform inputs by explicit request");
1134
+ else if (reason === "first-release") console.log("Publishing the first platform release for this app");
1135
+ else if (reason === "legacy-migration") console.log("Migrating the active legacy release to platform fingerprints");
1136
+ else if (reason === "runtime-upgrade") console.log("Publishing a Bleam platform runtime upgrade");
1137
+ else console.log("Publishing changed signed app metadata or resources");
1006
1138
  const archivePath = await buildNativeArchives({
1007
1139
  projectRoot: options.projectRoot,
1008
1140
  output: node_path.default.join(options.projectRoot, "dist"),
@@ -1031,7 +1163,7 @@ async function publishPlatform(options) {
1031
1163
  const sizeBytes = (0, node_fs.readFileSync)(artifactPath).byteLength;
1032
1164
  const digest = sha256(artifactPath);
1033
1165
  const releaseId = `platform_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
1034
- const release = platformRelease(config, releaseId, sizeBytes, digest);
1166
+ const release = platformRelease(config, releaseId, sizeBytes, digest, options.redeploy);
1035
1167
  const response = await fetch(`${options.serviceUrl}/v1/local/releases/platform`, {
1036
1168
  method: "POST",
1037
1169
  headers: { "content-type": "application/json" },
@@ -1044,7 +1176,7 @@ async function publishPlatform(options) {
1044
1176
  console.log(`Published platform update ${releaseId}`);
1045
1177
  console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/platform/macos-arm64`);
1046
1178
  }
1047
- function platformRelease(config, releaseId, sizeBytes, digest) {
1179
+ function platformRelease(config, releaseId, sizeBytes, digest, redeploy = false) {
1048
1180
  if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
1049
1181
  if (!config.teamId) throw missingBuildFieldError("teamId");
1050
1182
  return {
@@ -1056,7 +1188,9 @@ function platformRelease(config, releaseId, sizeBytes, digest) {
1056
1188
  releaseId,
1057
1189
  appVersion: config.version ?? "1.0.0",
1058
1190
  buildNumber: config.buildNumber,
1059
- bleamPlatformVersion: runtimeVersion,
1191
+ bleamPlatformVersion: config.runtimeVersion,
1192
+ platformFingerprint: platformInputFingerprint(config),
1193
+ redeploy,
1060
1194
  target: {
1061
1195
  os: "macos",
1062
1196
  architecture: "arm64",
@@ -1405,6 +1539,92 @@ async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
1405
1539
  quitRuntimeClient();
1406
1540
  openRuntimeClient(appPath, projectConfigPath, manifest);
1407
1541
  }
1542
+ async function updateServiceHealthy(serviceUrl) {
1543
+ try {
1544
+ const response = await fetch(`${serviceUrl}/v1/health`, {
1545
+ cache: "no-store",
1546
+ signal: AbortSignal.timeout(1e3)
1547
+ });
1548
+ if (!response.ok) return false;
1549
+ return (await response.json()).healthy === true;
1550
+ } catch {
1551
+ return false;
1552
+ }
1553
+ }
1554
+ function updateServiceLogTail(logPath) {
1555
+ if (!(0, node_fs.existsSync)(logPath)) return "";
1556
+ return (0, node_fs.readFileSync)(logPath, "utf8").slice(-4e3).trim();
1557
+ }
1558
+ async function waitForUpdateService(child, serviceUrl, logPath, timeoutMs = 3e4) {
1559
+ const startedAt = Date.now();
1560
+ let startupError;
1561
+ child.once("error", (error) => {
1562
+ startupError = error;
1563
+ });
1564
+ while (Date.now() - startedAt < timeoutMs) {
1565
+ if (startupError || child.exitCode !== null) break;
1566
+ if (await updateServiceHealthy(serviceUrl)) return;
1567
+ await new Promise((resolve) => setTimeout(resolve, 250));
1568
+ }
1569
+ const log = updateServiceLogTail(logPath);
1570
+ const detail = log ? `\n\n${log}` : "";
1571
+ throw new Error(`Local update Worker failed to start.\nLog: ${logPath}${detail}`, startupError ? { cause: startupError } : void 0);
1572
+ }
1573
+ async function startLocalUpdateService() {
1574
+ const configuredUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl;
1575
+ const serviceUrl = new URL(configuredUrl).origin;
1576
+ if (await updateServiceHealthy(serviceUrl)) {
1577
+ console.log(`Using local update Worker at ${serviceUrl}`);
1578
+ return {
1579
+ child: void 0,
1580
+ serviceUrl
1581
+ };
1582
+ }
1583
+ if (serviceUrl !== localUpdateServiceUrl) throw new Error(`BLEAM_UPDATE_SERVICE_URL is not healthy: ${serviceUrl}/v1/health`);
1584
+ const serviceRoot = localUpdateServiceRoot();
1585
+ const configPath = node_path.default.join(serviceRoot, "wrangler.jsonc");
1586
+ if (!(0, node_fs.existsSync)(configPath)) throw new Error(`Missing local update Worker: ${serviceRoot}`);
1587
+ const wrangler = node_path.default.join(resolvedDependencyRoot(), ".bin", "wrangler");
1588
+ if (!(0, node_fs.existsSync)(wrangler)) throw new Error("Missing Wrangler dependency for the local update Worker");
1589
+ const paths = localUpdateServicePaths();
1590
+ (0, node_fs.mkdirSync)(paths.state, { recursive: true });
1591
+ const log = (0, node_fs.openSync)(paths.log, "a");
1592
+ const child = (0, node_child_process.spawn)(wrangler, [
1593
+ "dev",
1594
+ "--config",
1595
+ configPath,
1596
+ "--local",
1597
+ "--persist-to",
1598
+ paths.state,
1599
+ "--ip",
1600
+ "127.0.0.1",
1601
+ "--port",
1602
+ "8787"
1603
+ ], {
1604
+ cwd: serviceRoot,
1605
+ stdio: [
1606
+ "ignore",
1607
+ log,
1608
+ log
1609
+ ],
1610
+ env: {
1611
+ ...process.env,
1612
+ NO_COLOR: "1"
1613
+ }
1614
+ });
1615
+ (0, node_fs.closeSync)(log);
1616
+ try {
1617
+ await waitForUpdateService(child, serviceUrl, paths.log);
1618
+ } catch (error) {
1619
+ child.kill("SIGTERM");
1620
+ throw error;
1621
+ }
1622
+ console.log(`Local update Worker running at ${serviceUrl}`);
1623
+ return {
1624
+ child,
1625
+ serviceUrl
1626
+ };
1627
+ }
1408
1628
  function focusRuntimeClient() {
1409
1629
  return (0, node_child_process.spawnSync)("osascript", ["-e", "tell application id \"dev.bleam.app\" to activate"], { stdio: "ignore" }).status === 0;
1410
1630
  }
@@ -1739,6 +1959,7 @@ async function startDevServer(options) {
1739
1959
  const workspaceRoot = createDevWorkspace(options.projectRoot, config);
1740
1960
  const origin = `http://localhost:${options.port}`;
1741
1961
  const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
1962
+ const updateService = await startLocalUpdateService();
1742
1963
  const child = (0, node_child_process.spawn)(process.execPath, [
1743
1964
  (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href),
1744
1965
  "internal:metro",
@@ -1759,6 +1980,7 @@ async function startDevServer(options) {
1759
1980
  BLEAM_DEV_PLATFORM: "macos",
1760
1981
  BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
1761
1982
  BLEAM_DEV_SDK_VERSION: config.sdkVersion,
1983
+ BLEAM_UPDATE_SERVICE_URL: updateService.serviceUrl,
1762
1984
  NODE_PATH: [
1763
1985
  node_path.default.join(options.projectRoot, "node_modules"),
1764
1986
  resolvedDependencyRoot(),
@@ -1778,6 +2000,7 @@ async function startDevServer(options) {
1778
2000
  didCleanupDevSession = true;
1779
2001
  cleanupInteractiveControls();
1780
2002
  if (didLaunchRuntimeClient) quitRuntimeClient();
2003
+ if (updateService.child?.exitCode === null) updateService.child.kill("SIGTERM");
1781
2004
  };
1782
2005
  const stop = (signal) => {
1783
2006
  if (stopping) return;
@@ -1860,10 +2083,10 @@ async function main(argv = process.argv.slice(2)) {
1860
2083
  return;
1861
2084
  }
1862
2085
  if (kind === "platform") {
1863
- await publishPlatform(parsePublishOtaOptions(publishArgs));
2086
+ await publishPlatform(parsePublishPlatformOptions(publishArgs));
1864
2087
  return;
1865
2088
  }
1866
- throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>]");
2089
+ throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>] [--redeploy]");
1867
2090
  }
1868
2091
  if (command === "doctor") {
1869
2092
  runDoctor(parseDoctorOptions(args));
@@ -1901,11 +2124,16 @@ const __test = {
1901
2124
  hasCurrentPodInstall,
1902
2125
  installedAppPath,
1903
2126
  installArchivedApp,
2127
+ localUpdateServicePaths,
2128
+ localUpdateServiceRoot,
1904
2129
  nativeRuntimeTemplateRoot,
1905
2130
  nativeArchiveArgs,
1906
2131
  parseBuildOptions,
1907
2132
  parseNewOptions,
1908
2133
  parsePublishOtaOptions,
2134
+ parsePublishPlatformOptions,
2135
+ platformInputFingerprint,
2136
+ platformPublicationReason,
1909
2137
  platformRelease,
1910
2138
  podInstallInputStamp,
1911
2139
  replaceOrInsertPlistString
package/dist/cli.d.cts CHANGED
@@ -13,6 +13,9 @@ interface PublishOtaOptions {
13
13
  projectRoot: string;
14
14
  serviceUrl: string;
15
15
  }
16
+ interface PublishPlatformOptions extends PublishOtaOptions {
17
+ redeploy: boolean;
18
+ }
16
19
  interface ProjectConfig {
17
20
  name: string;
18
21
  version?: string;
@@ -23,11 +26,13 @@ interface ProjectConfig {
23
26
  appearance: BleamAppearance;
24
27
  runtimeVersion: string;
25
28
  sdkVersion: string;
29
+ iconPath?: string;
26
30
  }
27
31
  declare function parsePublishOtaOptions(argv: string[]): PublishOtaOptions;
32
+ declare function parsePublishPlatformOptions(argv: string[]): PublishPlatformOptions;
28
33
  declare function parseNewOptions(argv: string[]): {
29
34
  projectRoot: string;
30
- template: "basic" | "state" | "foundation-models" | "image-generation";
35
+ template: "state" | "basic" | "foundation-models" | "image-generation";
31
36
  };
32
37
  declare const projectTemplates: readonly ["basic", "state", "foundation-models", "image-generation"];
33
38
  type ProjectTemplate = (typeof projectTemplates)[number];
@@ -37,7 +42,14 @@ declare function createProject(options: {
37
42
  template?: ProjectTemplate;
38
43
  }): void;
39
44
  declare function nativeRuntimeTemplateRoot(root?: string): string;
45
+ declare function localUpdateServiceRoot(root?: string): string;
46
+ declare function localUpdateServicePaths(home?: string): {
47
+ root: string;
48
+ state: string;
49
+ log: string;
50
+ };
40
51
  declare function replaceOrInsertPlistString(plist: string, key: string, value: string): string;
52
+ declare function platformInputFingerprint(config: ProjectConfig): string;
41
53
  declare function createBuildNativeWorkspace(options: BuildOptions, config: ProjectConfig): string;
42
54
  declare function podInstallInputStamp(iosRoot: string): string;
43
55
  declare function hasCurrentPodInstall(iosRoot: string, expectedStamp: string): boolean;
@@ -48,7 +60,16 @@ declare function nativeArchiveArgs(options: {
48
60
  }): string[];
49
61
  declare function installArchivedApp(archivePath: string, applications?: string): string;
50
62
  declare function installedAppPath(appName: string): string;
51
- declare function platformRelease(config: ProjectConfig, releaseId: string, sizeBytes: number, digest: string): {
63
+ interface ActivePlatformRelease {
64
+ bleamPlatformVersion: string;
65
+ platformFingerprint?: string;
66
+ }
67
+ type PlatformPublicationReason = 'first-release' | 'legacy-migration' | 'runtime-upgrade' | 'signed-input-change' | 'redeploy';
68
+ declare function platformPublicationReason(active: ActivePlatformRelease | undefined, target: {
69
+ bleamPlatformVersion: string;
70
+ platformFingerprint: string;
71
+ }, redeploy: boolean): PlatformPublicationReason;
72
+ declare function platformRelease(config: ProjectConfig, releaseId: string, sizeBytes: number, digest: string, redeploy?: boolean): {
52
73
  schemaVersion: number;
53
74
  kind: string;
54
75
  bundleIdentifier: string;
@@ -58,6 +79,8 @@ declare function platformRelease(config: ProjectConfig, releaseId: string, sizeB
58
79
  appVersion: string;
59
80
  buildNumber: string;
60
81
  bleamPlatformVersion: string;
82
+ platformFingerprint: string;
83
+ redeploy: boolean;
61
84
  target: {
62
85
  os: string;
63
86
  architecture: string;
@@ -78,11 +101,16 @@ declare const __test: {
78
101
  hasCurrentPodInstall: typeof hasCurrentPodInstall;
79
102
  installedAppPath: typeof installedAppPath;
80
103
  installArchivedApp: typeof installArchivedApp;
104
+ localUpdateServicePaths: typeof localUpdateServicePaths;
105
+ localUpdateServiceRoot: typeof localUpdateServiceRoot;
81
106
  nativeRuntimeTemplateRoot: typeof nativeRuntimeTemplateRoot;
82
107
  nativeArchiveArgs: typeof nativeArchiveArgs;
83
108
  parseBuildOptions: typeof parseBuildOptions;
84
109
  parseNewOptions: typeof parseNewOptions;
85
110
  parsePublishOtaOptions: typeof parsePublishOtaOptions;
111
+ parsePublishPlatformOptions: typeof parsePublishPlatformOptions;
112
+ platformInputFingerprint: typeof platformInputFingerprint;
113
+ platformPublicationReason: typeof platformPublicationReason;
86
114
  platformRelease: typeof platformRelease;
87
115
  podInstallInputStamp: typeof podInstallInputStamp;
88
116
  replaceOrInsertPlistString: typeof replaceOrInsertPlistString;
package/dist/cli.d.ts CHANGED
@@ -13,6 +13,9 @@ interface PublishOtaOptions {
13
13
  projectRoot: string;
14
14
  serviceUrl: string;
15
15
  }
16
+ interface PublishPlatformOptions extends PublishOtaOptions {
17
+ redeploy: boolean;
18
+ }
16
19
  interface ProjectConfig {
17
20
  name: string;
18
21
  version?: string;
@@ -23,11 +26,13 @@ interface ProjectConfig {
23
26
  appearance: BleamAppearance;
24
27
  runtimeVersion: string;
25
28
  sdkVersion: string;
29
+ iconPath?: string;
26
30
  }
27
31
  declare function parsePublishOtaOptions(argv: string[]): PublishOtaOptions;
32
+ declare function parsePublishPlatformOptions(argv: string[]): PublishPlatformOptions;
28
33
  declare function parseNewOptions(argv: string[]): {
29
34
  projectRoot: string;
30
- template: "basic" | "state" | "foundation-models" | "image-generation";
35
+ template: "state" | "basic" | "foundation-models" | "image-generation";
31
36
  };
32
37
  declare const projectTemplates: readonly ["basic", "state", "foundation-models", "image-generation"];
33
38
  type ProjectTemplate = (typeof projectTemplates)[number];
@@ -37,7 +42,14 @@ declare function createProject(options: {
37
42
  template?: ProjectTemplate;
38
43
  }): void;
39
44
  declare function nativeRuntimeTemplateRoot(root?: string): string;
45
+ declare function localUpdateServiceRoot(root?: string): string;
46
+ declare function localUpdateServicePaths(home?: string): {
47
+ root: string;
48
+ state: string;
49
+ log: string;
50
+ };
40
51
  declare function replaceOrInsertPlistString(plist: string, key: string, value: string): string;
52
+ declare function platformInputFingerprint(config: ProjectConfig): string;
41
53
  declare function createBuildNativeWorkspace(options: BuildOptions, config: ProjectConfig): string;
42
54
  declare function podInstallInputStamp(iosRoot: string): string;
43
55
  declare function hasCurrentPodInstall(iosRoot: string, expectedStamp: string): boolean;
@@ -48,7 +60,16 @@ declare function nativeArchiveArgs(options: {
48
60
  }): string[];
49
61
  declare function installArchivedApp(archivePath: string, applications?: string): string;
50
62
  declare function installedAppPath(appName: string): string;
51
- declare function platformRelease(config: ProjectConfig, releaseId: string, sizeBytes: number, digest: string): {
63
+ interface ActivePlatformRelease {
64
+ bleamPlatformVersion: string;
65
+ platformFingerprint?: string;
66
+ }
67
+ type PlatformPublicationReason = 'first-release' | 'legacy-migration' | 'runtime-upgrade' | 'signed-input-change' | 'redeploy';
68
+ declare function platformPublicationReason(active: ActivePlatformRelease | undefined, target: {
69
+ bleamPlatformVersion: string;
70
+ platformFingerprint: string;
71
+ }, redeploy: boolean): PlatformPublicationReason;
72
+ declare function platformRelease(config: ProjectConfig, releaseId: string, sizeBytes: number, digest: string, redeploy?: boolean): {
52
73
  schemaVersion: number;
53
74
  kind: string;
54
75
  bundleIdentifier: string;
@@ -58,6 +79,8 @@ declare function platformRelease(config: ProjectConfig, releaseId: string, sizeB
58
79
  appVersion: string;
59
80
  buildNumber: string;
60
81
  bleamPlatformVersion: string;
82
+ platformFingerprint: string;
83
+ redeploy: boolean;
61
84
  target: {
62
85
  os: string;
63
86
  architecture: string;
@@ -78,11 +101,16 @@ declare const __test: {
78
101
  hasCurrentPodInstall: typeof hasCurrentPodInstall;
79
102
  installedAppPath: typeof installedAppPath;
80
103
  installArchivedApp: typeof installArchivedApp;
104
+ localUpdateServicePaths: typeof localUpdateServicePaths;
105
+ localUpdateServiceRoot: typeof localUpdateServiceRoot;
81
106
  nativeRuntimeTemplateRoot: typeof nativeRuntimeTemplateRoot;
82
107
  nativeArchiveArgs: typeof nativeArchiveArgs;
83
108
  parseBuildOptions: typeof parseBuildOptions;
84
109
  parseNewOptions: typeof parseNewOptions;
85
110
  parsePublishOtaOptions: typeof parsePublishOtaOptions;
111
+ parsePublishPlatformOptions: typeof parsePublishPlatformOptions;
112
+ platformInputFingerprint: typeof platformInputFingerprint;
113
+ platformPublicationReason: typeof platformPublicationReason;
86
114
  platformRelease: typeof platformRelease;
87
115
  podInstallInputStamp: typeof podInstallInputStamp;
88
116
  replaceOrInsertPlistString: typeof replaceOrInsertPlistString;