@swiftbrowser/web 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.
- package/README.md +600 -0
- package/app/index.html +125 -0
- package/index.html +56 -0
- package/node/dist/node/apps.d.ts +58 -0
- package/node/dist/node/apps.d.ts.map +1 -0
- package/node/dist/node/apps.js +60 -0
- package/node/dist/node/apps.js.map +1 -0
- package/node/dist/node/config.d.ts +42 -0
- package/node/dist/node/config.d.ts.map +1 -0
- package/node/dist/node/config.js +99 -0
- package/node/dist/node/config.js.map +1 -0
- package/node/dist/node/examples.d.ts +39 -0
- package/node/dist/node/examples.d.ts.map +1 -0
- package/node/dist/node/examples.js +116 -0
- package/node/dist/node/examples.js.map +1 -0
- package/node/dist/node/index.d.ts +47 -0
- package/node/dist/node/index.d.ts.map +1 -0
- package/node/dist/node/index.js +52 -0
- package/node/dist/node/index.js.map +1 -0
- package/node/dist/plugins/app-manifests.d.ts +61 -0
- package/node/dist/plugins/app-manifests.d.ts.map +1 -0
- package/node/dist/plugins/app-manifests.js +168 -0
- package/node/dist/plugins/app-manifests.js.map +1 -0
- package/node/dist/plugins/asset-catalogs.d.ts +119 -0
- package/node/dist/plugins/asset-catalogs.d.ts.map +1 -0
- package/node/dist/plugins/asset-catalogs.js +488 -0
- package/node/dist/plugins/asset-catalogs.js.map +1 -0
- package/node/dist/plugins/bundle-resources.d.ts +49 -0
- package/node/dist/plugins/bundle-resources.d.ts.map +1 -0
- package/node/dist/plugins/bundle-resources.js +172 -0
- package/node/dist/plugins/bundle-resources.js.map +1 -0
- package/node/dist/plugins/swift-wasm.d.ts +33 -0
- package/node/dist/plugins/swift-wasm.d.ts.map +1 -0
- package/node/dist/plugins/swift-wasm.js +241 -0
- package/node/dist/plugins/swift-wasm.js.map +1 -0
- package/node/dist/src/assets.d.ts +60 -0
- package/node/dist/src/assets.d.ts.map +1 -0
- package/node/dist/src/assets.js +54 -0
- package/node/dist/src/assets.js.map +1 -0
- package/node/dist/src/devEvents.d.ts +31 -0
- package/node/dist/src/devEvents.d.ts.map +1 -0
- package/node/dist/src/devEvents.js +9 -0
- package/node/dist/src/devEvents.js.map +1 -0
- package/package.json +56 -0
- package/plugins/app-manifests.ts +192 -0
- package/plugins/asset-catalogs.ts +558 -0
- package/plugins/bundle-resources.ts +195 -0
- package/plugins/swift-wasm.ts +259 -0
- package/public/_headers +23 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/icon.svg +14 -0
- package/public/manifest.webmanifest +14 -0
- package/src/animation.ts +521 -0
- package/src/assets.ts +86 -0
- package/src/chartDom.ts +481 -0
- package/src/devEvents.ts +35 -0
- package/src/deviceMode.ts +80 -0
- package/src/ghost.ts +25 -0
- package/src/interaction.ts +1113 -0
- package/src/landing.css +261 -0
- package/src/landing.ts +141 -0
- package/src/layout/chart.ts +477 -0
- package/src/layout/controls.ts +116 -0
- package/src/layout/engine.ts +3581 -0
- package/src/layout/index.ts +91 -0
- package/src/layout/measure.ts +86 -0
- package/src/layout/text.ts +124 -0
- package/src/layout/types.ts +153 -0
- package/src/layout/typography.ts +188 -0
- package/src/layoutDom.ts +549 -0
- package/src/layoutEvents.ts +110 -0
- package/src/locale.ts +64 -0
- package/src/main.ts +554 -0
- package/src/mock.ts +752 -0
- package/src/nodeQueries.ts +60 -0
- package/src/protocol.ts +1421 -0
- package/src/renderer.ts +2882 -0
- package/src/styles.css +2834 -0
- package/src/styles.ts +373 -0
- package/src/swipe.ts +110 -0
- package/src/symbols.ts +595 -0
- package/src/typography.ts +76 -0
- package/src/vite-env.d.ts +26 -0
- package/src/wasm.ts +400 -0
package/app/index.html
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<!-- viewport-fit=cover lets the app draw under the real notch / home
|
|
6
|
+
indicator in device mode; the layout engine gets the insets from
|
|
7
|
+
env(safe-area-inset-*) (see --sb-inset-* in styles.css). -->
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no" />
|
|
9
|
+
<meta name="color-scheme" content="light dark" />
|
|
10
|
+
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" />
|
|
11
|
+
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000" />
|
|
12
|
+
<!-- Safari "Add to Home Screen": full screen. black-translucent keeps the
|
|
13
|
+
OS status bar transparent so the app's own background shows under it
|
|
14
|
+
(the layout engine gets the bar as a safe-area inset); `default` would
|
|
15
|
+
draw an opaque light bar whatever the app's color scheme. -->
|
|
16
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
17
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
18
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
19
|
+
<meta name="apple-mobile-web-app-title" content="SwiftBrowser" />
|
|
20
|
+
<title>SwiftBrowser</title>
|
|
21
|
+
<!-- Replaced below with the manifest of the app named by ?app= (see
|
|
22
|
+
plugins/app-manifests.ts); the home-screen icon must reopen that app. -->
|
|
23
|
+
<link rel="manifest" href="/manifest.webmanifest" />
|
|
24
|
+
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
|
25
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
26
|
+
<link rel="stylesheet" href="/src/styles.css" />
|
|
27
|
+
<script>
|
|
28
|
+
// Decide frame vs device mode before the first paint so a phone never
|
|
29
|
+
// flashes the fake bezel. src/deviceMode.ts holds the same rule and is
|
|
30
|
+
// what main.ts trusts; this only pre-sets the attribute the CSS keys on.
|
|
31
|
+
// A coarse pointer means device mode at any width (Phase 7).
|
|
32
|
+
(function () {
|
|
33
|
+
var mode = new URLSearchParams(location.search).get('mode');
|
|
34
|
+
var coarse = typeof matchMedia === 'function' && matchMedia('(pointer: coarse)').matches;
|
|
35
|
+
var device = mode === 'device' || (mode !== 'frame' && coarse);
|
|
36
|
+
document.documentElement.setAttribute('data-sb-mode', device ? 'device' : 'frame');
|
|
37
|
+
})();
|
|
38
|
+
// Point the manifest and the home-screen title at the requested app, so
|
|
39
|
+
// Share -> Add to Home Screen (with that app open) makes an icon that
|
|
40
|
+
// launches it, not the default app. Same name rule as main.ts. Apps that
|
|
41
|
+
// bring their own icon are listed in window.__SB_APP_ICONS__ (injected
|
|
42
|
+
// by plugins/app-manifests.ts); Safari reads the apple-touch-icon link,
|
|
43
|
+
// not the manifest, for the home-screen image.
|
|
44
|
+
(function () {
|
|
45
|
+
var app = (new URLSearchParams(location.search).get('app') || '').replace(/[^A-Za-z0-9_-]/g, '');
|
|
46
|
+
if (!app) return;
|
|
47
|
+
var manifest = document.querySelector('link[rel="manifest"]');
|
|
48
|
+
if (manifest) manifest.setAttribute('href', '/manifests/' + app + '.webmanifest');
|
|
49
|
+
var icons = window.__SB_APP_ICONS__ || {};
|
|
50
|
+
var touchIcon = document.querySelector('link[rel="apple-touch-icon"]');
|
|
51
|
+
if (touchIcon && icons[app]) touchIcon.setAttribute('href', icons[app]);
|
|
52
|
+
var title = document.querySelector('meta[name="apple-mobile-web-app-title"]');
|
|
53
|
+
if (title) title.setAttribute('content', app);
|
|
54
|
+
document.title = app + ' \u00b7 SwiftBrowser';
|
|
55
|
+
})();
|
|
56
|
+
</script>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<header class="toolbar" role="toolbar" aria-label="SwiftBrowser controls">
|
|
60
|
+
<span class="toolbar-brand">SwiftBrowser</span>
|
|
61
|
+
<span class="toolbar-sep" aria-hidden="true"></span>
|
|
62
|
+
<span class="toolbar-label">App</span>
|
|
63
|
+
<span id="app-name" class="toolbar-value">Loading…</span>
|
|
64
|
+
<span class="toolbar-spacer"></span>
|
|
65
|
+
<span id="build-status" class="toolbar-build" role="status" aria-live="polite" hidden>Rebuilding…</span>
|
|
66
|
+
<label class="toolbar-label" for="type-size">Text size</label>
|
|
67
|
+
<select id="type-size" class="toolbar-select" aria-label="Text size">
|
|
68
|
+
<option value="xSmall">xSmall</option>
|
|
69
|
+
<option value="small">Small</option>
|
|
70
|
+
<option value="medium">Medium</option>
|
|
71
|
+
<option value="large" selected>Large (default)</option>
|
|
72
|
+
<option value="xLarge">xLarge</option>
|
|
73
|
+
<option value="xxLarge">xxLarge</option>
|
|
74
|
+
<option value="xxxLarge">xxxLarge</option>
|
|
75
|
+
<option value="accessibility1">Accessibility 1</option>
|
|
76
|
+
<option value="accessibility2">Accessibility 2</option>
|
|
77
|
+
<option value="accessibility3">Accessibility 3</option>
|
|
78
|
+
<option value="accessibility4">Accessibility 4</option>
|
|
79
|
+
<option value="accessibility5">Accessibility 5</option>
|
|
80
|
+
</select>
|
|
81
|
+
<button id="theme-toggle" class="toolbar-button" type="button" aria-pressed="false">Dark mode</button>
|
|
82
|
+
</header>
|
|
83
|
+
|
|
84
|
+
<main class="stage">
|
|
85
|
+
<div id="device" class="device" data-theme="light" data-state="loading">
|
|
86
|
+
<div class="device-screen">
|
|
87
|
+
<div class="dynamic-island" aria-hidden="true"></div>
|
|
88
|
+
|
|
89
|
+
<div class="status-bar" aria-hidden="true">
|
|
90
|
+
<span class="status-time">9:41</span>
|
|
91
|
+
<span class="status-glyphs">
|
|
92
|
+
<!-- Cellular -->
|
|
93
|
+
<svg class="glyph glyph-cellular" viewBox="0 0 18 12" width="18" height="12">
|
|
94
|
+
<rect x="0" y="8" width="3" height="4" rx="0.8" />
|
|
95
|
+
<rect x="5" y="5.5" width="3" height="6.5" rx="0.8" />
|
|
96
|
+
<rect x="10" y="3" width="3" height="9" rx="0.8" />
|
|
97
|
+
<rect x="15" y="0" width="3" height="12" rx="0.8" />
|
|
98
|
+
</svg>
|
|
99
|
+
<!-- Wi-Fi -->
|
|
100
|
+
<svg class="glyph glyph-wifi" viewBox="0 0 16 12" width="16" height="12">
|
|
101
|
+
<path d="M8 2.6c2.4 0 4.6.9 6.3 2.5l1.1-1.2A10.6 10.6 0 0 0 8 .9 10.6 10.6 0 0 0 .6 3.9l1.1 1.2A9 9 0 0 1 8 2.6Z" />
|
|
102
|
+
<path d="M8 6.2c1.5 0 2.9.6 3.9 1.5l1.1-1.2A7.2 7.2 0 0 0 8 4.5a7.2 7.2 0 0 0-5 2l1.1 1.2A5.6 5.6 0 0 1 8 6.2Z" />
|
|
103
|
+
<path d="M8 9.8c.7 0 1.3.3 1.7.7L8 12.3l-1.7-1.8c.4-.4 1-.7 1.7-.7Z" />
|
|
104
|
+
</svg>
|
|
105
|
+
<!-- Battery -->
|
|
106
|
+
<svg class="glyph glyph-battery" viewBox="0 0 28 13" width="28" height="13">
|
|
107
|
+
<rect class="battery-shell" x="0.5" y="0.5" width="24" height="12" rx="3.5" />
|
|
108
|
+
<rect class="battery-fill" x="2" y="2" width="21" height="9" rx="2" />
|
|
109
|
+
<path class="battery-cap" d="M26 4.3v4.4c.9-.3 1.5-1.1 1.5-2.2S26.9 4.6 26 4.3Z" />
|
|
110
|
+
</svg>
|
|
111
|
+
</span>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Root container: element id 0. -->
|
|
115
|
+
<div id="screen" class="screen" data-sb-id="0"></div>
|
|
116
|
+
|
|
117
|
+
<div class="home-indicator" aria-hidden="true"></div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
<p id="status" class="status" role="alert" hidden></p>
|
|
121
|
+
</main>
|
|
122
|
+
|
|
123
|
+
<script type="module" src="/src/main.ts"></script>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
package/index.html
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
7
|
+
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#f2f2f7" />
|
|
8
|
+
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000" />
|
|
9
|
+
<meta name="description" content="SwiftUI example apps compiled to WebAssembly and rendered by SwiftBrowser. Open one on a phone or scan its QR code." />
|
|
10
|
+
<title>SwiftBrowser examples</title>
|
|
11
|
+
<link rel="manifest" href="/manifest.webmanifest" />
|
|
12
|
+
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
|
13
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
14
|
+
<link rel="stylesheet" href="/src/landing.css" />
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<header class="hero">
|
|
18
|
+
<img class="hero-icon" src="/icon.svg" width="56" height="56" alt="" />
|
|
19
|
+
<h1>SwiftBrowser examples</h1>
|
|
20
|
+
<p class="hero-lead">
|
|
21
|
+
SwiftUI apps compiled to WebAssembly and rendered in the browser. Open one here, or
|
|
22
|
+
scan its code with a phone.
|
|
23
|
+
</p>
|
|
24
|
+
</header>
|
|
25
|
+
|
|
26
|
+
<main>
|
|
27
|
+
<section aria-labelledby="apps-heading">
|
|
28
|
+
<h2 id="apps-heading" class="visually-hidden">Apps</h2>
|
|
29
|
+
<ul id="apps" class="app-grid" aria-live="polite"></ul>
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<section class="phone-help" aria-labelledby="phone-heading">
|
|
33
|
+
<h2 id="phone-heading">Testing on a phone</h2>
|
|
34
|
+
<ol>
|
|
35
|
+
<li>Open an app link on the phone, or scan its QR code from this page on a desktop.</li>
|
|
36
|
+
<li>
|
|
37
|
+
In Safari, tap <strong>Share</strong> → <strong>Add to Home Screen</strong> and open the
|
|
38
|
+
app from there. It runs full screen with the real status bar, so safe areas and
|
|
39
|
+
Dynamic Type match a native app.
|
|
40
|
+
</li>
|
|
41
|
+
<li>
|
|
42
|
+
On a phone the page runs the app without the fake iPhone frame (device mode). Add
|
|
43
|
+
<code>&mode=frame</code> to the URL to see the frame anyway, or
|
|
44
|
+
<code>&mode=device</code> on a desktop to preview device mode.
|
|
45
|
+
</li>
|
|
46
|
+
</ol>
|
|
47
|
+
</section>
|
|
48
|
+
</main>
|
|
49
|
+
|
|
50
|
+
<footer id="build-info" class="build-info">
|
|
51
|
+
<!-- Filled by landing.ts from the build-time defines. -->
|
|
52
|
+
</footer>
|
|
53
|
+
|
|
54
|
+
<script type="module" src="/src/landing.ts"></script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app registry: what the dev server and the site builder know about each
|
|
3
|
+
* app they serve. The example apps under `../Examples` are described this way
|
|
4
|
+
* by `vite.config.ts` (see ./examples.ts); the CLI describes the user's app.
|
|
5
|
+
*
|
|
6
|
+
* Node-side only (paths are absolute paths on disk).
|
|
7
|
+
*/
|
|
8
|
+
export interface AppSpec {
|
|
9
|
+
/**
|
|
10
|
+
* URL-safe name: `?app=<name>`, `/<name>.wasm`, `/catalog/<name>.json`,
|
|
11
|
+
* `/manifests/<name>.webmanifest`. `[A-Za-z0-9_-]+`
|
|
12
|
+
*/
|
|
13
|
+
name: string;
|
|
14
|
+
/** Shown on the landing page and in the web manifest; defaults to `name`. */
|
|
15
|
+
displayName?: string;
|
|
16
|
+
/** One line for the landing page card. */
|
|
17
|
+
description?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Absolute path of the built wasm module to serve at `/<name>.wasm` (dev)
|
|
20
|
+
* and copy into the site (build). May not exist yet when the dev server
|
|
21
|
+
* starts.
|
|
22
|
+
*/
|
|
23
|
+
wasm: string;
|
|
24
|
+
/** Absolute paths of `*.xcassets` directories (served as `/catalog/<name>.json` + `/catalog/<name>/<file>`). */
|
|
25
|
+
catalogs: string[];
|
|
26
|
+
/** Absolute directories whose `**\/*.swift` changes trigger `build` in the dev server. */
|
|
27
|
+
watch?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Rebuilds `wasm` (dev server, on source changes and on first request when
|
|
30
|
+
* the file is missing). Rejects with an Error whose message is the compiler
|
|
31
|
+
* output on failure.
|
|
32
|
+
*/
|
|
33
|
+
build?: () => Promise<void>;
|
|
34
|
+
/** Optional path of a PNG app icon (any size) for the manifest and landing card; the default icon otherwise. */
|
|
35
|
+
icon?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Absolute paths of the app's resource files and folders (the Xcode target's
|
|
38
|
+
* Copy Bundle Resources), served at `/bundle/<name>/…` and mounted at
|
|
39
|
+
* `/bundle` for `Bundle.main`. Folders keep their name.
|
|
40
|
+
*/
|
|
41
|
+
resources?: string[];
|
|
42
|
+
/**
|
|
43
|
+
* The languages whose locale data the module carries (BCP 47 tags; `*` for
|
|
44
|
+
* all). The page runs the app in the viewer's language when it is among them,
|
|
45
|
+
* else in the first; default `["en"]`.
|
|
46
|
+
*/
|
|
47
|
+
locales?: string[];
|
|
48
|
+
}
|
|
49
|
+
/** The name rule `?app=` in src/main.ts and every plugin URL applies. */
|
|
50
|
+
export declare const APP_NAME: RegExp;
|
|
51
|
+
/** `displayName` with its default. */
|
|
52
|
+
export declare function displayNameOf(spec: Pick<AppSpec, 'name' | 'displayName'>): string;
|
|
53
|
+
/**
|
|
54
|
+
* Checks a registry: valid, unique names and absolute paths. Throws an Error
|
|
55
|
+
* naming the first problem.
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateAppSpecs(specs: readonly AppSpec[]): void;
|
|
58
|
+
//# sourceMappingURL=apps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,gHAAgH;IAChH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,gHAAgH;IAChH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,yEAAyE;AACzE,eAAO,MAAM,QAAQ,QAAqB,CAAC;AAE3C,sCAAsC;AACtC,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,MAAM,CAEjF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAsChE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app registry: what the dev server and the site builder know about each
|
|
3
|
+
* app they serve. The example apps under `../Examples` are described this way
|
|
4
|
+
* by `vite.config.ts` (see ./examples.ts); the CLI describes the user's app.
|
|
5
|
+
*
|
|
6
|
+
* Node-side only (paths are absolute paths on disk).
|
|
7
|
+
*/
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
/** The name rule `?app=` in src/main.ts and every plugin URL applies. */
|
|
10
|
+
export const APP_NAME = /^[A-Za-z0-9_-]+$/;
|
|
11
|
+
/** `displayName` with its default. */
|
|
12
|
+
export function displayNameOf(spec) {
|
|
13
|
+
return spec.displayName ?? spec.name;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Checks a registry: valid, unique names and absolute paths. Throws an Error
|
|
17
|
+
* naming the first problem.
|
|
18
|
+
*/
|
|
19
|
+
export function validateAppSpecs(specs) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (const spec of specs) {
|
|
22
|
+
if (typeof spec.name !== 'string' || !APP_NAME.test(spec.name)) {
|
|
23
|
+
throw new Error(`Invalid app name ${JSON.stringify(spec.name)}: use letters, digits, "_" and "-" only`);
|
|
24
|
+
}
|
|
25
|
+
if (seen.has(spec.name))
|
|
26
|
+
throw new Error(`Duplicate app name ${JSON.stringify(spec.name)}`);
|
|
27
|
+
seen.add(spec.name);
|
|
28
|
+
const label = `app ${JSON.stringify(spec.name)}`;
|
|
29
|
+
if (typeof spec.wasm !== 'string' || !path.isAbsolute(spec.wasm)) {
|
|
30
|
+
throw new Error(`${label}: "wasm" must be an absolute path (got ${JSON.stringify(spec.wasm)})`);
|
|
31
|
+
}
|
|
32
|
+
if (!Array.isArray(spec.catalogs))
|
|
33
|
+
throw new Error(`${label}: "catalogs" must be an array of absolute paths`);
|
|
34
|
+
for (const dir of spec.catalogs) {
|
|
35
|
+
if (typeof dir !== 'string' || !path.isAbsolute(dir)) {
|
|
36
|
+
throw new Error(`${label}: catalog ${JSON.stringify(dir)} is not an absolute path`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const dir of spec.watch ?? []) {
|
|
40
|
+
if (typeof dir !== 'string' || !path.isAbsolute(dir)) {
|
|
41
|
+
throw new Error(`${label}: watch directory ${JSON.stringify(dir)} is not an absolute path`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (spec.icon !== undefined && !path.isAbsolute(spec.icon)) {
|
|
45
|
+
throw new Error(`${label}: "icon" must be an absolute path (got ${JSON.stringify(spec.icon)})`);
|
|
46
|
+
}
|
|
47
|
+
for (const resource of spec.resources ?? []) {
|
|
48
|
+
if (typeof resource !== 'string' || !path.isAbsolute(resource)) {
|
|
49
|
+
throw new Error(`${label}: resource ${JSON.stringify(resource)} is not an absolute path`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (spec.locales !== undefined && (!Array.isArray(spec.locales) || !spec.locales.every((l) => typeof l === 'string' && l.length > 0))) {
|
|
53
|
+
throw new Error(`${label}: "locales" must be an array of language tags`);
|
|
54
|
+
}
|
|
55
|
+
if (spec.build !== undefined && typeof spec.build !== 'function') {
|
|
56
|
+
throw new Error(`${label}: "build" must be a function`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=apps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../apps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the Vite config for a set of apps: the registry plugins
|
|
3
|
+
* (plugins/swift-wasm.ts, asset-catalogs.ts, app-manifests.ts), the two
|
|
4
|
+
* pages (landing + app) and the `define`s the landing page reads.
|
|
5
|
+
*
|
|
6
|
+
* `vite.config.ts` calls this with the example apps; the programmatic API
|
|
7
|
+
* (./index.ts) with whatever the caller registered.
|
|
8
|
+
*/
|
|
9
|
+
import type { UserConfig } from 'vite';
|
|
10
|
+
import { type AppSpec } from './apps.js';
|
|
11
|
+
export interface SiteOptions {
|
|
12
|
+
apps: AppSpec[];
|
|
13
|
+
/** Build info shown on the landing page: the commit sha (`dev` when unknown). */
|
|
14
|
+
commit?: string;
|
|
15
|
+
/** Build info shown on the landing page: the branch name ("" when unknown). */
|
|
16
|
+
branch?: string;
|
|
17
|
+
}
|
|
18
|
+
/** One entry of `__SB_APPS__`, what the landing page renders a card from (see src/vite-env.d.ts). */
|
|
19
|
+
export interface LandingApp {
|
|
20
|
+
name: string;
|
|
21
|
+
displayName: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
/** URL of the app's own icon, when its spec has one. */
|
|
24
|
+
icon?: string;
|
|
25
|
+
/** The languages the module carries locale data for (`AppSpec.locales`); the page picks the viewer's among them. */
|
|
26
|
+
locales?: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The Vite root: the `Web/` directory of this package (holds `index.html`,
|
|
30
|
+
* `app/index.html`, `src/` and `public/`). Found by walking up from this
|
|
31
|
+
* module, so it works from the TypeScript sources, from `node/dist/` and from
|
|
32
|
+
* `node_modules/@swiftbrowser/web`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function webRoot(): string;
|
|
35
|
+
/** What the landing page gets for each spec. */
|
|
36
|
+
export declare function landingApps(specs: readonly AppSpec[]): LandingApp[];
|
|
37
|
+
/**
|
|
38
|
+
* The Vite config for `options.apps`. Pass it to `vite.createServer` /
|
|
39
|
+
* `vite.build` with `configFile: false`, or export it from a config file.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createViteConfig(options: SiteOptions): UserConfig;
|
|
42
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAKvC,OAAO,EAAmC,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qGAAqG;AACrG,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oHAAoH;IACpH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAOD;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAoBhC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,UAAU,EAAE,CAQnE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CA+BjE"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the Vite config for a set of apps: the registry plugins
|
|
3
|
+
* (plugins/swift-wasm.ts, asset-catalogs.ts, app-manifests.ts), the two
|
|
4
|
+
* pages (landing + app) and the `define`s the landing page reads.
|
|
5
|
+
*
|
|
6
|
+
* `vite.config.ts` calls this with the example apps; the programmatic API
|
|
7
|
+
* (./index.ts) with whatever the caller registered.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { appManifests } from '../plugins/app-manifests.js';
|
|
13
|
+
import { assetCatalogs } from '../plugins/asset-catalogs.js';
|
|
14
|
+
import { bundleResources } from '../plugins/bundle-resources.js';
|
|
15
|
+
import { swiftWasm } from '../plugins/swift-wasm.js';
|
|
16
|
+
import { displayNameOf, validateAppSpecs } from './apps.js';
|
|
17
|
+
/** The package name that marks the Vite root. */
|
|
18
|
+
const PACKAGE_NAME = '@swiftbrowser/web';
|
|
19
|
+
let cachedRoot;
|
|
20
|
+
/**
|
|
21
|
+
* The Vite root: the `Web/` directory of this package (holds `index.html`,
|
|
22
|
+
* `app/index.html`, `src/` and `public/`). Found by walking up from this
|
|
23
|
+
* module, so it works from the TypeScript sources, from `node/dist/` and from
|
|
24
|
+
* `node_modules/@swiftbrowser/web`.
|
|
25
|
+
*/
|
|
26
|
+
export function webRoot() {
|
|
27
|
+
if (cachedRoot)
|
|
28
|
+
return cachedRoot;
|
|
29
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
for (;;) {
|
|
31
|
+
const file = path.join(dir, 'package.json');
|
|
32
|
+
if (existsSync(file)) {
|
|
33
|
+
try {
|
|
34
|
+
const pkg = JSON.parse(readFileSync(file, 'utf8'));
|
|
35
|
+
if (pkg.name === PACKAGE_NAME) {
|
|
36
|
+
cachedRoot = dir;
|
|
37
|
+
return dir;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Not ours; keep looking.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const parent = path.dirname(dir);
|
|
45
|
+
if (parent === dir)
|
|
46
|
+
throw new Error(`Could not find the ${PACKAGE_NAME} package root above ${import.meta.url}`);
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** What the landing page gets for each spec. */
|
|
51
|
+
export function landingApps(specs) {
|
|
52
|
+
return specs.map((spec) => {
|
|
53
|
+
const entry = { name: spec.name, displayName: displayNameOf(spec) };
|
|
54
|
+
if (spec.description !== undefined)
|
|
55
|
+
entry.description = spec.description;
|
|
56
|
+
if (spec.icon)
|
|
57
|
+
entry.icon = `/manifests/${spec.name}-icon.png`;
|
|
58
|
+
if (spec.locales !== undefined)
|
|
59
|
+
entry.locales = [...spec.locales];
|
|
60
|
+
return entry;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The Vite config for `options.apps`. Pass it to `vite.createServer` /
|
|
65
|
+
* `vite.build` with `configFile: false`, or export it from a config file.
|
|
66
|
+
*/
|
|
67
|
+
export function createViteConfig(options) {
|
|
68
|
+
validateAppSpecs(options.apps);
|
|
69
|
+
const root = webRoot();
|
|
70
|
+
const apps = options.apps;
|
|
71
|
+
return {
|
|
72
|
+
root,
|
|
73
|
+
plugins: [swiftWasm(apps), appManifests(apps), assetCatalogs(apps), bundleResources(apps)],
|
|
74
|
+
// Two pages: the landing page at `/` and the app at `/app/`. `mpa` keeps the
|
|
75
|
+
// dev server from answering unknown URLs with the landing page.
|
|
76
|
+
appType: 'mpa',
|
|
77
|
+
define: {
|
|
78
|
+
__SB_APPS__: JSON.stringify(landingApps(apps)),
|
|
79
|
+
__SB_COMMIT__: JSON.stringify(options.commit ?? 'dev'),
|
|
80
|
+
__SB_BRANCH__: JSON.stringify(options.branch ?? ''),
|
|
81
|
+
__SB_BUILT_AT__: JSON.stringify(new Date().toISOString()),
|
|
82
|
+
},
|
|
83
|
+
server: {
|
|
84
|
+
port: 5173,
|
|
85
|
+
strictPort: true,
|
|
86
|
+
},
|
|
87
|
+
build: {
|
|
88
|
+
target: 'es2022',
|
|
89
|
+
sourcemap: true,
|
|
90
|
+
rollupOptions: {
|
|
91
|
+
input: {
|
|
92
|
+
landing: path.resolve(root, 'index.html'),
|
|
93
|
+
app: path.resolve(root, 'app/index.html'),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The example apps (`../Examples/<Name>`) as app specs, for `vite.config.ts`:
|
|
3
|
+
* each is built by `bash scripts/build-wasm.sh <Name>` in the repo root into
|
|
4
|
+
* `Web/public/<Name>.wasm`, carries every `*.xcassets` under its directory,
|
|
5
|
+
* and rebuilds when `Sources/` or its own directory changes.
|
|
6
|
+
*/
|
|
7
|
+
import type { AppSpec } from './apps.js';
|
|
8
|
+
/** What each known example demonstrates (the landing page card line). */
|
|
9
|
+
export declare const EXAMPLE_DESCRIPTIONS: Readonly<Record<string, string>>;
|
|
10
|
+
/**
|
|
11
|
+
* Directory prepended to `PATH` for the build so `swift` resolves even when
|
|
12
|
+
* the dev server was started from a shell without the toolchain:
|
|
13
|
+
* `$SWIFT_TOOLCHAIN_BIN`, else the swiftly 6.4.0 location if it exists.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toolchainBin(): string | undefined;
|
|
16
|
+
export interface BuildScriptOptions {
|
|
17
|
+
/** Called with every line the script prints (stdout and stderr). */
|
|
18
|
+
onOutput?: (line: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `bash scripts/build-wasm.sh <app>` in `repoRoot` with `SB_WASM_OPT=0`: hot
|
|
22
|
+
* rebuilds skip wasm-opt, which costs 15-30 s per module and strips the names
|
|
23
|
+
* that make stack traces readable while developing. Rejects with an Error
|
|
24
|
+
* whose message is the script's combined output when it fails.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runBuildScript(repoRoot: string, app: string, options?: BuildScriptOptions): Promise<void>;
|
|
27
|
+
/** The names of the directories under `<repoRoot>/Examples`, sorted. */
|
|
28
|
+
export declare function exampleNames(repoRoot: string): string[];
|
|
29
|
+
/** One spec per example. `repoRoot` defaults to the parent of the Vite root. */
|
|
30
|
+
export declare function exampleAppSpecs(repoRoot?: string): AppSpec[];
|
|
31
|
+
/**
|
|
32
|
+
* Build info for the landing page. CI (GitHub Actions) sets `GITHUB_SHA` /
|
|
33
|
+
* `GITHUB_REF_NAME`; a local build asks git; a checkout without git says "dev".
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildInfo(repoRoot: string): {
|
|
36
|
+
commit: string;
|
|
37
|
+
branch: string;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=examples.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"examples.d.ts","sourceRoot":"","sources":["../../examples.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOjE,CAAC;AAIF;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,SAAS,CAIjD;AAED,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB7G;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAOvD;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,QAAQ,SAAgC,GAAG,OAAO,EAAE,CAkBnF;AAYD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAK9E"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The example apps (`../Examples/<Name>`) as app specs, for `vite.config.ts`:
|
|
3
|
+
* each is built by `bash scripts/build-wasm.sh <Name>` in the repo root into
|
|
4
|
+
* `Web/public/<Name>.wasm`, carries every `*.xcassets` under its directory,
|
|
5
|
+
* and rebuilds when `Sources/` or its own directory changes.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
8
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { findAssetCatalogs } from '../plugins/asset-catalogs.js';
|
|
11
|
+
import { webRoot } from './config.js';
|
|
12
|
+
/** What each known example demonstrates (the landing page card line). */
|
|
13
|
+
export const EXAMPLE_DESCRIPTIONS = {
|
|
14
|
+
Counter: '@State and buttons',
|
|
15
|
+
Todos: 'navigation, keyed lists, text input, @Observable',
|
|
16
|
+
Gallery: 'layout engine, animation, sheets, Dynamic Type',
|
|
17
|
+
Settings: 'tabs, forms, pickers, path navigation, tasks, GeometryReader',
|
|
18
|
+
FoodTruck: "Apple's Food Truck sample: FoundationEssentials, asset catalogs, ObservableObject",
|
|
19
|
+
HackingWithSwift: 'Five Hacking with SwiftUI projects: gestures, swipe actions, edit mode, alerts, controls',
|
|
20
|
+
};
|
|
21
|
+
const DEFAULT_TOOLCHAIN_BIN = '/root/.local/share/swiftly/toolchains/6.4.0/usr/bin';
|
|
22
|
+
/**
|
|
23
|
+
* Directory prepended to `PATH` for the build so `swift` resolves even when
|
|
24
|
+
* the dev server was started from a shell without the toolchain:
|
|
25
|
+
* `$SWIFT_TOOLCHAIN_BIN`, else the swiftly 6.4.0 location if it exists.
|
|
26
|
+
*/
|
|
27
|
+
export function toolchainBin() {
|
|
28
|
+
const explicit = process.env['SWIFT_TOOLCHAIN_BIN'];
|
|
29
|
+
if (explicit)
|
|
30
|
+
return explicit;
|
|
31
|
+
return existsSync(DEFAULT_TOOLCHAIN_BIN) ? DEFAULT_TOOLCHAIN_BIN : undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `bash scripts/build-wasm.sh <app>` in `repoRoot` with `SB_WASM_OPT=0`: hot
|
|
35
|
+
* rebuilds skip wasm-opt, which costs 15-30 s per module and strips the names
|
|
36
|
+
* that make stack traces readable while developing. Rejects with an Error
|
|
37
|
+
* whose message is the script's combined output when it fails.
|
|
38
|
+
*/
|
|
39
|
+
export function runBuildScript(repoRoot, app, options = {}) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const bin = toolchainBin();
|
|
42
|
+
const env = { ...process.env, SB_WASM_OPT: '0' };
|
|
43
|
+
if (bin)
|
|
44
|
+
env['PATH'] = `${bin}${path.delimiter}${env['PATH'] ?? ''}`;
|
|
45
|
+
const child = spawn('bash', ['scripts/build-wasm.sh', app], { cwd: repoRoot, env });
|
|
46
|
+
let output = '';
|
|
47
|
+
const collect = (chunk) => {
|
|
48
|
+
const text = chunk.toString();
|
|
49
|
+
output += text;
|
|
50
|
+
if (options.onOutput)
|
|
51
|
+
for (const line of text.split('\n'))
|
|
52
|
+
if (line.trim())
|
|
53
|
+
options.onOutput(line);
|
|
54
|
+
};
|
|
55
|
+
child.stdout.on('data', collect);
|
|
56
|
+
child.stderr.on('data', collect);
|
|
57
|
+
child.on('error', (error) => reject(new Error(`${output}\n${error.message}`)));
|
|
58
|
+
child.on('close', (code) => {
|
|
59
|
+
if (code === 0)
|
|
60
|
+
resolve();
|
|
61
|
+
else
|
|
62
|
+
reject(new Error(output || `scripts/build-wasm.sh ${app} exited with ${code}`));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** The names of the directories under `<repoRoot>/Examples`, sorted. */
|
|
67
|
+
export function exampleNames(repoRoot) {
|
|
68
|
+
const dir = path.join(repoRoot, 'Examples');
|
|
69
|
+
if (!existsSync(dir))
|
|
70
|
+
return [];
|
|
71
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
72
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
73
|
+
.map((entry) => entry.name)
|
|
74
|
+
.sort();
|
|
75
|
+
}
|
|
76
|
+
/** One spec per example. `repoRoot` defaults to the parent of the Vite root. */
|
|
77
|
+
export function exampleAppSpecs(repoRoot = path.resolve(webRoot(), '..')) {
|
|
78
|
+
const publicDir = path.join(webRoot(), 'public');
|
|
79
|
+
return exampleNames(repoRoot).map((name) => {
|
|
80
|
+
const dir = path.join(repoRoot, 'Examples', name);
|
|
81
|
+
const spec = {
|
|
82
|
+
name,
|
|
83
|
+
wasm: path.join(publicDir, `${name}.wasm`),
|
|
84
|
+
catalogs: findAssetCatalogs(dir),
|
|
85
|
+
watch: [path.join(repoRoot, 'Sources'), dir],
|
|
86
|
+
build: () => runBuildScript(repoRoot, name, {
|
|
87
|
+
onOutput: (line) => console.log(` ${line}`),
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
const description = EXAMPLE_DESCRIPTIONS[name];
|
|
91
|
+
if (description !== undefined)
|
|
92
|
+
spec.description = description;
|
|
93
|
+
return spec;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** `git <args>` in `repoRoot`, or null when git / the repo is unavailable. */
|
|
97
|
+
function git(repoRoot, ...args) {
|
|
98
|
+
try {
|
|
99
|
+
const out = execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
100
|
+
return out.trim() || null;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Build info for the landing page. CI (GitHub Actions) sets `GITHUB_SHA` /
|
|
108
|
+
* `GITHUB_REF_NAME`; a local build asks git; a checkout without git says "dev".
|
|
109
|
+
*/
|
|
110
|
+
export function buildInfo(repoRoot) {
|
|
111
|
+
return {
|
|
112
|
+
commit: process.env['GITHUB_SHA'] || git(repoRoot, 'rev-parse', '--short', 'HEAD') || 'dev',
|
|
113
|
+
branch: process.env['GITHUB_REF_NAME'] || git(repoRoot, 'rev-parse', '--abbrev-ref', 'HEAD') || '',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=examples.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"examples.js","sourceRoot":"","sources":["../../examples.ts"],"names":[],"mappings":"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"}
|