@neat.is/core 0.6.2-dev.20260724 → 0.6.2
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-ZZ3VUCWL.js → chunk-AOWANF2K.js} +46 -15
- package/dist/chunk-AOWANF2K.js.map +1 -0
- package/dist/{chunk-64JJOXES.js → chunk-MSZ4NALT.js} +59 -26
- package/dist/chunk-MSZ4NALT.js.map +1 -0
- package/dist/cli.cjs +311 -26
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +17 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +104 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +104 -41
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +45 -14
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-64JJOXES.js.map +0 -1
- package/dist/chunk-ZZ3VUCWL.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -706,6 +706,7 @@ __export(cli_exports, {
|
|
|
706
706
|
module.exports = __toCommonJS(cli_exports);
|
|
707
707
|
init_cjs_shims();
|
|
708
708
|
var import_node_path63 = __toESM(require("path"), 1);
|
|
709
|
+
var import_node_os6 = __toESM(require("os"), 1);
|
|
709
710
|
var import_node_fs41 = require("fs");
|
|
710
711
|
|
|
711
712
|
// src/banner.ts
|
|
@@ -5689,7 +5690,7 @@ function keywordArrayStrings(argsNode, key) {
|
|
|
5689
5690
|
}
|
|
5690
5691
|
return [];
|
|
5691
5692
|
}
|
|
5692
|
-
function
|
|
5693
|
+
function collectPythonRouterPrefixes(root) {
|
|
5693
5694
|
const prefixes = /* @__PURE__ */ new Map();
|
|
5694
5695
|
walk(root, (node) => {
|
|
5695
5696
|
if (node.type !== "assignment") return;
|
|
@@ -5698,7 +5699,8 @@ function collectApiRouterPrefixes(root) {
|
|
|
5698
5699
|
const fn = right.childForFieldName("function");
|
|
5699
5700
|
if (!fn) return;
|
|
5700
5701
|
const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
|
|
5701
|
-
|
|
5702
|
+
const prefixKey = ctor === "APIRouter" ? "prefix" : ctor === "Blueprint" ? "url_prefix" : null;
|
|
5703
|
+
if (!prefixKey) return;
|
|
5702
5704
|
const left = node.childForFieldName("left");
|
|
5703
5705
|
if (!left || left.type !== "identifier") return;
|
|
5704
5706
|
const args = right.childForFieldName("arguments");
|
|
@@ -5706,7 +5708,7 @@ function collectApiRouterPrefixes(root) {
|
|
|
5706
5708
|
for (let i = 0; i < args.namedChildCount; i++) {
|
|
5707
5709
|
const arg = args.namedChild(i);
|
|
5708
5710
|
if (arg?.type !== "keyword_argument") continue;
|
|
5709
|
-
if (arg.childForFieldName("name")?.text !==
|
|
5711
|
+
if (arg.childForFieldName("name")?.text !== prefixKey) continue;
|
|
5710
5712
|
const val = arg.childForFieldName("value");
|
|
5711
5713
|
const p = val ? pyStaticStringText(val) : null;
|
|
5712
5714
|
if (p !== null) prefixes.set(left.text, p);
|
|
@@ -5714,9 +5716,9 @@ function collectApiRouterPrefixes(root) {
|
|
|
5714
5716
|
});
|
|
5715
5717
|
return prefixes;
|
|
5716
5718
|
}
|
|
5717
|
-
function
|
|
5719
|
+
function pythonRoutesFromSource(source, parser, framework) {
|
|
5718
5720
|
const tree = parseSource2(parser, source);
|
|
5719
|
-
const prefixes =
|
|
5721
|
+
const prefixes = collectPythonRouterPrefixes(tree.rootNode);
|
|
5720
5722
|
const out = [];
|
|
5721
5723
|
walk(tree.rootNode, (node) => {
|
|
5722
5724
|
if (node.type !== "decorator") return;
|
|
@@ -5727,7 +5729,9 @@ function fastapiRoutesFromSource(source, parser) {
|
|
|
5727
5729
|
const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
|
|
5728
5730
|
if (!method) return;
|
|
5729
5731
|
const isVerb = FASTAPI_METHODS.has(method);
|
|
5730
|
-
|
|
5732
|
+
const isFlaskRoute = method === "route";
|
|
5733
|
+
const isApiRoute = method === "api_route";
|
|
5734
|
+
if (!isVerb && !isFlaskRoute && !isApiRoute) return;
|
|
5731
5735
|
const args = call.childForFieldName("arguments");
|
|
5732
5736
|
const first = args?.namedChild(0);
|
|
5733
5737
|
if (!first || first.type !== "string") return;
|
|
@@ -5738,17 +5742,41 @@ function fastapiRoutesFromSource(source, parser) {
|
|
|
5738
5742
|
const pathTemplate = canonicalizeTemplate(prefix + rawPath);
|
|
5739
5743
|
const line = node.startPosition.row + 1;
|
|
5740
5744
|
if (isVerb) {
|
|
5741
|
-
out.push({ method: method.toUpperCase(), pathTemplate, line, framework
|
|
5745
|
+
out.push({ method: method.toUpperCase(), pathTemplate, line, framework });
|
|
5742
5746
|
return;
|
|
5743
5747
|
}
|
|
5744
5748
|
const methods = keywordArrayStrings(args, "methods");
|
|
5745
|
-
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5749
|
+
const list = methods.length > 0 ? methods : isFlaskRoute ? ["GET"] : ["ALL"];
|
|
5746
5750
|
for (const m of list) {
|
|
5751
|
+
out.push({ method: m === "ALL" ? "ALL" : m.toUpperCase(), pathTemplate, line, framework });
|
|
5752
|
+
}
|
|
5753
|
+
});
|
|
5754
|
+
return out;
|
|
5755
|
+
}
|
|
5756
|
+
function djangoRoutesFromSource(source, parser) {
|
|
5757
|
+
const tree = parseSource2(parser, source);
|
|
5758
|
+
const out = [];
|
|
5759
|
+
walk(tree.rootNode, (node) => {
|
|
5760
|
+
if (node.type !== "assignment") return;
|
|
5761
|
+
if (node.childForFieldName("left")?.text !== "urlpatterns") return;
|
|
5762
|
+
const list = node.childForFieldName("right");
|
|
5763
|
+
if (!list || list.type !== "list") return;
|
|
5764
|
+
for (let i = 0; i < list.namedChildCount; i++) {
|
|
5765
|
+
const el = list.namedChild(i);
|
|
5766
|
+
if (el?.type !== "call") continue;
|
|
5767
|
+
if (el.childForFieldName("function")?.text !== "path") continue;
|
|
5768
|
+
const args = el.childForFieldName("arguments");
|
|
5769
|
+
const first = args?.namedChild(0);
|
|
5770
|
+
if (first?.type !== "string") continue;
|
|
5771
|
+
const raw = pyStaticStringText(first);
|
|
5772
|
+
if (raw === null) continue;
|
|
5773
|
+
const second = args?.namedChild(1);
|
|
5774
|
+
if (second?.type === "call" && second.childForFieldName("function")?.text === "include") continue;
|
|
5747
5775
|
out.push({
|
|
5748
|
-
method:
|
|
5749
|
-
pathTemplate,
|
|
5750
|
-
line,
|
|
5751
|
-
framework: "
|
|
5776
|
+
method: "ALL",
|
|
5777
|
+
pathTemplate: canonicalizeTemplate(raw),
|
|
5778
|
+
line: el.startPosition.row + 1,
|
|
5779
|
+
framework: "django"
|
|
5752
5780
|
});
|
|
5753
5781
|
}
|
|
5754
5782
|
});
|
|
@@ -5769,7 +5797,10 @@ async function addRoutes(graph, services) {
|
|
|
5769
5797
|
const hasHono = deps["hono"] !== void 0;
|
|
5770
5798
|
const hasNext = deps["next"] !== void 0;
|
|
5771
5799
|
const hasFastapi = deps["fastapi"] !== void 0;
|
|
5772
|
-
|
|
5800
|
+
const hasFlask = deps["flask"] !== void 0;
|
|
5801
|
+
const hasDjango = deps["django"] !== void 0;
|
|
5802
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi && !hasFlask && !hasDjango)
|
|
5803
|
+
continue;
|
|
5773
5804
|
const files = await loadSourceFiles(service.dir);
|
|
5774
5805
|
for (const file of files) {
|
|
5775
5806
|
if (isTestPath(file.path)) continue;
|
|
@@ -5780,7 +5811,8 @@ async function addRoutes(graph, services) {
|
|
|
5780
5811
|
let routes;
|
|
5781
5812
|
try {
|
|
5782
5813
|
if (isPy) {
|
|
5783
|
-
routes = hasFastapi ?
|
|
5814
|
+
routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
|
|
5815
|
+
if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
|
|
5784
5816
|
} else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5785
5817
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5786
5818
|
} else if (hasExpress || hasFastify || hasHono) {
|
|
@@ -9946,6 +9978,22 @@ function describeCredential(ref, env = process.env) {
|
|
|
9946
9978
|
init_cjs_shims();
|
|
9947
9979
|
var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
9948
9980
|
var records = /* @__PURE__ */ new Map();
|
|
9981
|
+
var MAX_ERROR_LEN = 200;
|
|
9982
|
+
function sanitizePollError(err) {
|
|
9983
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9984
|
+
const collapsed = msg.replace(/\s+/g, " ").trim();
|
|
9985
|
+
return collapsed.length > MAX_ERROR_LEN ? `${collapsed.slice(0, MAX_ERROR_LEN - 1)}\u2026` : collapsed;
|
|
9986
|
+
}
|
|
9987
|
+
function recordConnectorPoll(id, tick) {
|
|
9988
|
+
const prev = records.get(id);
|
|
9989
|
+
records.set(id, {
|
|
9990
|
+
lastPollAt: tick.at,
|
|
9991
|
+
lastOutcome: tick.outcome,
|
|
9992
|
+
lastError: tick.outcome === "error" ? tick.error ?? null : null,
|
|
9993
|
+
signalsLastPoll: tick.signalsLastPoll ?? 0,
|
|
9994
|
+
lastOkAt: tick.outcome === "ok" ? tick.at : prev?.lastOkAt ?? null
|
|
9995
|
+
});
|
|
9996
|
+
}
|
|
9949
9997
|
function deriveState(rec, now, thresholdMs) {
|
|
9950
9998
|
if (rec.lastOutcome === "error") return "error";
|
|
9951
9999
|
const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
|
|
@@ -10736,19 +10784,128 @@ async function buildApi(opts) {
|
|
|
10736
10784
|
init_auth();
|
|
10737
10785
|
init_otel();
|
|
10738
10786
|
|
|
10739
|
-
// src/
|
|
10787
|
+
// src/connectors/registry.ts
|
|
10740
10788
|
init_cjs_shims();
|
|
10741
|
-
var import_node_fs33 = require("fs");
|
|
10742
|
-
var import_node_path54 = __toESM(require("path"), 1);
|
|
10743
|
-
var import_node_module = require("module");
|
|
10744
|
-
init_otel();
|
|
10745
10789
|
|
|
10746
10790
|
// src/connectors/index.ts
|
|
10747
10791
|
init_cjs_shims();
|
|
10748
10792
|
var import_types37 = require("@neat.is/types");
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10793
|
+
var NO_ENV = "unknown";
|
|
10794
|
+
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
10795
|
+
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
10796
|
+
const sites = [];
|
|
10797
|
+
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
10798
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
10799
|
+
if (edge.provenance !== import_types37.Provenance.EXTRACTED) continue;
|
|
10800
|
+
const parsed = (0, import_types37.parseFileId)(edge.source);
|
|
10801
|
+
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
10802
|
+
const site = { relPath: edge.evidence.file };
|
|
10803
|
+
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
10804
|
+
sites.push(site);
|
|
10805
|
+
}
|
|
10806
|
+
return sites.length === 1 ? sites[0] : void 0;
|
|
10807
|
+
}
|
|
10808
|
+
function routeCallSiteFor(graph, targetNodeId) {
|
|
10809
|
+
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
10810
|
+
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
10811
|
+
if (attrs.type !== import_types37.NodeType.RouteNode || !attrs.path) return void 0;
|
|
10812
|
+
const site = { relPath: attrs.path };
|
|
10813
|
+
if (attrs.line !== void 0) site.line = attrs.line;
|
|
10814
|
+
return site;
|
|
10815
|
+
}
|
|
10816
|
+
async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
10817
|
+
const signals = await connector.poll(ctx);
|
|
10818
|
+
let edgesCreated = 0;
|
|
10819
|
+
let edgesUpdated = 0;
|
|
10820
|
+
let unresolved = 0;
|
|
10821
|
+
for (const signal of signals) {
|
|
10822
|
+
const resolved = resolveTarget(signal, ctx);
|
|
10823
|
+
if (!resolved) {
|
|
10824
|
+
unresolved++;
|
|
10825
|
+
continue;
|
|
10826
|
+
}
|
|
10827
|
+
if (resolved.ensureInfraNode) {
|
|
10828
|
+
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
10829
|
+
ensureInfraNode(graph, kind, name, provider);
|
|
10830
|
+
}
|
|
10831
|
+
const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
10832
|
+
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : routeCallSiteFor(graph, resolved.targetNodeId) ?? staticCallSiteFor(graph, resolved.serviceName, resolved.targetNodeId);
|
|
10833
|
+
const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
|
|
10834
|
+
const evidence = callSite ? {
|
|
10835
|
+
file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),
|
|
10836
|
+
line: callSite.line
|
|
10837
|
+
} : void 0;
|
|
10838
|
+
const calls = Math.trunc(signal.callCount);
|
|
10839
|
+
if (!Number.isFinite(calls) || calls < 1) continue;
|
|
10840
|
+
const errors = Number.isFinite(signal.errorCount) ? Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls) : 0;
|
|
10841
|
+
let created = false;
|
|
10842
|
+
let ok = true;
|
|
10843
|
+
for (let i = 0; i < calls; i++) {
|
|
10844
|
+
const result = upsertObservedEdge(
|
|
10845
|
+
graph,
|
|
10846
|
+
resolved.edgeType,
|
|
10847
|
+
sourceId,
|
|
10848
|
+
resolved.targetNodeId,
|
|
10849
|
+
signal.lastObservedIso,
|
|
10850
|
+
i < errors,
|
|
10851
|
+
evidence
|
|
10852
|
+
);
|
|
10853
|
+
if (!result) {
|
|
10854
|
+
ok = false;
|
|
10855
|
+
break;
|
|
10856
|
+
}
|
|
10857
|
+
if (i === 0) created = result.created;
|
|
10858
|
+
}
|
|
10859
|
+
if (!ok) {
|
|
10860
|
+
unresolved++;
|
|
10861
|
+
continue;
|
|
10862
|
+
}
|
|
10863
|
+
if (created) edgesCreated++;
|
|
10864
|
+
else edgesUpdated++;
|
|
10865
|
+
}
|
|
10866
|
+
return { signalCount: signals.length, edgesCreated, edgesUpdated, unresolved };
|
|
10867
|
+
}
|
|
10868
|
+
var DEFAULT_POLL_INTERVAL_MS = 6e4;
|
|
10869
|
+
function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options = {}) {
|
|
10870
|
+
let stopped = false;
|
|
10871
|
+
let since = ctx.since;
|
|
10872
|
+
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
10873
|
+
const connectorId = options.connectorId;
|
|
10874
|
+
const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
|
|
10875
|
+
const tick = () => {
|
|
10876
|
+
if (stopped) return;
|
|
10877
|
+
void (async () => {
|
|
10878
|
+
const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10879
|
+
try {
|
|
10880
|
+
const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
10881
|
+
since = tickStartedAt;
|
|
10882
|
+
if (connectorId) {
|
|
10883
|
+
recordConnectorPoll(connectorId, {
|
|
10884
|
+
outcome: "ok",
|
|
10885
|
+
at: tickStartedAt,
|
|
10886
|
+
signalsLastPoll: result.signalCount
|
|
10887
|
+
});
|
|
10888
|
+
}
|
|
10889
|
+
} catch (err) {
|
|
10890
|
+
onError(err);
|
|
10891
|
+
if (connectorId) {
|
|
10892
|
+
recordConnectorPoll(connectorId, {
|
|
10893
|
+
outcome: "error",
|
|
10894
|
+
at: tickStartedAt,
|
|
10895
|
+
error: sanitizePollError(err)
|
|
10896
|
+
});
|
|
10897
|
+
}
|
|
10898
|
+
}
|
|
10899
|
+
})();
|
|
10900
|
+
};
|
|
10901
|
+
tick();
|
|
10902
|
+
const interval = setInterval(tick, intervalMs);
|
|
10903
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
10904
|
+
return () => {
|
|
10905
|
+
stopped = true;
|
|
10906
|
+
clearInterval(interval);
|
|
10907
|
+
};
|
|
10908
|
+
}
|
|
10752
10909
|
|
|
10753
10910
|
// src/connectors/junction.ts
|
|
10754
10911
|
init_cjs_shims();
|
|
@@ -11357,17 +11514,22 @@ function readRailwayToken(credentials) {
|
|
|
11357
11514
|
}
|
|
11358
11515
|
return token;
|
|
11359
11516
|
}
|
|
11360
|
-
|
|
11361
|
-
|
|
11517
|
+
var RAILWAY_AUTH_STYLES = ["bearer", "project-access-token"];
|
|
11518
|
+
function railwayAuthHeader(style, token) {
|
|
11519
|
+
return style === "bearer" ? { Authorization: `Bearer ${token}` } : { "Project-Access-Token": token };
|
|
11362
11520
|
}
|
|
11363
|
-
|
|
11521
|
+
var resolvedRailwayAuthStyle = /* @__PURE__ */ new Map();
|
|
11522
|
+
function isRailwayNotAuthorized(err) {
|
|
11523
|
+
return err instanceof Error && /not authorized/i.test(err.message);
|
|
11524
|
+
}
|
|
11525
|
+
async function railwayGraphQLOnce(apiUrl, style, token, query, variables, accountKey, fetchImpl) {
|
|
11364
11526
|
const res = await junctionFetch(
|
|
11365
11527
|
apiUrl,
|
|
11366
11528
|
{
|
|
11367
11529
|
method: "POST",
|
|
11368
11530
|
headers: {
|
|
11369
11531
|
"Content-Type": "application/json",
|
|
11370
|
-
...
|
|
11532
|
+
...railwayAuthHeader(style, token)
|
|
11371
11533
|
},
|
|
11372
11534
|
body: JSON.stringify({ query, variables })
|
|
11373
11535
|
},
|
|
@@ -11383,6 +11545,26 @@ async function railwayGraphQL(apiUrl, token, query, variables, accountKey, fetch
|
|
|
11383
11545
|
if (!body.data) throw new Error("Railway GraphQL response carried no data");
|
|
11384
11546
|
return body.data;
|
|
11385
11547
|
}
|
|
11548
|
+
async function railwayGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
11549
|
+
const known = resolvedRailwayAuthStyle.get(token);
|
|
11550
|
+
const styles = known ? [known] : RAILWAY_AUTH_STYLES;
|
|
11551
|
+
let lastNotAuthorized;
|
|
11552
|
+
for (let i = 0; i < styles.length; i++) {
|
|
11553
|
+
const style = styles[i];
|
|
11554
|
+
try {
|
|
11555
|
+
const data = await railwayGraphQLOnce(apiUrl, style, token, query, variables, accountKey, fetchImpl);
|
|
11556
|
+
resolvedRailwayAuthStyle.set(token, style);
|
|
11557
|
+
return data;
|
|
11558
|
+
} catch (err) {
|
|
11559
|
+
if (isRailwayNotAuthorized(err) && i < styles.length - 1) {
|
|
11560
|
+
lastNotAuthorized = err;
|
|
11561
|
+
continue;
|
|
11562
|
+
}
|
|
11563
|
+
throw err;
|
|
11564
|
+
}
|
|
11565
|
+
}
|
|
11566
|
+
throw lastNotAuthorized ?? new Error("Railway GraphQL: no auth style resolved");
|
|
11567
|
+
}
|
|
11386
11568
|
var HTTP_LOGS_QUERY = `
|
|
11387
11569
|
query HttpLogs($deploymentId: String!, $startDate: String, $endDate: String, $limit: Int) {
|
|
11388
11570
|
httpLogs(deploymentId: $deploymentId, startDate: $startDate, endDate: $endDate, limit: $limit) {
|
|
@@ -12431,6 +12613,49 @@ function resolveEntryCredentials(dispatch, entry2, env) {
|
|
|
12431
12613
|
}
|
|
12432
12614
|
return { ok: true, credentials };
|
|
12433
12615
|
}
|
|
12616
|
+
function buildRegistration(entry2, graph, env = process.env) {
|
|
12617
|
+
const dispatch = PROVIDER_DISPATCH[entry2.provider];
|
|
12618
|
+
if (!dispatch) {
|
|
12619
|
+
if (isPushProvider(entry2.provider)) {
|
|
12620
|
+
return {
|
|
12621
|
+
ok: false,
|
|
12622
|
+
push: true,
|
|
12623
|
+
reason: `push provider "${entry2.provider}" ingests via the OTLP receiver \u2014 nothing to poll`
|
|
12624
|
+
};
|
|
12625
|
+
}
|
|
12626
|
+
return { ok: false, reason: `unknown provider "${entry2.provider}"` };
|
|
12627
|
+
}
|
|
12628
|
+
const creds = resolveEntryCredentials(dispatch, entry2, env);
|
|
12629
|
+
if (!creds.ok) return { ok: false, reason: creds.reason };
|
|
12630
|
+
const credentials = creds.credentials;
|
|
12631
|
+
const options = entry2.options ?? {};
|
|
12632
|
+
const missingOpts = dispatch.requiredOptionFields.filter((k) => !(k in options));
|
|
12633
|
+
if (missingOpts.length > 0) {
|
|
12634
|
+
return {
|
|
12635
|
+
ok: false,
|
|
12636
|
+
reason: `options missing required field(s): ${missingOpts.join(", ")}`
|
|
12637
|
+
};
|
|
12638
|
+
}
|
|
12639
|
+
let built;
|
|
12640
|
+
try {
|
|
12641
|
+
built = dispatch.build(graph, options);
|
|
12642
|
+
} catch (err) {
|
|
12643
|
+
return { ok: false, reason: err.message };
|
|
12644
|
+
}
|
|
12645
|
+
const intervalMs = typeof options.intervalMs === "number" ? options.intervalMs : void 0;
|
|
12646
|
+
return {
|
|
12647
|
+
ok: true,
|
|
12648
|
+
registration: {
|
|
12649
|
+
// Carry the entry id so the daemon can key this connector's poll-status
|
|
12650
|
+
// records to it (ADR-136).
|
|
12651
|
+
id: entry2.id,
|
|
12652
|
+
connector: built.connector,
|
|
12653
|
+
credentials,
|
|
12654
|
+
resolveTarget: built.resolveTarget,
|
|
12655
|
+
...intervalMs !== void 0 ? { intervalMs } : {}
|
|
12656
|
+
}
|
|
12657
|
+
};
|
|
12658
|
+
}
|
|
12434
12659
|
async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
|
|
12435
12660
|
const dispatch = PROVIDER_DISPATCH[entry2.provider] ?? PUSH_PROVIDER_DISPATCH[entry2.provider];
|
|
12436
12661
|
if (!dispatch) {
|
|
@@ -12456,6 +12681,48 @@ async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
|
|
|
12456
12681
|
});
|
|
12457
12682
|
return result.ok ? { status: "ok" } : { status: "rejected", reason: result.reason };
|
|
12458
12683
|
}
|
|
12684
|
+
async function loadConnectorRegistrations(input) {
|
|
12685
|
+
const { project, graph, home, env = process.env, onSkip } = input;
|
|
12686
|
+
let connectors;
|
|
12687
|
+
try {
|
|
12688
|
+
connectors = (await readConnectorsConfig(home)).connectors;
|
|
12689
|
+
} catch (err) {
|
|
12690
|
+
onSkip?.(
|
|
12691
|
+
{ id: "(file)", provider: "(all)", credential: "" },
|
|
12692
|
+
`connectors.json unreadable \u2014 ${err.message}`
|
|
12693
|
+
);
|
|
12694
|
+
return [];
|
|
12695
|
+
}
|
|
12696
|
+
const registrations = [];
|
|
12697
|
+
for (const entry2 of connectors) {
|
|
12698
|
+
if (!connectorMatchesProject(entry2, project)) continue;
|
|
12699
|
+
const result = buildRegistration(entry2, graph, env);
|
|
12700
|
+
if (result.ok) registrations.push(result.registration);
|
|
12701
|
+
else if (!result.push) onSkip?.(entry2, result.reason);
|
|
12702
|
+
}
|
|
12703
|
+
return registrations;
|
|
12704
|
+
}
|
|
12705
|
+
async function startConnectorPolling(input) {
|
|
12706
|
+
const fileConnectors = input.home ? await loadConnectorRegistrations({
|
|
12707
|
+
project: input.project,
|
|
12708
|
+
graph: input.graph,
|
|
12709
|
+
home: input.home,
|
|
12710
|
+
...input.onSkip ? { onSkip: input.onSkip } : {}
|
|
12711
|
+
}) : [];
|
|
12712
|
+
const all = [...input.extra ?? [], ...fileConnectors];
|
|
12713
|
+
const stopFns = all.map(
|
|
12714
|
+
(registration) => startConnectorPollLoop(
|
|
12715
|
+
registration.connector,
|
|
12716
|
+
{ projectDir: input.projectDir, credentials: registration.credentials },
|
|
12717
|
+
input.graph,
|
|
12718
|
+
registration.resolveTarget,
|
|
12719
|
+
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
12720
|
+
)
|
|
12721
|
+
);
|
|
12722
|
+
return () => {
|
|
12723
|
+
for (const stop of stopFns) stop();
|
|
12724
|
+
};
|
|
12725
|
+
}
|
|
12459
12726
|
function resolvePushEntry(entry2, env) {
|
|
12460
12727
|
const dispatch = PUSH_PROVIDER_DISPATCH[entry2.provider];
|
|
12461
12728
|
if (!dispatch) {
|
|
@@ -12506,6 +12773,11 @@ async function deprovisionConnector(entry2, env = process.env, fetchImpl) {
|
|
|
12506
12773
|
}
|
|
12507
12774
|
|
|
12508
12775
|
// src/daemon.ts
|
|
12776
|
+
init_cjs_shims();
|
|
12777
|
+
var import_node_fs33 = require("fs");
|
|
12778
|
+
var import_node_path54 = __toESM(require("path"), 1);
|
|
12779
|
+
var import_node_module = require("module");
|
|
12780
|
+
init_otel();
|
|
12509
12781
|
init_auth();
|
|
12510
12782
|
|
|
12511
12783
|
// src/unrouted.ts
|
|
@@ -13120,6 +13392,15 @@ async function startWatch(graph, opts) {
|
|
|
13120
13392
|
project: projectName,
|
|
13121
13393
|
onPolicyTrigger
|
|
13122
13394
|
});
|
|
13395
|
+
const stopConnectors = await startConnectorPolling({
|
|
13396
|
+
project: projectName,
|
|
13397
|
+
graph,
|
|
13398
|
+
projectDir: opts.scanPath,
|
|
13399
|
+
...opts.neatHome ? { home: opts.neatHome } : {},
|
|
13400
|
+
onSkip: (skipped, reason) => console.warn(
|
|
13401
|
+
`neat watch: connector "${skipped.id}" (${skipped.provider}) skipped for project "${projectName}" \u2014 ${reason}`
|
|
13402
|
+
)
|
|
13403
|
+
});
|
|
13123
13404
|
const auth = readAuthEnv();
|
|
13124
13405
|
const host = opts.host ?? (auth.authToken ? "0.0.0.0" : "127.0.0.1");
|
|
13125
13406
|
assertBindAuthority(host, auth.authToken);
|
|
@@ -13305,6 +13586,7 @@ async function startWatch(graph, opts) {
|
|
|
13305
13586
|
}
|
|
13306
13587
|
}
|
|
13307
13588
|
await watcher.close();
|
|
13589
|
+
stopConnectors();
|
|
13308
13590
|
stopStaleness();
|
|
13309
13591
|
stopPersist();
|
|
13310
13592
|
detachEventBus();
|
|
@@ -18247,6 +18529,9 @@ async function main() {
|
|
|
18247
18529
|
errorsPath,
|
|
18248
18530
|
staleEventsPath,
|
|
18249
18531
|
project,
|
|
18532
|
+
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
18533
|
+
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
18534
|
+
neatHome: process.env.NEAT_HOME ? import_node_path63.default.resolve(process.env.NEAT_HOME) : import_node_path63.default.join(import_node_os6.default.homedir(), ".neat"),
|
|
18250
18535
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
18251
18536
|
host: process.env.HOST ?? "0.0.0.0",
|
|
18252
18537
|
port: Number(process.env.PORT ?? 8080),
|