@danielblomma/cortex-mcp 2.4.0 → 2.4.2
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/CHANGELOG.md +226 -0
- package/README.md +24 -3
- package/bin/cli/arguments.mjs +60 -0
- package/bin/cli/context-passthrough.mjs +129 -0
- package/bin/cli/daemon.mjs +157 -0
- package/bin/cli/enterprise.mjs +516 -0
- package/bin/cli/help.mjs +88 -0
- package/bin/cli/hooks.mjs +199 -0
- package/bin/cli/mcp-command.mjs +87 -0
- package/bin/cli/paths.mjs +14 -0
- package/bin/cli/process.mjs +49 -0
- package/bin/cli/project-commands.mjs +237 -0
- package/bin/cli/project-runtime.mjs +34 -0
- package/bin/cli/query-command.mjs +18 -0
- package/bin/cli/router.mjs +66 -0
- package/bin/cli/run-command.mjs +44 -0
- package/bin/cli/scaffold-ownership.mjs +936 -0
- package/bin/cli/scaffold.mjs +687 -0
- package/bin/cli/stage-command.mjs +9 -0
- package/bin/cli/telemetry-command.mjs +18 -0
- package/bin/cli/trusted-runtime.mjs +18 -0
- package/bin/cortex.mjs +12 -1607
- package/bin/daemon-control.mjs +162 -0
- package/mcp-registry-submission.json +1 -1
- package/package.json +8 -3
- package/scaffold/mcp/dist/.cortex-build-hash +1 -0
- package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
- package/scaffold/mcp/dist/cli/govern.js +937 -0
- package/scaffold/mcp/dist/cli/query.js +409 -0
- package/scaffold/mcp/dist/cli/run.js +295 -0
- package/scaffold/mcp/dist/cli/stage.js +308 -0
- package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
- package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
- package/scaffold/mcp/dist/contextEntities.js +282 -0
- package/scaffold/mcp/dist/core/audit/query.js +72 -0
- package/scaffold/mcp/dist/core/audit/writer.js +28 -0
- package/scaffold/mcp/dist/core/config.js +235 -0
- package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
- package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
- package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
- package/scaffold/mcp/dist/core/govern-paths.js +21 -0
- package/scaffold/mcp/dist/core/index.js +14 -0
- package/scaffold/mcp/dist/core/license.js +278 -0
- package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
- package/scaffold/mcp/dist/core/policy/injection.js +172 -0
- package/scaffold/mcp/dist/core/policy/store.js +179 -0
- package/scaffold/mcp/dist/core/rbac/check.js +30 -0
- package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
- package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
- package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
- package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
- package/scaffold/mcp/dist/core/validators/config.js +44 -0
- package/scaffold/mcp/dist/core/validators/engine.js +120 -0
- package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
- package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
- package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
- package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
- package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
- package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
- package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
- package/scaffold/mcp/dist/core/workflow/index.js +11 -0
- package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
- package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
- package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
- package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
- package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
- package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
- package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
- package/scaffold/mcp/dist/daemon/client.js +130 -0
- package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
- package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
- package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
- package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
- package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
- package/scaffold/mcp/dist/daemon/main.js +449 -0
- package/scaffold/mcp/dist/daemon/paths.js +36 -0
- package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
- package/scaffold/mcp/dist/daemon/protocol.js +8 -0
- package/scaffold/mcp/dist/daemon/server.js +180 -0
- package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
- package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
- package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
- package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
- package/scaffold/mcp/dist/defaults.js +6 -0
- package/scaffold/mcp/dist/embed.js +627 -0
- package/scaffold/mcp/dist/embedScheduler.js +479 -0
- package/scaffold/mcp/dist/embeddings.js +167 -0
- package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
- package/scaffold/mcp/dist/enterprise/index.js +327 -0
- package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
- package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
- package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
- package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
- package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
- package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
- package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
- package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
- package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
- package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
- package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
- package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
- package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
- package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
- package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
- package/scaffold/mcp/dist/frontmatter.js +33 -0
- package/scaffold/mcp/dist/graph.js +769 -0
- package/scaffold/mcp/dist/graphCsv.js +55 -0
- package/scaffold/mcp/dist/graphMetrics.js +8 -0
- package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
- package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
- package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
- package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
- package/scaffold/mcp/dist/hooks/session-end.js +43 -0
- package/scaffold/mcp/dist/hooks/session-start.js +41 -0
- package/scaffold/mcp/dist/hooks/shared.js +194 -0
- package/scaffold/mcp/dist/hooks/stop.js +28 -0
- package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
- package/scaffold/mcp/dist/impactPresentation.js +137 -0
- package/scaffold/mcp/dist/impactRanking.js +191 -0
- package/scaffold/mcp/dist/impactResponse.js +30 -0
- package/scaffold/mcp/dist/impactResults.js +105 -0
- package/scaffold/mcp/dist/impactSeed.js +20 -0
- package/scaffold/mcp/dist/impactTraversal.js +64 -0
- package/scaffold/mcp/dist/jsonl.js +77 -0
- package/scaffold/mcp/dist/loadGraph.js +759 -0
- package/scaffold/mcp/dist/lruCache.js +38 -0
- package/scaffold/mcp/dist/paths.js +97 -0
- package/scaffold/mcp/dist/patternEvidence.js +272 -0
- package/scaffold/mcp/dist/plugin.js +81 -0
- package/scaffold/mcp/dist/presets.js +78 -0
- package/scaffold/mcp/dist/relatedResponse.js +18 -0
- package/scaffold/mcp/dist/relatedTraversal.js +78 -0
- package/scaffold/mcp/dist/rules.js +23 -0
- package/scaffold/mcp/dist/search.js +212 -0
- package/scaffold/mcp/dist/searchCore.js +457 -0
- package/scaffold/mcp/dist/searchResults.js +230 -0
- package/scaffold/mcp/dist/server.js +317 -0
- package/scaffold/mcp/dist/types.js +1 -0
- package/scaffold/mcp/package-lock.json +321 -200
- package/scaffold/mcp/package.json +15 -6
- package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
- package/scaffold/mcp/src/cli/govern.ts +137 -52
- package/scaffold/mcp/src/cli/run.ts +53 -19
- package/scaffold/mcp/src/cli/stage.ts +8 -3
- package/scaffold/mcp/src/core/config.ts +9 -2
- package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
- package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
- package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
- package/scaffold/mcp/src/core/govern-paths.ts +30 -0
- package/scaffold/mcp/src/core/license.ts +186 -17
- package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
- package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
- package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
- package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
- package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
- package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
- package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
- package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
- package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
- package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
- package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
- package/scaffold/mcp/src/daemon/main.ts +99 -25
- package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
- package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
- package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
- package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
- package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
- package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
- package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
- package/scaffold/mcp/src/enterprise/reviews/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
- package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
- package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
- package/scaffold/mcp/src/plugin.ts +20 -0
- package/scaffold/mcp/tests/copilot-shim.test.mjs +19 -1
- package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
- package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
- package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
- package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
- package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
- package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
- package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
- package/scaffold/mcp/tests/license.test.mjs +367 -0
- package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
- package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
- package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
- package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
- package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
- package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
- package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
- package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
- package/scaffold/ownership/baseline-v2.4.1.json +22 -0
- package/scaffold/ownership/current.json +4 -0
- package/scaffold/ownership/v1.json +456 -0
- package/scaffold/scripts/ingest-parsers.mjs +1 -387
- package/scaffold/scripts/ingest-worker.mjs +4 -1
- package/scaffold/scripts/ingest.mjs +5 -3899
- package/scaffold/scripts/lib/ingest/arguments.mjs +78 -0
- package/scaffold/scripts/lib/ingest/chunks.mjs +212 -0
- package/scaffold/scripts/lib/ingest/config.mjs +120 -0
- package/scaffold/scripts/lib/ingest/constants.mjs +222 -0
- package/scaffold/scripts/lib/ingest/files.mjs +387 -0
- package/scaffold/scripts/lib/ingest/incremental-state.mjs +131 -0
- package/scaffold/scripts/lib/ingest/io.mjs +78 -0
- package/scaffold/scripts/lib/ingest/main.mjs +76 -0
- package/scaffold/scripts/lib/ingest/parser-composition.mjs +140 -0
- package/scaffold/scripts/lib/ingest/parser-registry.mjs +387 -0
- package/scaffold/scripts/lib/ingest/pipeline-stages.mjs +1625 -0
- package/scaffold/scripts/lib/ingest/projects.mjs +272 -0
- package/scaffold/scripts/lib/ingest/relations.mjs +860 -0
- package/scaffold/scripts/lib/ingest/runtime-paths.mjs +11 -0
- package/scaffold/scripts/lib/ingest/workers.mjs +264 -0
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_KEY_REFERENCE_PATTERNS,
|
|
4
|
+
NAMED_RESOURCE_REFERENCE_SOURCE_EXTENSIONS,
|
|
5
|
+
PROJECT_DEFINITION_EXTENSIONS,
|
|
6
|
+
SQL_OBJECT_REFERENCE_PATTERNS,
|
|
7
|
+
SQL_REFERENCE_SOURCE_EXTENSIONS,
|
|
8
|
+
SQL_RESOURCE_KEY_PATTERNS,
|
|
9
|
+
SQL_STRING_REFERENCE_PATTERNS
|
|
10
|
+
} from "./constants.mjs";
|
|
11
|
+
import { normalizeToken, toPosixPath, uniqueSorted } from "./files.mjs";
|
|
12
|
+
import { isWindowChunkId } from "./chunks.mjs";
|
|
13
|
+
|
|
14
|
+
export function relationKey(...parts) {
|
|
15
|
+
return parts.map((part) => String(part ?? "")).join("|");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function uniqueRelations(relations) {
|
|
19
|
+
const deduped = new Map();
|
|
20
|
+
for (const relation of relations) {
|
|
21
|
+
const key = relationKey(relation.from, relation.to, relation.note);
|
|
22
|
+
if (!deduped.has(key)) {
|
|
23
|
+
deduped.set(key, relation);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return [...deduped.values()].sort((a, b) =>
|
|
27
|
+
relationKey(a.from, a.to, a.note).localeCompare(relationKey(b.from, b.to, b.note))
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function normalizeSqlName(value) {
|
|
32
|
+
if (!value) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return String(value)
|
|
37
|
+
.trim()
|
|
38
|
+
.replace(/[;"`]/g, "")
|
|
39
|
+
.replace(/\[(.+?)\]/g, "$1")
|
|
40
|
+
.replace(/\s+/g, "")
|
|
41
|
+
.replace(/^\.+|\.+$/g, "")
|
|
42
|
+
.replace(/\.\.+/g, ".")
|
|
43
|
+
.toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function sqlChunkAliases(name) {
|
|
47
|
+
const normalized = normalizeSqlName(name);
|
|
48
|
+
if (!normalized) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const aliases = new Set([normalized]);
|
|
53
|
+
const parts = normalized.split(".").filter(Boolean);
|
|
54
|
+
if (parts.length > 1) {
|
|
55
|
+
aliases.add(parts[parts.length - 1]);
|
|
56
|
+
}
|
|
57
|
+
return [...aliases];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function configChunkAliases(chunk) {
|
|
61
|
+
const aliases = new Set();
|
|
62
|
+
const rawKey = String(chunk?.configKey ?? chunk?.name ?? "");
|
|
63
|
+
const normalizedKey = normalizeToken(rawKey);
|
|
64
|
+
if (normalizedKey) {
|
|
65
|
+
aliases.add(normalizedKey);
|
|
66
|
+
}
|
|
67
|
+
const chunkName = String(chunk?.name ?? "");
|
|
68
|
+
const tail = chunkName.split(".").pop() ?? "";
|
|
69
|
+
const normalizedTail = normalizeToken(tail);
|
|
70
|
+
if (normalizedTail) {
|
|
71
|
+
aliases.add(normalizedTail);
|
|
72
|
+
}
|
|
73
|
+
return [...aliases];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function namedEntryChunkAliases(chunk) {
|
|
77
|
+
const aliases = new Set();
|
|
78
|
+
const rawKey = String(chunk?.resourceKey ?? chunk?.configKey ?? chunk?.name ?? "");
|
|
79
|
+
const normalizedKey = normalizeToken(rawKey);
|
|
80
|
+
if (normalizedKey) {
|
|
81
|
+
aliases.add(normalizedKey);
|
|
82
|
+
}
|
|
83
|
+
const chunkName = String(chunk?.name ?? "");
|
|
84
|
+
const tail = chunkName.split(".").pop() ?? "";
|
|
85
|
+
const normalizedTail = normalizeToken(tail);
|
|
86
|
+
if (normalizedTail) {
|
|
87
|
+
aliases.add(normalizedTail);
|
|
88
|
+
}
|
|
89
|
+
return [...aliases];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildChunkAliasIndexes(chunkRecords) {
|
|
93
|
+
const sqlChunkIdsByAlias = new Map();
|
|
94
|
+
const configChunkIdsByAlias = new Map();
|
|
95
|
+
const resourceChunkIdsByAlias = new Map();
|
|
96
|
+
const settingChunkIdsByAlias = new Map();
|
|
97
|
+
|
|
98
|
+
for (const chunk of chunkRecords) {
|
|
99
|
+
if (isWindowChunkId(chunk?.id)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const language = String(chunk?.language ?? "").toLowerCase();
|
|
104
|
+
if (language === "sql") {
|
|
105
|
+
for (const alias of sqlChunkAliases(chunk.name)) {
|
|
106
|
+
const existing = sqlChunkIdsByAlias.get(alias) ?? [];
|
|
107
|
+
sqlChunkIdsByAlias.set(alias, [...existing, chunk.id]);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (language === "config") {
|
|
113
|
+
for (const alias of configChunkAliases(chunk)) {
|
|
114
|
+
const existing = configChunkIdsByAlias.get(alias) ?? [];
|
|
115
|
+
configChunkIdsByAlias.set(alias, [...existing, chunk.id]);
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (language === "resource") {
|
|
121
|
+
for (const alias of namedEntryChunkAliases(chunk)) {
|
|
122
|
+
const existing = resourceChunkIdsByAlias.get(alias) ?? [];
|
|
123
|
+
resourceChunkIdsByAlias.set(alias, [...existing, chunk.id]);
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (language === "settings") {
|
|
129
|
+
for (const alias of namedEntryChunkAliases(chunk)) {
|
|
130
|
+
const existing = settingChunkIdsByAlias.get(alias) ?? [];
|
|
131
|
+
settingChunkIdsByAlias.set(alias, [...existing, chunk.id]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
sqlChunkIdsByAlias,
|
|
138
|
+
configChunkIdsByAlias,
|
|
139
|
+
resourceChunkIdsByAlias,
|
|
140
|
+
settingChunkIdsByAlias
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function extractSqlReferenceNamesFromString(text) {
|
|
145
|
+
const refs = new Set();
|
|
146
|
+
|
|
147
|
+
const normalizedName = normalizeSqlName(text);
|
|
148
|
+
if (/^[a-z0-9_.]+$/i.test(normalizedName) && normalizedName.includes(".")) {
|
|
149
|
+
refs.add(normalizedName);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const pattern of SQL_STRING_REFERENCE_PATTERNS) {
|
|
153
|
+
let match;
|
|
154
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
155
|
+
const name = normalizeSqlName(match[1]);
|
|
156
|
+
if (!name || name.startsWith("@") || name.startsWith("#")) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
refs.add(name);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return [...refs];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function parseResxSqlReferenceMap(content) {
|
|
167
|
+
const refsByKey = new Map();
|
|
168
|
+
const dataPattern = /<data\b[^>]*\bname="([^"]+)"[^>]*>([\s\S]*?)<\/data>/gi;
|
|
169
|
+
let match;
|
|
170
|
+
|
|
171
|
+
while ((match = dataPattern.exec(content)) !== null) {
|
|
172
|
+
const key = normalizeToken(decodeXmlEntities(match[1]));
|
|
173
|
+
if (!key) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const valueMatch = match[2].match(/<value>([\s\S]*?)<\/value>/i);
|
|
178
|
+
if (!valueMatch) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const value = decodeXmlEntities(valueMatch[1]).trim();
|
|
183
|
+
const refs = extractSqlReferenceNamesFromString(value);
|
|
184
|
+
if (refs.length === 0) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const existing = refsByKey.get(key) ?? [];
|
|
189
|
+
refsByKey.set(key, uniqueSorted([...existing, ...refs]));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return refsByKey;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseResxKeyMap(content) {
|
|
196
|
+
const fileKeys = new Map();
|
|
197
|
+
const dataPattern = /<data\b[^>]*\bname="([^"]+)"[^>]*>/gi;
|
|
198
|
+
let match;
|
|
199
|
+
|
|
200
|
+
while ((match = dataPattern.exec(content)) !== null) {
|
|
201
|
+
const key = normalizeToken(decodeXmlEntities(match[1]));
|
|
202
|
+
if (!key) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
fileKeys.set(key, true);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return fileKeys;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function parseSettingsSqlReferenceMap(content) {
|
|
212
|
+
const refsByKey = new Map();
|
|
213
|
+
const settingPattern = /<Setting\b[^>]*\bName="([^"]+)"[^>]*>([\s\S]*?)<\/Setting>/gi;
|
|
214
|
+
let match;
|
|
215
|
+
|
|
216
|
+
while ((match = settingPattern.exec(content)) !== null) {
|
|
217
|
+
const key = normalizeToken(decodeXmlEntities(match[1]));
|
|
218
|
+
if (!key) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const valueMatch = match[2].match(/<Value(?:\s[^>]*)?>([\s\S]*?)<\/Value>/i);
|
|
223
|
+
if (!valueMatch) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const value = decodeXmlEntities(valueMatch[1]).trim();
|
|
228
|
+
const refs = extractSqlReferenceNamesFromString(value);
|
|
229
|
+
if (refs.length === 0) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const existing = refsByKey.get(key) ?? [];
|
|
234
|
+
refsByKey.set(key, uniqueSorted([...existing, ...refs]));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return refsByKey;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function parseSettingsKeyMap(content) {
|
|
241
|
+
const fileKeys = new Map();
|
|
242
|
+
const settingPattern = /<Setting\b[^>]*\bName="([^"]+)"[^>]*>/gi;
|
|
243
|
+
let match;
|
|
244
|
+
|
|
245
|
+
while ((match = settingPattern.exec(content)) !== null) {
|
|
246
|
+
const key = normalizeToken(decodeXmlEntities(match[1]));
|
|
247
|
+
if (!key) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
fileKeys.set(key, true);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return fileKeys;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function parseConfigKeyMap(content) {
|
|
257
|
+
const fileKeys = new Map();
|
|
258
|
+
const addPattern = /<add\b([^>]+?)\/?>/gi;
|
|
259
|
+
let match;
|
|
260
|
+
|
|
261
|
+
while ((match = addPattern.exec(content)) !== null) {
|
|
262
|
+
const attributes = match[1];
|
|
263
|
+
const nameMatch = attributes.match(/\bname="([^"]+)"/i);
|
|
264
|
+
const keyMatch = attributes.match(/\bkey="([^"]+)"/i);
|
|
265
|
+
const normalized = normalizeToken(decodeXmlEntities(nameMatch?.[1] ?? keyMatch?.[1] ?? ""));
|
|
266
|
+
if (!normalized) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
fileKeys.set(normalized, true);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return fileKeys;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function buildSqlResourceReferenceMap(fileRecords) {
|
|
276
|
+
const refsByKey = new Map();
|
|
277
|
+
|
|
278
|
+
for (const fileRecord of fileRecords) {
|
|
279
|
+
const ext = path.extname(fileRecord.path).toLowerCase();
|
|
280
|
+
let fileRefs = null;
|
|
281
|
+
if (ext === ".resx") {
|
|
282
|
+
fileRefs = parseResxSqlReferenceMap(fileRecord.content);
|
|
283
|
+
} else if (ext === ".settings") {
|
|
284
|
+
fileRefs = parseSettingsSqlReferenceMap(fileRecord.content);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!fileRefs) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
for (const [key, refs] of fileRefs.entries()) {
|
|
292
|
+
const existing = refsByKey.get(key) ?? [];
|
|
293
|
+
refsByKey.set(key, uniqueSorted([...existing, ...refs]));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return refsByKey;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function buildNamedResourceFileMaps(fileRecords) {
|
|
301
|
+
const resourceFilesByKey = new Map();
|
|
302
|
+
const settingFilesByKey = new Map();
|
|
303
|
+
const configFilesByKey = new Map();
|
|
304
|
+
|
|
305
|
+
for (const fileRecord of fileRecords) {
|
|
306
|
+
const ext = path.extname(fileRecord.path).toLowerCase();
|
|
307
|
+
const keyMap =
|
|
308
|
+
ext === ".resx"
|
|
309
|
+
? parseResxKeyMap(fileRecord.content)
|
|
310
|
+
: ext === ".settings"
|
|
311
|
+
? parseSettingsKeyMap(fileRecord.content)
|
|
312
|
+
: ext === ".config"
|
|
313
|
+
? parseConfigKeyMap(fileRecord.content)
|
|
314
|
+
: null;
|
|
315
|
+
|
|
316
|
+
if (!keyMap) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const key of keyMap.keys()) {
|
|
321
|
+
const targetMap =
|
|
322
|
+
ext === ".resx" ? resourceFilesByKey : ext === ".settings" ? settingFilesByKey : configFilesByKey;
|
|
323
|
+
const list = targetMap.get(key) ?? [];
|
|
324
|
+
list.push(fileRecord.id);
|
|
325
|
+
targetMap.set(key, uniqueSorted(list));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return { resourceFilesByKey, settingFilesByKey, configFilesByKey };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function extractSqlResourceKeyReferences(content) {
|
|
333
|
+
const keys = new Set();
|
|
334
|
+
|
|
335
|
+
for (const pattern of SQL_RESOURCE_KEY_PATTERNS) {
|
|
336
|
+
let match;
|
|
337
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
338
|
+
const key = normalizeToken(match[1]);
|
|
339
|
+
if (key) {
|
|
340
|
+
keys.add(key);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return [...keys];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function extractConfigKeyReferences(content) {
|
|
349
|
+
const keys = new Set();
|
|
350
|
+
|
|
351
|
+
for (const pattern of CONFIG_KEY_REFERENCE_PATTERNS) {
|
|
352
|
+
let match;
|
|
353
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
354
|
+
const key = normalizeToken(match[1]);
|
|
355
|
+
if (key) {
|
|
356
|
+
keys.add(key);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return [...keys];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function shouldExtractNamedResourceReferences(filePath) {
|
|
365
|
+
return NAMED_RESOURCE_REFERENCE_SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function generateNamedResourceRelations(fileRecords) {
|
|
369
|
+
const { resourceFilesByKey, settingFilesByKey, configFilesByKey } = buildNamedResourceFileMaps(fileRecords);
|
|
370
|
+
const usesResourceRelations = [];
|
|
371
|
+
const usesSettingRelations = [];
|
|
372
|
+
const usesConfigRelations = [];
|
|
373
|
+
const resourceSeen = new Set();
|
|
374
|
+
const settingSeen = new Set();
|
|
375
|
+
const configSeen = new Set();
|
|
376
|
+
|
|
377
|
+
for (const fileRecord of fileRecords) {
|
|
378
|
+
if (!shouldExtractNamedResourceReferences(fileRecord.path)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for (const key of extractSqlResourceKeyReferences(fileRecord.content)) {
|
|
383
|
+
for (const targetFileId of resourceFilesByKey.get(key) ?? []) {
|
|
384
|
+
const relKey = relationKey(fileRecord.id, targetFileId, key);
|
|
385
|
+
if (!resourceSeen.has(relKey) && fileRecord.id !== targetFileId) {
|
|
386
|
+
resourceSeen.add(relKey);
|
|
387
|
+
usesResourceRelations.push({
|
|
388
|
+
from: fileRecord.id,
|
|
389
|
+
to: targetFileId,
|
|
390
|
+
note: key
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const targetFileId of settingFilesByKey.get(key) ?? []) {
|
|
396
|
+
const relKey = relationKey(fileRecord.id, targetFileId, key);
|
|
397
|
+
if (!settingSeen.has(relKey) && fileRecord.id !== targetFileId) {
|
|
398
|
+
settingSeen.add(relKey);
|
|
399
|
+
usesSettingRelations.push({
|
|
400
|
+
from: fileRecord.id,
|
|
401
|
+
to: targetFileId,
|
|
402
|
+
note: key
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
for (const key of extractConfigKeyReferences(fileRecord.content)) {
|
|
409
|
+
for (const targetFileId of configFilesByKey.get(key) ?? []) {
|
|
410
|
+
const relKey = relationKey(fileRecord.id, targetFileId, key);
|
|
411
|
+
if (!configSeen.has(relKey) && fileRecord.id !== targetFileId) {
|
|
412
|
+
configSeen.add(relKey);
|
|
413
|
+
usesConfigRelations.push({
|
|
414
|
+
from: fileRecord.id,
|
|
415
|
+
to: targetFileId,
|
|
416
|
+
note: key
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
usesResourceRelations.sort((a, b) =>
|
|
424
|
+
relationKey(a.from, a.to, a.note).localeCompare(relationKey(b.from, b.to, b.note))
|
|
425
|
+
);
|
|
426
|
+
usesSettingRelations.sort((a, b) =>
|
|
427
|
+
relationKey(a.from, a.to, a.note).localeCompare(relationKey(b.from, b.to, b.note))
|
|
428
|
+
);
|
|
429
|
+
usesConfigRelations.sort((a, b) =>
|
|
430
|
+
relationKey(a.from, a.to, a.note).localeCompare(relationKey(b.from, b.to, b.note))
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
return { usesResourceRelations, usesSettingRelations, usesConfigRelations };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function parseConfigIncludeTargets(fileRecord) {
|
|
437
|
+
const relPath = toPosixPath(String(fileRecord?.path ?? "").trim());
|
|
438
|
+
const lowerPath = relPath.toLowerCase();
|
|
439
|
+
if (!lowerPath.endsWith(".config")) {
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const content = String(fileRecord?.content ?? "");
|
|
444
|
+
const dir = path.posix.dirname(relPath);
|
|
445
|
+
const includes = [];
|
|
446
|
+
const sectionPattern =
|
|
447
|
+
/<([A-Za-z_][A-Za-z0-9_.:-]*)\b([^>]*?)\b(configSource|file)="([^"]+)"([^>]*)>/gi;
|
|
448
|
+
let match;
|
|
449
|
+
|
|
450
|
+
while ((match = sectionPattern.exec(content)) !== null) {
|
|
451
|
+
const sectionName = String(match[1] ?? "").trim().toLowerCase();
|
|
452
|
+
const attributeName = String(match[3] ?? "").trim().toLowerCase();
|
|
453
|
+
const includePath = decodeXmlEntities(match[4] ?? "").trim().replace(/\\/g, "/");
|
|
454
|
+
if (!sectionName || !attributeName || !includePath) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (includePath.startsWith("/") || includePath.startsWith("~")) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const resolvedPath = path.posix.normalize(dir === "." ? includePath : `${dir}/${includePath}`);
|
|
462
|
+
if (!resolvedPath || resolvedPath.startsWith("../")) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
includes.push({
|
|
467
|
+
targetPath: resolvedPath,
|
|
468
|
+
note: `${sectionName}:${attributeName}`
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return includes;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function generateConfigIncludeRelations(fileRecords) {
|
|
476
|
+
const fileIdByPath = new Map(fileRecords.map((record) => [toPosixPath(record.path), record.id]));
|
|
477
|
+
const relations = [];
|
|
478
|
+
const seen = new Set();
|
|
479
|
+
|
|
480
|
+
for (const fileRecord of fileRecords) {
|
|
481
|
+
for (const include of parseConfigIncludeTargets(fileRecord)) {
|
|
482
|
+
const targetFileId = fileIdByPath.get(include.targetPath);
|
|
483
|
+
if (!targetFileId || targetFileId === fileRecord.id) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const key = relationKey(fileRecord.id, targetFileId, include.note);
|
|
487
|
+
if (seen.has(key)) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
seen.add(key);
|
|
491
|
+
relations.push({
|
|
492
|
+
from: fileRecord.id,
|
|
493
|
+
to: targetFileId,
|
|
494
|
+
note: include.note
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
relations.sort((a, b) => relationKey(a.from, a.to, a.note).localeCompare(relationKey(b.from, b.to, b.note)));
|
|
500
|
+
return relations;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function parseSectionHandlerDeclarations(content) {
|
|
504
|
+
const declarations = [];
|
|
505
|
+
const sectionPattern = /<section\b([^>]*?)\/?>/gi;
|
|
506
|
+
let match;
|
|
507
|
+
|
|
508
|
+
while ((match = sectionPattern.exec(String(content ?? ""))) !== null) {
|
|
509
|
+
const attrs = match[1] ?? "";
|
|
510
|
+
const nameMatch = attrs.match(/\bname="([^"]+)"/i);
|
|
511
|
+
const typeMatch = attrs.match(/\btype="([^"]+)"/i);
|
|
512
|
+
const sectionName = normalizeToken(decodeXmlEntities(nameMatch?.[1] ?? ""));
|
|
513
|
+
const typeValue = decodeXmlEntities(typeMatch?.[1] ?? "").trim();
|
|
514
|
+
if (!sectionName || !typeValue) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const typeParts = typeValue.split(",").map((part) => part.trim()).filter(Boolean);
|
|
519
|
+
const fullTypeName = typeParts[0] ?? "";
|
|
520
|
+
const assemblyName = typeParts[1] ?? "";
|
|
521
|
+
const shortTypeName = fullTypeName.split(".").pop()?.split("+").pop() ?? "";
|
|
522
|
+
const normalizedTypeName = normalizeToken(shortTypeName);
|
|
523
|
+
const normalizedAssemblyName = normalizeToken(assemblyName);
|
|
524
|
+
if (!normalizedTypeName && !normalizedAssemblyName) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
declarations.push({
|
|
529
|
+
sectionName,
|
|
530
|
+
normalizedTypeName,
|
|
531
|
+
normalizedAssemblyName
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return declarations;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function buildProjectAssemblyFileMap(fileRecords) {
|
|
539
|
+
const aliasMap = new Map();
|
|
540
|
+
|
|
541
|
+
for (const fileRecord of fileRecords) {
|
|
542
|
+
const ext = path.extname(fileRecord.path).toLowerCase();
|
|
543
|
+
if (!PROJECT_DEFINITION_EXTENSIONS.has(ext) || ext === ".sln") {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const aliases = uniqueSorted([
|
|
548
|
+
normalizeToken(extractXmlTagValue(fileRecord.content, "AssemblyName")),
|
|
549
|
+
normalizeToken(extractXmlTagValue(fileRecord.content, "RootNamespace")),
|
|
550
|
+
normalizeToken(path.basename(fileRecord.path, ext))
|
|
551
|
+
].filter(Boolean));
|
|
552
|
+
|
|
553
|
+
for (const alias of aliases) {
|
|
554
|
+
const existing = aliasMap.get(alias) ?? [];
|
|
555
|
+
aliasMap.set(alias, uniqueSorted([...existing, fileRecord.id]));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return aliasMap;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export function extractDeclaredTypeNames(fileRecord) {
|
|
563
|
+
const ext = path.extname(fileRecord.path).toLowerCase();
|
|
564
|
+
const pattern =
|
|
565
|
+
ext === ".vb"
|
|
566
|
+
? /\b(?:Public|Friend|Private|Protected|Partial|MustInherit|NotInheritable|Shadows|Default|Overridable|Overrides|Shared|\s)*(?:Class|Module|Structure|Interface|Enum)\s+([A-Za-z_][A-Za-z0-9_]*)/gi
|
|
567
|
+
: ext === ".cs"
|
|
568
|
+
? /\b(?:public|internal|private|protected|abstract|sealed|static|partial|\s)*(?:class|struct|interface|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/gi
|
|
569
|
+
: null;
|
|
570
|
+
|
|
571
|
+
if (!pattern) {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const typeNames = new Set();
|
|
576
|
+
let match;
|
|
577
|
+
while ((match = pattern.exec(String(fileRecord.content ?? ""))) !== null) {
|
|
578
|
+
const normalized = normalizeToken(match[1] ?? "");
|
|
579
|
+
if (normalized) {
|
|
580
|
+
typeNames.add(normalized);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return [...typeNames];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export function buildCodeTypeFileMap(fileRecords) {
|
|
588
|
+
const typeMap = new Map();
|
|
589
|
+
|
|
590
|
+
for (const fileRecord of fileRecords) {
|
|
591
|
+
if (fileRecord.kind !== "CODE") {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
for (const typeName of extractDeclaredTypeNames(fileRecord)) {
|
|
595
|
+
const existing = typeMap.get(typeName) ?? [];
|
|
596
|
+
typeMap.set(typeName, uniqueSorted([...existing, fileRecord.id]));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return typeMap;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export function longestCommonPathPrefixLength(pathA, pathB) {
|
|
604
|
+
const partsA = toPosixPath(pathA).split("/").filter(Boolean);
|
|
605
|
+
const partsB = toPosixPath(pathB).split("/").filter(Boolean);
|
|
606
|
+
const limit = Math.min(partsA.length, partsB.length);
|
|
607
|
+
let count = 0;
|
|
608
|
+
while (count < limit && partsA[count] === partsB[count]) {
|
|
609
|
+
count += 1;
|
|
610
|
+
}
|
|
611
|
+
return count;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function generateMachineConfigRelations(fileRecords) {
|
|
615
|
+
const machineConfigs = fileRecords.filter(
|
|
616
|
+
(record) => path.basename(record.path).toLowerCase() === "machine.config"
|
|
617
|
+
);
|
|
618
|
+
if (machineConfigs.length === 0) {
|
|
619
|
+
return [];
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const relations = [];
|
|
623
|
+
const seen = new Set();
|
|
624
|
+
|
|
625
|
+
for (const fileRecord of fileRecords) {
|
|
626
|
+
const lowerPath = fileRecord.path.toLowerCase();
|
|
627
|
+
if (
|
|
628
|
+
!lowerPath.endsWith(".config") ||
|
|
629
|
+
path.basename(lowerPath) === "machine.config" ||
|
|
630
|
+
!/<configuration\b/i.test(String(fileRecord.content ?? "")) ||
|
|
631
|
+
parseConfigTransformTarget(fileRecord)
|
|
632
|
+
) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const rankedTargets = machineConfigs
|
|
637
|
+
.filter((candidate) => candidate.id !== fileRecord.id)
|
|
638
|
+
.map((candidate) => ({
|
|
639
|
+
id: candidate.id,
|
|
640
|
+
score: longestCommonPathPrefixLength(fileRecord.path, candidate.path)
|
|
641
|
+
}))
|
|
642
|
+
.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
643
|
+
|
|
644
|
+
const target = rankedTargets[0];
|
|
645
|
+
if (!target) {
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const key = relationKey(fileRecord.id, target.id, "inherits:machine");
|
|
650
|
+
if (seen.has(key)) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
seen.add(key);
|
|
654
|
+
relations.push({
|
|
655
|
+
from: fileRecord.id,
|
|
656
|
+
to: target.id,
|
|
657
|
+
note: "inherits:machine"
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return uniqueRelations(relations);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export function generateSectionHandlerRelations(fileRecords) {
|
|
665
|
+
const projectAssemblyFileMap = buildProjectAssemblyFileMap(fileRecords);
|
|
666
|
+
const codeTypeFileMap = buildCodeTypeFileMap(fileRecords);
|
|
667
|
+
const relations = [];
|
|
668
|
+
|
|
669
|
+
for (const fileRecord of fileRecords) {
|
|
670
|
+
if (!fileRecord.path.toLowerCase().endsWith(".config")) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
for (const declaration of parseSectionHandlerDeclarations(fileRecord.content)) {
|
|
675
|
+
const note = `section_handler:${declaration.sectionName}`;
|
|
676
|
+
|
|
677
|
+
for (const targetFileId of projectAssemblyFileMap.get(declaration.normalizedAssemblyName) ?? []) {
|
|
678
|
+
relations.push({
|
|
679
|
+
from: fileRecord.id,
|
|
680
|
+
to: targetFileId,
|
|
681
|
+
note
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
for (const targetFileId of codeTypeFileMap.get(declaration.normalizedTypeName) ?? []) {
|
|
686
|
+
relations.push({
|
|
687
|
+
from: fileRecord.id,
|
|
688
|
+
to: targetFileId,
|
|
689
|
+
note
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return uniqueRelations(relations.filter((relation) => relation.from !== relation.to));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function generateConfigTransformKeyRelations(fileRecords, chunkRecords) {
|
|
699
|
+
const fileIdByPath = new Map(fileRecords.map((record) => [toPosixPath(record.path), record.id]));
|
|
700
|
+
const chunkFileIdById = new Map(chunkRecords.map((chunk) => [chunk.id, chunk.file_id]));
|
|
701
|
+
const configChunkIdsByAlias = new Map();
|
|
702
|
+
|
|
703
|
+
for (const chunk of chunkRecords) {
|
|
704
|
+
if (isWindowChunkId(chunk.id) || String(chunk.language ?? "").toLowerCase() !== "config") {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
for (const alias of configChunkAliases(chunk)) {
|
|
708
|
+
const existing = configChunkIdsByAlias.get(alias) ?? [];
|
|
709
|
+
configChunkIdsByAlias.set(alias, [...existing, chunk.id]);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const relations = [];
|
|
714
|
+
for (const fileRecord of fileRecords) {
|
|
715
|
+
const transform = parseConfigTransformTarget(fileRecord);
|
|
716
|
+
if (!transform) {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const targetFileId = fileIdByPath.get(transform.targetPath);
|
|
721
|
+
if (!targetFileId) {
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
for (const key of parseConfigKeyMap(fileRecord.content).keys()) {
|
|
726
|
+
for (const targetChunkId of configChunkIdsByAlias.get(key) ?? []) {
|
|
727
|
+
if (chunkFileIdById.get(targetChunkId) !== targetFileId) {
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
relations.push({
|
|
731
|
+
from: fileRecord.id,
|
|
732
|
+
to: targetChunkId,
|
|
733
|
+
note: `${key}:${transform.environment}`
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return uniqueRelations(relations);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export function parseConfigTransformTarget(fileRecord) {
|
|
743
|
+
const relPath = toPosixPath(String(fileRecord?.path ?? "").trim());
|
|
744
|
+
const lowerPath = relPath.toLowerCase();
|
|
745
|
+
if (!lowerPath.endsWith(".config")) {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const content = String(fileRecord?.content ?? "");
|
|
750
|
+
if (!/\bxdt:(?:transform|locator)\b/i.test(content) && !/\bxmlns:xdt=/i.test(content)) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const dir = path.posix.dirname(relPath);
|
|
755
|
+
const baseName = path.posix.basename(relPath, ".config");
|
|
756
|
+
const match = baseName.match(/^(.+)\.([^.]+)$/);
|
|
757
|
+
if (!match) {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const baseStem = match[1]?.trim();
|
|
762
|
+
const environment = match[2]?.trim();
|
|
763
|
+
if (!baseStem || !environment) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const targetPath = dir === "." ? `${baseStem}.config` : `${dir}/${baseStem}.config`;
|
|
768
|
+
return {
|
|
769
|
+
targetPath,
|
|
770
|
+
environment: normalizeToken(environment)
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export function generateConfigTransformRelations(fileRecords) {
|
|
775
|
+
const fileIdByPath = new Map(fileRecords.map((record) => [toPosixPath(record.path), record.id]));
|
|
776
|
+
const relations = [];
|
|
777
|
+
const seen = new Set();
|
|
778
|
+
|
|
779
|
+
for (const fileRecord of fileRecords) {
|
|
780
|
+
const transform = parseConfigTransformTarget(fileRecord);
|
|
781
|
+
if (!transform) {
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const targetFileId = fileIdByPath.get(transform.targetPath);
|
|
786
|
+
if (!targetFileId || targetFileId === fileRecord.id) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const key = relationKey(fileRecord.id, targetFileId, transform.environment);
|
|
791
|
+
if (seen.has(key)) {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
seen.add(key);
|
|
795
|
+
relations.push({
|
|
796
|
+
from: fileRecord.id,
|
|
797
|
+
to: targetFileId,
|
|
798
|
+
note: transform.environment
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
relations.sort((a, b) => relationKey(a.from, a.to, a.note).localeCompare(relationKey(b.from, b.to, b.note)));
|
|
803
|
+
return relations;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export function shouldExtractSqlReferences(filePath) {
|
|
807
|
+
return SQL_REFERENCE_SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
export function extractSqlObjectReferencesFromContent(content, filePath = "", sqlResourceReferenceMap = new Map()) {
|
|
811
|
+
const refs = new Set();
|
|
812
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
813
|
+
|
|
814
|
+
if (ext === ".resx") {
|
|
815
|
+
for (const values of parseResxSqlReferenceMap(content).values()) {
|
|
816
|
+
for (const ref of values) {
|
|
817
|
+
refs.add(ref);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
} else if (ext === ".settings") {
|
|
821
|
+
for (const values of parseSettingsSqlReferenceMap(content).values()) {
|
|
822
|
+
for (const ref of values) {
|
|
823
|
+
refs.add(ref);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
for (const pattern of SQL_OBJECT_REFERENCE_PATTERNS) {
|
|
829
|
+
let match;
|
|
830
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
831
|
+
for (const ref of extractSqlReferenceNamesFromString(match[1])) {
|
|
832
|
+
refs.add(ref);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (sqlResourceReferenceMap.size > 0) {
|
|
838
|
+
for (const key of extractSqlResourceKeyReferences(content)) {
|
|
839
|
+
for (const ref of sqlResourceReferenceMap.get(key) ?? []) {
|
|
840
|
+
refs.add(ref);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return uniqueSorted([...refs]);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export function decodeXmlEntities(value) {
|
|
849
|
+
return String(value)
|
|
850
|
+
.replace(/"/g, '"')
|
|
851
|
+
.replace(/'/g, "'")
|
|
852
|
+
.replace(/</g, "<")
|
|
853
|
+
.replace(/>/g, ">")
|
|
854
|
+
.replace(/&/g, "&");
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export function extractXmlTagValue(content, tagName) {
|
|
858
|
+
const match = content.match(new RegExp(`<${tagName}>([\\s\\S]*?)<\\/${tagName}>`, "i"));
|
|
859
|
+
return match ? decodeXmlEntities(match[1]).trim() : "";
|
|
860
|
+
}
|