@justmpm/ai-tool 0.8.2 → 0.9.0
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 +47 -1
- package/dist/{chunk-OMEBMR2V.js → chunk-NVJXCSJF.js} +652 -480
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +51 -3
- package/dist/index.js +1 -1
- package/dist/server-L24EN4AN.js +649 -0
- package/package.json +2 -2
- package/dist/server-JLK3OH5F.js +0 -537
|
@@ -577,6 +577,28 @@ function formatImpactText(result) {
|
|
|
577
577
|
`;
|
|
578
578
|
}
|
|
579
579
|
}
|
|
580
|
+
if (result.gitHistory && result.gitHistory.hasGitRepo) {
|
|
581
|
+
out += `
|
|
582
|
+
\u{1F4DC} HIST\xD3RICO GIT (\xFAltimos ${result.gitHistory.recentCommits.length} commits)
|
|
583
|
+
|
|
584
|
+
`;
|
|
585
|
+
if (result.gitHistory.recentCommits.length === 0) {
|
|
586
|
+
out += ` Arquivo n\xE3o est\xE1 no reposit\xF3rio Git ou sem hist\xF3rico.
|
|
587
|
+
`;
|
|
588
|
+
} else {
|
|
589
|
+
for (const commit of result.gitHistory.recentCommits) {
|
|
590
|
+
const date = commit.date;
|
|
591
|
+
const author = commit.author;
|
|
592
|
+
const hash = commit.shortHash;
|
|
593
|
+
const message = commit.message.length > 60 ? commit.message.substring(0, 60) + "..." : commit.message;
|
|
594
|
+
out += ` ${date} ${author}
|
|
595
|
+
`;
|
|
596
|
+
out += ` ${hash} ${message}
|
|
597
|
+
|
|
598
|
+
`;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
580
602
|
return out;
|
|
581
603
|
}
|
|
582
604
|
function formatSuggestText(result) {
|
|
@@ -699,6 +721,19 @@ function formatSuggestText(result) {
|
|
|
699
721
|
out += ` \u{1F7E2} Opcionais: ${byPriority.low.length}
|
|
700
722
|
`;
|
|
701
723
|
}
|
|
724
|
+
if (result.testSuggestions.length > 0) {
|
|
725
|
+
out += `
|
|
726
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
727
|
+
|
|
728
|
+
`;
|
|
729
|
+
out += `\u{1F9EA} TESTES E VERIFICA\xC7\xD5ES
|
|
730
|
+
|
|
731
|
+
`;
|
|
732
|
+
for (const testSuggestion of result.testSuggestions) {
|
|
733
|
+
out += ` ${testSuggestion}
|
|
734
|
+
`;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
702
737
|
return out;
|
|
703
738
|
}
|
|
704
739
|
function formatContextText(result) {
|
|
@@ -1320,6 +1355,85 @@ function clearFirebaseCache() {
|
|
|
1320
1355
|
indexContentCache = null;
|
|
1321
1356
|
}
|
|
1322
1357
|
|
|
1358
|
+
// src/cache/schemas.ts
|
|
1359
|
+
import { z } from "zod";
|
|
1360
|
+
var mapResultSchema = z.object({
|
|
1361
|
+
version: z.string(),
|
|
1362
|
+
timestamp: z.string(),
|
|
1363
|
+
cwd: z.string(),
|
|
1364
|
+
summary: z.object({
|
|
1365
|
+
totalFiles: z.number(),
|
|
1366
|
+
totalFolders: z.number(),
|
|
1367
|
+
categories: z.record(z.string(), z.number()).optional()
|
|
1368
|
+
}),
|
|
1369
|
+
folders: z.array(
|
|
1370
|
+
z.object({
|
|
1371
|
+
path: z.string(),
|
|
1372
|
+
depth: z.number(),
|
|
1373
|
+
fileCount: z.number()
|
|
1374
|
+
})
|
|
1375
|
+
),
|
|
1376
|
+
files: z.array(
|
|
1377
|
+
z.object({
|
|
1378
|
+
path: z.string(),
|
|
1379
|
+
category: z.string(),
|
|
1380
|
+
size: z.number()
|
|
1381
|
+
})
|
|
1382
|
+
),
|
|
1383
|
+
circularDependencies: z.array(z.string())
|
|
1384
|
+
});
|
|
1385
|
+
var deadResultSchema = z.object({
|
|
1386
|
+
version: z.string(),
|
|
1387
|
+
timestamp: z.string(),
|
|
1388
|
+
cwd: z.string(),
|
|
1389
|
+
summary: z.object({
|
|
1390
|
+
totalDead: z.number(),
|
|
1391
|
+
byType: z.object({
|
|
1392
|
+
files: z.number(),
|
|
1393
|
+
exports: z.number(),
|
|
1394
|
+
dependencies: z.number()
|
|
1395
|
+
})
|
|
1396
|
+
}),
|
|
1397
|
+
files: z.array(
|
|
1398
|
+
z.object({
|
|
1399
|
+
path: z.string(),
|
|
1400
|
+
category: z.string(),
|
|
1401
|
+
type: z.enum(["file", "export", "dependency"])
|
|
1402
|
+
})
|
|
1403
|
+
),
|
|
1404
|
+
exports: z.array(
|
|
1405
|
+
z.object({
|
|
1406
|
+
file: z.string(),
|
|
1407
|
+
export: z.string()
|
|
1408
|
+
})
|
|
1409
|
+
),
|
|
1410
|
+
dependencies: z.array(z.string())
|
|
1411
|
+
});
|
|
1412
|
+
var projectIndexSchema = z.object({
|
|
1413
|
+
version: z.string(),
|
|
1414
|
+
timestamp: z.string(),
|
|
1415
|
+
files: z.record(
|
|
1416
|
+
z.object({
|
|
1417
|
+
path: z.string(),
|
|
1418
|
+
category: z.string(),
|
|
1419
|
+
symbols: z.array(z.any()),
|
|
1420
|
+
// SymbolInfo simplificado
|
|
1421
|
+
imports: z.array(
|
|
1422
|
+
z.object({
|
|
1423
|
+
source: z.string(),
|
|
1424
|
+
specifiers: z.array(z.string()),
|
|
1425
|
+
isTypeOnly: z.boolean()
|
|
1426
|
+
})
|
|
1427
|
+
),
|
|
1428
|
+
exports: z.array(z.string())
|
|
1429
|
+
})
|
|
1430
|
+
),
|
|
1431
|
+
symbolsByName: z.record(z.array(z.any())),
|
|
1432
|
+
// SymbolInfo[] simplificado
|
|
1433
|
+
fileCount: z.number(),
|
|
1434
|
+
symbolCount: z.number()
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1323
1437
|
// src/cache/index.ts
|
|
1324
1438
|
var CACHE_DIR = ".analyze";
|
|
1325
1439
|
var META_FILE = "meta.json";
|
|
@@ -1414,14 +1528,18 @@ function isCacheValid(cwd) {
|
|
|
1414
1528
|
return false;
|
|
1415
1529
|
}
|
|
1416
1530
|
}
|
|
1417
|
-
function readCache(cwd, file) {
|
|
1531
|
+
function readCache(cwd, file, schema) {
|
|
1418
1532
|
const cachePath = join2(getCacheDir(cwd), file);
|
|
1419
1533
|
if (!existsSync2(cachePath)) {
|
|
1420
1534
|
return null;
|
|
1421
1535
|
}
|
|
1422
1536
|
try {
|
|
1423
|
-
|
|
1424
|
-
|
|
1537
|
+
const data = JSON.parse(readFileSync2(cachePath, "utf-8"));
|
|
1538
|
+
return schema ? schema.parse(data) : data;
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
if (error && typeof error === "object" && "name" in error && error.name === "ZodError") {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1425
1543
|
return null;
|
|
1426
1544
|
}
|
|
1427
1545
|
}
|
|
@@ -1460,7 +1578,7 @@ function getCachedMapResult(cwd) {
|
|
|
1460
1578
|
if (!isCacheValid(cwd)) {
|
|
1461
1579
|
return null;
|
|
1462
1580
|
}
|
|
1463
|
-
return readCache(cwd, MAP_FILE);
|
|
1581
|
+
return readCache(cwd, MAP_FILE, mapResultSchema);
|
|
1464
1582
|
}
|
|
1465
1583
|
function cacheDeadResult(cwd, result) {
|
|
1466
1584
|
writeCache(cwd, DEAD_FILE, result);
|
|
@@ -1469,7 +1587,7 @@ function getCachedDeadResult(cwd) {
|
|
|
1469
1587
|
if (!isCacheValid(cwd)) {
|
|
1470
1588
|
return null;
|
|
1471
1589
|
}
|
|
1472
|
-
return readCache(cwd, DEAD_FILE);
|
|
1590
|
+
return readCache(cwd, DEAD_FILE, deadResultSchema);
|
|
1473
1591
|
}
|
|
1474
1592
|
function invalidateCache(cwd) {
|
|
1475
1593
|
const metaPath = join2(getCacheDir(cwd), META_FILE);
|
|
@@ -1489,7 +1607,7 @@ function getCachedSymbolsIndex(cwd) {
|
|
|
1489
1607
|
if (!isCacheValid(cwd)) {
|
|
1490
1608
|
return null;
|
|
1491
1609
|
}
|
|
1492
|
-
return readCache(cwd, SYMBOLS_FILE);
|
|
1610
|
+
return readCache(cwd, SYMBOLS_FILE, projectIndexSchema);
|
|
1493
1611
|
}
|
|
1494
1612
|
|
|
1495
1613
|
// src/areas/config.ts
|
|
@@ -1694,10 +1812,25 @@ function inferFileDescription(filePath, category) {
|
|
|
1694
1812
|
return categoryDescriptions[category];
|
|
1695
1813
|
}
|
|
1696
1814
|
|
|
1815
|
+
// src/commands/base.ts
|
|
1816
|
+
function parseCommandOptions(options) {
|
|
1817
|
+
return {
|
|
1818
|
+
cwd: options.cwd || process.cwd(),
|
|
1819
|
+
format: options.format || "text",
|
|
1820
|
+
...options
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function formatOutput(result, format, textFormatter, cacheMarker) {
|
|
1824
|
+
if (format === "json") {
|
|
1825
|
+
return JSON.stringify(result, null, 2);
|
|
1826
|
+
}
|
|
1827
|
+
const output = textFormatter(result);
|
|
1828
|
+
return cacheMarker ? output + "\n\n\u{1F4E6} (cache)" : output;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1697
1831
|
// src/commands/map.ts
|
|
1698
1832
|
async function map(options = {}) {
|
|
1699
|
-
const cwd
|
|
1700
|
-
const format = options.format || "text";
|
|
1833
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
1701
1834
|
const useCache = options.cache !== false;
|
|
1702
1835
|
const full = options.full ?? false;
|
|
1703
1836
|
if (useCache && isCacheValid(cwd)) {
|
|
@@ -1843,17 +1976,13 @@ function generateKnipConfig(cwd) {
|
|
|
1843
1976
|
return configPath;
|
|
1844
1977
|
}
|
|
1845
1978
|
async function dead(options = {}) {
|
|
1846
|
-
const cwd
|
|
1847
|
-
const format = options.format || "text";
|
|
1979
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
1848
1980
|
const useCache = options.cache !== false;
|
|
1849
1981
|
if (useCache && isCacheValid(cwd)) {
|
|
1850
1982
|
const cached = getCachedDeadResult(cwd);
|
|
1851
1983
|
if (cached) {
|
|
1852
1984
|
const result = { ...cached, timestamp: (/* @__PURE__ */ new Date()).toISOString(), fromCache: true };
|
|
1853
|
-
|
|
1854
|
-
return JSON.stringify(result, null, 2);
|
|
1855
|
-
}
|
|
1856
|
-
return formatDeadText(result) + "\n\n\u{1F4E6} (resultado do cache)";
|
|
1985
|
+
return formatOutput(result, format, formatDeadText, true);
|
|
1857
1986
|
}
|
|
1858
1987
|
}
|
|
1859
1988
|
try {
|
|
@@ -1936,10 +2065,7 @@ async function dead(options = {}) {
|
|
|
1936
2065
|
cacheDeadResult(cwd, result);
|
|
1937
2066
|
updateCacheMeta(cwd);
|
|
1938
2067
|
}
|
|
1939
|
-
|
|
1940
|
-
return JSON.stringify(result, null, 2);
|
|
1941
|
-
}
|
|
1942
|
-
return formatDeadText(result);
|
|
2068
|
+
return formatOutput(result, format, formatDeadText);
|
|
1943
2069
|
} catch (error) {
|
|
1944
2070
|
const message = error instanceof Error ? error.message : String(error);
|
|
1945
2071
|
throw new Error(`Erro ao executar dead: ${message}`);
|
|
@@ -2211,10 +2337,97 @@ function formatInvalidCommand(command) {
|
|
|
2211
2337
|
return out;
|
|
2212
2338
|
}
|
|
2213
2339
|
|
|
2340
|
+
// src/utils/file-matcher.ts
|
|
2341
|
+
function findTargetFile(target, allFiles) {
|
|
2342
|
+
const normalizedTarget = target.replace(/\\/g, "/").toLowerCase();
|
|
2343
|
+
if (allFiles.includes(normalizedTarget)) {
|
|
2344
|
+
return normalizedTarget;
|
|
2345
|
+
}
|
|
2346
|
+
const exactMatch = allFiles.find((f) => f.toLowerCase() === normalizedTarget);
|
|
2347
|
+
if (exactMatch) {
|
|
2348
|
+
return exactMatch;
|
|
2349
|
+
}
|
|
2350
|
+
const targetParts = normalizedTarget.split("/");
|
|
2351
|
+
const targetName = targetParts.pop() || "";
|
|
2352
|
+
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2353
|
+
const targetDir = targetParts.join("/");
|
|
2354
|
+
const matches = [];
|
|
2355
|
+
for (const file of allFiles) {
|
|
2356
|
+
const fileLower = file.toLowerCase();
|
|
2357
|
+
const fileParts = fileLower.split("/");
|
|
2358
|
+
const fileName = fileParts.pop() || "";
|
|
2359
|
+
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2360
|
+
const fileDir = fileParts.join("/");
|
|
2361
|
+
if (fileLower === normalizedTarget) {
|
|
2362
|
+
matches.push({ file, priority: 1 });
|
|
2363
|
+
} else if (fileNameNoExt === targetNameNoExt) {
|
|
2364
|
+
if (targetDir && fileDir.includes(targetDir)) {
|
|
2365
|
+
matches.push({ file, priority: 2 });
|
|
2366
|
+
} else if (targetDir && normalizedTarget.includes(fileDir)) {
|
|
2367
|
+
matches.push({ file, priority: 3 });
|
|
2368
|
+
} else {
|
|
2369
|
+
matches.push({ file, priority: 4 });
|
|
2370
|
+
}
|
|
2371
|
+
} else if (fileLower.includes(normalizedTarget)) {
|
|
2372
|
+
matches.push({ file, priority: 5 });
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (matches.length === 0) {
|
|
2376
|
+
for (const file of allFiles) {
|
|
2377
|
+
if (file.toLowerCase().includes(targetNameNoExt)) {
|
|
2378
|
+
matches.push({ file, priority: 6 });
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
if (matches.length > 0) {
|
|
2383
|
+
matches.sort((a, b) => a.priority - b.priority);
|
|
2384
|
+
return matches[0].file;
|
|
2385
|
+
}
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// src/integrations/git.ts
|
|
2390
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2391
|
+
import { execSync as execSync2 } from "child_process";
|
|
2392
|
+
function hasGitRepo(cwd) {
|
|
2393
|
+
return existsSync5(cwd + "/.git");
|
|
2394
|
+
}
|
|
2395
|
+
async function getCommitsForFile(filePath, cwd, limit = 10) {
|
|
2396
|
+
if (!hasGitRepo(cwd)) {
|
|
2397
|
+
return [];
|
|
2398
|
+
}
|
|
2399
|
+
try {
|
|
2400
|
+
const format = "%H|%h|%s|%ad|%an";
|
|
2401
|
+
const cmd = `git log --follow --oneline --date=short --format="${format}" -n ${limit} -- "${filePath}"`;
|
|
2402
|
+
const output = execSync2(cmd, {
|
|
2403
|
+
cwd,
|
|
2404
|
+
encoding: "utf-8",
|
|
2405
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2406
|
+
// Silenciar stderr
|
|
2407
|
+
});
|
|
2408
|
+
const commits = [];
|
|
2409
|
+
for (const line of output.trim().split("\n")) {
|
|
2410
|
+
if (!line) continue;
|
|
2411
|
+
const parts = line.split("|");
|
|
2412
|
+
if (parts.length >= 5) {
|
|
2413
|
+
commits.push({
|
|
2414
|
+
hash: parts[0],
|
|
2415
|
+
shortHash: parts[1],
|
|
2416
|
+
message: parts[2],
|
|
2417
|
+
date: parts[3],
|
|
2418
|
+
author: parts[4]
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
return commits;
|
|
2423
|
+
} catch (error) {
|
|
2424
|
+
return [];
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2214
2428
|
// src/commands/impact.ts
|
|
2215
2429
|
async function impact(target, options = {}) {
|
|
2216
|
-
const cwd
|
|
2217
|
-
const format = options.format || "text";
|
|
2430
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
2218
2431
|
const useCache = options.cache !== false;
|
|
2219
2432
|
if (!target) {
|
|
2220
2433
|
throw new Error("Target \xE9 obrigat\xF3rio. Exemplo: ai-tool impact src/components/Button.tsx");
|
|
@@ -2261,13 +2474,20 @@ async function impact(target, options = {}) {
|
|
|
2261
2474
|
}
|
|
2262
2475
|
const targetPath = findTargetFile(target, allFiles);
|
|
2263
2476
|
if (!targetPath) {
|
|
2264
|
-
return
|
|
2477
|
+
return formatFileNotFound({ target, allFiles, command: "impact" });
|
|
2265
2478
|
}
|
|
2266
2479
|
const { directUpstream, indirectUpstream, directDownstream, indirectDownstream } = calculateDependencies(targetPath, graph);
|
|
2267
2480
|
const dependingOn = [...directUpstream, ...indirectUpstream];
|
|
2268
2481
|
const dependencies = [...directDownstream, ...indirectDownstream];
|
|
2269
2482
|
const risks = detectRisks(targetPath, dependingOn, dependencies, findCircular);
|
|
2270
2483
|
const suggestions = generateSuggestions(dependingOn, dependencies, risks);
|
|
2484
|
+
const gitHistory = hasGitRepo(cwd) ? {
|
|
2485
|
+
hasGitRepo: true,
|
|
2486
|
+
recentCommits: await getCommitsForFile(targetPath, cwd, 5)
|
|
2487
|
+
} : {
|
|
2488
|
+
hasGitRepo: false,
|
|
2489
|
+
recentCommits: []
|
|
2490
|
+
};
|
|
2271
2491
|
const result = {
|
|
2272
2492
|
version: "1.0.0",
|
|
2273
2493
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2284,13 +2504,10 @@ async function impact(target, options = {}) {
|
|
|
2284
2504
|
total: dependencies.length
|
|
2285
2505
|
},
|
|
2286
2506
|
risks,
|
|
2287
|
-
suggestions
|
|
2507
|
+
suggestions,
|
|
2508
|
+
gitHistory
|
|
2288
2509
|
};
|
|
2289
|
-
|
|
2290
|
-
return JSON.stringify(result, null, 2);
|
|
2291
|
-
}
|
|
2292
|
-
const output = formatImpactText(result);
|
|
2293
|
-
return fromCache ? output + "\n\n\u{1F4E6} (grafo do cache)" : output;
|
|
2510
|
+
return formatOutput(result, format, formatImpactText, fromCache);
|
|
2294
2511
|
} catch (error) {
|
|
2295
2512
|
const message = error instanceof Error ? error.message : String(error);
|
|
2296
2513
|
throw new Error(`Erro ao executar impact: ${message}`);
|
|
@@ -2378,56 +2595,6 @@ function findCircularFromGraph(graph) {
|
|
|
2378
2595
|
}
|
|
2379
2596
|
return cycles;
|
|
2380
2597
|
}
|
|
2381
|
-
function findTargetFile(target, allFiles) {
|
|
2382
|
-
const normalizedTarget = target.replace(/\\/g, "/").toLowerCase();
|
|
2383
|
-
if (allFiles.includes(normalizedTarget)) {
|
|
2384
|
-
return normalizedTarget;
|
|
2385
|
-
}
|
|
2386
|
-
const exactMatch = allFiles.find((f) => f.toLowerCase() === normalizedTarget);
|
|
2387
|
-
if (exactMatch) {
|
|
2388
|
-
return exactMatch;
|
|
2389
|
-
}
|
|
2390
|
-
const targetParts = normalizedTarget.split("/");
|
|
2391
|
-
const targetName = targetParts.pop() || "";
|
|
2392
|
-
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2393
|
-
const targetDir = targetParts.join("/");
|
|
2394
|
-
const matches = [];
|
|
2395
|
-
for (const file of allFiles) {
|
|
2396
|
-
const fileLower = file.toLowerCase();
|
|
2397
|
-
const fileParts = fileLower.split("/");
|
|
2398
|
-
const fileName = fileParts.pop() || "";
|
|
2399
|
-
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2400
|
-
const fileDir = fileParts.join("/");
|
|
2401
|
-
if (fileLower === normalizedTarget) {
|
|
2402
|
-
matches.push({ file, priority: 1 });
|
|
2403
|
-
} else if (fileNameNoExt === targetNameNoExt) {
|
|
2404
|
-
if (targetDir && fileDir.includes(targetDir)) {
|
|
2405
|
-
matches.push({ file, priority: 2 });
|
|
2406
|
-
} else if (targetDir && normalizedTarget.includes(fileDir)) {
|
|
2407
|
-
matches.push({ file, priority: 3 });
|
|
2408
|
-
} else {
|
|
2409
|
-
matches.push({ file, priority: 4 });
|
|
2410
|
-
}
|
|
2411
|
-
} else if (fileLower.includes(normalizedTarget)) {
|
|
2412
|
-
matches.push({ file, priority: 5 });
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
if (matches.length === 0) {
|
|
2416
|
-
for (const file of allFiles) {
|
|
2417
|
-
if (file.toLowerCase().includes(targetNameNoExt)) {
|
|
2418
|
-
matches.push({ file, priority: 6 });
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
if (matches.length > 0) {
|
|
2423
|
-
matches.sort((a, b) => a.priority - b.priority);
|
|
2424
|
-
return matches[0].file;
|
|
2425
|
-
}
|
|
2426
|
-
return null;
|
|
2427
|
-
}
|
|
2428
|
-
function formatNotFound(target, allFiles) {
|
|
2429
|
-
return formatFileNotFound({ target, allFiles, command: "impact" });
|
|
2430
|
-
}
|
|
2431
2598
|
function toImpactFile(isDirect) {
|
|
2432
2599
|
return (path) => ({
|
|
2433
2600
|
path,
|
|
@@ -2499,8 +2666,7 @@ function generateSuggestions(upstream, downstream, risks) {
|
|
|
2499
2666
|
// src/commands/suggest.ts
|
|
2500
2667
|
import skott3 from "skott";
|
|
2501
2668
|
async function suggest(target, options = {}) {
|
|
2502
|
-
const cwd
|
|
2503
|
-
const format = options.format || "text";
|
|
2669
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
2504
2670
|
const useCache = options.cache !== false;
|
|
2505
2671
|
const limit = options.limit || 10;
|
|
2506
2672
|
if (!target) {
|
|
@@ -2542,23 +2708,21 @@ async function suggest(target, options = {}) {
|
|
|
2542
2708
|
updateCacheMeta(cwd);
|
|
2543
2709
|
}
|
|
2544
2710
|
}
|
|
2545
|
-
const targetPath =
|
|
2711
|
+
const targetPath = findTargetFile(target, allFiles);
|
|
2546
2712
|
if (!targetPath) {
|
|
2547
|
-
return
|
|
2713
|
+
return formatFileNotFound({ target, allFiles, command: "suggest" });
|
|
2548
2714
|
}
|
|
2549
2715
|
const suggestions = collectSuggestions(targetPath, graph, allFiles, limit);
|
|
2716
|
+
const testSuggestions = generateTestSuggestions(suggestions, allFiles);
|
|
2550
2717
|
const result = {
|
|
2551
2718
|
version: "1.0.0",
|
|
2552
2719
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2553
2720
|
target: targetPath,
|
|
2554
2721
|
category: detectCategory(targetPath),
|
|
2555
|
-
suggestions
|
|
2722
|
+
suggestions,
|
|
2723
|
+
testSuggestions
|
|
2556
2724
|
};
|
|
2557
|
-
|
|
2558
|
-
return JSON.stringify(result, null, 2);
|
|
2559
|
-
}
|
|
2560
|
-
const output = formatSuggestText(result);
|
|
2561
|
-
return fromCache ? output + "\n\n(grafo do cache)" : output;
|
|
2725
|
+
return formatOutput(result, format, formatSuggestText, fromCache);
|
|
2562
2726
|
} catch (error) {
|
|
2563
2727
|
const message = error instanceof Error ? error.message : String(error);
|
|
2564
2728
|
throw new Error(`Erro ao executar suggest: ${message}`);
|
|
@@ -2653,53 +2817,6 @@ function findRelatedTests(targetPath, allFiles) {
|
|
|
2653
2817
|
}
|
|
2654
2818
|
return tests;
|
|
2655
2819
|
}
|
|
2656
|
-
function findTargetFile2(target, allFiles) {
|
|
2657
|
-
const normalizedTarget = target.replace(/\\/g, "/").toLowerCase();
|
|
2658
|
-
if (allFiles.includes(normalizedTarget)) {
|
|
2659
|
-
return normalizedTarget;
|
|
2660
|
-
}
|
|
2661
|
-
const exactMatch = allFiles.find((f) => f.toLowerCase() === normalizedTarget);
|
|
2662
|
-
if (exactMatch) {
|
|
2663
|
-
return exactMatch;
|
|
2664
|
-
}
|
|
2665
|
-
const targetParts = normalizedTarget.split("/");
|
|
2666
|
-
const targetName = targetParts.pop() || "";
|
|
2667
|
-
const targetNameNoExt = targetName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2668
|
-
const targetDir = targetParts.join("/");
|
|
2669
|
-
const matches = [];
|
|
2670
|
-
for (const file of allFiles) {
|
|
2671
|
-
const fileLower = file.toLowerCase();
|
|
2672
|
-
const fileParts = fileLower.split("/");
|
|
2673
|
-
const fileName = fileParts.pop() || "";
|
|
2674
|
-
const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2675
|
-
const fileDir = fileParts.join("/");
|
|
2676
|
-
if (fileLower === normalizedTarget) {
|
|
2677
|
-
matches.push({ file, priority: 1 });
|
|
2678
|
-
} else if (fileNameNoExt === targetNameNoExt) {
|
|
2679
|
-
if (targetDir && fileDir.includes(targetDir)) {
|
|
2680
|
-
matches.push({ file, priority: 2 });
|
|
2681
|
-
} else if (targetDir && normalizedTarget.includes(fileDir)) {
|
|
2682
|
-
matches.push({ file, priority: 3 });
|
|
2683
|
-
} else {
|
|
2684
|
-
matches.push({ file, priority: 4 });
|
|
2685
|
-
}
|
|
2686
|
-
} else if (fileLower.includes(normalizedTarget)) {
|
|
2687
|
-
matches.push({ file, priority: 5 });
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
if (matches.length === 0) {
|
|
2691
|
-
for (const file of allFiles) {
|
|
2692
|
-
if (file.toLowerCase().includes(targetNameNoExt)) {
|
|
2693
|
-
matches.push({ file, priority: 6 });
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
if (matches.length > 0) {
|
|
2698
|
-
matches.sort((a, b) => a.priority - b.priority);
|
|
2699
|
-
return matches[0].file;
|
|
2700
|
-
}
|
|
2701
|
-
return null;
|
|
2702
|
-
}
|
|
2703
2820
|
function suggestFirebaseRules(targetPath, allFiles) {
|
|
2704
2821
|
const suggestions = [];
|
|
2705
2822
|
if (!targetPath.includes("functions/src/")) {
|
|
@@ -2757,38 +2874,124 @@ function suggestFirebaseRules(targetPath, allFiles) {
|
|
|
2757
2874
|
}
|
|
2758
2875
|
return suggestions;
|
|
2759
2876
|
}
|
|
2760
|
-
function
|
|
2761
|
-
|
|
2877
|
+
function generateTestSuggestions(suggestions, allFiles) {
|
|
2878
|
+
const testSuggestions = [];
|
|
2879
|
+
const relatedTests = allFiles.filter((f) => {
|
|
2880
|
+
const isTestFile = f.includes(".test.") || f.includes(".spec.") || f.startsWith("tests/") || f.includes("/__tests__/");
|
|
2881
|
+
if (!isTestFile) return false;
|
|
2882
|
+
const testName = f.split("/").pop()?.toLowerCase() || "";
|
|
2883
|
+
for (const suggestion of suggestions) {
|
|
2884
|
+
const suggestedName = suggestion.path.split("/").pop()?.toLowerCase() || "";
|
|
2885
|
+
const suggestedNameNoExt = suggestedName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2886
|
+
const testNameNoExt = testName.replace(/\.(test|spec)\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2887
|
+
if (testNameNoExt.includes(suggestedNameNoExt) || suggestedNameNoExt.includes(testNameNoExt)) {
|
|
2888
|
+
return true;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
return false;
|
|
2892
|
+
});
|
|
2893
|
+
if (relatedTests.length > 0) {
|
|
2894
|
+
const testNames = relatedTests.slice(0, 3).map((f) => f.split("/").pop()).join(", ");
|
|
2895
|
+
testSuggestions.push(`\u{1F9EA} Teste os arquivos modificados: ${testNames}`);
|
|
2896
|
+
if (relatedTests.length > 3) {
|
|
2897
|
+
testSuggestions.push(` ... e mais ${relatedTests.length - 3} teste(s)`);
|
|
2898
|
+
}
|
|
2899
|
+
} else {
|
|
2900
|
+
const hasTestDirectory = allFiles.some((f) => f.startsWith("tests/") || f.includes("/__tests__/"));
|
|
2901
|
+
if (hasTestDirectory) {
|
|
2902
|
+
testSuggestions.push(`\u{1F9EA} Rode a su\xEDte de testes completa: npm test`);
|
|
2903
|
+
} else {
|
|
2904
|
+
testSuggestions.push(`\u26A0\uFE0F Nenhum teste encontrado para os arquivos modificados`);
|
|
2905
|
+
testSuggestions.push(`\u{1F4A1} Considere criar testes para garantir qualidade`);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
const hasCritical = suggestions.some((s) => s.priority === "critical" && s.category === "type");
|
|
2909
|
+
if (hasCritical) {
|
|
2910
|
+
testSuggestions.push(`\u{1F534} Arquivos de tipos modificados - considere adicionar testes de tipagem`);
|
|
2911
|
+
}
|
|
2912
|
+
const hasHighPriority = suggestions.some((s) => s.priority === "high" && s.category === "service");
|
|
2913
|
+
if (hasHighPriority) {
|
|
2914
|
+
testSuggestions.push(`\u{1F7E1} Servi\xE7os modificados - verifique testes de integra\xE7\xE3o`);
|
|
2915
|
+
}
|
|
2916
|
+
return testSuggestions;
|
|
2762
2917
|
}
|
|
2763
2918
|
|
|
2764
2919
|
// src/commands/context.ts
|
|
2765
|
-
import { existsSync as
|
|
2920
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
2766
2921
|
import { join as join6, resolve as resolve2, basename, extname as extname3 } from "path";
|
|
2767
2922
|
|
|
2768
2923
|
// src/ts/extractor.ts
|
|
2769
2924
|
import { Project, SyntaxKind } from "ts-morph";
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2925
|
+
|
|
2926
|
+
// src/ts/utils.ts
|
|
2927
|
+
function simplifyType(typeText) {
|
|
2928
|
+
if (!typeText) return "unknown";
|
|
2929
|
+
let simplified = typeText.replace(/import\([^)]+\)\./g, "");
|
|
2930
|
+
if (simplified.length > 80) {
|
|
2931
|
+
simplified = simplified.slice(0, 77) + "...";
|
|
2932
|
+
}
|
|
2933
|
+
return simplified;
|
|
2775
2934
|
}
|
|
2776
|
-
function
|
|
2777
|
-
|
|
2935
|
+
function safeGetTypeText(getTypeFn) {
|
|
2936
|
+
try {
|
|
2937
|
+
const type = getTypeFn();
|
|
2938
|
+
if (!type) return "unknown";
|
|
2939
|
+
return type.getText();
|
|
2940
|
+
} catch {
|
|
2941
|
+
return "unknown";
|
|
2942
|
+
}
|
|
2778
2943
|
}
|
|
2779
|
-
function
|
|
2780
|
-
const
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2944
|
+
function formatInterfaceDefinition(iface) {
|
|
2945
|
+
const parts = [];
|
|
2946
|
+
const extendsClauses = iface.getExtends();
|
|
2947
|
+
if (extendsClauses.length > 0) {
|
|
2948
|
+
parts.push(
|
|
2949
|
+
`extends ${extendsClauses.map((e) => e.getText()).join(", ")}`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
const props = iface.getProperties();
|
|
2953
|
+
for (const prop of props.slice(0, 10)) {
|
|
2954
|
+
const propType = simplifyType(safeGetTypeText(() => prop.getType()));
|
|
2955
|
+
parts.push(`${prop.getName()}: ${propType}`);
|
|
2956
|
+
}
|
|
2957
|
+
if (props.length > 10) {
|
|
2958
|
+
parts.push(`... +${props.length - 10} props`);
|
|
2959
|
+
}
|
|
2960
|
+
if (iface.getMethods) {
|
|
2961
|
+
const methods = iface.getMethods();
|
|
2962
|
+
for (const method of methods) {
|
|
2963
|
+
const returnType = simplifyType(
|
|
2964
|
+
safeGetTypeText(() => method.getReturnType())
|
|
2965
|
+
);
|
|
2966
|
+
parts.push(`${method.getName()}(): ${returnType}`);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
return parts.length > 0 ? `{ ${parts.join("; ")} }` : "{}";
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
// src/ts/extractor.ts
|
|
2973
|
+
function createProject(cwd) {
|
|
2974
|
+
return new Project({
|
|
2975
|
+
tsConfigFilePath: `${cwd}/tsconfig.json`,
|
|
2976
|
+
skipAddingFilesFromTsConfig: true
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
function addSourceFile(project, filePath) {
|
|
2980
|
+
return project.addSourceFileAtPath(filePath);
|
|
2981
|
+
}
|
|
2982
|
+
function extractImports(sourceFile) {
|
|
2983
|
+
const imports = [];
|
|
2984
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
2985
|
+
const specifiers = [];
|
|
2986
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
2987
|
+
if (defaultImport) {
|
|
2988
|
+
specifiers.push(defaultImport.getText());
|
|
2989
|
+
}
|
|
2990
|
+
for (const namedImport of importDecl.getNamedImports()) {
|
|
2991
|
+
const alias = namedImport.getAliasNode();
|
|
2992
|
+
if (alias) {
|
|
2993
|
+
specifiers.push(`${namedImport.getName()} as ${alias.getText()}`);
|
|
2994
|
+
} else {
|
|
2792
2995
|
specifiers.push(namedImport.getName());
|
|
2793
2996
|
}
|
|
2794
2997
|
}
|
|
@@ -2824,13 +3027,6 @@ function extractParams(params) {
|
|
|
2824
3027
|
type: simplifyType(p.getType().getText())
|
|
2825
3028
|
}));
|
|
2826
3029
|
}
|
|
2827
|
-
function simplifyType(typeText) {
|
|
2828
|
-
let simplified = typeText.replace(/import\([^)]+\)\./g, "");
|
|
2829
|
-
if (simplified.length > 80) {
|
|
2830
|
-
simplified = simplified.slice(0, 77) + "...";
|
|
2831
|
-
}
|
|
2832
|
-
return simplified;
|
|
2833
|
-
}
|
|
2834
3030
|
function extractFunctions(sourceFile) {
|
|
2835
3031
|
const functions2 = [];
|
|
2836
3032
|
for (const func of sourceFile.getFunctions()) {
|
|
@@ -2909,24 +3105,6 @@ function extractTypes(sourceFile) {
|
|
|
2909
3105
|
}
|
|
2910
3106
|
return types;
|
|
2911
3107
|
}
|
|
2912
|
-
function formatInterfaceDefinition(iface) {
|
|
2913
|
-
const parts = [];
|
|
2914
|
-
const extendsClauses = iface.getExtends();
|
|
2915
|
-
if (extendsClauses.length > 0) {
|
|
2916
|
-
parts.push(`extends ${extendsClauses.map((e) => e.getText()).join(", ")}`);
|
|
2917
|
-
}
|
|
2918
|
-
const props = iface.getProperties();
|
|
2919
|
-
for (const prop of props) {
|
|
2920
|
-
const propType = simplifyType(prop.getType().getText());
|
|
2921
|
-
parts.push(`${prop.getName()}: ${propType}`);
|
|
2922
|
-
}
|
|
2923
|
-
const methods = iface.getMethods();
|
|
2924
|
-
for (const method of methods) {
|
|
2925
|
-
const returnType = simplifyType(method.getReturnType().getText());
|
|
2926
|
-
parts.push(`${method.getName()}(): ${returnType}`);
|
|
2927
|
-
}
|
|
2928
|
-
return parts.length > 0 ? `{ ${parts.join("; ")} }` : "{}";
|
|
2929
|
-
}
|
|
2930
3108
|
function extractExports(sourceFile) {
|
|
2931
3109
|
const exports = [];
|
|
2932
3110
|
for (const exportDecl of sourceFile.getExportDeclarations()) {
|
|
@@ -2969,23 +3147,53 @@ function extractExports(sourceFile) {
|
|
|
2969
3147
|
return [...new Set(exports)];
|
|
2970
3148
|
}
|
|
2971
3149
|
|
|
2972
|
-
// src/ts/
|
|
3150
|
+
// src/ts/cache.ts
|
|
3151
|
+
import { resolve } from "path";
|
|
3152
|
+
import { SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
3153
|
+
|
|
3154
|
+
// src/ts/index.ts
|
|
2973
3155
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2974
|
-
import { join as join5, extname as extname2
|
|
2975
|
-
import { Project as Project2
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
var
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3156
|
+
import { join as join5, extname as extname2 } from "path";
|
|
3157
|
+
import { Project as Project2 } from "ts-morph";
|
|
3158
|
+
|
|
3159
|
+
// src/utils/logger.ts
|
|
3160
|
+
var Logger = class {
|
|
3161
|
+
/** Flag de debug geral - ativada via DEBUG_ANALYZE=true */
|
|
3162
|
+
_debug = process.env.DEBUG_ANALYZE === "true";
|
|
3163
|
+
/** Flag de debug para Cloud Functions - ativada via DEBUG_FUNCTIONS=true ou DEBUG_ANALYZE=true */
|
|
3164
|
+
_debugFunctions = process.env.DEBUG_FUNCTIONS === "true" || this._debug;
|
|
3165
|
+
/**
|
|
3166
|
+
* Log de debug geral
|
|
3167
|
+
*
|
|
3168
|
+
* Só produz output quando DEBUG_ANALYZE=true
|
|
3169
|
+
*
|
|
3170
|
+
* @param args - Argumentos para log (qualquer tipo)
|
|
3171
|
+
* @example
|
|
3172
|
+
* logger.debug("Indexando", files.length, "arquivos");
|
|
3173
|
+
*/
|
|
3174
|
+
debug(...args) {
|
|
3175
|
+
if (this._debug) {
|
|
3176
|
+
console.error("[analyze:debug]", ...args);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
/**
|
|
3180
|
+
* Log de debug para Cloud Functions
|
|
3181
|
+
*
|
|
3182
|
+
* Só produz output quando DEBUG_FUNCTIONS=true ou DEBUG_ANALYZE=true
|
|
3183
|
+
*
|
|
3184
|
+
* @param args - Argumentos para log (qualquer tipo)
|
|
3185
|
+
* @example
|
|
3186
|
+
* logger.debugFunctions("Trigger detectado:", triggerName);
|
|
3187
|
+
*/
|
|
3188
|
+
debugFunctions(...args) {
|
|
3189
|
+
if (this._debugFunctions) {
|
|
3190
|
+
console.error("[functions:debug]", ...args);
|
|
3191
|
+
}
|
|
2987
3192
|
}
|
|
2988
|
-
}
|
|
3193
|
+
};
|
|
3194
|
+
var logger = new Logger();
|
|
3195
|
+
|
|
3196
|
+
// src/ts/index.ts
|
|
2989
3197
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2990
3198
|
"node_modules",
|
|
2991
3199
|
"dist",
|
|
@@ -3008,6 +3216,106 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
3008
3216
|
"firestore-debug.log",
|
|
3009
3217
|
"pubsub-debug.log"
|
|
3010
3218
|
]);
|
|
3219
|
+
var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
3220
|
+
function buildImportMap(sourceFile) {
|
|
3221
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
3222
|
+
if (!sourceFile.getImportDeclarations) return map2;
|
|
3223
|
+
for (const decl of sourceFile.getImportDeclarations()) {
|
|
3224
|
+
const module = decl.getModuleSpecifierValue();
|
|
3225
|
+
const ns = decl.getNamespaceImport();
|
|
3226
|
+
if (ns) {
|
|
3227
|
+
map2.set(ns.getText(), { name: "*", module });
|
|
3228
|
+
}
|
|
3229
|
+
for (const named of decl.getNamedImports()) {
|
|
3230
|
+
const alias = named.getAliasNode();
|
|
3231
|
+
const name = named.getName();
|
|
3232
|
+
const localName = alias ? alias.getText() : name;
|
|
3233
|
+
map2.set(localName, { name, module });
|
|
3234
|
+
}
|
|
3235
|
+
const def = decl.getDefaultImport();
|
|
3236
|
+
if (def) {
|
|
3237
|
+
map2.set(def.getText(), { name: "default", module });
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
return map2;
|
|
3241
|
+
}
|
|
3242
|
+
function createProject2(cwd) {
|
|
3243
|
+
try {
|
|
3244
|
+
const project = new Project2({
|
|
3245
|
+
tsConfigFilePath: `${cwd}/tsconfig.json`,
|
|
3246
|
+
skipAddingFilesFromTsConfig: true
|
|
3247
|
+
});
|
|
3248
|
+
logger.debug(`Projeto ts-morph criado com tsconfig: ${cwd}/tsconfig.json`);
|
|
3249
|
+
return project;
|
|
3250
|
+
} catch {
|
|
3251
|
+
logger.debug(`Falha ao ler tsconfig, criando projeto b\xE1sico`);
|
|
3252
|
+
return new Project2({
|
|
3253
|
+
skipAddingFilesFromTsConfig: true,
|
|
3254
|
+
compilerOptions: {
|
|
3255
|
+
allowJs: true,
|
|
3256
|
+
checkJs: false,
|
|
3257
|
+
target: 2,
|
|
3258
|
+
// ES2020
|
|
3259
|
+
module: 200,
|
|
3260
|
+
// ESNext
|
|
3261
|
+
moduleResolution: 100
|
|
3262
|
+
// Bundler
|
|
3263
|
+
}
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
function getAllCodeFiles(dir, files = [], baseDir = dir) {
|
|
3268
|
+
try {
|
|
3269
|
+
const entries = readdirSync2(dir);
|
|
3270
|
+
for (const entry of entries) {
|
|
3271
|
+
const fullPath = join5(dir, entry);
|
|
3272
|
+
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) {
|
|
3273
|
+
continue;
|
|
3274
|
+
}
|
|
3275
|
+
try {
|
|
3276
|
+
const stat = statSync2(fullPath);
|
|
3277
|
+
if (stat.isDirectory()) {
|
|
3278
|
+
getAllCodeFiles(fullPath, files, baseDir);
|
|
3279
|
+
} else {
|
|
3280
|
+
const ext = extname2(entry).toLowerCase();
|
|
3281
|
+
if (CODE_EXTENSIONS2.has(ext)) {
|
|
3282
|
+
const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
|
|
3283
|
+
files.push(relativePath);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
} catch {
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
} catch {
|
|
3290
|
+
}
|
|
3291
|
+
return files;
|
|
3292
|
+
}
|
|
3293
|
+
function inferSymbolKind(name, context2) {
|
|
3294
|
+
if (name.startsWith("use") && name.length > 3 && name[3] === name[3].toUpperCase()) {
|
|
3295
|
+
return "hook";
|
|
3296
|
+
}
|
|
3297
|
+
if (context2 === "function" && name[0] === name[0].toUpperCase() && !name.includes("_")) {
|
|
3298
|
+
return "component";
|
|
3299
|
+
}
|
|
3300
|
+
return context2 === "function" ? "function" : "const";
|
|
3301
|
+
}
|
|
3302
|
+
function safeGetReturnType(node) {
|
|
3303
|
+
try {
|
|
3304
|
+
const returnType = node.getReturnType();
|
|
3305
|
+
if (!returnType) return "unknown";
|
|
3306
|
+
return returnType.getText();
|
|
3307
|
+
} catch {
|
|
3308
|
+
return "unknown";
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
function truncateCode(code, maxLen) {
|
|
3312
|
+
const oneLine = code.replace(/\s+/g, " ").trim();
|
|
3313
|
+
if (oneLine.length <= maxLen) return oneLine;
|
|
3314
|
+
return oneLine.slice(0, maxLen - 3) + "...";
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
// src/ts/triggers.ts
|
|
3318
|
+
import { Node as Node2 } from "ts-morph";
|
|
3011
3319
|
var FIREBASE_V2_TRIGGERS = /* @__PURE__ */ new Set([
|
|
3012
3320
|
// HTTPS (firebase-functions/v2/https)
|
|
3013
3321
|
"onCall",
|
|
@@ -3064,36 +3372,127 @@ var FIREBASE_V2_TRIGGERS = /* @__PURE__ */ new Set([
|
|
|
3064
3372
|
// Test Lab (firebase-functions/v2/testLab)
|
|
3065
3373
|
"onTestMatrixCompleted"
|
|
3066
3374
|
]);
|
|
3067
|
-
function
|
|
3068
|
-
const
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3375
|
+
function extractFirebaseTriggerName(init, filePath, varName, importMap) {
|
|
3376
|
+
const text = init.getText().trim();
|
|
3377
|
+
const shouldDebug = filePath && filePath.includes("functions/src/");
|
|
3378
|
+
if (shouldDebug) {
|
|
3379
|
+
logger.debugFunctions(`[extractFirebaseTriggerName] Iniciando an\xE1lise`);
|
|
3380
|
+
logger.debugFunctions(` VarName: ${varName}`);
|
|
3381
|
+
logger.debugFunctions(` Node Kind: ${init.getKindName()} (${init.getKind()})`);
|
|
3382
|
+
logger.debugFunctions(` \xC9 CallExpression: ${Node2.isCallExpression(init)}`);
|
|
3383
|
+
logger.debugFunctions(` ImportMap dispon\xEDvel: ${importMap ? "SIM" : "N\xC3O"}`);
|
|
3384
|
+
}
|
|
3385
|
+
if (importMap && Node2.isCallExpression(init)) {
|
|
3386
|
+
const expr = init.getExpression();
|
|
3387
|
+
if (shouldDebug) {
|
|
3388
|
+
logger.debugFunctions(` Expression Kind: ${expr.getKindName()} (${expr.getKind()})`);
|
|
3389
|
+
logger.debugFunctions(` \xC9 Identifier: ${Node2.isIdentifier(expr)}`);
|
|
3390
|
+
logger.debugFunctions(` \xC9 PropertyAccess: ${Node2.isPropertyAccessExpression(expr)}`);
|
|
3075
3391
|
}
|
|
3076
|
-
|
|
3077
|
-
const
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3080
|
-
|
|
3392
|
+
if (Node2.isIdentifier(expr)) {
|
|
3393
|
+
const name = expr.getText();
|
|
3394
|
+
const importInfo = importMap.get(name);
|
|
3395
|
+
if (shouldDebug) {
|
|
3396
|
+
logger.debugFunctions(` [Caso 1: Identifier] Nome: ${name}`);
|
|
3397
|
+
logger.debugFunctions(` ImportInfo: ${importInfo ? JSON.stringify(importInfo) : "n\xE3o encontrado"}`);
|
|
3398
|
+
}
|
|
3399
|
+
if (importInfo && importInfo.module.includes("firebase-functions")) {
|
|
3400
|
+
if (FIREBASE_V2_TRIGGERS.has(importInfo.name)) {
|
|
3401
|
+
if (shouldDebug) logger.debugFunctions(` \u2713 Import detectado: ${name} -> ${importInfo.name} from ${importInfo.module}`);
|
|
3402
|
+
return importInfo.name;
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
if (FIREBASE_V2_TRIGGERS.has(name)) {
|
|
3406
|
+
if (shouldDebug) logger.debugFunctions(` \u2713 Trigger conhecido detectado: ${name}`);
|
|
3407
|
+
return name;
|
|
3408
|
+
}
|
|
3409
|
+
} else if (Node2.isPropertyAccessExpression(expr)) {
|
|
3410
|
+
const lastPart = expr.getName();
|
|
3411
|
+
if (shouldDebug) {
|
|
3412
|
+
logger.debugFunctions(` [Caso 2: PropertyAccess] \xDAltima parte: ${lastPart}`);
|
|
3413
|
+
}
|
|
3414
|
+
if (FIREBASE_V2_TRIGGERS.has(lastPart)) {
|
|
3415
|
+
let root = expr.getExpression();
|
|
3416
|
+
let depth = 0;
|
|
3417
|
+
while (Node2.isPropertyAccessExpression(root) && depth < 10) {
|
|
3418
|
+
root = root.getExpression();
|
|
3419
|
+
depth++;
|
|
3420
|
+
}
|
|
3421
|
+
if (Node2.isIdentifier(root)) {
|
|
3422
|
+
const rootName = root.getText();
|
|
3423
|
+
const importInfo = importMap.get(rootName);
|
|
3424
|
+
if (shouldDebug) {
|
|
3425
|
+
logger.debugFunctions(` Raiz: ${rootName} (profundidade: ${depth})`);
|
|
3426
|
+
logger.debugFunctions(` ImportInfo da raiz: ${importInfo ? JSON.stringify(importInfo) : "n\xE3o encontrado"}`);
|
|
3427
|
+
}
|
|
3428
|
+
if (importInfo && importInfo.module.includes("firebase-functions")) {
|
|
3429
|
+
if (shouldDebug) logger.debugFunctions(` \u2713 Chain detectada: ${rootName}...${lastPart} from ${importInfo.module}`);
|
|
3430
|
+
return lastPart;
|
|
3431
|
+
}
|
|
3432
|
+
if (["v2", "functions", "firebase", "admin"].includes(rootName)) {
|
|
3433
|
+
if (shouldDebug) logger.debugFunctions(` \u2713 Heur\xEDstica: raiz "${rootName}" \xE9 conhecida do Firebase`);
|
|
3434
|
+
return lastPart;
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3081
3438
|
}
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3439
|
+
}
|
|
3440
|
+
if (shouldDebug && varName) {
|
|
3441
|
+
logger.debugFunctions(` [regex] Analisando texto: "${text.slice(0, 60)}..."`);
|
|
3442
|
+
}
|
|
3443
|
+
for (const trigger of FIREBASE_V2_TRIGGERS) {
|
|
3444
|
+
const pattern = new RegExp(`(?:^|\\.|\\s|\\()${trigger}(?:<[\\s\\S]*?>)?\\s*\\(`);
|
|
3445
|
+
if (shouldDebug && varName) {
|
|
3446
|
+
const testResult = pattern.test(text);
|
|
3447
|
+
logger.debugFunctions(` [regex] Testando ${trigger}: ${testResult ? "\u2713 MATCH" : "\u2717 no match"}`);
|
|
3448
|
+
}
|
|
3449
|
+
if (pattern.test(text)) {
|
|
3450
|
+
if (shouldDebug && varName) {
|
|
3451
|
+
logger.debugFunctions(` [regex] \u2713\u2713\u2713 TRIGGER ENCONTRADO: ${trigger}`);
|
|
3452
|
+
}
|
|
3453
|
+
return trigger;
|
|
3085
3454
|
}
|
|
3086
3455
|
}
|
|
3087
|
-
return
|
|
3456
|
+
return null;
|
|
3088
3457
|
}
|
|
3458
|
+
function extractTriggerInfo(init, triggerName) {
|
|
3459
|
+
const text = init.getText();
|
|
3460
|
+
const info = { triggerType: triggerName };
|
|
3461
|
+
if (triggerName.startsWith("onDocument") || triggerName.startsWith("onValue")) {
|
|
3462
|
+
const pathMatch = text.match(/\(\s*["'`]([^"'`]+)["'`]/);
|
|
3463
|
+
if (pathMatch) {
|
|
3464
|
+
info.triggerPath = pathMatch[1];
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
if (triggerName === "onSchedule") {
|
|
3468
|
+
const scheduleMatch = text.match(/onSchedule\s*\(\s*["'`]([^"'`]+)["'`]/);
|
|
3469
|
+
if (scheduleMatch) {
|
|
3470
|
+
info.triggerSchedule = scheduleMatch[1];
|
|
3471
|
+
} else {
|
|
3472
|
+
const objectScheduleMatch = text.match(/schedule\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
3473
|
+
if (objectScheduleMatch) {
|
|
3474
|
+
info.triggerSchedule = objectScheduleMatch[1];
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
if (triggerName.startsWith("onObject") || triggerName === "onMetadataUpdated") {
|
|
3479
|
+
const bucketMatch = text.match(/bucket\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
3480
|
+
if (bucketMatch) {
|
|
3481
|
+
info.triggerPath = bucketMatch[1];
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
return info;
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// src/ts/cache.ts
|
|
3089
3488
|
function indexProject(cwd) {
|
|
3090
3489
|
const allFiles = getAllCodeFiles(cwd);
|
|
3091
|
-
|
|
3490
|
+
logger.debug(`Indexando ${allFiles.length} arquivos em ${cwd}`);
|
|
3092
3491
|
const functionFiles = allFiles.filter((f) => f.includes("functions/src/"));
|
|
3093
3492
|
if (functionFiles.length > 0) {
|
|
3094
|
-
|
|
3095
|
-
debugFunctions(`Arquivos em functions/src/:`);
|
|
3096
|
-
functionFiles.forEach((f) => debugFunctions(` - ${f}`));
|
|
3493
|
+
logger.debug(`Encontrados ${functionFiles.length} arquivos em functions/src/:`, functionFiles);
|
|
3494
|
+
logger.debugFunctions(`Arquivos em functions/src/:`);
|
|
3495
|
+
functionFiles.forEach((f) => logger.debugFunctions(` - ${f}`));
|
|
3097
3496
|
}
|
|
3098
3497
|
const project = createProject2(cwd);
|
|
3099
3498
|
let addedCount = 0;
|
|
@@ -3104,21 +3503,21 @@ function indexProject(cwd) {
|
|
|
3104
3503
|
addedCount++;
|
|
3105
3504
|
} catch {
|
|
3106
3505
|
errorCount++;
|
|
3107
|
-
if (
|
|
3108
|
-
|
|
3506
|
+
if (file.includes("functions/src/")) {
|
|
3507
|
+
logger.debug(`[indexer] Erro ao adicionar: ${file}`);
|
|
3109
3508
|
}
|
|
3110
3509
|
}
|
|
3111
3510
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3511
|
+
logger.debug(`[indexer] Total de arquivos encontrados: ${allFiles.length}`);
|
|
3512
|
+
logger.debug(`[indexer] Arquivos adicionados ao projeto: ${addedCount}`);
|
|
3513
|
+
logger.debug(`[indexer] Arquivos com erro: ${errorCount}`);
|
|
3514
|
+
logger.debug(`[indexer] SourceFiles no projeto: ${project.getSourceFiles().length}`);
|
|
3116
3515
|
const functionsInProject = project.getSourceFiles().filter(
|
|
3117
3516
|
(sf) => sf.getFilePath().includes("functions/src/")
|
|
3118
3517
|
);
|
|
3119
|
-
debugFunctions(`[indexer] Arquivos functions/src/ no projeto: ${functionsInProject.length}`);
|
|
3518
|
+
logger.debugFunctions(`[indexer] Arquivos functions/src/ no projeto: ${functionsInProject.length}`);
|
|
3120
3519
|
functionsInProject.forEach((sf) => {
|
|
3121
|
-
debugFunctions(` - ${sf.getFilePath()}`);
|
|
3520
|
+
logger.debugFunctions(` - ${sf.getFilePath()}`);
|
|
3122
3521
|
});
|
|
3123
3522
|
const files = {};
|
|
3124
3523
|
const symbolsByName = {};
|
|
@@ -3160,7 +3559,7 @@ function indexProject(cwd) {
|
|
|
3160
3559
|
if (!name) continue;
|
|
3161
3560
|
const isExported = func.isExported();
|
|
3162
3561
|
const params = func.getParameters().map((p) => p.getName());
|
|
3163
|
-
const returnType =
|
|
3562
|
+
const returnType = simplifyType(safeGetReturnType(func));
|
|
3164
3563
|
const kind = inferSymbolKind(name, "function");
|
|
3165
3564
|
const symbol = {
|
|
3166
3565
|
name,
|
|
@@ -3189,14 +3588,14 @@ function indexProject(cwd) {
|
|
|
3189
3588
|
if (!init) continue;
|
|
3190
3589
|
const initKind = init.getKind();
|
|
3191
3590
|
const initKindName = init.getKindName();
|
|
3192
|
-
if (
|
|
3193
|
-
debugFunctions(`[kind] ${name}: ${initKindName} (kind=${initKind})`);
|
|
3591
|
+
if (filePath.includes("functions/src/")) {
|
|
3592
|
+
logger.debugFunctions(`[kind] ${name}: ${initKindName} (kind=${initKind})`);
|
|
3194
3593
|
}
|
|
3195
3594
|
if (initKind === SyntaxKind2.ArrowFunction || initKind === SyntaxKind2.FunctionExpression) {
|
|
3196
3595
|
const funcLike = init.asKind(SyntaxKind2.ArrowFunction) || init.asKind(SyntaxKind2.FunctionExpression);
|
|
3197
3596
|
if (!funcLike) continue;
|
|
3198
3597
|
const params = funcLike.getParameters().map((p) => p.getName());
|
|
3199
|
-
const returnType =
|
|
3598
|
+
const returnType = simplifyType(safeGetReturnType(funcLike));
|
|
3200
3599
|
const kind = inferSymbolKind(name, "function");
|
|
3201
3600
|
const symbol = {
|
|
3202
3601
|
name,
|
|
@@ -3218,23 +3617,23 @@ function indexProject(cwd) {
|
|
|
3218
3617
|
}
|
|
3219
3618
|
} else if (initKind === SyntaxKind2.CallExpression) {
|
|
3220
3619
|
const importMap = buildImportMap(sourceFile);
|
|
3221
|
-
if (
|
|
3620
|
+
if (filePath.includes("functions/src/")) {
|
|
3222
3621
|
const initText = init.getText().slice(0, 100).replace(/\s+/g, " ");
|
|
3223
|
-
debugFunctions(`
|
|
3622
|
+
logger.debugFunctions(`
|
|
3224
3623
|
[CallExpression] ${filePath}:${varDecl.getStartLineNumber()}`);
|
|
3225
|
-
debugFunctions(` Vari\xE1vel: ${name}`);
|
|
3226
|
-
debugFunctions(` C\xF3digo: ${initText}...`);
|
|
3227
|
-
debugFunctions(` Imports encontrados: ${importMap.size}`);
|
|
3624
|
+
logger.debugFunctions(` Vari\xE1vel: ${name}`);
|
|
3625
|
+
logger.debugFunctions(` C\xF3digo: ${initText}...`);
|
|
3626
|
+
logger.debugFunctions(` Imports encontrados: ${importMap.size}`);
|
|
3228
3627
|
importMap.forEach((info, key) => {
|
|
3229
|
-
debugFunctions(` - ${key} -> ${info.name} from ${info.module}`);
|
|
3628
|
+
logger.debugFunctions(` - ${key} -> ${info.name} from ${info.module}`);
|
|
3230
3629
|
});
|
|
3231
3630
|
}
|
|
3232
3631
|
const triggerName = extractFirebaseTriggerName(init, filePath, name, importMap);
|
|
3233
|
-
if (
|
|
3632
|
+
if (filePath.includes("functions/src/")) {
|
|
3234
3633
|
if (triggerName) {
|
|
3235
|
-
debugFunctions(` \u2713\u2713\u2713 Trigger FINAL detectado: ${triggerName}`);
|
|
3634
|
+
logger.debugFunctions(` \u2713\u2713\u2713 Trigger FINAL detectado: ${triggerName}`);
|
|
3236
3635
|
} else {
|
|
3237
|
-
debugFunctions(` \u2717\u2717\u2717 Nenhum trigger detectado para: ${name}`);
|
|
3636
|
+
logger.debugFunctions(` \u2717\u2717\u2717 Nenhum trigger detectado para: ${name}`);
|
|
3238
3637
|
}
|
|
3239
3638
|
}
|
|
3240
3639
|
if (triggerName && FIREBASE_V2_TRIGGERS.has(triggerName)) {
|
|
@@ -3309,7 +3708,7 @@ function indexProject(cwd) {
|
|
|
3309
3708
|
kind: "interface",
|
|
3310
3709
|
signature: `${isExported ? "export " : ""}interface ${name}`,
|
|
3311
3710
|
isExported,
|
|
3312
|
-
definition:
|
|
3711
|
+
definition: formatInterfaceDefinition(iface)
|
|
3313
3712
|
};
|
|
3314
3713
|
symbols.push(symbol);
|
|
3315
3714
|
if (!symbolsByName[name]) {
|
|
@@ -3330,7 +3729,7 @@ function indexProject(cwd) {
|
|
|
3330
3729
|
kind: "type",
|
|
3331
3730
|
signature: `${isExported ? "export " : ""}type ${name}`,
|
|
3332
3731
|
isExported,
|
|
3333
|
-
definition:
|
|
3732
|
+
definition: simplifyType(safeGetTypeText(() => typeAlias.getType()))
|
|
3334
3733
|
};
|
|
3335
3734
|
symbols.push(symbol);
|
|
3336
3735
|
if (!symbolsByName[name]) {
|
|
@@ -3380,236 +3779,18 @@ function indexProject(cwd) {
|
|
|
3380
3779
|
symbolCount
|
|
3381
3780
|
};
|
|
3382
3781
|
}
|
|
3383
|
-
function createProject2(cwd) {
|
|
3384
|
-
try {
|
|
3385
|
-
const project = new Project2({
|
|
3386
|
-
tsConfigFilePath: `${cwd}/tsconfig.json`,
|
|
3387
|
-
skipAddingFilesFromTsConfig: true
|
|
3388
|
-
});
|
|
3389
|
-
debugLog(`Projeto ts-morph criado com tsconfig: ${cwd}/tsconfig.json`);
|
|
3390
|
-
return project;
|
|
3391
|
-
} catch {
|
|
3392
|
-
debugLog(`Falha ao ler tsconfig, criando projeto b\xE1sico`);
|
|
3393
|
-
return new Project2({
|
|
3394
|
-
skipAddingFilesFromTsConfig: true,
|
|
3395
|
-
compilerOptions: {
|
|
3396
|
-
allowJs: true,
|
|
3397
|
-
checkJs: false,
|
|
3398
|
-
target: 2,
|
|
3399
|
-
// ES2020
|
|
3400
|
-
module: 200,
|
|
3401
|
-
// ESNext
|
|
3402
|
-
moduleResolution: 100
|
|
3403
|
-
// Bundler
|
|
3404
|
-
}
|
|
3405
|
-
});
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
function getAllCodeFiles(dir, files = [], baseDir = dir) {
|
|
3409
|
-
try {
|
|
3410
|
-
const entries = readdirSync2(dir);
|
|
3411
|
-
for (const entry of entries) {
|
|
3412
|
-
const fullPath = join5(dir, entry);
|
|
3413
|
-
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) {
|
|
3414
|
-
continue;
|
|
3415
|
-
}
|
|
3416
|
-
try {
|
|
3417
|
-
const stat = statSync2(fullPath);
|
|
3418
|
-
if (stat.isDirectory()) {
|
|
3419
|
-
getAllCodeFiles(fullPath, files, baseDir);
|
|
3420
|
-
} else {
|
|
3421
|
-
const ext = extname2(entry).toLowerCase();
|
|
3422
|
-
if (CODE_EXTENSIONS2.has(ext)) {
|
|
3423
|
-
const relativePath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
|
|
3424
|
-
files.push(relativePath);
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
} catch {
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
} catch {
|
|
3431
|
-
}
|
|
3432
|
-
return files;
|
|
3433
|
-
}
|
|
3434
|
-
function inferSymbolKind(name, context2) {
|
|
3435
|
-
if (name.startsWith("use") && name.length > 3 && name[3] === name[3].toUpperCase()) {
|
|
3436
|
-
return "hook";
|
|
3437
|
-
}
|
|
3438
|
-
if (context2 === "function" && name[0] === name[0].toUpperCase() && !name.includes("_")) {
|
|
3439
|
-
return "component";
|
|
3440
|
-
}
|
|
3441
|
-
return context2 === "function" ? "function" : "const";
|
|
3442
|
-
}
|
|
3443
|
-
function extractFirebaseTriggerName(init, filePath, varName, importMap) {
|
|
3444
|
-
const text = init.getText().trim();
|
|
3445
|
-
const shouldDebug = DEBUG_FUNCTIONS && filePath && filePath.includes("functions/src/");
|
|
3446
|
-
if (shouldDebug) {
|
|
3447
|
-
debugFunctions(`[extractFirebaseTriggerName] Iniciando an\xE1lise`);
|
|
3448
|
-
debugFunctions(` VarName: ${varName}`);
|
|
3449
|
-
debugFunctions(` Node Kind: ${init.getKindName()} (${init.getKind()})`);
|
|
3450
|
-
debugFunctions(` \xC9 CallExpression: ${Node2.isCallExpression(init)}`);
|
|
3451
|
-
debugFunctions(` ImportMap dispon\xEDvel: ${importMap ? "SIM" : "N\xC3O"}`);
|
|
3452
|
-
}
|
|
3453
|
-
if (importMap && Node2.isCallExpression(init)) {
|
|
3454
|
-
const expr = init.getExpression();
|
|
3455
|
-
if (shouldDebug) {
|
|
3456
|
-
debugFunctions(` Expression Kind: ${expr.getKindName()} (${expr.getKind()})`);
|
|
3457
|
-
debugFunctions(` \xC9 Identifier: ${Node2.isIdentifier(expr)}`);
|
|
3458
|
-
debugFunctions(` \xC9 PropertyAccess: ${Node2.isPropertyAccessExpression(expr)}`);
|
|
3459
|
-
}
|
|
3460
|
-
if (Node2.isIdentifier(expr)) {
|
|
3461
|
-
const name = expr.getText();
|
|
3462
|
-
const importInfo = importMap.get(name);
|
|
3463
|
-
if (shouldDebug) {
|
|
3464
|
-
debugFunctions(` [Caso 1: Identifier] Nome: ${name}`);
|
|
3465
|
-
debugFunctions(` ImportInfo: ${importInfo ? JSON.stringify(importInfo) : "n\xE3o encontrado"}`);
|
|
3466
|
-
}
|
|
3467
|
-
if (importInfo && importInfo.module.includes("firebase-functions")) {
|
|
3468
|
-
if (FIREBASE_V2_TRIGGERS.has(importInfo.name)) {
|
|
3469
|
-
if (shouldDebug) debugFunctions(` \u2713 Import detectado: ${name} -> ${importInfo.name} from ${importInfo.module}`);
|
|
3470
|
-
return importInfo.name;
|
|
3471
|
-
}
|
|
3472
|
-
}
|
|
3473
|
-
if (FIREBASE_V2_TRIGGERS.has(name)) {
|
|
3474
|
-
if (shouldDebug) debugFunctions(` \u2713 Trigger conhecido detectado: ${name}`);
|
|
3475
|
-
return name;
|
|
3476
|
-
}
|
|
3477
|
-
} else if (Node2.isPropertyAccessExpression(expr)) {
|
|
3478
|
-
const lastPart = expr.getName();
|
|
3479
|
-
if (shouldDebug) {
|
|
3480
|
-
debugFunctions(` [Caso 2: PropertyAccess] \xDAltima parte: ${lastPart}`);
|
|
3481
|
-
}
|
|
3482
|
-
if (FIREBASE_V2_TRIGGERS.has(lastPart)) {
|
|
3483
|
-
let root = expr.getExpression();
|
|
3484
|
-
let depth = 0;
|
|
3485
|
-
while (Node2.isPropertyAccessExpression(root) && depth < 10) {
|
|
3486
|
-
root = root.getExpression();
|
|
3487
|
-
depth++;
|
|
3488
|
-
}
|
|
3489
|
-
if (Node2.isIdentifier(root)) {
|
|
3490
|
-
const rootName = root.getText();
|
|
3491
|
-
const importInfo = importMap.get(rootName);
|
|
3492
|
-
if (shouldDebug) {
|
|
3493
|
-
debugFunctions(` Raiz: ${rootName} (profundidade: ${depth})`);
|
|
3494
|
-
debugFunctions(` ImportInfo da raiz: ${importInfo ? JSON.stringify(importInfo) : "n\xE3o encontrado"}`);
|
|
3495
|
-
}
|
|
3496
|
-
if (importInfo && importInfo.module.includes("firebase-functions")) {
|
|
3497
|
-
if (shouldDebug) debugFunctions(` \u2713 Chain detectada: ${rootName}...${lastPart} from ${importInfo.module}`);
|
|
3498
|
-
return lastPart;
|
|
3499
|
-
}
|
|
3500
|
-
if (["v2", "functions", "firebase", "admin"].includes(rootName)) {
|
|
3501
|
-
if (shouldDebug) debugFunctions(` \u2713 Heur\xEDstica: raiz "${rootName}" \xE9 conhecida do Firebase`);
|
|
3502
|
-
return lastPart;
|
|
3503
|
-
}
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
}
|
|
3508
|
-
if (shouldDebug && varName) {
|
|
3509
|
-
debugFunctions(` [regex] Analisando texto: "${text.slice(0, 60)}..."`);
|
|
3510
|
-
}
|
|
3511
|
-
for (const trigger of FIREBASE_V2_TRIGGERS) {
|
|
3512
|
-
const pattern = new RegExp(`(?:^|\\.|\\s|\\()${trigger}(?:<[\\s\\S]*?>)?\\s*\\(`);
|
|
3513
|
-
if (shouldDebug && varName) {
|
|
3514
|
-
const testResult = pattern.test(text);
|
|
3515
|
-
debugFunctions(` [regex] Testando ${trigger}: ${testResult ? "\u2713 MATCH" : "\u2717 no match"}`);
|
|
3516
|
-
}
|
|
3517
|
-
if (pattern.test(text)) {
|
|
3518
|
-
if (shouldDebug && varName) {
|
|
3519
|
-
debugFunctions(` [regex] \u2713\u2713\u2713 TRIGGER ENCONTRADO: ${trigger}`);
|
|
3520
|
-
}
|
|
3521
|
-
return trigger;
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
return null;
|
|
3525
|
-
}
|
|
3526
|
-
function extractTriggerInfo(init, triggerName) {
|
|
3527
|
-
const text = init.getText();
|
|
3528
|
-
const info = { triggerType: triggerName };
|
|
3529
|
-
if (triggerName.startsWith("onDocument") || triggerName.startsWith("onValue")) {
|
|
3530
|
-
const pathMatch = text.match(/\(\s*["'`]([^"'`]+)["'`]/);
|
|
3531
|
-
if (pathMatch) {
|
|
3532
|
-
info.triggerPath = pathMatch[1];
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
if (triggerName === "onSchedule") {
|
|
3536
|
-
const scheduleMatch = text.match(/onSchedule\s*\(\s*["'`]([^"'`]+)["'`]/);
|
|
3537
|
-
if (scheduleMatch) {
|
|
3538
|
-
info.triggerSchedule = scheduleMatch[1];
|
|
3539
|
-
} else {
|
|
3540
|
-
const objectScheduleMatch = text.match(/schedule\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
3541
|
-
if (objectScheduleMatch) {
|
|
3542
|
-
info.triggerSchedule = objectScheduleMatch[1];
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
if (triggerName.startsWith("onObject") || triggerName === "onMetadataUpdated") {
|
|
3547
|
-
const bucketMatch = text.match(/bucket\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
3548
|
-
if (bucketMatch) {
|
|
3549
|
-
info.triggerPath = bucketMatch[1];
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3552
|
-
return info;
|
|
3553
|
-
}
|
|
3554
|
-
function simplifyType2(typeText) {
|
|
3555
|
-
if (!typeText) return "unknown";
|
|
3556
|
-
let simplified = typeText.replace(/import\([^)]+\)\./g, "");
|
|
3557
|
-
if (simplified.length > 80) {
|
|
3558
|
-
simplified = simplified.slice(0, 77) + "...";
|
|
3559
|
-
}
|
|
3560
|
-
return simplified;
|
|
3561
|
-
}
|
|
3562
|
-
function safeGetTypeText(getTypeFn) {
|
|
3563
|
-
try {
|
|
3564
|
-
const type = getTypeFn();
|
|
3565
|
-
if (!type) return "unknown";
|
|
3566
|
-
return type.getText();
|
|
3567
|
-
} catch {
|
|
3568
|
-
return "unknown";
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
function safeGetReturnType(node) {
|
|
3572
|
-
try {
|
|
3573
|
-
const returnType = node.getReturnType();
|
|
3574
|
-
if (!returnType) return "unknown";
|
|
3575
|
-
return returnType.getText();
|
|
3576
|
-
} catch {
|
|
3577
|
-
return "unknown";
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
function truncateCode(code, maxLen) {
|
|
3581
|
-
const oneLine = code.replace(/\s+/g, " ").trim();
|
|
3582
|
-
if (oneLine.length <= maxLen) return oneLine;
|
|
3583
|
-
return oneLine.slice(0, maxLen - 3) + "...";
|
|
3584
|
-
}
|
|
3585
|
-
function formatInterfaceDefinition2(iface) {
|
|
3586
|
-
const parts = [];
|
|
3587
|
-
const extendsClauses = iface.getExtends();
|
|
3588
|
-
if (extendsClauses.length > 0) {
|
|
3589
|
-
parts.push(`extends ${extendsClauses.map((e) => e.getText()).join(", ")}`);
|
|
3590
|
-
}
|
|
3591
|
-
const props = iface.getProperties();
|
|
3592
|
-
for (const prop of props.slice(0, 10)) {
|
|
3593
|
-
const propType = simplifyType2(safeGetTypeText(() => prop.getType()));
|
|
3594
|
-
parts.push(`${prop.getName()}: ${propType}`);
|
|
3595
|
-
}
|
|
3596
|
-
if (props.length > 10) {
|
|
3597
|
-
parts.push(`... +${props.length - 10} props`);
|
|
3598
|
-
}
|
|
3599
|
-
return parts.length > 0 ? `{ ${parts.join("; ")} }` : "{}";
|
|
3600
|
-
}
|
|
3601
3782
|
|
|
3602
3783
|
// src/commands/context.ts
|
|
3603
3784
|
async function context(target, options = {}) {
|
|
3604
|
-
const cwd
|
|
3605
|
-
const format = options.format || "text";
|
|
3785
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
3606
3786
|
if (!target) {
|
|
3607
3787
|
throw new Error("Target e obrigatorio. Exemplo: ai-tool context src/components/Button.tsx");
|
|
3608
3788
|
}
|
|
3609
3789
|
try {
|
|
3610
|
-
const targetPath =
|
|
3790
|
+
const targetPath = findTargetFile2(target, cwd);
|
|
3611
3791
|
if (!targetPath) {
|
|
3612
|
-
|
|
3792
|
+
const allFiles = getAllCodeFiles2(cwd);
|
|
3793
|
+
return formatFileNotFound({ target, allFiles, command: "context" });
|
|
3613
3794
|
}
|
|
3614
3795
|
const project = createProject(cwd);
|
|
3615
3796
|
const absolutePath = resolve2(cwd, targetPath);
|
|
@@ -3628,25 +3809,22 @@ async function context(target, options = {}) {
|
|
|
3628
3809
|
functions: functions2,
|
|
3629
3810
|
types
|
|
3630
3811
|
};
|
|
3631
|
-
|
|
3632
|
-
return JSON.stringify(result, null, 2);
|
|
3633
|
-
}
|
|
3634
|
-
return formatContextText(result);
|
|
3812
|
+
return formatOutput(result, format, formatContextText);
|
|
3635
3813
|
} catch (error) {
|
|
3636
3814
|
const message = error instanceof Error ? error.message : String(error);
|
|
3637
3815
|
throw new Error(`Erro ao executar context: ${message}`);
|
|
3638
3816
|
}
|
|
3639
3817
|
}
|
|
3640
3818
|
var CODE_EXTENSIONS3 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
3641
|
-
function
|
|
3819
|
+
function findTargetFile2(target, cwd) {
|
|
3642
3820
|
const normalizedTarget = target.replace(/\\/g, "/");
|
|
3643
3821
|
const directPath = resolve2(cwd, normalizedTarget);
|
|
3644
|
-
if (
|
|
3822
|
+
if (existsSync6(directPath) && isCodeFile2(directPath)) {
|
|
3645
3823
|
return normalizedTarget;
|
|
3646
3824
|
}
|
|
3647
3825
|
for (const ext of CODE_EXTENSIONS3) {
|
|
3648
3826
|
const withExt = directPath + ext;
|
|
3649
|
-
if (
|
|
3827
|
+
if (existsSync6(withExt)) {
|
|
3650
3828
|
return normalizedTarget + ext;
|
|
3651
3829
|
}
|
|
3652
3830
|
}
|
|
@@ -3709,10 +3887,6 @@ function shouldIgnore(name) {
|
|
|
3709
3887
|
];
|
|
3710
3888
|
return ignoredDirs.includes(name) || name.startsWith(".");
|
|
3711
3889
|
}
|
|
3712
|
-
function formatNotFound3(target, cwd) {
|
|
3713
|
-
const allFiles = getAllCodeFiles2(cwd);
|
|
3714
|
-
return formatFileNotFound({ target, allFiles, command: "context" });
|
|
3715
|
-
}
|
|
3716
3890
|
async function areaContext(areaName, options = {}) {
|
|
3717
3891
|
const cwd = options.cwd || process.cwd();
|
|
3718
3892
|
const format = options.format || "text";
|
|
@@ -4725,8 +4899,7 @@ function getTriggerIcon(trigger) {
|
|
|
4725
4899
|
|
|
4726
4900
|
// src/commands/find.ts
|
|
4727
4901
|
async function find(query, options = {}) {
|
|
4728
|
-
const cwd
|
|
4729
|
-
const format = options.format || "text";
|
|
4902
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
4730
4903
|
const filterType = options.type || "all";
|
|
4731
4904
|
const filterArea = options.area;
|
|
4732
4905
|
const defOnly = options.def ?? false;
|
|
@@ -4817,10 +4990,7 @@ async function find(query, options = {}) {
|
|
|
4817
4990
|
},
|
|
4818
4991
|
fromCache
|
|
4819
4992
|
};
|
|
4820
|
-
|
|
4821
|
-
return JSON.stringify(result, null, 2);
|
|
4822
|
-
}
|
|
4823
|
-
return formatFindText(result);
|
|
4993
|
+
return formatOutput(result, format, formatFindText, fromCache);
|
|
4824
4994
|
} catch (error) {
|
|
4825
4995
|
const message = error instanceof Error ? error.message : String(error);
|
|
4826
4996
|
throw new Error(`Erro ao executar find: ${message}`);
|
|
@@ -4967,6 +5137,8 @@ export {
|
|
|
4967
5137
|
getAreaName,
|
|
4968
5138
|
getAreaDescription,
|
|
4969
5139
|
inferFileDescription,
|
|
5140
|
+
parseCommandOptions,
|
|
5141
|
+
formatOutput,
|
|
4970
5142
|
map,
|
|
4971
5143
|
dead,
|
|
4972
5144
|
deadFix,
|