rnxsim 0.1.525 → 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.
Files changed (59) hide show
  1. package/cli/outbound-endpoints.ts +15 -0
  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 +33 -4
  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 +37 -8
  52. package/dist-lib/swift-submit.cjs +92 -0
  53. package/dist-lib/swift-submit.mjs +65 -0
  54. package/dist-lib/swift.cjs +1 -1
  55. package/dist-lib/vite.cjs +211 -11
  56. package/package.json +8 -1
  57. package/src/rolldown-plugin-swift.ts +240 -0
  58. package/src/swift-submit.ts +185 -0
  59. package/src/vite-plugin-one.ts +10 -3
@@ -0,0 +1,240 @@
1
+ // `.swift` handling for One's native rolldown engine: submit the owning
2
+ // swiftpm package to `/api/swift-compile` and answer every `.swift` import
3
+ // with the module that mounts the published artifact.
4
+ //
5
+ // this is the remote twin of `vite-plugin-swift.ts`. that plugin runs the
6
+ // local toolchain and serves the artifact from dev-server middleware; the
7
+ // native engine is its own rolldown instance with no middleware seam, so
8
+ // this plugin compiles nothing in-process and serves nothing. the artifact
9
+ // url the service answers is absolute (compile-cdn), so there is nothing to
10
+ // serve. same compiler, same store, same hash, same credential as the
11
+ // browser worker — one path, no second toolchain to install for `one dev`.
12
+ //
13
+ // an app registers it through One's native plugin seam, not `config.plugins`
14
+ // (which never reach the native engine):
15
+ //
16
+ // // vite.config.ts
17
+ // export default {
18
+ // plugins: [one({
19
+ // native: {
20
+ // bundler: 'vite',
21
+ // bundlerOptions: {
22
+ // plugins: [swiftRemotePlugin({
23
+ // endpoint: 'https://contrast.dev',
24
+ // assetReadToken: process.env.CONTRAST_CCDN_KEY,
25
+ // })],
26
+ // },
27
+ // },
28
+ // })],
29
+ // }
30
+ //
31
+ // no babel anywhere in this path: resolveId and load only, emitting an ESM
32
+ // module. offline `one dev` fails loud at the submit, the same as a browser
33
+ // build without a credential; the standalone `rnx` CLI keeps the local
34
+ // toolchain plugin for that case.
35
+
36
+ import fs from 'node:fs'
37
+ import path from 'node:path'
38
+ import {
39
+ enumerateBundleImages,
40
+ enumerateBundleJson,
41
+ } from '../../sootsim-swift/src/bundleAssets'
42
+ import { compileSwiftProject, httpSwiftCompileSubmit } from './swift-submit'
43
+ import { entryRootId } from './vite-plugin-swift'
44
+ import type { SwiftModuleAddress } from './swift-submit'
45
+ import type { Plugin } from 'vite'
46
+
47
+ const VIRTUAL_PREFIX = '\0rnx-swift-remote:'
48
+
49
+ export interface SwiftRemotePluginOptions {
50
+ /** origin serving `/api/swift-compile` (the Contrast app). */
51
+ endpoint: string
52
+ /** a `ccdn_` API key or dev session token the compile verifier accepts. */
53
+ assetReadToken: string
54
+ /**
55
+ * directory uploaded `.swift` imports resolve under. defaults to the
56
+ * nearest `Package.swift` above each imported file — the swiftpm package
57
+ * that owns it — which is the compile unit, exactly like the local host.
58
+ * an app that keeps sources somewhere else names that package explicitly.
59
+ */
60
+ packagePath?: string
61
+ }
62
+
63
+ interface RemoteProject {
64
+ packagePath: string
65
+ address: SwiftModuleAddress | null
66
+ }
67
+
68
+ // one address per package per process: a failed submit keeps serving the
69
+ // last published artifact (and warns) rather than failing the module, so
70
+ // the edit that fixes the error still has a graph to rebuild.
71
+ const projects = new Map<string, RemoteProject>()
72
+
73
+ function projectFor(packagePath: string): RemoteProject {
74
+ const existing = projects.get(packagePath)
75
+ if (existing) return existing
76
+ const created: RemoteProject = { packagePath, address: null }
77
+ projects.set(packagePath, created)
78
+ return created
79
+ }
80
+
81
+ // every file under a directory, posix-relative, skipping what can never be
82
+ // a source, a manifest, or bundle content. a nested Package.swift is a
83
+ // separate package with its own compile: its files belong to that unit.
84
+ // `.build` holds tens of thousands of intermediates and no bundle content;
85
+ // walking it per import would tax every reload.
86
+ function packageFiles(root: string): string[] {
87
+ const found: string[] = []
88
+ const walk = (dir: string): void => {
89
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
90
+ if (
91
+ entry.name.startsWith('.') ||
92
+ entry.name === 'node_modules' ||
93
+ entry.name === '.build'
94
+ )
95
+ continue
96
+ const full = path.join(dir, entry.name)
97
+ if (entry.isDirectory()) {
98
+ if (full !== root && fs.existsSync(path.join(full, 'Package.swift'))) continue
99
+ walk(full)
100
+ } else {
101
+ found.push(path.relative(root, full).split(path.sep).join(path.posix.sep))
102
+ }
103
+ }
104
+ }
105
+ walk(root)
106
+ return found
107
+ }
108
+
109
+ // the file map the service compiles: the package's `.swift` sources plus
110
+ // its root manifest, keyed package-relative. the manifest is partitioning
111
+ // input only — the service emits its own manifest that links every target
112
+ // against its SwiftUI module — so a standard local package travels as is.
113
+ function sourceFiles(packagePath: string): Record<string, string> {
114
+ const files: Record<string, string> = {}
115
+ for (const relative of packageFiles(packagePath)) {
116
+ if (!relative.endsWith('.swift')) continue
117
+ files[relative] = fs.readFileSync(
118
+ path.join(packagePath, ...relative.split('/')),
119
+ 'utf8',
120
+ )
121
+ }
122
+ return files
123
+ }
124
+
125
+ // the bundle pack and the photo uploads, through the shared enumeration
126
+ // both hosts pack with, so the collision winner here is the winner on the
127
+ // local host too. binary bytes straight from disk: this host is node.
128
+ function bundlePack(packagePath: string): {
129
+ resources: Record<string, string>
130
+ images: Record<string, { file: string; data: string }>
131
+ watched: string[]
132
+ } {
133
+ const relative = packageFiles(packagePath)
134
+ const disk = (file: string): string => path.join(packagePath, ...file.split('/'))
135
+ const resources: Record<string, string> = {}
136
+ const watched: string[] = []
137
+ for (const { base, file } of enumerateBundleJson(relative)) {
138
+ resources[base] = fs.readFileSync(disk(file)).toString('base64')
139
+ watched.push(file)
140
+ }
141
+ const images: Record<string, { file: string; data: string }> = {}
142
+ const byRelative = new Map(relative.map((file) => [file, disk(file)]))
143
+ for (const { name, file } of enumerateBundleImages(relative, (candidate) => {
144
+ const absolute = byRelative.get(candidate)
145
+ if (!absolute || path.basename(absolute) !== 'Contents.json') return null
146
+ try {
147
+ return fs.readFileSync(absolute, 'utf8')
148
+ } catch {
149
+ return null
150
+ }
151
+ })) {
152
+ const absolute = byRelative.get(file)
153
+ if (!absolute) continue
154
+ images[name] = {
155
+ file: path.posix.basename(file),
156
+ data: fs.readFileSync(absolute).toString('base64'),
157
+ }
158
+ watched.push(file)
159
+ }
160
+ return { resources, images, watched }
161
+ }
162
+
163
+ // the swiftpm package that owns an imported file: the nearest directory at
164
+ // or above the file that holds a Package.swift. a `.swift` file with no
165
+ // package above it (a pod, an xcode target) is refused loud rather than
166
+ // compiled as the wrong unit.
167
+ function packageFor(file: string, stop?: string): string {
168
+ let dir = path.dirname(file)
169
+ for (;;) {
170
+ if (fs.existsSync(path.join(dir, 'Package.swift'))) return dir
171
+ if ((stop !== undefined && dir === stop) || path.dirname(dir) === dir) break
172
+ dir = path.dirname(dir)
173
+ }
174
+ throw new Error(
175
+ `no Package.swift above ${file}; a .swift import needs a swiftpm package like the one in packages/sootsim-swift/example`,
176
+ )
177
+ }
178
+
179
+ export function swiftRemotePlugin(options: SwiftRemotePluginOptions): Plugin {
180
+ const submit = httpSwiftCompileSubmit({
181
+ assetReadToken: options.assetReadToken,
182
+ endpoint: options.endpoint,
183
+ })
184
+ if (!submit) {
185
+ throw new Error(
186
+ 'swiftRemotePlugin needs an assetReadToken: a build without a compile credential cannot compile .swift sources',
187
+ )
188
+ }
189
+ return {
190
+ name: 'rnx-swift-remote',
191
+ enforce: 'pre',
192
+
193
+ async resolveId(source, importer) {
194
+ if (source.startsWith(VIRTUAL_PREFIX)) return source
195
+ if (!source.endsWith('.swift')) return null
196
+ const resolved = await this.resolve(source, importer, { skipSelf: true })
197
+ if (!resolved) throw new Error(`cannot resolve swift source ${source}`)
198
+ return VIRTUAL_PREFIX + resolved.id
199
+ },
200
+
201
+ async load(id) {
202
+ if (!id.startsWith(VIRTUAL_PREFIX)) return null
203
+ const file = id.slice(VIRTUAL_PREFIX.length)
204
+ if (!fs.existsSync(file)) throw new Error(`swift source ${file} no longer exists`)
205
+ const packagePath = options.packagePath ?? packageFor(file)
206
+ const project = projectFor(packagePath)
207
+ const files = sourceFiles(packagePath)
208
+ const entry = path.relative(packagePath, file).split(path.sep).join(path.posix.sep)
209
+ for (const source of Object.keys(files)) {
210
+ this.addWatchFile(path.join(packagePath, ...source.split('/')))
211
+ }
212
+ const pack = bundlePack(packagePath)
213
+ for (const watched of pack.watched) {
214
+ this.addWatchFile(path.join(packagePath, ...watched.split('/')))
215
+ }
216
+ try {
217
+ project.address = await compileSwiftProject(
218
+ submit,
219
+ { files, resources: pack.resources, images: pack.images, entry },
220
+ options.endpoint,
221
+ )
222
+ } catch (error) {
223
+ // a compile error must not fail this module: the graph would stop
224
+ // watching it and the fix would never rebuild. the last published
225
+ // artifact keeps rendering while the diagnostic is reported here.
226
+ if (!project.address) throw error
227
+ this.warn(error instanceof Error ? error.message : String(error))
228
+ }
229
+ const address = project.address
230
+ // every import mounts through the one entry: the artifact answers
231
+ // whether it is a view or an app root at runtime, so the transform
232
+ // never re-derives the conformance from source.
233
+ return [
234
+ `import { createSwiftEntry } from 'rnxsim/swift'`,
235
+ `export default createSwiftEntry({ url: ${JSON.stringify(address.url)}, hash: ${JSON.stringify(address.hash)}, rootId: ${JSON.stringify(entryRootId(packagePath, file))}, resources: ${JSON.stringify(address.imageUrls ?? {})}, bundleFiles: ${JSON.stringify(pack.resources)} })`,
236
+ '',
237
+ ].join('\n')
238
+ },
239
+ }
240
+ }
@@ -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
+ }
@@ -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 swift app
255
- // registers it in both the bundler that builds its native bundle and the main
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'