peachsim 0.1.637 → 0.1.639

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 (94) hide show
  1. package/README.md +8 -5
  2. package/cli/bridge-flow-runner.ts +442 -14
  3. package/cli/command-registry.ts +1 -0
  4. package/cli/commands/control.ts +42 -5
  5. package/cli/commands/inspect/core.ts +93 -24
  6. package/cli/commands/maestro.ts +2 -2
  7. package/cli/commands/serve.ts +13 -1
  8. package/cli/commands/swift.ts +326 -0
  9. package/cli/drivers/playwright-provisioning.ts +8 -16
  10. package/cli/drivers/playwright-sim-host.ts +3 -0
  11. package/cli/drivers/playwright.ts +1 -0
  12. package/cli/internal-child.ts +51 -8
  13. package/cli/main.ts +11 -0
  14. package/cli/outbound-endpoints.ts +5 -1
  15. package/dist-lib/agent-daemon-client.cjs +1 -1
  16. package/dist-lib/agent-events.cjs +1 -1
  17. package/dist-lib/agent-identity.cjs +1 -1
  18. package/dist-lib/agent-sessions.cjs +1 -1
  19. package/dist-lib/attached-projects.cjs +1 -1
  20. package/dist-lib/auth/shared-session.cjs +1 -1
  21. package/dist-lib/backend-origin.cjs +1 -1
  22. package/dist-lib/beta.cjs +1 -1
  23. package/dist-lib/beta.mjs +1 -1
  24. package/dist-lib/bridge-constants.cjs +1 -1
  25. package/dist-lib/bridge-contract-input.cjs +3 -2
  26. package/dist-lib/bridge-contract-input.mjs +3 -2
  27. package/dist-lib/bridge-contract.cjs +1 -1
  28. package/dist-lib/bridge-contract.mjs +1 -1
  29. package/dist-lib/capture-contract.cjs +1 -1
  30. package/dist-lib/capture-contract.mjs +1 -1
  31. package/dist-lib/cli-constants.cjs +1 -1
  32. package/dist-lib/cloud-contract.cjs +1 -1
  33. package/dist-lib/cloud-contract.mjs +1 -1
  34. package/dist-lib/cloud-test-args.cjs +1 -1
  35. package/dist-lib/cloud-test-args.mjs +1 -1
  36. package/dist-lib/cloud.cjs +1 -1
  37. package/dist-lib/cloud.mjs +1 -1
  38. package/dist-lib/config.cjs +1 -1
  39. package/dist-lib/detox/index.cjs +1 -1
  40. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  41. package/dist-lib/home-paths.cjs +1 -1
  42. package/dist-lib/host/bridge-host.cjs +4 -2
  43. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  44. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  45. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  46. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  47. package/dist-lib/host/websocket-proxy.cjs +1 -1
  48. package/dist-lib/index.cjs +88 -23
  49. package/dist-lib/jump-to-source-babel.cjs +1 -1
  50. package/dist-lib/jump-to-source-native.cjs +1 -1
  51. package/dist-lib/kotlin-host.cjs +1 -1
  52. package/dist-lib/menu.cjs +1 -1
  53. package/dist-lib/menu.mjs +1 -1
  54. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  55. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  56. package/dist-lib/metro-production-bundle.cjs +1 -1
  57. package/dist-lib/metro-production-bundle.mjs +1 -1
  58. package/dist-lib/metro.cjs +1 -1
  59. package/dist-lib/native-app-identity.cjs +1 -1
  60. package/dist-lib/native-app-identity.mjs +1 -1
  61. package/dist-lib/profiles.cjs +1 -1
  62. package/dist-lib/public-brand.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/render-mode.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +78 -20
  68. package/dist-lib/sdk.mjs +77 -20
  69. package/dist-lib/skills.cjs +441 -94
  70. package/dist-lib/swift-compile.cjs +89 -7
  71. package/dist-lib/swift-compile.mjs +89 -7
  72. package/dist-lib/swift-package.cjs +1 -1
  73. package/dist-lib/swift-package.mjs +1 -1
  74. package/dist-lib/swift-submit.cjs +89 -8
  75. package/dist-lib/swift-submit.mjs +78 -8
  76. package/dist-lib/swift.cjs +16 -33220
  77. package/dist-lib/vite-swift.cjs +510 -253
  78. package/dist-lib/vite.cjs +13 -4
  79. package/package.json +1 -1
  80. package/skills/contrast/SKILL.md +1 -0
  81. package/src/assetCatalogContract.ts +16 -0
  82. package/src/bridge-contract-input.ts +3 -2
  83. package/src/bridge-contract.ts +8 -0
  84. package/src/host/bridge-host.ts +5 -5
  85. package/src/metro-runtime-source.ts +15 -0
  86. package/src/native-seam-manifest.ts +4 -6
  87. package/src/swift-compile.ts +105 -9
  88. package/src/swift-guest.ts +51 -0
  89. package/src/swift-host.ts +900 -0
  90. package/src/swift-submit.ts +97 -6
  91. package/src/swift.ts +36 -8
  92. package/src/swiftContract.ts +52 -0
  93. package/src/vite-plugin-one.ts +4 -3
  94. package/src/vite-plugin-swift.ts +43 -755
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.637 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.639 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -45,6 +45,31 @@ __export(vite_plugin_swift_exports, {
45
45
  swiftPlugin: () => swiftPlugin
46
46
  });
47
47
  module.exports = __toCommonJS(vite_plugin_swift_exports);
48
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
49
+ var import_node_path6 = __toESM(require("node:path"), 1);
50
+
51
+ // ../sootsim-swift/src/entryModule.ts
52
+ function nativeExportLines(args, commonjs) {
53
+ const native = args.native;
54
+ if (!native) return [];
55
+ return Object.entries(native.modules).map(([name, methods]) => {
56
+ const calls = methods.map(
57
+ (method) => `${method}: (...args) => callSwiftModule(options, ${JSON.stringify(`${native.packageId}.${name}`)}, ${JSON.stringify(method)}, args, ${JSON.stringify(native.contractHash)})`
58
+ ).join(", ");
59
+ return commonjs ? `module.exports.${name} = Object.freeze({ ${calls} })` : `export const ${name} = Object.freeze({ ${calls} })`;
60
+ });
61
+ }
62
+ function swiftEntryModuleEsm(args) {
63
+ return [
64
+ `import { createSwiftEntry, callSwiftModule } from 'peachsim/swift'`,
65
+ `const options = ${JSON.stringify({ url: args.url, hash: args.hash, icuUrl: args.icuUrl, rootId: args.rootId, resources: args.resources, bundleFiles: args.bundleFiles, colors: args.colors, symbols: args.symbols })}`,
66
+ ...!args.native || args.native.defaultView ? ["export default createSwiftEntry(options)"] : [],
67
+ ...nativeExportLines(args, false),
68
+ ""
69
+ ].join("\n");
70
+ }
71
+
72
+ // src/swift-host.ts
48
73
  var import_node_crypto3 = require("node:crypto");
49
74
  var import_node_fs4 = __toESM(require("node:fs"), 1);
50
75
  var import_node_os3 = __toESM(require("node:os"), 1);
@@ -82,8 +107,8 @@ function pickImagesetFile(images) {
82
107
  scale: pick.scale === "2x" ? 2 : pick.scale === "3x" ? 3 : 1
83
108
  };
84
109
  }
85
- function flatScale(path5) {
86
- const match = /@([23])x\.[^./]+$/.exec(path5);
110
+ function flatScale(path6) {
111
+ const match = /@([23])x\.[^./]+$/.exec(path6);
87
112
  return match ? Number(match[1]) : 1;
88
113
  }
89
114
  function providesNamespace(text) {
@@ -110,13 +135,13 @@ function catalogEntryName(contentsPath, kind, readText) {
110
135
  }
111
136
  return segments.join("/");
112
137
  }
113
- function isFlatResourceImage(path5) {
114
- return path5.split("/").includes("Resources") && RASTER_EXTENSIONS.has(extOf(path5));
138
+ function isFlatResourceImage(path6) {
139
+ return path6.split("/").includes("Resources") && RASTER_EXTENSIONS.has(extOf(path6));
115
140
  }
116
141
  function enumerateBundleImages(paths, readText) {
117
142
  const table = [];
118
143
  const seen = /* @__PURE__ */ new Set();
119
- const catalogs = paths.filter((path5) => baseOf(path5) === "Contents.json");
144
+ const catalogs = paths.filter((path6) => baseOf(path6) === "Contents.json");
120
145
  for (const contentsPath of catalogs) {
121
146
  const name = catalogEntryName(contentsPath, ".imageset", readText);
122
147
  if (name === null || seen.has(name)) continue;
@@ -137,12 +162,12 @@ function enumerateBundleImages(paths, readText) {
137
162
  seen.add(name);
138
163
  table.push({ name, file: `${dir}/${pick.filename}`, scale: pick.scale });
139
164
  }
140
- for (const path5 of paths) {
141
- if (!isFlatResourceImage(path5)) continue;
142
- const name = baseOf(path5).slice(0, -extOf(path5).length);
165
+ for (const path6 of paths) {
166
+ if (!isFlatResourceImage(path6)) continue;
167
+ const name = baseOf(path6).slice(0, -extOf(path6).length);
143
168
  if (name === "" || seen.has(name)) continue;
144
169
  seen.add(name);
145
- table.push({ name, file: path5, scale: flatScale(path5) });
170
+ table.push({ name, file: path6, scale: flatScale(path6) });
146
171
  }
147
172
  return table;
148
173
  }
@@ -171,7 +196,7 @@ function colorsetValue(color) {
171
196
  }
172
197
  function enumerateBundleColors(paths, readText) {
173
198
  const table = {};
174
- for (const contentsPath of paths.filter((path5) => baseOf(path5) === "Contents.json")) {
199
+ for (const contentsPath of paths.filter((path6) => baseOf(path6) === "Contents.json")) {
175
200
  const name = catalogEntryName(contentsPath, ".colorset", readText);
176
201
  if (name === null || name in table) continue;
177
202
  let colors;
@@ -236,8 +261,8 @@ function axisExtremes(points) {
236
261
  function pathBounds(d) {
237
262
  const xs = [];
238
263
  const ys = [];
239
- for (const path5 of d)
240
- (0, import_svgpath.default)(path5).abs().unshort().unarc().iterate((segment, _index, x, y) => {
264
+ for (const path6 of d)
265
+ (0, import_svgpath.default)(path6).abs().unshort().unarc().iterate((segment, _index, x, y) => {
241
266
  const [command, ...args] = segment;
242
267
  if (command === "M" || command === "L") {
243
268
  xs.push(Number(args[0]));
@@ -264,7 +289,7 @@ function guide(svg, id, axis) {
264
289
  }
265
290
  function enumerateBundleSymbols(paths, readText) {
266
291
  const table = {};
267
- for (const contentsPath of paths.filter((path5) => baseOf(path5) === "Contents.json")) {
292
+ for (const contentsPath of paths.filter((path6) => baseOf(path6) === "Contents.json")) {
268
293
  const name = catalogEntryName(contentsPath, ".symbolset", readText);
269
294
  if (name === null || name in table) continue;
270
295
  let symbols;
@@ -297,8 +322,8 @@ function enumerateBundleSymbols(paths, readText) {
297
322
  viewBox: "0 0 24 24",
298
323
  advance,
299
324
  box: [12 - advance / 2, 12 - tall / 2, advance, tall],
300
- layers: d.map((path5) => ({
301
- d: (0, import_svgpath.default)(path5).abs().scale(scale).translate(translateX, translateY).round(3).toString()
325
+ layers: d.map((path6) => ({
326
+ d: (0, import_svgpath.default)(path6).abs().scale(scale).translate(translateX, translateY).round(3).toString()
302
327
  }))
303
328
  };
304
329
  }
@@ -308,7 +333,7 @@ function enumerateBundleJson(paths) {
308
333
  const found = [];
309
334
  const seen = /* @__PURE__ */ new Set();
310
335
  const ordered = [...paths].filter(
311
- (path5) => path5.split("/").includes("Resources") && path5.split("/").every((segment) => segment !== "" && segment !== "..") && (extOf(path5) === ".json" || extOf(path5) === ".xcstrings")
336
+ (path6) => path6.split("/").includes("Resources") && path6.split("/").every((segment) => segment !== "" && segment !== "..") && (extOf(path6) === ".json" || extOf(path6) === ".xcstrings")
312
337
  ).sort((a, b) => {
313
338
  const aDepth = a.split("/").length;
314
339
  const bDepth = b.split("/").length;
@@ -323,27 +348,6 @@ function enumerateBundleJson(paths) {
323
348
  return found;
324
349
  }
325
350
 
326
- // ../sootsim-swift/src/entryModule.ts
327
- function nativeExportLines(args, commonjs) {
328
- const native = args.native;
329
- if (!native) return [];
330
- return Object.entries(native.modules).map(([name, methods]) => {
331
- const calls = methods.map(
332
- (method) => `${method}: (...args) => callSwiftModule(options, ${JSON.stringify(`${native.packageId}.${name}`)}, ${JSON.stringify(method)}, args, ${JSON.stringify(native.contractHash)})`
333
- ).join(", ");
334
- return commonjs ? `module.exports.${name} = Object.freeze({ ${calls} })` : `export const ${name} = Object.freeze({ ${calls} })`;
335
- });
336
- }
337
- function swiftEntryModuleEsm(args) {
338
- return [
339
- `import { createSwiftEntry, callSwiftModule } from 'peachsim/swift'`,
340
- `const options = ${JSON.stringify({ url: args.url, hash: args.hash, icuUrl: args.icuUrl, rootId: args.rootId, resources: args.resources, bundleFiles: args.bundleFiles, colors: args.colors, symbols: args.symbols })}`,
341
- ...!args.native || args.native.defaultView ? ["export default createSwiftEntry(options)"] : [],
342
- ...nativeExportLines(args, false),
343
- ""
344
- ].join("\n");
345
- }
346
-
347
351
  // src/auth/shared-session.ts
348
352
  var import_node_fs = require("node:fs");
349
353
  var import_node_path2 = require("node:path");
@@ -817,7 +821,7 @@ var ONE_IMPLICIT_FLAGS = ', "-Xfrontend", "-import-module", "-Xfrontend", "One"'
817
821
  var MULTI_MANIFEST_VERSION = 5;
818
822
  function swiftArtifactHash(input, release, moduleRoot) {
819
823
  const hash = (0, import_node_crypto2.createHash)("sha256");
820
- hash.update(`swift-compile/v2
824
+ hash.update(`swift-compile/v3
821
825
  ${release}
822
826
  `);
823
827
  if (input.oneModule) hash.update("one-module\n");
@@ -917,13 +921,95 @@ function syncSwiftTree(base, files) {
917
921
  (0, import_node_fs3.writeFileSync)(full, contents, "utf8");
918
922
  }
919
923
  }
920
- var CONDITION_LINE = /^([ \t]*#(?:if|elseif)\b)(.*)$/gm;
924
+ var DIRECTIVE = /^[ \t]*#(?:if|elseif)\b/;
921
925
  var OS_IOS = /\bos\(\s*iOS\s*\)/g;
922
926
  function swiftSourceAsIOS(source) {
923
- return source.replace(
924
- CONDITION_LINE,
925
- (_, directive, condition) => directive + condition.replace(OS_IOS, (match) => "true".padEnd(match.length))
926
- );
927
+ const code = swiftCodeLineStarts(source);
928
+ let offset = 0;
929
+ return source.split("\n").map((line) => {
930
+ const start = offset;
931
+ offset += line.length + 1;
932
+ if (!code.has(start) || !DIRECTIVE.test(line)) return line;
933
+ return line.replace(OS_IOS, (match) => "true".padEnd(match.length));
934
+ }).join("\n");
935
+ }
936
+ function swiftCodeLineStarts(source) {
937
+ const starts = /* @__PURE__ */ new Set();
938
+ const stack = [{ kind: "code", parens: 0 }];
939
+ const hashesAt = (at) => {
940
+ let end = at;
941
+ while (source[end] === "#") end++;
942
+ return end - at;
943
+ };
944
+ const closes = (at, hashes) => source.startsWith("#".repeat(hashes), at);
945
+ let i = 0;
946
+ while (i < source.length) {
947
+ const top = stack[stack.length - 1];
948
+ if ((i === 0 || source[i - 1] === "\n") && top.kind === "code" && stack.length === 1)
949
+ starts.add(i);
950
+ const char = source[i];
951
+ if (top.kind === "block") {
952
+ if (source.startsWith("/*", i)) {
953
+ top.depth++;
954
+ i += 2;
955
+ } else if (source.startsWith("*/", i)) {
956
+ if (--top.depth === 0) stack.pop();
957
+ i += 2;
958
+ } else i++;
959
+ continue;
960
+ }
961
+ if (top.kind === "string") {
962
+ if (char === "\\" && closes(i + 1, top.hashes)) {
963
+ const after = i + 1 + top.hashes;
964
+ if (source[after] === "(") {
965
+ stack.push({ kind: "code", parens: 0 });
966
+ i = after + 1;
967
+ } else i = after + 1;
968
+ continue;
969
+ }
970
+ const quote = top.multiline ? '"""' : '"';
971
+ if (source.startsWith(quote, i) && closes(i + quote.length, top.hashes)) {
972
+ stack.pop();
973
+ i += quote.length + top.hashes;
974
+ } else i++;
975
+ continue;
976
+ }
977
+ if (top.kind === "regex") {
978
+ if (char === "/" && closes(i + 1, top.hashes)) {
979
+ stack.pop();
980
+ i += 1 + top.hashes;
981
+ } else i++;
982
+ continue;
983
+ }
984
+ if (source.startsWith("//", i)) {
985
+ const end = source.indexOf("\n", i);
986
+ i = end === -1 ? source.length : end;
987
+ } else if (source.startsWith("/*", i)) {
988
+ stack.push({ kind: "block", depth: 1 });
989
+ i += 2;
990
+ } else if (char === '"' || char === "#") {
991
+ const hashes = hashesAt(i);
992
+ const at = i + hashes;
993
+ if (source.startsWith('"""', at)) {
994
+ stack.push({ kind: "string", hashes, multiline: true });
995
+ i = at + 3;
996
+ } else if (source[at] === '"') {
997
+ stack.push({ kind: "string", hashes, multiline: false });
998
+ i = at + 1;
999
+ } else if (hashes > 0 && source[at] === "/") {
1000
+ stack.push({ kind: "regex", hashes });
1001
+ i = at + 1;
1002
+ } else i = Math.max(at, i + 1);
1003
+ } else if (char === "(") {
1004
+ top.parens++;
1005
+ i++;
1006
+ } else if (char === ")") {
1007
+ if (top.parens === 0 && stack.length > 1) stack.pop();
1008
+ else top.parens--;
1009
+ i++;
1010
+ } else i++;
1011
+ }
1012
+ return starts;
927
1013
  }
928
1014
  var compiledSource = (file, contents) => file.endsWith(".swift") ? swiftSourceAsIOS(contents) : contents;
929
1015
  function writeSwiftProject(layout, input, moduleRoot) {
@@ -1073,7 +1159,15 @@ function compileSwiftProjectUnit(input, options) {
1073
1159
 
1074
1160
  // src/swift-submit.ts
1075
1161
  var SWIFT_COMPILE_PATH = "/api/swift-compile";
1076
- var SWIFT_HOST_ABI = 1;
1162
+ var MAX_SWIFT_COMPILE_FILES = 256;
1163
+ var MAX_SWIFT_COMPILE_SOURCE_BYTES = 2 * 1024 * 1024;
1164
+ var MAX_SWIFT_COMPILE_RESOURCES = 128;
1165
+ var MAX_SWIFT_COMPILE_RESOURCE_BYTES = 2 * 1024 * 1024;
1166
+ var MAX_SWIFT_COMPILE_RESOURCE_TOTAL_BYTES = 8 * 1024 * 1024;
1167
+ var MAX_SWIFT_COMPILE_IMAGES = 128;
1168
+ var MAX_SWIFT_COMPILE_IMAGE_BYTES = 4 * 1024 * 1024;
1169
+ var MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES = 16 * 1024 * 1024;
1170
+ var SWIFT_HOST_ABI = 2;
1077
1171
  var POLL_INTERVAL_MS = 1500;
1078
1172
  function swiftNativeEntry(native, file) {
1079
1173
  return {
@@ -1094,6 +1188,7 @@ function httpSwiftCompileSubmit(context) {
1094
1188
  try {
1095
1189
  response = await fetch(url, {
1096
1190
  method: "POST",
1191
+ signal: context.signal,
1097
1192
  headers: {
1098
1193
  "content-type": "application/json",
1099
1194
  ...authorization ? { authorization } : {}
@@ -1116,30 +1211,78 @@ function httpSwiftCompileSubmit(context) {
1116
1211
  `contrast-bundler: the swift compile for ${work.entry} answered ${response.status}${detail ? `: ${detail}` : ""}`
1117
1212
  );
1118
1213
  }
1119
- return await response.json();
1214
+ const outcome = await response.json();
1215
+ if (!isSwiftCompileOutcome(outcome)) {
1216
+ throw new Error("swift compile: the service returned invalid artifact metadata");
1217
+ }
1218
+ return outcome;
1120
1219
  };
1121
1220
  }
1221
+ function isRecord(value) {
1222
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1223
+ }
1224
+ function isSwiftNativeModules(value) {
1225
+ return isRecord(value) && typeof value.packageId === "string" && typeof value.hash === "string" && isRecord(value.files) && isRecord(value.modules) && [...Object.values(value.files), ...Object.values(value.modules)].every(
1226
+ (names) => Array.isArray(names) && names.every((name) => typeof name === "string")
1227
+ ) && Array.isArray(value.defaultViewFiles) && value.defaultViewFiles.every((file) => typeof file === "string");
1228
+ }
1229
+ function isSwiftCompileOutcome(value) {
1230
+ if (!isRecord(value)) return false;
1231
+ switch (value.status) {
1232
+ case "published":
1233
+ return typeof value.artifactUrl === "string" && typeof value.icuUrl === "string" && typeof value.hash === "string" && /^[a-f0-9]{64}$/.test(value.hash) && (value.imageUrls === void 0 || isRecord(value.imageUrls) && Object.values(value.imageUrls).every((url) => typeof url === "string")) && (value.native === void 0 || isSwiftNativeModules(value.native));
1234
+ case "compiling":
1235
+ return typeof value.hash === "string" && /^[a-f0-9]{64}$/.test(value.hash);
1236
+ case "refused":
1237
+ case "failed":
1238
+ return typeof value.detail === "string";
1239
+ default:
1240
+ return false;
1241
+ }
1242
+ }
1243
+ function compileAssetUrl(value, origin) {
1244
+ const url = new URL(value, origin);
1245
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password || url.origin !== new URL(origin).origin) {
1246
+ throw new Error("swift compile: artifact address escapes the compile origin");
1247
+ }
1248
+ return url.href;
1249
+ }
1122
1250
  function resolveImageUrls(urls, origin) {
1123
1251
  const resolved = {};
1124
1252
  for (const [name, url] of Object.entries(urls)) {
1125
- resolved[name] = new URL(url, origin).href;
1253
+ resolved[name] = compileAssetUrl(url, origin);
1126
1254
  }
1127
1255
  return resolved;
1128
1256
  }
1129
- async function compileSwiftProject(submit, work, origin) {
1257
+ async function compileSwiftProject(submit, work, origin, signal) {
1130
1258
  while (true) {
1259
+ signal?.throwIfAborted();
1131
1260
  const outcome = await submit(work);
1132
1261
  switch (outcome.status) {
1133
1262
  case "published":
1134
1263
  return {
1135
- url: new URL(outcome.artifactUrl, origin).href,
1264
+ url: compileAssetUrl(outcome.artifactUrl, origin),
1136
1265
  hash: outcome.hash,
1137
- icuUrl: new URL(outcome.icuUrl, origin).href,
1266
+ icuUrl: compileAssetUrl(outcome.icuUrl, origin),
1138
1267
  ...outcome.imageUrls === void 0 ? {} : { imageUrls: resolveImageUrls(outcome.imageUrls, origin) },
1139
1268
  ...outcome.native === void 0 ? {} : { native: outcome.native }
1140
1269
  };
1141
1270
  case "compiling":
1142
- await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL_MS));
1271
+ await new Promise((resolve2, reject) => {
1272
+ const finish = () => {
1273
+ signal?.removeEventListener("abort", abort);
1274
+ resolve2();
1275
+ };
1276
+ const timer = setTimeout(finish, POLL_INTERVAL_MS);
1277
+ const abort = () => {
1278
+ clearTimeout(timer);
1279
+ signal?.removeEventListener("abort", abort);
1280
+ const reason = signal?.reason;
1281
+ reject(reason instanceof Error ? reason : new Error(String(reason)));
1282
+ };
1283
+ signal?.addEventListener("abort", abort, { once: true });
1284
+ if (signal?.aborted) abort();
1285
+ });
1143
1286
  continue;
1144
1287
  case "refused":
1145
1288
  case "failed":
@@ -1150,11 +1293,10 @@ async function compileSwiftProject(submit, work, origin) {
1150
1293
  }
1151
1294
  }
1152
1295
 
1153
- // src/vite-plugin-swift.ts
1296
+ // src/swift-host.ts
1154
1297
  var ARTIFACT_PATH = "/__peach/swift/";
1155
1298
  var RESOURCE_PATH = "/__peach/swift-res/";
1156
1299
  var ICU_PATH = "/__peach/swift-icu/";
1157
- var VIRTUAL_PREFIX = "\0peach-swift:";
1158
1300
  var SKIP_SOURCE_DIRS = /* @__PURE__ */ new Set([
1159
1301
  "node_modules",
1160
1302
  "ios",
@@ -1169,89 +1311,124 @@ function swiftPackageId(packagePath) {
1169
1311
  return import_node_path5.default.basename(packagePath).replace(/[^A-Za-z0-9_]/g, "_").replace(/^(\d)/, "_$1");
1170
1312
  }
1171
1313
  function nativeSourceDescriptor(packagePath) {
1314
+ packagePath = import_node_fs4.default.realpathSync(packagePath);
1172
1315
  const file = import_node_path5.default.join(packagePath, NATIVE_DESCRIPTOR);
1173
1316
  if (!import_node_fs4.default.existsSync(file)) return null;
1174
- const value = JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
1317
+ const value = JSON.parse(
1318
+ readPackageFile(packagePath, file, MAX_SWIFT_COMPILE_SOURCE_BYTES).toString("utf8")
1319
+ );
1175
1320
  const invalid = () => new Error(`${file}: invalid One native source descriptor; run one typegen`);
1176
1321
  if (!value || typeof value !== "object" || Reflect.get(value, "generatedBy") !== "one typegen" || !import_node_fs4.default.existsSync(import_node_path5.default.join(packagePath, NATIVE_GLUE))) {
1177
1322
  throw invalid();
1178
1323
  }
1179
- const packageId = Reflect.get(value, "packageId");
1180
- const hash = Reflect.get(value, "hash");
1181
- const rawFiles = Reflect.get(value, "files");
1182
- const rawModules = Reflect.get(value, "modules");
1183
- const rawDefaults = Reflect.get(value, "defaultViewFiles");
1184
- if (typeof packageId !== "string" || typeof hash !== "string" || !rawFiles || typeof rawFiles !== "object" || !rawModules || typeof rawModules !== "object" || !Array.isArray(rawDefaults) || !rawDefaults.every((name) => typeof name === "string")) {
1324
+ if (!isSwiftNativeModules(value) || value.packageId !== swiftPackageId(packagePath)) {
1185
1325
  throw invalid();
1186
1326
  }
1187
- const files = {};
1188
- const modules = {};
1189
- for (const [key, names] of Object.entries(rawFiles)) {
1190
- if (!Array.isArray(names) || !names.every((name) => typeof name === "string"))
1191
- throw invalid();
1192
- files[key] = names;
1193
- }
1194
- for (const [key, names] of Object.entries(rawModules)) {
1195
- if (!Array.isArray(names) || !names.every((name) => typeof name === "string"))
1196
- throw invalid();
1197
- modules[key] = names;
1198
- }
1199
- if (packageId !== swiftPackageId(packagePath)) {
1200
- throw new Error(`${file}: invalid One native source descriptor; run one typegen`);
1201
- }
1202
- return { packageId, hash, files, modules, defaultViewFiles: rawDefaults };
1327
+ return value;
1203
1328
  }
1204
1329
  function nativeSourceFiles(packagePath) {
1330
+ packagePath = import_node_fs4.default.realpathSync(packagePath);
1205
1331
  return [NATIVE_DESCRIPTOR, NATIVE_GLUE].map((name) => import_node_path5.default.join(packagePath, name)).filter((file) => import_node_fs4.default.existsSync(file));
1206
1332
  }
1207
1333
  var projects = /* @__PURE__ */ new Map();
1208
- var devServer;
1209
- function projectFor(packagePath) {
1334
+ function projectFor(packagePath, hosted) {
1335
+ packagePath = import_node_fs4.default.realpathSync(packagePath);
1210
1336
  const existing = projects.get(packagePath);
1211
- if (existing) return existing;
1337
+ if (existing) {
1338
+ if (hosted && !existing.hosted) {
1339
+ throw new Error("a Swift package cannot share local and hosted compile ownership");
1340
+ }
1341
+ return existing;
1342
+ }
1212
1343
  const created = {
1213
1344
  packagePath,
1214
1345
  artifact: null,
1215
1346
  building: null,
1216
- resourceFiles: /* @__PURE__ */ new Map()
1347
+ resourceFiles: /* @__PURE__ */ new Map(),
1348
+ resourcesPrevious: /* @__PURE__ */ new Map(),
1349
+ artifacts: [],
1350
+ hosted
1217
1351
  };
1218
1352
  projects.set(packagePath, created);
1219
1353
  return created;
1220
1354
  }
1221
- function sourceFiles(root) {
1222
- const found = [];
1223
- const walk = (dir) => {
1224
- for (const entry of import_node_fs4.default.readdirSync(dir, { withFileTypes: true })) {
1225
- if (entry.name.startsWith(".") || SKIP_SOURCE_DIRS.has(entry.name)) continue;
1226
- const full = import_node_path5.default.join(dir, entry.name);
1227
- if (entry.isDirectory()) {
1228
- if (full !== root && import_node_fs4.default.existsSync(import_node_path5.default.join(full, "Package.swift"))) continue;
1229
- walk(full);
1230
- } else if (entry.name.endsWith(".swift") && (entry.isFile() || entry.isSymbolicLink() && import_node_fs4.default.statSync(full).isFile()))
1231
- found.push(full);
1232
- }
1233
- };
1234
- walk(root);
1235
- return found;
1236
- }
1237
- function resourceCandidateFiles(root) {
1355
+ function packageFiles(root) {
1356
+ const canonicalRoot = import_node_fs4.default.realpathSync(root);
1238
1357
  const found = [];
1239
1358
  const walk = (dir) => {
1240
- for (const entry of import_node_fs4.default.readdirSync(dir, { withFileTypes: true })) {
1241
- if (entry.name.startsWith(".") || SKIP_SOURCE_DIRS.has(entry.name) || entry.name === ".build")
1359
+ for (const entry of import_node_fs4.default.readdirSync(dir, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
1360
+ if (entry.name.startsWith(".") && entry.name !== NATIVE_DESCRIPTOR && entry.name !== NATIVE_GLUE || SKIP_SOURCE_DIRS.has(entry.name))
1242
1361
  continue;
1243
1362
  const full = import_node_path5.default.join(dir, entry.name);
1363
+ if (entry.isSymbolicLink()) throw new Error(`swift input is a symlink: ${full}`);
1364
+ const real = import_node_fs4.default.realpathSync(full);
1365
+ const relative = import_node_path5.default.relative(canonicalRoot, real);
1366
+ if (relative.startsWith(`..${import_node_path5.default.sep}`) || relative === ".." || import_node_path5.default.isAbsolute(relative)) {
1367
+ throw new Error(`swift input escapes the selected package: ${full}`);
1368
+ }
1244
1369
  if (entry.isDirectory()) {
1245
- if (full !== root && import_node_fs4.default.existsSync(import_node_path5.default.join(full, "Package.swift"))) continue;
1370
+ if (import_node_fs4.default.existsSync(import_node_path5.default.join(full, "Package.swift"))) continue;
1246
1371
  walk(full);
1247
- } else {
1372
+ } else if (entry.isFile()) {
1248
1373
  found.push(full);
1374
+ if (found.length > 4096) throw new Error("swift package exceeds 4096 input files");
1249
1375
  }
1250
1376
  }
1251
1377
  };
1252
- walk(root);
1378
+ walk(canonicalRoot);
1253
1379
  return found;
1254
1380
  }
1381
+ function readPackageFile(root, file, maxBytes) {
1382
+ const canonicalRoot = import_node_fs4.default.realpathSync(root);
1383
+ const relative = import_node_path5.default.relative(canonicalRoot, import_node_fs4.default.realpathSync(file));
1384
+ if (relative === ".." || relative.startsWith(`..${import_node_path5.default.sep}`) || import_node_path5.default.isAbsolute(relative)) {
1385
+ throw new Error(`swift input escapes the selected package: ${file}`);
1386
+ }
1387
+ let ancestor = import_node_path5.default.resolve(file);
1388
+ while (ancestor !== canonicalRoot) {
1389
+ if (import_node_fs4.default.lstatSync(ancestor).isSymbolicLink()) {
1390
+ throw new Error(`swift input is a symlink: ${ancestor}`);
1391
+ }
1392
+ const parent = import_node_path5.default.dirname(ancestor);
1393
+ if (parent === ancestor)
1394
+ throw new Error(`swift input escapes the selected package: ${file}`);
1395
+ ancestor = parent;
1396
+ }
1397
+ const expected = import_node_fs4.default.statSync(file);
1398
+ const fd = import_node_fs4.default.openSync(file, import_node_fs4.default.constants.O_RDONLY | import_node_fs4.default.constants.O_NOFOLLOW);
1399
+ try {
1400
+ const stat = import_node_fs4.default.fstatSync(fd);
1401
+ if (stat.dev !== expected.dev || stat.ino !== expected.ino) {
1402
+ throw new Error(`swift input changed while opening: ${file}`);
1403
+ }
1404
+ if (!stat.isFile() || stat.size > maxBytes) {
1405
+ throw new Error(`swift input exceeds ${maxBytes} bytes: ${file}`);
1406
+ }
1407
+ const bytes = Buffer.alloc(stat.size + 1);
1408
+ let offset = 0;
1409
+ while (offset < bytes.length) {
1410
+ const count = import_node_fs4.default.readSync(fd, bytes, offset, bytes.length - offset, null);
1411
+ if (count === 0) break;
1412
+ offset += count;
1413
+ }
1414
+ if (offset !== stat.size || import_node_fs4.default.fstatSync(fd).size !== stat.size) {
1415
+ throw new Error(`swift input changed while reading: ${file}`);
1416
+ }
1417
+ return bytes.subarray(0, offset);
1418
+ } finally {
1419
+ import_node_fs4.default.closeSync(fd);
1420
+ }
1421
+ }
1422
+ function sourceFiles(root) {
1423
+ const files = packageFiles(root).filter((file) => file.endsWith(".swift"));
1424
+ if (files.length > MAX_SWIFT_COMPILE_FILES || files.reduce((total, file) => total + import_node_fs4.default.statSync(file).size, 0) > MAX_SWIFT_COMPILE_SOURCE_BYTES) {
1425
+ throw new Error("swift sources exceed the compile service caps");
1426
+ }
1427
+ return files;
1428
+ }
1429
+ function resourceCandidateFiles(root) {
1430
+ return packageFiles(root).filter((file) => !file.endsWith(".swift"));
1431
+ }
1255
1432
  var RESOURCE_CONTENT_TYPES = {
1256
1433
  ".avif": "image/avif",
1257
1434
  ".bmp": "image/bmp",
@@ -1272,11 +1449,11 @@ function buildSwiftResourceTable(project, baseUrl) {
1272
1449
  const file = byRelative.get(candidate);
1273
1450
  if (!file || !/(^|\/)Contents\.json$|\.symbolset\/[^/]+\.svg$/.test(candidate))
1274
1451
  return null;
1275
- try {
1276
- return import_node_fs4.default.readFileSync(file, "utf8");
1277
- } catch {
1278
- return null;
1279
- }
1452
+ return readPackageFile(
1453
+ project.packagePath,
1454
+ file,
1455
+ MAX_SWIFT_COMPILE_RESOURCE_BYTES
1456
+ ).toString("utf8");
1280
1457
  };
1281
1458
  const found = enumerateBundleImages(relative, readContents);
1282
1459
  const colors = enumerateBundleColors(relative, readContents);
@@ -1285,15 +1462,19 @@ function buildSwiftResourceTable(project, baseUrl) {
1285
1462
  const images = {};
1286
1463
  const watched = [];
1287
1464
  const registered = /* @__PURE__ */ new Map();
1465
+ let imageBytes = 0;
1288
1466
  for (const { name, file, scale } of found) {
1289
1467
  const disk = byRelative.get(file);
1290
1468
  if (!disk) continue;
1291
- let bytes;
1292
- try {
1293
- bytes = import_node_fs4.default.readFileSync(disk);
1294
- } catch {
1295
- continue;
1469
+ if (Object.keys(images).length >= MAX_SWIFT_COMPILE_IMAGES || imageBytes + import_node_fs4.default.statSync(disk).size > MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES) {
1470
+ throw new Error("swift image pack exceeds the compile service caps");
1296
1471
  }
1472
+ const bytes = readPackageFile(
1473
+ project.packagePath,
1474
+ disk,
1475
+ MAX_SWIFT_COMPILE_IMAGE_BYTES
1476
+ );
1477
+ imageBytes += bytes.length;
1297
1478
  const hash = (0, import_node_crypto3.createHash)("sha256").update(bytes).digest("hex").slice(0, 16);
1298
1479
  const ext = import_node_path5.default.posix.extname(file).toLowerCase();
1299
1480
  table[name] = { uri: `${baseUrl}${RESOURCE_PATH}${hash}${ext}`, scale };
@@ -1304,65 +1485,60 @@ function buildSwiftResourceTable(project, baseUrl) {
1304
1485
  watched.push(file);
1305
1486
  const contents = file.replace(/\/[^/]+$/, "/Contents.json");
1306
1487
  if (byRelative.has(contents)) watched.push(contents);
1307
- registered.set(`${hash}${ext}`, disk);
1488
+ registered.set(`${hash}${ext}`, {
1489
+ bytes,
1490
+ contentType: RESOURCE_CONTENT_TYPES[ext] ?? "application/octet-stream"
1491
+ });
1308
1492
  }
1309
1493
  for (const file of relative) {
1310
1494
  if (file.endsWith(".colorset/Contents.json") || file.includes(".symbolset/"))
1311
1495
  watched.push(file);
1312
1496
  }
1313
- project.resourceFiles = registered;
1497
+ if (registered.size !== project.resourceFiles.size || [...registered].some(
1498
+ ([key, value]) => !project.resourceFiles.get(key)?.bytes.equals(value.bytes)
1499
+ )) {
1500
+ project.resourcesPrevious = project.resourceFiles;
1501
+ project.resourceFiles = registered;
1502
+ }
1314
1503
  return { table, images, colors, symbols, watched };
1315
1504
  }
1316
- var BUNDLE_MAX_FILES = 128;
1317
- var BUNDLE_MAX_FILE_BYTES = 4 * 1024 * 1024;
1318
- var BUNDLE_MAX_TOTAL_BYTES = 8 * 1024 * 1024;
1505
+ var BUNDLE_MAX_FILES = MAX_SWIFT_COMPILE_RESOURCES;
1506
+ var BUNDLE_MAX_FILE_BYTES = MAX_SWIFT_COMPILE_RESOURCE_BYTES;
1507
+ var BUNDLE_MAX_TOTAL_BYTES = MAX_SWIFT_COMPILE_RESOURCE_TOTAL_BYTES;
1319
1508
  function buildSwiftBundleFiles(packagePath) {
1509
+ packagePath = import_node_fs4.default.realpathSync(packagePath);
1320
1510
  const relative = resourceCandidateFiles(packagePath).map(
1321
1511
  (file) => import_node_path5.default.relative(packagePath, file).split(import_node_path5.default.sep).join(import_node_path5.default.posix.sep)
1322
1512
  );
1323
1513
  const files = {};
1324
1514
  const watched = [];
1325
- const skipped = [];
1326
1515
  let total = 0;
1327
1516
  for (const { base, file } of enumerateBundleJson(relative)) {
1328
1517
  watched.push(file);
1329
- let bytes;
1330
- try {
1331
- bytes = import_node_fs4.default.readFileSync(import_node_path5.default.join(packagePath, ...file.split("/")));
1332
- } catch {
1333
- continue;
1334
- }
1335
- if (bytes.length > BUNDLE_MAX_FILE_BYTES) {
1336
- skipped.push(`${file} (${bytes.length} bytes past the per-file cap)`);
1337
- continue;
1338
- }
1339
- if (Object.keys(files).length >= BUNDLE_MAX_FILES || total + bytes.length > BUNDLE_MAX_TOTAL_BYTES) {
1340
- skipped.push(
1341
- `${file} (pack caps: ${BUNDLE_MAX_FILES} files / ${BUNDLE_MAX_TOTAL_BYTES} bytes)`
1518
+ const disk = import_node_path5.default.join(packagePath, ...file.split("/"));
1519
+ if (Object.keys(files).length >= BUNDLE_MAX_FILES || total + import_node_fs4.default.statSync(disk).size > BUNDLE_MAX_TOTAL_BYTES) {
1520
+ throw new Error(
1521
+ `swift bundle pack exceeds ${BUNDLE_MAX_FILES} files / ${BUNDLE_MAX_TOTAL_BYTES} bytes`
1342
1522
  );
1343
- continue;
1344
1523
  }
1524
+ const bytes = readPackageFile(packagePath, disk, BUNDLE_MAX_FILE_BYTES);
1345
1525
  files[base] = bytes.toString("base64");
1346
1526
  total += bytes.length;
1347
1527
  }
1348
- return { files, watched, skipped };
1528
+ return { files, watched };
1349
1529
  }
1350
1530
  function serveSwiftResource(project, key) {
1351
- const disk = project.resourceFiles.get(key);
1352
- if (!disk) return null;
1353
- try {
1354
- return {
1355
- bytes: import_node_fs4.default.readFileSync(disk),
1356
- contentType: RESOURCE_CONTENT_TYPES[import_node_path5.default.extname(disk).toLowerCase()] ?? "application/octet-stream"
1357
- };
1358
- } catch {
1359
- return null;
1360
- }
1531
+ return project.resourceFiles.get(key) ?? project.resourcesPrevious.get(key) ?? null;
1361
1532
  }
1362
1533
  function linkedModuleRoots(packagePath) {
1534
+ packagePath = import_node_fs4.default.realpathSync(packagePath);
1363
1535
  let manifest;
1364
1536
  try {
1365
- manifest = import_node_fs4.default.readFileSync(import_node_path5.default.join(packagePath, "Package.swift"), "utf8");
1537
+ manifest = readPackageFile(
1538
+ packagePath,
1539
+ import_node_path5.default.join(packagePath, "Package.swift"),
1540
+ MAX_SWIFT_COMPILE_SOURCE_BYTES
1541
+ ).toString("utf8");
1366
1542
  } catch {
1367
1543
  return [];
1368
1544
  }
@@ -1374,25 +1550,16 @@ function linkedModuleRoots(packagePath) {
1374
1550
  }
1375
1551
  return [...roots].sort();
1376
1552
  }
1377
- function sourceStamp(packagePath, moduleRootOverride) {
1553
+ function sourceStamp(packagePath, moduleRootOverride, hosted = false) {
1378
1554
  const hash = (0, import_node_crypto3.createHash)("sha256");
1379
1555
  hash.update(packagePath);
1380
- const projectInputs = /* @__PURE__ */ new Set([
1381
- import_node_path5.default.join(packagePath, "Package.swift"),
1382
- ...sourceFiles(packagePath),
1383
- ...nativeSourceFiles(packagePath),
1384
- ...buildSwiftBundleFiles(packagePath).watched.map(
1385
- (file) => import_node_path5.default.join(packagePath, ...file.split(import_node_path5.default.posix.sep))
1386
- ),
1387
- ...buildSwiftResourceTable(projectFor(packagePath), "").watched.map(
1388
- (file) => import_node_path5.default.join(packagePath, ...file.split(import_node_path5.default.posix.sep))
1389
- )
1390
- ]);
1556
+ const projectInputs = new Set(packageFiles(packagePath));
1391
1557
  for (const file of [...projectInputs].sort()) {
1392
1558
  hash.update(import_node_path5.default.relative(packagePath, file));
1393
- hash.update(String(import_node_fs4.default.statSync(file).mtimeMs));
1559
+ const stat = import_node_fs4.default.statSync(file);
1560
+ hash.update(`${stat.mtimeMs}/${stat.ctimeMs}/${stat.size}`);
1394
1561
  }
1395
- const moduleRoot = moduleRootOverride ?? linkedModuleRoots(packagePath).find(
1562
+ const moduleRoot = hosted ? void 0 : moduleRootOverride ?? linkedModuleRoots(packagePath).find(
1396
1563
  (root) => import_node_fs4.default.existsSync(import_node_path5.default.join(root, "toolchain.json"))
1397
1564
  );
1398
1565
  if (moduleRoot) {
@@ -1408,46 +1575,85 @@ function entryRootId(packagePath, file) {
1408
1575
  const relative = import_node_path5.default.relative(packagePath, file).split(import_node_path5.default.sep).join(import_node_path5.default.posix.sep);
1409
1576
  return `${import_node_path5.default.basename(packagePath)}${import_node_path5.default.posix.sep}${relative}`;
1410
1577
  }
1411
- function serverOrigin() {
1412
- const address = devServer?.httpServer?.address();
1413
- const port = address && typeof address === "object" ? address.port : devServer?.config.server.port;
1414
- if (!port) throw new Error("the swift urls need the dev server port");
1415
- return `http://localhost:${port}`;
1416
- }
1417
- function artifactUrl(project, artifact) {
1418
- return `${serverOrigin()}${ARTIFACT_PATH}${artifact.hash}.wasm`;
1419
- }
1420
1578
  function icuUrl(origin, artifact) {
1421
1579
  return "url" in artifact.icu ? artifact.icu.url : `${origin}${ICU_PATH}${artifact.icu.sha256}`;
1422
1580
  }
1423
1581
  function packageSources(packagePath) {
1582
+ const sources = sourceFiles(packagePath);
1583
+ if (sources.length > MAX_SWIFT_COMPILE_FILES)
1584
+ throw new Error("swift source file cap exceeded");
1585
+ let total = 0;
1586
+ for (const file of sources) total += import_node_fs4.default.statSync(file).size;
1587
+ if (total > MAX_SWIFT_COMPILE_SOURCE_BYTES)
1588
+ throw new Error("swift source byte cap exceeded");
1424
1589
  const files = {};
1425
- for (const file of sourceFiles(packagePath)) {
1590
+ for (const file of sources) {
1426
1591
  const relative = import_node_path5.default.relative(packagePath, file).split(import_node_path5.default.sep).join(import_node_path5.default.posix.sep);
1427
- files[relative] = import_node_fs4.default.readFileSync(file, "utf8");
1592
+ files[relative] = readPackageFile(
1593
+ packagePath,
1594
+ file,
1595
+ MAX_SWIFT_COMPILE_SOURCE_BYTES
1596
+ ).toString("utf8");
1428
1597
  }
1429
- const manifest = import_node_path5.default.join(packagePath, "Package.swift");
1430
- if (import_node_fs4.default.existsSync(manifest)) files["Package.swift"] = import_node_fs4.default.readFileSync(manifest, "utf8");
1431
1598
  return files;
1432
1599
  }
1433
- function compileInput(packagePath, entry) {
1600
+ function packageSnapshot(project) {
1601
+ const files = packageSources(project.packagePath);
1602
+ const native = nativeSourceDescriptor(project.packagePath);
1603
+ return {
1604
+ files,
1605
+ resources: buildSwiftResourceTable(project, ""),
1606
+ bundleFiles: buildSwiftBundleFiles(project.packagePath),
1607
+ native,
1608
+ ...native ? {
1609
+ glue: readPackageFile(
1610
+ project.packagePath,
1611
+ import_node_path5.default.join(project.packagePath, NATIVE_GLUE),
1612
+ MAX_SWIFT_COMPILE_SOURCE_BYTES
1613
+ ).toString("utf8")
1614
+ } : {}
1615
+ };
1616
+ }
1617
+ function snapshotCompileInput(snapshot, entry) {
1434
1618
  const input = {
1435
- files: packageSources(packagePath),
1436
- resources: buildSwiftBundleFiles(packagePath).files,
1437
- images: buildSwiftResourceTable(projectFor(packagePath), "").images,
1619
+ files: snapshot.files,
1620
+ resources: snapshot.bundleFiles.files,
1621
+ images: snapshot.resources.images,
1438
1622
  entry,
1439
1623
  oneModule: true
1440
1624
  };
1441
- return nativeSourceDescriptor(packagePath) ? withNativeSourceGlue(
1442
- input,
1443
- import_node_fs4.default.readFileSync(import_node_path5.default.join(packagePath, NATIVE_GLUE), "utf8")
1444
- ) : input;
1625
+ return snapshot.glue ? withNativeSourceGlue(input, snapshot.glue) : input;
1445
1626
  }
1446
- async function compile(packagePath, entry, stamp, moduleRootOverride) {
1627
+ function compileInput(packagePath, entry) {
1628
+ return snapshotCompileInput(packageSnapshot(projectFor(packagePath)), entry);
1629
+ }
1630
+ async function compile(packagePath, entry, stamp, moduleRootOverride, hosted, snapshot) {
1631
+ const resources = {
1632
+ table: snapshot.resources.table,
1633
+ colors: snapshot.resources.colors,
1634
+ symbols: snapshot.resources.symbols,
1635
+ watched: snapshot.resources.watched
1636
+ };
1637
+ const content = {
1638
+ resources,
1639
+ bundleFiles: snapshot.bundleFiles,
1640
+ native: snapshot.native
1641
+ };
1642
+ if (hosted)
1643
+ return {
1644
+ ...await compileRemote(packagePath, entry, snapshot, hosted),
1645
+ stamp,
1646
+ package: content
1647
+ };
1447
1648
  const moduleRoot = moduleRootOverride ?? linkedModuleRoots(packagePath).find(
1448
1649
  (root) => import_node_fs4.default.existsSync(import_node_path5.default.join(root, "toolchain.json"))
1449
1650
  );
1450
- if (!moduleRoot) return { ...await compileRemote(packagePath, entry), stamp };
1651
+ if (!moduleRoot)
1652
+ return {
1653
+ ...await compileRemote(packagePath, entry, snapshot),
1654
+ stamp,
1655
+ package: content
1656
+ };
1451
1657
  const toolchain = JSON.parse(
1452
1658
  import_node_fs4.default.readFileSync(import_node_path5.default.join(moduleRoot, "toolchain.json"), "utf8")
1453
1659
  );
@@ -1455,7 +1661,7 @@ async function compile(packagePath, entry, stamp, moduleRootOverride) {
1455
1661
  if (typeof release !== "string") {
1456
1662
  throw new Error(`swift toolchain manifest at ${moduleRoot} has no release`);
1457
1663
  }
1458
- const artifact = await compileSwiftProjectUnit(compileInput(packagePath, entry), {
1664
+ const artifact = await compileSwiftProjectUnit(snapshotCompileInput(snapshot, entry), {
1459
1665
  // the compile rewrites Sources/App in its root before swiftpm builds, and
1460
1666
  // its lock orders one process only: two vites on one root (a lane's dev
1461
1667
  // server and a capture, or two checkouts) build each other's sources. so
@@ -1473,50 +1679,75 @@ async function compile(packagePath, entry, stamp, moduleRootOverride) {
1473
1679
  `Library/Developer/Toolchains/${release}.xctoolchain/usr/bin`
1474
1680
  )
1475
1681
  });
1476
- return { ...artifact, stamp };
1682
+ return { ...artifact, stamp, package: content };
1477
1683
  }
1478
- async function compileRemote(packagePath, entry) {
1479
- const auth = resolveCliAuth();
1480
- if (!auth || auth.kind === "github") {
1684
+ async function compileRemote(packagePath, entry, snapshot, hosted) {
1685
+ const auth = hosted ? null : resolveCliAuth();
1686
+ if (!hosted && (!auth || auth.kind === "github")) {
1481
1687
  throw new Error(
1482
1688
  `compiling ${import_node_path5.default.basename(packagePath)} for Peach needs \`peach login\` or PEACH_API_KEY: this peachsim has no local SwiftUI module, so Contrast compiles it`
1483
1689
  );
1484
1690
  }
1485
- const origin = resolveContrastBackendOrigin(
1486
- auth.kind === "session" ? auth.origin : null
1487
- );
1691
+ const origin = hosted?.origin ?? resolveContrastBackendOrigin(auth?.kind === "session" ? auth.origin : null);
1692
+ const authorization = hosted?.authorization ?? (auth ? authHeaderValue(auth) : void 0);
1693
+ const signal = AbortSignal.any([
1694
+ ...hosted ? [hosted.signal] : [],
1695
+ AbortSignal.timeout(5 * 6e4)
1696
+ ]);
1488
1697
  const submit = httpSwiftCompileSubmit({
1489
1698
  endpoint: origin,
1490
- authorization: authHeaderValue(auth)
1699
+ authorization,
1700
+ signal
1491
1701
  });
1492
1702
  if (!submit) throw new Error("swift compile: no credential to submit with");
1493
- const descriptor = nativeSourceDescriptor(packagePath);
1703
+ const descriptor = snapshot.native;
1494
1704
  const address = await compileSwiftProject(
1495
1705
  submit,
1496
1706
  {
1497
- files: packageSources(packagePath),
1707
+ files: snapshot.files,
1708
+ resources: snapshot.bundleFiles.files,
1709
+ images: snapshot.resources.images,
1498
1710
  entry,
1499
1711
  packageId: descriptor?.packageId ?? swiftPackageId(packagePath)
1500
1712
  },
1501
- origin
1713
+ origin,
1714
+ signal
1502
1715
  );
1503
1716
  if (descriptor && address.native?.hash !== descriptor.hash) {
1504
1717
  throw new Error(
1505
1718
  `${import_node_path5.default.basename(packagePath)}: Contrast compiled native contract ${address.native?.hash ?? "none"} but one typegen wrote ${descriptor.hash}; the app's One and the compile host's differ, or the compile host is older than this peachsim`
1506
1719
  );
1507
1720
  }
1508
- const response = await fetch(address.url);
1721
+ const response = await fetch(address.url, { signal });
1509
1722
  if (!response.ok) {
1510
1723
  throw new Error(`swift compile: ${address.url} answered ${response.status}`);
1511
1724
  }
1725
+ const maxArtifactBytes = 64 * 1024 * 1024;
1726
+ if (Number(response.headers.get("content-length")) > maxArtifactBytes || !response.body) {
1727
+ throw new Error("swift compile: artifact exceeds 64 MiB or has no body");
1728
+ }
1729
+ const reader = response.body.getReader();
1730
+ const chunks = [];
1731
+ let total = 0;
1732
+ for (; ; ) {
1733
+ signal.throwIfAborted();
1734
+ const next = await reader.read();
1735
+ if (next.done) break;
1736
+ total += next.value.byteLength;
1737
+ if (total > maxArtifactBytes) {
1738
+ await reader.cancel();
1739
+ throw new Error("swift compile: artifact exceeds 64 MiB");
1740
+ }
1741
+ chunks.push(next.value);
1742
+ }
1512
1743
  return {
1513
1744
  hash: address.hash,
1514
- bytes: Buffer.from(await response.arrayBuffer()),
1745
+ bytes: Buffer.concat(chunks),
1515
1746
  icu: { url: address.icuUrl }
1516
1747
  };
1517
1748
  }
1518
1749
  function artifactFor(project, entry, moduleRoot) {
1519
- const stamp = sourceStamp(project.packagePath, moduleRoot);
1750
+ const stamp = sourceStamp(project.packagePath, moduleRoot, Boolean(project.hosted));
1520
1751
  if (project.artifact?.stamp === stamp) return Promise.resolve(project.artifact);
1521
1752
  if (project.building) {
1522
1753
  const running = project.building;
@@ -1524,9 +1755,24 @@ function artifactFor(project, entry, moduleRoot) {
1524
1755
  const again = () => artifactFor(project, entry, moduleRoot);
1525
1756
  return running.promise.then(again, again);
1526
1757
  }
1527
- const promise = compile(project.packagePath, entry, stamp, moduleRoot).then(
1758
+ const snapshot = packageSnapshot(project);
1759
+ if (sourceStamp(project.packagePath, moduleRoot, Boolean(project.hosted)) !== stamp) {
1760
+ return artifactFor(project, entry, moduleRoot);
1761
+ }
1762
+ const promise = compile(
1763
+ project.packagePath,
1764
+ entry,
1765
+ stamp,
1766
+ moduleRoot,
1767
+ project.hosted,
1768
+ snapshot
1769
+ ).then(
1528
1770
  (artifact) => {
1529
1771
  project.artifact = artifact;
1772
+ project.artifacts = [
1773
+ artifact,
1774
+ ...project.artifacts.filter((old) => old.hash !== artifact.hash)
1775
+ ].slice(0, 2);
1530
1776
  project.building = null;
1531
1777
  return artifact;
1532
1778
  },
@@ -1535,10 +1781,14 @@ function artifactFor(project, entry, moduleRoot) {
1535
1781
  throw error;
1536
1782
  }
1537
1783
  );
1538
- project.building = { stamp, promise };
1539
- return promise;
1784
+ const fresh = promise.then(
1785
+ (artifact) => sourceStamp(project.packagePath, moduleRoot, Boolean(project.hosted)) === artifact.stamp ? artifact : artifactFor(project, entry, moduleRoot)
1786
+ );
1787
+ project.building = { stamp, promise: fresh };
1788
+ return fresh;
1540
1789
  }
1541
1790
  function swiftPackageWatchFiles(packagePath, moduleRoot) {
1791
+ packagePath = import_node_fs4.default.realpathSync(packagePath);
1542
1792
  return [
1543
1793
  import_node_path5.default.join(packagePath, "Package.swift"),
1544
1794
  ...sourceFiles(packagePath),
@@ -1547,9 +1797,12 @@ function swiftPackageWatchFiles(packagePath, moduleRoot) {
1547
1797
  ...moduleRoot ? swiftModuleInputFiles(moduleRoot).map((file) => import_node_path5.default.join(moduleRoot, file)) : []
1548
1798
  ];
1549
1799
  }
1550
- async function swiftPackageMetadata(packagePath, packageName, moduleRoot, origin) {
1800
+ async function swiftPackageMetadata(packagePath, packageName, moduleRoot, origin, hosted) {
1801
+ packagePath = import_node_fs4.default.realpathSync(packagePath);
1551
1802
  const mains = sourceFiles(packagePath).filter(
1552
- (file) => /@main\s+(?:public\s+)?struct\s+\w+\s*:/.test(import_node_fs4.default.readFileSync(file, "utf8"))
1803
+ (file) => /@main\s+(?:public\s+)?struct\s+\w+\s*:/.test(
1804
+ readPackageFile(packagePath, file, MAX_SWIFT_COMPILE_SOURCE_BYTES).toString("utf8")
1805
+ )
1553
1806
  );
1554
1807
  const native = nativeSourceDescriptor(packagePath);
1555
1808
  if (mains.length > 1 || mains.length === 0 && !native) {
@@ -1557,27 +1810,32 @@ async function swiftPackageMetadata(packagePath, packageName, moduleRoot, origin
1557
1810
  `swift package ${packageName} needs exactly one @main source or generated native modules; found ${mains.length}`
1558
1811
  );
1559
1812
  }
1560
- const project = projectFor(packagePath);
1813
+ const project = projectFor(packagePath, hosted);
1561
1814
  const entry = mains.length === 1 ? import_node_path5.default.relative(packagePath, mains[0]).split(import_node_path5.default.sep).join(import_node_path5.default.posix.sep) : Object.entries(native?.files ?? {}).find(
1562
1815
  ([_file, names]) => names.length > 0
1563
1816
  )?.[0];
1564
1817
  if (!entry) throw new Error(`swift package ${packageName} has no native module source`);
1565
1818
  const artifact = await artifactFor(project, entry, moduleRoot);
1566
- const resources = buildSwiftResourceTable(project, origin);
1567
- const bundleFiles = buildSwiftBundleFiles(packagePath);
1819
+ const resources = artifact.package.resources;
1820
+ const bundleFiles = artifact.package.bundleFiles;
1568
1821
  return {
1569
1822
  url: `${origin}${ARTIFACT_PATH}${artifact.hash}.wasm`,
1570
1823
  hash: artifact.hash,
1571
1824
  icuUrl: icuUrl(origin, artifact),
1572
1825
  rootId: `${packageName}/${entry}`,
1573
- resources: resources.table,
1826
+ resources: Object.fromEntries(
1827
+ Object.entries(resources.table).map(([name, image]) => [
1828
+ name,
1829
+ { ...image, uri: `${origin}${image.uri}` }
1830
+ ])
1831
+ ),
1574
1832
  bundleFiles: bundleFiles.files,
1575
1833
  colors: resources.colors,
1576
1834
  symbols: resources.symbols,
1577
- native: native ? {
1578
- packageId: native.packageId,
1579
- contractHash: native.hash,
1580
- modules: native.modules,
1835
+ native: artifact.package.native ? {
1836
+ packageId: artifact.package.native.packageId,
1837
+ contractHash: artifact.package.native.hash,
1838
+ modules: artifact.package.native.modules,
1581
1839
  defaultView: mains.length === 1
1582
1840
  } : void 0
1583
1841
  };
@@ -1598,7 +1856,9 @@ function serveSwiftPackageAsset(pathname, res) {
1598
1856
  }
1599
1857
  if (pathname.startsWith(ICU_PATH)) {
1600
1858
  for (const project of projects.values()) {
1601
- const icu = project.artifact?.icu;
1859
+ const icu = project.artifacts.find(
1860
+ (artifact) => "sha256" in artifact.icu && pathname === `${ICU_PATH}${artifact.icu.sha256}`
1861
+ )?.icu;
1602
1862
  if (!icu || "url" in icu || pathname !== `${ICU_PATH}${icu.sha256}`) continue;
1603
1863
  res.setHeader("access-control-allow-origin", "*");
1604
1864
  res.setHeader("content-type", "application/gzip");
@@ -1612,7 +1872,9 @@ function serveSwiftPackageAsset(pathname, res) {
1612
1872
  }
1613
1873
  if (!pathname.startsWith(ARTIFACT_PATH)) return false;
1614
1874
  for (const project of projects.values()) {
1615
- const artifact = project.artifact;
1875
+ const artifact = project.artifacts.find(
1876
+ (artifact2) => pathname === `${ARTIFACT_PATH}${artifact2.hash}.wasm`
1877
+ );
1616
1878
  if (!artifact || pathname !== `${ARTIFACT_PATH}${artifact.hash}.wasm`) continue;
1617
1879
  res.setHeader("access-control-allow-origin", "*");
1618
1880
  res.setHeader("content-type", "application/wasm");
@@ -1623,13 +1885,27 @@ function serveSwiftPackageAsset(pathname, res) {
1623
1885
  res.end("swift artifact is stale");
1624
1886
  return true;
1625
1887
  }
1888
+
1889
+ // src/vite-plugin-swift.ts
1890
+ var ARTIFACT_PATH2 = "/__peach/swift/";
1891
+ var VIRTUAL_PREFIX = "\0peach-swift:";
1892
+ var devServer;
1893
+ function serverOrigin() {
1894
+ const address = devServer?.httpServer?.address();
1895
+ const port = address && typeof address === "object" ? address.port : devServer?.config.server.port;
1896
+ if (!port) throw new Error("the swift urls need the dev server port");
1897
+ return `http://localhost:${port}`;
1898
+ }
1899
+ function artifactUrl(project, artifact) {
1900
+ return `${serverOrigin()}${ARTIFACT_PATH2}${artifact.hash}.wasm`;
1901
+ }
1626
1902
  function packageFor(file) {
1627
- let dir = import_node_path5.default.dirname(file);
1903
+ let dir = import_node_path6.default.dirname(file);
1628
1904
  const stop = devServer?.config.root;
1629
1905
  for (; ; ) {
1630
- if (import_node_fs4.default.existsSync(import_node_path5.default.join(dir, "Package.swift"))) return dir;
1631
- if (stop !== void 0 && dir === stop || import_node_path5.default.dirname(dir) === dir) break;
1632
- dir = import_node_path5.default.dirname(dir);
1906
+ if (import_node_fs5.default.existsSync(import_node_path6.default.join(dir, "Package.swift"))) return dir;
1907
+ if (stop !== void 0 && dir === stop || import_node_path6.default.dirname(dir) === dir) break;
1908
+ dir = import_node_path6.default.dirname(dir);
1633
1909
  }
1634
1910
  throw new Error(
1635
1911
  `no Package.swift above ${file}${stop === void 0 ? "" : ` (searched up to ${stop})`}; a .swift import needs a swiftpm package like the one in packages/sootsim-swift/example`
@@ -1650,9 +1926,9 @@ function swiftPlugin(options = {}) {
1650
1926
  if (!resolved) throw new Error(`cannot resolve swift source ${source}`);
1651
1927
  let id = resolved.id;
1652
1928
  if (importer && (source.startsWith("./") || source.startsWith("../"))) {
1653
- const raw2 = import_node_path5.default.resolve(import_node_path5.default.dirname(importer.split("?")[0] ?? importer), source);
1929
+ const raw2 = import_node_path6.default.resolve(import_node_path6.default.dirname(importer.split("?")[0] ?? importer), source);
1654
1930
  try {
1655
- if (import_node_fs4.default.realpathSync(raw2) === import_node_fs4.default.realpathSync(resolved.id)) id = raw2;
1931
+ if (import_node_fs5.default.realpathSync(raw2) === import_node_fs5.default.realpathSync(resolved.id)) id = raw2;
1656
1932
  } catch {
1657
1933
  }
1658
1934
  }
@@ -1661,16 +1937,16 @@ function swiftPlugin(options = {}) {
1661
1937
  async load(id) {
1662
1938
  if (!id.startsWith(VIRTUAL_PREFIX)) return null;
1663
1939
  const file = id.slice(VIRTUAL_PREFIX.length);
1664
- if (!import_node_fs4.default.existsSync(file)) throw new Error(`swift source ${file} no longer exists`);
1940
+ if (!import_node_fs5.default.existsSync(file)) throw new Error(`swift source ${file} no longer exists`);
1665
1941
  const packagePath = options.packagePath ?? packageFor(file);
1666
1942
  const project = projectFor(packagePath);
1667
- const entry = import_node_path5.default.relative(packagePath, file).split(import_node_path5.default.sep).join(import_node_path5.default.posix.sep);
1943
+ const entry = import_node_path6.default.relative(packagePath, file).split(import_node_path6.default.sep).join(import_node_path6.default.posix.sep);
1668
1944
  for (const source of [
1669
1945
  ...sourceFiles(packagePath),
1670
1946
  ...nativeSourceFiles(packagePath)
1671
1947
  ])
1672
1948
  this.addWatchFile(source);
1673
- this.addWatchFile(import_node_path5.default.join(packagePath, "Package.swift"));
1949
+ this.addWatchFile(import_node_path6.default.join(packagePath, "Package.swift"));
1674
1950
  for (const root of linkedModuleRoots(packagePath)) {
1675
1951
  for (const source of sourceFiles(root)) this.addWatchFile(source);
1676
1952
  }
@@ -1682,39 +1958,20 @@ function swiftPlugin(options = {}) {
1682
1958
  this.warn(error instanceof Error ? error.message : String(error));
1683
1959
  artifact = project.artifact;
1684
1960
  }
1685
- let resources = {};
1686
- let colors = {};
1687
- let symbols = {};
1688
- try {
1689
- const built = buildSwiftResourceTable(project, serverOrigin());
1690
- resources = built.table;
1691
- colors = built.colors;
1692
- symbols = built.symbols;
1693
- for (const watched of built.watched) {
1694
- this.addWatchFile(import_node_path5.default.join(packagePath, ...watched.split(import_node_path5.default.posix.sep)));
1695
- }
1696
- } catch (error) {
1697
- this.warn(
1698
- `swift resources for ${packagePath}: ${error instanceof Error ? error.message : String(error)}`
1699
- );
1700
- }
1701
- let bundleFiles = {};
1702
- try {
1703
- const packed = buildSwiftBundleFiles(packagePath);
1704
- bundleFiles = packed.files;
1705
- for (const watched of packed.watched) {
1706
- this.addWatchFile(import_node_path5.default.join(packagePath, ...watched.split(import_node_path5.default.posix.sep)));
1707
- }
1708
- if (packed.skipped.length > 0) {
1709
- this.warn(
1710
- `swift bundle files skipped for ${packagePath}: ${packed.skipped.join("; ")}`
1711
- );
1712
- }
1713
- } catch (error) {
1714
- this.warn(
1715
- `swift bundle files for ${packagePath}: ${error instanceof Error ? error.message : String(error)}`
1716
- );
1717
- }
1961
+ const resources = Object.fromEntries(
1962
+ Object.entries(artifact.package.resources.table).map(([name, image]) => [
1963
+ name,
1964
+ { ...image, uri: `${serverOrigin()}${image.uri}` }
1965
+ ])
1966
+ );
1967
+ for (const watched of [
1968
+ ...artifact.package.resources.watched,
1969
+ ...artifact.package.bundleFiles.watched
1970
+ ])
1971
+ this.addWatchFile(import_node_path6.default.join(packagePath, ...watched.split(import_node_path6.default.posix.sep)));
1972
+ const colors = artifact.package.resources.colors;
1973
+ const symbols = artifact.package.resources.symbols;
1974
+ const bundleFiles = artifact.package.bundleFiles.files;
1718
1975
  const native = nativeSourceDescriptor(packagePath);
1719
1976
  return swiftEntryModuleEsm({
1720
1977
  url: artifactUrl(project, artifact),