@pwfabric/cli 0.0.0-stage → 1.0.0-rc.1

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 (129) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +122 -2
  3. package/dist/args.d.ts +11 -0
  4. package/dist/args.js +43 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/bin.d.ts +2 -0
  7. package/dist/bin.js +10 -0
  8. package/dist/bin.js.map +1 -0
  9. package/dist/commands/add.d.ts +3 -0
  10. package/dist/commands/add.js +56 -0
  11. package/dist/commands/add.js.map +1 -0
  12. package/dist/commands/build.d.ts +3 -0
  13. package/dist/commands/build.js +55 -0
  14. package/dist/commands/build.js.map +1 -0
  15. package/dist/commands/create.d.ts +9 -0
  16. package/dist/commands/create.js +119 -0
  17. package/dist/commands/create.js.map +1 -0
  18. package/dist/commands/dev.d.ts +12 -0
  19. package/dist/commands/dev.js +107 -0
  20. package/dist/commands/dev.js.map +1 -0
  21. package/dist/commands/lib/flags.d.ts +10 -0
  22. package/dist/commands/lib/flags.js +33 -0
  23. package/dist/commands/lib/flags.js.map +1 -0
  24. package/dist/commands/lib/identity.d.ts +38 -0
  25. package/dist/commands/lib/identity.js +120 -0
  26. package/dist/commands/lib/identity.js.map +1 -0
  27. package/dist/commands/lib/project.d.ts +34 -0
  28. package/dist/commands/lib/project.js +78 -0
  29. package/dist/commands/lib/project.js.map +1 -0
  30. package/dist/commands/lib/report.d.ts +15 -0
  31. package/dist/commands/lib/report.js +75 -0
  32. package/dist/commands/lib/report.js.map +1 -0
  33. package/dist/commands/lib/starter.d.ts +41 -0
  34. package/dist/commands/lib/starter.js +136 -0
  35. package/dist/commands/lib/starter.js.map +1 -0
  36. package/dist/commands/lib/static-server.d.ts +45 -0
  37. package/dist/commands/lib/static-server.js +176 -0
  38. package/dist/commands/lib/static-server.js.map +1 -0
  39. package/dist/commands/pack.d.ts +5 -0
  40. package/dist/commands/pack.js +56 -0
  41. package/dist/commands/pack.js.map +1 -0
  42. package/dist/commands/registry.d.ts +7 -0
  43. package/dist/commands/registry.js +16 -0
  44. package/dist/commands/registry.js.map +1 -0
  45. package/dist/commands/run.d.ts +14 -0
  46. package/dist/commands/run.js +79 -0
  47. package/dist/commands/run.js.map +1 -0
  48. package/dist/commands/types.d.ts +34 -0
  49. package/dist/commands/types.js +10 -0
  50. package/dist/commands/types.js.map +1 -0
  51. package/dist/commands/validate.d.ts +6 -0
  52. package/dist/commands/validate.js +33 -0
  53. package/dist/commands/validate.js.map +1 -0
  54. package/dist/dev/assemble.d.ts +47 -0
  55. package/dist/dev/assemble.js +339 -0
  56. package/dist/dev/assemble.js.map +1 -0
  57. package/dist/dev/compile.d.ts +54 -0
  58. package/dist/dev/compile.js +215 -0
  59. package/dist/dev/compile.js.map +1 -0
  60. package/dist/dev/csp.d.ts +67 -0
  61. package/dist/dev/csp.js +260 -0
  62. package/dist/dev/csp.js.map +1 -0
  63. package/dist/dev/diagnostics.d.ts +21 -0
  64. package/dist/dev/diagnostics.js +22 -0
  65. package/dist/dev/diagnostics.js.map +1 -0
  66. package/dist/dev/envelope.d.ts +92 -0
  67. package/dist/dev/envelope.js +154 -0
  68. package/dist/dev/envelope.js.map +1 -0
  69. package/dist/dev/media.d.ts +53 -0
  70. package/dist/dev/media.js +128 -0
  71. package/dist/dev/media.js.map +1 -0
  72. package/dist/dev/needs-host.d.ts +2 -0
  73. package/dist/dev/needs-host.js +32 -0
  74. package/dist/dev/needs-host.js.map +1 -0
  75. package/dist/dev/output.d.ts +9 -0
  76. package/dist/dev/output.js +32 -0
  77. package/dist/dev/output.js.map +1 -0
  78. package/dist/dev/overlay.d.ts +21 -0
  79. package/dist/dev/overlay.js +59 -0
  80. package/dist/dev/overlay.js.map +1 -0
  81. package/dist/dev/server.d.ts +27 -0
  82. package/dist/dev/server.js +268 -0
  83. package/dist/dev/server.js.map +1 -0
  84. package/dist/dev/shell.d.ts +94 -0
  85. package/dist/dev/shell.js +267 -0
  86. package/dist/dev/shell.js.map +1 -0
  87. package/dist/dev/sources.d.ts +45 -0
  88. package/dist/dev/sources.js +165 -0
  89. package/dist/dev/sources.js.map +1 -0
  90. package/dist/dev/theme.d.ts +51 -0
  91. package/dist/dev/theme.js +185 -0
  92. package/dist/dev/theme.js.map +1 -0
  93. package/dist/dev/types.d.ts +40 -0
  94. package/dist/dev/types.js +11 -0
  95. package/dist/dev/types.js.map +1 -0
  96. package/dist/dev/watcher.d.ts +29 -0
  97. package/dist/dev/watcher.js +150 -0
  98. package/dist/dev/watcher.js.map +1 -0
  99. package/dist/host/definitions.d.ts +61 -0
  100. package/dist/host/definitions.js +136 -0
  101. package/dist/host/definitions.js.map +1 -0
  102. package/dist/host/http.d.ts +39 -0
  103. package/dist/host/http.js +156 -0
  104. package/dist/host/http.js.map +1 -0
  105. package/dist/host/local-host.d.ts +59 -0
  106. package/dist/host/local-host.js +420 -0
  107. package/dist/host/local-host.js.map +1 -0
  108. package/dist/host/oidc.d.ts +46 -0
  109. package/dist/host/oidc.js +192 -0
  110. package/dist/host/oidc.js.map +1 -0
  111. package/dist/host/query.d.ts +35 -0
  112. package/dist/host/query.js +158 -0
  113. package/dist/host/query.js.map +1 -0
  114. package/dist/host/receipts.d.ts +25 -0
  115. package/dist/host/receipts.js +72 -0
  116. package/dist/host/receipts.js.map +1 -0
  117. package/dist/host/records.d.ts +73 -0
  118. package/dist/host/records.js +229 -0
  119. package/dist/host/records.js.map +1 -0
  120. package/dist/host/tokens.d.ts +28 -0
  121. package/dist/host/tokens.js +52 -0
  122. package/dist/host/tokens.js.map +1 -0
  123. package/dist/host/types.d.ts +32 -0
  124. package/dist/host/types.js +11 -0
  125. package/dist/host/types.js.map +1 -0
  126. package/dist/index.d.ts +18 -0
  127. package/dist/index.js +51 -0
  128. package/dist/index.js.map +1 -0
  129. package/package.json +66 -3
@@ -0,0 +1,267 @@
1
+ /**
2
+ * The runtime half of a pwapp: the shell page, the runner and renderer
3
+ * bundles, and the shim modules the import map points at.
4
+ *
5
+ * Every bare specifier a compiled block may import comes from one list in
6
+ * the engine (`BLOCK_HOST_IMPORTS`). The import map and the shim files are
7
+ * derived from it here, so a block compiled against that list always links.
8
+ *
9
+ * A shim re-exports a global the renderer seeded from its own bundled copy
10
+ * (one React, one runtime, one zod for every block). Its named exports are
11
+ * not typed out by hand: they are read off the very globals the renderer
12
+ * bundle seeds, by evaluating that bundle once in an isolated context. An
13
+ * ESM import of a name a shim does not export fails the whole module at link
14
+ * time, so the list has to be complete — and reading it from the bundle that
15
+ * serves it is the only way it stays complete.
16
+ */
17
+ import { readFile } from 'node:fs/promises';
18
+ import { createRequire } from 'node:module';
19
+ import vm from 'node:vm';
20
+ import { BLOCK_HOST_IMPORTS, blockImportMap, blockShimFiles, specifiersForShim, } from '@pwfabric/core';
21
+ import { webfontStylesheetHref, themeWebfonts } from './theme.js';
22
+ const require = createRequire(import.meta.url);
23
+ let cachedAssets = null;
24
+ /** Reads the built runner and renderer from the installed engine packages (once per process). */
25
+ export function loadRuntimeAssets() {
26
+ cachedAssets ??= (async () => {
27
+ const runnerPath = require.resolve('@pwfabric/runner');
28
+ const embedPath = require.resolve('@pwfabric/embed/embed.js');
29
+ const [runnerJs, embedJs, runnerMap] = await Promise.all([
30
+ readFile(runnerPath, 'utf8'),
31
+ readFile(embedPath, 'utf8'),
32
+ readFile(`${runnerPath}.map`, 'utf8').catch(() => undefined),
33
+ ]);
34
+ return { runnerJs, embedJs, ...(runnerMap !== undefined ? { runnerMap } : {}) };
35
+ })();
36
+ cachedAssets.catch(() => {
37
+ cachedAssets = null;
38
+ });
39
+ return cachedAssets;
40
+ }
41
+ /** The vendor table the renderer exposes besides the `__pwfabric_*` globals. */
42
+ const VENDOR_GLOBAL = '__PW_VENDOR__';
43
+ /**
44
+ * Globals the renderer bundle does not seed itself, and the vendor-table key
45
+ * the shell seeds each from (see {@link buildShellHtml}), so blocks that reach
46
+ * for these get the renderer's own instance instead of `undefined`.
47
+ *
48
+ * The renderer bundle (`@pwfabric/embed`'s `embed.js`) carries `react-dom`,
49
+ * `@pwfabric/core` and `@pwfabric/authoring` (the `sdk@v1` shim's package)
50
+ * in its `__PW_VENDOR__` table but does not seed these globals itself, so a
51
+ * block that uses a VALUE from core or the authoring kit (as opposed to a
52
+ * type) reads the renderer's instance here instead of `undefined`.
53
+ */
54
+ export const SHELL_SEEDED_GLOBALS = {
55
+ __pwfabric_react_dom: 'react-dom',
56
+ __pwfabric_core: '@pwfabric/core',
57
+ __pwfabric_sdk: '@pwfabric/authoring',
58
+ };
59
+ /** Packages to read export names from when no bundle seeds their global. */
60
+ const NAME_SOURCES = {
61
+ __pwfabric_core: '@pwfabric/core',
62
+ __pwfabric_sdk: '@pwfabric/authoring',
63
+ };
64
+ /** Export names a shim falls back to when neither a bundle nor a package says more. */
65
+ const FALLBACK_NAMES = {
66
+ __pwfabric_sdk: ['defineBlock'],
67
+ };
68
+ const RESERVED = new Set(('break case catch class const continue debugger default delete do else enum export extends false finally for ' +
69
+ 'function if import in instanceof new null return super switch this throw true try typeof var void while with ' +
70
+ 'yield let static implements interface package private protected public await arguments eval').split(' '));
71
+ /** Whether `name` can be an `export const { name }` binding. */
72
+ export function isExportableName(name) {
73
+ return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED.has(name);
74
+ }
75
+ /** Evaluates the renderer bundle in an isolated context and returns what it put on `globalThis`. */
76
+ export function seededGlobals(embedJs) {
77
+ const sandbox = { console, setTimeout, clearTimeout, queueMicrotask };
78
+ sandbox['globalThis'] = sandbox;
79
+ sandbox['self'] = sandbox;
80
+ const context = vm.createContext(sandbox);
81
+ try {
82
+ vm.runInContext(embedJs, context, { filename: 'embed.js', timeout: 10_000 });
83
+ }
84
+ catch {
85
+ // A bundle that needs a document past its globals still seeded them first.
86
+ }
87
+ const out = {};
88
+ for (const [k, v] of Object.entries(sandbox)) {
89
+ if (k.startsWith('__pwfabric_') || k === VENDOR_GLOBAL)
90
+ out[k] = v;
91
+ }
92
+ return out;
93
+ }
94
+ async function namesFromPackage(pkg) {
95
+ try {
96
+ const mod = (await import(pkg));
97
+ return Object.keys(mod);
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ }
103
+ /** The global each shim file reads (every specifier sharing a shim names the same global). */
104
+ export function shimGlobal(shim) {
105
+ const entry = BLOCK_HOST_IMPORTS.find((i) => i.shim === shim && i.global);
106
+ return entry?.global ?? `__pwfabric_${shim.replace(/[^A-Za-z0-9]+/g, '_')}`;
107
+ }
108
+ /** Source of one shim module. */
109
+ export function shimSource(shim, global, names) {
110
+ const exported = [...new Set(names)].filter(isExportableName).sort();
111
+ const specifiers = specifiersForShim(shim);
112
+ const lines = [
113
+ `// Generated by pwf. Serves ${specifiers.map((s) => `'${s}'`).join(', ')} to compiled blocks:`,
114
+ `// re-exports the instance the page seeded on globalThis.${global}.`,
115
+ `const m = globalThis.${global} ?? {}`,
116
+ 'export default m',
117
+ ];
118
+ if (exported.length > 0) {
119
+ lines.push(`export const {\n${exported.map((n) => ` ${n},`).join('\n')}\n} = m`);
120
+ }
121
+ return `${lines.join('\n')}\n`;
122
+ }
123
+ /**
124
+ * Every shim file the import map can point at, with its source.
125
+ *
126
+ * Names come from, in order: the global the renderer seeds; the vendor table
127
+ * entry the shell seeds the global from; the package itself (for globals no
128
+ * standalone page seeds — their exports resolve to `undefined`, which keeps a
129
+ * block linking instead of failing at load).
130
+ */
131
+ export async function buildShims(embedJs) {
132
+ const seeded = seededGlobals(embedJs);
133
+ const vendor = (seeded[VENDOR_GLOBAL] ?? {});
134
+ const out = new Map();
135
+ for (const shim of blockShimFiles()) {
136
+ const global = shimGlobal(shim);
137
+ let names = null;
138
+ const value = seeded[global];
139
+ if (value && typeof value === 'object')
140
+ names = Object.keys(value);
141
+ const vendorKey = SHELL_SEEDED_GLOBALS[global];
142
+ if (!names && vendorKey && vendor[vendorKey] && typeof vendor[vendorKey] === 'object') {
143
+ names = Object.keys(vendor[vendorKey]);
144
+ }
145
+ if (!names && NAME_SOURCES[global])
146
+ names = await namesFromPackage(NAME_SOURCES[global]);
147
+ out.set(shim, shimSource(shim, global, names ?? [...(FALLBACK_NAMES[global] ?? [])]));
148
+ }
149
+ return out;
150
+ }
151
+ let cachedShims = null;
152
+ /** {@link buildShims}, once per renderer bundle. */
153
+ export function shimsFor(embedJs) {
154
+ if (cachedShims?.embedJs !== embedJs)
155
+ cachedShims = { embedJs, shims: buildShims(embedJs) };
156
+ return cachedShims.shims;
157
+ }
158
+ /** The import map: every host import → `./shims/<file>`. */
159
+ export function importMap() {
160
+ return blockImportMap('./shims');
161
+ }
162
+ export function escapeHtml(s) {
163
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
164
+ }
165
+ /** JSON safe to place inside a `<script>` element. */
166
+ export function scriptJson(value) {
167
+ return JSON.stringify(value).replace(/</g, '\\u003c');
168
+ }
169
+ /** The exact text of the shell's inline import map (the CSP hashes this). */
170
+ export function importMapText() {
171
+ return scriptJson(importMap());
172
+ }
173
+ /**
174
+ * `assets/seed.js`: seeds the globals the renderer does not seed itself from
175
+ * its vendor table (a file, not an inline script, so the policy needs no hash).
176
+ */
177
+ export function seedScript() {
178
+ const seeds = Object.entries(SHELL_SEEDED_GLOBALS)
179
+ .map(([global, key]) => ` g.${global} = g.${global} || v[${JSON.stringify(key)}];`)
180
+ .join('\n');
181
+ return `// Generated by pwf: globals the renderer carries in ${VENDOR_GLOBAL} but does not seed.
182
+ (function (g) {
183
+ var v = g.${VENDOR_GLOBAL} || {};
184
+ ${seeds}
185
+ })(globalThis)
186
+ `;
187
+ }
188
+ /** `assets/boot.js`: the module that starts the runner. */
189
+ export function bootScript(locale = 'en') {
190
+ return `// Generated by pwf: starts the app.
191
+ import { runApp } from './runner.js'
192
+
193
+ runApp({ source: './pwpack', blocks: './blocks/', engines: './engines', router: 'hash', locale: '${locale}' })
194
+ `;
195
+ }
196
+ /** A BCP-47-looking tag, or `en`. */
197
+ export function htmlLangOf(locale) {
198
+ const tag = (locale ?? '').trim();
199
+ return /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(tag) ? tag : 'en';
200
+ }
201
+ /** `tr-TR` → `tr`; the runner's copy knows `en` and `tr`. */
202
+ export function runnerLocaleOf(locale) {
203
+ return /^tr(?:[-_]|$)/i.test(locale ?? '') ? 'tr' : 'en';
204
+ }
205
+ /**
206
+ * `index.html`, in the order the pwapp format requires: import map, the
207
+ * renderer (a classic script that seeds the shared globals) and `seed.js`,
208
+ * the mount element, then `boot.js`, which starts the runner. The import map
209
+ * is the only inline script, so a Content Security Policy needs one hash.
210
+ * Every address is relative, so the folder works at a host root, under a
211
+ * sub-path, or from disk behind any static server.
212
+ */
213
+ export function buildShellHtml(options) {
214
+ const href = webfontStylesheetHref(themeWebfonts(options.appearance));
215
+ const fonts = href
216
+ ? `
217
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
218
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
219
+ <link rel="stylesheet" href="${escapeHtml(href)}" />`
220
+ : '';
221
+ const csp = options.csp
222
+ ? `
223
+ <meta http-equiv="Content-Security-Policy" content="${escapeHtml(options.csp)}" />`
224
+ : '';
225
+ return `<!doctype html>
226
+ <html lang="${escapeHtml(htmlLangOf(options.lang))}">
227
+ <head>
228
+ <meta charset="UTF-8" />${csp}
229
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
230
+ <title>${escapeHtml(options.title)}</title>
231
+ <link rel="icon" href="data:," />
232
+ <link rel="stylesheet" href="./assets/theme.css" />${fonts}
233
+ <script type="importmap">${importMapText()}</script>
234
+ <script src="./assets/embed.js"></script>
235
+ <script src="./assets/seed.js"></script>
236
+ </head>
237
+ <body>
238
+ <div id="root"></div>
239
+ <script type="module" src="./assets/boot.js"></script>${options.bodyExtra ?? ''}
240
+ </body>
241
+ </html>
242
+ `;
243
+ }
244
+ /**
245
+ * The shell with `<base href="/">` as the first element of its head, unless it
246
+ * already declares a base.
247
+ *
248
+ * The shell addresses its scripts and the import map relatively (`./assets/…`,
249
+ * `./shims/…`) so the folder works under any sub-path. When a server answers a
250
+ * nested page path (`/team/members`) with that shell, the browser would resolve
251
+ * those addresses against `/team/` and miss every file. The pwapp format puts
252
+ * the fix on the host: under path routing it declares the app's base with
253
+ * `<base href>`. This server serves the app at its root, so the base is `/`.
254
+ */
255
+ export function withBaseHref(body, href = '/') {
256
+ const html = new TextDecoder().decode(body);
257
+ if (/<bases/i.test(html))
258
+ return body;
259
+ const at = html.search(/<head[^>]*>/i);
260
+ if (at < 0)
261
+ return body;
262
+ const end = html.indexOf('>', at) + 1;
263
+ const tag = `
264
+ <base href="${href.replace(/"/g, '&quot;')}" />`;
265
+ return new TextEncoder().encode(html.slice(0, end) + tag + html.slice(end));
266
+ }
267
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/dev/shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The runtime half of a pwapp: the shell page, the runner and renderer\n * bundles, and the shim modules the import map points at.\n *\n * Every bare specifier a compiled block may import comes from one list in\n * the engine (`BLOCK_HOST_IMPORTS`). The import map and the shim files are\n * derived from it here, so a block compiled against that list always links.\n *\n * A shim re-exports a global the renderer seeded from its own bundled copy\n * (one React, one runtime, one zod for every block). Its named exports are\n * not typed out by hand: they are read off the very globals the renderer\n * bundle seeds, by evaluating that bundle once in an isolated context. An\n * ESM import of a name a shim does not export fails the whole module at link\n * time, so the list has to be complete — and reading it from the bundle that\n * serves it is the only way it stays complete.\n */\nimport { readFile } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport vm from 'node:vm'\nimport {\n BLOCK_HOST_IMPORTS,\n blockImportMap,\n blockShimFiles,\n specifiersForShim,\n} from '@pwfabric/core'\nimport { webfontStylesheetHref, themeWebfonts } from './theme.js'\n\nconst require = createRequire(import.meta.url)\n\n/** The runner and renderer bundles a pwapp ships. */\nexport interface RuntimeAssets {\n /** `assets/runner.js` — the ES module exporting `runApp`. */\n readonly runnerJs: string\n /** Its source map, when the build has one. */\n readonly runnerMap?: string\n /** `assets/embed.js` — the classic script that defines `window.PWFabric`. */\n readonly embedJs: string\n}\n\nlet cachedAssets: Promise<RuntimeAssets> | null = null\n\n/** Reads the built runner and renderer from the installed engine packages (once per process). */\nexport function loadRuntimeAssets(): Promise<RuntimeAssets> {\n cachedAssets ??= (async () => {\n const runnerPath = require.resolve('@pwfabric/runner')\n const embedPath = require.resolve('@pwfabric/embed/embed.js')\n const [runnerJs, embedJs, runnerMap] = await Promise.all([\n readFile(runnerPath, 'utf8'),\n readFile(embedPath, 'utf8'),\n readFile(`${runnerPath}.map`, 'utf8').catch(() => undefined),\n ])\n return { runnerJs, embedJs, ...(runnerMap !== undefined ? { runnerMap } : {}) }\n })()\n cachedAssets.catch(() => {\n cachedAssets = null\n })\n return cachedAssets\n}\n\n/** The vendor table the renderer exposes besides the `__pwfabric_*` globals. */\nconst VENDOR_GLOBAL = '__PW_VENDOR__'\n\n/**\n * Globals the renderer bundle does not seed itself, and the vendor-table key\n * the shell seeds each from (see {@link buildShellHtml}), so blocks that reach\n * for these get the renderer's own instance instead of `undefined`.\n *\n * The renderer bundle (`@pwfabric/embed`'s `embed.js`) carries `react-dom`,\n * `@pwfabric/core` and `@pwfabric/authoring` (the `sdk@v1` shim's package)\n * in its `__PW_VENDOR__` table but does not seed these globals itself, so a\n * block that uses a VALUE from core or the authoring kit (as opposed to a\n * type) reads the renderer's instance here instead of `undefined`.\n */\nexport const SHELL_SEEDED_GLOBALS: Readonly<Record<string, string>> = {\n __pwfabric_react_dom: 'react-dom',\n __pwfabric_core: '@pwfabric/core',\n __pwfabric_sdk: '@pwfabric/authoring',\n}\n\n/** Packages to read export names from when no bundle seeds their global. */\nconst NAME_SOURCES: Readonly<Record<string, string>> = {\n __pwfabric_core: '@pwfabric/core',\n __pwfabric_sdk: '@pwfabric/authoring',\n}\n\n/** Export names a shim falls back to when neither a bundle nor a package says more. */\nconst FALLBACK_NAMES: Readonly<Record<string, readonly string[]>> = {\n __pwfabric_sdk: ['defineBlock'],\n}\n\nconst RESERVED = new Set(\n (\n 'break case catch class const continue debugger default delete do else enum export extends false finally for ' +\n 'function if import in instanceof new null return super switch this throw true try typeof var void while with ' +\n 'yield let static implements interface package private protected public await arguments eval'\n ).split(' ')\n)\n\n/** Whether `name` can be an `export const { name }` binding. */\nexport function isExportableName(name: string): boolean {\n return /^[A-Za-z_$][\\w$]*$/.test(name) && !RESERVED.has(name)\n}\n\n/** Evaluates the renderer bundle in an isolated context and returns what it put on `globalThis`. */\nexport function seededGlobals(embedJs: string): Record<string, unknown> {\n const sandbox: Record<string, unknown> = { console, setTimeout, clearTimeout, queueMicrotask }\n sandbox['globalThis'] = sandbox\n sandbox['self'] = sandbox\n const context = vm.createContext(sandbox)\n try {\n vm.runInContext(embedJs, context, { filename: 'embed.js', timeout: 10_000 })\n } catch {\n // A bundle that needs a document past its globals still seeded them first.\n }\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(sandbox)) {\n if (k.startsWith('__pwfabric_') || k === VENDOR_GLOBAL) out[k] = v\n }\n return out\n}\n\nasync function namesFromPackage(pkg: string): Promise<string[] | null> {\n try {\n const mod = (await import(pkg)) as Record<string, unknown>\n return Object.keys(mod)\n } catch {\n return null\n }\n}\n\n/** The global each shim file reads (every specifier sharing a shim names the same global). */\nexport function shimGlobal(shim: string): string {\n const entry = BLOCK_HOST_IMPORTS.find((i) => i.shim === shim && i.global)\n return entry?.global ?? `__pwfabric_${shim.replace(/[^A-Za-z0-9]+/g, '_')}`\n}\n\n/** Source of one shim module. */\nexport function shimSource(shim: string, global: string, names: readonly string[]): string {\n const exported = [...new Set(names)].filter(isExportableName).sort()\n const specifiers = specifiersForShim(shim)\n const lines = [\n `// Generated by pwf. Serves ${specifiers.map((s) => `'${s}'`).join(', ')} to compiled blocks:`,\n `// re-exports the instance the page seeded on globalThis.${global}.`,\n `const m = globalThis.${global} ?? {}`,\n 'export default m',\n ]\n if (exported.length > 0) {\n lines.push(`export const {\\n${exported.map((n) => ` ${n},`).join('\\n')}\\n} = m`)\n }\n return `${lines.join('\\n')}\\n`\n}\n\n/**\n * Every shim file the import map can point at, with its source.\n *\n * Names come from, in order: the global the renderer seeds; the vendor table\n * entry the shell seeds the global from; the package itself (for globals no\n * standalone page seeds — their exports resolve to `undefined`, which keeps a\n * block linking instead of failing at load).\n */\nexport async function buildShims(embedJs: string): Promise<Map<string, string>> {\n const seeded = seededGlobals(embedJs)\n const vendor = (seeded[VENDOR_GLOBAL] ?? {}) as Record<string, unknown>\n const out = new Map<string, string>()\n for (const shim of blockShimFiles()) {\n const global = shimGlobal(shim)\n let names: string[] | null = null\n const value = seeded[global]\n if (value && typeof value === 'object') names = Object.keys(value)\n const vendorKey = SHELL_SEEDED_GLOBALS[global]\n if (!names && vendorKey && vendor[vendorKey] && typeof vendor[vendorKey] === 'object') {\n names = Object.keys(vendor[vendorKey] as object)\n }\n if (!names && NAME_SOURCES[global]) names = await namesFromPackage(NAME_SOURCES[global] as string)\n out.set(shim, shimSource(shim, global, names ?? [...(FALLBACK_NAMES[global] ?? [])]))\n }\n return out\n}\n\nlet cachedShims: { embedJs: string; shims: Promise<Map<string, string>> } | null = null\n\n/** {@link buildShims}, once per renderer bundle. */\nexport function shimsFor(embedJs: string): Promise<Map<string, string>> {\n if (cachedShims?.embedJs !== embedJs) cachedShims = { embedJs, shims: buildShims(embedJs) }\n return cachedShims.shims\n}\n\n/** The import map: every host import → `./shims/<file>`. */\nexport function importMap(): { readonly imports: Record<string, string> } {\n return blockImportMap('./shims')\n}\n\nexport function escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n\n/** JSON safe to place inside a `<script>` element. */\nexport function scriptJson(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c')\n}\n\nexport interface ShellOptions {\n readonly title: string\n /** `<html lang>`; default `en`. */\n readonly lang?: string\n /** The app's appearance configuration (its webfonts are linked). */\n readonly appearance?: Record<string, unknown>\n /** Markup appended to `<body>` (the dev server's reload client). */\n readonly bodyExtra?: string\n /** The policy for the shell's `<meta http-equiv=\"Content-Security-Policy\">`; none when absent. */\n readonly csp?: string\n}\n\n/** The exact text of the shell's inline import map (the CSP hashes this). */\nexport function importMapText(): string {\n return scriptJson(importMap())\n}\n\n/**\n * `assets/seed.js`: seeds the globals the renderer does not seed itself from\n * its vendor table (a file, not an inline script, so the policy needs no hash).\n */\nexport function seedScript(): string {\n const seeds = Object.entries(SHELL_SEEDED_GLOBALS)\n .map(([global, key]) => ` g.${global} = g.${global} || v[${JSON.stringify(key)}];`)\n .join('\\n')\n return `// Generated by pwf: globals the renderer carries in ${VENDOR_GLOBAL} but does not seed.\n(function (g) {\n var v = g.${VENDOR_GLOBAL} || {};\n${seeds}\n})(globalThis)\n`\n}\n\n/** `assets/boot.js`: the module that starts the runner. */\nexport function bootScript(locale: 'en' | 'tr' = 'en'): string {\n return `// Generated by pwf: starts the app.\nimport { runApp } from './runner.js'\n\nrunApp({ source: './pwpack', blocks: './blocks/', engines: './engines', router: 'hash', locale: '${locale}' })\n`\n}\n\n/** A BCP-47-looking tag, or `en`. */\nexport function htmlLangOf(locale: string | undefined): string {\n const tag = (locale ?? '').trim()\n return /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(tag) ? tag : 'en'\n}\n\n/** `tr-TR` → `tr`; the runner's copy knows `en` and `tr`. */\nexport function runnerLocaleOf(locale: string | undefined): 'en' | 'tr' {\n return /^tr(?:[-_]|$)/i.test(locale ?? '') ? 'tr' : 'en'\n}\n\n/**\n * `index.html`, in the order the pwapp format requires: import map, the\n * renderer (a classic script that seeds the shared globals) and `seed.js`,\n * the mount element, then `boot.js`, which starts the runner. The import map\n * is the only inline script, so a Content Security Policy needs one hash.\n * Every address is relative, so the folder works at a host root, under a\n * sub-path, or from disk behind any static server.\n */\nexport function buildShellHtml(options: ShellOptions): string {\n const href = webfontStylesheetHref(themeWebfonts(options.appearance))\n const fonts = href\n ? `\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link rel=\"stylesheet\" href=\"${escapeHtml(href)}\" />`\n : ''\n const csp = options.csp\n ? `\n <meta http-equiv=\"Content-Security-Policy\" content=\"${escapeHtml(options.csp)}\" />`\n : ''\n return `<!doctype html>\n<html lang=\"${escapeHtml(htmlLangOf(options.lang))}\">\n <head>\n <meta charset=\"UTF-8\" />${csp}\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtml(options.title)}</title>\n <link rel=\"icon\" href=\"data:,\" />\n <link rel=\"stylesheet\" href=\"./assets/theme.css\" />${fonts}\n <script type=\"importmap\">${importMapText()}</script>\n <script src=\"./assets/embed.js\"></script>\n <script src=\"./assets/seed.js\"></script>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"./assets/boot.js\"></script>${options.bodyExtra ?? ''}\n </body>\n</html>\n`\n}\n\n/**\n * The shell with `<base href=\"/\">` as the first element of its head, unless it\n * already declares a base.\n *\n * The shell addresses its scripts and the import map relatively (`./assets/…`,\n * `./shims/…`) so the folder works under any sub-path. When a server answers a\n * nested page path (`/team/members`) with that shell, the browser would resolve\n * those addresses against `/team/` and miss every file. The pwapp format puts\n * the fix on the host: under path routing it declares the app's base with\n * `<base href>`. This server serves the app at its root, so the base is `/`.\n */\nexport function withBaseHref(body: Uint8Array, href = '/'): Uint8Array {\n const html = new TextDecoder().decode(body)\n if (/<bases/i.test(html)) return body\n const at = html.search(/<head[^>]*>/i)\n if (at < 0) return body\n const end = html.indexOf('>', at) + 1\n const tag = `\n <base href=\"${href.replace(/\"/g, '&quot;')}\" />`\n return new TextEncoder().encode(html.slice(0, end) + tag + html.slice(end))\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { AtomRef, PwpackIdentity } from '@pwfabric/contracts';
2
+ import { type PackFolder, type PackValidation } from '@pwfabric/pack';
3
+ import type { AssembleDiagnostic } from './diagnostics.js';
4
+ /** The project's working folder; never part of the app. */
5
+ export declare const PWF_DIR = ".pwf";
6
+ /** Names skipped when the app folder is read. */
7
+ export declare const APP_IGNORED_NAMES: ReadonlySet<string>;
8
+ /** One folder the assembler reads atoms from: the app itself or a pinned pack build. */
9
+ export interface SourcePack {
10
+ readonly origin: 'app' | 'pack';
11
+ readonly id: string;
12
+ readonly version: string;
13
+ /** The pin's hash, when the app pinned one. */
14
+ readonly pinnedHash?: string;
15
+ /** Absolute folder. */
16
+ readonly dir: string;
17
+ /** Path of `dir` as diagnostics print it (relative to the app root). */
18
+ readonly label: string;
19
+ readonly folder: PackFolder;
20
+ readonly validation: PackValidation;
21
+ }
22
+ export interface LoadedSources {
23
+ readonly app: SourcePack | null;
24
+ readonly identity: PwpackIdentity | null;
25
+ /** The pinned packs, in pin order (the app's own atoms are in `app`). */
26
+ readonly packs: readonly SourcePack[];
27
+ readonly diagnostics: readonly AssembleDiagnostic[];
28
+ }
29
+ /** `.pwf/packs/<id>@<version>` as a POSIX path relative to the app root. */
30
+ export declare function packCacheRel(id: string, version: string): string | null;
31
+ /**
32
+ * Pack builds already read, by absolute folder. A cached build is immutable
33
+ * (`<id>@<version>`), so a dev session reads each once and forgets it only
34
+ * when the watcher sees its folder change.
35
+ */
36
+ export type PackMemo = Map<string, SourcePack>;
37
+ /** Reads the app at `root` and every pack build it pins. */
38
+ export declare function loadSources(root: string, memo?: PackMemo): Promise<LoadedSources>;
39
+ /** The text of a file in a pack, or undefined. */
40
+ export declare function textOf(folder: PackFolder, path: string): string | undefined;
41
+ /** A JSON file of a pack, parsed; `undefined` when absent, an `Error` when it does not parse. */
42
+ export declare function jsonOf(folder: PackFolder, path: string): unknown;
43
+ export declare function isRecord(v: unknown): v is Record<string, unknown>;
44
+ /** The atoms of one source, as validation derived them. */
45
+ export declare function atomsOf(source: SourcePack): readonly AtomRef[];
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Reading what an app is made of: the app folder itself and every pack build
3
+ * its `pwpack.json` pins, from the project's local cache
4
+ * (`.pwf/packs/<id>@<version>/`, written by `pwf create` / `pwf add`).
5
+ *
6
+ * Nothing here fetches: a pin that is not in the cache is a diagnostic that
7
+ * says which command fills it.
8
+ */
9
+ import { stat } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { DEFAULT_IGNORED_NAMES, PackError, isSafePackPath, packContent, readFolder, validatePack, } from '@pwfabric/pack';
12
+ /** The project's working folder; never part of the app. */
13
+ export const PWF_DIR = '.pwf';
14
+ /** Names skipped when the app folder is read. */
15
+ export const APP_IGNORED_NAMES = new Set([...DEFAULT_IGNORED_NAMES, PWF_DIR]);
16
+ /** `.pwf/packs/<id>@<version>` as a POSIX path relative to the app root. */
17
+ export function packCacheRel(id, version) {
18
+ const rel = `${id}@${version}`;
19
+ if (!isSafePackPath(rel) || /[<>:"|?*]/.test(rel))
20
+ return null;
21
+ return `${PWF_DIR}/packs/${rel}`;
22
+ }
23
+ async function isDir(path) {
24
+ return (await stat(path).catch(() => null))?.isDirectory() === true;
25
+ }
26
+ function issuesToDiagnostics(validation, label, downgrade) {
27
+ return validation.issues
28
+ .filter((i) => i.severity !== 'info')
29
+ .map((i) => ({
30
+ severity: downgrade && i.severity === 'error' ? 'warning' : i.severity,
31
+ code: i.code,
32
+ message: i.message,
33
+ ...(i.path !== undefined ? { path: label ? `${label}/${i.path}` : i.path } : {}),
34
+ }));
35
+ }
36
+ function packDiagnostics(source, pin) {
37
+ const out = issuesToDiagnostics(source.validation, source.label, true);
38
+ const cached = source.validation.identity;
39
+ if (cached && (cached.id !== pin.id || cached.version !== pin.version)) {
40
+ out.push({
41
+ severity: 'warning',
42
+ code: 'PACK_IDENTITY_MISMATCH',
43
+ message: `${source.label}/pwpack.json says ${cached.id}@${cached.version}, the pin says ${pin.id}@${pin.version}`,
44
+ path: `${source.label}/pwpack.json`,
45
+ });
46
+ }
47
+ return out;
48
+ }
49
+ async function readSource(dir, ignore) {
50
+ try {
51
+ return await readFolder(dir, { ignore });
52
+ }
53
+ catch (e) {
54
+ if (e instanceof PackError)
55
+ return `${e.code}: ${e.message}`;
56
+ throw e;
57
+ }
58
+ }
59
+ /** Reads the app at `root` and every pack build it pins. */
60
+ export async function loadSources(root, memo) {
61
+ const diagnostics = [];
62
+ const read = await readSource(root, APP_IGNORED_NAMES);
63
+ if (typeof read === 'string') {
64
+ diagnostics.push({ severity: 'error', code: 'APP_UNREADABLE', message: read });
65
+ return { app: null, identity: null, packs: [], diagnostics };
66
+ }
67
+ // The app is its pack content: the `.gitignore` that `pwf create` writes,
68
+ // editor dotfiles and the author's tooling are not part of it, so a build
69
+ // neither reads nor warns about them (`pwf validate` still lists tooling).
70
+ const appFolder = packContent(read);
71
+ const appValidation = validatePack(appFolder);
72
+ diagnostics.push(...issuesToDiagnostics(appValidation, '', false));
73
+ const identity = appValidation.identity;
74
+ if (!identity) {
75
+ if (!diagnostics.some((d) => d.severity === 'error')) {
76
+ diagnostics.push({ severity: 'error', code: 'IDENTITY_MISSING', message: 'pwpack.json is missing or invalid', path: 'pwpack.json' });
77
+ }
78
+ return { app: null, identity: null, packs: [], diagnostics };
79
+ }
80
+ const app = {
81
+ origin: 'app',
82
+ id: identity.id,
83
+ version: identity.version,
84
+ dir: root,
85
+ label: '',
86
+ folder: appFolder,
87
+ validation: appValidation,
88
+ };
89
+ const packs = [];
90
+ for (const pin of identity.packs ?? []) {
91
+ const rel = packCacheRel(pin.id, pin.version);
92
+ if (!rel) {
93
+ diagnostics.push({
94
+ severity: 'error',
95
+ code: 'PIN_INVALID',
96
+ message: `pack "${pin.id}" version "${pin.version}" cannot name a cache folder`,
97
+ path: 'pwpack.json',
98
+ });
99
+ continue;
100
+ }
101
+ const dir = join(root, ...rel.split('/'));
102
+ const remembered = memo?.get(dir);
103
+ if (remembered) {
104
+ diagnostics.push(...packDiagnostics(remembered, pin));
105
+ packs.push({ ...remembered, pinnedHash: pin.hash });
106
+ continue;
107
+ }
108
+ if (!(await isDir(dir))) {
109
+ diagnostics.push({
110
+ severity: 'error',
111
+ code: 'PACK_NOT_CACHED',
112
+ message: `${pin.id}@${pin.version} is pinned in pwpack.json but is not in ${rel}/ — run \`pwf add ${pin.id}@${pin.version}\` (or recreate the app with \`pwf create\`) to put it there`,
113
+ path: 'pwpack.json',
114
+ });
115
+ continue;
116
+ }
117
+ const folder = await readSource(dir, DEFAULT_IGNORED_NAMES);
118
+ if (typeof folder === 'string') {
119
+ diagnostics.push({ severity: 'error', code: 'PACK_UNREADABLE', message: folder, path: rel });
120
+ continue;
121
+ }
122
+ const source = {
123
+ origin: 'pack',
124
+ id: pin.id,
125
+ version: pin.version,
126
+ pinnedHash: pin.hash,
127
+ dir,
128
+ label: rel,
129
+ folder,
130
+ validation: validatePack(folder),
131
+ };
132
+ memo?.set(dir, source);
133
+ diagnostics.push(...packDiagnostics(source, pin));
134
+ packs.push(source);
135
+ }
136
+ return { app, identity, packs, diagnostics };
137
+ }
138
+ /** The text of a file in a pack, or undefined. */
139
+ export function textOf(folder, path) {
140
+ const bytes = folder.files.get(path);
141
+ if (!bytes)
142
+ return undefined;
143
+ const text = new TextDecoder().decode(bytes);
144
+ return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
145
+ }
146
+ /** A JSON file of a pack, parsed; `undefined` when absent, an `Error` when it does not parse. */
147
+ export function jsonOf(folder, path) {
148
+ const text = textOf(folder, path);
149
+ if (text === undefined)
150
+ return undefined;
151
+ try {
152
+ return JSON.parse(text);
153
+ }
154
+ catch (e) {
155
+ return new Error(e.message);
156
+ }
157
+ }
158
+ export function isRecord(v) {
159
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
160
+ }
161
+ /** The atoms of one source, as validation derived them. */
162
+ export function atomsOf(source) {
163
+ return source.validation.atoms;
164
+ }
165
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../../src/dev/sources.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reading what an app is made of: the app folder itself and every pack build\n * its `pwpack.json` pins, from the project's local cache\n * (`.pwf/packs/<id>@<version>/`, written by `pwf create` / `pwf add`).\n *\n * Nothing here fetches: a pin that is not in the cache is a diagnostic that\n * says which command fills it.\n */\nimport { stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { AtomRef, PwpackIdentity } from '@pwfabric/contracts'\nimport {\n DEFAULT_IGNORED_NAMES,\n PackError,\n isSafePackPath,\n packContent,\n readFolder,\n validatePack,\n type PackFolder,\n type PackValidation,\n} from '@pwfabric/pack'\nimport type { AssembleDiagnostic } from './diagnostics.js'\n\n/** The project's working folder; never part of the app. */\nexport const PWF_DIR = '.pwf'\n/** Names skipped when the app folder is read. */\nexport const APP_IGNORED_NAMES: ReadonlySet<string> = new Set([...DEFAULT_IGNORED_NAMES, PWF_DIR])\n\n/** One folder the assembler reads atoms from: the app itself or a pinned pack build. */\nexport interface SourcePack {\n readonly origin: 'app' | 'pack'\n readonly id: string\n readonly version: string\n /** The pin's hash, when the app pinned one. */\n readonly pinnedHash?: string\n /** Absolute folder. */\n readonly dir: string\n /** Path of `dir` as diagnostics print it (relative to the app root). */\n readonly label: string\n readonly folder: PackFolder\n readonly validation: PackValidation\n}\n\nexport interface LoadedSources {\n readonly app: SourcePack | null\n readonly identity: PwpackIdentity | null\n /** The pinned packs, in pin order (the app's own atoms are in `app`). */\n readonly packs: readonly SourcePack[]\n readonly diagnostics: readonly AssembleDiagnostic[]\n}\n\n/** `.pwf/packs/<id>@<version>` as a POSIX path relative to the app root. */\nexport function packCacheRel(id: string, version: string): string | null {\n const rel = `${id}@${version}`\n if (!isSafePackPath(rel) || /[<>:\"|?*]/.test(rel)) return null\n return `${PWF_DIR}/packs/${rel}`\n}\n\nasync function isDir(path: string): Promise<boolean> {\n return (await stat(path).catch(() => null))?.isDirectory() === true\n}\n\nfunction issuesToDiagnostics(\n validation: PackValidation,\n label: string,\n downgrade: boolean\n): AssembleDiagnostic[] {\n return validation.issues\n .filter((i) => i.severity !== 'info')\n .map((i) => ({\n severity: downgrade && i.severity === 'error' ? 'warning' : i.severity,\n code: i.code,\n message: i.message,\n ...(i.path !== undefined ? { path: label ? `${label}/${i.path}` : i.path } : {}),\n }))\n}\n\nfunction packDiagnostics(source: SourcePack, pin: { id: string; version: string }): AssembleDiagnostic[] {\n const out = issuesToDiagnostics(source.validation, source.label, true)\n const cached = source.validation.identity\n if (cached && (cached.id !== pin.id || cached.version !== pin.version)) {\n out.push({\n severity: 'warning',\n code: 'PACK_IDENTITY_MISMATCH',\n message: `${source.label}/pwpack.json says ${cached.id}@${cached.version}, the pin says ${pin.id}@${pin.version}`,\n path: `${source.label}/pwpack.json`,\n })\n }\n return out\n}\n\nasync function readSource(dir: string, ignore: ReadonlySet<string>): Promise<PackFolder | string> {\n try {\n return await readFolder(dir, { ignore })\n } catch (e) {\n if (e instanceof PackError) return `${e.code}: ${e.message}`\n throw e\n }\n}\n\n/**\n * Pack builds already read, by absolute folder. A cached build is immutable\n * (`<id>@<version>`), so a dev session reads each once and forgets it only\n * when the watcher sees its folder change.\n */\nexport type PackMemo = Map<string, SourcePack>\n\n/** Reads the app at `root` and every pack build it pins. */\nexport async function loadSources(root: string, memo?: PackMemo): Promise<LoadedSources> {\n const diagnostics: AssembleDiagnostic[] = []\n const read = await readSource(root, APP_IGNORED_NAMES)\n if (typeof read === 'string') {\n diagnostics.push({ severity: 'error', code: 'APP_UNREADABLE', message: read })\n return { app: null, identity: null, packs: [], diagnostics }\n }\n // The app is its pack content: the `.gitignore` that `pwf create` writes,\n // editor dotfiles and the author's tooling are not part of it, so a build\n // neither reads nor warns about them (`pwf validate` still lists tooling).\n const appFolder = packContent(read)\n const appValidation = validatePack(appFolder)\n diagnostics.push(...issuesToDiagnostics(appValidation, '', false))\n const identity = appValidation.identity\n if (!identity) {\n if (!diagnostics.some((d) => d.severity === 'error')) {\n diagnostics.push({ severity: 'error', code: 'IDENTITY_MISSING', message: 'pwpack.json is missing or invalid', path: 'pwpack.json' })\n }\n return { app: null, identity: null, packs: [], diagnostics }\n }\n const app: SourcePack = {\n origin: 'app',\n id: identity.id,\n version: identity.version,\n dir: root,\n label: '',\n folder: appFolder,\n validation: appValidation,\n }\n\n const packs: SourcePack[] = []\n for (const pin of identity.packs ?? []) {\n const rel = packCacheRel(pin.id, pin.version)\n if (!rel) {\n diagnostics.push({\n severity: 'error',\n code: 'PIN_INVALID',\n message: `pack \"${pin.id}\" version \"${pin.version}\" cannot name a cache folder`,\n path: 'pwpack.json',\n })\n continue\n }\n const dir = join(root, ...rel.split('/'))\n const remembered = memo?.get(dir)\n if (remembered) {\n diagnostics.push(...packDiagnostics(remembered, pin))\n packs.push({ ...remembered, pinnedHash: pin.hash })\n continue\n }\n if (!(await isDir(dir))) {\n diagnostics.push({\n severity: 'error',\n code: 'PACK_NOT_CACHED',\n message: `${pin.id}@${pin.version} is pinned in pwpack.json but is not in ${rel}/ — run \\`pwf add ${pin.id}@${pin.version}\\` (or recreate the app with \\`pwf create\\`) to put it there`,\n path: 'pwpack.json',\n })\n continue\n }\n const folder = await readSource(dir, DEFAULT_IGNORED_NAMES)\n if (typeof folder === 'string') {\n diagnostics.push({ severity: 'error', code: 'PACK_UNREADABLE', message: folder, path: rel })\n continue\n }\n const source: SourcePack = {\n origin: 'pack',\n id: pin.id,\n version: pin.version,\n pinnedHash: pin.hash,\n dir,\n label: rel,\n folder,\n validation: validatePack(folder),\n }\n memo?.set(dir, source)\n diagnostics.push(...packDiagnostics(source, pin))\n packs.push(source)\n }\n return { app, identity, packs, diagnostics }\n}\n\n/** The text of a file in a pack, or undefined. */\nexport function textOf(folder: PackFolder, path: string): string | undefined {\n const bytes = folder.files.get(path)\n if (!bytes) return undefined\n const text = new TextDecoder().decode(bytes)\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text\n}\n\n/** A JSON file of a pack, parsed; `undefined` when absent, an `Error` when it does not parse. */\nexport function jsonOf(folder: PackFolder, path: string): unknown {\n const text = textOf(folder, path)\n if (text === undefined) return undefined\n try {\n return JSON.parse(text) as unknown\n } catch (e) {\n return new Error((e as Error).message)\n }\n}\n\nexport function isRecord(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\n/** The atoms of one source, as validation derived them. */\nexport function atomsOf(source: SourcePack): readonly AtomRef[] {\n return source.validation.atoms\n}\n"]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The app's appearance: resolving a theme named by `use` against the themes
3
+ * the app and its packs carry, and the stylesheet the shell links so the page
4
+ * has its identity before any script runs.
5
+ */
6
+ import { type AllowedWebfont } from '@pwfabric/contracts';
7
+ import type { AssembleDiagnostic } from './diagnostics.js';
8
+ /** A `ui-appearance` atom an app can name: its id and its token sets. */
9
+ export interface ThemeAtom {
10
+ readonly id: string;
11
+ /** The manifest's own `id`, when it differs from the path-derived one. */
12
+ readonly manifestId?: string;
13
+ readonly theme: Record<string, unknown>;
14
+ readonly darkTheme?: Record<string, unknown>;
15
+ /** Where it came from, for diagnostics. */
16
+ readonly from: string;
17
+ }
18
+ /** Builds a {@link ThemeAtom} from a `ui-appearance` manifest, or null when it carries no token set. */
19
+ export declare function themeAtomOf(id: string, manifest: unknown, from: string): ThemeAtom | null;
20
+ /**
21
+ * The theme a name points at. Matched as given, then as `theme-<name>`, then
22
+ * with an `appearance-` / `theme-` prefix removed; the first atom wins (the
23
+ * app's own themes come before its packs').
24
+ */
25
+ export declare function findTheme(name: string, themes: readonly ThemeAtom[]): ThemeAtom | undefined;
26
+ /** Per-group merge, `over` winning. */
27
+ export declare function mergeGroups(base: Record<string, unknown>, over: unknown): Record<string, unknown>;
28
+ /**
29
+ * The app-wide appearance as the envelope carries it. A theme named by `use`
30
+ * is resolved to its tokens here, so the published app does not depend on
31
+ * the runner knowing the name. The three shapes a reader accepts are kept:
32
+ * a reference (`{ config }`) stays a reference, everything else becomes a
33
+ * bare configuration.
34
+ */
35
+ export declare function resolveAppearance(doc: unknown, themes: readonly ThemeAtom[], diagnostics: AssembleDiagnostic[], path?: string): Record<string, unknown> | undefined;
36
+ /** The configuration inside any of the three appearance shapes. */
37
+ export declare function appearanceConfigOf(doc: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
38
+ /**
39
+ * `assets/theme.css`: the full token set as custom properties (light, the
40
+ * dark variant per the mode policy) and the document binding — page margin,
41
+ * body font and colours — so the app wears its identity before hydration.
42
+ */
43
+ export declare function buildThemeCss(config: Record<string, unknown> | undefined): string;
44
+ /**
45
+ * The webfonts a theme names and may load: the first family of the body,
46
+ * heading and mono stacks, and only when it is on the engine's allow-list. A
47
+ * free-text family never becomes a URL.
48
+ */
49
+ export declare function themeWebfonts(config: Record<string, unknown> | undefined): AllowedWebfont[];
50
+ /** One stylesheet address for the families, or null when there are none. */
51
+ export declare function webfontStylesheetHref(fonts: readonly AllowedWebfont[]): string | null;