@swiftbrowser/cli 0.1.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 (45) hide show
  1. package/README.md +166 -0
  2. package/bin/swiftbrowser.js +11 -0
  3. package/dist/build.d.ts +36 -0
  4. package/dist/build.js +124 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +137 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/build.d.ts +8 -0
  10. package/dist/commands/build.js +33 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/check.d.ts +4 -0
  13. package/dist/commands/check.js +27 -0
  14. package/dist/commands/check.js.map +1 -0
  15. package/dist/commands/dev.d.ts +19 -0
  16. package/dist/commands/dev.js +83 -0
  17. package/dist/commands/dev.js.map +1 -0
  18. package/dist/commands/doctor.d.ts +12 -0
  19. package/dist/commands/doctor.js +66 -0
  20. package/dist/commands/doctor.js.map +1 -0
  21. package/dist/commands/shared.d.ts +23 -0
  22. package/dist/commands/shared.js +73 -0
  23. package/dist/commands/shared.js.map +1 -0
  24. package/dist/compat.d.ts +27 -0
  25. package/dist/compat.js +111 -0
  26. package/dist/compat.js.map +1 -0
  27. package/dist/config.d.ts +45 -0
  28. package/dist/config.js +70 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/icu.d.ts +94 -0
  31. package/dist/icu.js +368 -0
  32. package/dist/icu.js.map +1 -0
  33. package/dist/log.d.ts +16 -0
  34. package/dist/log.js +28 -0
  35. package/dist/log.js.map +1 -0
  36. package/dist/project.d.ts +56 -0
  37. package/dist/project.js +264 -0
  38. package/dist/project.js.map +1 -0
  39. package/dist/stage.d.ts +78 -0
  40. package/dist/stage.js +344 -0
  41. package/dist/stage.js.map +1 -0
  42. package/dist/toolchain.d.ts +42 -0
  43. package/dist/toolchain.js +159 -0
  44. package/dist/toolchain.js.map +1 -0
  45. package/package.json +37 -0
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # @swiftbrowser/cli
2
+
3
+ Run an iOS SwiftUI app in the browser, from its Xcode project, without a Mac.
4
+
5
+ ```sh
6
+ npx @swiftbrowser/cli doctor --install # Swift 6.4, the wasm SDK, wasm-opt
7
+ npx @swiftbrowser/cli dev ~/Projects/MyApp # http://localhost:5173, rebuilds on save
8
+ npx @swiftbrowser/cli build ~/Projects/MyApp # ./dist: a static site to deploy anywhere
9
+ ```
10
+
11
+ The app's sources compile unchanged against [SwiftBrowser](https://github.com/dabbott/SwiftBrowser)'s
12
+ SwiftUI shim and run as WebAssembly inside an iPhone frame (or full screen on a
13
+ phone: open the dev server's QR code, then Share → Add to Home Screen). What
14
+ the shim does not provide is a compile error, never a wrong rendering.
15
+
16
+ ## Commands
17
+
18
+ | Command | What it does |
19
+ |---|---|
20
+ | `doctor [--install]` | Checks Node ≥ 22, Swift 6.4, the Swift SDK for WebAssembly and `wasm-opt`; `--install` fetches the SDK (`swift sdk install`) and Binaryen into `~/.cache/swiftbrowser`. Swift itself comes from [swiftly](https://www.swift.org/install/) (`swiftly install 6.4.0`), or point `SWIFT_TOOLCHAIN_BIN` at a toolchain's `usr/bin`. |
21
+ | `check <path>` | Lists what the app imports (supported / rewritten / unsupported, with the usual way around each), then compiles the app natively against the shim. Seconds, no wasm; every error names your file and line. |
22
+ | `dev <path>` | Builds the app for WebAssembly and serves it with the SwiftBrowser dev server: landing page with a QR code at `/`, the app at `/app/?app=<Name>`; saving a Swift file rebuilds and reloads with the app's state carried over. `--port`, `--host` (expose on the LAN for phones), `--open`. |
23
+ | `build <path> [-o dist]` | A release build, optimized and stripped with `wasm-opt` (about 18 MB for a small app, 4.6 MB with brotli; 4.5 MB and 1.2 MB in lean mode), and a complete static site around it. `--no-optimize` skips wasm-opt. |
24
+
25
+ `<path>` is an `.xcodeproj`, a folder holding one (directly or one level
26
+ down), or a plain folder of Swift sources. Options for all three:
27
+ `--target <name>` when the project has several app targets, `--config <file>`,
28
+ `--foundation lean|full`, `--verbose`.
29
+
30
+ ## What happens to your project
31
+
32
+ Nothing, outside `<project>/.swiftbrowser/` (add it to `.gitignore`). The CLI
33
+ reads the Xcode project (`project.pbxproj`, including Xcode 16 synchronized
34
+ folders) for the app target's Swift sources, asset catalogs, display name and
35
+ app icon, then writes a SwiftPM package under `.swiftbrowser/<Name>/` whose one
36
+ executable target is a copy of those sources, adjusted for the shim:
37
+
38
+ - `import SwiftUI` also imports Foundation and the shim's `FoundationBridge`
39
+ (Apple's SwiftUI re-exports Foundation and Combine, so `UUID`, `JSONEncoder`,
40
+ `UserDefaults`, `Timer.publish` are in scope without an import). In lean mode
41
+ it imports `FoundationEssentials` and `FoundationLite` instead, and
42
+ `import Foundation` becomes those two (see Foundation below). Both also
43
+ import Observation, so `@Observable` works in a file that imports only
44
+ Foundation, as it does under Xcode.
45
+ - A generated `_SwiftBrowserFoundation.swift` settles the names both the shim
46
+ and Foundation declare (`CGFloat`, `CGPoint`, `CGSize`, `CGRect`, `Timer`,
47
+ `RunLoop`, `UserDefaults`, `Bundle`) with typealiases, in the shim's favor.
48
+ - The target's Copy Bundle Resources (or `resources` in `swiftbrowser.json`)
49
+ are served next to the module and mounted at `/bundle` before the app
50
+ starts, so `Bundle.main.url(forResource:withExtension:)` and
51
+ `Data(contentsOf:)` read them. Asset catalogs are handled separately
52
+ (`Image("name")`, `Color("name")`).
53
+ - `#Preview { … }` blocks are blanked (the shim has no preview macro).
54
+ - `SWIFTBROWSER` is defined, so platform-specific code can go behind
55
+ `#if SWIFTBROWSER` / `#if !SWIFTBROWSER`, like `#if os(iOS)`.
56
+
57
+ Every change keeps line numbers, so compiler diagnostics point at your files.
58
+ The package depends on `@swiftbrowser/swift` (the shim at the version this CLI
59
+ was released with).
60
+
61
+ ## Foundation
62
+
63
+ Two modes, measured through the same pipeline (release, `wasm-opt -Os`,
64
+ stripped) on the Counter example:
65
+
66
+ | Mode | What `import Foundation` means | Module | gzipped | brotli |
67
+ |---|---|---|---|---|
68
+ | `full` (default) | the wasm SDK's Foundation, ICU data trimmed to English | 18.3 MB | 6.7 MB | 4.6 MB |
69
+ | `full`, `"locales": ["*"]` | the same with ICU's whole data package | 47 MB | 19.4 MB | 12.5 MB |
70
+ | `lean` | `FoundationEssentials` + `FoundationLite` | 4.5 MB | 1.7 MB | 1.2 MB |
71
+
72
+ **Full** links the real Foundation (swift-corelibs-foundation, as the wasm SDK
73
+ ships it): `DateFormatter`, `NumberFormatter`, `NSString`, `Locale`-aware
74
+ `formatted()`, `Measurement`, `NSRegularExpression`, `UserDefaults`, and the
75
+ rest compile as on iOS. The SDK's Foundation carries ICU's complete 34 MB
76
+ locale data package; the CLI pulls that package out of the SDK's archive, keeps
77
+ the locale-independent tables plus the bundles of the languages in `"locales"`
78
+ (default `["en"]`, 4.7 MB; the region part of a tag is ignored, every regional
79
+ variant of a language stays), and links the trimmed package in the original's
80
+ place. Formatting for a language that was dropped falls back to ICU's root
81
+ locale (`11/14/2023, 22:13` and `€ 0.00` for French instead of
82
+ `14/11/2023 22:13` and `0,00 €`); it never fails. Each language adds 100 to
83
+ 300 kB, Chinese 1.7 MB (its collation table); `["*"]` keeps everything. The
84
+ trimmed package is cached per SDK and language set under
85
+ `~/.cache/swiftbrowser/icu/`; the SDK itself is not modified. What Foundation
86
+ lacks on WASI, the shim's `FoundationBridge` supplies: `Timer` (corelibs' needs
87
+ a run loop WASI does not have) with `Timer.publish` + `onReceive`, and
88
+ `RunLoop.main` for its signature; `UserDefaults`, which Foundation's cannot
89
+ persist on WASI, saved by the browser per app (localStorage) and restored at
90
+ the next launch; and `Bundle`, whose Foundation counterpart traps, reading the
91
+ app's resources. Known gaps of corelibs Foundation on WASI: `NotificationCenter`'s
92
+ closure observers and `OperationQueue` are missing.
93
+
94
+ ### Locale and time zone
95
+
96
+ The app runs in the viewer's time zone and, within the languages it ships, the
97
+ viewer's language, as an iOS app follows the device: `Locale.current`,
98
+ `TimeZone.current`, `formatted()`, `DateFormatter` and `Text(_:format:)` all
99
+ follow. The browser's first preferred language whose data the module carries
100
+ (`"locales"`) is used with its region, so a British visitor to an app shipping
101
+ `["en"]` gets `14/11/2023`; a visitor whose language is not shipped gets the
102
+ first shipped language rather than a half-formatted root locale. Add
103
+ `?locale=fr-FR` to the app URL to try a locale without changing the browser
104
+ (an Xcode scheme's "App Language"). The headless harness pins `en_001` and
105
+ GMT unless `SB_LOCALE` / `TZ` are set, so snapshots stay stable. Lean mode
106
+ formats in US English regardless.
107
+
108
+ The hook behind this: swift-foundation hard-codes `en_001` on WASI, so the
109
+ CLI redirects that one function to FoundationBridge at link time
110
+ (`--wrap`); it depends on the SDK's swift-foundation, which the CLI pins and
111
+ CI checks. Lean mode does not have it.
112
+
113
+ **Lean** (`--foundation lean`, or `"foundation": "lean"` in `swiftbrowser.json`)
114
+ is for apps that must stay small: `FoundationEssentials` (Date, Calendar, UUID,
115
+ Data, JSON, URL) plus `FoundationLite`, SwiftBrowser's Foundation-compatible
116
+ layer (number and date format styles, `IndexSet`, `CharacterSet`,
117
+ `UserDefaults`, `Timer`). A Foundation API outside it is a compile error naming
118
+ it. Output is fixed to US English conventions.
119
+
120
+ ## swiftbrowser.json (optional)
121
+
122
+ ```json
123
+ {
124
+ "name": "MyApp",
125
+ "displayName": "My App",
126
+ "target": "MyApp",
127
+ "sources": ["MyApp"],
128
+ "exclude": ["MyApp/Watch"],
129
+ "catalogs": ["MyApp/Assets.xcassets"],
130
+ "resources": ["MyApp/missions.json", "MyApp/Data"],
131
+ "icon": "MyApp/Assets.xcassets/AppIcon.appiconset/icon-1024.png",
132
+ "swiftLanguageMode": "5",
133
+ "foundation": "full",
134
+ "locales": ["en"],
135
+ "defines": ["DEMO"]
136
+ }
137
+ ```
138
+
139
+ Everything is optional; `sources`, `catalogs` and `resources` replace what the
140
+ Xcode project says when set (a plain folder has resources only through the
141
+ config); `locales` matters in full Foundation mode only (see above).
142
+
143
+ ## Frameworks
144
+
145
+ Supported: SwiftUI (the shim's compatibility matrix is in the repository
146
+ README), Observation, Swift Charts, Foundation (full mode), FoundationEssentials
147
+ and FoundationLite (lean mode).
148
+ `check` names the frameworks it knows have no browser counterpart yet (UIKit,
149
+ SwiftData, Core Data, Combine, MapKit, Core ML, AVFoundation, UserNotifications,
150
+ StoreKit, WidgetKit, PhotosUI, WebKit, …) with the usual way out: keep that code
151
+ behind `#if !SWIFTBROWSER` and give the browser a stand-in behind `#if SWIFTBROWSER`.
152
+
153
+ ## Example
154
+
155
+ Twelve of the nineteen "Hacking with SwiftUI" projects run from their Xcode
156
+ projects without a change (WeSplit, Guess the Flag, Views and Modifiers,
157
+ WordScramble, Animations, iExpense, Moonshot, Navigation, Accessibility,
158
+ Flashzilla, Layout and Geometry, SnowSeeker); `check` names what stops each
159
+ of the others, which all need a framework the browser has no counterpart for
160
+ yet (Core ML, URLSession, SwiftData, Core Image, MapKit):
161
+
162
+ ```sh
163
+ git clone --depth 1 https://github.com/twostraws/HackingWithSwift
164
+ npx @swiftbrowser/cli check HackingWithSwift/SwiftUI/project4 # BetterRest: Core ML
165
+ npx @swiftbrowser/cli dev HackingWithSwift/SwiftUI/project17 # Flashzilla
166
+ ```
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ // Entry point of the `swiftbrowser` command (see ../src/cli.ts).
3
+ import { main } from '../dist/cli.js';
4
+
5
+ main(process.argv.slice(2)).then(
6
+ (code) => process.exit(code),
7
+ (error) => {
8
+ console.error(error instanceof Error ? error.message : String(error));
9
+ process.exit(1);
10
+ },
11
+ );
@@ -0,0 +1,36 @@
1
+ import type { ResolvedApp } from './project.js';
2
+ import { type StagedApp } from './stage.js';
3
+ import { type Toolchain } from './toolchain.js';
4
+ export interface RunResult {
5
+ status: number;
6
+ output: string;
7
+ }
8
+ /** Runs a command, capturing stdout+stderr interleaved; `onOutput` streams it as it arrives. */
9
+ export declare function run(command: string, args: string[], options: {
10
+ cwd: string;
11
+ env: NodeJS.ProcessEnv;
12
+ onOutput?: (chunk: string) => void;
13
+ }): Promise<RunResult>;
14
+ /** Only the diagnostics and the summary lines of a SwiftPM build log. */
15
+ export declare function stripAnsi(text: string): string;
16
+ export declare function diagnosticsOnly(output: string): string;
17
+ export interface BuildOptions {
18
+ toolchain: Toolchain;
19
+ staged: StagedApp;
20
+ app: ResolvedApp;
21
+ /** Stream the compiler output while it runs. */
22
+ verbose?: boolean;
23
+ onOutput?: (chunk: string) => void;
24
+ }
25
+ /** `swift build` for the host: compile errors only, no module produced that we use. */
26
+ export declare function checkHost(options: BuildOptions): Promise<void>;
27
+ /**
28
+ * Builds the module for WebAssembly (release) and copies it to `wasmOutput(app)`;
29
+ * with `wasmOpt` set it is also optimized for size and stripped (the ~2.5×
30
+ * smaller download of a `build`; `dev` skips it for quick rebuilds).
31
+ */
32
+ export declare function buildWasm(options: BuildOptions & {
33
+ wasmOpt?: string | null;
34
+ }): Promise<string>;
35
+ export declare function fileSize(file: string): number;
36
+ export declare function formatBytes(bytes: number): string;
package/dist/build.js ADDED
@@ -0,0 +1,124 @@
1
+ // Compiling the staging package: natively for `check` (fast, same
2
+ // diagnostics), for WebAssembly for `dev` and `build`, then wasm-opt.
3
+ import { spawn } from 'node:child_process';
4
+ import { copyFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { CliError } from './log.js';
7
+ import { remapDiagnostics, wasmOutput } from './stage.js';
8
+ import { WASM_SDK, toolchainEnv } from './toolchain.js';
9
+ /** Runs a command, capturing stdout+stderr interleaved; `onOutput` streams it as it arrives. */
10
+ export function run(command, args, options) {
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(command, args, { cwd: options.cwd, env: options.env, stdio: ['ignore', 'pipe', 'pipe'] });
13
+ let output = '';
14
+ const collect = (chunk) => {
15
+ const text = chunk.toString('utf8');
16
+ output += text;
17
+ options.onOutput?.(text);
18
+ };
19
+ child.stdout.on('data', collect);
20
+ child.stderr.on('data', collect);
21
+ child.on('error', reject);
22
+ child.on('close', (status) => resolve({ status: status ?? 1, output }));
23
+ });
24
+ }
25
+ /** Only the diagnostics and the summary lines of a SwiftPM build log. */
26
+ export function stripAnsi(text) {
27
+ // eslint-disable-next-line no-control-regex
28
+ return text.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
29
+ }
30
+ export function diagnosticsOnly(output) {
31
+ const lines = stripAnsi(output).split('\n');
32
+ const kept = [];
33
+ let inDiagnostic = false;
34
+ for (const line of lines) {
35
+ // SwiftPM's own summary lines repeat what the compiler already said.
36
+ if (/^error: (SwiftCompile|Build failed|fatalError)/.test(line) || /^\s+(cd |builtin-)/.test(line)) {
37
+ inDiagnostic = false;
38
+ continue;
39
+ }
40
+ if (/:\d+:\d+: warning: /.test(line)) {
41
+ // Warnings (mostly the shim's own, compiled in the same build) are noise next to the errors.
42
+ inDiagnostic = false;
43
+ continue;
44
+ }
45
+ if (/:\d+:\d+: note: /.test(line)) {
46
+ // A note explains the diagnostic above it; keep it only after an error.
47
+ if (inDiagnostic)
48
+ kept.push(line);
49
+ continue;
50
+ }
51
+ if (/:\d+:\d+: error: /.test(line) || /^error: /.test(line)) {
52
+ inDiagnostic = true;
53
+ kept.push(line);
54
+ }
55
+ else if (inDiagnostic && (/^\s/.test(line) || line === '')) {
56
+ // Source excerpts and carets belong to the diagnostic above.
57
+ kept.push(line);
58
+ if (line === '')
59
+ inDiagnostic = false;
60
+ }
61
+ else {
62
+ inDiagnostic = false;
63
+ }
64
+ }
65
+ return kept.join('\n').trim();
66
+ }
67
+ /** `swift build` for the host: compile errors only, no module produced that we use. */
68
+ export async function checkHost(options) {
69
+ const { toolchain, staged, app } = options;
70
+ const result = await run(toolchain.swift, ['build', '--product', app.name], {
71
+ cwd: staged.dir,
72
+ env: toolchainEnv(toolchain),
73
+ ...(options.onOutput ? { onOutput: options.onOutput } : {}),
74
+ });
75
+ if (result.status !== 0)
76
+ throw new CliError(remapDiagnostics(diagnosticsOnly(result.output) || result.output, staged));
77
+ }
78
+ /**
79
+ * Builds the module for WebAssembly (release) and copies it to `wasmOutput(app)`;
80
+ * with `wasmOpt` set it is also optimized for size and stripped (the ~2.5×
81
+ * smaller download of a `build`; `dev` skips it for quick rebuilds).
82
+ */
83
+ export async function buildWasm(options) {
84
+ const { toolchain, staged, app } = options;
85
+ const env = toolchainEnv(toolchain);
86
+ const common = ['--swift-sdk', WASM_SDK, '-c', 'release'];
87
+ const result = await run(toolchain.swift, ['build', ...common, '--product', app.name], {
88
+ cwd: staged.dir,
89
+ env,
90
+ ...(options.onOutput ? { onOutput: options.onOutput } : {}),
91
+ });
92
+ if (result.status !== 0)
93
+ throw new CliError(remapDiagnostics(diagnosticsOnly(result.output) || result.output, staged));
94
+ const binPath = await run(toolchain.swift, ['build', ...common, '--show-bin-path'], { cwd: staged.dir, env });
95
+ if (binPath.status !== 0)
96
+ throw new CliError(`swift build --show-bin-path failed:\n${binPath.output}`);
97
+ const built = path.join(binPath.output.trim().split('\n').pop(), `${app.name}.wasm`);
98
+ if (!existsSync(built))
99
+ throw new CliError(`expected ${built} after the build`);
100
+ const out = wasmOutput(app);
101
+ mkdirSync(path.dirname(out), { recursive: true });
102
+ if (options.wasmOpt) {
103
+ // -Os without the slowest -Oz passes; the features come from the module.
104
+ // --strip-debug drops DWARF and names, --strip-producers the toolchain record.
105
+ const opt = await run(options.wasmOpt, ['-Os', '--strip-debug', '--strip-producers', built, '-o', out], { cwd: staged.dir, env });
106
+ if (opt.status !== 0)
107
+ throw new CliError(`wasm-opt failed:\n${opt.output}`);
108
+ }
109
+ else {
110
+ copyFileSync(built, out);
111
+ }
112
+ return out;
113
+ }
114
+ export function fileSize(file) {
115
+ return statSync(file).size;
116
+ }
117
+ export function formatBytes(bytes) {
118
+ if (bytes >= 1_000_000)
119
+ return `${(bytes / 1_000_000).toFixed(1)} MB`;
120
+ if (bytes >= 1_000)
121
+ return `${(bytes / 1_000).toFixed(0)} kB`;
122
+ return `${bytes} B`;
123
+ }
124
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const VERSION = "0.1.0";
2
+ export declare function main(argv: string[]): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,137 @@
1
+ // `swiftbrowser <command> [path] [options]`: argument parsing and dispatch.
2
+ import { parseArgs } from 'node:util';
3
+ import { CliError, bold, dim, error } from './log.js';
4
+ export const VERSION = '0.1.0';
5
+ const HELP = `${bold('swiftbrowser')} ${VERSION}: run an iOS SwiftUI app in the browser.
6
+
7
+ ${bold('Usage')}
8
+ swiftbrowser doctor [--install] check (and install) Swift, the wasm SDK and wasm-opt
9
+ swiftbrowser check <path> [options] import report and a native compile against the shim
10
+ swiftbrowser dev <path> [options] run the app with rebuild on save
11
+ swiftbrowser build <path> [-o <dir>] optimized module and a static site
12
+
13
+ ${bold('<path>')} is an .xcodeproj, a folder holding one, or a folder of Swift sources
14
+ (optionally with a swiftbrowser.json).
15
+
16
+ ${bold('Options')}
17
+ --target <name> the Xcode app target to build (when there are several)
18
+ --config <file> a swiftbrowser.json other than the project's
19
+ --foundation <mode> full (default: the wasm SDK's Foundation, ICU data trimmed to the
20
+ project's "locales"; ~19 MB) or lean (import Foundation →
21
+ FoundationEssentials + FoundationLite; ~5 MB, smaller API)
22
+ --port <n>, --host dev server port (default 5173) and LAN exposure (dev)
23
+ --open open the browser (dev)
24
+ -o, --out <dir> output directory (build; default <project>/dist)
25
+ --no-optimize skip wasm-opt (build)
26
+ --verbose stream the compiler output
27
+ -h, --help, --version
28
+ `;
29
+ export async function main(argv) {
30
+ let parsed;
31
+ try {
32
+ parsed = parseArgs({
33
+ args: argv,
34
+ allowPositionals: true,
35
+ options: {
36
+ help: { type: 'boolean', short: 'h' },
37
+ version: { type: 'boolean' },
38
+ install: { type: 'boolean' },
39
+ target: { type: 'string' },
40
+ config: { type: 'string' },
41
+ foundation: { type: 'string' },
42
+ port: { type: 'string' },
43
+ host: { type: 'string' },
44
+ open: { type: 'boolean' },
45
+ out: { type: 'string', short: 'o' },
46
+ optimize: { type: 'boolean', default: true },
47
+ 'no-optimize': { type: 'boolean' },
48
+ verbose: { type: 'boolean' },
49
+ },
50
+ });
51
+ }
52
+ catch (err) {
53
+ error(err instanceof Error ? err.message : String(err));
54
+ console.log(HELP);
55
+ return 2;
56
+ }
57
+ const { values, positionals } = parsed;
58
+ if (values.version) {
59
+ console.log(VERSION);
60
+ return 0;
61
+ }
62
+ const [command, input] = positionals;
63
+ if (values.help || !command) {
64
+ console.log(HELP);
65
+ return command ? 0 : 2;
66
+ }
67
+ const foundationArg = values.foundation;
68
+ if (foundationArg !== undefined && foundationArg !== 'lean' && foundationArg !== 'full') {
69
+ error(`--foundation must be lean or full`);
70
+ return 2;
71
+ }
72
+ const foundation = foundationArg;
73
+ const appOptions = {
74
+ ...(values.target ? { target: values.target } : {}),
75
+ ...(values.config ? { config: values.config } : {}),
76
+ ...(foundation ? { foundation } : {}),
77
+ };
78
+ const needsPath = () => {
79
+ if (!input)
80
+ throw new CliError(`${command} needs a path: an .xcodeproj, a folder with one, or a folder of Swift sources`, 2);
81
+ return input;
82
+ };
83
+ try {
84
+ switch (command) {
85
+ case 'doctor': {
86
+ const { doctor } = await import('./commands/doctor.js');
87
+ const result = await doctor({ ...(values.install ? { install: true } : {}) });
88
+ return result.toolchain && result.wasmSdk ? 0 : 1;
89
+ }
90
+ case 'check': {
91
+ const { check } = await import('./commands/check.js');
92
+ await check(needsPath(), { ...appOptions, ...(values.verbose ? { verbose: true } : {}) });
93
+ return 0;
94
+ }
95
+ case 'dev': {
96
+ const { dev } = await import('./commands/dev.js');
97
+ const port = values.port !== undefined ? Number(values.port) : undefined;
98
+ if (port !== undefined && !Number.isInteger(port))
99
+ throw new CliError('--port must be a number', 2);
100
+ await dev(needsPath(), {
101
+ ...appOptions,
102
+ ...(port !== undefined ? { port } : {}),
103
+ ...(values.host !== undefined ? { host: values.host === '' ? true : values.host } : {}),
104
+ ...(values.open ? { open: true } : {}),
105
+ });
106
+ return 0;
107
+ }
108
+ case 'build': {
109
+ const { build } = await import('./commands/build.js');
110
+ await build(needsPath(), {
111
+ ...appOptions,
112
+ ...(values.out ? { outDir: values.out } : {}),
113
+ ...(values['no-optimize'] ? { optimize: false } : {}),
114
+ ...(values.verbose ? { verbose: true } : {}),
115
+ });
116
+ return 0;
117
+ }
118
+ default:
119
+ error(`unknown command: ${command}`);
120
+ console.log(HELP);
121
+ return 2;
122
+ }
123
+ }
124
+ catch (err) {
125
+ if (err instanceof CliError) {
126
+ error(err.message);
127
+ return err.exitCode;
128
+ }
129
+ error(err instanceof Error ? (err.stack ?? err.message) : String(err));
130
+ return 1;
131
+ }
132
+ finally {
133
+ if (process.stdout.isTTY)
134
+ process.stdout.write(dim(''));
135
+ }
136
+ }
137
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type AppOptions } from './shared.js';
2
+ export interface BuildOptions extends AppOptions {
3
+ outDir?: string;
4
+ verbose?: boolean;
5
+ /** Skip wasm-opt even when available. */
6
+ optimize?: boolean;
7
+ }
8
+ export declare function build(input: string, options: BuildOptions): Promise<void>;
@@ -0,0 +1,33 @@
1
+ // `swiftbrowser build <path> -o <dir>`: an optimized module and a complete
2
+ // static site around it (landing page, app page, catalog, manifest).
3
+ import { mkdirSync, rmSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { buildSite } from '@swiftbrowser/web';
6
+ import { fileSize, formatBytes } from '../build.js';
7
+ import { bold, dim, info, ok, warn } from '../log.js';
8
+ import { findWasmOpt } from '../toolchain.js';
9
+ import { appSpec, icuDataFor } from './dev.js';
10
+ import { describeApp, describeReport, prepare, requireToolchain } from './shared.js';
11
+ export async function build(input, options) {
12
+ const { app, report, staged } = await prepare(input, options);
13
+ describeApp(app);
14
+ describeReport(report, app);
15
+ const toolchain = requireToolchain({ wasm: true });
16
+ const wasmOpt = options.optimize === false ? null : findWasmOpt();
17
+ if (options.optimize !== false && !wasmOpt) {
18
+ info(warn('wasm-opt not found; the module is not optimized (`swiftbrowser doctor --install` downloads binaryen)'));
19
+ }
20
+ const outDir = path.resolve(options.outDir ?? path.join(app.root, 'dist'));
21
+ const spec = appSpec(app, staged, toolchain, { wasmOpt, icu: icuDataFor(app, toolchain) });
22
+ info(dim(`\nbuilding ${app.name} for WebAssembly (release${wasmOpt ? ', wasm-opt -Os' : ''})…`));
23
+ const started = Date.now();
24
+ // Re-stages with the ICU data, builds, and optimizes into `spec.wasm`.
25
+ await spec.build();
26
+ info(ok(`${bold(app.name)}.wasm ${dim(`${formatBytes(fileSize(spec.wasm))}, ${((Date.now() - started) / 1000).toFixed(1)} s`)}`));
27
+ rmSync(outDir, { recursive: true, force: true });
28
+ mkdirSync(outDir, { recursive: true });
29
+ await buildSite({ apps: [spec], outDir, logLevel: options.verbose ? 'info' : 'warn' });
30
+ const shown = path.relative(process.cwd(), outDir);
31
+ info(ok(`site written to ${bold(shown && !shown.startsWith('..') ? shown : outDir)} ${dim(`(serve it from any static host; the app page is /app/?app=${app.name})`)}`));
32
+ }
33
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type AppOptions } from './shared.js';
2
+ export declare function check(input: string, options: AppOptions & {
3
+ verbose?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,27 @@
1
+ // `swiftbrowser check <path>`: the import report, then a native compile of the
2
+ // staged sources against the shim. Seconds, no wasm; every error names the
3
+ // user's own file and line.
4
+ import { checkHost } from '../build.js';
5
+ import { CliError, bold, dim, info, ok } from '../log.js';
6
+ import { describeApp, describeReport, prepare, requireToolchain } from './shared.js';
7
+ export async function check(input, options) {
8
+ const { app, report, staged } = await prepare(input, options);
9
+ describeApp(app);
10
+ const blocked = describeReport(report, app);
11
+ const toolchain = requireToolchain({ wasm: false });
12
+ info(dim(`\ncompiling ${app.name} against the shim (Swift ${toolchain.version}, host build)…`));
13
+ const started = Date.now();
14
+ try {
15
+ await checkHost({ toolchain, staged, app, ...(options.verbose ? { onOutput: (chunk) => process.stdout.write(chunk) } : {}) });
16
+ }
17
+ catch (error) {
18
+ if (error instanceof CliError) {
19
+ info(error.message);
20
+ const lines = (error.message.match(/: error: /g) ?? []).length;
21
+ throw new CliError(`\n${bold(`${app.name} does not compile against the shim`)}: ${lines} error${lines === 1 ? '' : 's'}${blocked ? ' (see the unsupported imports above)' : ''}`);
22
+ }
23
+ throw error;
24
+ }
25
+ info(ok(`${bold(app.name)} compiles against the shim ${dim(`(${((Date.now() - started) / 1000).toFixed(1)} s)`)}`));
26
+ }
27
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,2EAA2E;AAC3E,4BAA4B;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAmB,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtG,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,KAAa,EAAE,OAA2C;IACpF,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC9D,WAAW,CAAC,GAAG,CAAC,CAAC;IACjB,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,IAAI,4BAA4B,SAAS,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC;IAChG,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAChI,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACpB,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YAC/D,MAAM,IAAI,QAAQ,CAAC,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,oCAAoC,CAAC,KAAK,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpL,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,8BAA8B,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AACtH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type AppSpec } from '@swiftbrowser/web';
2
+ import { type ICUData } from '../icu.js';
3
+ import { type ResolvedApp } from '../project.js';
4
+ import { type StagedApp } from '../stage.js';
5
+ import type { Toolchain } from '../toolchain.js';
6
+ import { type AppOptions } from './shared.js';
7
+ export interface DevOptions extends AppOptions {
8
+ port?: number;
9
+ host?: string | boolean;
10
+ open?: boolean;
11
+ }
12
+ /** For a full-Foundation app, the trimmed ICU data (made once per SDK and language set, then cached). */
13
+ export declare function icuDataFor(app: ResolvedApp, toolchain: Toolchain): ICUData | null;
14
+ /** The registry entry the web dev server runs the app from. */
15
+ export declare function appSpec(app: ResolvedApp, staged: StagedApp, toolchain: Toolchain, options: {
16
+ wasmOpt?: string | null;
17
+ icu?: ICUData | null;
18
+ }): AppSpec;
19
+ export declare function dev(input: string, options: DevOptions): Promise<void>;