vibe-coding-master 0.8.12 → 0.9.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.
- package/README.md +7 -3
- package/dist/backend/cli/install-vcm-harness.js +57 -18
- package/dist/backend/gateway/gateway-service.js +5 -3
- package/dist/backend/runtime/terminal-submit.js +1 -0
- package/dist/backend/server.js +14 -4
- package/dist/backend/services/app-settings-service.js +3 -2
- package/dist/backend/services/auto-memory-service.js +6 -4
- package/dist/backend/services/claude-hook-service.js +6 -0
- package/dist/backend/services/claude-transcript-service.js +2 -13
- package/dist/backend/services/claude-version-check.js +46 -0
- package/dist/backend/services/gate-review-service.js +47 -8
- package/dist/backend/services/harness-service.js +41 -20
- package/dist/backend/services/message-service.js +30 -6
- package/dist/backend/services/pm-user-question.js +2 -1
- package/dist/backend/services/project-service.js +8 -2
- package/dist/backend/services/runtime-recovery-service.js +36 -9
- package/dist/backend/services/session-service.js +10 -19
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/terminal-process-exit-service.js +4 -1
- package/dist/backend/services/workflow-control-service.js +55 -11
- package/dist/backend/templates/harness/architect-agent.js +2 -1
- package/dist/backend/templates/harness/architect-evidence-worker-agent.js +2 -1
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +8 -2
- package/dist/backend/templates/harness/architect-validation-worker-agent.js +2 -1
- package/dist/backend/templates/harness/architect-worker-frontmatter.js +33 -0
- package/dist/backend/templates/harness/check-scaffold-ledger.js +480 -511
- package/dist/backend/templates/harness/coder-worker-agent.js +1 -0
- package/dist/backend/templates/harness/gate-review.js +676 -643
- package/dist/backend/templates/harness/node-tool-runtime.js +102 -0
- package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +49 -55
- package/dist/backend/templates/harness/restart-architect-skill.js +49 -58
- package/dist/backend/templates/harness/vcm-ask-user-skill.js +57 -59
- package/dist/backend/templates/harness/vcm-propose-memory-skill.js +4 -0
- package/dist/backend/templates/harness/vcm-report-harness-issue-skill.js +6 -2
- package/dist/backend/templates/harness/vcm-task-state-skill.js +66 -72
- package/dist/shared/types/app-settings.js +31 -0
- package/dist-frontend/assets/index-CShYYQQa.css +32 -0
- package/dist-frontend/assets/{index-DqANomt1.js → index-DOvvS76Q.js} +41 -41
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/check-durable-docs +332 -298
- package/scripts/harness-tools/generate-module-index +1047 -512
- package/scripts/harness-tools/generate-public-surface +1231 -1069
- package/scripts/harness-tools/package.json +3 -0
- package/scripts/harness-tools/run-long-check +608 -531
- package/scripts/harness-tools/vcm-artifact +181 -113
- package/scripts/harness-tools/vcm-bash-guard +839 -605
- package/scripts/harness-tools/vcm-subagent-guard +67 -63
- package/scripts/harness-tools/watch-job +313 -238
- package/dist-frontend/assets/index-xgiQeUD5.css +0 -32
|
@@ -1,521 +1,1056 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
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
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def maybe_find_node_root(project_root: Path) -> Path | None:
|
|
175
|
-
if (project_root / "package.json").is_file():
|
|
176
|
-
return project_root
|
|
177
|
-
|
|
178
|
-
for child in sorted(project_root.iterdir(), key=lambda path: path.name):
|
|
179
|
-
if child.is_dir() and (child / "package.json").is_file():
|
|
180
|
-
return child
|
|
181
|
-
|
|
182
|
-
return None
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def workspace_patterns(package_data: dict) -> list[str]:
|
|
186
|
-
workspaces = package_data.get("workspaces")
|
|
187
|
-
if isinstance(workspaces, list):
|
|
188
|
-
return [pattern for pattern in workspaces if isinstance(pattern, str)]
|
|
189
|
-
if isinstance(workspaces, dict):
|
|
190
|
-
packages = workspaces.get("packages", [])
|
|
191
|
-
if isinstance(packages, list):
|
|
192
|
-
return [pattern for pattern in packages if isinstance(pattern, str)]
|
|
193
|
-
return []
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def node_workspace_manifests(node_root: Path) -> list[Path]:
|
|
197
|
-
root_manifest = node_root / "package.json"
|
|
198
|
-
root_data = load_json(root_manifest)
|
|
199
|
-
patterns = workspace_patterns(root_data)
|
|
200
|
-
|
|
201
|
-
if not patterns:
|
|
202
|
-
return [root_manifest]
|
|
203
|
-
|
|
204
|
-
manifests: list[Path] = []
|
|
205
|
-
seen: set[Path] = set()
|
|
206
|
-
|
|
207
|
-
for pattern in patterns:
|
|
208
|
-
if pattern.startswith("!") or pattern.startswith("/") or ".." in Path(pattern).parts:
|
|
209
|
-
continue
|
|
210
|
-
for candidate in sorted(node_root.glob(pattern), key=lambda path: path.as_posix()):
|
|
211
|
-
manifest = candidate / "package.json"
|
|
212
|
-
if not candidate.is_dir() or not manifest.is_file() or manifest in seen:
|
|
213
|
-
continue
|
|
214
|
-
if any(part in NODE_EXCLUDED_DIRS for part in relative_path(candidate, node_root).split("/")):
|
|
215
|
-
continue
|
|
216
|
-
manifests.append(manifest)
|
|
217
|
-
seen.add(manifest)
|
|
218
|
-
|
|
219
|
-
if root_data.get("name") and (root_data.get("main") or root_data.get("exports") or (node_root / "src").is_dir()):
|
|
220
|
-
manifests.insert(0, root_manifest)
|
|
221
|
-
|
|
222
|
-
return manifests
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def node_dependency_names(manifest: Path) -> set[str]:
|
|
226
|
-
data = load_json(manifest)
|
|
227
|
-
names: set[str] = set()
|
|
228
|
-
for table_name in NODE_DEPENDENCY_TABLES:
|
|
229
|
-
table = data.get(table_name, {})
|
|
230
|
-
if isinstance(table, dict):
|
|
231
|
-
names.update(name for name in table.keys() if isinstance(name, str))
|
|
232
|
-
return names
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def node_source_files_under(module_dir: Path, project_root: Path) -> tuple[list[str], list[str]]:
|
|
236
|
-
source_root = module_dir / "src"
|
|
237
|
-
if not source_root.is_dir():
|
|
238
|
-
return ([], [])
|
|
239
|
-
|
|
240
|
-
source_files: list[str] = []
|
|
241
|
-
test_files: list[str] = []
|
|
242
|
-
|
|
243
|
-
for path in sorted(source_root.rglob("*"), key=lambda item: item.as_posix()):
|
|
244
|
-
if not path.is_file():
|
|
245
|
-
continue
|
|
246
|
-
if path.suffix not in NODE_SOURCE_EXTENSIONS or path.name.endswith(".d.ts"):
|
|
247
|
-
continue
|
|
248
|
-
if any(part in NODE_EXCLUDED_DIRS for part in path.relative_to(module_dir).parts):
|
|
249
|
-
continue
|
|
250
|
-
|
|
251
|
-
rel = relative_path(path, project_root)
|
|
252
|
-
if any(marker in path.name for marker in NODE_TEST_MARKERS):
|
|
253
|
-
test_files.append(rel)
|
|
254
|
-
else:
|
|
255
|
-
source_files.append(rel)
|
|
256
|
-
|
|
257
|
-
tests_root = module_dir / "tests"
|
|
258
|
-
if tests_root.is_dir():
|
|
259
|
-
for path in sorted(tests_root.rglob("*"), key=lambda item: item.as_posix()):
|
|
260
|
-
if (
|
|
261
|
-
path.is_file()
|
|
262
|
-
and path.suffix in NODE_SOURCE_EXTENSIONS
|
|
263
|
-
and not path.name.endswith(".d.ts")
|
|
264
|
-
and not any(part in NODE_EXCLUDED_DIRS for part in path.relative_to(module_dir).parts)
|
|
265
|
-
):
|
|
266
|
-
test_files.append(relative_path(path, project_root))
|
|
267
|
-
|
|
268
|
-
return (source_files, sorted(set(test_files)))
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def infer_node_layer(module_path: str, node_root: Path, project_root: Path) -> tuple[str, str]:
|
|
272
|
-
node_root_rel = relative_path(node_root, project_root)
|
|
273
|
-
if module_path == node_root_rel:
|
|
274
|
-
return ("root", node_root_rel)
|
|
275
|
-
|
|
276
|
-
relative_to_node_root = relative_path(project_root / module_path, node_root)
|
|
277
|
-
first = relative_to_node_root.split("/", 1)[0]
|
|
278
|
-
return (first, relative_path(node_root / first, project_root))
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def node_package_records(node_root: Path) -> list[dict]:
|
|
282
|
-
manifests = node_workspace_manifests(node_root)
|
|
283
|
-
records: list[dict] = []
|
|
284
|
-
names_by_manifest: dict[Path, str] = {}
|
|
285
|
-
|
|
286
|
-
for manifest in manifests:
|
|
287
|
-
data = load_json(manifest)
|
|
288
|
-
name = data.get("name")
|
|
289
|
-
if isinstance(name, str) and name:
|
|
290
|
-
names_by_manifest[manifest] = name
|
|
291
|
-
|
|
292
|
-
workspace_names = set(names_by_manifest.values())
|
|
293
|
-
|
|
294
|
-
for manifest, name in names_by_manifest.items():
|
|
295
|
-
dependency_names = node_dependency_names(manifest) & workspace_names
|
|
296
|
-
records.append(
|
|
297
|
-
{
|
|
298
|
-
"name": name,
|
|
299
|
-
"manifest": manifest,
|
|
300
|
-
"workspaceDependencies": sorted(dependency_names),
|
|
301
|
-
}
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
return records
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def build_node_index(project_root: Path, node_root: Path) -> dict:
|
|
308
|
-
records = node_package_records(node_root)
|
|
309
|
-
has_workspaces = bool(workspace_patterns(load_json(node_root / "package.json")))
|
|
310
|
-
layers: list[dict] = []
|
|
311
|
-
layer_by_name: dict[str, dict] = {}
|
|
312
|
-
|
|
313
|
-
for record in records:
|
|
314
|
-
manifest_path = record["manifest"]
|
|
315
|
-
module_dir = manifest_path.parent
|
|
316
|
-
module_path = relative_path(module_dir, project_root)
|
|
317
|
-
manifest_rel = relative_path(manifest_path, project_root)
|
|
318
|
-
layer_name, layer_path = infer_node_layer(module_path, node_root, project_root)
|
|
319
|
-
source_files, test_files = node_source_files_under(module_dir, project_root)
|
|
320
|
-
|
|
321
|
-
if layer_name not in layer_by_name:
|
|
322
|
-
layer = {
|
|
323
|
-
"name": layer_name,
|
|
324
|
-
"path": layer_path,
|
|
325
|
-
"modules": [],
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// Generate .ai/generated/module-index.json from Cargo or npm workspace metadata.
|
|
4
|
+
|
|
5
|
+
const childProcess = require("node:child_process");
|
|
6
|
+
const fs = require("node:fs");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
|
|
9
|
+
const NODE_SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
10
|
+
const NODE_EXCLUDED_DIRS = new Set(["node_modules", "dist", "build", "coverage", ".next", ".turbo", ".vite"]);
|
|
11
|
+
const NODE_DEPENDENCY_TABLES = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
12
|
+
const NODE_TEST_MARKERS = [".test.", ".spec."];
|
|
13
|
+
const RUST_EXCLUDED_DIRS = new Set(["target", ".git"]);
|
|
14
|
+
const USAGE = "usage: generate-module-index [-h] [--check] [--print] [--output OUTPUT]";
|
|
15
|
+
|
|
16
|
+
class ToolExit extends Error {}
|
|
17
|
+
|
|
18
|
+
function fail(message) {
|
|
19
|
+
throw new ToolExit(message);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isObject(value) {
|
|
23
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Python truthiness, so empty lists and objects count as false.
|
|
27
|
+
function truthy(value) {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value.length > 0;
|
|
30
|
+
}
|
|
31
|
+
if (isObject(value)) {
|
|
32
|
+
return Object.keys(value).length > 0;
|
|
33
|
+
}
|
|
34
|
+
return Boolean(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function compareStrings(left, right) {
|
|
38
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sortedStrings(values) {
|
|
42
|
+
return [...values].sort(compareStrings);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isFile(target) {
|
|
46
|
+
try {
|
|
47
|
+
return fs.statSync(target).isFile();
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isDir(target) {
|
|
54
|
+
try {
|
|
55
|
+
return fs.statSync(target).isDirectory();
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolvePath(target) {
|
|
62
|
+
try {
|
|
63
|
+
return fs.realpathSync(target);
|
|
64
|
+
} catch {
|
|
65
|
+
const parent = path.dirname(target);
|
|
66
|
+
return parent === target ? target : path.join(resolvePath(parent), path.basename(target));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toPosix(relative) {
|
|
71
|
+
return relative.split(path.sep).join("/");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isInside(relative) {
|
|
75
|
+
return relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function relativePath(target, root) {
|
|
79
|
+
let relative = path.relative(path.resolve(root), path.resolve(target));
|
|
80
|
+
if (!isInside(relative)) {
|
|
81
|
+
relative = path.relative(resolvePath(root), resolvePath(target));
|
|
82
|
+
if (!isInside(relative)) {
|
|
83
|
+
fail("'" + target + "' is not in the subpath of '" + root + "'");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return relative === "" ? "." : toPosix(relative);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function pathSuffix(target) {
|
|
90
|
+
const name = path.basename(target);
|
|
91
|
+
const index = name.lastIndexOf(".");
|
|
92
|
+
return index > 0 && index < name.length - 1 ? name.slice(index) : "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function listDir(directory) {
|
|
96
|
+
try {
|
|
97
|
+
return fs.readdirSync(directory, { withFileTypes: true });
|
|
98
|
+
} catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function fnmatchSource(pattern) {
|
|
104
|
+
let source = "";
|
|
105
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
106
|
+
const character = pattern[index];
|
|
107
|
+
if (character === "*") {
|
|
108
|
+
source += "[\\s\\S]*";
|
|
109
|
+
} else if (character === "?") {
|
|
110
|
+
source += "[\\s\\S]";
|
|
111
|
+
} else if (character === "[") {
|
|
112
|
+
let end = index + 1;
|
|
113
|
+
if (pattern[end] === "!") {
|
|
114
|
+
end += 1;
|
|
115
|
+
}
|
|
116
|
+
if (pattern[end] === "]") {
|
|
117
|
+
end += 1;
|
|
118
|
+
}
|
|
119
|
+
while (end < pattern.length && pattern[end] !== "]") {
|
|
120
|
+
end += 1;
|
|
121
|
+
}
|
|
122
|
+
if (end >= pattern.length) {
|
|
123
|
+
source += "\\[";
|
|
124
|
+
} else {
|
|
125
|
+
let body = pattern.slice(index + 1, end).replace(/\\/g, "\\\\");
|
|
126
|
+
if (body.startsWith("!")) {
|
|
127
|
+
body = "^" + body.slice(1);
|
|
128
|
+
} else if (body.startsWith("^")) {
|
|
129
|
+
body = "\\" + body;
|
|
130
|
+
}
|
|
131
|
+
source += "[" + body + "]";
|
|
132
|
+
index = end;
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
source += character.replace(/[.*+?^${}()|[\]\\/-]/g, "\\$&");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return source;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function fnmatch(name, pattern) {
|
|
142
|
+
return new RegExp("^(?:" + fnmatchSource(pattern) + ")$").test(name);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function hasWildcard(part) {
|
|
146
|
+
return /[*?[]/.test(part);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Mirrors pathlib.Path.glob: "**" matches this directory and every
|
|
150
|
+
// non-symlink subdirectory; other parts match one path component.
|
|
151
|
+
function globPaths(base, pattern) {
|
|
152
|
+
const parts = pattern.split("/").filter((part) => part !== "" && part !== ".");
|
|
153
|
+
let current = [base];
|
|
154
|
+
parts.forEach((part, partIndex) => {
|
|
155
|
+
const last = partIndex === parts.length - 1;
|
|
156
|
+
const next = [];
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
const add = (candidate) => {
|
|
159
|
+
if (!seen.has(candidate)) {
|
|
160
|
+
seen.add(candidate);
|
|
161
|
+
next.push(candidate);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
for (const directory of current) {
|
|
165
|
+
if (part === "**") {
|
|
166
|
+
const walk = (dir) => {
|
|
167
|
+
add(dir);
|
|
168
|
+
for (const entry of listDir(dir)) {
|
|
169
|
+
if (entry.isDirectory()) {
|
|
170
|
+
walk(path.join(dir, entry.name));
|
|
326
171
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
"name": record["name"],
|
|
332
|
-
"path": module_path,
|
|
333
|
-
"manifest": manifest_rel,
|
|
334
|
-
"workspaceDependencies": record["workspaceDependencies"],
|
|
335
|
-
"language": "typescript",
|
|
336
|
-
"files": {
|
|
337
|
-
"source": source_files,
|
|
338
|
-
"tests": test_files,
|
|
339
|
-
},
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
if (isDir(directory)) {
|
|
175
|
+
walk(directory);
|
|
340
176
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
177
|
+
} else if (hasWildcard(part)) {
|
|
178
|
+
for (const entry of listDir(directory)) {
|
|
179
|
+
const candidate = path.join(directory, entry.name);
|
|
180
|
+
if (fnmatch(entry.name, part) && (last || isDir(candidate))) {
|
|
181
|
+
add(candidate);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
const candidate = path.join(directory, part);
|
|
186
|
+
if (last ? fs.existsSync(candidate) : isDir(candidate)) {
|
|
187
|
+
add(candidate);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
current = next;
|
|
192
|
+
});
|
|
193
|
+
return current;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Recursive listing that, like Path.rglob, does not descend into symlinked directories.
|
|
197
|
+
function walkFiles(directory) {
|
|
198
|
+
const results = [];
|
|
199
|
+
const walk = (dir) => {
|
|
200
|
+
for (const entry of listDir(dir)) {
|
|
201
|
+
const candidate = path.join(dir, entry.name);
|
|
202
|
+
results.push(candidate);
|
|
203
|
+
if (entry.isDirectory()) {
|
|
204
|
+
walk(candidate);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
walk(directory);
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function comparePathParts(left, right) {
|
|
213
|
+
const leftParts = left.split(path.sep);
|
|
214
|
+
const rightParts = right.split(path.sep);
|
|
215
|
+
for (let index = 0; index < Math.min(leftParts.length, rightParts.length); index += 1) {
|
|
216
|
+
const order = compareStrings(leftParts[index], rightParts[index]);
|
|
217
|
+
if (order !== 0) {
|
|
218
|
+
return order;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return leftParts.length - rightParts.length;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Minimal TOML reader covering the syntax Cargo manifests use.
|
|
225
|
+
function parseToml(text) {
|
|
226
|
+
const root = {};
|
|
227
|
+
let current = root;
|
|
228
|
+
let index = 0;
|
|
229
|
+
|
|
230
|
+
const error = (message) => fail("Invalid TOML: " + message + " (offset " + index + ")");
|
|
231
|
+
const peek = (offset = 0) => text[index + offset];
|
|
232
|
+
const startsWith = (value) => text.startsWith(value, index);
|
|
233
|
+
|
|
234
|
+
function skipInlineSpace() {
|
|
235
|
+
while (peek() === " " || peek() === "\t") {
|
|
236
|
+
index += 1;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
345
239
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
240
|
+
function skipComment() {
|
|
241
|
+
if (peek() === "#") {
|
|
242
|
+
while (index < text.length && peek() !== "\n") {
|
|
243
|
+
index += 1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function skipBlank() {
|
|
249
|
+
while (index < text.length) {
|
|
250
|
+
skipInlineSpace();
|
|
251
|
+
skipComment();
|
|
252
|
+
if (peek() === "\n" || peek() === "\r") {
|
|
253
|
+
index += 1;
|
|
254
|
+
} else {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseEscape() {
|
|
261
|
+
const character = peek();
|
|
262
|
+
index += 1;
|
|
263
|
+
const simple = { b: "\b", t: "\t", n: "\n", f: "\f", r: "\r", "\"": "\"", "\\": "\\", e: "\x1b" };
|
|
264
|
+
if (character in simple) {
|
|
265
|
+
return simple[character];
|
|
266
|
+
}
|
|
267
|
+
if (character === "u" || character === "U") {
|
|
268
|
+
const length = character === "u" ? 4 : 8;
|
|
269
|
+
const hex = text.slice(index, index + length);
|
|
270
|
+
if (!/^[0-9A-Fa-f]+$/.test(hex) || hex.length !== length) {
|
|
271
|
+
error("invalid unicode escape");
|
|
272
|
+
}
|
|
273
|
+
index += length;
|
|
274
|
+
return String.fromCodePoint(parseInt(hex, 16));
|
|
275
|
+
}
|
|
276
|
+
if (character === "x") {
|
|
277
|
+
const hex = text.slice(index, index + 2);
|
|
278
|
+
index += 2;
|
|
279
|
+
return String.fromCharCode(parseInt(hex, 16));
|
|
280
|
+
}
|
|
281
|
+
return error("invalid escape");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function parseBasicString() {
|
|
285
|
+
if (startsWith("\"\"\"")) {
|
|
286
|
+
index += 3;
|
|
287
|
+
if (peek() === "\r" && peek(1) === "\n") {
|
|
288
|
+
index += 2;
|
|
289
|
+
} else if (peek() === "\n") {
|
|
290
|
+
index += 1;
|
|
291
|
+
}
|
|
292
|
+
let value = "";
|
|
293
|
+
while (index < text.length) {
|
|
294
|
+
if (startsWith("\"\"\"")) {
|
|
295
|
+
let quotes = 3;
|
|
296
|
+
while (quotes < 5 && text[index + quotes] === "\"") {
|
|
297
|
+
quotes += 1;
|
|
298
|
+
}
|
|
299
|
+
value += "\"".repeat(quotes - 3);
|
|
300
|
+
index += quotes;
|
|
301
|
+
return value;
|
|
382
302
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
)
|
|
390
|
-
return records
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
def package_records_from_manifests(cargo_root: Path) -> list[dict]:
|
|
394
|
-
manifests = valid_workspace_member_manifests(cargo_root)
|
|
395
|
-
names_by_manifest = {
|
|
396
|
-
manifest: name
|
|
397
|
-
for manifest in manifests
|
|
398
|
-
if (name := package_name(manifest)) is not None
|
|
399
|
-
}
|
|
400
|
-
workspace_names = set(names_by_manifest.values())
|
|
401
|
-
records = []
|
|
402
|
-
|
|
403
|
-
for manifest, name in names_by_manifest.items():
|
|
404
|
-
dependency_names = manifest_dependency_names(manifest) & workspace_names
|
|
405
|
-
records.append(
|
|
406
|
-
{
|
|
407
|
-
"name": name,
|
|
408
|
-
"manifest": manifest,
|
|
409
|
-
"workspaceDependencies": sorted(dependency_names),
|
|
410
|
-
}
|
|
411
|
-
)
|
|
412
|
-
return records
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
def build_cargo_index(project_root: Path, cargo_root: Path) -> dict:
|
|
416
|
-
metadata = load_cargo_metadata(cargo_root)
|
|
417
|
-
records = (
|
|
418
|
-
package_records_from_metadata(project_root, cargo_root, metadata)
|
|
419
|
-
if metadata is not None
|
|
420
|
-
else package_records_from_manifests(cargo_root)
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
layers: list[dict] = []
|
|
424
|
-
layer_by_name: dict[str, dict] = {}
|
|
425
|
-
|
|
426
|
-
for record in records:
|
|
427
|
-
manifest_path = record["manifest"]
|
|
428
|
-
module_dir = manifest_path.parent
|
|
429
|
-
module_path = relative_path(module_dir, project_root)
|
|
430
|
-
manifest_rel = relative_path(manifest_path, project_root)
|
|
431
|
-
module_cargo_path = relative_path(module_dir, cargo_root)
|
|
432
|
-
layer_name, layer_path = infer_layer(module_cargo_path, cargo_root, project_root)
|
|
433
|
-
|
|
434
|
-
if layer_name not in layer_by_name:
|
|
435
|
-
layer = {
|
|
436
|
-
"name": layer_name,
|
|
437
|
-
"path": layer_path,
|
|
438
|
-
"modules": [],
|
|
303
|
+
if (peek() === "\\") {
|
|
304
|
+
index += 1;
|
|
305
|
+
if (/^[ \t]*\r?\n/.test(text.slice(index))) {
|
|
306
|
+
while (/[ \t\r\n]/.test(peek() || "")) {
|
|
307
|
+
index += 1;
|
|
439
308
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
value += parseEscape();
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
value += peek();
|
|
315
|
+
index += 1;
|
|
316
|
+
}
|
|
317
|
+
return error("unterminated string");
|
|
318
|
+
}
|
|
319
|
+
index += 1;
|
|
320
|
+
let value = "";
|
|
321
|
+
while (index < text.length && peek() !== "\"") {
|
|
322
|
+
if (peek() === "\n") {
|
|
323
|
+
error("newline in string");
|
|
324
|
+
}
|
|
325
|
+
if (peek() === "\\") {
|
|
326
|
+
index += 1;
|
|
327
|
+
value += parseEscape();
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
value += peek();
|
|
331
|
+
index += 1;
|
|
332
|
+
}
|
|
333
|
+
if (peek() !== "\"") {
|
|
334
|
+
error("unterminated string");
|
|
335
|
+
}
|
|
336
|
+
index += 1;
|
|
337
|
+
return value;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function parseLiteralString() {
|
|
341
|
+
if (startsWith("'''")) {
|
|
342
|
+
index += 3;
|
|
343
|
+
if (peek() === "\r" && peek(1) === "\n") {
|
|
344
|
+
index += 2;
|
|
345
|
+
} else if (peek() === "\n") {
|
|
346
|
+
index += 1;
|
|
347
|
+
}
|
|
348
|
+
let end = text.indexOf("'''", index);
|
|
349
|
+
if (end < 0) {
|
|
350
|
+
error("unterminated string");
|
|
351
|
+
}
|
|
352
|
+
while (text[end + 3] === "'") {
|
|
353
|
+
end += 1;
|
|
354
|
+
}
|
|
355
|
+
const value = text.slice(index, end);
|
|
356
|
+
index = end + 3;
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
index += 1;
|
|
360
|
+
const end = text.indexOf("'", index);
|
|
361
|
+
if (end < 0 || text.slice(index, end).includes("\n")) {
|
|
362
|
+
error("unterminated string");
|
|
363
|
+
}
|
|
364
|
+
const value = text.slice(index, end);
|
|
365
|
+
index = end + 1;
|
|
366
|
+
return value;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function parseKeyPart() {
|
|
370
|
+
if (peek() === "\"") {
|
|
371
|
+
return parseBasicString();
|
|
372
|
+
}
|
|
373
|
+
if (peek() === "'") {
|
|
374
|
+
return parseLiteralString();
|
|
375
|
+
}
|
|
376
|
+
const match = /^[A-Za-z0-9_-]+/.exec(text.slice(index));
|
|
377
|
+
if (!match) {
|
|
378
|
+
error("invalid key");
|
|
379
|
+
}
|
|
380
|
+
index += match[0].length;
|
|
381
|
+
return match[0];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function parseKey() {
|
|
385
|
+
const parts = [];
|
|
386
|
+
while (true) {
|
|
387
|
+
skipInlineSpace();
|
|
388
|
+
parts.push(parseKeyPart());
|
|
389
|
+
skipInlineSpace();
|
|
390
|
+
if (peek() !== ".") {
|
|
391
|
+
return parts;
|
|
392
|
+
}
|
|
393
|
+
index += 1;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function parseArray() {
|
|
398
|
+
index += 1;
|
|
399
|
+
const values = [];
|
|
400
|
+
while (true) {
|
|
401
|
+
skipBlank();
|
|
402
|
+
if (peek() === "]") {
|
|
403
|
+
index += 1;
|
|
404
|
+
return values;
|
|
405
|
+
}
|
|
406
|
+
values.push(parseValue());
|
|
407
|
+
skipBlank();
|
|
408
|
+
if (peek() === ",") {
|
|
409
|
+
index += 1;
|
|
410
|
+
} else if (peek() !== "]") {
|
|
411
|
+
error("expected , or ]");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function parseInlineTable() {
|
|
417
|
+
index += 1;
|
|
418
|
+
const table = {};
|
|
419
|
+
skipInlineSpace();
|
|
420
|
+
if (peek() === "}") {
|
|
421
|
+
index += 1;
|
|
422
|
+
return table;
|
|
423
|
+
}
|
|
424
|
+
while (true) {
|
|
425
|
+
const key = parseKey();
|
|
426
|
+
if (peek() !== "=") {
|
|
427
|
+
error("expected =");
|
|
428
|
+
}
|
|
429
|
+
index += 1;
|
|
430
|
+
skipInlineSpace();
|
|
431
|
+
assign(table, key, parseValue());
|
|
432
|
+
skipInlineSpace();
|
|
433
|
+
if (peek() === ",") {
|
|
434
|
+
index += 1;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (peek() === "}") {
|
|
438
|
+
index += 1;
|
|
439
|
+
return table;
|
|
440
|
+
}
|
|
441
|
+
error("expected , or }");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function parseValue() {
|
|
446
|
+
const character = peek();
|
|
447
|
+
if (character === "\"") {
|
|
448
|
+
return parseBasicString();
|
|
449
|
+
}
|
|
450
|
+
if (character === "'") {
|
|
451
|
+
return parseLiteralString();
|
|
452
|
+
}
|
|
453
|
+
if (character === "[") {
|
|
454
|
+
return parseArray();
|
|
455
|
+
}
|
|
456
|
+
if (character === "{") {
|
|
457
|
+
return parseInlineTable();
|
|
458
|
+
}
|
|
459
|
+
const match = /^[^\s,\]}#]+(?: [0-9][^\s,\]}#]*)?/.exec(text.slice(index));
|
|
460
|
+
if (!match) {
|
|
461
|
+
error("invalid value");
|
|
462
|
+
}
|
|
463
|
+
index += match[0].length;
|
|
464
|
+
const raw = match[0];
|
|
465
|
+
if (raw === "true") {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
if (raw === "false") {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
if (/^[+-]?(?:inf|nan)$/.test(raw)) {
|
|
472
|
+
return raw.includes("nan") ? NaN : raw.startsWith("-") ? -Infinity : Infinity;
|
|
473
|
+
}
|
|
474
|
+
const cleaned = raw.replace(/_/g, "");
|
|
475
|
+
if (/^[+-]?(?:0x[0-9a-fA-F]+|0o[0-7]+|0b[01]+)$/.test(cleaned)) {
|
|
476
|
+
const sign = cleaned.startsWith("-") ? -1 : 1;
|
|
477
|
+
return sign * Number(cleaned.replace(/^[+-]/, ""));
|
|
478
|
+
}
|
|
479
|
+
if (/^[+-]?(?:\d+)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(cleaned)) {
|
|
480
|
+
return Number(cleaned);
|
|
481
|
+
}
|
|
482
|
+
// Dates and times are kept as their source text.
|
|
483
|
+
return raw;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function descend(table, key, forArrayTable) {
|
|
487
|
+
let target = table;
|
|
488
|
+
key.forEach((part, partIndex) => {
|
|
489
|
+
const last = partIndex === key.length - 1;
|
|
490
|
+
if (last && forArrayTable) {
|
|
491
|
+
if (!(part in target)) {
|
|
492
|
+
target[part] = [];
|
|
493
|
+
}
|
|
494
|
+
if (!Array.isArray(target[part])) {
|
|
495
|
+
error("key redefined as array of tables");
|
|
452
496
|
}
|
|
453
|
-
|
|
454
|
-
|
|
497
|
+
const entry = {};
|
|
498
|
+
target[part].push(entry);
|
|
499
|
+
target = entry;
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (!(part in target)) {
|
|
503
|
+
target[part] = {};
|
|
504
|
+
}
|
|
505
|
+
let next = target[part];
|
|
506
|
+
if (Array.isArray(next)) {
|
|
507
|
+
next = next[next.length - 1];
|
|
508
|
+
}
|
|
509
|
+
if (!isObject(next)) {
|
|
510
|
+
error("key redefined as table");
|
|
511
|
+
}
|
|
512
|
+
target = next;
|
|
513
|
+
});
|
|
514
|
+
return target;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function assign(table, key, value) {
|
|
518
|
+
const parent = descend(table, key.slice(0, -1), false);
|
|
519
|
+
const last = key[key.length - 1];
|
|
520
|
+
if (last in parent) {
|
|
521
|
+
error("duplicate key " + key.join("."));
|
|
522
|
+
}
|
|
523
|
+
parent[last] = value;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
while (true) {
|
|
527
|
+
skipBlank();
|
|
528
|
+
if (index >= text.length) {
|
|
529
|
+
return root;
|
|
530
|
+
}
|
|
531
|
+
if (startsWith("[[")) {
|
|
532
|
+
index += 2;
|
|
533
|
+
const key = parseKey();
|
|
534
|
+
if (!startsWith("]]")) {
|
|
535
|
+
error("expected ]]");
|
|
536
|
+
}
|
|
537
|
+
index += 2;
|
|
538
|
+
current = descend(root, key, true);
|
|
539
|
+
} else if (peek() === "[") {
|
|
540
|
+
index += 1;
|
|
541
|
+
const key = parseKey();
|
|
542
|
+
if (peek() !== "]") {
|
|
543
|
+
error("expected ]");
|
|
544
|
+
}
|
|
545
|
+
index += 1;
|
|
546
|
+
current = descend(root, key, false);
|
|
547
|
+
} else {
|
|
548
|
+
const key = parseKey();
|
|
549
|
+
if (peek() !== "=") {
|
|
550
|
+
error("expected =");
|
|
551
|
+
}
|
|
552
|
+
index += 1;
|
|
553
|
+
skipInlineSpace();
|
|
554
|
+
assign(current, key, parseValue());
|
|
555
|
+
}
|
|
556
|
+
skipInlineSpace();
|
|
557
|
+
skipComment();
|
|
558
|
+
if (index < text.length && peek() !== "\n" && peek() !== "\r") {
|
|
559
|
+
error("expected end of line");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function maybeFindRoot(projectRoot, manifestName) {
|
|
565
|
+
if (isFile(path.join(projectRoot, manifestName))) {
|
|
566
|
+
return projectRoot;
|
|
567
|
+
}
|
|
568
|
+
for (const name of sortedStrings(fs.readdirSync(projectRoot))) {
|
|
569
|
+
const child = path.join(projectRoot, name);
|
|
570
|
+
if (isDir(child) && isFile(path.join(child, manifestName))) {
|
|
571
|
+
return child;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function loadJson(target) {
|
|
578
|
+
return JSON.parse(fs.readFileSync(target, "utf8").replace(/\r\n?/g, "\n"));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function loadToml(target) {
|
|
582
|
+
return parseToml(fs.readFileSync(target, "utf8").replace(/\r\n?/g, "\n"));
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function getTable(data, key) {
|
|
586
|
+
return isObject(data) && key in data ? data[key] : {};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function loadCargoMetadata(cargoRoot) {
|
|
590
|
+
const result = childProcess.spawnSync("cargo", ["metadata", "--format-version", "1", "--no-deps"], {
|
|
591
|
+
cwd: cargoRoot,
|
|
592
|
+
encoding: "utf8",
|
|
593
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
594
|
+
maxBuffer: 1024 * 1024 * 1024
|
|
595
|
+
});
|
|
596
|
+
if (result.error || result.status !== 0) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
return JSON.parse(result.stdout);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function pathIsExcluded(relative, excludes) {
|
|
603
|
+
const normalized = relative.replace(/\/+$/, "");
|
|
604
|
+
return excludes.some((pattern) => {
|
|
605
|
+
const normalizedPattern = pattern.replace(/\/+$/, "");
|
|
606
|
+
return normalized === normalizedPattern
|
|
607
|
+
|| normalized.startsWith(normalizedPattern + "/")
|
|
608
|
+
|| fnmatch(normalized, normalizedPattern);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function validWorkspaceMemberManifests(cargoRoot) {
|
|
613
|
+
const data = loadToml(path.join(cargoRoot, "Cargo.toml"));
|
|
614
|
+
const workspace = getTable(data, "workspace");
|
|
615
|
+
const members = getTable(workspace, "members");
|
|
616
|
+
const excludes = isObject(workspace) && "exclude" in workspace ? workspace.exclude : [];
|
|
617
|
+
|
|
618
|
+
const manifests = [];
|
|
619
|
+
const seen = new Set();
|
|
620
|
+
|
|
621
|
+
if (truthy(getTable(data, "package").name)) {
|
|
622
|
+
manifests.push(path.join(cargoRoot, "Cargo.toml"));
|
|
623
|
+
seen.add(path.join(cargoRoot, "Cargo.toml"));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
for (const member of Array.isArray(members) ? members : Object.keys(members)) {
|
|
627
|
+
const candidates = member.includes("*")
|
|
628
|
+
? globPaths(cargoRoot, member).sort(comparePathParts)
|
|
629
|
+
: [path.join(cargoRoot, member)];
|
|
630
|
+
for (const candidate of candidates) {
|
|
631
|
+
if (!isDir(candidate)) {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (pathIsExcluded(relativePath(candidate, cargoRoot), excludes)) {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const manifest = path.join(candidate, "Cargo.toml");
|
|
638
|
+
if (!isFile(manifest) || seen.has(manifest)) {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
manifests.push(manifest);
|
|
642
|
+
seen.add(manifest);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return manifests;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function packageName(manifest) {
|
|
650
|
+
const name = getTable(loadToml(manifest), "package").name;
|
|
651
|
+
return typeof name === "string" ? name : null;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function dependencyNamesFromTable(table) {
|
|
655
|
+
const names = new Set();
|
|
656
|
+
for (const [name, value] of Object.entries(table)) {
|
|
657
|
+
if (isObject(value)) {
|
|
658
|
+
names.add(typeof value.package === "string" ? value.package : name);
|
|
659
|
+
} else {
|
|
660
|
+
names.add(name);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return names;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function manifestDependencyNames(manifest) {
|
|
667
|
+
const data = loadToml(manifest);
|
|
668
|
+
const names = new Set();
|
|
669
|
+
const addTables = (container) => {
|
|
670
|
+
for (const key of ["dependencies", "dev-dependencies", "build-dependencies"]) {
|
|
671
|
+
const table = getTable(container, key);
|
|
672
|
+
if (isObject(table)) {
|
|
673
|
+
for (const name of dependencyNamesFromTable(table)) {
|
|
674
|
+
names.add(name);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
addTables(data);
|
|
680
|
+
const target = getTable(data, "target");
|
|
681
|
+
if (isObject(target)) {
|
|
682
|
+
for (const targetTable of Object.values(target)) {
|
|
683
|
+
if (isObject(targetTable)) {
|
|
684
|
+
addTables(targetTable);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return names;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function inferLayer(moduleCargoPath, cargoRoot, projectRoot) {
|
|
692
|
+
if (moduleCargoPath === ".") {
|
|
693
|
+
return ["root", relativePath(cargoRoot, projectRoot)];
|
|
694
|
+
}
|
|
695
|
+
const first = moduleCargoPath.split("/")[0];
|
|
696
|
+
return [first, relativePath(path.join(cargoRoot, first), projectRoot)];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function rustFilesUnder(moduleDir, projectRoot, child) {
|
|
700
|
+
const root = path.join(moduleDir, child);
|
|
701
|
+
if (!isDir(root)) {
|
|
702
|
+
return [];
|
|
703
|
+
}
|
|
704
|
+
return sortedStrings(walkFiles(root)
|
|
705
|
+
.filter((candidate) => candidate.endsWith(".rs") && isFile(candidate))
|
|
706
|
+
.filter((candidate) => !path.relative(moduleDir, candidate).split(path.sep).some((part) => RUST_EXCLUDED_DIRS.has(part)))
|
|
707
|
+
.map((candidate) => relativePath(candidate, projectRoot)));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function workspacePatterns(packageData) {
|
|
711
|
+
const workspaces = packageData.workspaces;
|
|
712
|
+
if (Array.isArray(workspaces)) {
|
|
713
|
+
return workspaces.filter((pattern) => typeof pattern === "string");
|
|
714
|
+
}
|
|
715
|
+
if (isObject(workspaces)) {
|
|
716
|
+
const packages = "packages" in workspaces ? workspaces.packages : [];
|
|
717
|
+
if (Array.isArray(packages)) {
|
|
718
|
+
return packages.filter((pattern) => typeof pattern === "string");
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
455
723
|
|
|
456
|
-
|
|
724
|
+
function nodeWorkspaceManifests(nodeRoot) {
|
|
725
|
+
const rootManifest = path.join(nodeRoot, "package.json");
|
|
726
|
+
const rootData = loadJson(rootManifest);
|
|
727
|
+
const patterns = workspacePatterns(rootData);
|
|
457
728
|
|
|
729
|
+
if (patterns.length === 0) {
|
|
730
|
+
return [rootManifest];
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const manifests = [];
|
|
734
|
+
const seen = new Set();
|
|
735
|
+
|
|
736
|
+
for (const pattern of patterns) {
|
|
737
|
+
if (pattern.startsWith("!") || pattern.startsWith("/") || pattern.split("/").includes("..")) {
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
const candidates = globPaths(nodeRoot, pattern)
|
|
741
|
+
.sort((left, right) => compareStrings(toPosix(left), toPosix(right)));
|
|
742
|
+
for (const candidate of candidates) {
|
|
743
|
+
const manifest = path.join(candidate, "package.json");
|
|
744
|
+
if (!isDir(candidate) || !isFile(manifest) || seen.has(manifest)) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (relativePath(candidate, nodeRoot).split("/").some((part) => NODE_EXCLUDED_DIRS.has(part))) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
manifests.push(manifest);
|
|
751
|
+
seen.add(manifest);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (truthy(rootData.name) && (truthy(rootData.main) || truthy(rootData.exports) || isDir(path.join(nodeRoot, "src")))) {
|
|
756
|
+
manifests.unshift(rootManifest);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return manifests;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function nodeDependencyNames(manifest) {
|
|
763
|
+
const data = loadJson(manifest);
|
|
764
|
+
const names = new Set();
|
|
765
|
+
for (const tableName of NODE_DEPENDENCY_TABLES) {
|
|
766
|
+
const table = getTable(data, tableName);
|
|
767
|
+
if (isObject(table)) {
|
|
768
|
+
for (const name of Object.keys(table)) {
|
|
769
|
+
names.add(name);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return names;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function isNodeSource(candidate) {
|
|
777
|
+
return NODE_SOURCE_EXTENSIONS.includes(pathSuffix(candidate)) && !path.basename(candidate).endsWith(".d.ts");
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function hasExcludedNodePart(moduleDir, candidate) {
|
|
781
|
+
return path.relative(moduleDir, candidate).split(path.sep).some((part) => NODE_EXCLUDED_DIRS.has(part));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function nodeSourceFilesUnder(moduleDir, projectRoot) {
|
|
785
|
+
const sourceRoot = path.join(moduleDir, "src");
|
|
786
|
+
if (!isDir(sourceRoot)) {
|
|
787
|
+
return [[], []];
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const sourceFiles = [];
|
|
791
|
+
const testFiles = [];
|
|
792
|
+
const byPosix = (left, right) => compareStrings(toPosix(left), toPosix(right));
|
|
793
|
+
|
|
794
|
+
for (const candidate of walkFiles(sourceRoot).sort(byPosix)) {
|
|
795
|
+
if (!isFile(candidate) || !isNodeSource(candidate) || hasExcludedNodePart(moduleDir, candidate)) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
const relative = relativePath(candidate, projectRoot);
|
|
799
|
+
if (NODE_TEST_MARKERS.some((marker) => path.basename(candidate).includes(marker))) {
|
|
800
|
+
testFiles.push(relative);
|
|
801
|
+
} else {
|
|
802
|
+
sourceFiles.push(relative);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const testsRoot = path.join(moduleDir, "tests");
|
|
807
|
+
if (isDir(testsRoot)) {
|
|
808
|
+
for (const candidate of walkFiles(testsRoot).sort(byPosix)) {
|
|
809
|
+
if (isFile(candidate) && isNodeSource(candidate) && !hasExcludedNodePart(moduleDir, candidate)) {
|
|
810
|
+
testFiles.push(relativePath(candidate, projectRoot));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return [sourceFiles, sortedStrings(new Set(testFiles))];
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function inferNodeLayer(modulePath, nodeRoot, projectRoot) {
|
|
819
|
+
const nodeRootRelative = relativePath(nodeRoot, projectRoot);
|
|
820
|
+
if (modulePath === nodeRootRelative) {
|
|
821
|
+
return ["root", nodeRootRelative];
|
|
822
|
+
}
|
|
823
|
+
const first = relativePath(path.join(projectRoot, modulePath), nodeRoot).split("/")[0];
|
|
824
|
+
return [first, relativePath(path.join(nodeRoot, first), projectRoot)];
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function recordsFromNames(namesByManifest, dependencyNames) {
|
|
828
|
+
const workspaceNames = new Set(namesByManifest.values());
|
|
829
|
+
return [...namesByManifest].map(([manifest, name]) => ({
|
|
830
|
+
name,
|
|
831
|
+
manifest,
|
|
832
|
+
workspaceDependencies: sortedStrings([...dependencyNames(manifest)].filter((dependency) => workspaceNames.has(dependency)))
|
|
833
|
+
}));
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function nodePackageRecords(nodeRoot) {
|
|
837
|
+
const namesByManifest = new Map();
|
|
838
|
+
for (const manifest of nodeWorkspaceManifests(nodeRoot)) {
|
|
839
|
+
const name = loadJson(manifest).name;
|
|
840
|
+
if (typeof name === "string" && name) {
|
|
841
|
+
namesByManifest.set(manifest, name);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return recordsFromNames(namesByManifest, nodeDependencyNames);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function buildLayers(records, describe) {
|
|
848
|
+
const layers = [];
|
|
849
|
+
const layerByName = new Map();
|
|
850
|
+
for (const record of records) {
|
|
851
|
+
const moduleDir = path.dirname(record.manifest);
|
|
852
|
+
const { layerName, layerPath, moduleEntry } = describe(record, moduleDir);
|
|
853
|
+
if (!layerByName.has(layerName)) {
|
|
854
|
+
const layer = { name: layerName, path: layerPath, modules: [] };
|
|
855
|
+
layerByName.set(layerName, layer);
|
|
856
|
+
layers.push(layer);
|
|
857
|
+
}
|
|
858
|
+
if (moduleEntry.path !== ".") {
|
|
859
|
+
moduleEntry.architectureDoc = moduleEntry.path + "/ARCHITECTURE.md";
|
|
860
|
+
}
|
|
861
|
+
layerByName.get(layerName).modules.push(moduleEntry);
|
|
862
|
+
}
|
|
863
|
+
return layers;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function buildNodeIndex(projectRoot, nodeRoot) {
|
|
867
|
+
const records = nodePackageRecords(nodeRoot);
|
|
868
|
+
const hasWorkspaces = workspacePatterns(loadJson(path.join(nodeRoot, "package.json"))).length > 0;
|
|
869
|
+
const layers = buildLayers(records, (record, moduleDir) => {
|
|
870
|
+
const modulePath = relativePath(moduleDir, projectRoot);
|
|
871
|
+
const [layerName, layerPath] = inferNodeLayer(modulePath, nodeRoot, projectRoot);
|
|
872
|
+
const [sourceFiles, testFiles] = nodeSourceFilesUnder(moduleDir, projectRoot);
|
|
458
873
|
return {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
"
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
874
|
+
layerName,
|
|
875
|
+
layerPath,
|
|
876
|
+
moduleEntry: {
|
|
877
|
+
name: record.name,
|
|
878
|
+
path: modulePath,
|
|
879
|
+
manifest: relativePath(record.manifest, projectRoot),
|
|
880
|
+
workspaceDependencies: record.workspaceDependencies,
|
|
881
|
+
language: "typescript",
|
|
882
|
+
files: { source: sourceFiles, tests: testFiles }
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
schemaVersion: 1,
|
|
889
|
+
kind: "module-index",
|
|
890
|
+
generatedBy: ".ai/tools/generate-module-index",
|
|
891
|
+
workspace: {
|
|
892
|
+
type: hasWorkspaces ? "npm-workspaces" : "npm-package",
|
|
893
|
+
root: relativePath(nodeRoot, projectRoot),
|
|
894
|
+
manifest: relativePath(path.join(nodeRoot, "package.json"), projectRoot)
|
|
895
|
+
},
|
|
896
|
+
layerInference: {
|
|
897
|
+
method: "path-first-segment-from-workspace-root",
|
|
898
|
+
rootPackageLayer: "root"
|
|
899
|
+
},
|
|
900
|
+
layers
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function packageRecordsFromMetadata(metadata) {
|
|
905
|
+
const memberOrder = new Map(metadata.workspace_members.map((memberId, index) => [memberId, index]));
|
|
906
|
+
const packages = metadata.packages
|
|
907
|
+
.filter((pkg) => memberOrder.has(pkg.id))
|
|
908
|
+
.sort((left, right) => memberOrder.get(left.id) - memberOrder.get(right.id));
|
|
909
|
+
const workspaceNames = new Set(packages.map((pkg) => pkg.name));
|
|
910
|
+
return packages.map((pkg) => ({
|
|
911
|
+
name: pkg.name,
|
|
912
|
+
manifest: pkg.manifest_path,
|
|
913
|
+
workspaceDependencies: sortedStrings(new Set((pkg.dependencies || [])
|
|
914
|
+
.filter((dependency) => workspaceNames.has(dependency.name))
|
|
915
|
+
.map((dependency) => dependency.name)))
|
|
916
|
+
}));
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function packageRecordsFromManifests(cargoRoot) {
|
|
920
|
+
const namesByManifest = new Map();
|
|
921
|
+
for (const manifest of validWorkspaceMemberManifests(cargoRoot)) {
|
|
922
|
+
const name = packageName(manifest);
|
|
923
|
+
if (name !== null) {
|
|
924
|
+
namesByManifest.set(manifest, name);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return recordsFromNames(namesByManifest, manifestDependencyNames);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function buildCargoIndex(projectRoot, cargoRoot) {
|
|
931
|
+
const metadata = loadCargoMetadata(cargoRoot);
|
|
932
|
+
const records = metadata !== null ? packageRecordsFromMetadata(metadata) : packageRecordsFromManifests(cargoRoot);
|
|
933
|
+
const layers = buildLayers(records, (record, moduleDir) => {
|
|
934
|
+
const [layerName, layerPath] = inferLayer(relativePath(moduleDir, cargoRoot), cargoRoot, projectRoot);
|
|
935
|
+
return {
|
|
936
|
+
layerName,
|
|
937
|
+
layerPath,
|
|
938
|
+
moduleEntry: {
|
|
939
|
+
name: record.name,
|
|
940
|
+
path: relativePath(moduleDir, projectRoot),
|
|
941
|
+
manifest: relativePath(record.manifest, projectRoot),
|
|
942
|
+
workspaceDependencies: record.workspaceDependencies,
|
|
943
|
+
files: {
|
|
944
|
+
source: rustFilesUnder(moduleDir, projectRoot, "src"),
|
|
945
|
+
tests: rustFilesUnder(moduleDir, projectRoot, "tests")
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
return {
|
|
952
|
+
schemaVersion: 1,
|
|
953
|
+
kind: "module-index",
|
|
954
|
+
generatedBy: ".ai/tools/generate-module-index",
|
|
955
|
+
workspace: {
|
|
956
|
+
root: relativePath(cargoRoot, projectRoot),
|
|
957
|
+
manifest: relativePath(path.join(cargoRoot, "Cargo.toml"), projectRoot)
|
|
958
|
+
},
|
|
959
|
+
layerInference: {
|
|
960
|
+
method: "path-first-segment-from-cargo-root",
|
|
961
|
+
rootPackageLayer: "root"
|
|
962
|
+
},
|
|
963
|
+
layers
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function buildIndex(projectRoot) {
|
|
968
|
+
const cargoRoot = maybeFindRoot(projectRoot, "Cargo.toml");
|
|
969
|
+
if (cargoRoot !== null) {
|
|
970
|
+
return buildCargoIndex(projectRoot, cargoRoot);
|
|
971
|
+
}
|
|
972
|
+
const nodeRoot = maybeFindRoot(projectRoot, "package.json");
|
|
973
|
+
if (nodeRoot !== null) {
|
|
974
|
+
return buildNodeIndex(projectRoot, nodeRoot);
|
|
975
|
+
}
|
|
976
|
+
return fail("Could not find Cargo.toml or package.json in " + projectRoot + " or its direct child directories.");
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// Matches Python json.dumps(indent=2) with ensure_ascii.
|
|
980
|
+
function toJson(data) {
|
|
981
|
+
return JSON.stringify(data, null, 2)
|
|
982
|
+
.replace(/[\u007f-]/g, (character) => "\\u" + character.charCodeAt(0).toString(16).padStart(4, "0")) + "\n";
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function parseArgs(argv) {
|
|
986
|
+
const args = { check: false, print: false, output: ".ai/generated/module-index.json" };
|
|
987
|
+
const usageError = (message) => {
|
|
988
|
+
process.stderr.write(USAGE + "\ngenerate-module-index: error: " + message + "\n");
|
|
989
|
+
process.exit(2);
|
|
990
|
+
};
|
|
991
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
992
|
+
const argument = argv[index];
|
|
993
|
+
if (argument === "-h" || argument === "--help") {
|
|
994
|
+
process.stdout.write(USAGE + "\n\nGenerate .ai/generated/module-index.json from Cargo or npm workspace metadata.\n");
|
|
995
|
+
process.exit(0);
|
|
996
|
+
} else if (argument === "--check") {
|
|
997
|
+
args.check = true;
|
|
998
|
+
} else if (argument === "--print") {
|
|
999
|
+
args.print = true;
|
|
1000
|
+
} else if (argument === "--output" || argument.startsWith("--output=")) {
|
|
1001
|
+
if (argument === "--output") {
|
|
1002
|
+
if (index + 1 >= argv.length) {
|
|
1003
|
+
usageError("argument --output: expected one argument");
|
|
1004
|
+
}
|
|
1005
|
+
index += 1;
|
|
1006
|
+
args.output = argv[index];
|
|
1007
|
+
} else {
|
|
1008
|
+
args.output = argument.slice("--output=".length);
|
|
1009
|
+
}
|
|
1010
|
+
} else {
|
|
1011
|
+
usageError("unrecognized arguments: " + argv.slice(index).join(" "));
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return args;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function main(argv) {
|
|
1018
|
+
const args = parseArgs(argv);
|
|
1019
|
+
const root = path.resolve(path.dirname(fs.realpathSync(__filename)), "..", "..");
|
|
1020
|
+
const generated = toJson(buildIndex(root));
|
|
1021
|
+
const outputPath = path.join(root, args.output);
|
|
1022
|
+
const displayPath = toPosix(path.relative(root, outputPath));
|
|
1023
|
+
|
|
1024
|
+
if (args.check) {
|
|
1025
|
+
if (!isFile(outputPath)) {
|
|
1026
|
+
process.stderr.write("Missing generated module index: " + displayPath + "\n");
|
|
1027
|
+
return 1;
|
|
1028
|
+
}
|
|
1029
|
+
if (fs.readFileSync(outputPath, "utf8").replace(/\r\n?/g, "\n") !== generated) {
|
|
1030
|
+
process.stderr.write("Stale generated module index: " + displayPath + "\n");
|
|
1031
|
+
return 1;
|
|
1032
|
+
}
|
|
1033
|
+
process.stdout.write("generate-module-index check passed\n");
|
|
1034
|
+
return 0;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (args.print) {
|
|
1038
|
+
process.stdout.write(generated);
|
|
1039
|
+
return 0;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
1043
|
+
fs.writeFileSync(outputPath, generated);
|
|
1044
|
+
process.stdout.write("wrote " + displayPath + "\n");
|
|
1045
|
+
return 0;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
try {
|
|
1049
|
+
process.exitCode = main(process.argv.slice(2));
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (!(error instanceof ToolExit)) {
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
process.stderr.write(error.message + "\n");
|
|
1055
|
+
process.exitCode = 1;
|
|
1056
|
+
}
|