@neat.is/core 0.10.3-dev.20260930 → 0.10.4
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-DLQNMU3R.js → chunk-2OQVIBZZ.js} +59 -22
- package/dist/chunk-2OQVIBZZ.js.map +1 -0
- package/dist/{chunk-M7CGATVC.js → chunk-EFBWQIKG.js} +12 -2
- package/dist/chunk-EFBWQIKG.js.map +1 -0
- package/dist/{chunk-5SJHA5ZH.js → chunk-LXQCL4MP.js} +2 -2
- package/dist/{chunk-LNIVRE4L.js → chunk-TUGYIRUT.js} +51 -2
- package/dist/{chunk-LNIVRE4L.js.map → chunk-TUGYIRUT.js.map} +1 -1
- package/dist/cli.cjs +104 -19
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +50 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +114 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +114 -20
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-CU73Y5WT.js → otel-grpc-B7ABHLV7.js} +3 -3
- package/dist/server.cjs +62 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +7 -4
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-DLQNMU3R.js.map +0 -1
- package/dist/chunk-M7CGATVC.js.map +0 -1
- /package/dist/{chunk-5SJHA5ZH.js.map → chunk-LXQCL4MP.js.map} +0 -0
- /package/dist/{otel-grpc-CU73Y5WT.js.map → otel-grpc-B7ABHLV7.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,10 @@ function assertBindAuthority(host, token) {
|
|
|
50
50
|
if (isLoopbackHost2(host)) return;
|
|
51
51
|
throw new BindAuthorityError(host);
|
|
52
52
|
}
|
|
53
|
+
function isBearerDelegated(req) {
|
|
54
|
+
const config = req.routeOptions?.config;
|
|
55
|
+
return config?.bearerDelegated === true;
|
|
56
|
+
}
|
|
53
57
|
function mountBearerAuth(app, opts) {
|
|
54
58
|
if (!opts.token || opts.token.length === 0) return;
|
|
55
59
|
if (opts.trustProxy) return;
|
|
@@ -65,6 +69,10 @@ function mountBearerAuth(app, opts) {
|
|
|
65
69
|
done();
|
|
66
70
|
return;
|
|
67
71
|
}
|
|
72
|
+
if (isBearerDelegated(req)) {
|
|
73
|
+
done();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
68
76
|
if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
|
|
69
77
|
done();
|
|
70
78
|
return;
|
|
@@ -98,7 +106,7 @@ function readAuthEnv(env = process.env) {
|
|
|
98
106
|
publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
|
|
99
107
|
};
|
|
100
108
|
}
|
|
101
|
-
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN;
|
|
109
|
+
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN, BEARER_DELEGATED;
|
|
102
110
|
var init_auth = __esm({
|
|
103
111
|
"src/auth.ts"() {
|
|
104
112
|
"use strict";
|
|
@@ -128,6 +136,7 @@ var init_auth = __esm({
|
|
|
128
136
|
PROJECT_SCOPED_UNAUTH_PATTERN = new RegExp(
|
|
129
137
|
`^/projects/[^/]+/(?:${DEFAULT_UNAUTH_PATHS.map((p) => p.slice(1)).join("|")})$`
|
|
130
138
|
);
|
|
139
|
+
BEARER_DELEGATED = { bearerDelegated: true };
|
|
131
140
|
}
|
|
132
141
|
});
|
|
133
142
|
|
|
@@ -23616,6 +23625,19 @@ function registerRoutes(scope, ctx) {
|
|
|
23616
23625
|
});
|
|
23617
23626
|
}
|
|
23618
23627
|
});
|
|
23628
|
+
scope.post("/repo-sync", async (req, reply) => {
|
|
23629
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
23630
|
+
if (!proj) return;
|
|
23631
|
+
const syncNow = ctx.repoSync?.(proj.name);
|
|
23632
|
+
if (!syncNow) {
|
|
23633
|
+
return reply.code(404).send({
|
|
23634
|
+
error: "repo-sync is not running for this project",
|
|
23635
|
+
project: proj.name,
|
|
23636
|
+
hint: "Only a hosted daemon syncs bound repos. On a local daemon, POST /graph/scan re-extracts the project path."
|
|
23637
|
+
});
|
|
23638
|
+
}
|
|
23639
|
+
return reply.code(202).send({ project: proj.name, ...syncNow() });
|
|
23640
|
+
});
|
|
23619
23641
|
scope.post("/graph/scan", async (req, reply) => {
|
|
23620
23642
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
23621
23643
|
if (!proj) return;
|
|
@@ -23863,6 +23885,39 @@ function registerRoutes(scope, ctx) {
|
|
|
23863
23885
|
}
|
|
23864
23886
|
});
|
|
23865
23887
|
}
|
|
23888
|
+
var OTLP_BODY_LIMIT = 16 * 1024 * 1024;
|
|
23889
|
+
function otlpOnRest(getReceiver) {
|
|
23890
|
+
return async function plugin(scope) {
|
|
23891
|
+
scope.removeAllContentTypeParsers();
|
|
23892
|
+
scope.addContentTypeParser(
|
|
23893
|
+
"*",
|
|
23894
|
+
{ parseAs: "buffer", bodyLimit: OTLP_BODY_LIMIT },
|
|
23895
|
+
(_req, body, done) => done(null, body)
|
|
23896
|
+
);
|
|
23897
|
+
const relay = async (req, reply) => {
|
|
23898
|
+
const receiver = getReceiver();
|
|
23899
|
+
if (!receiver) {
|
|
23900
|
+
return reply.code(503).header("retry-after", "1").send({ error: "OTLP receiver is starting" });
|
|
23901
|
+
}
|
|
23902
|
+
const headers = { ...req.headers };
|
|
23903
|
+
delete headers["content-length"];
|
|
23904
|
+
delete headers["transfer-encoding"];
|
|
23905
|
+
delete headers.connection;
|
|
23906
|
+
const res = await receiver.inject({
|
|
23907
|
+
method: "POST",
|
|
23908
|
+
url: req.url,
|
|
23909
|
+
headers,
|
|
23910
|
+
payload: req.body
|
|
23911
|
+
});
|
|
23912
|
+
const contentType = res.headers["content-type"];
|
|
23913
|
+
if (contentType !== void 0) void reply.header("content-type", contentType);
|
|
23914
|
+
return reply.code(res.statusCode).send(res.rawPayload);
|
|
23915
|
+
};
|
|
23916
|
+
const route = { config: BEARER_DELEGATED, bodyLimit: OTLP_BODY_LIMIT };
|
|
23917
|
+
scope.post("/v1/traces", route, relay);
|
|
23918
|
+
scope.post("/projects/:project/v1/traces", route, relay);
|
|
23919
|
+
};
|
|
23920
|
+
}
|
|
23866
23921
|
async function buildApi(opts) {
|
|
23867
23922
|
const app = (0, import_fastify2.default)({ logger: false, routerOptions: { maxParamLength: 1024 } });
|
|
23868
23923
|
await app.register(import_cors.default, { origin: true });
|
|
@@ -23881,6 +23936,7 @@ async function buildApi(opts) {
|
|
|
23881
23936
|
authProxy: trustProxy === true,
|
|
23882
23937
|
requiresAuth
|
|
23883
23938
|
}));
|
|
23939
|
+
if (opts.otlpReceiver) await app.register(otlpOnRest(opts.otlpReceiver));
|
|
23884
23940
|
const startedAt = opts.startedAt ?? Date.now();
|
|
23885
23941
|
const registry = buildLegacyRegistry(opts);
|
|
23886
23942
|
const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
|
|
@@ -23911,6 +23967,7 @@ async function buildApi(opts) {
|
|
|
23911
23967
|
bootstrap: opts.bootstrap,
|
|
23912
23968
|
singleProject: opts.singleProject?.name,
|
|
23913
23969
|
connectorsHome: opts.connectorsHome,
|
|
23970
|
+
repoSync: opts.repoSync,
|
|
23914
23971
|
runPoll: opts.runPoll ?? runConnectorPoll
|
|
23915
23972
|
};
|
|
23916
23973
|
app.get("/health", async () => {
|
|
@@ -24477,6 +24534,7 @@ async function syncOneRepo(r, input) {
|
|
|
24477
24534
|
detail: `extracted ${nodes} node${nodes === 1 ? "" : "s"}, ${edges} edge${edges === 1 ? "" : "s"}`,
|
|
24478
24535
|
lastSyncAt: new Date(input.now?.() ?? Date.now()).toISOString()
|
|
24479
24536
|
});
|
|
24537
|
+
return true;
|
|
24480
24538
|
} catch (err) {
|
|
24481
24539
|
input.onError?.(label, err);
|
|
24482
24540
|
await cpPostStatus(deps, r.owner, r.name, {
|
|
@@ -24484,50 +24542,76 @@ async function syncOneRepo(r, input) {
|
|
|
24484
24542
|
detail: scrubToken(err.message).slice(0, 300)
|
|
24485
24543
|
}).catch(() => {
|
|
24486
24544
|
});
|
|
24545
|
+
return false;
|
|
24487
24546
|
} finally {
|
|
24488
24547
|
if (dir) await (0, import_promises2.rm)(dir, { recursive: true, force: true }).catch(() => {
|
|
24489
24548
|
});
|
|
24490
24549
|
}
|
|
24491
24550
|
}
|
|
24492
|
-
async function
|
|
24551
|
+
async function runRepoSyncPassCounted(input) {
|
|
24552
|
+
const counts = { listed: false, synced: 0, failed: 0 };
|
|
24493
24553
|
let repos;
|
|
24494
24554
|
try {
|
|
24495
24555
|
repos = await cpGet2(`/internal/projects/${input.deps.projectId}/repos`, input.deps);
|
|
24496
24556
|
} catch (err) {
|
|
24497
24557
|
input.onSkip?.("(all)", `control plane repo list unreadable \u2014 ${err.message}`);
|
|
24498
|
-
return
|
|
24558
|
+
return counts;
|
|
24499
24559
|
}
|
|
24500
|
-
if (!Array.isArray(repos)) return
|
|
24560
|
+
if (!Array.isArray(repos)) return counts;
|
|
24561
|
+
counts.listed = true;
|
|
24501
24562
|
for (const r of repos) {
|
|
24502
24563
|
if (!input.forceResync && !needsSync(r)) continue;
|
|
24503
|
-
await syncOneRepo(r, input)
|
|
24564
|
+
if (await syncOneRepo(r, input)) counts.synced++;
|
|
24565
|
+
else counts.failed++;
|
|
24504
24566
|
}
|
|
24505
|
-
return
|
|
24567
|
+
return counts;
|
|
24506
24568
|
}
|
|
24507
24569
|
async function startRepoSync(input) {
|
|
24508
24570
|
const intervalMs = input.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS;
|
|
24571
|
+
const now = () => input.now?.() ?? Date.now();
|
|
24509
24572
|
let stopped = false;
|
|
24510
|
-
let
|
|
24573
|
+
let inFlight = null;
|
|
24574
|
+
let again = false;
|
|
24575
|
+
let lastPass;
|
|
24511
24576
|
let bootResyncDone = false;
|
|
24512
|
-
const
|
|
24513
|
-
if (stopped || running) return;
|
|
24514
|
-
running = true;
|
|
24577
|
+
const run = async () => {
|
|
24515
24578
|
try {
|
|
24516
|
-
|
|
24517
|
-
|
|
24579
|
+
do {
|
|
24580
|
+
again = false;
|
|
24581
|
+
const startedAt = new Date(now()).toISOString();
|
|
24582
|
+
const counts = await runRepoSyncPassCounted({ ...input, forceResync: !bootResyncDone });
|
|
24583
|
+
if (counts.listed) bootResyncDone = true;
|
|
24584
|
+
lastPass = { ...counts, startedAt, finishedAt: new Date(now()).toISOString() };
|
|
24585
|
+
} while (again && !stopped);
|
|
24518
24586
|
} finally {
|
|
24519
|
-
|
|
24587
|
+
inFlight = null;
|
|
24520
24588
|
}
|
|
24521
24589
|
};
|
|
24522
|
-
|
|
24523
|
-
|
|
24524
|
-
|
|
24525
|
-
}
|
|
24590
|
+
const tick = () => {
|
|
24591
|
+
if (stopped || inFlight) return;
|
|
24592
|
+
inFlight = run();
|
|
24593
|
+
};
|
|
24594
|
+
tick();
|
|
24595
|
+
const timer = setInterval(tick, intervalMs);
|
|
24526
24596
|
if (typeof timer.unref === "function") timer.unref();
|
|
24527
|
-
|
|
24597
|
+
const stop = () => {
|
|
24528
24598
|
stopped = true;
|
|
24529
24599
|
clearInterval(timer);
|
|
24530
24600
|
};
|
|
24601
|
+
return Object.assign(stop, {
|
|
24602
|
+
syncNow: () => {
|
|
24603
|
+
const summary = lastPass ? { lastPass } : {};
|
|
24604
|
+
if (inFlight) {
|
|
24605
|
+
again = true;
|
|
24606
|
+
return { status: "queued", ...summary };
|
|
24607
|
+
}
|
|
24608
|
+
inFlight = run();
|
|
24609
|
+
return { status: "started", ...summary };
|
|
24610
|
+
},
|
|
24611
|
+
settled: async () => {
|
|
24612
|
+
while (inFlight) await inFlight;
|
|
24613
|
+
}
|
|
24614
|
+
});
|
|
24531
24615
|
}
|
|
24532
24616
|
async function maybeStartRepoSync(input) {
|
|
24533
24617
|
const env = input.env ?? process.env;
|
|
@@ -25283,6 +25367,7 @@ async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
|
25283
25367
|
stopHostedConnectors,
|
|
25284
25368
|
stopK8sSubstrate,
|
|
25285
25369
|
stopRepoSync,
|
|
25370
|
+
...stopRepoSync.syncNow ? { syncRepos: stopRepoSync.syncNow } : {},
|
|
25286
25371
|
detachEvents,
|
|
25287
25372
|
status: "active"
|
|
25288
25373
|
};
|
|
@@ -25554,7 +25639,14 @@ async function startDaemon(opts = {}) {
|
|
|
25554
25639
|
// through the same resolved home the slot bootstrap read it from, so a
|
|
25555
25640
|
// daemon given an explicit NEAT_HOME serves status for the same file it
|
|
25556
25641
|
// polls.
|
|
25557
|
-
connectorsHome: home
|
|
25642
|
+
connectorsHome: home,
|
|
25643
|
+
// #1293 — "sync this project's repos now". Looked up per request: the
|
|
25644
|
+
// slot, and with it the trigger, appears once bootstrap has run.
|
|
25645
|
+
repoSync: (name) => slots.get(name)?.syncRepos,
|
|
25646
|
+
// OTLP answers on this listener too, so a host that routes a single
|
|
25647
|
+
// port still delivers a tenant app's spans. Bound late: the receiver
|
|
25648
|
+
// is built below, once the REST bind has succeeded.
|
|
25649
|
+
otlpReceiver: () => otlpApp ?? void 0
|
|
25558
25650
|
});
|
|
25559
25651
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
25560
25652
|
console.log(
|
|
@@ -25735,7 +25827,9 @@ async function startDaemon(opts = {}) {
|
|
|
25735
25827
|
}
|
|
25736
25828
|
});
|
|
25737
25829
|
otlpAddress = await listenSteppingOtlp(otlpApp, otlpPort, host);
|
|
25738
|
-
console.log(
|
|
25830
|
+
console.log(
|
|
25831
|
+
`neatd: OTLP listening on http://${host}:${portFromListenAddress(otlpAddress, otlpPort)}/v1/traces (also at /v1/traces on the REST port)`
|
|
25832
|
+
);
|
|
25739
25833
|
} catch (err) {
|
|
25740
25834
|
for (const slot of slots.values()) {
|
|
25741
25835
|
teardownSlot(slot);
|