rnxsim 0.1.519 → 0.1.521
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/cloud-client.ts +419 -287
- package/cli/cloud-dispatch.ts +2 -21
- package/cli/cloud-session.ts +31 -85
- package/cli/commands/box.ts +15 -11
- package/cli/commands/inspect.ts +34 -1
- package/cli/commands/platform.ts +184 -232
- package/cli/main.ts +10 -2
- package/cli/outbound-endpoints.ts +21 -6
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +7 -2
- package/dist-lib/bridge-contract-input.mjs +7 -2
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud.cjs +18 -2
- package/dist-lib/cloud.mjs +18 -2
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +112 -39
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +44 -117
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +30 -1
- package/dist-lib/menu.mjs +27 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +41 -4
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +138 -164
- package/dist-lib/swift.cjs +766 -24
- package/dist-lib/vite.cjs +103 -30
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +72 -5
- package/src/bridge-contract-input.ts +10 -0
- package/src/bridge-contract.ts +5 -0
- package/src/cloud-contract.ts +7 -1
- package/src/cloud.ts +45 -6
- package/src/connect.ts +1 -1
- package/src/menu.ts +44 -0
- package/src/swift.ts +2 -0
- package/src/vite-plugin-one.ts +12 -0
- package/src/vite-plugin-swift.ts +141 -39
package/src/vite-plugin-swift.ts
CHANGED
|
@@ -16,18 +16,20 @@ import fs from 'node:fs'
|
|
|
16
16
|
import os from 'node:os'
|
|
17
17
|
import path from 'node:path'
|
|
18
18
|
import { promisify } from 'node:util'
|
|
19
|
+
import toolchain from '../../sootsim-swift/toolchain.json'
|
|
19
20
|
import type { Plugin, ViteDevServer } from 'vite'
|
|
20
21
|
|
|
21
22
|
const run = promisify(execFile)
|
|
22
23
|
|
|
23
|
-
// the
|
|
24
|
-
//
|
|
25
|
-
//
|
|
24
|
+
// the swift.org release scripts/build.sh builds with, from the one place it is
|
|
25
|
+
// named: the toolchain is <release>.xctoolchain and its matching wasm sdk is
|
|
26
|
+
// <release>_wasm. xcode's swift has no wasm backend, so this toolchain is the
|
|
27
|
+
// only one that can build the target.
|
|
26
28
|
const TOOLCHAIN_BIN = path.join(
|
|
27
29
|
os.homedir(),
|
|
28
|
-
|
|
30
|
+
`Library/Developer/Toolchains/${toolchain.release}.xctoolchain/usr/bin`,
|
|
29
31
|
)
|
|
30
|
-
const WASM_SDK =
|
|
32
|
+
const WASM_SDK = `${toolchain.release}_wasm`
|
|
31
33
|
const WASM_TRIPLE = 'wasm32-unknown-wasip1'
|
|
32
34
|
|
|
33
35
|
const ARTIFACT_PATH = '/__rnx/swift/'
|
|
@@ -61,35 +63,71 @@ function projectFor(packagePath: string): SwiftProject {
|
|
|
61
63
|
return created
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
function rootFor(file: string): string {
|
|
65
|
-
return devServer?.config.root ?? path.dirname(file)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
66
|
// 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
|
-
|
|
67
|
+
// in it, so one file's edit can change the artifact for all of them. also
|
|
68
|
+
// used for a linked module below, whose own nested packages are separate
|
|
69
|
+
// units by the same rule.
|
|
70
|
+
function sourceFiles(root: string): string[] {
|
|
71
71
|
const found: string[] = []
|
|
72
72
|
const walk = (dir: string): void => {
|
|
73
73
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
74
74
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue
|
|
75
75
|
const full = path.join(dir, entry.name)
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
// a nested Package.swift is a separate package with its own compile:
|
|
77
|
+
// its sources belong to that unit's stamp, not this one's
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
if (full !== root && fs.existsSync(path.join(full, 'Package.swift'))) continue
|
|
80
|
+
walk(full)
|
|
81
|
+
} else if (entry.name.endsWith('.swift')) found.push(full)
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
|
-
walk(
|
|
84
|
+
walk(root)
|
|
81
85
|
return found
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
// the SwiftUI module the project links: its manifest names it as a path
|
|
89
|
+
// dependency, and `swift build` compiles it with the project, so an edit
|
|
90
|
+
// there changes the artifact without moving any of the project's own mtimes.
|
|
91
|
+
export function linkedModuleRoots(packagePath: string): string[] {
|
|
92
|
+
let manifest: string
|
|
93
|
+
try {
|
|
94
|
+
manifest = fs.readFileSync(path.join(packagePath, 'Package.swift'), 'utf8')
|
|
95
|
+
} catch {
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
const roots = new Set<string>()
|
|
99
|
+
const dependency = /\.package\(\s*path:\s*"([^"]+)"\s*\)/g
|
|
100
|
+
for (const match of manifest.replace(/^\s*\/\/.*$/gm, '').matchAll(dependency)) {
|
|
101
|
+
const root = path.resolve(packagePath, match[1])
|
|
102
|
+
if (fs.existsSync(path.join(root, 'Package.swift'))) roots.add(root)
|
|
103
|
+
}
|
|
104
|
+
return [...roots].sort()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// the stamp covers the project plus the modules it links: a running dev
|
|
108
|
+
// server rebuilds only when this moves, so leaving the linked sources out
|
|
109
|
+
// would keep serving the artifact built before a SwiftUI module edit for as
|
|
110
|
+
// long as the project's own sources stayed the same. mirrors the compile
|
|
111
|
+
// service's artifact hash, which names the same closure.
|
|
112
|
+
export function sourceStamp(packagePath: string): string {
|
|
85
113
|
const hash = createHash('sha256')
|
|
86
|
-
for (const
|
|
87
|
-
hash.update(
|
|
88
|
-
|
|
114
|
+
for (const root of [packagePath, ...linkedModuleRoots(packagePath)]) {
|
|
115
|
+
hash.update(root)
|
|
116
|
+
for (const file of sourceFiles(root).sort()) {
|
|
117
|
+
hash.update(path.relative(root, file))
|
|
118
|
+
hash.update(String(fs.statSync(file).mtimeMs))
|
|
119
|
+
}
|
|
89
120
|
}
|
|
90
121
|
return hash.digest('hex').slice(0, 16)
|
|
91
122
|
}
|
|
92
123
|
|
|
124
|
+
// the registry slot for a view: the owning package plus the source within
|
|
125
|
+
// it. two packages in one app can hold the same filename, and a bare
|
|
126
|
+
// relative path would share one slot, one instance, and its state.
|
|
127
|
+
export function viewRootId(packagePath: string, file: string): string {
|
|
128
|
+
return path.join(packagePath, path.relative(packagePath, file))
|
|
129
|
+
}
|
|
130
|
+
|
|
93
131
|
// the app fetches the artifact from a worker whose origin is the shell, so the
|
|
94
132
|
// url has to name this dev server rather than resolve relative to the shell.
|
|
95
133
|
function artifactUrl(project: SwiftProject, artifact: SwiftArtifact): string {
|
|
@@ -171,20 +209,62 @@ function artifactFor(project: SwiftProject): Promise<SwiftArtifact> {
|
|
|
171
209
|
return project.building
|
|
172
210
|
}
|
|
173
211
|
|
|
212
|
+
// the swiftpm package that owns an imported file: the nearest directory at
|
|
213
|
+
// or above the file that holds a Package.swift, stopping at the vite root
|
|
214
|
+
// once the dev server has named it. a swift-only project keeps its sources at
|
|
215
|
+
// the root so that is its package; a React Native app keeps its in a
|
|
216
|
+
// subdirectory (a `native/` package beside the js), and each such package
|
|
217
|
+
// compiles on its own.
|
|
218
|
+
function packageFor(file: string): string {
|
|
219
|
+
let dir = path.dirname(file)
|
|
220
|
+
const stop = devServer?.config.root
|
|
221
|
+
for (;;) {
|
|
222
|
+
if (fs.existsSync(path.join(dir, 'Package.swift'))) return dir
|
|
223
|
+
if ((stop !== undefined && dir === stop) || path.dirname(dir) === dir) break
|
|
224
|
+
dir = path.dirname(dir)
|
|
225
|
+
}
|
|
226
|
+
throw new Error(
|
|
227
|
+
`no Package.swift above ${file}${stop === undefined ? '' : ` (searched up to ${stop})`}; a .swift import needs a swiftpm package like the one in packages/sootsim-swift/example`,
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// whether the package mounts as a view: its @main type conforms to
|
|
232
|
+
// RNXPackage instead of to App. the symbol graph is the exact read once the
|
|
233
|
+
// transform emits one; until then the conformance on the @main declaration is
|
|
234
|
+
// unambiguous, comments aside.
|
|
235
|
+
function isViewPackage(packagePath: string): boolean {
|
|
236
|
+
const main =
|
|
237
|
+
/@main\s+(?:\w+\s+)*(?:struct|class|enum|actor)\s+\w+[^{]*:\s*[^{]*\bRNXPackage\b/
|
|
238
|
+
for (const file of sourceFiles(packagePath)) {
|
|
239
|
+
const text = fs.readFileSync(file, 'utf8').replace(/^\s*\/\/.*$/gm, '')
|
|
240
|
+
if (main.test(text)) return true
|
|
241
|
+
}
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
|
|
174
245
|
export interface SwiftPluginOptions {
|
|
175
246
|
// swiftpm package that owns the project's .swift sources. defaults to the
|
|
176
|
-
//
|
|
247
|
+
// nearest Package.swift above each imported file, which is the vite root
|
|
248
|
+
// for the layout a swift-only project uses.
|
|
177
249
|
packagePath?: string
|
|
178
250
|
}
|
|
179
251
|
|
|
180
252
|
export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
181
|
-
const packagePathFor = (file: string): string => options.packagePath ?? rootFor(file)
|
|
182
|
-
|
|
183
253
|
return {
|
|
184
254
|
name: 'rnx-swift',
|
|
185
255
|
enforce: 'pre',
|
|
186
256
|
|
|
187
257
|
async resolveId(source, importer) {
|
|
258
|
+
if (source.startsWith(VIRTUAL_PREFIX)) return source
|
|
259
|
+
// import-analysis re-resolves the served URL form of the id
|
|
260
|
+
// (/@id/__x00__..., with the null byte encoded) while transforming the
|
|
261
|
+
// importer. it still names this module, so answer the decoded id; any
|
|
262
|
+
// other /@id/ url is already resolved and must not fall through to the
|
|
263
|
+
// filesystem resolve below, which would throw for it.
|
|
264
|
+
if (source.startsWith('/@id/')) {
|
|
265
|
+
const decoded = source.slice('/@id/'.length).replace(/^__x00__/, '\0')
|
|
266
|
+
return decoded.startsWith(VIRTUAL_PREFIX) ? decoded : null
|
|
267
|
+
}
|
|
188
268
|
if (!source.endsWith('.swift')) return null
|
|
189
269
|
const resolved = await this.resolve(source, importer, { skipSelf: true })
|
|
190
270
|
if (!resolved) throw new Error(`cannot resolve swift source ${source}`)
|
|
@@ -193,13 +273,21 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
193
273
|
|
|
194
274
|
async load(id) {
|
|
195
275
|
if (!id.startsWith(VIRTUAL_PREFIX)) return null
|
|
196
|
-
const
|
|
276
|
+
const file = id.slice(VIRTUAL_PREFIX.length)
|
|
277
|
+
const packagePath = options.packagePath ?? packageFor(file)
|
|
197
278
|
const project = projectFor(packagePath)
|
|
198
279
|
// every source in the package is a dependency of this module, so a
|
|
199
280
|
// bundler watching this module learns about a `.swift` edit and re-runs
|
|
200
281
|
// load. that is the whole hot reload: the new hash in the emitted module
|
|
201
282
|
// is what tells the mount it is looking at a different app.
|
|
202
283
|
for (const source of sourceFiles(packagePath)) this.addWatchFile(source)
|
|
284
|
+
// an edit to the linked SwiftUI module rebuilds the artifact without
|
|
285
|
+
// touching the project's own sources, so those files gate the reload
|
|
286
|
+
// too: the new hash in the emitted module is what tells the mount it
|
|
287
|
+
// is looking at a different app.
|
|
288
|
+
for (const root of linkedModuleRoots(packagePath)) {
|
|
289
|
+
for (const source of sourceFiles(root)) this.addWatchFile(source)
|
|
290
|
+
}
|
|
203
291
|
let artifact: SwiftArtifact
|
|
204
292
|
try {
|
|
205
293
|
artifact = await artifactFor(project)
|
|
@@ -213,6 +301,18 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
213
301
|
this.warn(error instanceof Error ? error.message : String(error))
|
|
214
302
|
artifact = project.artifact
|
|
215
303
|
}
|
|
304
|
+
// a view package mounts into its importer: the default export is the
|
|
305
|
+
// view component, rooted by its package and source path so a hot reload
|
|
306
|
+
// finds the same instance. an app package keeps the root component it
|
|
307
|
+
// always had.
|
|
308
|
+
if (isViewPackage(packagePath)) {
|
|
309
|
+
const rootId = viewRootId(packagePath, file)
|
|
310
|
+
return [
|
|
311
|
+
`import { createSwiftView } from 'rnxsim/swift'`,
|
|
312
|
+
`export default createSwiftView({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)}, rootId: ${JSON.stringify(rootId)} })`,
|
|
313
|
+
'',
|
|
314
|
+
].join('\n')
|
|
315
|
+
}
|
|
216
316
|
return [
|
|
217
317
|
`import { createSwiftModule } from 'rnxsim/swift'`,
|
|
218
318
|
`export default createSwiftModule({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)} })`,
|
|
@@ -222,7 +322,6 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
222
322
|
|
|
223
323
|
configureServer(server) {
|
|
224
324
|
devServer = server
|
|
225
|
-
const packagePath = packagePathFor('')
|
|
226
325
|
|
|
227
326
|
server.middlewares.use((req, res, next) => {
|
|
228
327
|
const url = (req.url ?? '').split('?')[0]
|
|
@@ -230,24 +329,27 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
230
329
|
next()
|
|
231
330
|
return
|
|
232
331
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
332
|
+
// the url names a content hash, not a package: the request is answered
|
|
333
|
+
// by whichever known project built it, so a React Native app whose
|
|
334
|
+
// swift package sits below its root serves the same path its imports
|
|
335
|
+
// compiled.
|
|
336
|
+
void (async () => {
|
|
337
|
+
for (const project of projects.values()) {
|
|
338
|
+
const artifact = await artifactFor(project)
|
|
339
|
+
if (url === `${ARTIFACT_PATH}${artifact.hash}.wasm`) {
|
|
340
|
+
// the tenant worker's origin is the shell, never this dev server
|
|
341
|
+
res.setHeader('access-control-allow-origin', '*')
|
|
342
|
+
res.setHeader('content-type', 'application/wasm')
|
|
343
|
+
res.end(artifact.bytes)
|
|
239
344
|
return
|
|
240
345
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
(error:
|
|
247
|
-
|
|
248
|
-
res.end(error instanceof Error ? error.message : String(error))
|
|
249
|
-
},
|
|
250
|
-
)
|
|
346
|
+
}
|
|
347
|
+
res.statusCode = 404
|
|
348
|
+
res.end('swift artifact is stale')
|
|
349
|
+
})().catch((error: unknown) => {
|
|
350
|
+
res.statusCode = 500
|
|
351
|
+
res.end(error instanceof Error ? error.message : String(error))
|
|
352
|
+
})
|
|
251
353
|
})
|
|
252
354
|
},
|
|
253
355
|
}
|