Repository navigation
Expand file tree
/
Copy patheditor.mjs
More file actions
156 lines (153 loc) · 5.18 KB
/
Copy patheditor.mjs
File metadata and controls
156 lines (153 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// GRIDA-SEC-011 — isolated editor snapshot, fixture environment, and network guard.
import { spawn, execFileSync } from "node:child_process";
import {
copyFile,
mkdir,
mkdtemp,
readFile,
symlink,
writeFile,
} from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { parseEnv } from "node:util";
import { readState } from "./stack.mjs";
import { guards } from "./guards.mjs";
const scripts = path.dirname(fileURLToPath(import.meta.url));
// Runs a source snapshot with a private HOME and build output. Next's .env loader
// never sees the developer's files; symlinks are limited to installed dependencies.
async function main() {
const args = process.argv.slice(2);
if (args.length !== 2 || args[0] !== "--state") {
throw new Error(
"Usage: node scripts/auth-local/editor.mjs --state <fixture.json>"
);
}
const state = await readState(path.resolve(args[1]));
const setup = JSON.parse(await readFile(state.setupPath, "utf8"));
const fixtureEnv = parseEnv(await readFile(state.editorEnvPath, "utf8"));
if (
setup.editorOrigin !== "http://127.0.0.1:3041" ||
setup.apiUrl !== "http://127.0.0.1:55431" ||
fixtureEnv.NEXT_PUBLIC_SUPABASE_URL !== setup.apiUrl ||
fixtureEnv.GRIDA_OAUTH_ISSUER !== `${setup.apiUrl}/auth/v1` ||
fixtureEnv.GRIDA_OAUTH_ORIGIN !== setup.editorOrigin
) {
throw new Error("Expected the isolated local auth fixture origins");
}
const cache = path.join(state.repoRoot, ".cache", "auth-local");
await mkdir(cache, { recursive: true, mode: 0o700 });
const workspace = await mkdtemp(path.join(cache, "editor-"));
const editor = path.join(workspace, "editor");
await mkdir(editor, { mode: 0o700 });
const files = execFileSync(
"/usr/bin/git",
["ls-files", "--cached", "--others", "--exclude-standard", "-z", "editor"],
{
cwd: state.repoRoot,
encoding: "utf8",
env: guards.childEnv(state),
}
)
.split("\0")
.filter(Boolean);
for (const file of new Set(files)) {
const relative = path.relative("editor", file);
if (relative.startsWith("..") || path.isAbsolute(relative))
throw new Error("Invalid source path");
if (
relative
.split(path.sep)
.some(
(part) =>
part.startsWith(".env") ||
[
".next",
"node_modules",
"test-results",
"playwright-report",
].includes(part)
)
)
continue;
const target = path.join(editor, relative);
await mkdir(path.dirname(target), { recursive: true });
try {
await copyFile(path.join(state.repoRoot, file), target);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
await symlink(
path.join(state.repoRoot, "editor", "node_modules"),
path.join(editor, "node_modules"),
"dir"
);
await symlink(
path.join(state.repoRoot, "node_modules"),
path.join(workspace, "node_modules"),
"dir"
);
await writeFile(
path.join(editor, "next.config.ts"),
`import original from ${JSON.stringify(path.join(state.repoRoot, "editor", "next.config.ts"))};
export default { ...original, turbopack: { ...original.turbopack, root: ${JSON.stringify(state.repoRoot)} }, logging: false };
`
);
// Auth tests do not need Google Fonts. Next's own test hook supplies local CSS
// while preserving the real layouts, login form, and consent rendering.
const fonts = path.join(workspace, "fonts.cjs");
await writeFile(
fonts,
`module.exports = ${JSON.stringify({
"https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap":
"@font-face { font-family: 'Inter'; src: local('Arial'); font-weight: 100 900; }",
})};\n`
);
const env = {
PATH: [
path.dirname(process.execPath),
"/usr/local/bin",
"/usr/bin",
"/bin",
].join(path.delimiter),
HOME: state.home,
TMPDIR: state.root,
LANG: "en_US.UTF-8",
...fixtureEnv,
NODE_ENV: "development",
NEXT_TELEMETRY_DISABLED: "1",
NEXT_PUBLIC_GRIDA_USE_TELEMETRY: "0",
NEXT_PUBLIC_DOCS_URL: setup.editorOrigin,
NEXT_PUBLIC_BLOG_URL: setup.editorOrigin,
NEXT_FONT_GOOGLE_MOCKED_RESPONSES: fonts,
NODE_OPTIONS: `--require=${JSON.stringify(path.join(scripts, "network.cjs"))}`,
};
const require = createRequire(
path.join(state.repoRoot, "editor", "package.json")
);
const binary = require.resolve("next/dist/bin/next");
console.log(`Local auth editor: ${setup.editorOrigin}`);
console.log(`Isolated source/build directory: ${editor}`);
const child = spawn(
process.execPath,
[binary, "dev", "--hostname", "127.0.0.1", "--port", "3041"],
{ cwd: editor, env, stdio: "inherit" }
);
for (const signal of ["SIGINT", "SIGTERM"])
process.once(signal, () => child.kill(signal));
child.on("error", () => {
console.error("Unable to start local auth editor");
process.exitCode = 1;
});
child.on("exit", (code) => {
process.exitCode = code ?? 1;
});
}
main().catch(() => {
console.error(
"Local auth editor failed; verify the prepared fixture and dependency installation."
);
process.exitCode = 1;
});