@driftless-sh/cli 0.1.44 → 0.1.45
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/dist/index.js +277 -236
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -213426,6 +213426,65 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213426
213426
|
}
|
|
213427
213427
|
return null;
|
|
213428
213428
|
}
|
|
213429
|
+
function argText(arg) {
|
|
213430
|
+
if (typescript_1.default.isStringLiteralLike(arg))
|
|
213431
|
+
return [arg.text];
|
|
213432
|
+
if (typescript_1.default.isIdentifier(arg))
|
|
213433
|
+
return [arg.text];
|
|
213434
|
+
if (typescript_1.default.isObjectLiteralExpression(arg)) {
|
|
213435
|
+
const out = [];
|
|
213436
|
+
for (const prop of arg.properties) {
|
|
213437
|
+
if (typescript_1.default.isPropertyAssignment(prop) && typescript_1.default.isIdentifier(prop.name) && prop.name.text === "path" && typescript_1.default.isStringLiteralLike(prop.initializer)) {
|
|
213438
|
+
out.push(prop.initializer.text);
|
|
213439
|
+
}
|
|
213440
|
+
}
|
|
213441
|
+
return out;
|
|
213442
|
+
}
|
|
213443
|
+
return [];
|
|
213444
|
+
}
|
|
213445
|
+
function decoratorArgsOf(decs) {
|
|
213446
|
+
const map = {};
|
|
213447
|
+
for (const d of decs || []) {
|
|
213448
|
+
const name = getDecoratorName(d);
|
|
213449
|
+
if (!name)
|
|
213450
|
+
continue;
|
|
213451
|
+
const args = [];
|
|
213452
|
+
if (typescript_1.default.isCallExpression(d.expression)) {
|
|
213453
|
+
for (const a of d.expression.arguments)
|
|
213454
|
+
args.push(...argText(a));
|
|
213455
|
+
}
|
|
213456
|
+
if (!(name in map))
|
|
213457
|
+
map[name] = args;
|
|
213458
|
+
}
|
|
213459
|
+
return map;
|
|
213460
|
+
}
|
|
213461
|
+
function moduleMetaOf(decs) {
|
|
213462
|
+
for (const d of decs || []) {
|
|
213463
|
+
if (getDecoratorName(d) !== "Module")
|
|
213464
|
+
continue;
|
|
213465
|
+
if (!typescript_1.default.isCallExpression(d.expression) || d.expression.arguments.length === 0)
|
|
213466
|
+
return { imports: [], providers: [], controllers: [] };
|
|
213467
|
+
const obj = d.expression.arguments[0];
|
|
213468
|
+
const meta = { imports: [], providers: [], controllers: [] };
|
|
213469
|
+
if (typescript_1.default.isObjectLiteralExpression(obj)) {
|
|
213470
|
+
for (const prop of obj.properties) {
|
|
213471
|
+
if (!typescript_1.default.isPropertyAssignment(prop) || !typescript_1.default.isIdentifier(prop.name))
|
|
213472
|
+
continue;
|
|
213473
|
+
const key = prop.name.text;
|
|
213474
|
+
if (key !== "imports" && key !== "providers" && key !== "controllers")
|
|
213475
|
+
continue;
|
|
213476
|
+
if (!typescript_1.default.isArrayLiteralExpression(prop.initializer))
|
|
213477
|
+
continue;
|
|
213478
|
+
for (const el of prop.initializer.elements) {
|
|
213479
|
+
if (typescript_1.default.isIdentifier(el))
|
|
213480
|
+
meta[key].push(el.text);
|
|
213481
|
+
}
|
|
213482
|
+
}
|
|
213483
|
+
}
|
|
213484
|
+
return meta;
|
|
213485
|
+
}
|
|
213486
|
+
return void 0;
|
|
213487
|
+
}
|
|
213429
213488
|
function extractTypeNode(node) {
|
|
213430
213489
|
if (!node)
|
|
213431
213490
|
return "unknown";
|
|
@@ -213505,6 +213564,7 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213505
213564
|
name: member.name.text,
|
|
213506
213565
|
line: sourceFile.getLineAndCharacterOfPosition(member.getStart(sourceFile)).line + 1,
|
|
213507
213566
|
decorators,
|
|
213567
|
+
decoratorArgs: decoratorArgsOf(typescript_1.default.getDecorators(member)),
|
|
213508
213568
|
params,
|
|
213509
213569
|
returnType
|
|
213510
213570
|
});
|
|
@@ -213554,10 +213614,13 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213554
213614
|
}
|
|
213555
213615
|
}
|
|
213556
213616
|
}
|
|
213617
|
+
const classDecs = typescript_1.default.getDecorators(node);
|
|
213557
213618
|
facts.classes.push({
|
|
213558
213619
|
name: className,
|
|
213559
213620
|
line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
213560
213621
|
decorators,
|
|
213622
|
+
decoratorArgs: decoratorArgsOf(classDecs),
|
|
213623
|
+
moduleMeta: moduleMetaOf(classDecs),
|
|
213561
213624
|
implements: implementsList,
|
|
213562
213625
|
constructorParams: extractConstructorParams(node),
|
|
213563
213626
|
methods: extractMethods(node),
|
|
@@ -213594,58 +213657,16 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213594
213657
|
visit(sourceFile);
|
|
213595
213658
|
return facts;
|
|
213596
213659
|
}
|
|
213597
|
-
function
|
|
213598
|
-
const
|
|
213599
|
-
|
|
213600
|
-
for (const cls of facts.classes) {
|
|
213601
|
-
index.set(cls.name, { filePath: facts.filePath, kind: "class", decorators: cls.decorators, implements: cls.implements });
|
|
213602
|
-
}
|
|
213603
|
-
for (const fn of facts.functions) {
|
|
213604
|
-
index.set(fn.name, { filePath: facts.filePath, kind: "function", decorators: fn.decorators, implements: [] });
|
|
213605
|
-
}
|
|
213606
|
-
for (const iface of facts.interfaces) {
|
|
213607
|
-
index.set(iface.name, { filePath: facts.filePath, kind: "interface", decorators: [], implements: [] });
|
|
213608
|
-
}
|
|
213660
|
+
function indexFileSymbols(facts, index) {
|
|
213661
|
+
for (const cls of facts.classes) {
|
|
213662
|
+
index.set(cls.name, { filePath: facts.filePath, kind: "class", decorators: cls.decorators, implements: cls.implements });
|
|
213609
213663
|
}
|
|
213610
|
-
|
|
213611
|
-
|
|
213612
|
-
|
|
213613
|
-
const
|
|
213614
|
-
|
|
213615
|
-
const importedNames = /* @__PURE__ */ new Map();
|
|
213616
|
-
for (const imp of facts.imports) {
|
|
213617
|
-
importedNames.set(imp.localName, imp.importedName);
|
|
213618
|
-
}
|
|
213619
|
-
for (const cls of facts.classes) {
|
|
213620
|
-
for (const param of cls.constructorParams) {
|
|
213621
|
-
const resolvedType = normalizeTypeName(param.type);
|
|
213622
|
-
if (symbolIndex.has(resolvedType) && !isPrimitive(resolvedType)) {
|
|
213623
|
-
const entries = index.get(resolvedType) || [];
|
|
213624
|
-
entries.push({ filePath: facts.filePath, context: "constructor-param", ownerSymbol: cls.name });
|
|
213625
|
-
index.set(resolvedType, entries);
|
|
213626
|
-
}
|
|
213627
|
-
}
|
|
213628
|
-
for (const method of cls.methods) {
|
|
213629
|
-
for (const param of method.params) {
|
|
213630
|
-
const resolvedType = normalizeTypeName(param.type);
|
|
213631
|
-
if (symbolIndex.has(resolvedType) && !isPrimitive(resolvedType)) {
|
|
213632
|
-
const entries = index.get(resolvedType) || [];
|
|
213633
|
-
entries.push({ filePath: facts.filePath, context: "method-param", ownerSymbol: `${cls.name}.${method.name}` });
|
|
213634
|
-
index.set(resolvedType, entries);
|
|
213635
|
-
}
|
|
213636
|
-
}
|
|
213637
|
-
}
|
|
213638
|
-
}
|
|
213639
|
-
for (const imp of facts.imports) {
|
|
213640
|
-
const targetName = imp.importedName !== "default" ? imp.importedName : imp.localName;
|
|
213641
|
-
if (symbolIndex.has(targetName)) {
|
|
213642
|
-
const entries = index.get(targetName) || [];
|
|
213643
|
-
entries.push({ filePath: facts.filePath, context: "import" });
|
|
213644
|
-
index.set(targetName, entries);
|
|
213645
|
-
}
|
|
213646
|
-
}
|
|
213664
|
+
for (const fn of facts.functions) {
|
|
213665
|
+
index.set(fn.name, { filePath: facts.filePath, kind: "function", decorators: fn.decorators, implements: [] });
|
|
213666
|
+
}
|
|
213667
|
+
for (const iface of facts.interfaces) {
|
|
213668
|
+
index.set(iface.name, { filePath: facts.filePath, kind: "interface", decorators: [], implements: [] });
|
|
213647
213669
|
}
|
|
213648
|
-
return index;
|
|
213649
213670
|
}
|
|
213650
213671
|
function normalizeTypeName(typeText) {
|
|
213651
213672
|
return typeText.replace(/^typeof\s+/, "").replace(/^import\(".*"\)\./, "").replace(/<.*>$/, "").replace(/\[\]$/, "").split("|")[0].trim();
|
|
@@ -213653,15 +213674,6 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213653
213674
|
function isPrimitive(typeName) {
|
|
213654
213675
|
return ["string", "number", "boolean", "object", "unknown", "any", "void", "Promise", "Date", "Array", "Record", "Partial", "Required", "Pick", "Omit", "Readonly"].includes(typeName);
|
|
213655
213676
|
}
|
|
213656
|
-
function extractModuleFromPath(filePath) {
|
|
213657
|
-
const segments = filePath.split("/");
|
|
213658
|
-
const srcIndex = segments.indexOf("src");
|
|
213659
|
-
if (srcIndex >= 0 && srcIndex + 1 < segments.length) {
|
|
213660
|
-
const next = segments[srcIndex + 1];
|
|
213661
|
-
return next.replace(/\.module\.ts$/, "").replace(/\.ts$/, "");
|
|
213662
|
-
}
|
|
213663
|
-
return "root";
|
|
213664
|
-
}
|
|
213665
213677
|
function isInSourceDir(filePath) {
|
|
213666
213678
|
if (filePath.includes("node_modules") || filePath.includes("/dist/"))
|
|
213667
213679
|
return false;
|
|
@@ -213767,74 +213779,27 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213767
213779
|
}
|
|
213768
213780
|
return false;
|
|
213769
213781
|
}
|
|
213770
|
-
function
|
|
213771
|
-
const
|
|
213772
|
-
|
|
213773
|
-
|
|
213774
|
-
|
|
213775
|
-
|
|
213776
|
-
|
|
213777
|
-
} catch {
|
|
213778
|
-
}
|
|
213779
|
-
return "";
|
|
213782
|
+
function joinRoute(base, sub) {
|
|
213783
|
+
const parts = [base, sub].map((s) => (s || "").trim().replace(/^\/+|\/+$/g, "")).filter(Boolean);
|
|
213784
|
+
const joined = parts.join("/").replace(/\/{2,}/g, "/");
|
|
213785
|
+
return "/" + joined;
|
|
213786
|
+
}
|
|
213787
|
+
function extractControllerPath(cls) {
|
|
213788
|
+
return cls.decoratorArgs["Controller"]?.[0] ?? "";
|
|
213780
213789
|
}
|
|
213781
|
-
function extractEndpointPath(
|
|
213790
|
+
function extractEndpointPath(method, clsPath) {
|
|
213782
213791
|
const results = [];
|
|
213783
|
-
const
|
|
213784
|
-
|
|
213785
|
-
|
|
213786
|
-
|
|
213787
|
-
|
|
213788
|
-
|
|
213789
|
-
if (line.includes(methodName) && (line.includes("(") || line.includes(":"))) {
|
|
213790
|
-
for (let j = i - 1; j >= Math.max(0, i - 10); j--) {
|
|
213791
|
-
const decoratorLine = lines[j].trim();
|
|
213792
|
-
for (const [decoratorName, httpMethod] of Object.entries(HTTP_METHODS)) {
|
|
213793
|
-
const match = decoratorLine.match(new RegExp(`@${decoratorName}\\s*\\(\\s*['"]([^'"]+)['"]\\s*\\)`));
|
|
213794
|
-
if (match) {
|
|
213795
|
-
results.push({ method: httpMethod, path: clsPath + match[1] });
|
|
213796
|
-
} else if (decoratorLine.match(new RegExp(`@${decoratorName}\\s*\\(\\s*\\)`)) || decoratorLine.match(new RegExp(`@${decoratorName}$`))) {
|
|
213797
|
-
results.push({ method: httpMethod, path: clsPath || "/" });
|
|
213798
|
-
}
|
|
213799
|
-
}
|
|
213800
|
-
}
|
|
213801
|
-
break;
|
|
213802
|
-
}
|
|
213803
|
-
}
|
|
213804
|
-
} catch {
|
|
213805
|
-
for (const dec of methodDecorators) {
|
|
213806
|
-
const httpMethod = HTTP_METHODS[dec];
|
|
213807
|
-
if (httpMethod) {
|
|
213808
|
-
results.push({ method: httpMethod, path: clsPath || "/" });
|
|
213809
|
-
}
|
|
213810
|
-
}
|
|
213792
|
+
for (const dec of method.decorators) {
|
|
213793
|
+
const httpMethod = HTTP_METHODS[dec];
|
|
213794
|
+
if (!httpMethod)
|
|
213795
|
+
continue;
|
|
213796
|
+
const sub = method.decoratorArgs[dec]?.[0] ?? "";
|
|
213797
|
+
results.push({ method: httpMethod, path: joinRoute(clsPath, sub) });
|
|
213811
213798
|
}
|
|
213812
|
-
return results
|
|
213799
|
+
return results;
|
|
213813
213800
|
}
|
|
213814
|
-
function extractGuardsFromMethod(
|
|
213815
|
-
|
|
213816
|
-
const fullPath = node_path_1.default.join(process.cwd(), facts.filePath);
|
|
213817
|
-
try {
|
|
213818
|
-
const content = node_fs_1.default.readFileSync(fullPath, "utf8");
|
|
213819
|
-
const lines = content.split("\n");
|
|
213820
|
-
for (let i = 0; i < lines.length; i++) {
|
|
213821
|
-
const line = lines[i];
|
|
213822
|
-
if (line.includes(methodName) && (line.includes("(") || line.includes(":"))) {
|
|
213823
|
-
for (let j = i - 1; j >= Math.max(0, i - 10); j--) {
|
|
213824
|
-
const decoratorLine = lines[j].trim();
|
|
213825
|
-
const useGuardsMatch = decoratorLine.match(/@UseGuards\s*\((.+)\)/);
|
|
213826
|
-
if (useGuardsMatch) {
|
|
213827
|
-
const guardNames = useGuardsMatch[1].match(/[A-Z]\w*Guard/g);
|
|
213828
|
-
if (guardNames)
|
|
213829
|
-
guards.push(...guardNames);
|
|
213830
|
-
}
|
|
213831
|
-
}
|
|
213832
|
-
break;
|
|
213833
|
-
}
|
|
213834
|
-
}
|
|
213835
|
-
} catch {
|
|
213836
|
-
}
|
|
213837
|
-
return guards;
|
|
213801
|
+
function extractGuardsFromMethod(method) {
|
|
213802
|
+
return (method.decoratorArgs["UseGuards"] ?? []).filter((g) => /^[A-Z]\w*$/.test(g));
|
|
213838
213803
|
}
|
|
213839
213804
|
function extractEndpointParams(method) {
|
|
213840
213805
|
const params = [];
|
|
@@ -213871,35 +213836,48 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213871
213836
|
};
|
|
213872
213837
|
});
|
|
213873
213838
|
}
|
|
213839
|
+
function stamp(c, confidence) {
|
|
213840
|
+
c.metadata.evidence = { file: c.file_path, line: c.line_number };
|
|
213841
|
+
c.metadata.confidence = confidence;
|
|
213842
|
+
for (const r of c.relations)
|
|
213843
|
+
if (r.confidence === void 0)
|
|
213844
|
+
r.confidence = 1;
|
|
213845
|
+
return c;
|
|
213846
|
+
}
|
|
213874
213847
|
function extractNestJS(rootPath) {
|
|
213875
213848
|
const components = [];
|
|
213876
|
-
const files = discoverFiles(rootPath);
|
|
213849
|
+
const files = discoverFiles(rootPath).filter((f) => isInSourceDir(node_path_1.default.relative(rootPath, f))).sort();
|
|
213877
213850
|
if (files.length === 0)
|
|
213878
213851
|
return components;
|
|
213879
|
-
const
|
|
213852
|
+
const symbolIndex = /* @__PURE__ */ new Map();
|
|
213853
|
+
const moduleByDir = /* @__PURE__ */ new Map();
|
|
213880
213854
|
for (const file of files) {
|
|
213881
|
-
const relative = node_path_1.default.relative(rootPath, file);
|
|
213882
|
-
if (!isInSourceDir(relative))
|
|
213883
|
-
continue;
|
|
213884
213855
|
const facts = parseFile(file, rootPath);
|
|
213885
|
-
if (facts)
|
|
213886
|
-
|
|
213856
|
+
if (!facts)
|
|
213857
|
+
continue;
|
|
213858
|
+
indexFileSymbols(facts, symbolIndex);
|
|
213859
|
+
for (const cls of facts.classes) {
|
|
213860
|
+
if (cls.decorators.includes("Module")) {
|
|
213861
|
+
moduleByDir.set(node_path_1.default.dirname(facts.filePath), cls.name);
|
|
213862
|
+
}
|
|
213863
|
+
}
|
|
213887
213864
|
}
|
|
213888
|
-
const symbolIndex = buildSymbolIndex(allFacts);
|
|
213889
|
-
const referenceIndex = buildReferenceIndex(allFacts, symbolIndex);
|
|
213890
213865
|
const guardNames = /* @__PURE__ */ new Set();
|
|
213891
|
-
for (const
|
|
213866
|
+
for (const file of files) {
|
|
213867
|
+
const facts = parseFile(file, rootPath);
|
|
213868
|
+
if (!facts)
|
|
213869
|
+
continue;
|
|
213892
213870
|
for (const cls of facts.classes) {
|
|
213893
213871
|
const { decorators, name: className, line, constructorParams, methods, properties, implements: implementsList } = cls;
|
|
213894
213872
|
if (decorators.includes("Controller")) {
|
|
213895
213873
|
const controllerName = className || "UnknownController";
|
|
213896
|
-
const controllerPath = extractControllerPath(
|
|
213897
|
-
const moduleName =
|
|
213874
|
+
const controllerPath = extractControllerPath(cls);
|
|
213875
|
+
const moduleName = moduleByDir.get(node_path_1.default.dirname(facts.filePath)) ?? null;
|
|
213898
213876
|
for (const method of methods) {
|
|
213899
|
-
const endpointDecorators = extractEndpointPath(method
|
|
213877
|
+
const endpointDecorators = extractEndpointPath(method, controllerPath);
|
|
213900
213878
|
if (endpointDecorators.length === 0)
|
|
213901
213879
|
continue;
|
|
213902
|
-
const methodGuards = extractGuardsFromMethod(
|
|
213880
|
+
const methodGuards = extractGuardsFromMethod(method);
|
|
213903
213881
|
methodGuards.forEach((g) => guardNames.add(g));
|
|
213904
213882
|
const { params, bodyDto, return_type } = extractEndpointParams(method);
|
|
213905
213883
|
const paramInfos = params.filter((p) => p.source === "param");
|
|
@@ -213919,7 +213897,7 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213919
213897
|
}
|
|
213920
213898
|
for (const { method: httpMethod, path: p } of endpointDecorators) {
|
|
213921
213899
|
const fullPath = p.replace(/\/\//g, "/") || "/";
|
|
213922
|
-
components.push({
|
|
213900
|
+
components.push(stamp({
|
|
213923
213901
|
type: "endpoint",
|
|
213924
213902
|
name: `${controllerName}.${method.name}`,
|
|
213925
213903
|
file_path: facts.filePath,
|
|
@@ -213935,7 +213913,7 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213935
213913
|
return_type: return_type || void 0
|
|
213936
213914
|
},
|
|
213937
213915
|
relations: relationGuards
|
|
213938
|
-
});
|
|
213916
|
+
}, 1));
|
|
213939
213917
|
}
|
|
213940
213918
|
}
|
|
213941
213919
|
const controllerRelations = [];
|
|
@@ -213950,7 +213928,7 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213950
213928
|
});
|
|
213951
213929
|
}
|
|
213952
213930
|
}
|
|
213953
|
-
components.push({
|
|
213931
|
+
components.push(stamp({
|
|
213954
213932
|
type: "controller",
|
|
213955
213933
|
name: controllerName,
|
|
213956
213934
|
file_path: facts.filePath,
|
|
@@ -213960,7 +213938,7 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213960
213938
|
module_name: moduleName || void 0
|
|
213961
213939
|
},
|
|
213962
213940
|
relations: dedupeRelations(controllerRelations)
|
|
213963
|
-
});
|
|
213941
|
+
}, 1));
|
|
213964
213942
|
}
|
|
213965
213943
|
if (decorators.includes("Injectable")) {
|
|
213966
213944
|
const serviceRelations = [];
|
|
@@ -213975,28 +213953,28 @@ var require_nestjs_extractor = __commonJS({
|
|
|
213975
213953
|
});
|
|
213976
213954
|
}
|
|
213977
213955
|
}
|
|
213978
|
-
components.push({
|
|
213956
|
+
components.push(stamp({
|
|
213979
213957
|
type: "service",
|
|
213980
213958
|
name: className || "UnknownService",
|
|
213981
213959
|
file_path: facts.filePath,
|
|
213982
213960
|
line_number: line,
|
|
213983
213961
|
metadata: {},
|
|
213984
213962
|
relations: dedupeRelations(serviceRelations)
|
|
213985
|
-
});
|
|
213963
|
+
}, 1));
|
|
213986
213964
|
}
|
|
213987
213965
|
if (decorators.includes("Module")) {
|
|
213988
|
-
components.push({
|
|
213966
|
+
components.push(stamp({
|
|
213989
213967
|
type: "module",
|
|
213990
213968
|
name: className || "UnknownModule",
|
|
213991
213969
|
file_path: facts.filePath,
|
|
213992
213970
|
line_number: line,
|
|
213993
213971
|
metadata: {},
|
|
213994
|
-
relations: extractModuleRelations(
|
|
213995
|
-
});
|
|
213972
|
+
relations: extractModuleRelations(cls, symbolIndex)
|
|
213973
|
+
}, 1));
|
|
213996
213974
|
}
|
|
213997
213975
|
if (className.endsWith("Guard") && !decorators.includes("Controller")) {
|
|
213998
213976
|
guardNames.add(className);
|
|
213999
|
-
components.push({
|
|
213977
|
+
components.push(stamp({
|
|
214000
213978
|
type: "guard",
|
|
214001
213979
|
name: className,
|
|
214002
213980
|
file_path: facts.filePath,
|
|
@@ -214005,11 +213983,12 @@ var require_nestjs_extractor = __commonJS({
|
|
|
214005
213983
|
implements_can_activate: implementsList.includes("CanActivate") ? true : void 0
|
|
214006
213984
|
},
|
|
214007
213985
|
relations: []
|
|
214008
|
-
});
|
|
213986
|
+
}, 0.7));
|
|
214009
213987
|
}
|
|
214010
213988
|
if (isDTOClass(className, decorators, properties)) {
|
|
214011
213989
|
const dtoFields = extractDTOFields(properties);
|
|
214012
|
-
|
|
213990
|
+
const dtoConfidence = dtoFields.some((f) => f.validations.length > 0) ? 0.9 : 0.6;
|
|
213991
|
+
components.push(stamp({
|
|
214013
213992
|
type: "dto",
|
|
214014
213993
|
name: className,
|
|
214015
213994
|
file_path: facts.filePath,
|
|
@@ -214018,7 +213997,7 @@ var require_nestjs_extractor = __commonJS({
|
|
|
214018
213997
|
dto_fields: dtoFields.length > 0 ? dtoFields : void 0
|
|
214019
213998
|
},
|
|
214020
213999
|
relations: []
|
|
214021
|
-
});
|
|
214000
|
+
}, dtoConfidence));
|
|
214022
214001
|
}
|
|
214023
214002
|
}
|
|
214024
214003
|
}
|
|
@@ -214035,94 +214014,28 @@ var require_nestjs_extractor = __commonJS({
|
|
|
214035
214014
|
}
|
|
214036
214015
|
}
|
|
214037
214016
|
}
|
|
214038
|
-
enrichWithReferenceIndex(components, referenceIndex);
|
|
214039
214017
|
return components;
|
|
214040
214018
|
}
|
|
214041
|
-
function extractModuleRelations(
|
|
214019
|
+
function extractModuleRelations(cls, symbolIndex) {
|
|
214020
|
+
const meta = cls.moduleMeta;
|
|
214021
|
+
if (!meta)
|
|
214022
|
+
return [];
|
|
214042
214023
|
const relations = [];
|
|
214043
|
-
const
|
|
214044
|
-
|
|
214045
|
-
|
|
214046
|
-
|
|
214047
|
-
|
|
214048
|
-
|
|
214049
|
-
for (const name of names) {
|
|
214050
|
-
const entry = symbolIndex.get(name);
|
|
214051
|
-
relations.push({
|
|
214052
|
-
type: "module_imports",
|
|
214053
|
-
target_name: name,
|
|
214054
|
-
target_file: entry?.filePath || ""
|
|
214055
|
-
});
|
|
214056
|
-
}
|
|
214057
|
-
}
|
|
214058
|
-
const providersMatch = content.match(/providers\s*:\s*\[([\s\S]*?)\]/);
|
|
214059
|
-
if (providersMatch) {
|
|
214060
|
-
const names = providersMatch[1].match(/[A-Z]\w*/g) || [];
|
|
214061
|
-
for (const name of names) {
|
|
214062
|
-
const entry = symbolIndex.get(name);
|
|
214063
|
-
relations.push({
|
|
214064
|
-
type: "declares_provider",
|
|
214065
|
-
target_name: name,
|
|
214066
|
-
target_file: entry?.filePath || ""
|
|
214067
|
-
});
|
|
214068
|
-
}
|
|
214069
|
-
}
|
|
214070
|
-
const controllersMatch = content.match(/controllers\s*:\s*\[([\s\S]*?)\]/);
|
|
214071
|
-
if (controllersMatch) {
|
|
214072
|
-
const names = controllersMatch[1].match(/[A-Z]\w*/g) || [];
|
|
214073
|
-
for (const name of names) {
|
|
214074
|
-
const entry = symbolIndex.get(name);
|
|
214075
|
-
relations.push({
|
|
214076
|
-
type: "declares_provider",
|
|
214077
|
-
target_name: name,
|
|
214078
|
-
target_file: entry?.filePath || ""
|
|
214079
|
-
});
|
|
214080
|
-
}
|
|
214081
|
-
}
|
|
214082
|
-
} catch {
|
|
214083
|
-
}
|
|
214084
|
-
return dedupeRelations(relations);
|
|
214085
|
-
}
|
|
214086
|
-
function findModuleName(filePath, allFacts) {
|
|
214087
|
-
const dir = node_path_1.default.dirname(filePath);
|
|
214088
|
-
for (const facts of allFacts) {
|
|
214089
|
-
const factsDir = node_path_1.default.dirname(facts.filePath);
|
|
214090
|
-
if (factsDir === dir) {
|
|
214091
|
-
for (const cls of facts.classes) {
|
|
214092
|
-
if (cls.decorators.includes("Module")) {
|
|
214093
|
-
return cls.name;
|
|
214094
|
-
}
|
|
214095
|
-
}
|
|
214096
|
-
}
|
|
214024
|
+
for (const name of meta.imports) {
|
|
214025
|
+
relations.push({
|
|
214026
|
+
type: "module_imports",
|
|
214027
|
+
target_name: name,
|
|
214028
|
+
target_file: symbolIndex.get(name)?.filePath || ""
|
|
214029
|
+
});
|
|
214097
214030
|
}
|
|
214098
|
-
|
|
214099
|
-
|
|
214100
|
-
|
|
214101
|
-
|
|
214102
|
-
|
|
214103
|
-
|
|
214104
|
-
const refs = referenceIndex.get(comp.name) || [];
|
|
214105
|
-
const usedByModules = /* @__PURE__ */ new Set();
|
|
214106
|
-
for (const ref of refs) {
|
|
214107
|
-
if (!isInSourceDir(ref.filePath))
|
|
214108
|
-
continue;
|
|
214109
|
-
if (ref.filePath === comp.file_path)
|
|
214110
|
-
continue;
|
|
214111
|
-
const refModule = extractModuleFromPath(ref.filePath);
|
|
214112
|
-
if (refModule && refModule !== "root") {
|
|
214113
|
-
usedByModules.add(refModule);
|
|
214114
|
-
}
|
|
214115
|
-
}
|
|
214116
|
-
if (usedByModules.size > 0) {
|
|
214117
|
-
for (const mod of usedByModules) {
|
|
214118
|
-
comp.relations.push({
|
|
214119
|
-
type: "depends_on",
|
|
214120
|
-
target_name: mod,
|
|
214121
|
-
target_file: `src/${mod}/`
|
|
214122
|
-
});
|
|
214123
|
-
}
|
|
214124
|
-
}
|
|
214031
|
+
for (const name of [...meta.providers, ...meta.controllers]) {
|
|
214032
|
+
relations.push({
|
|
214033
|
+
type: "declares_provider",
|
|
214034
|
+
target_name: name,
|
|
214035
|
+
target_file: symbolIndex.get(name)?.filePath || ""
|
|
214036
|
+
});
|
|
214125
214037
|
}
|
|
214038
|
+
return dedupeRelations(relations);
|
|
214126
214039
|
}
|
|
214127
214040
|
function dedupeRelations(relations) {
|
|
214128
214041
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -214538,6 +214451,11 @@ var AGENTS_BLOCK = `
|
|
|
214538
214451
|
Before touching files:
|
|
214539
214452
|
driftless context load --files "<files you're about to edit>"
|
|
214540
214453
|
|
|
214454
|
+
Understand the code graph before changing it (deterministic, no LLM):
|
|
214455
|
+
driftless graph file <path> # entrypoints, upstream, downstream, contracts
|
|
214456
|
+
driftless graph impact --files "a,b" # blast radius \u2014 what this change can break
|
|
214457
|
+
Add --json for machine-readable output, --depth N (1-6) to widen traversal.
|
|
214458
|
+
|
|
214541
214459
|
Before pushing:
|
|
214542
214460
|
driftless scan --diff
|
|
214543
214461
|
|
|
@@ -214642,7 +214560,7 @@ async function installSkillCommand() {
|
|
|
214642
214560
|
// src/commands/init.ts
|
|
214643
214561
|
function getVersion() {
|
|
214644
214562
|
try {
|
|
214645
|
-
return "0.1.
|
|
214563
|
+
return "0.1.45";
|
|
214646
214564
|
} catch {
|
|
214647
214565
|
return "0.0.0";
|
|
214648
214566
|
}
|
|
@@ -215062,8 +214980,23 @@ async function initCommand(args) {
|
|
|
215062
214980
|
step("uploading baseline", "failed (continuing)");
|
|
215063
214981
|
}
|
|
215064
214982
|
if (components.length > 0) {
|
|
214983
|
+
const CHUNK_THRESHOLD = 2e3;
|
|
214984
|
+
const CHUNK_SIZE = 1e3;
|
|
215065
214985
|
try {
|
|
215066
|
-
|
|
214986
|
+
if (components.length <= CHUNK_THRESHOLD) {
|
|
214987
|
+
await api.post(`/workspaces/${workspaceSlug}/repos/${repo.id}/components`, {
|
|
214988
|
+
schema_version: 2,
|
|
214989
|
+
components
|
|
214990
|
+
});
|
|
214991
|
+
} else {
|
|
214992
|
+
for (let i = 0; i < components.length; i += CHUNK_SIZE) {
|
|
214993
|
+
await api.post(`/workspaces/${workspaceSlug}/repos/${repo.id}/components`, {
|
|
214994
|
+
schema_version: 2,
|
|
214995
|
+
replace: i === 0,
|
|
214996
|
+
components: components.slice(i, i + CHUNK_SIZE)
|
|
214997
|
+
});
|
|
214998
|
+
}
|
|
214999
|
+
}
|
|
215067
215000
|
step("uploading components", `${components.length} components \xB7 ${relationCount} relations \u2713`);
|
|
215068
215001
|
} catch {
|
|
215069
215002
|
step("uploading components", "failed (continuing)");
|
|
@@ -216634,8 +216567,92 @@ function pad2(s, n) {
|
|
|
216634
216567
|
return s + " ".repeat(n - s.length);
|
|
216635
216568
|
}
|
|
216636
216569
|
|
|
216570
|
+
// src/commands/graph.ts
|
|
216571
|
+
init_api_client();
|
|
216572
|
+
function emitJSON4(data) {
|
|
216573
|
+
console.log(JSON.stringify(data, null, 2));
|
|
216574
|
+
}
|
|
216575
|
+
async function graphCommand(args) {
|
|
216576
|
+
if (!isGitRepo()) {
|
|
216577
|
+
console.error("Error: not a git repository.");
|
|
216578
|
+
process.exit(1);
|
|
216579
|
+
}
|
|
216580
|
+
const sub = args[0];
|
|
216581
|
+
const isJSON = args.includes("--json");
|
|
216582
|
+
const depthIdx = args.indexOf("--depth");
|
|
216583
|
+
const depth = depthIdx !== -1 && args[depthIdx + 1] ? `&depth=${encodeURIComponent(args[depthIdx + 1])}` : "";
|
|
216584
|
+
if (sub !== "file" && sub !== "impact") {
|
|
216585
|
+
console.error('Usage:\n driftless graph file <path> [--depth N] [--json]\n driftless graph impact --files "a,b" [--depth N] [--json]');
|
|
216586
|
+
process.exit(1);
|
|
216587
|
+
}
|
|
216588
|
+
const resolution = await resolveRepo();
|
|
216589
|
+
if (!resolution.ok) {
|
|
216590
|
+
if (resolution.reason === "not_linked") {
|
|
216591
|
+
console.error(notLinkedMessage(resolution.remote, resolution.workspaceSlug));
|
|
216592
|
+
} else {
|
|
216593
|
+
console.error("Error: could not resolve workspace. Run `driftless doctor`.");
|
|
216594
|
+
}
|
|
216595
|
+
process.exit(1);
|
|
216596
|
+
}
|
|
216597
|
+
const { workspaceSlug, repoId } = resolution;
|
|
216598
|
+
const base = `/workspaces/${workspaceSlug}/repos/${repoId}/graph`;
|
|
216599
|
+
try {
|
|
216600
|
+
if (sub === "file") {
|
|
216601
|
+
const path = args[1];
|
|
216602
|
+
if (!path || path.startsWith("--")) {
|
|
216603
|
+
console.error("Usage: driftless graph file <path>");
|
|
216604
|
+
process.exit(1);
|
|
216605
|
+
}
|
|
216606
|
+
const g2 = await api.get(`${base}/file?path=${encodeURIComponent(path)}${depth}`);
|
|
216607
|
+
if (isJSON) {
|
|
216608
|
+
emitJSON4(g2);
|
|
216609
|
+
process.exit(0);
|
|
216610
|
+
}
|
|
216611
|
+
if (!g2.found) {
|
|
216612
|
+
console.log(`No scanned components for ${path}. Run \`driftless init\` (or --src for monorepos).`);
|
|
216613
|
+
process.exit(0);
|
|
216614
|
+
}
|
|
216615
|
+
console.log(`\u258C ${path}
|
|
216616
|
+
`);
|
|
216617
|
+
if (g2.entrypoints.length) {
|
|
216618
|
+
console.log("entrypoints:");
|
|
216619
|
+
for (const e of g2.entrypoints) console.log(` ${e.method ?? "?"} ${e.path ?? "?"} \u2192 ${e.handler}`);
|
|
216620
|
+
console.log("");
|
|
216621
|
+
}
|
|
216622
|
+
console.log(`upstream (${g2.upstream.length}): ${g2.upstream.slice(0, 12).join(", ")}${g2.upstream.length > 12 ? " \u2026" : ""}`);
|
|
216623
|
+
console.log(`downstream (${g2.downstream.length}): ${g2.downstream.slice(0, 12).join(", ")}${g2.downstream.length > 12 ? " \u2026" : ""}`);
|
|
216624
|
+
if (g2.contracts.length) console.log(`contracts (${g2.contracts.length}): ${g2.contracts.join(", ")}`);
|
|
216625
|
+
process.exit(0);
|
|
216626
|
+
}
|
|
216627
|
+
const filesIdx = args.indexOf("--files");
|
|
216628
|
+
const filesCsv = filesIdx !== -1 ? args[filesIdx + 1] : "";
|
|
216629
|
+
if (!filesCsv) {
|
|
216630
|
+
console.error('Usage: driftless graph impact --files "a,b"');
|
|
216631
|
+
process.exit(1);
|
|
216632
|
+
}
|
|
216633
|
+
const g = await api.get(`${base}/impact?files=${encodeURIComponent(filesCsv)}${depth}`);
|
|
216634
|
+
if (isJSON) {
|
|
216635
|
+
emitJSON4(g);
|
|
216636
|
+
process.exit(0);
|
|
216637
|
+
}
|
|
216638
|
+
if (!g.found) {
|
|
216639
|
+
console.log("No scanned components for those files. Run `driftless init`.");
|
|
216640
|
+
process.exit(0);
|
|
216641
|
+
}
|
|
216642
|
+
console.log(`Impact of ${g.files.length} file(s) \u2014 ${g.impacted.length} component(s) across ${g.impacted_files.length} file(s):
|
|
216643
|
+
`);
|
|
216644
|
+
for (const f of g.impacted_files.slice(0, 30)) console.log(` ${f}`);
|
|
216645
|
+
if (g.impacted_files.length > 30) console.log(` \u2026 and ${g.impacted_files.length - 30} more`);
|
|
216646
|
+
process.exit(0);
|
|
216647
|
+
} catch (e) {
|
|
216648
|
+
console.error(`graph failed: ${formatError(e)}`);
|
|
216649
|
+
console.error("Run `driftless doctor` to diagnose.");
|
|
216650
|
+
process.exit(1);
|
|
216651
|
+
}
|
|
216652
|
+
}
|
|
216653
|
+
|
|
216637
216654
|
// src/index.ts
|
|
216638
|
-
var VERSION = "0.1.
|
|
216655
|
+
var VERSION = "0.1.45";
|
|
216639
216656
|
var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
|
|
216640
216657
|
|
|
216641
216658
|
Install: npm install -g @driftless-sh/cli
|
|
@@ -216664,6 +216681,7 @@ Commands:
|
|
|
216664
216681
|
context Live repo context (topics, search, anchors)
|
|
216665
216682
|
install-skill Install AGENTS.md into current repo
|
|
216666
216683
|
doctor Check environment health (auth, API, git, workspace, repo, baseline)
|
|
216684
|
+
graph Deterministic code graph: graph file <path> \xB7 graph impact --files "a,b"
|
|
216667
216685
|
|
|
216668
216686
|
Context subcommands:
|
|
216669
216687
|
list [flags] List context topics
|
|
@@ -216835,6 +216853,22 @@ Exits 1 if any check fails.
|
|
|
216835
216853
|
|
|
216836
216854
|
Example:
|
|
216837
216855
|
driftless doctor
|
|
216856
|
+
`,
|
|
216857
|
+
graph: `driftless graph <subcommand>
|
|
216858
|
+
|
|
216859
|
+
Deterministic code graph from the scanned components (no LLM).
|
|
216860
|
+
|
|
216861
|
+
Subcommands:
|
|
216862
|
+
file <path> Entrypoints, upstream, downstream, contracts for a file
|
|
216863
|
+
impact --files "a,b" Blast radius: components/files a change can break
|
|
216864
|
+
|
|
216865
|
+
Flags:
|
|
216866
|
+
--depth N Traversal depth (1-6, default 3)
|
|
216867
|
+
--json Machine-readable output (agents/CI)
|
|
216868
|
+
|
|
216869
|
+
Examples:
|
|
216870
|
+
driftless graph file src/roulette/roulette.service.ts
|
|
216871
|
+
driftless graph impact --files "src/auth/auth.guard.ts" --json
|
|
216838
216872
|
`
|
|
216839
216873
|
};
|
|
216840
216874
|
if (help[cmd]) {
|
|
@@ -216896,6 +216930,13 @@ async function main() {
|
|
|
216896
216930
|
await doctorCommand(args.slice(1));
|
|
216897
216931
|
}
|
|
216898
216932
|
break;
|
|
216933
|
+
case "graph":
|
|
216934
|
+
if (args[1] === "--help") {
|
|
216935
|
+
showCommandHelp("graph");
|
|
216936
|
+
} else {
|
|
216937
|
+
await graphCommand(args.slice(1));
|
|
216938
|
+
}
|
|
216939
|
+
break;
|
|
216899
216940
|
case "help":
|
|
216900
216941
|
if (args[1]) {
|
|
216901
216942
|
showCommandHelp(args[1]);
|