rnxsim 0.1.524 → 0.1.526
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/outbound-endpoints.ts +15 -0
- 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 +33 -4
- 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 +3 -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 +37 -8
- package/dist-lib/swift-submit.cjs +92 -0
- package/dist-lib/swift-submit.mjs +65 -0
- package/dist-lib/swift.cjs +316 -23
- package/dist-lib/vite.cjs +280 -11
- package/package.json +8 -1
- package/src/rolldown-plugin-swift.ts +240 -0
- package/src/swift-submit.ts +185 -0
- package/src/vite-plugin-one.ts +10 -3
- package/src/vite-plugin-swift.ts +86 -2
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* swift-compile's caller half: ask the compile host for this project's wasm
|
|
3
|
+
* artifact, and poll until it is published.
|
|
4
|
+
*
|
|
5
|
+
* This is the `.swift` transform's one request path, shared by every host
|
|
6
|
+
* that renders a project's swift sources: the in-IDE bundler worker (posts
|
|
7
|
+
* its own origin's `/api/swift-compile`), the node bundle builder used by
|
|
8
|
+
* the test-drive CLI and the deploy publisher (posts an absolute endpoint),
|
|
9
|
+
* and the One/rolldown `.swift` transform (same, with a `ccdn_` credential).
|
|
10
|
+
* A caller never compiles in-process: the container is the one compiler, and
|
|
11
|
+
* no bundler is that instance.
|
|
12
|
+
*
|
|
13
|
+
* It lives here (rather than in the app's `src/bundler/`) because the One
|
|
14
|
+
* transform ships in this package and cannot import app code; the app's own
|
|
15
|
+
* callers import this subpath back. The route path and the outcome shape are
|
|
16
|
+
* the wire coupling to `src/compile/swiftCompileContract.ts`, which owns the
|
|
17
|
+
* server-side caps, parsers, and refusals and imports these back.
|
|
18
|
+
*
|
|
19
|
+
* Browser-safe: node-only submitters live beside their call sites.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const SWIFT_COMPILE_PATH = '/api/swift-compile'
|
|
23
|
+
|
|
24
|
+
export interface SwiftImageUpload {
|
|
25
|
+
/** the raster file picked for the asset (has the extension). */
|
|
26
|
+
file: string
|
|
27
|
+
/** the raster bytes, base64: carried once so the service can publish them. */
|
|
28
|
+
data: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SwiftCompileOutcome =
|
|
32
|
+
/**
|
|
33
|
+
* the artifact is published; the bundler mounts it by url and hash.
|
|
34
|
+
*
|
|
35
|
+
* `artifactUrl` is root-relative (`/compile/asset/<sha256>`), because the
|
|
36
|
+
* host that compiles has no browser origin in front of it: the local node
|
|
37
|
+
* dev server and the cf-build container would each have to guess one. The
|
|
38
|
+
* caller that renders the module resolves it against its own origin, which
|
|
39
|
+
* is the origin the artifact route is served from in dev and in prod alike.
|
|
40
|
+
*/
|
|
41
|
+
| {
|
|
42
|
+
status: 'published'
|
|
43
|
+
artifactUrl: string
|
|
44
|
+
hash: string
|
|
45
|
+
/**
|
|
46
|
+
* the published photo rasters, bundle asset name to absolute url.
|
|
47
|
+
* root-relative like the artifact url; the caller resolves it against
|
|
48
|
+
* its own origin, and the entry embeds it as the image table. absent
|
|
49
|
+
* when the request packs no images.
|
|
50
|
+
*/
|
|
51
|
+
imageUrls?: Record<string, string>
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* the build is running; poll the same source set again. One build is tens of
|
|
55
|
+
* seconds and a service-binding request is cancelled at about four, so the
|
|
56
|
+
* container answers this before the connection dies and the caller polls.
|
|
57
|
+
*/
|
|
58
|
+
| { status: 'compiling'; hash: string }
|
|
59
|
+
/** refused before any work: no entry, too many files, over a cap, over a rate limit. */
|
|
60
|
+
| { status: 'refused'; detail: string }
|
|
61
|
+
/** the compiler ran and failed; detail carries its diagnostic verbatim. */
|
|
62
|
+
| { status: 'failed'; detail: string }
|
|
63
|
+
|
|
64
|
+
const POLL_INTERVAL_MS = 1500
|
|
65
|
+
|
|
66
|
+
export interface SwiftCompileContext {
|
|
67
|
+
/**
|
|
68
|
+
* the compile credential: the signed preview token `BundlerSessionRuntime`
|
|
69
|
+
* mints for a live project session, or a `ccdn_` API key. `compileCredential.server.ts`
|
|
70
|
+
* is the one verifier. absent means this build has no credential and a
|
|
71
|
+
* project with `.swift` sources cannot be compiled.
|
|
72
|
+
*/
|
|
73
|
+
assetReadToken?: string
|
|
74
|
+
/**
|
|
75
|
+
* absolute origin of the app serving `/api/swift-compile`. The browser
|
|
76
|
+
* worker omits it and posts its own origin; a node caller that is not that
|
|
77
|
+
* app must pass one.
|
|
78
|
+
*/
|
|
79
|
+
endpoint?: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type SwiftCompileSubmit = (work: {
|
|
83
|
+
files: Record<string, string>
|
|
84
|
+
/** the bundle pack; absent means the project packs none. */
|
|
85
|
+
resources?: Record<string, string>
|
|
86
|
+
/** the photo rasters to publish; absent means the project packs none. */
|
|
87
|
+
images?: Record<string, SwiftImageUpload>
|
|
88
|
+
entry: string
|
|
89
|
+
}) => Promise<SwiftCompileOutcome>
|
|
90
|
+
|
|
91
|
+
/** the HTTP submitter, shared by the browser worker and the node build. */
|
|
92
|
+
export function httpSwiftCompileSubmit(
|
|
93
|
+
context: SwiftCompileContext,
|
|
94
|
+
): SwiftCompileSubmit | undefined {
|
|
95
|
+
const { assetReadToken, endpoint } = context
|
|
96
|
+
if (!assetReadToken) return undefined
|
|
97
|
+
const url = endpoint
|
|
98
|
+
? `${endpoint.replace(/\/+$/, '')}${SWIFT_COMPILE_PATH}`
|
|
99
|
+
: SWIFT_COMPILE_PATH
|
|
100
|
+
|
|
101
|
+
return async (work) => {
|
|
102
|
+
let response: Response
|
|
103
|
+
try {
|
|
104
|
+
response = await fetch(url, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'content-type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ ...work, token: assetReadToken }),
|
|
108
|
+
})
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`contrast-bundler: fetch failed for ${url} while compiling ${work.entry}: ${error instanceof Error ? error.message : String(error)}`,
|
|
112
|
+
{ cause: error },
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const detail = (await response.text()).trim()
|
|
117
|
+
throw new Error(
|
|
118
|
+
`contrast-bundler: the swift compile for ${work.entry} answered ${response.status}${detail ? `: ${detail}` : ''}`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
return (await response.json()) as SwiftCompileOutcome
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface SwiftModuleAddress {
|
|
126
|
+
/** absolute url the tenant fetches the wasm from. */
|
|
127
|
+
url: string
|
|
128
|
+
hash: string
|
|
129
|
+
/** bundle asset name to absolute photo url, when the pack publishes any. */
|
|
130
|
+
imageUrls?: Record<string, string>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** the service answers root-relative; the caller that knows the origin resolves. */
|
|
134
|
+
function resolveImageUrls(
|
|
135
|
+
urls: Record<string, string>,
|
|
136
|
+
origin: string,
|
|
137
|
+
): Record<string, string> {
|
|
138
|
+
const resolved: Record<string, string> = {}
|
|
139
|
+
for (const [name, url] of Object.entries(urls)) {
|
|
140
|
+
resolved[name] = new URL(url, origin).href
|
|
141
|
+
}
|
|
142
|
+
return resolved
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* one project's swift sources as a published artifact address.
|
|
147
|
+
*
|
|
148
|
+
* `origin` is the origin the artifact route answers on — the app's own origin
|
|
149
|
+
* for the browser worker, the caller's compile origin for a node build. The
|
|
150
|
+
* contract's url is root-relative because the compile host has no browser
|
|
151
|
+
* origin in front of it, so the resolution happens here, at the one caller
|
|
152
|
+
* that knows both.
|
|
153
|
+
*/
|
|
154
|
+
export async function compileSwiftProject(
|
|
155
|
+
submit: SwiftCompileSubmit,
|
|
156
|
+
work: {
|
|
157
|
+
files: Record<string, string>
|
|
158
|
+
resources?: Record<string, string>
|
|
159
|
+
images?: Record<string, SwiftImageUpload>
|
|
160
|
+
entry: string
|
|
161
|
+
},
|
|
162
|
+
origin: string,
|
|
163
|
+
): Promise<SwiftModuleAddress> {
|
|
164
|
+
while (true) {
|
|
165
|
+
const outcome = await submit(work)
|
|
166
|
+
switch (outcome.status) {
|
|
167
|
+
case 'published':
|
|
168
|
+
return {
|
|
169
|
+
url: new URL(outcome.artifactUrl, origin).href,
|
|
170
|
+
hash: outcome.hash,
|
|
171
|
+
...(outcome.imageUrls === undefined
|
|
172
|
+
? {}
|
|
173
|
+
: { imageUrls: resolveImageUrls(outcome.imageUrls, origin) }),
|
|
174
|
+
}
|
|
175
|
+
case 'compiling':
|
|
176
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
|
|
177
|
+
continue
|
|
178
|
+
case 'refused':
|
|
179
|
+
case 'failed':
|
|
180
|
+
throw new Error(
|
|
181
|
+
`contrast-bundler: the swift sources for ${work.entry} cannot be compiled: ${outcome.detail}`,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
package/src/vite-plugin-one.ts
CHANGED
|
@@ -251,7 +251,14 @@ export default rnxPlugin
|
|
|
251
251
|
// the `.swift` transform is opt-in per project: an app that never imports a
|
|
252
252
|
// `.swift` file has no reason to watch for one, and an app that ships a native
|
|
253
253
|
// ios/ tree (every swift file in it is a pod or an xcode target, not app
|
|
254
|
-
// source) would otherwise rebuild the wrong package on every edit. a
|
|
255
|
-
// registers
|
|
256
|
-
// dev server that serves the artifact
|
|
254
|
+
// source) would otherwise rebuild the wrong package on every edit. a local
|
|
255
|
+
// vite app registers `swiftPlugin` in both the bundler that builds its native
|
|
256
|
+
// bundle and the main dev server that serves the artifact; a One app on the
|
|
257
|
+
// rolldown native engine registers `swiftRemotePlugin` through
|
|
258
|
+
// `native.bundlerOptions.plugins` instead, which submits to
|
|
259
|
+
// `/api/swift-compile` and needs no local toolchain and no serving.
|
|
257
260
|
export { swiftPlugin, type SwiftPluginOptions } from './vite-plugin-swift.ts'
|
|
261
|
+
export {
|
|
262
|
+
swiftRemotePlugin,
|
|
263
|
+
type SwiftRemotePluginOptions,
|
|
264
|
+
} from './rolldown-plugin-swift.ts'
|
package/src/vite-plugin-swift.ts
CHANGED
|
@@ -16,7 +16,10 @@ 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 {
|
|
19
|
+
import {
|
|
20
|
+
enumerateBundleImages,
|
|
21
|
+
enumerateBundleJson,
|
|
22
|
+
} from '../../sootsim-swift/src/bundleAssets'
|
|
20
23
|
import toolchain from '../../sootsim-swift/toolchain.json'
|
|
21
24
|
import type { Plugin, ViteDevServer } from 'vite'
|
|
22
25
|
|
|
@@ -189,6 +192,67 @@ export function buildSwiftResourceTable(
|
|
|
189
192
|
return { table, watched }
|
|
190
193
|
}
|
|
191
194
|
|
|
195
|
+
// the /res pack: every Resources/ json in the package, basename-keyed and
|
|
196
|
+
// base64, riding the entry module so the mount serves it without a fetch.
|
|
197
|
+
// the namespace is flat like the bundle the wasm side reads: shallower
|
|
198
|
+
// paths pack first (the Resources root is the bundle root) with a lexical
|
|
199
|
+
// tiebreak, and the first spelling of a basename wins a collision. caps are
|
|
200
|
+
// INFERRED (no app in either rung is near them; landmarkData-scale json is
|
|
201
|
+
// tens of KB): a file past them is skipped and named in `skipped` so the
|
|
202
|
+
// load warns instead of silently serving a partial bundle. binary bytes
|
|
203
|
+
// travel the same map when a port reaches past json; the worker half packs
|
|
204
|
+
// text only until then (see the gaps ledger).
|
|
205
|
+
const BUNDLE_MAX_FILES = 128
|
|
206
|
+
const BUNDLE_MAX_FILE_BYTES = 2 * 1024 * 1024
|
|
207
|
+
const BUNDLE_MAX_TOTAL_BYTES = 8 * 1024 * 1024
|
|
208
|
+
|
|
209
|
+
export interface SwiftBundlePack {
|
|
210
|
+
/** basename to base64 bytes for the mount's /res. */
|
|
211
|
+
files: Record<string, string>
|
|
212
|
+
/** project-relative json files this load's pack covers (for watching). */
|
|
213
|
+
watched: string[]
|
|
214
|
+
/** project-relative files skipped by a cap, for the load warning. */
|
|
215
|
+
skipped: string[]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function buildSwiftBundleFiles(packagePath: string): SwiftBundlePack {
|
|
219
|
+
// resourceCandidateFiles walks every non-ignored file with the nested
|
|
220
|
+
// package exclusion; the shared enumeration decides namespace, order and
|
|
221
|
+
// collisions, identically for every host that packs.
|
|
222
|
+
const relative = resourceCandidateFiles(packagePath).map((file) =>
|
|
223
|
+
path.relative(packagePath, file).split(path.sep).join(path.posix.sep),
|
|
224
|
+
)
|
|
225
|
+
const files: Record<string, string> = {}
|
|
226
|
+
const watched: string[] = []
|
|
227
|
+
const skipped: string[] = []
|
|
228
|
+
let total = 0
|
|
229
|
+
for (const { base, file } of enumerateBundleJson(relative)) {
|
|
230
|
+
watched.push(file)
|
|
231
|
+
let bytes: Buffer
|
|
232
|
+
try {
|
|
233
|
+
bytes = fs.readFileSync(path.join(packagePath, ...file.split('/')))
|
|
234
|
+
} catch {
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
if (bytes.length > BUNDLE_MAX_FILE_BYTES) {
|
|
238
|
+
skipped.push(`${file} (${bytes.length} bytes past the per-file cap)`)
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
if (
|
|
242
|
+
Object.keys(files).length >= BUNDLE_MAX_FILES ||
|
|
243
|
+
total + bytes.length > BUNDLE_MAX_TOTAL_BYTES
|
|
244
|
+
) {
|
|
245
|
+
skipped.push(
|
|
246
|
+
`${file} (pack caps: ${BUNDLE_MAX_FILES} files / ${BUNDLE_MAX_TOTAL_BYTES} bytes)`,
|
|
247
|
+
)
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
files[base] = bytes.toString('base64')
|
|
251
|
+
total += bytes.length
|
|
252
|
+
}
|
|
253
|
+
return { files, watched, skipped }
|
|
254
|
+
}
|
|
255
|
+
|
|
192
256
|
// the middleware's lookup, exported for the producer's own proof: a hit is
|
|
193
257
|
// known bytes for a known hash, anything else is null, never a file.
|
|
194
258
|
export function serveSwiftResource(
|
|
@@ -472,13 +536,33 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
472
536
|
`swift resources for ${packagePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
473
537
|
)
|
|
474
538
|
}
|
|
539
|
+
// the /res pack rides the same module as bytes: a json edit moves the
|
|
540
|
+
// pack, the watched files re-run this load, and the mount's
|
|
541
|
+
// bundleFiles compare treats it as a new app and remounts through it.
|
|
542
|
+
let bundleFiles: Record<string, string> = {}
|
|
543
|
+
try {
|
|
544
|
+
const packed = buildSwiftBundleFiles(packagePath)
|
|
545
|
+
bundleFiles = packed.files
|
|
546
|
+
for (const watched of packed.watched) {
|
|
547
|
+
this.addWatchFile(path.join(packagePath, ...watched.split(path.posix.sep)))
|
|
548
|
+
}
|
|
549
|
+
if (packed.skipped.length > 0) {
|
|
550
|
+
this.warn(
|
|
551
|
+
`swift bundle files skipped for ${packagePath}: ${packed.skipped.join('; ')}`,
|
|
552
|
+
)
|
|
553
|
+
}
|
|
554
|
+
} catch (error) {
|
|
555
|
+
this.warn(
|
|
556
|
+
`swift bundle files for ${packagePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
557
|
+
)
|
|
558
|
+
}
|
|
475
559
|
// every import mounts through the one entry: the artifact answers
|
|
476
560
|
// whether it is a view or an app root at runtime, so the transform
|
|
477
561
|
// never re-derives the conformance from source. the entry is rooted by
|
|
478
562
|
// its package and source path so a hot reload finds the same instance.
|
|
479
563
|
return [
|
|
480
564
|
`import { createSwiftEntry } from 'rnxsim/swift'`,
|
|
481
|
-
`export default createSwiftEntry({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)}, rootId: ${JSON.stringify(entryRootId(packagePath, file))}, resources: ${JSON.stringify(resources)} })`,
|
|
565
|
+
`export default createSwiftEntry({ url: ${JSON.stringify(artifactUrl(project, artifact))}, hash: ${JSON.stringify(artifact.hash)}, rootId: ${JSON.stringify(entryRootId(packagePath, file))}, resources: ${JSON.stringify(resources)}, bundleFiles: ${JSON.stringify(bundleFiles)} })`,
|
|
482
566
|
'',
|
|
483
567
|
].join('\n')
|
|
484
568
|
},
|