rnxsim 0.1.521 → 0.1.523
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/commands/control.ts +31 -15
- 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 +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- 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 +1 -1
- package/dist-lib/cloud.mjs +1 -1
- 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 +7 -3
- 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 +1 -1
- 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 +1 -1
- package/dist-lib/menu.mjs +1 -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 +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +32 -14
- package/dist-lib/swift.cjs +155 -72
- package/dist-lib/vite.cjs +38 -42
- package/package.json +1 -1
- package/src/swift.ts +2 -3
- package/src/vite-plugin-swift.ts +81 -59
package/src/vite-plugin-swift.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// artifact. both share the build below, so the artifact is compiled once.
|
|
8
8
|
//
|
|
9
9
|
// the hash is the hash of the built wasm, so a new hash means a genuinely
|
|
10
|
-
// different app:
|
|
10
|
+
// different app: createSwiftEntry moves the @State store across it instead of
|
|
11
11
|
// remounting.
|
|
12
12
|
|
|
13
13
|
import { execFile } from 'node:child_process'
|
|
@@ -47,7 +47,8 @@ interface SwiftArtifact {
|
|
|
47
47
|
interface SwiftProject {
|
|
48
48
|
packagePath: string
|
|
49
49
|
artifact: SwiftArtifact | null
|
|
50
|
-
|
|
50
|
+
// the build in flight and the sources it was started from
|
|
51
|
+
building: { stamp: string; promise: Promise<SwiftArtifact> } | null
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// a project is its swiftpm package: the build, the artifact, and the dev
|
|
@@ -107,8 +108,12 @@ export function linkedModuleRoots(packagePath: string): string[] {
|
|
|
107
108
|
// the stamp covers the project plus the modules it links: a running dev
|
|
108
109
|
// server rebuilds only when this moves, so leaving the linked sources out
|
|
109
110
|
// would keep serving the artifact built before a SwiftUI module edit for as
|
|
110
|
-
// long as the project's own sources stayed the same.
|
|
111
|
-
// service's artifact hash,
|
|
111
|
+
// long as the project's own sources stayed the same. it names the same
|
|
112
|
+
// closure the compile service's artifact hash names, but it is not the same
|
|
113
|
+
// function: the stamp hashes mtimes, which is the cheap rebuild signal for a
|
|
114
|
+
// watcher, while the artifact address hashes content, which is what stays
|
|
115
|
+
// stable when a checkout restores byte-identical files. a restore rebuilds
|
|
116
|
+
// here and reuses the stored artifact there, by design.
|
|
112
117
|
export function sourceStamp(packagePath: string): string {
|
|
113
118
|
const hash = createHash('sha256')
|
|
114
119
|
for (const root of [packagePath, ...linkedModuleRoots(packagePath)]) {
|
|
@@ -121,11 +126,15 @@ export function sourceStamp(packagePath: string): string {
|
|
|
121
126
|
return hash.digest('hex').slice(0, 16)
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
// the registry slot for
|
|
125
|
-
// it. two packages in one app can hold the same filename, and
|
|
126
|
-
// relative path would share one slot, one instance, and its state.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
// the registry slot for an entry: the owning package's own name plus the
|
|
130
|
+
// source within it. two packages in one app can hold the same filename, and
|
|
131
|
+
// a bare relative path would share one slot, one instance, and its state.
|
|
132
|
+
// the name travels instead of the path so identical source in two checkouts
|
|
133
|
+
// emits the identical module; two same-named packages sharing one app still
|
|
134
|
+
// collide, and take the rootId override for that.
|
|
135
|
+
export function entryRootId(packagePath: string, file: string): string {
|
|
136
|
+
const relative = path.relative(packagePath, file).split(path.sep).join(path.posix.sep)
|
|
137
|
+
return `${path.basename(packagePath)}${path.posix.sep}${relative}`
|
|
129
138
|
}
|
|
130
139
|
|
|
131
140
|
// the app fetches the artifact from a worker whose origin is the shell, so the
|
|
@@ -156,6 +165,12 @@ function builtArtifact(packagePath: string): string {
|
|
|
156
165
|
return path.join(binDir, artifacts[0])
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
function streamText(value: unknown): string {
|
|
169
|
+
if (typeof value === 'string') return value.trim()
|
|
170
|
+
if (Buffer.isBuffer(value)) return value.toString('utf8').trim()
|
|
171
|
+
return ''
|
|
172
|
+
}
|
|
173
|
+
|
|
159
174
|
async function compile(packagePath: string, stamp: string): Promise<SwiftArtifact> {
|
|
160
175
|
const swift = path.join(TOOLCHAIN_BIN, 'swift')
|
|
161
176
|
if (!fs.existsSync(swift)) {
|
|
@@ -180,9 +195,24 @@ async function compile(packagePath: string, stamp: string): Promise<SwiftArtifac
|
|
|
180
195
|
{ maxBuffer: 64 * 1024 * 1024 },
|
|
181
196
|
)
|
|
182
197
|
} catch (error) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
198
|
+
// `swift build` prints the compiler's diagnostics on stdout and swiftpm's
|
|
199
|
+
// own on stderr; the failure's message holds only the command line, so
|
|
200
|
+
// both streams are what names the line that broke. stdout leads because
|
|
201
|
+
// compiler diagnostics are what the editor needs to show.
|
|
202
|
+
const streams =
|
|
203
|
+
error && typeof error === 'object'
|
|
204
|
+
? [
|
|
205
|
+
streamText(Reflect.get(error, 'stdout')),
|
|
206
|
+
streamText(Reflect.get(error, 'stderr')),
|
|
207
|
+
].filter(Boolean)
|
|
208
|
+
: []
|
|
209
|
+
const detail =
|
|
210
|
+
streams.length > 0
|
|
211
|
+
? streams.join('\n')
|
|
212
|
+
: error instanceof Error
|
|
213
|
+
? error.message
|
|
214
|
+
: String(error)
|
|
215
|
+
throw new Error(`swift build failed for ${packagePath}\n${detail}`)
|
|
186
216
|
}
|
|
187
217
|
const bytes = fs.readFileSync(builtArtifact(packagePath))
|
|
188
218
|
return {
|
|
@@ -195,7 +225,17 @@ async function compile(packagePath: string, stamp: string): Promise<SwiftArtifac
|
|
|
195
225
|
function artifactFor(project: SwiftProject): Promise<SwiftArtifact> {
|
|
196
226
|
const stamp = sourceStamp(project.packagePath)
|
|
197
227
|
if (project.artifact?.stamp === stamp) return Promise.resolve(project.artifact)
|
|
198
|
-
|
|
228
|
+
if (project.building) {
|
|
229
|
+
// a second edit during a build: the build in flight was started from
|
|
230
|
+
// older sources, so its artifact is not the answer for these. wait it out
|
|
231
|
+
// and ask again, which starts the build these sources need; joining it
|
|
232
|
+
// would emit the older artifact and nothing would trigger another load.
|
|
233
|
+
const running = project.building
|
|
234
|
+
if (running.stamp === stamp) return running.promise
|
|
235
|
+
const again = () => artifactFor(project)
|
|
236
|
+
return running.promise.then(again, again)
|
|
237
|
+
}
|
|
238
|
+
const promise = compile(project.packagePath, stamp).then(
|
|
199
239
|
(artifact) => {
|
|
200
240
|
project.artifact = artifact
|
|
201
241
|
project.building = null
|
|
@@ -206,7 +246,8 @@ function artifactFor(project: SwiftProject): Promise<SwiftArtifact> {
|
|
|
206
246
|
throw error
|
|
207
247
|
},
|
|
208
248
|
)
|
|
209
|
-
|
|
249
|
+
project.building = { stamp, promise }
|
|
250
|
+
return promise
|
|
210
251
|
}
|
|
211
252
|
|
|
212
253
|
// the swiftpm package that owns an imported file: the nearest directory at
|
|
@@ -228,20 +269,6 @@ function packageFor(file: string): string {
|
|
|
228
269
|
)
|
|
229
270
|
}
|
|
230
271
|
|
|
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
|
-
|
|
245
272
|
export interface SwiftPluginOptions {
|
|
246
273
|
// swiftpm package that owns the project's .swift sources. defaults to the
|
|
247
274
|
// nearest Package.swift above each imported file, which is the vite root
|
|
@@ -274,6 +301,10 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
274
301
|
async load(id) {
|
|
275
302
|
if (!id.startsWith(VIRTUAL_PREFIX)) return null
|
|
276
303
|
const file = id.slice(VIRTUAL_PREFIX.length)
|
|
304
|
+
// the module is the whole package's artifact, so an import of a file
|
|
305
|
+
// that was deleted would keep answering with it. a deleted .tsx fails
|
|
306
|
+
// its importer; so does a deleted .swift.
|
|
307
|
+
if (!fs.existsSync(file)) throw new Error(`swift source ${file} no longer exists`)
|
|
277
308
|
const packagePath = options.packagePath ?? packageFor(file)
|
|
278
309
|
const project = projectFor(packagePath)
|
|
279
310
|
// every source in the package is a dependency of this module, so a
|
|
@@ -301,21 +332,13 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
301
332
|
this.warn(error instanceof Error ? error.message : String(error))
|
|
302
333
|
artifact = project.artifact
|
|
303
334
|
}
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
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
|
-
}
|
|
335
|
+
// every import mounts through the one entry: the artifact answers
|
|
336
|
+
// whether it is a view or an app root at runtime, so the transform
|
|
337
|
+
// never re-derives the conformance from source. the entry is rooted by
|
|
338
|
+
// its package and source path so a hot reload finds the same instance.
|
|
316
339
|
return [
|
|
317
|
-
`import {
|
|
318
|
-
`export default
|
|
340
|
+
`import { createSwiftEntry } from 'rnxsim/swift'`,
|
|
341
|
+
`export default createSwiftEntry({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)}, rootId: ${JSON.stringify(entryRootId(packagePath, file))} })`,
|
|
319
342
|
'',
|
|
320
343
|
].join('\n')
|
|
321
344
|
},
|
|
@@ -332,24 +355,23 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
332
355
|
// the url names a content hash, not a package: the request is answered
|
|
333
356
|
// by whichever known project built it, so a React Native app whose
|
|
334
357
|
// swift package sits below its root serves the same path its imports
|
|
335
|
-
// compiled.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
358
|
+
// compiled. served from what is already built, never rebuilt here: the
|
|
359
|
+
// page only knows a hash that `load` emitted, and `load` under a compile
|
|
360
|
+
// error emits the last good hash, whose bytes are still the project's
|
|
361
|
+
// artifact. rebuilding would fail that fetch with the compile error and
|
|
362
|
+
// fail every other project's artifact with it.
|
|
363
|
+
for (const project of projects.values()) {
|
|
364
|
+
const artifact = project.artifact
|
|
365
|
+
if (artifact && url === `${ARTIFACT_PATH}${artifact.hash}.wasm`) {
|
|
366
|
+
// the tenant worker's origin is the shell, never this dev server
|
|
367
|
+
res.setHeader('access-control-allow-origin', '*')
|
|
368
|
+
res.setHeader('content-type', 'application/wasm')
|
|
369
|
+
res.end(artifact.bytes)
|
|
370
|
+
return
|
|
346
371
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
res.statusCode = 500
|
|
351
|
-
res.end(error instanceof Error ? error.message : String(error))
|
|
352
|
-
})
|
|
372
|
+
}
|
|
373
|
+
res.statusCode = 404
|
|
374
|
+
res.end('swift artifact is stale')
|
|
353
375
|
})
|
|
354
376
|
},
|
|
355
377
|
}
|