@swiftbrowser/cli 0.6.7 → 0.7.0

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 (90) hide show
  1. package/README.md +73 -5
  2. package/dist/appSources.d.ts +116 -0
  3. package/dist/appSources.js +624 -0
  4. package/dist/appSources.js.map +1 -0
  5. package/dist/browser.d.ts +4 -0
  6. package/dist/browser.js +8 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/build.d.ts +17 -2
  9. package/dist/build.js +38 -11
  10. package/dist/build.js.map +1 -1
  11. package/dist/cli.js +28 -4
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/build.js +10 -7
  14. package/dist/commands/build.js.map +1 -1
  15. package/dist/commands/check.js +38 -8
  16. package/dist/commands/check.js.map +1 -1
  17. package/dist/commands/dev.d.ts +8 -4
  18. package/dist/commands/dev.js +25 -10
  19. package/dist/commands/dev.js.map +1 -1
  20. package/dist/commands/doctor.d.ts +8 -0
  21. package/dist/commands/doctor.js +52 -4
  22. package/dist/commands/doctor.js.map +1 -1
  23. package/dist/commands/kit.d.ts +1 -0
  24. package/dist/commands/kit.js +20 -5
  25. package/dist/commands/kit.js.map +1 -1
  26. package/dist/commands/shared.d.ts +24 -6
  27. package/dist/commands/shared.js +92 -16
  28. package/dist/commands/shared.js.map +1 -1
  29. package/dist/commands/test.d.ts +4 -2
  30. package/dist/commands/test.js +25 -17
  31. package/dist/commands/test.js.map +1 -1
  32. package/dist/compat.d.ts +2 -30
  33. package/dist/compat.js +2 -44
  34. package/dist/compat.js.map +1 -1
  35. package/dist/compiler.d.ts +50 -0
  36. package/dist/compiler.js +94 -0
  37. package/dist/compiler.js.map +1 -0
  38. package/dist/config.d.ts +5 -2
  39. package/dist/config.js +7 -3
  40. package/dist/config.js.map +1 -1
  41. package/dist/frameworks.d.ts +30 -0
  42. package/dist/frameworks.js +48 -0
  43. package/dist/frameworks.js.map +1 -0
  44. package/dist/icu.d.ts +2 -4
  45. package/dist/icu.js +7 -36
  46. package/dist/icu.js.map +1 -1
  47. package/dist/kit.d.ts +44 -12
  48. package/dist/kit.js +149 -27
  49. package/dist/kit.js.map +1 -1
  50. package/dist/log.js +2 -1
  51. package/dist/log.js.map +1 -1
  52. package/dist/manifest.d.ts +73 -0
  53. package/dist/manifest.js +324 -0
  54. package/dist/manifest.js.map +1 -0
  55. package/dist/moduleCache.d.ts +20 -0
  56. package/dist/moduleCache.js +98 -0
  57. package/dist/moduleCache.js.map +1 -0
  58. package/dist/objc.js +1 -1
  59. package/dist/objc.js.map +1 -1
  60. package/dist/packages.d.ts +8 -1
  61. package/dist/packages.js +10 -2
  62. package/dist/packages.js.map +1 -1
  63. package/dist/registry.d.ts +46 -0
  64. package/dist/registry.js +184 -0
  65. package/dist/registry.js.map +1 -0
  66. package/dist/resolve.d.ts +80 -0
  67. package/dist/resolve.js +396 -0
  68. package/dist/resolve.js.map +1 -0
  69. package/dist/stage.d.ts +5 -144
  70. package/dist/stage.js +19 -516
  71. package/dist/stage.js.map +1 -1
  72. package/dist/testDiscovery.d.ts +44 -0
  73. package/dist/testDiscovery.js +111 -0
  74. package/dist/testDiscovery.js.map +1 -0
  75. package/dist/vendor.d.ts +41 -5
  76. package/dist/vendor.js +71 -31
  77. package/dist/vendor.js.map +1 -1
  78. package/dist/wasmCompiler.d.ts +160 -0
  79. package/dist/wasmCompiler.js +628 -0
  80. package/dist/wasmCompiler.js.map +1 -0
  81. package/dist/wasmFlags.d.ts +31 -0
  82. package/dist/wasmFlags.js +56 -0
  83. package/dist/wasmFlags.js.map +1 -0
  84. package/dist/wasmPackages.d.ts +163 -0
  85. package/dist/wasmPackages.js +466 -0
  86. package/dist/wasmPackages.js.map +1 -0
  87. package/dist/wasmSdk.d.ts +27 -0
  88. package/dist/wasmSdk.js +103 -0
  89. package/dist/wasmSdk.js.map +1 -0
  90. package/package.json +6 -5
package/dist/stage.js CHANGED
@@ -7,11 +7,12 @@ import path from 'node:path';
7
7
  import { ASSET_SYMBOLS_FILE, assetSymbolsSource, declaredStaticMembers } from './assetSymbols.js';
8
8
  import { swiftSettingsEntries } from './buildSettings.js';
9
9
  import { compileLocalizations } from './strings.js';
10
- import { LINKED_FRAMEWORKS, importsOf } from './compat.js';
11
10
  import { ModelError, compileModel } from './coreml.js';
12
11
  import { ICU_TARGET, icuAssembly } from './icu.js';
13
- import { extensionOverrides, keyPathStrings, objcProtocols, optionalRequirementCalls, optionalRequirementNames } from './objc.js';
12
+ import { optionalRequirementNames } from './objc.js';
14
13
  import { planPackages } from './packages.js';
14
+ import { FULL_FOUNDATION_FILE, LOCALE_WRAPS, APP_STACK_SIZE, WASM_EXPORTS, declaredTypeNames, featuresOf, fullFoundationSource, linkedProducts, transformSource } from './appSources.js';
15
+ export { transformSource, sceneDelegateClasses, numberCasts, usesObjC, selectorPointers, importsObjectiveC, importsDispatch, usesPosixMutex, rewriteObjC, stripPreviews, matchingBrace, WASM_EXPORTS, APP_STACK_SIZE, LOCALE_WRAPS, appLinkerFlags, linkedProducts, featuresOf, FULL_FOUNDATION_FILE, fullFoundationSource, declaredTypeNames } from './appSources.js';
15
16
  /**
16
17
  * `$SWIFTBROWSER_BUILD_ROOT`, resolved; unset by default. With it, every app is
17
18
  * staged at `<root>/<App>/` and built with one SwiftPM scratch path,
@@ -51,366 +52,6 @@ export function scratchPathArgs() {
51
52
  export function wasmOutput(app) {
52
53
  return path.join(stagingDir(app), `${app.name}.wasm`);
53
54
  }
54
- /**
55
- * Adjusts one source for the shim without moving any line, so compiler
56
- * diagnostics keep their line numbers:
57
- * - `import SwiftUI` also imports Foundation, FoundationBridge and Observation,
58
- * as Apple's SwiftUI re-exports Foundation (and Foundation re-exports
59
- * Observation, so `@Observable` works in a file that imports either alone);
60
- * - `import Foundation` also imports Observation, for the same reason, and
61
- * (`bridged`: in a module built against the bridge) FoundationBridge, whose
62
- * `URLSession`, `UserDefaults`… the alias file names: a public default
63
- * argument (`session: URLSession = .shared`) may only use a member of a
64
- * module its own file imports;
65
- * - `import CoreImage.CIFilterBuiltins` → `import CoreImage`, and likewise
66
- * `import UIKit.UIImage`, `import os.log`, `import CoreGraphics.CGImage`:
67
- * on Apple platforms those are Clang submodules; the shim's frameworks are
68
- * Swift modules, which have no submodules to import;
69
- * - `delegate?.urlSession?(…)` → `delegate?.urlSession(…)`: the delegate
70
- * methods are `@objc optional` on Apple platforms, plain requirements with
71
- * default implementations in FoundationBridge;
72
- * - `ProcessInfo.processInfo.physicalMemory` → `_swiftBrowserPhysicalMemory` (the alias file's):
73
- * swift-foundation answers 0 on WASI, and limits sized from it refuse everything;
74
- * - `import Dispatch` → `import FoundationBridge`: the SDK has no Dispatch on
75
- * WASI, and the bridge's queues run on the main actor (a package that
76
- * imports it is built from a copy, vendor.ts);
77
- * - `import ObjectiveC` → `import FoundationBridge`, which has the associated
78
- * objects (CoreFoundation.swift), and `unsafeBitCast(Selector(…), to:
79
- * UnsafeRawPointer.self)` → `Selector(…)._uniquePointer`: on Apple platforms
80
- * a selector is a uniqued pointer, the usual key of an associated object;
81
- * here it is a struct, which the bit cast would trap on (`selectorPointers`;
82
- * a package that imports ObjectiveC is built from a copy, vendor.ts);
83
- * - `import FoundationLite` → `import FoundationBridge`: FoundationLite was the
84
- * lean mode's formatting layer (Phase 14 folded it into Foundation); a file
85
- * that still names it gets the bridge, which holds what remains;
86
- * - `configuration.delegateClass = SceneDelegate.self` records how to make
87
- * the scene delegate (`sceneDelegateClasses`);
88
- * - `x as? AnyObject` → `x as? _AnyObjectValue` in a file that reads a number
89
- * off it (`.doubleValue`, `.intValue`…): Objective-C finds those members on
90
- * any object at run time, which the browser's Swift cannot (`numberCasts`);
91
- * - `@objc` protocols become class-bound, with their `optional` requirements
92
- * as plain ones that have default implementations, and calls of those
93
- * requirements (`delegate?.method?(…)`, in any of the module's files:
94
- * `optionalRequirements`) plain calls; an `override` in an extension is
95
- * blanked; `#keyPath(Type.property)` becomes `"property"` (objc.ts);
96
- * - `ProcessInfo()` → `ProcessInfo.processInfo`;
97
- * - `#Preview { … }` blocks (and `#Preview("name", traits: …) { … }`) become
98
- * blank lines: the shim has no preview macro and previews are not part of
99
- * the app. So do the older `struct …: PreviewProvider { … }` declarations,
100
- * which packages still carry (with preview-only modifiers inside).
101
- */
102
- export function transformSource(text, options = {}) {
103
- let foundationRewrites = 0;
104
- let out = text;
105
- // On iOS `import SwiftUI` re-exports Foundation (UUID, JSON, Date,
106
- // UserDefaults are in scope without an import) and UIKit (`UIColor`,
107
- // `UIApplication.shared`); the shim's SwiftUI does not, so the same modules
108
- // are imported next to it (and UIKit is linked wherever SwiftUI is, featuresOf).
109
- // An access level on the import (`public import SwiftUI`, with
110
- // `InternalImportsByDefault`) goes on its companions too.
111
- const companions = ['Foundation', 'FoundationBridge', 'Observation', 'UIKit'];
112
- out = out.replace(/^([ \t]*)((?:(?:public|package|internal|fileprivate|private)[ \t]+)?)import SwiftUI[ \t]*(\r?)$/gm, (_, indent, access, cr) => `${indent}${access}import SwiftUI; ${companions.map((module) => `${access}import ${module}`).join('; ')}${cr}`);
113
- const foundationCompanions = options.bridged ? ['FoundationBridge', 'Observation'] : ['Observation'];
114
- out = out.replace(/^([ \t]*)((?:(?:public|package|internal|fileprivate|private)[ \t]+)?)import Foundation[ \t]*(\r?)$/gm, (_, indent, access, cr) => `${indent}${access}import Foundation; ${foundationCompanions.map((module) => `${access}import ${module}`).join('; ')}${cr}`);
115
- out = out.replace(SUBMODULE_IMPORT, (_, lead, module, cr) => `${lead}import ${module}${cr}`);
116
- // URLSession's delegate methods are `@objc optional` on Apple platforms;
117
- // FoundationBridge's are requirements with defaults (URLSession.swift).
118
- out = out.replace(/\.urlSession\?\(/g, '.urlSession(');
119
- // swift-foundation reports no physical memory on WASI (FoundationBridge/ProcessInfo.swift).
120
- out = out.replace(/\bProcessInfo\.processInfo\.physicalMemory\b/g, '_swiftBrowserPhysicalMemory');
121
- // The browser runs iOS apps: `#if os(iOS)` holds, as it does on the
122
- // iPhone the page stands for (the build's real OS is WASI or Linux).
123
- out = out.replace(IOS_CONDITION, (line) => line.replace(/\bos\(\s*iOS\s*\)/g, '(os(iOS) || SWIFTBROWSER)'));
124
- // The SDK has no Dispatch on WASI: the bridge's queues stand in (Dispatch.swift).
125
- out = out.replace(/^([ \t]*(?:@[A-Za-z_]+[ \t]+)*(?:(?:public|package|internal|fileprivate|private)[ \t]+)?)import Dispatch[ \t]*(\r?)$/gm, (_, lead, cr) => `${lead}import FoundationBridge${cr}`);
126
- // Associated objects without the Objective-C runtime (FoundationBridge/CoreFoundation.swift).
127
- out = out.replace(/^([ \t]*(?:@[A-Za-z_]+[ \t]+)*(?:(?:public|package|internal|fileprivate|private)[ \t]+)?)import ObjectiveC[ \t]*(\r?)$/gm, (_, lead, cr) => `${lead}import FoundationBridge${cr}`);
128
- out = selectorPointers(out);
129
- out = out.replace(/^([ \t]*)import FoundationLite[ \t]*(\r?)$/gm, (_, indent, cr) => {
130
- foundationRewrites += 1;
131
- return `${indent}import FoundationBridge${cr}`;
132
- });
133
- // Objective-C protocols and what Swift allows only with the runtime (objc.ts).
134
- out = objcProtocols(out);
135
- out = optionalRequirementCalls(out, options.optionalRequirements ?? optionalRequirementNames([text]));
136
- out = extensionOverrides(out);
137
- out = keyPathStrings(out);
138
- // `ProcessInfo()` is the process's info on Apple platforms; corelibs' initializer is internal.
139
- out = out.replace(/\bProcessInfo\(\)/g, 'ProcessInfo.processInfo');
140
- out = rewriteObjC(out);
141
- out = sceneDelegateClasses(out);
142
- out = numberCasts(out);
143
- const stripped = stripPreviews(out);
144
- return { text: stripped.text, previewsStripped: stripped.count, foundationRewrites };
145
- }
146
- /**
147
- * `configuration.delegateClass = SceneDelegate.self` →
148
- * `configuration._setDelegateClass(SceneDelegate.self) { SceneDelegate() }`:
149
- * UIKit makes the scene delegate through the Objective-C runtime, and Swift
150
- * without it can only make an instance from a metatype through a `required`
151
- * initializer (UIKit/SceneDelegates.swift). Same line, so diagnostics keep
152
- * their places.
153
- */
154
- export function sceneDelegateClasses(text) {
155
- if (!text.includes('delegateClass'))
156
- return text;
157
- return text.replace(/(\b[A-Za-z_][\w.]*)\.delegateClass\s*=\s*([A-Z][\w.]*)\.self\b/g, '$1._setDelegateClass($2.self) { $2() }');
158
- }
159
- /**
160
- * `as? AnyObject` → `as? _AnyObjectValue` where the file reads a number from
161
- * the result: `(dict[key] as? AnyObject)?.doubleValue` looks the member up on
162
- * any object through Objective-C (nil where there is none); FoundationBridge's
163
- * `_AnyObjectValue` (Selector.swift) has those members, optional likewise, on
164
- * the numbers, booleans and strings a dictionary of `Any` holds.
165
- */
166
- export function numberCasts(text) {
167
- if (!/\bas\?\s*AnyObject\b/.test(text))
168
- return text;
169
- if (!/\.(?:doubleValue|floatValue|intValue|integerValue|int64Value|uint64Value|uintValue|boolValue)\b/.test(text))
170
- return text;
171
- return text.replace(/\bas\?(\s*)AnyObject\b/g, 'as?$1_AnyObjectValue');
172
- }
173
- /** Whether a source uses the Objective-C runtime's selectors or attributes. */
174
- export function usesObjC(text) {
175
- return /#selector\s*\(|@objc\b|@objcMembers\b/.test(text);
176
- }
177
- /**
178
- * Whether a source calls POSIX mutex functions. WASI's libc module leaves
179
- * `pthread.h` out, so a package that does is built from a copy: there
180
- * `#if os(iOS)` holds (Darwin's `os_unfair_lock` branch) and FoundationBridge
181
- * declares the mutex functions for the page's single thread (Locks.swift).
182
- */
183
- /**
184
- * `unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self)` →
185
- * `Selector(#function)._uniquePointer`: the address that stands for the
186
- * selector's name (FoundationBridge/Selector.swift), which on Apple
187
- * platforms the bit cast gives, the selector being a uniqued pointer.
188
- */
189
- export function selectorPointers(text) {
190
- if (!text.includes('unsafeBitCast'))
191
- return text;
192
- return text.replace(/\bunsafeBitCast\(\s*(Selector\((?:[^()]|\([^()]*\))*\))\s*,\s*to:\s*UnsafeRawPointer\.self\s*\)/g, '$1._uniquePointer');
193
- }
194
- /** Whether a source imports ObjectiveC, which the SDK does not have on WASI (transformSource). */
195
- export function importsObjectiveC(text) {
196
- return importsOf(text).includes('ObjectiveC');
197
- }
198
- /** Whether a source imports Dispatch, which the SDK does not have on WASI (transformSource). */
199
- export function importsDispatch(text) {
200
- return importsOf(text).includes('Dispatch');
201
- }
202
- export function usesPosixMutex(text) {
203
- return /\bpthread_mutex(?:attr)?_(?!t\b)[a-z]+\s*\(/.test(text);
204
- }
205
- /**
206
- * Objective-C without its runtime (FoundationBridge/Selector.swift). The
207
- * browser's Swift has no Objective-C interop, so `@objc` (and the Interface
208
- * Builder attributes that imply it) is dropped, and `#selector(handle(_:))`
209
- * becomes `Selector(Self.handle(_:), name: "handle(_:)")`: the method itself,
210
- * which the target/selector APIs call on their target. A qualified method
211
- * (`Type.method`) is used as it is; one of another object
212
- * (`#selector(model.refresh)`) is called bound; `getter:`/`setter:`
213
- * selectors only have a name. A method the file declares `@MainActor` is
214
- * bound to its target in a closure instead,
215
- * `Selector(name: "tick", isolated: { (target: Self) in target.tick })`:
216
- * Swift 6 rejects naming a main-actor method of a class that is not
217
- * `Sendable` unapplied (Gifu's display link proxy).
218
- */
219
- export function rewriteObjC(text) {
220
- if (!usesObjC(text))
221
- return text;
222
- let out = text.replace(/@(?:objc\b(?:\([^)\n]*\))?|(?:objcMembers|IBAction|IBOutlet|IBInspectable|IBDesignable)\b)[ \t]*/g, '');
223
- const mainActorMethods = new Set([...out.matchAll(/@MainActor\b(?:\s*@\w+(?:\([^)\n]*\))?)*\s*(?:(?:public|private|fileprivate|internal|open|final|override|dynamic|nonisolated)\s+)*func\s+`?([A-Za-z_][A-Za-z0-9_]*)/g)].map((m) => m[1]));
224
- let result = '';
225
- let index = 0;
226
- const selectors = /#selector\s*\(/g;
227
- for (;;) {
228
- selectors.lastIndex = index;
229
- const match = selectors.exec(out);
230
- if (!match)
231
- break;
232
- const open = match.index + match[0].length - 1;
233
- const close = matchingParen(out, open);
234
- if (close < 0)
235
- break;
236
- const inner = out.slice(open + 1, close).trim();
237
- const name = inner.replace(/\s+/g, '');
238
- result += out.slice(index, match.index);
239
- const accessor = /^(?:getter|setter)\s*:\s*(.+)$/s.exec(inner);
240
- let method = inner.replace(/^self\s*\.\s*/, '');
241
- const qualified = /^([A-Z][A-Za-z0-9_]*)\s*\.\s*(.+)$/s.exec(method);
242
- const unqualified = method.replace(/^`?([A-Za-z_][A-Za-z0-9_]*)`?/, '$1');
243
- const base = /^([A-Za-z_][A-Za-z0-9_]*)/.exec(qualified ? qualified[2] : unqualified)?.[1];
244
- if (accessor) {
245
- result += `Selector(${JSON.stringify(accessor[1].replace(/\s+/g, ''))})`;
246
- }
247
- else if (base && mainActorMethods.has(base) && (qualified || !/^[A-Za-z_][A-Za-z0-9_]*\s*\./.test(method))) {
248
- const type = qualified ? qualified[1] : 'Self';
249
- const member = (qualified ? qualified[2] : unqualified).replace(/\s+/g, '');
250
- result += `Selector(name: ${JSON.stringify(name)}, isolated: { (target: ${type}) in target.${member} })`;
251
- }
252
- else if (/^[A-Z][A-Za-z0-9_]*\s*\./.test(method)) {
253
- result += `Selector(${method}, name: ${JSON.stringify(name)})`;
254
- }
255
- else if (/^[A-Za-z_][A-Za-z0-9_]*\s*\./.test(method)) {
256
- result += `Selector(bound: ${method}, name: ${JSON.stringify(name)})`;
257
- }
258
- else {
259
- method = method.replace(/^`?([A-Za-z_][A-Za-z0-9_]*)`?/, '$1');
260
- result += `Selector(Self.${method}, name: ${JSON.stringify(name)})`;
261
- }
262
- index = close + 1;
263
- }
264
- result += out.slice(index);
265
- return result;
266
- }
267
- /** Index of the `)` closing the `(` at `open`; -1 when unbalanced. */
268
- function matchingParen(text, open) {
269
- let depth = 0;
270
- for (let i = open; i < text.length; i += 1) {
271
- if (text[i] === '(')
272
- depth += 1;
273
- else if (text[i] === ')') {
274
- depth -= 1;
275
- if (depth === 0)
276
- return i;
277
- }
278
- }
279
- return -1;
280
- }
281
- /** A `#if` or `#elseif` line that tests `os(iOS)`. */
282
- const IOS_CONDITION = /^[ \t]*#(?:if|elseif)\b[^\n]*\bos\(\s*iOS\s*\)[^\n]*$/gm;
283
- /** `import UIKit.UIImage`-style imports of the frameworks the shim provides as Swift modules. */
284
- const SUBMODULE_IMPORT = /^([ \t]*(?:@[A-Za-z_]+[ \t]+)*)import[ \t]+(CoreImage|UIKit|CoreGraphics|ImageIO|os)\.[A-Za-z_][A-Za-z0-9_.]*[ \t]*(\r?)$/gm;
285
- /** Replaces every `#Preview …{ … }` block and `PreviewProvider` struct with blank lines of the same count. */
286
- export function stripPreviews(text) {
287
- let count = 0;
288
- let result = '';
289
- let index = 0;
290
- const pattern = /^[ \t]*(?:#Preview\b|(?:(?:@\w+(?:\([^)\n]*\))?|public|internal|fileprivate|private)\s+)*struct[ \t]+\w+[ \t]*:[^{\n]*\bPreviewProvider\b)/gm;
291
- for (;;) {
292
- pattern.lastIndex = index;
293
- const match = pattern.exec(text);
294
- if (!match)
295
- break;
296
- let start = match.index;
297
- const open = text.indexOf('{', start);
298
- if (open < 0)
299
- break;
300
- const close = matchingBrace(text, open);
301
- if (close < 0)
302
- break;
303
- // Consume the rest of the line after the closing brace (usually nothing).
304
- let end = close + 1;
305
- while (end < text.length && (text[end] === ' ' || text[end] === '\t'))
306
- end += 1;
307
- // Attribute lines just above it (`@available(iOS 17, *)`) go with it.
308
- for (;;) {
309
- const lineEnd = start - 1;
310
- if (lineEnd < index || text[lineEnd] !== '\n')
311
- break;
312
- const lineStart = text.lastIndexOf('\n', lineEnd - 1) + 1;
313
- if (lineStart < index || !/^[ \t]*@\w+(?:\([^)\n]*\))?[ \t]*\r?$/.test(text.slice(lineStart, lineEnd)))
314
- break;
315
- start = lineStart;
316
- }
317
- result += text.slice(index, start);
318
- const removed = text.slice(start, end);
319
- result += removed.replace(/[^\n]/g, '');
320
- count += 1;
321
- index = end;
322
- }
323
- result += text.slice(index);
324
- return { text: result, count };
325
- }
326
- /** Index of the `}` closing the `{` at `open`, skipping strings and comments; -1 when unbalanced. */
327
- export function matchingBrace(text, open) {
328
- let depth = 0;
329
- let i = open;
330
- while (i < text.length) {
331
- const ch = text[i];
332
- const next = text[i + 1];
333
- if (ch === '/' && next === '/') {
334
- i = text.indexOf('\n', i);
335
- if (i < 0)
336
- return -1;
337
- continue;
338
- }
339
- if (ch === '/' && next === '*') {
340
- const end = text.indexOf('*/', i + 2);
341
- if (end < 0)
342
- return -1;
343
- i = end + 2;
344
- continue;
345
- }
346
- if (ch === '"') {
347
- i = skipString(text, i);
348
- if (i < 0)
349
- return -1;
350
- continue;
351
- }
352
- if (ch === '{')
353
- depth += 1;
354
- if (ch === '}') {
355
- depth -= 1;
356
- if (depth === 0)
357
- return i;
358
- }
359
- i += 1;
360
- }
361
- return -1;
362
- }
363
- /** Index just past a string literal starting at `start` (`"…"`, `"""…"""`, raw `#"…"#`), interpolations included; -1 when unterminated. */
364
- function skipString(text, start) {
365
- const triple = text.startsWith('"""', start);
366
- const quote = triple ? '"""' : '"';
367
- let i = start + quote.length;
368
- while (i < text.length) {
369
- if (text[i] === '\\') {
370
- if (text[i + 1] === '(') {
371
- // Interpolation: skip to its matching parenthesis.
372
- let depth = 0;
373
- let j = i + 1;
374
- while (j < text.length) {
375
- const c = text[j];
376
- if (c === '"') {
377
- j = skipString(text, j);
378
- if (j < 0)
379
- return -1;
380
- continue;
381
- }
382
- if (c === '(')
383
- depth += 1;
384
- if (c === ')') {
385
- depth -= 1;
386
- if (depth === 0)
387
- break;
388
- }
389
- j += 1;
390
- }
391
- i = j + 1;
392
- continue;
393
- }
394
- i += 2;
395
- continue;
396
- }
397
- if (text.startsWith(quote, i))
398
- return i + quote.length;
399
- i += 1;
400
- }
401
- return -1;
402
- }
403
- /** The features `sources` (their text) call for. */
404
- export function featuresOf(sources) {
405
- const imported = new Set(sources.flatMap((text) => importsOf(text)));
406
- // `import SwiftUI` brings UIKit along, as on iOS (transformSource).
407
- if (imported.has('SwiftUI'))
408
- imported.add('UIKit');
409
- return {
410
- swiftData: imported.has('SwiftData'),
411
- frameworks: LINKED_FRAMEWORKS.filter((framework) => imported.has(framework)),
412
- };
413
- }
414
55
  /**
415
56
  * The Package.swift of the staging package; with `tests` (the names of the
416
57
  * app's test targets, for `swiftbrowser test`), a test target for each that
@@ -424,7 +65,7 @@ export function packageManifest(app, swiftPackagePath, features = { swiftData: f
424
65
  const concurrency = swiftSettingsEntries(app.swiftSettings);
425
66
  const toolsVersion = app.swiftSettings?.defaultIsolationMainActor ? '6.2' : '6.0';
426
67
  const swiftSettings = [defines, `.swiftLanguageMode(${languageMode})`, ...concurrency].join(', ');
427
- const products = [...new Set(['SwiftUI', 'Charts', 'FoundationBridge', 'os', ...(features.swiftData ? ['SwiftData'] : []), ...features.frameworks])];
68
+ const products = linkedProducts(features);
428
69
  // The Xcode project's package dependencies that build for the browser are
429
70
  // real dependencies; the ones the shim stands in for are linked above, by
430
71
  // import (packages.ts). A package that imports SwiftUI is built from its
@@ -462,8 +103,8 @@ export function packageManifest(app, swiftPackagePath, features = { swiftData: f
462
103
  const localeWrap = `
463
104
  // Route swift-foundation's hard-coded current locale (and preferred
464
105
  // languages) to FoundationBridge/Locale.swift.
465
- + ["-Xlinker", "--wrap=$s20FoundationEssentials11LocaleCacheV11preferencesAA0C11PreferencesV_SbtyF",
466
- "-Xlinker", "--wrap=$s20FoundationEssentials6LocaleV18preferredLanguagesSaySSGvgZ"]`;
106
+ + ["-Xlinker", "--wrap=${LOCALE_WRAPS[0]}",
107
+ "-Xlinker", "--wrap=${LOCALE_WRAPS[1]}"]`;
467
108
  const icuTarget = `
468
109
  // Foundation's ICU reads its locale data from a symbol this target
469
110
  // defines, trimmed to the app's languages, ahead of the SDK's 34 MB
@@ -476,12 +117,12 @@ export function packageManifest(app, swiftPackagePath, features = { swiftData: f
476
117
  import PackageDescription
477
118
 
478
119
  // C-ABI entry points the browser renderer calls; the linker must keep them.
479
- let wasmExports = ["sb_event", "sb_ops_ptr", "sb_ops_len", "sb_ops_clear",
480
- "sb_alloc", "sb_event_json", "sb_state_ptr", "sb_state_len", "sb_run_jobs",
481
- "sb_bytes", "sb_bytes_count", "sb_bytes_ptr", "sb_bytes_len"]
120
+ let wasmExports = [${WASM_EXPORTS.slice(0, 4).map((e) => JSON.stringify(e)).join(', ')},
121
+ ${WASM_EXPORTS.slice(4, 9).map((e) => JSON.stringify(e)).join(', ')},
122
+ ${WASM_EXPORTS.slice(9).map((e) => JSON.stringify(e)).join(', ')}]
482
123
  .flatMap { ["-Xlinker", "--export=\\($0)"] }
483
124
  // The reconciler recurses once per view; 8 MB of stack like a native thread.
484
- + ["-Xlinker", "-z", "-Xlinker", "stack-size=8388608"]${localeWrap}
125
+ + ["-Xlinker", "-z", "-Xlinker", "stack-size=${APP_STACK_SIZE}"]${localeWrap}
485
126
 
486
127
  let package = Package(
487
128
  name: ${JSON.stringify(app.name)},
@@ -504,152 +145,6 @@ let package = Package(
504
145
  }
505
146
  /** Where the compiled string tables go in the staging directory (`<language>.lproj` folders). */
506
147
  export const LOCALIZATION_DIR = 'Localization';
507
- /** The staged file that settles names declared by both the shim and Foundation. */
508
- export const FULL_FOUNDATION_FILE = '_SwiftBrowserFoundation.swift';
509
- /**
510
- * The app imports both the shim and Foundation, and both declare a few names
511
- * (a module does not shadow another's declarations, only the current module's
512
- * do). A typealias in the app module does: these point the shared names at
513
- * the shim's types (what its `frame(width:)` and friends take) and at
514
- * FoundationBridge's `Timer` and `RunLoop`, which Foundation lacks on WASI
515
- * (`RunLoop` is marked unavailable, its `Timer` needs CFRunLoop and does not
516
- * link).
517
- */
518
- export function fullFoundationSource(features = { swiftData: false, frameworks: [] }, declared = new Set()) {
519
- // A name the module declares itself (an app's own `Operation` or `Timer`)
520
- // is the module's: an alias of it would be a redeclaration.
521
- const text = fullFoundationTemplate(features);
522
- if (declared.size === 0)
523
- return text;
524
- return text
525
- .split('\n')
526
- .filter((line) => {
527
- const alias = /^public typealias ([A-Za-z_][A-Za-z0-9_]*) = /.exec(line);
528
- return !alias || !declared.has(alias[1]);
529
- })
530
- .join('\n');
531
- }
532
- /** The type names a module's sources declare at the top level (lines that start with the declaration). */
533
- export function declaredTypeNames(sources) {
534
- const names = new Set();
535
- const pattern = /^(?:@[A-Za-z_]+(?:\([^)\n]*\))?\s+)*(?:(?:public|package|internal|private|fileprivate|open|final|indirect)\s+)*(?:class|struct|enum|protocol|actor|typealias)\s+([A-Za-z_][A-Za-z0-9_]*)/gm;
536
- for (const text of sources) {
537
- for (const match of text.matchAll(pattern))
538
- names.add(match[1]);
539
- }
540
- return names;
541
- }
542
- function fullFoundationTemplate(features) {
543
- return `// Generated by @swiftbrowser/cli; rewritten on every build.
544
- // The shim and Foundation both declare these names. A typealias in the app
545
- // module settles each in the shim's favor (SwiftUI's geometry) or on the
546
- // bridge (Timer and RunLoop, which the SDK's Foundation does not have on WASI).
547
- // The imports are public: a package may make imports internal by default
548
- // (\`InternalImportsByDefault\`), and public aliases need public imports.
549
- public import SwiftUI
550
- public import Foundation
551
- public import FoundationBridge
552
- public import os
553
-
554
- // Public, so the app's own public API may use the names (a public method of
555
- // a Layout returning CGSize, for one).
556
- public typealias CGFloat = SwiftUI.CGFloat
557
- public typealias CGPoint = SwiftUI.CGPoint
558
- public typealias CGSize = SwiftUI.CGSize
559
- public typealias CGRect = SwiftUI.CGRect
560
- public typealias Timer = FoundationBridge.Timer
561
- public typealias RunLoop = FoundationBridge.RunLoop
562
- // UserDefaults persists through the browser; Bundle reads the app's resources.
563
- // Foundation's own cannot persist on WASI, and its Bundle.main traps.
564
- public typealias UserDefaults = FoundationBridge.UserDefaults
565
- public typealias Bundle = FoundationBridge.Bundle
566
- // Networking runs over the browser's fetch; Foundation's own is unavailable on
567
- // WASI (it moved to FoundationNetworking, which the SDK does not ship).
568
- public typealias URLSession = FoundationBridge.URLSession
569
- public typealias URLSessionConfiguration = FoundationBridge.URLSessionConfiguration
570
- public typealias URLSessionTask = FoundationBridge.URLSessionTask
571
- public typealias URLSessionDataTask = FoundationBridge.URLSessionDataTask
572
- public typealias URLSessionUploadTask = FoundationBridge.URLSessionUploadTask
573
- public typealias URLSessionDelegate = FoundationBridge.URLSessionDelegate
574
- public typealias URLSessionTaskDelegate = FoundationBridge.URLSessionTaskDelegate
575
- public typealias URLSessionDataDelegate = FoundationBridge.URLSessionDataDelegate
576
- public typealias URLRequest = FoundationBridge.URLRequest
577
- public typealias URLResponse = FoundationBridge.URLResponse
578
- public typealias HTTPURLResponse = FoundationBridge.HTTPURLResponse
579
- public typealias URLError = FoundationBridge.URLError
580
- // The SDK has no Dispatch on WASI; the bridge's queues run on the main actor.
581
- public typealias DispatchQueue = FoundationBridge.DispatchQueue
582
- public typealias DispatchTime = FoundationBridge.DispatchTime
583
- public typealias DispatchTimeInterval = FoundationBridge.DispatchTimeInterval
584
- public typealias DispatchQoS = FoundationBridge.DispatchQoS
585
- public typealias DispatchSpecificKey = FoundationBridge.DispatchSpecificKey
586
- // Dispatch declares DispatchTime's arithmetic globally (so that .now() + 0.5
587
- // finds it); declared here, it reaches files that import only Foundation.
588
- func + (time: DispatchTime, seconds: Double) -> DispatchTime { time._adding(nanoseconds: seconds * 1_000_000_000) }
589
- func - (time: DispatchTime, seconds: Double) -> DispatchTime { time._adding(nanoseconds: -seconds * 1_000_000_000) }
590
- func + (time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime { FoundationBridge.DispatchTime._plus(time, interval) }
591
- func - (time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime { FoundationBridge.DispatchTime._minus(time, interval) }
592
- public typealias DispatchSource = FoundationBridge.DispatchSource
593
- public typealias DispatchWorkItem = FoundationBridge.DispatchWorkItem
594
- public typealias DispatchWorkItemFlags = FoundationBridge.DispatchWorkItemFlags
595
- public typealias DispatchGroup = FoundationBridge.DispatchGroup
596
- public typealias DispatchSourceProtocol = FoundationBridge.DispatchSourceProtocol
597
- public typealias DispatchSourceMemoryPressure = FoundationBridge.DispatchSourceMemoryPressure
598
- public typealias DispatchSourceTimer = FoundationBridge.DispatchSourceTimer
599
- // Nor Operation and OperationQueue (the bridge's run on the main actor), nor
600
- // the rest of URL loading's types.
601
- public typealias Operation = FoundationBridge.Operation
602
- public typealias BlockOperation = FoundationBridge.BlockOperation
603
- public typealias OperationQueue = FoundationBridge.OperationQueue
604
- public typealias URLCache = FoundationBridge.URLCache
605
- public typealias CachedURLResponse = FoundationBridge.CachedURLResponse
606
- public typealias URLSessionTaskMetrics = FoundationBridge.URLSessionTaskMetrics
607
- public typealias URLAuthenticationChallenge = FoundationBridge.URLAuthenticationChallenge
608
- public typealias URLCredential = FoundationBridge.URLCredential
609
- public typealias URLProtectionSpace = FoundationBridge.URLProtectionSpace
610
- public typealias URLSessionWebSocketTask = FoundationBridge.URLSessionWebSocketTask
611
- // Objective-C's selectors without its runtime (FoundationBridge/Selector.swift).
612
- public typealias Selector = FoundationBridge.Selector
613
- // WASI's Foundation keeps only the message-based center; the bridge has the classic one.
614
- public typealias NotificationCenter = FoundationBridge.NotificationCenter
615
- func NSSelectorFromString(_ name: String) -> FoundationBridge.Selector { FoundationBridge.NSSelectorFromString(name) }
616
- func NSStringFromSelector(_ selector: FoundationBridge.Selector) -> String { FoundationBridge.NSStringFromSelector(selector) }
617
- public typealias URLSessionWebSocketDelegate = FoundationBridge.URLSessionWebSocketDelegate
618
- // Nor RelativeDateTimeFormatter ("2 hr. ago").
619
- public typealias RelativeDateTimeFormatter = FoundationBridge.RelativeDateTimeFormatter
620
- // What Darwin's Foundation brings along: the Core Foundation clock, the
621
- // Objective-C runtime's autorelease pools and associated objects, and os's lock.
622
- public typealias CFAbsoluteTime = FoundationBridge.CFAbsoluteTime
623
- public typealias CFTimeInterval = FoundationBridge.CFTimeInterval
624
- func CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime { FoundationBridge.CFAbsoluteTimeGetCurrent() }
625
- func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result { try FoundationBridge.autoreleasepool(invoking: body) }
626
- public typealias objc_AssociationPolicy = FoundationBridge.objc_AssociationPolicy
627
- func objc_setAssociatedObject(_ object: Any, _ key: UnsafeRawPointer, _ value: Any?, _ policy: objc_AssociationPolicy) { FoundationBridge.objc_setAssociatedObject(object, key, value, policy) }
628
- func objc_getAssociatedObject(_ object: Any, _ key: UnsafeRawPointer) -> Any? { FoundationBridge.objc_getAssociatedObject(object, key) }
629
- func class_getInstanceSize(_ cls: AnyClass?) -> Int { FoundationBridge.class_getInstanceSize(cls) }
630
- func sysctlbyname(_ name: UnsafePointer<CChar>?, _ oldp: UnsafeMutableRawPointer?, _ oldlenp: UnsafeMutablePointer<Int>?, _ newp: UnsafeMutableRawPointer?, _ newlen: Int) -> Int32 { FoundationBridge.sysctlbyname(name, oldp, oldlenp, newp, newlen) }
631
- // ProcessInfo.processInfo.physicalMemory, which reads 0 on WASI (the staging rewrites it to this).
632
- var _swiftBrowserPhysicalMemory: UInt64 { FoundationBridge._physicalMemory }
633
- public typealias os_unfair_lock = os.os_unfair_lock
634
- public typealias os_unfair_lock_t = os.os_unfair_lock_t
635
- public typealias os_unfair_lock_s = os.os_unfair_lock_s
636
- func os_unfair_lock_lock(_ lock: os_unfair_lock_t) { os.os_unfair_lock_lock(lock) }
637
- func os_unfair_lock_unlock(_ lock: os_unfair_lock_t) { os.os_unfair_lock_unlock(lock) }
638
- func os_unfair_lock_trylock(_ lock: os_unfair_lock_t) -> Bool { os.os_unfair_lock_trylock(lock) }
639
- ${features.swiftData ? SWIFTDATA_PREDICATE : ''}`;
640
- }
641
- /**
642
- * For an app that imports SwiftData: `#Predicate` expands with SwiftData's
643
- * copy of Foundation's macro, which accepts `localizedStandardContains`,
644
- * `localizedCompare` and `caseInsensitiveCompare` as Apple's does (the
645
- * open-source macro rejects them). A macro declared in the app module shadows
646
- * Foundation's; the type `Predicate` stays Foundation's.
647
- */
648
- const SWIFTDATA_PREDICATE = `
649
- // SwiftData: #Predicate as on Apple platforms (Sources/SwiftDataMacros/PredicateMacro.swift).
650
- @freestanding(expression)
651
- macro Predicate<each Input>(_ body: (repeat each Input) -> Bool) -> Foundation.Predicate<repeat each Input> = #externalMacro(module: "SwiftDataMacros", type: "PredicateMacro")
652
- `;
653
148
  /** The directory the staged sources keep their layout relative to. */
654
149
  export function commonSourceBase(app) {
655
150
  return commonDirectory(app.sources);
@@ -700,6 +195,14 @@ export function stage(app, swiftPackagePath, options = {}) {
700
195
  const features = featuresOf([...texts.values(), ...models.map((model) => model.source), ...testTexts.values()]);
701
196
  const optionalRequirements = optionalRequirementNames(texts.values());
702
197
  writeIfChanged(path.join(dir, 'Package.swift'), packageManifest(app, swiftPackagePath, features, options.vendored, tests.map((target) => target.name)));
198
+ // The app's package registries (registry.ts), where SwiftPM looks for the
199
+ // staging package's: the user's own (~/.swiftpm) it finds by itself.
200
+ const registries = path.join(app.root, '.swiftpm', 'configuration', 'registries.json');
201
+ const stagedRegistries = path.join(dir, '.swiftpm', 'configuration', 'registries.json');
202
+ if (existsSync(registries))
203
+ writeIfChanged(stagedRegistries, readFileSync(registries, 'utf8'));
204
+ else
205
+ rmSync(stagedRegistries, { force: true });
703
206
  const icuDir = path.join(dir, ICU_TARGET);
704
207
  // Tests link the SDK's whole ICU data: a test bundle links its
705
208
  // dependencies' objects as one archive, after the SDK's (no trimmed data).
@@ -774,7 +277,7 @@ export function stage(app, swiftPackagePath, options = {}) {
774
277
  }
775
278
  // The string tables, compiled beside the package (not a source of it).
776
279
  const localization = compileLocalizations(app.localizations ?? [], app.locales, path.join(dir, LOCALIZATION_DIR));
777
- return { dir, originals, previewsStripped, localizations: localization.dirs, localizationWarnings: localization.warnings };
280
+ return { dir, originals, previewsStripped, localizations: localization.dirs, localizationWarnings: localization.warnings, features };
778
281
  }
779
282
  /** The staged file of a model's generated class: `<Class>.mlmodel.swift`, a name no app source has. */
780
283
  export function modelSourceName(model) {