@neat.is/core 0.4.10 → 0.4.12
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-RC3CIDZO.js → chunk-7JY6F7BY.js} +4 -2
- package/dist/{chunk-RC3CIDZO.js.map → chunk-7JY6F7BY.js.map} +1 -1
- package/dist/{chunk-J5CEKCTR.js → chunk-WDG4QEFO.js} +782 -192
- package/dist/chunk-WDG4QEFO.js.map +1 -0
- package/dist/cli.cjs +1387 -550
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +442 -192
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +831 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +829 -237
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +884 -364
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +5 -2
- package/dist/chunk-J5CEKCTR.js.map +0 -1
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 path40 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
60
60
|
for (const suffix of suffixes) {
|
|
61
|
-
if (
|
|
61
|
+
if (path40 === suffix || path40.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_path36.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
188
|
+
return import_node_path36.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_path36, 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_path36 = __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_path37.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
373
|
+
const protoRoot = import_node_path37.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_path37.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_path37, 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_path37 = __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_path39 = __toESM(require("path"), 1);
|
|
590
590
|
|
|
591
591
|
// src/graph.ts
|
|
592
592
|
init_cjs_shims();
|
|
@@ -612,15 +612,330 @@ var import_fastify = __toESM(require("fastify"), 1);
|
|
|
612
612
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
613
613
|
var import_types23 = require("@neat.is/types");
|
|
614
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
|
+
}
|
|
929
|
+
|
|
615
930
|
// src/divergences.ts
|
|
616
931
|
init_cjs_shims();
|
|
617
932
|
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, path40, edges) {
|
|
1357
|
+
if (path40.length > best.path.length) {
|
|
1358
|
+
best = { path: [...path40], edges: [...edges] };
|
|
1044
1359
|
}
|
|
1045
|
-
if (
|
|
1360
|
+
if (path40.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
|
+
path40.push(srcId);
|
|
1051
1366
|
edges.push(edge);
|
|
1052
|
-
step(srcId,
|
|
1053
|
-
|
|
1367
|
+
step(srcId, path40, edges);
|
|
1368
|
+
path40.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,9 @@ 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);
|
|
2137
|
+
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
1822
2138
|
var import_types4 = require("@neat.is/types");
|
|
1823
2139
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
1824
2140
|
var DAY_MS = 24 * HOUR_MS;
|
|
@@ -1864,6 +2180,14 @@ function warnUnidentifiedSpan(project) {
|
|
|
1864
2180
|
`[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
|
|
1865
2181
|
);
|
|
1866
2182
|
}
|
|
2183
|
+
var noSourceMapWarnedServices = /* @__PURE__ */ new Set();
|
|
2184
|
+
function warnNoSourceMaps(serviceName) {
|
|
2185
|
+
if (noSourceMapWarnedServices.has(serviceName)) return;
|
|
2186
|
+
noSourceMapWarnedServices.add(serviceName);
|
|
2187
|
+
console.warn(
|
|
2188
|
+
`[neat] ${serviceName}: no .map files found under dist/; observed file edges will land on dist paths, not src. Set sourceMap: true in tsconfig to enable file-level reconciliation.`
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
1867
2191
|
function pickAttr(span, ...keys) {
|
|
1868
2192
|
for (const k of keys) {
|
|
1869
2193
|
const v = span.attributes[k];
|
|
@@ -1906,8 +2230,13 @@ function languageForExt(relPath) {
|
|
|
1906
2230
|
return void 0;
|
|
1907
2231
|
}
|
|
1908
2232
|
}
|
|
1909
|
-
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
2233
|
+
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1910
2234
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
2235
|
+
if (scanPath && scanPath.length > 0) {
|
|
2236
|
+
const absRoot = toPosix(import_node_path5.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
2237
|
+
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
2238
|
+
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
2239
|
+
}
|
|
1911
2240
|
const root = serviceNode?.repoPath;
|
|
1912
2241
|
if (root && root !== "." && root.length > 0) {
|
|
1913
2242
|
const rootPosix = toPosix(root);
|
|
@@ -1924,16 +2253,65 @@ function relPathForRuntimeFile(filepath, serviceNode) {
|
|
|
1924
2253
|
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1925
2254
|
return p.length > 0 ? p : null;
|
|
1926
2255
|
}
|
|
1927
|
-
|
|
2256
|
+
var sourceMapCache = /* @__PURE__ */ new Map();
|
|
2257
|
+
function resolveDistToSrc(absFilepath, line) {
|
|
2258
|
+
if (!absFilepath.endsWith(".js")) return null;
|
|
2259
|
+
let entry = sourceMapCache.get(absFilepath);
|
|
2260
|
+
if (entry === void 0) {
|
|
2261
|
+
entry = null;
|
|
2262
|
+
const mapPath = `${absFilepath}.map`;
|
|
2263
|
+
try {
|
|
2264
|
+
if ((0, import_node_fs5.existsSync)(mapPath)) {
|
|
2265
|
+
const raw = JSON.parse((0, import_node_fs5.readFileSync)(mapPath, "utf8"));
|
|
2266
|
+
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
2267
|
+
entry = { consumer, dir: import_node_path5.default.dirname(mapPath) };
|
|
2268
|
+
}
|
|
2269
|
+
} catch {
|
|
2270
|
+
entry = null;
|
|
2271
|
+
}
|
|
2272
|
+
sourceMapCache.set(absFilepath, entry);
|
|
2273
|
+
}
|
|
2274
|
+
if (!entry) return null;
|
|
2275
|
+
try {
|
|
2276
|
+
const pos = entry.consumer.originalPositionFor({
|
|
2277
|
+
line: line !== void 0 && Number.isFinite(line) ? line : 1,
|
|
2278
|
+
column: 0
|
|
2279
|
+
});
|
|
2280
|
+
if (!pos || !pos.source) return null;
|
|
2281
|
+
const root = entry.consumer.sourceRoot ?? "";
|
|
2282
|
+
const resolved = import_node_path5.default.resolve(entry.dir, root, pos.source);
|
|
2283
|
+
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
2284
|
+
} catch {
|
|
2285
|
+
return null;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
1928
2289
|
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1929
2290
|
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1930
|
-
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1931
|
-
if (!relPath) return null;
|
|
1932
2291
|
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1933
|
-
|
|
2292
|
+
let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
2293
|
+
const abs = toPosix(filepath).replace(/^file:\/\//, "");
|
|
2294
|
+
const resolved = resolveDistToSrc(abs, line);
|
|
2295
|
+
let effectivePath = filepath;
|
|
2296
|
+
let originalRelPath;
|
|
2297
|
+
if (resolved) {
|
|
2298
|
+
originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? void 0;
|
|
2299
|
+
effectivePath = resolved.filepath;
|
|
2300
|
+
if (resolved.line !== void 0) line = resolved.line;
|
|
2301
|
+
}
|
|
2302
|
+
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
2303
|
+
if (!relPath) return null;
|
|
2304
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
2305
|
+
warnNoSourceMaps(serviceNode.name);
|
|
2306
|
+
}
|
|
1934
2307
|
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1935
2308
|
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1936
|
-
return {
|
|
2309
|
+
return {
|
|
2310
|
+
relPath,
|
|
2311
|
+
...line !== void 0 ? { line } : {},
|
|
2312
|
+
...fn ? { fn } : {},
|
|
2313
|
+
...originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}
|
|
2314
|
+
};
|
|
1937
2315
|
}
|
|
1938
2316
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1939
2317
|
const fileNodeId = (0, import_types4.fileId)(serviceName, callSite.relPath);
|
|
@@ -1945,6 +2323,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
1945
2323
|
service: serviceName,
|
|
1946
2324
|
path: callSite.relPath,
|
|
1947
2325
|
...language ? { language } : {},
|
|
2326
|
+
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
1948
2327
|
discoveredVia: "otel"
|
|
1949
2328
|
};
|
|
1950
2329
|
graph.addNode(fileNodeId, node);
|
|
@@ -1970,6 +2349,12 @@ function makeInferredEdgeId(type, source, target) {
|
|
|
1970
2349
|
}
|
|
1971
2350
|
var INFERRED_CONFIDENCE = 0.6;
|
|
1972
2351
|
var STITCH_MAX_DEPTH = 2;
|
|
2352
|
+
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
2353
|
+
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
2354
|
+
function spanMintsObservedEdge(kind) {
|
|
2355
|
+
if (kind === void 0 || kind === 0) return true;
|
|
2356
|
+
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
2357
|
+
}
|
|
1973
2358
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
1974
2359
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1975
2360
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2076,7 +2461,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
2076
2461
|
graph.addNode(id, node);
|
|
2077
2462
|
return id;
|
|
2078
2463
|
}
|
|
2079
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
2464
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
2080
2465
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
2081
2466
|
const id = makeObservedEdgeId(type, source, target);
|
|
2082
2467
|
if (graph.hasEdge(id)) {
|
|
@@ -2113,7 +2498,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
2113
2498
|
confidence: (0, import_types4.confidenceForObservedSignal)(signal),
|
|
2114
2499
|
lastObserved: ts,
|
|
2115
2500
|
callCount: 1,
|
|
2116
|
-
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 } : {}
|
|
2117
2505
|
};
|
|
2118
2506
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2119
2507
|
return { edge, created: true };
|
|
@@ -2158,8 +2546,8 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
2158
2546
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2159
2547
|
}
|
|
2160
2548
|
async function appendErrorEvent(ctx, ev) {
|
|
2161
|
-
await
|
|
2162
|
-
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");
|
|
2163
2551
|
}
|
|
2164
2552
|
function sanitizeAttributes(attrs) {
|
|
2165
2553
|
const out = {};
|
|
@@ -2180,12 +2568,14 @@ async function handleSpan(ctx, span) {
|
|
|
2180
2568
|
const isError = span.statusCode === 2;
|
|
2181
2569
|
cacheSpanService(span, nowMs);
|
|
2182
2570
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2183
|
-
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2571
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2184
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;
|
|
2185
2574
|
let affectedNode = sourceId;
|
|
2575
|
+
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2186
2576
|
if (span.dbSystem) {
|
|
2187
2577
|
const host = pickAddress(span);
|
|
2188
|
-
if (host) {
|
|
2578
|
+
if (mintsFromCallerSide && host) {
|
|
2189
2579
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2190
2580
|
const targetId = (0, import_types4.databaseId)(host);
|
|
2191
2581
|
const result = upsertObservedEdge(
|
|
@@ -2194,14 +2584,15 @@ async function handleSpan(ctx, span) {
|
|
|
2194
2584
|
observedSource(),
|
|
2195
2585
|
targetId,
|
|
2196
2586
|
ts,
|
|
2197
|
-
isError
|
|
2587
|
+
isError,
|
|
2588
|
+
callSiteEvidence
|
|
2198
2589
|
);
|
|
2199
2590
|
if (result) affectedNode = targetId;
|
|
2200
2591
|
}
|
|
2201
2592
|
} else {
|
|
2202
2593
|
const host = pickAddress(span);
|
|
2203
2594
|
let resolvedViaAddress = false;
|
|
2204
|
-
if (host && host !== span.service) {
|
|
2595
|
+
if (mintsFromCallerSide && host && host !== span.service) {
|
|
2205
2596
|
const targetId = resolveServiceId(ctx.graph, host, env);
|
|
2206
2597
|
if (targetId && targetId !== sourceId) {
|
|
2207
2598
|
upsertObservedEdge(
|
|
@@ -2210,7 +2601,8 @@ async function handleSpan(ctx, span) {
|
|
|
2210
2601
|
observedSource(),
|
|
2211
2602
|
targetId,
|
|
2212
2603
|
ts,
|
|
2213
|
-
isError
|
|
2604
|
+
isError,
|
|
2605
|
+
callSiteEvidence
|
|
2214
2606
|
);
|
|
2215
2607
|
affectedNode = targetId;
|
|
2216
2608
|
resolvedViaAddress = true;
|
|
@@ -2222,7 +2614,8 @@ async function handleSpan(ctx, span) {
|
|
|
2222
2614
|
observedSource(),
|
|
2223
2615
|
frontierNodeId,
|
|
2224
2616
|
ts,
|
|
2225
|
-
isError
|
|
2617
|
+
isError,
|
|
2618
|
+
callSiteEvidence
|
|
2226
2619
|
);
|
|
2227
2620
|
affectedNode = frontierNodeId;
|
|
2228
2621
|
resolvedViaAddress = true;
|
|
@@ -2380,13 +2773,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
2380
2773
|
return { count: events.length, events };
|
|
2381
2774
|
}
|
|
2382
2775
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
2383
|
-
await
|
|
2776
|
+
await import_node_fs5.promises.mkdir(import_node_path5.default.dirname(staleEventsPath), { recursive: true });
|
|
2384
2777
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2385
|
-
await
|
|
2778
|
+
await import_node_fs5.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
2386
2779
|
}
|
|
2387
2780
|
async function readStaleEvents(staleEventsPath) {
|
|
2388
2781
|
try {
|
|
2389
|
-
const raw = await
|
|
2782
|
+
const raw = await import_node_fs5.promises.readFile(staleEventsPath, "utf8");
|
|
2390
2783
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2391
2784
|
} catch (err) {
|
|
2392
2785
|
if (err.code === "ENOENT") return [];
|
|
@@ -2420,7 +2813,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
2420
2813
|
}
|
|
2421
2814
|
async function readErrorEvents(errorsPath) {
|
|
2422
2815
|
try {
|
|
2423
|
-
const raw = await
|
|
2816
|
+
const raw = await import_node_fs5.promises.readFile(errorsPath, "utf8");
|
|
2424
2817
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2425
2818
|
} catch (err) {
|
|
2426
2819
|
if (err.code === "ENOENT") return [];
|
|
@@ -2452,16 +2845,16 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
2452
2845
|
|
|
2453
2846
|
// src/extract/services.ts
|
|
2454
2847
|
init_cjs_shims();
|
|
2455
|
-
var
|
|
2456
|
-
var
|
|
2848
|
+
var import_node_fs10 = require("fs");
|
|
2849
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2457
2850
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
2458
2851
|
var import_minimatch2 = require("minimatch");
|
|
2459
2852
|
var import_types6 = require("@neat.is/types");
|
|
2460
2853
|
|
|
2461
2854
|
// src/extract/shared.ts
|
|
2462
2855
|
init_cjs_shims();
|
|
2463
|
-
var
|
|
2464
|
-
var
|
|
2856
|
+
var import_node_fs6 = require("fs");
|
|
2857
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2465
2858
|
var import_yaml = require("yaml");
|
|
2466
2859
|
var import_types5 = require("@neat.is/types");
|
|
2467
2860
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
|
|
@@ -2493,7 +2886,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2493
2886
|
const cached = PYVENV_MARKER_CACHE.get(dir);
|
|
2494
2887
|
if (cached !== void 0) return cached;
|
|
2495
2888
|
try {
|
|
2496
|
-
const stat = await
|
|
2889
|
+
const stat = await import_node_fs6.promises.stat(import_node_path6.default.join(dir, "pyvenv.cfg"));
|
|
2497
2890
|
const ok = stat.isFile();
|
|
2498
2891
|
PYVENV_MARKER_CACHE.set(dir, ok);
|
|
2499
2892
|
return ok;
|
|
@@ -2503,7 +2896,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2503
2896
|
}
|
|
2504
2897
|
}
|
|
2505
2898
|
function isConfigFile(name) {
|
|
2506
|
-
const ext =
|
|
2899
|
+
const ext = import_node_path6.default.extname(name);
|
|
2507
2900
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2508
2901
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2509
2902
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
@@ -2611,16 +3004,16 @@ function cleanVersion(raw) {
|
|
|
2611
3004
|
return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
|
|
2612
3005
|
}
|
|
2613
3006
|
async function readJson(filePath) {
|
|
2614
|
-
const raw = await
|
|
3007
|
+
const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
|
|
2615
3008
|
return JSON.parse(raw);
|
|
2616
3009
|
}
|
|
2617
3010
|
async function readYaml(filePath) {
|
|
2618
|
-
const raw = await
|
|
3011
|
+
const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
|
|
2619
3012
|
return (0, import_yaml.parse)(raw);
|
|
2620
3013
|
}
|
|
2621
|
-
async function
|
|
3014
|
+
async function exists2(p) {
|
|
2622
3015
|
try {
|
|
2623
|
-
await
|
|
3016
|
+
await import_node_fs6.promises.access(p);
|
|
2624
3017
|
return true;
|
|
2625
3018
|
} catch {
|
|
2626
3019
|
return false;
|
|
@@ -2629,8 +3022,8 @@ async function exists(p) {
|
|
|
2629
3022
|
|
|
2630
3023
|
// src/extract/python.ts
|
|
2631
3024
|
init_cjs_shims();
|
|
2632
|
-
var
|
|
2633
|
-
var
|
|
3025
|
+
var import_node_fs7 = require("fs");
|
|
3026
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2634
3027
|
var import_smol_toml = require("smol-toml");
|
|
2635
3028
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
2636
3029
|
function parseRequirementsTxt(content) {
|
|
@@ -2663,25 +3056,25 @@ function depsFromPyProject(pyproject) {
|
|
|
2663
3056
|
return out;
|
|
2664
3057
|
}
|
|
2665
3058
|
async function discoverPythonService(serviceDir) {
|
|
2666
|
-
const pyprojectPath =
|
|
2667
|
-
const requirementsPath =
|
|
2668
|
-
const setupPath =
|
|
2669
|
-
const hasPyproject = await
|
|
2670
|
-
const hasRequirements = await
|
|
2671
|
-
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);
|
|
2672
3065
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
2673
|
-
let name =
|
|
3066
|
+
let name = import_node_path7.default.basename(serviceDir);
|
|
2674
3067
|
let version;
|
|
2675
3068
|
const dependencies = {};
|
|
2676
3069
|
if (hasPyproject) {
|
|
2677
|
-
const raw = await
|
|
3070
|
+
const raw = await import_node_fs7.promises.readFile(pyprojectPath, "utf8");
|
|
2678
3071
|
const pyproject = (0, import_smol_toml.parse)(raw);
|
|
2679
3072
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
2680
3073
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
2681
3074
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
2682
3075
|
}
|
|
2683
3076
|
if (hasRequirements) {
|
|
2684
|
-
const raw = await
|
|
3077
|
+
const raw = await import_node_fs7.promises.readFile(requirementsPath, "utf8");
|
|
2685
3078
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
2686
3079
|
}
|
|
2687
3080
|
return { name, version, dependencies };
|
|
@@ -2696,17 +3089,17 @@ function pythonToPackage(service) {
|
|
|
2696
3089
|
|
|
2697
3090
|
// src/extract/owners.ts
|
|
2698
3091
|
init_cjs_shims();
|
|
2699
|
-
var
|
|
2700
|
-
var
|
|
3092
|
+
var import_node_fs8 = require("fs");
|
|
3093
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2701
3094
|
var import_minimatch = require("minimatch");
|
|
2702
3095
|
async function loadCodeowners(scanPath) {
|
|
2703
3096
|
const candidates = [
|
|
2704
|
-
|
|
2705
|
-
|
|
3097
|
+
import_node_path8.default.join(scanPath, "CODEOWNERS"),
|
|
3098
|
+
import_node_path8.default.join(scanPath, ".github", "CODEOWNERS")
|
|
2706
3099
|
];
|
|
2707
3100
|
for (const file of candidates) {
|
|
2708
|
-
if (await
|
|
2709
|
-
const raw = await
|
|
3101
|
+
if (await exists2(file)) {
|
|
3102
|
+
const raw = await import_node_fs8.promises.readFile(file, "utf8");
|
|
2710
3103
|
return parseCodeowners(raw);
|
|
2711
3104
|
}
|
|
2712
3105
|
}
|
|
@@ -2724,7 +3117,7 @@ function parseCodeowners(raw) {
|
|
|
2724
3117
|
return { rules };
|
|
2725
3118
|
}
|
|
2726
3119
|
function matchOwner(file, repoPath) {
|
|
2727
|
-
const normalized = repoPath.split(
|
|
3120
|
+
const normalized = repoPath.split(import_node_path8.default.sep).join("/");
|
|
2728
3121
|
for (const rule of file.rules) {
|
|
2729
3122
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
2730
3123
|
}
|
|
@@ -2740,8 +3133,8 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
2740
3133
|
return false;
|
|
2741
3134
|
}
|
|
2742
3135
|
async function readPackageJsonAuthor(serviceDir) {
|
|
2743
|
-
const pkgPath =
|
|
2744
|
-
if (!await
|
|
3136
|
+
const pkgPath = import_node_path8.default.join(serviceDir, "package.json");
|
|
3137
|
+
if (!await exists2(pkgPath)) return null;
|
|
2745
3138
|
try {
|
|
2746
3139
|
const pkg = await readJson(pkgPath);
|
|
2747
3140
|
if (!pkg.author) return null;
|
|
@@ -2763,8 +3156,8 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
|
2763
3156
|
|
|
2764
3157
|
// src/extract/errors.ts
|
|
2765
3158
|
init_cjs_shims();
|
|
2766
|
-
var
|
|
2767
|
-
var
|
|
3159
|
+
var import_node_fs9 = require("fs");
|
|
3160
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2768
3161
|
var sink = [];
|
|
2769
3162
|
function recordExtractionError(producer, file, err) {
|
|
2770
3163
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -2783,9 +3176,9 @@ function drainExtractionErrors() {
|
|
|
2783
3176
|
}
|
|
2784
3177
|
async function writeExtractionErrors(errors, errorsPath) {
|
|
2785
3178
|
if (errors.length === 0) return;
|
|
2786
|
-
await
|
|
3179
|
+
await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(errorsPath), { recursive: true });
|
|
2787
3180
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2788
|
-
await
|
|
3181
|
+
await import_node_fs9.promises.appendFile(errorsPath, lines, "utf8");
|
|
2789
3182
|
}
|
|
2790
3183
|
var droppedSink = [];
|
|
2791
3184
|
function noteExtractedDropped(edge) {
|
|
@@ -2800,9 +3193,9 @@ function isRejectedLogEnabled() {
|
|
|
2800
3193
|
}
|
|
2801
3194
|
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
2802
3195
|
if (drops.length === 0) return;
|
|
2803
|
-
await
|
|
3196
|
+
await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(rejectedPath), { recursive: true });
|
|
2804
3197
|
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
2805
|
-
await
|
|
3198
|
+
await import_node_fs9.promises.appendFile(rejectedPath, lines, "utf8");
|
|
2806
3199
|
}
|
|
2807
3200
|
|
|
2808
3201
|
// src/extract/services.ts
|
|
@@ -2821,21 +3214,21 @@ function workspaceGlobs(pkg) {
|
|
|
2821
3214
|
return null;
|
|
2822
3215
|
}
|
|
2823
3216
|
async function loadGitignore(scanPath) {
|
|
2824
|
-
const gitignorePath =
|
|
2825
|
-
if (!await
|
|
2826
|
-
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");
|
|
2827
3220
|
return (0, import_ignore.default)().add(raw);
|
|
2828
3221
|
}
|
|
2829
3222
|
async function walkDirs(start, scanPath, options, visit) {
|
|
2830
3223
|
async function recurse(current, depth) {
|
|
2831
3224
|
if (depth > options.maxDepth) return;
|
|
2832
|
-
const entries = await
|
|
3225
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2833
3226
|
for (const entry of entries) {
|
|
2834
3227
|
if (!entry.isDirectory()) continue;
|
|
2835
3228
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2836
|
-
const child =
|
|
3229
|
+
const child = import_node_path10.default.join(current, entry.name);
|
|
2837
3230
|
if (options.ig) {
|
|
2838
|
-
const rel =
|
|
3231
|
+
const rel = import_node_path10.default.relative(scanPath, child).split(import_node_path10.default.sep).join("/");
|
|
2839
3232
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2840
3233
|
}
|
|
2841
3234
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -2851,8 +3244,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2851
3244
|
for (const raw of globs) {
|
|
2852
3245
|
const pattern = raw.replace(/^\.\//, "");
|
|
2853
3246
|
if (!pattern.includes("*")) {
|
|
2854
|
-
const candidate =
|
|
2855
|
-
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);
|
|
2856
3249
|
continue;
|
|
2857
3250
|
}
|
|
2858
3251
|
const segments = pattern.split("/");
|
|
@@ -2861,13 +3254,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2861
3254
|
if (seg.includes("*")) break;
|
|
2862
3255
|
staticSegments.push(seg);
|
|
2863
3256
|
}
|
|
2864
|
-
const start =
|
|
2865
|
-
if (!await
|
|
3257
|
+
const start = import_node_path10.default.join(scanPath, ...staticSegments);
|
|
3258
|
+
if (!await exists2(start)) continue;
|
|
2866
3259
|
const hasDoubleStar = pattern.includes("**");
|
|
2867
3260
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2868
3261
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2869
|
-
const rel =
|
|
2870
|
-
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"))) {
|
|
2871
3264
|
found.add(dir);
|
|
2872
3265
|
}
|
|
2873
3266
|
});
|
|
@@ -2887,13 +3280,13 @@ function detectJsFramework(pkg) {
|
|
|
2887
3280
|
return void 0;
|
|
2888
3281
|
}
|
|
2889
3282
|
async function discoverNodeService(scanPath, dir) {
|
|
2890
|
-
const pkgPath =
|
|
2891
|
-
if (!await
|
|
3283
|
+
const pkgPath = import_node_path10.default.join(dir, "package.json");
|
|
3284
|
+
if (!await exists2(pkgPath)) return null;
|
|
2892
3285
|
let pkg;
|
|
2893
3286
|
try {
|
|
2894
3287
|
pkg = await readJson(pkgPath);
|
|
2895
3288
|
} catch (err) {
|
|
2896
|
-
recordExtractionError("services",
|
|
3289
|
+
recordExtractionError("services", import_node_path10.default.relative(scanPath, pkgPath), err);
|
|
2897
3290
|
return null;
|
|
2898
3291
|
}
|
|
2899
3292
|
if (!pkg.name) return null;
|
|
@@ -2905,7 +3298,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2905
3298
|
language: "javascript",
|
|
2906
3299
|
version: pkg.version,
|
|
2907
3300
|
dependencies: pkg.dependencies ?? {},
|
|
2908
|
-
repoPath:
|
|
3301
|
+
repoPath: import_node_path10.default.relative(scanPath, dir),
|
|
2909
3302
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
2910
3303
|
...framework ? { framework } : {}
|
|
2911
3304
|
};
|
|
@@ -2922,20 +3315,20 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2922
3315
|
language: "python",
|
|
2923
3316
|
version: py.version,
|
|
2924
3317
|
dependencies: py.dependencies,
|
|
2925
|
-
repoPath:
|
|
3318
|
+
repoPath: import_node_path10.default.relative(scanPath, dir)
|
|
2926
3319
|
};
|
|
2927
3320
|
return { pkg, dir, node };
|
|
2928
3321
|
}
|
|
2929
3322
|
async function discoverServices(scanPath) {
|
|
2930
|
-
const rootPkgPath =
|
|
3323
|
+
const rootPkgPath = import_node_path10.default.join(scanPath, "package.json");
|
|
2931
3324
|
let rootPkg = null;
|
|
2932
|
-
if (await
|
|
3325
|
+
if (await exists2(rootPkgPath)) {
|
|
2933
3326
|
try {
|
|
2934
3327
|
rootPkg = await readJson(rootPkgPath);
|
|
2935
3328
|
} catch (err) {
|
|
2936
3329
|
recordExtractionError(
|
|
2937
3330
|
"services workspaces",
|
|
2938
|
-
|
|
3331
|
+
import_node_path10.default.relative(scanPath, rootPkgPath),
|
|
2939
3332
|
err
|
|
2940
3333
|
);
|
|
2941
3334
|
}
|
|
@@ -2952,9 +3345,9 @@ async function discoverServices(scanPath) {
|
|
|
2952
3345
|
scanPath,
|
|
2953
3346
|
{ maxDepth: parseScanDepth(), ig },
|
|
2954
3347
|
async (dir) => {
|
|
2955
|
-
if (await
|
|
3348
|
+
if (await exists2(import_node_path10.default.join(dir, "package.json"))) {
|
|
2956
3349
|
candidateDirs.push(dir);
|
|
2957
|
-
} 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"))) {
|
|
2958
3351
|
candidateDirs.push(dir);
|
|
2959
3352
|
}
|
|
2960
3353
|
}
|
|
@@ -2968,8 +3361,8 @@ async function discoverServices(scanPath) {
|
|
|
2968
3361
|
if (!service) continue;
|
|
2969
3362
|
const existingDir = seen.get(service.node.name);
|
|
2970
3363
|
if (existingDir !== void 0) {
|
|
2971
|
-
const a =
|
|
2972
|
-
const b =
|
|
3364
|
+
const a = import_node_path10.default.relative(scanPath, existingDir) || ".";
|
|
3365
|
+
const b = import_node_path10.default.relative(scanPath, dir) || ".";
|
|
2973
3366
|
console.warn(
|
|
2974
3367
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2975
3368
|
);
|
|
@@ -3006,8 +3399,8 @@ function addServiceNodes(graph, services) {
|
|
|
3006
3399
|
|
|
3007
3400
|
// src/extract/aliases.ts
|
|
3008
3401
|
init_cjs_shims();
|
|
3009
|
-
var
|
|
3010
|
-
var
|
|
3402
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3403
|
+
var import_node_fs11 = require("fs");
|
|
3011
3404
|
var import_yaml2 = require("yaml");
|
|
3012
3405
|
var import_types7 = require("@neat.is/types");
|
|
3013
3406
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -3034,15 +3427,15 @@ function indexServicesByName(services) {
|
|
|
3034
3427
|
const map = /* @__PURE__ */ new Map();
|
|
3035
3428
|
for (const s of services) {
|
|
3036
3429
|
map.set(s.node.name, s.node.id);
|
|
3037
|
-
map.set(
|
|
3430
|
+
map.set(import_node_path11.default.basename(s.dir), s.node.id);
|
|
3038
3431
|
}
|
|
3039
3432
|
return map;
|
|
3040
3433
|
}
|
|
3041
3434
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
3042
3435
|
let composePath = null;
|
|
3043
3436
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3044
|
-
const abs =
|
|
3045
|
-
if (await
|
|
3437
|
+
const abs = import_node_path11.default.join(scanPath, name);
|
|
3438
|
+
if (await exists2(abs)) {
|
|
3046
3439
|
composePath = abs;
|
|
3047
3440
|
break;
|
|
3048
3441
|
}
|
|
@@ -3054,7 +3447,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3054
3447
|
} catch (err) {
|
|
3055
3448
|
recordExtractionError(
|
|
3056
3449
|
"aliases compose",
|
|
3057
|
-
|
|
3450
|
+
import_node_path11.default.relative(scanPath, composePath),
|
|
3058
3451
|
err
|
|
3059
3452
|
);
|
|
3060
3453
|
return;
|
|
@@ -3097,11 +3490,11 @@ function parseDockerfileLabels(content) {
|
|
|
3097
3490
|
}
|
|
3098
3491
|
async function collectDockerfileAliases(graph, services) {
|
|
3099
3492
|
for (const service of services) {
|
|
3100
|
-
const dockerfilePath =
|
|
3101
|
-
if (!await
|
|
3493
|
+
const dockerfilePath = import_node_path11.default.join(service.dir, "Dockerfile");
|
|
3494
|
+
if (!await exists2(dockerfilePath)) continue;
|
|
3102
3495
|
let content;
|
|
3103
3496
|
try {
|
|
3104
|
-
content = await
|
|
3497
|
+
content = await import_node_fs11.promises.readFile(dockerfilePath, "utf8");
|
|
3105
3498
|
} catch (err) {
|
|
3106
3499
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
3107
3500
|
continue;
|
|
@@ -3113,15 +3506,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
3113
3506
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
3114
3507
|
if (depth > max) return [];
|
|
3115
3508
|
const out = [];
|
|
3116
|
-
const entries = await
|
|
3509
|
+
const entries = await import_node_fs11.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3117
3510
|
for (const entry of entries) {
|
|
3118
3511
|
if (entry.isDirectory()) {
|
|
3119
3512
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3120
|
-
const child =
|
|
3513
|
+
const child = import_node_path11.default.join(start, entry.name);
|
|
3121
3514
|
if (await isPythonVenvDir(child)) continue;
|
|
3122
3515
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
3123
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3124
|
-
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));
|
|
3125
3518
|
}
|
|
3126
3519
|
}
|
|
3127
3520
|
return out;
|
|
@@ -3148,7 +3541,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
3148
3541
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
3149
3542
|
const files = await walkYamlFiles(scanPath);
|
|
3150
3543
|
for (const file of files) {
|
|
3151
|
-
const content = await
|
|
3544
|
+
const content = await import_node_fs11.promises.readFile(file, "utf8");
|
|
3152
3545
|
let docs;
|
|
3153
3546
|
try {
|
|
3154
3547
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3173,15 +3566,112 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3173
3566
|
|
|
3174
3567
|
// src/extract/databases/index.ts
|
|
3175
3568
|
init_cjs_shims();
|
|
3176
|
-
var
|
|
3569
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3570
|
+
var import_types9 = require("@neat.is/types");
|
|
3571
|
+
|
|
3572
|
+
// src/extract/calls/shared.ts
|
|
3573
|
+
init_cjs_shims();
|
|
3574
|
+
var import_node_fs12 = require("fs");
|
|
3575
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3177
3576
|
var import_types8 = require("@neat.is/types");
|
|
3577
|
+
async function walkSourceFiles(dir) {
|
|
3578
|
+
const out = [];
|
|
3579
|
+
async function walk(current) {
|
|
3580
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3581
|
+
for (const entry of entries) {
|
|
3582
|
+
const full = import_node_path12.default.join(current, entry.name);
|
|
3583
|
+
if (entry.isDirectory()) {
|
|
3584
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3585
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3586
|
+
await walk(full);
|
|
3587
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path12.default.extname(entry.name))) {
|
|
3588
|
+
out.push(full);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
await walk(dir);
|
|
3593
|
+
return out;
|
|
3594
|
+
}
|
|
3595
|
+
async function loadSourceFiles(dir) {
|
|
3596
|
+
const paths = await walkSourceFiles(dir);
|
|
3597
|
+
const out = [];
|
|
3598
|
+
for (const p of paths) {
|
|
3599
|
+
try {
|
|
3600
|
+
const content = await import_node_fs12.promises.readFile(p, "utf8");
|
|
3601
|
+
out.push({ path: p, content });
|
|
3602
|
+
} catch {
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
return out;
|
|
3606
|
+
}
|
|
3607
|
+
function lineOf(text, needle) {
|
|
3608
|
+
const idx = text.indexOf(needle);
|
|
3609
|
+
if (idx < 0) return 1;
|
|
3610
|
+
return text.slice(0, idx).split("\n").length;
|
|
3611
|
+
}
|
|
3612
|
+
function snippet(text, line) {
|
|
3613
|
+
const lines = text.split("\n");
|
|
3614
|
+
return (lines[line - 1] ?? "").trim();
|
|
3615
|
+
}
|
|
3616
|
+
function toPosix2(p) {
|
|
3617
|
+
return p.split("\\").join("/");
|
|
3618
|
+
}
|
|
3619
|
+
function languageForPath(relPath) {
|
|
3620
|
+
switch (import_node_path12.default.extname(relPath).toLowerCase()) {
|
|
3621
|
+
case ".py":
|
|
3622
|
+
return "python";
|
|
3623
|
+
case ".ts":
|
|
3624
|
+
case ".tsx":
|
|
3625
|
+
return "typescript";
|
|
3626
|
+
case ".js":
|
|
3627
|
+
case ".jsx":
|
|
3628
|
+
case ".mjs":
|
|
3629
|
+
case ".cjs":
|
|
3630
|
+
return "javascript";
|
|
3631
|
+
default:
|
|
3632
|
+
return void 0;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3636
|
+
let nodesAdded = 0;
|
|
3637
|
+
let edgesAdded = 0;
|
|
3638
|
+
const fileNodeId = (0, import_types8.fileId)(serviceName, relPath);
|
|
3639
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3640
|
+
const language = languageForPath(relPath);
|
|
3641
|
+
const node = {
|
|
3642
|
+
id: fileNodeId,
|
|
3643
|
+
type: import_types8.NodeType.FileNode,
|
|
3644
|
+
service: serviceName,
|
|
3645
|
+
path: relPath,
|
|
3646
|
+
...language ? { language } : {},
|
|
3647
|
+
discoveredVia: "static"
|
|
3648
|
+
};
|
|
3649
|
+
graph.addNode(fileNodeId, node);
|
|
3650
|
+
nodesAdded++;
|
|
3651
|
+
}
|
|
3652
|
+
const containsId = (0, import_types8.extractedEdgeId)(serviceNodeId, fileNodeId, import_types8.EdgeType.CONTAINS);
|
|
3653
|
+
if (!graph.hasEdge(containsId)) {
|
|
3654
|
+
const edge = {
|
|
3655
|
+
id: containsId,
|
|
3656
|
+
source: serviceNodeId,
|
|
3657
|
+
target: fileNodeId,
|
|
3658
|
+
type: import_types8.EdgeType.CONTAINS,
|
|
3659
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3660
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3661
|
+
evidence: { file: relPath }
|
|
3662
|
+
};
|
|
3663
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3664
|
+
edgesAdded++;
|
|
3665
|
+
}
|
|
3666
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3667
|
+
}
|
|
3178
3668
|
|
|
3179
3669
|
// src/extract/databases/db-config-yaml.ts
|
|
3180
3670
|
init_cjs_shims();
|
|
3181
|
-
var
|
|
3671
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3182
3672
|
async function parse(serviceDir) {
|
|
3183
|
-
const yamlPath =
|
|
3184
|
-
if (!await
|
|
3673
|
+
const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
|
|
3674
|
+
if (!await exists2(yamlPath)) return [];
|
|
3185
3675
|
const raw = await readYaml(yamlPath);
|
|
3186
3676
|
return [
|
|
3187
3677
|
{
|
|
@@ -3198,13 +3688,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3198
3688
|
|
|
3199
3689
|
// src/extract/databases/dotenv.ts
|
|
3200
3690
|
init_cjs_shims();
|
|
3201
|
-
var
|
|
3202
|
-
var
|
|
3691
|
+
var import_node_fs14 = require("fs");
|
|
3692
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3203
3693
|
|
|
3204
3694
|
// src/extract/databases/shared.ts
|
|
3205
3695
|
init_cjs_shims();
|
|
3206
|
-
var
|
|
3207
|
-
var
|
|
3696
|
+
var import_node_fs13 = require("fs");
|
|
3697
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3208
3698
|
function schemeToEngine(scheme) {
|
|
3209
3699
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3210
3700
|
switch (s) {
|
|
@@ -3243,14 +3733,14 @@ function parseConnectionString(url) {
|
|
|
3243
3733
|
}
|
|
3244
3734
|
async function readIfExists(filePath) {
|
|
3245
3735
|
try {
|
|
3246
|
-
return await
|
|
3736
|
+
return await import_node_fs13.promises.readFile(filePath, "utf8");
|
|
3247
3737
|
} catch {
|
|
3248
3738
|
return null;
|
|
3249
3739
|
}
|
|
3250
3740
|
}
|
|
3251
3741
|
async function findFirst(serviceDir, candidates) {
|
|
3252
3742
|
for (const rel of candidates) {
|
|
3253
|
-
const abs =
|
|
3743
|
+
const abs = import_node_path14.default.join(serviceDir, rel);
|
|
3254
3744
|
const content = await readIfExists(abs);
|
|
3255
3745
|
if (content !== null) return abs;
|
|
3256
3746
|
}
|
|
@@ -3301,15 +3791,15 @@ function parseDotenvLine(line) {
|
|
|
3301
3791
|
return { key, value };
|
|
3302
3792
|
}
|
|
3303
3793
|
async function parse2(serviceDir) {
|
|
3304
|
-
const entries = await
|
|
3794
|
+
const entries = await import_node_fs14.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3305
3795
|
const configs = [];
|
|
3306
3796
|
const seen = /* @__PURE__ */ new Set();
|
|
3307
3797
|
for (const entry of entries) {
|
|
3308
3798
|
if (!entry.isFile()) continue;
|
|
3309
3799
|
const match = isConfigFile(entry.name);
|
|
3310
3800
|
if (!match.match || match.fileType !== "env") continue;
|
|
3311
|
-
const filePath =
|
|
3312
|
-
const content = await
|
|
3801
|
+
const filePath = import_node_path15.default.join(serviceDir, entry.name);
|
|
3802
|
+
const content = await import_node_fs14.promises.readFile(filePath, "utf8");
|
|
3313
3803
|
for (const line of content.split("\n")) {
|
|
3314
3804
|
const parsed = parseDotenvLine(line);
|
|
3315
3805
|
if (!parsed) continue;
|
|
@@ -3328,9 +3818,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3328
3818
|
|
|
3329
3819
|
// src/extract/databases/prisma.ts
|
|
3330
3820
|
init_cjs_shims();
|
|
3331
|
-
var
|
|
3821
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3332
3822
|
async function parse3(serviceDir) {
|
|
3333
|
-
const schemaPath =
|
|
3823
|
+
const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3334
3824
|
const content = await readIfExists(schemaPath);
|
|
3335
3825
|
if (!content) return [];
|
|
3336
3826
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3462,11 +3952,11 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3462
3952
|
|
|
3463
3953
|
// src/extract/databases/ormconfig.ts
|
|
3464
3954
|
init_cjs_shims();
|
|
3465
|
-
var
|
|
3955
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3466
3956
|
async function parse6(serviceDir) {
|
|
3467
3957
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3468
|
-
const abs =
|
|
3469
|
-
if (!await
|
|
3958
|
+
const abs = import_node_path17.default.join(serviceDir, candidate);
|
|
3959
|
+
if (!await exists2(abs)) continue;
|
|
3470
3960
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3471
3961
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
3472
3962
|
const out = [];
|
|
@@ -3525,10 +4015,10 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3525
4015
|
|
|
3526
4016
|
// src/extract/databases/sequelize.ts
|
|
3527
4017
|
init_cjs_shims();
|
|
3528
|
-
var
|
|
4018
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3529
4019
|
async function parse8(serviceDir) {
|
|
3530
|
-
const configPath =
|
|
3531
|
-
if (!await
|
|
4020
|
+
const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
|
|
4021
|
+
if (!await exists2(configPath)) return [];
|
|
3532
4022
|
const raw = await readJson(configPath);
|
|
3533
4023
|
const out = [];
|
|
3534
4024
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3554,7 +4044,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3554
4044
|
|
|
3555
4045
|
// src/extract/databases/docker-compose.ts
|
|
3556
4046
|
init_cjs_shims();
|
|
3557
|
-
var
|
|
4047
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3558
4048
|
function portFromService(svc) {
|
|
3559
4049
|
for (const raw of svc.ports ?? []) {
|
|
3560
4050
|
const str = String(raw);
|
|
@@ -3581,8 +4071,8 @@ function databaseFromEnv(svc) {
|
|
|
3581
4071
|
}
|
|
3582
4072
|
async function parse9(serviceDir) {
|
|
3583
4073
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3584
|
-
const abs =
|
|
3585
|
-
if (!await
|
|
4074
|
+
const abs = import_node_path19.default.join(serviceDir, name);
|
|
4075
|
+
if (!await exists2(abs)) continue;
|
|
3586
4076
|
const raw = await readYaml(abs);
|
|
3587
4077
|
if (!raw?.services) return [];
|
|
3588
4078
|
const out = [];
|
|
@@ -3622,8 +4112,8 @@ function compatibleDriversFor(engine) {
|
|
|
3622
4112
|
}
|
|
3623
4113
|
function toDatabaseNode(config) {
|
|
3624
4114
|
return {
|
|
3625
|
-
id: (0,
|
|
3626
|
-
type:
|
|
4115
|
+
id: (0, import_types9.databaseId)(config.host),
|
|
4116
|
+
type: import_types9.NodeType.DatabaseNode,
|
|
3627
4117
|
name: config.database || config.host,
|
|
3628
4118
|
engine: config.engine,
|
|
3629
4119
|
engineVersion: config.engineVersion,
|
|
@@ -3753,19 +4243,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3753
4243
|
discoveredVia: mergedDiscoveredVia
|
|
3754
4244
|
});
|
|
3755
4245
|
}
|
|
4246
|
+
const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
|
|
4247
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4248
|
+
graph,
|
|
4249
|
+
service.pkg.name,
|
|
4250
|
+
service.node.id,
|
|
4251
|
+
relConfigFile
|
|
4252
|
+
);
|
|
4253
|
+
nodesAdded += fn;
|
|
4254
|
+
edgesAdded += fe;
|
|
4255
|
+
const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
|
|
3756
4256
|
const edge = {
|
|
3757
|
-
id: (0, import_types5.extractedEdgeId)(
|
|
3758
|
-
source:
|
|
4257
|
+
id: (0, import_types5.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
|
|
4258
|
+
source: fileNodeId,
|
|
3759
4259
|
target: dbNode.id,
|
|
3760
|
-
type:
|
|
3761
|
-
provenance:
|
|
3762
|
-
confidence: (0,
|
|
3763
|
-
|
|
3764
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3765
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3766
|
-
evidence: {
|
|
3767
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3768
|
-
}
|
|
4260
|
+
type: import_types9.EdgeType.CONNECTS_TO,
|
|
4261
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
4262
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
4263
|
+
evidence: { file: evidenceFile }
|
|
3769
4264
|
};
|
|
3770
4265
|
if (!graph.hasEdge(edge.id)) {
|
|
3771
4266
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3791,15 +4286,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3791
4286
|
|
|
3792
4287
|
// src/extract/configs.ts
|
|
3793
4288
|
init_cjs_shims();
|
|
3794
|
-
var
|
|
3795
|
-
var
|
|
3796
|
-
var
|
|
4289
|
+
var import_node_fs15 = require("fs");
|
|
4290
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
4291
|
+
var import_types10 = require("@neat.is/types");
|
|
3797
4292
|
async function walkConfigFiles(dir) {
|
|
3798
4293
|
const out = [];
|
|
3799
4294
|
async function walk(current) {
|
|
3800
|
-
const entries = await
|
|
4295
|
+
const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
|
|
3801
4296
|
for (const entry of entries) {
|
|
3802
|
-
const full =
|
|
4297
|
+
const full = import_node_path21.default.join(current, entry.name);
|
|
3803
4298
|
if (entry.isDirectory()) {
|
|
3804
4299
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3805
4300
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3818,26 +4313,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3818
4313
|
for (const service of services) {
|
|
3819
4314
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3820
4315
|
for (const file of configFiles) {
|
|
3821
|
-
const relPath =
|
|
4316
|
+
const relPath = import_node_path21.default.relative(scanPath, file);
|
|
3822
4317
|
const node = {
|
|
3823
|
-
id: (0,
|
|
3824
|
-
type:
|
|
3825
|
-
name:
|
|
4318
|
+
id: (0, import_types10.configId)(relPath),
|
|
4319
|
+
type: import_types10.NodeType.ConfigNode,
|
|
4320
|
+
name: import_node_path21.default.basename(file),
|
|
3826
4321
|
path: relPath,
|
|
3827
|
-
fileType: isConfigFile(
|
|
4322
|
+
fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
|
|
3828
4323
|
};
|
|
3829
4324
|
if (!graph.hasNode(node.id)) {
|
|
3830
4325
|
graph.addNode(node.id, node);
|
|
3831
4326
|
nodesAdded++;
|
|
3832
4327
|
}
|
|
4328
|
+
const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
|
|
4329
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4330
|
+
graph,
|
|
4331
|
+
service.pkg.name,
|
|
4332
|
+
service.node.id,
|
|
4333
|
+
relToService
|
|
4334
|
+
);
|
|
4335
|
+
nodesAdded += fn;
|
|
4336
|
+
edgesAdded += fe;
|
|
3833
4337
|
const edge = {
|
|
3834
|
-
id: (0, import_types5.extractedEdgeId)(
|
|
3835
|
-
source:
|
|
4338
|
+
id: (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
|
|
4339
|
+
source: fileNodeId,
|
|
3836
4340
|
target: node.id,
|
|
3837
|
-
type:
|
|
3838
|
-
provenance:
|
|
3839
|
-
confidence: (0,
|
|
3840
|
-
evidence: { file: relPath.split(
|
|
4341
|
+
type: import_types10.EdgeType.CONFIGURED_BY,
|
|
4342
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
4343
|
+
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4344
|
+
evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
|
|
3841
4345
|
};
|
|
3842
4346
|
if (!graph.hasEdge(edge.id)) {
|
|
3843
4347
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3854,110 +4358,11 @@ var import_types16 = require("@neat.is/types");
|
|
|
3854
4358
|
|
|
3855
4359
|
// src/extract/calls/http.ts
|
|
3856
4360
|
init_cjs_shims();
|
|
3857
|
-
var
|
|
4361
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3858
4362
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3859
4363
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3860
4364
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3861
4365
|
var import_types11 = require("@neat.is/types");
|
|
3862
|
-
|
|
3863
|
-
// src/extract/calls/shared.ts
|
|
3864
|
-
init_cjs_shims();
|
|
3865
|
-
var import_node_fs13 = require("fs");
|
|
3866
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3867
|
-
var import_types10 = require("@neat.is/types");
|
|
3868
|
-
async function walkSourceFiles(dir) {
|
|
3869
|
-
const out = [];
|
|
3870
|
-
async function walk(current) {
|
|
3871
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3872
|
-
for (const entry of entries) {
|
|
3873
|
-
const full = import_node_path19.default.join(current, entry.name);
|
|
3874
|
-
if (entry.isDirectory()) {
|
|
3875
|
-
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3876
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3877
|
-
await walk(full);
|
|
3878
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3879
|
-
out.push(full);
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
await walk(dir);
|
|
3884
|
-
return out;
|
|
3885
|
-
}
|
|
3886
|
-
async function loadSourceFiles(dir) {
|
|
3887
|
-
const paths = await walkSourceFiles(dir);
|
|
3888
|
-
const out = [];
|
|
3889
|
-
for (const p of paths) {
|
|
3890
|
-
try {
|
|
3891
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3892
|
-
out.push({ path: p, content });
|
|
3893
|
-
} catch {
|
|
3894
|
-
}
|
|
3895
|
-
}
|
|
3896
|
-
return out;
|
|
3897
|
-
}
|
|
3898
|
-
function lineOf(text, needle) {
|
|
3899
|
-
const idx = text.indexOf(needle);
|
|
3900
|
-
if (idx < 0) return 1;
|
|
3901
|
-
return text.slice(0, idx).split("\n").length;
|
|
3902
|
-
}
|
|
3903
|
-
function snippet(text, line) {
|
|
3904
|
-
const lines = text.split("\n");
|
|
3905
|
-
return (lines[line - 1] ?? "").trim();
|
|
3906
|
-
}
|
|
3907
|
-
function toPosix2(p) {
|
|
3908
|
-
return p.split("\\").join("/");
|
|
3909
|
-
}
|
|
3910
|
-
function languageForPath(relPath) {
|
|
3911
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3912
|
-
case ".py":
|
|
3913
|
-
return "python";
|
|
3914
|
-
case ".ts":
|
|
3915
|
-
case ".tsx":
|
|
3916
|
-
return "typescript";
|
|
3917
|
-
case ".js":
|
|
3918
|
-
case ".jsx":
|
|
3919
|
-
case ".mjs":
|
|
3920
|
-
case ".cjs":
|
|
3921
|
-
return "javascript";
|
|
3922
|
-
default:
|
|
3923
|
-
return void 0;
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3927
|
-
let nodesAdded = 0;
|
|
3928
|
-
let edgesAdded = 0;
|
|
3929
|
-
const fileNodeId = (0, import_types10.fileId)(serviceName, relPath);
|
|
3930
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3931
|
-
const language = languageForPath(relPath);
|
|
3932
|
-
const node = {
|
|
3933
|
-
id: fileNodeId,
|
|
3934
|
-
type: import_types10.NodeType.FileNode,
|
|
3935
|
-
service: serviceName,
|
|
3936
|
-
path: relPath,
|
|
3937
|
-
...language ? { language } : {},
|
|
3938
|
-
discoveredVia: "static"
|
|
3939
|
-
};
|
|
3940
|
-
graph.addNode(fileNodeId, node);
|
|
3941
|
-
nodesAdded++;
|
|
3942
|
-
}
|
|
3943
|
-
const containsId = (0, import_types10.extractedEdgeId)(serviceNodeId, fileNodeId, import_types10.EdgeType.CONTAINS);
|
|
3944
|
-
if (!graph.hasEdge(containsId)) {
|
|
3945
|
-
const edge = {
|
|
3946
|
-
id: containsId,
|
|
3947
|
-
source: serviceNodeId,
|
|
3948
|
-
target: fileNodeId,
|
|
3949
|
-
type: import_types10.EdgeType.CONTAINS,
|
|
3950
|
-
provenance: import_types10.Provenance.EXTRACTED,
|
|
3951
|
-
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
3952
|
-
evidence: { file: relPath }
|
|
3953
|
-
};
|
|
3954
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3955
|
-
edgesAdded++;
|
|
3956
|
-
}
|
|
3957
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3958
|
-
}
|
|
3959
|
-
|
|
3960
|
-
// src/extract/calls/http.ts
|
|
3961
4366
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3962
4367
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3963
4368
|
function isInsideJsxExternalLink(node) {
|
|
@@ -3985,8 +4390,14 @@ function collectStringLiterals(node, out) {
|
|
|
3985
4390
|
if (child) collectStringLiterals(child, out);
|
|
3986
4391
|
}
|
|
3987
4392
|
}
|
|
4393
|
+
var PARSE_CHUNK = 16384;
|
|
4394
|
+
function parseSource(parser, source) {
|
|
4395
|
+
return parser.parse(
|
|
4396
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
4397
|
+
);
|
|
4398
|
+
}
|
|
3988
4399
|
function callsFromSource(source, parser, knownHosts) {
|
|
3989
|
-
const tree = parser
|
|
4400
|
+
const tree = parseSource(parser, source);
|
|
3990
4401
|
const literals = [];
|
|
3991
4402
|
collectStringLiterals(tree.rootNode, literals);
|
|
3992
4403
|
const out = [];
|
|
@@ -4016,9 +4427,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4016
4427
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
4017
4428
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4018
4429
|
for (const service of services) {
|
|
4019
|
-
knownHosts.add(
|
|
4430
|
+
knownHosts.add(import_node_path22.default.basename(service.dir));
|
|
4020
4431
|
knownHosts.add(service.pkg.name);
|
|
4021
|
-
hostToNodeId.set(
|
|
4432
|
+
hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
|
|
4022
4433
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4023
4434
|
}
|
|
4024
4435
|
let nodesAdded = 0;
|
|
@@ -4028,7 +4439,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4028
4439
|
const seen = /* @__PURE__ */ new Set();
|
|
4029
4440
|
for (const file of files) {
|
|
4030
4441
|
if (isTestPath(file.path)) continue;
|
|
4031
|
-
const parser =
|
|
4442
|
+
const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
4032
4443
|
let sites;
|
|
4033
4444
|
try {
|
|
4034
4445
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -4037,7 +4448,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4037
4448
|
continue;
|
|
4038
4449
|
}
|
|
4039
4450
|
if (sites.length === 0) continue;
|
|
4040
|
-
const relFile = toPosix2(
|
|
4451
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
4041
4452
|
for (const site of sites) {
|
|
4042
4453
|
const targetId = hostToNodeId.get(site.host);
|
|
4043
4454
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -4091,7 +4502,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4091
4502
|
|
|
4092
4503
|
// src/extract/calls/kafka.ts
|
|
4093
4504
|
init_cjs_shims();
|
|
4094
|
-
var
|
|
4505
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
4095
4506
|
var import_types12 = require("@neat.is/types");
|
|
4096
4507
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4097
4508
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -4122,7 +4533,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4122
4533
|
// tier (ADR-066).
|
|
4123
4534
|
confidenceKind: "verified-call-site",
|
|
4124
4535
|
evidence: {
|
|
4125
|
-
file:
|
|
4536
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
4126
4537
|
line,
|
|
4127
4538
|
snippet: snippet(file.content, line)
|
|
4128
4539
|
}
|
|
@@ -4135,7 +4546,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4135
4546
|
|
|
4136
4547
|
// src/extract/calls/redis.ts
|
|
4137
4548
|
init_cjs_shims();
|
|
4138
|
-
var
|
|
4549
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4139
4550
|
var import_types13 = require("@neat.is/types");
|
|
4140
4551
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
4141
4552
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -4158,7 +4569,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4158
4569
|
// support tier (ADR-066).
|
|
4159
4570
|
confidenceKind: "url-with-structural-support",
|
|
4160
4571
|
evidence: {
|
|
4161
|
-
file:
|
|
4572
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
4162
4573
|
line,
|
|
4163
4574
|
snippet: snippet(file.content, line)
|
|
4164
4575
|
}
|
|
@@ -4169,7 +4580,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4169
4580
|
|
|
4170
4581
|
// src/extract/calls/aws.ts
|
|
4171
4582
|
init_cjs_shims();
|
|
4172
|
-
var
|
|
4583
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
4173
4584
|
var import_types14 = require("@neat.is/types");
|
|
4174
4585
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4175
4586
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -4203,7 +4614,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4203
4614
|
// (ADR-066).
|
|
4204
4615
|
confidenceKind: "verified-call-site",
|
|
4205
4616
|
evidence: {
|
|
4206
|
-
file:
|
|
4617
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
4207
4618
|
line,
|
|
4208
4619
|
snippet: snippet(file.content, line)
|
|
4209
4620
|
}
|
|
@@ -4228,13 +4639,14 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4228
4639
|
|
|
4229
4640
|
// src/extract/calls/grpc.ts
|
|
4230
4641
|
init_cjs_shims();
|
|
4231
|
-
var
|
|
4642
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
4232
4643
|
var import_types15 = require("@neat.is/types");
|
|
4233
4644
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4234
4645
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4235
4646
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4236
4647
|
function isLikelyAddress(value) {
|
|
4237
4648
|
if (!value) return false;
|
|
4649
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4238
4650
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4239
4651
|
}
|
|
4240
4652
|
function normaliseForMatch(s) {
|
|
@@ -4286,7 +4698,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4286
4698
|
// tier (ADR-066).
|
|
4287
4699
|
confidenceKind: "verified-call-site",
|
|
4288
4700
|
evidence: {
|
|
4289
|
-
file:
|
|
4701
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
4290
4702
|
line,
|
|
4291
4703
|
snippet: snippet(file.content, line)
|
|
4292
4704
|
}
|
|
@@ -4397,7 +4809,7 @@ init_cjs_shims();
|
|
|
4397
4809
|
|
|
4398
4810
|
// src/extract/infra/docker-compose.ts
|
|
4399
4811
|
init_cjs_shims();
|
|
4400
|
-
var
|
|
4812
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
4401
4813
|
var import_types18 = require("@neat.is/types");
|
|
4402
4814
|
|
|
4403
4815
|
// src/extract/infra/shared.ts
|
|
@@ -4435,7 +4847,7 @@ function dependsOnList(value) {
|
|
|
4435
4847
|
}
|
|
4436
4848
|
function serviceNameToServiceNode(name, services) {
|
|
4437
4849
|
for (const s of services) {
|
|
4438
|
-
if (s.node.name === name ||
|
|
4850
|
+
if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
|
|
4439
4851
|
}
|
|
4440
4852
|
return null;
|
|
4441
4853
|
}
|
|
@@ -4444,8 +4856,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4444
4856
|
let edgesAdded = 0;
|
|
4445
4857
|
let composePath = null;
|
|
4446
4858
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4447
|
-
const abs =
|
|
4448
|
-
if (await
|
|
4859
|
+
const abs = import_node_path27.default.join(scanPath, name);
|
|
4860
|
+
if (await exists2(abs)) {
|
|
4449
4861
|
composePath = abs;
|
|
4450
4862
|
break;
|
|
4451
4863
|
}
|
|
@@ -4457,13 +4869,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4457
4869
|
} catch (err) {
|
|
4458
4870
|
recordExtractionError(
|
|
4459
4871
|
"infra docker-compose",
|
|
4460
|
-
|
|
4872
|
+
import_node_path27.default.relative(scanPath, composePath),
|
|
4461
4873
|
err
|
|
4462
4874
|
);
|
|
4463
4875
|
return { nodesAdded, edgesAdded };
|
|
4464
4876
|
}
|
|
4465
4877
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4466
|
-
const evidenceFile =
|
|
4878
|
+
const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
|
|
4467
4879
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4468
4880
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4469
4881
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4505,8 +4917,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4505
4917
|
|
|
4506
4918
|
// src/extract/infra/dockerfile.ts
|
|
4507
4919
|
init_cjs_shims();
|
|
4508
|
-
var
|
|
4509
|
-
var
|
|
4920
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
4921
|
+
var import_node_fs16 = require("fs");
|
|
4510
4922
|
var import_types19 = require("@neat.is/types");
|
|
4511
4923
|
function runtimeImage(content) {
|
|
4512
4924
|
const lines = content.split("\n");
|
|
@@ -4526,15 +4938,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4526
4938
|
let nodesAdded = 0;
|
|
4527
4939
|
let edgesAdded = 0;
|
|
4528
4940
|
for (const service of services) {
|
|
4529
|
-
const dockerfilePath =
|
|
4530
|
-
if (!await
|
|
4941
|
+
const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
|
|
4942
|
+
if (!await exists2(dockerfilePath)) continue;
|
|
4531
4943
|
let content;
|
|
4532
4944
|
try {
|
|
4533
|
-
content = await
|
|
4945
|
+
content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
|
|
4534
4946
|
} catch (err) {
|
|
4535
4947
|
recordExtractionError(
|
|
4536
4948
|
"infra dockerfile",
|
|
4537
|
-
|
|
4949
|
+
import_node_path28.default.relative(scanPath, dockerfilePath),
|
|
4538
4950
|
err
|
|
4539
4951
|
);
|
|
4540
4952
|
continue;
|
|
@@ -4546,17 +4958,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4546
4958
|
graph.addNode(node.id, node);
|
|
4547
4959
|
nodesAdded++;
|
|
4548
4960
|
}
|
|
4549
|
-
const
|
|
4961
|
+
const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
|
|
4962
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4963
|
+
graph,
|
|
4964
|
+
service.pkg.name,
|
|
4965
|
+
service.node.id,
|
|
4966
|
+
relDockerfile
|
|
4967
|
+
);
|
|
4968
|
+
nodesAdded += fn;
|
|
4969
|
+
edgesAdded += fe;
|
|
4970
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
|
|
4550
4971
|
if (!graph.hasEdge(edgeId)) {
|
|
4551
4972
|
const edge = {
|
|
4552
4973
|
id: edgeId,
|
|
4553
|
-
source:
|
|
4974
|
+
source: fileNodeId,
|
|
4554
4975
|
target: node.id,
|
|
4555
4976
|
type: import_types19.EdgeType.RUNS_ON,
|
|
4556
4977
|
provenance: import_types19.Provenance.EXTRACTED,
|
|
4557
4978
|
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4558
4979
|
evidence: {
|
|
4559
|
-
file:
|
|
4980
|
+
file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
|
|
4560
4981
|
}
|
|
4561
4982
|
};
|
|
4562
4983
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4568,21 +4989,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4568
4989
|
|
|
4569
4990
|
// src/extract/infra/terraform.ts
|
|
4570
4991
|
init_cjs_shims();
|
|
4571
|
-
var
|
|
4572
|
-
var
|
|
4992
|
+
var import_node_fs17 = require("fs");
|
|
4993
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4573
4994
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4574
4995
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4575
4996
|
if (depth > max) return [];
|
|
4576
4997
|
const out = [];
|
|
4577
|
-
const entries = await
|
|
4998
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4578
4999
|
for (const entry of entries) {
|
|
4579
5000
|
if (entry.isDirectory()) {
|
|
4580
5001
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
4581
|
-
const child =
|
|
5002
|
+
const child = import_node_path29.default.join(start, entry.name);
|
|
4582
5003
|
if (await isPythonVenvDir(child)) continue;
|
|
4583
5004
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4584
5005
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
4585
|
-
out.push(
|
|
5006
|
+
out.push(import_node_path29.default.join(start, entry.name));
|
|
4586
5007
|
}
|
|
4587
5008
|
}
|
|
4588
5009
|
return out;
|
|
@@ -4591,7 +5012,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4591
5012
|
let nodesAdded = 0;
|
|
4592
5013
|
const files = await walkTfFiles(scanPath);
|
|
4593
5014
|
for (const file of files) {
|
|
4594
|
-
const content = await
|
|
5015
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
4595
5016
|
RESOURCE_RE.lastIndex = 0;
|
|
4596
5017
|
let m;
|
|
4597
5018
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -4609,8 +5030,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4609
5030
|
|
|
4610
5031
|
// src/extract/infra/k8s.ts
|
|
4611
5032
|
init_cjs_shims();
|
|
4612
|
-
var
|
|
4613
|
-
var
|
|
5033
|
+
var import_node_fs18 = require("fs");
|
|
5034
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4614
5035
|
var import_yaml3 = require("yaml");
|
|
4615
5036
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4616
5037
|
Service: "k8s-service",
|
|
@@ -4624,15 +5045,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4624
5045
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4625
5046
|
if (depth > max) return [];
|
|
4626
5047
|
const out = [];
|
|
4627
|
-
const entries = await
|
|
5048
|
+
const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4628
5049
|
for (const entry of entries) {
|
|
4629
5050
|
if (entry.isDirectory()) {
|
|
4630
5051
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4631
|
-
const child =
|
|
5052
|
+
const child = import_node_path30.default.join(start, entry.name);
|
|
4632
5053
|
if (await isPythonVenvDir(child)) continue;
|
|
4633
5054
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4634
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4635
|
-
out.push(
|
|
5055
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry.name))) {
|
|
5056
|
+
out.push(import_node_path30.default.join(start, entry.name));
|
|
4636
5057
|
}
|
|
4637
5058
|
}
|
|
4638
5059
|
return out;
|
|
@@ -4641,7 +5062,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4641
5062
|
let nodesAdded = 0;
|
|
4642
5063
|
const files = await walkYamlFiles2(scanPath);
|
|
4643
5064
|
for (const file of files) {
|
|
4644
|
-
const content = await
|
|
5065
|
+
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
4645
5066
|
let docs;
|
|
4646
5067
|
try {
|
|
4647
5068
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -4676,12 +5097,12 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4676
5097
|
}
|
|
4677
5098
|
|
|
4678
5099
|
// src/extract/index.ts
|
|
4679
|
-
var
|
|
5100
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4680
5101
|
|
|
4681
5102
|
// src/extract/retire.ts
|
|
4682
5103
|
init_cjs_shims();
|
|
4683
|
-
var
|
|
4684
|
-
var
|
|
5104
|
+
var import_node_fs19 = require("fs");
|
|
5105
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4685
5106
|
var import_types20 = require("@neat.is/types");
|
|
4686
5107
|
function dropOrphanedFileNodes(graph) {
|
|
4687
5108
|
const orphans = [];
|
|
@@ -4702,11 +5123,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4702
5123
|
if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
|
|
4703
5124
|
const evidenceFile = edge.evidence?.file;
|
|
4704
5125
|
if (!evidenceFile) return;
|
|
4705
|
-
if (
|
|
4706
|
-
if (!(0,
|
|
5126
|
+
if (import_node_path31.default.isAbsolute(evidenceFile)) {
|
|
5127
|
+
if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
|
|
4707
5128
|
return;
|
|
4708
5129
|
}
|
|
4709
|
-
const found = bases.some((base) => (0,
|
|
5130
|
+
const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path31.default.join(base, evidenceFile)));
|
|
4710
5131
|
if (!found) toDrop.push(id);
|
|
4711
5132
|
});
|
|
4712
5133
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4744,7 +5165,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4744
5165
|
}
|
|
4745
5166
|
const droppedEntries = drainDroppedExtracted();
|
|
4746
5167
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4747
|
-
const rejectedPath =
|
|
5168
|
+
const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4748
5169
|
try {
|
|
4749
5170
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4750
5171
|
} catch (err) {
|
|
@@ -4778,7 +5199,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4778
5199
|
|
|
4779
5200
|
// src/diff.ts
|
|
4780
5201
|
init_cjs_shims();
|
|
4781
|
-
var
|
|
5202
|
+
var import_node_fs20 = require("fs");
|
|
4782
5203
|
async function loadSnapshotForDiff(target) {
|
|
4783
5204
|
if (/^https?:\/\//i.test(target)) {
|
|
4784
5205
|
const res = await fetch(target);
|
|
@@ -4787,7 +5208,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4787
5208
|
}
|
|
4788
5209
|
return await res.json();
|
|
4789
5210
|
}
|
|
4790
|
-
const raw = await
|
|
5211
|
+
const raw = await import_node_fs20.promises.readFile(target, "utf8");
|
|
4791
5212
|
return JSON.parse(raw);
|
|
4792
5213
|
}
|
|
4793
5214
|
function indexEntries(entries) {
|
|
@@ -4855,8 +5276,8 @@ function canonicalJson(value) {
|
|
|
4855
5276
|
|
|
4856
5277
|
// src/persist.ts
|
|
4857
5278
|
init_cjs_shims();
|
|
4858
|
-
var
|
|
4859
|
-
var
|
|
5279
|
+
var import_node_fs21 = require("fs");
|
|
5280
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4860
5281
|
var import_types21 = require("@neat.is/types");
|
|
4861
5282
|
var SCHEMA_VERSION = 4;
|
|
4862
5283
|
function migrateV1ToV2(payload) {
|
|
@@ -4893,7 +5314,7 @@ function migrateV2ToV3(payload) {
|
|
|
4893
5314
|
return { ...payload, schemaVersion: 3 };
|
|
4894
5315
|
}
|
|
4895
5316
|
async function ensureDir(filePath) {
|
|
4896
|
-
await
|
|
5317
|
+
await import_node_fs21.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
|
|
4897
5318
|
}
|
|
4898
5319
|
async function saveGraphToDisk(graph, outPath) {
|
|
4899
5320
|
await ensureDir(outPath);
|
|
@@ -4903,13 +5324,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4903
5324
|
graph: graph.export()
|
|
4904
5325
|
};
|
|
4905
5326
|
const tmp = `${outPath}.tmp`;
|
|
4906
|
-
await
|
|
4907
|
-
await
|
|
5327
|
+
await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
5328
|
+
await import_node_fs21.promises.rename(tmp, outPath);
|
|
4908
5329
|
}
|
|
4909
5330
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4910
5331
|
let raw;
|
|
4911
5332
|
try {
|
|
4912
|
-
raw = await
|
|
5333
|
+
raw = await import_node_fs21.promises.readFile(outPath, "utf8");
|
|
4913
5334
|
} catch (err) {
|
|
4914
5335
|
if (err.code === "ENOENT") return;
|
|
4915
5336
|
throw err;
|
|
@@ -4968,23 +5389,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
4968
5389
|
|
|
4969
5390
|
// src/projects.ts
|
|
4970
5391
|
init_cjs_shims();
|
|
4971
|
-
var
|
|
5392
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
4972
5393
|
function pathsForProject(project, baseDir) {
|
|
4973
5394
|
if (project === DEFAULT_PROJECT) {
|
|
4974
5395
|
return {
|
|
4975
|
-
snapshotPath:
|
|
4976
|
-
errorsPath:
|
|
4977
|
-
staleEventsPath:
|
|
4978
|
-
embeddingsCachePath:
|
|
4979
|
-
policyViolationsPath:
|
|
5396
|
+
snapshotPath: import_node_path34.default.join(baseDir, "graph.json"),
|
|
5397
|
+
errorsPath: import_node_path34.default.join(baseDir, "errors.ndjson"),
|
|
5398
|
+
staleEventsPath: import_node_path34.default.join(baseDir, "stale-events.ndjson"),
|
|
5399
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, "embeddings.json"),
|
|
5400
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, "policy-violations.ndjson")
|
|
4980
5401
|
};
|
|
4981
5402
|
}
|
|
4982
5403
|
return {
|
|
4983
|
-
snapshotPath:
|
|
4984
|
-
errorsPath:
|
|
4985
|
-
staleEventsPath:
|
|
4986
|
-
embeddingsCachePath:
|
|
4987
|
-
policyViolationsPath:
|
|
5404
|
+
snapshotPath: import_node_path34.default.join(baseDir, `${project}.json`),
|
|
5405
|
+
errorsPath: import_node_path34.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5406
|
+
staleEventsPath: import_node_path34.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5407
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, `embeddings.${project}.json`),
|
|
5408
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4988
5409
|
};
|
|
4989
5410
|
}
|
|
4990
5411
|
var Projects = class {
|
|
@@ -5024,23 +5445,23 @@ function parseExtraProjects(raw) {
|
|
|
5024
5445
|
|
|
5025
5446
|
// src/registry.ts
|
|
5026
5447
|
init_cjs_shims();
|
|
5027
|
-
var
|
|
5028
|
-
var
|
|
5029
|
-
var
|
|
5448
|
+
var import_node_fs22 = require("fs");
|
|
5449
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5450
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5030
5451
|
var import_types22 = require("@neat.is/types");
|
|
5031
5452
|
function neatHome() {
|
|
5032
5453
|
const override = process.env.NEAT_HOME;
|
|
5033
|
-
if (override && override.length > 0) return
|
|
5034
|
-
return
|
|
5454
|
+
if (override && override.length > 0) return import_node_path35.default.resolve(override);
|
|
5455
|
+
return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5035
5456
|
}
|
|
5036
5457
|
function registryPath() {
|
|
5037
|
-
return
|
|
5458
|
+
return import_node_path35.default.join(neatHome(), "projects.json");
|
|
5038
5459
|
}
|
|
5039
5460
|
async function readRegistry() {
|
|
5040
5461
|
const file = registryPath();
|
|
5041
5462
|
let raw;
|
|
5042
5463
|
try {
|
|
5043
|
-
raw = await
|
|
5464
|
+
raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5044
5465
|
} catch (err) {
|
|
5045
5466
|
if (err.code === "ENOENT") {
|
|
5046
5467
|
return { version: 1, projects: [] };
|
|
@@ -5517,6 +5938,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5517
5938
|
violations
|
|
5518
5939
|
};
|
|
5519
5940
|
});
|
|
5941
|
+
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
5942
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5943
|
+
if (!proj) return;
|
|
5944
|
+
if (!proj.scanPath) {
|
|
5945
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
5946
|
+
}
|
|
5947
|
+
try {
|
|
5948
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
5949
|
+
return { libraries: results };
|
|
5950
|
+
} catch (err) {
|
|
5951
|
+
return reply.code(500).send({ error: err.message });
|
|
5952
|
+
}
|
|
5953
|
+
});
|
|
5954
|
+
scope.get("/extend/lookup", async (req, reply) => {
|
|
5955
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5956
|
+
if (!proj) return;
|
|
5957
|
+
const { library, version } = req.query;
|
|
5958
|
+
if (!library) {
|
|
5959
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
5960
|
+
}
|
|
5961
|
+
const result = lookupInstrumentation(library, version);
|
|
5962
|
+
if (!result) {
|
|
5963
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
5964
|
+
}
|
|
5965
|
+
return result;
|
|
5966
|
+
});
|
|
5967
|
+
scope.get("/extend/describe", async (req, reply) => {
|
|
5968
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5969
|
+
if (!proj) return;
|
|
5970
|
+
if (!proj.scanPath) {
|
|
5971
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
5972
|
+
}
|
|
5973
|
+
try {
|
|
5974
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
5975
|
+
return state;
|
|
5976
|
+
} catch (err) {
|
|
5977
|
+
return reply.code(500).send({ error: err.message });
|
|
5978
|
+
}
|
|
5979
|
+
});
|
|
5980
|
+
scope.post("/extend/apply", async (req, reply) => {
|
|
5981
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5982
|
+
if (!proj) return;
|
|
5983
|
+
if (!proj.scanPath) {
|
|
5984
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
5985
|
+
}
|
|
5986
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
5987
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
5988
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
5989
|
+
}
|
|
5990
|
+
try {
|
|
5991
|
+
const result = await applyExtension(
|
|
5992
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
5993
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
5994
|
+
);
|
|
5995
|
+
return result;
|
|
5996
|
+
} catch (err) {
|
|
5997
|
+
return reply.code(500).send({ error: err.message });
|
|
5998
|
+
}
|
|
5999
|
+
});
|
|
6000
|
+
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6001
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6002
|
+
if (!proj) return;
|
|
6003
|
+
if (!proj.scanPath) {
|
|
6004
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6005
|
+
}
|
|
6006
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6007
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6008
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6009
|
+
}
|
|
6010
|
+
try {
|
|
6011
|
+
const result = await dryRunExtension(
|
|
6012
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6013
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6014
|
+
);
|
|
6015
|
+
return result;
|
|
6016
|
+
} catch (err) {
|
|
6017
|
+
return reply.code(500).send({ error: err.message });
|
|
6018
|
+
}
|
|
6019
|
+
});
|
|
6020
|
+
scope.post("/extend/rollback", async (req, reply) => {
|
|
6021
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6022
|
+
if (!proj) return;
|
|
6023
|
+
if (!proj.scanPath) {
|
|
6024
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6025
|
+
}
|
|
6026
|
+
const { library } = req.body ?? {};
|
|
6027
|
+
if (!library) {
|
|
6028
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6029
|
+
}
|
|
6030
|
+
try {
|
|
6031
|
+
const result = await rollbackExtension(
|
|
6032
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6033
|
+
{ library }
|
|
6034
|
+
);
|
|
6035
|
+
return result;
|
|
6036
|
+
} catch (err) {
|
|
6037
|
+
return reply.code(500).send({ error: err.message });
|
|
6038
|
+
}
|
|
6039
|
+
});
|
|
5520
6040
|
}
|
|
5521
6041
|
async function buildApi(opts) {
|
|
5522
6042
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5627,8 +6147,8 @@ init_otel_grpc();
|
|
|
5627
6147
|
|
|
5628
6148
|
// src/search.ts
|
|
5629
6149
|
init_cjs_shims();
|
|
5630
|
-
var
|
|
5631
|
-
var
|
|
6150
|
+
var import_node_fs23 = require("fs");
|
|
6151
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5632
6152
|
var import_node_crypto3 = require("crypto");
|
|
5633
6153
|
var DEFAULT_LIMIT = 10;
|
|
5634
6154
|
var NOMIC_DIM = 768;
|
|
@@ -5758,7 +6278,7 @@ async function pickEmbedder() {
|
|
|
5758
6278
|
}
|
|
5759
6279
|
async function readCache(cachePath) {
|
|
5760
6280
|
try {
|
|
5761
|
-
const raw = await
|
|
6281
|
+
const raw = await import_node_fs23.promises.readFile(cachePath, "utf8");
|
|
5762
6282
|
const parsed = JSON.parse(raw);
|
|
5763
6283
|
if (parsed.version !== 1) return null;
|
|
5764
6284
|
return parsed;
|
|
@@ -5767,8 +6287,8 @@ async function readCache(cachePath) {
|
|
|
5767
6287
|
}
|
|
5768
6288
|
}
|
|
5769
6289
|
async function writeCache(cachePath, cache) {
|
|
5770
|
-
await
|
|
5771
|
-
await
|
|
6290
|
+
await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(cachePath), { recursive: true });
|
|
6291
|
+
await import_node_fs23.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
5772
6292
|
}
|
|
5773
6293
|
var VectorIndex = class {
|
|
5774
6294
|
constructor(embedder, cachePath) {
|
|
@@ -5948,14 +6468,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
5948
6468
|
async function main() {
|
|
5949
6469
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
5950
6470
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
5951
|
-
const baseDir = baseDirEnv ?
|
|
5952
|
-
const defaultScanPath =
|
|
6471
|
+
const baseDir = baseDirEnv ? import_node_path39.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path39.default.resolve(import_node_path39.default.dirname(legacyOutPath)) : import_node_path39.default.resolve("./neat-out");
|
|
6472
|
+
const defaultScanPath = import_node_path39.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
5953
6473
|
const registry = new Projects();
|
|
5954
6474
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
5955
6475
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
5956
6476
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
5957
6477
|
const projectScan = process.env[envKey];
|
|
5958
|
-
await bootProject(registry, name, projectScan ?
|
|
6478
|
+
await bootProject(registry, name, projectScan ? import_node_path39.default.resolve(projectScan) : void 0, baseDir);
|
|
5959
6479
|
}
|
|
5960
6480
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
5961
6481
|
const port = Number(process.env.PORT ?? 8080);
|