rnxsim 0.1.506 → 0.1.508

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 (61) hide show
  1. package/cli/commands/control.ts +3 -3
  2. package/cli/commands/flow.ts +8 -12
  3. package/cli/commands/platform.ts +2 -1
  4. package/cli/ws-bridge.ts +28 -15
  5. package/detox/index.ts +24 -0
  6. package/dist-lib/agent-daemon-client.cjs +1 -1
  7. package/dist-lib/agent-events.cjs +1 -1
  8. package/dist-lib/agent-identity.cjs +1 -1
  9. package/dist-lib/agent-sessions.cjs +1 -1
  10. package/dist-lib/attached-projects.cjs +1 -1
  11. package/dist-lib/auth/shared-session.cjs +1 -1
  12. package/dist-lib/backend-origin.cjs +1 -1
  13. package/dist-lib/beta.cjs +1 -1
  14. package/dist-lib/beta.mjs +1 -1
  15. package/dist-lib/bridge-constants.cjs +1 -1
  16. package/dist-lib/bridge-contract-input.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.mjs +1 -1
  18. package/dist-lib/bridge-contract.cjs +1 -1
  19. package/dist-lib/bridge-contract.mjs +1 -1
  20. package/dist-lib/capture-contract.cjs +1 -1
  21. package/dist-lib/capture-contract.mjs +1 -1
  22. package/dist-lib/cli-constants.cjs +1 -1
  23. package/dist-lib/cloud-contract.cjs +1 -1
  24. package/dist-lib/cloud-contract.mjs +1 -1
  25. package/dist-lib/cloud.cjs +1 -1
  26. package/dist-lib/cloud.mjs +1 -1
  27. package/dist-lib/config.cjs +1 -1
  28. package/dist-lib/detox/index.cjs +24 -1
  29. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  30. package/dist-lib/home-paths.cjs +1 -1
  31. package/dist-lib/host/bridge-host.cjs +1 -1
  32. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  33. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  34. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  35. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  36. package/dist-lib/host/websocket-proxy.cjs +1 -1
  37. package/dist-lib/index.cjs +36 -20
  38. package/dist-lib/jump-to-source-babel.cjs +1 -1
  39. package/dist-lib/jump-to-source-native.cjs +1 -1
  40. package/dist-lib/menu.cjs +1 -1
  41. package/dist-lib/menu.mjs +1 -1
  42. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  43. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  44. package/dist-lib/metro-production-bundle.cjs +1 -1
  45. package/dist-lib/metro-production-bundle.mjs +1 -1
  46. package/dist-lib/metro.cjs +1 -1
  47. package/dist-lib/profiles.cjs +1 -1
  48. package/dist-lib/public-brand.cjs +1 -1
  49. package/dist-lib/react-native-host-modules.cjs +1 -1
  50. package/dist-lib/react-native-host-modules.mjs +1 -1
  51. package/dist-lib/render-mode.cjs +1 -1
  52. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  53. package/dist-lib/sdk.cjs +1 -1
  54. package/dist-lib/sdk.mjs +1 -1
  55. package/dist-lib/skills.cjs +26 -21
  56. package/dist-lib/swift.cjs +507 -0
  57. package/dist-lib/vite.cjs +185 -5
  58. package/package.json +6 -1
  59. package/src/swift.ts +6 -0
  60. package/src/vite-plugin-one.ts +8 -0
  61. package/src/vite-plugin-swift.ts +254 -0
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.506 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.508 | (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;
@@ -34,11 +34,12 @@ var vite_plugin_one_exports = {};
34
34
  __export(vite_plugin_one_exports, {
35
35
  default: () => vite_plugin_one_default,
36
36
  rnxPlugin: () => rnxPlugin,
37
- sootsimPlugin: () => sootsimPlugin
37
+ sootsimPlugin: () => sootsimPlugin,
38
+ swiftPlugin: () => swiftPlugin
38
39
  });
39
40
  module.exports = __toCommonJS(vite_plugin_one_exports);
40
41
  var import_fs2 = __toESM(require("fs"), 1);
41
- var import_node_crypto2 = require("node:crypto");
42
+ var import_node_crypto3 = require("node:crypto");
42
43
  var import_path2 = __toESM(require("path"), 1);
43
44
 
44
45
  // src/bridge-constants.ts
@@ -748,6 +749,184 @@ function serveRuntimeFile(req, res, fullPath) {
748
749
  import_fs.default.createReadStream(fullPath).pipe(res);
749
750
  }
750
751
 
752
+ // src/vite-plugin-swift.ts
753
+ var import_node_child_process2 = require("node:child_process");
754
+ var import_node_crypto2 = require("node:crypto");
755
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
756
+ var import_node_os2 = __toESM(require("node:os"), 1);
757
+ var import_node_path4 = __toESM(require("node:path"), 1);
758
+ var import_node_util = require("node:util");
759
+ var run = (0, import_node_util.promisify)(import_node_child_process2.execFile);
760
+ var TOOLCHAIN_BIN = import_node_path4.default.join(
761
+ import_node_os2.default.homedir(),
762
+ "Library/Developer/Toolchains/swift-6.2-RELEASE.xctoolchain/usr/bin"
763
+ );
764
+ var WASM_SDK = "swift-6.2-RELEASE_wasm";
765
+ var WASM_TRIPLE = "wasm32-unknown-wasip1";
766
+ var ARTIFACT_PATH = "/__rnx/swift/";
767
+ var VIRTUAL_PREFIX = "\0rnx-swift:";
768
+ var projects = /* @__PURE__ */ new Map();
769
+ var devServer;
770
+ function projectFor(packagePath) {
771
+ const existing = projects.get(packagePath);
772
+ if (existing) return existing;
773
+ const created = { packagePath, artifact: null, building: null };
774
+ projects.set(packagePath, created);
775
+ return created;
776
+ }
777
+ function rootFor(file) {
778
+ return devServer?.config.root ?? import_node_path4.default.dirname(file);
779
+ }
780
+ function sourceFiles(packagePath) {
781
+ const found = [];
782
+ const walk = (dir) => {
783
+ for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
784
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
785
+ const full = import_node_path4.default.join(dir, entry.name);
786
+ if (entry.isDirectory()) walk(full);
787
+ else if (entry.name.endsWith(".swift")) found.push(full);
788
+ }
789
+ };
790
+ walk(packagePath);
791
+ return found;
792
+ }
793
+ function sourceStamp(packagePath) {
794
+ const hash = (0, import_node_crypto2.createHash)("sha256");
795
+ for (const file of sourceFiles(packagePath).sort()) {
796
+ hash.update(import_node_path4.default.relative(packagePath, file));
797
+ hash.update(String(import_node_fs3.default.statSync(file).mtimeMs));
798
+ }
799
+ return hash.digest("hex").slice(0, 16);
800
+ }
801
+ function artifactUrl(project, artifact) {
802
+ const address = devServer?.httpServer?.address();
803
+ const port = address && typeof address === "object" ? address.port : devServer?.config.server.port;
804
+ if (!port) throw new Error("the swift artifact url needs the dev server port");
805
+ return `http://localhost:${port}${ARTIFACT_PATH}${artifact.hash}.wasm`;
806
+ }
807
+ function builtArtifact(packagePath) {
808
+ const binDir = import_node_path4.default.join(packagePath, ".build", WASM_TRIPLE, "release");
809
+ const artifacts = import_node_fs3.default.existsSync(binDir) ? import_node_fs3.default.readdirSync(binDir).filter((name) => name.endsWith(".wasm")).sort() : [];
810
+ if (artifacts.length !== 1) {
811
+ throw new Error(
812
+ `swift project ${packagePath} built ${artifacts.length} wasm artifacts; exactly one executable is the app`
813
+ );
814
+ }
815
+ return import_node_path4.default.join(binDir, artifacts[0]);
816
+ }
817
+ async function compile(packagePath, stamp) {
818
+ const swift = import_node_path4.default.join(TOOLCHAIN_BIN, "swift");
819
+ if (!import_node_fs3.default.existsSync(swift)) {
820
+ throw new Error(
821
+ `no swift.org toolchain at ${TOOLCHAIN_BIN}. install it and the ${WASM_SDK} sdk; see packages/sootsim-swift/README.md`
822
+ );
823
+ }
824
+ try {
825
+ await run(
826
+ swift,
827
+ [
828
+ "build",
829
+ "--package-path",
830
+ packagePath,
831
+ "--swift-sdk",
832
+ WASM_SDK,
833
+ "-c",
834
+ "release",
835
+ "-Xswiftc",
836
+ "-Osize"
837
+ ],
838
+ { maxBuffer: 64 * 1024 * 1024 }
839
+ );
840
+ } catch (error) {
841
+ throw new Error(
842
+ `swift build failed for ${packagePath}
843
+ ${error instanceof Error ? error.message : String(error)}`
844
+ );
845
+ }
846
+ const bytes = import_node_fs3.default.readFileSync(builtArtifact(packagePath));
847
+ return {
848
+ hash: (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex").slice(0, 16),
849
+ bytes,
850
+ stamp
851
+ };
852
+ }
853
+ function artifactFor(project) {
854
+ const stamp = sourceStamp(project.packagePath);
855
+ if (project.artifact?.stamp === stamp) return Promise.resolve(project.artifact);
856
+ project.building ??= compile(project.packagePath, stamp).then(
857
+ (artifact) => {
858
+ project.artifact = artifact;
859
+ project.building = null;
860
+ return artifact;
861
+ },
862
+ (error) => {
863
+ project.building = null;
864
+ throw error;
865
+ }
866
+ );
867
+ return project.building;
868
+ }
869
+ function swiftPlugin(options = {}) {
870
+ const packagePathFor = (file) => options.packagePath ?? rootFor(file);
871
+ return {
872
+ name: "rnx-swift",
873
+ enforce: "pre",
874
+ async resolveId(source, importer) {
875
+ if (!source.endsWith(".swift")) return null;
876
+ const resolved = await this.resolve(source, importer, { skipSelf: true });
877
+ if (!resolved) throw new Error(`cannot resolve swift source ${source}`);
878
+ return VIRTUAL_PREFIX + resolved.id;
879
+ },
880
+ async load(id) {
881
+ if (!id.startsWith(VIRTUAL_PREFIX)) return null;
882
+ const packagePath = packagePathFor(id.slice(VIRTUAL_PREFIX.length));
883
+ const project = projectFor(packagePath);
884
+ for (const source of sourceFiles(packagePath)) this.addWatchFile(source);
885
+ let artifact;
886
+ try {
887
+ artifact = await artifactFor(project);
888
+ } catch (error) {
889
+ if (!project.artifact) throw error;
890
+ this.warn(error instanceof Error ? error.message : String(error));
891
+ artifact = project.artifact;
892
+ }
893
+ return [
894
+ `import { createSwiftModule } from 'rnxsim/swift'`,
895
+ `export default createSwiftModule({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)} })`,
896
+ ""
897
+ ].join("\n");
898
+ },
899
+ configureServer(server) {
900
+ devServer = server;
901
+ const packagePath = packagePathFor("");
902
+ server.middlewares.use((req, res, next) => {
903
+ const url = (req.url ?? "").split("?")[0];
904
+ if (!url.startsWith(ARTIFACT_PATH)) {
905
+ next();
906
+ return;
907
+ }
908
+ const project = projectFor(packagePath);
909
+ void artifactFor(project).then(
910
+ (artifact) => {
911
+ if (url !== `${ARTIFACT_PATH}${artifact.hash}.wasm`) {
912
+ res.statusCode = 404;
913
+ res.end("swift artifact is stale");
914
+ return;
915
+ }
916
+ res.setHeader("access-control-allow-origin", "*");
917
+ res.setHeader("content-type", "application/wasm");
918
+ res.end(artifact.bytes);
919
+ },
920
+ (error) => {
921
+ res.statusCode = 500;
922
+ res.end(error instanceof Error ? error.message : String(error));
923
+ }
924
+ );
925
+ });
926
+ }
927
+ };
928
+ }
929
+
751
930
  // src/vite-plugin-one.ts
752
931
  function rnxPlugin(options = {}) {
753
932
  const prefix = options.prefix || "/__soot";
@@ -757,7 +936,7 @@ function rnxPlugin(options = {}) {
757
936
  if (options.enabled === false) return;
758
937
  const bundleUrl = options.bundleUrl || "/node_modules/one/metro-entry.bundle?platform=ios&dev=true&minify=false";
759
938
  const appName = resolveRnxDesktopAppName(options.open, server.config.root);
760
- const replacementToken = (0, import_node_crypto2.randomUUID)();
939
+ const replacementToken = (0, import_node_crypto3.randomUUID)();
761
940
  const configuredPort = server.config.server.port || 8081;
762
941
  const listeningPort = () => {
763
942
  const address = server.httpServer?.address();
@@ -883,5 +1062,6 @@ var vite_plugin_one_default = rnxPlugin;
883
1062
  // Annotate the CommonJS export names for ESM import in node:
884
1063
  0 && (module.exports = {
885
1064
  rnxPlugin,
886
- sootsimPlugin
1065
+ sootsimPlugin,
1066
+ swiftPlugin
887
1067
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnxsim",
3
- "version": "0.1.506",
3
+ "version": "0.1.508",
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",
@@ -34,6 +34,11 @@
34
34
  "source": "./src/vite-plugin-one.ts",
35
35
  "default": "./dist-lib/vite.cjs"
36
36
  },
37
+ "./swift": {
38
+ "types": "./src/swift.ts",
39
+ "source": "./src/swift.ts",
40
+ "default": "./dist-lib/swift.cjs"
41
+ },
37
42
  "./metro": {
38
43
  "source": "./src/metro-plugin.ts",
39
44
  "default": "./dist-lib/metro.cjs"
package/src/swift.ts ADDED
@@ -0,0 +1,6 @@
1
+ // the runtime the `.swift` vite transform emits its module against. the
2
+ // implementation lives beside the toolchain it drives, in sootsim-swift.
3
+ export {
4
+ createSwiftModule,
5
+ type SwiftModuleOptions,
6
+ } from '../../sootsim-swift/src/mount.tsx'
@@ -235,3 +235,11 @@ export function rnxPlugin(options: RnxPluginOptions = {}): Plugin {
235
235
 
236
236
  export const sootsimPlugin = rnxPlugin
237
237
  export default rnxPlugin
238
+
239
+ // the `.swift` transform is opt-in per project: an app that never imports a
240
+ // `.swift` file has no reason to watch for one, and an app that ships a native
241
+ // ios/ tree (every swift file in it is a pod or an xcode target, not app
242
+ // source) would otherwise rebuild the wrong package on every edit. a swift app
243
+ // registers it in both the bundler that builds its native bundle and the main
244
+ // dev server that serves the artifact.
245
+ export { swiftPlugin, type SwiftPluginOptions } from './vite-plugin-swift.ts'
@@ -0,0 +1,254 @@
1
+ // `.swift` handling for a vite dev server: compile the project's swift sources
2
+ // with the swift.org toolchain, serve the artifact at a content-hashed url,
3
+ // and answer every `.swift` import with the module that mounts it.
4
+ //
5
+ // two hosts use this. the bundler that builds the app's native bundle runs
6
+ // resolveId/load, and the dev server runs the middleware that serves the
7
+ // artifact. both share the build below, so the artifact is compiled once.
8
+ //
9
+ // the hash is the hash of the built wasm, so a new hash means a genuinely
10
+ // different app: createSwiftModule moves the @State store across it instead of
11
+ // remounting.
12
+
13
+ import { execFile } from 'node:child_process'
14
+ import { createHash } from 'node:crypto'
15
+ import fs from 'node:fs'
16
+ import os from 'node:os'
17
+ import path from 'node:path'
18
+ import { promisify } from 'node:util'
19
+ import type { Plugin, ViteDevServer } from 'vite'
20
+
21
+ const run = promisify(execFile)
22
+
23
+ // the toolchain layout scripts/build.sh pins. xcode's swift has no wasm
24
+ // backend, so the swift.org toolchain and its matching wasm sdk are the only
25
+ // ones that can build this target.
26
+ const TOOLCHAIN_BIN = path.join(
27
+ os.homedir(),
28
+ 'Library/Developer/Toolchains/swift-6.2-RELEASE.xctoolchain/usr/bin',
29
+ )
30
+ const WASM_SDK = 'swift-6.2-RELEASE_wasm'
31
+ const WASM_TRIPLE = 'wasm32-unknown-wasip1'
32
+
33
+ const ARTIFACT_PATH = '/__rnx/swift/'
34
+ const VIRTUAL_PREFIX = '\0rnx-swift:'
35
+
36
+ interface SwiftArtifact {
37
+ hash: string
38
+ bytes: Buffer
39
+ // the sources this artifact was built from. a watcher event and a `load`
40
+ // reach the project in either order, so freshness is read from the sources
41
+ // rather than from whichever of them ran first.
42
+ stamp: string
43
+ }
44
+
45
+ interface SwiftProject {
46
+ packagePath: string
47
+ artifact: SwiftArtifact | null
48
+ building: Promise<SwiftArtifact> | null
49
+ }
50
+
51
+ // a project is its swiftpm package: the build, the artifact, and the dev
52
+ // server that serves it are shared by every plugin instance in this process.
53
+ const projects = new Map<string, SwiftProject>()
54
+ let devServer: ViteDevServer | undefined
55
+
56
+ function projectFor(packagePath: string): SwiftProject {
57
+ const existing = projects.get(packagePath)
58
+ if (existing) return existing
59
+ const created: SwiftProject = { packagePath, artifact: null, building: null }
60
+ projects.set(packagePath, created)
61
+ return created
62
+ }
63
+
64
+ function rootFor(file: string): string {
65
+ return devServer?.config.root ?? path.dirname(file)
66
+ }
67
+
68
+ // the compile unit is the whole package: `swift build` compiles every source
69
+ // in it, so one file's edit can change the artifact for all of them.
70
+ function sourceFiles(packagePath: string): string[] {
71
+ const found: string[] = []
72
+ const walk = (dir: string): void => {
73
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
74
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue
75
+ const full = path.join(dir, entry.name)
76
+ if (entry.isDirectory()) walk(full)
77
+ else if (entry.name.endsWith('.swift')) found.push(full)
78
+ }
79
+ }
80
+ walk(packagePath)
81
+ return found
82
+ }
83
+
84
+ function sourceStamp(packagePath: string): string {
85
+ const hash = createHash('sha256')
86
+ for (const file of sourceFiles(packagePath).sort()) {
87
+ hash.update(path.relative(packagePath, file))
88
+ hash.update(String(fs.statSync(file).mtimeMs))
89
+ }
90
+ return hash.digest('hex').slice(0, 16)
91
+ }
92
+
93
+ // the app fetches the artifact from a worker whose origin is the shell, so the
94
+ // url has to name this dev server rather than resolve relative to the shell.
95
+ function artifactUrl(project: SwiftProject, artifact: SwiftArtifact): string {
96
+ const address = devServer?.httpServer?.address()
97
+ const port =
98
+ address && typeof address === 'object' ? address.port : devServer?.config.server.port
99
+ if (!port) throw new Error('the swift artifact url needs the dev server port')
100
+ return `http://localhost:${port}${ARTIFACT_PATH}${artifact.hash}.wasm`
101
+ }
102
+
103
+ // one executable is the app. a package with several is ambiguous: the host
104
+ // cannot know which one the page is, so it says so instead of guessing.
105
+ function builtArtifact(packagePath: string): string {
106
+ const binDir = path.join(packagePath, '.build', WASM_TRIPLE, 'release')
107
+ const artifacts = fs.existsSync(binDir)
108
+ ? fs
109
+ .readdirSync(binDir)
110
+ .filter((name) => name.endsWith('.wasm'))
111
+ .sort()
112
+ : []
113
+ if (artifacts.length !== 1) {
114
+ throw new Error(
115
+ `swift project ${packagePath} built ${artifacts.length} wasm artifacts; exactly one executable is the app`,
116
+ )
117
+ }
118
+ return path.join(binDir, artifacts[0])
119
+ }
120
+
121
+ async function compile(packagePath: string, stamp: string): Promise<SwiftArtifact> {
122
+ const swift = path.join(TOOLCHAIN_BIN, 'swift')
123
+ if (!fs.existsSync(swift)) {
124
+ throw new Error(
125
+ `no swift.org toolchain at ${TOOLCHAIN_BIN}. install it and the ${WASM_SDK} sdk; see packages/sootsim-swift/README.md`,
126
+ )
127
+ }
128
+ try {
129
+ await run(
130
+ swift,
131
+ [
132
+ 'build',
133
+ '--package-path',
134
+ packagePath,
135
+ '--swift-sdk',
136
+ WASM_SDK,
137
+ '-c',
138
+ 'release',
139
+ '-Xswiftc',
140
+ '-Osize',
141
+ ],
142
+ { maxBuffer: 64 * 1024 * 1024 },
143
+ )
144
+ } catch (error) {
145
+ throw new Error(
146
+ `swift build failed for ${packagePath}\n${error instanceof Error ? error.message : String(error)}`,
147
+ )
148
+ }
149
+ const bytes = fs.readFileSync(builtArtifact(packagePath))
150
+ return {
151
+ hash: createHash('sha256').update(bytes).digest('hex').slice(0, 16),
152
+ bytes,
153
+ stamp,
154
+ }
155
+ }
156
+
157
+ function artifactFor(project: SwiftProject): Promise<SwiftArtifact> {
158
+ const stamp = sourceStamp(project.packagePath)
159
+ if (project.artifact?.stamp === stamp) return Promise.resolve(project.artifact)
160
+ project.building ??= compile(project.packagePath, stamp).then(
161
+ (artifact) => {
162
+ project.artifact = artifact
163
+ project.building = null
164
+ return artifact
165
+ },
166
+ (error: unknown) => {
167
+ project.building = null
168
+ throw error
169
+ },
170
+ )
171
+ return project.building
172
+ }
173
+
174
+ export interface SwiftPluginOptions {
175
+ // swiftpm package that owns the project's .swift sources. defaults to the
176
+ // vite root, which is the layout a swift-only project uses.
177
+ packagePath?: string
178
+ }
179
+
180
+ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
181
+ const packagePathFor = (file: string): string => options.packagePath ?? rootFor(file)
182
+
183
+ return {
184
+ name: 'rnx-swift',
185
+ enforce: 'pre',
186
+
187
+ async resolveId(source, importer) {
188
+ if (!source.endsWith('.swift')) return null
189
+ const resolved = await this.resolve(source, importer, { skipSelf: true })
190
+ if (!resolved) throw new Error(`cannot resolve swift source ${source}`)
191
+ return VIRTUAL_PREFIX + resolved.id
192
+ },
193
+
194
+ async load(id) {
195
+ if (!id.startsWith(VIRTUAL_PREFIX)) return null
196
+ const packagePath = packagePathFor(id.slice(VIRTUAL_PREFIX.length))
197
+ const project = projectFor(packagePath)
198
+ // every source in the package is a dependency of this module, so a
199
+ // bundler watching this module learns about a `.swift` edit and re-runs
200
+ // load. that is the whole hot reload: the new hash in the emitted module
201
+ // is what tells the mount it is looking at a different app.
202
+ for (const source of sourceFiles(packagePath)) this.addWatchFile(source)
203
+ let artifact: SwiftArtifact
204
+ try {
205
+ artifact = await artifactFor(project)
206
+ } catch (error) {
207
+ // a compile error must not fail this module. a module whose load threw
208
+ // leaves the graph unwatched, so the edit that fixes the error would
209
+ // never rebuild it and the app would sit on a stale frame until the
210
+ // dev server restarted. the previous artifact keeps rendering, and the
211
+ // error is reported here instead.
212
+ if (!project.artifact) throw error
213
+ this.warn(error instanceof Error ? error.message : String(error))
214
+ artifact = project.artifact
215
+ }
216
+ return [
217
+ `import { createSwiftModule } from 'rnxsim/swift'`,
218
+ `export default createSwiftModule({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)} })`,
219
+ '',
220
+ ].join('\n')
221
+ },
222
+
223
+ configureServer(server) {
224
+ devServer = server
225
+ const packagePath = packagePathFor('')
226
+
227
+ server.middlewares.use((req, res, next) => {
228
+ const url = (req.url ?? '').split('?')[0]
229
+ if (!url.startsWith(ARTIFACT_PATH)) {
230
+ next()
231
+ return
232
+ }
233
+ const project = projectFor(packagePath)
234
+ void artifactFor(project).then(
235
+ (artifact) => {
236
+ if (url !== `${ARTIFACT_PATH}${artifact.hash}.wasm`) {
237
+ res.statusCode = 404
238
+ res.end('swift artifact is stale')
239
+ return
240
+ }
241
+ // the tenant worker's origin is the shell, never this dev server
242
+ res.setHeader('access-control-allow-origin', '*')
243
+ res.setHeader('content-type', 'application/wasm')
244
+ res.end(artifact.bytes)
245
+ },
246
+ (error: unknown) => {
247
+ res.statusCode = 500
248
+ res.end(error instanceof Error ? error.message : String(error))
249
+ },
250
+ )
251
+ })
252
+ },
253
+ }
254
+ }