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.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { execFileSync, spawn, spawnSync } from "node:child_process";
4
4
  import { createHash, randomUUID } from "node:crypto";
5
- import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
5
+ import { appendFileSync, closeSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
6
6
  import http from "node:http";
7
7
  import os from "node:os";
8
8
  import path from "node:path";
@@ -12,6 +12,7 @@ import yoctoSpinner from "yocto-spinner";
12
12
  import { bold, dim, green, red, yellow } from "yoctocolors";
13
13
 
14
14
  //#region src/cli.ts
15
+ const localUpdateServiceUrl = "http://127.0.0.1:8787";
15
16
  const require = createRequire(import.meta.url);
16
17
  const packageRoot = path.dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
17
18
  const packageJson = require("../package.json");
@@ -84,7 +85,7 @@ Commands:
84
85
  Build and install a standalone macOS app
85
86
  publish ota [dir] [--service-url <url>]
86
87
  Publish an OTA update to the local update service
87
- publish platform [dir] [--service-url <url>]
88
+ publish platform [dir] [--service-url <url>] [--redeploy]
88
89
  Build and publish a platform update locally
89
90
  doctor [dir] Check the local Bleam environment
90
91
  typecheck [dir] Typecheck project files
@@ -96,7 +97,7 @@ function parsePublishOtaOptions(argv) {
96
97
  const root = invocationRoot();
97
98
  let projectRoot = root;
98
99
  let hasProjectRootArg = false;
99
- let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? "http://127.0.0.1:8787";
100
+ let serviceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl;
100
101
  for (let index = 0; index < argv.length; index += 1) {
101
102
  const arg = argv[index];
102
103
  if (arg === "--service-url") {
@@ -120,6 +121,13 @@ function parsePublishOtaOptions(argv) {
120
121
  serviceUrl: url.origin
121
122
  };
122
123
  }
124
+ function parsePublishPlatformOptions(argv) {
125
+ const redeploy = argv.includes("--redeploy");
126
+ return {
127
+ ...parsePublishOtaOptions(argv.filter((argument) => argument !== "--redeploy")),
128
+ redeploy
129
+ };
130
+ }
123
131
  function invocationRoot() {
124
132
  const pwd = process.env.PWD;
125
133
  return pwd && path.isAbsolute(pwd) ? pwd : process.cwd();
@@ -467,7 +475,8 @@ function readProjectConfig(options) {
467
475
  teamId: config.teamId,
468
476
  appearance: config.appearance ?? normalizeAppearance(void 0),
469
477
  runtimeVersion,
470
- sdkVersion: runtimeVersion
478
+ sdkVersion: runtimeVersion,
479
+ iconPath: existsSync(path.join(options.projectRoot, "icon.png")) ? path.join(options.projectRoot, "icon.png") : void 0
471
480
  };
472
481
  }
473
482
  function slugFromName(name) {
@@ -627,6 +636,18 @@ function nativeRuntimeTemplateRoot(root = packageRoot) {
627
636
  const bundledTemplate = path.join(root, "templates", "native");
628
637
  return existsSync(bundledTemplate) ? bundledTemplate : path.resolve(root, "..", "..", "app", "bleam");
629
638
  }
639
+ function localUpdateServiceRoot(root = packageRoot) {
640
+ const bundledService = path.join(root, "templates", "updates");
641
+ return existsSync(bundledService) ? bundledService : path.resolve(root, "..", "..", "services", "updates");
642
+ }
643
+ function localUpdateServicePaths(home = os.homedir()) {
644
+ const root = path.join(home, "Library", "Caches", "bleam", "updates");
645
+ return {
646
+ root,
647
+ state: path.join(root, "state"),
648
+ log: path.join(root, "worker.log")
649
+ };
650
+ }
630
651
  function safeArtifactName(name) {
631
652
  return name.trim().replace(/[/:\\]/g, "-").replace(/\s+/g, " ") || "App";
632
653
  }
@@ -661,11 +682,13 @@ function replaceOrInsertPlistStringArray(plist, key, values) {
661
682
  return `${plist.slice(0, closingDictionary)}\t<key>${key}</key>\n\t<array>\n${plistStringArray(values)}\n\t</array>\n${plist.slice(closingDictionary)}`;
662
683
  }
663
684
  function writeGeneratedAppJson(nativeRoot, config) {
685
+ const icon = config.iconPath ? "./icon.png" : void 0;
664
686
  writeFileSync(path.join(nativeRoot, "app.json"), `${JSON.stringify({ expo: {
665
687
  name: config.name,
666
688
  slug: slugFromName(config.name),
667
689
  version: config.version ?? "1.0.0",
668
690
  platforms: ["ios"],
691
+ icon,
669
692
  ios: {
670
693
  appleTeamId: config.teamId,
671
694
  buildNumber: config.buildNumber,
@@ -673,24 +696,65 @@ function writeGeneratedAppJson(nativeRoot, config) {
673
696
  }
674
697
  } }, null, 2)}\n`);
675
698
  }
699
+ function prepareNativeIcon(nativeRoot, iconPath) {
700
+ const generatedIcon = path.join(nativeRoot, "icon.png");
701
+ const appIconRoot = path.join(nativeRoot, "ios", "Bleam", "Images.xcassets", "AppIcon.appiconset");
702
+ rmSync(generatedIcon, { force: true });
703
+ rmSync(path.join(appIconRoot, "AppIcon.png"), { force: true });
704
+ if (!iconPath) return;
705
+ cpSync(iconPath, generatedIcon);
706
+ cpSync(iconPath, path.join(appIconRoot, "AppIcon.png"));
707
+ writeFileSync(path.join(appIconRoot, "Contents.json"), `${JSON.stringify({
708
+ images: [{
709
+ filename: "AppIcon.png",
710
+ idiom: "universal",
711
+ platform: "ios",
712
+ size: "1024x1024"
713
+ }],
714
+ info: {
715
+ version: 1,
716
+ author: "bleam"
717
+ }
718
+ }, null, 2)}\n`);
719
+ }
720
+ function platformInputFingerprint(config) {
721
+ const iconSha256 = config.iconPath ? sha256(config.iconPath) : null;
722
+ return createHash("sha256").update(JSON.stringify({
723
+ schemaVersion: 1,
724
+ bleamPlatformVersion: config.runtimeVersion,
725
+ name: config.name,
726
+ appVersion: config.version ?? "1.0.0",
727
+ buildNumber: config.buildNumber,
728
+ bundleIdentifier: config.bundleIdentifier,
729
+ teamIdentifier: config.teamId,
730
+ appGroups: config.appGroups,
731
+ appearance: config.appearance,
732
+ iconSha256,
733
+ updateServiceUrl: process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl,
734
+ target: {
735
+ os: "macos",
736
+ architecture: "arm64",
737
+ minimumMacOSVersion: "14.0"
738
+ }
739
+ })).digest("hex");
740
+ }
676
741
  function patchNativeProject(nativeRoot, config) {
677
742
  const projectPath = path.join(nativeRoot, "ios", "Bleam.xcodeproj", "project.pbxproj");
678
743
  const infoPlistPath = path.join(nativeRoot, "ios", "Bleam", "Info.plist");
679
744
  const generationInfoPlistPath = path.join(nativeRoot, "ios", "GenerationService", "Info.plist");
680
- const platformInfoPlistPath = path.join(nativeRoot, "ios", "PlatformHelper", "Info.plist");
681
745
  const appEntitlementsPath = path.join(nativeRoot, "ios", "Bleam", "bleam.entitlements");
682
746
  const generationEntitlementsPath = path.join(nativeRoot, "ios", "GenerationService", "GenerationService.entitlements");
683
747
  const productName = xcodeSettingValue(config.name);
684
748
  const bundleIdentifier = xcodeSettingValue(config.bundleIdentifier ?? "");
685
749
  const generationBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.GenerationService`);
686
- const platformBundleIdentifier = xcodeSettingValue(`${config.bundleIdentifier}.PlatformHelper`);
687
750
  const teamId = xcodeSettingValue(config.teamId ?? "");
688
751
  const version = xcodeSettingValue(config.version ?? "1.0.0");
689
752
  const buildNumber = xcodeSettingValue(config.buildNumber);
690
753
  const primaryAppGroup = config.appGroups[0];
754
+ const fingerprint = platformInputFingerprint(config);
691
755
  let project = readFileSync(projectPath, "utf8");
692
756
  project = project.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g, (_, current) => {
693
- return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : current.endsWith(".PlatformHelper") ? platformBundleIdentifier : bundleIdentifier};`;
757
+ return `PRODUCT_BUNDLE_IDENTIFIER = ${current.endsWith(".GenerationService") ? generationBundleIdentifier : bundleIdentifier};`;
694
758
  }).replace(/DEVELOPMENT_TEAM = [^;]+;/g, () => {
695
759
  return `DEVELOPMENT_TEAM = ${teamId};`;
696
760
  }).replace(/MARKETING_VERSION = [^;]+;/g, () => {
@@ -705,9 +769,10 @@ function patchNativeProject(nativeRoot, config) {
705
769
  infoPlist = replacePlistString(infoPlist, "CFBundleDisplayName", config.name);
706
770
  infoPlist = replacePlistString(infoPlist, "CFBundleShortVersionString", config.version ?? "1.0.0");
707
771
  infoPlist = replaceOrInsertPlistString(infoPlist, "BleamRuntimeVersion", config.runtimeVersion);
772
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamPlatformFingerprint", fingerprint);
708
773
  infoPlist = replaceOrInsertPlistString(infoPlist, "BleamTeamIdentifier", config.teamId ?? "");
709
- const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL;
710
- if (updateServiceUrl) infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
774
+ const updateServiceUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl;
775
+ infoPlist = replaceOrInsertPlistString(infoPlist, "BleamUpdateServiceURL", updateServiceUrl);
711
776
  infoPlist = replacePlistString(infoPlist, "CFBundleVersion", config.buildNumber);
712
777
  if (primaryAppGroup) infoPlist = replaceOrInsertPlistString(infoPlist, "AppGroupIdentifier", primaryAppGroup);
713
778
  writeFileSync(infoPlistPath, infoPlist);
@@ -715,7 +780,6 @@ function patchNativeProject(nativeRoot, config) {
715
780
  let generationInfoPlist = readFileSync(generationInfoPlistPath, "utf8");
716
781
  generationInfoPlist = replaceOrInsertPlistString(generationInfoPlist, "AppGroupIdentifier", primaryAppGroup);
717
782
  writeFileSync(generationInfoPlistPath, generationInfoPlist);
718
- writeFileSync(platformInfoPlistPath, replaceOrInsertPlistString(readFileSync(platformInfoPlistPath, "utf8"), "BleamPlatformVersion", config.runtimeVersion));
719
783
  for (const entitlementsPath of [appEntitlementsPath, generationEntitlementsPath]) writeFileSync(entitlementsPath, replaceOrInsertPlistStringArray(readFileSync(entitlementsPath, "utf8"), "com.apple.security.application-groups", config.appGroups));
720
784
  }
721
785
  }
@@ -748,6 +812,7 @@ function createBuildNativeWorkspace(options, config) {
748
812
  writeFileSync(path.join(nativeRoot, "index.ts"), `import { registerApp } from 'bleam/app'\nimport App from ${JSON.stringify(path.join(options.projectRoot, "app"))}\n\nregisterApp(App)\n`);
749
813
  writeFileSync(path.join(nativeRoot, "metro.config.js"), `const { getDefaultConfig } = require(${JSON.stringify(path.join(packageRoot, "metro-config.cjs"))})\n\nmodule.exports = getDefaultConfig(__dirname)\n`);
750
814
  writeFileSync(path.join(nativeRoot, "ios", ".xcode.env"), `export NODE_BINARY=$(command -v node)\nexport BLEAM_PROJECT_ROOT=${singleQuotedString(options.projectRoot)}\nexport BLEAM_DEV_PLATFORM=macos\n`);
815
+ prepareNativeIcon(nativeRoot, config.iconPath);
751
816
  patchNativeProject(nativeRoot, config);
752
817
  return nativeRoot;
753
818
  }
@@ -772,14 +837,13 @@ function runInheritedCommand(command, args, cwd) {
772
837
  }
773
838
  function installPods(nativeRoot) {
774
839
  const iosRoot = path.join(nativeRoot, "ios");
775
- const expectedStamp = podInstallInputStamp(iosRoot);
776
- if (hasCurrentPodInstall(iosRoot, expectedStamp)) {
840
+ if (hasCurrentPodInstall(iosRoot, podInstallInputStamp(iosRoot))) {
777
841
  console.log("Using cached native dependencies");
778
842
  return;
779
843
  }
780
844
  console.log("Installing native dependencies...");
781
845
  runInheritedCommand("pod", ["install"], iosRoot);
782
- writeFileSync(podInstallStampPath(iosRoot), `${expectedStamp}\n`);
846
+ writeFileSync(podInstallStampPath(iosRoot), `${podInstallInputStamp(iosRoot)}\n`);
783
847
  }
784
848
  function ensureNativeFrameworks(nativeRoot) {
785
849
  const frameworksRoot = path.join(nativeRoot, "ios", "Vendor", "MLXSwift", "0.31.6-macos");
@@ -812,7 +876,9 @@ function podInstallStampPath(iosRoot) {
812
876
  }
813
877
  function hasCurrentPodInstall(iosRoot, expectedStamp) {
814
878
  const stampPath = podInstallStampPath(iosRoot);
815
- return existsSync(path.join(iosRoot, "Bleam.xcworkspace")) && existsSync(path.join(iosRoot, "Pods", "Manifest.lock")) && existsSync(stampPath) && readFileSync(stampPath, "utf8").trim() === expectedStamp;
879
+ const lockPath = path.join(iosRoot, "Podfile.lock");
880
+ const manifestPath = path.join(iosRoot, "Pods", "Manifest.lock");
881
+ return existsSync(path.join(iosRoot, "Bleam.xcworkspace")) && existsSync(lockPath) && existsSync(manifestPath) && readFileSync(lockPath, "utf8") === readFileSync(manifestPath, "utf8") && existsSync(stampPath) && readFileSync(stampPath, "utf8").trim() === expectedStamp;
816
882
  }
817
883
  function nativeArchiveArgs(options) {
818
884
  return [
@@ -987,10 +1053,76 @@ async function publishOta(options) {
987
1053
  console.log(`Published OTA update ${releaseId}`);
988
1054
  console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/ota/${runtimeVersion}`);
989
1055
  }
1056
+ function compareVersions(left, right) {
1057
+ const parse = (value) => {
1058
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+.*)?$/.exec(value);
1059
+ if (!match) throw new Error(`Invalid Bleam platform version: ${value}`);
1060
+ return {
1061
+ core: match.slice(1, 4).map(Number),
1062
+ prerelease: match[4]?.split(".")
1063
+ };
1064
+ };
1065
+ const a = parse(left);
1066
+ const b = parse(right);
1067
+ 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;
1068
+ if (!a.prerelease && !b.prerelease) return 0;
1069
+ if (!a.prerelease) return 1;
1070
+ if (!b.prerelease) return -1;
1071
+ const length = Math.max(a.prerelease.length, b.prerelease.length);
1072
+ for (let index = 0; index < length; index += 1) {
1073
+ const aPart = a.prerelease[index];
1074
+ const bPart = b.prerelease[index];
1075
+ if (aPart === void 0) return -1;
1076
+ if (bPart === void 0) return 1;
1077
+ if (aPart === bPart) continue;
1078
+ const aNumber = /^\d+$/.test(aPart) ? Number(aPart) : void 0;
1079
+ const bNumber = /^\d+$/.test(bPart) ? Number(bPart) : void 0;
1080
+ if (aNumber !== void 0 && bNumber !== void 0) return aNumber < bNumber ? -1 : 1;
1081
+ if (aNumber !== void 0) return -1;
1082
+ if (bNumber !== void 0) return 1;
1083
+ return aPart < bPart ? -1 : 1;
1084
+ }
1085
+ return 0;
1086
+ }
1087
+ function platformPublicationReason(active, target, redeploy) {
1088
+ 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.`);
1089
+ const unchanged = active?.platformFingerprint === target.platformFingerprint;
1090
+ 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.");
1091
+ if (!unchanged && redeploy) throw new Error("--redeploy requires an existing release with the same platform fingerprint; publish normally because platform inputs changed.");
1092
+ if (unchanged) return "redeploy";
1093
+ if (!active) return "first-release";
1094
+ if (!active.platformFingerprint) return "legacy-migration";
1095
+ if (active.bleamPlatformVersion !== target.bleamPlatformVersion) return "runtime-upgrade";
1096
+ return "signed-input-change";
1097
+ }
1098
+ async function activePlatformRelease(serviceUrl, config) {
1099
+ const response = await fetch(`${serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier ?? "")}/platform/macos-arm64`, { cache: "no-store" });
1100
+ if (response.status === 404) return void 0;
1101
+ if (!response.ok) throw new Error(`Could not resolve the active platform release: ${response.status} ${await response.text()}`);
1102
+ const release = await response.json();
1103
+ 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");
1104
+ return {
1105
+ bleamPlatformVersion: release.bleamPlatformVersion,
1106
+ platformFingerprint: release.platformFingerprint
1107
+ };
1108
+ }
990
1109
  async function publishPlatform(options) {
991
1110
  const config = readProjectConfig(options);
992
1111
  if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
993
1112
  if (!config.teamId) throw missingBuildFieldError("teamId");
1113
+ const fingerprint = platformInputFingerprint(config);
1114
+ const active = await activePlatformRelease(options.serviceUrl, config);
1115
+ console.log(`Bleam platform: ${active?.bleamPlatformVersion ?? "none"} -> ${config.runtimeVersion}`);
1116
+ console.log(`Platform fingerprint: ${active?.platformFingerprint ?? "none"} -> ${fingerprint}`);
1117
+ const reason = platformPublicationReason(active, {
1118
+ bleamPlatformVersion: config.runtimeVersion,
1119
+ platformFingerprint: fingerprint
1120
+ }, options.redeploy);
1121
+ if (reason === "redeploy") console.log("Redeploying unchanged platform inputs by explicit request");
1122
+ else if (reason === "first-release") console.log("Publishing the first platform release for this app");
1123
+ else if (reason === "legacy-migration") console.log("Migrating the active legacy release to platform fingerprints");
1124
+ else if (reason === "runtime-upgrade") console.log("Publishing a Bleam platform runtime upgrade");
1125
+ else console.log("Publishing changed signed app metadata or resources");
994
1126
  const archivePath = await buildNativeArchives({
995
1127
  projectRoot: options.projectRoot,
996
1128
  output: path.join(options.projectRoot, "dist"),
@@ -1019,7 +1151,7 @@ async function publishPlatform(options) {
1019
1151
  const sizeBytes = readFileSync(artifactPath).byteLength;
1020
1152
  const digest = sha256(artifactPath);
1021
1153
  const releaseId = `platform_${Date.now().toString(36)}_${digest.slice(0, 12)}`;
1022
- const release = platformRelease(config, releaseId, sizeBytes, digest);
1154
+ const release = platformRelease(config, releaseId, sizeBytes, digest, options.redeploy);
1023
1155
  const response = await fetch(`${options.serviceUrl}/v1/local/releases/platform`, {
1024
1156
  method: "POST",
1025
1157
  headers: { "content-type": "application/json" },
@@ -1032,7 +1164,7 @@ async function publishPlatform(options) {
1032
1164
  console.log(`Published platform update ${releaseId}`);
1033
1165
  console.log(`${options.serviceUrl}/v1/apps/${encodeURIComponent(config.bundleIdentifier)}/platform/macos-arm64`);
1034
1166
  }
1035
- function platformRelease(config, releaseId, sizeBytes, digest) {
1167
+ function platformRelease(config, releaseId, sizeBytes, digest, redeploy = false) {
1036
1168
  if (!config.bundleIdentifier) throw missingBuildFieldError("bundleIdentifier");
1037
1169
  if (!config.teamId) throw missingBuildFieldError("teamId");
1038
1170
  return {
@@ -1044,7 +1176,9 @@ function platformRelease(config, releaseId, sizeBytes, digest) {
1044
1176
  releaseId,
1045
1177
  appVersion: config.version ?? "1.0.0",
1046
1178
  buildNumber: config.buildNumber,
1047
- bleamPlatformVersion: runtimeVersion,
1179
+ bleamPlatformVersion: config.runtimeVersion,
1180
+ platformFingerprint: platformInputFingerprint(config),
1181
+ redeploy,
1048
1182
  target: {
1049
1183
  os: "macos",
1050
1184
  architecture: "arm64",
@@ -1393,6 +1527,92 @@ async function launchRuntimeClient(appPath, projectConfigPath, manifest) {
1393
1527
  quitRuntimeClient();
1394
1528
  openRuntimeClient(appPath, projectConfigPath, manifest);
1395
1529
  }
1530
+ async function updateServiceHealthy(serviceUrl) {
1531
+ try {
1532
+ const response = await fetch(`${serviceUrl}/v1/health`, {
1533
+ cache: "no-store",
1534
+ signal: AbortSignal.timeout(1e3)
1535
+ });
1536
+ if (!response.ok) return false;
1537
+ return (await response.json()).healthy === true;
1538
+ } catch {
1539
+ return false;
1540
+ }
1541
+ }
1542
+ function updateServiceLogTail(logPath) {
1543
+ if (!existsSync(logPath)) return "";
1544
+ return readFileSync(logPath, "utf8").slice(-4e3).trim();
1545
+ }
1546
+ async function waitForUpdateService(child, serviceUrl, logPath, timeoutMs = 3e4) {
1547
+ const startedAt = Date.now();
1548
+ let startupError;
1549
+ child.once("error", (error) => {
1550
+ startupError = error;
1551
+ });
1552
+ while (Date.now() - startedAt < timeoutMs) {
1553
+ if (startupError || child.exitCode !== null) break;
1554
+ if (await updateServiceHealthy(serviceUrl)) return;
1555
+ await new Promise((resolve) => setTimeout(resolve, 250));
1556
+ }
1557
+ const log = updateServiceLogTail(logPath);
1558
+ const detail = log ? `\n\n${log}` : "";
1559
+ throw new Error(`Local update Worker failed to start.\nLog: ${logPath}${detail}`, startupError ? { cause: startupError } : void 0);
1560
+ }
1561
+ async function startLocalUpdateService() {
1562
+ const configuredUrl = process.env.BLEAM_UPDATE_SERVICE_URL ?? localUpdateServiceUrl;
1563
+ const serviceUrl = new URL(configuredUrl).origin;
1564
+ if (await updateServiceHealthy(serviceUrl)) {
1565
+ console.log(`Using local update Worker at ${serviceUrl}`);
1566
+ return {
1567
+ child: void 0,
1568
+ serviceUrl
1569
+ };
1570
+ }
1571
+ if (serviceUrl !== localUpdateServiceUrl) throw new Error(`BLEAM_UPDATE_SERVICE_URL is not healthy: ${serviceUrl}/v1/health`);
1572
+ const serviceRoot = localUpdateServiceRoot();
1573
+ const configPath = path.join(serviceRoot, "wrangler.jsonc");
1574
+ if (!existsSync(configPath)) throw new Error(`Missing local update Worker: ${serviceRoot}`);
1575
+ const wrangler = path.join(resolvedDependencyRoot(), ".bin", "wrangler");
1576
+ if (!existsSync(wrangler)) throw new Error("Missing Wrangler dependency for the local update Worker");
1577
+ const paths = localUpdateServicePaths();
1578
+ mkdirSync(paths.state, { recursive: true });
1579
+ const log = openSync(paths.log, "a");
1580
+ const child = spawn(wrangler, [
1581
+ "dev",
1582
+ "--config",
1583
+ configPath,
1584
+ "--local",
1585
+ "--persist-to",
1586
+ paths.state,
1587
+ "--ip",
1588
+ "127.0.0.1",
1589
+ "--port",
1590
+ "8787"
1591
+ ], {
1592
+ cwd: serviceRoot,
1593
+ stdio: [
1594
+ "ignore",
1595
+ log,
1596
+ log
1597
+ ],
1598
+ env: {
1599
+ ...process.env,
1600
+ NO_COLOR: "1"
1601
+ }
1602
+ });
1603
+ closeSync(log);
1604
+ try {
1605
+ await waitForUpdateService(child, serviceUrl, paths.log);
1606
+ } catch (error) {
1607
+ child.kill("SIGTERM");
1608
+ throw error;
1609
+ }
1610
+ console.log(`Local update Worker running at ${serviceUrl}`);
1611
+ return {
1612
+ child,
1613
+ serviceUrl
1614
+ };
1615
+ }
1396
1616
  function focusRuntimeClient() {
1397
1617
  return spawnSync("osascript", ["-e", "tell application id \"dev.bleam.app\" to activate"], { stdio: "ignore" }).status === 0;
1398
1618
  }
@@ -1727,6 +1947,7 @@ async function startDevServer(options) {
1727
1947
  const workspaceRoot = createDevWorkspace(options.projectRoot, config);
1728
1948
  const origin = `http://localhost:${options.port}`;
1729
1949
  const projectConfigPath = writeProjectRuntimeConfig(options.projectRoot, config, origin);
1950
+ const updateService = await startLocalUpdateService();
1730
1951
  const child = spawn(process.execPath, [
1731
1952
  fileURLToPath(import.meta.url),
1732
1953
  "internal:metro",
@@ -1747,6 +1968,7 @@ async function startDevServer(options) {
1747
1968
  BLEAM_DEV_PLATFORM: "macos",
1748
1969
  BLEAM_DEV_RUNTIME_VERSION: config.runtimeVersion,
1749
1970
  BLEAM_DEV_SDK_VERSION: config.sdkVersion,
1971
+ BLEAM_UPDATE_SERVICE_URL: updateService.serviceUrl,
1750
1972
  NODE_PATH: [
1751
1973
  path.join(options.projectRoot, "node_modules"),
1752
1974
  resolvedDependencyRoot(),
@@ -1766,6 +1988,7 @@ async function startDevServer(options) {
1766
1988
  didCleanupDevSession = true;
1767
1989
  cleanupInteractiveControls();
1768
1990
  if (didLaunchRuntimeClient) quitRuntimeClient();
1991
+ if (updateService.child?.exitCode === null) updateService.child.kill("SIGTERM");
1769
1992
  };
1770
1993
  const stop = (signal) => {
1771
1994
  if (stopping) return;
@@ -1848,10 +2071,10 @@ async function main(argv = process.argv.slice(2)) {
1848
2071
  return;
1849
2072
  }
1850
2073
  if (kind === "platform") {
1851
- await publishPlatform(parsePublishOtaOptions(publishArgs));
2074
+ await publishPlatform(parsePublishPlatformOptions(publishArgs));
1852
2075
  return;
1853
2076
  }
1854
- throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>]");
2077
+ throw new Error("Usage: bleam publish <ota|platform> [dir] [--service-url <url>] [--redeploy]");
1855
2078
  }
1856
2079
  if (command === "doctor") {
1857
2080
  runDoctor(parseDoctorOptions(args));
@@ -1889,11 +2112,16 @@ const __test = {
1889
2112
  hasCurrentPodInstall,
1890
2113
  installedAppPath,
1891
2114
  installArchivedApp,
2115
+ localUpdateServicePaths,
2116
+ localUpdateServiceRoot,
1892
2117
  nativeRuntimeTemplateRoot,
1893
2118
  nativeArchiveArgs,
1894
2119
  parseBuildOptions,
1895
2120
  parseNewOptions,
1896
2121
  parsePublishOtaOptions,
2122
+ parsePublishPlatformOptions,
2123
+ platformInputFingerprint,
2124
+ platformPublicationReason,
1897
2125
  platformRelease,
1898
2126
  podInstallInputStamp,
1899
2127
  replaceOrInsertPlistString
@@ -24,6 +24,8 @@ interface PlatformDownload {
24
24
  appVersion: string;
25
25
  buildNumber: string;
26
26
  platformVersion: string;
27
+ platformFingerprint: string;
28
+ redeploy: boolean;
27
29
  artifactPath: string;
28
30
  appPath: string;
29
31
  sizeBytes: number;
@@ -24,6 +24,8 @@ interface PlatformDownload {
24
24
  appVersion: string;
25
25
  buildNumber: string;
26
26
  platformVersion: string;
27
+ platformFingerprint: string;
28
+ redeploy: boolean;
27
29
  artifactPath: string;
28
30
  appPath: string;
29
31
  sizeBytes: number;
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from "react";
2
2
  import { ColorValue, StyleProp, TextStyle, ViewStyle } from "react-native";
3
- import * as _nativescript_react_native27 from "@nativescript/react-native";
3
+ import * as _nativescript_react_native31 from "@nativescript/react-native";
4
4
 
5
5
  //#region src/ui/shared.d.ts
6
6
  type NativeStyle = ViewStyle & TextStyle;
@@ -18,7 +18,7 @@ type ButtonProps = {
18
18
  onPress?: () => void;
19
19
  style?: StyleProp<NativeStyle>;
20
20
  };
21
- declare const Button: _nativescript_react_native27.UIKitViewComponent<ButtonProps, UIButton>;
21
+ declare const Button: _nativescript_react_native31.UIKitViewComponent<ButtonProps, UIButton>;
22
22
  //#endregion
23
23
  //#region src/ui/label.d.ts
24
24
  type LabelTone = 'primary' | 'secondary' | 'tertiary';
@@ -29,7 +29,7 @@ type LabelProps = {
29
29
  numberOfLines?: number;
30
30
  style?: StyleProp<NativeStyle>;
31
31
  };
32
- declare const Label: _nativescript_react_native27.UIKitViewComponent<LabelProps, UILabel>;
32
+ declare const Label: _nativescript_react_native31.UIKitViewComponent<LabelProps, UILabel>;
33
33
  //#endregion
34
34
  //#region src/ui/symbol.d.ts
35
35
  type SymbolName = string;
@@ -43,7 +43,7 @@ type SymbolProps = {
43
43
  color?: ColorValue;
44
44
  style?: StyleProp<NativeStyle>;
45
45
  };
46
- declare const Symbol: _nativescript_react_native27.UIKitViewComponent<SymbolProps, UIImageView>;
46
+ declare const Symbol: _nativescript_react_native31.UIKitViewComponent<SymbolProps, UIImageView>;
47
47
  //#endregion
48
48
  //#region src/ui/glass-shared.d.ts
49
49
  type GlassStyle = 'regular' | 'clear' | 'none';
@@ -73,10 +73,10 @@ type GlassContainerProps = {
73
73
  };
74
74
  //#endregion
75
75
  //#region src/ui/glass-view.d.ts
76
- declare const GlassView: _nativescript_react_native27.UIKitViewComponent<GlassViewProps, _nativescript_react_native27.UIKitContainerResult<UIVisualEffectView, UIView>>;
76
+ declare const GlassView: _nativescript_react_native31.UIKitViewComponent<GlassViewProps, _nativescript_react_native31.UIKitContainerResult<UIVisualEffectView, UIView>>;
77
77
  //#endregion
78
78
  //#region src/ui/glass-container.d.ts
79
- declare const GlassContainer: _nativescript_react_native27.UIKitViewComponent<GlassContainerProps, _nativescript_react_native27.UIKitContainerResult<UIVisualEffectView, UIView>>;
79
+ declare const GlassContainer: _nativescript_react_native31.UIKitViewComponent<GlassContainerProps, _nativescript_react_native31.UIKitContainerResult<UIVisualEffectView, UIView>>;
80
80
  //#endregion
81
81
  //#region src/ui/blur-view.d.ts
82
82
  type BlurIntensity = 'ultraThin' | 'thin' | 'regular' | 'thick' | 'chrome';
@@ -87,6 +87,6 @@ type BlurViewProps = {
87
87
  intensity?: BlurIntensity;
88
88
  colorScheme?: BlurColorScheme;
89
89
  };
90
- declare const BlurView: _nativescript_react_native27.UIKitViewComponent<BlurViewProps, _nativescript_react_native27.UIKitContainerResult<UIVisualEffectView, UIView>>;
90
+ declare const BlurView: _nativescript_react_native31.UIKitViewComponent<BlurViewProps, _nativescript_react_native31.UIKitContainerResult<UIVisualEffectView, UIView>>;
91
91
  //#endregion
92
92
  export { ButtonVariant as C, ButtonSize as S, Label as _, GlassContainer as a, Button as b, GlassEffectStyleConfig as c, GlassVisualStyle as d, Symbol as f, SymbolWeight as g, SymbolScale as h, BlurViewProps as i, GlassStyle as l, SymbolProps as m, BlurIntensity as n, GlassView as o, SymbolName as p, BlurView as r, GlassContainerProps as s, BlurColorScheme as t, GlassViewProps as u, LabelProps as v, ButtonProps as x, LabelTone as y };
package/dist/ui.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { C as ButtonVariant, S as ButtonSize, _ as Label, a as GlassContainer, b as Button, c as GlassEffectStyleConfig, d as GlassVisualStyle, f as Symbol, g as SymbolWeight, h as SymbolScale, i as BlurViewProps, l as GlassStyle, m as SymbolProps, n as BlurIntensity, o as GlassView, p as SymbolName, r as BlurView, s as GlassContainerProps, t as BlurColorScheme, u as GlassViewProps, v as LabelProps, x as ButtonProps, y as LabelTone } from "./ui-1WepaMS4.cjs";
1
+ import { C as ButtonVariant, S as ButtonSize, _ as Label, a as GlassContainer, b as Button, c as GlassEffectStyleConfig, d as GlassVisualStyle, f as Symbol, g as SymbolWeight, h as SymbolScale, i as BlurViewProps, l as GlassStyle, m as SymbolProps, n as BlurIntensity, o as GlassView, p as SymbolName, r as BlurView, s as GlassContainerProps, t as BlurColorScheme, u as GlassViewProps, v as LabelProps, x as ButtonProps, y as LabelTone } from "./ui-Dd7SXdbg.cjs";
2
2
  export { BlurColorScheme, BlurIntensity, BlurView, BlurViewProps, Button, ButtonProps, ButtonSize, ButtonVariant, GlassContainer, GlassContainerProps, GlassEffectStyleConfig, GlassStyle, GlassView, GlassViewProps, GlassVisualStyle, Label, LabelProps, LabelTone, Symbol, SymbolName, SymbolProps, SymbolScale, SymbolWeight };
package/dist/window.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { p as SymbolName } from "./ui-1WepaMS4.cjs";
1
+ import { p as SymbolName } from "./ui-Dd7SXdbg.cjs";
2
2
  import * as react3 from "react";
3
3
  import { ComponentType } from "react";
4
4
  import { StyleProp, ViewStyle } from "react-native";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bleam",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "packageManager": "yarn@1.22.22",
5
5
  "type": "module",
6
6
  "files": [
@@ -39,6 +39,7 @@
39
39
  "react-native": "0.86.0",
40
40
  "tsx": "^4.21.0",
41
41
  "typescript": "~6.0.3",
42
+ "wrangler": "4.110.0",
42
43
  "ws": "^8.21.0",
43
44
  "yocto-spinner": "^1.2.1",
44
45
  "yoctocolors": "^2.1.2"