@neat.is/core 0.4.11 → 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-W4RNPPB5.js → chunk-7JY6F7BY.js} +2 -2
- package/dist/{chunk-OJHI33LD.js → chunk-WDG4QEFO.js} +616 -173
- package/dist/chunk-WDG4QEFO.js.map +1 -0
- package/dist/cli.cjs +994 -534
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +189 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +669 -226
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +667 -224
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +802 -359
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-OJHI33LD.js.map +0 -1
- /package/dist/{chunk-W4RNPPB5.js.map → chunk-7JY6F7BY.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -56,9 +56,9 @@ function mountBearerAuth(app, opts) {
|
|
|
56
56
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
57
57
|
const publicRead = opts.publicRead === true;
|
|
58
58
|
app.addHook("preHandler", (req, reply, done) => {
|
|
59
|
-
const
|
|
59
|
+
const 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,8 @@ init_cjs_shims();
|
|
|
1817
2132
|
|
|
1818
2133
|
// src/ingest.ts
|
|
1819
2134
|
init_cjs_shims();
|
|
1820
|
-
var
|
|
1821
|
-
var
|
|
2135
|
+
var import_node_fs5 = require("fs");
|
|
2136
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1822
2137
|
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
1823
2138
|
var import_types4 = require("@neat.is/types");
|
|
1824
2139
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
@@ -1918,7 +2233,7 @@ function languageForExt(relPath) {
|
|
|
1918
2233
|
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1919
2234
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1920
2235
|
if (scanPath && scanPath.length > 0) {
|
|
1921
|
-
const absRoot = toPosix(
|
|
2236
|
+
const absRoot = toPosix(import_node_path5.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
1922
2237
|
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
1923
2238
|
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
1924
2239
|
}
|
|
@@ -1946,10 +2261,10 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
1946
2261
|
entry = null;
|
|
1947
2262
|
const mapPath = `${absFilepath}.map`;
|
|
1948
2263
|
try {
|
|
1949
|
-
if ((0,
|
|
1950
|
-
const raw = JSON.parse((0,
|
|
2264
|
+
if ((0, import_node_fs5.existsSync)(mapPath)) {
|
|
2265
|
+
const raw = JSON.parse((0, import_node_fs5.readFileSync)(mapPath, "utf8"));
|
|
1951
2266
|
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
1952
|
-
entry = { consumer, dir:
|
|
2267
|
+
entry = { consumer, dir: import_node_path5.default.dirname(mapPath) };
|
|
1953
2268
|
}
|
|
1954
2269
|
} catch {
|
|
1955
2270
|
entry = null;
|
|
@@ -1964,7 +2279,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
1964
2279
|
});
|
|
1965
2280
|
if (!pos || !pos.source) return null;
|
|
1966
2281
|
const root = entry.consumer.sourceRoot ?? "";
|
|
1967
|
-
const resolved =
|
|
2282
|
+
const resolved = import_node_path5.default.resolve(entry.dir, root, pos.source);
|
|
1968
2283
|
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
1969
2284
|
} catch {
|
|
1970
2285
|
return null;
|
|
@@ -2146,7 +2461,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
2146
2461
|
graph.addNode(id, node);
|
|
2147
2462
|
return id;
|
|
2148
2463
|
}
|
|
2149
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
2464
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
2150
2465
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
2151
2466
|
const id = makeObservedEdgeId(type, source, target);
|
|
2152
2467
|
if (graph.hasEdge(id)) {
|
|
@@ -2183,7 +2498,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
2183
2498
|
confidence: (0, import_types4.confidenceForObservedSignal)(signal),
|
|
2184
2499
|
lastObserved: ts,
|
|
2185
2500
|
callCount: 1,
|
|
2186
|
-
signal
|
|
2501
|
+
signal,
|
|
2502
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2503
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
2504
|
+
...evidence ? { evidence } : {}
|
|
2187
2505
|
};
|
|
2188
2506
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2189
2507
|
return { edge, created: true };
|
|
@@ -2228,8 +2546,8 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
2228
2546
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2229
2547
|
}
|
|
2230
2548
|
async function appendErrorEvent(ctx, ev) {
|
|
2231
|
-
await
|
|
2232
|
-
await
|
|
2549
|
+
await import_node_fs5.promises.mkdir(import_node_path5.default.dirname(ctx.errorsPath), { recursive: true });
|
|
2550
|
+
await import_node_fs5.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
2233
2551
|
}
|
|
2234
2552
|
function sanitizeAttributes(attrs) {
|
|
2235
2553
|
const out = {};
|
|
@@ -2252,6 +2570,7 @@ async function handleSpan(ctx, span) {
|
|
|
2252
2570
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2253
2571
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2254
2572
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2573
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2255
2574
|
let affectedNode = sourceId;
|
|
2256
2575
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2257
2576
|
if (span.dbSystem) {
|
|
@@ -2265,7 +2584,8 @@ async function handleSpan(ctx, span) {
|
|
|
2265
2584
|
observedSource(),
|
|
2266
2585
|
targetId,
|
|
2267
2586
|
ts,
|
|
2268
|
-
isError
|
|
2587
|
+
isError,
|
|
2588
|
+
callSiteEvidence
|
|
2269
2589
|
);
|
|
2270
2590
|
if (result) affectedNode = targetId;
|
|
2271
2591
|
}
|
|
@@ -2281,7 +2601,8 @@ async function handleSpan(ctx, span) {
|
|
|
2281
2601
|
observedSource(),
|
|
2282
2602
|
targetId,
|
|
2283
2603
|
ts,
|
|
2284
|
-
isError
|
|
2604
|
+
isError,
|
|
2605
|
+
callSiteEvidence
|
|
2285
2606
|
);
|
|
2286
2607
|
affectedNode = targetId;
|
|
2287
2608
|
resolvedViaAddress = true;
|
|
@@ -2293,7 +2614,8 @@ async function handleSpan(ctx, span) {
|
|
|
2293
2614
|
observedSource(),
|
|
2294
2615
|
frontierNodeId,
|
|
2295
2616
|
ts,
|
|
2296
|
-
isError
|
|
2617
|
+
isError,
|
|
2618
|
+
callSiteEvidence
|
|
2297
2619
|
);
|
|
2298
2620
|
affectedNode = frontierNodeId;
|
|
2299
2621
|
resolvedViaAddress = true;
|
|
@@ -2451,13 +2773,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
2451
2773
|
return { count: events.length, events };
|
|
2452
2774
|
}
|
|
2453
2775
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
2454
|
-
await
|
|
2776
|
+
await import_node_fs5.promises.mkdir(import_node_path5.default.dirname(staleEventsPath), { recursive: true });
|
|
2455
2777
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2456
|
-
await
|
|
2778
|
+
await import_node_fs5.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
2457
2779
|
}
|
|
2458
2780
|
async function readStaleEvents(staleEventsPath) {
|
|
2459
2781
|
try {
|
|
2460
|
-
const raw = await
|
|
2782
|
+
const raw = await import_node_fs5.promises.readFile(staleEventsPath, "utf8");
|
|
2461
2783
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2462
2784
|
} catch (err) {
|
|
2463
2785
|
if (err.code === "ENOENT") return [];
|
|
@@ -2491,7 +2813,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
2491
2813
|
}
|
|
2492
2814
|
async function readErrorEvents(errorsPath) {
|
|
2493
2815
|
try {
|
|
2494
|
-
const raw = await
|
|
2816
|
+
const raw = await import_node_fs5.promises.readFile(errorsPath, "utf8");
|
|
2495
2817
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2496
2818
|
} catch (err) {
|
|
2497
2819
|
if (err.code === "ENOENT") return [];
|
|
@@ -2523,16 +2845,16 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
2523
2845
|
|
|
2524
2846
|
// src/extract/services.ts
|
|
2525
2847
|
init_cjs_shims();
|
|
2526
|
-
var
|
|
2527
|
-
var
|
|
2848
|
+
var import_node_fs10 = require("fs");
|
|
2849
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2528
2850
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
2529
2851
|
var import_minimatch2 = require("minimatch");
|
|
2530
2852
|
var import_types6 = require("@neat.is/types");
|
|
2531
2853
|
|
|
2532
2854
|
// src/extract/shared.ts
|
|
2533
2855
|
init_cjs_shims();
|
|
2534
|
-
var
|
|
2535
|
-
var
|
|
2856
|
+
var import_node_fs6 = require("fs");
|
|
2857
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2536
2858
|
var import_yaml = require("yaml");
|
|
2537
2859
|
var import_types5 = require("@neat.is/types");
|
|
2538
2860
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
|
|
@@ -2564,7 +2886,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2564
2886
|
const cached = PYVENV_MARKER_CACHE.get(dir);
|
|
2565
2887
|
if (cached !== void 0) return cached;
|
|
2566
2888
|
try {
|
|
2567
|
-
const stat = await
|
|
2889
|
+
const stat = await import_node_fs6.promises.stat(import_node_path6.default.join(dir, "pyvenv.cfg"));
|
|
2568
2890
|
const ok = stat.isFile();
|
|
2569
2891
|
PYVENV_MARKER_CACHE.set(dir, ok);
|
|
2570
2892
|
return ok;
|
|
@@ -2574,7 +2896,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2574
2896
|
}
|
|
2575
2897
|
}
|
|
2576
2898
|
function isConfigFile(name) {
|
|
2577
|
-
const ext =
|
|
2899
|
+
const ext = import_node_path6.default.extname(name);
|
|
2578
2900
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2579
2901
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2580
2902
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
@@ -2682,16 +3004,16 @@ function cleanVersion(raw) {
|
|
|
2682
3004
|
return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
|
|
2683
3005
|
}
|
|
2684
3006
|
async function readJson(filePath) {
|
|
2685
|
-
const raw = await
|
|
3007
|
+
const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
|
|
2686
3008
|
return JSON.parse(raw);
|
|
2687
3009
|
}
|
|
2688
3010
|
async function readYaml(filePath) {
|
|
2689
|
-
const raw = await
|
|
3011
|
+
const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
|
|
2690
3012
|
return (0, import_yaml.parse)(raw);
|
|
2691
3013
|
}
|
|
2692
|
-
async function
|
|
3014
|
+
async function exists2(p) {
|
|
2693
3015
|
try {
|
|
2694
|
-
await
|
|
3016
|
+
await import_node_fs6.promises.access(p);
|
|
2695
3017
|
return true;
|
|
2696
3018
|
} catch {
|
|
2697
3019
|
return false;
|
|
@@ -2700,8 +3022,8 @@ async function exists(p) {
|
|
|
2700
3022
|
|
|
2701
3023
|
// src/extract/python.ts
|
|
2702
3024
|
init_cjs_shims();
|
|
2703
|
-
var
|
|
2704
|
-
var
|
|
3025
|
+
var import_node_fs7 = require("fs");
|
|
3026
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2705
3027
|
var import_smol_toml = require("smol-toml");
|
|
2706
3028
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
2707
3029
|
function parseRequirementsTxt(content) {
|
|
@@ -2734,25 +3056,25 @@ function depsFromPyProject(pyproject) {
|
|
|
2734
3056
|
return out;
|
|
2735
3057
|
}
|
|
2736
3058
|
async function discoverPythonService(serviceDir) {
|
|
2737
|
-
const pyprojectPath =
|
|
2738
|
-
const requirementsPath =
|
|
2739
|
-
const setupPath =
|
|
2740
|
-
const hasPyproject = await
|
|
2741
|
-
const hasRequirements = await
|
|
2742
|
-
const hasSetup = await
|
|
3059
|
+
const pyprojectPath = import_node_path7.default.join(serviceDir, "pyproject.toml");
|
|
3060
|
+
const requirementsPath = import_node_path7.default.join(serviceDir, "requirements.txt");
|
|
3061
|
+
const setupPath = import_node_path7.default.join(serviceDir, "setup.py");
|
|
3062
|
+
const hasPyproject = await exists2(pyprojectPath);
|
|
3063
|
+
const hasRequirements = await exists2(requirementsPath);
|
|
3064
|
+
const hasSetup = await exists2(setupPath);
|
|
2743
3065
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
2744
|
-
let name =
|
|
3066
|
+
let name = import_node_path7.default.basename(serviceDir);
|
|
2745
3067
|
let version;
|
|
2746
3068
|
const dependencies = {};
|
|
2747
3069
|
if (hasPyproject) {
|
|
2748
|
-
const raw = await
|
|
3070
|
+
const raw = await import_node_fs7.promises.readFile(pyprojectPath, "utf8");
|
|
2749
3071
|
const pyproject = (0, import_smol_toml.parse)(raw);
|
|
2750
3072
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
2751
3073
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
2752
3074
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
2753
3075
|
}
|
|
2754
3076
|
if (hasRequirements) {
|
|
2755
|
-
const raw = await
|
|
3077
|
+
const raw = await import_node_fs7.promises.readFile(requirementsPath, "utf8");
|
|
2756
3078
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
2757
3079
|
}
|
|
2758
3080
|
return { name, version, dependencies };
|
|
@@ -2767,17 +3089,17 @@ function pythonToPackage(service) {
|
|
|
2767
3089
|
|
|
2768
3090
|
// src/extract/owners.ts
|
|
2769
3091
|
init_cjs_shims();
|
|
2770
|
-
var
|
|
2771
|
-
var
|
|
3092
|
+
var import_node_fs8 = require("fs");
|
|
3093
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2772
3094
|
var import_minimatch = require("minimatch");
|
|
2773
3095
|
async function loadCodeowners(scanPath) {
|
|
2774
3096
|
const candidates = [
|
|
2775
|
-
|
|
2776
|
-
|
|
3097
|
+
import_node_path8.default.join(scanPath, "CODEOWNERS"),
|
|
3098
|
+
import_node_path8.default.join(scanPath, ".github", "CODEOWNERS")
|
|
2777
3099
|
];
|
|
2778
3100
|
for (const file of candidates) {
|
|
2779
|
-
if (await
|
|
2780
|
-
const raw = await
|
|
3101
|
+
if (await exists2(file)) {
|
|
3102
|
+
const raw = await import_node_fs8.promises.readFile(file, "utf8");
|
|
2781
3103
|
return parseCodeowners(raw);
|
|
2782
3104
|
}
|
|
2783
3105
|
}
|
|
@@ -2795,7 +3117,7 @@ function parseCodeowners(raw) {
|
|
|
2795
3117
|
return { rules };
|
|
2796
3118
|
}
|
|
2797
3119
|
function matchOwner(file, repoPath) {
|
|
2798
|
-
const normalized = repoPath.split(
|
|
3120
|
+
const normalized = repoPath.split(import_node_path8.default.sep).join("/");
|
|
2799
3121
|
for (const rule of file.rules) {
|
|
2800
3122
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
2801
3123
|
}
|
|
@@ -2811,8 +3133,8 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
2811
3133
|
return false;
|
|
2812
3134
|
}
|
|
2813
3135
|
async function readPackageJsonAuthor(serviceDir) {
|
|
2814
|
-
const pkgPath =
|
|
2815
|
-
if (!await
|
|
3136
|
+
const pkgPath = import_node_path8.default.join(serviceDir, "package.json");
|
|
3137
|
+
if (!await exists2(pkgPath)) return null;
|
|
2816
3138
|
try {
|
|
2817
3139
|
const pkg = await readJson(pkgPath);
|
|
2818
3140
|
if (!pkg.author) return null;
|
|
@@ -2834,8 +3156,8 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
|
2834
3156
|
|
|
2835
3157
|
// src/extract/errors.ts
|
|
2836
3158
|
init_cjs_shims();
|
|
2837
|
-
var
|
|
2838
|
-
var
|
|
3159
|
+
var import_node_fs9 = require("fs");
|
|
3160
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2839
3161
|
var sink = [];
|
|
2840
3162
|
function recordExtractionError(producer, file, err) {
|
|
2841
3163
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -2854,9 +3176,9 @@ function drainExtractionErrors() {
|
|
|
2854
3176
|
}
|
|
2855
3177
|
async function writeExtractionErrors(errors, errorsPath) {
|
|
2856
3178
|
if (errors.length === 0) return;
|
|
2857
|
-
await
|
|
3179
|
+
await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(errorsPath), { recursive: true });
|
|
2858
3180
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2859
|
-
await
|
|
3181
|
+
await import_node_fs9.promises.appendFile(errorsPath, lines, "utf8");
|
|
2860
3182
|
}
|
|
2861
3183
|
var droppedSink = [];
|
|
2862
3184
|
function noteExtractedDropped(edge) {
|
|
@@ -2871,9 +3193,9 @@ function isRejectedLogEnabled() {
|
|
|
2871
3193
|
}
|
|
2872
3194
|
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
2873
3195
|
if (drops.length === 0) return;
|
|
2874
|
-
await
|
|
3196
|
+
await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(rejectedPath), { recursive: true });
|
|
2875
3197
|
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
2876
|
-
await
|
|
3198
|
+
await import_node_fs9.promises.appendFile(rejectedPath, lines, "utf8");
|
|
2877
3199
|
}
|
|
2878
3200
|
|
|
2879
3201
|
// src/extract/services.ts
|
|
@@ -2892,21 +3214,21 @@ function workspaceGlobs(pkg) {
|
|
|
2892
3214
|
return null;
|
|
2893
3215
|
}
|
|
2894
3216
|
async function loadGitignore(scanPath) {
|
|
2895
|
-
const gitignorePath =
|
|
2896
|
-
if (!await
|
|
2897
|
-
const raw = await
|
|
3217
|
+
const gitignorePath = import_node_path10.default.join(scanPath, ".gitignore");
|
|
3218
|
+
if (!await exists2(gitignorePath)) return null;
|
|
3219
|
+
const raw = await import_node_fs10.promises.readFile(gitignorePath, "utf8");
|
|
2898
3220
|
return (0, import_ignore.default)().add(raw);
|
|
2899
3221
|
}
|
|
2900
3222
|
async function walkDirs(start, scanPath, options, visit) {
|
|
2901
3223
|
async function recurse(current, depth) {
|
|
2902
3224
|
if (depth > options.maxDepth) return;
|
|
2903
|
-
const entries = await
|
|
3225
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2904
3226
|
for (const entry of entries) {
|
|
2905
3227
|
if (!entry.isDirectory()) continue;
|
|
2906
3228
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2907
|
-
const child =
|
|
3229
|
+
const child = import_node_path10.default.join(current, entry.name);
|
|
2908
3230
|
if (options.ig) {
|
|
2909
|
-
const rel =
|
|
3231
|
+
const rel = import_node_path10.default.relative(scanPath, child).split(import_node_path10.default.sep).join("/");
|
|
2910
3232
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2911
3233
|
}
|
|
2912
3234
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -2922,8 +3244,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2922
3244
|
for (const raw of globs) {
|
|
2923
3245
|
const pattern = raw.replace(/^\.\//, "");
|
|
2924
3246
|
if (!pattern.includes("*")) {
|
|
2925
|
-
const candidate =
|
|
2926
|
-
if (await
|
|
3247
|
+
const candidate = import_node_path10.default.join(scanPath, pattern);
|
|
3248
|
+
if (await exists2(import_node_path10.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2927
3249
|
continue;
|
|
2928
3250
|
}
|
|
2929
3251
|
const segments = pattern.split("/");
|
|
@@ -2932,13 +3254,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2932
3254
|
if (seg.includes("*")) break;
|
|
2933
3255
|
staticSegments.push(seg);
|
|
2934
3256
|
}
|
|
2935
|
-
const start =
|
|
2936
|
-
if (!await
|
|
3257
|
+
const start = import_node_path10.default.join(scanPath, ...staticSegments);
|
|
3258
|
+
if (!await exists2(start)) continue;
|
|
2937
3259
|
const hasDoubleStar = pattern.includes("**");
|
|
2938
3260
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2939
3261
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2940
|
-
const rel =
|
|
2941
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await
|
|
3262
|
+
const rel = import_node_path10.default.relative(scanPath, dir).split(import_node_path10.default.sep).join("/");
|
|
3263
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path10.default.join(dir, "package.json"))) {
|
|
2942
3264
|
found.add(dir);
|
|
2943
3265
|
}
|
|
2944
3266
|
});
|
|
@@ -2958,13 +3280,13 @@ function detectJsFramework(pkg) {
|
|
|
2958
3280
|
return void 0;
|
|
2959
3281
|
}
|
|
2960
3282
|
async function discoverNodeService(scanPath, dir) {
|
|
2961
|
-
const pkgPath =
|
|
2962
|
-
if (!await
|
|
3283
|
+
const pkgPath = import_node_path10.default.join(dir, "package.json");
|
|
3284
|
+
if (!await exists2(pkgPath)) return null;
|
|
2963
3285
|
let pkg;
|
|
2964
3286
|
try {
|
|
2965
3287
|
pkg = await readJson(pkgPath);
|
|
2966
3288
|
} catch (err) {
|
|
2967
|
-
recordExtractionError("services",
|
|
3289
|
+
recordExtractionError("services", import_node_path10.default.relative(scanPath, pkgPath), err);
|
|
2968
3290
|
return null;
|
|
2969
3291
|
}
|
|
2970
3292
|
if (!pkg.name) return null;
|
|
@@ -2976,7 +3298,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2976
3298
|
language: "javascript",
|
|
2977
3299
|
version: pkg.version,
|
|
2978
3300
|
dependencies: pkg.dependencies ?? {},
|
|
2979
|
-
repoPath:
|
|
3301
|
+
repoPath: import_node_path10.default.relative(scanPath, dir),
|
|
2980
3302
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
2981
3303
|
...framework ? { framework } : {}
|
|
2982
3304
|
};
|
|
@@ -2993,20 +3315,20 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2993
3315
|
language: "python",
|
|
2994
3316
|
version: py.version,
|
|
2995
3317
|
dependencies: py.dependencies,
|
|
2996
|
-
repoPath:
|
|
3318
|
+
repoPath: import_node_path10.default.relative(scanPath, dir)
|
|
2997
3319
|
};
|
|
2998
3320
|
return { pkg, dir, node };
|
|
2999
3321
|
}
|
|
3000
3322
|
async function discoverServices(scanPath) {
|
|
3001
|
-
const rootPkgPath =
|
|
3323
|
+
const rootPkgPath = import_node_path10.default.join(scanPath, "package.json");
|
|
3002
3324
|
let rootPkg = null;
|
|
3003
|
-
if (await
|
|
3325
|
+
if (await exists2(rootPkgPath)) {
|
|
3004
3326
|
try {
|
|
3005
3327
|
rootPkg = await readJson(rootPkgPath);
|
|
3006
3328
|
} catch (err) {
|
|
3007
3329
|
recordExtractionError(
|
|
3008
3330
|
"services workspaces",
|
|
3009
|
-
|
|
3331
|
+
import_node_path10.default.relative(scanPath, rootPkgPath),
|
|
3010
3332
|
err
|
|
3011
3333
|
);
|
|
3012
3334
|
}
|
|
@@ -3023,9 +3345,9 @@ async function discoverServices(scanPath) {
|
|
|
3023
3345
|
scanPath,
|
|
3024
3346
|
{ maxDepth: parseScanDepth(), ig },
|
|
3025
3347
|
async (dir) => {
|
|
3026
|
-
if (await
|
|
3348
|
+
if (await exists2(import_node_path10.default.join(dir, "package.json"))) {
|
|
3027
3349
|
candidateDirs.push(dir);
|
|
3028
|
-
} else if (await
|
|
3350
|
+
} else if (await exists2(import_node_path10.default.join(dir, "pyproject.toml")) || await exists2(import_node_path10.default.join(dir, "requirements.txt")) || await exists2(import_node_path10.default.join(dir, "setup.py"))) {
|
|
3029
3351
|
candidateDirs.push(dir);
|
|
3030
3352
|
}
|
|
3031
3353
|
}
|
|
@@ -3039,8 +3361,8 @@ async function discoverServices(scanPath) {
|
|
|
3039
3361
|
if (!service) continue;
|
|
3040
3362
|
const existingDir = seen.get(service.node.name);
|
|
3041
3363
|
if (existingDir !== void 0) {
|
|
3042
|
-
const a =
|
|
3043
|
-
const b =
|
|
3364
|
+
const a = import_node_path10.default.relative(scanPath, existingDir) || ".";
|
|
3365
|
+
const b = import_node_path10.default.relative(scanPath, dir) || ".";
|
|
3044
3366
|
console.warn(
|
|
3045
3367
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
3046
3368
|
);
|
|
@@ -3077,8 +3399,8 @@ function addServiceNodes(graph, services) {
|
|
|
3077
3399
|
|
|
3078
3400
|
// src/extract/aliases.ts
|
|
3079
3401
|
init_cjs_shims();
|
|
3080
|
-
var
|
|
3081
|
-
var
|
|
3402
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3403
|
+
var import_node_fs11 = require("fs");
|
|
3082
3404
|
var import_yaml2 = require("yaml");
|
|
3083
3405
|
var import_types7 = require("@neat.is/types");
|
|
3084
3406
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -3105,15 +3427,15 @@ function indexServicesByName(services) {
|
|
|
3105
3427
|
const map = /* @__PURE__ */ new Map();
|
|
3106
3428
|
for (const s of services) {
|
|
3107
3429
|
map.set(s.node.name, s.node.id);
|
|
3108
|
-
map.set(
|
|
3430
|
+
map.set(import_node_path11.default.basename(s.dir), s.node.id);
|
|
3109
3431
|
}
|
|
3110
3432
|
return map;
|
|
3111
3433
|
}
|
|
3112
3434
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
3113
3435
|
let composePath = null;
|
|
3114
3436
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3115
|
-
const abs =
|
|
3116
|
-
if (await
|
|
3437
|
+
const abs = import_node_path11.default.join(scanPath, name);
|
|
3438
|
+
if (await exists2(abs)) {
|
|
3117
3439
|
composePath = abs;
|
|
3118
3440
|
break;
|
|
3119
3441
|
}
|
|
@@ -3125,7 +3447,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3125
3447
|
} catch (err) {
|
|
3126
3448
|
recordExtractionError(
|
|
3127
3449
|
"aliases compose",
|
|
3128
|
-
|
|
3450
|
+
import_node_path11.default.relative(scanPath, composePath),
|
|
3129
3451
|
err
|
|
3130
3452
|
);
|
|
3131
3453
|
return;
|
|
@@ -3168,11 +3490,11 @@ function parseDockerfileLabels(content) {
|
|
|
3168
3490
|
}
|
|
3169
3491
|
async function collectDockerfileAliases(graph, services) {
|
|
3170
3492
|
for (const service of services) {
|
|
3171
|
-
const dockerfilePath =
|
|
3172
|
-
if (!await
|
|
3493
|
+
const dockerfilePath = import_node_path11.default.join(service.dir, "Dockerfile");
|
|
3494
|
+
if (!await exists2(dockerfilePath)) continue;
|
|
3173
3495
|
let content;
|
|
3174
3496
|
try {
|
|
3175
|
-
content = await
|
|
3497
|
+
content = await import_node_fs11.promises.readFile(dockerfilePath, "utf8");
|
|
3176
3498
|
} catch (err) {
|
|
3177
3499
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
3178
3500
|
continue;
|
|
@@ -3184,15 +3506,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
3184
3506
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
3185
3507
|
if (depth > max) return [];
|
|
3186
3508
|
const out = [];
|
|
3187
|
-
const entries = await
|
|
3509
|
+
const entries = await import_node_fs11.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3188
3510
|
for (const entry of entries) {
|
|
3189
3511
|
if (entry.isDirectory()) {
|
|
3190
3512
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3191
|
-
const child =
|
|
3513
|
+
const child = import_node_path11.default.join(start, entry.name);
|
|
3192
3514
|
if (await isPythonVenvDir(child)) continue;
|
|
3193
3515
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
3194
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3195
|
-
out.push(
|
|
3516
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry.name))) {
|
|
3517
|
+
out.push(import_node_path11.default.join(start, entry.name));
|
|
3196
3518
|
}
|
|
3197
3519
|
}
|
|
3198
3520
|
return out;
|
|
@@ -3219,7 +3541,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
3219
3541
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
3220
3542
|
const files = await walkYamlFiles(scanPath);
|
|
3221
3543
|
for (const file of files) {
|
|
3222
|
-
const content = await
|
|
3544
|
+
const content = await import_node_fs11.promises.readFile(file, "utf8");
|
|
3223
3545
|
let docs;
|
|
3224
3546
|
try {
|
|
3225
3547
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3244,15 +3566,112 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3244
3566
|
|
|
3245
3567
|
// src/extract/databases/index.ts
|
|
3246
3568
|
init_cjs_shims();
|
|
3247
|
-
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);
|
|
3248
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
|
+
}
|
|
3249
3668
|
|
|
3250
3669
|
// src/extract/databases/db-config-yaml.ts
|
|
3251
3670
|
init_cjs_shims();
|
|
3252
|
-
var
|
|
3671
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3253
3672
|
async function parse(serviceDir) {
|
|
3254
|
-
const yamlPath =
|
|
3255
|
-
if (!await
|
|
3673
|
+
const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
|
|
3674
|
+
if (!await exists2(yamlPath)) return [];
|
|
3256
3675
|
const raw = await readYaml(yamlPath);
|
|
3257
3676
|
return [
|
|
3258
3677
|
{
|
|
@@ -3269,13 +3688,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3269
3688
|
|
|
3270
3689
|
// src/extract/databases/dotenv.ts
|
|
3271
3690
|
init_cjs_shims();
|
|
3272
|
-
var
|
|
3273
|
-
var
|
|
3691
|
+
var import_node_fs14 = require("fs");
|
|
3692
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3274
3693
|
|
|
3275
3694
|
// src/extract/databases/shared.ts
|
|
3276
3695
|
init_cjs_shims();
|
|
3277
|
-
var
|
|
3278
|
-
var
|
|
3696
|
+
var import_node_fs13 = require("fs");
|
|
3697
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3279
3698
|
function schemeToEngine(scheme) {
|
|
3280
3699
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3281
3700
|
switch (s) {
|
|
@@ -3314,14 +3733,14 @@ function parseConnectionString(url) {
|
|
|
3314
3733
|
}
|
|
3315
3734
|
async function readIfExists(filePath) {
|
|
3316
3735
|
try {
|
|
3317
|
-
return await
|
|
3736
|
+
return await import_node_fs13.promises.readFile(filePath, "utf8");
|
|
3318
3737
|
} catch {
|
|
3319
3738
|
return null;
|
|
3320
3739
|
}
|
|
3321
3740
|
}
|
|
3322
3741
|
async function findFirst(serviceDir, candidates) {
|
|
3323
3742
|
for (const rel of candidates) {
|
|
3324
|
-
const abs =
|
|
3743
|
+
const abs = import_node_path14.default.join(serviceDir, rel);
|
|
3325
3744
|
const content = await readIfExists(abs);
|
|
3326
3745
|
if (content !== null) return abs;
|
|
3327
3746
|
}
|
|
@@ -3372,15 +3791,15 @@ function parseDotenvLine(line) {
|
|
|
3372
3791
|
return { key, value };
|
|
3373
3792
|
}
|
|
3374
3793
|
async function parse2(serviceDir) {
|
|
3375
|
-
const entries = await
|
|
3794
|
+
const entries = await import_node_fs14.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3376
3795
|
const configs = [];
|
|
3377
3796
|
const seen = /* @__PURE__ */ new Set();
|
|
3378
3797
|
for (const entry of entries) {
|
|
3379
3798
|
if (!entry.isFile()) continue;
|
|
3380
3799
|
const match = isConfigFile(entry.name);
|
|
3381
3800
|
if (!match.match || match.fileType !== "env") continue;
|
|
3382
|
-
const filePath =
|
|
3383
|
-
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");
|
|
3384
3803
|
for (const line of content.split("\n")) {
|
|
3385
3804
|
const parsed = parseDotenvLine(line);
|
|
3386
3805
|
if (!parsed) continue;
|
|
@@ -3399,9 +3818,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3399
3818
|
|
|
3400
3819
|
// src/extract/databases/prisma.ts
|
|
3401
3820
|
init_cjs_shims();
|
|
3402
|
-
var
|
|
3821
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3403
3822
|
async function parse3(serviceDir) {
|
|
3404
|
-
const schemaPath =
|
|
3823
|
+
const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3405
3824
|
const content = await readIfExists(schemaPath);
|
|
3406
3825
|
if (!content) return [];
|
|
3407
3826
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3533,11 +3952,11 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3533
3952
|
|
|
3534
3953
|
// src/extract/databases/ormconfig.ts
|
|
3535
3954
|
init_cjs_shims();
|
|
3536
|
-
var
|
|
3955
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3537
3956
|
async function parse6(serviceDir) {
|
|
3538
3957
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3539
|
-
const abs =
|
|
3540
|
-
if (!await
|
|
3958
|
+
const abs = import_node_path17.default.join(serviceDir, candidate);
|
|
3959
|
+
if (!await exists2(abs)) continue;
|
|
3541
3960
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3542
3961
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
3543
3962
|
const out = [];
|
|
@@ -3596,10 +4015,10 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3596
4015
|
|
|
3597
4016
|
// src/extract/databases/sequelize.ts
|
|
3598
4017
|
init_cjs_shims();
|
|
3599
|
-
var
|
|
4018
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3600
4019
|
async function parse8(serviceDir) {
|
|
3601
|
-
const configPath =
|
|
3602
|
-
if (!await
|
|
4020
|
+
const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
|
|
4021
|
+
if (!await exists2(configPath)) return [];
|
|
3603
4022
|
const raw = await readJson(configPath);
|
|
3604
4023
|
const out = [];
|
|
3605
4024
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3625,7 +4044,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3625
4044
|
|
|
3626
4045
|
// src/extract/databases/docker-compose.ts
|
|
3627
4046
|
init_cjs_shims();
|
|
3628
|
-
var
|
|
4047
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3629
4048
|
function portFromService(svc) {
|
|
3630
4049
|
for (const raw of svc.ports ?? []) {
|
|
3631
4050
|
const str = String(raw);
|
|
@@ -3652,8 +4071,8 @@ function databaseFromEnv(svc) {
|
|
|
3652
4071
|
}
|
|
3653
4072
|
async function parse9(serviceDir) {
|
|
3654
4073
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3655
|
-
const abs =
|
|
3656
|
-
if (!await
|
|
4074
|
+
const abs = import_node_path19.default.join(serviceDir, name);
|
|
4075
|
+
if (!await exists2(abs)) continue;
|
|
3657
4076
|
const raw = await readYaml(abs);
|
|
3658
4077
|
if (!raw?.services) return [];
|
|
3659
4078
|
const out = [];
|
|
@@ -3693,8 +4112,8 @@ function compatibleDriversFor(engine) {
|
|
|
3693
4112
|
}
|
|
3694
4113
|
function toDatabaseNode(config) {
|
|
3695
4114
|
return {
|
|
3696
|
-
id: (0,
|
|
3697
|
-
type:
|
|
4115
|
+
id: (0, import_types9.databaseId)(config.host),
|
|
4116
|
+
type: import_types9.NodeType.DatabaseNode,
|
|
3698
4117
|
name: config.database || config.host,
|
|
3699
4118
|
engine: config.engine,
|
|
3700
4119
|
engineVersion: config.engineVersion,
|
|
@@ -3824,19 +4243,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3824
4243
|
discoveredVia: mergedDiscoveredVia
|
|
3825
4244
|
});
|
|
3826
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));
|
|
3827
4256
|
const edge = {
|
|
3828
|
-
id: (0, import_types5.extractedEdgeId)(
|
|
3829
|
-
source:
|
|
4257
|
+
id: (0, import_types5.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
|
|
4258
|
+
source: fileNodeId,
|
|
3830
4259
|
target: dbNode.id,
|
|
3831
|
-
type:
|
|
3832
|
-
provenance:
|
|
3833
|
-
confidence: (0,
|
|
3834
|
-
|
|
3835
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3836
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3837
|
-
evidence: {
|
|
3838
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3839
|
-
}
|
|
4260
|
+
type: import_types9.EdgeType.CONNECTS_TO,
|
|
4261
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
4262
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
4263
|
+
evidence: { file: evidenceFile }
|
|
3840
4264
|
};
|
|
3841
4265
|
if (!graph.hasEdge(edge.id)) {
|
|
3842
4266
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3862,15 +4286,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3862
4286
|
|
|
3863
4287
|
// src/extract/configs.ts
|
|
3864
4288
|
init_cjs_shims();
|
|
3865
|
-
var
|
|
3866
|
-
var
|
|
3867
|
-
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");
|
|
3868
4292
|
async function walkConfigFiles(dir) {
|
|
3869
4293
|
const out = [];
|
|
3870
4294
|
async function walk(current) {
|
|
3871
|
-
const entries = await
|
|
4295
|
+
const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
|
|
3872
4296
|
for (const entry of entries) {
|
|
3873
|
-
const full =
|
|
4297
|
+
const full = import_node_path21.default.join(current, entry.name);
|
|
3874
4298
|
if (entry.isDirectory()) {
|
|
3875
4299
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3876
4300
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3889,26 +4313,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3889
4313
|
for (const service of services) {
|
|
3890
4314
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3891
4315
|
for (const file of configFiles) {
|
|
3892
|
-
const relPath =
|
|
4316
|
+
const relPath = import_node_path21.default.relative(scanPath, file);
|
|
3893
4317
|
const node = {
|
|
3894
|
-
id: (0,
|
|
3895
|
-
type:
|
|
3896
|
-
name:
|
|
4318
|
+
id: (0, import_types10.configId)(relPath),
|
|
4319
|
+
type: import_types10.NodeType.ConfigNode,
|
|
4320
|
+
name: import_node_path21.default.basename(file),
|
|
3897
4321
|
path: relPath,
|
|
3898
|
-
fileType: isConfigFile(
|
|
4322
|
+
fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
|
|
3899
4323
|
};
|
|
3900
4324
|
if (!graph.hasNode(node.id)) {
|
|
3901
4325
|
graph.addNode(node.id, node);
|
|
3902
4326
|
nodesAdded++;
|
|
3903
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;
|
|
3904
4337
|
const edge = {
|
|
3905
|
-
id: (0, import_types5.extractedEdgeId)(
|
|
3906
|
-
source:
|
|
4338
|
+
id: (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
|
|
4339
|
+
source: fileNodeId,
|
|
3907
4340
|
target: node.id,
|
|
3908
|
-
type:
|
|
3909
|
-
provenance:
|
|
3910
|
-
confidence: (0,
|
|
3911
|
-
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("/") }
|
|
3912
4345
|
};
|
|
3913
4346
|
if (!graph.hasEdge(edge.id)) {
|
|
3914
4347
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3925,110 +4358,11 @@ var import_types16 = require("@neat.is/types");
|
|
|
3925
4358
|
|
|
3926
4359
|
// src/extract/calls/http.ts
|
|
3927
4360
|
init_cjs_shims();
|
|
3928
|
-
var
|
|
4361
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3929
4362
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3930
4363
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3931
4364
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3932
4365
|
var import_types11 = require("@neat.is/types");
|
|
3933
|
-
|
|
3934
|
-
// src/extract/calls/shared.ts
|
|
3935
|
-
init_cjs_shims();
|
|
3936
|
-
var import_node_fs13 = require("fs");
|
|
3937
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3938
|
-
var import_types10 = require("@neat.is/types");
|
|
3939
|
-
async function walkSourceFiles(dir) {
|
|
3940
|
-
const out = [];
|
|
3941
|
-
async function walk(current) {
|
|
3942
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3943
|
-
for (const entry of entries) {
|
|
3944
|
-
const full = import_node_path19.default.join(current, entry.name);
|
|
3945
|
-
if (entry.isDirectory()) {
|
|
3946
|
-
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3947
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3948
|
-
await walk(full);
|
|
3949
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3950
|
-
out.push(full);
|
|
3951
|
-
}
|
|
3952
|
-
}
|
|
3953
|
-
}
|
|
3954
|
-
await walk(dir);
|
|
3955
|
-
return out;
|
|
3956
|
-
}
|
|
3957
|
-
async function loadSourceFiles(dir) {
|
|
3958
|
-
const paths = await walkSourceFiles(dir);
|
|
3959
|
-
const out = [];
|
|
3960
|
-
for (const p of paths) {
|
|
3961
|
-
try {
|
|
3962
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3963
|
-
out.push({ path: p, content });
|
|
3964
|
-
} catch {
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3967
|
-
return out;
|
|
3968
|
-
}
|
|
3969
|
-
function lineOf(text, needle) {
|
|
3970
|
-
const idx = text.indexOf(needle);
|
|
3971
|
-
if (idx < 0) return 1;
|
|
3972
|
-
return text.slice(0, idx).split("\n").length;
|
|
3973
|
-
}
|
|
3974
|
-
function snippet(text, line) {
|
|
3975
|
-
const lines = text.split("\n");
|
|
3976
|
-
return (lines[line - 1] ?? "").trim();
|
|
3977
|
-
}
|
|
3978
|
-
function toPosix2(p) {
|
|
3979
|
-
return p.split("\\").join("/");
|
|
3980
|
-
}
|
|
3981
|
-
function languageForPath(relPath) {
|
|
3982
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3983
|
-
case ".py":
|
|
3984
|
-
return "python";
|
|
3985
|
-
case ".ts":
|
|
3986
|
-
case ".tsx":
|
|
3987
|
-
return "typescript";
|
|
3988
|
-
case ".js":
|
|
3989
|
-
case ".jsx":
|
|
3990
|
-
case ".mjs":
|
|
3991
|
-
case ".cjs":
|
|
3992
|
-
return "javascript";
|
|
3993
|
-
default:
|
|
3994
|
-
return void 0;
|
|
3995
|
-
}
|
|
3996
|
-
}
|
|
3997
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3998
|
-
let nodesAdded = 0;
|
|
3999
|
-
let edgesAdded = 0;
|
|
4000
|
-
const fileNodeId = (0, import_types10.fileId)(serviceName, relPath);
|
|
4001
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
4002
|
-
const language = languageForPath(relPath);
|
|
4003
|
-
const node = {
|
|
4004
|
-
id: fileNodeId,
|
|
4005
|
-
type: import_types10.NodeType.FileNode,
|
|
4006
|
-
service: serviceName,
|
|
4007
|
-
path: relPath,
|
|
4008
|
-
...language ? { language } : {},
|
|
4009
|
-
discoveredVia: "static"
|
|
4010
|
-
};
|
|
4011
|
-
graph.addNode(fileNodeId, node);
|
|
4012
|
-
nodesAdded++;
|
|
4013
|
-
}
|
|
4014
|
-
const containsId = (0, import_types10.extractedEdgeId)(serviceNodeId, fileNodeId, import_types10.EdgeType.CONTAINS);
|
|
4015
|
-
if (!graph.hasEdge(containsId)) {
|
|
4016
|
-
const edge = {
|
|
4017
|
-
id: containsId,
|
|
4018
|
-
source: serviceNodeId,
|
|
4019
|
-
target: fileNodeId,
|
|
4020
|
-
type: import_types10.EdgeType.CONTAINS,
|
|
4021
|
-
provenance: import_types10.Provenance.EXTRACTED,
|
|
4022
|
-
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4023
|
-
evidence: { file: relPath }
|
|
4024
|
-
};
|
|
4025
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
4026
|
-
edgesAdded++;
|
|
4027
|
-
}
|
|
4028
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
4029
|
-
}
|
|
4030
|
-
|
|
4031
|
-
// src/extract/calls/http.ts
|
|
4032
4366
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
4033
4367
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
4034
4368
|
function isInsideJsxExternalLink(node) {
|
|
@@ -4093,9 +4427,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4093
4427
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
4094
4428
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4095
4429
|
for (const service of services) {
|
|
4096
|
-
knownHosts.add(
|
|
4430
|
+
knownHosts.add(import_node_path22.default.basename(service.dir));
|
|
4097
4431
|
knownHosts.add(service.pkg.name);
|
|
4098
|
-
hostToNodeId.set(
|
|
4432
|
+
hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
|
|
4099
4433
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4100
4434
|
}
|
|
4101
4435
|
let nodesAdded = 0;
|
|
@@ -4105,7 +4439,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4105
4439
|
const seen = /* @__PURE__ */ new Set();
|
|
4106
4440
|
for (const file of files) {
|
|
4107
4441
|
if (isTestPath(file.path)) continue;
|
|
4108
|
-
const parser =
|
|
4442
|
+
const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
4109
4443
|
let sites;
|
|
4110
4444
|
try {
|
|
4111
4445
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -4114,7 +4448,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4114
4448
|
continue;
|
|
4115
4449
|
}
|
|
4116
4450
|
if (sites.length === 0) continue;
|
|
4117
|
-
const relFile = toPosix2(
|
|
4451
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
4118
4452
|
for (const site of sites) {
|
|
4119
4453
|
const targetId = hostToNodeId.get(site.host);
|
|
4120
4454
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -4168,7 +4502,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4168
4502
|
|
|
4169
4503
|
// src/extract/calls/kafka.ts
|
|
4170
4504
|
init_cjs_shims();
|
|
4171
|
-
var
|
|
4505
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
4172
4506
|
var import_types12 = require("@neat.is/types");
|
|
4173
4507
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4174
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;
|
|
@@ -4199,7 +4533,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4199
4533
|
// tier (ADR-066).
|
|
4200
4534
|
confidenceKind: "verified-call-site",
|
|
4201
4535
|
evidence: {
|
|
4202
|
-
file:
|
|
4536
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
4203
4537
|
line,
|
|
4204
4538
|
snippet: snippet(file.content, line)
|
|
4205
4539
|
}
|
|
@@ -4212,7 +4546,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4212
4546
|
|
|
4213
4547
|
// src/extract/calls/redis.ts
|
|
4214
4548
|
init_cjs_shims();
|
|
4215
|
-
var
|
|
4549
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4216
4550
|
var import_types13 = require("@neat.is/types");
|
|
4217
4551
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
4218
4552
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -4235,7 +4569,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4235
4569
|
// support tier (ADR-066).
|
|
4236
4570
|
confidenceKind: "url-with-structural-support",
|
|
4237
4571
|
evidence: {
|
|
4238
|
-
file:
|
|
4572
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
4239
4573
|
line,
|
|
4240
4574
|
snippet: snippet(file.content, line)
|
|
4241
4575
|
}
|
|
@@ -4246,7 +4580,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4246
4580
|
|
|
4247
4581
|
// src/extract/calls/aws.ts
|
|
4248
4582
|
init_cjs_shims();
|
|
4249
|
-
var
|
|
4583
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
4250
4584
|
var import_types14 = require("@neat.is/types");
|
|
4251
4585
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4252
4586
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -4280,7 +4614,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4280
4614
|
// (ADR-066).
|
|
4281
4615
|
confidenceKind: "verified-call-site",
|
|
4282
4616
|
evidence: {
|
|
4283
|
-
file:
|
|
4617
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
4284
4618
|
line,
|
|
4285
4619
|
snippet: snippet(file.content, line)
|
|
4286
4620
|
}
|
|
@@ -4305,13 +4639,14 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4305
4639
|
|
|
4306
4640
|
// src/extract/calls/grpc.ts
|
|
4307
4641
|
init_cjs_shims();
|
|
4308
|
-
var
|
|
4642
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
4309
4643
|
var import_types15 = require("@neat.is/types");
|
|
4310
4644
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4311
4645
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4312
4646
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4313
4647
|
function isLikelyAddress(value) {
|
|
4314
4648
|
if (!value) return false;
|
|
4649
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4315
4650
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4316
4651
|
}
|
|
4317
4652
|
function normaliseForMatch(s) {
|
|
@@ -4363,7 +4698,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4363
4698
|
// tier (ADR-066).
|
|
4364
4699
|
confidenceKind: "verified-call-site",
|
|
4365
4700
|
evidence: {
|
|
4366
|
-
file:
|
|
4701
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
4367
4702
|
line,
|
|
4368
4703
|
snippet: snippet(file.content, line)
|
|
4369
4704
|
}
|
|
@@ -4474,7 +4809,7 @@ init_cjs_shims();
|
|
|
4474
4809
|
|
|
4475
4810
|
// src/extract/infra/docker-compose.ts
|
|
4476
4811
|
init_cjs_shims();
|
|
4477
|
-
var
|
|
4812
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
4478
4813
|
var import_types18 = require("@neat.is/types");
|
|
4479
4814
|
|
|
4480
4815
|
// src/extract/infra/shared.ts
|
|
@@ -4512,7 +4847,7 @@ function dependsOnList(value) {
|
|
|
4512
4847
|
}
|
|
4513
4848
|
function serviceNameToServiceNode(name, services) {
|
|
4514
4849
|
for (const s of services) {
|
|
4515
|
-
if (s.node.name === name ||
|
|
4850
|
+
if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
|
|
4516
4851
|
}
|
|
4517
4852
|
return null;
|
|
4518
4853
|
}
|
|
@@ -4521,8 +4856,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4521
4856
|
let edgesAdded = 0;
|
|
4522
4857
|
let composePath = null;
|
|
4523
4858
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4524
|
-
const abs =
|
|
4525
|
-
if (await
|
|
4859
|
+
const abs = import_node_path27.default.join(scanPath, name);
|
|
4860
|
+
if (await exists2(abs)) {
|
|
4526
4861
|
composePath = abs;
|
|
4527
4862
|
break;
|
|
4528
4863
|
}
|
|
@@ -4534,13 +4869,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4534
4869
|
} catch (err) {
|
|
4535
4870
|
recordExtractionError(
|
|
4536
4871
|
"infra docker-compose",
|
|
4537
|
-
|
|
4872
|
+
import_node_path27.default.relative(scanPath, composePath),
|
|
4538
4873
|
err
|
|
4539
4874
|
);
|
|
4540
4875
|
return { nodesAdded, edgesAdded };
|
|
4541
4876
|
}
|
|
4542
4877
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4543
|
-
const evidenceFile =
|
|
4878
|
+
const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
|
|
4544
4879
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4545
4880
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4546
4881
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4582,8 +4917,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4582
4917
|
|
|
4583
4918
|
// src/extract/infra/dockerfile.ts
|
|
4584
4919
|
init_cjs_shims();
|
|
4585
|
-
var
|
|
4586
|
-
var
|
|
4920
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
4921
|
+
var import_node_fs16 = require("fs");
|
|
4587
4922
|
var import_types19 = require("@neat.is/types");
|
|
4588
4923
|
function runtimeImage(content) {
|
|
4589
4924
|
const lines = content.split("\n");
|
|
@@ -4603,15 +4938,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4603
4938
|
let nodesAdded = 0;
|
|
4604
4939
|
let edgesAdded = 0;
|
|
4605
4940
|
for (const service of services) {
|
|
4606
|
-
const dockerfilePath =
|
|
4607
|
-
if (!await
|
|
4941
|
+
const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
|
|
4942
|
+
if (!await exists2(dockerfilePath)) continue;
|
|
4608
4943
|
let content;
|
|
4609
4944
|
try {
|
|
4610
|
-
content = await
|
|
4945
|
+
content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
|
|
4611
4946
|
} catch (err) {
|
|
4612
4947
|
recordExtractionError(
|
|
4613
4948
|
"infra dockerfile",
|
|
4614
|
-
|
|
4949
|
+
import_node_path28.default.relative(scanPath, dockerfilePath),
|
|
4615
4950
|
err
|
|
4616
4951
|
);
|
|
4617
4952
|
continue;
|
|
@@ -4623,17 +4958,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4623
4958
|
graph.addNode(node.id, node);
|
|
4624
4959
|
nodesAdded++;
|
|
4625
4960
|
}
|
|
4626
|
-
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);
|
|
4627
4971
|
if (!graph.hasEdge(edgeId)) {
|
|
4628
4972
|
const edge = {
|
|
4629
4973
|
id: edgeId,
|
|
4630
|
-
source:
|
|
4974
|
+
source: fileNodeId,
|
|
4631
4975
|
target: node.id,
|
|
4632
4976
|
type: import_types19.EdgeType.RUNS_ON,
|
|
4633
4977
|
provenance: import_types19.Provenance.EXTRACTED,
|
|
4634
4978
|
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4635
4979
|
evidence: {
|
|
4636
|
-
file:
|
|
4980
|
+
file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
|
|
4637
4981
|
}
|
|
4638
4982
|
};
|
|
4639
4983
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4645,21 +4989,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4645
4989
|
|
|
4646
4990
|
// src/extract/infra/terraform.ts
|
|
4647
4991
|
init_cjs_shims();
|
|
4648
|
-
var
|
|
4649
|
-
var
|
|
4992
|
+
var import_node_fs17 = require("fs");
|
|
4993
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4650
4994
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4651
4995
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4652
4996
|
if (depth > max) return [];
|
|
4653
4997
|
const out = [];
|
|
4654
|
-
const entries = await
|
|
4998
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4655
4999
|
for (const entry of entries) {
|
|
4656
5000
|
if (entry.isDirectory()) {
|
|
4657
5001
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
4658
|
-
const child =
|
|
5002
|
+
const child = import_node_path29.default.join(start, entry.name);
|
|
4659
5003
|
if (await isPythonVenvDir(child)) continue;
|
|
4660
5004
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4661
5005
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
4662
|
-
out.push(
|
|
5006
|
+
out.push(import_node_path29.default.join(start, entry.name));
|
|
4663
5007
|
}
|
|
4664
5008
|
}
|
|
4665
5009
|
return out;
|
|
@@ -4668,7 +5012,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4668
5012
|
let nodesAdded = 0;
|
|
4669
5013
|
const files = await walkTfFiles(scanPath);
|
|
4670
5014
|
for (const file of files) {
|
|
4671
|
-
const content = await
|
|
5015
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
4672
5016
|
RESOURCE_RE.lastIndex = 0;
|
|
4673
5017
|
let m;
|
|
4674
5018
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -4686,8 +5030,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4686
5030
|
|
|
4687
5031
|
// src/extract/infra/k8s.ts
|
|
4688
5032
|
init_cjs_shims();
|
|
4689
|
-
var
|
|
4690
|
-
var
|
|
5033
|
+
var import_node_fs18 = require("fs");
|
|
5034
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4691
5035
|
var import_yaml3 = require("yaml");
|
|
4692
5036
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4693
5037
|
Service: "k8s-service",
|
|
@@ -4701,15 +5045,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4701
5045
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4702
5046
|
if (depth > max) return [];
|
|
4703
5047
|
const out = [];
|
|
4704
|
-
const entries = await
|
|
5048
|
+
const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4705
5049
|
for (const entry of entries) {
|
|
4706
5050
|
if (entry.isDirectory()) {
|
|
4707
5051
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4708
|
-
const child =
|
|
5052
|
+
const child = import_node_path30.default.join(start, entry.name);
|
|
4709
5053
|
if (await isPythonVenvDir(child)) continue;
|
|
4710
5054
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4711
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4712
|
-
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));
|
|
4713
5057
|
}
|
|
4714
5058
|
}
|
|
4715
5059
|
return out;
|
|
@@ -4718,7 +5062,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4718
5062
|
let nodesAdded = 0;
|
|
4719
5063
|
const files = await walkYamlFiles2(scanPath);
|
|
4720
5064
|
for (const file of files) {
|
|
4721
|
-
const content = await
|
|
5065
|
+
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
4722
5066
|
let docs;
|
|
4723
5067
|
try {
|
|
4724
5068
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -4753,12 +5097,12 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4753
5097
|
}
|
|
4754
5098
|
|
|
4755
5099
|
// src/extract/index.ts
|
|
4756
|
-
var
|
|
5100
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4757
5101
|
|
|
4758
5102
|
// src/extract/retire.ts
|
|
4759
5103
|
init_cjs_shims();
|
|
4760
|
-
var
|
|
4761
|
-
var
|
|
5104
|
+
var import_node_fs19 = require("fs");
|
|
5105
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4762
5106
|
var import_types20 = require("@neat.is/types");
|
|
4763
5107
|
function dropOrphanedFileNodes(graph) {
|
|
4764
5108
|
const orphans = [];
|
|
@@ -4779,11 +5123,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4779
5123
|
if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
|
|
4780
5124
|
const evidenceFile = edge.evidence?.file;
|
|
4781
5125
|
if (!evidenceFile) return;
|
|
4782
|
-
if (
|
|
4783
|
-
if (!(0,
|
|
5126
|
+
if (import_node_path31.default.isAbsolute(evidenceFile)) {
|
|
5127
|
+
if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
|
|
4784
5128
|
return;
|
|
4785
5129
|
}
|
|
4786
|
-
const found = bases.some((base) => (0,
|
|
5130
|
+
const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path31.default.join(base, evidenceFile)));
|
|
4787
5131
|
if (!found) toDrop.push(id);
|
|
4788
5132
|
});
|
|
4789
5133
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4821,7 +5165,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4821
5165
|
}
|
|
4822
5166
|
const droppedEntries = drainDroppedExtracted();
|
|
4823
5167
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4824
|
-
const rejectedPath =
|
|
5168
|
+
const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4825
5169
|
try {
|
|
4826
5170
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4827
5171
|
} catch (err) {
|
|
@@ -4855,7 +5199,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4855
5199
|
|
|
4856
5200
|
// src/diff.ts
|
|
4857
5201
|
init_cjs_shims();
|
|
4858
|
-
var
|
|
5202
|
+
var import_node_fs20 = require("fs");
|
|
4859
5203
|
async function loadSnapshotForDiff(target) {
|
|
4860
5204
|
if (/^https?:\/\//i.test(target)) {
|
|
4861
5205
|
const res = await fetch(target);
|
|
@@ -4864,7 +5208,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4864
5208
|
}
|
|
4865
5209
|
return await res.json();
|
|
4866
5210
|
}
|
|
4867
|
-
const raw = await
|
|
5211
|
+
const raw = await import_node_fs20.promises.readFile(target, "utf8");
|
|
4868
5212
|
return JSON.parse(raw);
|
|
4869
5213
|
}
|
|
4870
5214
|
function indexEntries(entries) {
|
|
@@ -4932,8 +5276,8 @@ function canonicalJson(value) {
|
|
|
4932
5276
|
|
|
4933
5277
|
// src/persist.ts
|
|
4934
5278
|
init_cjs_shims();
|
|
4935
|
-
var
|
|
4936
|
-
var
|
|
5279
|
+
var import_node_fs21 = require("fs");
|
|
5280
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4937
5281
|
var import_types21 = require("@neat.is/types");
|
|
4938
5282
|
var SCHEMA_VERSION = 4;
|
|
4939
5283
|
function migrateV1ToV2(payload) {
|
|
@@ -4970,7 +5314,7 @@ function migrateV2ToV3(payload) {
|
|
|
4970
5314
|
return { ...payload, schemaVersion: 3 };
|
|
4971
5315
|
}
|
|
4972
5316
|
async function ensureDir(filePath) {
|
|
4973
|
-
await
|
|
5317
|
+
await import_node_fs21.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
|
|
4974
5318
|
}
|
|
4975
5319
|
async function saveGraphToDisk(graph, outPath) {
|
|
4976
5320
|
await ensureDir(outPath);
|
|
@@ -4980,13 +5324,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4980
5324
|
graph: graph.export()
|
|
4981
5325
|
};
|
|
4982
5326
|
const tmp = `${outPath}.tmp`;
|
|
4983
|
-
await
|
|
4984
|
-
await
|
|
5327
|
+
await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
5328
|
+
await import_node_fs21.promises.rename(tmp, outPath);
|
|
4985
5329
|
}
|
|
4986
5330
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4987
5331
|
let raw;
|
|
4988
5332
|
try {
|
|
4989
|
-
raw = await
|
|
5333
|
+
raw = await import_node_fs21.promises.readFile(outPath, "utf8");
|
|
4990
5334
|
} catch (err) {
|
|
4991
5335
|
if (err.code === "ENOENT") return;
|
|
4992
5336
|
throw err;
|
|
@@ -5045,23 +5389,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
5045
5389
|
|
|
5046
5390
|
// src/projects.ts
|
|
5047
5391
|
init_cjs_shims();
|
|
5048
|
-
var
|
|
5392
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
5049
5393
|
function pathsForProject(project, baseDir) {
|
|
5050
5394
|
if (project === DEFAULT_PROJECT) {
|
|
5051
5395
|
return {
|
|
5052
|
-
snapshotPath:
|
|
5053
|
-
errorsPath:
|
|
5054
|
-
staleEventsPath:
|
|
5055
|
-
embeddingsCachePath:
|
|
5056
|
-
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")
|
|
5057
5401
|
};
|
|
5058
5402
|
}
|
|
5059
5403
|
return {
|
|
5060
|
-
snapshotPath:
|
|
5061
|
-
errorsPath:
|
|
5062
|
-
staleEventsPath:
|
|
5063
|
-
embeddingsCachePath:
|
|
5064
|
-
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`)
|
|
5065
5409
|
};
|
|
5066
5410
|
}
|
|
5067
5411
|
var Projects = class {
|
|
@@ -5101,23 +5445,23 @@ function parseExtraProjects(raw) {
|
|
|
5101
5445
|
|
|
5102
5446
|
// src/registry.ts
|
|
5103
5447
|
init_cjs_shims();
|
|
5104
|
-
var
|
|
5105
|
-
var
|
|
5106
|
-
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);
|
|
5107
5451
|
var import_types22 = require("@neat.is/types");
|
|
5108
5452
|
function neatHome() {
|
|
5109
5453
|
const override = process.env.NEAT_HOME;
|
|
5110
|
-
if (override && override.length > 0) return
|
|
5111
|
-
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");
|
|
5112
5456
|
}
|
|
5113
5457
|
function registryPath() {
|
|
5114
|
-
return
|
|
5458
|
+
return import_node_path35.default.join(neatHome(), "projects.json");
|
|
5115
5459
|
}
|
|
5116
5460
|
async function readRegistry() {
|
|
5117
5461
|
const file = registryPath();
|
|
5118
5462
|
let raw;
|
|
5119
5463
|
try {
|
|
5120
|
-
raw = await
|
|
5464
|
+
raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5121
5465
|
} catch (err) {
|
|
5122
5466
|
if (err.code === "ENOENT") {
|
|
5123
5467
|
return { version: 1, projects: [] };
|
|
@@ -5594,6 +5938,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5594
5938
|
violations
|
|
5595
5939
|
};
|
|
5596
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
|
+
});
|
|
5597
6040
|
}
|
|
5598
6041
|
async function buildApi(opts) {
|
|
5599
6042
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5704,8 +6147,8 @@ init_otel_grpc();
|
|
|
5704
6147
|
|
|
5705
6148
|
// src/search.ts
|
|
5706
6149
|
init_cjs_shims();
|
|
5707
|
-
var
|
|
5708
|
-
var
|
|
6150
|
+
var import_node_fs23 = require("fs");
|
|
6151
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5709
6152
|
var import_node_crypto3 = require("crypto");
|
|
5710
6153
|
var DEFAULT_LIMIT = 10;
|
|
5711
6154
|
var NOMIC_DIM = 768;
|
|
@@ -5835,7 +6278,7 @@ async function pickEmbedder() {
|
|
|
5835
6278
|
}
|
|
5836
6279
|
async function readCache(cachePath) {
|
|
5837
6280
|
try {
|
|
5838
|
-
const raw = await
|
|
6281
|
+
const raw = await import_node_fs23.promises.readFile(cachePath, "utf8");
|
|
5839
6282
|
const parsed = JSON.parse(raw);
|
|
5840
6283
|
if (parsed.version !== 1) return null;
|
|
5841
6284
|
return parsed;
|
|
@@ -5844,8 +6287,8 @@ async function readCache(cachePath) {
|
|
|
5844
6287
|
}
|
|
5845
6288
|
}
|
|
5846
6289
|
async function writeCache(cachePath, cache) {
|
|
5847
|
-
await
|
|
5848
|
-
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));
|
|
5849
6292
|
}
|
|
5850
6293
|
var VectorIndex = class {
|
|
5851
6294
|
constructor(embedder, cachePath) {
|
|
@@ -6025,14 +6468,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
6025
6468
|
async function main() {
|
|
6026
6469
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
6027
6470
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
6028
|
-
const baseDir = baseDirEnv ?
|
|
6029
|
-
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");
|
|
6030
6473
|
const registry = new Projects();
|
|
6031
6474
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
6032
6475
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
6033
6476
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
6034
6477
|
const projectScan = process.env[envKey];
|
|
6035
|
-
await bootProject(registry, name, projectScan ?
|
|
6478
|
+
await bootProject(registry, name, projectScan ? import_node_path39.default.resolve(projectScan) : void 0, baseDir);
|
|
6036
6479
|
}
|
|
6037
6480
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
6038
6481
|
const port = Number(process.env.PORT ?? 8080);
|