peachsim 0.1.578 → 0.1.580
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/LICENSE +1 -1
- package/README.md +11 -9
- package/cli/app-catalog.ts +77 -0
- package/cli/app-fonts.ts +4 -20
- package/cli/app-splash.ts +2 -18
- package/cli/bridge-flow-runner.ts +505 -139
- package/cli/cloud-client.ts +1 -4
- package/cli/cloud-session.ts +1 -4
- package/cli/cloud-test-result-files.ts +3 -0
- package/cli/cloud-test-tunnel.ts +2 -5
- package/cli/command-registry.ts +2 -0
- package/cli/commands/agent.ts +149 -23
- package/cli/commands/assert.ts +1 -4
- package/cli/commands/box/peach-command.ts +62 -0
- package/cli/commands/box/plane-share.ts +0 -70
- package/cli/commands/box/rnx-command.ts +1 -60
- package/cli/commands/box/search-command.ts +14 -0
- package/cli/commands/box.ts +2 -2
- package/cli/commands/control.ts +121 -27
- package/cli/commands/cpu-profile.ts +23 -10
- package/cli/commands/daemon.ts +1 -9
- package/cli/commands/detox.ts +26 -18
- package/cli/commands/device.ts +129 -1
- package/cli/commands/film.ts +25 -16
- package/cli/commands/flow.ts +9 -2
- package/cli/commands/inspect/actions.ts +75 -1
- package/cli/commands/inspect/core.ts +81 -2
- package/cli/commands/inspect/describe.ts +2 -6
- package/cli/commands/inspect/env.ts +1 -1
- package/cli/commands/inspect/settling.ts +20 -3
- package/cli/commands/inspect/shared.ts +1 -4
- package/cli/commands/inspect/sleep.ts +1 -1
- package/cli/commands/inspect.ts +16 -49
- package/cli/commands/install-desktop.ts +8 -6
- package/cli/commands/login.ts +3 -5
- package/cli/commands/maestro-generate.ts +1 -4
- package/cli/commands/maestro.ts +38 -21
- package/cli/commands/no-bridge-hint.ts +0 -14
- package/cli/commands/permissions.ts +2 -2
- package/cli/commands/platform.ts +13 -6
- package/cli/commands/screenshot.ts +6 -1
- package/cli/commands/skills.ts +1 -1
- package/cli/commands/test.ts +6 -2
- package/cli/commands/three-mode.ts +162 -19
- package/cli/commands/upload.ts +3 -18
- package/cli/current-sim.ts +0 -1
- package/cli/desktop-companion.ts +40 -8
- package/cli/drivers/playwright-provisioning.ts +1 -4
- package/cli/flow-export.ts +1 -8
- package/cli/flow-session.ts +0 -4
- package/cli/hints.ts +1 -16
- package/cli/inspect-notice-state.ts +0 -4
- package/cli/main.ts +2 -4
- package/cli/outbound-endpoints.ts +1 -24
- package/cli/parse-args.ts +1 -2
- package/cli/rnx-in-process.ts +3 -1
- package/cli/run-rnx-core.ts +4 -2
- package/cli/run-rnx.ts +18 -10
- package/cli/send-to-box.ts +1 -4
- package/cli/telemetry.ts +1 -1
- package/cli/ws-bridge.ts +1 -5
- package/detox/gestures.ts +20 -6
- package/detox/index.ts +379 -123
- package/detox/navigation-lifecycle.ts +22 -13
- package/dist-lib/agent-daemon-client.cjs +21 -15
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +3 -17
- package/dist-lib/agent-sessions.cjs +5 -4
- package/dist-lib/attached-projects.cjs +1 -77
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +11 -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 -5
- package/dist-lib/bridge-contract-input.mjs +7 -5
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +30 -7
- package/dist-lib/capture-contract.mjs +30 -7
- 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-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +17 -11
- package/dist-lib/cloud.mjs +17 -11
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +321 -122
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +427 -212
- package/dist-lib/host/fetch-proxy-handler.cjs +5 -28
- 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 +315 -58
- 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 +67 -31
- package/dist-lib/menu.mjs +64 -28
- package/dist-lib/metro-fingerprint-registry.cjs +8 -4
- package/dist-lib/metro-fingerprint-registry.mjs +6 -4
- package/dist-lib/metro-production-bundle.cjs +2 -2
- package/dist-lib/metro-production-bundle.mjs +2 -2
- package/dist-lib/metro.cjs +8 -4
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +7 -4
- 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 +3 -13
- package/dist-lib/scripts/dev-server-scanner.cjs +5 -17
- package/dist-lib/sdk.cjs +129 -10
- package/dist-lib/sdk.mjs +127 -10
- package/dist-lib/skills.cjs +1635 -1121
- package/dist-lib/swift-compile.cjs +277 -70
- package/dist-lib/swift-compile.mjs +275 -70
- package/dist-lib/swift-package.cjs +17 -5
- package/dist-lib/swift-package.mjs +15 -4
- package/dist-lib/swift-submit.cjs +32 -8
- package/dist-lib/swift-submit.mjs +29 -7
- package/dist-lib/swift.cjs +30912 -577
- package/dist-lib/vite-kotlin.cjs +356 -0
- package/dist-lib/vite-swift.cjs +1743 -0
- package/dist-lib/vite.cjs +208 -1039
- package/kotlin/README.md +18 -0
- package/kotlin/Sources/androidx/annotation/Keep.kt +4 -0
- package/kotlin/Sources/dev/onejs/one/OneNativeSourceDispatch.kt +7 -0
- package/kotlin/Sources/dev/onejs/one/source/OneModule.kt +4 -0
- package/kotlin/Sources/org/json/JSON.kt +244 -0
- package/kotlin/toolchain.json +19 -0
- package/package.json +14 -2
- package/scripts/dev-server-scanner.ts +0 -18
- package/scripts/optional-demo-registry.ts +0 -17
- package/skills/contrast/SKILL.md +2 -2
- package/skills/peach-setup/SKILL.md +16 -0
- package/src/agent-daemon-client.ts +36 -22
- package/src/agent-identity.ts +0 -15
- package/src/agent-prompt.ts +12 -0
- package/src/attached-projects.ts +0 -72
- package/src/backend-origin.ts +10 -0
- package/src/bridge-contract-input.ts +2 -5
- package/src/bridge-contract.ts +2 -0
- package/src/capture-contract.ts +53 -11
- package/src/cloud.ts +4 -9
- package/src/dev-server-open.ts +0 -99
- package/src/home-paths.ts +2 -3
- package/src/host/agent-host.ts +82 -36
- package/src/host/bridge-host.ts +5 -11
- package/src/host/fetch-proxy-handler.ts +19 -43
- package/src/is-record.ts +4 -0
- package/src/menu.ts +82 -37
- package/src/metro-fingerprint-registry.ts +1 -4
- package/src/metro-production-bundle.ts +1 -1
- package/src/plist.ts +1 -4
- package/src/public-brand.ts +5 -3
- package/src/render-mode.ts +0 -12
- package/src/runtime-assets.ts +9 -2
- package/src/screenshots/compose.ts +7 -1
- package/src/screenshots/frame-compose.ts +21 -10
- package/src/screenshots/schema.ts +1 -4
- package/src/sim-client.ts +2 -4
- package/src/skills/builtin/maestro-test.ts +1 -4
- package/src/sleep.ts +4 -0
- package/src/swift-compile.ts +209 -47
- package/src/swift-icu.ts +140 -0
- package/src/swift-package.ts +26 -2
- package/src/swift-submit.ts +84 -4
- package/src/swift.ts +2 -0
- package/src/vite-plugin-kotlin.ts +376 -0
- package/src/vite-plugin-one.ts +224 -36
- package/src/vite-plugin-swift.ts +382 -96
- package/src/vite-plugin.ts +121 -134
- package/src/vite.d.ts +10 -0
- package/cli/resolve-assets.ts +0 -63
- package/src/rolldown-plugin-swift.ts +0 -244
- package/src/team-machine-prompt.ts +0 -73
package/src/vite-plugin-swift.ts
CHANGED
|
@@ -16,22 +16,119 @@ import fs from 'node:fs'
|
|
|
16
16
|
import os from 'node:os'
|
|
17
17
|
import path from 'node:path'
|
|
18
18
|
import {
|
|
19
|
+
enumerateBundleColors,
|
|
20
|
+
enumerateBundleSymbols,
|
|
19
21
|
enumerateBundleImages,
|
|
20
22
|
enumerateBundleJson,
|
|
23
|
+
type SwiftImageSource,
|
|
21
24
|
} from '../../sootsim-swift/src/bundleAssets'
|
|
22
25
|
import { swiftEntryModuleEsm } from '../../sootsim-swift/src/entryModule'
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
+
import { authHeaderValue, resolveCliAuth } from '../cli/auth'
|
|
27
|
+
import { resolveContrastBackendOrigin } from './backend-origin'
|
|
28
|
+
import {
|
|
29
|
+
compileSwiftProjectUnit,
|
|
30
|
+
withNativeSourceGlue,
|
|
31
|
+
swiftModuleInputFiles,
|
|
32
|
+
} from './swift-compile'
|
|
33
|
+
import {
|
|
34
|
+
compileSwiftProject,
|
|
35
|
+
httpSwiftCompileSubmit,
|
|
36
|
+
swiftNativeEntry,
|
|
37
|
+
type SwiftImageUpload,
|
|
38
|
+
type SwiftNativeModules,
|
|
39
|
+
} from './swift-submit'
|
|
40
|
+
import type { SwiftEntryModuleArgs } from '../../sootsim-swift/src/entryModule'
|
|
41
|
+
import type { SwiftCompileInput, SwiftIcuAsset } from './swift-compile'
|
|
42
|
+
import type { ServerResponse } from 'node:http'
|
|
43
|
+
import type { SfSymbolVector } from 'peach-engine/ios/sf-symbol-catalog'
|
|
44
|
+
import type { AppCatalogColor } from 'peach-engine/react-native/platform-colors'
|
|
26
45
|
import type { Plugin, ViteDevServer } from 'vite'
|
|
27
46
|
|
|
28
47
|
const ARTIFACT_PATH = '/__peach/swift/'
|
|
29
48
|
const RESOURCE_PATH = '/__peach/swift-res/'
|
|
49
|
+
const ICU_PATH = '/__peach/swift-icu/'
|
|
30
50
|
const VIRTUAL_PREFIX = '\0peach-swift:'
|
|
51
|
+
const SKIP_SOURCE_DIRS = new Set([
|
|
52
|
+
'node_modules',
|
|
53
|
+
'ios',
|
|
54
|
+
'android',
|
|
55
|
+
'dist',
|
|
56
|
+
'types',
|
|
57
|
+
'build',
|
|
58
|
+
])
|
|
59
|
+
const NATIVE_DESCRIPTOR = '.one-native-source.json'
|
|
60
|
+
const NATIVE_GLUE = '.one-native-source.swift.txt'
|
|
61
|
+
|
|
62
|
+
// vxrn's `swiftPackageId`: the id One's device registry and typegen name a
|
|
63
|
+
// package by.
|
|
64
|
+
function swiftPackageId(packagePath: string): string {
|
|
65
|
+
return path
|
|
66
|
+
.basename(packagePath)
|
|
67
|
+
.replace(/[^A-Za-z0-9_]/g, '_')
|
|
68
|
+
.replace(/^(\d)/, '_$1')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function nativeSourceDescriptor(packagePath: string): SwiftNativeModules | null {
|
|
72
|
+
const file = path.join(packagePath, NATIVE_DESCRIPTOR)
|
|
73
|
+
if (!fs.existsSync(file)) return null
|
|
74
|
+
const value: unknown = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
75
|
+
const invalid = () =>
|
|
76
|
+
new Error(`${file}: invalid One native source descriptor; run one typegen`)
|
|
77
|
+
if (
|
|
78
|
+
!value ||
|
|
79
|
+
typeof value !== 'object' ||
|
|
80
|
+
Reflect.get(value, 'generatedBy') !== 'one typegen' ||
|
|
81
|
+
!fs.existsSync(path.join(packagePath, NATIVE_GLUE))
|
|
82
|
+
) {
|
|
83
|
+
throw invalid()
|
|
84
|
+
}
|
|
85
|
+
const packageId = Reflect.get(value, 'packageId')
|
|
86
|
+
const hash = Reflect.get(value, 'hash')
|
|
87
|
+
const rawFiles: unknown = Reflect.get(value, 'files')
|
|
88
|
+
const rawModules: unknown = Reflect.get(value, 'modules')
|
|
89
|
+
const rawDefaults: unknown = Reflect.get(value, 'defaultViewFiles')
|
|
90
|
+
if (
|
|
91
|
+
typeof packageId !== 'string' ||
|
|
92
|
+
typeof hash !== 'string' ||
|
|
93
|
+
!rawFiles ||
|
|
94
|
+
typeof rawFiles !== 'object' ||
|
|
95
|
+
!rawModules ||
|
|
96
|
+
typeof rawModules !== 'object' ||
|
|
97
|
+
!Array.isArray(rawDefaults) ||
|
|
98
|
+
!rawDefaults.every((name) => typeof name === 'string')
|
|
99
|
+
) {
|
|
100
|
+
throw invalid()
|
|
101
|
+
}
|
|
102
|
+
const files: Record<string, string[]> = {}
|
|
103
|
+
const modules: Record<string, string[]> = {}
|
|
104
|
+
for (const [key, names] of Object.entries(rawFiles)) {
|
|
105
|
+
if (!Array.isArray(names) || !names.every((name) => typeof name === 'string'))
|
|
106
|
+
throw invalid()
|
|
107
|
+
files[key] = names
|
|
108
|
+
}
|
|
109
|
+
for (const [key, names] of Object.entries(rawModules)) {
|
|
110
|
+
if (!Array.isArray(names) || !names.every((name) => typeof name === 'string'))
|
|
111
|
+
throw invalid()
|
|
112
|
+
modules[key] = names
|
|
113
|
+
}
|
|
114
|
+
if (packageId !== swiftPackageId(packagePath)) {
|
|
115
|
+
throw new Error(`${file}: invalid One native source descriptor; run one typegen`)
|
|
116
|
+
}
|
|
117
|
+
return { packageId, hash, files, modules, defaultViewFiles: rawDefaults }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function nativeSourceFiles(packagePath: string): string[] {
|
|
121
|
+
return [NATIVE_DESCRIPTOR, NATIVE_GLUE]
|
|
122
|
+
.map((name) => path.join(packagePath, name))
|
|
123
|
+
.filter((file) => fs.existsSync(file))
|
|
124
|
+
}
|
|
31
125
|
|
|
32
126
|
interface SwiftArtifact {
|
|
33
127
|
hash: string
|
|
34
128
|
bytes: Buffer
|
|
129
|
+
// the icu common data the artifact reserves: built here and served from
|
|
130
|
+
// this server, or already published by the Contrast compile that built it
|
|
131
|
+
icu: SwiftIcuAsset | { url: string }
|
|
35
132
|
// the sources this artifact was built from. a watcher event and a `load`
|
|
36
133
|
// reach the project in either order, so freshness is read from the sources
|
|
37
134
|
// rather than from whichever of them ran first.
|
|
@@ -74,14 +171,18 @@ function sourceFiles(root: string): string[] {
|
|
|
74
171
|
const found: string[] = []
|
|
75
172
|
const walk = (dir: string): void => {
|
|
76
173
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
77
|
-
if (entry.name.startsWith('.') || entry.name
|
|
174
|
+
if (entry.name.startsWith('.') || SKIP_SOURCE_DIRS.has(entry.name)) continue
|
|
78
175
|
const full = path.join(dir, entry.name)
|
|
79
176
|
// a nested Package.swift is a separate package with its own compile:
|
|
80
177
|
// its sources belong to that unit's stamp, not this one's
|
|
81
178
|
if (entry.isDirectory()) {
|
|
82
179
|
if (full !== root && fs.existsSync(path.join(full, 'Package.swift'))) continue
|
|
83
180
|
walk(full)
|
|
84
|
-
} else if (
|
|
181
|
+
} else if (
|
|
182
|
+
entry.name.endsWith('.swift') &&
|
|
183
|
+
(entry.isFile() || (entry.isSymbolicLink() && fs.statSync(full).isFile()))
|
|
184
|
+
)
|
|
185
|
+
found.push(full)
|
|
85
186
|
}
|
|
86
187
|
}
|
|
87
188
|
walk(root)
|
|
@@ -98,7 +199,7 @@ function resourceCandidateFiles(root: string): string[] {
|
|
|
98
199
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
99
200
|
if (
|
|
100
201
|
entry.name.startsWith('.') ||
|
|
101
|
-
entry.name
|
|
202
|
+
SKIP_SOURCE_DIRS.has(entry.name) ||
|
|
102
203
|
entry.name === '.build'
|
|
103
204
|
)
|
|
104
205
|
continue
|
|
@@ -127,11 +228,14 @@ const RESOURCE_CONTENT_TYPES: Record<string, string> = {
|
|
|
127
228
|
}
|
|
128
229
|
|
|
129
230
|
export interface SwiftResourceTable {
|
|
130
|
-
/** bundle asset name to absolute serving url. */
|
|
131
|
-
table: Record<string,
|
|
231
|
+
/** bundle asset name to its absolute serving url and scale. */
|
|
232
|
+
table: Record<string, SwiftImageSource>
|
|
132
233
|
/** the same rasters as compile-address inputs. */
|
|
133
234
|
images: Record<string, SwiftImageUpload>
|
|
134
|
-
/**
|
|
235
|
+
/** asset-catalog color name to its value. */
|
|
236
|
+
colors: Record<string, AppCatalogColor>
|
|
237
|
+
symbols: Record<string, SfSymbolVector>
|
|
238
|
+
/** project-relative image and color files this load's table covers (for watching). */
|
|
135
239
|
watched: string[]
|
|
136
240
|
}
|
|
137
241
|
|
|
@@ -148,20 +252,25 @@ export function buildSwiftResourceTable(
|
|
|
148
252
|
path.relative(project.packagePath, file).split(path.sep).join(path.posix.sep),
|
|
149
253
|
)
|
|
150
254
|
const byRelative = new Map(absolute.map((file, index) => [relative[index], file]))
|
|
151
|
-
const
|
|
255
|
+
const readContents = (candidate: string): string | null => {
|
|
152
256
|
const file = byRelative.get(candidate)
|
|
153
|
-
|
|
257
|
+
// catalog json, and the glyph template a symbolset names
|
|
258
|
+
if (!file || !/(^|\/)Contents\.json$|\.symbolset\/[^/]+\.svg$/.test(candidate))
|
|
259
|
+
return null
|
|
154
260
|
try {
|
|
155
261
|
return fs.readFileSync(file, 'utf8')
|
|
156
262
|
} catch {
|
|
157
263
|
return null
|
|
158
264
|
}
|
|
159
|
-
}
|
|
160
|
-
const
|
|
265
|
+
}
|
|
266
|
+
const found = enumerateBundleImages(relative, readContents)
|
|
267
|
+
const colors = enumerateBundleColors(relative, readContents)
|
|
268
|
+
const symbols = enumerateBundleSymbols(relative, readContents)
|
|
269
|
+
const table: Record<string, SwiftImageSource> = {}
|
|
161
270
|
const images: Record<string, SwiftImageUpload> = {}
|
|
162
271
|
const watched: string[] = []
|
|
163
272
|
const registered = new Map<string, string>()
|
|
164
|
-
for (const { name, file } of found) {
|
|
273
|
+
for (const { name, file, scale } of found) {
|
|
165
274
|
const disk = byRelative.get(file)
|
|
166
275
|
if (!disk) continue
|
|
167
276
|
let bytes: Buffer
|
|
@@ -172,7 +281,7 @@ export function buildSwiftResourceTable(
|
|
|
172
281
|
}
|
|
173
282
|
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 16)
|
|
174
283
|
const ext = path.posix.extname(file).toLowerCase()
|
|
175
|
-
table[name] = `${baseUrl}${RESOURCE_PATH}${hash}${ext}
|
|
284
|
+
table[name] = { uri: `${baseUrl}${RESOURCE_PATH}${hash}${ext}`, scale }
|
|
176
285
|
images[name] = {
|
|
177
286
|
file: path.posix.basename(file),
|
|
178
287
|
data: bytes.toString('base64'),
|
|
@@ -184,22 +293,28 @@ export function buildSwiftResourceTable(
|
|
|
184
293
|
if (byRelative.has(contents)) watched.push(contents)
|
|
185
294
|
registered.set(`${hash}${ext}`, disk)
|
|
186
295
|
}
|
|
296
|
+
// a colorset or symbolset edit re-resolves like an imageset edit.
|
|
297
|
+
for (const file of relative) {
|
|
298
|
+
if (file.endsWith('.colorset/Contents.json') || file.includes('.symbolset/'))
|
|
299
|
+
watched.push(file)
|
|
300
|
+
}
|
|
187
301
|
project.resourceFiles = registered
|
|
188
|
-
return { table, images, watched }
|
|
302
|
+
return { table, images, colors, symbols, watched }
|
|
189
303
|
}
|
|
190
304
|
|
|
191
305
|
// the /res pack: every Resources/ json in the package, basename-keyed and
|
|
192
306
|
// base64, riding the entry module so the mount serves it without a fetch.
|
|
193
307
|
// the namespace is flat like the bundle the wasm side reads: shallower
|
|
194
308
|
// paths pack first (the Resources root is the bundle root) with a lexical
|
|
195
|
-
// tiebreak, and the first spelling of a basename wins a collision.
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
309
|
+
// tiebreak, and the first spelling of a basename wins a collision. data json
|
|
310
|
+
// runs tens of KB, but a string catalog carries every language it ships (a
|
|
311
|
+
// 19-language app catalog measured 2.2MB), which sets the per-file cap: a
|
|
312
|
+
// file past a cap is skipped and named in `skipped` so the load warns
|
|
313
|
+
// instead of silently serving a partial bundle. binary bytes travel the same
|
|
314
|
+
// map when a port reaches past json; the worker half packs text only until
|
|
315
|
+
// then (see the gaps ledger).
|
|
201
316
|
const BUNDLE_MAX_FILES = 128
|
|
202
|
-
const BUNDLE_MAX_FILE_BYTES =
|
|
317
|
+
const BUNDLE_MAX_FILE_BYTES = 4 * 1024 * 1024
|
|
203
318
|
const BUNDLE_MAX_TOTAL_BYTES = 8 * 1024 * 1024
|
|
204
319
|
|
|
205
320
|
export interface SwiftBundlePack {
|
|
@@ -295,12 +410,13 @@ export function linkedModuleRoots(packagePath: string): string[] {
|
|
|
295
410
|
// signal hashes mtimes while that stable address hashes content. a checkout
|
|
296
411
|
// restoring byte-identical files rebuilds here and reuses the same address
|
|
297
412
|
// there, by design.
|
|
298
|
-
export function sourceStamp(packagePath: string): string {
|
|
413
|
+
export function sourceStamp(packagePath: string, moduleRootOverride?: string): string {
|
|
299
414
|
const hash = createHash('sha256')
|
|
300
415
|
hash.update(packagePath)
|
|
301
416
|
const projectInputs = new Set([
|
|
302
417
|
path.join(packagePath, 'Package.swift'),
|
|
303
418
|
...sourceFiles(packagePath),
|
|
419
|
+
...nativeSourceFiles(packagePath),
|
|
304
420
|
...buildSwiftBundleFiles(packagePath).watched.map((file) =>
|
|
305
421
|
path.join(packagePath, ...file.split(path.posix.sep)),
|
|
306
422
|
),
|
|
@@ -312,15 +428,14 @@ export function sourceStamp(packagePath: string): string {
|
|
|
312
428
|
hash.update(path.relative(packagePath, file))
|
|
313
429
|
hash.update(String(fs.statSync(file).mtimeMs))
|
|
314
430
|
}
|
|
315
|
-
const moduleRoot =
|
|
316
|
-
|
|
317
|
-
|
|
431
|
+
const moduleRoot =
|
|
432
|
+
moduleRootOverride ??
|
|
433
|
+
linkedModuleRoots(packagePath).find((root) =>
|
|
434
|
+
fs.existsSync(path.join(root, 'toolchain.json')),
|
|
435
|
+
)
|
|
318
436
|
if (moduleRoot) {
|
|
319
437
|
hash.update(moduleRoot)
|
|
320
|
-
for (const relative of
|
|
321
|
-
...swiftModuleInputFiles(moduleRoot),
|
|
322
|
-
'toolchain.json',
|
|
323
|
-
].sort()) {
|
|
438
|
+
for (const relative of swiftModuleInputFiles(moduleRoot)) {
|
|
324
439
|
hash.update(relative)
|
|
325
440
|
hash.update(String(fs.statSync(path.join(moduleRoot, relative)).mtimeMs))
|
|
326
441
|
}
|
|
@@ -353,7 +468,13 @@ function artifactUrl(project: SwiftProject, artifact: SwiftArtifact): string {
|
|
|
353
468
|
return `${serverOrigin()}${ARTIFACT_PATH}${artifact.hash}.wasm`
|
|
354
469
|
}
|
|
355
470
|
|
|
356
|
-
function
|
|
471
|
+
function icuUrl(origin: string, artifact: SwiftArtifact): string {
|
|
472
|
+
return 'url' in artifact.icu
|
|
473
|
+
? artifact.icu.url
|
|
474
|
+
: `${origin}${ICU_PATH}${artifact.icu.sha256}`
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function packageSources(packagePath: string): Record<string, string> {
|
|
357
478
|
const files: Record<string, string> = {}
|
|
358
479
|
for (const file of sourceFiles(packagePath)) {
|
|
359
480
|
const relative = path.relative(packagePath, file).split(path.sep).join(path.posix.sep)
|
|
@@ -361,29 +482,37 @@ function compileInput(packagePath: string, entry: string): SwiftCompileInput {
|
|
|
361
482
|
}
|
|
362
483
|
const manifest = path.join(packagePath, 'Package.swift')
|
|
363
484
|
if (fs.existsSync(manifest)) files['Package.swift'] = fs.readFileSync(manifest, 'utf8')
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
485
|
+
return files
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function compileInput(packagePath: string, entry: string): SwiftCompileInput {
|
|
489
|
+
const input: SwiftCompileInput = {
|
|
490
|
+
files: packageSources(packagePath),
|
|
491
|
+
resources: buildSwiftBundleFiles(packagePath).files,
|
|
492
|
+
images: buildSwiftResourceTable(projectFor(packagePath), '').images,
|
|
370
493
|
entry,
|
|
494
|
+
oneModule: true,
|
|
371
495
|
}
|
|
496
|
+
return nativeSourceDescriptor(packagePath)
|
|
497
|
+
? withNativeSourceGlue(
|
|
498
|
+
input,
|
|
499
|
+
fs.readFileSync(path.join(packagePath, NATIVE_GLUE), 'utf8'),
|
|
500
|
+
)
|
|
501
|
+
: input
|
|
372
502
|
}
|
|
373
503
|
|
|
374
504
|
async function compile(
|
|
375
505
|
packagePath: string,
|
|
376
506
|
entry: string,
|
|
377
507
|
stamp: string,
|
|
508
|
+
moduleRootOverride?: string,
|
|
378
509
|
): Promise<SwiftArtifact> {
|
|
379
|
-
const moduleRoot =
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
throw new Error(
|
|
384
|
-
`swift project ${packagePath} has no linked rnx SwiftUI package with toolchain.json`,
|
|
510
|
+
const moduleRoot =
|
|
511
|
+
moduleRootOverride ??
|
|
512
|
+
linkedModuleRoots(packagePath).find((root) =>
|
|
513
|
+
fs.existsSync(path.join(root, 'toolchain.json')),
|
|
385
514
|
)
|
|
386
|
-
}
|
|
515
|
+
if (!moduleRoot) return { ...(await compileRemote(packagePath, entry)), stamp }
|
|
387
516
|
const toolchain: unknown = JSON.parse(
|
|
388
517
|
fs.readFileSync(path.join(moduleRoot, 'toolchain.json'), 'utf8'),
|
|
389
518
|
)
|
|
@@ -395,9 +524,21 @@ async function compile(
|
|
|
395
524
|
throw new Error(`swift toolchain manifest at ${moduleRoot} has no release`)
|
|
396
525
|
}
|
|
397
526
|
const artifact = await compileSwiftProjectUnit(compileInput(packagePath, entry), {
|
|
527
|
+
// the compile rewrites Sources/App in its root before swiftpm builds, and
|
|
528
|
+
// its lock orders one process only: two vites on one root (a lane's dev
|
|
529
|
+
// server and a capture, or two checkouts) build each other's sources. so
|
|
530
|
+
// each app and module checkout gets its own root, which keeps the same
|
|
531
|
+
// app's incremental build across restarts.
|
|
398
532
|
buildRoot:
|
|
399
533
|
process.env.CONTRAST_SWIFT_BUILD_ROOT ??
|
|
400
|
-
path.join(
|
|
534
|
+
path.join(
|
|
535
|
+
os.tmpdir(),
|
|
536
|
+
'contrast-swift-build',
|
|
537
|
+
createHash('sha256')
|
|
538
|
+
.update(`${moduleRoot}\0${path.resolve(packagePath)}`)
|
|
539
|
+
.digest('hex')
|
|
540
|
+
.slice(0, 16),
|
|
541
|
+
),
|
|
401
542
|
moduleRoot,
|
|
402
543
|
release,
|
|
403
544
|
toolchainBin: path.join(
|
|
@@ -408,8 +549,64 @@ async function compile(
|
|
|
408
549
|
return { ...artifact, stamp }
|
|
409
550
|
}
|
|
410
551
|
|
|
411
|
-
|
|
412
|
-
|
|
552
|
+
// an npm-installed peachsim ships neither the SwiftUI module nor a toolchain,
|
|
553
|
+
// so the package compiles where a Contrast project's does: Contrast's compile
|
|
554
|
+
// route, authorized by `peach login` or PEACH_API_KEY. the service renders the
|
|
555
|
+
// native dispatch glue itself under the package's id and answers the wasm at a
|
|
556
|
+
// public content address, which is fetched once and served from this dev
|
|
557
|
+
// server like a local build. bundle images, colors and json stay local.
|
|
558
|
+
async function compileRemote(
|
|
559
|
+
packagePath: string,
|
|
560
|
+
entry: string,
|
|
561
|
+
): Promise<Omit<SwiftArtifact, 'stamp'>> {
|
|
562
|
+
const auth = resolveCliAuth()
|
|
563
|
+
if (!auth || auth.kind === 'github') {
|
|
564
|
+
throw new Error(
|
|
565
|
+
`compiling ${path.basename(packagePath)} for Peach needs \`peach login\` or PEACH_API_KEY: this peachsim has no local SwiftUI module, so Contrast compiles it`,
|
|
566
|
+
)
|
|
567
|
+
}
|
|
568
|
+
const origin = resolveContrastBackendOrigin(
|
|
569
|
+
auth.kind === 'session' ? auth.origin : null,
|
|
570
|
+
)
|
|
571
|
+
const submit = httpSwiftCompileSubmit({
|
|
572
|
+
endpoint: origin,
|
|
573
|
+
authorization: authHeaderValue(auth),
|
|
574
|
+
})
|
|
575
|
+
if (!submit) throw new Error('swift compile: no credential to submit with')
|
|
576
|
+
const descriptor = nativeSourceDescriptor(packagePath)
|
|
577
|
+
const address = await compileSwiftProject(
|
|
578
|
+
submit,
|
|
579
|
+
{
|
|
580
|
+
files: packageSources(packagePath),
|
|
581
|
+
entry,
|
|
582
|
+
packageId: descriptor?.packageId ?? swiftPackageId(packagePath),
|
|
583
|
+
},
|
|
584
|
+
origin,
|
|
585
|
+
)
|
|
586
|
+
// the app's JS calls with the hash its own `one typegen` wrote; glue the
|
|
587
|
+
// service rendered from another One would reject every call as stale.
|
|
588
|
+
if (descriptor && address.native?.hash !== descriptor.hash) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`${path.basename(packagePath)}: Contrast compiled native contract ${address.native?.hash ?? 'none'} but one typegen wrote ${descriptor.hash}; the app's One and the compile host's differ, or the compile host is older than this peachsim`,
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
const response = await fetch(address.url)
|
|
594
|
+
if (!response.ok) {
|
|
595
|
+
throw new Error(`swift compile: ${address.url} answered ${response.status}`)
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
hash: address.hash,
|
|
599
|
+
bytes: Buffer.from(await response.arrayBuffer()),
|
|
600
|
+
icu: { url: address.icuUrl },
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function artifactFor(
|
|
605
|
+
project: SwiftProject,
|
|
606
|
+
entry: string,
|
|
607
|
+
moduleRoot?: string,
|
|
608
|
+
): Promise<SwiftArtifact> {
|
|
609
|
+
const stamp = sourceStamp(project.packagePath, moduleRoot)
|
|
413
610
|
if (project.artifact?.stamp === stamp) return Promise.resolve(project.artifact)
|
|
414
611
|
if (project.building) {
|
|
415
612
|
// a second edit during a build: the build in flight was started from
|
|
@@ -418,10 +615,10 @@ function artifactFor(project: SwiftProject, entry: string): Promise<SwiftArtifac
|
|
|
418
615
|
// would emit the older artifact and nothing would trigger another load.
|
|
419
616
|
const running = project.building
|
|
420
617
|
if (running.stamp === stamp) return running.promise
|
|
421
|
-
const again = () => artifactFor(project, entry)
|
|
618
|
+
const again = () => artifactFor(project, entry, moduleRoot)
|
|
422
619
|
return running.promise.then(again, again)
|
|
423
620
|
}
|
|
424
|
-
const promise = compile(project.packagePath, entry, stamp).then(
|
|
621
|
+
const promise = compile(project.packagePath, entry, stamp, moduleRoot).then(
|
|
425
622
|
(artifact) => {
|
|
426
623
|
project.artifact = artifact
|
|
427
624
|
project.building = null
|
|
@@ -436,6 +633,111 @@ function artifactFor(project: SwiftProject, entry: string): Promise<SwiftArtifac
|
|
|
436
633
|
return promise
|
|
437
634
|
}
|
|
438
635
|
|
|
636
|
+
export function swiftPackageWatchFiles(
|
|
637
|
+
packagePath: string,
|
|
638
|
+
moduleRoot: string | undefined,
|
|
639
|
+
): string[] {
|
|
640
|
+
return [
|
|
641
|
+
path.join(packagePath, 'Package.swift'),
|
|
642
|
+
...sourceFiles(packagePath),
|
|
643
|
+
...nativeSourceFiles(packagePath),
|
|
644
|
+
...resourceCandidateFiles(packagePath),
|
|
645
|
+
...(moduleRoot
|
|
646
|
+
? swiftModuleInputFiles(moduleRoot).map((file) => path.join(moduleRoot, file))
|
|
647
|
+
: []),
|
|
648
|
+
]
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export async function swiftPackageMetadata(
|
|
652
|
+
packagePath: string,
|
|
653
|
+
packageName: string,
|
|
654
|
+
moduleRoot: string | undefined,
|
|
655
|
+
origin: string,
|
|
656
|
+
): Promise<SwiftEntryModuleArgs> {
|
|
657
|
+
const mains = sourceFiles(packagePath).filter((file) =>
|
|
658
|
+
/@main\s+(?:public\s+)?struct\s+\w+\s*:/.test(fs.readFileSync(file, 'utf8')),
|
|
659
|
+
)
|
|
660
|
+
const native = nativeSourceDescriptor(packagePath)
|
|
661
|
+
if (mains.length > 1 || (mains.length === 0 && !native)) {
|
|
662
|
+
throw new Error(
|
|
663
|
+
`swift package ${packageName} needs exactly one @main source or generated native modules; found ${mains.length}`,
|
|
664
|
+
)
|
|
665
|
+
}
|
|
666
|
+
const project = projectFor(packagePath)
|
|
667
|
+
const entry =
|
|
668
|
+
mains.length === 1
|
|
669
|
+
? path.relative(packagePath, mains[0]).split(path.sep).join(path.posix.sep)
|
|
670
|
+
: Object.entries(native?.files ?? {}).find(
|
|
671
|
+
([_file, names]) => names.length > 0,
|
|
672
|
+
)?.[0]
|
|
673
|
+
if (!entry) throw new Error(`swift package ${packageName} has no native module source`)
|
|
674
|
+
const artifact = await artifactFor(project, entry, moduleRoot)
|
|
675
|
+
const resources = buildSwiftResourceTable(project, origin)
|
|
676
|
+
const bundleFiles = buildSwiftBundleFiles(packagePath)
|
|
677
|
+
return {
|
|
678
|
+
url: `${origin}${ARTIFACT_PATH}${artifact.hash}.wasm`,
|
|
679
|
+
hash: artifact.hash,
|
|
680
|
+
icuUrl: icuUrl(origin, artifact),
|
|
681
|
+
rootId: `${packageName}/${entry}`,
|
|
682
|
+
resources: resources.table,
|
|
683
|
+
bundleFiles: bundleFiles.files,
|
|
684
|
+
colors: resources.colors,
|
|
685
|
+
symbols: resources.symbols,
|
|
686
|
+
native: native
|
|
687
|
+
? {
|
|
688
|
+
packageId: native.packageId,
|
|
689
|
+
contractHash: native.hash,
|
|
690
|
+
modules: native.modules,
|
|
691
|
+
defaultView: mains.length === 1,
|
|
692
|
+
}
|
|
693
|
+
: undefined,
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export function serveSwiftPackageAsset(pathname: string, res: ServerResponse): boolean {
|
|
698
|
+
if (pathname.startsWith(RESOURCE_PATH)) {
|
|
699
|
+
for (const project of projects.values()) {
|
|
700
|
+
const served = serveSwiftResource(project, pathname.slice(RESOURCE_PATH.length))
|
|
701
|
+
if (!served) continue
|
|
702
|
+
res.setHeader('access-control-allow-origin', '*')
|
|
703
|
+
res.setHeader('content-type', served.contentType)
|
|
704
|
+
res.end(served.bytes)
|
|
705
|
+
return true
|
|
706
|
+
}
|
|
707
|
+
res.statusCode = 404
|
|
708
|
+
res.end('swift resource is stale')
|
|
709
|
+
return true
|
|
710
|
+
}
|
|
711
|
+
if (pathname.startsWith(ICU_PATH)) {
|
|
712
|
+
for (const project of projects.values()) {
|
|
713
|
+
const icu = project.artifact?.icu
|
|
714
|
+
if (!icu || 'url' in icu || pathname !== `${ICU_PATH}${icu.sha256}`) continue
|
|
715
|
+
// the address is the content, so the browser keeps it across reloads,
|
|
716
|
+
// restarts and every app built with this toolchain
|
|
717
|
+
res.setHeader('access-control-allow-origin', '*')
|
|
718
|
+
res.setHeader('content-type', 'application/gzip')
|
|
719
|
+
res.setHeader('cache-control', 'public, max-age=31536000, immutable')
|
|
720
|
+
res.end(icu.gzip)
|
|
721
|
+
return true
|
|
722
|
+
}
|
|
723
|
+
res.statusCode = 404
|
|
724
|
+
res.end('swift icu data is stale')
|
|
725
|
+
return true
|
|
726
|
+
}
|
|
727
|
+
if (!pathname.startsWith(ARTIFACT_PATH)) return false
|
|
728
|
+
for (const project of projects.values()) {
|
|
729
|
+
const artifact = project.artifact
|
|
730
|
+
if (!artifact || pathname !== `${ARTIFACT_PATH}${artifact.hash}.wasm`) continue
|
|
731
|
+
res.setHeader('access-control-allow-origin', '*')
|
|
732
|
+
res.setHeader('content-type', 'application/wasm')
|
|
733
|
+
res.end(artifact.bytes)
|
|
734
|
+
return true
|
|
735
|
+
}
|
|
736
|
+
res.statusCode = 404
|
|
737
|
+
res.end('swift artifact is stale')
|
|
738
|
+
return true
|
|
739
|
+
}
|
|
740
|
+
|
|
439
741
|
// the swiftpm package that owns an imported file: the nearest directory at
|
|
440
742
|
// or above the file that holds a Package.swift, stopping at the vite root
|
|
441
743
|
// once the dev server has named it. a swift-only project keeps its sources at
|
|
@@ -481,7 +783,20 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
481
783
|
if (!source.endsWith('.swift')) return null
|
|
482
784
|
const resolved = await this.resolve(source, importer, { skipSelf: true })
|
|
483
785
|
if (!resolved) throw new Error(`cannot resolve swift source ${source}`)
|
|
484
|
-
|
|
786
|
+
// a symlinked source (the showcase app links the files Xcode builds)
|
|
787
|
+
// resolves to its target, and the package walk then finds the target's
|
|
788
|
+
// package instead of the importing one. keep the importer's own path
|
|
789
|
+
// when it names the same file, so the import's package owns the compile.
|
|
790
|
+
let id = resolved.id
|
|
791
|
+
if (importer && (source.startsWith('./') || source.startsWith('../'))) {
|
|
792
|
+
const raw = path.resolve(path.dirname(importer.split('?')[0] ?? importer), source)
|
|
793
|
+
try {
|
|
794
|
+
if (fs.realpathSync(raw) === fs.realpathSync(resolved.id)) id = raw
|
|
795
|
+
} catch {
|
|
796
|
+
// fall through with the resolver's answer
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return VIRTUAL_PREFIX + id
|
|
485
800
|
},
|
|
486
801
|
|
|
487
802
|
async load(id) {
|
|
@@ -498,7 +813,11 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
498
813
|
// bundler watching this module learns about a `.swift` edit and re-runs
|
|
499
814
|
// load. that is the whole hot reload: the new hash in the emitted module
|
|
500
815
|
// is what tells the mount it is looking at a different app.
|
|
501
|
-
for (const source of
|
|
816
|
+
for (const source of [
|
|
817
|
+
...sourceFiles(packagePath),
|
|
818
|
+
...nativeSourceFiles(packagePath),
|
|
819
|
+
])
|
|
820
|
+
this.addWatchFile(source)
|
|
502
821
|
this.addWatchFile(path.join(packagePath, 'Package.swift'))
|
|
503
822
|
// an edit to the linked SwiftUI module rebuilds the artifact without
|
|
504
823
|
// touching the project's own sources, so those files gate the reload
|
|
@@ -523,10 +842,14 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
523
842
|
// the bundle image table rides the same module: a photo edit moves
|
|
524
843
|
// its urls without rebuilding the artifact, and the watched files
|
|
525
844
|
// re-run this load so the mount sees the new table on hot reload.
|
|
526
|
-
let resources: Record<string,
|
|
845
|
+
let resources: Record<string, SwiftImageSource> = {}
|
|
846
|
+
let colors: Record<string, AppCatalogColor> = {}
|
|
847
|
+
let symbols: Record<string, SfSymbolVector> = {}
|
|
527
848
|
try {
|
|
528
849
|
const built = buildSwiftResourceTable(project, serverOrigin())
|
|
529
850
|
resources = built.table
|
|
851
|
+
colors = built.colors
|
|
852
|
+
symbols = built.symbols
|
|
530
853
|
for (const watched of built.watched) {
|
|
531
854
|
this.addWatchFile(path.join(packagePath, ...watched.split(path.posix.sep)))
|
|
532
855
|
}
|
|
@@ -559,12 +882,17 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
559
882
|
// whether it is a view or an app root at runtime, so the transform
|
|
560
883
|
// never re-derives the conformance from source. the entry is rooted by
|
|
561
884
|
// its package and source path so a hot reload finds the same instance.
|
|
885
|
+
const native = nativeSourceDescriptor(packagePath)
|
|
562
886
|
return swiftEntryModuleEsm({
|
|
563
887
|
url: artifactUrl(project, artifact),
|
|
564
888
|
hash: artifact.hash,
|
|
889
|
+
icuUrl: icuUrl(serverOrigin(), artifact),
|
|
565
890
|
rootId: entryRootId(packagePath, file),
|
|
566
891
|
resources,
|
|
567
892
|
bundleFiles,
|
|
893
|
+
colors,
|
|
894
|
+
symbols,
|
|
895
|
+
native: native ? swiftNativeEntry(native, entry) : undefined,
|
|
568
896
|
})
|
|
569
897
|
},
|
|
570
898
|
|
|
@@ -572,49 +900,7 @@ export function swiftPlugin(options: SwiftPluginOptions = {}): Plugin {
|
|
|
572
900
|
devServer = server
|
|
573
901
|
|
|
574
902
|
server.middlewares.use((req, res, next) => {
|
|
575
|
-
|
|
576
|
-
if (url.startsWith(RESOURCE_PATH)) {
|
|
577
|
-
// hash-addressed: the table this load emitted names these hashes,
|
|
578
|
-
// so a hit serves known bytes and anything else is a 404, never a
|
|
579
|
-
// neighboring file. the tenant worker is cross-origin like the
|
|
580
|
-
// artifact fetch, hence the same cors header.
|
|
581
|
-
for (const project of projects.values()) {
|
|
582
|
-
const served = serveSwiftResource(project, url.slice(RESOURCE_PATH.length))
|
|
583
|
-
if (served) {
|
|
584
|
-
res.setHeader('access-control-allow-origin', '*')
|
|
585
|
-
res.setHeader('content-type', served.contentType)
|
|
586
|
-
res.end(served.bytes)
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
res.statusCode = 404
|
|
591
|
-
res.end('swift resource is stale')
|
|
592
|
-
return
|
|
593
|
-
}
|
|
594
|
-
if (!url.startsWith(ARTIFACT_PATH)) {
|
|
595
|
-
next()
|
|
596
|
-
return
|
|
597
|
-
}
|
|
598
|
-
// the url names a content hash, not a package: the request is answered
|
|
599
|
-
// by whichever known project built it, so a React Native app whose
|
|
600
|
-
// swift package sits below its root serves the same path its imports
|
|
601
|
-
// compiled. served from what is already built, never rebuilt here: the
|
|
602
|
-
// page only knows a hash that `load` emitted, and `load` under a compile
|
|
603
|
-
// error emits the last good hash, whose bytes are still the project's
|
|
604
|
-
// artifact. rebuilding would fail that fetch with the compile error and
|
|
605
|
-
// fail every other project's artifact with it.
|
|
606
|
-
for (const project of projects.values()) {
|
|
607
|
-
const artifact = project.artifact
|
|
608
|
-
if (artifact && url === `${ARTIFACT_PATH}${artifact.hash}.wasm`) {
|
|
609
|
-
// the tenant worker's origin is the shell, never this dev server
|
|
610
|
-
res.setHeader('access-control-allow-origin', '*')
|
|
611
|
-
res.setHeader('content-type', 'application/wasm')
|
|
612
|
-
res.end(artifact.bytes)
|
|
613
|
-
return
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
res.statusCode = 404
|
|
617
|
-
res.end('swift artifact is stale')
|
|
903
|
+
if (!serveSwiftPackageAsset((req.url ?? '').split('?')[0], res)) next()
|
|
618
904
|
})
|
|
619
905
|
},
|
|
620
906
|
}
|