@topogram/cli 0.3.79 → 0.3.81
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/package.json +1 -1
- package/src/cli/command-parser.js +2 -0
- package/src/cli/command-parsers/extractor.js +40 -0
- package/src/cli/commands/extractor.js +621 -0
- package/src/cli/commands/import/help.js +3 -1
- package/src/cli/commands/import/workspace.js +8 -2
- package/src/cli/commands/import-runner.js +4 -2
- package/src/cli/dispatcher.js +14 -0
- package/src/cli/help-dispatch.js +11 -0
- package/src/cli/help.js +14 -1
- package/src/cli/options.js +17 -0
- package/src/extractor/check.js +155 -0
- package/src/extractor/first-party.js +93 -0
- package/src/extractor/packages.js +295 -0
- package/src/extractor/registry.js +196 -0
- package/src/extractor-policy.js +249 -0
- package/src/generator/check.js +24 -87
- package/src/generator/registry/index.js +16 -75
- package/src/generator-policy.js +9 -57
- package/src/import/core/registry.d.ts +3 -0
- package/src/import/core/registry.js +82 -8
- package/src/import/core/runner/run.js +2 -0
- package/src/import/core/runner/tracks.js +66 -4
- package/src/import/provenance.js +2 -1
- package/src/package-adapters/adapter.js +64 -0
- package/src/package-adapters/file-map.js +30 -0
- package/src/package-adapters/index.js +27 -0
- package/src/package-adapters/manifest.js +108 -0
- package/src/package-adapters/policy.js +81 -0
- package/src/package-adapters/spec.js +51 -0
- package/src/topogram-config.js +14 -0
|
@@ -4,7 +4,7 @@ import { TOPOGRAM_IMPORT_FILE } from "../../../import/provenance.js";
|
|
|
4
4
|
import { TOPOGRAM_IMPORT_ADOPTIONS_FILE } from "./paths.js";
|
|
5
5
|
|
|
6
6
|
export function printExtractHelp() {
|
|
7
|
-
console.log("Usage: topogram extract <app-path> --out <target> [--from <track[,track]>] [--json]");
|
|
7
|
+
console.log("Usage: topogram extract <app-path> --out <target> [--from <track[,track]>] [--extractor <id-or-package-or-path>] [--json]");
|
|
8
8
|
console.log(" or: topogram extract refresh [path] [--from <app-path>] [--dry-run] [--json]");
|
|
9
9
|
console.log(" or: topogram extract diff [path] [--json]");
|
|
10
10
|
console.log(" or: topogram extract check [path] [--json]");
|
|
@@ -26,6 +26,8 @@ export function printExtractHelp() {
|
|
|
26
26
|
console.log(" topogram extract ./existing-app --out ./extracted-topogram");
|
|
27
27
|
console.log(" topogram extract ./existing-app --out ./extracted-topogram --from db,api,ui");
|
|
28
28
|
console.log(" topogram extract ./existing-cli --out ./extracted-topogram --from cli");
|
|
29
|
+
console.log(" topogram extract ./existing-cli --out ./extracted-topogram --from cli --extractor @topogram/extractor-node-cli");
|
|
30
|
+
console.log(" topogram extract ./existing-app --out ./extracted-topogram --extractor-policy ./topogram.extractor-policy.json");
|
|
29
31
|
console.log(" topogram extract diff ./extracted-topogram");
|
|
30
32
|
console.log(" topogram extract refresh ./extracted-topogram --from ./existing-app --dry-run");
|
|
31
33
|
console.log(" topogram extract refresh ./extracted-topogram --from ./existing-app");
|
|
@@ -149,7 +149,7 @@ export function countFilesRecursive(rootPath) {
|
|
|
149
149
|
/**
|
|
150
150
|
* @param {string} sourcePath
|
|
151
151
|
* @param {string} targetPath
|
|
152
|
-
* @param {{ from?: string|null }} [options]
|
|
152
|
+
* @param {{ from?: string|null, extractorSpecs?: string[], extractorPolicyPath?: string|null, cwd?: string|null }} [options]
|
|
153
153
|
* @returns {{ ok: boolean, sourcePath: string, targetPath: string, workspaceRoot: string, topogramRoot: string, projectConfigPath: string, provenancePath: string, tracks: string[], sourceFiles: number, rawCandidateFiles: number, reconcileFiles: number, writtenFiles: string[], candidateCounts: Record<string, number>, nextCommands: string[] }}
|
|
154
154
|
*/
|
|
155
155
|
export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, options = {}) {
|
|
@@ -166,7 +166,12 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
166
166
|
const topogramRoot = path.join(targetRoot, DEFAULT_TOPO_FOLDER_NAME);
|
|
167
167
|
fs.mkdirSync(topogramRoot, { recursive: true });
|
|
168
168
|
const sourceFiles = collectImportSourceFileRecords(sourceRoot, { excludeRoots: [targetRoot] });
|
|
169
|
-
const importResult = runWorkflow("import-app", sourceRoot, {
|
|
169
|
+
const importResult = runWorkflow("import-app", sourceRoot, {
|
|
170
|
+
from: options.from || null,
|
|
171
|
+
extractorSpecs: options.extractorSpecs || [],
|
|
172
|
+
extractorPolicyPath: options.extractorPolicyPath || null,
|
|
173
|
+
cwd: options.cwd || process.cwd()
|
|
174
|
+
});
|
|
170
175
|
const rawCandidateFiles = writeRelativeFiles(topogramRoot, importResult.files || {});
|
|
171
176
|
|
|
172
177
|
const projectConfigPath = path.join(targetRoot, "topogram.project.json");
|
|
@@ -182,6 +187,7 @@ export function buildBrownfieldImportWorkspacePayload(sourcePath, targetPath, op
|
|
|
182
187
|
tracks: importResult.summary.tracks || [],
|
|
183
188
|
findingsCount: importResult.summary.findings_count || 0,
|
|
184
189
|
candidateCounts,
|
|
190
|
+
extractorPackages: importResult.summary.package_extractors || [],
|
|
185
191
|
files: sourceFiles
|
|
186
192
|
});
|
|
187
193
|
const writtenFiles = [
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "./import.js";
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* @param {{ commandArgs: Record<string, any>, inputPath: string|null|undefined, outPath?: string|null, fromValue?: string|null, reasonValue?: string|null, refreshAdopted?: boolean, dryRun?: boolean, write?: boolean, force?: boolean, json?: boolean }} context
|
|
28
|
+
* @param {{ commandArgs: Record<string, any>, inputPath: string|null|undefined, outPath?: string|null, fromValue?: string|null, extractorSpecs?: string[], extractorPolicyPath?: string|null, reasonValue?: string|null, refreshAdopted?: boolean, dryRun?: boolean, write?: boolean, force?: boolean, json?: boolean }} context
|
|
29
29
|
* @returns {number}
|
|
30
30
|
*/
|
|
31
31
|
export function runImportCommand(context) {
|
|
@@ -34,6 +34,8 @@ export function runImportCommand(context) {
|
|
|
34
34
|
inputPath,
|
|
35
35
|
outPath = null,
|
|
36
36
|
fromValue = null,
|
|
37
|
+
extractorSpecs = [],
|
|
38
|
+
extractorPolicyPath = null,
|
|
37
39
|
reasonValue = null,
|
|
38
40
|
refreshAdopted = false,
|
|
39
41
|
dryRun = false,
|
|
@@ -49,7 +51,7 @@ export function runImportCommand(context) {
|
|
|
49
51
|
printExtractHelp();
|
|
50
52
|
return 1;
|
|
51
53
|
}
|
|
52
|
-
const payload = buildBrownfieldImportWorkspacePayload(inputPath || "", outPath, { from: fromValue });
|
|
54
|
+
const payload = buildBrownfieldImportWorkspacePayload(inputPath || "", outPath, { from: fromValue, extractorSpecs, extractorPolicyPath, cwd: process.cwd() });
|
|
53
55
|
if (json) {
|
|
54
56
|
console.log(stableStringify(payload));
|
|
55
57
|
} else {
|
package/src/cli/dispatcher.js
CHANGED
|
@@ -8,6 +8,7 @@ import { runCheckCommand } from "./commands/check.js";
|
|
|
8
8
|
import { runCopyCommand } from "./commands/copy.js";
|
|
9
9
|
import { runEmitCommand } from "./commands/emit.js";
|
|
10
10
|
import { runGenerateAppCommand } from "./commands/generate.js";
|
|
11
|
+
import { runExtractorCommand } from "./commands/extractor.js";
|
|
11
12
|
import { runGeneratorCommand } from "./commands/generator.js";
|
|
12
13
|
import { runGeneratorPolicyCommand } from "./commands/generator-policy.js";
|
|
13
14
|
import { runImportCommand } from "./commands/import-runner.js";
|
|
@@ -81,6 +82,8 @@ export async function runCliDispatch(context) {
|
|
|
81
82
|
workflowName,
|
|
82
83
|
workflowId,
|
|
83
84
|
fromValue,
|
|
85
|
+
extractorSpecs,
|
|
86
|
+
extractorPolicyPath,
|
|
84
87
|
adoptValue,
|
|
85
88
|
reasonValue,
|
|
86
89
|
modeId,
|
|
@@ -225,6 +228,15 @@ export async function runCliDispatch(context) {
|
|
|
225
228
|
});
|
|
226
229
|
}
|
|
227
230
|
|
|
231
|
+
if (commandArgs?.extractorCommand || commandArgs?.extractorPolicyCommand) {
|
|
232
|
+
return runExtractorCommand({
|
|
233
|
+
commandArgs,
|
|
234
|
+
inputPath,
|
|
235
|
+
json: emitJson,
|
|
236
|
+
cwd: process.cwd()
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
228
240
|
if (commandArgs?.generatorPolicyCommand) {
|
|
229
241
|
return runGeneratorPolicyCommand({ commandArgs, inputPath, json: emitJson });
|
|
230
242
|
}
|
|
@@ -259,6 +271,8 @@ export async function runCliDispatch(context) {
|
|
|
259
271
|
inputPath,
|
|
260
272
|
outPath,
|
|
261
273
|
fromValue,
|
|
274
|
+
extractorSpecs,
|
|
275
|
+
extractorPolicyPath,
|
|
262
276
|
reasonValue,
|
|
263
277
|
refreshAdopted,
|
|
264
278
|
dryRun: args.includes("--dry-run"),
|
package/src/cli/help-dispatch.js
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
printDoctorHelp
|
|
14
14
|
} from "./commands/doctor.js";
|
|
15
|
+
import {
|
|
16
|
+
printExtractorHelp
|
|
17
|
+
} from "./commands/extractor.js";
|
|
15
18
|
import {
|
|
16
19
|
printGeneratorHelp
|
|
17
20
|
} from "./commands/generator.js";
|
|
@@ -86,6 +89,10 @@ export function printCommandHelp(command) {
|
|
|
86
89
|
printGeneratorHelp();
|
|
87
90
|
return true;
|
|
88
91
|
}
|
|
92
|
+
if (command === "extractor") {
|
|
93
|
+
printExtractorHelp();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
89
96
|
if (command === "template") {
|
|
90
97
|
printTemplateHelp();
|
|
91
98
|
return true;
|
|
@@ -176,6 +183,10 @@ export function handleUnparsedCommandHelp(args) {
|
|
|
176
183
|
printGeneratorHelp();
|
|
177
184
|
return args[1] ? 1 : 0;
|
|
178
185
|
}
|
|
186
|
+
if (args[0] === "extractor") {
|
|
187
|
+
printExtractorHelp();
|
|
188
|
+
return args[1] ? 1 : 0;
|
|
189
|
+
}
|
|
179
190
|
if (args[0] === "template") {
|
|
180
191
|
printTemplateHelp();
|
|
181
192
|
return args[1] ? 1 : 0;
|
package/src/cli/help.js
CHANGED
|
@@ -38,7 +38,7 @@ export function printUsage(options = {}) {
|
|
|
38
38
|
console.log(" or: topogram package update-cli <version|--latest> [--json]");
|
|
39
39
|
console.log(" or: topogram copy <source> <target> [--version <version>] [--catalog <path-or-source>] [--json]");
|
|
40
40
|
console.log(" or: topogram copy --list [--json] [--catalog <path-or-source>]");
|
|
41
|
-
console.log(" or: topogram extract <app-path> --out <target> [--from <track[,track]>] [--json]");
|
|
41
|
+
console.log(" or: topogram extract <app-path> --out <target> [--from <track[,track]>] [--extractor <id-or-package-or-path>] [--json]");
|
|
42
42
|
console.log(" or: topogram extract refresh [path] [--from <app-path>] [--dry-run] [--json]");
|
|
43
43
|
console.log(" or: topogram extract diff [path] [--json]");
|
|
44
44
|
console.log(" or: topogram extract check [path] [--json]");
|
|
@@ -67,6 +67,14 @@ export function printUsage(options = {}) {
|
|
|
67
67
|
console.log(" or: topogram generator policy check [path] [--json]");
|
|
68
68
|
console.log(" or: topogram generator policy explain [path] [--json]");
|
|
69
69
|
console.log(" or: topogram generator policy pin [package@version] [path] [--json]");
|
|
70
|
+
console.log(" or: topogram extractor list [--json]");
|
|
71
|
+
console.log(" or: topogram extractor show <id-or-package> [--json]");
|
|
72
|
+
console.log(" or: topogram extractor check <path-or-package> [--json]");
|
|
73
|
+
console.log(" or: topogram extractor policy init [path] [--json]");
|
|
74
|
+
console.log(" or: topogram extractor policy status [path] [--json]");
|
|
75
|
+
console.log(" or: topogram extractor policy check [path] [--json]");
|
|
76
|
+
console.log(" or: topogram extractor policy explain [path] [--json]");
|
|
77
|
+
console.log(" or: topogram extractor policy pin [package@version] [path] [--json]");
|
|
70
78
|
console.log(" or: topogram init [path] [--with-sdlc] [--json]");
|
|
71
79
|
console.log("");
|
|
72
80
|
console.log("Common commands:");
|
|
@@ -98,6 +106,11 @@ export function printUsage(options = {}) {
|
|
|
98
106
|
console.log(" topogram generator show @topogram/generator-react-web");
|
|
99
107
|
console.log(" topogram generator check ./generator-package");
|
|
100
108
|
console.log(" topogram generator policy check");
|
|
109
|
+
console.log(" topogram extractor list");
|
|
110
|
+
console.log(" topogram extractor show @topogram/extractor-prisma-db");
|
|
111
|
+
console.log(" topogram extractor check ./extractor-package");
|
|
112
|
+
console.log(" topogram extractor policy check");
|
|
113
|
+
console.log(" topogram extract ./express-api --out ./extracted-topogram --from api --extractor @topogram/extractor-express-api");
|
|
101
114
|
console.log(" topogram generate");
|
|
102
115
|
console.log(" topogram extract ./existing-app --out ./extracted-topogram");
|
|
103
116
|
console.log(" topogram extract diff ./extracted-topogram");
|
package/src/cli/options.js
CHANGED
|
@@ -22,6 +22,21 @@ export function optionValueIfPresent(args, flag) {
|
|
|
22
22
|
return value && !value.startsWith("-") ? value : null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @param {string[]} args
|
|
27
|
+
* @param {string} flag
|
|
28
|
+
* @returns {string[]}
|
|
29
|
+
*/
|
|
30
|
+
export function optionValues(args, flag) {
|
|
31
|
+
const values = [];
|
|
32
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
33
|
+
if (args[index] === flag && args[index + 1] && !args[index + 1].startsWith("-")) {
|
|
34
|
+
values.push(args[index + 1]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return values;
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
/**
|
|
26
41
|
* @param {string[]} args
|
|
27
42
|
* @param {string} flag
|
|
@@ -51,6 +66,8 @@ export function parseCliOptions(args, commandArgs) {
|
|
|
51
66
|
workflowName: commandArgs?.workflowName || (!generateTarget && workflowFlagValue ? workflowFlagValue : null),
|
|
52
67
|
workflowId: generateTarget ? workflowFlagValue : null,
|
|
53
68
|
fromValue: optionValue(args, "--from"),
|
|
69
|
+
extractorSpecs: optionValues(args, "--extractor"),
|
|
70
|
+
extractorPolicyPath: optionValueIfPresent(args, "--extractor-policy"),
|
|
54
71
|
adoptValue: commandArgs?.adoptValue || optionValue(args, "--adopt"),
|
|
55
72
|
reasonValue: optionValueIfPresent(args, "--reason"),
|
|
56
73
|
modeId: optionValue(args, "--mode"),
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createExtractorSmokeContext,
|
|
5
|
+
loadExtractorPackageAdapterForSpec,
|
|
6
|
+
validateExtractorAdapter
|
|
7
|
+
} from "./packages.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import("./registry.js").ExtractorManifest} ExtractorManifest
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ExtractorCheckResult
|
|
15
|
+
* @property {boolean} ok
|
|
16
|
+
* @property {string} sourceSpec
|
|
17
|
+
* @property {"path"|"package"} source
|
|
18
|
+
* @property {string|null} packageName
|
|
19
|
+
* @property {string|null} packageRoot
|
|
20
|
+
* @property {string|null} manifestPath
|
|
21
|
+
* @property {ExtractorManifest|null} manifest
|
|
22
|
+
* @property {Array<{ name: string, ok: boolean, message: string }>} checks
|
|
23
|
+
* @property {string[]} errors
|
|
24
|
+
* @property {{ extractors: number, findings: number, candidateKeys: number, diagnostics: number }|null} smoke
|
|
25
|
+
* @property {boolean} executesPackageCode
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {any} result
|
|
30
|
+
* @returns {{ ok: boolean, message: string, smoke: { findings: number, candidateKeys: number, diagnostics: number }|null }}
|
|
31
|
+
*/
|
|
32
|
+
function validateExtractResult(result) {
|
|
33
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
34
|
+
return { ok: false, message: "extract(context) must return an object", smoke: null };
|
|
35
|
+
}
|
|
36
|
+
if (result.findings != null && !Array.isArray(result.findings)) {
|
|
37
|
+
return { ok: false, message: "extract(context) findings must be an array when present", smoke: null };
|
|
38
|
+
}
|
|
39
|
+
if (result.diagnostics != null && !Array.isArray(result.diagnostics)) {
|
|
40
|
+
return { ok: false, message: "extract(context) diagnostics must be an array when present", smoke: null };
|
|
41
|
+
}
|
|
42
|
+
if (!result.candidates || typeof result.candidates !== "object" || Array.isArray(result.candidates)) {
|
|
43
|
+
return { ok: false, message: "extract(context) result must include a candidates object", smoke: null };
|
|
44
|
+
}
|
|
45
|
+
for (const [key, value] of Object.entries(result.candidates)) {
|
|
46
|
+
if (!Array.isArray(value)) {
|
|
47
|
+
return { ok: false, message: `extract(context) candidates.${key} must be an array`, smoke: null };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
message: `extract(context) returned ${Object.keys(result.candidates).length} candidate bucket(s)`,
|
|
53
|
+
smoke: {
|
|
54
|
+
findings: Array.isArray(result.findings) ? result.findings.length : 0,
|
|
55
|
+
candidateKeys: Object.keys(result.candidates).length,
|
|
56
|
+
diagnostics: Array.isArray(result.diagnostics) ? result.diagnostics.length : 0
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} sourceSpec
|
|
63
|
+
* @param {{ cwd?: string }} [options]
|
|
64
|
+
* @returns {ExtractorCheckResult}
|
|
65
|
+
*/
|
|
66
|
+
export function checkExtractorPack(sourceSpec, options = {}) {
|
|
67
|
+
/** @type {ExtractorCheckResult} */
|
|
68
|
+
const payload = {
|
|
69
|
+
ok: false,
|
|
70
|
+
sourceSpec,
|
|
71
|
+
source: "package",
|
|
72
|
+
packageName: null,
|
|
73
|
+
packageRoot: null,
|
|
74
|
+
manifestPath: null,
|
|
75
|
+
manifest: null,
|
|
76
|
+
checks: [],
|
|
77
|
+
errors: [],
|
|
78
|
+
smoke: null,
|
|
79
|
+
executesPackageCode: true
|
|
80
|
+
};
|
|
81
|
+
if (!sourceSpec || sourceSpec.startsWith("-")) {
|
|
82
|
+
payload.errors.push("Usage: topogram extractor check <path-or-package>");
|
|
83
|
+
payload.checks.push({ name: "source", ok: false, message: payload.errors[0] });
|
|
84
|
+
return payload;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const loaded = loadExtractorPackageAdapterForSpec(sourceSpec, options);
|
|
88
|
+
payload.source = loaded.source;
|
|
89
|
+
payload.packageName = loaded.packageName;
|
|
90
|
+
payload.packageRoot = loaded.packageRoot;
|
|
91
|
+
payload.manifestPath = loaded.manifestPath;
|
|
92
|
+
payload.manifest = loaded.manifest;
|
|
93
|
+
if (!loaded.manifest) {
|
|
94
|
+
payload.errors.push(...loaded.errors);
|
|
95
|
+
payload.checks.push({ name: "manifest-load", ok: false, message: loaded.errors.join(" ") || "Could not load extractor manifest." });
|
|
96
|
+
return payload;
|
|
97
|
+
}
|
|
98
|
+
payload.checks.push({ name: "manifest-load", ok: true, message: loaded.manifestPath || sourceSpec });
|
|
99
|
+
if (!loaded.adapter || loaded.errors.length > 0) {
|
|
100
|
+
payload.errors.push(...loaded.errors);
|
|
101
|
+
payload.checks.push({ name: "adapter-load", ok: false, message: loaded.errors.join(" ") || "Could not load extractor adapter." });
|
|
102
|
+
return payload;
|
|
103
|
+
}
|
|
104
|
+
payload.checks.push({ name: "adapter-load", ok: true, message: "Adapter export loaded." });
|
|
105
|
+
|
|
106
|
+
const adapterValidation = validateExtractorAdapter(loaded.adapter, loaded.manifest);
|
|
107
|
+
payload.checks.push({
|
|
108
|
+
name: "adapter-shape",
|
|
109
|
+
ok: adapterValidation.errors.length === 0,
|
|
110
|
+
message: adapterValidation.errors.length === 0 ? "Adapter shape is valid." : adapterValidation.errors.join(" ")
|
|
111
|
+
});
|
|
112
|
+
if (adapterValidation.errors.length > 0) {
|
|
113
|
+
payload.errors.push(...adapterValidation.errors);
|
|
114
|
+
return payload;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const context = createExtractorSmokeContext();
|
|
118
|
+
let totalFindings = 0;
|
|
119
|
+
let totalCandidateKeys = 0;
|
|
120
|
+
let totalDiagnostics = 0;
|
|
121
|
+
for (const extractor of adapterValidation.extractors) {
|
|
122
|
+
try {
|
|
123
|
+
const detection = extractor.detect(context) || { score: 0, reasons: [] };
|
|
124
|
+
if (!detection || typeof detection !== "object" || typeof detection.score !== "number") {
|
|
125
|
+
payload.errors.push(`Extractor '${extractor.id}' detect(context) must return { score, reasons }.`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const result = extractor.extract(context) || { findings: [], candidates: {} };
|
|
129
|
+
const validation = validateExtractResult(result);
|
|
130
|
+
if (!validation.ok || !validation.smoke) {
|
|
131
|
+
payload.errors.push(`Extractor '${extractor.id}' ${validation.message}.`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
totalFindings += validation.smoke.findings;
|
|
135
|
+
totalCandidateKeys += validation.smoke.candidateKeys;
|
|
136
|
+
totalDiagnostics += validation.smoke.diagnostics;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
payload.errors.push(`Extractor '${extractor.id}' smoke failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
payload.checks.push({
|
|
142
|
+
name: "smoke-extract",
|
|
143
|
+
ok: payload.errors.length === 0,
|
|
144
|
+
message: payload.errors.length === 0 ? `Ran ${adapterValidation.extractors.length} extractor smoke check(s).` : payload.errors.join(" ")
|
|
145
|
+
});
|
|
146
|
+
payload.smoke = {
|
|
147
|
+
extractors: adapterValidation.extractors.length,
|
|
148
|
+
findings: totalFindings,
|
|
149
|
+
candidateKeys: totalCandidateKeys,
|
|
150
|
+
diagnostics: totalDiagnostics
|
|
151
|
+
};
|
|
152
|
+
payload.ok = payload.errors.length === 0;
|
|
153
|
+
return payload;
|
|
154
|
+
}
|
|
155
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
export const FIRST_PARTY_EXTRACTOR_PACKAGES = [
|
|
4
|
+
{
|
|
5
|
+
package: "@topogram/extractor-prisma-db",
|
|
6
|
+
id: "@topogram/extractor-prisma-db",
|
|
7
|
+
version: "1",
|
|
8
|
+
label: "Prisma DB",
|
|
9
|
+
tracks: ["db"],
|
|
10
|
+
stack: { orm: "prisma", domain: "database" },
|
|
11
|
+
capabilities: { schema: true, migrations: true, maintainedSeams: true },
|
|
12
|
+
candidateKinds: ["entity", "enum", "relation", "index", "maintained_db_migration_seam"],
|
|
13
|
+
evidenceTypes: ["runtime_source", "parser_config"],
|
|
14
|
+
extractors: ["db.prisma"],
|
|
15
|
+
useWhen: "Use for Prisma schema.prisma models plus Prisma migration evidence.",
|
|
16
|
+
extracts: ["entities", "enums", "relations", "indexes", "maintained DB seam proposals"],
|
|
17
|
+
exampleSource: "./prisma-app"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
package: "@topogram/extractor-express-api",
|
|
21
|
+
id: "@topogram/extractor-express-api",
|
|
22
|
+
version: "1",
|
|
23
|
+
label: "Express API",
|
|
24
|
+
tracks: ["api"],
|
|
25
|
+
stack: { runtime: "node", framework: "express" },
|
|
26
|
+
capabilities: { routes: true, parameters: true, authHints: true },
|
|
27
|
+
candidateKinds: ["capability", "route", "stack"],
|
|
28
|
+
evidenceTypes: ["runtime_source", "parser_config"],
|
|
29
|
+
extractors: ["api.express"],
|
|
30
|
+
useWhen: "Use for Express route files, routers, params, middleware, and auth hints.",
|
|
31
|
+
extracts: ["API route candidates", "capability suggestions", "parameter evidence", "auth hints", "stack evidence"],
|
|
32
|
+
exampleSource: "./express-api"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
package: "@topogram/extractor-drizzle-db",
|
|
36
|
+
id: "@topogram/extractor-drizzle-db",
|
|
37
|
+
version: "1",
|
|
38
|
+
label: "Drizzle DB",
|
|
39
|
+
tracks: ["db"],
|
|
40
|
+
stack: { orm: "drizzle", domain: "database" },
|
|
41
|
+
capabilities: { schema: true, migrations: true, maintainedSeams: true },
|
|
42
|
+
candidateKinds: ["entity", "enum", "relation", "index", "maintained_db_migration_seam"],
|
|
43
|
+
evidenceTypes: ["runtime_source", "parser_config"],
|
|
44
|
+
extractors: ["db.drizzle"],
|
|
45
|
+
useWhen: "Use for Drizzle config, schema modules, table definitions, and migration output.",
|
|
46
|
+
extracts: ["entities", "relations", "indexes", "maintained DB seam proposals", "schema/migration evidence"],
|
|
47
|
+
exampleSource: "./drizzle-app"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
package: "@topogram/extractor-node-cli",
|
|
51
|
+
id: "@topogram/extractor-node-cli",
|
|
52
|
+
version: "1",
|
|
53
|
+
label: "Node CLI",
|
|
54
|
+
tracks: ["cli"],
|
|
55
|
+
stack: { runtime: "node", domain: "cli" },
|
|
56
|
+
capabilities: { commands: true, options: true, effects: true },
|
|
57
|
+
candidateKinds: ["command", "capability", "cli_surface"],
|
|
58
|
+
evidenceTypes: ["runtime_source", "parser_config"],
|
|
59
|
+
extractors: ["cli.node-package"],
|
|
60
|
+
useWhen: "Use for Node package CLIs with bin entries, scripts, command modules, and help text.",
|
|
61
|
+
extracts: ["command candidates", "options", "effects", "CLI surface projections", "capability suggestions"],
|
|
62
|
+
exampleSource: "./existing-cli"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
package: "@topogram/extractor-react-router",
|
|
66
|
+
id: "@topogram/extractor-react-router",
|
|
67
|
+
version: "1",
|
|
68
|
+
label: "React Router UI",
|
|
69
|
+
tracks: ["ui"],
|
|
70
|
+
stack: { framework: "react-router", domain: "ui" },
|
|
71
|
+
capabilities: { routes: true, screens: true, flows: true, widgets: true },
|
|
72
|
+
candidateKinds: ["screen", "route", "action", "flow", "widget", "shape", "stack"],
|
|
73
|
+
evidenceTypes: ["runtime_source", "parser_config"],
|
|
74
|
+
extractors: ["ui.react-router"],
|
|
75
|
+
useWhen: "Use for React Router route trees, route modules, screen hints, and non-resource UI flows.",
|
|
76
|
+
extracts: ["screens", "routes", "flow candidates", "widget evidence", "stack evidence"],
|
|
77
|
+
exampleSource: "./react-router-app"
|
|
78
|
+
}
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
export const FIRST_PARTY_EXTRACTORS_BY_PACKAGE = new Map(FIRST_PARTY_EXTRACTOR_PACKAGES.map((item) => [item.package, item]));
|
|
82
|
+
export const FIRST_PARTY_EXTRACTORS_BY_ID = new Map(FIRST_PARTY_EXTRACTOR_PACKAGES.map((item) => [item.id, item]));
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string|null|undefined} value
|
|
86
|
+
* @returns {typeof FIRST_PARTY_EXTRACTOR_PACKAGES[number]|null}
|
|
87
|
+
*/
|
|
88
|
+
export function firstPartyExtractorInfo(value) {
|
|
89
|
+
if (!value) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return FIRST_PARTY_EXTRACTORS_BY_PACKAGE.get(value) || FIRST_PARTY_EXTRACTORS_BY_ID.get(value) || null;
|
|
93
|
+
}
|