sentinelayer-cli 0.1.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +998 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +63 -54
- package/src/agents/jules/config/definition.js +209 -209
- package/src/agents/jules/config/system-prompt.js +175 -175
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +377 -377
- package/src/agents/jules/loop.js +367 -367
- package/src/agents/jules/pulse.js +327 -319
- package/src/agents/jules/stream.js +186 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +308 -308
- package/src/agents/jules/tools/auth-audit.js +557 -222
- package/src/agents/jules/tools/dispatch.js +327 -327
- package/src/agents/jules/tools/file-edit.js +180 -180
- package/src/agents/jules/tools/file-read.js +100 -100
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +168 -168
- package/src/agents/jules/tools/grep.js +228 -228
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +161 -161
- package/src/agents/jules/tools/runtime-audit.js +503 -493
- package/src/agents/jules/tools/shell.js +383 -383
- package/src/agents/jules/tools/url-policy.js +100 -0
- package/src/ai/aidenid.js +972 -945
- package/src/ai/client.js +508 -508
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +45 -11
- package/src/auth/http.js +270 -113
- package/src/auth/service.js +891 -848
- package/src/auth/session-store.js +359 -345
- package/src/cli.js +252 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1337
- package/src/commands/ai/provision-governance.js +1272 -1246
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1166 -1166
- package/src/commands/auth.js +375 -366
- package/src/commands/chat.js +191 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +10 -10
- package/src/commands/mcp.js +461 -404
- package/src/commands/omargate.js +15 -15
- package/src/commands/persona.js +20 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +866 -788
- package/src/commands/spec.js +716 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +510 -510
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +770 -770
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +626 -626
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/jira-lifecycle.js +632 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/watchdog.js +971 -971
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/legacy-cli.js +2592 -2435
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +118 -106
- package/src/review/ai-review.js +669 -669
- package/src/review/local-review.js +1295 -1284
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -0
- package/src/scaffold/templates.js +150 -0
- package/src/scan/generator.js +418 -351
- package/src/scan/gh-secrets.js +107 -0
- package/src/spec/generator.js +519 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/sync.js +107 -61
- package/src/ui/markdown.js +220 -220
|
@@ -1,258 +1,258 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { extname } from "node:path";
|
|
3
|
-
import { promisify } from "node:util";
|
|
4
|
-
|
|
5
|
-
import { parse } from "@babel/parser";
|
|
6
|
-
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
8
|
-
const PYTHON_EXECUTABLE_CANDIDATES = ["python3", "python"];
|
|
9
|
-
let pythonExecutablePromise = null;
|
|
10
|
-
|
|
11
|
-
function normalizeString(value) {
|
|
12
|
-
return String(value || "").trim();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function normalizeLanguage(language) {
|
|
16
|
-
return normalizeString(language).toLowerCase();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function normalizeSpecifierList(values = []) {
|
|
20
|
-
const unique = new Set();
|
|
21
|
-
for (const value of values) {
|
|
22
|
-
const normalized = normalizeString(value);
|
|
23
|
-
if (!normalized) {
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
unique.add(normalized);
|
|
27
|
-
}
|
|
28
|
-
return [...unique];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function parseRegexSpecifiers(content = "", normalizedLanguage = "") {
|
|
32
|
-
const raw = String(content || "");
|
|
33
|
-
const specifiers = new Set();
|
|
34
|
-
if (normalizedLanguage.includes("javascript") || normalizedLanguage.includes("typescript")) {
|
|
35
|
-
const pattern =
|
|
36
|
-
/(?:import\s+[^'"]*from\s*|export\s+[^'"]*from\s*|import\s*\(\s*|require\s*\()\s*['"]([^'"]+)['"]/g;
|
|
37
|
-
let match;
|
|
38
|
-
while ((match = pattern.exec(raw))) {
|
|
39
|
-
if (match[1]) {
|
|
40
|
-
specifiers.add(match[1]);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (normalizedLanguage === "python") {
|
|
45
|
-
const fromPattern = /^\s*from\s+([a-zA-Z0-9_\.]+)\s+import\s+/gm;
|
|
46
|
-
let fromMatch;
|
|
47
|
-
while ((fromMatch = fromPattern.exec(raw))) {
|
|
48
|
-
if (fromMatch[1]) {
|
|
49
|
-
specifiers.add(fromMatch[1]);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const importPattern = /^\s*import\s+([a-zA-Z0-9_\.]+)/gm;
|
|
53
|
-
let importMatch;
|
|
54
|
-
while ((importMatch = importPattern.exec(raw))) {
|
|
55
|
-
if (importMatch[1]) {
|
|
56
|
-
specifiers.add(importMatch[1]);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return [...specifiers];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function collectBabelSpecifiers(astRoot) {
|
|
64
|
-
const specifiers = new Set();
|
|
65
|
-
const queue = [astRoot];
|
|
66
|
-
while (queue.length > 0) {
|
|
67
|
-
const node = queue.shift();
|
|
68
|
-
if (!node || typeof node !== "object") {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (Array.isArray(node)) {
|
|
72
|
-
for (const value of node) {
|
|
73
|
-
queue.push(value);
|
|
74
|
-
}
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
(node.type === "ImportDeclaration" ||
|
|
80
|
-
node.type === "ExportAllDeclaration" ||
|
|
81
|
-
node.type === "ExportNamedDeclaration") &&
|
|
82
|
-
node.source &&
|
|
83
|
-
typeof node.source.value === "string"
|
|
84
|
-
) {
|
|
85
|
-
specifiers.add(node.source.value);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
node.type === "CallExpression" &&
|
|
90
|
-
node.callee &&
|
|
91
|
-
node.callee.type === "Identifier" &&
|
|
92
|
-
node.callee.name === "require"
|
|
93
|
-
) {
|
|
94
|
-
const firstArg = Array.isArray(node.arguments) ? node.arguments[0] : null;
|
|
95
|
-
if (firstArg && firstArg.type === "StringLiteral" && typeof firstArg.value === "string") {
|
|
96
|
-
specifiers.add(firstArg.value);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (node.type === "CallExpression" && node.callee && node.callee.type === "Import") {
|
|
101
|
-
const firstArg = Array.isArray(node.arguments) ? node.arguments[0] : null;
|
|
102
|
-
if (firstArg && firstArg.type === "StringLiteral" && typeof firstArg.value === "string") {
|
|
103
|
-
specifiers.add(firstArg.value);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (node.type === "ImportExpression" && node.source) {
|
|
108
|
-
if (node.source.type === "StringLiteral" && typeof node.source.value === "string") {
|
|
109
|
-
specifiers.add(node.source.value);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
for (const value of Object.values(node)) {
|
|
114
|
-
if (value && typeof value === "object") {
|
|
115
|
-
queue.push(value);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return [...specifiers];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function buildBabelPlugins(absolutePath = "") {
|
|
123
|
-
const extension = extname(normalizeString(absolutePath)).toLowerCase();
|
|
124
|
-
const plugins = ["importAttributes", "dynamicImport"];
|
|
125
|
-
if (extension === ".ts" || extension === ".tsx" || extension === ".mts" || extension === ".cts") {
|
|
126
|
-
plugins.push("typescript");
|
|
127
|
-
}
|
|
128
|
-
if (extension === ".jsx" || extension === ".tsx") {
|
|
129
|
-
plugins.push("jsx");
|
|
130
|
-
}
|
|
131
|
-
return plugins;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function parseWithBabelAst(content = "", absolutePath = "") {
|
|
135
|
-
const ast = parse(String(content || ""), {
|
|
136
|
-
sourceType: "unambiguous",
|
|
137
|
-
errorRecovery: true,
|
|
138
|
-
allowAwaitOutsideFunction: true,
|
|
139
|
-
plugins: buildBabelPlugins(absolutePath),
|
|
140
|
-
});
|
|
141
|
-
if (Array.isArray(ast.errors) && ast.errors.length > 0) {
|
|
142
|
-
throw ast.errors[0];
|
|
143
|
-
}
|
|
144
|
-
return collectBabelSpecifiers(ast);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function resolvePythonExecutable() {
|
|
148
|
-
if (!pythonExecutablePromise) {
|
|
149
|
-
pythonExecutablePromise = (async () => {
|
|
150
|
-
for (const executable of PYTHON_EXECUTABLE_CANDIDATES) {
|
|
151
|
-
try {
|
|
152
|
-
await execFileAsync(executable, ["--version"], { timeout: 5000 });
|
|
153
|
-
return executable;
|
|
154
|
-
} catch {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return "";
|
|
159
|
-
})();
|
|
160
|
-
}
|
|
161
|
-
return pythonExecutablePromise;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const PYTHON_AST_SCRIPT = `
|
|
165
|
-
import ast
|
|
166
|
-
import json
|
|
167
|
-
import sys
|
|
168
|
-
|
|
169
|
-
target_path = sys.argv[1]
|
|
170
|
-
with open(target_path, "r", encoding="utf-8") as source_file:
|
|
171
|
-
source_text = source_file.read()
|
|
172
|
-
|
|
173
|
-
parsed = ast.parse(source_text, filename=target_path)
|
|
174
|
-
specifiers = []
|
|
175
|
-
for node in ast.walk(parsed):
|
|
176
|
-
if isinstance(node, ast.Import):
|
|
177
|
-
for alias in node.names:
|
|
178
|
-
if alias.name:
|
|
179
|
-
specifiers.append(alias.name)
|
|
180
|
-
elif isinstance(node, ast.ImportFrom):
|
|
181
|
-
if node.module:
|
|
182
|
-
specifiers.append(node.module)
|
|
183
|
-
|
|
184
|
-
print(json.dumps({"specifiers": specifiers}))
|
|
185
|
-
`;
|
|
186
|
-
|
|
187
|
-
async function parsePythonAstSpecifiers(absolutePath = "") {
|
|
188
|
-
const executable = await resolvePythonExecutable();
|
|
189
|
-
if (!executable) {
|
|
190
|
-
return {
|
|
191
|
-
specifiers: [],
|
|
192
|
-
parseError: "python_executable_not_found",
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
const { stdout } = await execFileAsync(executable, ["-c", PYTHON_AST_SCRIPT, absolutePath], {
|
|
197
|
-
timeout: 10000,
|
|
198
|
-
maxBuffer: 1024 * 1024,
|
|
199
|
-
});
|
|
200
|
-
const parsed = JSON.parse(String(stdout || "{}"));
|
|
201
|
-
const specifiers = Array.isArray(parsed.specifiers) ? parsed.specifiers : [];
|
|
202
|
-
return {
|
|
203
|
-
specifiers: normalizeSpecifierList(specifiers),
|
|
204
|
-
parseError: "",
|
|
205
|
-
};
|
|
206
|
-
} catch (error) {
|
|
207
|
-
return {
|
|
208
|
-
specifiers: [],
|
|
209
|
-
parseError: normalizeString(error?.message) || "python_ast_parse_failed",
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export async function parseAstModuleSpecifiers({
|
|
215
|
-
absolutePath = "",
|
|
216
|
-
content = "",
|
|
217
|
-
language = "",
|
|
218
|
-
} = {}) {
|
|
219
|
-
const normalizedLanguage = normalizeLanguage(language);
|
|
220
|
-
if (normalizedLanguage.includes("javascript") || normalizedLanguage.includes("typescript")) {
|
|
221
|
-
try {
|
|
222
|
-
const specifiers = parseWithBabelAst(content, absolutePath);
|
|
223
|
-
return {
|
|
224
|
-
specifiers: normalizeSpecifierList(specifiers),
|
|
225
|
-
parserMode: "babel_ast",
|
|
226
|
-
parseError: "",
|
|
227
|
-
};
|
|
228
|
-
} catch (error) {
|
|
229
|
-
return {
|
|
230
|
-
specifiers: normalizeSpecifierList(parseRegexSpecifiers(content, normalizedLanguage)),
|
|
231
|
-
parserMode: "regex_fallback",
|
|
232
|
-
parseError: normalizeString(error?.message) || "babel_parse_failed",
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (normalizedLanguage === "python") {
|
|
238
|
-
const parsed = await parsePythonAstSpecifiers(absolutePath);
|
|
239
|
-
if (parsed.parseError) {
|
|
240
|
-
return {
|
|
241
|
-
specifiers: normalizeSpecifierList(parseRegexSpecifiers(content, normalizedLanguage)),
|
|
242
|
-
parserMode: "regex_fallback_python",
|
|
243
|
-
parseError: parsed.parseError,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
return {
|
|
247
|
-
specifiers: parsed.specifiers,
|
|
248
|
-
parserMode: "python_ast",
|
|
249
|
-
parseError: "",
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
specifiers: normalizeSpecifierList(parseRegexSpecifiers(content, normalizedLanguage)),
|
|
255
|
-
parserMode: "regex_fallback_generic",
|
|
256
|
-
parseError: "",
|
|
257
|
-
};
|
|
258
|
-
}
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
import { parse } from "@babel/parser";
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const PYTHON_EXECUTABLE_CANDIDATES = ["python3", "python"];
|
|
9
|
+
let pythonExecutablePromise = null;
|
|
10
|
+
|
|
11
|
+
function normalizeString(value) {
|
|
12
|
+
return String(value || "").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeLanguage(language) {
|
|
16
|
+
return normalizeString(language).toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeSpecifierList(values = []) {
|
|
20
|
+
const unique = new Set();
|
|
21
|
+
for (const value of values) {
|
|
22
|
+
const normalized = normalizeString(value);
|
|
23
|
+
if (!normalized) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
unique.add(normalized);
|
|
27
|
+
}
|
|
28
|
+
return [...unique];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseRegexSpecifiers(content = "", normalizedLanguage = "") {
|
|
32
|
+
const raw = String(content || "");
|
|
33
|
+
const specifiers = new Set();
|
|
34
|
+
if (normalizedLanguage.includes("javascript") || normalizedLanguage.includes("typescript")) {
|
|
35
|
+
const pattern =
|
|
36
|
+
/(?:import\s+[^'"]*from\s*|export\s+[^'"]*from\s*|import\s*\(\s*|require\s*\()\s*['"]([^'"]+)['"]/g;
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = pattern.exec(raw))) {
|
|
39
|
+
if (match[1]) {
|
|
40
|
+
specifiers.add(match[1]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (normalizedLanguage === "python") {
|
|
45
|
+
const fromPattern = /^\s*from\s+([a-zA-Z0-9_\.]+)\s+import\s+/gm;
|
|
46
|
+
let fromMatch;
|
|
47
|
+
while ((fromMatch = fromPattern.exec(raw))) {
|
|
48
|
+
if (fromMatch[1]) {
|
|
49
|
+
specifiers.add(fromMatch[1]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const importPattern = /^\s*import\s+([a-zA-Z0-9_\.]+)/gm;
|
|
53
|
+
let importMatch;
|
|
54
|
+
while ((importMatch = importPattern.exec(raw))) {
|
|
55
|
+
if (importMatch[1]) {
|
|
56
|
+
specifiers.add(importMatch[1]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...specifiers];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function collectBabelSpecifiers(astRoot) {
|
|
64
|
+
const specifiers = new Set();
|
|
65
|
+
const queue = [astRoot];
|
|
66
|
+
while (queue.length > 0) {
|
|
67
|
+
const node = queue.shift();
|
|
68
|
+
if (!node || typeof node !== "object") {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(node)) {
|
|
72
|
+
for (const value of node) {
|
|
73
|
+
queue.push(value);
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
(node.type === "ImportDeclaration" ||
|
|
80
|
+
node.type === "ExportAllDeclaration" ||
|
|
81
|
+
node.type === "ExportNamedDeclaration") &&
|
|
82
|
+
node.source &&
|
|
83
|
+
typeof node.source.value === "string"
|
|
84
|
+
) {
|
|
85
|
+
specifiers.add(node.source.value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
node.type === "CallExpression" &&
|
|
90
|
+
node.callee &&
|
|
91
|
+
node.callee.type === "Identifier" &&
|
|
92
|
+
node.callee.name === "require"
|
|
93
|
+
) {
|
|
94
|
+
const firstArg = Array.isArray(node.arguments) ? node.arguments[0] : null;
|
|
95
|
+
if (firstArg && firstArg.type === "StringLiteral" && typeof firstArg.value === "string") {
|
|
96
|
+
specifiers.add(firstArg.value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (node.type === "CallExpression" && node.callee && node.callee.type === "Import") {
|
|
101
|
+
const firstArg = Array.isArray(node.arguments) ? node.arguments[0] : null;
|
|
102
|
+
if (firstArg && firstArg.type === "StringLiteral" && typeof firstArg.value === "string") {
|
|
103
|
+
specifiers.add(firstArg.value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (node.type === "ImportExpression" && node.source) {
|
|
108
|
+
if (node.source.type === "StringLiteral" && typeof node.source.value === "string") {
|
|
109
|
+
specifiers.add(node.source.value);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const value of Object.values(node)) {
|
|
114
|
+
if (value && typeof value === "object") {
|
|
115
|
+
queue.push(value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return [...specifiers];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildBabelPlugins(absolutePath = "") {
|
|
123
|
+
const extension = extname(normalizeString(absolutePath)).toLowerCase();
|
|
124
|
+
const plugins = ["importAttributes", "dynamicImport"];
|
|
125
|
+
if (extension === ".ts" || extension === ".tsx" || extension === ".mts" || extension === ".cts") {
|
|
126
|
+
plugins.push("typescript");
|
|
127
|
+
}
|
|
128
|
+
if (extension === ".jsx" || extension === ".tsx") {
|
|
129
|
+
plugins.push("jsx");
|
|
130
|
+
}
|
|
131
|
+
return plugins;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseWithBabelAst(content = "", absolutePath = "") {
|
|
135
|
+
const ast = parse(String(content || ""), {
|
|
136
|
+
sourceType: "unambiguous",
|
|
137
|
+
errorRecovery: true,
|
|
138
|
+
allowAwaitOutsideFunction: true,
|
|
139
|
+
plugins: buildBabelPlugins(absolutePath),
|
|
140
|
+
});
|
|
141
|
+
if (Array.isArray(ast.errors) && ast.errors.length > 0) {
|
|
142
|
+
throw ast.errors[0];
|
|
143
|
+
}
|
|
144
|
+
return collectBabelSpecifiers(ast);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function resolvePythonExecutable() {
|
|
148
|
+
if (!pythonExecutablePromise) {
|
|
149
|
+
pythonExecutablePromise = (async () => {
|
|
150
|
+
for (const executable of PYTHON_EXECUTABLE_CANDIDATES) {
|
|
151
|
+
try {
|
|
152
|
+
await execFileAsync(executable, ["--version"], { timeout: 5000 });
|
|
153
|
+
return executable;
|
|
154
|
+
} catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return "";
|
|
159
|
+
})();
|
|
160
|
+
}
|
|
161
|
+
return pythonExecutablePromise;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const PYTHON_AST_SCRIPT = `
|
|
165
|
+
import ast
|
|
166
|
+
import json
|
|
167
|
+
import sys
|
|
168
|
+
|
|
169
|
+
target_path = sys.argv[1]
|
|
170
|
+
with open(target_path, "r", encoding="utf-8") as source_file:
|
|
171
|
+
source_text = source_file.read()
|
|
172
|
+
|
|
173
|
+
parsed = ast.parse(source_text, filename=target_path)
|
|
174
|
+
specifiers = []
|
|
175
|
+
for node in ast.walk(parsed):
|
|
176
|
+
if isinstance(node, ast.Import):
|
|
177
|
+
for alias in node.names:
|
|
178
|
+
if alias.name:
|
|
179
|
+
specifiers.append(alias.name)
|
|
180
|
+
elif isinstance(node, ast.ImportFrom):
|
|
181
|
+
if node.module:
|
|
182
|
+
specifiers.append(node.module)
|
|
183
|
+
|
|
184
|
+
print(json.dumps({"specifiers": specifiers}))
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
async function parsePythonAstSpecifiers(absolutePath = "") {
|
|
188
|
+
const executable = await resolvePythonExecutable();
|
|
189
|
+
if (!executable) {
|
|
190
|
+
return {
|
|
191
|
+
specifiers: [],
|
|
192
|
+
parseError: "python_executable_not_found",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const { stdout } = await execFileAsync(executable, ["-c", PYTHON_AST_SCRIPT, absolutePath], {
|
|
197
|
+
timeout: 10000,
|
|
198
|
+
maxBuffer: 1024 * 1024,
|
|
199
|
+
});
|
|
200
|
+
const parsed = JSON.parse(String(stdout || "{}"));
|
|
201
|
+
const specifiers = Array.isArray(parsed.specifiers) ? parsed.specifiers : [];
|
|
202
|
+
return {
|
|
203
|
+
specifiers: normalizeSpecifierList(specifiers),
|
|
204
|
+
parseError: "",
|
|
205
|
+
};
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return {
|
|
208
|
+
specifiers: [],
|
|
209
|
+
parseError: normalizeString(error?.message) || "python_ast_parse_failed",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function parseAstModuleSpecifiers({
|
|
215
|
+
absolutePath = "",
|
|
216
|
+
content = "",
|
|
217
|
+
language = "",
|
|
218
|
+
} = {}) {
|
|
219
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
220
|
+
if (normalizedLanguage.includes("javascript") || normalizedLanguage.includes("typescript")) {
|
|
221
|
+
try {
|
|
222
|
+
const specifiers = parseWithBabelAst(content, absolutePath);
|
|
223
|
+
return {
|
|
224
|
+
specifiers: normalizeSpecifierList(specifiers),
|
|
225
|
+
parserMode: "babel_ast",
|
|
226
|
+
parseError: "",
|
|
227
|
+
};
|
|
228
|
+
} catch (error) {
|
|
229
|
+
return {
|
|
230
|
+
specifiers: normalizeSpecifierList(parseRegexSpecifiers(content, normalizedLanguage)),
|
|
231
|
+
parserMode: "regex_fallback",
|
|
232
|
+
parseError: normalizeString(error?.message) || "babel_parse_failed",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (normalizedLanguage === "python") {
|
|
238
|
+
const parsed = await parsePythonAstSpecifiers(absolutePath);
|
|
239
|
+
if (parsed.parseError) {
|
|
240
|
+
return {
|
|
241
|
+
specifiers: normalizeSpecifierList(parseRegexSpecifiers(content, normalizedLanguage)),
|
|
242
|
+
parserMode: "regex_fallback_python",
|
|
243
|
+
parseError: parsed.parseError,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
specifiers: parsed.specifiers,
|
|
248
|
+
parserMode: "python_ast",
|
|
249
|
+
parseError: "",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
specifiers: normalizeSpecifierList(parseRegexSpecifiers(content, normalizedLanguage)),
|
|
255
|
+
parserMode: "regex_fallback_generic",
|
|
256
|
+
parseError: "",
|
|
257
|
+
};
|
|
258
|
+
}
|