rnxsim 0.1.522 → 0.1.524

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 (56) hide show
  1. package/cli/commands/control.ts +31 -15
  2. package/dist-lib/agent-daemon-client.cjs +1 -1
  3. package/dist-lib/agent-events.cjs +1 -1
  4. package/dist-lib/agent-identity.cjs +1 -1
  5. package/dist-lib/agent-sessions.cjs +1 -1
  6. package/dist-lib/attached-projects.cjs +1 -1
  7. package/dist-lib/auth/shared-session.cjs +1 -1
  8. package/dist-lib/backend-origin.cjs +1 -1
  9. package/dist-lib/beta.cjs +1 -1
  10. package/dist-lib/beta.mjs +1 -1
  11. package/dist-lib/bridge-constants.cjs +1 -1
  12. package/dist-lib/bridge-contract-input.cjs +1 -1
  13. package/dist-lib/bridge-contract-input.mjs +1 -1
  14. package/dist-lib/bridge-contract.cjs +1 -1
  15. package/dist-lib/bridge-contract.mjs +1 -1
  16. package/dist-lib/capture-contract.cjs +1 -1
  17. package/dist-lib/capture-contract.mjs +1 -1
  18. package/dist-lib/cli-constants.cjs +1 -1
  19. package/dist-lib/cloud-contract.cjs +1 -1
  20. package/dist-lib/cloud-contract.mjs +1 -1
  21. package/dist-lib/cloud.cjs +1 -1
  22. package/dist-lib/cloud.mjs +1 -1
  23. package/dist-lib/config.cjs +1 -1
  24. package/dist-lib/detox/index.cjs +1 -1
  25. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  26. package/dist-lib/home-paths.cjs +1 -1
  27. package/dist-lib/host/bridge-host.cjs +1 -1
  28. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  29. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  31. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  32. package/dist-lib/host/websocket-proxy.cjs +1 -1
  33. package/dist-lib/index.cjs +1 -1
  34. package/dist-lib/jump-to-source-babel.cjs +1 -1
  35. package/dist-lib/jump-to-source-native.cjs +1 -1
  36. package/dist-lib/menu.cjs +1 -1
  37. package/dist-lib/menu.mjs +1 -1
  38. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  39. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  40. package/dist-lib/metro-production-bundle.cjs +1 -1
  41. package/dist-lib/metro-production-bundle.mjs +1 -1
  42. package/dist-lib/metro.cjs +1 -1
  43. package/dist-lib/profiles.cjs +1 -1
  44. package/dist-lib/public-brand.cjs +1 -1
  45. package/dist-lib/react-native-host-modules.cjs +1 -1
  46. package/dist-lib/react-native-host-modules.mjs +1 -1
  47. package/dist-lib/render-mode.cjs +1 -1
  48. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  49. package/dist-lib/sdk.cjs +1 -1
  50. package/dist-lib/sdk.mjs +1 -1
  51. package/dist-lib/skills.cjs +26 -12
  52. package/dist-lib/swift.cjs +151 -72
  53. package/dist-lib/vite.cjs +189 -24
  54. package/package.json +1 -1
  55. package/src/swift.ts +2 -3
  56. package/src/vite-plugin-swift.ts +185 -41
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.522 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.524 | (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;
@@ -32,8 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  // src/swift.ts
33
33
  var swift_exports = {};
34
34
  __export(swift_exports, {
35
- createSwiftModule: () => createSwiftModule,
36
- createSwiftView: () => createSwiftView
35
+ createSwiftEntry: () => createSwiftEntry
37
36
  });
38
37
  module.exports = __toCommonJS(swift_exports);
39
38
 
@@ -45,6 +44,17 @@ var import_ui = require("contrast-kit/ui");
45
44
  var import_react = require("react");
46
45
  var import_react_native = require("react-native");
47
46
  var import_react_native_bottom_tabs = __toESM(require("react-native-bottom-tabs"), 1);
47
+
48
+ // ../sootsim-swift/src/bundleAssets.ts
49
+ function resolveImageSource(source, table, onMiss) {
50
+ if (typeof source !== "string") return source;
51
+ const url = table[source];
52
+ if (typeof url === "string") return { uri: url };
53
+ onMiss(source);
54
+ return source;
55
+ }
56
+
57
+ // ../sootsim-swift/src/mount.tsx
48
58
  var import_jsx_runtime = require("react/jsx-runtime");
49
59
  function isRecord(value) {
50
60
  return typeof value === "object" && value !== null;
@@ -190,20 +200,6 @@ function pointerExport(exports2, name) {
190
200
  return result;
191
201
  };
192
202
  }
193
- function maybePointerExport(exports2, name) {
194
- const value = exports2[name];
195
- if (value === void 0) return void 0;
196
- if (typeof value !== "function") {
197
- throw new Error(`swift export ${name} is not a function`);
198
- }
199
- return (...args) => {
200
- const result = Reflect.apply(value, void 0, args);
201
- if (typeof result !== "number") {
202
- throw new Error(`swift export ${name} returned ${typeof result}`);
203
- }
204
- return result;
205
- };
206
- }
207
203
  function voidExport(exports2, name) {
208
204
  const fn = rawExport(exports2, name);
209
205
  return (...args) => {
@@ -236,13 +232,35 @@ function instantiate(module2) {
236
232
  const initialize = voidExport(instance.exports, "_initialize");
237
233
  const mainArgv = voidExport(instance.exports, "__main_argc_argv");
238
234
  const mount = pointerExport(instance.exports, "rnx_mount");
239
- const mountViewExport = maybePointerExport(instance.exports, "rnx_mount_view");
240
- const hasViewExport = maybePointerExport(instance.exports, "rnx_has_view");
235
+ const mountViewExport = pointerExport(instance.exports, "rnx_mount_view");
236
+ const hasViewExport = pointerExport(instance.exports, "rnx_has_view");
241
237
  const event = pointerExport(instance.exports, "rnx_event");
242
238
  const stateExport = pointerExport(instance.exports, "rnx_state_export");
243
239
  const stateImport = voidExport(instance.exports, "rnx_state_import");
244
240
  const alloc = pointerExport(instance.exports, "rnx_alloc");
245
241
  const free = voidExport(instance.exports, "rnx_free");
242
+ const openURLDrain = typeof instance.exports.rnx_open_url_drain === "function" ? pointerExport(instance.exports, "rnx_open_url_drain") : null;
243
+ const drainOpenURLs = () => {
244
+ if (!openURLDrain) return;
245
+ let urls;
246
+ try {
247
+ urls = JSON.parse(readBuffer(openURLDrain()));
248
+ } catch {
249
+ return;
250
+ }
251
+ if (!Array.isArray(urls)) return;
252
+ for (const url of urls) {
253
+ if (typeof url !== "string") continue;
254
+ import_react_native.Linking.canOpenURL(url).then(
255
+ (open) => {
256
+ if (open) import_react_native.Linking.openURL(url).catch(() => {
257
+ });
258
+ },
259
+ () => {
260
+ }
261
+ );
262
+ }
263
+ };
246
264
  const readBuffer = (ptr) => {
247
265
  const view = new DataView(requireMemory().buffer);
248
266
  const length = view.getUint32(ptr, true);
@@ -262,41 +280,44 @@ function instantiate(module2) {
262
280
  mainArgv(0, 0);
263
281
  return {
264
282
  mount: () => {
265
- if ((hasViewExport?.() ?? 0) === 1) {
283
+ if (hasViewExport() === 1) {
266
284
  throw new Error(
267
285
  "swift package has an RNXPackage entry, not an App entry; mount it as a view or make its @main type conform to App"
268
286
  );
269
287
  }
270
- return readTree(readBuffer(mount()));
288
+ const tree = readTree(readBuffer(mount()));
289
+ drainOpenURLs();
290
+ return tree;
271
291
  },
272
292
  mountView: (props) => {
273
- if (!mountViewExport) {
274
- throw new Error(
275
- "swift artifact has no rnx_mount_view export; rebuild the swift package"
276
- );
277
- }
278
- if ((hasViewExport?.() ?? 0) !== 1) {
293
+ if (hasViewExport() !== 1) {
279
294
  throw new Error(
280
295
  "swift package has no RNXPackage entry; its @main type must conform to RNXPackage to mount as a view"
281
296
  );
282
297
  }
283
- return readTree(
298
+ const tree = readTree(
284
299
  readBuffer(withPayload(props, (ptr, length) => mountViewExport(ptr, length)))
285
300
  );
301
+ drainOpenURLs();
302
+ return tree;
286
303
  },
287
- hasView: () => (hasViewExport?.() ?? 0) === 1,
288
- deliver: (handlerId2, payload) => readTree(
289
- readBuffer(
290
- withPayload(
291
- payload,
292
- (ptr, length) => (
293
- // handler ids are opaque to the tenant: they come from the tree
294
- // this instance just returned and they are only meaningful to it
295
- event(handlerId2, ptr, length)
304
+ hasView: () => hasViewExport() === 1,
305
+ deliver: (handlerId2, payload) => {
306
+ const tree = readTree(
307
+ readBuffer(
308
+ withPayload(
309
+ payload,
310
+ (ptr, length) => (
311
+ // handler ids are opaque to the tenant: they come from the tree
312
+ // this instance just returned and they are only meaningful to it
313
+ event(handlerId2, ptr, length)
314
+ )
296
315
  )
297
316
  )
298
- )
299
- ),
317
+ );
318
+ drainOpenURLs();
319
+ return tree;
320
+ },
300
321
  exportState: () => readBuffer(stateExport()),
301
322
  importState: (state) => {
302
323
  withPayload(JSON.parse(state), (ptr, length) => {
@@ -378,6 +399,18 @@ function resolveModifier(modifier) {
378
399
  }
379
400
  return { ...modifier, color: resolveColor(color) };
380
401
  }
402
+ var SwiftResourcesContext = (0, import_react.createContext)(void 0);
403
+ function SwiftImage(props) {
404
+ const table = (0, import_react.useContext)(SwiftResourcesContext);
405
+ const { source, ...rest } = props ?? {};
406
+ const resolved = resolveImageSource(source, table ?? {}, (name) => {
407
+ console.error(`swift: no bundle asset named '${name}' in this mount's resources`);
408
+ });
409
+ return (0, import_react.createElement)(import_ui.Image, {
410
+ ...rest,
411
+ ...source === void 0 ? {} : { source: resolved }
412
+ });
413
+ }
381
414
  var NODE_COMPONENTS = {
382
415
  Alert: SwiftAlert,
383
416
  BottomSheet: import_ui.BottomSheet,
@@ -390,14 +423,17 @@ var NODE_COMPONENTS = {
390
423
  Divider: import_ui.Divider,
391
424
  Ellipse: import_ui.Ellipse,
392
425
  Form: import_ui.Form,
426
+ GeometryReader: import_ui.GeometryReader,
393
427
  GlassEffectContainer: import_ui.GlassEffectContainer,
394
428
  Group: import_ui.Group,
395
429
  HStack: import_ui.HStack,
396
- Image: import_ui.Image,
430
+ Image: SwiftImage,
397
431
  Label: import_ui.Label,
398
432
  LazyVStack: import_ui.LazyVStack,
433
+ LinearGradient: import_ui.LinearGradient,
399
434
  List: import_ui.List,
400
435
  Menu: import_ui.Menu,
436
+ Path: import_ui.Path,
401
437
  Picker: import_ui.Picker,
402
438
  ProgressView: import_ui.ProgressView,
403
439
  Rectangle: import_ui.Rectangle,
@@ -613,6 +649,12 @@ function nodeProps(node, deliver) {
613
649
  const size = typeof font?.size === "number" ? font.size : void 0;
614
650
  return {
615
651
  ...typeof props.systemName === "string" ? { systemName: props.systemName } : {},
652
+ // a bundle asset name rides as the kit image source; the kit paints
653
+ // resolved { uri } sources and keeps its placeholder for names the
654
+ // table could not resolve.
655
+ ...typeof props.name === "string" ? { source: props.name } : {},
656
+ ...props.resizable === true ? { resizable: true } : {},
657
+ ...typeof props.renderingMode === "string" ? { renderingMode: props.renderingMode } : {},
616
658
  ...size === void 0 ? {} : { size },
617
659
  ...pressProps(node, deliver)
618
660
  };
@@ -1095,10 +1137,26 @@ function SplitViewHost({
1095
1137
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: { flex: 1 }, children: pushed.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavigationStackHost, { node: { ...node, c: pushed }, deliver }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: detail.map((child) => renderNode(child, deliver, cache)) }) })
1096
1138
  ] });
1097
1139
  }
1140
+ function SwiftAppear({
1141
+ node,
1142
+ deliver,
1143
+ children
1144
+ }) {
1145
+ const onAppear = handlerId(node, "onAppear");
1146
+ const onDisappear = handlerId(node, "onDisappear");
1147
+ (0, import_react.useEffect)(() => {
1148
+ if (onAppear !== void 0) deliver(onAppear, null);
1149
+ return () => {
1150
+ if (onDisappear !== void 0) deliver(onDisappear, null);
1151
+ };
1152
+ }, []);
1153
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
1154
+ }
1098
1155
  function renderNode(node, deliver, cache) {
1099
1156
  const cached = cache?.entries.get(node.id);
1100
1157
  if (cached && sameNode(cached.node, node)) return cached.element;
1101
- const element = buildNode(node, deliver, cache);
1158
+ const built = buildNode(node, deliver, cache);
1159
+ const element = node.h?.onAppear !== void 0 || node.h?.onDisappear !== void 0 ? (0, import_react.createElement)(SwiftAppear, { key: node.id, node, deliver }, built) : built;
1102
1160
  cache?.entries.set(node.id, { node, element });
1103
1161
  return element;
1104
1162
  }
@@ -1140,20 +1198,29 @@ function buildNode(node, deliver, cache) {
1140
1198
  function SwiftTree({
1141
1199
  tree,
1142
1200
  deliver,
1143
- cache
1201
+ cache,
1202
+ resources
1144
1203
  }) {
1145
1204
  return (
1146
1205
  // a navigation stack at the root lays its own bar out under the status
1147
1206
  // bar and insets each screen itself, so the host must not also pad it
1148
1207
  // down: that pad would move the whole stack a status bar lower
1208
+ // a swiftui scene is opaque: the window behind every screen is
1209
+ // systemBackground, and lists paint their grouped fill over it (the kit's
1210
+ // grouped container already does). without this a listless screen shows
1211
+ // whatever sits beneath the mount. RAN on the safeArea probe (iphone 17
1212
+ // pro): (600,1300) reads #ffffff ios against #f2f2f7 rnx, 99.92% of the
1213
+ // frame; dark matches on both legs (the oracle's falsifier: the two
1214
+ // fills coincide there). PlatformColor is this lane's spelling for a
1215
+ // semantic color.
1149
1216
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1150
1217
  import_ui.Host,
1151
1218
  {
1152
- style: { flex: 1 },
1219
+ style: { flex: 1, backgroundColor: (0, import_react_native.PlatformColor)("systemBackground") },
1153
1220
  ignoreSafeArea: tree.some(
1154
1221
  (node) => node.t === "NavigationStack" || node.t === "NavigationSplitView"
1155
1222
  ),
1156
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_native.NavigationIndependentTree, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_native.NavigationContainer, { children: tree.map((node) => renderNode(node, deliver, cache)) }) })
1223
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_native.NavigationIndependentTree, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_native.NavigationContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SwiftResourcesContext.Provider, { value: resources, children: tree.map((node) => renderNode(node, deliver, cache)) }) }) })
1157
1224
  }
1158
1225
  )
1159
1226
  );
@@ -1166,19 +1233,19 @@ function rootRegistry() {
1166
1233
  Reflect.set(globalThis, ROOT_KEY, created);
1167
1234
  return created;
1168
1235
  }
1169
- function slotFor(rootId, mountTree, props) {
1236
+ function slotFor(rootId, mountTree) {
1170
1237
  const registry = rootRegistry();
1171
1238
  const existing = registry.get(rootId);
1172
1239
  if (existing) return existing;
1173
- const created = { root: createRoot(mountTree, props), props };
1240
+ const created = { root: createRoot(mountTree) };
1174
1241
  registry.set(rootId, created);
1175
1242
  return created;
1176
1243
  }
1177
- function createRoot(mountTree, props) {
1244
+ function createRoot(mountTree) {
1178
1245
  let instance = null;
1179
1246
  let source = null;
1180
1247
  let refresh;
1181
- let state = { tree: null, error: null };
1248
+ let state = { tree: null, isView: false, error: null };
1182
1249
  let swap = Promise.resolve();
1183
1250
  const listeners = /* @__PURE__ */ new Set();
1184
1251
  const cache = { entries: /* @__PURE__ */ new Map() };
@@ -1186,13 +1253,14 @@ function createRoot(mountTree, props) {
1186
1253
  state = next;
1187
1254
  for (const listener of listeners) listener();
1188
1255
  };
1189
- const apply = async (options) => {
1256
+ const apply = async (options, props) => {
1190
1257
  const carried = instance?.exportState() ?? null;
1191
1258
  const created = instantiate(await compile(options.url));
1192
1259
  if (carried !== null) created.importState(carried);
1193
1260
  if (source !== options) return;
1194
1261
  instance = created;
1195
- publish({ tree: mountTree(created, props.current), error: null });
1262
+ const mounted = mountTree(created, props);
1263
+ publish({ tree: mounted.tree, isView: mounted.isView, error: null });
1196
1264
  };
1197
1265
  const component = function SwiftRoot() {
1198
1266
  const rendered = (0, import_react.useSyncExternalStore)(
@@ -1206,24 +1274,41 @@ function createRoot(mountTree, props) {
1206
1274
  if (!rendered.tree) return null;
1207
1275
  const deliver = (handlerId2, payload) => {
1208
1276
  if (!instance) throw new Error("swift app received an event before it mounted");
1209
- publish({ tree: instance.deliver(handlerId2, payload), error: null });
1277
+ publish({
1278
+ tree: instance.deliver(handlerId2, payload),
1279
+ isView: rendered.isView,
1280
+ error: null
1281
+ });
1210
1282
  };
1211
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SwiftTree, { tree: rendered.tree, deliver, cache });
1283
+ const resources = source?.resources;
1284
+ if (rendered.isView)
1285
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SwiftResourcesContext.Provider, { value: resources, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: rendered.tree.map((node) => renderNode(node, deliver, cache)) }) });
1286
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1287
+ SwiftTree,
1288
+ {
1289
+ tree: rendered.tree,
1290
+ deliver,
1291
+ cache,
1292
+ resources
1293
+ }
1294
+ );
1212
1295
  };
1213
1296
  return {
1214
1297
  component,
1215
- use(options, refreshKey) {
1216
- const changed = source === null || source.hash !== options.hash || source.url !== options.url || refreshKey !== void 0 && refreshKey !== refresh;
1298
+ use(options, refreshKey, props) {
1299
+ const changed = source === null || source.hash !== options.hash || source.url !== options.url || JSON.stringify(source.resources ?? null) !== JSON.stringify(options.resources ?? null) || refreshKey !== void 0 && refreshKey !== refresh;
1217
1300
  if (changed) {
1218
1301
  source = options;
1219
1302
  refresh = refreshKey;
1303
+ const next = props ?? {};
1220
1304
  swap = swap.then(
1221
- () => apply(options),
1222
- () => apply(options)
1305
+ () => apply(options, next),
1306
+ () => apply(options, next)
1223
1307
  );
1224
1308
  swap.catch((error) => {
1225
1309
  publish({
1226
1310
  tree: null,
1311
+ isView: false,
1227
1312
  error: error instanceof Error ? error : new Error(String(error))
1228
1313
  });
1229
1314
  });
@@ -1232,11 +1317,6 @@ function createRoot(mountTree, props) {
1232
1317
  }
1233
1318
  };
1234
1319
  }
1235
- function createSwiftModule(options) {
1236
- const rootId = options.rootId ?? "root";
1237
- const slot = slotFor(rootId, (instance) => instance.mount(), { current: {} });
1238
- return slot.root.use(options);
1239
- }
1240
1320
  function propsSignature(props) {
1241
1321
  try {
1242
1322
  return JSON.stringify(props, (_key, value) => {
@@ -1253,22 +1333,21 @@ function propsSignature(props) {
1253
1333
  );
1254
1334
  }
1255
1335
  }
1256
- function createSwiftView(options) {
1257
- const rootId = options.rootId ?? "view";
1258
- const slot = slotFor(rootId, (instance, props) => instance.mountView(props), {
1259
- current: {}
1260
- });
1261
- return function SwiftView(props) {
1336
+ function createSwiftEntry(options) {
1337
+ const rootId = options.rootId ?? "root";
1338
+ const slot = slotFor(
1339
+ rootId,
1340
+ (instance, props) => instance.hasView() ? { tree: instance.mountView(props), isView: true } : { tree: instance.mount(), isView: false }
1341
+ );
1342
+ return function SwiftEntry(props) {
1262
1343
  if (props.children !== void 0) {
1263
- throw new Error("swift views take data props, not children");
1344
+ throw new Error("swift entries take data props, not children");
1264
1345
  }
1265
- slot.props.current = props;
1266
- const component = slot.root.use(options, propsSignature(props));
1346
+ const component = slot.root.use(options, propsSignature(props), props);
1267
1347
  return (0, import_react.createElement)(component);
1268
1348
  };
1269
1349
  }
1270
1350
  // Annotate the CommonJS export names for ESM import in node:
1271
1351
  0 && (module.exports = {
1272
- createSwiftModule,
1273
- createSwiftView
1352
+ createSwiftEntry
1274
1353
  });
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.522 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.524 | (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;
@@ -763,6 +763,75 @@ var import_node_os2 = __toESM(require("node:os"), 1);
763
763
  var import_node_path4 = __toESM(require("node:path"), 1);
764
764
  var import_node_util = require("node:util");
765
765
 
766
+ // ../sootsim-swift/src/bundleAssets.ts
767
+ var RASTER_EXTENSIONS = /* @__PURE__ */ new Set([
768
+ ".avif",
769
+ ".bmp",
770
+ ".gif",
771
+ ".ico",
772
+ ".jpeg",
773
+ ".jpg",
774
+ ".png",
775
+ ".webp"
776
+ ]);
777
+ function extOf(file) {
778
+ const dot = file.lastIndexOf(".");
779
+ return dot < 0 ? "" : file.slice(dot).toLowerCase();
780
+ }
781
+ function baseOf(file) {
782
+ const slash = file.lastIndexOf("/");
783
+ return slash < 0 ? file : file.slice(slash + 1);
784
+ }
785
+ function pickImagesetFile(images) {
786
+ const rasters = images.filter(
787
+ (image) => typeof image.filename === "string" && RASTER_EXTENSIONS.has(extOf(image.filename))
788
+ );
789
+ if (rasters.length === 0) return null;
790
+ const rank = (scale) => scale === "2x" ? 0 : scale === "3x" ? 1 : scale === "1x" ? 2 : 3;
791
+ return [...rasters].sort((a, b) => rank(a.scale) - rank(b.scale))[0].filename;
792
+ }
793
+ function imagesetName(contentsPath) {
794
+ const dir = contentsPath.split("/").slice(0, -1).join("/");
795
+ const leaf = baseOf(dir);
796
+ if (!leaf.endsWith(".imageset")) return null;
797
+ return leaf.slice(0, -".imageset".length) || null;
798
+ }
799
+ function isFlatResourceImage(path7) {
800
+ return path7.split("/").includes("Resources") && RASTER_EXTENSIONS.has(extOf(path7));
801
+ }
802
+ function enumerateBundleImages(paths, readText) {
803
+ const table = [];
804
+ const seen = /* @__PURE__ */ new Set();
805
+ const catalogs = paths.filter((path7) => baseOf(path7) === "Contents.json");
806
+ for (const contentsPath of catalogs) {
807
+ const name = imagesetName(contentsPath);
808
+ if (name === null || seen.has(name)) continue;
809
+ let parsed;
810
+ try {
811
+ const text = readText(contentsPath);
812
+ if (text === null) continue;
813
+ parsed = JSON.parse(text);
814
+ } catch {
815
+ continue;
816
+ }
817
+ const images = parsed?.images;
818
+ if (!Array.isArray(images)) continue;
819
+ const dir = contentsPath.split("/").slice(0, -1).join("/");
820
+ const file = pickImagesetFile(images);
821
+ if (file === null || file.includes("/") || file.includes("\\")) continue;
822
+ seen.add(name);
823
+ table.push({ name, file: `${dir}/${file}` });
824
+ }
825
+ for (const path7 of paths) {
826
+ if (!isFlatResourceImage(path7)) continue;
827
+ const name = baseOf(path7).slice(0, -extOf(path7).length);
828
+ if (name === "" || seen.has(name)) continue;
829
+ seen.add(name);
830
+ table.push({ name, file: path7 });
831
+ }
832
+ return table;
833
+ }
834
+
766
835
  // ../sootsim-swift/toolchain.json
767
836
  var toolchain_default = {
768
837
  release: "swift-6.3-RELEASE",
@@ -778,13 +847,19 @@ var TOOLCHAIN_BIN = import_node_path4.default.join(
778
847
  var WASM_SDK = `${toolchain_default.release}_wasm`;
779
848
  var WASM_TRIPLE = "wasm32-unknown-wasip1";
780
849
  var ARTIFACT_PATH = "/__rnx/swift/";
850
+ var RESOURCE_PATH = "/__rnx/swift-res/";
781
851
  var VIRTUAL_PREFIX = "\0rnx-swift:";
782
852
  var projects = /* @__PURE__ */ new Map();
783
853
  var devServer;
784
854
  function projectFor(packagePath) {
785
855
  const existing = projects.get(packagePath);
786
856
  if (existing) return existing;
787
- const created = { packagePath, artifact: null, building: null };
857
+ const created = {
858
+ packagePath,
859
+ artifact: null,
860
+ building: null,
861
+ resourceFiles: /* @__PURE__ */ new Map()
862
+ };
788
863
  projects.set(packagePath, created);
789
864
  return created;
790
865
  }
@@ -803,6 +878,84 @@ function sourceFiles(root) {
803
878
  walk(root);
804
879
  return found;
805
880
  }
881
+ function resourceCandidateFiles(root) {
882
+ const found = [];
883
+ const walk = (dir) => {
884
+ for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
885
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === ".build")
886
+ continue;
887
+ const full = import_node_path4.default.join(dir, entry.name);
888
+ if (entry.isDirectory()) {
889
+ if (full !== root && import_node_fs3.default.existsSync(import_node_path4.default.join(full, "Package.swift"))) continue;
890
+ walk(full);
891
+ } else {
892
+ found.push(full);
893
+ }
894
+ }
895
+ };
896
+ walk(root);
897
+ return found;
898
+ }
899
+ var RESOURCE_CONTENT_TYPES = {
900
+ ".avif": "image/avif",
901
+ ".bmp": "image/bmp",
902
+ ".gif": "image/gif",
903
+ ".ico": "image/x-icon",
904
+ ".jpeg": "image/jpeg",
905
+ ".jpg": "image/jpeg",
906
+ ".png": "image/png",
907
+ ".webp": "image/webp"
908
+ };
909
+ function buildSwiftResourceTable(project, baseUrl) {
910
+ const absolute = resourceCandidateFiles(project.packagePath);
911
+ const relative2 = absolute.map(
912
+ (file) => import_node_path4.default.relative(project.packagePath, file).split(import_node_path4.default.sep).join(import_node_path4.default.posix.sep)
913
+ );
914
+ const byRelative = new Map(absolute.map((file, index) => [relative2[index], file]));
915
+ const found = enumerateBundleImages(relative2, (candidate) => {
916
+ const file = byRelative.get(candidate);
917
+ if (!file || import_node_path4.default.basename(file) !== "Contents.json") return null;
918
+ try {
919
+ return import_node_fs3.default.readFileSync(file, "utf8");
920
+ } catch {
921
+ return null;
922
+ }
923
+ });
924
+ const table = {};
925
+ const watched = [];
926
+ const registered = /* @__PURE__ */ new Map();
927
+ for (const { name, file } of found) {
928
+ const disk = byRelative.get(file);
929
+ if (!disk) continue;
930
+ let bytes;
931
+ try {
932
+ bytes = import_node_fs3.default.readFileSync(disk);
933
+ } catch {
934
+ continue;
935
+ }
936
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex").slice(0, 16);
937
+ const ext = import_node_path4.default.posix.extname(file).toLowerCase();
938
+ table[name] = `${baseUrl}${RESOURCE_PATH}${hash}${ext}`;
939
+ watched.push(file);
940
+ const contents = file.replace(/\/[^/]+$/, "/Contents.json");
941
+ if (byRelative.has(contents)) watched.push(contents);
942
+ registered.set(`${hash}${ext}`, disk);
943
+ }
944
+ project.resourceFiles = registered;
945
+ return { table, watched };
946
+ }
947
+ function serveSwiftResource(project, key) {
948
+ const disk = project.resourceFiles.get(key);
949
+ if (!disk) return null;
950
+ try {
951
+ return {
952
+ bytes: import_node_fs3.default.readFileSync(disk),
953
+ contentType: RESOURCE_CONTENT_TYPES[import_node_path4.default.extname(disk).toLowerCase()] ?? "application/octet-stream"
954
+ };
955
+ } catch {
956
+ return null;
957
+ }
958
+ }
806
959
  function linkedModuleRoots(packagePath) {
807
960
  let manifest;
808
961
  try {
@@ -829,14 +982,18 @@ function sourceStamp(packagePath) {
829
982
  }
830
983
  return hash.digest("hex").slice(0, 16);
831
984
  }
832
- function viewRootId(packagePath, file) {
833
- return import_node_path4.default.join(packagePath, import_node_path4.default.relative(packagePath, file));
985
+ function entryRootId(packagePath, file) {
986
+ const relative2 = import_node_path4.default.relative(packagePath, file).split(import_node_path4.default.sep).join(import_node_path4.default.posix.sep);
987
+ return `${import_node_path4.default.basename(packagePath)}${import_node_path4.default.posix.sep}${relative2}`;
834
988
  }
835
- function artifactUrl(project, artifact) {
989
+ function serverOrigin() {
836
990
  const address = devServer?.httpServer?.address();
837
991
  const port = address && typeof address === "object" ? address.port : devServer?.config.server.port;
838
- if (!port) throw new Error("the swift artifact url needs the dev server port");
839
- return `http://localhost:${port}${ARTIFACT_PATH}${artifact.hash}.wasm`;
992
+ if (!port) throw new Error("the swift urls need the dev server port");
993
+ return `http://localhost:${port}`;
994
+ }
995
+ function artifactUrl(project, artifact) {
996
+ return `${serverOrigin()}${ARTIFACT_PATH}${artifact.hash}.wasm`;
840
997
  }
841
998
  function builtArtifact(packagePath) {
842
999
  const binDir = import_node_path4.default.join(packagePath, ".build", WASM_TRIPLE, "release");
@@ -927,14 +1084,6 @@ function packageFor(file) {
927
1084
  `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`
928
1085
  );
929
1086
  }
930
- function isViewPackage(packagePath) {
931
- const main = /@main\s+(?:\w+\s+)*(?:struct|class|enum|actor)\s+\w+[^{]*:\s*[^{]*\bRNXPackage\b/;
932
- for (const file of sourceFiles(packagePath)) {
933
- const text = import_node_fs3.default.readFileSync(file, "utf8").replace(/^\s*\/\/.*$/gm, "");
934
- if (main.test(text)) return true;
935
- }
936
- return false;
937
- }
938
1087
  function swiftPlugin(options = {}) {
939
1088
  return {
940
1089
  name: "rnx-swift",
@@ -968,17 +1117,19 @@ function swiftPlugin(options = {}) {
968
1117
  this.warn(error instanceof Error ? error.message : String(error));
969
1118
  artifact = project.artifact;
970
1119
  }
971
- if (isViewPackage(packagePath)) {
972
- const rootId = viewRootId(packagePath, file);
973
- return [
974
- `import { createSwiftView } from 'rnxsim/swift'`,
975
- `export default createSwiftView({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)}, rootId: ${JSON.stringify(rootId)} })`,
976
- ""
977
- ].join("\n");
1120
+ let resources = {};
1121
+ try {
1122
+ const built = buildSwiftResourceTable(project, serverOrigin());
1123
+ resources = built.table;
1124
+ for (const watched of built.watched) this.addWatchFile(watched);
1125
+ } catch (error) {
1126
+ this.warn(
1127
+ `swift resources for ${packagePath}: ${error instanceof Error ? error.message : String(error)}`
1128
+ );
978
1129
  }
979
1130
  return [
980
- `import { createSwiftModule } from 'rnxsim/swift'`,
981
- `export default createSwiftModule({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)} })`,
1131
+ `import { createSwiftEntry } from 'rnxsim/swift'`,
1132
+ `export default createSwiftEntry({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)}, rootId: ${JSON.stringify(entryRootId(packagePath, file))}, resources: ${JSON.stringify(resources)} })`,
982
1133
  ""
983
1134
  ].join("\n");
984
1135
  },
@@ -986,6 +1137,20 @@ function swiftPlugin(options = {}) {
986
1137
  devServer = server;
987
1138
  server.middlewares.use((req, res, next) => {
988
1139
  const url = (req.url ?? "").split("?")[0];
1140
+ if (url.startsWith(RESOURCE_PATH)) {
1141
+ for (const project of projects.values()) {
1142
+ const served = serveSwiftResource(project, url.slice(RESOURCE_PATH.length));
1143
+ if (served) {
1144
+ res.setHeader("access-control-allow-origin", "*");
1145
+ res.setHeader("content-type", served.contentType);
1146
+ res.end(served.bytes);
1147
+ return;
1148
+ }
1149
+ }
1150
+ res.statusCode = 404;
1151
+ res.end("swift resource is stale");
1152
+ return;
1153
+ }
989
1154
  if (!url.startsWith(ARTIFACT_PATH)) {
990
1155
  next();
991
1156
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnxsim",
3
- "version": "0.1.522",
3
+ "version": "0.1.524",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for rnx.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",