@raypx-kit/devkit 0.0.0-alpha.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.
@@ -0,0 +1,266 @@
1
+ import { compatible, exportPaths, kinds, resolveImport, sourceTarget, targets, workspaceFor } from "./workspaces.mjs";
2
+ import { builtinModules } from "node:module";
3
+ import { existsSync, globSync, readFileSync } from "node:fs";
4
+ import { join, posix } from "node:path";
5
+ import { parseSync, visitorKeys } from "oxc-parser";
6
+ //#region src/architecture.ts
7
+ /** Static import, re-export, dynamic import and `import()` type edges, without executing code. */
8
+ function imports(file, source) {
9
+ const parsed = parseSync(file, source);
10
+ if (parsed.errors.length) throw new Error(`${file}: ${parsed.errors[0].message}`);
11
+ const found = [];
12
+ function walk(node) {
13
+ if ((node.type === "ImportDeclaration" || node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") && node.source) {
14
+ const record = node;
15
+ const typesOnly = record.importKind === "type" || record.exportKind === "type" || !!record.specifiers?.length && record.specifiers.every((s) => s.importKind === "type" || s.exportKind === "type");
16
+ found.push({
17
+ request: node.source.value,
18
+ typesOnly
19
+ });
20
+ }
21
+ if (node.type === "ImportExpression" && node.source.type === "Literal" && typeof node.source.value === "string") found.push({
22
+ request: node.source.value,
23
+ typesOnly: false
24
+ });
25
+ if (node.type === "TSImportType") found.push({
26
+ request: node.source.value,
27
+ typesOnly: true
28
+ });
29
+ const record = node;
30
+ for (const key of visitorKeys[node.type] ?? []) for (const child of Array.isArray(record[key]) ? record[key] : [record[key]]) if (child && typeof child === "object" && "type" in child) walk(child);
31
+ }
32
+ walk(parsed.program);
33
+ return found;
34
+ }
35
+ const builtins = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));
36
+ const testFile = /(?:\.(?:test|spec)\.[cm]?[jt]sx?$|\/(?:testing|test-utils)(?:\.[cm]?[jt]sx?$|\/)|\/(?:tests?|__tests__|test-fixtures)\/)/;
37
+ const codeFile = /\.[cm]?[jt]sx?$/;
38
+ const runtimeFile = /\/(?:src|server|native|app)\//;
39
+ function children(node) {
40
+ const record = node;
41
+ return (visitorKeys[node.type] ?? []).flatMap((key) => (Array.isArray(record[key]) ? record[key] : [record[key]]).filter((child) => !!child && typeof child === "object" && "type" in child));
42
+ }
43
+ /** Resolve local bindings so data named `document` is not mistaken for the DOM. */
44
+ function reachesDomGlobal(file, source) {
45
+ const { program, errors } = parseSync(file, source);
46
+ if (errors.length) throw new Error(`${file}: ${errors[0].message}`);
47
+ const lexicalScopes = /* @__PURE__ */ new Set([
48
+ "BlockStatement",
49
+ "ForStatement",
50
+ "ForInStatement",
51
+ "ForOfStatement",
52
+ "SwitchStatement"
53
+ ]);
54
+ function bind(node, names) {
55
+ if (!node) return;
56
+ if (node.type === "Identifier") names.add(node.name);
57
+ else if (node.type === "RestElement") bind(node.argument, names);
58
+ else if (node.type === "AssignmentPattern") bind(node.left, names);
59
+ else if (node.type === "ArrayPattern") for (const child of node.elements) bind(child, names);
60
+ else if (node.type === "ObjectPattern") for (const property of node.properties) bind(property.type === "RestElement" ? property.argument : property.value, names);
61
+ }
62
+ function declarations(node, names, nested = false) {
63
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression") {
64
+ if (!nested && "id" in node) bind(node.id, names);
65
+ return;
66
+ }
67
+ if (node.type === "VariableDeclaration") {
68
+ if (!nested || node.kind === "var") for (const declaration of node.declarations) bind(declaration.id, names);
69
+ return;
70
+ }
71
+ if (!nested && node.type === "ImportDeclaration") {
72
+ for (const specifier of node.specifiers) bind(specifier.local, names);
73
+ return;
74
+ }
75
+ for (const child of children(node)) declarations(child, names, nested || lexicalScopes.has(child.type));
76
+ }
77
+ function bound(name, scope) {
78
+ return !!scope && (scope.names.has(name) || bound(name, scope.parent));
79
+ }
80
+ const dom = /* @__PURE__ */ new Set([
81
+ "window",
82
+ "document",
83
+ "localStorage",
84
+ "sessionStorage"
85
+ ]);
86
+ function walk(node, parent) {
87
+ let scope = parent;
88
+ if (node.type === "Program" || lexicalScopes.has(node.type) || node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "CatchClause") {
89
+ scope = {
90
+ names: /* @__PURE__ */ new Set(),
91
+ parent
92
+ };
93
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
94
+ for (const parameter of node.params) bind(parameter, scope.names);
95
+ if ("id" in node) bind(node.id, scope.names);
96
+ if (node.body) declarations(node.body, scope.names);
97
+ } else if (node.type === "CatchClause") bind(node.param, scope.names);
98
+ else for (const child of children(node)) declarations(child, scope.names, lexicalScopes.has(child.type));
99
+ }
100
+ if (node.type === "MemberExpression" && node.object.type === "Identifier" && dom.has(node.object.name) && !bound(node.object.name, scope)) return true;
101
+ return children(node).some((child) => walk(child, scope));
102
+ }
103
+ return walk(program, void 0);
104
+ }
105
+ function hostViolation(request, target) {
106
+ if (request.startsWith("bun:")) return target !== "bun";
107
+ if (request.startsWith("node:") || builtins.has(request)) return target !== "node" && target !== "bun";
108
+ if (/^(react-dom|@base-ui\/react)(\/|$)/.test(request)) return target !== "browser";
109
+ if (/^(react-native|expo(?:-[^/]+)?)(\/|$)/.test(request)) return target !== "native";
110
+ if (/^electrobun\/(bun|main)(\/|$)/.test(request)) return target !== "bun";
111
+ return false;
112
+ }
113
+ function resolveCode(index, path) {
114
+ const withoutJs = path.replace(/\.[cm]?js$/, "");
115
+ return [
116
+ path,
117
+ `${path}.ts`,
118
+ `${path}.tsx`,
119
+ `${withoutJs}.ts`,
120
+ `${withoutJs}.tsx`,
121
+ `${path}/index.ts`,
122
+ `${path}/index.tsx`
123
+ ].find((candidate) => codeFile.test(candidate) && existsSync(join(index.root, candidate)));
124
+ }
125
+ /**
126
+ * Checks workspace metadata, exports, the dependency graph and every runtime import:
127
+ * declarations, internal paths, platform targets, tooling and test-only boundaries.
128
+ */
129
+ function checkArchitecture(index) {
130
+ const findings = [...index.findings];
131
+ const graph = /* @__PURE__ */ new Map();
132
+ const parsed = /* @__PURE__ */ new Map();
133
+ const getImports = (file) => {
134
+ let edges = parsed.get(file);
135
+ if (!edges) {
136
+ edges = imports(file, readFileSync(join(index.root, file), "utf8"));
137
+ parsed.set(file, edges);
138
+ }
139
+ return edges;
140
+ };
141
+ for (const workspace of index.workspaces) {
142
+ const { manifest, path, metadata } = workspace;
143
+ if (!metadata || !kinds.includes(metadata.kind) || !targets.includes(metadata.target)) {
144
+ const finding = index.metadataSource === "options" ? `${path}: ${metadata === void 0 ? "missing" : "invalid"} supplied workspace metadata` : `${path}/package.json: invalid or missing ${index.metadataKey} metadata`;
145
+ if (!findings.includes(finding)) findings.push(finding);
146
+ continue;
147
+ }
148
+ if (manifest.type !== "module") findings.push(`${path}: workspaces must be ESM`);
149
+ if (metadata.kind === "app" && !manifest.private) findings.push(`${path}: applications must be private`);
150
+ for (const [entry, target] of Object.entries(metadata.entryTargets ?? {})) if (!manifest.exports?.[entry] || !targets.includes(target)) findings.push(`${path}: invalid entryTargets override ${entry}`);
151
+ for (const [scope, target] of Object.entries(metadata.sourceTargets ?? {})) if (!scope || !targets.includes(target)) findings.push(`${path}: invalid sourceTargets override ${scope}`);
152
+ for (const [entry, source] of Object.entries(metadata.sourceEntries ?? {})) if (!manifest.exports?.[entry] || !source.startsWith("./") || source.split("/").includes("..") || !codeFile.test(source) || (source.includes("*") ? !entry.includes("*") || !globSync(posix.join(path, source), { cwd: index.root }).length : !existsSync(join(index.root, path, source)))) findings.push(`${path}: invalid sourceEntries override ${entry}: ${source}`);
153
+ for (const name of metadata.bundledDependencies ?? []) if (!manifest.devDependencies?.[name] || index.byName.has(name)) findings.push(`${path}: bundled dependency must be an external development dependency: ${name}`);
154
+ if (metadata.kind === "library" && !Object.keys(manifest.exports ?? {}).length) findings.push(`${path}: libraries need explicit exports`);
155
+ for (const [entry, value] of Object.entries(manifest.exports ?? {})) for (const exported of exportPaths(value)) {
156
+ const exists = exported.includes("*") ? globSync(posix.join(path, exported), { cwd: index.root }).length > 0 : existsSync(join(index.root, path, exported));
157
+ if (!exported.startsWith("./") || exported.includes("..") || !metadata.sourceEntries?.[entry] && !exists) findings.push(`${path}: missing or invalid export ${entry}: ${exported}`);
158
+ }
159
+ const edges = /* @__PURE__ */ new Set();
160
+ graph.set(manifest.name, edges);
161
+ for (const [field, deps] of Object.entries({
162
+ dependencies: manifest.dependencies,
163
+ devDependencies: manifest.devDependencies,
164
+ peerDependencies: manifest.peerDependencies
165
+ })) for (const [name, range] of Object.entries(deps ?? {})) {
166
+ const to = index.byName.get(name);
167
+ if (!to) {
168
+ if (range.startsWith("workspace:")) findings.push(`${path}: missing workspace dependency ${name}`);
169
+ continue;
170
+ }
171
+ edges.add(name);
172
+ if (range !== "workspace:*") findings.push(`${path}: internal dependency ${name} must use workspace:*`);
173
+ if (to.metadata?.kind === "app") findings.push(`${path}: applications cannot be imported: ${name}`);
174
+ if (field === "dependencies" && metadata.kind !== "tooling" && to.metadata?.kind === "tooling") findings.push(`${path}: runtime cannot depend on tooling: ${name}`);
175
+ }
176
+ }
177
+ const visited = /* @__PURE__ */ new Set();
178
+ const active = [];
179
+ function visit(name) {
180
+ if (active.includes(name)) {
181
+ findings.push(`workspace dependency cycle: ${[...active.slice(active.indexOf(name)), name].join(" -> ")}`);
182
+ return;
183
+ }
184
+ if (visited.has(name)) return;
185
+ active.push(name);
186
+ for (const next of graph.get(name) ?? []) visit(next);
187
+ active.pop();
188
+ visited.add(name);
189
+ }
190
+ for (const name of graph.keys()) visit(name);
191
+ function checkEdge(workspace, file, edge, platform, transitive) {
192
+ const { request, typesOnly } = edge;
193
+ if (/\/(?:testing|test-utils)(?:\/|$)/.test(request) && !testFile.test(file) && workspace.metadata?.kind !== "tooling") findings.push(`${file}: runtime cannot import test-only entry ${request}`);
194
+ if (!typesOnly && hostViolation(request, platform)) findings.push(`${file}: ${platform} code cannot import ${request}`);
195
+ const resolved = resolveImport(index, workspace, file, request);
196
+ const to = resolved.target;
197
+ if (!to) {
198
+ if (!resolved.paths.length && !request.startsWith(".") && !request.startsWith("#") && !request.startsWith("virtual:") && !request.startsWith("bun:") && !request.startsWith("node:") && !builtins.has(request)) {
199
+ const name = request.startsWith("@") ? request.split("/").slice(0, 2).join("/") : request.split("/")[0];
200
+ if (!(workspace.manifest.dependencies?.[name] || workspace.manifest.peerDependencies?.[name] || workspace.metadata?.bundledDependencies?.includes(name) && workspace.manifest.devDependencies?.[name] || typesOnly && (workspace.manifest.devDependencies?.[name] || workspace.manifest.devDependencies?.[`@types/${name.startsWith("@") ? name.slice(1).replace("/", "__") : name}`]))) findings.push(`${file}: undeclared ${typesOnly ? "type" : "runtime"} dependency ${request}`);
201
+ }
202
+ return resolved.paths;
203
+ }
204
+ if (resolved.internalPath) findings.push(`${file}: internal cross-workspace import ${request}`);
205
+ if (to !== workspace) {
206
+ if (to.metadata?.kind === "app") findings.push(`${file}: application source cannot be imported: ${request}`);
207
+ if (!(workspace.manifest.dependencies?.[to.manifest.name] || workspace.manifest.peerDependencies?.[to.manifest.name]) && !(typesOnly && workspace.manifest.devDependencies?.[to.manifest.name])) findings.push(`${file}: undeclared ${typesOnly ? "type" : "runtime"} dependency ${request}`);
208
+ if (!typesOnly && workspace.metadata?.kind !== "tooling" && to.metadata?.kind === "tooling") findings.push(`${file}: runtime cannot import tooling ${request}`);
209
+ if (!typesOnly && !compatible(platform, resolved.targetPlatform)) findings.push(`${file}: ${platform} entry cannot import ${resolved.targetPlatform} entry ${request}${transitive ? " (transitive)" : ""}`);
210
+ }
211
+ for (const path of resolved.paths) if (!testFile.test(file) && testFile.test(path)) findings.push(`${file}: runtime cannot import test-only source ${request}`);
212
+ return typesOnly ? [] : resolved.paths;
213
+ }
214
+ for (const workspace of index.workspaces) {
215
+ const files = globSync(`${workspace.path}/**/*.{ts,tsx,mts,js,mjs}`, {
216
+ cwd: index.root,
217
+ exclude: [
218
+ "**/node_modules/**",
219
+ "**/dist/**",
220
+ "**/.output/**",
221
+ "**/.hutch/**",
222
+ "**/build/**",
223
+ "**/artifacts/**",
224
+ "**/routeTree.gen.ts",
225
+ "**/*.d.ts"
226
+ ]
227
+ });
228
+ for (const file of files) {
229
+ const normalized = file.replaceAll("\\", "/");
230
+ if (!runtimeFile.test(normalized) || testFile.test(normalized)) continue;
231
+ try {
232
+ for (const edge of getImports(normalized)) checkEdge(workspace, normalized, edge, sourceTarget(workspace, normalized), false);
233
+ } catch (error) {
234
+ findings.push(String(error));
235
+ }
236
+ }
237
+ for (const [entry, value] of Object.entries(workspace.manifest.exports ?? {})) {
238
+ const target = workspace.metadata?.entryTargets?.[entry] ?? workspace.metadata?.target ?? "universal";
239
+ const seen = /* @__PURE__ */ new Set();
240
+ function walk(path) {
241
+ const file = resolveCode(index, path);
242
+ if (!file || seen.has(file)) return;
243
+ seen.add(file);
244
+ const owner = workspaceFor(index, file);
245
+ if (!owner) return;
246
+ try {
247
+ if ((target === "universal" || target === "native") && reachesDomGlobal(file, readFileSync(join(index.root, file), "utf8"))) findings.push(`${workspace.path}: ${entry} reaches DOM global in ${file}`);
248
+ for (const edge of getImports(file)) for (const next of checkEdge(owner, file, edge, target, true)) walk(next);
249
+ } catch (error) {
250
+ findings.push(String(error));
251
+ }
252
+ }
253
+ const source = workspace.metadata?.sourceEntries?.[entry];
254
+ for (const path of source ? [source] : exportPaths(value)) {
255
+ const exported = posix.join(workspace.path, path);
256
+ if (exported.includes("*")) for (const file of globSync(exported, { cwd: index.root })) walk(file.replaceAll("\\", "/"));
257
+ else walk(exported);
258
+ }
259
+ }
260
+ }
261
+ return [...new Set(findings)];
262
+ }
263
+ //#endregion
264
+ export { checkArchitecture, imports };
265
+
266
+ //# sourceMappingURL=architecture.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"architecture.mjs","names":[],"sources":["../src/architecture.ts"],"sourcesContent":["import { existsSync, globSync, readFileSync } from \"node:fs\";\nimport { builtinModules } from \"node:module\";\nimport { join, posix } from \"node:path\";\nimport { parseSync, visitorKeys, type Node } from \"oxc-parser\";\nimport {\n compatible,\n exportPaths,\n kinds,\n resolveImport,\n sourceTarget,\n targets,\n workspaceFor,\n type Target,\n type Workspace,\n type WorkspaceIndex,\n} from \"./workspaces.js\";\n\nexport interface ImportEdge {\n request: string;\n typesOnly: boolean;\n}\n/** Static import, re-export, dynamic import and `import()` type edges, without executing code. */\nexport function imports(file: string, source: string): ImportEdge[] {\n const parsed = parseSync(file, source);\n if (parsed.errors.length) throw new Error(`${file}: ${parsed.errors[0]!.message}`);\n const found: ImportEdge[] = [];\n function walk(node: Node) {\n if (\n (node.type === \"ImportDeclaration\" ||\n node.type === \"ExportNamedDeclaration\" ||\n node.type === \"ExportAllDeclaration\") &&\n node.source\n ) {\n const record = node as unknown as {\n importKind?: string;\n exportKind?: string;\n specifiers?: { importKind?: string; exportKind?: string }[];\n };\n const typesOnly =\n record.importKind === \"type\" ||\n record.exportKind === \"type\" ||\n (!!record.specifiers?.length &&\n record.specifiers.every((s) => s.importKind === \"type\" || s.exportKind === \"type\"));\n found.push({ request: node.source.value, typesOnly });\n }\n if (\n node.type === \"ImportExpression\" &&\n node.source.type === \"Literal\" &&\n typeof node.source.value === \"string\"\n )\n found.push({ request: node.source.value, typesOnly: false });\n if (node.type === \"TSImportType\") found.push({ request: node.source.value, typesOnly: true });\n const record = node as unknown as Record<string, unknown>;\n for (const key of visitorKeys[node.type] ?? []) {\n for (const child of Array.isArray(record[key]) ? record[key] : [record[key]]) {\n if (child && typeof child === \"object\" && \"type\" in child) walk(child as Node);\n }\n }\n }\n walk(parsed.program);\n return found;\n}\n\nconst builtins = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));\nconst testFile =\n /(?:\\.(?:test|spec)\\.[cm]?[jt]sx?$|\\/(?:testing|test-utils)(?:\\.[cm]?[jt]sx?$|\\/)|\\/(?:tests?|__tests__|test-fixtures)\\/)/;\nconst codeFile = /\\.[cm]?[jt]sx?$/;\nconst runtimeFile = /\\/(?:src|server|native|app)\\//;\n\nfunction children(node: Node): Node[] {\n const record = node as unknown as Record<string, unknown>;\n return (visitorKeys[node.type] ?? []).flatMap((key) =>\n (Array.isArray(record[key]) ? record[key] : [record[key]]).filter(\n (child): child is Node => !!child && typeof child === \"object\" && \"type\" in child,\n ),\n );\n}\n\n/** Resolve local bindings so data named `document` is not mistaken for the DOM. */\nfunction reachesDomGlobal(file: string, source: string): boolean {\n const { program, errors } = parseSync(file, source);\n if (errors.length) throw new Error(`${file}: ${errors[0]!.message}`);\n interface Scope {\n names: Set<string>;\n parent: Scope | undefined;\n }\n const lexicalScopes = new Set([\n \"BlockStatement\",\n \"ForStatement\",\n \"ForInStatement\",\n \"ForOfStatement\",\n \"SwitchStatement\",\n ]);\n function bind(node: Node | null | undefined, names: Set<string>) {\n if (!node) return;\n if (node.type === \"Identifier\") names.add(node.name);\n else if (node.type === \"RestElement\") bind(node.argument, names);\n else if (node.type === \"AssignmentPattern\") bind(node.left, names);\n else if (node.type === \"ArrayPattern\") for (const child of node.elements) bind(child, names);\n else if (node.type === \"ObjectPattern\")\n for (const property of node.properties)\n bind(property.type === \"RestElement\" ? property.argument : property.value, names);\n }\n function declarations(node: Node, names: Set<string>, nested = false) {\n if (\n node.type === \"FunctionDeclaration\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"ClassDeclaration\" ||\n node.type === \"ClassExpression\"\n ) {\n if (!nested && \"id\" in node) bind(node.id, names);\n return;\n }\n if (node.type === \"VariableDeclaration\") {\n if (!nested || node.kind === \"var\")\n for (const declaration of node.declarations) bind(declaration.id, names);\n return;\n }\n if (!nested && node.type === \"ImportDeclaration\") {\n for (const specifier of node.specifiers) bind(specifier.local, names);\n return;\n }\n for (const child of children(node))\n declarations(child, names, nested || lexicalScopes.has(child.type));\n }\n function bound(name: string, scope: Scope | undefined): boolean {\n return !!scope && (scope.names.has(name) || bound(name, scope.parent));\n }\n const dom = new Set([\"window\", \"document\", \"localStorage\", \"sessionStorage\"]);\n function walk(node: Node, parent: Scope | undefined): boolean {\n let scope = parent;\n if (\n node.type === \"Program\" ||\n lexicalScopes.has(node.type) ||\n node.type === \"FunctionDeclaration\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"CatchClause\"\n ) {\n scope = { names: new Set(), parent };\n if (\n node.type === \"FunctionDeclaration\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"ArrowFunctionExpression\"\n ) {\n for (const parameter of node.params) bind(parameter, scope.names);\n if (\"id\" in node) bind(node.id, scope.names);\n if (node.body) declarations(node.body, scope.names);\n } else if (node.type === \"CatchClause\") bind(node.param, scope.names);\n else\n for (const child of children(node))\n declarations(child, scope.names, lexicalScopes.has(child.type));\n }\n if (\n node.type === \"MemberExpression\" &&\n node.object.type === \"Identifier\" &&\n dom.has(node.object.name) &&\n !bound(node.object.name, scope)\n )\n return true;\n return children(node).some((child) => walk(child, scope));\n }\n return walk(program, undefined);\n}\n\nfunction hostViolation(request: string, target: Target): boolean {\n if (request.startsWith(\"bun:\")) return target !== \"bun\";\n if (request.startsWith(\"node:\") || builtins.has(request))\n return target !== \"node\" && target !== \"bun\";\n if (/^(react-dom|@base-ui\\/react)(\\/|$)/.test(request)) return target !== \"browser\";\n if (/^(react-native|expo(?:-[^/]+)?)(\\/|$)/.test(request)) return target !== \"native\";\n if (/^electrobun\\/(bun|main)(\\/|$)/.test(request)) return target !== \"bun\";\n return false;\n}\n\nfunction resolveCode(index: WorkspaceIndex, path: string): string | undefined {\n const withoutJs = path.replace(/\\.[cm]?js$/, \"\");\n return [\n path,\n `${path}.ts`,\n `${path}.tsx`,\n `${withoutJs}.ts`,\n `${withoutJs}.tsx`,\n `${path}/index.ts`,\n `${path}/index.tsx`,\n ].find((candidate) => codeFile.test(candidate) && existsSync(join(index.root, candidate)));\n}\n\n/**\n * Checks workspace metadata, exports, the dependency graph and every runtime import:\n * declarations, internal paths, platform targets, tooling and test-only boundaries.\n */\nexport function checkArchitecture(index: WorkspaceIndex): string[] {\n const findings = [...index.findings];\n const graph = new Map<string, Set<string>>();\n const parsed = new Map<string, ImportEdge[]>();\n const getImports = (file: string) => {\n let edges = parsed.get(file);\n if (!edges) {\n edges = imports(file, readFileSync(join(index.root, file), \"utf8\"));\n parsed.set(file, edges);\n }\n return edges;\n };\n for (const workspace of index.workspaces) {\n const { manifest, path, metadata } = workspace;\n if (!metadata || !kinds.includes(metadata.kind) || !targets.includes(metadata.target)) {\n const finding =\n index.metadataSource === \"options\"\n ? `${path}: ${metadata === undefined ? \"missing\" : \"invalid\"} supplied workspace metadata`\n : `${path}/package.json: invalid or missing ${index.metadataKey} metadata`;\n if (!findings.includes(finding)) findings.push(finding);\n continue;\n }\n if (manifest.type !== \"module\") findings.push(`${path}: workspaces must be ESM`);\n if (metadata.kind === \"app\" && !manifest.private)\n findings.push(`${path}: applications must be private`);\n for (const [entry, target] of Object.entries(metadata.entryTargets ?? {})) {\n if (!manifest.exports?.[entry] || !targets.includes(target))\n findings.push(`${path}: invalid entryTargets override ${entry}`);\n }\n for (const [scope, target] of Object.entries(metadata.sourceTargets ?? {})) {\n if (!scope || !targets.includes(target))\n findings.push(`${path}: invalid sourceTargets override ${scope}`);\n }\n for (const [entry, source] of Object.entries(metadata.sourceEntries ?? {})) {\n if (\n !manifest.exports?.[entry] ||\n !source.startsWith(\"./\") ||\n source.split(\"/\").includes(\"..\") ||\n !codeFile.test(source) ||\n (source.includes(\"*\")\n ? !entry.includes(\"*\") || !globSync(posix.join(path, source), { cwd: index.root }).length\n : !existsSync(join(index.root, path, source)))\n )\n findings.push(`${path}: invalid sourceEntries override ${entry}: ${source}`);\n }\n for (const name of metadata.bundledDependencies ?? []) {\n if (!manifest.devDependencies?.[name] || index.byName.has(name))\n findings.push(\n `${path}: bundled dependency must be an external development dependency: ${name}`,\n );\n }\n if (metadata.kind === \"library\" && !Object.keys(manifest.exports ?? {}).length)\n findings.push(`${path}: libraries need explicit exports`);\n for (const [entry, value] of Object.entries(manifest.exports ?? {})) {\n for (const exported of exportPaths(value)) {\n const exists = exported.includes(\"*\")\n ? globSync(posix.join(path, exported), { cwd: index.root }).length > 0\n : existsSync(join(index.root, path, exported));\n if (\n !exported.startsWith(\"./\") ||\n exported.includes(\"..\") ||\n (!metadata.sourceEntries?.[entry] && !exists)\n )\n findings.push(`${path}: missing or invalid export ${entry}: ${exported}`);\n }\n }\n const edges = new Set<string>();\n graph.set(manifest.name, edges);\n for (const [field, deps] of Object.entries({\n dependencies: manifest.dependencies,\n devDependencies: manifest.devDependencies,\n peerDependencies: manifest.peerDependencies,\n })) {\n for (const [name, range] of Object.entries(deps ?? {})) {\n const to = index.byName.get(name);\n if (!to) {\n if (range.startsWith(\"workspace:\"))\n findings.push(`${path}: missing workspace dependency ${name}`);\n continue;\n }\n edges.add(name);\n if (range !== \"workspace:*\")\n findings.push(`${path}: internal dependency ${name} must use workspace:*`);\n if (to.metadata?.kind === \"app\")\n findings.push(`${path}: applications cannot be imported: ${name}`);\n if (\n field === \"dependencies\" &&\n metadata.kind !== \"tooling\" &&\n to.metadata?.kind === \"tooling\"\n )\n findings.push(`${path}: runtime cannot depend on tooling: ${name}`);\n }\n }\n }\n\n const visited = new Set<string>();\n const active: string[] = [];\n function visit(name: string) {\n if (active.includes(name)) {\n findings.push(\n `workspace dependency cycle: ${[...active.slice(active.indexOf(name)), name].join(\" -> \")}`,\n );\n return;\n }\n if (visited.has(name)) return;\n active.push(name);\n for (const next of graph.get(name) ?? []) visit(next);\n active.pop();\n visited.add(name);\n }\n for (const name of graph.keys()) visit(name);\n\n function checkEdge(\n workspace: Workspace,\n file: string,\n edge: ImportEdge,\n platform: Target,\n transitive: boolean,\n ): string[] {\n const { request, typesOnly } = edge;\n if (\n /\\/(?:testing|test-utils)(?:\\/|$)/.test(request) &&\n !testFile.test(file) &&\n workspace.metadata?.kind !== \"tooling\"\n )\n findings.push(`${file}: runtime cannot import test-only entry ${request}`);\n if (!typesOnly && hostViolation(request, platform))\n findings.push(`${file}: ${platform} code cannot import ${request}`);\n const resolved = resolveImport(index, workspace, file, request);\n const to = resolved.target;\n if (!to) {\n if (\n !resolved.paths.length &&\n !request.startsWith(\".\") &&\n !request.startsWith(\"#\") &&\n !request.startsWith(\"virtual:\") &&\n !request.startsWith(\"bun:\") &&\n !request.startsWith(\"node:\") &&\n !builtins.has(request)\n ) {\n const name = request.startsWith(\"@\")\n ? request.split(\"/\").slice(0, 2).join(\"/\")\n : request.split(\"/\")[0]!;\n const declared =\n workspace.manifest.dependencies?.[name] ||\n workspace.manifest.peerDependencies?.[name] ||\n (workspace.metadata?.bundledDependencies?.includes(name) &&\n workspace.manifest.devDependencies?.[name]) ||\n (typesOnly &&\n (workspace.manifest.devDependencies?.[name] ||\n workspace.manifest.devDependencies?.[\n `@types/${name.startsWith(\"@\") ? name.slice(1).replace(\"/\", \"__\") : name}`\n ]));\n if (!declared)\n findings.push(\n `${file}: undeclared ${typesOnly ? \"type\" : \"runtime\"} dependency ${request}`,\n );\n }\n return resolved.paths;\n }\n if (resolved.internalPath) findings.push(`${file}: internal cross-workspace import ${request}`);\n if (to !== workspace) {\n if (to.metadata?.kind === \"app\")\n findings.push(`${file}: application source cannot be imported: ${request}`);\n const declared =\n workspace.manifest.dependencies?.[to.manifest.name] ||\n workspace.manifest.peerDependencies?.[to.manifest.name];\n if (!declared && !(typesOnly && workspace.manifest.devDependencies?.[to.manifest.name]))\n findings.push(\n `${file}: undeclared ${typesOnly ? \"type\" : \"runtime\"} dependency ${request}`,\n );\n if (!typesOnly && workspace.metadata?.kind !== \"tooling\" && to.metadata?.kind === \"tooling\")\n findings.push(`${file}: runtime cannot import tooling ${request}`);\n if (!typesOnly && !compatible(platform, resolved.targetPlatform))\n findings.push(\n `${file}: ${platform} entry cannot import ${resolved.targetPlatform} entry ${request}${transitive ? \" (transitive)\" : \"\"}`,\n );\n }\n for (const path of resolved.paths)\n if (!testFile.test(file) && testFile.test(path))\n findings.push(`${file}: runtime cannot import test-only source ${request}`);\n return typesOnly ? [] : resolved.paths;\n }\n\n for (const workspace of index.workspaces) {\n const files = globSync(`${workspace.path}/**/*.{ts,tsx,mts,js,mjs}`, {\n cwd: index.root,\n exclude: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/.output/**\",\n \"**/.hutch/**\",\n \"**/build/**\",\n \"**/artifacts/**\",\n \"**/routeTree.gen.ts\",\n \"**/*.d.ts\",\n ],\n });\n for (const file of files) {\n const normalized = file.replaceAll(\"\\\\\", \"/\");\n if (!runtimeFile.test(normalized) || testFile.test(normalized)) continue;\n try {\n for (const edge of getImports(normalized))\n checkEdge(workspace, normalized, edge, sourceTarget(workspace, normalized), false);\n } catch (error) {\n findings.push(String(error));\n }\n }\n for (const [entry, value] of Object.entries(workspace.manifest.exports ?? {})) {\n const target =\n workspace.metadata?.entryTargets?.[entry] ?? workspace.metadata?.target ?? \"universal\";\n const seen = new Set<string>();\n function walk(path: string) {\n const file = resolveCode(index, path);\n if (!file || seen.has(file)) return;\n seen.add(file);\n const owner = workspaceFor(index, file);\n if (!owner) return;\n try {\n if (\n (target === \"universal\" || target === \"native\") &&\n reachesDomGlobal(file, readFileSync(join(index.root, file), \"utf8\"))\n )\n findings.push(`${workspace.path}: ${entry} reaches DOM global in ${file}`);\n for (const edge of getImports(file))\n for (const next of checkEdge(owner, file, edge, target, true)) walk(next);\n } catch (error) {\n findings.push(String(error));\n }\n }\n const source = workspace.metadata?.sourceEntries?.[entry];\n for (const path of source ? [source] : exportPaths(value)) {\n const exported = posix.join(workspace.path, path);\n if (exported.includes(\"*\"))\n for (const file of globSync(exported, { cwd: index.root }))\n walk(file.replaceAll(\"\\\\\", \"/\"));\n else walk(exported);\n }\n }\n }\n return [...new Set(findings)];\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ //#region src/artifact.d.ts
2
+ /** A relocated probe must not quietly copy dependencies from outside the artifact. */
3
+ export declare function stageArtifact(source: string, destination: string): Promise<void>;
4
+ export interface ArtifactProbe {
5
+ /** Label used in errors and the temporary directory name. */
6
+ name: string;
7
+ /** Output directory, staged as `.output` in a temporary directory. */
8
+ output: string;
9
+ /** Node executable, normally from `pinnedNode`. */
10
+ node: string;
11
+ /** Entry point inside the staged directory. */
12
+ entry?: string;
13
+ /** Path that returns a successful response once the server is ready. */
14
+ healthPath?: string;
15
+ /** Extra environment for the server; HOST and PORT are always set. */
16
+ environment?: (base: string) => Record<string, string>;
17
+ /** Runs against the started server. */
18
+ check: (base: string) => Promise<void>;
19
+ /** Server output that fails the probe even when every check passed. */
20
+ failOnOutput?: readonly string[];
21
+ }
22
+ /** Starts a relocated Node server output on a free port and probes it while it runs. */
23
+ export declare function probeArtifact({ name, output, node, entry, healthPath, environment, check, failOnOutput }: ArtifactProbe): Promise<void>;
24
+ //#endregion
25
+ //# sourceMappingURL=artifact.d.mts.map
@@ -0,0 +1,104 @@
1
+ import { isAbsolute, join, relative, sep } from "node:path";
2
+ import { spawn } from "node:child_process";
3
+ import { once } from "node:events";
4
+ import { cp, mkdtemp, readdir, readlink, realpath, rm } from "node:fs/promises";
5
+ import { createServer } from "node:net";
6
+ import { tmpdir } from "node:os";
7
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
8
+ //#region src/artifact.ts
9
+ /** A relocated probe must not quietly copy dependencies from outside the artifact. */
10
+ async function stageArtifact(source, destination) {
11
+ const root = await realpath(source);
12
+ async function validate(directory) {
13
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
14
+ const path = join(directory, entry.name);
15
+ if (entry.isSymbolicLink()) {
16
+ const resolved = await realpath(path);
17
+ const within = relative(root, resolved);
18
+ if (within === ".." || within.startsWith(`..${sep}`) || isAbsolute(within)) throw new Error(`Artifact link escapes its output: ${relative(root, path)}`);
19
+ if (isAbsolute(await readlink(path))) throw new Error(`Artifact link must be relative to survive relocation: ${relative(root, path)}`);
20
+ } else if (entry.isDirectory()) await validate(path);
21
+ }
22
+ }
23
+ await validate(root);
24
+ await cp(root, destination, {
25
+ recursive: true,
26
+ dereference: false,
27
+ verbatimSymlinks: true
28
+ });
29
+ }
30
+ async function freePort() {
31
+ return new Promise((resolve, reject) => {
32
+ const server = createServer();
33
+ server.once("error", reject);
34
+ server.listen(0, "127.0.0.1", () => {
35
+ const address = server.address();
36
+ const port = typeof address === "object" && address ? address.port : 0;
37
+ server.close(() => resolve(port));
38
+ });
39
+ });
40
+ }
41
+ /** Starts a relocated Node server output on a free port and probes it while it runs. */
42
+ async function probeArtifact({ name, output, node, entry = ".output/server/index.mjs", healthPath = "/health", environment = () => ({}), check, failOnOutput = [] }) {
43
+ const stage = await mkdtemp(join(tmpdir(), `devkit-${name}-`));
44
+ const port = await freePort();
45
+ const base = `http://127.0.0.1:${port}`;
46
+ let child;
47
+ try {
48
+ await stageArtifact(output, join(stage, ".output"));
49
+ child = spawn(node, [entry], {
50
+ cwd: stage,
51
+ stdio: [
52
+ "ignore",
53
+ "pipe",
54
+ "pipe"
55
+ ],
56
+ env: {
57
+ ...process.env,
58
+ HOST: "127.0.0.1",
59
+ PORT: String(port),
60
+ ...environment(base)
61
+ }
62
+ });
63
+ let logs = "";
64
+ const capture = (chunk) => {
65
+ logs = `${logs}${chunk.toString()}`.slice(-8e3);
66
+ };
67
+ child.stdout?.on("data", capture);
68
+ child.stderr?.on("data", capture);
69
+ let startupError;
70
+ child.once("error", (error) => {
71
+ startupError = error;
72
+ });
73
+ let ready = false;
74
+ for (let attempt = 0; attempt < 100; attempt++) {
75
+ if (startupError) throw startupError;
76
+ if (child.exitCode !== null) throw new Error(`${name} exited during startup: ${logs}`);
77
+ try {
78
+ ready = (await fetch(`${base}${healthPath}`, { signal: AbortSignal.timeout(300) })).ok;
79
+ } catch {}
80
+ if (ready) break;
81
+ await setTimeout$1(100);
82
+ }
83
+ if (!ready) throw new Error(`${name} startup deadline exceeded: ${logs}`);
84
+ await check(base);
85
+ if (failOnOutput.some((text) => logs.includes(text))) throw new Error(`${name} reported a server error: ${logs}`);
86
+ } finally {
87
+ if (child && child.exitCode === null && child.pid) {
88
+ const closed = once(child, "close");
89
+ child.kill("SIGTERM");
90
+ const timer = setTimeout(() => child?.kill("SIGKILL"), 3e3);
91
+ timer.unref();
92
+ await closed;
93
+ clearTimeout(timer);
94
+ }
95
+ await rm(stage, {
96
+ recursive: true,
97
+ force: true
98
+ });
99
+ }
100
+ }
101
+ //#endregion
102
+ export { probeArtifact, stageArtifact };
103
+
104
+ //# sourceMappingURL=artifact.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact.mjs","names":["delay"],"sources":["../src/artifact.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { once } from \"node:events\";\nimport { cp, mkdtemp, readdir, readlink, realpath, rm } from \"node:fs/promises\";\nimport { createServer } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { isAbsolute, join, relative, sep } from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\n/** A relocated probe must not quietly copy dependencies from outside the artifact. */\nexport async function stageArtifact(source: string, destination: string) {\n const root = await realpath(source);\n async function validate(directory: string): Promise<void> {\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n const path = join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n const resolved = await realpath(path);\n const within = relative(root, resolved);\n if (within === \"..\" || within.startsWith(`..${sep}`) || isAbsolute(within))\n throw new Error(`Artifact link escapes its output: ${relative(root, path)}`);\n if (isAbsolute(await readlink(path)))\n throw new Error(\n `Artifact link must be relative to survive relocation: ${relative(root, path)}`,\n );\n } else if (entry.isDirectory()) await validate(path);\n }\n }\n await validate(root);\n await cp(root, destination, { recursive: true, dereference: false, verbatimSymlinks: true });\n}\n\nexport interface ArtifactProbe {\n /** Label used in errors and the temporary directory name. */\n name: string;\n /** Output directory, staged as `.output` in a temporary directory. */\n output: string;\n /** Node executable, normally from `pinnedNode`. */\n node: string;\n /** Entry point inside the staged directory. */\n entry?: string;\n /** Path that returns a successful response once the server is ready. */\n healthPath?: string;\n /** Extra environment for the server; HOST and PORT are always set. */\n environment?: (base: string) => Record<string, string>;\n /** Runs against the started server. */\n check: (base: string) => Promise<void>;\n /** Server output that fails the probe even when every check passed. */\n failOnOutput?: readonly string[];\n}\n\nasync function freePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n const port = typeof address === \"object\" && address ? address.port : 0;\n server.close(() => resolve(port));\n });\n });\n}\n\n/** Starts a relocated Node server output on a free port and probes it while it runs. */\nexport async function probeArtifact({\n name,\n output,\n node,\n entry = \".output/server/index.mjs\",\n healthPath = \"/health\",\n environment = () => ({}),\n check,\n failOnOutput = [],\n}: ArtifactProbe): Promise<void> {\n const stage = await mkdtemp(join(tmpdir(), `devkit-${name}-`));\n const port = await freePort();\n const base = `http://127.0.0.1:${port}`;\n let child: ReturnType<typeof spawn> | undefined;\n try {\n await stageArtifact(output, join(stage, \".output\"));\n child = spawn(node, [entry], {\n cwd: stage,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: { ...process.env, HOST: \"127.0.0.1\", PORT: String(port), ...environment(base) },\n });\n let logs = \"\";\n const capture = (chunk: Buffer | string) => {\n logs = `${logs}${chunk.toString()}`.slice(-8000);\n };\n child.stdout?.on(\"data\", capture);\n child.stderr?.on(\"data\", capture);\n let startupError: Error | undefined;\n child.once(\"error\", (error) => {\n startupError = error;\n });\n let ready = false;\n for (let attempt = 0; attempt < 100; attempt++) {\n if (startupError) throw startupError;\n if (child.exitCode !== null) throw new Error(`${name} exited during startup: ${logs}`);\n try {\n ready = (await fetch(`${base}${healthPath}`, { signal: AbortSignal.timeout(300) })).ok;\n } catch {\n /* Wait for the listener. */\n }\n if (ready) break;\n await delay(100);\n }\n if (!ready) throw new Error(`${name} startup deadline exceeded: ${logs}`);\n await check(base);\n if (failOnOutput.some((text) => logs.includes(text)))\n throw new Error(`${name} reported a server error: ${logs}`);\n } finally {\n if (child && child.exitCode === null && child.pid) {\n const closed = once(child, \"close\");\n child.kill(\"SIGTERM\");\n const timer = setTimeout(() => child?.kill(\"SIGKILL\"), 3000);\n timer.unref();\n await closed;\n clearTimeout(timer);\n }\n await rm(stage, { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region src/catalogs.d.ts
2
+ /** A message catalog: nested objects whose leaves are strings. */
3
+ export interface CatalogTree {
4
+ readonly [key: string]: string | CatalogTree;
5
+ }
6
+ /**
7
+ * Reads a `.json` catalog or the static default export of a TS/JS module without
8
+ * executing it. Only object literals with string values, `as const` and `satisfies`
9
+ * are accepted; anything computed is rejected.
10
+ */
11
+ export declare function readStaticCatalog(path: string, source?: string): CatalogTree;
12
+ //#endregion
13
+ //# sourceMappingURL=catalogs.d.mts.map
@@ -0,0 +1,49 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { parseSync } from "oxc-parser";
3
+ //#region src/catalogs.ts
4
+ /**
5
+ * Reads a `.json` catalog or the static default export of a TS/JS module without
6
+ * executing it. Only object literals with string values, `as const` and `satisfies`
7
+ * are accepted; anything computed is rejected.
8
+ */
9
+ function readStaticCatalog(path, source = readFileSync(path, "utf8")) {
10
+ const object = () => Object.create(null);
11
+ function readNode(value) {
12
+ if (!value || typeof value !== "object") throw new Error(`${path}: catalogs must contain static string objects`);
13
+ const node = value;
14
+ if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") return readNode(node.expression);
15
+ if (node.type === "Literal" && typeof node.value === "string") return node.value;
16
+ if (node.type !== "ObjectExpression") throw new Error(`${path}: catalogs must contain static string objects`);
17
+ const result = object();
18
+ for (const property of node.properties) {
19
+ if (property.type !== "Property" || property.computed) throw new Error(`${path}: dynamic catalog keys are unsupported`);
20
+ const key = property.key;
21
+ const name = typeof key.value === "string" ? key.value : key.name;
22
+ if (!name) throw new Error(`${path}: invalid catalog key`);
23
+ result[name] = readNode(property.value);
24
+ }
25
+ return result;
26
+ }
27
+ function readJson(value) {
28
+ if (typeof value === "string") return value;
29
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${path}: expected static string objects`);
30
+ const result = object();
31
+ for (const [key, child] of Object.entries(value)) result[key] = readJson(child);
32
+ return result;
33
+ }
34
+ let result;
35
+ if (path.endsWith(".json")) result = readJson(JSON.parse(source));
36
+ else {
37
+ const parsed = parseSync(path, source);
38
+ if (parsed.errors.length) throw new Error(`${path}: ${parsed.errors[0].message}`);
39
+ const declaration = parsed.program.body.find((node) => node.type === "ExportDefaultDeclaration");
40
+ if (!declaration || declaration.type !== "ExportDefaultDeclaration") throw new Error(`${path}: expected a default static catalog`);
41
+ result = readNode(declaration.declaration);
42
+ }
43
+ if (typeof result === "string") throw new Error(`${path}: expected a catalog object`);
44
+ return result;
45
+ }
46
+ //#endregion
47
+ export { readStaticCatalog };
48
+
49
+ //# sourceMappingURL=catalogs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogs.mjs","names":[],"sources":["../src/catalogs.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { parseSync } from \"oxc-parser\";\n\n/** A message catalog: nested objects whose leaves are strings. */\nexport interface CatalogTree {\n readonly [key: string]: string | CatalogTree;\n}\n\n/**\n * Reads a `.json` catalog or the static default export of a TS/JS module without\n * executing it. Only object literals with string values, `as const` and `satisfies`\n * are accepted; anything computed is rejected.\n */\nexport function readStaticCatalog(path: string, source = readFileSync(path, \"utf8\")): CatalogTree {\n const object = (): Record<string, string | CatalogTree> => Object.create(null);\n function readNode(value: unknown): string | CatalogTree {\n if (!value || typeof value !== \"object\")\n throw new Error(`${path}: catalogs must contain static string objects`);\n const node = value as Record<string, unknown>;\n if (node.type === \"TSAsExpression\" || node.type === \"TSSatisfiesExpression\")\n return readNode(node.expression);\n if (node.type === \"Literal\" && typeof node.value === \"string\") return node.value;\n if (node.type !== \"ObjectExpression\")\n throw new Error(`${path}: catalogs must contain static string objects`);\n const result = object();\n for (const property of node.properties as Record<string, unknown>[]) {\n if (property.type !== \"Property\" || property.computed)\n throw new Error(`${path}: dynamic catalog keys are unsupported`);\n const key = property.key as { value?: unknown; name?: string };\n const name = typeof key.value === \"string\" ? key.value : key.name;\n if (!name) throw new Error(`${path}: invalid catalog key`);\n result[name] = readNode(property.value);\n }\n return result;\n }\n function readJson(value: unknown): string | CatalogTree {\n if (typeof value === \"string\") return value;\n if (!value || typeof value !== \"object\" || Array.isArray(value))\n throw new Error(`${path}: expected static string objects`);\n const result = object();\n for (const [key, child] of Object.entries(value)) result[key] = readJson(child);\n return result;\n }\n let result: string | CatalogTree;\n if (path.endsWith(\".json\")) result = readJson(JSON.parse(source));\n else {\n const parsed = parseSync(path, source);\n if (parsed.errors.length) throw new Error(`${path}: ${parsed.errors[0]!.message}`);\n const declaration = parsed.program.body.find(\n (node) => node.type === \"ExportDefaultDeclaration\",\n );\n if (!declaration || declaration.type !== \"ExportDefaultDeclaration\")\n throw new Error(`${path}: expected a default static catalog`);\n result = readNode(declaration.declaration);\n }\n if (typeof result === \"string\") throw new Error(`${path}: expected a catalog object`);\n return result;\n}\n"],"mappings":";;;;;;;;AAaA,SAAgB,kBAAkB,MAAc,SAAS,aAAa,MAAM,MAAM,GAAgB;CAChG,MAAM,eAAqD,OAAO,OAAO,IAAI;CAC7E,SAAS,SAAS,OAAsC;EACtD,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,MAAM,IAAI,MAAM,GAAG,KAAK,8CAA8C;EACxE,MAAM,OAAO;EACb,IAAI,KAAK,SAAS,oBAAoB,KAAK,SAAS,yBAClD,OAAO,SAAS,KAAK,UAAU;EACjC,IAAI,KAAK,SAAS,aAAa,OAAO,KAAK,UAAU,UAAU,OAAO,KAAK;EAC3E,IAAI,KAAK,SAAS,oBAChB,MAAM,IAAI,MAAM,GAAG,KAAK,8CAA8C;EACxE,MAAM,SAAS,OAAO;EACtB,KAAK,MAAM,YAAY,KAAK,YAAyC;GACnE,IAAI,SAAS,SAAS,cAAc,SAAS,UAC3C,MAAM,IAAI,MAAM,GAAG,KAAK,uCAAuC;GACjE,MAAM,MAAM,SAAS;GACrB,MAAM,OAAO,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,IAAI;GAC7D,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,GAAG,KAAK,sBAAsB;GACzD,OAAO,QAAQ,SAAS,SAAS,KAAK;EACxC;EACA,OAAO;CACT;CACA,SAAS,SAAS,OAAsC;EACtD,IAAI,OAAO,UAAU,UAAU,OAAO;EACtC,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAC5D,MAAM,IAAI,MAAM,GAAG,KAAK,iCAAiC;EAC3D,MAAM,SAAS,OAAO;EACtB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG,OAAO,OAAO,SAAS,KAAK;EAC9E,OAAO;CACT;CACA,IAAI;CACJ,IAAI,KAAK,SAAS,OAAO,GAAG,SAAS,SAAS,KAAK,MAAM,MAAM,CAAC;MAC3D;EACH,MAAM,SAAS,UAAU,MAAM,MAAM;EACrC,IAAI,OAAO,OAAO,QAAQ,MAAM,IAAI,MAAM,GAAG,KAAK,IAAI,OAAO,OAAO,EAAE,CAAE,SAAS;EACjF,MAAM,cAAc,OAAO,QAAQ,KAAK,MACrC,SAAS,KAAK,SAAS,0BAC1B;EACA,IAAI,CAAC,eAAe,YAAY,SAAS,4BACvC,MAAM,IAAI,MAAM,GAAG,KAAK,oCAAoC;EAC9D,SAAS,SAAS,YAAY,WAAW;CAC3C;CACA,IAAI,OAAO,WAAW,UAAU,MAAM,IAAI,MAAM,GAAG,KAAK,4BAA4B;CACpF,OAAO;AACT"}
@@ -0,0 +1,5 @@
1
+ //#region src/docs.d.ts
2
+ /** Reports relative Markdown links whose target file does not exist. */
3
+ export declare function checkDocumentLinks(root: string, files: readonly string[]): string[];
4
+ //#endregion
5
+ //# sourceMappingURL=docs.d.mts.map
package/dist/docs.mjs ADDED
@@ -0,0 +1,21 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ //#region src/docs.ts
4
+ /** Reports relative Markdown links whose target file does not exist. */
5
+ function checkDocumentLinks(root, files) {
6
+ const findings = [];
7
+ for (const file of files) {
8
+ const source = readFileSync(join(root, file), "utf8");
9
+ for (const match of source.matchAll(/\]\(([^\s)]+)(?:\s+"[^"]*")?\)/g)) {
10
+ const url = match[1];
11
+ if (/^(?:[a-z]+:|#|\/)/i.test(url)) continue;
12
+ const path = url.split("#")[0].split("?")[0];
13
+ if (path && !existsSync(resolve(root, file, "..", decodeURIComponent(path)))) findings.push(`${file}: broken documentation link ${url}`);
14
+ }
15
+ }
16
+ return findings;
17
+ }
18
+ //#endregion
19
+ export { checkDocumentLinks };
20
+
21
+ //# sourceMappingURL=docs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.mjs","names":[],"sources":["../src/docs.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\n\n/** Reports relative Markdown links whose target file does not exist. */\nexport function checkDocumentLinks(root: string, files: readonly string[]): string[] {\n const findings: string[] = [];\n for (const file of files) {\n const source = readFileSync(join(root, file), \"utf8\");\n for (const match of source.matchAll(/\\]\\(([^\\s)]+)(?:\\s+\"[^\"]*\")?\\)/g)) {\n const url = match[1]!;\n if (/^(?:[a-z]+:|#|\\/)/i.test(url)) continue;\n const path = url.split(\"#\")[0]!.split(\"?\")[0]!;\n if (path && !existsSync(resolve(root, file, \"..\", decodeURIComponent(path))))\n findings.push(`${file}: broken documentation link ${url}`);\n }\n }\n return findings;\n}\n"],"mappings":";;;;AAIA,SAAgB,mBAAmB,MAAc,OAAoC;CACnF,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,aAAa,KAAK,MAAM,IAAI,GAAG,MAAM;EACpD,KAAK,MAAM,SAAS,OAAO,SAAS,iCAAiC,GAAG;GACtE,MAAM,MAAM,MAAM;GAClB,IAAI,qBAAqB,KAAK,GAAG,GAAG;GACpC,MAAM,OAAO,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE,CAAE,MAAM,GAAG,CAAC,CAAC;GAC3C,IAAI,QAAQ,CAAC,WAAW,QAAQ,MAAM,MAAM,MAAM,mBAAmB,IAAI,CAAC,CAAC,GACzE,SAAS,KAAK,GAAG,KAAK,8BAA8B,KAAK;EAC7D;CACF;CACA,OAAO;AACT"}
@@ -0,0 +1,23 @@
1
+ //#region src/process.d.ts
2
+ /**
3
+ * Copies the current environment and layers local dotenv files beneath it; values
4
+ * already set in the process win. Loaded secrets are never printed.
5
+ */
6
+ export declare function developmentEnvironment(root: string, files?: readonly string[]): NodeJS.ProcessEnv;
7
+ /** Runs a command with inherited output, forwarding interrupts and rejecting on failure. */
8
+ export declare function run(command: string, args: string[], cwd: string, env?: NodeJS.ProcessEnv): Promise<void>;
9
+ export interface TurboOptions {
10
+ /** Load development dotenv files for these tasks. */
11
+ developmentTasks?: readonly string[];
12
+ /** Environment entries added to every task, such as a cache platform key. */
13
+ env?: Record<string, string>;
14
+ /** Only when this variable is `"true"` are remote cache credentials passed through. */
15
+ trustedCacheEnv?: string;
16
+ }
17
+ /**
18
+ * Runs a Turbo task under Bun. Untrusted runs drop remote cache credentials and use
19
+ * the local cache only, so forks never read or write the shared remote cache.
20
+ */
21
+ export declare function turbo(root: string, task: string, args?: string[], { developmentTasks, env: extra, trustedCacheEnv }?: TurboOptions): Promise<void>;
22
+ //#endregion
23
+ //# sourceMappingURL=process.d.mts.map