@bananacool467/authtics-host 0.2.5 → 0.2.7

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.
@@ -1,5 +1,14 @@
1
+ import type { BuildStyle, BuildTrigger } from "./config.js";
2
+ export declare function setBuildStyle(style: BuildStyle, projectRoot?: string): void;
3
+ export declare function getBuildStyle(): BuildStyle;
4
+ export declare function setBuildTrigger(trigger: BuildTrigger): void;
5
+ export declare function getBuildTrigger(): BuildTrigger;
6
+ export declare function getBuildOutputRoot(): string;
1
7
  export declare function ensureTempDirs(): void;
2
- export declare function tempFileForTsx(tsxPath: string): string;
8
+ export declare function tempFileForTsx(tsxPath: string, variant?: string): string;
9
+ export declare function clientBundleForTsx(tsxPath: string, variant?: string): string;
10
+ export declare function serverBundleForTsx(tsxPath: string, variant?: string, tempPath?: string): string;
11
+ export declare function ssgHtmlForTsx(tsxPath: string, variant?: string, tempPath?: string): string;
3
12
  export declare function tempFileForDev(): string;
4
13
  export declare function isTempUpToDate(tsxPath: string, tempPath: string): boolean;
5
14
  export declare function writeTempFile(tempPath: string, content: string): void;
@@ -1,25 +1,100 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- const AUTHTICS_DIR = path.join(process.cwd(), ".authtics_host");
4
- const DEV_CACHE_DIR = path.join(AUTHTICS_DIR, "dev-cache");
5
- const CACHE_DIR = path.join(AUTHTICS_DIR, "cache");
3
+ let activeBuildStyle = "default";
4
+ let activeBuildTrigger = "default";
5
+ let activeProjectRoot = process.cwd();
6
+ function hostjsDir() {
7
+ return path.join(activeProjectRoot, ".hostjs");
8
+ }
9
+ function legacyDir() {
10
+ return path.join(activeProjectRoot, ".authtics_host");
11
+ }
12
+ function artifactName(tsxPath, variant = "normal") {
13
+ const base = path.basename(tsxPath);
14
+ const hash = simpleHash(`${tsxPath}:${variant}`);
15
+ return `${base}_${hash}`;
16
+ }
17
+ export function setBuildStyle(style, projectRoot = process.cwd()) {
18
+ if (style !== "default" && style !== "super") {
19
+ throw new Error('[HostJS] Invalid buildStyle. Expected "default" or "super".');
20
+ }
21
+ activeBuildStyle = style;
22
+ activeProjectRoot = path.resolve(projectRoot);
23
+ ensureTempDirs();
24
+ }
25
+ export function getBuildStyle() {
26
+ return activeBuildStyle;
27
+ }
28
+ export function setBuildTrigger(trigger) {
29
+ if (trigger !== "default" && trigger !== "prebuilt") {
30
+ throw new Error('[HostJS] Invalid buildTrigger. Expected "default" or "prebuilt".');
31
+ }
32
+ activeBuildTrigger = trigger;
33
+ }
34
+ export function getBuildTrigger() {
35
+ return activeBuildTrigger;
36
+ }
37
+ export function getBuildOutputRoot() {
38
+ return activeBuildStyle === "super" ? hostjsDir() : legacyDir();
39
+ }
6
40
  export function ensureTempDirs() {
7
- if (!fs.existsSync(AUTHTICS_DIR))
8
- fs.mkdirSync(AUTHTICS_DIR, { recursive: true });
9
- if (!fs.existsSync(CACHE_DIR))
10
- fs.mkdirSync(CACHE_DIR, { recursive: true });
11
- if (!fs.existsSync(DEV_CACHE_DIR))
12
- fs.mkdirSync(DEV_CACHE_DIR, { recursive: true });
13
- }
14
- export function tempFileForTsx(tsxPath) {
41
+ if (activeBuildStyle === "default") {
42
+ fs.mkdirSync(legacyDir(), { recursive: true });
43
+ fs.mkdirSync(path.join(legacyDir(), "cache"), { recursive: true });
44
+ fs.mkdirSync(path.join(legacyDir(), "dev-cache"), { recursive: true });
45
+ return;
46
+ }
47
+ const dirs = [
48
+ path.join(hostjsDir(), "cache", "bundler", "client-production"),
49
+ path.join(hostjsDir(), "cache", "bundler", "server-production"),
50
+ path.join(hostjsDir(), "cache", "bundler", "edge-server-production"),
51
+ path.join(hostjsDir(), "diagnostics"),
52
+ path.join(hostjsDir(), "server", "app"),
53
+ path.join(hostjsDir(), "server", "pages"),
54
+ path.join(hostjsDir(), "server", "chunks"),
55
+ path.join(hostjsDir(), "server", "route-cache"),
56
+ path.join(hostjsDir(), "types", "app"),
57
+ path.join(hostjsDir(), "dev"),
58
+ path.join(hostjsDir(), "trace"),
59
+ path.join(hostjsDir(), "trace-build"),
60
+ ];
61
+ for (const dir of dirs)
62
+ fs.mkdirSync(dir, { recursive: true });
63
+ }
64
+ export function tempFileForTsx(tsxPath, variant = "normal") {
15
65
  ensureTempDirs();
16
- const base = path.basename(tsxPath);
17
- const hash = simpleHash(tsxPath);
18
- return path.join(CACHE_DIR, `${base}_${hash}.json`);
66
+ const name = artifactName(tsxPath, variant);
67
+ const dir = activeBuildStyle === "super"
68
+ ? path.join(hostjsDir(), "server", "route-cache")
69
+ : path.join(legacyDir(), "cache");
70
+ return path.join(dir, `${name}.json`);
71
+ }
72
+ export function clientBundleForTsx(tsxPath, variant = "normal") {
73
+ ensureTempDirs();
74
+ if (activeBuildStyle === "default") {
75
+ return tempFileForTsx(tsxPath, variant).replace(/\.json$/, ".client.js");
76
+ }
77
+ return path.join(hostjsDir(), "cache", "bundler", "client-production", `${artifactName(tsxPath, variant)}.js`);
78
+ }
79
+ export function serverBundleForTsx(tsxPath, variant = "normal", tempPath) {
80
+ ensureTempDirs();
81
+ if (activeBuildStyle === "default") {
82
+ return (tempPath ?? tempFileForTsx(tsxPath, variant)).replace(/\.json$/, ".server.cjs");
83
+ }
84
+ return path.join(hostjsDir(), "cache", "bundler", "server-production", `${artifactName(tsxPath, variant)}.server.cjs`);
85
+ }
86
+ export function ssgHtmlForTsx(tsxPath, variant = "normal", tempPath) {
87
+ ensureTempDirs();
88
+ if (activeBuildStyle === "default") {
89
+ return (tempPath ?? tempFileForTsx(tsxPath, variant)).replace(/\.json$/, ".ssg.html");
90
+ }
91
+ return path.join(hostjsDir(), "server", "route-cache", `${artifactName(tsxPath, variant)}.ssg.html`);
19
92
  }
20
93
  export function tempFileForDev() {
21
94
  ensureTempDirs();
22
- return path.join(DEV_CACHE_DIR, "page.cache.json");
95
+ return activeBuildStyle === "super"
96
+ ? path.join(hostjsDir(), "dev", "page.cache.json")
97
+ : path.join(legacyDir(), "dev-cache", "page.cache.json");
23
98
  }
24
99
  export function isTempUpToDate(tsxPath, tempPath) {
25
100
  try {
@@ -32,14 +107,23 @@ export function isTempUpToDate(tsxPath, tempPath) {
32
107
  }
33
108
  }
34
109
  export function writeTempFile(tempPath, content) {
110
+ fs.mkdirSync(path.dirname(tempPath), { recursive: true });
35
111
  fs.writeFileSync(tempPath, content, "utf-8");
36
112
  }
37
113
  export function readTempFile(tempPath) {
38
114
  return fs.readFileSync(tempPath, "utf-8");
39
115
  }
40
116
  export function clearCache() {
41
- if (fs.existsSync(CACHE_DIR)) {
42
- fs.rmSync(CACHE_DIR, { recursive: true, force: true });
117
+ if (activeBuildStyle === "default") {
118
+ fs.rmSync(path.join(legacyDir(), "cache"), { recursive: true, force: true });
119
+ }
120
+ else {
121
+ for (const dir of [
122
+ path.join(hostjsDir(), "cache", "bundler"),
123
+ path.join(hostjsDir(), "server", "route-cache"),
124
+ ]) {
125
+ fs.rmSync(dir, { recursive: true, force: true });
126
+ }
43
127
  }
44
128
  ensureTempDirs();
45
129
  }
@@ -0,0 +1,9 @@
1
+ import type { AuthticsConfig } from "./config.js";
2
+ export type HostJSRouteKind = "page" | "api" | "static";
3
+ export interface HostJSRouteRecord {
4
+ pathname: string;
5
+ kind: HostJSRouteKind;
6
+ source: string;
7
+ }
8
+ export declare function refreshSuperRouteManifest(projectRoot: string, config: AuthticsConfig, pagesDir?: string, publicDir?: string): void;
9
+ export declare function initializeSuperBuildOutput(projectRoot: string, config: AuthticsConfig, pagesDir?: string, publicDir?: string): void;
@@ -0,0 +1,175 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const HOSTJS_DIR = ".hostjs";
4
+ function writeJson(filePath, value) {
5
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
6
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
7
+ }
8
+ function readJson(filePath) {
9
+ try {
10
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
11
+ }
12
+ catch {
13
+ return {};
14
+ }
15
+ }
16
+ function toProjectPath(root, filePath) {
17
+ return path.relative(root, filePath).split(path.sep).join("/");
18
+ }
19
+ function walkFiles(dir) {
20
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory())
21
+ return [];
22
+ const result = [];
23
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
24
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".hostjs")
25
+ continue;
26
+ const fullPath = path.join(dir, entry.name);
27
+ if (entry.isDirectory())
28
+ result.push(...walkFiles(fullPath));
29
+ else if (entry.isFile())
30
+ result.push(fullPath);
31
+ }
32
+ return result;
33
+ }
34
+ function routePathFromFile(relativePath) {
35
+ const withoutExtension = relativePath.replace(/\.(?:tsx?|jsx?|html)$/i, "");
36
+ if (withoutExtension.toLowerCase() === "index")
37
+ return "/";
38
+ return `/${withoutExtension.split(path.sep).join("/")}`;
39
+ }
40
+ function collectRoutes(projectRoot, pagesDir, publicDir) {
41
+ const routes = [];
42
+ if (pagesDir) {
43
+ for (const filePath of walkFiles(pagesDir)) {
44
+ const relativePath = path.relative(pagesDir, filePath);
45
+ const normalized = relativePath.split(path.sep).join("/");
46
+ const lower = normalized.toLowerCase();
47
+ if (lower === "_template.html" || lower === "_paused.tsx")
48
+ continue;
49
+ if (/^api\/.+\.(?:ts|tsx)$/i.test(normalized)) {
50
+ const pathname = `/${normalized.replace(/\.(?:tsx?)$/i, "")}`;
51
+ routes.push({ pathname, kind: "api", source: toProjectPath(projectRoot, filePath) });
52
+ continue;
53
+ }
54
+ if (/\.(?:tsx|html)$/i.test(normalized)) {
55
+ routes.push({
56
+ pathname: routePathFromFile(relativePath),
57
+ kind: "page",
58
+ source: toProjectPath(projectRoot, filePath),
59
+ });
60
+ }
61
+ }
62
+ }
63
+ if (publicDir) {
64
+ for (const filePath of walkFiles(publicDir)) {
65
+ routes.push({
66
+ pathname: `/${path.relative(publicDir, filePath).split(path.sep).join("/")}`,
67
+ kind: "static",
68
+ source: toProjectPath(projectRoot, filePath),
69
+ });
70
+ }
71
+ }
72
+ const unique = new Map();
73
+ for (const route of routes)
74
+ unique.set(`${route.kind}:${route.pathname}`, route);
75
+ return [...unique.values()].sort((a, b) => a.pathname.localeCompare(b.pathname) || a.kind.localeCompare(b.kind));
76
+ }
77
+ function typeUnion(values) {
78
+ const unique = [...new Set(values)].sort();
79
+ return unique.length ? unique.map((value) => JSON.stringify(value)).join(" | ") : "never";
80
+ }
81
+ function resolveDirectories(root, config, pagesDir, publicDir) {
82
+ return {
83
+ pagesDir: config.autoPage === true
84
+ ? pagesDir ?? path.join(root, config.autoPageDir || "")
85
+ : undefined,
86
+ publicDir: config.autoFile === true
87
+ ? publicDir ?? path.join(root, config.autoFileDir || "")
88
+ : undefined,
89
+ };
90
+ }
91
+ function writeRouteManifestAndTypes(root, outputDir, config, pagesDir, publicDir) {
92
+ const directories = resolveDirectories(root, config, pagesDir, publicDir);
93
+ const routes = collectRoutes(root, directories.pagesDir, directories.publicDir);
94
+ writeJson(path.join(outputDir, "server", "routes-manifest.json"), {
95
+ version: 1,
96
+ generatedBy: "HostJS",
97
+ buildStyle: "super",
98
+ pagesDirectory: directories.pagesDir ? toProjectPath(root, directories.pagesDir) : null,
99
+ publicDirectory: directories.publicDir ? toProjectPath(root, directories.publicDir) : null,
100
+ routes,
101
+ });
102
+ const pageRoutes = routes.filter((route) => route.kind === "page").map((route) => route.pathname);
103
+ const apiRoutes = routes.filter((route) => route.kind === "api").map((route) => route.pathname);
104
+ const staticRoutes = routes.filter((route) => route.kind === "static").map((route) => route.pathname);
105
+ const routeDeclarations = `// Generated by HostJS buildStyle: "super". Do not edit.
106
+ export type HostJSPageRoute = ${typeUnion(pageRoutes)};
107
+ export type HostJSApiRoute = ${typeUnion(apiRoutes)};
108
+ export type HostJSStaticRoute = ${typeUnion(staticRoutes)};
109
+ `;
110
+ fs.mkdirSync(path.join(outputDir, "types", "app"), { recursive: true });
111
+ fs.writeFileSync(path.join(outputDir, "types", "routes.d.ts"), routeDeclarations, "utf-8");
112
+ fs.writeFileSync(path.join(outputDir, "types", "validator.ts"), `// Generated by HostJS buildStyle: "super". Do not edit.
113
+ import type { HostJSPageRoute, HostJSApiRoute, HostJSStaticRoute } from "./routes.js";
114
+
115
+ const pageRoutes = ${JSON.stringify([...new Set(pageRoutes)].sort())} as const;
116
+ const apiRoutes = ${JSON.stringify([...new Set(apiRoutes)].sort())} as const;
117
+ const staticRoutes = ${JSON.stringify([...new Set(staticRoutes)].sort())} as const;
118
+
119
+ export function isHostJSPageRoute(value: string): value is HostJSPageRoute {
120
+ return (pageRoutes as readonly string[]).includes(value);
121
+ }
122
+
123
+ export function isHostJSApiRoute(value: string): value is HostJSApiRoute {
124
+ return (apiRoutes as readonly string[]).includes(value);
125
+ }
126
+
127
+ export function isHostJSStaticRoute(value: string): value is HostJSStaticRoute {
128
+ return (staticRoutes as readonly string[]).includes(value);
129
+ }
130
+ `, "utf-8");
131
+ fs.writeFileSync(path.join(outputDir, "types", "app", "routes.d.ts"), `// Generated by HostJS buildStyle: "super". Do not edit.
132
+ export type { HostJSPageRoute, HostJSApiRoute, HostJSStaticRoute } from "../routes.js";
133
+ `, "utf-8");
134
+ }
135
+ export function refreshSuperRouteManifest(projectRoot, config, pagesDir, publicDir) {
136
+ const root = path.resolve(projectRoot);
137
+ const outputDir = path.join(root, HOSTJS_DIR);
138
+ if (!fs.existsSync(outputDir))
139
+ return;
140
+ writeRouteManifestAndTypes(root, outputDir, config, pagesDir, publicDir);
141
+ }
142
+ export function initializeSuperBuildOutput(projectRoot, config, pagesDir, publicDir) {
143
+ const root = path.resolve(projectRoot);
144
+ const outputDir = path.join(root, HOSTJS_DIR);
145
+ const projectPackage = readJson(path.join(root, "package.json"));
146
+ fs.mkdirSync(outputDir, { recursive: true });
147
+ writeJson(path.join(outputDir, "package.json"), {
148
+ schemaVersion: 1,
149
+ framework: "@bananacool467/authtics-host",
150
+ frameworkName: "Authtics Host",
151
+ shortName: "HostJS",
152
+ buildStyle: "super",
153
+ project: {
154
+ name: typeof projectPackage.name === "string" ? projectPackage.name : path.basename(root),
155
+ version: typeof projectPackage.version === "string" ? projectPackage.version : null,
156
+ },
157
+ });
158
+ writeJson(path.join(outputDir, "diagnostics", "framework.json"), {
159
+ name: "Authtics Host",
160
+ shortName: "HostJS",
161
+ buildStyle: "super",
162
+ schemaVersion: 1,
163
+ });
164
+ writeJson(path.join(outputDir, "diagnostics", "build-diagnostics.json"), {
165
+ version: 1,
166
+ diagnostics: [],
167
+ });
168
+ writeJson(path.join(outputDir, "server", "middleware-manifest.json"), {
169
+ version: 1,
170
+ middleware: [],
171
+ });
172
+ writeRouteManifestAndTypes(root, outputDir, config, pagesDir, publicDir);
173
+ fs.mkdirSync(path.join(outputDir, "trace"), { recursive: true });
174
+ fs.mkdirSync(path.join(outputDir, "trace-build"), { recursive: true });
175
+ }
@@ -0,0 +1 @@
1
+ export default function Slope(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export default function Slope() {
3
+ return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", xmlnsXlink: "http://www.w3.org/1999/xlink", id: "eAC8Baof2Sn1", viewBox: "0 0 300 300", shapeRendering: "geometricPrecision", textRendering: "geometricPrecision", children: _jsx("path", { d: "M38.988202,35.395066v224.168752h226.313907C186.013992,256.687591,95.751962,198.420051,74.383268,65.427243C71.454867,53.642201,60.354042,34.44468,38.988202,35.395066Z", transform: "matrix(1.306635 0 0 1.306641 -48.798185 -42.681458)", fill: "#fff", stroke: "#e6e3e3" }) }));
4
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@bananacool467/authtics-host",
3
- "version": "0.2.5",
4
- "description": "Authtics Host, A Next.js-inspired SSR framework for Node.js with TypeScript and JSX support.",
3
+ "version": "0.2.7",
4
+ "description": "Authtics Host, A Next.js-inspired web framework for Node.js and React with TypeScript and JSX support.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "type": "module",
8
8
  "sideEffects": false,
9
9
  "bin": {
10
- "hostjs": "dist/cli.js"
10
+ "host": "dist/cli.js"
11
11
  },
12
12
  "exports": {
13
13
  ".": "./dist/index.js"
@@ -17,6 +17,7 @@
17
17
  "dev": "tsc --watch",
18
18
  "prepublishOnly": "npm run build",
19
19
  "test": "npm run build && node dist/index.js",
20
+ "test:source-maps": "npm run build && node --test tests/source-map-*.test.mjs",
20
21
  "playgroundTest": "npm run build && cd playground && node --experimental-strip-types index.ts",
21
22
  "playgroundTest2": "npm run build && cd playground2 && node --experimental-strip-types index.ts"
22
23
  },
@@ -25,6 +26,9 @@
25
26
  "host",
26
27
  "server-side-rendering",
27
28
  "ssr",
29
+ "ssg",
30
+ "csr",
31
+ "srtws",
28
32
  "framework",
29
33
  "nodejs",
30
34
  "typescript",
@@ -44,26 +48,34 @@
44
48
  "author": "bananacool467",
45
49
  "license": "MIT",
46
50
  "dependencies": {
47
- "esbuild": "^0.27.3",
51
+ "@jridgewell/trace-mapping": "^0.3.31",
52
+ "esbuild": "^0.28.2",
48
53
  "jiti": "^2.6.1",
49
54
  "lucide-react": "^0.577.0",
50
- "prismjs": "^1.30.0"
55
+ "prismjs": "^1.30.0",
56
+ "ws": "^8.21.3"
51
57
  },
52
58
  "peerDependencies": {
53
59
  "react": "^19.2.4",
54
60
  "react-dom": "^19.2.4"
55
61
  },
56
62
  "devDependencies": {
63
+ "@bananacool467/ui-tools": "^0.2.2-beta",
64
+ "@babel/core": "^7.29.0",
65
+ "@babel/preset-env": "^7.29.0",
66
+ "@babel/preset-react": "^7.28.5",
67
+ "@babel/preset-typescript": "^7.28.5",
68
+ "@types/babel__core": "^7.20.5",
69
+ "@types/babel__preset-env": "^7.10.0",
57
70
  "@types/node": "^22.13.11",
58
71
  "@types/prismjs": "^1.26.6",
59
72
  "@types/react": "^19.2.14",
60
73
  "@types/react-dom": "^19.2.3",
61
74
  "@types/react-syntax-highlighter": "^15.5.13",
62
- "@types/babel__core": "^7.20.5",
63
- "@types/babel__preset-env": "^7.10.0",
64
- "@babel/core": "^7.29.0",
65
- "@babel/preset-env": "^7.29.0",
66
- "@babel/preset-react": "^7.28.5",
67
- "@babel/preset-typescript": "^7.28.5"
75
+ "@types/ws": "^8.18.1"
76
+ },
77
+ "repository": {
78
+ "type": "git",
79
+ "url": "git+https://github.com/bananakitssu/authtics-host"
68
80
  }
69
81
  }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ npm ci --no-audit --no-fund
5
+ npm run build
@@ -0,0 +1,19 @@
1
+ export default function Slope() {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ xmlnsXlink="http://www.w3.org/1999/xlink"
6
+ id="eAC8Baof2Sn1"
7
+ viewBox="0 0 300 300"
8
+ shapeRendering="geometricPrecision"
9
+ textRendering="geometricPrecision"
10
+ >
11
+ <path
12
+ d="M38.988202,35.395066v224.168752h226.313907C186.013992,256.687591,95.751962,198.420051,74.383268,65.427243C71.454867,53.642201,60.354042,34.44468,38.988202,35.395066Z"
13
+ transform="matrix(1.306635 0 0 1.306641 -48.798185 -42.681458)"
14
+ fill="#fff"
15
+ stroke="#e6e3e3"
16
+ />
17
+ </svg>
18
+ );
19
+ }
@@ -0,0 +1,167 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawn } from "node:child_process";
3
+ import { once } from "node:events";
4
+ import fs from "node:fs/promises";
5
+ import net from "node:net";
6
+ import path from "node:path";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+ import test from "node:test";
9
+
10
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
+ const cliPath = path.join(projectRoot, "dist", "cli.js");
12
+ const allowedDat = "source-map-test-dat";
13
+ const sourceMarker = "source-map-source-content-test-marker";
14
+
15
+ function childHasExited(child) {
16
+ return child.exitCode !== null || child.signalCode !== null;
17
+ }
18
+
19
+ async function reservePort() {
20
+ const server = net.createServer();
21
+ server.listen(0, "127.0.0.1");
22
+ await once(server, "listening");
23
+ const { port } = server.address();
24
+ await new Promise((resolve, reject) => {
25
+ server.close((error) => (error ? reject(error) : resolve()));
26
+ });
27
+ return port;
28
+ }
29
+
30
+ async function startFixtureServer(dat) {
31
+ const fixtureRoot = await fs.mkdtemp(path.join(projectRoot, ".source-map-access-"));
32
+ const pagesDir = path.join(fixtureRoot, "pages");
33
+ const port = await reservePort();
34
+ await fs.mkdir(pagesDir);
35
+ await fs.writeFile(
36
+ path.join(fixtureRoot, "authtics_host.config.ts"),
37
+ `export default {
38
+ port: ${port},
39
+ renderType: "csr",
40
+ devMode: true,
41
+ autoPage: true,
42
+ autoPageDir: "pages",
43
+ autoFile: false,
44
+ dat: ${dat === undefined ? "undefined" : JSON.stringify(dat)}
45
+ };\n`,
46
+ );
47
+ await fs.writeFile(
48
+ path.join(pagesDir, "index.tsx"),
49
+ `export default function Page() {
50
+ return <main>${sourceMarker}</main>;
51
+ }\n`,
52
+ );
53
+ const serverEntryPath = path.join(fixtureRoot, "server.mjs");
54
+ await fs.writeFile(
55
+ serverEntryPath,
56
+ `import App from ${JSON.stringify(pathToFileURL(cliPath).href)};\nnew App();\n`,
57
+ );
58
+ const child = spawn(process.execPath, [serverEntryPath], {
59
+ cwd: fixtureRoot,
60
+ stdio: ["ignore", "pipe", "pipe"],
61
+ });
62
+
63
+ let output = "";
64
+ child.stdout.setEncoding("utf8").on("data", (chunk) => (output += chunk));
65
+ child.stderr.setEncoding("utf8").on("data", (chunk) => (output += chunk));
66
+
67
+ try {
68
+ const deadline = Date.now() + 20_000;
69
+ let lastFailure = "Server did not become ready.";
70
+ while (Date.now() < deadline) {
71
+ if (childHasExited(child)) {
72
+ throw new Error(
73
+ `Server exited with code ${child.exitCode} and signal ${child.signalCode}.\n${output}`,
74
+ );
75
+ }
76
+
77
+ try {
78
+ const response = await fetch(`http://127.0.0.1:${port}/`, {
79
+ headers: dat === undefined ? {} : { cookie: `dat=${dat}` },
80
+ });
81
+ const html = await response.text();
82
+ if (response.ok) {
83
+ const scriptUrl = html.match(/<script src="([^"]+\.js)"><\/script>/)?.[1];
84
+ assert.ok(scriptUrl, `Rendered page did not include its JavaScript bundle.\n${html}`);
85
+ return {
86
+ baseUrl: `http://127.0.0.1:${port}`,
87
+ child,
88
+ fixtureRoot,
89
+ mapUrl: `${scriptUrl}.map`,
90
+ };
91
+ }
92
+ lastFailure = `Page returned HTTP ${response.status}:\n${html}`;
93
+ } catch (error) {
94
+ lastFailure = error instanceof Error ? error.message : String(error);
95
+ }
96
+
97
+ await new Promise((resolve) => setTimeout(resolve, 100));
98
+ }
99
+
100
+ throw new Error(`${lastFailure}\n${output}`);
101
+ } catch (error) {
102
+ child.kill("SIGKILL");
103
+ await fs.rm(fixtureRoot, { recursive: true, force: true });
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ async function stopFixtureServer(fixture) {
109
+ if (!childHasExited(fixture.child)) {
110
+ const exited = once(fixture.child, "exit");
111
+ fixture.child.kill("SIGTERM");
112
+ const stopped = await Promise.race([
113
+ exited.then(() => true),
114
+ new Promise((resolve) => setTimeout(() => resolve(false), 3_000)),
115
+ ]);
116
+ if (!stopped && !childHasExited(fixture.child)) {
117
+ const forceExit = once(fixture.child, "exit");
118
+ fixture.child.kill("SIGKILL");
119
+ await forceExit;
120
+ }
121
+ }
122
+ await fs.rm(fixture.fixtureRoot, { recursive: true, force: true });
123
+ }
124
+
125
+ async function assertValidSourceMap(response) {
126
+ assert.equal(response.status, 200);
127
+ assert.match(response.headers.get("content-type") ?? "", /application\/json/i);
128
+ const sourceMap = await response.json();
129
+ assert.equal(sourceMap.version, 3);
130
+ assert.ok(Array.isArray(sourceMap.sourcesContent));
131
+ assert.ok(
132
+ sourceMap.sourcesContent.some((content) => content?.includes(sourceMarker)),
133
+ "The source map should contain the original page source for this regression test.",
134
+ );
135
+ }
136
+
137
+ test("DAT-protected source maps deny missing and incorrect tokens but allow the configured DAT", async () => {
138
+ const fixture = await startFixtureServer(allowedDat);
139
+ try {
140
+ const missingDatResponse = await fetch(`${fixture.baseUrl}${fixture.mapUrl}`);
141
+ assert.equal(missingDatResponse.status, 404);
142
+ assert.equal(await missingDatResponse.text(), "");
143
+
144
+ const incorrectDatResponse = await fetch(`${fixture.baseUrl}${fixture.mapUrl}`, {
145
+ headers: { cookie: "dat=wrong-source-map-test-dat" },
146
+ });
147
+ assert.equal(incorrectDatResponse.status, 404);
148
+ assert.equal(await incorrectDatResponse.text(), "");
149
+
150
+ const matchingDatResponse = await fetch(`${fixture.baseUrl}${fixture.mapUrl}`, {
151
+ headers: { cookie: `dat=${allowedDat}` },
152
+ });
153
+ await assertValidSourceMap(matchingDatResponse);
154
+ } finally {
155
+ await stopFixtureServer(fixture);
156
+ }
157
+ });
158
+
159
+ test("unrestricted projects can load valid external source maps without a DAT", async () => {
160
+ const fixture = await startFixtureServer(undefined);
161
+ try {
162
+ const response = await fetch(`${fixture.baseUrl}${fixture.mapUrl}`);
163
+ await assertValidSourceMap(response);
164
+ } finally {
165
+ await stopFixtureServer(fixture);
166
+ }
167
+ });