feat(devkit): add resumable workspace automation and UI review tooling
Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
This commit is contained in:
@@ -5,9 +5,16 @@ import path from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const [metaRootArgument] = process.argv.slice(2);
|
||||
const [metaRootArgument, ...argumentsRest] = process.argv.slice(2);
|
||||
if (!metaRootArgument) {
|
||||
throw new Error("Usage: extract-webui-structure.mjs META_ROOT");
|
||||
throw new Error("Usage: extract-webui-structure.mjs META_ROOT [--workspace-root ROOT]");
|
||||
}
|
||||
let explicitWorkspaceRoot;
|
||||
for (let index = 0; index < argumentsRest.length; index++) {
|
||||
if (argumentsRest[index] !== "--workspace-root" || explicitWorkspaceRoot !== undefined || !argumentsRest[index + 1]) {
|
||||
throw new Error("Expected one --workspace-root ROOT option");
|
||||
}
|
||||
explicitWorkspaceRoot = path.resolve(argumentsRest[++index]);
|
||||
}
|
||||
|
||||
const metaRoot = path.resolve(metaRootArgument);
|
||||
@@ -15,12 +22,18 @@ const repositoryCatalog = JSON.parse(
|
||||
fs.readFileSync(path.join(metaRoot, "repositories.json"), "utf8")
|
||||
);
|
||||
const siblingWorkspaceRoot = path.dirname(metaRoot);
|
||||
const configuredWorkspaceRoot = path.resolve(repositoryCatalog.default_parent);
|
||||
const workspaceRoot = fs.existsSync(
|
||||
// Discovery is retained only for old direct callers. An explicit root wins
|
||||
// even when another configured checkout contains more optional modules.
|
||||
const workspaceRoot = explicitWorkspaceRoot ?? (fs.existsSync(
|
||||
path.join(siblingWorkspaceRoot, "govoplan-core", "webui")
|
||||
)
|
||||
? siblingWorkspaceRoot
|
||||
: configuredWorkspaceRoot;
|
||||
) ? siblingWorkspaceRoot : path.resolve(repositoryCatalog.default_parent));
|
||||
if (!fs.statSync(workspaceRoot).isDirectory()) throw new Error("Inventory workspace root must be an existing directory");
|
||||
const actualWorkspaceRoot = fs.realpathSync(workspaceRoot);
|
||||
function withinWorkspace(candidate) {
|
||||
const relative = path.relative(actualWorkspaceRoot, fs.realpathSync(candidate));
|
||||
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
||||
}
|
||||
if (!Array.isArray(repositoryCatalog.repositories)) throw new Error("Repository catalog requires a repositories array");
|
||||
const typescriptPath = path.join(
|
||||
workspaceRoot,
|
||||
"govoplan-core",
|
||||
@@ -96,6 +109,7 @@ const contributionTypes = new Map([
|
||||
]);
|
||||
|
||||
const result = {
|
||||
workspaceRoot: actualWorkspaceRoot,
|
||||
fields: [],
|
||||
actions: [],
|
||||
labels: [],
|
||||
@@ -111,8 +125,12 @@ const result = {
|
||||
};
|
||||
|
||||
for (const repository of repositoryCatalog.repositories) {
|
||||
const sourceRoot = path.join(workspaceRoot, repository.path, "webui", "src");
|
||||
if (!repository || typeof repository.path !== "string" || !repository.path || path.isAbsolute(repository.path) || repository.path.split(/[\\/]/).includes("..")) {
|
||||
throw new Error("Inventory repository paths must remain inside the selected workspace");
|
||||
}
|
||||
const sourceRoot = path.join(actualWorkspaceRoot, repository.path, "webui", "src");
|
||||
if (!fs.existsSync(sourceRoot)) continue;
|
||||
if (!withinWorkspace(sourceRoot)) throw new Error("Inventory source root escapes the selected workspace");
|
||||
for (const sourcePath of sourceFiles(sourceRoot)) {
|
||||
inspectSource(repository.name, sourceRoot, sourcePath);
|
||||
}
|
||||
@@ -135,7 +153,7 @@ function sourceFiles(root) {
|
||||
}
|
||||
const candidate = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) pending.push(candidate);
|
||||
else if (/\.(?:ts|tsx)$/.test(entry.name)) files.push(candidate);
|
||||
else if (entry.isFile() && /\.(?:ts|tsx)$/.test(entry.name)) files.push(candidate);
|
||||
}
|
||||
}
|
||||
return files.sort();
|
||||
@@ -150,7 +168,7 @@ function inspectSource(repository, sourceRoot, sourcePath) {
|
||||
true,
|
||||
sourcePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
||||
);
|
||||
const relativeFile = path.relative(path.join(workspaceRoot, repository), sourcePath);
|
||||
const relativeFile = path.relative(path.resolve(sourceRoot, "..", ".."), sourcePath);
|
||||
const identityCounters = new Map();
|
||||
|
||||
function location(node) {
|
||||
|
||||
Reference in New Issue
Block a user