@neat.is/core 0.4.15 → 0.4.17
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-VMLWUK7W.js → chunk-CEDXXMGO.js} +16 -2
- package/dist/chunk-CEDXXMGO.js.map +1 -0
- package/dist/{chunk-XS4CGNRO.js → chunk-LUDSPX5N.js} +293 -71
- package/dist/chunk-LUDSPX5N.js.map +1 -0
- package/dist/cli.cjs +923 -590
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +19 -4
- package/dist/cli.d.ts +19 -4
- package/dist/cli.js +291 -186
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +377 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +384 -149
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +269 -97
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +3 -4
- package/dist/chunk-VMLWUK7W.js.map +0 -1
- package/dist/chunk-XS4CGNRO.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
|
|
|
58
58
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
59
59
|
const publicRead = opts.publicRead === true;
|
|
60
60
|
app.addHook("preHandler", (req, reply, done) => {
|
|
61
|
-
const
|
|
61
|
+
const path51 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
62
62
|
for (const suffix of suffixes) {
|
|
63
|
-
if (
|
|
63
|
+
if (path51 === suffix || path51.endsWith(suffix)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -188,8 +188,8 @@ function reshapeGrpcRequest(req) {
|
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
function resolveProtoRoot() {
|
|
191
|
-
const here =
|
|
192
|
-
return
|
|
191
|
+
const here = import_node_path41.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
192
|
+
return import_node_path41.default.resolve(here, "..", "proto");
|
|
193
193
|
}
|
|
194
194
|
function loadTraceService() {
|
|
195
195
|
const protoRoot = resolveProtoRoot();
|
|
@@ -257,13 +257,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
257
257
|
})
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
|
-
var
|
|
260
|
+
var import_node_url2, import_node_path41, import_node_crypto2, grpc, protoLoader;
|
|
261
261
|
var init_otel_grpc = __esm({
|
|
262
262
|
"src/otel-grpc.ts"() {
|
|
263
263
|
"use strict";
|
|
264
264
|
init_cjs_shims();
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
import_node_url2 = require("url");
|
|
266
|
+
import_node_path41 = __toESM(require("path"), 1);
|
|
267
267
|
import_node_crypto2 = require("crypto");
|
|
268
268
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
269
269
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -373,10 +373,10 @@ function parseOtlpRequest(body) {
|
|
|
373
373
|
return out;
|
|
374
374
|
}
|
|
375
375
|
function loadProtoRoot() {
|
|
376
|
-
const here =
|
|
377
|
-
const protoRoot =
|
|
376
|
+
const here = import_node_path42.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
377
|
+
const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
|
|
378
378
|
const root = new import_protobufjs.default.Root();
|
|
379
|
-
root.resolvePath = (_origin, target) =>
|
|
379
|
+
root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
|
|
380
380
|
root.loadSync(
|
|
381
381
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
382
382
|
{ keepCase: true }
|
|
@@ -572,13 +572,13 @@ async function buildOtelReceiver(opts) {
|
|
|
572
572
|
};
|
|
573
573
|
return decorated;
|
|
574
574
|
}
|
|
575
|
-
var
|
|
575
|
+
var import_node_path42, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
576
576
|
var init_otel = __esm({
|
|
577
577
|
"src/otel.ts"() {
|
|
578
578
|
"use strict";
|
|
579
579
|
init_cjs_shims();
|
|
580
|
-
|
|
581
|
-
|
|
580
|
+
import_node_path42 = __toESM(require("path"), 1);
|
|
581
|
+
import_node_url3 = require("url");
|
|
582
582
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
583
583
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
584
584
|
init_auth();
|
|
@@ -595,19 +595,60 @@ var init_otel = __esm({
|
|
|
595
595
|
var cli_exports = {};
|
|
596
596
|
__export(cli_exports, {
|
|
597
597
|
CLAUDE_SKILL_CONFIG: () => CLAUDE_SKILL_CONFIG,
|
|
598
|
+
ProjectResolutionError: () => ProjectResolutionError,
|
|
598
599
|
QUERY_VERBS: () => QUERY_VERBS,
|
|
600
|
+
commandPrefix: () => commandPrefix,
|
|
601
|
+
isNpxInvocation: () => isNpxInvocation,
|
|
602
|
+
main: () => main,
|
|
599
603
|
parseArgs: () => parseArgs,
|
|
600
604
|
printBanner: () => printBanner,
|
|
601
605
|
readPackageVersion: () => readPackageVersion,
|
|
606
|
+
resolveProjectForVerb: () => resolveProjectForVerb,
|
|
602
607
|
runInit: () => runInit,
|
|
603
608
|
runQueryVerb: () => runQueryVerb,
|
|
604
|
-
runSkill: () => runSkill
|
|
609
|
+
runSkill: () => runSkill,
|
|
610
|
+
usage: () => usage
|
|
605
611
|
});
|
|
606
612
|
module.exports = __toCommonJS(cli_exports);
|
|
607
613
|
init_cjs_shims();
|
|
608
|
-
var
|
|
609
|
-
var
|
|
610
|
-
|
|
614
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
615
|
+
var import_node_fs32 = require("fs");
|
|
616
|
+
|
|
617
|
+
// src/banner.ts
|
|
618
|
+
init_cjs_shims();
|
|
619
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
620
|
+
var import_node_fs = require("fs");
|
|
621
|
+
var import_node_url = require("url");
|
|
622
|
+
function readPackageVersion() {
|
|
623
|
+
const here = typeof __dirname !== "undefined" ? __dirname : import_node_path.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
624
|
+
const candidates = [
|
|
625
|
+
import_node_path.default.resolve(here, "../package.json"),
|
|
626
|
+
import_node_path.default.resolve(here, "../../package.json")
|
|
627
|
+
];
|
|
628
|
+
for (const candidate of candidates) {
|
|
629
|
+
try {
|
|
630
|
+
const raw = (0, import_node_fs.readFileSync)(candidate, "utf8");
|
|
631
|
+
const parsed = JSON.parse(raw);
|
|
632
|
+
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
633
|
+
return parsed.version;
|
|
634
|
+
}
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return "unknown";
|
|
639
|
+
}
|
|
640
|
+
function printBanner() {
|
|
641
|
+
console.log("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557");
|
|
642
|
+
console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
|
|
643
|
+
console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
644
|
+
console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
645
|
+
console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
646
|
+
console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
|
|
647
|
+
console.log("");
|
|
648
|
+
console.log(" Network Expressive Architecting Tool");
|
|
649
|
+
console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
|
|
650
|
+
console.log("");
|
|
651
|
+
}
|
|
611
652
|
|
|
612
653
|
// src/graph.ts
|
|
613
654
|
init_cjs_shims();
|
|
@@ -642,21 +683,21 @@ init_cjs_shims();
|
|
|
642
683
|
|
|
643
684
|
// src/ingest.ts
|
|
644
685
|
init_cjs_shims();
|
|
645
|
-
var
|
|
646
|
-
var
|
|
686
|
+
var import_node_fs4 = require("fs");
|
|
687
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
647
688
|
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
648
689
|
|
|
649
690
|
// src/policy.ts
|
|
650
691
|
init_cjs_shims();
|
|
651
|
-
var
|
|
652
|
-
var
|
|
692
|
+
var import_node_fs3 = require("fs");
|
|
693
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
653
694
|
var import_types2 = require("@neat.is/types");
|
|
654
695
|
|
|
655
696
|
// src/compat.ts
|
|
656
697
|
init_cjs_shims();
|
|
657
|
-
var
|
|
698
|
+
var import_node_fs2 = require("fs");
|
|
658
699
|
var import_node_os = __toESM(require("os"), 1);
|
|
659
|
-
var
|
|
700
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
660
701
|
var import_semver = __toESM(require("semver"), 1);
|
|
661
702
|
|
|
662
703
|
// compat.json
|
|
@@ -785,8 +826,8 @@ var compat_default = {
|
|
|
785
826
|
var bundledMatrix = compat_default;
|
|
786
827
|
var mergedMatrix = null;
|
|
787
828
|
var remoteLoadAttempted = false;
|
|
788
|
-
var REMOTE_CACHE_DIR =
|
|
789
|
-
var REMOTE_CACHE_PATH =
|
|
829
|
+
var REMOTE_CACHE_DIR = import_node_path2.default.join(import_node_os.default.homedir(), ".neat");
|
|
830
|
+
var REMOTE_CACHE_PATH = import_node_path2.default.join(REMOTE_CACHE_DIR, "compat-cache.json");
|
|
790
831
|
var REMOTE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
791
832
|
function engineMeetsThreshold(engineVersion, threshold) {
|
|
792
833
|
const e = parseInt(engineVersion, 10);
|
|
@@ -897,7 +938,7 @@ function mergeMatrices(a, b) {
|
|
|
897
938
|
}
|
|
898
939
|
async function readRemoteCache(url) {
|
|
899
940
|
try {
|
|
900
|
-
const raw = await
|
|
941
|
+
const raw = await import_node_fs2.promises.readFile(REMOTE_CACHE_PATH, "utf8");
|
|
901
942
|
const parsed = JSON.parse(raw);
|
|
902
943
|
if (parsed.url !== url) return null;
|
|
903
944
|
const age = Date.now() - new Date(parsed.fetchedAt).getTime();
|
|
@@ -914,8 +955,8 @@ async function writeRemoteCache(url, matrix) {
|
|
|
914
955
|
matrix
|
|
915
956
|
};
|
|
916
957
|
try {
|
|
917
|
-
await
|
|
918
|
-
await
|
|
958
|
+
await import_node_fs2.promises.mkdir(REMOTE_CACHE_DIR, { recursive: true });
|
|
959
|
+
await import_node_fs2.promises.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), "utf8");
|
|
919
960
|
} catch (err) {
|
|
920
961
|
console.warn(`[neat] failed to cache compat matrix: ${err.message}`);
|
|
921
962
|
}
|
|
@@ -1136,19 +1177,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1136
1177
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1137
1178
|
let best = { path: [start], edges: [] };
|
|
1138
1179
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1139
|
-
function step(node,
|
|
1140
|
-
if (
|
|
1141
|
-
best = { path: [...
|
|
1180
|
+
function step(node, path51, edges) {
|
|
1181
|
+
if (path51.length > best.path.length) {
|
|
1182
|
+
best = { path: [...path51], edges: [...edges] };
|
|
1142
1183
|
}
|
|
1143
|
-
if (
|
|
1184
|
+
if (path51.length - 1 >= maxDepth) return;
|
|
1144
1185
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1145
1186
|
for (const [srcId, edge] of incoming) {
|
|
1146
1187
|
if (visited.has(srcId)) continue;
|
|
1147
1188
|
visited.add(srcId);
|
|
1148
|
-
|
|
1189
|
+
path51.push(srcId);
|
|
1149
1190
|
edges.push(edge);
|
|
1150
|
-
step(srcId,
|
|
1151
|
-
|
|
1191
|
+
step(srcId, path51, edges);
|
|
1192
|
+
path51.pop();
|
|
1152
1193
|
edges.pop();
|
|
1153
1194
|
visited.delete(srcId);
|
|
1154
1195
|
}
|
|
@@ -1607,7 +1648,7 @@ function evaluateAllPolicies(graph, policies, ctx) {
|
|
|
1607
1648
|
async function loadPolicyFile(policyPath) {
|
|
1608
1649
|
let raw;
|
|
1609
1650
|
try {
|
|
1610
|
-
raw = await
|
|
1651
|
+
raw = await import_node_fs3.promises.readFile(policyPath, "utf8");
|
|
1611
1652
|
} catch (err) {
|
|
1612
1653
|
if (err.code === "ENOENT") return [];
|
|
1613
1654
|
throw err;
|
|
@@ -1628,8 +1669,8 @@ var PolicyViolationsLog = class {
|
|
|
1628
1669
|
if (!this.seen) await this.hydrate();
|
|
1629
1670
|
if (this.seen.has(v.id)) return false;
|
|
1630
1671
|
this.seen.add(v.id);
|
|
1631
|
-
await
|
|
1632
|
-
await
|
|
1672
|
+
await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(this.path), { recursive: true });
|
|
1673
|
+
await import_node_fs3.promises.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
|
|
1633
1674
|
emitNeatEvent({
|
|
1634
1675
|
type: "policy-violation",
|
|
1635
1676
|
project: this.project,
|
|
@@ -1639,7 +1680,7 @@ var PolicyViolationsLog = class {
|
|
|
1639
1680
|
}
|
|
1640
1681
|
async readAll() {
|
|
1641
1682
|
try {
|
|
1642
|
-
const raw = await
|
|
1683
|
+
const raw = await import_node_fs3.promises.readFile(this.path, "utf8");
|
|
1643
1684
|
return raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
1644
1685
|
} catch (err) {
|
|
1645
1686
|
if (err.code === "ENOENT") return [];
|
|
@@ -1688,6 +1729,41 @@ function thresholdForEdgeType(edgeType, overrides) {
|
|
|
1688
1729
|
const map = overrides ?? loadStaleThresholdsFromEnv();
|
|
1689
1730
|
return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS;
|
|
1690
1731
|
}
|
|
1732
|
+
var DEFAULT_INCIDENT_THRESHOLDS = {
|
|
1733
|
+
threshold: 5,
|
|
1734
|
+
windowMs: 6e4
|
|
1735
|
+
};
|
|
1736
|
+
function loadIncidentThresholdsFromEnv() {
|
|
1737
|
+
const raw = process.env.NEAT_INCIDENT_THRESHOLDS;
|
|
1738
|
+
if (!raw) return DEFAULT_INCIDENT_THRESHOLDS;
|
|
1739
|
+
try {
|
|
1740
|
+
const overrides = JSON.parse(raw);
|
|
1741
|
+
const merged = { ...DEFAULT_INCIDENT_THRESHOLDS };
|
|
1742
|
+
if (typeof overrides.threshold === "number" && Number.isFinite(overrides.threshold) && overrides.threshold >= 1) {
|
|
1743
|
+
merged.threshold = Math.floor(overrides.threshold);
|
|
1744
|
+
}
|
|
1745
|
+
if (typeof overrides.windowMs === "number" && Number.isFinite(overrides.windowMs) && overrides.windowMs >= 0) {
|
|
1746
|
+
merged.windowMs = overrides.windowMs;
|
|
1747
|
+
}
|
|
1748
|
+
return merged;
|
|
1749
|
+
} catch (err) {
|
|
1750
|
+
console.warn(
|
|
1751
|
+
`[neat] NEAT_INCIDENT_THRESHOLDS could not be parsed (${err.message}); using defaults`
|
|
1752
|
+
);
|
|
1753
|
+
return DEFAULT_INCIDENT_THRESHOLDS;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
function httpResponseStatus(span) {
|
|
1757
|
+
for (const key of ["http.response.status_code", "http.status_code"]) {
|
|
1758
|
+
const v = span.attributes[key];
|
|
1759
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1760
|
+
if (typeof v === "string") {
|
|
1761
|
+
const n = Number(v);
|
|
1762
|
+
if (Number.isFinite(n)) return n;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
return void 0;
|
|
1766
|
+
}
|
|
1691
1767
|
function nowIso(ctx) {
|
|
1692
1768
|
return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
|
|
1693
1769
|
}
|
|
@@ -1752,7 +1828,7 @@ function languageForExt(relPath) {
|
|
|
1752
1828
|
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1753
1829
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1754
1830
|
if (scanPath && scanPath.length > 0) {
|
|
1755
|
-
const absRoot = toPosix(
|
|
1831
|
+
const absRoot = toPosix(import_node_path4.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
1756
1832
|
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
1757
1833
|
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
1758
1834
|
}
|
|
@@ -1780,10 +1856,10 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
1780
1856
|
entry2 = null;
|
|
1781
1857
|
const mapPath = `${absFilepath}.map`;
|
|
1782
1858
|
try {
|
|
1783
|
-
if ((0,
|
|
1784
|
-
const raw = JSON.parse((0,
|
|
1859
|
+
if ((0, import_node_fs4.existsSync)(mapPath)) {
|
|
1860
|
+
const raw = JSON.parse((0, import_node_fs4.readFileSync)(mapPath, "utf8"));
|
|
1785
1861
|
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
1786
|
-
entry2 = { consumer, dir:
|
|
1862
|
+
entry2 = { consumer, dir: import_node_path4.default.dirname(mapPath) };
|
|
1787
1863
|
}
|
|
1788
1864
|
} catch {
|
|
1789
1865
|
entry2 = null;
|
|
@@ -1798,7 +1874,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
1798
1874
|
});
|
|
1799
1875
|
if (!pos || !pos.source) return null;
|
|
1800
1876
|
const root = entry2.consumer.sourceRoot ?? "";
|
|
1801
|
-
const resolved =
|
|
1877
|
+
const resolved = import_node_path4.default.resolve(entry2.dir, root, pos.source);
|
|
1802
1878
|
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
1803
1879
|
} catch {
|
|
1804
1880
|
return null;
|
|
@@ -2065,8 +2141,8 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
2065
2141
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2066
2142
|
}
|
|
2067
2143
|
async function appendErrorEvent(ctx, ev) {
|
|
2068
|
-
await
|
|
2069
|
-
await
|
|
2144
|
+
await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(ctx.errorsPath), { recursive: true });
|
|
2145
|
+
await import_node_fs4.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
2070
2146
|
}
|
|
2071
2147
|
function sanitizeAttributes(attrs) {
|
|
2072
2148
|
const out = {};
|
|
@@ -2097,10 +2173,75 @@ function makeErrorSpanWriter(errorsPath) {
|
|
|
2097
2173
|
return async (span) => {
|
|
2098
2174
|
const ev = buildErrorEventForReceiver(span);
|
|
2099
2175
|
if (!ev) return;
|
|
2100
|
-
await
|
|
2101
|
-
await
|
|
2176
|
+
await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(errorsPath), { recursive: true });
|
|
2177
|
+
await import_node_fs4.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
2102
2178
|
};
|
|
2103
2179
|
}
|
|
2180
|
+
async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
|
|
2181
|
+
const attrs = sanitizeAttributes(span.attributes);
|
|
2182
|
+
const first = firstTimestamp ?? timestamp;
|
|
2183
|
+
const peer = pickAddress(span);
|
|
2184
|
+
const message = count > 1 ? `${count} consecutive HTTP ${statusCode} responses` + (peer ? ` to ${peer}` : "") : `HTTP ${statusCode} response` + (peer ? ` from ${peer}` : "");
|
|
2185
|
+
const ev = {
|
|
2186
|
+
id: `${span.traceId}:${span.spanId}`,
|
|
2187
|
+
timestamp,
|
|
2188
|
+
service: span.service,
|
|
2189
|
+
traceId: span.traceId,
|
|
2190
|
+
spanId: span.spanId,
|
|
2191
|
+
errorType: "http-failure",
|
|
2192
|
+
errorMessage: message,
|
|
2193
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
2194
|
+
affectedNode,
|
|
2195
|
+
httpStatusCode: statusCode,
|
|
2196
|
+
incidentCount: count,
|
|
2197
|
+
firstTimestamp: first,
|
|
2198
|
+
lastTimestamp: timestamp
|
|
2199
|
+
};
|
|
2200
|
+
await appendErrorEvent(ctx, ev);
|
|
2201
|
+
}
|
|
2202
|
+
async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status2) {
|
|
2203
|
+
const { threshold, windowMs } = loadIncidentThresholdsFromEnv();
|
|
2204
|
+
if (!ctx.burstState) ctx.burstState = /* @__PURE__ */ new Map();
|
|
2205
|
+
const peer = pickAddress(span) ?? span.spanId;
|
|
2206
|
+
const key = `${span.service}->${peer}`;
|
|
2207
|
+
const existing = ctx.burstState.get(key);
|
|
2208
|
+
let state;
|
|
2209
|
+
if (existing && nowMs - existing.lastMs <= windowMs) {
|
|
2210
|
+
existing.count += 1;
|
|
2211
|
+
existing.lastTs = ts;
|
|
2212
|
+
existing.lastMs = nowMs;
|
|
2213
|
+
existing.codes.set(status2, (existing.codes.get(status2) ?? 0) + 1);
|
|
2214
|
+
state = existing;
|
|
2215
|
+
} else {
|
|
2216
|
+
state = {
|
|
2217
|
+
count: 1,
|
|
2218
|
+
firstTs: ts,
|
|
2219
|
+
lastTs: ts,
|
|
2220
|
+
lastMs: nowMs,
|
|
2221
|
+
codes: /* @__PURE__ */ new Map([[status2, 1]])
|
|
2222
|
+
};
|
|
2223
|
+
ctx.burstState.set(key, state);
|
|
2224
|
+
}
|
|
2225
|
+
if (state.count < threshold) return;
|
|
2226
|
+
let dominant = status2;
|
|
2227
|
+
let max = 0;
|
|
2228
|
+
for (const [code, n] of state.codes) {
|
|
2229
|
+
if (n > max) {
|
|
2230
|
+
max = n;
|
|
2231
|
+
dominant = code;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
await recordFailingResponseIncident(
|
|
2235
|
+
ctx,
|
|
2236
|
+
span,
|
|
2237
|
+
affectedNode,
|
|
2238
|
+
state.lastTs,
|
|
2239
|
+
dominant,
|
|
2240
|
+
state.count,
|
|
2241
|
+
state.firstTs
|
|
2242
|
+
);
|
|
2243
|
+
ctx.burstState.delete(key);
|
|
2244
|
+
}
|
|
2104
2245
|
async function handleSpan(ctx, span) {
|
|
2105
2246
|
const ts = span.startTimeIso ?? nowIso(ctx);
|
|
2106
2247
|
const nowMs = ctx.now ? ctx.now() : Date.now();
|
|
@@ -2199,6 +2340,14 @@ async function handleSpan(ctx, span) {
|
|
|
2199
2340
|
await appendErrorEvent(ctx, ev);
|
|
2200
2341
|
}
|
|
2201
2342
|
}
|
|
2343
|
+
if (span.statusCode !== 2) {
|
|
2344
|
+
const status2 = httpResponseStatus(span);
|
|
2345
|
+
if (status2 !== void 0 && status2 >= 500) {
|
|
2346
|
+
await recordFailingResponseIncident(ctx, span, sourceId, ts, status2, 1);
|
|
2347
|
+
} else if (status2 !== void 0 && status2 >= 400 && spanMintsObservedEdge(span.kind)) {
|
|
2348
|
+
await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status2);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2202
2351
|
void affectedNode;
|
|
2203
2352
|
if (ctx.onPolicyTrigger) await ctx.onPolicyTrigger(ctx.graph);
|
|
2204
2353
|
}
|
|
@@ -2317,13 +2466,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
2317
2466
|
return { count: events.length, events };
|
|
2318
2467
|
}
|
|
2319
2468
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
2320
|
-
await
|
|
2469
|
+
await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(staleEventsPath), { recursive: true });
|
|
2321
2470
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2322
|
-
await
|
|
2471
|
+
await import_node_fs4.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
2323
2472
|
}
|
|
2324
2473
|
async function readStaleEvents(staleEventsPath) {
|
|
2325
2474
|
try {
|
|
2326
|
-
const raw = await
|
|
2475
|
+
const raw = await import_node_fs4.promises.readFile(staleEventsPath, "utf8");
|
|
2327
2476
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2328
2477
|
} catch (err) {
|
|
2329
2478
|
if (err.code === "ENOENT") return [];
|
|
@@ -2357,7 +2506,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
2357
2506
|
}
|
|
2358
2507
|
async function readErrorEvents(errorsPath) {
|
|
2359
2508
|
try {
|
|
2360
|
-
const raw = await
|
|
2509
|
+
const raw = await import_node_fs4.promises.readFile(errorsPath, "utf8");
|
|
2361
2510
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2362
2511
|
} catch (err) {
|
|
2363
2512
|
if (err.code === "ENOENT") return [];
|
|
@@ -2389,16 +2538,16 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
2389
2538
|
|
|
2390
2539
|
// src/extract/services.ts
|
|
2391
2540
|
init_cjs_shims();
|
|
2392
|
-
var
|
|
2393
|
-
var
|
|
2541
|
+
var import_node_fs9 = require("fs");
|
|
2542
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2394
2543
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
2395
2544
|
var import_minimatch2 = require("minimatch");
|
|
2396
2545
|
var import_types5 = require("@neat.is/types");
|
|
2397
2546
|
|
|
2398
2547
|
// src/extract/shared.ts
|
|
2399
2548
|
init_cjs_shims();
|
|
2400
|
-
var
|
|
2401
|
-
var
|
|
2549
|
+
var import_node_fs5 = require("fs");
|
|
2550
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
2402
2551
|
var import_yaml = require("yaml");
|
|
2403
2552
|
var import_types4 = require("@neat.is/types");
|
|
2404
2553
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
|
|
@@ -2430,7 +2579,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2430
2579
|
const cached = PYVENV_MARKER_CACHE.get(dir);
|
|
2431
2580
|
if (cached !== void 0) return cached;
|
|
2432
2581
|
try {
|
|
2433
|
-
const stat = await
|
|
2582
|
+
const stat = await import_node_fs5.promises.stat(import_node_path5.default.join(dir, "pyvenv.cfg"));
|
|
2434
2583
|
const ok = stat.isFile();
|
|
2435
2584
|
PYVENV_MARKER_CACHE.set(dir, ok);
|
|
2436
2585
|
return ok;
|
|
@@ -2440,7 +2589,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2440
2589
|
}
|
|
2441
2590
|
}
|
|
2442
2591
|
function isConfigFile(name) {
|
|
2443
|
-
const ext =
|
|
2592
|
+
const ext = import_node_path5.default.extname(name);
|
|
2444
2593
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2445
2594
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2446
2595
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
@@ -2548,16 +2697,16 @@ function cleanVersion(raw) {
|
|
|
2548
2697
|
return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
|
|
2549
2698
|
}
|
|
2550
2699
|
async function readJson(filePath) {
|
|
2551
|
-
const raw = await
|
|
2700
|
+
const raw = await import_node_fs5.promises.readFile(filePath, "utf8");
|
|
2552
2701
|
return JSON.parse(raw);
|
|
2553
2702
|
}
|
|
2554
2703
|
async function readYaml(filePath) {
|
|
2555
|
-
const raw = await
|
|
2704
|
+
const raw = await import_node_fs5.promises.readFile(filePath, "utf8");
|
|
2556
2705
|
return (0, import_yaml.parse)(raw);
|
|
2557
2706
|
}
|
|
2558
2707
|
async function exists(p) {
|
|
2559
2708
|
try {
|
|
2560
|
-
await
|
|
2709
|
+
await import_node_fs5.promises.access(p);
|
|
2561
2710
|
return true;
|
|
2562
2711
|
} catch {
|
|
2563
2712
|
return false;
|
|
@@ -2566,8 +2715,8 @@ async function exists(p) {
|
|
|
2566
2715
|
|
|
2567
2716
|
// src/extract/python.ts
|
|
2568
2717
|
init_cjs_shims();
|
|
2569
|
-
var
|
|
2570
|
-
var
|
|
2718
|
+
var import_node_fs6 = require("fs");
|
|
2719
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2571
2720
|
var import_smol_toml = require("smol-toml");
|
|
2572
2721
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
2573
2722
|
function parseRequirementsTxt(content) {
|
|
@@ -2600,25 +2749,25 @@ function depsFromPyProject(pyproject) {
|
|
|
2600
2749
|
return out;
|
|
2601
2750
|
}
|
|
2602
2751
|
async function discoverPythonService(serviceDir) {
|
|
2603
|
-
const pyprojectPath =
|
|
2604
|
-
const requirementsPath =
|
|
2605
|
-
const setupPath =
|
|
2752
|
+
const pyprojectPath = import_node_path6.default.join(serviceDir, "pyproject.toml");
|
|
2753
|
+
const requirementsPath = import_node_path6.default.join(serviceDir, "requirements.txt");
|
|
2754
|
+
const setupPath = import_node_path6.default.join(serviceDir, "setup.py");
|
|
2606
2755
|
const hasPyproject = await exists(pyprojectPath);
|
|
2607
2756
|
const hasRequirements = await exists(requirementsPath);
|
|
2608
2757
|
const hasSetup = await exists(setupPath);
|
|
2609
2758
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
2610
|
-
let name =
|
|
2759
|
+
let name = import_node_path6.default.basename(serviceDir);
|
|
2611
2760
|
let version;
|
|
2612
2761
|
const dependencies = {};
|
|
2613
2762
|
if (hasPyproject) {
|
|
2614
|
-
const raw = await
|
|
2763
|
+
const raw = await import_node_fs6.promises.readFile(pyprojectPath, "utf8");
|
|
2615
2764
|
const pyproject = (0, import_smol_toml.parse)(raw);
|
|
2616
2765
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
2617
2766
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
2618
2767
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
2619
2768
|
}
|
|
2620
2769
|
if (hasRequirements) {
|
|
2621
|
-
const raw = await
|
|
2770
|
+
const raw = await import_node_fs6.promises.readFile(requirementsPath, "utf8");
|
|
2622
2771
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
2623
2772
|
}
|
|
2624
2773
|
return { name, version, dependencies };
|
|
@@ -2633,17 +2782,17 @@ function pythonToPackage(service) {
|
|
|
2633
2782
|
|
|
2634
2783
|
// src/extract/owners.ts
|
|
2635
2784
|
init_cjs_shims();
|
|
2636
|
-
var
|
|
2637
|
-
var
|
|
2785
|
+
var import_node_fs7 = require("fs");
|
|
2786
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2638
2787
|
var import_minimatch = require("minimatch");
|
|
2639
2788
|
async function loadCodeowners(scanPath) {
|
|
2640
2789
|
const candidates = [
|
|
2641
|
-
|
|
2642
|
-
|
|
2790
|
+
import_node_path7.default.join(scanPath, "CODEOWNERS"),
|
|
2791
|
+
import_node_path7.default.join(scanPath, ".github", "CODEOWNERS")
|
|
2643
2792
|
];
|
|
2644
2793
|
for (const file of candidates) {
|
|
2645
2794
|
if (await exists(file)) {
|
|
2646
|
-
const raw = await
|
|
2795
|
+
const raw = await import_node_fs7.promises.readFile(file, "utf8");
|
|
2647
2796
|
return parseCodeowners(raw);
|
|
2648
2797
|
}
|
|
2649
2798
|
}
|
|
@@ -2661,7 +2810,7 @@ function parseCodeowners(raw) {
|
|
|
2661
2810
|
return { rules };
|
|
2662
2811
|
}
|
|
2663
2812
|
function matchOwner(file, repoPath) {
|
|
2664
|
-
const normalized = repoPath.split(
|
|
2813
|
+
const normalized = repoPath.split(import_node_path7.default.sep).join("/");
|
|
2665
2814
|
for (const rule of file.rules) {
|
|
2666
2815
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
2667
2816
|
}
|
|
@@ -2677,7 +2826,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
2677
2826
|
return false;
|
|
2678
2827
|
}
|
|
2679
2828
|
async function readPackageJsonAuthor(serviceDir) {
|
|
2680
|
-
const pkgPath =
|
|
2829
|
+
const pkgPath = import_node_path7.default.join(serviceDir, "package.json");
|
|
2681
2830
|
if (!await exists(pkgPath)) return null;
|
|
2682
2831
|
try {
|
|
2683
2832
|
const pkg = await readJson(pkgPath);
|
|
@@ -2700,8 +2849,8 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
|
2700
2849
|
|
|
2701
2850
|
// src/extract/errors.ts
|
|
2702
2851
|
init_cjs_shims();
|
|
2703
|
-
var
|
|
2704
|
-
var
|
|
2852
|
+
var import_node_fs8 = require("fs");
|
|
2853
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2705
2854
|
var sink = [];
|
|
2706
2855
|
function recordExtractionError(producer, file, err) {
|
|
2707
2856
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -2720,9 +2869,9 @@ function drainExtractionErrors() {
|
|
|
2720
2869
|
}
|
|
2721
2870
|
async function writeExtractionErrors(errors, errorsPath) {
|
|
2722
2871
|
if (errors.length === 0) return;
|
|
2723
|
-
await
|
|
2872
|
+
await import_node_fs8.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
|
|
2724
2873
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2725
|
-
await
|
|
2874
|
+
await import_node_fs8.promises.appendFile(errorsPath, lines, "utf8");
|
|
2726
2875
|
}
|
|
2727
2876
|
function isStrictExtractionEnabled() {
|
|
2728
2877
|
const raw = process.env.NEAT_STRICT_EXTRACTION;
|
|
@@ -2745,9 +2894,9 @@ function isRejectedLogEnabled() {
|
|
|
2745
2894
|
}
|
|
2746
2895
|
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
2747
2896
|
if (drops.length === 0) return;
|
|
2748
|
-
await
|
|
2897
|
+
await import_node_fs8.promises.mkdir(import_node_path8.default.dirname(rejectedPath), { recursive: true });
|
|
2749
2898
|
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
2750
|
-
await
|
|
2899
|
+
await import_node_fs8.promises.appendFile(rejectedPath, lines, "utf8");
|
|
2751
2900
|
}
|
|
2752
2901
|
function formatPrecisionFloorBanner(count) {
|
|
2753
2902
|
if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
|
|
@@ -2770,21 +2919,21 @@ function workspaceGlobs(pkg) {
|
|
|
2770
2919
|
return null;
|
|
2771
2920
|
}
|
|
2772
2921
|
async function loadGitignore(scanPath) {
|
|
2773
|
-
const gitignorePath =
|
|
2922
|
+
const gitignorePath = import_node_path9.default.join(scanPath, ".gitignore");
|
|
2774
2923
|
if (!await exists(gitignorePath)) return null;
|
|
2775
|
-
const raw = await
|
|
2924
|
+
const raw = await import_node_fs9.promises.readFile(gitignorePath, "utf8");
|
|
2776
2925
|
return (0, import_ignore.default)().add(raw);
|
|
2777
2926
|
}
|
|
2778
2927
|
async function walkDirs(start, scanPath, options, visit) {
|
|
2779
2928
|
async function recurse(current, depth) {
|
|
2780
2929
|
if (depth > options.maxDepth) return;
|
|
2781
|
-
const entries = await
|
|
2930
|
+
const entries = await import_node_fs9.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2782
2931
|
for (const entry2 of entries) {
|
|
2783
2932
|
if (!entry2.isDirectory()) continue;
|
|
2784
2933
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2785
|
-
const child =
|
|
2934
|
+
const child = import_node_path9.default.join(current, entry2.name);
|
|
2786
2935
|
if (options.ig) {
|
|
2787
|
-
const rel =
|
|
2936
|
+
const rel = import_node_path9.default.relative(scanPath, child).split(import_node_path9.default.sep).join("/");
|
|
2788
2937
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2789
2938
|
}
|
|
2790
2939
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -2800,8 +2949,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2800
2949
|
for (const raw of globs) {
|
|
2801
2950
|
const pattern = raw.replace(/^\.\//, "");
|
|
2802
2951
|
if (!pattern.includes("*")) {
|
|
2803
|
-
const candidate =
|
|
2804
|
-
if (await exists(
|
|
2952
|
+
const candidate = import_node_path9.default.join(scanPath, pattern);
|
|
2953
|
+
if (await exists(import_node_path9.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2805
2954
|
continue;
|
|
2806
2955
|
}
|
|
2807
2956
|
const segments = pattern.split("/");
|
|
@@ -2810,13 +2959,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2810
2959
|
if (seg.includes("*")) break;
|
|
2811
2960
|
staticSegments.push(seg);
|
|
2812
2961
|
}
|
|
2813
|
-
const start =
|
|
2962
|
+
const start = import_node_path9.default.join(scanPath, ...staticSegments);
|
|
2814
2963
|
if (!await exists(start)) continue;
|
|
2815
2964
|
const hasDoubleStar = pattern.includes("**");
|
|
2816
2965
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2817
2966
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2818
|
-
const rel =
|
|
2819
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(
|
|
2967
|
+
const rel = import_node_path9.default.relative(scanPath, dir).split(import_node_path9.default.sep).join("/");
|
|
2968
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path9.default.join(dir, "package.json"))) {
|
|
2820
2969
|
found.add(dir);
|
|
2821
2970
|
}
|
|
2822
2971
|
});
|
|
@@ -2836,13 +2985,13 @@ function detectJsFramework(pkg) {
|
|
|
2836
2985
|
return void 0;
|
|
2837
2986
|
}
|
|
2838
2987
|
async function discoverNodeService(scanPath, dir) {
|
|
2839
|
-
const pkgPath =
|
|
2988
|
+
const pkgPath = import_node_path9.default.join(dir, "package.json");
|
|
2840
2989
|
if (!await exists(pkgPath)) return null;
|
|
2841
2990
|
let pkg;
|
|
2842
2991
|
try {
|
|
2843
2992
|
pkg = await readJson(pkgPath);
|
|
2844
2993
|
} catch (err) {
|
|
2845
|
-
recordExtractionError("services",
|
|
2994
|
+
recordExtractionError("services", import_node_path9.default.relative(scanPath, pkgPath), err);
|
|
2846
2995
|
return null;
|
|
2847
2996
|
}
|
|
2848
2997
|
if (!pkg.name) return null;
|
|
@@ -2854,7 +3003,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2854
3003
|
language: "javascript",
|
|
2855
3004
|
version: pkg.version,
|
|
2856
3005
|
dependencies: pkg.dependencies ?? {},
|
|
2857
|
-
repoPath:
|
|
3006
|
+
repoPath: import_node_path9.default.relative(scanPath, dir),
|
|
2858
3007
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
2859
3008
|
...framework ? { framework } : {}
|
|
2860
3009
|
};
|
|
@@ -2871,12 +3020,12 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2871
3020
|
language: "python",
|
|
2872
3021
|
version: py.version,
|
|
2873
3022
|
dependencies: py.dependencies,
|
|
2874
|
-
repoPath:
|
|
3023
|
+
repoPath: import_node_path9.default.relative(scanPath, dir)
|
|
2875
3024
|
};
|
|
2876
3025
|
return { pkg, dir, node };
|
|
2877
3026
|
}
|
|
2878
3027
|
async function discoverServices(scanPath) {
|
|
2879
|
-
const rootPkgPath =
|
|
3028
|
+
const rootPkgPath = import_node_path9.default.join(scanPath, "package.json");
|
|
2880
3029
|
let rootPkg = null;
|
|
2881
3030
|
if (await exists(rootPkgPath)) {
|
|
2882
3031
|
try {
|
|
@@ -2884,7 +3033,7 @@ async function discoverServices(scanPath) {
|
|
|
2884
3033
|
} catch (err) {
|
|
2885
3034
|
recordExtractionError(
|
|
2886
3035
|
"services workspaces",
|
|
2887
|
-
|
|
3036
|
+
import_node_path9.default.relative(scanPath, rootPkgPath),
|
|
2888
3037
|
err
|
|
2889
3038
|
);
|
|
2890
3039
|
}
|
|
@@ -2901,9 +3050,9 @@ async function discoverServices(scanPath) {
|
|
|
2901
3050
|
scanPath,
|
|
2902
3051
|
{ maxDepth: parseScanDepth(), ig },
|
|
2903
3052
|
async (dir) => {
|
|
2904
|
-
if (await exists(
|
|
3053
|
+
if (await exists(import_node_path9.default.join(dir, "package.json"))) {
|
|
2905
3054
|
candidateDirs.push(dir);
|
|
2906
|
-
} else if (await exists(
|
|
3055
|
+
} else if (await exists(import_node_path9.default.join(dir, "pyproject.toml")) || await exists(import_node_path9.default.join(dir, "requirements.txt")) || await exists(import_node_path9.default.join(dir, "setup.py"))) {
|
|
2907
3056
|
candidateDirs.push(dir);
|
|
2908
3057
|
}
|
|
2909
3058
|
}
|
|
@@ -2917,8 +3066,8 @@ async function discoverServices(scanPath) {
|
|
|
2917
3066
|
if (!service) continue;
|
|
2918
3067
|
const existingDir = seen.get(service.node.name);
|
|
2919
3068
|
if (existingDir !== void 0) {
|
|
2920
|
-
const a =
|
|
2921
|
-
const b =
|
|
3069
|
+
const a = import_node_path9.default.relative(scanPath, existingDir) || ".";
|
|
3070
|
+
const b = import_node_path9.default.relative(scanPath, dir) || ".";
|
|
2922
3071
|
console.warn(
|
|
2923
3072
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2924
3073
|
);
|
|
@@ -2955,8 +3104,8 @@ function addServiceNodes(graph, services) {
|
|
|
2955
3104
|
|
|
2956
3105
|
// src/extract/aliases.ts
|
|
2957
3106
|
init_cjs_shims();
|
|
2958
|
-
var
|
|
2959
|
-
var
|
|
3107
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
3108
|
+
var import_node_fs10 = require("fs");
|
|
2960
3109
|
var import_yaml2 = require("yaml");
|
|
2961
3110
|
var import_types6 = require("@neat.is/types");
|
|
2962
3111
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -2983,14 +3132,14 @@ function indexServicesByName(services) {
|
|
|
2983
3132
|
const map = /* @__PURE__ */ new Map();
|
|
2984
3133
|
for (const s of services) {
|
|
2985
3134
|
map.set(s.node.name, s.node.id);
|
|
2986
|
-
map.set(
|
|
3135
|
+
map.set(import_node_path10.default.basename(s.dir), s.node.id);
|
|
2987
3136
|
}
|
|
2988
3137
|
return map;
|
|
2989
3138
|
}
|
|
2990
3139
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
2991
3140
|
let composePath = null;
|
|
2992
3141
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2993
|
-
const abs =
|
|
3142
|
+
const abs = import_node_path10.default.join(scanPath, name);
|
|
2994
3143
|
if (await exists(abs)) {
|
|
2995
3144
|
composePath = abs;
|
|
2996
3145
|
break;
|
|
@@ -3003,7 +3152,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3003
3152
|
} catch (err) {
|
|
3004
3153
|
recordExtractionError(
|
|
3005
3154
|
"aliases compose",
|
|
3006
|
-
|
|
3155
|
+
import_node_path10.default.relative(scanPath, composePath),
|
|
3007
3156
|
err
|
|
3008
3157
|
);
|
|
3009
3158
|
return;
|
|
@@ -3046,11 +3195,11 @@ function parseDockerfileLabels(content) {
|
|
|
3046
3195
|
}
|
|
3047
3196
|
async function collectDockerfileAliases(graph, services) {
|
|
3048
3197
|
for (const service of services) {
|
|
3049
|
-
const dockerfilePath =
|
|
3198
|
+
const dockerfilePath = import_node_path10.default.join(service.dir, "Dockerfile");
|
|
3050
3199
|
if (!await exists(dockerfilePath)) continue;
|
|
3051
3200
|
let content;
|
|
3052
3201
|
try {
|
|
3053
|
-
content = await
|
|
3202
|
+
content = await import_node_fs10.promises.readFile(dockerfilePath, "utf8");
|
|
3054
3203
|
} catch (err) {
|
|
3055
3204
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
3056
3205
|
continue;
|
|
@@ -3062,15 +3211,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
3062
3211
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
3063
3212
|
if (depth > max) return [];
|
|
3064
3213
|
const out = [];
|
|
3065
|
-
const entries = await
|
|
3214
|
+
const entries = await import_node_fs10.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3066
3215
|
for (const entry2 of entries) {
|
|
3067
3216
|
if (entry2.isDirectory()) {
|
|
3068
3217
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3069
|
-
const child =
|
|
3218
|
+
const child = import_node_path10.default.join(start, entry2.name);
|
|
3070
3219
|
if (await isPythonVenvDir(child)) continue;
|
|
3071
3220
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
3072
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3073
|
-
out.push(
|
|
3221
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
|
|
3222
|
+
out.push(import_node_path10.default.join(start, entry2.name));
|
|
3074
3223
|
}
|
|
3075
3224
|
}
|
|
3076
3225
|
return out;
|
|
@@ -3097,7 +3246,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
3097
3246
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
3098
3247
|
const files = await walkYamlFiles(scanPath);
|
|
3099
3248
|
for (const file of files) {
|
|
3100
|
-
const content = await
|
|
3249
|
+
const content = await import_node_fs10.promises.readFile(file, "utf8");
|
|
3101
3250
|
let docs;
|
|
3102
3251
|
try {
|
|
3103
3252
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3125,20 +3274,20 @@ init_cjs_shims();
|
|
|
3125
3274
|
|
|
3126
3275
|
// src/extract/calls/shared.ts
|
|
3127
3276
|
init_cjs_shims();
|
|
3128
|
-
var
|
|
3129
|
-
var
|
|
3277
|
+
var import_node_fs11 = require("fs");
|
|
3278
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3130
3279
|
var import_types7 = require("@neat.is/types");
|
|
3131
3280
|
async function walkSourceFiles(dir) {
|
|
3132
3281
|
const out = [];
|
|
3133
3282
|
async function walk(current) {
|
|
3134
|
-
const entries = await
|
|
3283
|
+
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3135
3284
|
for (const entry2 of entries) {
|
|
3136
|
-
const full =
|
|
3285
|
+
const full = import_node_path11.default.join(current, entry2.name);
|
|
3137
3286
|
if (entry2.isDirectory()) {
|
|
3138
3287
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3139
3288
|
if (await isPythonVenvDir(full)) continue;
|
|
3140
3289
|
await walk(full);
|
|
3141
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
3290
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry2.name))) {
|
|
3142
3291
|
out.push(full);
|
|
3143
3292
|
}
|
|
3144
3293
|
}
|
|
@@ -3151,7 +3300,7 @@ async function loadSourceFiles(dir) {
|
|
|
3151
3300
|
const out = [];
|
|
3152
3301
|
for (const p of paths) {
|
|
3153
3302
|
try {
|
|
3154
|
-
const content = await
|
|
3303
|
+
const content = await import_node_fs11.promises.readFile(p, "utf8");
|
|
3155
3304
|
out.push({ path: p, content });
|
|
3156
3305
|
} catch {
|
|
3157
3306
|
}
|
|
@@ -3171,7 +3320,7 @@ function toPosix2(p) {
|
|
|
3171
3320
|
return p.split("\\").join("/");
|
|
3172
3321
|
}
|
|
3173
3322
|
function languageForPath(relPath) {
|
|
3174
|
-
switch (
|
|
3323
|
+
switch (import_node_path11.default.extname(relPath).toLowerCase()) {
|
|
3175
3324
|
case ".py":
|
|
3176
3325
|
return "python";
|
|
3177
3326
|
case ".ts":
|
|
@@ -3221,14 +3370,14 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
3221
3370
|
}
|
|
3222
3371
|
|
|
3223
3372
|
// src/extract/files.ts
|
|
3224
|
-
var
|
|
3373
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3225
3374
|
async function addFiles(graph, services) {
|
|
3226
3375
|
let nodesAdded = 0;
|
|
3227
3376
|
let edgesAdded = 0;
|
|
3228
3377
|
for (const service of services) {
|
|
3229
3378
|
const filePaths = await walkSourceFiles(service.dir);
|
|
3230
3379
|
for (const filePath of filePaths) {
|
|
3231
|
-
const relPath = toPosix2(
|
|
3380
|
+
const relPath = toPosix2(import_node_path12.default.relative(service.dir, filePath));
|
|
3232
3381
|
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3233
3382
|
graph,
|
|
3234
3383
|
service.pkg.name,
|
|
@@ -3244,8 +3393,8 @@ async function addFiles(graph, services) {
|
|
|
3244
3393
|
|
|
3245
3394
|
// src/extract/imports.ts
|
|
3246
3395
|
init_cjs_shims();
|
|
3247
|
-
var
|
|
3248
|
-
var
|
|
3396
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3397
|
+
var import_node_fs12 = require("fs");
|
|
3249
3398
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3250
3399
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3251
3400
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
@@ -3349,15 +3498,15 @@ function collectPyImports(node, out) {
|
|
|
3349
3498
|
}
|
|
3350
3499
|
async function fileExists(p) {
|
|
3351
3500
|
try {
|
|
3352
|
-
await
|
|
3501
|
+
await import_node_fs12.promises.access(p);
|
|
3353
3502
|
return true;
|
|
3354
3503
|
} catch {
|
|
3355
3504
|
return false;
|
|
3356
3505
|
}
|
|
3357
3506
|
}
|
|
3358
3507
|
function isWithinServiceDir(candidate, serviceDir) {
|
|
3359
|
-
const rel =
|
|
3360
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
3508
|
+
const rel = import_node_path13.default.relative(serviceDir, candidate);
|
|
3509
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path13.default.isAbsolute(rel);
|
|
3361
3510
|
}
|
|
3362
3511
|
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
3363
3512
|
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -3365,22 +3514,22 @@ async function firstExistingCandidate(base, serviceDir) {
|
|
|
3365
3514
|
for (const ext of JS_EXTENSIONS) {
|
|
3366
3515
|
const candidate = base + ext;
|
|
3367
3516
|
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3368
|
-
return toPosix2(
|
|
3517
|
+
return toPosix2(import_node_path13.default.relative(serviceDir, candidate));
|
|
3369
3518
|
}
|
|
3370
3519
|
}
|
|
3371
3520
|
for (const indexFile of JS_INDEX_FILES) {
|
|
3372
|
-
const candidate =
|
|
3521
|
+
const candidate = import_node_path13.default.join(base, indexFile);
|
|
3373
3522
|
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3374
|
-
return toPosix2(
|
|
3523
|
+
return toPosix2(import_node_path13.default.relative(serviceDir, candidate));
|
|
3375
3524
|
}
|
|
3376
3525
|
}
|
|
3377
3526
|
return null;
|
|
3378
3527
|
}
|
|
3379
3528
|
async function loadTsPathConfig(serviceDir) {
|
|
3380
|
-
const tsconfigPath =
|
|
3529
|
+
const tsconfigPath = import_node_path13.default.join(serviceDir, "tsconfig.json");
|
|
3381
3530
|
let raw;
|
|
3382
3531
|
try {
|
|
3383
|
-
raw = await
|
|
3532
|
+
raw = await import_node_fs12.promises.readFile(tsconfigPath, "utf8");
|
|
3384
3533
|
} catch {
|
|
3385
3534
|
return null;
|
|
3386
3535
|
}
|
|
@@ -3389,7 +3538,7 @@ async function loadTsPathConfig(serviceDir) {
|
|
|
3389
3538
|
const paths = parsed.compilerOptions?.paths;
|
|
3390
3539
|
if (!paths || Object.keys(paths).length === 0) return null;
|
|
3391
3540
|
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
3392
|
-
return { paths, baseDir: baseUrl ?
|
|
3541
|
+
return { paths, baseDir: baseUrl ? import_node_path13.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
3393
3542
|
} catch (err) {
|
|
3394
3543
|
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
3395
3544
|
return null;
|
|
@@ -3407,11 +3556,11 @@ async function resolveTsAlias(specifier, config, serviceDir) {
|
|
|
3407
3556
|
if (suffix === null) continue;
|
|
3408
3557
|
for (const target of targets) {
|
|
3409
3558
|
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
3410
|
-
const resolvedBase =
|
|
3559
|
+
const resolvedBase = import_node_path13.default.resolve(config.baseDir, targetBase, suffix);
|
|
3411
3560
|
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
3412
3561
|
if (hit) return hit;
|
|
3413
3562
|
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
3414
|
-
return toPosix2(
|
|
3563
|
+
return toPosix2(import_node_path13.default.relative(serviceDir, resolvedBase));
|
|
3415
3564
|
}
|
|
3416
3565
|
}
|
|
3417
3566
|
}
|
|
@@ -3420,18 +3569,18 @@ async function resolveTsAlias(specifier, config, serviceDir) {
|
|
|
3420
3569
|
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
3421
3570
|
if (!specifier) return null;
|
|
3422
3571
|
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
3423
|
-
const base =
|
|
3424
|
-
const ext =
|
|
3572
|
+
const base = import_node_path13.default.resolve(importerDir, specifier);
|
|
3573
|
+
const ext = import_node_path13.default.extname(specifier);
|
|
3425
3574
|
if (ext) {
|
|
3426
3575
|
if (ext === ".js" || ext === ".jsx") {
|
|
3427
3576
|
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
3428
3577
|
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
3429
3578
|
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
3430
|
-
return toPosix2(
|
|
3579
|
+
return toPosix2(import_node_path13.default.relative(serviceDir, tsSibling));
|
|
3431
3580
|
}
|
|
3432
3581
|
}
|
|
3433
3582
|
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
3434
|
-
return toPosix2(
|
|
3583
|
+
return toPosix2(import_node_path13.default.relative(serviceDir, base));
|
|
3435
3584
|
}
|
|
3436
3585
|
return null;
|
|
3437
3586
|
}
|
|
@@ -3445,15 +3594,15 @@ async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
|
3445
3594
|
const relPath = imp.modulePath.split(".").join("/");
|
|
3446
3595
|
let baseDir;
|
|
3447
3596
|
if (imp.level > 0) {
|
|
3448
|
-
baseDir =
|
|
3449
|
-
for (let i = 1; i < imp.level; i++) baseDir =
|
|
3597
|
+
baseDir = import_node_path13.default.dirname(importerPath);
|
|
3598
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path13.default.dirname(baseDir);
|
|
3450
3599
|
} else {
|
|
3451
3600
|
baseDir = serviceDir;
|
|
3452
3601
|
}
|
|
3453
|
-
const candidates = [
|
|
3602
|
+
const candidates = [import_node_path13.default.join(baseDir, `${relPath}.py`), import_node_path13.default.join(baseDir, relPath, "__init__.py")];
|
|
3454
3603
|
for (const candidate of candidates) {
|
|
3455
3604
|
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3456
|
-
return toPosix2(
|
|
3605
|
+
return toPosix2(import_node_path13.default.relative(serviceDir, candidate));
|
|
3457
3606
|
}
|
|
3458
3607
|
}
|
|
3459
3608
|
return null;
|
|
@@ -3484,9 +3633,9 @@ async function addImports(graph, services) {
|
|
|
3484
3633
|
const files = await loadSourceFiles(service.dir);
|
|
3485
3634
|
for (const file of files) {
|
|
3486
3635
|
if (isTestPath(file.path)) continue;
|
|
3487
|
-
const relFile = toPosix2(
|
|
3636
|
+
const relFile = toPosix2(import_node_path13.default.relative(service.dir, file.path));
|
|
3488
3637
|
const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
|
|
3489
|
-
const isPython =
|
|
3638
|
+
const isPython = import_node_path13.default.extname(file.path) === ".py";
|
|
3490
3639
|
if (isPython) {
|
|
3491
3640
|
let pyImports = [];
|
|
3492
3641
|
try {
|
|
@@ -3520,7 +3669,7 @@ async function addImports(graph, services) {
|
|
|
3520
3669
|
continue;
|
|
3521
3670
|
}
|
|
3522
3671
|
for (const imp of jsImports) {
|
|
3523
|
-
const resolved = await resolveJsImport(imp.specifier,
|
|
3672
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path13.default.dirname(file.path), service.dir, tsPaths);
|
|
3524
3673
|
if (!resolved) continue;
|
|
3525
3674
|
edgesAdded += emitImportEdge(
|
|
3526
3675
|
graph,
|
|
@@ -3539,14 +3688,14 @@ async function addImports(graph, services) {
|
|
|
3539
3688
|
|
|
3540
3689
|
// src/extract/databases/index.ts
|
|
3541
3690
|
init_cjs_shims();
|
|
3542
|
-
var
|
|
3691
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3543
3692
|
var import_types9 = require("@neat.is/types");
|
|
3544
3693
|
|
|
3545
3694
|
// src/extract/databases/db-config-yaml.ts
|
|
3546
3695
|
init_cjs_shims();
|
|
3547
|
-
var
|
|
3696
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3548
3697
|
async function parse(serviceDir) {
|
|
3549
|
-
const yamlPath =
|
|
3698
|
+
const yamlPath = import_node_path14.default.join(serviceDir, "db-config.yaml");
|
|
3550
3699
|
if (!await exists(yamlPath)) return [];
|
|
3551
3700
|
const raw = await readYaml(yamlPath);
|
|
3552
3701
|
return [
|
|
@@ -3564,13 +3713,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3564
3713
|
|
|
3565
3714
|
// src/extract/databases/dotenv.ts
|
|
3566
3715
|
init_cjs_shims();
|
|
3567
|
-
var
|
|
3568
|
-
var
|
|
3716
|
+
var import_node_fs14 = require("fs");
|
|
3717
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3569
3718
|
|
|
3570
3719
|
// src/extract/databases/shared.ts
|
|
3571
3720
|
init_cjs_shims();
|
|
3572
|
-
var
|
|
3573
|
-
var
|
|
3721
|
+
var import_node_fs13 = require("fs");
|
|
3722
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3574
3723
|
function schemeToEngine(scheme) {
|
|
3575
3724
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3576
3725
|
switch (s) {
|
|
@@ -3609,14 +3758,14 @@ function parseConnectionString(url) {
|
|
|
3609
3758
|
}
|
|
3610
3759
|
async function readIfExists(filePath) {
|
|
3611
3760
|
try {
|
|
3612
|
-
return await
|
|
3761
|
+
return await import_node_fs13.promises.readFile(filePath, "utf8");
|
|
3613
3762
|
} catch {
|
|
3614
3763
|
return null;
|
|
3615
3764
|
}
|
|
3616
3765
|
}
|
|
3617
3766
|
async function findFirst(serviceDir, candidates) {
|
|
3618
3767
|
for (const rel of candidates) {
|
|
3619
|
-
const abs =
|
|
3768
|
+
const abs = import_node_path15.default.join(serviceDir, rel);
|
|
3620
3769
|
const content = await readIfExists(abs);
|
|
3621
3770
|
if (content !== null) return abs;
|
|
3622
3771
|
}
|
|
@@ -3667,15 +3816,15 @@ function parseDotenvLine(line) {
|
|
|
3667
3816
|
return { key, value };
|
|
3668
3817
|
}
|
|
3669
3818
|
async function parse2(serviceDir) {
|
|
3670
|
-
const entries = await
|
|
3819
|
+
const entries = await import_node_fs14.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3671
3820
|
const configs = [];
|
|
3672
3821
|
const seen = /* @__PURE__ */ new Set();
|
|
3673
3822
|
for (const entry2 of entries) {
|
|
3674
3823
|
if (!entry2.isFile()) continue;
|
|
3675
3824
|
const match = isConfigFile(entry2.name);
|
|
3676
3825
|
if (!match.match || match.fileType !== "env") continue;
|
|
3677
|
-
const filePath =
|
|
3678
|
-
const content = await
|
|
3826
|
+
const filePath = import_node_path16.default.join(serviceDir, entry2.name);
|
|
3827
|
+
const content = await import_node_fs14.promises.readFile(filePath, "utf8");
|
|
3679
3828
|
for (const line of content.split("\n")) {
|
|
3680
3829
|
const parsed = parseDotenvLine(line);
|
|
3681
3830
|
if (!parsed) continue;
|
|
@@ -3694,9 +3843,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3694
3843
|
|
|
3695
3844
|
// src/extract/databases/prisma.ts
|
|
3696
3845
|
init_cjs_shims();
|
|
3697
|
-
var
|
|
3846
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3698
3847
|
async function parse3(serviceDir) {
|
|
3699
|
-
const schemaPath =
|
|
3848
|
+
const schemaPath = import_node_path17.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3700
3849
|
const content = await readIfExists(schemaPath);
|
|
3701
3850
|
if (!content) return [];
|
|
3702
3851
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3828,10 +3977,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3828
3977
|
|
|
3829
3978
|
// src/extract/databases/ormconfig.ts
|
|
3830
3979
|
init_cjs_shims();
|
|
3831
|
-
var
|
|
3980
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3832
3981
|
async function parse6(serviceDir) {
|
|
3833
3982
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3834
|
-
const abs =
|
|
3983
|
+
const abs = import_node_path18.default.join(serviceDir, candidate);
|
|
3835
3984
|
if (!await exists(abs)) continue;
|
|
3836
3985
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3837
3986
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3891,9 +4040,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3891
4040
|
|
|
3892
4041
|
// src/extract/databases/sequelize.ts
|
|
3893
4042
|
init_cjs_shims();
|
|
3894
|
-
var
|
|
4043
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3895
4044
|
async function parse8(serviceDir) {
|
|
3896
|
-
const configPath =
|
|
4045
|
+
const configPath = import_node_path19.default.join(serviceDir, "config", "config.json");
|
|
3897
4046
|
if (!await exists(configPath)) return [];
|
|
3898
4047
|
const raw = await readJson(configPath);
|
|
3899
4048
|
const out = [];
|
|
@@ -3920,7 +4069,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3920
4069
|
|
|
3921
4070
|
// src/extract/databases/docker-compose.ts
|
|
3922
4071
|
init_cjs_shims();
|
|
3923
|
-
var
|
|
4072
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3924
4073
|
function portFromService(svc) {
|
|
3925
4074
|
for (const raw of svc.ports ?? []) {
|
|
3926
4075
|
const str = String(raw);
|
|
@@ -3947,7 +4096,7 @@ function databaseFromEnv(svc) {
|
|
|
3947
4096
|
}
|
|
3948
4097
|
async function parse9(serviceDir) {
|
|
3949
4098
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3950
|
-
const abs =
|
|
4099
|
+
const abs = import_node_path20.default.join(serviceDir, name);
|
|
3951
4100
|
if (!await exists(abs)) continue;
|
|
3952
4101
|
const raw = await readYaml(abs);
|
|
3953
4102
|
if (!raw?.services) return [];
|
|
@@ -4119,7 +4268,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
4119
4268
|
discoveredVia: mergedDiscoveredVia
|
|
4120
4269
|
});
|
|
4121
4270
|
}
|
|
4122
|
-
const relConfigFile = toPosix2(
|
|
4271
|
+
const relConfigFile = toPosix2(import_node_path21.default.relative(service.dir, config.sourceFile));
|
|
4123
4272
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4124
4273
|
graph,
|
|
4125
4274
|
service.pkg.name,
|
|
@@ -4128,7 +4277,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
4128
4277
|
);
|
|
4129
4278
|
nodesAdded += fn;
|
|
4130
4279
|
edgesAdded += fe;
|
|
4131
|
-
const evidenceFile = toPosix2(
|
|
4280
|
+
const evidenceFile = toPosix2(import_node_path21.default.relative(scanPath, config.sourceFile));
|
|
4132
4281
|
const edge = {
|
|
4133
4282
|
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
|
|
4134
4283
|
source: fileNodeId,
|
|
@@ -4162,15 +4311,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
4162
4311
|
|
|
4163
4312
|
// src/extract/configs.ts
|
|
4164
4313
|
init_cjs_shims();
|
|
4165
|
-
var
|
|
4166
|
-
var
|
|
4314
|
+
var import_node_fs15 = require("fs");
|
|
4315
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
4167
4316
|
var import_types10 = require("@neat.is/types");
|
|
4168
4317
|
async function walkConfigFiles(dir) {
|
|
4169
4318
|
const out = [];
|
|
4170
4319
|
async function walk(current) {
|
|
4171
|
-
const entries = await
|
|
4320
|
+
const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
|
|
4172
4321
|
for (const entry2 of entries) {
|
|
4173
|
-
const full =
|
|
4322
|
+
const full = import_node_path22.default.join(current, entry2.name);
|
|
4174
4323
|
if (entry2.isDirectory()) {
|
|
4175
4324
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4176
4325
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -4189,19 +4338,19 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4189
4338
|
for (const service of services) {
|
|
4190
4339
|
const configFiles = await walkConfigFiles(service.dir);
|
|
4191
4340
|
for (const file of configFiles) {
|
|
4192
|
-
const relPath =
|
|
4341
|
+
const relPath = import_node_path22.default.relative(scanPath, file);
|
|
4193
4342
|
const node = {
|
|
4194
4343
|
id: (0, import_types10.configId)(relPath),
|
|
4195
4344
|
type: import_types10.NodeType.ConfigNode,
|
|
4196
|
-
name:
|
|
4345
|
+
name: import_node_path22.default.basename(file),
|
|
4197
4346
|
path: relPath,
|
|
4198
|
-
fileType: isConfigFile(
|
|
4347
|
+
fileType: isConfigFile(import_node_path22.default.basename(file)).fileType
|
|
4199
4348
|
};
|
|
4200
4349
|
if (!graph.hasNode(node.id)) {
|
|
4201
4350
|
graph.addNode(node.id, node);
|
|
4202
4351
|
nodesAdded++;
|
|
4203
4352
|
}
|
|
4204
|
-
const relToService = toPosix2(
|
|
4353
|
+
const relToService = toPosix2(import_node_path22.default.relative(service.dir, file));
|
|
4205
4354
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4206
4355
|
graph,
|
|
4207
4356
|
service.pkg.name,
|
|
@@ -4217,7 +4366,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4217
4366
|
type: import_types10.EdgeType.CONFIGURED_BY,
|
|
4218
4367
|
provenance: import_types10.Provenance.EXTRACTED,
|
|
4219
4368
|
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4220
|
-
evidence: { file: relPath.split(
|
|
4369
|
+
evidence: { file: relPath.split(import_node_path22.default.sep).join("/") }
|
|
4221
4370
|
};
|
|
4222
4371
|
if (!graph.hasEdge(edge.id)) {
|
|
4223
4372
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -4230,11 +4379,11 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4230
4379
|
|
|
4231
4380
|
// src/extract/calls/index.ts
|
|
4232
4381
|
init_cjs_shims();
|
|
4233
|
-
var
|
|
4382
|
+
var import_types17 = require("@neat.is/types");
|
|
4234
4383
|
|
|
4235
4384
|
// src/extract/calls/http.ts
|
|
4236
4385
|
init_cjs_shims();
|
|
4237
|
-
var
|
|
4386
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
4238
4387
|
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
4239
4388
|
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4240
4389
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
@@ -4303,9 +4452,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4303
4452
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
4304
4453
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4305
4454
|
for (const service of services) {
|
|
4306
|
-
knownHosts.add(
|
|
4455
|
+
knownHosts.add(import_node_path23.default.basename(service.dir));
|
|
4307
4456
|
knownHosts.add(service.pkg.name);
|
|
4308
|
-
hostToNodeId.set(
|
|
4457
|
+
hostToNodeId.set(import_node_path23.default.basename(service.dir), service.node.id);
|
|
4309
4458
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4310
4459
|
}
|
|
4311
4460
|
let nodesAdded = 0;
|
|
@@ -4315,7 +4464,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4315
4464
|
const seen = /* @__PURE__ */ new Set();
|
|
4316
4465
|
for (const file of files) {
|
|
4317
4466
|
if (isTestPath(file.path)) continue;
|
|
4318
|
-
const parser =
|
|
4467
|
+
const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
4319
4468
|
let sites;
|
|
4320
4469
|
try {
|
|
4321
4470
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -4324,7 +4473,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4324
4473
|
continue;
|
|
4325
4474
|
}
|
|
4326
4475
|
if (sites.length === 0) continue;
|
|
4327
|
-
const relFile = toPosix2(
|
|
4476
|
+
const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
|
|
4328
4477
|
for (const site of sites) {
|
|
4329
4478
|
const targetId = hostToNodeId.get(site.host);
|
|
4330
4479
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -4378,7 +4527,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4378
4527
|
|
|
4379
4528
|
// src/extract/calls/kafka.ts
|
|
4380
4529
|
init_cjs_shims();
|
|
4381
|
-
var
|
|
4530
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4382
4531
|
var import_types12 = require("@neat.is/types");
|
|
4383
4532
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4384
4533
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -4409,7 +4558,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4409
4558
|
// tier (ADR-066).
|
|
4410
4559
|
confidenceKind: "verified-call-site",
|
|
4411
4560
|
evidence: {
|
|
4412
|
-
file:
|
|
4561
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
4413
4562
|
line,
|
|
4414
4563
|
snippet: snippet(file.content, line)
|
|
4415
4564
|
}
|
|
@@ -4422,7 +4571,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4422
4571
|
|
|
4423
4572
|
// src/extract/calls/redis.ts
|
|
4424
4573
|
init_cjs_shims();
|
|
4425
|
-
var
|
|
4574
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
4426
4575
|
var import_types13 = require("@neat.is/types");
|
|
4427
4576
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
4428
4577
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -4445,7 +4594,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4445
4594
|
// support tier (ADR-066).
|
|
4446
4595
|
confidenceKind: "url-with-structural-support",
|
|
4447
4596
|
evidence: {
|
|
4448
|
-
file:
|
|
4597
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
4449
4598
|
line,
|
|
4450
4599
|
snippet: snippet(file.content, line)
|
|
4451
4600
|
}
|
|
@@ -4456,7 +4605,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4456
4605
|
|
|
4457
4606
|
// src/extract/calls/aws.ts
|
|
4458
4607
|
init_cjs_shims();
|
|
4459
|
-
var
|
|
4608
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
4460
4609
|
var import_types14 = require("@neat.is/types");
|
|
4461
4610
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4462
4611
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -4490,7 +4639,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4490
4639
|
// (ADR-066).
|
|
4491
4640
|
confidenceKind: "verified-call-site",
|
|
4492
4641
|
evidence: {
|
|
4493
|
-
file:
|
|
4642
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
4494
4643
|
line,
|
|
4495
4644
|
snippet: snippet(file.content, line)
|
|
4496
4645
|
}
|
|
@@ -4515,7 +4664,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4515
4664
|
|
|
4516
4665
|
// src/extract/calls/grpc.ts
|
|
4517
4666
|
init_cjs_shims();
|
|
4518
|
-
var
|
|
4667
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
4519
4668
|
var import_types15 = require("@neat.is/types");
|
|
4520
4669
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4521
4670
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -4574,7 +4723,67 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4574
4723
|
// tier (ADR-066).
|
|
4575
4724
|
confidenceKind: "verified-call-site",
|
|
4576
4725
|
evidence: {
|
|
4577
|
-
file:
|
|
4726
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
4727
|
+
line,
|
|
4728
|
+
snippet: snippet(file.content, line)
|
|
4729
|
+
}
|
|
4730
|
+
});
|
|
4731
|
+
}
|
|
4732
|
+
return out;
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
// src/extract/calls/supabase.ts
|
|
4736
|
+
init_cjs_shims();
|
|
4737
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
4738
|
+
var import_types16 = require("@neat.is/types");
|
|
4739
|
+
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
4740
|
+
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
4741
|
+
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
4742
|
+
function hostFromLiteral(literal) {
|
|
4743
|
+
if (!literal) return null;
|
|
4744
|
+
const m = /^https?:\/\/([^/:'"`\s]+)/.exec(literal.trim());
|
|
4745
|
+
if (!m) return null;
|
|
4746
|
+
const host = m[1];
|
|
4747
|
+
if (!/\.supabase\.(co|in)$/i.test(host)) return null;
|
|
4748
|
+
return host;
|
|
4749
|
+
}
|
|
4750
|
+
function readImports2(content) {
|
|
4751
|
+
return {
|
|
4752
|
+
hasSupabaseJs: SUPABASE_JS_IMPORT_RE.test(content),
|
|
4753
|
+
hasSupabaseSsr: SUPABASE_SSR_IMPORT_RE.test(content)
|
|
4754
|
+
};
|
|
4755
|
+
}
|
|
4756
|
+
function constructorMatchesImport(name, ctx) {
|
|
4757
|
+
if (name === "createClient") return ctx.hasSupabaseJs;
|
|
4758
|
+
return ctx.hasSupabaseSsr;
|
|
4759
|
+
}
|
|
4760
|
+
function supabaseEndpointsFromFile(file, serviceDir) {
|
|
4761
|
+
const ctx = readImports2(file.content);
|
|
4762
|
+
if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return [];
|
|
4763
|
+
const out = [];
|
|
4764
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4765
|
+
SUPABASE_CLIENT_RE.lastIndex = 0;
|
|
4766
|
+
let m;
|
|
4767
|
+
while ((m = SUPABASE_CLIENT_RE.exec(file.content)) !== null) {
|
|
4768
|
+
const ctor = m[1];
|
|
4769
|
+
if (!constructorMatchesImport(ctor, ctx)) continue;
|
|
4770
|
+
const host = hostFromLiteral(m[2]);
|
|
4771
|
+
const name = host ?? "env";
|
|
4772
|
+
if (seen.has(name)) continue;
|
|
4773
|
+
seen.add(name);
|
|
4774
|
+
const line = lineOf(file.content, m[0]);
|
|
4775
|
+
out.push({
|
|
4776
|
+
infraId: (0, import_types16.infraId)("supabase", name),
|
|
4777
|
+
name,
|
|
4778
|
+
kind: "supabase",
|
|
4779
|
+
edgeType: "CALLS",
|
|
4780
|
+
// `createClient(...)` from @supabase/supabase-js (or createServerClient /
|
|
4781
|
+
// createBrowserClient from @supabase/ssr) with the import in scope — a
|
|
4782
|
+
// framework-aware recognizer matched the SDK shape. Verified-call-site
|
|
4783
|
+
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
4784
|
+
confidenceKind: "verified-call-site",
|
|
4785
|
+
evidence: {
|
|
4786
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
4578
4787
|
line,
|
|
4579
4788
|
snippet: snippet(file.content, line)
|
|
4580
4789
|
}
|
|
@@ -4587,11 +4796,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4587
4796
|
function edgeTypeFromEndpoint(ep) {
|
|
4588
4797
|
switch (ep.edgeType) {
|
|
4589
4798
|
case "PUBLISHES_TO":
|
|
4590
|
-
return
|
|
4799
|
+
return import_types17.EdgeType.PUBLISHES_TO;
|
|
4591
4800
|
case "CONSUMES_FROM":
|
|
4592
|
-
return
|
|
4801
|
+
return import_types17.EdgeType.CONSUMES_FROM;
|
|
4593
4802
|
default:
|
|
4594
|
-
return
|
|
4803
|
+
return import_types17.EdgeType.CALLS;
|
|
4595
4804
|
}
|
|
4596
4805
|
}
|
|
4597
4806
|
function isAwsKind(kind) {
|
|
@@ -4611,6 +4820,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4611
4820
|
endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
|
|
4612
4821
|
endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
|
|
4613
4822
|
endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
|
|
4823
|
+
endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
|
|
4614
4824
|
}
|
|
4615
4825
|
if (endpoints.length === 0) continue;
|
|
4616
4826
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
@@ -4618,7 +4828,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4618
4828
|
if (!graph.hasNode(ep.infraId)) {
|
|
4619
4829
|
const node = {
|
|
4620
4830
|
id: ep.infraId,
|
|
4621
|
-
type:
|
|
4831
|
+
type: import_types17.NodeType.InfraNode,
|
|
4622
4832
|
name: ep.name,
|
|
4623
4833
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4624
4834
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4630,7 +4840,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4630
4840
|
nodesAdded++;
|
|
4631
4841
|
}
|
|
4632
4842
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4633
|
-
const confidence = (0,
|
|
4843
|
+
const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
|
|
4634
4844
|
const relFile = toPosix2(ep.evidence.file);
|
|
4635
4845
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4636
4846
|
graph,
|
|
@@ -4640,7 +4850,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4640
4850
|
);
|
|
4641
4851
|
nodesAdded += n;
|
|
4642
4852
|
edgesAdded += e;
|
|
4643
|
-
if (!(0,
|
|
4853
|
+
if (!(0, import_types17.passesExtractedFloor)(confidence)) {
|
|
4644
4854
|
noteExtractedDropped({
|
|
4645
4855
|
source: fileNodeId,
|
|
4646
4856
|
target: ep.infraId,
|
|
@@ -4660,7 +4870,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4660
4870
|
source: fileNodeId,
|
|
4661
4871
|
target: ep.infraId,
|
|
4662
4872
|
type: edgeType,
|
|
4663
|
-
provenance:
|
|
4873
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
4664
4874
|
confidence,
|
|
4665
4875
|
evidence: ep.evidence
|
|
4666
4876
|
};
|
|
@@ -4685,16 +4895,16 @@ init_cjs_shims();
|
|
|
4685
4895
|
|
|
4686
4896
|
// src/extract/infra/docker-compose.ts
|
|
4687
4897
|
init_cjs_shims();
|
|
4688
|
-
var
|
|
4689
|
-
var
|
|
4898
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4899
|
+
var import_types19 = require("@neat.is/types");
|
|
4690
4900
|
|
|
4691
4901
|
// src/extract/infra/shared.ts
|
|
4692
4902
|
init_cjs_shims();
|
|
4693
|
-
var
|
|
4903
|
+
var import_types18 = require("@neat.is/types");
|
|
4694
4904
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4695
4905
|
return {
|
|
4696
|
-
id: (0,
|
|
4697
|
-
type:
|
|
4906
|
+
id: (0, import_types18.infraId)(kind, name),
|
|
4907
|
+
type: import_types18.NodeType.InfraNode,
|
|
4698
4908
|
name,
|
|
4699
4909
|
provider,
|
|
4700
4910
|
kind,
|
|
@@ -4723,7 +4933,7 @@ function dependsOnList(value) {
|
|
|
4723
4933
|
}
|
|
4724
4934
|
function serviceNameToServiceNode(name, services) {
|
|
4725
4935
|
for (const s of services) {
|
|
4726
|
-
if (s.node.name === name ||
|
|
4936
|
+
if (s.node.name === name || import_node_path29.default.basename(s.dir) === name) return s.node.id;
|
|
4727
4937
|
}
|
|
4728
4938
|
return null;
|
|
4729
4939
|
}
|
|
@@ -4732,7 +4942,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4732
4942
|
let edgesAdded = 0;
|
|
4733
4943
|
let composePath = null;
|
|
4734
4944
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4735
|
-
const abs =
|
|
4945
|
+
const abs = import_node_path29.default.join(scanPath, name);
|
|
4736
4946
|
if (await exists(abs)) {
|
|
4737
4947
|
composePath = abs;
|
|
4738
4948
|
break;
|
|
@@ -4745,13 +4955,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4745
4955
|
} catch (err) {
|
|
4746
4956
|
recordExtractionError(
|
|
4747
4957
|
"infra docker-compose",
|
|
4748
|
-
|
|
4958
|
+
import_node_path29.default.relative(scanPath, composePath),
|
|
4749
4959
|
err
|
|
4750
4960
|
);
|
|
4751
4961
|
return { nodesAdded, edgesAdded };
|
|
4752
4962
|
}
|
|
4753
4963
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4754
|
-
const evidenceFile =
|
|
4964
|
+
const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
|
|
4755
4965
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4756
4966
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4757
4967
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4773,15 +4983,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4773
4983
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4774
4984
|
const targetId = composeNameToNodeId.get(dep);
|
|
4775
4985
|
if (!targetId) continue;
|
|
4776
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4986
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
|
|
4777
4987
|
if (graph.hasEdge(edgeId)) continue;
|
|
4778
4988
|
const edge = {
|
|
4779
4989
|
id: edgeId,
|
|
4780
4990
|
source: sourceId,
|
|
4781
4991
|
target: targetId,
|
|
4782
|
-
type:
|
|
4783
|
-
provenance:
|
|
4784
|
-
confidence: (0,
|
|
4992
|
+
type: import_types19.EdgeType.DEPENDS_ON,
|
|
4993
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
4994
|
+
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4785
4995
|
evidence: { file: evidenceFile }
|
|
4786
4996
|
};
|
|
4787
4997
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4793,9 +5003,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4793
5003
|
|
|
4794
5004
|
// src/extract/infra/dockerfile.ts
|
|
4795
5005
|
init_cjs_shims();
|
|
4796
|
-
var
|
|
4797
|
-
var
|
|
4798
|
-
var
|
|
5006
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
5007
|
+
var import_node_fs16 = require("fs");
|
|
5008
|
+
var import_types20 = require("@neat.is/types");
|
|
4799
5009
|
function runtimeImage(content) {
|
|
4800
5010
|
const lines = content.split("\n");
|
|
4801
5011
|
let last = null;
|
|
@@ -4814,15 +5024,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4814
5024
|
let nodesAdded = 0;
|
|
4815
5025
|
let edgesAdded = 0;
|
|
4816
5026
|
for (const service of services) {
|
|
4817
|
-
const dockerfilePath =
|
|
5027
|
+
const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
|
|
4818
5028
|
if (!await exists(dockerfilePath)) continue;
|
|
4819
5029
|
let content;
|
|
4820
5030
|
try {
|
|
4821
|
-
content = await
|
|
5031
|
+
content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
|
|
4822
5032
|
} catch (err) {
|
|
4823
5033
|
recordExtractionError(
|
|
4824
5034
|
"infra dockerfile",
|
|
4825
|
-
|
|
5035
|
+
import_node_path30.default.relative(scanPath, dockerfilePath),
|
|
4826
5036
|
err
|
|
4827
5037
|
);
|
|
4828
5038
|
continue;
|
|
@@ -4834,7 +5044,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4834
5044
|
graph.addNode(node.id, node);
|
|
4835
5045
|
nodesAdded++;
|
|
4836
5046
|
}
|
|
4837
|
-
const relDockerfile = toPosix2(
|
|
5047
|
+
const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
|
|
4838
5048
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4839
5049
|
graph,
|
|
4840
5050
|
service.pkg.name,
|
|
@@ -4843,17 +5053,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4843
5053
|
);
|
|
4844
5054
|
nodesAdded += fn;
|
|
4845
5055
|
edgesAdded += fe;
|
|
4846
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
5056
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
|
|
4847
5057
|
if (!graph.hasEdge(edgeId)) {
|
|
4848
5058
|
const edge = {
|
|
4849
5059
|
id: edgeId,
|
|
4850
5060
|
source: fileNodeId,
|
|
4851
5061
|
target: node.id,
|
|
4852
|
-
type:
|
|
4853
|
-
provenance:
|
|
4854
|
-
confidence: (0,
|
|
5062
|
+
type: import_types20.EdgeType.RUNS_ON,
|
|
5063
|
+
provenance: import_types20.Provenance.EXTRACTED,
|
|
5064
|
+
confidence: (0, import_types20.confidenceForExtracted)("structural"),
|
|
4855
5065
|
evidence: {
|
|
4856
|
-
file: toPosix2(
|
|
5066
|
+
file: toPosix2(import_node_path30.default.relative(scanPath, dockerfilePath))
|
|
4857
5067
|
}
|
|
4858
5068
|
};
|
|
4859
5069
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4865,21 +5075,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4865
5075
|
|
|
4866
5076
|
// src/extract/infra/terraform.ts
|
|
4867
5077
|
init_cjs_shims();
|
|
4868
|
-
var
|
|
4869
|
-
var
|
|
5078
|
+
var import_node_fs17 = require("fs");
|
|
5079
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4870
5080
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4871
5081
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4872
5082
|
if (depth > max) return [];
|
|
4873
5083
|
const out = [];
|
|
4874
|
-
const entries = await
|
|
5084
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4875
5085
|
for (const entry2 of entries) {
|
|
4876
5086
|
if (entry2.isDirectory()) {
|
|
4877
5087
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
4878
|
-
const child =
|
|
5088
|
+
const child = import_node_path31.default.join(start, entry2.name);
|
|
4879
5089
|
if (await isPythonVenvDir(child)) continue;
|
|
4880
5090
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4881
5091
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
4882
|
-
out.push(
|
|
5092
|
+
out.push(import_node_path31.default.join(start, entry2.name));
|
|
4883
5093
|
}
|
|
4884
5094
|
}
|
|
4885
5095
|
return out;
|
|
@@ -4888,7 +5098,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4888
5098
|
let nodesAdded = 0;
|
|
4889
5099
|
const files = await walkTfFiles(scanPath);
|
|
4890
5100
|
for (const file of files) {
|
|
4891
|
-
const content = await
|
|
5101
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
4892
5102
|
RESOURCE_RE.lastIndex = 0;
|
|
4893
5103
|
let m;
|
|
4894
5104
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -4906,8 +5116,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4906
5116
|
|
|
4907
5117
|
// src/extract/infra/k8s.ts
|
|
4908
5118
|
init_cjs_shims();
|
|
4909
|
-
var
|
|
4910
|
-
var
|
|
5119
|
+
var import_node_fs18 = require("fs");
|
|
5120
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4911
5121
|
var import_yaml3 = require("yaml");
|
|
4912
5122
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4913
5123
|
Service: "k8s-service",
|
|
@@ -4921,15 +5131,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4921
5131
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4922
5132
|
if (depth > max) return [];
|
|
4923
5133
|
const out = [];
|
|
4924
|
-
const entries = await
|
|
5134
|
+
const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4925
5135
|
for (const entry2 of entries) {
|
|
4926
5136
|
if (entry2.isDirectory()) {
|
|
4927
5137
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4928
|
-
const child =
|
|
5138
|
+
const child = import_node_path32.default.join(start, entry2.name);
|
|
4929
5139
|
if (await isPythonVenvDir(child)) continue;
|
|
4930
5140
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4931
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4932
|
-
out.push(
|
|
5141
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path32.default.extname(entry2.name))) {
|
|
5142
|
+
out.push(import_node_path32.default.join(start, entry2.name));
|
|
4933
5143
|
}
|
|
4934
5144
|
}
|
|
4935
5145
|
return out;
|
|
@@ -4938,7 +5148,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4938
5148
|
let nodesAdded = 0;
|
|
4939
5149
|
const files = await walkYamlFiles2(scanPath);
|
|
4940
5150
|
for (const file of files) {
|
|
4941
|
-
const content = await
|
|
5151
|
+
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
4942
5152
|
let docs;
|
|
4943
5153
|
try {
|
|
4944
5154
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -4973,17 +5183,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4973
5183
|
}
|
|
4974
5184
|
|
|
4975
5185
|
// src/extract/index.ts
|
|
4976
|
-
var
|
|
5186
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
4977
5187
|
|
|
4978
5188
|
// src/extract/retire.ts
|
|
4979
5189
|
init_cjs_shims();
|
|
4980
|
-
var
|
|
4981
|
-
var
|
|
4982
|
-
var
|
|
5190
|
+
var import_node_fs19 = require("fs");
|
|
5191
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
5192
|
+
var import_types21 = require("@neat.is/types");
|
|
4983
5193
|
function dropOrphanedFileNodes(graph) {
|
|
4984
5194
|
const orphans = [];
|
|
4985
5195
|
graph.forEachNode((id, attrs) => {
|
|
4986
|
-
if (attrs.type !==
|
|
5196
|
+
if (attrs.type !== import_types21.NodeType.FileNode) return;
|
|
4987
5197
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4988
5198
|
orphans.push(id);
|
|
4989
5199
|
}
|
|
@@ -4996,7 +5206,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
4996
5206
|
const toDrop = [];
|
|
4997
5207
|
graph.forEachEdge((id, attrs) => {
|
|
4998
5208
|
const edge = attrs;
|
|
4999
|
-
if (edge.provenance !==
|
|
5209
|
+
if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
|
|
5000
5210
|
if (!edge.evidence?.file) return;
|
|
5001
5211
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
5002
5212
|
});
|
|
@@ -5009,14 +5219,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
5009
5219
|
const bases = [scanPath, ...serviceDirs];
|
|
5010
5220
|
graph.forEachEdge((id, attrs) => {
|
|
5011
5221
|
const edge = attrs;
|
|
5012
|
-
if (edge.provenance !==
|
|
5222
|
+
if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
|
|
5013
5223
|
const evidenceFile = edge.evidence?.file;
|
|
5014
5224
|
if (!evidenceFile) return;
|
|
5015
|
-
if (
|
|
5016
|
-
if (!(0,
|
|
5225
|
+
if (import_node_path33.default.isAbsolute(evidenceFile)) {
|
|
5226
|
+
if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
|
|
5017
5227
|
return;
|
|
5018
5228
|
}
|
|
5019
|
-
const found = bases.some((base) => (0,
|
|
5229
|
+
const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path33.default.join(base, evidenceFile)));
|
|
5020
5230
|
if (!found) toDrop.push(id);
|
|
5021
5231
|
});
|
|
5022
5232
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -5056,7 +5266,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5056
5266
|
}
|
|
5057
5267
|
const droppedEntries = drainDroppedExtracted();
|
|
5058
5268
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
5059
|
-
const rejectedPath =
|
|
5269
|
+
const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
5060
5270
|
try {
|
|
5061
5271
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
5062
5272
|
} catch (err) {
|
|
@@ -5090,7 +5300,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5090
5300
|
|
|
5091
5301
|
// src/divergences.ts
|
|
5092
5302
|
init_cjs_shims();
|
|
5093
|
-
var
|
|
5303
|
+
var import_types22 = require("@neat.is/types");
|
|
5094
5304
|
function bucketKey(source, target, type) {
|
|
5095
5305
|
return `${type}|${source}|${target}`;
|
|
5096
5306
|
}
|
|
@@ -5098,22 +5308,22 @@ function bucketEdges(graph) {
|
|
|
5098
5308
|
const buckets = /* @__PURE__ */ new Map();
|
|
5099
5309
|
graph.forEachEdge((id, attrs) => {
|
|
5100
5310
|
const e = attrs;
|
|
5101
|
-
const parsed = (0,
|
|
5311
|
+
const parsed = (0, import_types22.parseEdgeId)(id);
|
|
5102
5312
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
5103
5313
|
const key = bucketKey(e.source, e.target, e.type);
|
|
5104
5314
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
5105
5315
|
switch (provenance) {
|
|
5106
|
-
case
|
|
5316
|
+
case import_types22.Provenance.EXTRACTED:
|
|
5107
5317
|
cur.extracted = e;
|
|
5108
5318
|
break;
|
|
5109
|
-
case
|
|
5319
|
+
case import_types22.Provenance.OBSERVED:
|
|
5110
5320
|
cur.observed = e;
|
|
5111
5321
|
break;
|
|
5112
|
-
case
|
|
5322
|
+
case import_types22.Provenance.INFERRED:
|
|
5113
5323
|
cur.inferred = e;
|
|
5114
5324
|
break;
|
|
5115
5325
|
default:
|
|
5116
|
-
if (e.provenance ===
|
|
5326
|
+
if (e.provenance === import_types22.Provenance.STALE) cur.stale = e;
|
|
5117
5327
|
}
|
|
5118
5328
|
buckets.set(key, cur);
|
|
5119
5329
|
});
|
|
@@ -5122,7 +5332,7 @@ function bucketEdges(graph) {
|
|
|
5122
5332
|
function nodeIsFrontier(graph, nodeId) {
|
|
5123
5333
|
if (!graph.hasNode(nodeId)) return false;
|
|
5124
5334
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
5125
|
-
return attrs.type ===
|
|
5335
|
+
return attrs.type === import_types22.NodeType.FrontierNode;
|
|
5126
5336
|
}
|
|
5127
5337
|
function clampConfidence(n) {
|
|
5128
5338
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -5143,7 +5353,7 @@ function gradedConfidence(edge) {
|
|
|
5143
5353
|
}
|
|
5144
5354
|
function detectMissingDivergences(graph, bucket) {
|
|
5145
5355
|
const out = [];
|
|
5146
|
-
if (bucket.type ===
|
|
5356
|
+
if (bucket.type === import_types22.EdgeType.CONTAINS) return out;
|
|
5147
5357
|
if (bucket.extracted && !bucket.observed) {
|
|
5148
5358
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
5149
5359
|
out.push({
|
|
@@ -5184,7 +5394,7 @@ function declaredHostFor(svc) {
|
|
|
5184
5394
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
5185
5395
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
5186
5396
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5187
|
-
if (e.type ===
|
|
5397
|
+
if (e.type === import_types22.EdgeType.CONFIGURED_BY && e.provenance === import_types22.Provenance.EXTRACTED) {
|
|
5188
5398
|
return true;
|
|
5189
5399
|
}
|
|
5190
5400
|
}
|
|
@@ -5197,10 +5407,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
5197
5407
|
const out = [];
|
|
5198
5408
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
5199
5409
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
5200
|
-
if (edge.type !==
|
|
5201
|
-
if (edge.provenance !==
|
|
5410
|
+
if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
|
|
5411
|
+
if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
|
|
5202
5412
|
const target = graph.getNodeAttributes(edge.target);
|
|
5203
|
-
if (target.type !==
|
|
5413
|
+
if (target.type !== import_types22.NodeType.DatabaseNode) continue;
|
|
5204
5414
|
const observedHost = target.host?.trim();
|
|
5205
5415
|
if (!observedHost) continue;
|
|
5206
5416
|
if (observedHost === declaredHost) continue;
|
|
@@ -5222,10 +5432,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
5222
5432
|
const deps = svc.dependencies ?? {};
|
|
5223
5433
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
5224
5434
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
5225
|
-
if (edge.type !==
|
|
5226
|
-
if (edge.provenance !==
|
|
5435
|
+
if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
|
|
5436
|
+
if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
|
|
5227
5437
|
const target = graph.getNodeAttributes(edge.target);
|
|
5228
|
-
if (target.type !==
|
|
5438
|
+
if (target.type !== import_types22.NodeType.DatabaseNode) continue;
|
|
5229
5439
|
for (const pair of compatPairs()) {
|
|
5230
5440
|
if (pair.engine !== target.engine) continue;
|
|
5231
5441
|
const declared = deps[pair.driver];
|
|
@@ -5286,7 +5496,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5286
5496
|
}
|
|
5287
5497
|
graph.forEachNode((nodeId, attrs) => {
|
|
5288
5498
|
const n = attrs;
|
|
5289
|
-
if (n.type !==
|
|
5499
|
+
if (n.type !== import_types22.NodeType.ServiceNode) return;
|
|
5290
5500
|
const svc = n;
|
|
5291
5501
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
5292
5502
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -5319,7 +5529,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5319
5529
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
5320
5530
|
return a.target.localeCompare(b.target);
|
|
5321
5531
|
});
|
|
5322
|
-
return
|
|
5532
|
+
return import_types22.DivergenceResultSchema.parse({
|
|
5323
5533
|
divergences: filtered,
|
|
5324
5534
|
totalAffected: filtered.length,
|
|
5325
5535
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5328,9 +5538,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5328
5538
|
|
|
5329
5539
|
// src/persist.ts
|
|
5330
5540
|
init_cjs_shims();
|
|
5331
|
-
var
|
|
5332
|
-
var
|
|
5333
|
-
var
|
|
5541
|
+
var import_node_fs20 = require("fs");
|
|
5542
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5543
|
+
var import_types23 = require("@neat.is/types");
|
|
5334
5544
|
var SCHEMA_VERSION = 4;
|
|
5335
5545
|
function migrateV1ToV2(payload) {
|
|
5336
5546
|
const nodes = payload.graph.nodes;
|
|
@@ -5352,12 +5562,12 @@ function migrateV2ToV3(payload) {
|
|
|
5352
5562
|
for (const edge of edges) {
|
|
5353
5563
|
const attrs = edge.attributes;
|
|
5354
5564
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
5355
|
-
attrs.provenance =
|
|
5565
|
+
attrs.provenance = import_types23.Provenance.OBSERVED;
|
|
5356
5566
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
5357
5567
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
5358
5568
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
5359
5569
|
if (type && source && target) {
|
|
5360
|
-
const newId = (0,
|
|
5570
|
+
const newId = (0, import_types23.observedEdgeId)(source, target, type);
|
|
5361
5571
|
attrs.id = newId;
|
|
5362
5572
|
if (edge.key) edge.key = newId;
|
|
5363
5573
|
}
|
|
@@ -5366,7 +5576,7 @@ function migrateV2ToV3(payload) {
|
|
|
5366
5576
|
return { ...payload, schemaVersion: 3 };
|
|
5367
5577
|
}
|
|
5368
5578
|
async function ensureDir(filePath) {
|
|
5369
|
-
await
|
|
5579
|
+
await import_node_fs20.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
|
|
5370
5580
|
}
|
|
5371
5581
|
async function saveGraphToDisk(graph, outPath) {
|
|
5372
5582
|
await ensureDir(outPath);
|
|
@@ -5376,13 +5586,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
5376
5586
|
graph: graph.export()
|
|
5377
5587
|
};
|
|
5378
5588
|
const tmp = `${outPath}.tmp`;
|
|
5379
|
-
await
|
|
5380
|
-
await
|
|
5589
|
+
await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
5590
|
+
await import_node_fs20.promises.rename(tmp, outPath);
|
|
5381
5591
|
}
|
|
5382
5592
|
async function loadGraphFromDisk(graph, outPath) {
|
|
5383
5593
|
let raw;
|
|
5384
5594
|
try {
|
|
5385
|
-
raw = await
|
|
5595
|
+
raw = await import_node_fs20.promises.readFile(outPath, "utf8");
|
|
5386
5596
|
} catch (err) {
|
|
5387
5597
|
if (err.code === "ENOENT") return;
|
|
5388
5598
|
throw err;
|
|
@@ -5441,8 +5651,8 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
5441
5651
|
|
|
5442
5652
|
// src/gitignore.ts
|
|
5443
5653
|
init_cjs_shims();
|
|
5444
|
-
var
|
|
5445
|
-
var
|
|
5654
|
+
var import_node_fs21 = require("fs");
|
|
5655
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5446
5656
|
var NEAT_OUT_LINE = "neat-out/";
|
|
5447
5657
|
var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
|
|
5448
5658
|
function isNeatOutLine(line) {
|
|
@@ -5450,15 +5660,15 @@ function isNeatOutLine(line) {
|
|
|
5450
5660
|
return trimmed === "neat-out/" || trimmed === "neat-out";
|
|
5451
5661
|
}
|
|
5452
5662
|
async function ensureNeatOutIgnored(projectDir) {
|
|
5453
|
-
const file =
|
|
5663
|
+
const file = import_node_path36.default.join(projectDir, ".gitignore");
|
|
5454
5664
|
let existing = null;
|
|
5455
5665
|
try {
|
|
5456
|
-
existing = await
|
|
5666
|
+
existing = await import_node_fs21.promises.readFile(file, "utf8");
|
|
5457
5667
|
} catch (err) {
|
|
5458
5668
|
if (err.code !== "ENOENT") throw err;
|
|
5459
5669
|
}
|
|
5460
5670
|
if (existing === null) {
|
|
5461
|
-
await
|
|
5671
|
+
await import_node_fs21.promises.writeFile(file, `${NEAT_HEADER}
|
|
5462
5672
|
${NEAT_OUT_LINE}
|
|
5463
5673
|
`, "utf8");
|
|
5464
5674
|
return { action: "created", file };
|
|
@@ -5471,13 +5681,13 @@ ${NEAT_OUT_LINE}
|
|
|
5471
5681
|
${NEAT_HEADER}
|
|
5472
5682
|
${NEAT_OUT_LINE}
|
|
5473
5683
|
`;
|
|
5474
|
-
await
|
|
5684
|
+
await import_node_fs21.promises.writeFile(file, existing + appended, "utf8");
|
|
5475
5685
|
return { action: "added", file };
|
|
5476
5686
|
}
|
|
5477
5687
|
|
|
5478
5688
|
// src/summary.ts
|
|
5479
5689
|
init_cjs_shims();
|
|
5480
|
-
var
|
|
5690
|
+
var import_types24 = require("@neat.is/types");
|
|
5481
5691
|
function renderOtelEnvBlock() {
|
|
5482
5692
|
return [
|
|
5483
5693
|
"for prod OTel routing, set these in your deploy platform's env:",
|
|
@@ -5487,19 +5697,19 @@ function renderOtelEnvBlock() {
|
|
|
5487
5697
|
}
|
|
5488
5698
|
function findIncompatServices(nodes) {
|
|
5489
5699
|
return nodes.filter(
|
|
5490
|
-
(n) => n.type ===
|
|
5700
|
+
(n) => n.type === import_types24.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
|
|
5491
5701
|
);
|
|
5492
5702
|
}
|
|
5493
5703
|
function servicesWithoutObserved(nodes, edges) {
|
|
5494
5704
|
const seen = /* @__PURE__ */ new Set();
|
|
5495
5705
|
for (const e of edges) {
|
|
5496
|
-
if (e.provenance ===
|
|
5706
|
+
if (e.provenance === import_types24.Provenance.OBSERVED) {
|
|
5497
5707
|
seen.add(e.source);
|
|
5498
5708
|
seen.add(e.target);
|
|
5499
5709
|
}
|
|
5500
5710
|
}
|
|
5501
5711
|
return nodes.filter(
|
|
5502
|
-
(n) => n.type ===
|
|
5712
|
+
(n) => n.type === import_types24.NodeType.ServiceNode && !seen.has(n.id)
|
|
5503
5713
|
);
|
|
5504
5714
|
}
|
|
5505
5715
|
function formatDivergence(d) {
|
|
@@ -5573,27 +5783,27 @@ function formatIncompat(inc) {
|
|
|
5573
5783
|
|
|
5574
5784
|
// src/watch.ts
|
|
5575
5785
|
init_cjs_shims();
|
|
5576
|
-
var
|
|
5577
|
-
var
|
|
5786
|
+
var import_node_fs27 = __toESM(require("fs"), 1);
|
|
5787
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
5578
5788
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
5579
5789
|
|
|
5580
5790
|
// src/api.ts
|
|
5581
5791
|
init_cjs_shims();
|
|
5582
5792
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
5583
5793
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
5584
|
-
var
|
|
5794
|
+
var import_types26 = require("@neat.is/types");
|
|
5585
5795
|
|
|
5586
5796
|
// src/extend/index.ts
|
|
5587
5797
|
init_cjs_shims();
|
|
5588
|
-
var
|
|
5589
|
-
var
|
|
5798
|
+
var import_node_fs23 = require("fs");
|
|
5799
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5590
5800
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
5591
5801
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
5592
5802
|
|
|
5593
5803
|
// src/installers/package-manager.ts
|
|
5594
5804
|
init_cjs_shims();
|
|
5595
|
-
var
|
|
5596
|
-
var
|
|
5805
|
+
var import_node_fs22 = require("fs");
|
|
5806
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5597
5807
|
var import_node_child_process = require("child_process");
|
|
5598
5808
|
var LOCKFILE_PRIORITY = [
|
|
5599
5809
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -5608,29 +5818,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
5608
5818
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
5609
5819
|
async function exists2(p) {
|
|
5610
5820
|
try {
|
|
5611
|
-
await
|
|
5821
|
+
await import_node_fs22.promises.access(p);
|
|
5612
5822
|
return true;
|
|
5613
5823
|
} catch {
|
|
5614
5824
|
return false;
|
|
5615
5825
|
}
|
|
5616
5826
|
}
|
|
5617
5827
|
async function detectPackageManager(serviceDir) {
|
|
5618
|
-
let dir =
|
|
5828
|
+
let dir = import_node_path37.default.resolve(serviceDir);
|
|
5619
5829
|
const stops = /* @__PURE__ */ new Set();
|
|
5620
5830
|
for (let i = 0; i < 64; i++) {
|
|
5621
5831
|
if (stops.has(dir)) break;
|
|
5622
5832
|
stops.add(dir);
|
|
5623
5833
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
5624
|
-
const lockPath =
|
|
5834
|
+
const lockPath = import_node_path37.default.join(dir, candidate.lockfile);
|
|
5625
5835
|
if (await exists2(lockPath)) {
|
|
5626
5836
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
5627
5837
|
}
|
|
5628
5838
|
}
|
|
5629
|
-
const parent =
|
|
5839
|
+
const parent = import_node_path37.default.dirname(dir);
|
|
5630
5840
|
if (parent === dir) break;
|
|
5631
5841
|
dir = parent;
|
|
5632
5842
|
}
|
|
5633
|
-
return { pm: "npm", cwd:
|
|
5843
|
+
return { pm: "npm", cwd: import_node_path37.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
5634
5844
|
}
|
|
5635
5845
|
async function runPackageManagerInstall(cmd) {
|
|
5636
5846
|
return new Promise((resolve) => {
|
|
@@ -5672,30 +5882,30 @@ ${err.message}`
|
|
|
5672
5882
|
// src/extend/index.ts
|
|
5673
5883
|
async function fileExists2(p) {
|
|
5674
5884
|
try {
|
|
5675
|
-
await
|
|
5885
|
+
await import_node_fs23.promises.access(p);
|
|
5676
5886
|
return true;
|
|
5677
5887
|
} catch {
|
|
5678
5888
|
return false;
|
|
5679
5889
|
}
|
|
5680
5890
|
}
|
|
5681
5891
|
async function readPackageJson(scanPath) {
|
|
5682
|
-
const pkgPath =
|
|
5683
|
-
const raw = await
|
|
5892
|
+
const pkgPath = import_node_path38.default.join(scanPath, "package.json");
|
|
5893
|
+
const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
|
|
5684
5894
|
return JSON.parse(raw);
|
|
5685
5895
|
}
|
|
5686
5896
|
async function findHookFiles(scanPath) {
|
|
5687
|
-
const entries = await
|
|
5897
|
+
const entries = await import_node_fs23.promises.readdir(scanPath);
|
|
5688
5898
|
return entries.filter(
|
|
5689
5899
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
5690
5900
|
).sort();
|
|
5691
5901
|
}
|
|
5692
5902
|
function extendLogPath() {
|
|
5693
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
5903
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path38.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
5694
5904
|
}
|
|
5695
5905
|
async function appendExtendLog(entry2) {
|
|
5696
5906
|
const logPath = extendLogPath();
|
|
5697
|
-
await
|
|
5698
|
-
await
|
|
5907
|
+
await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(logPath), { recursive: true });
|
|
5908
|
+
await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
5699
5909
|
}
|
|
5700
5910
|
function splicedContent(fileContent, snippet2) {
|
|
5701
5911
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -5753,7 +5963,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
5753
5963
|
}
|
|
5754
5964
|
async function describeProjectInstrumentation(ctx) {
|
|
5755
5965
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5756
|
-
const envNeat = await fileExists2(
|
|
5966
|
+
const envNeat = await fileExists2(import_node_path38.default.join(ctx.scanPath, ".env.neat"));
|
|
5757
5967
|
const registryInstrPackages = new Set(
|
|
5758
5968
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5759
5969
|
);
|
|
@@ -5775,31 +5985,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
5775
5985
|
);
|
|
5776
5986
|
}
|
|
5777
5987
|
for (const file of hookFiles) {
|
|
5778
|
-
const content = await
|
|
5988
|
+
const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
|
|
5779
5989
|
if (content.includes(args.registration_snippet)) {
|
|
5780
5990
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5781
5991
|
}
|
|
5782
5992
|
}
|
|
5783
5993
|
const primaryFile = hookFiles[0];
|
|
5784
|
-
const primaryPath =
|
|
5994
|
+
const primaryPath = import_node_path38.default.join(ctx.scanPath, primaryFile);
|
|
5785
5995
|
const filesTouched = [];
|
|
5786
5996
|
const depsAdded = [];
|
|
5787
|
-
const pkgPath =
|
|
5997
|
+
const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
|
|
5788
5998
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
5789
5999
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5790
6000
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5791
|
-
await
|
|
6001
|
+
await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5792
6002
|
filesTouched.push("package.json");
|
|
5793
6003
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5794
6004
|
}
|
|
5795
|
-
const hookContent = await
|
|
6005
|
+
const hookContent = await import_node_fs23.promises.readFile(primaryPath, "utf8");
|
|
5796
6006
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5797
6007
|
if (!patched) {
|
|
5798
6008
|
throw new Error(
|
|
5799
6009
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5800
6010
|
);
|
|
5801
6011
|
}
|
|
5802
|
-
await
|
|
6012
|
+
await import_node_fs23.promises.writeFile(primaryPath, patched, "utf8");
|
|
5803
6013
|
filesTouched.push(primaryFile);
|
|
5804
6014
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5805
6015
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -5830,7 +6040,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
5830
6040
|
};
|
|
5831
6041
|
}
|
|
5832
6042
|
for (const file of hookFiles) {
|
|
5833
|
-
const content = await
|
|
6043
|
+
const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
|
|
5834
6044
|
if (content.includes(args.registration_snippet)) {
|
|
5835
6045
|
return {
|
|
5836
6046
|
library: args.library,
|
|
@@ -5852,7 +6062,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
5852
6062
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5853
6063
|
filesTouched.push("package.json");
|
|
5854
6064
|
}
|
|
5855
|
-
const hookContent = await
|
|
6065
|
+
const hookContent = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5856
6066
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5857
6067
|
if (patched) {
|
|
5858
6068
|
filesTouched.push(primaryFile);
|
|
@@ -5867,28 +6077,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
5867
6077
|
if (!await fileExists2(logPath)) {
|
|
5868
6078
|
return { undone: false, message: "no apply found for library" };
|
|
5869
6079
|
}
|
|
5870
|
-
const raw = await
|
|
6080
|
+
const raw = await import_node_fs23.promises.readFile(logPath, "utf8");
|
|
5871
6081
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5872
6082
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5873
6083
|
if (!match) {
|
|
5874
6084
|
return { undone: false, message: "no apply found for library" };
|
|
5875
6085
|
}
|
|
5876
|
-
const pkgPath =
|
|
6086
|
+
const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
|
|
5877
6087
|
if (await fileExists2(pkgPath)) {
|
|
5878
6088
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
5879
6089
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5880
6090
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5881
6091
|
pkg.dependencies = rest;
|
|
5882
|
-
await
|
|
6092
|
+
await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5883
6093
|
}
|
|
5884
6094
|
}
|
|
5885
6095
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5886
6096
|
for (const file of hookFiles) {
|
|
5887
|
-
const filePath =
|
|
5888
|
-
const content = await
|
|
6097
|
+
const filePath = import_node_path38.default.join(ctx.scanPath, file);
|
|
6098
|
+
const content = await import_node_fs23.promises.readFile(filePath, "utf8");
|
|
5889
6099
|
if (content.includes(match.registration_snippet)) {
|
|
5890
6100
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5891
|
-
await
|
|
6101
|
+
await import_node_fs23.promises.writeFile(filePath, filtered, "utf8");
|
|
5892
6102
|
break;
|
|
5893
6103
|
}
|
|
5894
6104
|
}
|
|
@@ -5900,7 +6110,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
5900
6110
|
|
|
5901
6111
|
// src/diff.ts
|
|
5902
6112
|
init_cjs_shims();
|
|
5903
|
-
var
|
|
6113
|
+
var import_node_fs24 = require("fs");
|
|
5904
6114
|
async function loadSnapshotForDiff(target) {
|
|
5905
6115
|
if (/^https?:\/\//i.test(target)) {
|
|
5906
6116
|
const res = await fetch(target);
|
|
@@ -5909,7 +6119,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
5909
6119
|
}
|
|
5910
6120
|
return await res.json();
|
|
5911
6121
|
}
|
|
5912
|
-
const raw = await
|
|
6122
|
+
const raw = await import_node_fs24.promises.readFile(target, "utf8");
|
|
5913
6123
|
return JSON.parse(raw);
|
|
5914
6124
|
}
|
|
5915
6125
|
function indexEntries(entries) {
|
|
@@ -5977,23 +6187,23 @@ function canonicalJson(value) {
|
|
|
5977
6187
|
|
|
5978
6188
|
// src/projects.ts
|
|
5979
6189
|
init_cjs_shims();
|
|
5980
|
-
var
|
|
6190
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
5981
6191
|
function pathsForProject(project, baseDir) {
|
|
5982
6192
|
if (project === DEFAULT_PROJECT) {
|
|
5983
6193
|
return {
|
|
5984
|
-
snapshotPath:
|
|
5985
|
-
errorsPath:
|
|
5986
|
-
staleEventsPath:
|
|
5987
|
-
embeddingsCachePath:
|
|
5988
|
-
policyViolationsPath:
|
|
6194
|
+
snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
|
|
6195
|
+
errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
|
|
6196
|
+
staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
|
|
6197
|
+
embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
|
|
6198
|
+
policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
|
|
5989
6199
|
};
|
|
5990
6200
|
}
|
|
5991
6201
|
return {
|
|
5992
|
-
snapshotPath:
|
|
5993
|
-
errorsPath:
|
|
5994
|
-
staleEventsPath:
|
|
5995
|
-
embeddingsCachePath:
|
|
5996
|
-
policyViolationsPath:
|
|
6202
|
+
snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
|
|
6203
|
+
errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
|
|
6204
|
+
staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
6205
|
+
embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
|
|
6206
|
+
policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5997
6207
|
};
|
|
5998
6208
|
}
|
|
5999
6209
|
var Projects = class {
|
|
@@ -6029,25 +6239,25 @@ var Projects = class {
|
|
|
6029
6239
|
|
|
6030
6240
|
// src/registry.ts
|
|
6031
6241
|
init_cjs_shims();
|
|
6032
|
-
var
|
|
6242
|
+
var import_node_fs25 = require("fs");
|
|
6033
6243
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
6034
|
-
var
|
|
6035
|
-
var
|
|
6244
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
6245
|
+
var import_types25 = require("@neat.is/types");
|
|
6036
6246
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
6037
6247
|
var LOCK_RETRY_MS = 50;
|
|
6038
6248
|
function neatHome() {
|
|
6039
6249
|
const override = process.env.NEAT_HOME;
|
|
6040
|
-
if (override && override.length > 0) return
|
|
6041
|
-
return
|
|
6250
|
+
if (override && override.length > 0) return import_node_path40.default.resolve(override);
|
|
6251
|
+
return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
|
|
6042
6252
|
}
|
|
6043
6253
|
function registryPath() {
|
|
6044
|
-
return
|
|
6254
|
+
return import_node_path40.default.join(neatHome(), "projects.json");
|
|
6045
6255
|
}
|
|
6046
6256
|
function registryLockPath() {
|
|
6047
|
-
return
|
|
6257
|
+
return import_node_path40.default.join(neatHome(), "projects.json.lock");
|
|
6048
6258
|
}
|
|
6049
6259
|
function daemonPidPath() {
|
|
6050
|
-
return
|
|
6260
|
+
return import_node_path40.default.join(neatHome(), "neatd.pid");
|
|
6051
6261
|
}
|
|
6052
6262
|
function isPidAliveDefault(pid) {
|
|
6053
6263
|
try {
|
|
@@ -6059,7 +6269,7 @@ function isPidAliveDefault(pid) {
|
|
|
6059
6269
|
}
|
|
6060
6270
|
async function readPidFile(file) {
|
|
6061
6271
|
try {
|
|
6062
|
-
const raw = await
|
|
6272
|
+
const raw = await import_node_fs25.promises.readFile(file, "utf8");
|
|
6063
6273
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
6064
6274
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
6065
6275
|
} catch {
|
|
@@ -6107,32 +6317,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6107
6317
|
}
|
|
6108
6318
|
}
|
|
6109
6319
|
async function normalizeProjectPath(input) {
|
|
6110
|
-
const resolved =
|
|
6320
|
+
const resolved = import_node_path40.default.resolve(input);
|
|
6111
6321
|
try {
|
|
6112
|
-
return await
|
|
6322
|
+
return await import_node_fs25.promises.realpath(resolved);
|
|
6113
6323
|
} catch {
|
|
6114
6324
|
return resolved;
|
|
6115
6325
|
}
|
|
6116
6326
|
}
|
|
6117
6327
|
async function writeAtomically(target, contents) {
|
|
6118
|
-
await
|
|
6328
|
+
await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
|
|
6119
6329
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
6120
|
-
const fd = await
|
|
6330
|
+
const fd = await import_node_fs25.promises.open(tmp, "w");
|
|
6121
6331
|
try {
|
|
6122
6332
|
await fd.writeFile(contents, "utf8");
|
|
6123
6333
|
await fd.sync();
|
|
6124
6334
|
} finally {
|
|
6125
6335
|
await fd.close();
|
|
6126
6336
|
}
|
|
6127
|
-
await
|
|
6337
|
+
await import_node_fs25.promises.rename(tmp, target);
|
|
6128
6338
|
}
|
|
6129
6339
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
6130
6340
|
const deadline = Date.now() + timeoutMs;
|
|
6131
|
-
await
|
|
6341
|
+
await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
|
|
6132
6342
|
let probedHolder = false;
|
|
6133
6343
|
while (true) {
|
|
6134
6344
|
try {
|
|
6135
|
-
const fd = await
|
|
6345
|
+
const fd = await import_node_fs25.promises.open(lockPath, "wx");
|
|
6136
6346
|
try {
|
|
6137
6347
|
await fd.writeFile(`${process.pid}
|
|
6138
6348
|
`, "utf8");
|
|
@@ -6157,7 +6367,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
6157
6367
|
}
|
|
6158
6368
|
}
|
|
6159
6369
|
async function releaseLock(lockPath) {
|
|
6160
|
-
await
|
|
6370
|
+
await import_node_fs25.promises.unlink(lockPath).catch(() => {
|
|
6161
6371
|
});
|
|
6162
6372
|
}
|
|
6163
6373
|
async function withLock(fn) {
|
|
@@ -6173,7 +6383,7 @@ async function readRegistry() {
|
|
|
6173
6383
|
const file = registryPath();
|
|
6174
6384
|
let raw;
|
|
6175
6385
|
try {
|
|
6176
|
-
raw = await
|
|
6386
|
+
raw = await import_node_fs25.promises.readFile(file, "utf8");
|
|
6177
6387
|
} catch (err) {
|
|
6178
6388
|
if (err.code === "ENOENT") {
|
|
6179
6389
|
return { version: 1, projects: [] };
|
|
@@ -6181,10 +6391,10 @@ async function readRegistry() {
|
|
|
6181
6391
|
throw err;
|
|
6182
6392
|
}
|
|
6183
6393
|
const parsed = JSON.parse(raw);
|
|
6184
|
-
return
|
|
6394
|
+
return import_types25.RegistryFileSchema.parse(parsed);
|
|
6185
6395
|
}
|
|
6186
6396
|
async function writeRegistry(reg) {
|
|
6187
|
-
const validated =
|
|
6397
|
+
const validated = import_types25.RegistryFileSchema.parse(reg);
|
|
6188
6398
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
6189
6399
|
}
|
|
6190
6400
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -6255,6 +6465,58 @@ async function removeProject(name) {
|
|
|
6255
6465
|
return removed;
|
|
6256
6466
|
});
|
|
6257
6467
|
}
|
|
6468
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
6469
|
+
var DEFAULT_PRUNE_TTL_MS = 7 * DAY_MS2;
|
|
6470
|
+
function pruneTtlMs() {
|
|
6471
|
+
const raw = process.env.NEAT_REGISTRY_PRUNE_TTL_MS;
|
|
6472
|
+
if (!raw) return DEFAULT_PRUNE_TTL_MS;
|
|
6473
|
+
const n = Number.parseInt(raw, 10);
|
|
6474
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
6475
|
+
console.warn(
|
|
6476
|
+
`[neat] NEAT_REGISTRY_PRUNE_TTL_MS could not be parsed (${raw}); using default ${DEFAULT_PRUNE_TTL_MS}ms`
|
|
6477
|
+
);
|
|
6478
|
+
return DEFAULT_PRUNE_TTL_MS;
|
|
6479
|
+
}
|
|
6480
|
+
async function statPathStatus(p) {
|
|
6481
|
+
try {
|
|
6482
|
+
const stat = await import_node_fs25.promises.stat(p);
|
|
6483
|
+
return stat.isDirectory() ? "present" : "unknown";
|
|
6484
|
+
} catch (err) {
|
|
6485
|
+
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
async function pruneRegistry(opts = {}) {
|
|
6489
|
+
const ttlMs = opts.ttlMs ?? pruneTtlMs();
|
|
6490
|
+
const statPath = opts.statPath ?? statPathStatus;
|
|
6491
|
+
const now = opts.now ?? Date.now;
|
|
6492
|
+
return withLock(async () => {
|
|
6493
|
+
const reg = await readRegistry();
|
|
6494
|
+
const removed = [];
|
|
6495
|
+
const kept = [];
|
|
6496
|
+
for (const entry2 of reg.projects) {
|
|
6497
|
+
const status2 = await statPath(entry2.path);
|
|
6498
|
+
if (status2 !== "gone") {
|
|
6499
|
+
kept.push(entry2);
|
|
6500
|
+
continue;
|
|
6501
|
+
}
|
|
6502
|
+
if (ttlMs <= 0) {
|
|
6503
|
+
removed.push(entry2);
|
|
6504
|
+
continue;
|
|
6505
|
+
}
|
|
6506
|
+
const lastSeen = Date.parse(entry2.lastSeenAt ?? entry2.registeredAt);
|
|
6507
|
+
const age = now() - (Number.isFinite(lastSeen) ? lastSeen : 0);
|
|
6508
|
+
if (age > ttlMs) {
|
|
6509
|
+
removed.push(entry2);
|
|
6510
|
+
} else {
|
|
6511
|
+
kept.push(entry2);
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
if (removed.length === 0) return [];
|
|
6515
|
+
reg.projects = kept;
|
|
6516
|
+
await writeRegistry(reg);
|
|
6517
|
+
return removed;
|
|
6518
|
+
});
|
|
6519
|
+
}
|
|
6258
6520
|
|
|
6259
6521
|
// src/streaming.ts
|
|
6260
6522
|
init_cjs_shims();
|
|
@@ -6268,6 +6530,7 @@ function handleSse(req, reply, opts) {
|
|
|
6268
6530
|
reply.raw.setHeader("Connection", "keep-alive");
|
|
6269
6531
|
reply.raw.setHeader("X-Accel-Buffering", "no");
|
|
6270
6532
|
reply.raw.flushHeaders?.();
|
|
6533
|
+
reply.raw.write(":open\n\n");
|
|
6271
6534
|
let pending = 0;
|
|
6272
6535
|
let dropped = false;
|
|
6273
6536
|
const closeConnection = () => {
|
|
@@ -6447,11 +6710,11 @@ function registerRoutes(scope, ctx) {
|
|
|
6447
6710
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6448
6711
|
const parsed = [];
|
|
6449
6712
|
for (const c of candidates) {
|
|
6450
|
-
const r =
|
|
6713
|
+
const r = import_types26.DivergenceTypeSchema.safeParse(c);
|
|
6451
6714
|
if (!r.success) {
|
|
6452
6715
|
return reply.code(400).send({
|
|
6453
6716
|
error: `unknown divergence type "${c}"`,
|
|
6454
|
-
allowed:
|
|
6717
|
+
allowed: import_types26.DivergenceTypeSchema.options
|
|
6455
6718
|
});
|
|
6456
6719
|
}
|
|
6457
6720
|
parsed.push(r.data);
|
|
@@ -6664,7 +6927,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6664
6927
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
6665
6928
|
let violations = await log.readAll();
|
|
6666
6929
|
if (req.query.severity) {
|
|
6667
|
-
const sev =
|
|
6930
|
+
const sev = import_types26.PolicySeveritySchema.safeParse(req.query.severity);
|
|
6668
6931
|
if (!sev.success) {
|
|
6669
6932
|
return reply.code(400).send({
|
|
6670
6933
|
error: "invalid severity",
|
|
@@ -6681,7 +6944,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6681
6944
|
scope.post("/policies/check", async (req, reply) => {
|
|
6682
6945
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6683
6946
|
if (!proj) return;
|
|
6684
|
-
const parsed =
|
|
6947
|
+
const parsed = import_types26.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
6685
6948
|
if (!parsed.success) {
|
|
6686
6949
|
return reply.code(400).send({
|
|
6687
6950
|
error: "invalid /policies/check body",
|
|
@@ -6924,8 +7187,8 @@ init_otel_grpc();
|
|
|
6924
7187
|
|
|
6925
7188
|
// src/search.ts
|
|
6926
7189
|
init_cjs_shims();
|
|
6927
|
-
var
|
|
6928
|
-
var
|
|
7190
|
+
var import_node_fs26 = require("fs");
|
|
7191
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
6929
7192
|
var import_node_crypto3 = require("crypto");
|
|
6930
7193
|
var DEFAULT_LIMIT = 10;
|
|
6931
7194
|
var NOMIC_DIM = 768;
|
|
@@ -7055,7 +7318,7 @@ async function pickEmbedder() {
|
|
|
7055
7318
|
}
|
|
7056
7319
|
async function readCache(cachePath) {
|
|
7057
7320
|
try {
|
|
7058
|
-
const raw = await
|
|
7321
|
+
const raw = await import_node_fs26.promises.readFile(cachePath, "utf8");
|
|
7059
7322
|
const parsed = JSON.parse(raw);
|
|
7060
7323
|
if (parsed.version !== 1) return null;
|
|
7061
7324
|
return parsed;
|
|
@@ -7064,8 +7327,8 @@ async function readCache(cachePath) {
|
|
|
7064
7327
|
}
|
|
7065
7328
|
}
|
|
7066
7329
|
async function writeCache(cachePath, cache) {
|
|
7067
|
-
await
|
|
7068
|
-
await
|
|
7330
|
+
await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(cachePath), { recursive: true });
|
|
7331
|
+
await import_node_fs26.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
7069
7332
|
}
|
|
7070
7333
|
var VectorIndex = class {
|
|
7071
7334
|
constructor(embedder, cachePath) {
|
|
@@ -7221,8 +7484,8 @@ var ALL_PHASES = [
|
|
|
7221
7484
|
];
|
|
7222
7485
|
function classifyChange(relPath) {
|
|
7223
7486
|
const phases = /* @__PURE__ */ new Set();
|
|
7224
|
-
const base =
|
|
7225
|
-
const segments = relPath.split(
|
|
7487
|
+
const base = import_node_path44.default.basename(relPath).toLowerCase();
|
|
7488
|
+
const segments = relPath.split(import_node_path44.default.sep).map((s) => s.toLowerCase());
|
|
7226
7489
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
7227
7490
|
phases.add("services");
|
|
7228
7491
|
phases.add("aliases");
|
|
@@ -7336,16 +7599,16 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
7336
7599
|
if (count >= limit) return;
|
|
7337
7600
|
let entries;
|
|
7338
7601
|
try {
|
|
7339
|
-
entries =
|
|
7602
|
+
entries = import_node_fs27.default.readdirSync(dir, { withFileTypes: true });
|
|
7340
7603
|
} catch {
|
|
7341
7604
|
return;
|
|
7342
7605
|
}
|
|
7343
7606
|
for (const e of entries) {
|
|
7344
7607
|
if (count >= limit) return;
|
|
7345
7608
|
if (!e.isDirectory()) continue;
|
|
7346
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
7609
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path44.default.join(dir, e.name) + import_node_path44.default.sep))) continue;
|
|
7347
7610
|
count++;
|
|
7348
|
-
if (depth < 2) visit(
|
|
7611
|
+
if (depth < 2) visit(import_node_path44.default.join(dir, e.name), depth + 1);
|
|
7349
7612
|
}
|
|
7350
7613
|
};
|
|
7351
7614
|
visit(scanPath, 0);
|
|
@@ -7363,8 +7626,8 @@ async function startWatch(graph, opts) {
|
|
|
7363
7626
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
7364
7627
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
7365
7628
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
7366
|
-
const policyFilePath =
|
|
7367
|
-
const policyViolationsPath =
|
|
7629
|
+
const policyFilePath = import_node_path44.default.join(opts.scanPath, "policy.json");
|
|
7630
|
+
const policyViolationsPath = import_node_path44.default.join(import_node_path44.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
7368
7631
|
let policies = [];
|
|
7369
7632
|
try {
|
|
7370
7633
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -7415,7 +7678,7 @@ async function startWatch(graph, opts) {
|
|
|
7415
7678
|
assertBindAuthority(host, auth.authToken);
|
|
7416
7679
|
const port = opts.port ?? 8080;
|
|
7417
7680
|
const otelPort = opts.otelPort ?? 4318;
|
|
7418
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
7681
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path44.default.join(import_node_path44.default.dirname(opts.outPath), "embeddings.json");
|
|
7419
7682
|
let searchIndex;
|
|
7420
7683
|
try {
|
|
7421
7684
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -7433,7 +7696,7 @@ async function startWatch(graph, opts) {
|
|
|
7433
7696
|
// Paths are derived from the explicit options the watch caller passes
|
|
7434
7697
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
7435
7698
|
// fields so the registry shape is complete.
|
|
7436
|
-
...pathsForProject(projectName,
|
|
7699
|
+
...pathsForProject(projectName, import_node_path44.default.dirname(opts.outPath)),
|
|
7437
7700
|
snapshotPath: opts.outPath,
|
|
7438
7701
|
errorsPath: opts.errorsPath,
|
|
7439
7702
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -7520,9 +7783,9 @@ async function startWatch(graph, opts) {
|
|
|
7520
7783
|
};
|
|
7521
7784
|
const onPath = (absPath) => {
|
|
7522
7785
|
if (shouldIgnore(absPath)) return;
|
|
7523
|
-
const rel =
|
|
7786
|
+
const rel = import_node_path44.default.relative(opts.scanPath, absPath);
|
|
7524
7787
|
if (!rel || rel.startsWith("..")) return;
|
|
7525
|
-
pendingPaths.add(rel.split(
|
|
7788
|
+
pendingPaths.add(rel.split(import_node_path44.default.sep).join("/"));
|
|
7526
7789
|
const phases = classifyChange(rel);
|
|
7527
7790
|
if (phases.size === 0) {
|
|
7528
7791
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -7576,8 +7839,8 @@ async function startWatch(graph, opts) {
|
|
|
7576
7839
|
|
|
7577
7840
|
// src/deploy/detect.ts
|
|
7578
7841
|
init_cjs_shims();
|
|
7579
|
-
var
|
|
7580
|
-
var
|
|
7842
|
+
var import_node_fs28 = require("fs");
|
|
7843
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
7581
7844
|
var import_node_child_process2 = require("child_process");
|
|
7582
7845
|
var import_node_crypto4 = require("crypto");
|
|
7583
7846
|
function generateToken() {
|
|
@@ -7677,21 +7940,21 @@ async function runDeploy(opts = {}) {
|
|
|
7677
7940
|
const token = generateToken();
|
|
7678
7941
|
switch (substrate) {
|
|
7679
7942
|
case "docker-compose": {
|
|
7680
|
-
const artifactPath =
|
|
7943
|
+
const artifactPath = import_node_path45.default.join(cwd, "docker-compose.neat.yml");
|
|
7681
7944
|
const contents = emitDockerCompose(cwd);
|
|
7682
|
-
await
|
|
7945
|
+
await import_node_fs28.promises.writeFile(artifactPath, contents, "utf8");
|
|
7683
7946
|
return {
|
|
7684
7947
|
substrate,
|
|
7685
7948
|
artifactPath,
|
|
7686
7949
|
token,
|
|
7687
7950
|
contents,
|
|
7688
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
7951
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path45.default.basename(artifactPath)} up -d`
|
|
7689
7952
|
};
|
|
7690
7953
|
}
|
|
7691
7954
|
case "systemd": {
|
|
7692
|
-
const artifactPath =
|
|
7955
|
+
const artifactPath = import_node_path45.default.join(cwd, "neat.service");
|
|
7693
7956
|
const contents = emitSystemdUnit(cwd);
|
|
7694
|
-
await
|
|
7957
|
+
await import_node_fs28.promises.writeFile(artifactPath, contents, "utf8");
|
|
7695
7958
|
return {
|
|
7696
7959
|
substrate,
|
|
7697
7960
|
artifactPath,
|
|
@@ -7724,8 +7987,8 @@ init_cjs_shims();
|
|
|
7724
7987
|
|
|
7725
7988
|
// src/installers/javascript.ts
|
|
7726
7989
|
init_cjs_shims();
|
|
7727
|
-
var
|
|
7728
|
-
var
|
|
7990
|
+
var import_node_fs29 = require("fs");
|
|
7991
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
7729
7992
|
var import_semver2 = __toESM(require("semver"), 1);
|
|
7730
7993
|
|
|
7731
7994
|
// src/installers/templates.ts
|
|
@@ -8272,15 +8535,15 @@ var OTEL_ENV = {
|
|
|
8272
8535
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
8273
8536
|
};
|
|
8274
8537
|
function serviceNodeName(pkg, serviceDir) {
|
|
8275
|
-
return pkg.name ??
|
|
8538
|
+
return pkg.name ?? import_node_path46.default.basename(serviceDir);
|
|
8276
8539
|
}
|
|
8277
8540
|
function projectToken(pkg, serviceDir, project) {
|
|
8278
8541
|
if (project && project.length > 0) return project;
|
|
8279
|
-
return pkg.name ??
|
|
8542
|
+
return pkg.name ?? import_node_path46.default.basename(serviceDir);
|
|
8280
8543
|
}
|
|
8281
8544
|
async function readJsonFile(p) {
|
|
8282
8545
|
try {
|
|
8283
|
-
const raw = await
|
|
8546
|
+
const raw = await import_node_fs29.promises.readFile(p, "utf8");
|
|
8284
8547
|
return JSON.parse(raw);
|
|
8285
8548
|
} catch {
|
|
8286
8549
|
return null;
|
|
@@ -8289,16 +8552,16 @@ async function readJsonFile(p) {
|
|
|
8289
8552
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
8290
8553
|
const deps = allDeps(pkg);
|
|
8291
8554
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
8292
|
-
const appJson = await readJsonFile(
|
|
8555
|
+
const appJson = await readJsonFile(import_node_path46.default.join(pkgRoot, "app.json"));
|
|
8293
8556
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
8294
8557
|
return "react-native";
|
|
8295
8558
|
}
|
|
8296
|
-
if (await exists3(
|
|
8559
|
+
if (await exists3(import_node_path46.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path46.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path46.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
8297
8560
|
return "browser-bundle";
|
|
8298
8561
|
}
|
|
8299
|
-
if (await exists3(
|
|
8300
|
-
if (await exists3(
|
|
8301
|
-
if (await exists3(
|
|
8562
|
+
if (await exists3(import_node_path46.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
8563
|
+
if (await exists3(import_node_path46.default.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
8564
|
+
if (await exists3(import_node_path46.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path46.default.join(pkgRoot, "deno.lock"))) {
|
|
8302
8565
|
return "deno";
|
|
8303
8566
|
}
|
|
8304
8567
|
const engines = pkg.engines ?? {};
|
|
@@ -8307,7 +8570,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
8307
8570
|
}
|
|
8308
8571
|
async function readPackageJson2(serviceDir) {
|
|
8309
8572
|
try {
|
|
8310
|
-
const raw = await
|
|
8573
|
+
const raw = await import_node_fs29.promises.readFile(import_node_path46.default.join(serviceDir, "package.json"), "utf8");
|
|
8311
8574
|
return JSON.parse(raw);
|
|
8312
8575
|
} catch {
|
|
8313
8576
|
return null;
|
|
@@ -8315,7 +8578,7 @@ async function readPackageJson2(serviceDir) {
|
|
|
8315
8578
|
}
|
|
8316
8579
|
async function exists3(p) {
|
|
8317
8580
|
try {
|
|
8318
|
-
await
|
|
8581
|
+
await import_node_fs29.promises.stat(p);
|
|
8319
8582
|
return true;
|
|
8320
8583
|
} catch {
|
|
8321
8584
|
return false;
|
|
@@ -8323,7 +8586,7 @@ async function exists3(p) {
|
|
|
8323
8586
|
}
|
|
8324
8587
|
async function readFileMaybe(p) {
|
|
8325
8588
|
try {
|
|
8326
|
-
return await
|
|
8589
|
+
return await import_node_fs29.promises.readFile(p, "utf8");
|
|
8327
8590
|
} catch {
|
|
8328
8591
|
return null;
|
|
8329
8592
|
}
|
|
@@ -8351,7 +8614,7 @@ function needsVersionUpgrade(installed, expected) {
|
|
|
8351
8614
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
8352
8615
|
async function findNextConfig(serviceDir) {
|
|
8353
8616
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
8354
|
-
const candidate =
|
|
8617
|
+
const candidate = import_node_path46.default.join(serviceDir, name);
|
|
8355
8618
|
if (await exists3(candidate)) return candidate;
|
|
8356
8619
|
}
|
|
8357
8620
|
return null;
|
|
@@ -8378,7 +8641,7 @@ function hasRemixDependency(pkg) {
|
|
|
8378
8641
|
}
|
|
8379
8642
|
async function findRemixEntry(serviceDir) {
|
|
8380
8643
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
8381
|
-
const candidate =
|
|
8644
|
+
const candidate = import_node_path46.default.join(serviceDir, rel);
|
|
8382
8645
|
if (await exists3(candidate)) return candidate;
|
|
8383
8646
|
}
|
|
8384
8647
|
return null;
|
|
@@ -8390,14 +8653,14 @@ function hasSvelteKitDependency(pkg) {
|
|
|
8390
8653
|
}
|
|
8391
8654
|
async function findSvelteKitHooks(serviceDir) {
|
|
8392
8655
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
8393
|
-
const candidate =
|
|
8656
|
+
const candidate = import_node_path46.default.join(serviceDir, rel);
|
|
8394
8657
|
if (await exists3(candidate)) return candidate;
|
|
8395
8658
|
}
|
|
8396
8659
|
return null;
|
|
8397
8660
|
}
|
|
8398
8661
|
async function findSvelteKitConfig(serviceDir) {
|
|
8399
8662
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
8400
|
-
const candidate =
|
|
8663
|
+
const candidate = import_node_path46.default.join(serviceDir, rel);
|
|
8401
8664
|
if (await exists3(candidate)) return candidate;
|
|
8402
8665
|
}
|
|
8403
8666
|
return null;
|
|
@@ -8408,7 +8671,7 @@ function hasNuxtDependency(pkg) {
|
|
|
8408
8671
|
}
|
|
8409
8672
|
async function findNuxtConfig(serviceDir) {
|
|
8410
8673
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
8411
|
-
const candidate =
|
|
8674
|
+
const candidate = import_node_path46.default.join(serviceDir, name);
|
|
8412
8675
|
if (await exists3(candidate)) return candidate;
|
|
8413
8676
|
}
|
|
8414
8677
|
return null;
|
|
@@ -8419,7 +8682,7 @@ function hasAstroDependency(pkg) {
|
|
|
8419
8682
|
}
|
|
8420
8683
|
async function findAstroConfig(serviceDir) {
|
|
8421
8684
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
8422
|
-
const candidate =
|
|
8685
|
+
const candidate = import_node_path46.default.join(serviceDir, name);
|
|
8423
8686
|
if (await exists3(candidate)) return candidate;
|
|
8424
8687
|
}
|
|
8425
8688
|
return null;
|
|
@@ -8433,7 +8696,7 @@ function parseNextMajor(range) {
|
|
|
8433
8696
|
return Number.isFinite(n) ? n : null;
|
|
8434
8697
|
}
|
|
8435
8698
|
async function isTypeScriptProject(serviceDir) {
|
|
8436
|
-
return exists3(
|
|
8699
|
+
return exists3(import_node_path46.default.join(serviceDir, "tsconfig.json"));
|
|
8437
8700
|
}
|
|
8438
8701
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
8439
8702
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -8479,7 +8742,7 @@ function entryFromScript(script) {
|
|
|
8479
8742
|
}
|
|
8480
8743
|
async function resolveEntry(serviceDir, pkg) {
|
|
8481
8744
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
8482
|
-
const candidate =
|
|
8745
|
+
const candidate = import_node_path46.default.resolve(serviceDir, pkg.main);
|
|
8483
8746
|
if (await exists3(candidate)) return candidate;
|
|
8484
8747
|
}
|
|
8485
8748
|
if (pkg.bin) {
|
|
@@ -8493,36 +8756,36 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
8493
8756
|
if (typeof first === "string") binEntry = first;
|
|
8494
8757
|
}
|
|
8495
8758
|
if (binEntry) {
|
|
8496
|
-
const candidate =
|
|
8759
|
+
const candidate = import_node_path46.default.resolve(serviceDir, binEntry);
|
|
8497
8760
|
if (await exists3(candidate)) return candidate;
|
|
8498
8761
|
}
|
|
8499
8762
|
}
|
|
8500
8763
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
8501
8764
|
if (startEntry) {
|
|
8502
|
-
const candidate =
|
|
8765
|
+
const candidate = import_node_path46.default.resolve(serviceDir, startEntry);
|
|
8503
8766
|
if (await exists3(candidate)) return candidate;
|
|
8504
8767
|
}
|
|
8505
8768
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
8506
8769
|
if (devEntry) {
|
|
8507
|
-
const candidate =
|
|
8770
|
+
const candidate = import_node_path46.default.resolve(serviceDir, devEntry);
|
|
8508
8771
|
if (await exists3(candidate)) return candidate;
|
|
8509
8772
|
}
|
|
8510
8773
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
8511
|
-
const candidate =
|
|
8774
|
+
const candidate = import_node_path46.default.join(serviceDir, rel);
|
|
8512
8775
|
if (await exists3(candidate)) return candidate;
|
|
8513
8776
|
}
|
|
8514
8777
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
8515
|
-
const candidate =
|
|
8778
|
+
const candidate = import_node_path46.default.join(serviceDir, rel);
|
|
8516
8779
|
if (await exists3(candidate)) return candidate;
|
|
8517
8780
|
}
|
|
8518
8781
|
for (const name of INDEX_CANDIDATES) {
|
|
8519
|
-
const candidate =
|
|
8782
|
+
const candidate = import_node_path46.default.join(serviceDir, name);
|
|
8520
8783
|
if (await exists3(candidate)) return candidate;
|
|
8521
8784
|
}
|
|
8522
8785
|
return null;
|
|
8523
8786
|
}
|
|
8524
8787
|
function dispatchEntry(entryFile, pkg) {
|
|
8525
|
-
const ext =
|
|
8788
|
+
const ext = import_node_path46.default.extname(entryFile).toLowerCase();
|
|
8526
8789
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
8527
8790
|
if (ext === ".mjs") return "esm";
|
|
8528
8791
|
if (ext === ".cjs") return "cjs";
|
|
@@ -8539,9 +8802,9 @@ function otelInitContents(flavor) {
|
|
|
8539
8802
|
return OTEL_INIT_CJS;
|
|
8540
8803
|
}
|
|
8541
8804
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
8542
|
-
let rel =
|
|
8805
|
+
let rel = import_node_path46.default.relative(import_node_path46.default.dirname(entryFile), otelInitFile);
|
|
8543
8806
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
8544
|
-
rel = rel.split(
|
|
8807
|
+
rel = rel.split(import_node_path46.default.sep).join("/");
|
|
8545
8808
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
8546
8809
|
if (flavor === "esm") return `import '${rel}'`;
|
|
8547
8810
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -8554,23 +8817,23 @@ function lineIsOtelInjection(line) {
|
|
|
8554
8817
|
}
|
|
8555
8818
|
async function detectsSrcLayout(serviceDir) {
|
|
8556
8819
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
8557
|
-
exists3(
|
|
8558
|
-
exists3(
|
|
8559
|
-
exists3(
|
|
8560
|
-
exists3(
|
|
8820
|
+
exists3(import_node_path46.default.join(serviceDir, "src", "app")),
|
|
8821
|
+
exists3(import_node_path46.default.join(serviceDir, "src", "pages")),
|
|
8822
|
+
exists3(import_node_path46.default.join(serviceDir, "app")),
|
|
8823
|
+
exists3(import_node_path46.default.join(serviceDir, "pages"))
|
|
8561
8824
|
]);
|
|
8562
8825
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
8563
8826
|
}
|
|
8564
8827
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
8565
8828
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
8566
8829
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
8567
|
-
const baseDir = srcLayout ?
|
|
8568
|
-
const instrumentationFile =
|
|
8569
|
-
const instrumentationNodeFile =
|
|
8830
|
+
const baseDir = srcLayout ? import_node_path46.default.join(serviceDir, "src") : serviceDir;
|
|
8831
|
+
const instrumentationFile = import_node_path46.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
8832
|
+
const instrumentationNodeFile = import_node_path46.default.join(
|
|
8570
8833
|
baseDir,
|
|
8571
8834
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
8572
8835
|
);
|
|
8573
|
-
const envNeatFile =
|
|
8836
|
+
const envNeatFile = import_node_path46.default.join(baseDir, ".env.neat");
|
|
8574
8837
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
8575
8838
|
const dependencyEdits = [];
|
|
8576
8839
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -8627,7 +8890,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
8627
8890
|
const nextMajor = parseNextMajor(nextRange);
|
|
8628
8891
|
if (nextMajor !== null && nextMajor < 15) {
|
|
8629
8892
|
try {
|
|
8630
|
-
const raw = await
|
|
8893
|
+
const raw = await import_node_fs29.promises.readFile(nextConfigPath, "utf8");
|
|
8631
8894
|
if (!raw.includes("instrumentationHook")) {
|
|
8632
8895
|
nextConfigEdit = {
|
|
8633
8896
|
file: nextConfigPath,
|
|
@@ -8675,7 +8938,7 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
8675
8938
|
return edits;
|
|
8676
8939
|
}
|
|
8677
8940
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
8678
|
-
const envNeatFile =
|
|
8941
|
+
const envNeatFile = import_node_path46.default.join(serviceDir, ".env.neat");
|
|
8679
8942
|
if (!await exists3(envNeatFile)) {
|
|
8680
8943
|
generatedFiles.push({
|
|
8681
8944
|
file: envNeatFile,
|
|
@@ -8710,7 +8973,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
8710
8973
|
}
|
|
8711
8974
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
8712
8975
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
8713
|
-
const otelServerFile =
|
|
8976
|
+
const otelServerFile = import_node_path46.default.join(
|
|
8714
8977
|
serviceDir,
|
|
8715
8978
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
8716
8979
|
);
|
|
@@ -8731,7 +8994,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
8731
8994
|
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
8732
8995
|
const entrypointEdits = [];
|
|
8733
8996
|
try {
|
|
8734
|
-
const raw = await
|
|
8997
|
+
const raw = await import_node_fs29.promises.readFile(entryFile, "utf8");
|
|
8735
8998
|
if (!fileImportsOtelHook(raw, ["./otel.server"])) {
|
|
8736
8999
|
const lines = raw.split(/\r?\n/);
|
|
8737
9000
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -8767,11 +9030,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
8767
9030
|
}
|
|
8768
9031
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
8769
9032
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
8770
|
-
const otelInitFile =
|
|
9033
|
+
const otelInitFile = import_node_path46.default.join(
|
|
8771
9034
|
serviceDir,
|
|
8772
9035
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
8773
9036
|
);
|
|
8774
|
-
const resolvedHooksFile = hooksFile ??
|
|
9037
|
+
const resolvedHooksFile = hooksFile ?? import_node_path46.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
8775
9038
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
8776
9039
|
const generatedFiles = [];
|
|
8777
9040
|
const entrypointEdits = [];
|
|
@@ -8796,7 +9059,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
8796
9059
|
});
|
|
8797
9060
|
} else {
|
|
8798
9061
|
try {
|
|
8799
|
-
const raw = await
|
|
9062
|
+
const raw = await import_node_fs29.promises.readFile(hooksFile, "utf8");
|
|
8800
9063
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
8801
9064
|
const lines = raw.split(/\r?\n/);
|
|
8802
9065
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -8833,11 +9096,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
8833
9096
|
}
|
|
8834
9097
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
8835
9098
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
8836
|
-
const otelPluginFile =
|
|
9099
|
+
const otelPluginFile = import_node_path46.default.join(
|
|
8837
9100
|
serviceDir,
|
|
8838
9101
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
8839
9102
|
);
|
|
8840
|
-
const otelInitFile =
|
|
9103
|
+
const otelInitFile = import_node_path46.default.join(
|
|
8841
9104
|
serviceDir,
|
|
8842
9105
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
8843
9106
|
);
|
|
@@ -8888,19 +9151,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
8888
9151
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
8889
9152
|
async function findAstroMiddleware(serviceDir) {
|
|
8890
9153
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
8891
|
-
const candidate =
|
|
9154
|
+
const candidate = import_node_path46.default.join(serviceDir, rel);
|
|
8892
9155
|
if (await exists3(candidate)) return candidate;
|
|
8893
9156
|
}
|
|
8894
9157
|
return null;
|
|
8895
9158
|
}
|
|
8896
9159
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
8897
9160
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
8898
|
-
const otelInitFile =
|
|
9161
|
+
const otelInitFile = import_node_path46.default.join(
|
|
8899
9162
|
serviceDir,
|
|
8900
9163
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
8901
9164
|
);
|
|
8902
9165
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
8903
|
-
const middlewareFile = existingMiddleware ??
|
|
9166
|
+
const middlewareFile = existingMiddleware ?? import_node_path46.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
8904
9167
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
8905
9168
|
const generatedFiles = [];
|
|
8906
9169
|
const entrypointEdits = [];
|
|
@@ -8925,7 +9188,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
8925
9188
|
});
|
|
8926
9189
|
} else {
|
|
8927
9190
|
try {
|
|
8928
|
-
const raw = await
|
|
9191
|
+
const raw = await import_node_fs29.promises.readFile(existingMiddleware, "utf8");
|
|
8929
9192
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
8930
9193
|
const lines = raw.split(/\r?\n/);
|
|
8931
9194
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -8996,7 +9259,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
8996
9259
|
}
|
|
8997
9260
|
async function plan(serviceDir, opts) {
|
|
8998
9261
|
const pkg = await readPackageJson2(serviceDir);
|
|
8999
|
-
const manifestPath =
|
|
9262
|
+
const manifestPath = import_node_path46.default.join(serviceDir, "package.json");
|
|
9000
9263
|
const project = opts?.project;
|
|
9001
9264
|
const empty = {
|
|
9002
9265
|
language: "javascript",
|
|
@@ -9031,8 +9294,8 @@ async function plan(serviceDir, opts) {
|
|
|
9031
9294
|
return { ...empty, libOnly: true };
|
|
9032
9295
|
}
|
|
9033
9296
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
9034
|
-
const otelInitFile =
|
|
9035
|
-
const envNeatFile =
|
|
9297
|
+
const otelInitFile = import_node_path46.default.join(import_node_path46.default.dirname(entryFile), otelInitFilename(flavor));
|
|
9298
|
+
const envNeatFile = import_node_path46.default.join(serviceDir, ".env.neat");
|
|
9036
9299
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
9037
9300
|
const dependencyEdits = [];
|
|
9038
9301
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -9056,7 +9319,7 @@ async function plan(serviceDir, opts) {
|
|
|
9056
9319
|
}
|
|
9057
9320
|
const entrypointEdits = [];
|
|
9058
9321
|
try {
|
|
9059
|
-
const raw = await
|
|
9322
|
+
const raw = await import_node_fs29.promises.readFile(entryFile, "utf8");
|
|
9060
9323
|
const lines = raw.split(/\r?\n/);
|
|
9061
9324
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
9062
9325
|
if (!lineIsOtelInjection(firstReal)) {
|
|
@@ -9101,13 +9364,13 @@ async function plan(serviceDir, opts) {
|
|
|
9101
9364
|
};
|
|
9102
9365
|
}
|
|
9103
9366
|
function isAllowedWritePath(serviceDir, target) {
|
|
9104
|
-
const rel =
|
|
9367
|
+
const rel = import_node_path46.default.relative(serviceDir, target);
|
|
9105
9368
|
if (rel.startsWith("..")) return false;
|
|
9106
|
-
const base =
|
|
9369
|
+
const base = import_node_path46.default.basename(target);
|
|
9107
9370
|
if (base === "package.json") return true;
|
|
9108
9371
|
if (base === ".env.neat") return true;
|
|
9109
9372
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
9110
|
-
const relPosix = rel.split(
|
|
9373
|
+
const relPosix = rel.split(import_node_path46.default.sep).join("/");
|
|
9111
9374
|
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
9112
9375
|
if (relPosix === base) return true;
|
|
9113
9376
|
if (relPosix === `src/${base}`) return true;
|
|
@@ -9124,10 +9387,10 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
9124
9387
|
return false;
|
|
9125
9388
|
}
|
|
9126
9389
|
async function writeAtomic(file, contents) {
|
|
9127
|
-
await
|
|
9390
|
+
await import_node_fs29.promises.mkdir(import_node_path46.default.dirname(file), { recursive: true });
|
|
9128
9391
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
9129
|
-
await
|
|
9130
|
-
await
|
|
9392
|
+
await import_node_fs29.promises.writeFile(tmp, contents, "utf8");
|
|
9393
|
+
await import_node_fs29.promises.rename(tmp, file);
|
|
9131
9394
|
}
|
|
9132
9395
|
async function apply(installPlan) {
|
|
9133
9396
|
const { serviceDir } = installPlan;
|
|
@@ -9203,7 +9466,7 @@ async function apply(installPlan) {
|
|
|
9203
9466
|
for (const target of allTargets) {
|
|
9204
9467
|
if (await exists3(target)) {
|
|
9205
9468
|
try {
|
|
9206
|
-
originals.set(target, await
|
|
9469
|
+
originals.set(target, await import_node_fs29.promises.readFile(target, "utf8"));
|
|
9207
9470
|
} catch {
|
|
9208
9471
|
}
|
|
9209
9472
|
}
|
|
@@ -9286,14 +9549,14 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
9286
9549
|
const removed = [];
|
|
9287
9550
|
for (const [file, raw] of originals.entries()) {
|
|
9288
9551
|
try {
|
|
9289
|
-
await
|
|
9552
|
+
await import_node_fs29.promises.writeFile(file, raw, "utf8");
|
|
9290
9553
|
restored.push(file);
|
|
9291
9554
|
} catch {
|
|
9292
9555
|
}
|
|
9293
9556
|
}
|
|
9294
9557
|
for (const file of createdFiles) {
|
|
9295
9558
|
try {
|
|
9296
|
-
await
|
|
9559
|
+
await import_node_fs29.promises.unlink(file);
|
|
9297
9560
|
removed.push(file);
|
|
9298
9561
|
} catch {
|
|
9299
9562
|
}
|
|
@@ -9308,8 +9571,8 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
9308
9571
|
...removed.map((f) => `removed: ${f}`),
|
|
9309
9572
|
""
|
|
9310
9573
|
];
|
|
9311
|
-
const rollbackPath =
|
|
9312
|
-
await
|
|
9574
|
+
const rollbackPath = import_node_path46.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
9575
|
+
await import_node_fs29.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
9313
9576
|
}
|
|
9314
9577
|
function injectInstrumentationHook(raw) {
|
|
9315
9578
|
if (raw.includes("instrumentationHook")) return raw;
|
|
@@ -9338,8 +9601,8 @@ var javascriptInstaller = {
|
|
|
9338
9601
|
|
|
9339
9602
|
// src/installers/python.ts
|
|
9340
9603
|
init_cjs_shims();
|
|
9341
|
-
var
|
|
9342
|
-
var
|
|
9604
|
+
var import_node_fs30 = require("fs");
|
|
9605
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
9343
9606
|
var SDK_PACKAGES2 = [
|
|
9344
9607
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
9345
9608
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -9351,7 +9614,7 @@ var OTEL_ENV2 = {
|
|
|
9351
9614
|
};
|
|
9352
9615
|
async function exists4(p) {
|
|
9353
9616
|
try {
|
|
9354
|
-
await
|
|
9617
|
+
await import_node_fs30.promises.stat(p);
|
|
9355
9618
|
return true;
|
|
9356
9619
|
} catch {
|
|
9357
9620
|
return false;
|
|
@@ -9360,7 +9623,7 @@ async function exists4(p) {
|
|
|
9360
9623
|
async function detect2(serviceDir) {
|
|
9361
9624
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
9362
9625
|
for (const m of markers) {
|
|
9363
|
-
if (await exists4(
|
|
9626
|
+
if (await exists4(import_node_path47.default.join(serviceDir, m))) return true;
|
|
9364
9627
|
}
|
|
9365
9628
|
return false;
|
|
9366
9629
|
}
|
|
@@ -9370,9 +9633,9 @@ function reqPackageName(line) {
|
|
|
9370
9633
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
9371
9634
|
}
|
|
9372
9635
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
9373
|
-
const file =
|
|
9636
|
+
const file = import_node_path47.default.join(serviceDir, "requirements.txt");
|
|
9374
9637
|
if (!await exists4(file)) return null;
|
|
9375
|
-
const raw = await
|
|
9638
|
+
const raw = await import_node_fs30.promises.readFile(file, "utf8");
|
|
9376
9639
|
const presentNames = new Set(
|
|
9377
9640
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
9378
9641
|
);
|
|
@@ -9380,9 +9643,9 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
9380
9643
|
return { manifest: file, missing: [...missing] };
|
|
9381
9644
|
}
|
|
9382
9645
|
async function planProcfileEdits(serviceDir) {
|
|
9383
|
-
const procfile =
|
|
9646
|
+
const procfile = import_node_path47.default.join(serviceDir, "Procfile");
|
|
9384
9647
|
if (!await exists4(procfile)) return [];
|
|
9385
|
-
const raw = await
|
|
9648
|
+
const raw = await import_node_fs30.promises.readFile(procfile, "utf8");
|
|
9386
9649
|
const edits = [];
|
|
9387
9650
|
for (const line of raw.split(/\r?\n/)) {
|
|
9388
9651
|
if (line.length === 0) continue;
|
|
@@ -9434,8 +9697,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
9434
9697
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
9435
9698
|
`;
|
|
9436
9699
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
9437
|
-
await
|
|
9438
|
-
await
|
|
9700
|
+
await import_node_fs30.promises.writeFile(tmp, next, "utf8");
|
|
9701
|
+
await import_node_fs30.promises.rename(tmp, manifest);
|
|
9439
9702
|
}
|
|
9440
9703
|
async function applyProcfile(procfile, edits, original) {
|
|
9441
9704
|
let next = original;
|
|
@@ -9444,8 +9707,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
9444
9707
|
next = next.replace(e.before, e.after);
|
|
9445
9708
|
}
|
|
9446
9709
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
9447
|
-
await
|
|
9448
|
-
await
|
|
9710
|
+
await import_node_fs30.promises.writeFile(tmp, next, "utf8");
|
|
9711
|
+
await import_node_fs30.promises.rename(tmp, procfile);
|
|
9449
9712
|
}
|
|
9450
9713
|
async function apply2(installPlan) {
|
|
9451
9714
|
const { serviceDir } = installPlan;
|
|
@@ -9458,7 +9721,7 @@ async function apply2(installPlan) {
|
|
|
9458
9721
|
const originals = /* @__PURE__ */ new Map();
|
|
9459
9722
|
for (const file of touched) {
|
|
9460
9723
|
try {
|
|
9461
|
-
originals.set(file, await
|
|
9724
|
+
originals.set(file, await import_node_fs30.promises.readFile(file, "utf8"));
|
|
9462
9725
|
} catch {
|
|
9463
9726
|
}
|
|
9464
9727
|
}
|
|
@@ -9469,7 +9732,7 @@ async function apply2(installPlan) {
|
|
|
9469
9732
|
if (raw === void 0) {
|
|
9470
9733
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
9471
9734
|
}
|
|
9472
|
-
const base =
|
|
9735
|
+
const base = import_node_path47.default.basename(file);
|
|
9473
9736
|
if (base === "requirements.txt") {
|
|
9474
9737
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
9475
9738
|
if (edits.length > 0) {
|
|
@@ -9494,7 +9757,7 @@ async function rollback2(installPlan, originals) {
|
|
|
9494
9757
|
const restored = [];
|
|
9495
9758
|
for (const [file, raw] of originals.entries()) {
|
|
9496
9759
|
try {
|
|
9497
|
-
await
|
|
9760
|
+
await import_node_fs30.promises.writeFile(file, raw, "utf8");
|
|
9498
9761
|
restored.push(file);
|
|
9499
9762
|
} catch {
|
|
9500
9763
|
}
|
|
@@ -9508,8 +9771,8 @@ async function rollback2(installPlan, originals) {
|
|
|
9508
9771
|
...restored.map((f) => `restored: ${f}`),
|
|
9509
9772
|
""
|
|
9510
9773
|
];
|
|
9511
|
-
const rollbackPath =
|
|
9512
|
-
await
|
|
9774
|
+
const rollbackPath = import_node_path47.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
9775
|
+
await import_node_fs30.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
9513
9776
|
}
|
|
9514
9777
|
var pythonInstaller = {
|
|
9515
9778
|
name: "python",
|
|
@@ -9632,10 +9895,10 @@ function renderPatch(sections) {
|
|
|
9632
9895
|
|
|
9633
9896
|
// src/orchestrator.ts
|
|
9634
9897
|
init_cjs_shims();
|
|
9635
|
-
var
|
|
9898
|
+
var import_node_fs31 = require("fs");
|
|
9636
9899
|
var import_node_http = __toESM(require("http"), 1);
|
|
9637
9900
|
var import_node_net = __toESM(require("net"), 1);
|
|
9638
|
-
var
|
|
9901
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
9639
9902
|
var import_node_child_process3 = require("child_process");
|
|
9640
9903
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
9641
9904
|
async function extractAndPersist(opts) {
|
|
@@ -9644,7 +9907,7 @@ async function extractAndPersist(opts) {
|
|
|
9644
9907
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
9645
9908
|
resetGraph(graphKey);
|
|
9646
9909
|
const graph = getGraph(graphKey);
|
|
9647
|
-
const projectPaths = pathsForProject(graphKey,
|
|
9910
|
+
const projectPaths = pathsForProject(graphKey, import_node_path48.default.join(opts.scanPath, "neat-out"));
|
|
9648
9911
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
9649
9912
|
errorsPath: projectPaths.errorsPath
|
|
9650
9913
|
});
|
|
@@ -9698,7 +9961,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9698
9961
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
9699
9962
|
} else if (outcome.outcome === "react-native") {
|
|
9700
9963
|
reactNative++;
|
|
9701
|
-
const svcName =
|
|
9964
|
+
const svcName = import_node_path48.default.basename(svc.dir);
|
|
9702
9965
|
console.log(
|
|
9703
9966
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
9704
9967
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9709,7 +9972,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9709
9972
|
);
|
|
9710
9973
|
} else if (outcome.outcome === "bun") {
|
|
9711
9974
|
bun++;
|
|
9712
|
-
const svcName =
|
|
9975
|
+
const svcName = import_node_path48.default.basename(svc.dir);
|
|
9713
9976
|
console.log(
|
|
9714
9977
|
`neat: ${svc.dir} detected as Bun
|
|
9715
9978
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9720,7 +9983,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9720
9983
|
);
|
|
9721
9984
|
} else if (outcome.outcome === "deno") {
|
|
9722
9985
|
deno++;
|
|
9723
|
-
const svcName =
|
|
9986
|
+
const svcName = import_node_path48.default.basename(svc.dir);
|
|
9724
9987
|
console.log(
|
|
9725
9988
|
`neat: ${svc.dir} detected as Deno
|
|
9726
9989
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9731,7 +9994,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9731
9994
|
);
|
|
9732
9995
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
9733
9996
|
cloudflareWorkers++;
|
|
9734
|
-
const svcName =
|
|
9997
|
+
const svcName = import_node_path48.default.basename(svc.dir);
|
|
9735
9998
|
console.log(
|
|
9736
9999
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
9737
10000
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9742,7 +10005,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9742
10005
|
);
|
|
9743
10006
|
} else if (outcome.outcome === "electron") {
|
|
9744
10007
|
electron++;
|
|
9745
|
-
const svcName =
|
|
10008
|
+
const svcName = import_node_path48.default.basename(svc.dir);
|
|
9746
10009
|
console.log(
|
|
9747
10010
|
`neat: ${svc.dir} detected as Electron
|
|
9748
10011
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9916,10 +10179,10 @@ function formatPortCollisionMessage(port) {
|
|
|
9916
10179
|
];
|
|
9917
10180
|
}
|
|
9918
10181
|
function spawnDaemonDetached() {
|
|
9919
|
-
const here =
|
|
10182
|
+
const here = import_node_path48.default.dirname(new URL(importMetaUrl).pathname);
|
|
9920
10183
|
const candidates = [
|
|
9921
|
-
|
|
9922
|
-
|
|
10184
|
+
import_node_path48.default.join(here, "neatd.cjs"),
|
|
10185
|
+
import_node_path48.default.join(here, "neatd.js")
|
|
9923
10186
|
];
|
|
9924
10187
|
let entry2 = null;
|
|
9925
10188
|
const fsSync = require("fs");
|
|
@@ -9977,12 +10240,13 @@ async function runOrchestrator(opts) {
|
|
|
9977
10240
|
browser: "skipped"
|
|
9978
10241
|
}
|
|
9979
10242
|
};
|
|
9980
|
-
const stat = await
|
|
10243
|
+
const stat = await import_node_fs31.promises.stat(opts.scanPath).catch(() => null);
|
|
9981
10244
|
if (!stat || !stat.isDirectory()) {
|
|
9982
10245
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
9983
10246
|
result.exitCode = 2;
|
|
9984
10247
|
return result;
|
|
9985
10248
|
}
|
|
10249
|
+
printBanner();
|
|
9986
10250
|
console.log(`neat: ${opts.scanPath}`);
|
|
9987
10251
|
console.log("");
|
|
9988
10252
|
const persisted = await extractAndPersist({
|
|
@@ -9993,6 +10257,13 @@ async function runOrchestrator(opts) {
|
|
|
9993
10257
|
const { graph, services, languages } = persisted;
|
|
9994
10258
|
result.steps.discovery = { services: services.length, languages };
|
|
9995
10259
|
console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
|
|
10260
|
+
if (services.length === 0) {
|
|
10261
|
+
console.error(
|
|
10262
|
+
`neat: no services found in ${opts.scanPath} \u2014 run from inside your project root, or \`npx neat.is <path>\``
|
|
10263
|
+
);
|
|
10264
|
+
result.exitCode = 2;
|
|
10265
|
+
return result;
|
|
10266
|
+
}
|
|
9996
10267
|
let runApply = !opts.noInstrument;
|
|
9997
10268
|
if (runApply && !opts.yes && process.stdout.isTTY && process.stdin.isTTY) {
|
|
9998
10269
|
runApply = await promptYesNo("instrument your services and open the dashboard?");
|
|
@@ -10116,15 +10387,21 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
10116
10387
|
for (const [t, c] of [...byEdge.entries()].sort()) console.log(` ${t}: ${c}`);
|
|
10117
10388
|
console.log("");
|
|
10118
10389
|
console.log(`dashboard: ${dashboardUrl}`);
|
|
10390
|
+
const token = process.env.NEAT_AUTH_TOKEN;
|
|
10391
|
+
if (typeof token === "string" && token.length > 0) {
|
|
10392
|
+
console.log(`auth token: ${token}`);
|
|
10393
|
+
} else {
|
|
10394
|
+
console.log("running locally \u2014 open the dashboard, no token needed");
|
|
10395
|
+
}
|
|
10119
10396
|
}
|
|
10120
10397
|
|
|
10121
10398
|
// src/cli-verbs.ts
|
|
10122
10399
|
init_cjs_shims();
|
|
10123
|
-
var
|
|
10400
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
10124
10401
|
|
|
10125
10402
|
// src/cli-client.ts
|
|
10126
10403
|
init_cjs_shims();
|
|
10127
|
-
var
|
|
10404
|
+
var import_types27 = require("@neat.is/types");
|
|
10128
10405
|
var HttpError = class extends Error {
|
|
10129
10406
|
constructor(status2, message, responseBody = "") {
|
|
10130
10407
|
super(message);
|
|
@@ -10149,10 +10426,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
10149
10426
|
const root = baseUrl.replace(/\/$/, "");
|
|
10150
10427
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
10151
10428
|
return {
|
|
10152
|
-
async get(
|
|
10429
|
+
async get(path51) {
|
|
10153
10430
|
let res;
|
|
10154
10431
|
try {
|
|
10155
|
-
res = await fetch(`${root}${
|
|
10432
|
+
res = await fetch(`${root}${path51}`, {
|
|
10156
10433
|
headers: { ...authHeader }
|
|
10157
10434
|
});
|
|
10158
10435
|
} catch (err) {
|
|
@@ -10164,16 +10441,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
10164
10441
|
const body = await res.text().catch(() => "");
|
|
10165
10442
|
throw new HttpError(
|
|
10166
10443
|
res.status,
|
|
10167
|
-
`${res.status} ${res.statusText} on GET ${
|
|
10444
|
+
`${res.status} ${res.statusText} on GET ${path51}: ${body}`,
|
|
10168
10445
|
body
|
|
10169
10446
|
);
|
|
10170
10447
|
}
|
|
10171
10448
|
return await res.json();
|
|
10172
10449
|
},
|
|
10173
|
-
async post(
|
|
10450
|
+
async post(path51, body) {
|
|
10174
10451
|
let res;
|
|
10175
10452
|
try {
|
|
10176
|
-
res = await fetch(`${root}${
|
|
10453
|
+
res = await fetch(`${root}${path51}`, {
|
|
10177
10454
|
method: "POST",
|
|
10178
10455
|
headers: { "content-type": "application/json", ...authHeader },
|
|
10179
10456
|
body: JSON.stringify(body)
|
|
@@ -10187,7 +10464,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
10187
10464
|
const text = await res.text().catch(() => "");
|
|
10188
10465
|
throw new HttpError(
|
|
10189
10466
|
res.status,
|
|
10190
|
-
`${res.status} ${res.statusText} on POST ${
|
|
10467
|
+
`${res.status} ${res.statusText} on POST ${path51}: ${text}`,
|
|
10191
10468
|
text
|
|
10192
10469
|
);
|
|
10193
10470
|
}
|
|
@@ -10201,12 +10478,12 @@ function projectPath(project, suffix) {
|
|
|
10201
10478
|
}
|
|
10202
10479
|
async function runRootCause(client, input) {
|
|
10203
10480
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
10204
|
-
const
|
|
10481
|
+
const path51 = projectPath(
|
|
10205
10482
|
input.project,
|
|
10206
10483
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
10207
10484
|
);
|
|
10208
10485
|
try {
|
|
10209
|
-
const result = await client.get(
|
|
10486
|
+
const result = await client.get(path51);
|
|
10210
10487
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
10211
10488
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
10212
10489
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -10232,12 +10509,12 @@ async function runRootCause(client, input) {
|
|
|
10232
10509
|
}
|
|
10233
10510
|
async function runBlastRadius(client, input) {
|
|
10234
10511
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
10235
|
-
const
|
|
10512
|
+
const path51 = projectPath(
|
|
10236
10513
|
input.project,
|
|
10237
10514
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
10238
10515
|
);
|
|
10239
10516
|
try {
|
|
10240
|
-
const result = await client.get(
|
|
10517
|
+
const result = await client.get(path51);
|
|
10241
10518
|
if (result.totalAffected === 0) {
|
|
10242
10519
|
return {
|
|
10243
10520
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -10266,17 +10543,17 @@ async function runBlastRadius(client, input) {
|
|
|
10266
10543
|
}
|
|
10267
10544
|
}
|
|
10268
10545
|
function formatBlastEntry(n) {
|
|
10269
|
-
const tag = n.edgeProvenance ===
|
|
10546
|
+
const tag = n.edgeProvenance === import_types27.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
10270
10547
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
10271
10548
|
}
|
|
10272
10549
|
async function runDependencies(client, input) {
|
|
10273
10550
|
const depth = input.depth ?? 3;
|
|
10274
|
-
const
|
|
10551
|
+
const path51 = projectPath(
|
|
10275
10552
|
input.project,
|
|
10276
10553
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
10277
10554
|
);
|
|
10278
10555
|
try {
|
|
10279
|
-
const result = await client.get(
|
|
10556
|
+
const result = await client.get(path51);
|
|
10280
10557
|
if (result.total === 0) {
|
|
10281
10558
|
return {
|
|
10282
10559
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -10312,9 +10589,9 @@ async function runObservedDependencies(client, input) {
|
|
|
10312
10589
|
const edges = await client.get(
|
|
10313
10590
|
projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
|
|
10314
10591
|
);
|
|
10315
|
-
const observed = edges.outbound.filter((e) => e.provenance ===
|
|
10592
|
+
const observed = edges.outbound.filter((e) => e.provenance === import_types27.Provenance.OBSERVED);
|
|
10316
10593
|
if (observed.length === 0) {
|
|
10317
|
-
const hasExtracted = edges.outbound.some((e) => e.provenance ===
|
|
10594
|
+
const hasExtracted = edges.outbound.some((e) => e.provenance === import_types27.Provenance.EXTRACTED);
|
|
10318
10595
|
const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
10319
10596
|
return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
|
|
10320
10597
|
}
|
|
@@ -10322,7 +10599,7 @@ async function runObservedDependencies(client, input) {
|
|
|
10322
10599
|
return {
|
|
10323
10600
|
summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
10324
10601
|
block: blockLines.join("\n"),
|
|
10325
|
-
provenance:
|
|
10602
|
+
provenance: import_types27.Provenance.OBSERVED
|
|
10326
10603
|
};
|
|
10327
10604
|
} catch (err) {
|
|
10328
10605
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -10357,9 +10634,9 @@ function formatDuration(ms) {
|
|
|
10357
10634
|
return `${Math.round(h / 24)}d`;
|
|
10358
10635
|
}
|
|
10359
10636
|
async function runIncidents(client, input) {
|
|
10360
|
-
const
|
|
10637
|
+
const path51 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
10361
10638
|
try {
|
|
10362
|
-
const body = await client.get(
|
|
10639
|
+
const body = await client.get(path51);
|
|
10363
10640
|
const events = body.events;
|
|
10364
10641
|
if (events.length === 0) {
|
|
10365
10642
|
return {
|
|
@@ -10376,7 +10653,7 @@ async function runIncidents(client, input) {
|
|
|
10376
10653
|
return {
|
|
10377
10654
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
10378
10655
|
block: blockLines.join("\n"),
|
|
10379
|
-
provenance:
|
|
10656
|
+
provenance: import_types27.Provenance.OBSERVED
|
|
10380
10657
|
};
|
|
10381
10658
|
} catch (err) {
|
|
10382
10659
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -10485,7 +10762,7 @@ async function runStaleEdges(client, input) {
|
|
|
10485
10762
|
return {
|
|
10486
10763
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
10487
10764
|
block: blockLines.join("\n"),
|
|
10488
|
-
provenance:
|
|
10765
|
+
provenance: import_types27.Provenance.STALE
|
|
10489
10766
|
};
|
|
10490
10767
|
}
|
|
10491
10768
|
async function runPolicies(client, input) {
|
|
@@ -10649,7 +10926,7 @@ async function resolveProjectEntry(opts) {
|
|
|
10649
10926
|
const cwd = opts.cwd ?? process.cwd();
|
|
10650
10927
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
10651
10928
|
for (const entry2 of entries) {
|
|
10652
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
10929
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path49.default.sep}`)) {
|
|
10653
10930
|
return entry2;
|
|
10654
10931
|
}
|
|
10655
10932
|
}
|
|
@@ -10799,9 +11076,25 @@ async function runSync(opts) {
|
|
|
10799
11076
|
}
|
|
10800
11077
|
|
|
10801
11078
|
// src/cli.ts
|
|
10802
|
-
var
|
|
11079
|
+
var import_types28 = require("@neat.is/types");
|
|
11080
|
+
function isNpxInvocation() {
|
|
11081
|
+
if (process.env.npm_command === "exec") return true;
|
|
11082
|
+
const execpath = process.env.npm_execpath ?? "";
|
|
11083
|
+
if (execpath.includes("npx")) return true;
|
|
11084
|
+
const entry2 = process.argv[1] ?? "";
|
|
11085
|
+
if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
|
|
11086
|
+
return false;
|
|
11087
|
+
}
|
|
11088
|
+
function commandPrefix() {
|
|
11089
|
+
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
11090
|
+
}
|
|
10803
11091
|
function usage() {
|
|
10804
|
-
|
|
11092
|
+
const neat = commandPrefix();
|
|
11093
|
+
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
11094
|
+
console.log("");
|
|
11095
|
+
console.log(`usage: ${neat} <command> [args] [--project <name>]`);
|
|
11096
|
+
console.log("");
|
|
11097
|
+
console.log(`Run \`${neat}\` with no command from inside your project to go zero-to-graph in one step.`);
|
|
10805
11098
|
console.log("");
|
|
10806
11099
|
console.log("lifecycle commands:");
|
|
10807
11100
|
console.log(" init <path> One-time install: discover, extract, register, plan SDK install.");
|
|
@@ -10820,6 +11113,8 @@ function usage() {
|
|
|
10820
11113
|
console.log(" uninstall <name>");
|
|
10821
11114
|
console.log(" Remove a project from the registry. Does not touch");
|
|
10822
11115
|
console.log(" neat-out/, policy.json, or any user file.");
|
|
11116
|
+
console.log(" prune Drop registry entries whose path is gone from disk.");
|
|
11117
|
+
console.log(" Flags: --json emit the removed list as JSON");
|
|
10823
11118
|
console.log(" version Print the installed @neat.is/core version and exit.");
|
|
10824
11119
|
console.log(" Aliases: --version, -v.");
|
|
10825
11120
|
console.log(" skill Install or print the Claude Code MCP drop-in.");
|
|
@@ -10841,30 +11136,30 @@ function usage() {
|
|
|
10841
11136
|
console.log("");
|
|
10842
11137
|
console.log("query commands (mirror the MCP tools, ADR-050):");
|
|
10843
11138
|
console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
|
|
10844
|
-
console.log(
|
|
11139
|
+
console.log(` example: ${neat} root-cause service:<name>`);
|
|
10845
11140
|
console.log(" blast-radius <node-id> BFS outbound \u2014 what would break if this dies.");
|
|
10846
|
-
console.log(
|
|
11141
|
+
console.log(` example: ${neat} blast-radius database:<host>`);
|
|
10847
11142
|
console.log(" dependencies <node-id> Transitive outbound dependencies.");
|
|
10848
11143
|
console.log(" Flags: --depth N (default 3, max 10)");
|
|
10849
|
-
console.log(
|
|
11144
|
+
console.log(` example: ${neat} dependencies service:<name> --depth 2`);
|
|
10850
11145
|
console.log(" observed-dependencies <node-id> OBSERVED-only outbound edges (runtime traffic).");
|
|
10851
|
-
console.log(
|
|
11146
|
+
console.log(` example: ${neat} observed-dependencies service:<name>`);
|
|
10852
11147
|
console.log(" incidents [<node-id>] Recent error events; per-node when an id is given.");
|
|
10853
11148
|
console.log(" Flags: --limit N (default 20)");
|
|
10854
|
-
console.log(
|
|
11149
|
+
console.log(` example: ${neat} incidents service:<name> --limit 5`);
|
|
10855
11150
|
console.log(" search <query> Semantic (or substring) match on node names/ids.");
|
|
10856
|
-
console.log(
|
|
11151
|
+
console.log(` example: ${neat} search "checkout"`);
|
|
10857
11152
|
console.log(" diff --against <snapshot> Compare the live graph to a saved snapshot.");
|
|
10858
|
-
console.log(
|
|
11153
|
+
console.log(` example: ${neat} diff --against ./snapshots/baseline.json`);
|
|
10859
11154
|
console.log(" stale-edges Recent OBSERVED \u2192 STALE transitions.");
|
|
10860
11155
|
console.log(" Flags: --limit N, --edge-type CALLS|CONNECTS_TO|...");
|
|
10861
|
-
console.log(
|
|
11156
|
+
console.log(` example: ${neat} stale-edges --edge-type CALLS`);
|
|
10862
11157
|
console.log(" policies Current policy violations.");
|
|
10863
11158
|
console.log(" Flags: --node <id>, --hypothetical-action <json>");
|
|
10864
|
-
console.log(
|
|
11159
|
+
console.log(` example: ${neat} policies --node service:<name> --json`);
|
|
10865
11160
|
console.log(" divergences Where code (EXTRACTED) and production (OBSERVED) disagree.");
|
|
10866
11161
|
console.log(" Flags: --type <list>, --min-confidence <0..1>, --node <id>");
|
|
10867
|
-
console.log(
|
|
11162
|
+
console.log(` example: ${neat} divergences --min-confidence 0.7`);
|
|
10868
11163
|
console.log("");
|
|
10869
11164
|
console.log("flags:");
|
|
10870
11165
|
console.log(' --project <name> Name the project this command targets. Default: "default".');
|
|
@@ -10879,6 +11174,7 @@ function usage() {
|
|
|
10879
11174
|
console.log("");
|
|
10880
11175
|
console.log("environment:");
|
|
10881
11176
|
console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
|
|
11177
|
+
console.log(" alias: NEAT_CORE_URL (the name the MCP server reads)");
|
|
10882
11178
|
console.log(" NEAT_PROJECT project name when --project isn't passed");
|
|
10883
11179
|
}
|
|
10884
11180
|
var STRING_FLAGS = [
|
|
@@ -11008,40 +11304,10 @@ function assignFlag(out, field, value) {
|
|
|
11008
11304
|
;
|
|
11009
11305
|
out[field] = value;
|
|
11010
11306
|
}
|
|
11011
|
-
function readPackageVersion() {
|
|
11012
|
-
const here = typeof __dirname !== "undefined" ? __dirname : import_node_path48.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
11013
|
-
const candidates = [
|
|
11014
|
-
import_node_path48.default.resolve(here, "../package.json"),
|
|
11015
|
-
import_node_path48.default.resolve(here, "../../package.json")
|
|
11016
|
-
];
|
|
11017
|
-
for (const candidate of candidates) {
|
|
11018
|
-
try {
|
|
11019
|
-
const raw = (0, import_node_fs31.readFileSync)(candidate, "utf8");
|
|
11020
|
-
const parsed = JSON.parse(raw);
|
|
11021
|
-
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
11022
|
-
return parsed.version;
|
|
11023
|
-
}
|
|
11024
|
-
} catch {
|
|
11025
|
-
}
|
|
11026
|
-
}
|
|
11027
|
-
return "unknown";
|
|
11028
|
-
}
|
|
11029
11307
|
function printVersion() {
|
|
11030
11308
|
process.stdout.write(`${readPackageVersion()}
|
|
11031
11309
|
`);
|
|
11032
11310
|
}
|
|
11033
|
-
function printBanner() {
|
|
11034
|
-
console.log("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557");
|
|
11035
|
-
console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
|
|
11036
|
-
console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
11037
|
-
console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
11038
|
-
console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
11039
|
-
console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
|
|
11040
|
-
console.log("");
|
|
11041
|
-
console.log(" Network Expressive Architecting Tool");
|
|
11042
|
-
console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
|
|
11043
|
-
console.log("");
|
|
11044
|
-
}
|
|
11045
11311
|
function printDiscoveryReport(opts, services) {
|
|
11046
11312
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
11047
11313
|
const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
|
|
@@ -11080,7 +11346,7 @@ async function buildPatchSections(services, project) {
|
|
|
11080
11346
|
}
|
|
11081
11347
|
async function runInit(opts) {
|
|
11082
11348
|
const written = [];
|
|
11083
|
-
const stat = await
|
|
11349
|
+
const stat = await import_node_fs32.promises.stat(opts.scanPath).catch(() => null);
|
|
11084
11350
|
if (!stat || !stat.isDirectory()) {
|
|
11085
11351
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
11086
11352
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -11089,13 +11355,13 @@ async function runInit(opts) {
|
|
|
11089
11355
|
printDiscoveryReport(opts, services);
|
|
11090
11356
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
11091
11357
|
const patch = renderPatch(sections);
|
|
11092
|
-
const patchPath =
|
|
11358
|
+
const patchPath = import_node_path50.default.join(opts.scanPath, "neat.patch");
|
|
11093
11359
|
if (opts.dryRun) {
|
|
11094
|
-
await
|
|
11360
|
+
await import_node_fs32.promises.writeFile(patchPath, patch, "utf8");
|
|
11095
11361
|
written.push(patchPath);
|
|
11096
11362
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
11097
|
-
const gitignorePath =
|
|
11098
|
-
const gitignoreExists = await
|
|
11363
|
+
const gitignorePath = import_node_path50.default.join(opts.scanPath, ".gitignore");
|
|
11364
|
+
const gitignoreExists = await import_node_fs32.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
11099
11365
|
const verb = gitignoreExists ? "append" : "create";
|
|
11100
11366
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
11101
11367
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -11106,9 +11372,9 @@ async function runInit(opts) {
|
|
|
11106
11372
|
const graph = getGraph(graphKey);
|
|
11107
11373
|
const projectPaths = pathsForProject(
|
|
11108
11374
|
graphKey,
|
|
11109
|
-
|
|
11375
|
+
import_node_path50.default.join(opts.scanPath, "neat-out")
|
|
11110
11376
|
);
|
|
11111
|
-
const errorsPath =
|
|
11377
|
+
const errorsPath = import_node_path50.default.join(import_node_path50.default.dirname(opts.outPath), import_node_path50.default.basename(projectPaths.errorsPath));
|
|
11112
11378
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
11113
11379
|
await saveGraphToDisk(graph, opts.outPath);
|
|
11114
11380
|
written.push(opts.outPath);
|
|
@@ -11187,7 +11453,7 @@ async function runInit(opts) {
|
|
|
11187
11453
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
11188
11454
|
}
|
|
11189
11455
|
} else {
|
|
11190
|
-
await
|
|
11456
|
+
await import_node_fs32.promises.writeFile(patchPath, patch, "utf8");
|
|
11191
11457
|
written.push(patchPath);
|
|
11192
11458
|
}
|
|
11193
11459
|
}
|
|
@@ -11220,16 +11486,16 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
11220
11486
|
command: "npx",
|
|
11221
11487
|
args: ["-y", "@neat.is/mcp"],
|
|
11222
11488
|
env: {
|
|
11223
|
-
|
|
11489
|
+
NEAT_CORE_URL: "http://localhost:8080"
|
|
11224
11490
|
}
|
|
11225
11491
|
}
|
|
11226
11492
|
}
|
|
11227
11493
|
};
|
|
11228
11494
|
function claudeConfigPath() {
|
|
11229
11495
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
11230
|
-
if (override && override.length > 0) return
|
|
11496
|
+
if (override && override.length > 0) return import_node_path50.default.resolve(override);
|
|
11231
11497
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11232
|
-
return
|
|
11498
|
+
return import_node_path50.default.join(home, ".claude.json");
|
|
11233
11499
|
}
|
|
11234
11500
|
async function runSkill(opts) {
|
|
11235
11501
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -11241,7 +11507,7 @@ async function runSkill(opts) {
|
|
|
11241
11507
|
const target = claudeConfigPath();
|
|
11242
11508
|
let existing = {};
|
|
11243
11509
|
try {
|
|
11244
|
-
existing = JSON.parse(await
|
|
11510
|
+
existing = JSON.parse(await import_node_fs32.promises.readFile(target, "utf8"));
|
|
11245
11511
|
} catch (err) {
|
|
11246
11512
|
if (err.code !== "ENOENT") {
|
|
11247
11513
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -11253,8 +11519,8 @@ async function runSkill(opts) {
|
|
|
11253
11519
|
...existing,
|
|
11254
11520
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
11255
11521
|
};
|
|
11256
|
-
await
|
|
11257
|
-
await
|
|
11522
|
+
await import_node_fs32.promises.mkdir(import_node_path50.default.dirname(target), { recursive: true });
|
|
11523
|
+
await import_node_fs32.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
11258
11524
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
11259
11525
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
11260
11526
|
return { exitCode: 0 };
|
|
@@ -11266,19 +11532,30 @@ async function runSkill(opts) {
|
|
|
11266
11532
|
console.log("");
|
|
11267
11533
|
console.log("Manual install: copy mcpServers.neat from --print-config into ~/.claude.json,");
|
|
11268
11534
|
console.log("then restart Claude Code. See packages/claude-skill/SKILL.md for the tool list.");
|
|
11535
|
+
console.log("");
|
|
11536
|
+
console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
|
|
11537
|
+
console.log("non-default daemon by editing that value in the generated config.");
|
|
11269
11538
|
return { exitCode: 0 };
|
|
11270
11539
|
}
|
|
11271
11540
|
async function main() {
|
|
11272
|
-
const
|
|
11273
|
-
|
|
11541
|
+
const argv = process.argv.slice(2);
|
|
11542
|
+
const cmd0 = argv[0];
|
|
11543
|
+
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
11274
11544
|
usage();
|
|
11275
11545
|
process.exit(0);
|
|
11276
11546
|
}
|
|
11277
|
-
if (
|
|
11547
|
+
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
11278
11548
|
printVersion();
|
|
11279
11549
|
process.exit(0);
|
|
11280
11550
|
}
|
|
11281
|
-
const
|
|
11551
|
+
const argvParsed = parseArgs(argv);
|
|
11552
|
+
if (argvParsed.positional.length === 0) {
|
|
11553
|
+
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
11554
|
+
if (orchestratorCode2 !== null && orchestratorCode2 !== 0) process.exit(orchestratorCode2);
|
|
11555
|
+
return;
|
|
11556
|
+
}
|
|
11557
|
+
const cmd = argvParsed.positional[0];
|
|
11558
|
+
const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
|
|
11282
11559
|
const { positional, apply: apply3, dryRun, noInstall } = parsed;
|
|
11283
11560
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
11284
11561
|
if (cmd === "init") {
|
|
@@ -11292,12 +11569,12 @@ async function main() {
|
|
|
11292
11569
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
11293
11570
|
process.exit(2);
|
|
11294
11571
|
}
|
|
11295
|
-
const scanPath =
|
|
11572
|
+
const scanPath = import_node_path50.default.resolve(target);
|
|
11296
11573
|
const projectExplicit = parsed.project !== null;
|
|
11297
|
-
const projectName = projectExplicit ? project :
|
|
11574
|
+
const projectName = projectExplicit ? project : import_node_path50.default.basename(scanPath);
|
|
11298
11575
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
11299
|
-
const fallback = pathsForProject(projectKey,
|
|
11300
|
-
const outPath =
|
|
11576
|
+
const fallback = pathsForProject(projectKey, import_node_path50.default.join(scanPath, "neat-out")).snapshotPath;
|
|
11577
|
+
const outPath = import_node_path50.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
11301
11578
|
const result = await runInit({
|
|
11302
11579
|
scanPath,
|
|
11303
11580
|
outPath,
|
|
@@ -11318,21 +11595,21 @@ async function main() {
|
|
|
11318
11595
|
usage();
|
|
11319
11596
|
process.exit(2);
|
|
11320
11597
|
}
|
|
11321
|
-
const scanPath =
|
|
11322
|
-
const stat = await
|
|
11598
|
+
const scanPath = import_node_path50.default.resolve(target);
|
|
11599
|
+
const stat = await import_node_fs32.promises.stat(scanPath).catch(() => null);
|
|
11323
11600
|
if (!stat || !stat.isDirectory()) {
|
|
11324
11601
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
11325
11602
|
process.exit(2);
|
|
11326
11603
|
}
|
|
11327
|
-
const projectPaths = pathsForProject(project,
|
|
11328
|
-
const outPath =
|
|
11329
|
-
const errorsPath =
|
|
11330
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
11604
|
+
const projectPaths = pathsForProject(project, import_node_path50.default.join(scanPath, "neat-out"));
|
|
11605
|
+
const outPath = import_node_path50.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
11606
|
+
const errorsPath = import_node_path50.default.resolve(
|
|
11607
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path50.default.join(import_node_path50.default.dirname(outPath), import_node_path50.default.basename(projectPaths.errorsPath))
|
|
11331
11608
|
);
|
|
11332
|
-
const staleEventsPath =
|
|
11333
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
11609
|
+
const staleEventsPath = import_node_path50.default.resolve(
|
|
11610
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path50.default.join(import_node_path50.default.dirname(outPath), import_node_path50.default.basename(projectPaths.staleEventsPath))
|
|
11334
11611
|
);
|
|
11335
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
11612
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path50.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
11336
11613
|
const handle = await startWatch(getGraph(project), {
|
|
11337
11614
|
scanPath,
|
|
11338
11615
|
outPath,
|
|
@@ -11425,6 +11702,19 @@ async function main() {
|
|
|
11425
11702
|
console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
|
|
11426
11703
|
return;
|
|
11427
11704
|
}
|
|
11705
|
+
if (cmd === "prune") {
|
|
11706
|
+
const removed = await pruneRegistry({ ttlMs: 0 });
|
|
11707
|
+
if (parsed.json) {
|
|
11708
|
+
console.log(JSON.stringify(removed.map((p) => ({ name: p.name, path: p.path })), null, 2));
|
|
11709
|
+
return;
|
|
11710
|
+
}
|
|
11711
|
+
if (removed.length === 0) {
|
|
11712
|
+
console.log("nothing to prune \u2014 every registered project path still exists.");
|
|
11713
|
+
return;
|
|
11714
|
+
}
|
|
11715
|
+
console.log(`pruned ${removed.length} project${removed.length === 1 ? "" : "s"}: ${removed.map((p) => p.name).join(", ")}`);
|
|
11716
|
+
return;
|
|
11717
|
+
}
|
|
11428
11718
|
if (cmd === "deploy") {
|
|
11429
11719
|
const artifact = await runDeploy();
|
|
11430
11720
|
const block = renderOtelEnvBlock2(artifact.token);
|
|
@@ -11478,11 +11768,11 @@ async function main() {
|
|
|
11478
11768
|
process.exit(1);
|
|
11479
11769
|
}
|
|
11480
11770
|
async function tryOrchestrator(cmd, parsed) {
|
|
11481
|
-
const scanPath =
|
|
11482
|
-
const stat = await
|
|
11771
|
+
const scanPath = import_node_path50.default.resolve(cmd);
|
|
11772
|
+
const stat = await import_node_fs32.promises.stat(scanPath).catch(() => null);
|
|
11483
11773
|
if (!stat || !stat.isDirectory()) return null;
|
|
11484
11774
|
const projectExplicit = parsed.project !== null;
|
|
11485
|
-
const projectName = projectExplicit ? parsed.project :
|
|
11775
|
+
const projectName = projectExplicit ? parsed.project : import_node_path50.default.basename(scanPath);
|
|
11486
11776
|
const result = await runOrchestrator({
|
|
11487
11777
|
scanPath,
|
|
11488
11778
|
project: projectName,
|
|
@@ -11512,12 +11802,44 @@ function resolveProjectFlag(parsed) {
|
|
|
11512
11802
|
if (env && env.length > 0 && env !== DEFAULT_PROJECT) return env;
|
|
11513
11803
|
return void 0;
|
|
11514
11804
|
}
|
|
11805
|
+
var ProjectResolutionError = class extends Error {
|
|
11806
|
+
constructor(message, exitCode = 2) {
|
|
11807
|
+
super(message);
|
|
11808
|
+
this.exitCode = exitCode;
|
|
11809
|
+
this.name = "ProjectResolutionError";
|
|
11810
|
+
}
|
|
11811
|
+
exitCode;
|
|
11812
|
+
};
|
|
11813
|
+
async function resolveProjectForVerb(client, parsed) {
|
|
11814
|
+
const explicit = resolveProjectFlag(parsed);
|
|
11815
|
+
if (explicit) return explicit;
|
|
11816
|
+
const projects = await client.get("/projects");
|
|
11817
|
+
if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
|
|
11818
|
+
return void 0;
|
|
11819
|
+
}
|
|
11820
|
+
if (projects.length === 1) {
|
|
11821
|
+
return projects[0].name;
|
|
11822
|
+
}
|
|
11823
|
+
if (projects.length === 0) {
|
|
11824
|
+
throw new ProjectResolutionError(
|
|
11825
|
+
"No projects are registered with the daemon yet. Run `npx neat.is` in a repo to build a graph first, then re-run this command."
|
|
11826
|
+
);
|
|
11827
|
+
}
|
|
11828
|
+
const names = projects.map((p) => p.name).sort().map((n) => ` ${n}`).join("\n");
|
|
11829
|
+
throw new ProjectResolutionError(
|
|
11830
|
+
`Several projects are registered and none is named "default", so I can't pick one for you.
|
|
11831
|
+
Pass --project <name> to choose:
|
|
11832
|
+
${names}`
|
|
11833
|
+
);
|
|
11834
|
+
}
|
|
11835
|
+
function resolveDaemonUrl() {
|
|
11836
|
+
return process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL ?? "http://localhost:8080";
|
|
11837
|
+
}
|
|
11515
11838
|
async function runQueryVerb(cmd, parsed) {
|
|
11516
|
-
const baseUrl =
|
|
11839
|
+
const baseUrl = resolveDaemonUrl();
|
|
11517
11840
|
const client = createHttpClient(baseUrl, resolveAuthToken());
|
|
11518
|
-
const project = resolveProjectFlag(parsed);
|
|
11519
11841
|
const positional = parsed.positional;
|
|
11520
|
-
let
|
|
11842
|
+
let makeWork;
|
|
11521
11843
|
switch (cmd) {
|
|
11522
11844
|
case "root-cause": {
|
|
11523
11845
|
const node = positional[0];
|
|
@@ -11525,7 +11847,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11525
11847
|
console.error("neat root-cause: missing <node-id>");
|
|
11526
11848
|
return 2;
|
|
11527
11849
|
}
|
|
11528
|
-
|
|
11850
|
+
makeWork = (project) => runRootCause(client, {
|
|
11529
11851
|
errorNode: node,
|
|
11530
11852
|
...parsed.errorId ? { errorId: parsed.errorId } : {},
|
|
11531
11853
|
...project ? { project } : {}
|
|
@@ -11538,7 +11860,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11538
11860
|
console.error("neat blast-radius: missing <node-id>");
|
|
11539
11861
|
return 2;
|
|
11540
11862
|
}
|
|
11541
|
-
|
|
11863
|
+
makeWork = (project) => runBlastRadius(client, {
|
|
11542
11864
|
nodeId: node,
|
|
11543
11865
|
...parsed.depth !== null ? { depth: parsed.depth } : {},
|
|
11544
11866
|
...project ? { project } : {}
|
|
@@ -11551,7 +11873,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11551
11873
|
console.error("neat dependencies: missing <node-id>");
|
|
11552
11874
|
return 2;
|
|
11553
11875
|
}
|
|
11554
|
-
|
|
11876
|
+
makeWork = (project) => runDependencies(client, {
|
|
11555
11877
|
nodeId: node,
|
|
11556
11878
|
...parsed.depth !== null ? { depth: parsed.depth } : {},
|
|
11557
11879
|
...project ? { project } : {}
|
|
@@ -11564,14 +11886,14 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11564
11886
|
console.error("neat observed-dependencies: missing <node-id>");
|
|
11565
11887
|
return 2;
|
|
11566
11888
|
}
|
|
11567
|
-
|
|
11889
|
+
makeWork = (project) => runObservedDependencies(client, {
|
|
11568
11890
|
nodeId: node,
|
|
11569
11891
|
...project ? { project } : {}
|
|
11570
11892
|
});
|
|
11571
11893
|
break;
|
|
11572
11894
|
}
|
|
11573
11895
|
case "incidents": {
|
|
11574
|
-
|
|
11896
|
+
makeWork = (project) => runIncidents(client, {
|
|
11575
11897
|
...positional[0] ? { nodeId: positional[0] } : {},
|
|
11576
11898
|
...parsed.limit !== null ? { limit: parsed.limit } : {},
|
|
11577
11899
|
...project ? { project } : {}
|
|
@@ -11584,7 +11906,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11584
11906
|
console.error("neat search: missing <query>");
|
|
11585
11907
|
return 2;
|
|
11586
11908
|
}
|
|
11587
|
-
|
|
11909
|
+
makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
|
|
11588
11910
|
break;
|
|
11589
11911
|
}
|
|
11590
11912
|
case "diff": {
|
|
@@ -11593,14 +11915,14 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11593
11915
|
console.error("neat diff: --against <snapshot-path> is required");
|
|
11594
11916
|
return 2;
|
|
11595
11917
|
}
|
|
11596
|
-
|
|
11918
|
+
makeWork = (project) => runDiff(client, {
|
|
11597
11919
|
againstSnapshot: against,
|
|
11598
11920
|
...project ? { project } : {}
|
|
11599
11921
|
});
|
|
11600
11922
|
break;
|
|
11601
11923
|
}
|
|
11602
11924
|
case "stale-edges": {
|
|
11603
|
-
|
|
11925
|
+
makeWork = (project) => runStaleEdges(client, {
|
|
11604
11926
|
...parsed.limit !== null ? { limit: parsed.limit } : {},
|
|
11605
11927
|
...parsed.edgeType ? { edgeType: parsed.edgeType } : {},
|
|
11606
11928
|
...project ? { project } : {}
|
|
@@ -11619,7 +11941,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11619
11941
|
return 2;
|
|
11620
11942
|
}
|
|
11621
11943
|
}
|
|
11622
|
-
|
|
11944
|
+
makeWork = (project) => runPolicies(client, {
|
|
11623
11945
|
...parsed.node ? { nodeId: parsed.node } : {},
|
|
11624
11946
|
...hypothetical ? { hypotheticalAction: hypothetical } : {},
|
|
11625
11947
|
...project ? { project } : {}
|
|
@@ -11632,10 +11954,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11632
11954
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
11633
11955
|
const out = [];
|
|
11634
11956
|
for (const p of parts) {
|
|
11635
|
-
const r =
|
|
11957
|
+
const r = import_types28.DivergenceTypeSchema.safeParse(p);
|
|
11636
11958
|
if (!r.success) {
|
|
11637
11959
|
console.error(
|
|
11638
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
11960
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types28.DivergenceTypeSchema.options.join(", ")}`
|
|
11639
11961
|
);
|
|
11640
11962
|
return 2;
|
|
11641
11963
|
}
|
|
@@ -11643,7 +11965,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11643
11965
|
}
|
|
11644
11966
|
typeFilter = out;
|
|
11645
11967
|
}
|
|
11646
|
-
|
|
11968
|
+
makeWork = (project) => runDivergences(client, {
|
|
11647
11969
|
...typeFilter ? { type: typeFilter } : {},
|
|
11648
11970
|
...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
|
|
11649
11971
|
...parsed.node ? { node: parsed.node } : {},
|
|
@@ -11656,16 +11978,21 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
11656
11978
|
return 2;
|
|
11657
11979
|
}
|
|
11658
11980
|
try {
|
|
11659
|
-
const
|
|
11981
|
+
const project = await resolveProjectForVerb(client, parsed);
|
|
11982
|
+
const result = await makeWork(project);
|
|
11660
11983
|
if (parsed.json) process.stdout.write(formatJson(result) + "\n");
|
|
11661
11984
|
else process.stdout.write(formatHuman(result) + "\n");
|
|
11662
11985
|
return 0;
|
|
11663
11986
|
} catch (err) {
|
|
11987
|
+
if (err instanceof ProjectResolutionError) {
|
|
11988
|
+
console.error(`neat ${cmd}: ${err.message}`);
|
|
11989
|
+
return err.exitCode;
|
|
11990
|
+
}
|
|
11664
11991
|
if (err instanceof HttpError) {
|
|
11665
11992
|
const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
|
|
11666
11993
|
console.error(`neat ${cmd}: ${detail.trim()}`);
|
|
11667
11994
|
} else if (err instanceof TransportError) {
|
|
11668
|
-
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${
|
|
11995
|
+
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${resolveDaemonUrl()})`);
|
|
11669
11996
|
} else {
|
|
11670
11997
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
11671
11998
|
}
|
|
@@ -11682,12 +12009,18 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
|
|
|
11682
12009
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11683
12010
|
0 && (module.exports = {
|
|
11684
12011
|
CLAUDE_SKILL_CONFIG,
|
|
12012
|
+
ProjectResolutionError,
|
|
11685
12013
|
QUERY_VERBS,
|
|
12014
|
+
commandPrefix,
|
|
12015
|
+
isNpxInvocation,
|
|
12016
|
+
main,
|
|
11686
12017
|
parseArgs,
|
|
11687
12018
|
printBanner,
|
|
11688
12019
|
readPackageVersion,
|
|
12020
|
+
resolveProjectForVerb,
|
|
11689
12021
|
runInit,
|
|
11690
12022
|
runQueryVerb,
|
|
11691
|
-
runSkill
|
|
12023
|
+
runSkill,
|
|
12024
|
+
usage
|
|
11692
12025
|
});
|
|
11693
12026
|
//# sourceMappingURL=cli.cjs.map
|