@pwfabric/cli 0.0.0-stage → 1.0.0-rc.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 (171) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +122 -2
  3. package/dist/args.d.ts +12 -0
  4. package/dist/args.d.ts.map +1 -0
  5. package/dist/args.js +43 -0
  6. package/dist/args.js.map +1 -0
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +10 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/commands/add.d.ts +4 -0
  12. package/dist/commands/add.d.ts.map +1 -0
  13. package/dist/commands/add.js +56 -0
  14. package/dist/commands/add.js.map +1 -0
  15. package/dist/commands/build.d.ts +4 -0
  16. package/dist/commands/build.d.ts.map +1 -0
  17. package/dist/commands/build.js +55 -0
  18. package/dist/commands/build.js.map +1 -0
  19. package/dist/commands/create.d.ts +10 -0
  20. package/dist/commands/create.d.ts.map +1 -0
  21. package/dist/commands/create.js +119 -0
  22. package/dist/commands/create.js.map +1 -0
  23. package/dist/commands/dev.d.ts +13 -0
  24. package/dist/commands/dev.d.ts.map +1 -0
  25. package/dist/commands/dev.js +107 -0
  26. package/dist/commands/dev.js.map +1 -0
  27. package/dist/commands/lib/flags.d.ts +11 -0
  28. package/dist/commands/lib/flags.d.ts.map +1 -0
  29. package/dist/commands/lib/flags.js +33 -0
  30. package/dist/commands/lib/flags.js.map +1 -0
  31. package/dist/commands/lib/identity.d.ts +39 -0
  32. package/dist/commands/lib/identity.d.ts.map +1 -0
  33. package/dist/commands/lib/identity.js +120 -0
  34. package/dist/commands/lib/identity.js.map +1 -0
  35. package/dist/commands/lib/project.d.ts +35 -0
  36. package/dist/commands/lib/project.d.ts.map +1 -0
  37. package/dist/commands/lib/project.js +78 -0
  38. package/dist/commands/lib/project.js.map +1 -0
  39. package/dist/commands/lib/report.d.ts +16 -0
  40. package/dist/commands/lib/report.d.ts.map +1 -0
  41. package/dist/commands/lib/report.js +75 -0
  42. package/dist/commands/lib/report.js.map +1 -0
  43. package/dist/commands/lib/starter.d.ts +42 -0
  44. package/dist/commands/lib/starter.d.ts.map +1 -0
  45. package/dist/commands/lib/starter.js +136 -0
  46. package/dist/commands/lib/starter.js.map +1 -0
  47. package/dist/commands/lib/static-server.d.ts +46 -0
  48. package/dist/commands/lib/static-server.d.ts.map +1 -0
  49. package/dist/commands/lib/static-server.js +173 -0
  50. package/dist/commands/lib/static-server.js.map +1 -0
  51. package/dist/commands/pack.d.ts +6 -0
  52. package/dist/commands/pack.d.ts.map +1 -0
  53. package/dist/commands/pack.js +56 -0
  54. package/dist/commands/pack.js.map +1 -0
  55. package/dist/commands/registry.d.ts +8 -0
  56. package/dist/commands/registry.d.ts.map +1 -0
  57. package/dist/commands/registry.js +16 -0
  58. package/dist/commands/registry.js.map +1 -0
  59. package/dist/commands/run.d.ts +15 -0
  60. package/dist/commands/run.d.ts.map +1 -0
  61. package/dist/commands/run.js +79 -0
  62. package/dist/commands/run.js.map +1 -0
  63. package/dist/commands/types.d.ts +35 -0
  64. package/dist/commands/types.d.ts.map +1 -0
  65. package/dist/commands/types.js +10 -0
  66. package/dist/commands/types.js.map +1 -0
  67. package/dist/commands/validate.d.ts +7 -0
  68. package/dist/commands/validate.d.ts.map +1 -0
  69. package/dist/commands/validate.js +33 -0
  70. package/dist/commands/validate.js.map +1 -0
  71. package/dist/dev/assemble.d.ts +48 -0
  72. package/dist/dev/assemble.d.ts.map +1 -0
  73. package/dist/dev/assemble.js +339 -0
  74. package/dist/dev/assemble.js.map +1 -0
  75. package/dist/dev/compile.d.ts +55 -0
  76. package/dist/dev/compile.d.ts.map +1 -0
  77. package/dist/dev/compile.js +215 -0
  78. package/dist/dev/compile.js.map +1 -0
  79. package/dist/dev/csp.d.ts +68 -0
  80. package/dist/dev/csp.d.ts.map +1 -0
  81. package/dist/dev/csp.js +260 -0
  82. package/dist/dev/csp.js.map +1 -0
  83. package/dist/dev/diagnostics.d.ts +22 -0
  84. package/dist/dev/diagnostics.d.ts.map +1 -0
  85. package/dist/dev/diagnostics.js +22 -0
  86. package/dist/dev/diagnostics.js.map +1 -0
  87. package/dist/dev/envelope.d.ts +93 -0
  88. package/dist/dev/envelope.d.ts.map +1 -0
  89. package/dist/dev/envelope.js +154 -0
  90. package/dist/dev/envelope.js.map +1 -0
  91. package/dist/dev/media.d.ts +54 -0
  92. package/dist/dev/media.d.ts.map +1 -0
  93. package/dist/dev/media.js +128 -0
  94. package/dist/dev/media.js.map +1 -0
  95. package/dist/dev/needs-host.d.ts +3 -0
  96. package/dist/dev/needs-host.d.ts.map +1 -0
  97. package/dist/dev/needs-host.js +32 -0
  98. package/dist/dev/needs-host.js.map +1 -0
  99. package/dist/dev/output.d.ts +10 -0
  100. package/dist/dev/output.d.ts.map +1 -0
  101. package/dist/dev/output.js +32 -0
  102. package/dist/dev/output.js.map +1 -0
  103. package/dist/dev/overlay.d.ts +22 -0
  104. package/dist/dev/overlay.d.ts.map +1 -0
  105. package/dist/dev/overlay.js +59 -0
  106. package/dist/dev/overlay.js.map +1 -0
  107. package/dist/dev/server.d.ts +28 -0
  108. package/dist/dev/server.d.ts.map +1 -0
  109. package/dist/dev/server.js +264 -0
  110. package/dist/dev/server.js.map +1 -0
  111. package/dist/dev/shell.d.ts +83 -0
  112. package/dist/dev/shell.d.ts.map +1 -0
  113. package/dist/dev/shell.js +244 -0
  114. package/dist/dev/shell.js.map +1 -0
  115. package/dist/dev/sources.d.ts +46 -0
  116. package/dist/dev/sources.d.ts.map +1 -0
  117. package/dist/dev/sources.js +165 -0
  118. package/dist/dev/sources.js.map +1 -0
  119. package/dist/dev/theme.d.ts +52 -0
  120. package/dist/dev/theme.d.ts.map +1 -0
  121. package/dist/dev/theme.js +185 -0
  122. package/dist/dev/theme.js.map +1 -0
  123. package/dist/dev/types.d.ts +41 -0
  124. package/dist/dev/types.d.ts.map +1 -0
  125. package/dist/dev/types.js +11 -0
  126. package/dist/dev/types.js.map +1 -0
  127. package/dist/dev/watcher.d.ts +30 -0
  128. package/dist/dev/watcher.d.ts.map +1 -0
  129. package/dist/dev/watcher.js +150 -0
  130. package/dist/dev/watcher.js.map +1 -0
  131. package/dist/host/definitions.d.ts +62 -0
  132. package/dist/host/definitions.d.ts.map +1 -0
  133. package/dist/host/definitions.js +136 -0
  134. package/dist/host/definitions.js.map +1 -0
  135. package/dist/host/http.d.ts +40 -0
  136. package/dist/host/http.d.ts.map +1 -0
  137. package/dist/host/http.js +156 -0
  138. package/dist/host/http.js.map +1 -0
  139. package/dist/host/local-host.d.ts +60 -0
  140. package/dist/host/local-host.d.ts.map +1 -0
  141. package/dist/host/local-host.js +420 -0
  142. package/dist/host/local-host.js.map +1 -0
  143. package/dist/host/oidc.d.ts +47 -0
  144. package/dist/host/oidc.d.ts.map +1 -0
  145. package/dist/host/oidc.js +192 -0
  146. package/dist/host/oidc.js.map +1 -0
  147. package/dist/host/query.d.ts +36 -0
  148. package/dist/host/query.d.ts.map +1 -0
  149. package/dist/host/query.js +158 -0
  150. package/dist/host/query.js.map +1 -0
  151. package/dist/host/receipts.d.ts +26 -0
  152. package/dist/host/receipts.d.ts.map +1 -0
  153. package/dist/host/receipts.js +72 -0
  154. package/dist/host/receipts.js.map +1 -0
  155. package/dist/host/records.d.ts +74 -0
  156. package/dist/host/records.d.ts.map +1 -0
  157. package/dist/host/records.js +229 -0
  158. package/dist/host/records.js.map +1 -0
  159. package/dist/host/tokens.d.ts +29 -0
  160. package/dist/host/tokens.d.ts.map +1 -0
  161. package/dist/host/tokens.js +52 -0
  162. package/dist/host/tokens.js.map +1 -0
  163. package/dist/host/types.d.ts +33 -0
  164. package/dist/host/types.d.ts.map +1 -0
  165. package/dist/host/types.js +11 -0
  166. package/dist/host/types.js.map +1 -0
  167. package/dist/index.d.ts +17 -0
  168. package/dist/index.d.ts.map +1 -0
  169. package/dist/index.js +41 -0
  170. package/dist/index.js.map +1 -0
  171. package/package.json +65 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/dev/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAuD,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACrG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAG1D,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAChD,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;CACzC;AAuBD,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,GAAG,cAAc,CAwClG;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CA4BxH"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Media: the app's declared images and files, copied into the pwapp.
3
+ *
4
+ * pwpack-format §1.3: `app/media/<key>.json` declares one image or file
5
+ * (`{ purpose, kind, origin, slots, status, url?, decorative?, file? }`) and
6
+ * carries no bytes; the bytes are `media/<file>`, each recorded in
7
+ * `media/provenance.json`. The pack library's `mediaDeclarations` reads
8
+ * that layout (its findings reach the diagnostics through `validatePack`).
9
+ * pwapp-format §1 puts the bytes the app serves at `pwpack/media/<file>`,
10
+ * and §4 records each declaration's resolved address in `publish.json`
11
+ * `media[]`.
12
+ *
13
+ * For each declaration the bytes are looked up, in order:
14
+ *
15
+ * 1. the declaration's `file` (a pack path, `media/<name>`);
16
+ * 2. `media/<path>` — a `url` that is itself a `media/…` path;
17
+ * 3. `media/<key>.<ext>` — the one media file named after the key.
18
+ *
19
+ * Found bytes are served at `pwpack/media/…` and the declaration resolves to
20
+ * that address. A declaration without bytes but with an absolute `url` keeps
21
+ * the url. Every other one is reported (info) and left alone.
22
+ *
23
+ * Slots: a declared slot (`{ surfaceKey, blockId, prop }`) whose prop is
24
+ * empty in the served receipt gets the resolved address. A value already in
25
+ * the prop is never replaced (plan.md §2 rule 1). The app's own files are
26
+ * never rewritten; only the served copy of the receipt is.
27
+ */
28
+ import { MEDIA_DIR, MEDIA_PROVENANCE_FILE, mediaDeclarations } from '@pwfabric/pack';
29
+ import { isRecord } from './sources.js';
30
+ const ABSOLUTE_URL = /^(?:https?:|data:)/i;
31
+ /** A single path segment that is safe to serve: no separators, no dot names. */
32
+ function isSafeName(name) {
33
+ return name.length > 0 && !/[\\/\0]/.test(name) && name !== '.' && name !== '..';
34
+ }
35
+ /** A relative `media/…` path with no `.`/`..` segment. */
36
+ function isSafeMediaPath(path) {
37
+ return path.startsWith(MEDIA_DIR) && path.slice(MEDIA_DIR.length).split('/').every(isSafeName);
38
+ }
39
+ /** The media file named after a key (`media/<key>.<ext>`), when exactly one exists. */
40
+ function byKey(mediaPaths, key) {
41
+ const hits = mediaPaths.filter((p) => {
42
+ const name = p.slice(MEDIA_DIR.length);
43
+ return !name.includes('/') && name.slice(0, name.lastIndexOf('.') > 0 ? name.lastIndexOf('.') : undefined) === key;
44
+ });
45
+ return hits.length === 1 ? hits[0] : undefined;
46
+ }
47
+ /** Reads the app's media declarations and bytes. */
48
+ export function collectMedia(folder, diagnostics) {
49
+ const files = new Map();
50
+ const records = [];
51
+ const slots = new Map();
52
+ const mediaPaths = [...folder.files.keys()].filter((p) => p !== MEDIA_PROVENANCE_FILE && isSafeMediaPath(p)).sort();
53
+ for (const path of mediaPaths)
54
+ files.set(path, folder.files.get(path));
55
+ for (const { key, path, declaration, filePath } of mediaDeclarations(folder).declarations) {
56
+ const declaredUrl = declaration.url?.trim() ?? '';
57
+ const urlPath = declaredUrl.replace(/^\.\//, '');
58
+ let url;
59
+ const inMedia = (filePath !== undefined && files.has(filePath) ? filePath : undefined) ??
60
+ (isSafeMediaPath(urlPath) && files.has(urlPath) ? urlPath : undefined) ??
61
+ byKey(mediaPaths, key);
62
+ if (inMedia) {
63
+ url = `pwpack/${inMedia}`;
64
+ }
65
+ else if (ABSOLUTE_URL.test(declaredUrl)) {
66
+ url = declaredUrl;
67
+ }
68
+ if (url === undefined) {
69
+ diagnostics.push({
70
+ severity: 'info',
71
+ code: 'MEDIA_UNRESOLVED',
72
+ message: `media "${key}" has no bytes in media/ and no absolute url; its slots keep what the receipts say`,
73
+ path,
74
+ });
75
+ continue;
76
+ }
77
+ records.push({ key, url });
78
+ const served = ABSOLUTE_URL.test(url) ? url : `./${url}`;
79
+ for (const { surfaceKey, blockId, prop } of declaration.slots ?? []) {
80
+ const list = slots.get(surfaceKey) ?? [];
81
+ list.push({ blockId, prop, url: served });
82
+ slots.set(surfaceKey, list);
83
+ }
84
+ }
85
+ return { files, records, slots };
86
+ }
87
+ const isEmpty = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
88
+ /**
89
+ * The served receipt with its empty media slots filled, or `undefined` when
90
+ * nothing changed (the bytes are then served exactly as written).
91
+ */
92
+ export function fillMediaSlots(receiptBytes, slots) {
93
+ if (!slots || slots.length === 0)
94
+ return undefined;
95
+ let receipt;
96
+ try {
97
+ receipt = JSON.parse(new TextDecoder().decode(receiptBytes));
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ if (!isRecord(receipt))
103
+ return undefined;
104
+ const copy = structuredClone(receipt);
105
+ let changed = false;
106
+ const walk = (node) => {
107
+ if (Array.isArray(node)) {
108
+ node.forEach(walk);
109
+ return;
110
+ }
111
+ if (!isRecord(node))
112
+ return;
113
+ for (const slot of slots) {
114
+ if (node['id'] !== slot.blockId)
115
+ continue;
116
+ const props = isRecord(node['props']) ? node['props'] : {};
117
+ if (!isEmpty(props[slot.prop]))
118
+ continue;
119
+ node['props'] = { ...props, [slot.prop]: slot.url };
120
+ changed = true;
121
+ }
122
+ if (Array.isArray(node['children']))
123
+ node['children'].forEach(walk);
124
+ };
125
+ walk(copy['blocks']);
126
+ return changed ? new TextEncoder().encode(JSON.stringify(copy)) : undefined;
127
+ }
128
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/dev/media.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** What the app declares that only a host can serve. Empty when it needs none. */
2
+ export declare function hostNeeds(root: string): Promise<string[]>;
3
+ //# sourceMappingURL=needs-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"needs-host.d.ts","sourceRoot":"","sources":["../../src/dev/needs-host.ts"],"names":[],"mappings":"AAgBA,kFAAkF;AAClF,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgB/D"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Whether an app needs a local host: it declares sign-in
3
+ * (`app/definitions/auth.json`) or records (`app/definitions/entities.json`).
4
+ */
5
+ import { readFile } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+ import { isRecord } from './sources.js';
8
+ async function readJson(path) {
9
+ try {
10
+ return JSON.parse((await readFile(path, 'utf8')).replace(/^\uFEFF/, ''));
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ /** What the app declares that only a host can serve. Empty when it needs none. */
17
+ export async function hostNeeds(root) {
18
+ const needs = [];
19
+ const auth = await readJson(join(root, 'app', 'definitions', 'auth.json'));
20
+ if (isRecord(auth) &&
21
+ (auth['required'] === true ||
22
+ auth['topology'] === 'external' ||
23
+ (Array.isArray(auth['methods']) && auth['methods'].length > 0))) {
24
+ needs.push('sign-in');
25
+ }
26
+ const entities = await readJson(join(root, 'app', 'definitions', 'entities.json'));
27
+ if (isRecord(entities) && Array.isArray(entities['entities']) && entities['entities'].length > 0) {
28
+ needs.push('records');
29
+ }
30
+ return needs;
31
+ }
32
+ //# sourceMappingURL=needs-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"needs-host.js","sourceRoot":"","sources":["../../src/dev/needs-host.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEvC,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAY,CAAA;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY;IAC1C,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAA;IAC1E,IACE,QAAQ,CAAC,IAAI,CAAC;QACd,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI;YACxB,IAAI,CAAC,UAAU,CAAC,KAAK,UAAU;YAC/B,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EACjE,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACvB,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,eAAe,CAAC,CAAC,CAAA;IAClF,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACvB,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** Thrown when the output folder holds something that is not a previous build. */
2
+ export declare class OutputFolderError extends Error {
3
+ }
4
+ /**
5
+ * Writes `files` into `outDir`, replacing a previous build there. A folder
6
+ * that exists, is not empty and holds no `publish.json` is not a build this
7
+ * command wrote, and is left alone.
8
+ */
9
+ export declare function writePwapp(files: ReadonlyMap<string, Uint8Array>, outDir: string): Promise<void>;
10
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/dev/output.ts"],"names":[],"mappings":"AAMA,kFAAkF;AAClF,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBtG"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Writing an assembled pwapp to disk.
3
+ */
4
+ import { mkdir, readdir, rm, stat, writeFile } from 'node:fs/promises';
5
+ import { dirname, join } from 'node:path';
6
+ /** Thrown when the output folder holds something that is not a previous build. */
7
+ export class OutputFolderError extends Error {
8
+ }
9
+ /**
10
+ * Writes `files` into `outDir`, replacing a previous build there. A folder
11
+ * that exists, is not empty and holds no `publish.json` is not a build this
12
+ * command wrote, and is left alone.
13
+ */
14
+ export async function writePwapp(files, outDir) {
15
+ const st = await stat(outDir).catch(() => null);
16
+ if (st) {
17
+ if (!st.isDirectory())
18
+ throw new OutputFolderError(`${outDir} exists and is not a folder`);
19
+ const entries = await readdir(outDir);
20
+ if (entries.length > 0 && !entries.includes('publish.json')) {
21
+ throw new OutputFolderError(`${outDir} is not empty and holds no previous build (no publish.json); choose another --out`);
22
+ }
23
+ await rm(outDir, { recursive: true, force: true });
24
+ }
25
+ await mkdir(outDir, { recursive: true });
26
+ for (const [path, bytes] of files) {
27
+ const target = join(outDir, ...path.split('/'));
28
+ await mkdir(dirname(target), { recursive: true });
29
+ await writeFile(target, bytes);
30
+ }
31
+ }
32
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/dev/output.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEzC,kFAAkF;AAClF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAE/C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAsC,EAAE,MAAc;IACrF,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IAC/C,IAAI,EAAE,EAAE,CAAC;QACP,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE;YAAE,MAAM,IAAI,iBAAiB,CAAC,GAAG,MAAM,6BAA6B,CAAC,CAAA;QAC1F,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;QACrC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,iBAAiB,CAAC,GAAG,MAAM,mFAAmF,CAAC,CAAA;QAC3H,CAAC;QACD,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACxC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/C,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACjD,MAAM,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChC,CAAC;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * What `pwf dev` adds to a page: the reload client, and the overlay shown
3
+ * instead of the app when the last build failed.
4
+ */
5
+ import type { AssembleDiagnostic } from './diagnostics.js';
6
+ /** Server-Sent Events endpoint the page listens on. */
7
+ export declare const EVENTS_PATH = "/_pwf/events";
8
+ /** The current build's status as JSON. */
9
+ export declare const STATUS_PATH = "/_pwf/status";
10
+ /** Where the dev server serves the reload client's script. */
11
+ export declare const RELOAD_PATH = "/_pwf/reload.js";
12
+ /** The reload client's script: reloads the page when the server announces a new build. */
13
+ export declare const RELOAD_JS = "(function () {\n if (typeof EventSource === 'undefined') return\n var es = new EventSource('/_pwf/events')\n es.addEventListener('reload', function () { location.reload() })\n})()\n";
14
+ /**
15
+ * The reload client, as markup for a page. A script file on the dev server's
16
+ * own origin, not an inline script, so the app's Content Security Policy
17
+ * covers it with 'self' (and its event stream with connect-src 'self').
18
+ */
19
+ export declare const RELOAD_CLIENT = "\n <script src=\"/_pwf/reload.js\"></script>";
20
+ /** The page shown while the last build has errors. */
21
+ export declare function overlayHtml(diagnostics: readonly AssembleDiagnostic[], title?: string): string;
22
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/dev/overlay.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAG1D,uDAAuD;AACvD,eAAO,MAAM,WAAW,iBAAiB,CAAA;AACzC,0CAA0C;AAC1C,eAAO,MAAM,WAAW,iBAAiB,CAAA;AAEzC,8DAA8D;AAC9D,eAAO,MAAM,WAAW,oBAAoB,CAAA;AAE5C,0FAA0F;AAC1F,eAAO,MAAM,SAAS,6LAKrB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,oDACiB,CAAA;AAE3C,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,WAAW,EAAE,SAAS,kBAAkB,EAAE,EAAE,KAAK,SAAiB,GAAG,MAAM,CAqCtG"}
@@ -0,0 +1,59 @@
1
+ import { escapeHtml } from './shell.js';
2
+ /** Server-Sent Events endpoint the page listens on. */
3
+ export const EVENTS_PATH = '/_pwf/events';
4
+ /** The current build's status as JSON. */
5
+ export const STATUS_PATH = '/_pwf/status';
6
+ /** Where the dev server serves the reload client's script. */
7
+ export const RELOAD_PATH = '/_pwf/reload.js';
8
+ /** The reload client's script: reloads the page when the server announces a new build. */
9
+ export const RELOAD_JS = `(function () {
10
+ if (typeof EventSource === 'undefined') return
11
+ var es = new EventSource('${EVENTS_PATH}')
12
+ es.addEventListener('reload', function () { location.reload() })
13
+ })()
14
+ `;
15
+ /**
16
+ * The reload client, as markup for a page. A script file on the dev server's
17
+ * own origin, not an inline script, so the app's Content Security Policy
18
+ * covers it with 'self' (and its event stream with connect-src 'self').
19
+ */
20
+ export const RELOAD_CLIENT = `
21
+ <script src="${RELOAD_PATH}"></script>`;
22
+ /** The page shown while the last build has errors. */
23
+ export function overlayHtml(diagnostics, title = 'Build failed') {
24
+ const errors = diagnostics.filter((d) => d.severity === 'error');
25
+ const rest = diagnostics.length - errors.length;
26
+ const items = errors
27
+ .map((d) => `<li><code>${escapeHtml(d.code)}</code>${d.path ? ` <span class="path">${escapeHtml(d.path)}${d.line !== undefined ? `:${d.line}` : ''}</span>` : ''}<pre>${escapeHtml(d.message)}</pre></li>`)
28
+ .join('\n');
29
+ return `<!doctype html>
30
+ <html lang="en">
31
+ <head>
32
+ <meta charset="UTF-8" />
33
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
34
+ <title>${escapeHtml(title)} — pwf dev</title>
35
+ <style>
36
+ body { margin: 0; font: 14px/1.5 ui-sans-serif, system-ui, sans-serif; background: #1c1917; color: #fafaf9; }
37
+ main { max-width: 960px; margin: 0 auto; padding: 32px 16px; }
38
+ h1 { font-size: 20px; color: #fca5a5; margin: 0 0 4px; }
39
+ p { color: #d6d3d1; margin: 0 0 24px; }
40
+ ul { list-style: none; padding: 0; margin: 0; }
41
+ li { border-left: 3px solid #f87171; background: #292524; padding: 12px 16px; margin: 0 0 12px; border-radius: 4px; }
42
+ code { color: #fca5a5; font-weight: 600; }
43
+ .path { color: #a8a29e; margin-left: 8px; }
44
+ pre { white-space: pre-wrap; word-break: break-word; margin: 8px 0 0; font: 13px/1.5 ui-monospace, Menlo, Consolas, monospace; }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <main>
49
+ <h1>${escapeHtml(title)}</h1>
50
+ <p>${errors.length} error${errors.length === 1 ? '' : 's'}${rest > 0 ? `, ${rest} other finding${rest === 1 ? '' : 's'} in the terminal` : ''}. Fix the files and the page reloads.</p>
51
+ <ul>
52
+ ${items}
53
+ </ul>
54
+ </main>${RELOAD_CLIENT}
55
+ </body>
56
+ </html>
57
+ `;
58
+ }
59
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/dev/overlay.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvC,uDAAuD;AACvD,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAA;AACzC,0CAA0C;AAC1C,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAA;AAEzC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAA;AAE5C,0FAA0F;AAC1F,MAAM,CAAC,MAAM,SAAS,GAAG;;8BAEK,WAAW;;;CAGxC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;mBACV,WAAW,aAAa,CAAA;AAE3C,sDAAsD;AACtD,MAAM,UAAU,WAAW,CAAC,WAA0C,EAAE,KAAK,GAAG,cAAc;IAC5F,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAA;IAChE,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;IAC/C,MAAM,KAAK,GAAG,MAAM;SACjB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,uBAAuB,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CACtM;SACA,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,OAAO;;;;;aAKI,UAAU,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;YAelB,UAAU,CAAC,KAAK,CAAC;WAClB,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,iBAAiB,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE;;EAEjJ,KAAK;;aAEM,aAAa;;;CAGzB,CAAA;AACD,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { CreateLocalHost } from '../host/types.js';
2
+ import { type AssembleOptions } from './assemble.js';
3
+ import { type AssembleDiagnostic } from './diagnostics.js';
4
+ import type { DevServer, DevServerOptions, StartDevServer } from './types.js';
5
+ import { type WatchOptions } from './watcher.js';
6
+ export declare const DEFAULT_PORT = 4600;
7
+ export declare const DEFAULT_HOSTNAME = "127.0.0.1";
8
+ /** Where a host this server creates is mounted. */
9
+ export declare const HOST_PREFIX = "/_host";
10
+ /** Seams for the `dev` command and for tests; not part of the public contract. */
11
+ export interface DevServerInternals {
12
+ /** Watching; `false` turns it off. */
13
+ readonly watch?: WatchOptions | false;
14
+ /** Makes a host when the app needs one and none was passed. */
15
+ readonly createHost?: CreateLocalHost;
16
+ /** Assembly options (runtime bundles, cache folder). */
17
+ readonly assemble?: Pick<AssembleOptions, 'runtime' | 'cacheDir' | 'publishedAt'>;
18
+ }
19
+ export declare function contentTypeOf(path: string): string;
20
+ /** The address a browser should open for a bound interface. */
21
+ export declare function urlFor(hostname: string, port: number): string;
22
+ /** Lines for the terminal: everything that is the app's own, and a count of what pinned packs say. */
23
+ export declare function logLines(diagnostics: readonly AssembleDiagnostic[]): string[];
24
+ /** Starts the dev server with the internal seams. {@link startDevServer} is the public entry. */
25
+ export declare function createDevServer(options: DevServerOptions, internals?: DevServerInternals): Promise<DevServer>;
26
+ /** Implements the `pwf dev` contract (see ./types.ts). */
27
+ export declare const startDevServer: StartDevServer;
28
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev/server.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAiB,KAAK,eAAe,EAAuB,MAAM,eAAe,CAAA;AAExF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAI5E,OAAO,KAAK,EAAkB,SAAS,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC7F,OAAO,EAAe,KAAK,YAAY,EAAgB,MAAM,cAAc,CAAA;AAE3E,eAAO,MAAM,YAAY,OAAO,CAAA;AAChC,eAAO,MAAM,gBAAgB,cAAc,CAAA;AAC3C,mDAAmD;AACnD,eAAO,MAAM,WAAW,WAAW,CAAA;AAEnC,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAA;IACrC,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAA;IACrC,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC,CAAA;CAClF;AAqBD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAWD,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7D;AAWD,sGAAsG;AACtG,wBAAgB,QAAQ,CAAC,WAAW,EAAE,SAAS,kBAAkB,EAAE,GAAG,MAAM,EAAE,CAa7E;AAED,iGAAiG;AACjG,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,GAAE,kBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC,CA6LvH;AAED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,cAAsD,CAAA"}
@@ -0,0 +1,264 @@
1
+ /**
2
+ * `pwf dev`: serves an app folder as a pwapp and keeps it current.
3
+ *
4
+ * One `node:http` server on 127.0.0.1 (port 4600 by default). It assembles
5
+ * the app into memory (see ./assemble.ts), serves the files from there,
6
+ * watches the folder, reassembles on change and tells the open page to reload
7
+ * through Server-Sent Events. While the last build has errors the page is an
8
+ * overlay listing them; the terminal gets the same list.
9
+ *
10
+ * When the app declares sign-in or records it needs a local host. A host
11
+ * passed in `localHost` is mounted on the same server: requests it claims go
12
+ * to `host.handle()`, and the envelope carries `host.envelopeEntries()`.
13
+ * This module never constructs a host itself (it depends on the host's
14
+ * interface only); a caller that can make one passes `createHost`.
15
+ */
16
+ import { createServer } from 'node:http';
17
+ import { extname, join, resolve } from 'node:path';
18
+ import { assemblePwapp } from './assemble.js';
19
+ import { createCompileCache } from './compile.js';
20
+ import { formatDiagnostic } from './diagnostics.js';
21
+ import { hostNeeds } from './needs-host.js';
22
+ import { EVENTS_PATH, overlayHtml, RELOAD_CLIENT, RELOAD_JS, RELOAD_PATH, STATUS_PATH } from './overlay.js';
23
+ import { PWF_DIR } from './sources.js';
24
+ import { watchFolder } from './watcher.js';
25
+ export const DEFAULT_PORT = 4600;
26
+ export const DEFAULT_HOSTNAME = '127.0.0.1';
27
+ /** Where a host this server creates is mounted. */
28
+ export const HOST_PREFIX = '/_host';
29
+ const CONTENT_TYPES = {
30
+ '.html': 'text/html; charset=utf-8',
31
+ '.js': 'text/javascript; charset=utf-8',
32
+ '.mjs': 'text/javascript; charset=utf-8',
33
+ '.json': 'application/json; charset=utf-8',
34
+ '.map': 'application/json; charset=utf-8',
35
+ '.css': 'text/css; charset=utf-8',
36
+ '.svg': 'image/svg+xml',
37
+ '.png': 'image/png',
38
+ '.jpg': 'image/jpeg',
39
+ '.jpeg': 'image/jpeg',
40
+ '.gif': 'image/gif',
41
+ '.webp': 'image/webp',
42
+ '.avif': 'image/avif',
43
+ '.ico': 'image/x-icon',
44
+ '.woff2': 'font/woff2',
45
+ '.txt': 'text/plain; charset=utf-8',
46
+ };
47
+ export function contentTypeOf(path) {
48
+ return CONTENT_TYPES[extname(path).toLowerCase()] ?? 'application/octet-stream';
49
+ }
50
+ function isLocalHost(value) {
51
+ return (value !== null &&
52
+ typeof value === 'object' &&
53
+ typeof value.handle === 'function' &&
54
+ typeof value.envelopeEntries === 'function');
55
+ }
56
+ /** The address a browser should open for a bound interface. */
57
+ export function urlFor(hostname, port) {
58
+ const h = hostname === '0.0.0.0' || hostname === '::' || hostname === '' ? 'localhost' : hostname;
59
+ return `http://${h.includes(':') ? `[${h}]` : h}:${port}/`;
60
+ }
61
+ function send(res, status, type, body, head = false) {
62
+ res.writeHead(status, {
63
+ 'content-type': type,
64
+ 'cache-control': 'no-store',
65
+ 'content-length': typeof body === 'string' ? Buffer.byteLength(body) : body.byteLength,
66
+ });
67
+ res.end(head ? undefined : body);
68
+ }
69
+ /** Lines for the terminal: everything that is the app's own, and a count of what pinned packs say. */
70
+ export function logLines(diagnostics) {
71
+ const own = [];
72
+ let packWarnings = 0;
73
+ for (const d of diagnostics) {
74
+ if (d.severity !== 'error' && d.path?.startsWith(`${PWF_DIR}/`)) {
75
+ packWarnings++;
76
+ continue;
77
+ }
78
+ if (d.severity === 'info')
79
+ continue;
80
+ own.push(formatDiagnostic(d));
81
+ }
82
+ if (packWarnings > 0)
83
+ own.push(`${packWarnings} finding${packWarnings === 1 ? '' : 's'} in pinned packs (\`pwf build\` lists them)`);
84
+ return own;
85
+ }
86
+ /** Starts the dev server with the internal seams. {@link startDevServer} is the public entry. */
87
+ export async function createDevServer(options, internals = {}) {
88
+ const root = resolve(options.root);
89
+ const log = options.log ?? (() => undefined);
90
+ const hostname = options.hostname ?? DEFAULT_HOSTNAME;
91
+ const cache = createCompileCache(internals.assemble?.cacheDir === undefined ? join(root, PWF_DIR, 'build-cache') : internals.assemble.cacheDir);
92
+ // ── the local host ───────────────────────────────────────────────
93
+ let host = null;
94
+ if (isLocalHost(options.localHost)) {
95
+ host = options.localHost;
96
+ }
97
+ else if (options.localHost !== false) {
98
+ const needs = await hostNeeds(root);
99
+ const wanted = options.localHost === true || needs.length > 0;
100
+ if (wanted && internals.createHost) {
101
+ host = internals.createHost({ root, prefix: HOST_PREFIX });
102
+ }
103
+ else if (wanted) {
104
+ log(`note: this app declares ${needs.join(' and ') || 'a host'}, but no local host is mounted — pages render, ${needs.includes('sign-in') ? 'sign-in cannot complete and ' : ''}data stays empty`);
105
+ }
106
+ }
107
+ // ── state ────────────────────────────────────────────────────────
108
+ let state = null;
109
+ const packMemo = new Map();
110
+ const clients = new Set();
111
+ let origin = '';
112
+ const broadcast = (event, data) => {
113
+ const frame = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
114
+ for (const c of clients)
115
+ c.write(frame);
116
+ };
117
+ const build = async () => {
118
+ let result;
119
+ try {
120
+ result = await assemblePwapp(root, {
121
+ ...internals.assemble,
122
+ cache,
123
+ packMemo,
124
+ shellExtra: RELOAD_CLIENT,
125
+ ...(host ? { envelopeEntries: host.envelopeEntries(origin) } : {}),
126
+ });
127
+ }
128
+ catch (e) {
129
+ const diagnostics = [
130
+ { severity: 'error', code: 'INTERNAL', message: e instanceof Error ? (e.stack ?? e.message) : String(e) },
131
+ ];
132
+ result = {
133
+ ok: false,
134
+ files: state?.files ?? new Map(),
135
+ diagnostics,
136
+ stats: { pages: 0, blocks: 0, engines: 0, atoms: 0, compiled: 0, fromCache: 0, failed: 0 },
137
+ ms: 0,
138
+ };
139
+ }
140
+ state = result;
141
+ const { stats } = result;
142
+ log(`${result.ok ? 'built' : 'build failed'} in ${result.ms} ms — ${stats.pages} page${stats.pages === 1 ? '' : 's'}, ${stats.blocks} block type${stats.blocks === 1 ? '' : 's'}, ${stats.compiled}/${stats.atoms} atoms compiled (${stats.fromCache} from cache${stats.failed ? `, ${stats.failed} failed` : ''})`);
143
+ for (const line of logLines(result.diagnostics))
144
+ log(` ${line}`);
145
+ broadcast('reload', { ok: result.ok });
146
+ return { ok: result.ok, compiled: stats.compiled, diagnostics: result.diagnostics.map(formatDiagnostic), ms: result.ms };
147
+ };
148
+ let chain = Promise.resolve();
149
+ const rebuild = () => {
150
+ const next = chain.then(build, build);
151
+ chain = next.catch(() => undefined);
152
+ return next;
153
+ };
154
+ // ── HTTP ─────────────────────────────────────────────────────────
155
+ const handle = async (req, res) => {
156
+ const url = new URL(req.url ?? '/', 'http://localhost');
157
+ if (url.pathname === EVENTS_PATH) {
158
+ res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', connection: 'keep-alive' });
159
+ res.write(': connected\n\n');
160
+ clients.add(res);
161
+ req.on('close', () => clients.delete(res));
162
+ return;
163
+ }
164
+ if (url.pathname === RELOAD_PATH) {
165
+ send(res, 200, CONTENT_TYPES['.js'], RELOAD_JS, req.method === 'HEAD');
166
+ return;
167
+ }
168
+ if (url.pathname === STATUS_PATH) {
169
+ send(res, 200, CONTENT_TYPES['.json'], JSON.stringify({ ok: state?.ok ?? false, diagnostics: state?.diagnostics ?? [] }));
170
+ return;
171
+ }
172
+ if (host && (await host.handle(req, res)))
173
+ return;
174
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
175
+ send(res, 405, CONTENT_TYPES['.txt'], 'method not allowed');
176
+ return;
177
+ }
178
+ const head = req.method === 'HEAD';
179
+ let path;
180
+ try {
181
+ path = decodeURIComponent(url.pathname);
182
+ }
183
+ catch {
184
+ send(res, 400, CONTENT_TYPES['.txt'], 'bad path', head);
185
+ return;
186
+ }
187
+ let rel = path.replace(/^\/+/, '');
188
+ if (rel === '' || rel.endsWith('/'))
189
+ rel += 'index.html';
190
+ if (rel.split('/').some((s) => s === '..' || s === '.') || rel.includes('\\') || rel.includes('\0')) {
191
+ send(res, 400, CONTENT_TYPES['.txt'], 'bad path', head);
192
+ return;
193
+ }
194
+ const current = state;
195
+ const isPage = rel === 'index.html' || extname(rel) === '';
196
+ if (isPage && current && !current.ok) {
197
+ send(res, 200, CONTENT_TYPES['.html'], overlayHtml(current.diagnostics), head);
198
+ return;
199
+ }
200
+ // `_headers` configures a static host; it is not part of the site.
201
+ const file = rel === '_headers'
202
+ ? undefined
203
+ : (current?.files.get(rel) ?? (extname(rel) === '' ? current?.files.get('index.html') : undefined));
204
+ if (!file) {
205
+ send(res, 404, CONTENT_TYPES['.txt'], `not found: /${rel}`, head);
206
+ return;
207
+ }
208
+ send(res, 200, contentTypeOf(isPage ? 'index.html' : rel), file, head);
209
+ };
210
+ const server = createServer((req, res) => {
211
+ handle(req, res).catch((e) => {
212
+ if (!res.headersSent)
213
+ send(res, 500, CONTENT_TYPES['.txt'], e instanceof Error ? e.message : String(e));
214
+ else
215
+ res.end();
216
+ });
217
+ });
218
+ await new Promise((ok, fail) => {
219
+ server.once('error', fail);
220
+ server.listen(options.port ?? DEFAULT_PORT, hostname, () => {
221
+ server.off('error', fail);
222
+ ok();
223
+ });
224
+ });
225
+ const port = server.address().port;
226
+ const url = urlFor(hostname, port);
227
+ origin = url.replace(/\/$/, '');
228
+ if (host) {
229
+ log(`local host mounted at ${origin}${HOST_PREFIX} — development only: records in memory, one user, any password`);
230
+ }
231
+ await rebuild();
232
+ let watcher = null;
233
+ if (internals.watch !== false) {
234
+ watcher = watchFolder(root, (paths) => {
235
+ // A pack build changed (or the platform could not say what did): read it again.
236
+ if (paths === null || paths.some((p) => p.startsWith(`${PWF_DIR}/packs`)))
237
+ packMemo.clear();
238
+ void rebuild();
239
+ }, internals.watch ?? {});
240
+ log(`watching ${root} (${watcher.mode === 'native' ? 'file events' : 'polling'})`);
241
+ }
242
+ let closed = null;
243
+ return {
244
+ url,
245
+ rebuild,
246
+ close() {
247
+ closed ??= (async () => {
248
+ watcher?.close();
249
+ for (const c of clients)
250
+ c.end();
251
+ clients.clear();
252
+ await new Promise((ok) => {
253
+ server.close(() => ok());
254
+ server.closeAllConnections();
255
+ });
256
+ await chain;
257
+ })();
258
+ return closed;
259
+ },
260
+ };
261
+ }
262
+ /** Implements the `pwf dev` contract (see ./types.ts). */
263
+ export const startDevServer = (options) => createDevServer(options);
264
+ //# sourceMappingURL=server.js.map