@blinkk/root 3.0.6 → 3.1.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 (39) hide show
  1. package/dist/build-worker.js +108 -0
  2. package/dist/build-worker.js.map +7 -0
  3. package/dist/chunk-3PWR4F2R.js +77 -0
  4. package/dist/chunk-3PWR4F2R.js.map +7 -0
  5. package/dist/{chunk-3EFECH2D.js → chunk-E4F6CMO3.js} +439 -553
  6. package/dist/chunk-E4F6CMO3.js.map +7 -0
  7. package/dist/{chunk-7HK6F5RQ.js → chunk-I3RCAIW7.js} +1 -75
  8. package/dist/chunk-I3RCAIW7.js.map +7 -0
  9. package/dist/chunk-KHO3HMX6.js +291 -0
  10. package/dist/chunk-KHO3HMX6.js.map +7 -0
  11. package/dist/chunk-M3E4RKS7.js +374 -0
  12. package/dist/chunk-M3E4RKS7.js.map +7 -0
  13. package/dist/{chunk-6NBKAR5Y.js → chunk-TM6QRBGS.js} +31 -1
  14. package/dist/chunk-TM6QRBGS.js.map +7 -0
  15. package/dist/chunk-WX62JK5E.js +81 -0
  16. package/dist/chunk-WX62JK5E.js.map +7 -0
  17. package/dist/{chunk-7PSEEE6C.js → chunk-YU22SAIG.js} +31 -317
  18. package/dist/chunk-YU22SAIG.js.map +7 -0
  19. package/dist/cli/build-page.d.ts +57 -0
  20. package/dist/cli/build-worker-pool.d.ts +46 -0
  21. package/dist/cli/build-worker.d.ts +4 -0
  22. package/dist/cli/build.d.ts +6 -0
  23. package/dist/cli.js +8 -4
  24. package/dist/core/security.d.ts +14 -0
  25. package/dist/functions.js +8 -4
  26. package/dist/functions.js.map +1 -1
  27. package/dist/middleware/common.d.ts +18 -0
  28. package/dist/middleware/compression.d.ts +12 -0
  29. package/dist/middleware/middleware.d.ts +1 -0
  30. package/dist/middleware.js +6 -1
  31. package/dist/node.js +7 -5
  32. package/dist/render/render.d.ts +15 -9
  33. package/dist/render.js +42 -95
  34. package/dist/render.js.map +2 -2
  35. package/package.json +2 -2
  36. package/dist/chunk-3EFECH2D.js.map +0 -7
  37. package/dist/chunk-6NBKAR5Y.js.map +0 -7
  38. package/dist/chunk-7HK6F5RQ.js.map +0 -7
  39. package/dist/chunk-7PSEEE6C.js.map +0 -7
@@ -0,0 +1,374 @@
1
+ import {
2
+ directoryContains,
3
+ isDirectory,
4
+ isJsFile,
5
+ makeDir,
6
+ writeFile
7
+ } from "./chunk-KHO3HMX6.js";
8
+ import {
9
+ isValidTagName,
10
+ parseTagNames,
11
+ transformHtml
12
+ } from "./chunk-3PWR4F2R.js";
13
+
14
+ // src/node/element-graph.ts
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import glob from "tiny-glob";
18
+ import { searchForWorkspaceRoot } from "vite";
19
+ var ElementGraph = class _ElementGraph {
20
+ /**
21
+ * Element tagName => sourceFile.
22
+ */
23
+ sourceFiles = {};
24
+ /**
25
+ * Element tagName => set of element tagName dependencies.
26
+ */
27
+ deps = {};
28
+ constructor(sourceFiles) {
29
+ this.sourceFiles = sourceFiles;
30
+ }
31
+ toJson() {
32
+ for (const tagName in this.sourceFiles) {
33
+ this.deps[tagName] ??= this.parseDepsFromSource(tagName);
34
+ }
35
+ return {
36
+ sourceFiles: this.sourceFiles,
37
+ deps: this.deps
38
+ };
39
+ }
40
+ static fromJson(data) {
41
+ const graph = new _ElementGraph(data.sourceFiles);
42
+ graph.deps = data.deps;
43
+ return graph;
44
+ }
45
+ getDeps(tagName, visited) {
46
+ visited ??= /* @__PURE__ */ new Set();
47
+ if (visited.has(tagName)) {
48
+ return [];
49
+ }
50
+ visited.add(tagName);
51
+ this.deps[tagName] ??= this.parseDepsFromSource(tagName);
52
+ const deps = new Set(this.deps[tagName]);
53
+ for (const depTagName of this.deps[tagName]) {
54
+ for (const childDep of this.getDeps(depTagName, visited)) {
55
+ deps.add(childDep);
56
+ }
57
+ }
58
+ return Array.from(deps);
59
+ }
60
+ /**
61
+ * Parses an element's source file for usage of other custom elements.
62
+ */
63
+ parseDepsFromSource(tagName) {
64
+ const srcFile = this.sourceFiles[tagName];
65
+ if (!srcFile) {
66
+ throw new Error(`could not find file path for tagName <${tagName}>`);
67
+ }
68
+ const src = fs.readFileSync(srcFile.filePath, "utf-8");
69
+ const tagNames = parseTagNames(src);
70
+ const deps = /* @__PURE__ */ new Set();
71
+ for (const depTagName of tagNames) {
72
+ if (depTagName !== tagName && depTagName in this.sourceFiles) {
73
+ deps.add(depTagName);
74
+ }
75
+ }
76
+ return Array.from(deps);
77
+ }
78
+ };
79
+ async function getElements(rootConfig, pods) {
80
+ const rootDir = rootConfig.rootDir;
81
+ const elementsDirs = getElementsDirs(rootConfig, pods);
82
+ const excludePatterns = rootConfig.elements?.exclude || [];
83
+ const excludeElement = (moduleId) => {
84
+ return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));
85
+ };
86
+ const elementFilePaths = {};
87
+ for (const dirPath of elementsDirs) {
88
+ if (await isDirectory(dirPath)) {
89
+ const files = await glob("**/*", { cwd: dirPath });
90
+ files.forEach((file) => {
91
+ const parts = path.parse(file);
92
+ if (isJsFile(parts.base) && isValidTagName(parts.name)) {
93
+ const tagName = parts.name;
94
+ const filePath = path.join(dirPath, file);
95
+ const relPath = path.relative(rootDir, filePath);
96
+ if (!excludeElement(relPath)) {
97
+ elementFilePaths[tagName] = { filePath, relPath };
98
+ }
99
+ }
100
+ });
101
+ }
102
+ }
103
+ const graph = new ElementGraph(elementFilePaths);
104
+ return graph;
105
+ }
106
+ function getElementsDirs(rootConfig, pods) {
107
+ const rootDir = rootConfig.rootDir;
108
+ const workspaceRoot = searchForWorkspaceRoot(rootDir);
109
+ const elementsDirs = [path.join(rootDir, "elements")];
110
+ const elementsInclude = rootConfig.elements?.include || [];
111
+ for (const dirPath of elementsInclude) {
112
+ const elementsDir = path.resolve(rootDir, dirPath);
113
+ if (!directoryContains(rootDir, elementsDir)) {
114
+ throw new Error(
115
+ `the elements dir (${dirPath}) should be within the project's workspace (${workspaceRoot})`
116
+ );
117
+ }
118
+ elementsDirs.push(elementsDir);
119
+ }
120
+ if (pods) {
121
+ for (const pod of pods) {
122
+ for (const dir of pod.elementsDirs) {
123
+ elementsDirs.push(dir);
124
+ }
125
+ }
126
+ }
127
+ return elementsDirs;
128
+ }
129
+
130
+ // src/render/asset-map/build-asset-map.ts
131
+ import fs2 from "node:fs";
132
+ import path2 from "node:path";
133
+ var BuildAssetMap = class _BuildAssetMap {
134
+ rootConfig;
135
+ srcToAsset;
136
+ constructor(rootConfig) {
137
+ this.rootConfig = rootConfig;
138
+ this.srcToAsset = /* @__PURE__ */ new Map();
139
+ }
140
+ async get(src) {
141
+ const asset = this.srcToAsset.get(src);
142
+ if (asset) {
143
+ return asset;
144
+ }
145
+ const realSrc = realPathRelativeTo(this.rootConfig.rootDir, src);
146
+ if (realSrc !== src) {
147
+ const asset2 = this.srcToAsset.get(realSrc);
148
+ if (asset2) {
149
+ return asset2;
150
+ }
151
+ }
152
+ console.log(`could not find build asset: ${src}`);
153
+ return null;
154
+ }
155
+ add(asset) {
156
+ this.srcToAsset.set(asset.src, asset);
157
+ }
158
+ toJson() {
159
+ const result = {};
160
+ for (const src of this.srcToAsset.keys()) {
161
+ result[src] = this.srcToAsset.get(src).toJson();
162
+ }
163
+ return result;
164
+ }
165
+ static fromViteManifest(rootConfig, clientManifest, elementsGraph, podRoutes = []) {
166
+ const assetMap = new _BuildAssetMap(rootConfig);
167
+ const elementFiles = /* @__PURE__ */ new Set();
168
+ Object.values(elementsGraph.sourceFiles).forEach((elementSource) => {
169
+ elementFiles.add(elementSource.relPath);
170
+ const realSrc = realPathRelativeTo(
171
+ rootConfig.rootDir,
172
+ elementSource.relPath
173
+ );
174
+ if (realSrc !== elementSource.filePath) {
175
+ elementFiles.add(realSrc);
176
+ }
177
+ });
178
+ Object.keys(clientManifest).forEach((manifestKey) => {
179
+ const src = manifestKey;
180
+ const manifestChunk = clientManifest[manifestKey];
181
+ const isElement = elementFiles.has(src);
182
+ const assetUrl = src.startsWith("routes/") && isJsFile(src) ? "" : `/${manifestChunk.file}`;
183
+ const assetData = {
184
+ src,
185
+ assetUrl,
186
+ importedModules: manifestChunk.imports || [],
187
+ importedCss: (manifestChunk.css || []).map((relPath) => `/${relPath}`),
188
+ isElement
189
+ };
190
+ assetMap.add(new BuildAsset(assetMap, assetData));
191
+ });
192
+ podRoutes.forEach((podRoute) => {
193
+ if (assetMap.srcToAsset.has(podRoute.src)) {
194
+ return;
195
+ }
196
+ const relPath = path2.relative(rootConfig.rootDir, podRoute.filePath).replace(/\\/g, "/");
197
+ const realPath = realPathRelativeTo(rootConfig.rootDir, relPath).replace(
198
+ /\\/g,
199
+ "/"
200
+ );
201
+ const candidateKeys = [relPath, realPath];
202
+ for (const key of candidateKeys) {
203
+ const asset = assetMap.srcToAsset.get(key);
204
+ if (asset) {
205
+ assetMap.add(
206
+ new BuildAsset(assetMap, {
207
+ ...asset.toJson(),
208
+ src: podRoute.src,
209
+ assetUrl: ""
210
+ })
211
+ );
212
+ break;
213
+ }
214
+ }
215
+ });
216
+ return assetMap;
217
+ }
218
+ static fromRootManifest(rootConfig, rootManifest) {
219
+ const assetMap = new _BuildAssetMap(rootConfig);
220
+ Object.keys(rootManifest).forEach((moduleId) => {
221
+ const assetData = rootManifest[moduleId];
222
+ assetMap.add(new BuildAsset(assetMap, assetData));
223
+ });
224
+ return assetMap;
225
+ }
226
+ };
227
+ function realPathRelativeTo(rootDir, src) {
228
+ const fullPath = path2.resolve(rootDir, src);
229
+ if (!fs2.existsSync(fullPath)) {
230
+ return src;
231
+ }
232
+ const realpath = fs2.realpathSync(path2.resolve(rootDir, src));
233
+ return path2.relative(rootDir, realpath);
234
+ }
235
+ var BuildAsset = class {
236
+ src;
237
+ assetUrl;
238
+ assetMap;
239
+ importedModules;
240
+ importedCss;
241
+ isElement;
242
+ constructor(assetMap, assetData) {
243
+ this.assetMap = assetMap;
244
+ this.src = assetData.src;
245
+ this.assetUrl = assetData.assetUrl;
246
+ this.importedModules = assetData.importedModules;
247
+ this.importedCss = assetData.importedCss;
248
+ this.isElement = assetData.isElement;
249
+ }
250
+ async getCssDeps() {
251
+ const visited = /* @__PURE__ */ new Set();
252
+ const deps = /* @__PURE__ */ new Set();
253
+ await this.collectCss(this, deps, visited);
254
+ return Array.from(deps);
255
+ }
256
+ async getJsDeps() {
257
+ const visited = /* @__PURE__ */ new Set();
258
+ const deps = /* @__PURE__ */ new Set();
259
+ await this.collectJs(this, deps, visited);
260
+ return Array.from(deps);
261
+ }
262
+ async collectJs(asset, urls, visited) {
263
+ if (!asset) {
264
+ return;
265
+ }
266
+ if (!asset.src) {
267
+ return;
268
+ }
269
+ if (visited.has(asset.src)) {
270
+ return;
271
+ }
272
+ visited.add(asset.src);
273
+ if (asset.isElement) {
274
+ urls.add(asset.assetUrl);
275
+ }
276
+ await Promise.all(
277
+ asset.importedModules.map(async (src) => {
278
+ const importedAsset = await this.assetMap.get(src);
279
+ this.collectJs(importedAsset, urls, visited);
280
+ })
281
+ );
282
+ }
283
+ async collectCss(asset, urls, visited) {
284
+ if (!asset) {
285
+ return;
286
+ }
287
+ if (!asset.src) {
288
+ return;
289
+ }
290
+ if (visited.has(asset.src)) {
291
+ return;
292
+ }
293
+ visited.add(asset.src);
294
+ if (asset.importedCss) {
295
+ asset.importedCss.forEach((cssUrl) => urls.add(cssUrl));
296
+ }
297
+ await Promise.all(
298
+ asset.importedModules.map(async (moduleId) => {
299
+ const importedAsset = await this.assetMap.get(moduleId);
300
+ this.collectCss(importedAsset, urls, visited);
301
+ })
302
+ );
303
+ }
304
+ toJson() {
305
+ return {
306
+ src: this.src,
307
+ assetUrl: this.assetUrl,
308
+ importedModules: this.importedModules,
309
+ importedCss: this.importedCss,
310
+ isElement: this.isElement
311
+ };
312
+ }
313
+ };
314
+
315
+ // src/cli/build-page.ts
316
+ import path3 from "node:path";
317
+ async function buildPage(renderer, rootConfig, buildDir, route, task) {
318
+ const { urlPath, params } = task;
319
+ const routeModule = route.module;
320
+ if (routeModule.getStaticContent) {
321
+ let props;
322
+ if (routeModule.getStaticProps) {
323
+ props = await routeModule.getStaticProps({
324
+ rootConfig,
325
+ params
326
+ });
327
+ if (props?.notFound) {
328
+ return { urlPath, notFound: true };
329
+ }
330
+ } else {
331
+ props = { rootConfig, params };
332
+ }
333
+ const result = await routeModule.getStaticContent(props);
334
+ let body;
335
+ if (typeof result === "string") {
336
+ body = result;
337
+ } else if (result && typeof result === "object") {
338
+ body = result.body;
339
+ } else {
340
+ body = "";
341
+ }
342
+ const outFilePath2 = urlPath.slice(1);
343
+ const outPath2 = path3.join(buildDir, outFilePath2);
344
+ await makeDir(path3.dirname(outPath2));
345
+ await writeFile(outPath2, normalizeLineEndings(body));
346
+ return { urlPath, outFilePath: outFilePath2, staticContent: true };
347
+ }
348
+ const data = await renderer.renderRoute(route, {
349
+ routeParams: params
350
+ });
351
+ if (data.notFound) {
352
+ return { urlPath, notFound: true };
353
+ }
354
+ let outFilePath = path3.join(urlPath.slice(1), "index.html");
355
+ if (outFilePath.endsWith("404/index.html")) {
356
+ outFilePath = outFilePath.replace("404/index.html", "404.html");
357
+ }
358
+ const outPath = path3.join(buildDir, outFilePath);
359
+ const html = await transformHtml(data.html || "", rootConfig);
360
+ await writeFile(outPath, normalizeLineEndings(html));
361
+ return { urlPath, outFilePath };
362
+ }
363
+ function normalizeLineEndings(str) {
364
+ return str.replace(/\r\n?/g, "\n");
365
+ }
366
+
367
+ export {
368
+ ElementGraph,
369
+ getElements,
370
+ getElementsDirs,
371
+ BuildAssetMap,
372
+ buildPage
373
+ };
374
+ //# sourceMappingURL=chunk-M3E4RKS7.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/node/element-graph.ts", "../src/render/asset-map/build-asset-map.ts", "../src/cli/build-page.ts"],
4
+ "sourcesContent": ["import fs from 'node:fs';\nimport path from 'node:path';\nimport glob from 'tiny-glob';\nimport {searchForWorkspaceRoot} from 'vite';\nimport {RootConfig} from '../core/config.js';\nimport {isValidTagName, parseTagNames} from '../utils/elements.js';\nimport {directoryContains, isDirectory, isJsFile} from '../utils/fsutils.js';\nimport type {ResolvedPod} from './pod-collector.js';\n\ninterface ElementSourceFile {\n /** Full file path. */\n filePath: string;\n /** Path relative to the root project directory. */\n relPath: string;\n}\n\n/**\n * Dependency graph for element files to capture which other elements are used\n * by any particular element.\n */\nexport class ElementGraph {\n /**\n * Element tagName => sourceFile.\n */\n readonly sourceFiles: {[tagName: string]: ElementSourceFile} = {};\n\n /**\n * Element tagName => set of element tagName dependencies.\n */\n private deps: Record<string, string[]> = {};\n\n constructor(sourceFiles: {[tagName: string]: ElementSourceFile}) {\n this.sourceFiles = sourceFiles;\n }\n\n toJson() {\n for (const tagName in this.sourceFiles) {\n this.deps[tagName] ??= this.parseDepsFromSource(tagName);\n }\n return {\n sourceFiles: this.sourceFiles,\n deps: this.deps,\n };\n }\n\n static fromJson(data: {deps: any; sourceFiles: any}) {\n const graph = new ElementGraph(data.sourceFiles);\n graph.deps = data.deps;\n return graph;\n }\n\n getDeps(tagName: string, visited?: Set<string>): string[] {\n visited ??= new Set();\n if (visited.has(tagName)) {\n return [];\n }\n\n visited.add(tagName);\n this.deps[tagName] ??= this.parseDepsFromSource(tagName);\n\n const deps = new Set(this.deps[tagName]);\n for (const depTagName of this.deps[tagName]) {\n for (const childDep of this.getDeps(depTagName, visited)) {\n deps.add(childDep);\n }\n }\n return Array.from(deps);\n }\n\n /**\n * Parses an element's source file for usage of other custom elements.\n */\n private parseDepsFromSource(tagName: string): string[] {\n const srcFile = this.sourceFiles[tagName];\n if (!srcFile) {\n throw new Error(`could not find file path for tagName <${tagName}>`);\n }\n const src = fs.readFileSync(srcFile.filePath, 'utf-8');\n const tagNames = parseTagNames(src);\n const deps = new Set<string>();\n for (const depTagName of tagNames) {\n if (depTagName !== tagName && depTagName in this.sourceFiles) {\n deps.add(depTagName);\n }\n }\n return Array.from(deps);\n }\n}\n\n/**\n * Returns a map of all the element file definitions in the project.\n */\nexport async function getElements(\n rootConfig: RootConfig,\n pods?: ResolvedPod[]\n): Promise<ElementGraph> {\n const rootDir = rootConfig.rootDir;\n\n const elementsDirs = getElementsDirs(rootConfig, pods);\n const excludePatterns = rootConfig.elements?.exclude || [];\n const excludeElement = (moduleId: string) => {\n return excludePatterns.some((pattern) => Boolean(moduleId.match(pattern)));\n };\n\n const elementFilePaths: {[tagName: string]: ElementSourceFile} = {};\n for (const dirPath of elementsDirs) {\n if (await isDirectory(dirPath)) {\n const files = await glob('**/*', {cwd: dirPath});\n files.forEach((file) => {\n const parts = path.parse(file);\n if (isJsFile(parts.base) && isValidTagName(parts.name)) {\n const tagName = parts.name;\n const filePath = path.join(dirPath, file);\n const relPath = path.relative(rootDir, filePath);\n if (!excludeElement(relPath)) {\n elementFilePaths[tagName] = {filePath, relPath};\n }\n }\n });\n }\n }\n\n const graph = new ElementGraph(elementFilePaths);\n return graph;\n}\n\n/**\n * Get all element dirs.\n */\nexport function getElementsDirs(rootConfig: RootConfig, pods?: ResolvedPod[]) {\n const rootDir = rootConfig.rootDir;\n const workspaceRoot = searchForWorkspaceRoot(rootDir);\n\n const elementsDirs = [path.join(rootDir, 'elements')];\n const elementsInclude = rootConfig.elements?.include || [];\n\n for (const dirPath of elementsInclude) {\n const elementsDir = path.resolve(rootDir, dirPath);\n if (!directoryContains(rootDir, elementsDir)) {\n throw new Error(\n `the elements dir (${dirPath}) should be within the project's workspace (${workspaceRoot})`\n );\n }\n elementsDirs.push(elementsDir);\n }\n\n // Pod-contributed element dirs bypass the workspace containment check\n // because they are trusted (registered by plugins, not user input).\n if (pods) {\n for (const pod of pods) {\n for (const dir of pod.elementsDirs) {\n elementsDirs.push(dir);\n }\n }\n }\n\n return elementsDirs;\n}\n", "import fs from 'node:fs';\nimport path from 'node:path';\nimport {Manifest} from 'vite';\nimport {RootConfig} from '../../core/config.js';\nimport {ElementGraph} from '../../node/element-graph.js';\nimport {isJsFile} from '../../utils/fsutils.js';\nimport {Asset, AssetMap} from './asset-map.js';\n\nexport type BuildAssetManifest = Record<\n string,\n {\n src: string;\n assetUrl: string;\n importedModules: string[];\n importedCss: string[];\n isElement: boolean;\n }\n>;\n\n/**\n * A pod route, used to alias the route's virtual `pod/<name>/...` src to the\n * real asset built from the route's file. See `fromViteManifest()`.\n */\nexport interface PodRouteAsset {\n /** Virtual src, e.g. `pod/<name>/<relPath>`. */\n src: string;\n /** Absolute path to the pod route file. */\n filePath: string;\n}\n\nexport class BuildAssetMap implements AssetMap {\n private rootConfig: RootConfig;\n private srcToAsset: Map<string, BuildAsset>;\n\n constructor(rootConfig: RootConfig) {\n this.rootConfig = rootConfig;\n this.srcToAsset = new Map();\n }\n\n async get(src: string): Promise<Asset | null> {\n const asset = this.srcToAsset.get(src);\n if (asset) {\n return asset;\n }\n // Try resolving the realpath of the asset, following symlinks.\n const realSrc = realPathRelativeTo(this.rootConfig.rootDir, src);\n if (realSrc !== src) {\n const asset = this.srcToAsset.get(realSrc);\n if (asset) {\n return asset;\n }\n }\n console.log(`could not find build asset: ${src}`);\n return null;\n }\n\n private add(asset: BuildAsset) {\n this.srcToAsset.set(asset.src, asset);\n }\n\n toJson(): BuildAssetManifest {\n const result: BuildAssetManifest = {};\n for (const src of this.srcToAsset.keys()) {\n result[src] = this.srcToAsset.get(src)!.toJson();\n }\n return result;\n }\n\n static fromViteManifest(\n rootConfig: RootConfig,\n clientManifest: Manifest,\n elementsGraph: ElementGraph,\n podRoutes: PodRouteAsset[] = []\n ) {\n const assetMap = new BuildAssetMap(rootConfig);\n\n const elementFiles = new Set();\n Object.values(elementsGraph.sourceFiles).forEach((elementSource) => {\n elementFiles.add(elementSource.relPath);\n // Vite will resolve symlinks, so we need to follow the src and add the\n // realpath to the element files.\n const realSrc = realPathRelativeTo(\n rootConfig.rootDir,\n elementSource.relPath\n );\n if (realSrc !== elementSource.filePath) {\n elementFiles.add(realSrc);\n }\n });\n\n Object.keys(clientManifest).forEach((manifestKey) => {\n const src = manifestKey;\n const manifestChunk = clientManifest[manifestKey];\n const isElement = elementFiles.has(src);\n // NOTES(stevenle): routes/ files are included in the manifest for\n // their CSS deps, but do not have an asset URL.\n const assetUrl =\n src.startsWith('routes/') && isJsFile(src)\n ? ''\n : `/${manifestChunk.file}`;\n const assetData = {\n src: src,\n assetUrl: assetUrl,\n importedModules: manifestChunk.imports || [],\n importedCss: (manifestChunk.css || []).map((relPath) => `/${relPath}`),\n isElement: isElement,\n };\n assetMap.add(new BuildAsset(assetMap, assetData));\n });\n\n // Pod routes are looked up by their virtual `pod/<name>/...` src at render\n // time, but the manifest keys them by their real file path. Register an\n // alias for each pod route so `get(route.src)` resolves to the built asset\n // (and the route's CSS deps are collected) instead of logging\n // \"could not find build asset\". Like user `routes/`, the alias has no asset\n // URL of its own (the route's client JS is injected via elements/bundles).\n podRoutes.forEach((podRoute) => {\n if (assetMap.srcToAsset.has(podRoute.src)) {\n return;\n }\n const relPath = path\n .relative(rootConfig.rootDir, podRoute.filePath)\n .replace(/\\\\/g, '/');\n // `realPathRelativeTo()` returns a `path.relative()` result with\n // OS-specific separators; normalize to forward slashes to match the\n // manifest keys (and `relPath` above) on Windows/symlinked setups.\n const realPath = realPathRelativeTo(rootConfig.rootDir, relPath).replace(\n /\\\\/g,\n '/'\n );\n const candidateKeys = [relPath, realPath];\n for (const key of candidateKeys) {\n const asset = assetMap.srcToAsset.get(key);\n if (asset) {\n assetMap.add(\n new BuildAsset(assetMap, {\n ...asset.toJson(),\n src: podRoute.src,\n assetUrl: '',\n })\n );\n break;\n }\n }\n });\n\n return assetMap;\n }\n\n static fromRootManifest(\n rootConfig: RootConfig,\n rootManifest: BuildAssetManifest\n ) {\n const assetMap = new BuildAssetMap(rootConfig);\n Object.keys(rootManifest).forEach((moduleId) => {\n const assetData = rootManifest[moduleId];\n assetMap.add(new BuildAsset(assetMap, assetData));\n });\n return assetMap;\n }\n}\n\n/**\n * Returns the realpath of a src file, relative to the rootDir.\n */\nfunction realPathRelativeTo(rootDir: string, src: string) {\n const fullPath = path.resolve(rootDir, src);\n if (!fs.existsSync(fullPath)) {\n return src;\n }\n const realpath = fs.realpathSync(path.resolve(rootDir, src));\n return path.relative(rootDir, realpath);\n}\n\nexport class BuildAsset {\n src: string;\n assetUrl: string;\n private assetMap: BuildAssetMap;\n private importedModules: string[];\n private importedCss: string[];\n isElement: boolean;\n\n constructor(\n assetMap: BuildAssetMap,\n assetData: {\n src: string;\n assetUrl: string;\n importedModules: string[];\n importedCss: string[];\n isElement: boolean;\n }\n ) {\n this.assetMap = assetMap;\n this.src = assetData.src;\n this.assetUrl = assetData.assetUrl;\n this.importedModules = assetData.importedModules;\n this.importedCss = assetData.importedCss;\n this.isElement = assetData.isElement;\n }\n\n async getCssDeps(): Promise<string[]> {\n const visited = new Set<string>();\n const deps = new Set<string>();\n await this.collectCss(this, deps, visited);\n return Array.from(deps);\n }\n\n async getJsDeps(): Promise<string[]> {\n const visited = new Set<string>();\n const deps = new Set<string>();\n await this.collectJs(this, deps, visited);\n return Array.from(deps);\n }\n\n private async collectJs(\n asset: BuildAsset | null,\n urls: Set<string>,\n visited: Set<string>\n ) {\n if (!asset) {\n return;\n }\n if (!asset.src) {\n return;\n }\n if (visited.has(asset.src)) {\n return;\n }\n visited.add(asset.src);\n if (asset.isElement) {\n urls.add(asset.assetUrl);\n }\n await Promise.all(\n asset.importedModules.map(async (src) => {\n const importedAsset = (await this.assetMap.get(src)) as BuildAsset;\n this.collectJs(importedAsset, urls, visited);\n })\n );\n }\n\n private async collectCss(\n asset: BuildAsset | null,\n urls: Set<string>,\n visited: Set<string>\n ) {\n if (!asset) {\n return;\n }\n if (!asset.src) {\n return;\n }\n if (visited.has(asset.src)) {\n return;\n }\n visited.add(asset.src);\n if (asset.importedCss) {\n asset.importedCss.forEach((cssUrl) => urls.add(cssUrl));\n }\n await Promise.all(\n asset.importedModules.map(async (moduleId) => {\n const importedAsset = (await this.assetMap.get(moduleId)) as BuildAsset;\n this.collectCss(importedAsset, urls, visited);\n })\n );\n }\n\n toJson() {\n return {\n src: this.src,\n assetUrl: this.assetUrl,\n importedModules: this.importedModules,\n importedCss: this.importedCss,\n isElement: this.isElement,\n };\n }\n}\n", "import path from 'node:path';\n\nimport {RootConfig} from '../core/config.js';\nimport {Route} from '../core/types.js';\nimport {transformHtml} from '../render/html-transform.js';\nimport {makeDir, writeFile} from '../utils/fsutils.js';\n\ntype Renderer = import('../render/render.js').Renderer;\n\n/** A single page to build during the SSG phase of `root build`. */\nexport interface BuildPageTask {\n /** The url path to build, e.g. `/intl/de/about/`. */\n urlPath: string;\n /** Route params used to build the page. */\n params: Record<string, string>;\n /**\n * The route's src file (e.g. `routes/blog/[slug].tsx`). Used by worker\n * threads to disambiguate overlapping routes that match the same url path.\n */\n routeSrc?: string;\n /** The route's locale, used with `routeSrc` to disambiguate routes. */\n locale?: string;\n}\n\n/** The result of building a single page. */\nexport interface BuildPageResult {\n urlPath: string;\n /** Output file path relative to the build dir. Unset when `notFound`. */\n outFilePath?: string;\n notFound?: boolean;\n /** Whether the output was generated via `getStaticContent()`. */\n staticContent?: boolean;\n}\n\n/** Message sent from the build worker to the main thread. */\nexport type BuildWorkerResponse =\n | {type: 'ready'}\n | {type: 'result'; id: number; result: BuildPageResult}\n | {\n type: 'error';\n id: number;\n urlPath: string;\n params: Record<string, string>;\n routeSrc?: string;\n error: string;\n }\n | {type: 'fatal'; error: string};\n\n/** Message sent from the main thread to the build worker. */\nexport type BuildWorkerRequest = {\n type: 'render';\n id: number;\n task: BuildPageTask;\n};\n\n/**\n * Builds a single page (or static content file) to the build dir. Shared by\n * the in-process SSG build loop and the `--threads` build worker.\n */\nexport async function buildPage(\n renderer: Renderer,\n rootConfig: RootConfig,\n buildDir: string,\n route: Route,\n task: BuildPageTask\n): Promise<BuildPageResult> {\n const {urlPath, params} = task;\n const routeModule = route.module;\n if (routeModule.getStaticContent) {\n let props: any;\n if (routeModule.getStaticProps) {\n props = await routeModule.getStaticProps({\n rootConfig,\n params,\n });\n if (props?.notFound) {\n return {urlPath, notFound: true};\n }\n } else {\n props = {rootConfig, params};\n }\n const result = await routeModule.getStaticContent(props);\n let body: string;\n if (typeof result === 'string') {\n body = result;\n } else if (result && typeof result === 'object') {\n body = result.body;\n } else {\n body = '';\n }\n const outFilePath = urlPath.slice(1);\n const outPath = path.join(buildDir, outFilePath);\n await makeDir(path.dirname(outPath));\n await writeFile(outPath, normalizeLineEndings(body));\n return {urlPath, outFilePath, staticContent: true};\n }\n\n const data = await renderer.renderRoute(route, {\n routeParams: params,\n });\n if (data.notFound) {\n return {urlPath, notFound: true};\n }\n\n let outFilePath = path.join(urlPath.slice(1), 'index.html');\n if (outFilePath.endsWith('404/index.html')) {\n outFilePath = outFilePath.replace('404/index.html', '404.html');\n }\n const outPath = path.join(buildDir, outFilePath);\n\n const html = await transformHtml(data.html || '', rootConfig);\n await writeFile(outPath, normalizeLineEndings(html));\n return {urlPath, outFilePath};\n}\n\nexport function normalizeLineEndings(str: string): string {\n return str.replace(/\\r\\n?/g, '\\n');\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["fs", "path", "asset", "path", "fs", "path", "outFilePath", "outPath", "path"]
7
+ }
@@ -1,3 +1,8 @@
1
+ import {
2
+ getSecurityConfig,
3
+ setSecurityHeaders
4
+ } from "./chunk-WX62JK5E.js";
5
+
1
6
  // src/middleware/common.ts
2
7
  import path from "node:path";
3
8
  import micromatch from "micromatch";
@@ -7,6 +12,16 @@ function rootProjectMiddleware(options) {
7
12
  next();
8
13
  };
9
14
  }
15
+ function securityHeadersMiddleware(options) {
16
+ const securityConfig = {
17
+ ...getSecurityConfig(options.rootConfig),
18
+ contentSecurityPolicy: false
19
+ };
20
+ return (req, res, next) => {
21
+ setSecurityHeaders(res, { securityConfig });
22
+ next();
23
+ };
24
+ }
10
25
  function headersMiddleware(options) {
11
26
  const headersUserConfig = options.rootConfig.server?.headers || [];
12
27
  const headersConfig = headersUserConfig.filter((headerConfig) => {
@@ -60,6 +75,19 @@ function removeTrailingSlashes(urlPath) {
60
75
  return urlPath;
61
76
  }
62
77
 
78
+ // src/middleware/compression.ts
79
+ import zlib from "node:zlib";
80
+ import compression from "compression";
81
+ function compressionMiddleware() {
82
+ return compression({
83
+ brotli: {
84
+ params: {
85
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 4
86
+ }
87
+ }
88
+ });
89
+ }
90
+
63
91
  // src/middleware/multipart.ts
64
92
  import busboy from "busboy";
65
93
  var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
@@ -212,11 +240,13 @@ function base64Decode(str) {
212
240
 
213
241
  export {
214
242
  rootProjectMiddleware,
243
+ securityHeadersMiddleware,
215
244
  headersMiddleware,
216
245
  trailingSlashMiddleware,
246
+ compressionMiddleware,
217
247
  multipartMiddleware,
218
248
  SESSION_COOKIE,
219
249
  sessionMiddleware,
220
250
  Session
221
251
  };
222
- //# sourceMappingURL=chunk-6NBKAR5Y.js.map
252
+ //# sourceMappingURL=chunk-TM6QRBGS.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/middleware/common.ts", "../src/middleware/compression.ts", "../src/middleware/multipart.ts", "../src/middleware/session.ts"],
4
+ "sourcesContent": ["import path from 'node:path';\nimport micromatch from 'micromatch';\nimport {RootConfig} from '../core/config.js';\nimport {getSecurityConfig, setSecurityHeaders} from '../core/security.js';\nimport {Request, Response, NextFunction} from '../core/types.js';\n\n/**\n * Middleware that injects the root.js project config into the request context.\n */\nexport function rootProjectMiddleware(options: {rootConfig: RootConfig}) {\n return (req: Request, _: Response, next: NextFunction) => {\n req.rootConfig = options.rootConfig;\n next();\n };\n}\n\n/**\n * Middleware that sets security-related HTTP headers (e.g.\n * `Strict-Transport-Security`) on all responses using the `server.security`\n * config in root.config.ts.\n *\n * The renderer sets these headers on rendered HTML responses, but responses\n * that bypass the renderer (e.g. redirects, static files, plugin-served\n * responses, 404s) would otherwise be served without them. Per\n * https://hstspreload.org, HSTS preload eligibility requires the HSTS header\n * on redirects as well.\n *\n * NOTE: `Content-Security-Policy` is excluded here since the CSP is\n * document-specific and depends on a per-request nonce, which is generated by\n * the renderer at render time.\n */\nexport function securityHeadersMiddleware(options: {rootConfig: RootConfig}) {\n const securityConfig = {\n ...getSecurityConfig(options.rootConfig),\n contentSecurityPolicy: false as const,\n };\n return (req: Request, res: Response, next: NextFunction) => {\n setSecurityHeaders(res, {securityConfig});\n next();\n };\n}\n\n/**\n * Middleware that injects HTTP headers from the `server.headers` config in\n * root.config.ts.\n */\nexport function headersMiddleware(options: {rootConfig: RootConfig}) {\n const headersUserConfig = options.rootConfig.server?.headers || [];\n // Filter header config values that are invalid.\n const headersConfig = headersUserConfig.filter((headerConfig) => {\n return (\n headerConfig.source &&\n headerConfig.headers &&\n headerConfig.headers.length > 0\n );\n });\n return (req: Request, res: Response, next: NextFunction) => {\n headersConfig.forEach((headerConfig) => {\n if (micromatch.isMatch(req.path, headerConfig.source)) {\n headerConfig.headers.forEach((header) => {\n if (header.key) {\n res.setHeader(String(header.key), String(header.value));\n }\n });\n }\n });\n next();\n };\n}\n\n/**\n * Trailing slash middleware. Handles trailing slash redirects (preserving any\n * query params) using the `server.trailingSlash` config in root.config.ts.\n */\nexport function trailingSlashMiddleware(options: {rootConfig: RootConfig}) {\n const trailingSlash = options.rootConfig.server?.trailingSlash;\n\n return (req: Request, res: Response, next: NextFunction) => {\n // If `trailingSlash: false`, force a trailing slash in the URL.\n if (\n trailingSlash === true &&\n !path.extname(req.path) &&\n !req.path.endsWith('/')\n ) {\n const redirectPath = `${req.path}/`;\n redirectWithQuery(req, res, 301, redirectPath);\n return;\n }\n\n // If `trailingSlash: false`, remove any trailing slash from the URL.\n if (\n trailingSlash === false &&\n !path.extname(req.path) &&\n req.path !== '/' &&\n req.path.endsWith('/')\n ) {\n const redirectPath = removeTrailingSlashes(req.path);\n redirectWithQuery(req, res, 301, redirectPath);\n return;\n }\n\n next();\n };\n}\n\n/**\n * Issues an HTTP redirect, preserving any query params from the original req.\n */\nfunction redirectWithQuery(\n req: Request,\n res: Response,\n redirectCode: number,\n redirectPath: string\n) {\n const queryStr = getQueryStr(req);\n const redirectUrl = queryStr ? `${redirectPath}?${queryStr}` : redirectPath;\n res.redirect(redirectCode, redirectUrl);\n}\n\n/**\n * Returns the query string for a request, or empty string if no query.\n */\nfunction getQueryStr(req: Request): string {\n const qIndex = req.originalUrl.indexOf('?');\n if (qIndex === -1) {\n return '';\n }\n return req.originalUrl.slice(qIndex + 1);\n}\n\n/**\n * Removes trailing slashes from a URL path.\n * Note: A path with only slashes (e.g. `///`) returns `/`.\n */\nfunction removeTrailingSlashes(urlPath: string) {\n while (urlPath.endsWith('/') && urlPath !== '/') {\n urlPath = urlPath.slice(0, -1);\n }\n return urlPath;\n}\n", "import zlib from 'node:zlib';\n\nimport compression from 'compression';\n\nimport {Request, Response, NextFunction} from '../core/types.js';\n\n/**\n * Response compression middleware shared by the preview and prod servers.\n *\n * Uses brotli when the client accepts it (`Accept-Encoding: br`), falling\n * back to gzip. Brotli quality 4 is used as a balance between compression\n * ratio and CPU cost for dynamically-rendered responses \u2014 it generally\n * compresses better than gzip's default (level 6) at comparable speed.\n * Pre-compressed static assets should still be served with higher quality\n * settings by a CDN or static file server where possible.\n */\nexport function compressionMiddleware(): (\n req: Request,\n res: Response,\n next: NextFunction\n) => void {\n return compression({\n brotli: {\n params: {\n [zlib.constants.BROTLI_PARAM_QUALITY]: 4,\n },\n },\n }) as (req: Request, res: Response, next: NextFunction) => void;\n}\n", "import busboy from 'busboy';\nimport {RequestHandler} from 'express';\nimport {Request, Response, NextFunction, MultipartFile} from '../core/types.js';\n\nconst DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\n\n/**\n * Middleware for parsing multipart file uploads that's compatible with the dev\n * server and Firebase Functions.\n *\n * Context:\n * https://stackoverflow.com/questions/47242340/how-to-perform-an-http-file-upload-using-express-on-cloud-functions-for-firebase\n */\nexport function multipartMiddleware(options?: {\n maxFileSize?: number;\n}): RequestHandler {\n const maxFileSize = options?.maxFileSize || DEFAULT_MAX_FILE_SIZE;\n const handler: any = (req: Request, res: Response, next: NextFunction) => {\n const contentType = String(req.headers['content-type'] || '');\n if (\n req.method === 'POST' &&\n contentType.startsWith('multipart/form-data')\n ) {\n const parser = busboy({headers: req.headers});\n\n // Data storage for fields and files\n const fields: {[fieldname: string]: string} = {};\n const files: {[name: string]: MultipartFile} = {};\n\n // Handle field data.\n parser.on('field', (fieldname: string, val: any) => {\n fields[fieldname] = val;\n });\n\n // Handle file data. Files are saved to an in-memory buffer.\n parser.on(\n 'file',\n (\n fieldname: string,\n file: any,\n meta: {\n filename: string;\n encoding: string;\n mimeType: string;\n }\n ) => {\n const {filename, encoding, mimeType: mimetype} = meta;\n const fileChunks: Uint8Array[] = [];\n let totalBytesRead = 0;\n\n file.on('data', (chunk: Uint8Array) => {\n totalBytesRead += chunk.length;\n\n if (totalBytesRead > maxFileSize) {\n // File size exceeds the limit, stop reading.\n console.error(`File size exceeds the limit: ${fieldname}.`);\n file.removeAllListeners('data');\n // Consume and discard remaining data.\n file.resume();\n } else {\n fileChunks.push(chunk);\n }\n });\n\n file.on('end', () => {\n if (totalBytesRead <= maxFileSize) {\n const buffer = Buffer.concat(fileChunks);\n\n files[fieldname] = {\n fieldname,\n buffer,\n originalName: filename,\n encoding,\n mimetype,\n };\n }\n });\n }\n );\n\n // Update `req.body` and `req.files`.\n parser.on('finish', () => {\n req.body = fields;\n req.files = files;\n next();\n });\n\n // Pipe the request to Busboy. On Firebase Functions, `rawBody` contains\n // the multipart request. Otherwise the express request can be piped to\n // the Busboy parser.\n if (req.rawBody) {\n parser.end(req.rawBody);\n } else {\n req.pipe(parser);\n }\n } else {\n next();\n }\n };\n return handler;\n}\n", "import {NextFunction, Request, Response} from '../core/types.js';\n\nexport const SESSION_COOKIE = '__session';\nlet hasWarnedMissingSessionCookieSecret = false;\n\n// 5 days.\nconst DEFAULT_MAX_AGE = 60 * 60 * 24 * 5 * 1000;\n\nexport interface SessionMiddlewareOptions {\n maxAge?: number;\n}\n\nexport interface SaveSessionOptions {\n sameSite?: 'strict' | 'lax' | 'none';\n}\n\n/**\n * Middleware for storing session data stored in an http cookie called\n * `__session`. This cookie is compatible with Firebase Hosting:\n * https://firebase.google.com/docs/hosting/manage-cache#using_cookies\n */\nexport function sessionMiddleware(options?: SessionMiddlewareOptions) {\n const maxAge = options?.maxAge || DEFAULT_MAX_AGE;\n return (req: Request, res: Response, next: NextFunction) => {\n const cookieValue = String(req.signedCookies[SESSION_COOKIE] || '');\n const session = Session.fromCookieValue(cookieValue);\n req.session = session;\n res.session = session;\n res.saveSession = (saveSessionOptions?: SaveSessionOptions) => {\n if (!session.hasChanges) {\n return;\n }\n\n const hasConfiguredSessionSecret = Boolean(\n req.rootConfig?.server?.sessionCookieSecret\n );\n if (!hasConfiguredSessionSecret && !hasWarnedMissingSessionCookieSecret) {\n console.warn(\n 'session cookie is being set without a configured server.sessionCookieSecret; sessions may be invalidated across server restarts'\n );\n hasWarnedMissingSessionCookieSecret = true;\n }\n\n // \"secure\" cookies require https, so disable \"secure\" when in development.\n const secureCookie = Boolean(process.env.NODE_ENV !== 'development');\n const cookieValue = session.toString();\n let sameSite = saveSessionOptions?.sameSite;\n if (!sameSite) {\n // Default the sameSite setting to \"none\", which allows it to be used in\n // multi-site contexts.\n // \"Cookies with SameSite=None must also specify Secure, meaning they require a secure context.\"\n // https://web.dev/articles/samesite-cookies-explained#samesite_cookie_recipes\n sameSite = secureCookie ? 'none' : 'strict';\n }\n res.cookie(SESSION_COOKIE, cookieValue, {\n maxAge: maxAge,\n httpOnly: true,\n secure: secureCookie,\n signed: true,\n sameSite: sameSite,\n });\n };\n req.hooks.add('preRender', () => {\n res.saveSession();\n });\n next();\n };\n}\n\nexport class Session {\n private data: Record<string, string> = {};\n hasChanges = false;\n\n constructor(data?: Record<string, string>) {\n this.data = data || {};\n }\n\n static fromCookieValue(cookieValue: string) {\n const data: Record<string, string> = {};\n if (cookieValue.startsWith('b64:')) {\n try {\n const encodedStr = cookieValue.slice(4);\n const params = new URLSearchParams(base64Decode(encodedStr));\n for (const [key, value] of params.entries()) {\n data[key] = value;\n }\n } catch (err) {\n console.warn('failed to parse session cookie:', err);\n return new Session();\n }\n }\n return new Session(data);\n }\n\n getItem(key: string): string | null {\n return this.data[key] ?? null;\n }\n\n setItem(key: string, value: string) {\n this.data[key] = value;\n this.hasChanges = true;\n }\n\n removeItem(key: string) {\n delete this.data[key];\n this.hasChanges = true;\n }\n\n toString(): string {\n const params = new URLSearchParams(this.data);\n return `b64:${base64Encode(params.toString())}`;\n }\n}\n\nfunction base64Encode(str: string): string {\n return Buffer.from(str, 'utf-8').toString('base64');\n}\n\nfunction base64Decode(str: string): string {\n return Buffer.from(str, 'base64').toString('utf-8');\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["cookieValue"]
7
+ }
@@ -0,0 +1,81 @@
1
+ // src/core/security.ts
2
+ function getSecurityConfig(rootConfig) {
3
+ const userConfig = rootConfig.server?.security || {};
4
+ const securityConfig = {};
5
+ if (isTrueOrUndefined(userConfig.contentSecurityPolicy)) {
6
+ securityConfig.contentSecurityPolicy = {
7
+ directives: {
8
+ "base-uri": ["'none'"],
9
+ "object-src": ["'none'"],
10
+ // NOTE: nonce is automatically added to this list.
11
+ "script-src": [
12
+ "'unsafe-inline'",
13
+ "'unsafe-eval'",
14
+ "'strict-dynamic' https: http:"
15
+ ]
16
+ },
17
+ reportOnly: true
18
+ };
19
+ } else {
20
+ securityConfig.contentSecurityPolicy = userConfig.contentSecurityPolicy;
21
+ }
22
+ if (isTrueOrUndefined(userConfig.xFrameOptions)) {
23
+ securityConfig.xFrameOptions = "SAMEORIGIN";
24
+ } else {
25
+ securityConfig.xFrameOptions = userConfig.xFrameOptions;
26
+ }
27
+ securityConfig.strictTransportSecurity = userConfig.strictTransportSecurity ?? true;
28
+ securityConfig.xContentTypeOptions = userConfig.xContentTypeOptions ?? true;
29
+ securityConfig.xXssProtection = userConfig.xXssProtection ?? true;
30
+ return securityConfig;
31
+ }
32
+ function setSecurityHeaders(res, options) {
33
+ const securityConfig = options.securityConfig;
34
+ const contentSecurityPolicy = securityConfig.contentSecurityPolicy;
35
+ if (typeof contentSecurityPolicy === "object") {
36
+ const directives = { ...contentSecurityPolicy.directives };
37
+ if (options.nonce) {
38
+ const scriptSrc = directives["script-src"] || [
39
+ "'unsafe-inline'",
40
+ "'unsafe-eval'",
41
+ "'strict-dynamic' https: http:"
42
+ ];
43
+ const scriptSrcWithNonce = [...scriptSrc, `'nonce-${options.nonce}'`];
44
+ directives["script-src"] = scriptSrcWithNonce;
45
+ }
46
+ const headerSegments = [];
47
+ Object.entries(directives).forEach(([key, values]) => {
48
+ headerSegments.push([key, ...values].join(" "));
49
+ });
50
+ const csp = headerSegments.join("; ");
51
+ if (contentSecurityPolicy.reportOnly === false) {
52
+ res.setHeader("content-security-policy", csp);
53
+ } else {
54
+ res.setHeader("content-security-policy-report-only", csp);
55
+ }
56
+ }
57
+ if (typeof securityConfig.xFrameOptions === "string") {
58
+ res.setHeader("x-frame-options", securityConfig.xFrameOptions);
59
+ }
60
+ if (securityConfig.strictTransportSecurity) {
61
+ res.setHeader(
62
+ "strict-transport-security",
63
+ "max-age=63072000; includeSubDomains; preload"
64
+ );
65
+ }
66
+ if (securityConfig.xContentTypeOptions) {
67
+ res.setHeader("x-content-type-options", "nosniff");
68
+ }
69
+ if (securityConfig.xXssProtection) {
70
+ res.setHeader("x-xss-protection", "1; mode=block");
71
+ }
72
+ }
73
+ function isTrueOrUndefined(value) {
74
+ return value === true || value === void 0;
75
+ }
76
+
77
+ export {
78
+ getSecurityConfig,
79
+ setSecurityHeaders
80
+ };
81
+ //# sourceMappingURL=chunk-WX62JK5E.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/security.ts"],
4
+ "sourcesContent": ["import {RootConfig, RootSecurityConfig} from './config.js';\nimport {Response} from './types.js';\n\n/**\n * Returns the `security` config value with default values inserted wherever\n * a user config value is blank or set to `true`.\n */\nexport function getSecurityConfig(\n rootConfig: RootConfig\n): Required<RootSecurityConfig> {\n const userConfig: Partial<RootSecurityConfig> =\n rootConfig.server?.security || {};\n const securityConfig: Partial<RootSecurityConfig> = {};\n\n if (isTrueOrUndefined(userConfig.contentSecurityPolicy)) {\n // CSP default values from:\n // https://csp.withgoogle.com/docs/strict-csp.html\n securityConfig.contentSecurityPolicy = {\n directives: {\n 'base-uri': [\"'none'\"],\n 'object-src': [\"'none'\"],\n // NOTE: nonce is automatically added to this list.\n 'script-src': [\n \"'unsafe-inline'\",\n \"'unsafe-eval'\",\n \"'strict-dynamic' https: http:\",\n ],\n },\n reportOnly: true,\n };\n } else {\n securityConfig.contentSecurityPolicy = userConfig.contentSecurityPolicy;\n }\n\n if (isTrueOrUndefined(userConfig.xFrameOptions)) {\n securityConfig.xFrameOptions = 'SAMEORIGIN';\n } else {\n securityConfig.xFrameOptions = userConfig.xFrameOptions;\n }\n\n securityConfig.strictTransportSecurity =\n userConfig.strictTransportSecurity ?? true;\n securityConfig.xContentTypeOptions = userConfig.xContentTypeOptions ?? true;\n securityConfig.xXssProtection = userConfig.xXssProtection ?? true;\n\n return securityConfig as Required<RootSecurityConfig>;\n}\n\n/**\n * Sets security-related HTTP headers.\n */\nexport function setSecurityHeaders(\n res: Response,\n options: {securityConfig: Required<RootSecurityConfig>; nonce?: string}\n) {\n const securityConfig = options.securityConfig;\n\n // Content-Security-Policy.\n const contentSecurityPolicy = securityConfig.contentSecurityPolicy;\n if (typeof contentSecurityPolicy === 'object') {\n // Copy the CSP `directives` value since the `script-src` value will be\n // updated with a `nonce-` value.\n const directives = {...contentSecurityPolicy.directives};\n if (options.nonce) {\n // Create a new array for `script-src` and append a `nonce-` value.\n const scriptSrc = directives['script-src'] || [\n \"'unsafe-inline'\",\n \"'unsafe-eval'\",\n \"'strict-dynamic' https: http:\",\n ];\n const scriptSrcWithNonce = [...scriptSrc, `'nonce-${options.nonce}'`];\n directives['script-src'] = scriptSrcWithNonce;\n }\n const headerSegments: string[] = [];\n Object.entries(directives).forEach(([key, values]) => {\n headerSegments.push([key, ...values].join(' '));\n });\n const csp = headerSegments.join('; ');\n if (contentSecurityPolicy.reportOnly === false) {\n res.setHeader('content-security-policy', csp);\n } else {\n res.setHeader('content-security-policy-report-only', csp);\n }\n }\n\n // X-Frame-Options.\n if (typeof securityConfig.xFrameOptions === 'string') {\n res.setHeader('x-frame-options', securityConfig.xFrameOptions);\n }\n\n // Strict-Transport-Security.\n if (securityConfig.strictTransportSecurity) {\n res.setHeader(\n 'strict-transport-security',\n 'max-age=63072000; includeSubDomains; preload'\n );\n }\n\n // X-Content-Type-Options.\n if (securityConfig.xContentTypeOptions) {\n res.setHeader('x-content-type-options', 'nosniff');\n }\n\n // X-XSS-Protection.\n if (securityConfig.xXssProtection) {\n res.setHeader('x-xss-protection', '1; mode=block');\n }\n}\n\nfunction isTrueOrUndefined(value: any) {\n return value === true || value === undefined;\n}\n"],
5
+ "mappings": ";AAOO,SAAS,kBACd,YAC8B;AAC9B,QAAM,aACJ,WAAW,QAAQ,YAAY,CAAC;AAClC,QAAM,iBAA8C,CAAC;AAErD,MAAI,kBAAkB,WAAW,qBAAqB,GAAG;AAGvD,mBAAe,wBAAwB;AAAA,MACrC,YAAY;AAAA,QACV,YAAY,CAAC,QAAQ;AAAA,QACrB,cAAc,CAAC,QAAQ;AAAA;AAAA,QAEvB,cAAc;AAAA,UACZ;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF,OAAO;AACL,mBAAe,wBAAwB,WAAW;AAAA,EACpD;AAEA,MAAI,kBAAkB,WAAW,aAAa,GAAG;AAC/C,mBAAe,gBAAgB;AAAA,EACjC,OAAO;AACL,mBAAe,gBAAgB,WAAW;AAAA,EAC5C;AAEA,iBAAe,0BACb,WAAW,2BAA2B;AACxC,iBAAe,sBAAsB,WAAW,uBAAuB;AACvE,iBAAe,iBAAiB,WAAW,kBAAkB;AAE7D,SAAO;AACT;AAKO,SAAS,mBACd,KACA,SACA;AACA,QAAM,iBAAiB,QAAQ;AAG/B,QAAM,wBAAwB,eAAe;AAC7C,MAAI,OAAO,0BAA0B,UAAU;AAG7C,UAAM,aAAa,EAAC,GAAG,sBAAsB,WAAU;AACvD,QAAI,QAAQ,OAAO;AAEjB,YAAM,YAAY,WAAW,YAAY,KAAK;AAAA,QAC5C;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,qBAAqB,CAAC,GAAG,WAAW,UAAU,QAAQ,KAAK,GAAG;AACpE,iBAAW,YAAY,IAAI;AAAA,IAC7B;AACA,UAAM,iBAA2B,CAAC;AAClC,WAAO,QAAQ,UAAU,EAAE,QAAQ,CAAC,CAAC,KAAK,MAAM,MAAM;AACpD,qBAAe,KAAK,CAAC,KAAK,GAAG,MAAM,EAAE,KAAK,GAAG,CAAC;AAAA,IAChD,CAAC;AACD,UAAM,MAAM,eAAe,KAAK,IAAI;AACpC,QAAI,sBAAsB,eAAe,OAAO;AAC9C,UAAI,UAAU,2BAA2B,GAAG;AAAA,IAC9C,OAAO;AACL,UAAI,UAAU,uCAAuC,GAAG;AAAA,IAC1D;AAAA,EACF;AAGA,MAAI,OAAO,eAAe,kBAAkB,UAAU;AACpD,QAAI,UAAU,mBAAmB,eAAe,aAAa;AAAA,EAC/D;AAGA,MAAI,eAAe,yBAAyB;AAC1C,QAAI;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,eAAe,qBAAqB;AACtC,QAAI,UAAU,0BAA0B,SAAS;AAAA,EACnD;AAGA,MAAI,eAAe,gBAAgB;AACjC,QAAI,UAAU,oBAAoB,eAAe;AAAA,EACnD;AACF;AAEA,SAAS,kBAAkB,OAAY;AACrC,SAAO,UAAU,QAAQ,UAAU;AACrC;",
6
+ "names": []
7
+ }