@swiftbrowser/cli 0.6.6 → 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
@@ -0,0 +1,624 @@
1
+ // The app's sources as the shim compiles them, and what they decide about
2
+ // the build: the rewrites `stage` applies to each file, the features that
3
+ // choose what links, the generated file that settles names Foundation and
4
+ // the shim share, and the app target's linker settings. No Node APIs, so the
5
+ // browser (packages/playground) stages an app with the same code as the CLI;
6
+ // stage.ts re-exports all of it.
7
+ import { LINKED_FRAMEWORKS, importsOf } from './frameworks.js';
8
+ import { extensionOverrides, keyPathStrings, objcProtocols, optionalRequirementCalls, optionalRequirementNames } from './objc.js';
9
+ /**
10
+ * Adjusts one source for the shim without moving any line, so compiler
11
+ * diagnostics keep their line numbers:
12
+ * - `import SwiftUI` also imports Foundation, FoundationBridge and Observation,
13
+ * as Apple's SwiftUI re-exports Foundation (and Foundation re-exports
14
+ * Observation, so `@Observable` works in a file that imports either alone);
15
+ * - `import Foundation` also imports Observation, for the same reason, and
16
+ * (`bridged`: in a module built against the bridge) FoundationBridge, whose
17
+ * `URLSession`, `UserDefaults`… the alias file names: a public default
18
+ * argument (`session: URLSession = .shared`) may only use a member of a
19
+ * module its own file imports;
20
+ * - `import CoreImage.CIFilterBuiltins` → `import CoreImage`, and likewise
21
+ * `import UIKit.UIImage`, `import os.log`, `import CoreGraphics.CGImage`:
22
+ * on Apple platforms those are Clang submodules; the shim's frameworks are
23
+ * Swift modules, which have no submodules to import;
24
+ * - `delegate?.urlSession?(…)` → `delegate?.urlSession(…)`: the delegate
25
+ * methods are `@objc optional` on Apple platforms, plain requirements with
26
+ * default implementations in FoundationBridge;
27
+ * - `ProcessInfo.processInfo.physicalMemory` → `_swiftBrowserPhysicalMemory` (the alias file's):
28
+ * swift-foundation answers 0 on WASI, and limits sized from it refuse everything;
29
+ * - `import Dispatch` → `import FoundationBridge`: the SDK has no Dispatch on
30
+ * WASI, and the bridge's queues run on the main actor (a package that
31
+ * imports it is built from a copy, vendor.ts);
32
+ * - `import ObjectiveC` → `import FoundationBridge`, which has the associated
33
+ * objects (CoreFoundation.swift), and `unsafeBitCast(Selector(…), to:
34
+ * UnsafeRawPointer.self)` → `Selector(…)._uniquePointer`: on Apple platforms
35
+ * a selector is a uniqued pointer, the usual key of an associated object;
36
+ * here it is a struct, which the bit cast would trap on (`selectorPointers`;
37
+ * a package that imports ObjectiveC is built from a copy, vendor.ts);
38
+ * - `import FoundationLite` → `import FoundationBridge`: FoundationLite was the
39
+ * lean mode's formatting layer (Phase 14 folded it into Foundation); a file
40
+ * that still names it gets the bridge, which holds what remains;
41
+ * - `configuration.delegateClass = SceneDelegate.self` records how to make
42
+ * the scene delegate (`sceneDelegateClasses`);
43
+ * - `x as? AnyObject` → `x as? _AnyObjectValue` in a file that reads a number
44
+ * off it (`.doubleValue`, `.intValue`…): Objective-C finds those members on
45
+ * any object at run time, which the browser's Swift cannot (`numberCasts`);
46
+ * - `@objc` protocols become class-bound, with their `optional` requirements
47
+ * as plain ones that have default implementations, and calls of those
48
+ * requirements (`delegate?.method?(…)`, in any of the module's files:
49
+ * `optionalRequirements`) plain calls; an `override` in an extension is
50
+ * blanked; `#keyPath(Type.property)` becomes `"property"` (objc.ts);
51
+ * - `ProcessInfo()` → `ProcessInfo.processInfo`;
52
+ * - `#Preview { … }` blocks (and `#Preview("name", traits: …) { … }`) become
53
+ * blank lines: the shim has no preview macro and previews are not part of
54
+ * the app. So do the older `struct …: PreviewProvider { … }` declarations,
55
+ * which packages still carry (with preview-only modifiers inside).
56
+ */
57
+ /**
58
+ * Optional requirements of the shim's delegate protocols (Objective-C
59
+ * `optional` methods on Apple platforms), which the shim declares as
60
+ * requirements with defaults: a call `delegate?.method?(…)` is a plain call.
61
+ */
62
+ const SHIM_OPTIONAL_REQUIREMENTS = [
63
+ 'navigationController', 'navigationControllerSupportedInterfaceOrientations', 'navigationControllerPreferredInterfaceOrientationForPresentation',
64
+ ];
65
+ export function transformSource(text, options = {}) {
66
+ let foundationRewrites = 0;
67
+ let out = text;
68
+ // On iOS `import SwiftUI` re-exports Foundation (UUID, JSON, Date,
69
+ // UserDefaults are in scope without an import) and UIKit (`UIColor`,
70
+ // `UIApplication.shared`); the shim's SwiftUI does not, so the same modules
71
+ // are imported next to it (and UIKit is linked wherever SwiftUI is, featuresOf).
72
+ // An access level on the import (`public import SwiftUI`, with
73
+ // `InternalImportsByDefault`) goes on its companions too.
74
+ const companions = ['Foundation', 'FoundationBridge', 'Observation', 'UIKit'];
75
+ // Attributes on the import (`@preconcurrency`) go on its companions too.
76
+ out = out.replace(/^([ \t]*)((?:@[A-Za-z_]+[ \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}`);
77
+ const foundationCompanions = options.bridged ? ['FoundationBridge', 'Observation'] : ['Observation'];
78
+ out = out.replace(/^([ \t]*)((?:@[A-Za-z_]+[ \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}`);
79
+ out = out.replace(SUBMODULE_IMPORT, (_, lead, module, cr) => `${lead}import ${module}${cr}`);
80
+ // URLSession's delegate methods are `@objc optional` on Apple platforms;
81
+ // FoundationBridge's are requirements with defaults (URLSession.swift).
82
+ out = out.replace(/\.urlSession\?\(/g, '.urlSession(');
83
+ // swift-foundation reports no physical memory on WASI (FoundationBridge/ProcessInfo.swift).
84
+ out = out.replace(/\bProcessInfo\.processInfo\.physicalMemory\b/g, '_swiftBrowserPhysicalMemory');
85
+ // The browser runs iOS apps: `#if os(iOS)` holds, as it does on the
86
+ // iPhone the page stands for (the build's real OS is WASI or Linux).
87
+ out = out.replace(IOS_CONDITION, (line) => line.replace(/\bos\(\s*iOS\s*\)/g, '(os(iOS) || SWIFTBROWSER)'));
88
+ // In a package built against the shim, as on iOS, the Objective-C
89
+ // runtime's NSObject is there: the bridge's, with associated objects
90
+ // (`import ObjectiveC` imports it, below). swift-navigation's UIKit
91
+ // observation is NSObject's.
92
+ if (options.bridged) {
93
+ out = out.replace(OBJC_CONDITION, (line) => line.replace(/\bcanImport\(\s*ObjectiveC\s*\)/g, '(canImport(ObjectiveC) || SWIFTBROWSER)'));
94
+ }
95
+ // The SDK has no Dispatch on WASI: the bridge's queues stand in (Dispatch.swift).
96
+ 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}`);
97
+ // Associated objects without the Objective-C runtime (FoundationBridge/CoreFoundation.swift).
98
+ // NSObject is Foundation's there.
99
+ out = out.replace(/^([ \t]*(?:@[A-Za-z_]+[ \t]+)*(?:(?:public|package|internal|fileprivate|private)[ \t]+)?)import ObjectiveC[ \t]*(\r?)$/gm, (_, lead, cr) => `${lead}import Foundation; ${lead.trimStart()}import FoundationBridge${cr}`);
100
+ // Apple's Foundation declares Dispatch's, RunLoop's and OperationQueue's
101
+ // Combine schedulers; the shim's Combine does (Combine/Schedulers.swift).
102
+ // A file whose imports are internal by default (`public import Foundation`
103
+ // beside `import Combine`) may use them in public declarations, so its
104
+ // Combine import is public too.
105
+ if (/^[ \t]*public[ \t]+import[ \t]/m.test(out)) {
106
+ out = out.replace(/^([ \t]*)import Combine[ \t]*(\r?)$/gm, (_, indent, cr) => `${indent}public import Combine${cr}`);
107
+ }
108
+ // Key-value observing's callback is not NSObject's without the Objective-C
109
+ // runtime (FoundationBridge/KeyValueObserving.swift): an override of it is
110
+ // a plain method, which nothing calls.
111
+ out = out.replace(/\boverride[ \t]+((?:(?:public|open|internal|fileprivate|private)[ \t]+)?func[ \t]+observeValue\(\s*forKeyPath\b)/g, '$1');
112
+ // Nor can `responds(to:)` be overridden (FoundationBridge/Selector.swift
113
+ // declares it in an extension): the override is renamed, and nothing
114
+ // asks it (no runtime sends the message).
115
+ out = out.replace(/\boverride[ \t]+((?:(?:public|open|internal|fileprivate|private)[ \t]+)?func[ \t]+)responds(\(\s*to\b)/g, '$1_swiftBrowserResponds$2');
116
+ // CoreFoundation's headers need wasi-libc's emulations (WASI_EMULATION_FLAGS),
117
+ // which only the module importing it is compiled with. Imported
118
+ // implementation-only, its clients never load it (an `internal import` of
119
+ // a module without library evolution, they still would).
120
+ out = out.replace(/^([ \t]*)import CoreFoundation[ \t]*(\r?)$/gm, (_, indent, cr) => `${indent}@_implementationOnly import CoreFoundation${cr}`);
121
+ out = selectorPointers(out);
122
+ out = out.replace(/^([ \t]*)import FoundationLite[ \t]*(\r?)$/gm, (_, indent, cr) => {
123
+ foundationRewrites += 1;
124
+ return `${indent}import FoundationBridge${cr}`;
125
+ });
126
+ // Objective-C protocols and what Swift allows only with the runtime (objc.ts).
127
+ out = objcProtocols(out);
128
+ out = optionalRequirementCalls(out, new Set([...(options.optionalRequirements ?? optionalRequirementNames([text])), ...SHIM_OPTIONAL_REQUIREMENTS]));
129
+ out = extensionOverrides(out);
130
+ out = keyPathStrings(out);
131
+ // `ProcessInfo()` is the process's info on Apple platforms; corelibs' initializer is internal.
132
+ out = out.replace(/\bProcessInfo\(\)/g, 'ProcessInfo.processInfo');
133
+ out = rewriteObjC(out);
134
+ out = sceneDelegateClasses(out);
135
+ out = numberCasts(out);
136
+ const stripped = stripPreviews(out);
137
+ return { text: stripped.text, previewsStripped: stripped.count, foundationRewrites };
138
+ }
139
+ /**
140
+ * `configuration.delegateClass = SceneDelegate.self` →
141
+ * `configuration._setDelegateClass(SceneDelegate.self) { SceneDelegate() }`:
142
+ * UIKit makes the scene delegate through the Objective-C runtime, and Swift
143
+ * without it can only make an instance from a metatype through a `required`
144
+ * initializer (UIKit/SceneDelegates.swift). Same line, so diagnostics keep
145
+ * their places.
146
+ */
147
+ export function sceneDelegateClasses(text) {
148
+ if (!text.includes('delegateClass'))
149
+ return text;
150
+ return text.replace(/(\b[A-Za-z_][\w.]*)\.delegateClass\s*=\s*([A-Z][\w.]*)\.self\b/g, '$1._setDelegateClass($2.self) { $2() }');
151
+ }
152
+ /**
153
+ * `as? AnyObject` → `as? _AnyObjectValue` where the file reads a number from
154
+ * the result: `(dict[key] as? AnyObject)?.doubleValue` looks the member up on
155
+ * any object through Objective-C (nil where there is none); FoundationBridge's
156
+ * `_AnyObjectValue` (Selector.swift) has those members, optional likewise, on
157
+ * the numbers, booleans and strings a dictionary of `Any` holds.
158
+ */
159
+ export function numberCasts(text) {
160
+ if (!/\bas\?\s*AnyObject\b/.test(text))
161
+ return text;
162
+ if (!/\.(?:doubleValue|floatValue|intValue|integerValue|int64Value|uint64Value|uintValue|boolValue)\b/.test(text))
163
+ return text;
164
+ return text.replace(/\bas\?(\s*)AnyObject\b/g, 'as?$1_AnyObjectValue');
165
+ }
166
+ /** Whether a source uses the Objective-C runtime's selectors or attributes. */
167
+ export function usesObjC(text) {
168
+ return /#selector\s*\(|@objc\b|@objcMembers\b/.test(text);
169
+ }
170
+ /**
171
+ * Whether a source calls POSIX mutex functions. WASI's libc module leaves
172
+ * `pthread.h` out, so a package that does is built from a copy: there
173
+ * `#if os(iOS)` holds (Darwin's `os_unfair_lock` branch) and FoundationBridge
174
+ * declares the mutex functions for the page's single thread (Locks.swift).
175
+ */
176
+ /**
177
+ * `unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self)` →
178
+ * `Selector(#function)._uniquePointer`: the address that stands for the
179
+ * selector's name (FoundationBridge/Selector.swift), which on Apple
180
+ * platforms the bit cast gives, the selector being a uniqued pointer.
181
+ */
182
+ export function selectorPointers(text) {
183
+ if (!text.includes('unsafeBitCast'))
184
+ return text;
185
+ return text.replace(/\bunsafeBitCast\(\s*(Selector\((?:[^()]|\([^()]*\))*\))\s*,\s*to:\s*UnsafeRawPointer\.self\s*\)/g, '$1._uniquePointer');
186
+ }
187
+ /** Whether a source imports ObjectiveC, which the SDK does not have on WASI (transformSource). */
188
+ export function importsObjectiveC(text) {
189
+ return importsOf(text).includes('ObjectiveC');
190
+ }
191
+ /** Whether a source imports Dispatch, which the SDK does not have on WASI (transformSource). */
192
+ export function importsDispatch(text) {
193
+ return importsOf(text).includes('Dispatch');
194
+ }
195
+ /** CoreFoundation's headers need wasi-libc's signal and mman emulations (`WASI_EMULATION_FLAGS`). */
196
+ export function importsCoreFoundation(text) {
197
+ return importsOf(text).includes('CoreFoundation');
198
+ }
199
+ /** What a Swift module that imports CoreFoundation is compiled with for WASI: the emulations its headers include (the link has their libraries). */
200
+ export const WASI_EMULATION_FLAGS = ['-Xcc', '-D_WASI_EMULATED_SIGNAL', '-Xcc', '-D_WASI_EMULATED_MMAN'];
201
+ export function usesPosixMutex(text) {
202
+ return /\bpthread_mutex(?:attr)?_(?!t\b)[a-z]+\s*\(/.test(text);
203
+ }
204
+ /**
205
+ * Objective-C without its runtime (FoundationBridge/Selector.swift). The
206
+ * browser's Swift has no Objective-C interop, so `@objc` (and the Interface
207
+ * Builder attributes that imply it) is dropped, and `#selector(handle(_:))`
208
+ * becomes `Selector(Self.handle(_:), name: "handle(_:)")`: the method itself,
209
+ * which the target/selector APIs call on their target. A qualified method
210
+ * (`Type.method`) is used as it is; one of another object
211
+ * (`#selector(model.refresh)`) is called bound; `getter:`/`setter:`
212
+ * selectors only have a name, as does a method of three or more arguments
213
+ * (`navigationController(_:didShow:animated:)`), which target-action never
214
+ * sends (it passes the sender and the event at most) and code only compares
215
+ * or asks about (`responds(to:)`). A method the file declares `@MainActor` is
216
+ * bound to its target in a closure instead,
217
+ * `Selector(name: "tick", isolated: { (target: Self) in target.tick })`:
218
+ * Swift 6 rejects naming a main-actor method of a class that is not
219
+ * `Sendable` unapplied (Gifu's display link proxy).
220
+ */
221
+ export function rewriteObjC(text) {
222
+ if (!usesObjC(text))
223
+ return text;
224
+ let out = text.replace(/@(?:objc\b(?:\([^)\n]*\))?|(?:objcMembers|IBAction|IBOutlet|IBInspectable|IBDesignable)\b)[ \t]*/g, '');
225
+ 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]));
226
+ let result = '';
227
+ let index = 0;
228
+ const selectors = /#selector\s*\(/g;
229
+ for (;;) {
230
+ selectors.lastIndex = index;
231
+ const match = selectors.exec(out);
232
+ if (!match)
233
+ break;
234
+ const open = match.index + match[0].length - 1;
235
+ const close = matchingParen(out, open);
236
+ if (close < 0)
237
+ break;
238
+ const inner = out.slice(open + 1, close).trim();
239
+ const name = inner.replace(/\s+/g, '');
240
+ result += out.slice(index, match.index);
241
+ const accessor = /^(?:getter|setter)\s*:\s*(.+)$/s.exec(inner);
242
+ let method = inner.replace(/^self\s*\.\s*/, '');
243
+ const qualified = /^([A-Z][A-Za-z0-9_]*)\s*\.\s*(.+)$/s.exec(method);
244
+ const unqualified = method.replace(/^`?([A-Za-z_][A-Za-z0-9_]*)`?/, '$1');
245
+ const base = /^([A-Za-z_][A-Za-z0-9_]*)/.exec(qualified ? qualified[2] : unqualified)?.[1];
246
+ if (accessor) {
247
+ result += `Selector(${JSON.stringify(accessor[1].replace(/\s+/g, ''))})`;
248
+ }
249
+ else if ((name.match(/:/g) ?? []).length >= 3) {
250
+ result += `Selector(${JSON.stringify(name.replace(/^(?:self|Self|[A-Z][A-Za-z0-9_]*)\./, ''))})`;
251
+ }
252
+ else if (base && mainActorMethods.has(base) && (qualified || !/^[A-Za-z_][A-Za-z0-9_]*\s*\./.test(method))) {
253
+ const type = qualified ? qualified[1] : 'Self';
254
+ const member = (qualified ? qualified[2] : unqualified).replace(/\s+/g, '');
255
+ result += `Selector(name: ${JSON.stringify(name)}, isolated: { (target: ${type}) in target.${member} })`;
256
+ }
257
+ else if (/^[A-Z][A-Za-z0-9_]*\s*\./.test(method)) {
258
+ result += `Selector(${method}, name: ${JSON.stringify(name)})`;
259
+ }
260
+ else if (/^[A-Za-z_][A-Za-z0-9_]*\s*\./.test(method)) {
261
+ result += `Selector(bound: ${method}, name: ${JSON.stringify(name)})`;
262
+ }
263
+ else {
264
+ method = method.replace(/^`?([A-Za-z_][A-Za-z0-9_]*)`?/, '$1');
265
+ result += `Selector(Self.${method}, name: ${JSON.stringify(name)})`;
266
+ }
267
+ index = close + 1;
268
+ }
269
+ result += out.slice(index);
270
+ return result;
271
+ }
272
+ /** Index of the `)` closing the `(` at `open`; -1 when unbalanced. */
273
+ function matchingParen(text, open) {
274
+ let depth = 0;
275
+ for (let i = open; i < text.length; i += 1) {
276
+ if (text[i] === '(')
277
+ depth += 1;
278
+ else if (text[i] === ')') {
279
+ depth -= 1;
280
+ if (depth === 0)
281
+ return i;
282
+ }
283
+ }
284
+ return -1;
285
+ }
286
+ /** A `#if` or `#elseif` line that tests `os(iOS)`. */
287
+ const IOS_CONDITION = /^[ \t]*#(?:if|elseif)\b[^\n]*\bos\(\s*iOS\s*\)[^\n]*$/gm;
288
+ const OBJC_CONDITION = /^[ \t]*#(?:if|elseif)\b[^\n]*\bcanImport\(\s*ObjectiveC\s*\)[^\n]*$/gm;
289
+ /** `import UIKit.UIImage`-style imports of the frameworks the shim provides as Swift modules. */
290
+ 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;
291
+ /** Replaces every `#Preview …{ … }` block and `PreviewProvider` struct with blank lines of the same count. */
292
+ export function stripPreviews(text) {
293
+ let count = 0;
294
+ let result = '';
295
+ let index = 0;
296
+ const pattern = /^[ \t]*(?:#Preview\b|(?:(?:@\w+(?:\([^)\n]*\))?|public|internal|fileprivate|private)\s+)*struct[ \t]+\w+[ \t]*:[^{\n]*\bPreviewProvider\b)/gm;
297
+ for (;;) {
298
+ pattern.lastIndex = index;
299
+ const match = pattern.exec(text);
300
+ if (!match)
301
+ break;
302
+ let start = match.index;
303
+ const open = text.indexOf('{', start);
304
+ if (open < 0)
305
+ break;
306
+ const close = matchingBrace(text, open);
307
+ if (close < 0)
308
+ break;
309
+ // Consume the rest of the line after the closing brace (usually nothing).
310
+ let end = close + 1;
311
+ while (end < text.length && (text[end] === ' ' || text[end] === '\t'))
312
+ end += 1;
313
+ // Attribute lines just above it (`@available(iOS 17, *)`) go with it.
314
+ for (;;) {
315
+ const lineEnd = start - 1;
316
+ if (lineEnd < index || text[lineEnd] !== '\n')
317
+ break;
318
+ const lineStart = text.lastIndexOf('\n', lineEnd - 1) + 1;
319
+ if (lineStart < index || !/^[ \t]*@\w+(?:\([^)\n]*\))?[ \t]*\r?$/.test(text.slice(lineStart, lineEnd)))
320
+ break;
321
+ start = lineStart;
322
+ }
323
+ result += text.slice(index, start);
324
+ const removed = text.slice(start, end);
325
+ result += removed.replace(/[^\n]/g, '');
326
+ count += 1;
327
+ index = end;
328
+ }
329
+ result += text.slice(index);
330
+ return { text: result, count };
331
+ }
332
+ /** Index of the `}` closing the `{` at `open`, skipping strings and comments; -1 when unbalanced. */
333
+ export function matchingBrace(text, open) {
334
+ let depth = 0;
335
+ let i = open;
336
+ while (i < text.length) {
337
+ const ch = text[i];
338
+ const next = text[i + 1];
339
+ if (ch === '/' && next === '/') {
340
+ i = text.indexOf('\n', i);
341
+ if (i < 0)
342
+ return -1;
343
+ continue;
344
+ }
345
+ if (ch === '/' && next === '*') {
346
+ const end = text.indexOf('*/', i + 2);
347
+ if (end < 0)
348
+ return -1;
349
+ i = end + 2;
350
+ continue;
351
+ }
352
+ if (ch === '"') {
353
+ i = skipString(text, i);
354
+ if (i < 0)
355
+ return -1;
356
+ continue;
357
+ }
358
+ if (ch === '{')
359
+ depth += 1;
360
+ if (ch === '}') {
361
+ depth -= 1;
362
+ if (depth === 0)
363
+ return i;
364
+ }
365
+ i += 1;
366
+ }
367
+ return -1;
368
+ }
369
+ /** Index just past a string literal starting at `start` (`"…"`, `"""…"""`, raw `#"…"#`), interpolations included; -1 when unterminated. */
370
+ function skipString(text, start) {
371
+ const triple = text.startsWith('"""', start);
372
+ const quote = triple ? '"""' : '"';
373
+ let i = start + quote.length;
374
+ while (i < text.length) {
375
+ if (text[i] === '\\') {
376
+ if (text[i + 1] === '(') {
377
+ // Interpolation: skip to its matching parenthesis.
378
+ let depth = 0;
379
+ let j = i + 1;
380
+ while (j < text.length) {
381
+ const c = text[j];
382
+ if (c === '"') {
383
+ j = skipString(text, j);
384
+ if (j < 0)
385
+ return -1;
386
+ continue;
387
+ }
388
+ if (c === '(')
389
+ depth += 1;
390
+ if (c === ')') {
391
+ depth -= 1;
392
+ if (depth === 0)
393
+ break;
394
+ }
395
+ j += 1;
396
+ }
397
+ i = j + 1;
398
+ continue;
399
+ }
400
+ i += 2;
401
+ continue;
402
+ }
403
+ if (text.startsWith(quote, i))
404
+ return i + quote.length;
405
+ i += 1;
406
+ }
407
+ return -1;
408
+ }
409
+ /** C-ABI entry points the browser renderer calls; the linker must keep them. */
410
+ export const WASM_EXPORTS = ['sb_event', 'sb_ops_ptr', 'sb_ops_len', 'sb_ops_clear',
411
+ 'sb_alloc', 'sb_event_json', 'sb_state_ptr', 'sb_state_len', 'sb_run_jobs',
412
+ 'sb_bytes', 'sb_bytes_count', 'sb_bytes_ptr', 'sb_bytes_len'];
413
+ /** The reconciler recurses once per view; 8 MB of stack like a native thread. */
414
+ export const APP_STACK_SIZE = 8 * 1024 * 1024;
415
+ /**
416
+ * Locale.current follows the host's choice (SB_LOCALE): the two functions
417
+ * that hard-code en_001 in swift-foundation are redirected to FoundationBridge.
418
+ */
419
+ export const LOCALE_WRAPS = [
420
+ '$s20FoundationEssentials11LocaleCacheV11preferencesAA0C11PreferencesV_SbtyF',
421
+ '$s20FoundationEssentials6LocaleV18preferredLanguagesSaySSGvgZ',
422
+ ];
423
+ /** The linker flags of the app's target (the manifest's `wasmExports`, without `-Xlinker`). */
424
+ export function appLinkerFlags() {
425
+ return [
426
+ ...WASM_EXPORTS.map((name) => `--export=${name}`),
427
+ '-z', `stack-size=${APP_STACK_SIZE}`,
428
+ ...LOCALE_WRAPS.map((symbol) => `--wrap=${symbol}`),
429
+ ];
430
+ }
431
+ /** The shim's products the app links: the defaults, SwiftData and the frameworks it imports. */
432
+ export function linkedProducts(features) {
433
+ return [...new Set(['SwiftUI', 'Charts', 'FoundationBridge', 'os', ...(features.swiftData ? ['SwiftData'] : []), ...features.frameworks])];
434
+ }
435
+ /** The features `sources` (their text) call for. */
436
+ export function featuresOf(sources) {
437
+ const imported = new Set(sources.flatMap((text) => importsOf(text)));
438
+ // `import SwiftUI` brings UIKit along, as on iOS (transformSource).
439
+ if (imported.has('SwiftUI'))
440
+ imported.add('UIKit');
441
+ return {
442
+ swiftData: imported.has('SwiftData'),
443
+ frameworks: LINKED_FRAMEWORKS.filter((framework) => imported.has(framework)),
444
+ };
445
+ }
446
+ /** The staged file that settles names declared by both the shim and Foundation. */
447
+ export const FULL_FOUNDATION_FILE = '_SwiftBrowserFoundation.swift';
448
+ /**
449
+ * The app imports both the shim and Foundation, and both declare a few names
450
+ * (a module does not shadow another's declarations, only the current module's
451
+ * do). A typealias in the app module does: these point the shared names at
452
+ * the shim's types (what its `frame(width:)` and friends take) and at
453
+ * FoundationBridge's `Timer` and `RunLoop`, which Foundation lacks on WASI
454
+ * (`RunLoop` is marked unavailable, its `Timer` needs CFRunLoop and does not
455
+ * link).
456
+ */
457
+ export function fullFoundationSource(features = { swiftData: false, frameworks: [] }, declared = new Set()) {
458
+ // A name the module declares itself (an app's own `Operation` or `Timer`)
459
+ // is the module's: an alias of it would be a redeclaration.
460
+ const text = fullFoundationTemplate(features);
461
+ if (declared.size === 0)
462
+ return text;
463
+ return text
464
+ .split('\n')
465
+ .filter((line) => {
466
+ const alias = /^public typealias ([A-Za-z_][A-Za-z0-9_]*) = /.exec(line);
467
+ return !alias || !declared.has(alias[1]);
468
+ })
469
+ .join('\n');
470
+ }
471
+ /** The type names a module's sources declare at the top level (lines that start with the declaration). */
472
+ export function declaredTypeNames(sources) {
473
+ const names = new Set();
474
+ const pattern = /^\s*(?:@[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_]*)/;
475
+ for (const text of sources) {
476
+ // Top-level declarations only (outside any braces), indented or not:
477
+ // code inside `#if` is often indented (swift-composable-architecture's packages).
478
+ let depth = 0;
479
+ for (const line of text.split('\n')) {
480
+ if (depth === 0) {
481
+ const match = pattern.exec(line);
482
+ if (match)
483
+ names.add(match[1]);
484
+ }
485
+ const code = line.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/\/\/.*$/, '');
486
+ for (const char of code) {
487
+ if (char === '{')
488
+ depth++;
489
+ else if (char === '}')
490
+ depth = Math.max(0, depth - 1);
491
+ }
492
+ }
493
+ }
494
+ return names;
495
+ }
496
+ function fullFoundationTemplate(features) {
497
+ return `// Generated by @swiftbrowser/cli; rewritten on every build.
498
+ // The shim and Foundation both declare these names. A typealias in the app
499
+ // module settles each in the shim's favor (SwiftUI's geometry) or on the
500
+ // bridge (Timer and RunLoop, which the SDK's Foundation does not have on WASI).
501
+ // The imports are public: a package may make imports internal by default
502
+ // (\`InternalImportsByDefault\`), and public aliases need public imports.
503
+ public import SwiftUI
504
+ public import Foundation
505
+ public import FoundationBridge
506
+ public import os
507
+
508
+ // Public, so the app's own public API may use the names (a public method of
509
+ // a Layout returning CGSize, for one).
510
+ public typealias CGFloat = SwiftUI.CGFloat
511
+ public typealias CGPoint = SwiftUI.CGPoint
512
+ public typealias CGSize = SwiftUI.CGSize
513
+ public typealias CGRect = SwiftUI.CGRect
514
+ public typealias Timer = FoundationBridge.Timer
515
+ public typealias RunLoop = FoundationBridge.RunLoop
516
+ // UserDefaults persists through the browser; Bundle reads the app's resources.
517
+ // Foundation's own cannot persist on WASI, and its Bundle.main traps.
518
+ public typealias UserDefaults = FoundationBridge.UserDefaults
519
+ public typealias Bundle = FoundationBridge.Bundle
520
+ // Networking runs over the browser's fetch; Foundation's own is unavailable on
521
+ // WASI (it moved to FoundationNetworking, which the SDK does not ship).
522
+ public typealias URLSession = FoundationBridge.URLSession
523
+ public typealias URLSessionConfiguration = FoundationBridge.URLSessionConfiguration
524
+ public typealias URLSessionTask = FoundationBridge.URLSessionTask
525
+ public typealias URLSessionDataTask = FoundationBridge.URLSessionDataTask
526
+ public typealias URLSessionUploadTask = FoundationBridge.URLSessionUploadTask
527
+ public typealias URLSessionDelegate = FoundationBridge.URLSessionDelegate
528
+ public typealias URLSessionTaskDelegate = FoundationBridge.URLSessionTaskDelegate
529
+ public typealias URLSessionDataDelegate = FoundationBridge.URLSessionDataDelegate
530
+ public typealias URLRequest = FoundationBridge.URLRequest
531
+ public typealias URLResponse = FoundationBridge.URLResponse
532
+ public typealias HTTPURLResponse = FoundationBridge.HTTPURLResponse
533
+ public typealias URLError = FoundationBridge.URLError
534
+ // The SDK has no Dispatch on WASI; the bridge's queues run on the main actor.
535
+ public typealias DispatchQueue = FoundationBridge.DispatchQueue
536
+ public typealias DispatchTime = FoundationBridge.DispatchTime
537
+ public typealias DispatchTimeInterval = FoundationBridge.DispatchTimeInterval
538
+ public typealias DispatchQoS = FoundationBridge.DispatchQoS
539
+ public typealias DispatchSpecificKey = FoundationBridge.DispatchSpecificKey
540
+ // Dispatch declares DispatchTime's arithmetic globally (so that .now() + 0.5
541
+ // finds it); declared here, it reaches files that import only Foundation.
542
+ @usableFromInline func + (time: DispatchTime, seconds: Double) -> DispatchTime { time._adding(nanoseconds: seconds * 1_000_000_000) }
543
+ @usableFromInline func - (time: DispatchTime, seconds: Double) -> DispatchTime { time._adding(nanoseconds: -seconds * 1_000_000_000) }
544
+ @usableFromInline func + (time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime { FoundationBridge.DispatchTime._plus(time, interval) }
545
+ @usableFromInline func - (time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime { FoundationBridge.DispatchTime._minus(time, interval) }
546
+ public typealias DispatchSource = FoundationBridge.DispatchSource
547
+ public typealias DispatchWorkItem = FoundationBridge.DispatchWorkItem
548
+ public typealias DispatchWorkItemFlags = FoundationBridge.DispatchWorkItemFlags
549
+ public typealias DispatchGroup = FoundationBridge.DispatchGroup
550
+ public typealias DispatchSourceProtocol = FoundationBridge.DispatchSourceProtocol
551
+ public typealias DispatchSourceMemoryPressure = FoundationBridge.DispatchSourceMemoryPressure
552
+ public typealias DispatchSourceTimer = FoundationBridge.DispatchSourceTimer
553
+ // Nor Operation and OperationQueue (the bridge's run on the main actor), nor
554
+ // the rest of URL loading's types.
555
+ public typealias Operation = FoundationBridge.Operation
556
+ public typealias BlockOperation = FoundationBridge.BlockOperation
557
+ public typealias OperationQueue = FoundationBridge.OperationQueue
558
+ public typealias URLCache = FoundationBridge.URLCache
559
+ public typealias CachedURLResponse = FoundationBridge.CachedURLResponse
560
+ public typealias URLSessionTaskMetrics = FoundationBridge.URLSessionTaskMetrics
561
+ public typealias URLAuthenticationChallenge = FoundationBridge.URLAuthenticationChallenge
562
+ public typealias URLCredential = FoundationBridge.URLCredential
563
+ public typealias URLProtectionSpace = FoundationBridge.URLProtectionSpace
564
+ public typealias URLSessionWebSocketTask = FoundationBridge.URLSessionWebSocketTask
565
+ // Objective-C's selectors without its runtime (FoundationBridge/Selector.swift).
566
+ public typealias Selector = FoundationBridge.Selector
567
+ // WASI's Foundation keeps only the message-based center; the bridge has the classic one.
568
+ public typealias NotificationCenter = FoundationBridge.NotificationCenter
569
+ @usableFromInline func NSSelectorFromString(_ name: String) -> FoundationBridge.Selector { FoundationBridge.NSSelectorFromString(name) }
570
+ @usableFromInline func NSStringFromSelector(_ selector: FoundationBridge.Selector) -> String { FoundationBridge.NSStringFromSelector(selector) }
571
+ public typealias URLSessionWebSocketDelegate = FoundationBridge.URLSessionWebSocketDelegate
572
+ // Nor RelativeDateTimeFormatter ("2 hr. ago").
573
+ public typealias RelativeDateTimeFormatter = FoundationBridge.RelativeDateTimeFormatter
574
+ // What Darwin's Foundation brings along: the Core Foundation clock, the
575
+ // Objective-C runtime's autorelease pools and associated objects, and os's lock.
576
+ public typealias CFAbsoluteTime = FoundationBridge.CFAbsoluteTime
577
+ public typealias CFTimeInterval = FoundationBridge.CFTimeInterval
578
+ @usableFromInline func CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime { FoundationBridge.CFAbsoluteTimeGetCurrent() }
579
+ @usableFromInline func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result { try FoundationBridge.autoreleasepool(invoking: body) }
580
+ public typealias objc_AssociationPolicy = FoundationBridge.objc_AssociationPolicy
581
+ @usableFromInline func objc_setAssociatedObject(_ object: Any, _ key: UnsafeRawPointer, _ value: Any?, _ policy: objc_AssociationPolicy) { FoundationBridge.objc_setAssociatedObject(object, key, value, policy) }
582
+ @usableFromInline func objc_getAssociatedObject(_ object: Any, _ key: UnsafeRawPointer) -> Any? { FoundationBridge.objc_getAssociatedObject(object, key) }
583
+ @usableFromInline func class_getInstanceSize(_ cls: AnyClass?) -> Int { FoundationBridge.class_getInstanceSize(cls) }
584
+ @usableFromInline func sysctlbyname(_ name: UnsafePointer<CChar>?, _ oldp: UnsafeMutableRawPointer?, _ oldlenp: UnsafeMutablePointer<Int>?, _ newp: UnsafeMutableRawPointer?, _ newlen: Int) -> Int32 { FoundationBridge.sysctlbyname(name, oldp, oldlenp, newp, newlen) }
585
+ // ProcessInfo.processInfo.physicalMemory, which reads 0 on WASI (the staging rewrites it to this).
586
+ @usableFromInline var _swiftBrowserPhysicalMemory: UInt64 { FoundationBridge._physicalMemory }
587
+ public typealias os_unfair_lock = os.os_unfair_lock
588
+ public typealias os_unfair_lock_t = os.os_unfair_lock_t
589
+ public typealias os_unfair_lock_s = os.os_unfair_lock_s
590
+ @usableFromInline func os_unfair_lock_lock(_ lock: os_unfair_lock_t) { os.os_unfair_lock_lock(lock) }
591
+ @usableFromInline func os_unfair_lock_unlock(_ lock: os_unfair_lock_t) { os.os_unfair_lock_unlock(lock) }
592
+ @usableFromInline func os_unfair_lock_trylock(_ lock: os_unfair_lock_t) -> Bool { os.os_unfair_lock_trylock(lock) }
593
+ // FoundationBridge's POSIX mutex functions (Locks.swift; WebAssembly only),
594
+ // settled here over libc's: a module that imports CoreFoundation sees
595
+ // pthread.h's too.
596
+ #if os(WASI)
597
+ @discardableResult @usableFromInline func pthread_mutex_init(_ mutex: UnsafeMutablePointer<pthread_mutex_t>?, _ attributes: UnsafePointer<pthread_mutexattr_t>?) -> Int32 { FoundationBridge.pthread_mutex_init(mutex, attributes) }
598
+ @discardableResult @usableFromInline func pthread_mutex_destroy(_ mutex: UnsafeMutablePointer<pthread_mutex_t>?) -> Int32 { FoundationBridge.pthread_mutex_destroy(mutex) }
599
+ @discardableResult @usableFromInline func pthread_mutex_lock(_ mutex: UnsafeMutablePointer<pthread_mutex_t>?) -> Int32 { FoundationBridge.pthread_mutex_lock(mutex) }
600
+ @discardableResult @usableFromInline func pthread_mutex_trylock(_ mutex: UnsafeMutablePointer<pthread_mutex_t>?) -> Int32 { FoundationBridge.pthread_mutex_trylock(mutex) }
601
+ @discardableResult @usableFromInline func pthread_mutex_unlock(_ mutex: UnsafeMutablePointer<pthread_mutex_t>?) -> Int32 { FoundationBridge.pthread_mutex_unlock(mutex) }
602
+ @discardableResult @usableFromInline func pthread_mutexattr_init(_ attributes: UnsafeMutablePointer<pthread_mutexattr_t>?) -> Int32 { FoundationBridge.pthread_mutexattr_init(attributes) }
603
+ @discardableResult @usableFromInline func pthread_mutexattr_destroy(_ attributes: UnsafeMutablePointer<pthread_mutexattr_t>?) -> Int32 { FoundationBridge.pthread_mutexattr_destroy(attributes) }
604
+ @discardableResult @usableFromInline func pthread_mutexattr_settype(_ attributes: UnsafeMutablePointer<pthread_mutexattr_t>?, _ type: Int32) -> Int32 { FoundationBridge.pthread_mutexattr_settype(attributes, type) }
605
+ @usableFromInline var PTHREAD_MUTEX_NORMAL: Int32 { FoundationBridge.PTHREAD_MUTEX_NORMAL }
606
+ @usableFromInline var PTHREAD_MUTEX_RECURSIVE: Int32 { FoundationBridge.PTHREAD_MUTEX_RECURSIVE }
607
+ @usableFromInline var PTHREAD_MUTEX_ERRORCHECK: Int32 { FoundationBridge.PTHREAD_MUTEX_ERRORCHECK }
608
+ @usableFromInline var PTHREAD_MUTEX_DEFAULT: Int32 { FoundationBridge.PTHREAD_MUTEX_DEFAULT }
609
+ #endif
610
+ ${features.swiftData ? SWIFTDATA_PREDICATE : ''}`;
611
+ }
612
+ /**
613
+ * For an app that imports SwiftData: `#Predicate` expands with SwiftData's
614
+ * copy of Foundation's macro, which accepts `localizedStandardContains`,
615
+ * `localizedCompare` and `caseInsensitiveCompare` as Apple's does (the
616
+ * open-source macro rejects them). A macro declared in the app module shadows
617
+ * Foundation's; the type `Predicate` stays Foundation's.
618
+ */
619
+ const SWIFTDATA_PREDICATE = `
620
+ // SwiftData: #Predicate as on Apple platforms (Sources/SwiftDataMacros/PredicateMacro.swift).
621
+ @freestanding(expression)
622
+ macro Predicate<each Input>(_ body: (repeat each Input) -> Bool) -> Foundation.Predicate<repeat each Input> = #externalMacro(module: "SwiftDataMacros", type: "PredicateMacro")
623
+ `;
624
+ //# sourceMappingURL=appSources.js.map