@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.
@@ -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, { from: options.from || null });
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 {
@@ -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"),
@@ -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");
@@ -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
+ }