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.
Files changed (60) 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 +3 -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 +316 -23
  55. package/dist-lib/vite.cjs +280 -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
  60. 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
+ }
@@ -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'
@@ -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 { enumerateBundleImages } from '../../sootsim-swift/src/bundleAssets'
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
  },