@neat.is/core 0.4.11 → 0.4.13
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/{chunk-OJHI33LD.js → chunk-5W7H35JJ.js} +1111 -343
- package/dist/chunk-5W7H35JJ.js.map +1 -0
- package/dist/{chunk-W4RNPPB5.js → chunk-TWNJX26R.js} +2 -2
- package/dist/cli.cjs +1471 -682
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +197 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1145 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1133 -368
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1267 -502
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-OJHI33LD.js.map +0 -1
- /package/dist/{chunk-W4RNPPB5.js.map → chunk-TWNJX26R.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -56,9 +56,9 @@ function mountBearerAuth(app, opts) {
|
|
|
56
56
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
57
57
|
const publicRead = opts.publicRead === true;
|
|
58
58
|
app.addHook("preHandler", (req, reply, done) => {
|
|
59
|
-
const
|
|
59
|
+
const path42 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
60
60
|
for (const suffix of suffixes) {
|
|
61
|
-
if (
|
|
61
|
+
if (path42 === suffix || path42.endsWith(suffix)) {
|
|
62
62
|
done();
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
@@ -184,8 +184,8 @@ function reshapeGrpcRequest(req) {
|
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
186
|
function resolveProtoRoot() {
|
|
187
|
-
const here =
|
|
188
|
-
return
|
|
187
|
+
const here = import_node_path38.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
188
|
+
return import_node_path38.default.resolve(here, "..", "proto");
|
|
189
189
|
}
|
|
190
190
|
function loadTraceService() {
|
|
191
191
|
const protoRoot = resolveProtoRoot();
|
|
@@ -253,13 +253,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
253
253
|
})
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
|
-
var import_node_url,
|
|
256
|
+
var import_node_url, import_node_path38, import_node_crypto2, grpc, protoLoader;
|
|
257
257
|
var init_otel_grpc = __esm({
|
|
258
258
|
"src/otel-grpc.ts"() {
|
|
259
259
|
"use strict";
|
|
260
260
|
init_cjs_shims();
|
|
261
261
|
import_node_url = require("url");
|
|
262
|
-
|
|
262
|
+
import_node_path38 = __toESM(require("path"), 1);
|
|
263
263
|
import_node_crypto2 = require("crypto");
|
|
264
264
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
265
265
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -369,10 +369,10 @@ function parseOtlpRequest(body) {
|
|
|
369
369
|
return out;
|
|
370
370
|
}
|
|
371
371
|
function loadProtoRoot() {
|
|
372
|
-
const here =
|
|
373
|
-
const protoRoot =
|
|
372
|
+
const here = import_node_path39.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
373
|
+
const protoRoot = import_node_path39.default.resolve(here, "..", "proto");
|
|
374
374
|
const root = new import_protobufjs.default.Root();
|
|
375
|
-
root.resolvePath = (_origin, target) =>
|
|
375
|
+
root.resolvePath = (_origin, target) => import_node_path39.default.resolve(protoRoot, target);
|
|
376
376
|
root.loadSync(
|
|
377
377
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
378
378
|
{ keepCase: true }
|
|
@@ -565,12 +565,12 @@ async function buildOtelReceiver(opts) {
|
|
|
565
565
|
};
|
|
566
566
|
return decorated;
|
|
567
567
|
}
|
|
568
|
-
var
|
|
568
|
+
var import_node_path39, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
569
569
|
var init_otel = __esm({
|
|
570
570
|
"src/otel.ts"() {
|
|
571
571
|
"use strict";
|
|
572
572
|
init_cjs_shims();
|
|
573
|
-
|
|
573
|
+
import_node_path39 = __toESM(require("path"), 1);
|
|
574
574
|
import_node_url2 = require("url");
|
|
575
575
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
576
576
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -586,7 +586,7 @@ var init_otel = __esm({
|
|
|
586
586
|
|
|
587
587
|
// src/server.ts
|
|
588
588
|
init_cjs_shims();
|
|
589
|
-
var
|
|
589
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
590
590
|
|
|
591
591
|
// src/graph.ts
|
|
592
592
|
init_cjs_shims();
|
|
@@ -610,7 +610,322 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
610
610
|
init_cjs_shims();
|
|
611
611
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
612
612
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
613
|
-
var
|
|
613
|
+
var import_types24 = require("@neat.is/types");
|
|
614
|
+
|
|
615
|
+
// src/extend/index.ts
|
|
616
|
+
init_cjs_shims();
|
|
617
|
+
var import_node_fs2 = require("fs");
|
|
618
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
619
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
620
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
621
|
+
|
|
622
|
+
// src/installers/package-manager.ts
|
|
623
|
+
init_cjs_shims();
|
|
624
|
+
var import_node_fs = require("fs");
|
|
625
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
626
|
+
var import_node_child_process = require("child_process");
|
|
627
|
+
var LOCKFILE_PRIORITY = [
|
|
628
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
629
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
630
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
631
|
+
{
|
|
632
|
+
lockfile: "package-lock.json",
|
|
633
|
+
pm: "npm",
|
|
634
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
635
|
+
}
|
|
636
|
+
];
|
|
637
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
638
|
+
async function exists(p) {
|
|
639
|
+
try {
|
|
640
|
+
await import_node_fs.promises.access(p);
|
|
641
|
+
return true;
|
|
642
|
+
} catch {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async function detectPackageManager(serviceDir) {
|
|
647
|
+
let dir = import_node_path.default.resolve(serviceDir);
|
|
648
|
+
const stops = /* @__PURE__ */ new Set();
|
|
649
|
+
for (let i = 0; i < 64; i++) {
|
|
650
|
+
if (stops.has(dir)) break;
|
|
651
|
+
stops.add(dir);
|
|
652
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
653
|
+
const lockPath = import_node_path.default.join(dir, candidate.lockfile);
|
|
654
|
+
if (await exists(lockPath)) {
|
|
655
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const parent = import_node_path.default.dirname(dir);
|
|
659
|
+
if (parent === dir) break;
|
|
660
|
+
dir = parent;
|
|
661
|
+
}
|
|
662
|
+
return { pm: "npm", cwd: import_node_path.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
663
|
+
}
|
|
664
|
+
async function runPackageManagerInstall(cmd) {
|
|
665
|
+
return new Promise((resolve) => {
|
|
666
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
667
|
+
cwd: cmd.cwd,
|
|
668
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
669
|
+
env: process.env,
|
|
670
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
671
|
+
// exits before install finishes. Cross-platform-safe.
|
|
672
|
+
shell: false,
|
|
673
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
674
|
+
});
|
|
675
|
+
let stderr = "";
|
|
676
|
+
child.stderr?.on("data", (chunk) => {
|
|
677
|
+
stderr += chunk.toString("utf8");
|
|
678
|
+
});
|
|
679
|
+
child.on("error", (err) => {
|
|
680
|
+
resolve({
|
|
681
|
+
pm: cmd.pm,
|
|
682
|
+
cwd: cmd.cwd,
|
|
683
|
+
args: cmd.args,
|
|
684
|
+
exitCode: 127,
|
|
685
|
+
stderr: stderr + `
|
|
686
|
+
${err.message}`
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
child.on("close", (code) => {
|
|
690
|
+
resolve({
|
|
691
|
+
pm: cmd.pm,
|
|
692
|
+
cwd: cmd.cwd,
|
|
693
|
+
args: cmd.args,
|
|
694
|
+
exitCode: code ?? 1,
|
|
695
|
+
stderr: stderr.trim()
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/extend/index.ts
|
|
702
|
+
async function fileExists(p) {
|
|
703
|
+
try {
|
|
704
|
+
await import_node_fs2.promises.access(p);
|
|
705
|
+
return true;
|
|
706
|
+
} catch {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function readPackageJson(scanPath) {
|
|
711
|
+
const pkgPath = import_node_path2.default.join(scanPath, "package.json");
|
|
712
|
+
const raw = await import_node_fs2.promises.readFile(pkgPath, "utf8");
|
|
713
|
+
return JSON.parse(raw);
|
|
714
|
+
}
|
|
715
|
+
async function findHookFiles(scanPath) {
|
|
716
|
+
const entries = await import_node_fs2.promises.readdir(scanPath);
|
|
717
|
+
return entries.filter(
|
|
718
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
719
|
+
).sort();
|
|
720
|
+
}
|
|
721
|
+
function extendLogPath() {
|
|
722
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path2.default.join(import_node_os.default.homedir(), ".neat", "extend-log.ndjson");
|
|
723
|
+
}
|
|
724
|
+
async function appendExtendLog(entry) {
|
|
725
|
+
const logPath = extendLogPath();
|
|
726
|
+
await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(logPath), { recursive: true });
|
|
727
|
+
await import_node_fs2.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
728
|
+
}
|
|
729
|
+
function splicedContent(fileContent, snippet2) {
|
|
730
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
731
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
732
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
733
|
+
}
|
|
734
|
+
const lines = fileContent.split("\n");
|
|
735
|
+
let lastPushIdx = -1;
|
|
736
|
+
for (let i = 0; i < lines.length; i++) {
|
|
737
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
738
|
+
}
|
|
739
|
+
if (lastPushIdx >= 0) {
|
|
740
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
741
|
+
return lines.join("\n");
|
|
742
|
+
}
|
|
743
|
+
for (let i = 0; i < lines.length; i++) {
|
|
744
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
745
|
+
lines.splice(i, 0, snippet2);
|
|
746
|
+
return lines.join("\n");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
async function listUninstrumented(ctx) {
|
|
752
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
753
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
754
|
+
const results = [];
|
|
755
|
+
for (const [library, installedVersion] of Object.entries(allDeps)) {
|
|
756
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
757
|
+
if (!entry) continue;
|
|
758
|
+
if (entry.coverage === "bundled" || entry.coverage === "http-only") continue;
|
|
759
|
+
results.push({
|
|
760
|
+
library,
|
|
761
|
+
coverage: entry.coverage,
|
|
762
|
+
installedVersion,
|
|
763
|
+
instrumentation_package: entry.instrumentation_package,
|
|
764
|
+
package_version: entry.package_version,
|
|
765
|
+
registration: entry.registration,
|
|
766
|
+
notes: entry.notes
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
return results;
|
|
770
|
+
}
|
|
771
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
772
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
773
|
+
if (!entry) return null;
|
|
774
|
+
return {
|
|
775
|
+
library: entry.library,
|
|
776
|
+
coverage: entry.coverage,
|
|
777
|
+
instrumentation_package: entry.instrumentation_package,
|
|
778
|
+
package_version: entry.package_version,
|
|
779
|
+
registration: entry.registration,
|
|
780
|
+
notes: entry.notes
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
async function describeProjectInstrumentation(ctx) {
|
|
784
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
785
|
+
const envNeat = await fileExists(import_node_path2.default.join(ctx.scanPath, ".env.neat"));
|
|
786
|
+
const registryInstrPackages = new Set(
|
|
787
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
788
|
+
);
|
|
789
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
790
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
791
|
+
const installedDeps = {};
|
|
792
|
+
for (const [key, version] of Object.entries(allDeps)) {
|
|
793
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
794
|
+
installedDeps[key] = version;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return { hookFiles, envNeat, installedDeps };
|
|
798
|
+
}
|
|
799
|
+
async function applyExtension(ctx, args, options) {
|
|
800
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
801
|
+
if (hookFiles.length === 0) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
for (const file of hookFiles) {
|
|
807
|
+
const content = await import_node_fs2.promises.readFile(import_node_path2.default.join(ctx.scanPath, file), "utf8");
|
|
808
|
+
if (content.includes(args.registration_snippet)) {
|
|
809
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const primaryFile = hookFiles[0];
|
|
813
|
+
const primaryPath = import_node_path2.default.join(ctx.scanPath, primaryFile);
|
|
814
|
+
const filesTouched = [];
|
|
815
|
+
const depsAdded = [];
|
|
816
|
+
const pkgPath = import_node_path2.default.join(ctx.scanPath, "package.json");
|
|
817
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
818
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
819
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
820
|
+
await import_node_fs2.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
821
|
+
filesTouched.push("package.json");
|
|
822
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
823
|
+
}
|
|
824
|
+
const hookContent = await import_node_fs2.promises.readFile(primaryPath, "utf8");
|
|
825
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
826
|
+
if (!patched) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
await import_node_fs2.promises.writeFile(primaryPath, patched, "utf8");
|
|
832
|
+
filesTouched.push(primaryFile);
|
|
833
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
834
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
835
|
+
const install = await installer(cmd);
|
|
836
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
837
|
+
await appendExtendLog({
|
|
838
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
839
|
+
project: ctx.project,
|
|
840
|
+
library: args.library,
|
|
841
|
+
instrumentation_package: args.instrumentation_package,
|
|
842
|
+
version: args.version,
|
|
843
|
+
registration_snippet: args.registration_snippet,
|
|
844
|
+
filesTouched,
|
|
845
|
+
depsAdded,
|
|
846
|
+
installOutput
|
|
847
|
+
});
|
|
848
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
849
|
+
}
|
|
850
|
+
async function dryRunExtension(ctx, args) {
|
|
851
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
852
|
+
if (hookFiles.length === 0) {
|
|
853
|
+
return {
|
|
854
|
+
library: args.library,
|
|
855
|
+
filesTouched: [],
|
|
856
|
+
depsToAdd: [],
|
|
857
|
+
packageJsonPatch: {},
|
|
858
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
for (const file of hookFiles) {
|
|
862
|
+
const content = await import_node_fs2.promises.readFile(import_node_path2.default.join(ctx.scanPath, file), "utf8");
|
|
863
|
+
if (content.includes(args.registration_snippet)) {
|
|
864
|
+
return {
|
|
865
|
+
library: args.library,
|
|
866
|
+
filesTouched: [],
|
|
867
|
+
depsToAdd: [],
|
|
868
|
+
packageJsonPatch: {},
|
|
869
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const primaryFile = hookFiles[0];
|
|
874
|
+
const filesTouched = [];
|
|
875
|
+
const depsToAdd = [];
|
|
876
|
+
let packageJsonPatch = {};
|
|
877
|
+
let templatePatch = "";
|
|
878
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
879
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
880
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
881
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
882
|
+
filesTouched.push("package.json");
|
|
883
|
+
}
|
|
884
|
+
const hookContent = await import_node_fs2.promises.readFile(import_node_path2.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
885
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
886
|
+
if (patched) {
|
|
887
|
+
filesTouched.push(primaryFile);
|
|
888
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
889
|
+
} else {
|
|
890
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
891
|
+
}
|
|
892
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
893
|
+
}
|
|
894
|
+
async function rollbackExtension(ctx, args) {
|
|
895
|
+
const logPath = extendLogPath();
|
|
896
|
+
if (!await fileExists(logPath)) {
|
|
897
|
+
return { undone: false, message: "no apply found for library" };
|
|
898
|
+
}
|
|
899
|
+
const raw = await import_node_fs2.promises.readFile(logPath, "utf8");
|
|
900
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
901
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
902
|
+
if (!match) {
|
|
903
|
+
return { undone: false, message: "no apply found for library" };
|
|
904
|
+
}
|
|
905
|
+
const pkgPath = import_node_path2.default.join(ctx.scanPath, "package.json");
|
|
906
|
+
if (await fileExists(pkgPath)) {
|
|
907
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
908
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
909
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
910
|
+
pkg.dependencies = rest;
|
|
911
|
+
await import_node_fs2.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
915
|
+
for (const file of hookFiles) {
|
|
916
|
+
const filePath = import_node_path2.default.join(ctx.scanPath, file);
|
|
917
|
+
const content = await import_node_fs2.promises.readFile(filePath, "utf8");
|
|
918
|
+
if (content.includes(match.registration_snippet)) {
|
|
919
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
920
|
+
await import_node_fs2.promises.writeFile(filePath, filtered, "utf8");
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
undone: true,
|
|
926
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
927
|
+
};
|
|
928
|
+
}
|
|
614
929
|
|
|
615
930
|
// src/divergences.ts
|
|
616
931
|
init_cjs_shims();
|
|
@@ -618,9 +933,9 @@ var import_types2 = require("@neat.is/types");
|
|
|
618
933
|
|
|
619
934
|
// src/compat.ts
|
|
620
935
|
init_cjs_shims();
|
|
621
|
-
var
|
|
622
|
-
var
|
|
623
|
-
var
|
|
936
|
+
var import_node_fs3 = require("fs");
|
|
937
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
938
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
624
939
|
var import_semver = __toESM(require("semver"), 1);
|
|
625
940
|
|
|
626
941
|
// compat.json
|
|
@@ -749,8 +1064,8 @@ var compat_default = {
|
|
|
749
1064
|
var bundledMatrix = compat_default;
|
|
750
1065
|
var mergedMatrix = null;
|
|
751
1066
|
var remoteLoadAttempted = false;
|
|
752
|
-
var REMOTE_CACHE_DIR =
|
|
753
|
-
var REMOTE_CACHE_PATH =
|
|
1067
|
+
var REMOTE_CACHE_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".neat");
|
|
1068
|
+
var REMOTE_CACHE_PATH = import_node_path3.default.join(REMOTE_CACHE_DIR, "compat-cache.json");
|
|
754
1069
|
var REMOTE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
755
1070
|
function engineMeetsThreshold(engineVersion, threshold) {
|
|
756
1071
|
const e = parseInt(engineVersion, 10);
|
|
@@ -861,7 +1176,7 @@ function mergeMatrices(a, b) {
|
|
|
861
1176
|
}
|
|
862
1177
|
async function readRemoteCache(url) {
|
|
863
1178
|
try {
|
|
864
|
-
const raw = await
|
|
1179
|
+
const raw = await import_node_fs3.promises.readFile(REMOTE_CACHE_PATH, "utf8");
|
|
865
1180
|
const parsed = JSON.parse(raw);
|
|
866
1181
|
if (parsed.url !== url) return null;
|
|
867
1182
|
const age = Date.now() - new Date(parsed.fetchedAt).getTime();
|
|
@@ -878,8 +1193,8 @@ async function writeRemoteCache(url, matrix) {
|
|
|
878
1193
|
matrix
|
|
879
1194
|
};
|
|
880
1195
|
try {
|
|
881
|
-
await
|
|
882
|
-
await
|
|
1196
|
+
await import_node_fs3.promises.mkdir(REMOTE_CACHE_DIR, { recursive: true });
|
|
1197
|
+
await import_node_fs3.promises.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), "utf8");
|
|
883
1198
|
} catch (err) {
|
|
884
1199
|
console.warn(`[neat] failed to cache compat matrix: ${err.message}`);
|
|
885
1200
|
}
|
|
@@ -1038,19 +1353,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1038
1353
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1039
1354
|
let best = { path: [start], edges: [] };
|
|
1040
1355
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1041
|
-
function step(node,
|
|
1042
|
-
if (
|
|
1043
|
-
best = { path: [...
|
|
1356
|
+
function step(node, path42, edges) {
|
|
1357
|
+
if (path42.length > best.path.length) {
|
|
1358
|
+
best = { path: [...path42], edges: [...edges] };
|
|
1044
1359
|
}
|
|
1045
|
-
if (
|
|
1360
|
+
if (path42.length - 1 >= maxDepth) return;
|
|
1046
1361
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1047
1362
|
for (const [srcId, edge] of incoming) {
|
|
1048
1363
|
if (visited.has(srcId)) continue;
|
|
1049
1364
|
visited.add(srcId);
|
|
1050
|
-
|
|
1365
|
+
path42.push(srcId);
|
|
1051
1366
|
edges.push(edge);
|
|
1052
|
-
step(srcId,
|
|
1053
|
-
|
|
1367
|
+
step(srcId, path42, edges);
|
|
1368
|
+
path42.pop();
|
|
1054
1369
|
edges.pop();
|
|
1055
1370
|
visited.delete(srcId);
|
|
1056
1371
|
}
|
|
@@ -1476,8 +1791,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
1476
1791
|
|
|
1477
1792
|
// src/policy.ts
|
|
1478
1793
|
init_cjs_shims();
|
|
1479
|
-
var
|
|
1480
|
-
var
|
|
1794
|
+
var import_node_fs4 = require("fs");
|
|
1795
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
1481
1796
|
var import_types3 = require("@neat.is/types");
|
|
1482
1797
|
|
|
1483
1798
|
// src/events.ts
|
|
@@ -1763,7 +2078,7 @@ function evaluateAllPolicies(graph, policies, ctx) {
|
|
|
1763
2078
|
async function loadPolicyFile(policyPath) {
|
|
1764
2079
|
let raw;
|
|
1765
2080
|
try {
|
|
1766
|
-
raw = await
|
|
2081
|
+
raw = await import_node_fs4.promises.readFile(policyPath, "utf8");
|
|
1767
2082
|
} catch (err) {
|
|
1768
2083
|
if (err.code === "ENOENT") return [];
|
|
1769
2084
|
throw err;
|
|
@@ -1784,8 +2099,8 @@ var PolicyViolationsLog = class {
|
|
|
1784
2099
|
if (!this.seen) await this.hydrate();
|
|
1785
2100
|
if (this.seen.has(v.id)) return false;
|
|
1786
2101
|
this.seen.add(v.id);
|
|
1787
|
-
await
|
|
1788
|
-
await
|
|
2102
|
+
await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(this.path), { recursive: true });
|
|
2103
|
+
await import_node_fs4.promises.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
|
|
1789
2104
|
emitNeatEvent({
|
|
1790
2105
|
type: "policy-violation",
|
|
1791
2106
|
project: this.project,
|
|
@@ -1795,7 +2110,7 @@ var PolicyViolationsLog = class {
|
|
|
1795
2110
|
}
|
|
1796
2111
|
async readAll() {
|
|
1797
2112
|
try {
|
|
1798
|
-
const raw = await
|
|
2113
|
+
const raw = await import_node_fs4.promises.readFile(this.path, "utf8");
|
|
1799
2114
|
return raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
1800
2115
|
} catch (err) {
|
|
1801
2116
|
if (err.code === "ENOENT") return [];
|
|
@@ -1817,8 +2132,8 @@ init_cjs_shims();
|
|
|
1817
2132
|
|
|
1818
2133
|
// src/ingest.ts
|
|
1819
2134
|
init_cjs_shims();
|
|
1820
|
-
var
|
|
1821
|
-
var
|
|
2135
|
+
var import_node_fs5 = require("fs");
|
|
2136
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1822
2137
|
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
1823
2138
|
var import_types4 = require("@neat.is/types");
|
|
1824
2139
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
@@ -1918,7 +2233,7 @@ function languageForExt(relPath) {
|
|
|
1918
2233
|
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1919
2234
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1920
2235
|
if (scanPath && scanPath.length > 0) {
|
|
1921
|
-
const absRoot = toPosix(
|
|
2236
|
+
const absRoot = toPosix(import_node_path5.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
1922
2237
|
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
1923
2238
|
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
1924
2239
|
}
|
|
@@ -1946,10 +2261,10 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
1946
2261
|
entry = null;
|
|
1947
2262
|
const mapPath = `${absFilepath}.map`;
|
|
1948
2263
|
try {
|
|
1949
|
-
if ((0,
|
|
1950
|
-
const raw = JSON.parse((0,
|
|
2264
|
+
if ((0, import_node_fs5.existsSync)(mapPath)) {
|
|
2265
|
+
const raw = JSON.parse((0, import_node_fs5.readFileSync)(mapPath, "utf8"));
|
|
1951
2266
|
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
1952
|
-
entry = { consumer, dir:
|
|
2267
|
+
entry = { consumer, dir: import_node_path5.default.dirname(mapPath) };
|
|
1953
2268
|
}
|
|
1954
2269
|
} catch {
|
|
1955
2270
|
entry = null;
|
|
@@ -1964,7 +2279,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
1964
2279
|
});
|
|
1965
2280
|
if (!pos || !pos.source) return null;
|
|
1966
2281
|
const root = entry.consumer.sourceRoot ?? "";
|
|
1967
|
-
const resolved =
|
|
2282
|
+
const resolved = import_node_path5.default.resolve(entry.dir, root, pos.source);
|
|
1968
2283
|
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
1969
2284
|
} catch {
|
|
1970
2285
|
return null;
|
|
@@ -2146,7 +2461,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
2146
2461
|
graph.addNode(id, node);
|
|
2147
2462
|
return id;
|
|
2148
2463
|
}
|
|
2149
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
2464
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
2150
2465
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
2151
2466
|
const id = makeObservedEdgeId(type, source, target);
|
|
2152
2467
|
if (graph.hasEdge(id)) {
|
|
@@ -2183,7 +2498,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
2183
2498
|
confidence: (0, import_types4.confidenceForObservedSignal)(signal),
|
|
2184
2499
|
lastObserved: ts,
|
|
2185
2500
|
callCount: 1,
|
|
2186
|
-
signal
|
|
2501
|
+
signal,
|
|
2502
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2503
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
2504
|
+
...evidence ? { evidence } : {}
|
|
2187
2505
|
};
|
|
2188
2506
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2189
2507
|
return { edge, created: true };
|
|
@@ -2228,8 +2546,8 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
2228
2546
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2229
2547
|
}
|
|
2230
2548
|
async function appendErrorEvent(ctx, ev) {
|
|
2231
|
-
await
|
|
2232
|
-
await
|
|
2549
|
+
await import_node_fs5.promises.mkdir(import_node_path5.default.dirname(ctx.errorsPath), { recursive: true });
|
|
2550
|
+
await import_node_fs5.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
2233
2551
|
}
|
|
2234
2552
|
function sanitizeAttributes(attrs) {
|
|
2235
2553
|
const out = {};
|
|
@@ -2252,6 +2570,7 @@ async function handleSpan(ctx, span) {
|
|
|
2252
2570
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2253
2571
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2254
2572
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2573
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2255
2574
|
let affectedNode = sourceId;
|
|
2256
2575
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2257
2576
|
if (span.dbSystem) {
|
|
@@ -2265,7 +2584,8 @@ async function handleSpan(ctx, span) {
|
|
|
2265
2584
|
observedSource(),
|
|
2266
2585
|
targetId,
|
|
2267
2586
|
ts,
|
|
2268
|
-
isError
|
|
2587
|
+
isError,
|
|
2588
|
+
callSiteEvidence
|
|
2269
2589
|
);
|
|
2270
2590
|
if (result) affectedNode = targetId;
|
|
2271
2591
|
}
|
|
@@ -2281,7 +2601,8 @@ async function handleSpan(ctx, span) {
|
|
|
2281
2601
|
observedSource(),
|
|
2282
2602
|
targetId,
|
|
2283
2603
|
ts,
|
|
2284
|
-
isError
|
|
2604
|
+
isError,
|
|
2605
|
+
callSiteEvidence
|
|
2285
2606
|
);
|
|
2286
2607
|
affectedNode = targetId;
|
|
2287
2608
|
resolvedViaAddress = true;
|
|
@@ -2293,7 +2614,8 @@ async function handleSpan(ctx, span) {
|
|
|
2293
2614
|
observedSource(),
|
|
2294
2615
|
frontierNodeId,
|
|
2295
2616
|
ts,
|
|
2296
|
-
isError
|
|
2617
|
+
isError,
|
|
2618
|
+
callSiteEvidence
|
|
2297
2619
|
);
|
|
2298
2620
|
affectedNode = frontierNodeId;
|
|
2299
2621
|
resolvedViaAddress = true;
|
|
@@ -2451,13 +2773,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
2451
2773
|
return { count: events.length, events };
|
|
2452
2774
|
}
|
|
2453
2775
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
2454
|
-
await
|
|
2776
|
+
await import_node_fs5.promises.mkdir(import_node_path5.default.dirname(staleEventsPath), { recursive: true });
|
|
2455
2777
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2456
|
-
await
|
|
2778
|
+
await import_node_fs5.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
2457
2779
|
}
|
|
2458
2780
|
async function readStaleEvents(staleEventsPath) {
|
|
2459
2781
|
try {
|
|
2460
|
-
const raw = await
|
|
2782
|
+
const raw = await import_node_fs5.promises.readFile(staleEventsPath, "utf8");
|
|
2461
2783
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2462
2784
|
} catch (err) {
|
|
2463
2785
|
if (err.code === "ENOENT") return [];
|
|
@@ -2491,7 +2813,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
2491
2813
|
}
|
|
2492
2814
|
async function readErrorEvents(errorsPath) {
|
|
2493
2815
|
try {
|
|
2494
|
-
const raw = await
|
|
2816
|
+
const raw = await import_node_fs5.promises.readFile(errorsPath, "utf8");
|
|
2495
2817
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2496
2818
|
} catch (err) {
|
|
2497
2819
|
if (err.code === "ENOENT") return [];
|
|
@@ -2523,16 +2845,16 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
2523
2845
|
|
|
2524
2846
|
// src/extract/services.ts
|
|
2525
2847
|
init_cjs_shims();
|
|
2526
|
-
var
|
|
2527
|
-
var
|
|
2848
|
+
var import_node_fs10 = require("fs");
|
|
2849
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2528
2850
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
2529
2851
|
var import_minimatch2 = require("minimatch");
|
|
2530
2852
|
var import_types6 = require("@neat.is/types");
|
|
2531
2853
|
|
|
2532
2854
|
// src/extract/shared.ts
|
|
2533
2855
|
init_cjs_shims();
|
|
2534
|
-
var
|
|
2535
|
-
var
|
|
2856
|
+
var import_node_fs6 = require("fs");
|
|
2857
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2536
2858
|
var import_yaml = require("yaml");
|
|
2537
2859
|
var import_types5 = require("@neat.is/types");
|
|
2538
2860
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
|
|
@@ -2564,7 +2886,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2564
2886
|
const cached = PYVENV_MARKER_CACHE.get(dir);
|
|
2565
2887
|
if (cached !== void 0) return cached;
|
|
2566
2888
|
try {
|
|
2567
|
-
const stat = await
|
|
2889
|
+
const stat = await import_node_fs6.promises.stat(import_node_path6.default.join(dir, "pyvenv.cfg"));
|
|
2568
2890
|
const ok = stat.isFile();
|
|
2569
2891
|
PYVENV_MARKER_CACHE.set(dir, ok);
|
|
2570
2892
|
return ok;
|
|
@@ -2574,7 +2896,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2574
2896
|
}
|
|
2575
2897
|
}
|
|
2576
2898
|
function isConfigFile(name) {
|
|
2577
|
-
const ext =
|
|
2899
|
+
const ext = import_node_path6.default.extname(name);
|
|
2578
2900
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2579
2901
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2580
2902
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
@@ -2682,16 +3004,16 @@ function cleanVersion(raw) {
|
|
|
2682
3004
|
return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
|
|
2683
3005
|
}
|
|
2684
3006
|
async function readJson(filePath) {
|
|
2685
|
-
const raw = await
|
|
3007
|
+
const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
|
|
2686
3008
|
return JSON.parse(raw);
|
|
2687
3009
|
}
|
|
2688
3010
|
async function readYaml(filePath) {
|
|
2689
|
-
const raw = await
|
|
3011
|
+
const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
|
|
2690
3012
|
return (0, import_yaml.parse)(raw);
|
|
2691
3013
|
}
|
|
2692
|
-
async function
|
|
3014
|
+
async function exists2(p) {
|
|
2693
3015
|
try {
|
|
2694
|
-
await
|
|
3016
|
+
await import_node_fs6.promises.access(p);
|
|
2695
3017
|
return true;
|
|
2696
3018
|
} catch {
|
|
2697
3019
|
return false;
|
|
@@ -2700,8 +3022,8 @@ async function exists(p) {
|
|
|
2700
3022
|
|
|
2701
3023
|
// src/extract/python.ts
|
|
2702
3024
|
init_cjs_shims();
|
|
2703
|
-
var
|
|
2704
|
-
var
|
|
3025
|
+
var import_node_fs7 = require("fs");
|
|
3026
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2705
3027
|
var import_smol_toml = require("smol-toml");
|
|
2706
3028
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
2707
3029
|
function parseRequirementsTxt(content) {
|
|
@@ -2734,25 +3056,25 @@ function depsFromPyProject(pyproject) {
|
|
|
2734
3056
|
return out;
|
|
2735
3057
|
}
|
|
2736
3058
|
async function discoverPythonService(serviceDir) {
|
|
2737
|
-
const pyprojectPath =
|
|
2738
|
-
const requirementsPath =
|
|
2739
|
-
const setupPath =
|
|
2740
|
-
const hasPyproject = await
|
|
2741
|
-
const hasRequirements = await
|
|
2742
|
-
const hasSetup = await
|
|
3059
|
+
const pyprojectPath = import_node_path7.default.join(serviceDir, "pyproject.toml");
|
|
3060
|
+
const requirementsPath = import_node_path7.default.join(serviceDir, "requirements.txt");
|
|
3061
|
+
const setupPath = import_node_path7.default.join(serviceDir, "setup.py");
|
|
3062
|
+
const hasPyproject = await exists2(pyprojectPath);
|
|
3063
|
+
const hasRequirements = await exists2(requirementsPath);
|
|
3064
|
+
const hasSetup = await exists2(setupPath);
|
|
2743
3065
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
2744
|
-
let name =
|
|
3066
|
+
let name = import_node_path7.default.basename(serviceDir);
|
|
2745
3067
|
let version;
|
|
2746
3068
|
const dependencies = {};
|
|
2747
3069
|
if (hasPyproject) {
|
|
2748
|
-
const raw = await
|
|
3070
|
+
const raw = await import_node_fs7.promises.readFile(pyprojectPath, "utf8");
|
|
2749
3071
|
const pyproject = (0, import_smol_toml.parse)(raw);
|
|
2750
3072
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
2751
3073
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
2752
3074
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
2753
3075
|
}
|
|
2754
3076
|
if (hasRequirements) {
|
|
2755
|
-
const raw = await
|
|
3077
|
+
const raw = await import_node_fs7.promises.readFile(requirementsPath, "utf8");
|
|
2756
3078
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
2757
3079
|
}
|
|
2758
3080
|
return { name, version, dependencies };
|
|
@@ -2767,17 +3089,17 @@ function pythonToPackage(service) {
|
|
|
2767
3089
|
|
|
2768
3090
|
// src/extract/owners.ts
|
|
2769
3091
|
init_cjs_shims();
|
|
2770
|
-
var
|
|
2771
|
-
var
|
|
3092
|
+
var import_node_fs8 = require("fs");
|
|
3093
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2772
3094
|
var import_minimatch = require("minimatch");
|
|
2773
3095
|
async function loadCodeowners(scanPath) {
|
|
2774
3096
|
const candidates = [
|
|
2775
|
-
|
|
2776
|
-
|
|
3097
|
+
import_node_path8.default.join(scanPath, "CODEOWNERS"),
|
|
3098
|
+
import_node_path8.default.join(scanPath, ".github", "CODEOWNERS")
|
|
2777
3099
|
];
|
|
2778
3100
|
for (const file of candidates) {
|
|
2779
|
-
if (await
|
|
2780
|
-
const raw = await
|
|
3101
|
+
if (await exists2(file)) {
|
|
3102
|
+
const raw = await import_node_fs8.promises.readFile(file, "utf8");
|
|
2781
3103
|
return parseCodeowners(raw);
|
|
2782
3104
|
}
|
|
2783
3105
|
}
|
|
@@ -2795,7 +3117,7 @@ function parseCodeowners(raw) {
|
|
|
2795
3117
|
return { rules };
|
|
2796
3118
|
}
|
|
2797
3119
|
function matchOwner(file, repoPath) {
|
|
2798
|
-
const normalized = repoPath.split(
|
|
3120
|
+
const normalized = repoPath.split(import_node_path8.default.sep).join("/");
|
|
2799
3121
|
for (const rule of file.rules) {
|
|
2800
3122
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
2801
3123
|
}
|
|
@@ -2811,8 +3133,8 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
2811
3133
|
return false;
|
|
2812
3134
|
}
|
|
2813
3135
|
async function readPackageJsonAuthor(serviceDir) {
|
|
2814
|
-
const pkgPath =
|
|
2815
|
-
if (!await
|
|
3136
|
+
const pkgPath = import_node_path8.default.join(serviceDir, "package.json");
|
|
3137
|
+
if (!await exists2(pkgPath)) return null;
|
|
2816
3138
|
try {
|
|
2817
3139
|
const pkg = await readJson(pkgPath);
|
|
2818
3140
|
if (!pkg.author) return null;
|
|
@@ -2834,8 +3156,8 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
|
2834
3156
|
|
|
2835
3157
|
// src/extract/errors.ts
|
|
2836
3158
|
init_cjs_shims();
|
|
2837
|
-
var
|
|
2838
|
-
var
|
|
3159
|
+
var import_node_fs9 = require("fs");
|
|
3160
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2839
3161
|
var sink = [];
|
|
2840
3162
|
function recordExtractionError(producer, file, err) {
|
|
2841
3163
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -2854,9 +3176,9 @@ function drainExtractionErrors() {
|
|
|
2854
3176
|
}
|
|
2855
3177
|
async function writeExtractionErrors(errors, errorsPath) {
|
|
2856
3178
|
if (errors.length === 0) return;
|
|
2857
|
-
await
|
|
3179
|
+
await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(errorsPath), { recursive: true });
|
|
2858
3180
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2859
|
-
await
|
|
3181
|
+
await import_node_fs9.promises.appendFile(errorsPath, lines, "utf8");
|
|
2860
3182
|
}
|
|
2861
3183
|
var droppedSink = [];
|
|
2862
3184
|
function noteExtractedDropped(edge) {
|
|
@@ -2871,9 +3193,9 @@ function isRejectedLogEnabled() {
|
|
|
2871
3193
|
}
|
|
2872
3194
|
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
2873
3195
|
if (drops.length === 0) return;
|
|
2874
|
-
await
|
|
3196
|
+
await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(rejectedPath), { recursive: true });
|
|
2875
3197
|
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
2876
|
-
await
|
|
3198
|
+
await import_node_fs9.promises.appendFile(rejectedPath, lines, "utf8");
|
|
2877
3199
|
}
|
|
2878
3200
|
|
|
2879
3201
|
// src/extract/services.ts
|
|
@@ -2892,21 +3214,21 @@ function workspaceGlobs(pkg) {
|
|
|
2892
3214
|
return null;
|
|
2893
3215
|
}
|
|
2894
3216
|
async function loadGitignore(scanPath) {
|
|
2895
|
-
const gitignorePath =
|
|
2896
|
-
if (!await
|
|
2897
|
-
const raw = await
|
|
3217
|
+
const gitignorePath = import_node_path10.default.join(scanPath, ".gitignore");
|
|
3218
|
+
if (!await exists2(gitignorePath)) return null;
|
|
3219
|
+
const raw = await import_node_fs10.promises.readFile(gitignorePath, "utf8");
|
|
2898
3220
|
return (0, import_ignore.default)().add(raw);
|
|
2899
3221
|
}
|
|
2900
3222
|
async function walkDirs(start, scanPath, options, visit) {
|
|
2901
3223
|
async function recurse(current, depth) {
|
|
2902
3224
|
if (depth > options.maxDepth) return;
|
|
2903
|
-
const entries = await
|
|
3225
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2904
3226
|
for (const entry of entries) {
|
|
2905
3227
|
if (!entry.isDirectory()) continue;
|
|
2906
3228
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2907
|
-
const child =
|
|
3229
|
+
const child = import_node_path10.default.join(current, entry.name);
|
|
2908
3230
|
if (options.ig) {
|
|
2909
|
-
const rel =
|
|
3231
|
+
const rel = import_node_path10.default.relative(scanPath, child).split(import_node_path10.default.sep).join("/");
|
|
2910
3232
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2911
3233
|
}
|
|
2912
3234
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -2922,8 +3244,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2922
3244
|
for (const raw of globs) {
|
|
2923
3245
|
const pattern = raw.replace(/^\.\//, "");
|
|
2924
3246
|
if (!pattern.includes("*")) {
|
|
2925
|
-
const candidate =
|
|
2926
|
-
if (await
|
|
3247
|
+
const candidate = import_node_path10.default.join(scanPath, pattern);
|
|
3248
|
+
if (await exists2(import_node_path10.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2927
3249
|
continue;
|
|
2928
3250
|
}
|
|
2929
3251
|
const segments = pattern.split("/");
|
|
@@ -2932,13 +3254,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2932
3254
|
if (seg.includes("*")) break;
|
|
2933
3255
|
staticSegments.push(seg);
|
|
2934
3256
|
}
|
|
2935
|
-
const start =
|
|
2936
|
-
if (!await
|
|
3257
|
+
const start = import_node_path10.default.join(scanPath, ...staticSegments);
|
|
3258
|
+
if (!await exists2(start)) continue;
|
|
2937
3259
|
const hasDoubleStar = pattern.includes("**");
|
|
2938
3260
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2939
3261
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2940
|
-
const rel =
|
|
2941
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await
|
|
3262
|
+
const rel = import_node_path10.default.relative(scanPath, dir).split(import_node_path10.default.sep).join("/");
|
|
3263
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path10.default.join(dir, "package.json"))) {
|
|
2942
3264
|
found.add(dir);
|
|
2943
3265
|
}
|
|
2944
3266
|
});
|
|
@@ -2958,13 +3280,13 @@ function detectJsFramework(pkg) {
|
|
|
2958
3280
|
return void 0;
|
|
2959
3281
|
}
|
|
2960
3282
|
async function discoverNodeService(scanPath, dir) {
|
|
2961
|
-
const pkgPath =
|
|
2962
|
-
if (!await
|
|
3283
|
+
const pkgPath = import_node_path10.default.join(dir, "package.json");
|
|
3284
|
+
if (!await exists2(pkgPath)) return null;
|
|
2963
3285
|
let pkg;
|
|
2964
3286
|
try {
|
|
2965
3287
|
pkg = await readJson(pkgPath);
|
|
2966
3288
|
} catch (err) {
|
|
2967
|
-
recordExtractionError("services",
|
|
3289
|
+
recordExtractionError("services", import_node_path10.default.relative(scanPath, pkgPath), err);
|
|
2968
3290
|
return null;
|
|
2969
3291
|
}
|
|
2970
3292
|
if (!pkg.name) return null;
|
|
@@ -2976,7 +3298,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2976
3298
|
language: "javascript",
|
|
2977
3299
|
version: pkg.version,
|
|
2978
3300
|
dependencies: pkg.dependencies ?? {},
|
|
2979
|
-
repoPath:
|
|
3301
|
+
repoPath: import_node_path10.default.relative(scanPath, dir),
|
|
2980
3302
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
2981
3303
|
...framework ? { framework } : {}
|
|
2982
3304
|
};
|
|
@@ -2993,20 +3315,20 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2993
3315
|
language: "python",
|
|
2994
3316
|
version: py.version,
|
|
2995
3317
|
dependencies: py.dependencies,
|
|
2996
|
-
repoPath:
|
|
3318
|
+
repoPath: import_node_path10.default.relative(scanPath, dir)
|
|
2997
3319
|
};
|
|
2998
3320
|
return { pkg, dir, node };
|
|
2999
3321
|
}
|
|
3000
3322
|
async function discoverServices(scanPath) {
|
|
3001
|
-
const rootPkgPath =
|
|
3323
|
+
const rootPkgPath = import_node_path10.default.join(scanPath, "package.json");
|
|
3002
3324
|
let rootPkg = null;
|
|
3003
|
-
if (await
|
|
3325
|
+
if (await exists2(rootPkgPath)) {
|
|
3004
3326
|
try {
|
|
3005
3327
|
rootPkg = await readJson(rootPkgPath);
|
|
3006
3328
|
} catch (err) {
|
|
3007
3329
|
recordExtractionError(
|
|
3008
3330
|
"services workspaces",
|
|
3009
|
-
|
|
3331
|
+
import_node_path10.default.relative(scanPath, rootPkgPath),
|
|
3010
3332
|
err
|
|
3011
3333
|
);
|
|
3012
3334
|
}
|
|
@@ -3023,9 +3345,9 @@ async function discoverServices(scanPath) {
|
|
|
3023
3345
|
scanPath,
|
|
3024
3346
|
{ maxDepth: parseScanDepth(), ig },
|
|
3025
3347
|
async (dir) => {
|
|
3026
|
-
if (await
|
|
3348
|
+
if (await exists2(import_node_path10.default.join(dir, "package.json"))) {
|
|
3027
3349
|
candidateDirs.push(dir);
|
|
3028
|
-
} else if (await
|
|
3350
|
+
} else if (await exists2(import_node_path10.default.join(dir, "pyproject.toml")) || await exists2(import_node_path10.default.join(dir, "requirements.txt")) || await exists2(import_node_path10.default.join(dir, "setup.py"))) {
|
|
3029
3351
|
candidateDirs.push(dir);
|
|
3030
3352
|
}
|
|
3031
3353
|
}
|
|
@@ -3039,8 +3361,8 @@ async function discoverServices(scanPath) {
|
|
|
3039
3361
|
if (!service) continue;
|
|
3040
3362
|
const existingDir = seen.get(service.node.name);
|
|
3041
3363
|
if (existingDir !== void 0) {
|
|
3042
|
-
const a =
|
|
3043
|
-
const b =
|
|
3364
|
+
const a = import_node_path10.default.relative(scanPath, existingDir) || ".";
|
|
3365
|
+
const b = import_node_path10.default.relative(scanPath, dir) || ".";
|
|
3044
3366
|
console.warn(
|
|
3045
3367
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
3046
3368
|
);
|
|
@@ -3077,8 +3399,8 @@ function addServiceNodes(graph, services) {
|
|
|
3077
3399
|
|
|
3078
3400
|
// src/extract/aliases.ts
|
|
3079
3401
|
init_cjs_shims();
|
|
3080
|
-
var
|
|
3081
|
-
var
|
|
3402
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3403
|
+
var import_node_fs11 = require("fs");
|
|
3082
3404
|
var import_yaml2 = require("yaml");
|
|
3083
3405
|
var import_types7 = require("@neat.is/types");
|
|
3084
3406
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -3105,15 +3427,15 @@ function indexServicesByName(services) {
|
|
|
3105
3427
|
const map = /* @__PURE__ */ new Map();
|
|
3106
3428
|
for (const s of services) {
|
|
3107
3429
|
map.set(s.node.name, s.node.id);
|
|
3108
|
-
map.set(
|
|
3430
|
+
map.set(import_node_path11.default.basename(s.dir), s.node.id);
|
|
3109
3431
|
}
|
|
3110
3432
|
return map;
|
|
3111
3433
|
}
|
|
3112
3434
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
3113
3435
|
let composePath = null;
|
|
3114
3436
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3115
|
-
const abs =
|
|
3116
|
-
if (await
|
|
3437
|
+
const abs = import_node_path11.default.join(scanPath, name);
|
|
3438
|
+
if (await exists2(abs)) {
|
|
3117
3439
|
composePath = abs;
|
|
3118
3440
|
break;
|
|
3119
3441
|
}
|
|
@@ -3125,7 +3447,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3125
3447
|
} catch (err) {
|
|
3126
3448
|
recordExtractionError(
|
|
3127
3449
|
"aliases compose",
|
|
3128
|
-
|
|
3450
|
+
import_node_path11.default.relative(scanPath, composePath),
|
|
3129
3451
|
err
|
|
3130
3452
|
);
|
|
3131
3453
|
return;
|
|
@@ -3168,11 +3490,11 @@ function parseDockerfileLabels(content) {
|
|
|
3168
3490
|
}
|
|
3169
3491
|
async function collectDockerfileAliases(graph, services) {
|
|
3170
3492
|
for (const service of services) {
|
|
3171
|
-
const dockerfilePath =
|
|
3172
|
-
if (!await
|
|
3493
|
+
const dockerfilePath = import_node_path11.default.join(service.dir, "Dockerfile");
|
|
3494
|
+
if (!await exists2(dockerfilePath)) continue;
|
|
3173
3495
|
let content;
|
|
3174
3496
|
try {
|
|
3175
|
-
content = await
|
|
3497
|
+
content = await import_node_fs11.promises.readFile(dockerfilePath, "utf8");
|
|
3176
3498
|
} catch (err) {
|
|
3177
3499
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
3178
3500
|
continue;
|
|
@@ -3184,15 +3506,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
3184
3506
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
3185
3507
|
if (depth > max) return [];
|
|
3186
3508
|
const out = [];
|
|
3187
|
-
const entries = await
|
|
3509
|
+
const entries = await import_node_fs11.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3188
3510
|
for (const entry of entries) {
|
|
3189
3511
|
if (entry.isDirectory()) {
|
|
3190
3512
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3191
|
-
const child =
|
|
3513
|
+
const child = import_node_path11.default.join(start, entry.name);
|
|
3192
3514
|
if (await isPythonVenvDir(child)) continue;
|
|
3193
3515
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
3194
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3195
|
-
out.push(
|
|
3516
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry.name))) {
|
|
3517
|
+
out.push(import_node_path11.default.join(start, entry.name));
|
|
3196
3518
|
}
|
|
3197
3519
|
}
|
|
3198
3520
|
return out;
|
|
@@ -3219,7 +3541,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
3219
3541
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
3220
3542
|
const files = await walkYamlFiles(scanPath);
|
|
3221
3543
|
for (const file of files) {
|
|
3222
|
-
const content = await
|
|
3544
|
+
const content = await import_node_fs11.promises.readFile(file, "utf8");
|
|
3223
3545
|
let docs;
|
|
3224
3546
|
try {
|
|
3225
3547
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3242,105 +3564,522 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3242
3564
|
await collectK8sAliases(graph, scanPath, byName);
|
|
3243
3565
|
}
|
|
3244
3566
|
|
|
3245
|
-
// src/extract/
|
|
3567
|
+
// src/extract/files.ts
|
|
3246
3568
|
init_cjs_shims();
|
|
3247
|
-
var import_node_path17 = __toESM(require("path"), 1);
|
|
3248
|
-
var import_types8 = require("@neat.is/types");
|
|
3249
3569
|
|
|
3250
|
-
// src/extract/
|
|
3570
|
+
// src/extract/calls/shared.ts
|
|
3251
3571
|
init_cjs_shims();
|
|
3252
|
-
var
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
{
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3572
|
+
var import_node_fs12 = require("fs");
|
|
3573
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3574
|
+
var import_types8 = require("@neat.is/types");
|
|
3575
|
+
async function walkSourceFiles(dir) {
|
|
3576
|
+
const out = [];
|
|
3577
|
+
async function walk(current) {
|
|
3578
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3579
|
+
for (const entry of entries) {
|
|
3580
|
+
const full = import_node_path12.default.join(current, entry.name);
|
|
3581
|
+
if (entry.isDirectory()) {
|
|
3582
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3583
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3584
|
+
await walk(full);
|
|
3585
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path12.default.extname(entry.name))) {
|
|
3586
|
+
out.push(full);
|
|
3587
|
+
}
|
|
3265
3588
|
}
|
|
3266
|
-
|
|
3589
|
+
}
|
|
3590
|
+
await walk(dir);
|
|
3591
|
+
return out;
|
|
3267
3592
|
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
var import_node_fs10 = require("fs");
|
|
3278
|
-
var import_node_path11 = __toESM(require("path"), 1);
|
|
3279
|
-
function schemeToEngine(scheme) {
|
|
3280
|
-
const s = scheme.toLowerCase().split("+")[0];
|
|
3281
|
-
switch (s) {
|
|
3282
|
-
case "postgres":
|
|
3283
|
-
case "postgresql":
|
|
3284
|
-
return "postgresql";
|
|
3285
|
-
case "mysql":
|
|
3286
|
-
case "mariadb":
|
|
3287
|
-
return "mysql";
|
|
3288
|
-
case "mongodb":
|
|
3289
|
-
case "mongodb+srv":
|
|
3290
|
-
return "mongodb";
|
|
3291
|
-
case "redis":
|
|
3292
|
-
case "rediss":
|
|
3293
|
-
return "redis";
|
|
3294
|
-
case "sqlite":
|
|
3295
|
-
return "sqlite";
|
|
3296
|
-
default:
|
|
3297
|
-
return null;
|
|
3593
|
+
async function loadSourceFiles(dir) {
|
|
3594
|
+
const paths = await walkSourceFiles(dir);
|
|
3595
|
+
const out = [];
|
|
3596
|
+
for (const p of paths) {
|
|
3597
|
+
try {
|
|
3598
|
+
const content = await import_node_fs12.promises.readFile(p, "utf8");
|
|
3599
|
+
out.push({ path: p, content });
|
|
3600
|
+
} catch {
|
|
3601
|
+
}
|
|
3298
3602
|
}
|
|
3603
|
+
return out;
|
|
3299
3604
|
}
|
|
3300
|
-
function
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3303
|
-
);
|
|
3304
|
-
if (!m || !m.groups) return null;
|
|
3305
|
-
const engine = schemeToEngine(m.groups.scheme);
|
|
3306
|
-
if (!engine) return null;
|
|
3307
|
-
return {
|
|
3308
|
-
host: m.groups.host,
|
|
3309
|
-
port: m.groups.port ? Number(m.groups.port) : void 0,
|
|
3310
|
-
database: m.groups.db ?? "",
|
|
3311
|
-
engine,
|
|
3312
|
-
engineVersion: "unknown"
|
|
3313
|
-
};
|
|
3605
|
+
function lineOf(text, needle) {
|
|
3606
|
+
const idx = text.indexOf(needle);
|
|
3607
|
+
if (idx < 0) return 1;
|
|
3608
|
+
return text.slice(0, idx).split("\n").length;
|
|
3314
3609
|
}
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
} catch {
|
|
3319
|
-
return null;
|
|
3320
|
-
}
|
|
3610
|
+
function snippet(text, line) {
|
|
3611
|
+
const lines = text.split("\n");
|
|
3612
|
+
return (lines[line - 1] ?? "").trim();
|
|
3321
3613
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3614
|
+
function toPosix2(p) {
|
|
3615
|
+
return p.split("\\").join("/");
|
|
3616
|
+
}
|
|
3617
|
+
function languageForPath(relPath) {
|
|
3618
|
+
switch (import_node_path12.default.extname(relPath).toLowerCase()) {
|
|
3619
|
+
case ".py":
|
|
3620
|
+
return "python";
|
|
3621
|
+
case ".ts":
|
|
3622
|
+
case ".tsx":
|
|
3623
|
+
return "typescript";
|
|
3624
|
+
case ".js":
|
|
3625
|
+
case ".jsx":
|
|
3626
|
+
case ".mjs":
|
|
3627
|
+
case ".cjs":
|
|
3628
|
+
return "javascript";
|
|
3629
|
+
default:
|
|
3630
|
+
return void 0;
|
|
3327
3631
|
}
|
|
3328
|
-
return null;
|
|
3329
3632
|
}
|
|
3330
|
-
function
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3633
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3634
|
+
let nodesAdded = 0;
|
|
3635
|
+
let edgesAdded = 0;
|
|
3636
|
+
const fileNodeId = (0, import_types8.fileId)(serviceName, relPath);
|
|
3637
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3638
|
+
const language = languageForPath(relPath);
|
|
3639
|
+
const node = {
|
|
3640
|
+
id: fileNodeId,
|
|
3641
|
+
type: import_types8.NodeType.FileNode,
|
|
3642
|
+
service: serviceName,
|
|
3643
|
+
path: relPath,
|
|
3644
|
+
...language ? { language } : {},
|
|
3645
|
+
discoveredVia: "static"
|
|
3646
|
+
};
|
|
3647
|
+
graph.addNode(fileNodeId, node);
|
|
3648
|
+
nodesAdded++;
|
|
3649
|
+
}
|
|
3650
|
+
const containsId = (0, import_types8.extractedEdgeId)(serviceNodeId, fileNodeId, import_types8.EdgeType.CONTAINS);
|
|
3651
|
+
if (!graph.hasEdge(containsId)) {
|
|
3652
|
+
const edge = {
|
|
3653
|
+
id: containsId,
|
|
3654
|
+
source: serviceNodeId,
|
|
3655
|
+
target: fileNodeId,
|
|
3656
|
+
type: import_types8.EdgeType.CONTAINS,
|
|
3657
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3658
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3659
|
+
evidence: { file: relPath }
|
|
3660
|
+
};
|
|
3661
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3662
|
+
edgesAdded++;
|
|
3663
|
+
}
|
|
3664
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// src/extract/files.ts
|
|
3668
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3669
|
+
async function addFiles(graph, services) {
|
|
3670
|
+
let nodesAdded = 0;
|
|
3671
|
+
let edgesAdded = 0;
|
|
3672
|
+
for (const service of services) {
|
|
3673
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
3674
|
+
for (const filePath of filePaths) {
|
|
3675
|
+
const relPath = toPosix2(import_node_path13.default.relative(service.dir, filePath));
|
|
3676
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3677
|
+
graph,
|
|
3678
|
+
service.pkg.name,
|
|
3679
|
+
service.node.id,
|
|
3680
|
+
relPath
|
|
3681
|
+
);
|
|
3682
|
+
nodesAdded += n;
|
|
3683
|
+
edgesAdded += e;
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
return { nodesAdded, edgesAdded };
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
// src/extract/imports.ts
|
|
3690
|
+
init_cjs_shims();
|
|
3691
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3692
|
+
var import_node_fs13 = require("fs");
|
|
3693
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3694
|
+
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3695
|
+
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3696
|
+
var import_types9 = require("@neat.is/types");
|
|
3697
|
+
var PARSE_CHUNK = 16384;
|
|
3698
|
+
function parseSource(parser, source) {
|
|
3699
|
+
return parser.parse(
|
|
3700
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3703
|
+
function makeJsParser() {
|
|
3704
|
+
const p = new import_tree_sitter.default();
|
|
3705
|
+
p.setLanguage(import_tree_sitter_javascript.default);
|
|
3706
|
+
return p;
|
|
3707
|
+
}
|
|
3708
|
+
function makePyParser() {
|
|
3709
|
+
const p = new import_tree_sitter.default();
|
|
3710
|
+
p.setLanguage(import_tree_sitter_python.default);
|
|
3711
|
+
return p;
|
|
3712
|
+
}
|
|
3713
|
+
function stringLiteralText(node) {
|
|
3714
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3715
|
+
const child = node.child(i);
|
|
3716
|
+
if (child?.type === "string_fragment") return child.text;
|
|
3717
|
+
}
|
|
3718
|
+
const raw = node.text;
|
|
3719
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
3720
|
+
return raw.length === 0 ? null : "";
|
|
3721
|
+
}
|
|
3722
|
+
function clipSnippet(text) {
|
|
3723
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
3724
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
3725
|
+
}
|
|
3726
|
+
function collectJsImports(node, out) {
|
|
3727
|
+
if (node.type === "import_statement") {
|
|
3728
|
+
const source = node.childForFieldName("source");
|
|
3729
|
+
if (source) {
|
|
3730
|
+
const specifier = stringLiteralText(source);
|
|
3731
|
+
if (specifier) {
|
|
3732
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
if (node.type === "call_expression") {
|
|
3738
|
+
const fn = node.childForFieldName("function");
|
|
3739
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
3740
|
+
const args = node.childForFieldName("arguments");
|
|
3741
|
+
const firstArg = args?.namedChild(0);
|
|
3742
|
+
if (firstArg?.type === "string") {
|
|
3743
|
+
const specifier = stringLiteralText(firstArg);
|
|
3744
|
+
if (specifier) {
|
|
3745
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3751
|
+
const child = node.namedChild(i);
|
|
3752
|
+
if (child) collectJsImports(child, out);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
function collectPyImports(node, out) {
|
|
3756
|
+
if (node.type === "import_from_statement") {
|
|
3757
|
+
let level = 0;
|
|
3758
|
+
let modulePath = "";
|
|
3759
|
+
let pastFrom = false;
|
|
3760
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3761
|
+
const child = node.child(i);
|
|
3762
|
+
if (!child) continue;
|
|
3763
|
+
if (!pastFrom) {
|
|
3764
|
+
if (child.type === "from") pastFrom = true;
|
|
3765
|
+
continue;
|
|
3766
|
+
}
|
|
3767
|
+
if (child.type === "import") break;
|
|
3768
|
+
if (child.type === "relative_import") {
|
|
3769
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
3770
|
+
const rc = child.child(j);
|
|
3771
|
+
if (!rc) continue;
|
|
3772
|
+
if (rc.type === "import_prefix") {
|
|
3773
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
3774
|
+
if (rc.child(k)?.type === ".") level++;
|
|
3775
|
+
}
|
|
3776
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
3777
|
+
}
|
|
3778
|
+
break;
|
|
3779
|
+
}
|
|
3780
|
+
if (child.type === "dotted_name") {
|
|
3781
|
+
modulePath = child.text;
|
|
3782
|
+
break;
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
if (level > 0 || modulePath) {
|
|
3786
|
+
out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3790
|
+
const child = node.namedChild(i);
|
|
3791
|
+
if (child) collectPyImports(child, out);
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
async function fileExists2(p) {
|
|
3795
|
+
try {
|
|
3796
|
+
await import_node_fs13.promises.access(p);
|
|
3797
|
+
return true;
|
|
3798
|
+
} catch {
|
|
3799
|
+
return false;
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
3803
|
+
const rel = import_node_path14.default.relative(serviceDir, candidate);
|
|
3804
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path14.default.isAbsolute(rel);
|
|
3805
|
+
}
|
|
3806
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
3807
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
3808
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
3809
|
+
for (const ext of JS_EXTENSIONS) {
|
|
3810
|
+
const candidate = base + ext;
|
|
3811
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
3812
|
+
return toPosix2(import_node_path14.default.relative(serviceDir, candidate));
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
3816
|
+
const candidate = import_node_path14.default.join(base, indexFile);
|
|
3817
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
3818
|
+
return toPosix2(import_node_path14.default.relative(serviceDir, candidate));
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
return null;
|
|
3822
|
+
}
|
|
3823
|
+
async function loadTsPathConfig(serviceDir) {
|
|
3824
|
+
const tsconfigPath = import_node_path14.default.join(serviceDir, "tsconfig.json");
|
|
3825
|
+
let raw;
|
|
3826
|
+
try {
|
|
3827
|
+
raw = await import_node_fs13.promises.readFile(tsconfigPath, "utf8");
|
|
3828
|
+
} catch {
|
|
3829
|
+
return null;
|
|
3830
|
+
}
|
|
3831
|
+
try {
|
|
3832
|
+
const parsed = JSON.parse(raw);
|
|
3833
|
+
const paths = parsed.compilerOptions?.paths;
|
|
3834
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
3835
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
3836
|
+
return { paths, baseDir: baseUrl ? import_node_path14.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
3837
|
+
} catch (err) {
|
|
3838
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
3839
|
+
return null;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
3843
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
3844
|
+
let suffix = null;
|
|
3845
|
+
if (pattern === specifier) {
|
|
3846
|
+
suffix = "";
|
|
3847
|
+
} else if (pattern.endsWith("/*")) {
|
|
3848
|
+
const prefix = pattern.slice(0, -1);
|
|
3849
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
3850
|
+
}
|
|
3851
|
+
if (suffix === null) continue;
|
|
3852
|
+
for (const target of targets) {
|
|
3853
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
3854
|
+
const resolvedBase = import_node_path14.default.resolve(config.baseDir, targetBase, suffix);
|
|
3855
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
3856
|
+
if (hit) return hit;
|
|
3857
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists2(resolvedBase)) {
|
|
3858
|
+
return toPosix2(import_node_path14.default.relative(serviceDir, resolvedBase));
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
return null;
|
|
3863
|
+
}
|
|
3864
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
3865
|
+
if (!specifier) return null;
|
|
3866
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
3867
|
+
const base = import_node_path14.default.resolve(importerDir, specifier);
|
|
3868
|
+
const ext = import_node_path14.default.extname(specifier);
|
|
3869
|
+
if (ext) {
|
|
3870
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
3871
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
3872
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
3873
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists2(tsSibling)) {
|
|
3874
|
+
return toPosix2(import_node_path14.default.relative(serviceDir, tsSibling));
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists2(base)) {
|
|
3878
|
+
return toPosix2(import_node_path14.default.relative(serviceDir, base));
|
|
3879
|
+
}
|
|
3880
|
+
return null;
|
|
3881
|
+
}
|
|
3882
|
+
return firstExistingCandidate(base, serviceDir);
|
|
3883
|
+
}
|
|
3884
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
3885
|
+
return null;
|
|
3886
|
+
}
|
|
3887
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
3888
|
+
if (!imp.modulePath) return null;
|
|
3889
|
+
const relPath = imp.modulePath.split(".").join("/");
|
|
3890
|
+
let baseDir;
|
|
3891
|
+
if (imp.level > 0) {
|
|
3892
|
+
baseDir = import_node_path14.default.dirname(importerPath);
|
|
3893
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path14.default.dirname(baseDir);
|
|
3894
|
+
} else {
|
|
3895
|
+
baseDir = serviceDir;
|
|
3896
|
+
}
|
|
3897
|
+
const candidates = [import_node_path14.default.join(baseDir, `${relPath}.py`), import_node_path14.default.join(baseDir, relPath, "__init__.py")];
|
|
3898
|
+
for (const candidate of candidates) {
|
|
3899
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
3900
|
+
return toPosix2(import_node_path14.default.relative(serviceDir, candidate));
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
return null;
|
|
3904
|
+
}
|
|
3905
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
3906
|
+
const importeeFileId = (0, import_types9.fileId)(serviceName, importeeRelPath);
|
|
3907
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
3908
|
+
const edgeId = (0, import_types9.extractedEdgeId)(importerFileId, importeeFileId, import_types9.EdgeType.IMPORTS);
|
|
3909
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
3910
|
+
const edge = {
|
|
3911
|
+
id: edgeId,
|
|
3912
|
+
source: importerFileId,
|
|
3913
|
+
target: importeeFileId,
|
|
3914
|
+
type: import_types9.EdgeType.IMPORTS,
|
|
3915
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3916
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3917
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
3918
|
+
};
|
|
3919
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
3920
|
+
return 1;
|
|
3921
|
+
}
|
|
3922
|
+
async function addImports(graph, services) {
|
|
3923
|
+
const jsParser = makeJsParser();
|
|
3924
|
+
const pyParser = makePyParser();
|
|
3925
|
+
let edgesAdded = 0;
|
|
3926
|
+
for (const service of services) {
|
|
3927
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
3928
|
+
const files = await loadSourceFiles(service.dir);
|
|
3929
|
+
for (const file of files) {
|
|
3930
|
+
if (isTestPath(file.path)) continue;
|
|
3931
|
+
const relFile = toPosix2(import_node_path14.default.relative(service.dir, file.path));
|
|
3932
|
+
const importerFileId = (0, import_types9.fileId)(service.pkg.name, relFile);
|
|
3933
|
+
const isPython = import_node_path14.default.extname(file.path) === ".py";
|
|
3934
|
+
if (isPython) {
|
|
3935
|
+
let pyImports = [];
|
|
3936
|
+
try {
|
|
3937
|
+
const tree = parseSource(pyParser, file.content);
|
|
3938
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
3939
|
+
} catch (err) {
|
|
3940
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3941
|
+
continue;
|
|
3942
|
+
}
|
|
3943
|
+
for (const imp of pyImports) {
|
|
3944
|
+
const resolved = await resolvePyImport(imp, file.path, service.dir);
|
|
3945
|
+
if (!resolved) continue;
|
|
3946
|
+
edgesAdded += emitImportEdge(
|
|
3947
|
+
graph,
|
|
3948
|
+
service.pkg.name,
|
|
3949
|
+
importerFileId,
|
|
3950
|
+
relFile,
|
|
3951
|
+
resolved,
|
|
3952
|
+
imp.line,
|
|
3953
|
+
imp.snippet
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
continue;
|
|
3957
|
+
}
|
|
3958
|
+
let jsImports = [];
|
|
3959
|
+
try {
|
|
3960
|
+
const tree = parseSource(jsParser, file.content);
|
|
3961
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
3962
|
+
} catch (err) {
|
|
3963
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3964
|
+
continue;
|
|
3965
|
+
}
|
|
3966
|
+
for (const imp of jsImports) {
|
|
3967
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path14.default.dirname(file.path), service.dir, tsPaths);
|
|
3968
|
+
if (!resolved) continue;
|
|
3969
|
+
edgesAdded += emitImportEdge(
|
|
3970
|
+
graph,
|
|
3971
|
+
service.pkg.name,
|
|
3972
|
+
importerFileId,
|
|
3973
|
+
relFile,
|
|
3974
|
+
resolved,
|
|
3975
|
+
imp.line,
|
|
3976
|
+
imp.snippet
|
|
3977
|
+
);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
return { nodesAdded: 0, edgesAdded };
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
// src/extract/databases/index.ts
|
|
3985
|
+
init_cjs_shims();
|
|
3986
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3987
|
+
var import_types10 = require("@neat.is/types");
|
|
3988
|
+
|
|
3989
|
+
// src/extract/databases/db-config-yaml.ts
|
|
3990
|
+
init_cjs_shims();
|
|
3991
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3992
|
+
async function parse(serviceDir) {
|
|
3993
|
+
const yamlPath = import_node_path15.default.join(serviceDir, "db-config.yaml");
|
|
3994
|
+
if (!await exists2(yamlPath)) return [];
|
|
3995
|
+
const raw = await readYaml(yamlPath);
|
|
3996
|
+
return [
|
|
3997
|
+
{
|
|
3998
|
+
host: raw.host,
|
|
3999
|
+
port: raw.port,
|
|
4000
|
+
database: raw.database,
|
|
4001
|
+
engine: raw.engine,
|
|
4002
|
+
engineVersion: raw.engineVersion !== void 0 ? String(raw.engineVersion) : "unknown",
|
|
4003
|
+
sourceFile: yamlPath
|
|
4004
|
+
}
|
|
4005
|
+
];
|
|
4006
|
+
}
|
|
4007
|
+
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
4008
|
+
|
|
4009
|
+
// src/extract/databases/dotenv.ts
|
|
4010
|
+
init_cjs_shims();
|
|
4011
|
+
var import_node_fs15 = require("fs");
|
|
4012
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
4013
|
+
|
|
4014
|
+
// src/extract/databases/shared.ts
|
|
4015
|
+
init_cjs_shims();
|
|
4016
|
+
var import_node_fs14 = require("fs");
|
|
4017
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
4018
|
+
function schemeToEngine(scheme) {
|
|
4019
|
+
const s = scheme.toLowerCase().split("+")[0];
|
|
4020
|
+
switch (s) {
|
|
4021
|
+
case "postgres":
|
|
4022
|
+
case "postgresql":
|
|
4023
|
+
return "postgresql";
|
|
4024
|
+
case "mysql":
|
|
4025
|
+
case "mariadb":
|
|
4026
|
+
return "mysql";
|
|
4027
|
+
case "mongodb":
|
|
4028
|
+
case "mongodb+srv":
|
|
4029
|
+
return "mongodb";
|
|
4030
|
+
case "redis":
|
|
4031
|
+
case "rediss":
|
|
4032
|
+
return "redis";
|
|
4033
|
+
case "sqlite":
|
|
4034
|
+
return "sqlite";
|
|
4035
|
+
default:
|
|
4036
|
+
return null;
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
function parseConnectionString(url) {
|
|
4040
|
+
const m = url.match(
|
|
4041
|
+
/^(?<scheme>[a-z][a-z+]*):\/\/(?:[^@/]+(?::[^@]*)?@)?(?<host>[^:/?]+)(?::(?<port>\d+))?(?:\/(?<db>[^?#]*))?/i
|
|
4042
|
+
);
|
|
4043
|
+
if (!m || !m.groups) return null;
|
|
4044
|
+
const engine = schemeToEngine(m.groups.scheme);
|
|
4045
|
+
if (!engine) return null;
|
|
4046
|
+
return {
|
|
4047
|
+
host: m.groups.host,
|
|
4048
|
+
port: m.groups.port ? Number(m.groups.port) : void 0,
|
|
4049
|
+
database: m.groups.db ?? "",
|
|
4050
|
+
engine,
|
|
4051
|
+
engineVersion: "unknown"
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
async function readIfExists(filePath) {
|
|
4055
|
+
try {
|
|
4056
|
+
return await import_node_fs14.promises.readFile(filePath, "utf8");
|
|
4057
|
+
} catch {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
async function findFirst(serviceDir, candidates) {
|
|
4062
|
+
for (const rel of candidates) {
|
|
4063
|
+
const abs = import_node_path16.default.join(serviceDir, rel);
|
|
4064
|
+
const content = await readIfExists(abs);
|
|
4065
|
+
if (content !== null) return abs;
|
|
4066
|
+
}
|
|
4067
|
+
return null;
|
|
4068
|
+
}
|
|
4069
|
+
function engineFromImage(image) {
|
|
4070
|
+
const lower = image.toLowerCase();
|
|
4071
|
+
const colon = lower.lastIndexOf(":");
|
|
4072
|
+
const repo = colon >= 0 ? lower.slice(0, colon) : lower;
|
|
4073
|
+
const tag = colon >= 0 ? lower.slice(colon + 1) : "latest";
|
|
4074
|
+
const last = repo.split("/").pop() ?? repo;
|
|
4075
|
+
let engine = null;
|
|
4076
|
+
if (last.startsWith("postgres")) engine = "postgresql";
|
|
4077
|
+
else if (last.startsWith("mysql") || last.startsWith("mariadb")) engine = "mysql";
|
|
4078
|
+
else if (last.startsWith("mongo")) engine = "mongodb";
|
|
4079
|
+
else if (last.startsWith("redis")) engine = "redis";
|
|
4080
|
+
else if (last.startsWith("sqlite")) engine = "sqlite";
|
|
4081
|
+
if (!engine) return null;
|
|
4082
|
+
const versionMatch = tag.match(/^(\d+(?:\.\d+){0,2})/);
|
|
3344
4083
|
return {
|
|
3345
4084
|
engine,
|
|
3346
4085
|
engineVersion: versionMatch ? versionMatch[1] : "unknown"
|
|
@@ -3372,15 +4111,15 @@ function parseDotenvLine(line) {
|
|
|
3372
4111
|
return { key, value };
|
|
3373
4112
|
}
|
|
3374
4113
|
async function parse2(serviceDir) {
|
|
3375
|
-
const entries = await
|
|
4114
|
+
const entries = await import_node_fs15.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3376
4115
|
const configs = [];
|
|
3377
4116
|
const seen = /* @__PURE__ */ new Set();
|
|
3378
4117
|
for (const entry of entries) {
|
|
3379
4118
|
if (!entry.isFile()) continue;
|
|
3380
4119
|
const match = isConfigFile(entry.name);
|
|
3381
4120
|
if (!match.match || match.fileType !== "env") continue;
|
|
3382
|
-
const filePath =
|
|
3383
|
-
const content = await
|
|
4121
|
+
const filePath = import_node_path17.default.join(serviceDir, entry.name);
|
|
4122
|
+
const content = await import_node_fs15.promises.readFile(filePath, "utf8");
|
|
3384
4123
|
for (const line of content.split("\n")) {
|
|
3385
4124
|
const parsed = parseDotenvLine(line);
|
|
3386
4125
|
if (!parsed) continue;
|
|
@@ -3399,9 +4138,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3399
4138
|
|
|
3400
4139
|
// src/extract/databases/prisma.ts
|
|
3401
4140
|
init_cjs_shims();
|
|
3402
|
-
var
|
|
4141
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3403
4142
|
async function parse3(serviceDir) {
|
|
3404
|
-
const schemaPath =
|
|
4143
|
+
const schemaPath = import_node_path18.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3405
4144
|
const content = await readIfExists(schemaPath);
|
|
3406
4145
|
if (!content) return [];
|
|
3407
4146
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3533,11 +4272,11 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3533
4272
|
|
|
3534
4273
|
// src/extract/databases/ormconfig.ts
|
|
3535
4274
|
init_cjs_shims();
|
|
3536
|
-
var
|
|
4275
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3537
4276
|
async function parse6(serviceDir) {
|
|
3538
4277
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3539
|
-
const abs =
|
|
3540
|
-
if (!await
|
|
4278
|
+
const abs = import_node_path19.default.join(serviceDir, candidate);
|
|
4279
|
+
if (!await exists2(abs)) continue;
|
|
3541
4280
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3542
4281
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
3543
4282
|
const out = [];
|
|
@@ -3596,10 +4335,10 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3596
4335
|
|
|
3597
4336
|
// src/extract/databases/sequelize.ts
|
|
3598
4337
|
init_cjs_shims();
|
|
3599
|
-
var
|
|
4338
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3600
4339
|
async function parse8(serviceDir) {
|
|
3601
|
-
const configPath =
|
|
3602
|
-
if (!await
|
|
4340
|
+
const configPath = import_node_path20.default.join(serviceDir, "config", "config.json");
|
|
4341
|
+
if (!await exists2(configPath)) return [];
|
|
3603
4342
|
const raw = await readJson(configPath);
|
|
3604
4343
|
const out = [];
|
|
3605
4344
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3625,7 +4364,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3625
4364
|
|
|
3626
4365
|
// src/extract/databases/docker-compose.ts
|
|
3627
4366
|
init_cjs_shims();
|
|
3628
|
-
var
|
|
4367
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3629
4368
|
function portFromService(svc) {
|
|
3630
4369
|
for (const raw of svc.ports ?? []) {
|
|
3631
4370
|
const str = String(raw);
|
|
@@ -3652,8 +4391,8 @@ function databaseFromEnv(svc) {
|
|
|
3652
4391
|
}
|
|
3653
4392
|
async function parse9(serviceDir) {
|
|
3654
4393
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3655
|
-
const abs =
|
|
3656
|
-
if (!await
|
|
4394
|
+
const abs = import_node_path21.default.join(serviceDir, name);
|
|
4395
|
+
if (!await exists2(abs)) continue;
|
|
3657
4396
|
const raw = await readYaml(abs);
|
|
3658
4397
|
if (!raw?.services) return [];
|
|
3659
4398
|
const out = [];
|
|
@@ -3693,8 +4432,8 @@ function compatibleDriversFor(engine) {
|
|
|
3693
4432
|
}
|
|
3694
4433
|
function toDatabaseNode(config) {
|
|
3695
4434
|
return {
|
|
3696
|
-
id: (0,
|
|
3697
|
-
type:
|
|
4435
|
+
id: (0, import_types10.databaseId)(config.host),
|
|
4436
|
+
type: import_types10.NodeType.DatabaseNode,
|
|
3698
4437
|
name: config.database || config.host,
|
|
3699
4438
|
engine: config.engine,
|
|
3700
4439
|
engineVersion: config.engineVersion,
|
|
@@ -3824,19 +4563,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3824
4563
|
discoveredVia: mergedDiscoveredVia
|
|
3825
4564
|
});
|
|
3826
4565
|
}
|
|
4566
|
+
const relConfigFile = toPosix2(import_node_path22.default.relative(service.dir, config.sourceFile));
|
|
4567
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4568
|
+
graph,
|
|
4569
|
+
service.pkg.name,
|
|
4570
|
+
service.node.id,
|
|
4571
|
+
relConfigFile
|
|
4572
|
+
);
|
|
4573
|
+
nodesAdded += fn;
|
|
4574
|
+
edgesAdded += fe;
|
|
4575
|
+
const evidenceFile = toPosix2(import_node_path22.default.relative(scanPath, config.sourceFile));
|
|
3827
4576
|
const edge = {
|
|
3828
|
-
id: (0, import_types5.extractedEdgeId)(
|
|
3829
|
-
source:
|
|
4577
|
+
id: (0, import_types5.extractedEdgeId)(fileNodeId, dbNode.id, import_types10.EdgeType.CONNECTS_TO),
|
|
4578
|
+
source: fileNodeId,
|
|
3830
4579
|
target: dbNode.id,
|
|
3831
|
-
type:
|
|
3832
|
-
provenance:
|
|
3833
|
-
confidence: (0,
|
|
3834
|
-
|
|
3835
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3836
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3837
|
-
evidence: {
|
|
3838
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3839
|
-
}
|
|
4580
|
+
type: import_types10.EdgeType.CONNECTS_TO,
|
|
4581
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
4582
|
+
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4583
|
+
evidence: { file: evidenceFile }
|
|
3840
4584
|
};
|
|
3841
4585
|
if (!graph.hasEdge(edge.id)) {
|
|
3842
4586
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3862,15 +4606,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3862
4606
|
|
|
3863
4607
|
// src/extract/configs.ts
|
|
3864
4608
|
init_cjs_shims();
|
|
3865
|
-
var
|
|
3866
|
-
var
|
|
3867
|
-
var
|
|
4609
|
+
var import_node_fs16 = require("fs");
|
|
4610
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
4611
|
+
var import_types11 = require("@neat.is/types");
|
|
3868
4612
|
async function walkConfigFiles(dir) {
|
|
3869
4613
|
const out = [];
|
|
3870
4614
|
async function walk(current) {
|
|
3871
|
-
const entries = await
|
|
4615
|
+
const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true });
|
|
3872
4616
|
for (const entry of entries) {
|
|
3873
|
-
const full =
|
|
4617
|
+
const full = import_node_path23.default.join(current, entry.name);
|
|
3874
4618
|
if (entry.isDirectory()) {
|
|
3875
4619
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3876
4620
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3889,26 +4633,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3889
4633
|
for (const service of services) {
|
|
3890
4634
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3891
4635
|
for (const file of configFiles) {
|
|
3892
|
-
const relPath =
|
|
4636
|
+
const relPath = import_node_path23.default.relative(scanPath, file);
|
|
3893
4637
|
const node = {
|
|
3894
|
-
id: (0,
|
|
3895
|
-
type:
|
|
3896
|
-
name:
|
|
4638
|
+
id: (0, import_types11.configId)(relPath),
|
|
4639
|
+
type: import_types11.NodeType.ConfigNode,
|
|
4640
|
+
name: import_node_path23.default.basename(file),
|
|
3897
4641
|
path: relPath,
|
|
3898
|
-
fileType: isConfigFile(
|
|
4642
|
+
fileType: isConfigFile(import_node_path23.default.basename(file)).fileType
|
|
3899
4643
|
};
|
|
3900
4644
|
if (!graph.hasNode(node.id)) {
|
|
3901
4645
|
graph.addNode(node.id, node);
|
|
3902
4646
|
nodesAdded++;
|
|
3903
4647
|
}
|
|
4648
|
+
const relToService = toPosix2(import_node_path23.default.relative(service.dir, file));
|
|
4649
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4650
|
+
graph,
|
|
4651
|
+
service.pkg.name,
|
|
4652
|
+
service.node.id,
|
|
4653
|
+
relToService
|
|
4654
|
+
);
|
|
4655
|
+
nodesAdded += fn;
|
|
4656
|
+
edgesAdded += fe;
|
|
3904
4657
|
const edge = {
|
|
3905
|
-
id: (0, import_types5.extractedEdgeId)(
|
|
3906
|
-
source:
|
|
4658
|
+
id: (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types11.EdgeType.CONFIGURED_BY),
|
|
4659
|
+
source: fileNodeId,
|
|
3907
4660
|
target: node.id,
|
|
3908
|
-
type:
|
|
3909
|
-
provenance:
|
|
3910
|
-
confidence: (0,
|
|
3911
|
-
evidence: { file: relPath.split(
|
|
4661
|
+
type: import_types11.EdgeType.CONFIGURED_BY,
|
|
4662
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
4663
|
+
confidence: (0, import_types11.confidenceForExtracted)("structural"),
|
|
4664
|
+
evidence: { file: relPath.split(import_node_path23.default.sep).join("/") }
|
|
3912
4665
|
};
|
|
3913
4666
|
if (!graph.hasEdge(edge.id)) {
|
|
3914
4667
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3921,114 +4674,15 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3921
4674
|
|
|
3922
4675
|
// src/extract/calls/index.ts
|
|
3923
4676
|
init_cjs_shims();
|
|
3924
|
-
var
|
|
4677
|
+
var import_types17 = require("@neat.is/types");
|
|
3925
4678
|
|
|
3926
4679
|
// src/extract/calls/http.ts
|
|
3927
4680
|
init_cjs_shims();
|
|
3928
|
-
var
|
|
3929
|
-
var
|
|
3930
|
-
var
|
|
3931
|
-
var
|
|
3932
|
-
var
|
|
3933
|
-
|
|
3934
|
-
// src/extract/calls/shared.ts
|
|
3935
|
-
init_cjs_shims();
|
|
3936
|
-
var import_node_fs13 = require("fs");
|
|
3937
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3938
|
-
var import_types10 = require("@neat.is/types");
|
|
3939
|
-
async function walkSourceFiles(dir) {
|
|
3940
|
-
const out = [];
|
|
3941
|
-
async function walk(current) {
|
|
3942
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3943
|
-
for (const entry of entries) {
|
|
3944
|
-
const full = import_node_path19.default.join(current, entry.name);
|
|
3945
|
-
if (entry.isDirectory()) {
|
|
3946
|
-
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3947
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3948
|
-
await walk(full);
|
|
3949
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3950
|
-
out.push(full);
|
|
3951
|
-
}
|
|
3952
|
-
}
|
|
3953
|
-
}
|
|
3954
|
-
await walk(dir);
|
|
3955
|
-
return out;
|
|
3956
|
-
}
|
|
3957
|
-
async function loadSourceFiles(dir) {
|
|
3958
|
-
const paths = await walkSourceFiles(dir);
|
|
3959
|
-
const out = [];
|
|
3960
|
-
for (const p of paths) {
|
|
3961
|
-
try {
|
|
3962
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3963
|
-
out.push({ path: p, content });
|
|
3964
|
-
} catch {
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3967
|
-
return out;
|
|
3968
|
-
}
|
|
3969
|
-
function lineOf(text, needle) {
|
|
3970
|
-
const idx = text.indexOf(needle);
|
|
3971
|
-
if (idx < 0) return 1;
|
|
3972
|
-
return text.slice(0, idx).split("\n").length;
|
|
3973
|
-
}
|
|
3974
|
-
function snippet(text, line) {
|
|
3975
|
-
const lines = text.split("\n");
|
|
3976
|
-
return (lines[line - 1] ?? "").trim();
|
|
3977
|
-
}
|
|
3978
|
-
function toPosix2(p) {
|
|
3979
|
-
return p.split("\\").join("/");
|
|
3980
|
-
}
|
|
3981
|
-
function languageForPath(relPath) {
|
|
3982
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3983
|
-
case ".py":
|
|
3984
|
-
return "python";
|
|
3985
|
-
case ".ts":
|
|
3986
|
-
case ".tsx":
|
|
3987
|
-
return "typescript";
|
|
3988
|
-
case ".js":
|
|
3989
|
-
case ".jsx":
|
|
3990
|
-
case ".mjs":
|
|
3991
|
-
case ".cjs":
|
|
3992
|
-
return "javascript";
|
|
3993
|
-
default:
|
|
3994
|
-
return void 0;
|
|
3995
|
-
}
|
|
3996
|
-
}
|
|
3997
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3998
|
-
let nodesAdded = 0;
|
|
3999
|
-
let edgesAdded = 0;
|
|
4000
|
-
const fileNodeId = (0, import_types10.fileId)(serviceName, relPath);
|
|
4001
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
4002
|
-
const language = languageForPath(relPath);
|
|
4003
|
-
const node = {
|
|
4004
|
-
id: fileNodeId,
|
|
4005
|
-
type: import_types10.NodeType.FileNode,
|
|
4006
|
-
service: serviceName,
|
|
4007
|
-
path: relPath,
|
|
4008
|
-
...language ? { language } : {},
|
|
4009
|
-
discoveredVia: "static"
|
|
4010
|
-
};
|
|
4011
|
-
graph.addNode(fileNodeId, node);
|
|
4012
|
-
nodesAdded++;
|
|
4013
|
-
}
|
|
4014
|
-
const containsId = (0, import_types10.extractedEdgeId)(serviceNodeId, fileNodeId, import_types10.EdgeType.CONTAINS);
|
|
4015
|
-
if (!graph.hasEdge(containsId)) {
|
|
4016
|
-
const edge = {
|
|
4017
|
-
id: containsId,
|
|
4018
|
-
source: serviceNodeId,
|
|
4019
|
-
target: fileNodeId,
|
|
4020
|
-
type: import_types10.EdgeType.CONTAINS,
|
|
4021
|
-
provenance: import_types10.Provenance.EXTRACTED,
|
|
4022
|
-
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4023
|
-
evidence: { file: relPath }
|
|
4024
|
-
};
|
|
4025
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
4026
|
-
edgesAdded++;
|
|
4027
|
-
}
|
|
4028
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
4029
|
-
}
|
|
4030
|
-
|
|
4031
|
-
// src/extract/calls/http.ts
|
|
4681
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4682
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
4683
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4684
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4685
|
+
var import_types12 = require("@neat.is/types");
|
|
4032
4686
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
4033
4687
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
4034
4688
|
function isInsideJsxExternalLink(node) {
|
|
@@ -4056,14 +4710,14 @@ function collectStringLiterals(node, out) {
|
|
|
4056
4710
|
if (child) collectStringLiterals(child, out);
|
|
4057
4711
|
}
|
|
4058
4712
|
}
|
|
4059
|
-
var
|
|
4060
|
-
function
|
|
4713
|
+
var PARSE_CHUNK2 = 16384;
|
|
4714
|
+
function parseSource2(parser, source) {
|
|
4061
4715
|
return parser.parse(
|
|
4062
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
4716
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
4063
4717
|
);
|
|
4064
4718
|
}
|
|
4065
4719
|
function callsFromSource(source, parser, knownHosts) {
|
|
4066
|
-
const tree =
|
|
4720
|
+
const tree = parseSource2(parser, source);
|
|
4067
4721
|
const literals = [];
|
|
4068
4722
|
collectStringLiterals(tree.rootNode, literals);
|
|
4069
4723
|
const out = [];
|
|
@@ -4077,25 +4731,25 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
4077
4731
|
}
|
|
4078
4732
|
return out;
|
|
4079
4733
|
}
|
|
4080
|
-
function
|
|
4081
|
-
const p = new
|
|
4082
|
-
p.setLanguage(
|
|
4734
|
+
function makeJsParser2() {
|
|
4735
|
+
const p = new import_tree_sitter2.default();
|
|
4736
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
4083
4737
|
return p;
|
|
4084
4738
|
}
|
|
4085
|
-
function
|
|
4086
|
-
const p = new
|
|
4087
|
-
p.setLanguage(
|
|
4739
|
+
function makePyParser2() {
|
|
4740
|
+
const p = new import_tree_sitter2.default();
|
|
4741
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
4088
4742
|
return p;
|
|
4089
4743
|
}
|
|
4090
4744
|
async function addHttpCallEdges(graph, services) {
|
|
4091
|
-
const jsParser =
|
|
4092
|
-
const pyParser =
|
|
4745
|
+
const jsParser = makeJsParser2();
|
|
4746
|
+
const pyParser = makePyParser2();
|
|
4093
4747
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
4094
4748
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4095
4749
|
for (const service of services) {
|
|
4096
|
-
knownHosts.add(
|
|
4750
|
+
knownHosts.add(import_node_path24.default.basename(service.dir));
|
|
4097
4751
|
knownHosts.add(service.pkg.name);
|
|
4098
|
-
hostToNodeId.set(
|
|
4752
|
+
hostToNodeId.set(import_node_path24.default.basename(service.dir), service.node.id);
|
|
4099
4753
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4100
4754
|
}
|
|
4101
4755
|
let nodesAdded = 0;
|
|
@@ -4105,7 +4759,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4105
4759
|
const seen = /* @__PURE__ */ new Set();
|
|
4106
4760
|
for (const file of files) {
|
|
4107
4761
|
if (isTestPath(file.path)) continue;
|
|
4108
|
-
const parser =
|
|
4762
|
+
const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
4109
4763
|
let sites;
|
|
4110
4764
|
try {
|
|
4111
4765
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -4114,14 +4768,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4114
4768
|
continue;
|
|
4115
4769
|
}
|
|
4116
4770
|
if (sites.length === 0) continue;
|
|
4117
|
-
const relFile = toPosix2(
|
|
4771
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
4118
4772
|
for (const site of sites) {
|
|
4119
4773
|
const targetId = hostToNodeId.get(site.host);
|
|
4120
4774
|
if (!targetId || targetId === service.node.id) continue;
|
|
4121
4775
|
const dedupKey = `${relFile}|${targetId}`;
|
|
4122
4776
|
if (seen.has(dedupKey)) continue;
|
|
4123
4777
|
seen.add(dedupKey);
|
|
4124
|
-
const confidence = (0,
|
|
4778
|
+
const confidence = (0, import_types12.confidenceForExtracted)("hostname-shape-match");
|
|
4125
4779
|
const ev = {
|
|
4126
4780
|
file: relFile,
|
|
4127
4781
|
line: site.line,
|
|
@@ -4135,25 +4789,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4135
4789
|
);
|
|
4136
4790
|
nodesAdded += n;
|
|
4137
4791
|
edgesAdded += e;
|
|
4138
|
-
if (!(0,
|
|
4792
|
+
if (!(0, import_types12.passesExtractedFloor)(confidence)) {
|
|
4139
4793
|
noteExtractedDropped({
|
|
4140
4794
|
source: fileNodeId,
|
|
4141
4795
|
target: targetId,
|
|
4142
|
-
type:
|
|
4796
|
+
type: import_types12.EdgeType.CALLS,
|
|
4143
4797
|
confidence,
|
|
4144
4798
|
confidenceKind: "hostname-shape-match",
|
|
4145
4799
|
evidence: ev
|
|
4146
4800
|
});
|
|
4147
4801
|
continue;
|
|
4148
4802
|
}
|
|
4149
|
-
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId,
|
|
4803
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
|
|
4150
4804
|
if (!graph.hasEdge(edgeId)) {
|
|
4151
4805
|
const edge = {
|
|
4152
4806
|
id: edgeId,
|
|
4153
4807
|
source: fileNodeId,
|
|
4154
4808
|
target: targetId,
|
|
4155
|
-
type:
|
|
4156
|
-
provenance:
|
|
4809
|
+
type: import_types12.EdgeType.CALLS,
|
|
4810
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
4157
4811
|
confidence,
|
|
4158
4812
|
evidence: ev
|
|
4159
4813
|
};
|
|
@@ -4168,8 +4822,8 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4168
4822
|
|
|
4169
4823
|
// src/extract/calls/kafka.ts
|
|
4170
4824
|
init_cjs_shims();
|
|
4171
|
-
var
|
|
4172
|
-
var
|
|
4825
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
4826
|
+
var import_types13 = require("@neat.is/types");
|
|
4173
4827
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4174
4828
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
4175
4829
|
function findAll(re, text) {
|
|
@@ -4190,7 +4844,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4190
4844
|
seen.add(key);
|
|
4191
4845
|
const line = lineOf(file.content, topic);
|
|
4192
4846
|
out.push({
|
|
4193
|
-
infraId: (0,
|
|
4847
|
+
infraId: (0, import_types13.infraId)("kafka-topic", topic),
|
|
4194
4848
|
name: topic,
|
|
4195
4849
|
kind: "kafka-topic",
|
|
4196
4850
|
edgeType,
|
|
@@ -4199,7 +4853,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4199
4853
|
// tier (ADR-066).
|
|
4200
4854
|
confidenceKind: "verified-call-site",
|
|
4201
4855
|
evidence: {
|
|
4202
|
-
file:
|
|
4856
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
4203
4857
|
line,
|
|
4204
4858
|
snippet: snippet(file.content, line)
|
|
4205
4859
|
}
|
|
@@ -4212,8 +4866,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4212
4866
|
|
|
4213
4867
|
// src/extract/calls/redis.ts
|
|
4214
4868
|
init_cjs_shims();
|
|
4215
|
-
var
|
|
4216
|
-
var
|
|
4869
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
4870
|
+
var import_types14 = require("@neat.is/types");
|
|
4217
4871
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
4218
4872
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
4219
4873
|
const out = [];
|
|
@@ -4226,7 +4880,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4226
4880
|
seen.add(host);
|
|
4227
4881
|
const line = lineOf(file.content, host);
|
|
4228
4882
|
out.push({
|
|
4229
|
-
infraId: (0,
|
|
4883
|
+
infraId: (0, import_types14.infraId)("redis", host),
|
|
4230
4884
|
name: host,
|
|
4231
4885
|
kind: "redis",
|
|
4232
4886
|
edgeType: "CALLS",
|
|
@@ -4235,7 +4889,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4235
4889
|
// support tier (ADR-066).
|
|
4236
4890
|
confidenceKind: "url-with-structural-support",
|
|
4237
4891
|
evidence: {
|
|
4238
|
-
file:
|
|
4892
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
4239
4893
|
line,
|
|
4240
4894
|
snippet: snippet(file.content, line)
|
|
4241
4895
|
}
|
|
@@ -4246,8 +4900,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4246
4900
|
|
|
4247
4901
|
// src/extract/calls/aws.ts
|
|
4248
4902
|
init_cjs_shims();
|
|
4249
|
-
var
|
|
4250
|
-
var
|
|
4903
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
4904
|
+
var import_types15 = require("@neat.is/types");
|
|
4251
4905
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4252
4906
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4253
4907
|
function hasMarker(text, markers) {
|
|
@@ -4271,7 +4925,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4271
4925
|
seen.add(key);
|
|
4272
4926
|
const line = lineOf(file.content, name);
|
|
4273
4927
|
out.push({
|
|
4274
|
-
infraId: (0,
|
|
4928
|
+
infraId: (0, import_types15.infraId)(kind, name),
|
|
4275
4929
|
name,
|
|
4276
4930
|
kind,
|
|
4277
4931
|
edgeType: "CALLS",
|
|
@@ -4280,7 +4934,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4280
4934
|
// (ADR-066).
|
|
4281
4935
|
confidenceKind: "verified-call-site",
|
|
4282
4936
|
evidence: {
|
|
4283
|
-
file:
|
|
4937
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
4284
4938
|
line,
|
|
4285
4939
|
snippet: snippet(file.content, line)
|
|
4286
4940
|
}
|
|
@@ -4305,13 +4959,14 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4305
4959
|
|
|
4306
4960
|
// src/extract/calls/grpc.ts
|
|
4307
4961
|
init_cjs_shims();
|
|
4308
|
-
var
|
|
4309
|
-
var
|
|
4962
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
4963
|
+
var import_types16 = require("@neat.is/types");
|
|
4310
4964
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4311
4965
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4312
4966
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4313
4967
|
function isLikelyAddress(value) {
|
|
4314
4968
|
if (!value) return false;
|
|
4969
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4315
4970
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4316
4971
|
}
|
|
4317
4972
|
function normaliseForMatch(s) {
|
|
@@ -4354,7 +5009,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4354
5009
|
const { kind } = classified;
|
|
4355
5010
|
const line = lineOf(file.content, m[0]);
|
|
4356
5011
|
out.push({
|
|
4357
|
-
infraId: (0,
|
|
5012
|
+
infraId: (0, import_types16.infraId)(kind, name),
|
|
4358
5013
|
name,
|
|
4359
5014
|
kind,
|
|
4360
5015
|
edgeType: "CALLS",
|
|
@@ -4363,7 +5018,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4363
5018
|
// tier (ADR-066).
|
|
4364
5019
|
confidenceKind: "verified-call-site",
|
|
4365
5020
|
evidence: {
|
|
4366
|
-
file:
|
|
5021
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
4367
5022
|
line,
|
|
4368
5023
|
snippet: snippet(file.content, line)
|
|
4369
5024
|
}
|
|
@@ -4376,11 +5031,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4376
5031
|
function edgeTypeFromEndpoint(ep) {
|
|
4377
5032
|
switch (ep.edgeType) {
|
|
4378
5033
|
case "PUBLISHES_TO":
|
|
4379
|
-
return
|
|
5034
|
+
return import_types17.EdgeType.PUBLISHES_TO;
|
|
4380
5035
|
case "CONSUMES_FROM":
|
|
4381
|
-
return
|
|
5036
|
+
return import_types17.EdgeType.CONSUMES_FROM;
|
|
4382
5037
|
default:
|
|
4383
|
-
return
|
|
5038
|
+
return import_types17.EdgeType.CALLS;
|
|
4384
5039
|
}
|
|
4385
5040
|
}
|
|
4386
5041
|
function isAwsKind(kind) {
|
|
@@ -4407,7 +5062,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4407
5062
|
if (!graph.hasNode(ep.infraId)) {
|
|
4408
5063
|
const node = {
|
|
4409
5064
|
id: ep.infraId,
|
|
4410
|
-
type:
|
|
5065
|
+
type: import_types17.NodeType.InfraNode,
|
|
4411
5066
|
name: ep.name,
|
|
4412
5067
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4413
5068
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4419,7 +5074,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4419
5074
|
nodesAdded++;
|
|
4420
5075
|
}
|
|
4421
5076
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4422
|
-
const confidence = (0,
|
|
5077
|
+
const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
|
|
4423
5078
|
const relFile = toPosix2(ep.evidence.file);
|
|
4424
5079
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4425
5080
|
graph,
|
|
@@ -4429,7 +5084,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4429
5084
|
);
|
|
4430
5085
|
nodesAdded += n;
|
|
4431
5086
|
edgesAdded += e;
|
|
4432
|
-
if (!(0,
|
|
5087
|
+
if (!(0, import_types17.passesExtractedFloor)(confidence)) {
|
|
4433
5088
|
noteExtractedDropped({
|
|
4434
5089
|
source: fileNodeId,
|
|
4435
5090
|
target: ep.infraId,
|
|
@@ -4449,7 +5104,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4449
5104
|
source: fileNodeId,
|
|
4450
5105
|
target: ep.infraId,
|
|
4451
5106
|
type: edgeType,
|
|
4452
|
-
provenance:
|
|
5107
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
4453
5108
|
confidence,
|
|
4454
5109
|
evidence: ep.evidence
|
|
4455
5110
|
};
|
|
@@ -4474,16 +5129,16 @@ init_cjs_shims();
|
|
|
4474
5129
|
|
|
4475
5130
|
// src/extract/infra/docker-compose.ts
|
|
4476
5131
|
init_cjs_shims();
|
|
4477
|
-
var
|
|
4478
|
-
var
|
|
5132
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
5133
|
+
var import_types19 = require("@neat.is/types");
|
|
4479
5134
|
|
|
4480
5135
|
// src/extract/infra/shared.ts
|
|
4481
5136
|
init_cjs_shims();
|
|
4482
|
-
var
|
|
5137
|
+
var import_types18 = require("@neat.is/types");
|
|
4483
5138
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4484
5139
|
return {
|
|
4485
|
-
id: (0,
|
|
4486
|
-
type:
|
|
5140
|
+
id: (0, import_types18.infraId)(kind, name),
|
|
5141
|
+
type: import_types18.NodeType.InfraNode,
|
|
4487
5142
|
name,
|
|
4488
5143
|
provider,
|
|
4489
5144
|
kind,
|
|
@@ -4512,7 +5167,7 @@ function dependsOnList(value) {
|
|
|
4512
5167
|
}
|
|
4513
5168
|
function serviceNameToServiceNode(name, services) {
|
|
4514
5169
|
for (const s of services) {
|
|
4515
|
-
if (s.node.name === name ||
|
|
5170
|
+
if (s.node.name === name || import_node_path29.default.basename(s.dir) === name) return s.node.id;
|
|
4516
5171
|
}
|
|
4517
5172
|
return null;
|
|
4518
5173
|
}
|
|
@@ -4521,8 +5176,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4521
5176
|
let edgesAdded = 0;
|
|
4522
5177
|
let composePath = null;
|
|
4523
5178
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4524
|
-
const abs =
|
|
4525
|
-
if (await
|
|
5179
|
+
const abs = import_node_path29.default.join(scanPath, name);
|
|
5180
|
+
if (await exists2(abs)) {
|
|
4526
5181
|
composePath = abs;
|
|
4527
5182
|
break;
|
|
4528
5183
|
}
|
|
@@ -4534,13 +5189,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4534
5189
|
} catch (err) {
|
|
4535
5190
|
recordExtractionError(
|
|
4536
5191
|
"infra docker-compose",
|
|
4537
|
-
|
|
5192
|
+
import_node_path29.default.relative(scanPath, composePath),
|
|
4538
5193
|
err
|
|
4539
5194
|
);
|
|
4540
5195
|
return { nodesAdded, edgesAdded };
|
|
4541
5196
|
}
|
|
4542
5197
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4543
|
-
const evidenceFile =
|
|
5198
|
+
const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
|
|
4544
5199
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4545
5200
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4546
5201
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4562,15 +5217,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4562
5217
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4563
5218
|
const targetId = composeNameToNodeId.get(dep);
|
|
4564
5219
|
if (!targetId) continue;
|
|
4565
|
-
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId,
|
|
5220
|
+
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
|
|
4566
5221
|
if (graph.hasEdge(edgeId)) continue;
|
|
4567
5222
|
const edge = {
|
|
4568
5223
|
id: edgeId,
|
|
4569
5224
|
source: sourceId,
|
|
4570
5225
|
target: targetId,
|
|
4571
|
-
type:
|
|
4572
|
-
provenance:
|
|
4573
|
-
confidence: (0,
|
|
5226
|
+
type: import_types19.EdgeType.DEPENDS_ON,
|
|
5227
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
5228
|
+
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4574
5229
|
evidence: { file: evidenceFile }
|
|
4575
5230
|
};
|
|
4576
5231
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4582,9 +5237,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4582
5237
|
|
|
4583
5238
|
// src/extract/infra/dockerfile.ts
|
|
4584
5239
|
init_cjs_shims();
|
|
4585
|
-
var
|
|
4586
|
-
var
|
|
4587
|
-
var
|
|
5240
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
5241
|
+
var import_node_fs17 = require("fs");
|
|
5242
|
+
var import_types20 = require("@neat.is/types");
|
|
4588
5243
|
function runtimeImage(content) {
|
|
4589
5244
|
const lines = content.split("\n");
|
|
4590
5245
|
let last = null;
|
|
@@ -4603,15 +5258,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4603
5258
|
let nodesAdded = 0;
|
|
4604
5259
|
let edgesAdded = 0;
|
|
4605
5260
|
for (const service of services) {
|
|
4606
|
-
const dockerfilePath =
|
|
4607
|
-
if (!await
|
|
5261
|
+
const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
|
|
5262
|
+
if (!await exists2(dockerfilePath)) continue;
|
|
4608
5263
|
let content;
|
|
4609
5264
|
try {
|
|
4610
|
-
content = await
|
|
5265
|
+
content = await import_node_fs17.promises.readFile(dockerfilePath, "utf8");
|
|
4611
5266
|
} catch (err) {
|
|
4612
5267
|
recordExtractionError(
|
|
4613
5268
|
"infra dockerfile",
|
|
4614
|
-
|
|
5269
|
+
import_node_path30.default.relative(scanPath, dockerfilePath),
|
|
4615
5270
|
err
|
|
4616
5271
|
);
|
|
4617
5272
|
continue;
|
|
@@ -4623,17 +5278,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4623
5278
|
graph.addNode(node.id, node);
|
|
4624
5279
|
nodesAdded++;
|
|
4625
5280
|
}
|
|
4626
|
-
const
|
|
5281
|
+
const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
|
|
5282
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5283
|
+
graph,
|
|
5284
|
+
service.pkg.name,
|
|
5285
|
+
service.node.id,
|
|
5286
|
+
relDockerfile
|
|
5287
|
+
);
|
|
5288
|
+
nodesAdded += fn;
|
|
5289
|
+
edgesAdded += fe;
|
|
5290
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
|
|
4627
5291
|
if (!graph.hasEdge(edgeId)) {
|
|
4628
5292
|
const edge = {
|
|
4629
5293
|
id: edgeId,
|
|
4630
|
-
source:
|
|
5294
|
+
source: fileNodeId,
|
|
4631
5295
|
target: node.id,
|
|
4632
|
-
type:
|
|
4633
|
-
provenance:
|
|
4634
|
-
confidence: (0,
|
|
5296
|
+
type: import_types20.EdgeType.RUNS_ON,
|
|
5297
|
+
provenance: import_types20.Provenance.EXTRACTED,
|
|
5298
|
+
confidence: (0, import_types20.confidenceForExtracted)("structural"),
|
|
4635
5299
|
evidence: {
|
|
4636
|
-
file:
|
|
5300
|
+
file: toPosix2(import_node_path30.default.relative(scanPath, dockerfilePath))
|
|
4637
5301
|
}
|
|
4638
5302
|
};
|
|
4639
5303
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4645,21 +5309,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4645
5309
|
|
|
4646
5310
|
// src/extract/infra/terraform.ts
|
|
4647
5311
|
init_cjs_shims();
|
|
4648
|
-
var
|
|
4649
|
-
var
|
|
5312
|
+
var import_node_fs18 = require("fs");
|
|
5313
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4650
5314
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4651
5315
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4652
5316
|
if (depth > max) return [];
|
|
4653
5317
|
const out = [];
|
|
4654
|
-
const entries = await
|
|
5318
|
+
const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4655
5319
|
for (const entry of entries) {
|
|
4656
5320
|
if (entry.isDirectory()) {
|
|
4657
5321
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
4658
|
-
const child =
|
|
5322
|
+
const child = import_node_path31.default.join(start, entry.name);
|
|
4659
5323
|
if (await isPythonVenvDir(child)) continue;
|
|
4660
5324
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4661
5325
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
4662
|
-
out.push(
|
|
5326
|
+
out.push(import_node_path31.default.join(start, entry.name));
|
|
4663
5327
|
}
|
|
4664
5328
|
}
|
|
4665
5329
|
return out;
|
|
@@ -4668,7 +5332,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4668
5332
|
let nodesAdded = 0;
|
|
4669
5333
|
const files = await walkTfFiles(scanPath);
|
|
4670
5334
|
for (const file of files) {
|
|
4671
|
-
const content = await
|
|
5335
|
+
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
4672
5336
|
RESOURCE_RE.lastIndex = 0;
|
|
4673
5337
|
let m;
|
|
4674
5338
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -4686,8 +5350,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4686
5350
|
|
|
4687
5351
|
// src/extract/infra/k8s.ts
|
|
4688
5352
|
init_cjs_shims();
|
|
4689
|
-
var
|
|
4690
|
-
var
|
|
5353
|
+
var import_node_fs19 = require("fs");
|
|
5354
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4691
5355
|
var import_yaml3 = require("yaml");
|
|
4692
5356
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4693
5357
|
Service: "k8s-service",
|
|
@@ -4701,15 +5365,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4701
5365
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4702
5366
|
if (depth > max) return [];
|
|
4703
5367
|
const out = [];
|
|
4704
|
-
const entries = await
|
|
5368
|
+
const entries = await import_node_fs19.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4705
5369
|
for (const entry of entries) {
|
|
4706
5370
|
if (entry.isDirectory()) {
|
|
4707
5371
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4708
|
-
const child =
|
|
5372
|
+
const child = import_node_path32.default.join(start, entry.name);
|
|
4709
5373
|
if (await isPythonVenvDir(child)) continue;
|
|
4710
5374
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4711
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4712
|
-
out.push(
|
|
5375
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path32.default.extname(entry.name))) {
|
|
5376
|
+
out.push(import_node_path32.default.join(start, entry.name));
|
|
4713
5377
|
}
|
|
4714
5378
|
}
|
|
4715
5379
|
return out;
|
|
@@ -4718,7 +5382,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4718
5382
|
let nodesAdded = 0;
|
|
4719
5383
|
const files = await walkYamlFiles2(scanPath);
|
|
4720
5384
|
for (const file of files) {
|
|
4721
|
-
const content = await
|
|
5385
|
+
const content = await import_node_fs19.promises.readFile(file, "utf8");
|
|
4722
5386
|
let docs;
|
|
4723
5387
|
try {
|
|
4724
5388
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -4753,17 +5417,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4753
5417
|
}
|
|
4754
5418
|
|
|
4755
5419
|
// src/extract/index.ts
|
|
4756
|
-
var
|
|
5420
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
4757
5421
|
|
|
4758
5422
|
// src/extract/retire.ts
|
|
4759
5423
|
init_cjs_shims();
|
|
4760
|
-
var
|
|
4761
|
-
var
|
|
4762
|
-
var
|
|
5424
|
+
var import_node_fs20 = require("fs");
|
|
5425
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
5426
|
+
var import_types21 = require("@neat.is/types");
|
|
4763
5427
|
function dropOrphanedFileNodes(graph) {
|
|
4764
5428
|
const orphans = [];
|
|
4765
5429
|
graph.forEachNode((id, attrs) => {
|
|
4766
|
-
if (attrs.type !==
|
|
5430
|
+
if (attrs.type !== import_types21.NodeType.FileNode) return;
|
|
4767
5431
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4768
5432
|
orphans.push(id);
|
|
4769
5433
|
}
|
|
@@ -4776,14 +5440,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4776
5440
|
const bases = [scanPath, ...serviceDirs];
|
|
4777
5441
|
graph.forEachEdge((id, attrs) => {
|
|
4778
5442
|
const edge = attrs;
|
|
4779
|
-
if (edge.provenance !==
|
|
5443
|
+
if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
|
|
4780
5444
|
const evidenceFile = edge.evidence?.file;
|
|
4781
5445
|
if (!evidenceFile) return;
|
|
4782
|
-
if (
|
|
4783
|
-
if (!(0,
|
|
5446
|
+
if (import_node_path33.default.isAbsolute(evidenceFile)) {
|
|
5447
|
+
if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
|
|
4784
5448
|
return;
|
|
4785
5449
|
}
|
|
4786
|
-
const found = bases.some((base) => (0,
|
|
5450
|
+
const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path33.default.join(base, evidenceFile)));
|
|
4787
5451
|
if (!found) toDrop.push(id);
|
|
4788
5452
|
});
|
|
4789
5453
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4798,6 +5462,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4798
5462
|
const services = await discoverServices(scanPath);
|
|
4799
5463
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
4800
5464
|
await addServiceAliases(graph, scanPath, services);
|
|
5465
|
+
const fileEnum = await addFiles(graph, services);
|
|
5466
|
+
const importGraph = await addImports(graph, services);
|
|
4801
5467
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
4802
5468
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
4803
5469
|
const phase4 = await addCallEdges(graph, services);
|
|
@@ -4821,7 +5487,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4821
5487
|
}
|
|
4822
5488
|
const droppedEntries = drainDroppedExtracted();
|
|
4823
5489
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4824
|
-
const rejectedPath =
|
|
5490
|
+
const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4825
5491
|
try {
|
|
4826
5492
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4827
5493
|
} catch (err) {
|
|
@@ -4831,8 +5497,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4831
5497
|
}
|
|
4832
5498
|
}
|
|
4833
5499
|
const result = {
|
|
4834
|
-
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4835
|
-
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5500
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
5501
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4836
5502
|
frontiersPromoted,
|
|
4837
5503
|
extractionErrors: errorEntries.length,
|
|
4838
5504
|
errorEntries,
|
|
@@ -4855,7 +5521,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4855
5521
|
|
|
4856
5522
|
// src/diff.ts
|
|
4857
5523
|
init_cjs_shims();
|
|
4858
|
-
var
|
|
5524
|
+
var import_node_fs21 = require("fs");
|
|
4859
5525
|
async function loadSnapshotForDiff(target) {
|
|
4860
5526
|
if (/^https?:\/\//i.test(target)) {
|
|
4861
5527
|
const res = await fetch(target);
|
|
@@ -4864,7 +5530,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4864
5530
|
}
|
|
4865
5531
|
return await res.json();
|
|
4866
5532
|
}
|
|
4867
|
-
const raw = await
|
|
5533
|
+
const raw = await import_node_fs21.promises.readFile(target, "utf8");
|
|
4868
5534
|
return JSON.parse(raw);
|
|
4869
5535
|
}
|
|
4870
5536
|
function indexEntries(entries) {
|
|
@@ -4932,9 +5598,9 @@ function canonicalJson(value) {
|
|
|
4932
5598
|
|
|
4933
5599
|
// src/persist.ts
|
|
4934
5600
|
init_cjs_shims();
|
|
4935
|
-
var
|
|
4936
|
-
var
|
|
4937
|
-
var
|
|
5601
|
+
var import_node_fs22 = require("fs");
|
|
5602
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5603
|
+
var import_types22 = require("@neat.is/types");
|
|
4938
5604
|
var SCHEMA_VERSION = 4;
|
|
4939
5605
|
function migrateV1ToV2(payload) {
|
|
4940
5606
|
const nodes = payload.graph.nodes;
|
|
@@ -4956,12 +5622,12 @@ function migrateV2ToV3(payload) {
|
|
|
4956
5622
|
for (const edge of edges) {
|
|
4957
5623
|
const attrs = edge.attributes;
|
|
4958
5624
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4959
|
-
attrs.provenance =
|
|
5625
|
+
attrs.provenance = import_types22.Provenance.OBSERVED;
|
|
4960
5626
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4961
5627
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4962
5628
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4963
5629
|
if (type && source && target) {
|
|
4964
|
-
const newId = (0,
|
|
5630
|
+
const newId = (0, import_types22.observedEdgeId)(source, target, type);
|
|
4965
5631
|
attrs.id = newId;
|
|
4966
5632
|
if (edge.key) edge.key = newId;
|
|
4967
5633
|
}
|
|
@@ -4970,7 +5636,7 @@ function migrateV2ToV3(payload) {
|
|
|
4970
5636
|
return { ...payload, schemaVersion: 3 };
|
|
4971
5637
|
}
|
|
4972
5638
|
async function ensureDir(filePath) {
|
|
4973
|
-
await
|
|
5639
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
|
|
4974
5640
|
}
|
|
4975
5641
|
async function saveGraphToDisk(graph, outPath) {
|
|
4976
5642
|
await ensureDir(outPath);
|
|
@@ -4980,13 +5646,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4980
5646
|
graph: graph.export()
|
|
4981
5647
|
};
|
|
4982
5648
|
const tmp = `${outPath}.tmp`;
|
|
4983
|
-
await
|
|
4984
|
-
await
|
|
5649
|
+
await import_node_fs22.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
5650
|
+
await import_node_fs22.promises.rename(tmp, outPath);
|
|
4985
5651
|
}
|
|
4986
5652
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4987
5653
|
let raw;
|
|
4988
5654
|
try {
|
|
4989
|
-
raw = await
|
|
5655
|
+
raw = await import_node_fs22.promises.readFile(outPath, "utf8");
|
|
4990
5656
|
} catch (err) {
|
|
4991
5657
|
if (err.code === "ENOENT") return;
|
|
4992
5658
|
throw err;
|
|
@@ -5045,23 +5711,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
5045
5711
|
|
|
5046
5712
|
// src/projects.ts
|
|
5047
5713
|
init_cjs_shims();
|
|
5048
|
-
var
|
|
5714
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5049
5715
|
function pathsForProject(project, baseDir) {
|
|
5050
5716
|
if (project === DEFAULT_PROJECT) {
|
|
5051
5717
|
return {
|
|
5052
|
-
snapshotPath:
|
|
5053
|
-
errorsPath:
|
|
5054
|
-
staleEventsPath:
|
|
5055
|
-
embeddingsCachePath:
|
|
5056
|
-
policyViolationsPath:
|
|
5718
|
+
snapshotPath: import_node_path36.default.join(baseDir, "graph.json"),
|
|
5719
|
+
errorsPath: import_node_path36.default.join(baseDir, "errors.ndjson"),
|
|
5720
|
+
staleEventsPath: import_node_path36.default.join(baseDir, "stale-events.ndjson"),
|
|
5721
|
+
embeddingsCachePath: import_node_path36.default.join(baseDir, "embeddings.json"),
|
|
5722
|
+
policyViolationsPath: import_node_path36.default.join(baseDir, "policy-violations.ndjson")
|
|
5057
5723
|
};
|
|
5058
5724
|
}
|
|
5059
5725
|
return {
|
|
5060
|
-
snapshotPath:
|
|
5061
|
-
errorsPath:
|
|
5062
|
-
staleEventsPath:
|
|
5063
|
-
embeddingsCachePath:
|
|
5064
|
-
policyViolationsPath:
|
|
5726
|
+
snapshotPath: import_node_path36.default.join(baseDir, `${project}.json`),
|
|
5727
|
+
errorsPath: import_node_path36.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5728
|
+
staleEventsPath: import_node_path36.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5729
|
+
embeddingsCachePath: import_node_path36.default.join(baseDir, `embeddings.${project}.json`),
|
|
5730
|
+
policyViolationsPath: import_node_path36.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5065
5731
|
};
|
|
5066
5732
|
}
|
|
5067
5733
|
var Projects = class {
|
|
@@ -5101,23 +5767,23 @@ function parseExtraProjects(raw) {
|
|
|
5101
5767
|
|
|
5102
5768
|
// src/registry.ts
|
|
5103
5769
|
init_cjs_shims();
|
|
5104
|
-
var
|
|
5105
|
-
var
|
|
5106
|
-
var
|
|
5107
|
-
var
|
|
5770
|
+
var import_node_fs23 = require("fs");
|
|
5771
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5772
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5773
|
+
var import_types23 = require("@neat.is/types");
|
|
5108
5774
|
function neatHome() {
|
|
5109
5775
|
const override = process.env.NEAT_HOME;
|
|
5110
|
-
if (override && override.length > 0) return
|
|
5111
|
-
return
|
|
5776
|
+
if (override && override.length > 0) return import_node_path37.default.resolve(override);
|
|
5777
|
+
return import_node_path37.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5112
5778
|
}
|
|
5113
5779
|
function registryPath() {
|
|
5114
|
-
return
|
|
5780
|
+
return import_node_path37.default.join(neatHome(), "projects.json");
|
|
5115
5781
|
}
|
|
5116
5782
|
async function readRegistry() {
|
|
5117
5783
|
const file = registryPath();
|
|
5118
5784
|
let raw;
|
|
5119
5785
|
try {
|
|
5120
|
-
raw = await
|
|
5786
|
+
raw = await import_node_fs23.promises.readFile(file, "utf8");
|
|
5121
5787
|
} catch (err) {
|
|
5122
5788
|
if (err.code === "ENOENT") {
|
|
5123
5789
|
return { version: 1, projects: [] };
|
|
@@ -5125,7 +5791,7 @@ async function readRegistry() {
|
|
|
5125
5791
|
throw err;
|
|
5126
5792
|
}
|
|
5127
5793
|
const parsed = JSON.parse(raw);
|
|
5128
|
-
return
|
|
5794
|
+
return import_types23.RegistryFileSchema.parse(parsed);
|
|
5129
5795
|
}
|
|
5130
5796
|
async function getProject(name) {
|
|
5131
5797
|
const reg = await readRegistry();
|
|
@@ -5327,11 +5993,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5327
5993
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5328
5994
|
const parsed = [];
|
|
5329
5995
|
for (const c of candidates) {
|
|
5330
|
-
const r =
|
|
5996
|
+
const r = import_types24.DivergenceTypeSchema.safeParse(c);
|
|
5331
5997
|
if (!r.success) {
|
|
5332
5998
|
return reply.code(400).send({
|
|
5333
5999
|
error: `unknown divergence type "${c}"`,
|
|
5334
|
-
allowed:
|
|
6000
|
+
allowed: import_types24.DivergenceTypeSchema.options
|
|
5335
6001
|
});
|
|
5336
6002
|
}
|
|
5337
6003
|
parsed.push(r.data);
|
|
@@ -5544,7 +6210,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5544
6210
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5545
6211
|
let violations = await log.readAll();
|
|
5546
6212
|
if (req.query.severity) {
|
|
5547
|
-
const sev =
|
|
6213
|
+
const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
|
|
5548
6214
|
if (!sev.success) {
|
|
5549
6215
|
return reply.code(400).send({
|
|
5550
6216
|
error: "invalid severity",
|
|
@@ -5561,7 +6227,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5561
6227
|
scope.post("/policies/check", async (req, reply) => {
|
|
5562
6228
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5563
6229
|
if (!proj) return;
|
|
5564
|
-
const parsed =
|
|
6230
|
+
const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
5565
6231
|
if (!parsed.success) {
|
|
5566
6232
|
return reply.code(400).send({
|
|
5567
6233
|
error: "invalid /policies/check body",
|
|
@@ -5594,6 +6260,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5594
6260
|
violations
|
|
5595
6261
|
};
|
|
5596
6262
|
});
|
|
6263
|
+
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
6264
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6265
|
+
if (!proj) return;
|
|
6266
|
+
if (!proj.scanPath) {
|
|
6267
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6268
|
+
}
|
|
6269
|
+
try {
|
|
6270
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6271
|
+
return { libraries: results };
|
|
6272
|
+
} catch (err) {
|
|
6273
|
+
return reply.code(500).send({ error: err.message });
|
|
6274
|
+
}
|
|
6275
|
+
});
|
|
6276
|
+
scope.get("/extend/lookup", async (req, reply) => {
|
|
6277
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6278
|
+
if (!proj) return;
|
|
6279
|
+
const { library, version } = req.query;
|
|
6280
|
+
if (!library) {
|
|
6281
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6282
|
+
}
|
|
6283
|
+
const result = lookupInstrumentation(library, version);
|
|
6284
|
+
if (!result) {
|
|
6285
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6286
|
+
}
|
|
6287
|
+
return result;
|
|
6288
|
+
});
|
|
6289
|
+
scope.get("/extend/describe", async (req, reply) => {
|
|
6290
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6291
|
+
if (!proj) return;
|
|
6292
|
+
if (!proj.scanPath) {
|
|
6293
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6294
|
+
}
|
|
6295
|
+
try {
|
|
6296
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6297
|
+
return state;
|
|
6298
|
+
} catch (err) {
|
|
6299
|
+
return reply.code(500).send({ error: err.message });
|
|
6300
|
+
}
|
|
6301
|
+
});
|
|
6302
|
+
scope.post("/extend/apply", async (req, reply) => {
|
|
6303
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6304
|
+
if (!proj) return;
|
|
6305
|
+
if (!proj.scanPath) {
|
|
6306
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6307
|
+
}
|
|
6308
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6309
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6310
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6311
|
+
}
|
|
6312
|
+
try {
|
|
6313
|
+
const result = await applyExtension(
|
|
6314
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6315
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6316
|
+
);
|
|
6317
|
+
return result;
|
|
6318
|
+
} catch (err) {
|
|
6319
|
+
return reply.code(500).send({ error: err.message });
|
|
6320
|
+
}
|
|
6321
|
+
});
|
|
6322
|
+
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6323
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6324
|
+
if (!proj) return;
|
|
6325
|
+
if (!proj.scanPath) {
|
|
6326
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6327
|
+
}
|
|
6328
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6329
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6330
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6331
|
+
}
|
|
6332
|
+
try {
|
|
6333
|
+
const result = await dryRunExtension(
|
|
6334
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6335
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6336
|
+
);
|
|
6337
|
+
return result;
|
|
6338
|
+
} catch (err) {
|
|
6339
|
+
return reply.code(500).send({ error: err.message });
|
|
6340
|
+
}
|
|
6341
|
+
});
|
|
6342
|
+
scope.post("/extend/rollback", async (req, reply) => {
|
|
6343
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6344
|
+
if (!proj) return;
|
|
6345
|
+
if (!proj.scanPath) {
|
|
6346
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6347
|
+
}
|
|
6348
|
+
const { library } = req.body ?? {};
|
|
6349
|
+
if (!library) {
|
|
6350
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6351
|
+
}
|
|
6352
|
+
try {
|
|
6353
|
+
const result = await rollbackExtension(
|
|
6354
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6355
|
+
{ library }
|
|
6356
|
+
);
|
|
6357
|
+
return result;
|
|
6358
|
+
} catch (err) {
|
|
6359
|
+
return reply.code(500).send({ error: err.message });
|
|
6360
|
+
}
|
|
6361
|
+
});
|
|
5597
6362
|
}
|
|
5598
6363
|
async function buildApi(opts) {
|
|
5599
6364
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5704,8 +6469,8 @@ init_otel_grpc();
|
|
|
5704
6469
|
|
|
5705
6470
|
// src/search.ts
|
|
5706
6471
|
init_cjs_shims();
|
|
5707
|
-
var
|
|
5708
|
-
var
|
|
6472
|
+
var import_node_fs24 = require("fs");
|
|
6473
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
5709
6474
|
var import_node_crypto3 = require("crypto");
|
|
5710
6475
|
var DEFAULT_LIMIT = 10;
|
|
5711
6476
|
var NOMIC_DIM = 768;
|
|
@@ -5835,7 +6600,7 @@ async function pickEmbedder() {
|
|
|
5835
6600
|
}
|
|
5836
6601
|
async function readCache(cachePath) {
|
|
5837
6602
|
try {
|
|
5838
|
-
const raw = await
|
|
6603
|
+
const raw = await import_node_fs24.promises.readFile(cachePath, "utf8");
|
|
5839
6604
|
const parsed = JSON.parse(raw);
|
|
5840
6605
|
if (parsed.version !== 1) return null;
|
|
5841
6606
|
return parsed;
|
|
@@ -5844,8 +6609,8 @@ async function readCache(cachePath) {
|
|
|
5844
6609
|
}
|
|
5845
6610
|
}
|
|
5846
6611
|
async function writeCache(cachePath, cache) {
|
|
5847
|
-
await
|
|
5848
|
-
await
|
|
6612
|
+
await import_node_fs24.promises.mkdir(import_node_path40.default.dirname(cachePath), { recursive: true });
|
|
6613
|
+
await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
5849
6614
|
}
|
|
5850
6615
|
var VectorIndex = class {
|
|
5851
6616
|
constructor(embedder, cachePath) {
|
|
@@ -6025,14 +6790,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
6025
6790
|
async function main() {
|
|
6026
6791
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
6027
6792
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
6028
|
-
const baseDir = baseDirEnv ?
|
|
6029
|
-
const defaultScanPath =
|
|
6793
|
+
const baseDir = baseDirEnv ? import_node_path41.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path41.default.resolve(import_node_path41.default.dirname(legacyOutPath)) : import_node_path41.default.resolve("./neat-out");
|
|
6794
|
+
const defaultScanPath = import_node_path41.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
6030
6795
|
const registry = new Projects();
|
|
6031
6796
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
6032
6797
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
6033
6798
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
6034
6799
|
const projectScan = process.env[envKey];
|
|
6035
|
-
await bootProject(registry, name, projectScan ?
|
|
6800
|
+
await bootProject(registry, name, projectScan ? import_node_path41.default.resolve(projectScan) : void 0, baseDir);
|
|
6036
6801
|
}
|
|
6037
6802
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
6038
6803
|
const port = Number(process.env.PORT ?? 8080);
|