@neat.is/core 0.6.2-dev.20260723 → 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/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MSZ4NALT.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-AOWANF2K.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
43
|
} from "./chunk-OY7FGJAX.js";
|
package/dist/neatd.cjs
CHANGED
|
@@ -5631,7 +5631,7 @@ function keywordArrayStrings(argsNode, key) {
|
|
|
5631
5631
|
}
|
|
5632
5632
|
return [];
|
|
5633
5633
|
}
|
|
5634
|
-
function
|
|
5634
|
+
function collectPythonRouterPrefixes(root) {
|
|
5635
5635
|
const prefixes = /* @__PURE__ */ new Map();
|
|
5636
5636
|
walk(root, (node) => {
|
|
5637
5637
|
if (node.type !== "assignment") return;
|
|
@@ -5640,7 +5640,8 @@ function collectApiRouterPrefixes(root) {
|
|
|
5640
5640
|
const fn = right.childForFieldName("function");
|
|
5641
5641
|
if (!fn) return;
|
|
5642
5642
|
const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
|
|
5643
|
-
|
|
5643
|
+
const prefixKey = ctor === "APIRouter" ? "prefix" : ctor === "Blueprint" ? "url_prefix" : null;
|
|
5644
|
+
if (!prefixKey) return;
|
|
5644
5645
|
const left = node.childForFieldName("left");
|
|
5645
5646
|
if (!left || left.type !== "identifier") return;
|
|
5646
5647
|
const args = right.childForFieldName("arguments");
|
|
@@ -5648,7 +5649,7 @@ function collectApiRouterPrefixes(root) {
|
|
|
5648
5649
|
for (let i = 0; i < args.namedChildCount; i++) {
|
|
5649
5650
|
const arg = args.namedChild(i);
|
|
5650
5651
|
if (arg?.type !== "keyword_argument") continue;
|
|
5651
|
-
if (arg.childForFieldName("name")?.text !==
|
|
5652
|
+
if (arg.childForFieldName("name")?.text !== prefixKey) continue;
|
|
5652
5653
|
const val = arg.childForFieldName("value");
|
|
5653
5654
|
const p = val ? pyStaticStringText(val) : null;
|
|
5654
5655
|
if (p !== null) prefixes.set(left.text, p);
|
|
@@ -5656,9 +5657,9 @@ function collectApiRouterPrefixes(root) {
|
|
|
5656
5657
|
});
|
|
5657
5658
|
return prefixes;
|
|
5658
5659
|
}
|
|
5659
|
-
function
|
|
5660
|
+
function pythonRoutesFromSource(source, parser, framework) {
|
|
5660
5661
|
const tree = parseSource2(parser, source);
|
|
5661
|
-
const prefixes =
|
|
5662
|
+
const prefixes = collectPythonRouterPrefixes(tree.rootNode);
|
|
5662
5663
|
const out = [];
|
|
5663
5664
|
walk(tree.rootNode, (node) => {
|
|
5664
5665
|
if (node.type !== "decorator") return;
|
|
@@ -5669,7 +5670,9 @@ function fastapiRoutesFromSource(source, parser) {
|
|
|
5669
5670
|
const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
|
|
5670
5671
|
if (!method) return;
|
|
5671
5672
|
const isVerb = FASTAPI_METHODS.has(method);
|
|
5672
|
-
|
|
5673
|
+
const isFlaskRoute = method === "route";
|
|
5674
|
+
const isApiRoute = method === "api_route";
|
|
5675
|
+
if (!isVerb && !isFlaskRoute && !isApiRoute) return;
|
|
5673
5676
|
const args = call.childForFieldName("arguments");
|
|
5674
5677
|
const first = args?.namedChild(0);
|
|
5675
5678
|
if (!first || first.type !== "string") return;
|
|
@@ -5680,17 +5683,41 @@ function fastapiRoutesFromSource(source, parser) {
|
|
|
5680
5683
|
const pathTemplate = canonicalizeTemplate(prefix + rawPath);
|
|
5681
5684
|
const line = node.startPosition.row + 1;
|
|
5682
5685
|
if (isVerb) {
|
|
5683
|
-
out.push({ method: method.toUpperCase(), pathTemplate, line, framework
|
|
5686
|
+
out.push({ method: method.toUpperCase(), pathTemplate, line, framework });
|
|
5684
5687
|
return;
|
|
5685
5688
|
}
|
|
5686
5689
|
const methods = keywordArrayStrings(args, "methods");
|
|
5687
|
-
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5690
|
+
const list = methods.length > 0 ? methods : isFlaskRoute ? ["GET"] : ["ALL"];
|
|
5688
5691
|
for (const m of list) {
|
|
5692
|
+
out.push({ method: m === "ALL" ? "ALL" : m.toUpperCase(), pathTemplate, line, framework });
|
|
5693
|
+
}
|
|
5694
|
+
});
|
|
5695
|
+
return out;
|
|
5696
|
+
}
|
|
5697
|
+
function djangoRoutesFromSource(source, parser) {
|
|
5698
|
+
const tree = parseSource2(parser, source);
|
|
5699
|
+
const out = [];
|
|
5700
|
+
walk(tree.rootNode, (node) => {
|
|
5701
|
+
if (node.type !== "assignment") return;
|
|
5702
|
+
if (node.childForFieldName("left")?.text !== "urlpatterns") return;
|
|
5703
|
+
const list = node.childForFieldName("right");
|
|
5704
|
+
if (!list || list.type !== "list") return;
|
|
5705
|
+
for (let i = 0; i < list.namedChildCount; i++) {
|
|
5706
|
+
const el = list.namedChild(i);
|
|
5707
|
+
if (el?.type !== "call") continue;
|
|
5708
|
+
if (el.childForFieldName("function")?.text !== "path") continue;
|
|
5709
|
+
const args = el.childForFieldName("arguments");
|
|
5710
|
+
const first = args?.namedChild(0);
|
|
5711
|
+
if (first?.type !== "string") continue;
|
|
5712
|
+
const raw = pyStaticStringText(first);
|
|
5713
|
+
if (raw === null) continue;
|
|
5714
|
+
const second = args?.namedChild(1);
|
|
5715
|
+
if (second?.type === "call" && second.childForFieldName("function")?.text === "include") continue;
|
|
5689
5716
|
out.push({
|
|
5690
|
-
method:
|
|
5691
|
-
pathTemplate,
|
|
5692
|
-
line,
|
|
5693
|
-
framework: "
|
|
5717
|
+
method: "ALL",
|
|
5718
|
+
pathTemplate: canonicalizeTemplate(raw),
|
|
5719
|
+
line: el.startPosition.row + 1,
|
|
5720
|
+
framework: "django"
|
|
5694
5721
|
});
|
|
5695
5722
|
}
|
|
5696
5723
|
});
|
|
@@ -5711,7 +5738,10 @@ async function addRoutes(graph, services) {
|
|
|
5711
5738
|
const hasHono = deps["hono"] !== void 0;
|
|
5712
5739
|
const hasNext = deps["next"] !== void 0;
|
|
5713
5740
|
const hasFastapi = deps["fastapi"] !== void 0;
|
|
5714
|
-
|
|
5741
|
+
const hasFlask = deps["flask"] !== void 0;
|
|
5742
|
+
const hasDjango = deps["django"] !== void 0;
|
|
5743
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi && !hasFlask && !hasDjango)
|
|
5744
|
+
continue;
|
|
5715
5745
|
const files = await loadSourceFiles(service.dir);
|
|
5716
5746
|
for (const file of files) {
|
|
5717
5747
|
if (isTestPath(file.path)) continue;
|
|
@@ -5722,7 +5752,8 @@ async function addRoutes(graph, services) {
|
|
|
5722
5752
|
let routes;
|
|
5723
5753
|
try {
|
|
5724
5754
|
if (isPy) {
|
|
5725
|
-
routes = hasFastapi ?
|
|
5755
|
+
routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
|
|
5756
|
+
if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
|
|
5726
5757
|
} else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5727
5758
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5728
5759
|
} else if (hasExpress || hasFastify || hasHono) {
|
|
@@ -10267,6 +10298,9 @@ async function buildApi(opts) {
|
|
|
10267
10298
|
// src/daemon.ts
|
|
10268
10299
|
init_otel();
|
|
10269
10300
|
|
|
10301
|
+
// src/connectors/registry.ts
|
|
10302
|
+
init_cjs_shims();
|
|
10303
|
+
|
|
10270
10304
|
// src/connectors/index.ts
|
|
10271
10305
|
init_cjs_shims();
|
|
10272
10306
|
var import_types36 = require("@neat.is/types");
|
|
@@ -10378,6 +10412,7 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
10378
10412
|
}
|
|
10379
10413
|
})();
|
|
10380
10414
|
};
|
|
10415
|
+
tick();
|
|
10381
10416
|
const interval = setInterval(tick, intervalMs);
|
|
10382
10417
|
if (typeof interval.unref === "function") interval.unref();
|
|
10383
10418
|
return () => {
|
|
@@ -10386,9 +10421,6 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
10386
10421
|
};
|
|
10387
10422
|
}
|
|
10388
10423
|
|
|
10389
|
-
// src/connectors/registry.ts
|
|
10390
|
-
init_cjs_shims();
|
|
10391
|
-
|
|
10392
10424
|
// src/connectors/junction.ts
|
|
10393
10425
|
init_cjs_shims();
|
|
10394
10426
|
var buckets = /* @__PURE__ */ new Map();
|
|
@@ -10996,17 +11028,22 @@ function readRailwayToken(credentials) {
|
|
|
10996
11028
|
}
|
|
10997
11029
|
return token;
|
|
10998
11030
|
}
|
|
10999
|
-
|
|
11000
|
-
|
|
11031
|
+
var RAILWAY_AUTH_STYLES = ["bearer", "project-access-token"];
|
|
11032
|
+
function railwayAuthHeader(style, token) {
|
|
11033
|
+
return style === "bearer" ? { Authorization: `Bearer ${token}` } : { "Project-Access-Token": token };
|
|
11001
11034
|
}
|
|
11002
|
-
|
|
11035
|
+
var resolvedRailwayAuthStyle = /* @__PURE__ */ new Map();
|
|
11036
|
+
function isRailwayNotAuthorized(err) {
|
|
11037
|
+
return err instanceof Error && /not authorized/i.test(err.message);
|
|
11038
|
+
}
|
|
11039
|
+
async function railwayGraphQLOnce(apiUrl, style, token, query, variables, accountKey, fetchImpl) {
|
|
11003
11040
|
const res = await junctionFetch(
|
|
11004
11041
|
apiUrl,
|
|
11005
11042
|
{
|
|
11006
11043
|
method: "POST",
|
|
11007
11044
|
headers: {
|
|
11008
11045
|
"Content-Type": "application/json",
|
|
11009
|
-
...
|
|
11046
|
+
...railwayAuthHeader(style, token)
|
|
11010
11047
|
},
|
|
11011
11048
|
body: JSON.stringify({ query, variables })
|
|
11012
11049
|
},
|
|
@@ -11022,6 +11059,26 @@ async function railwayGraphQL(apiUrl, token, query, variables, accountKey, fetch
|
|
|
11022
11059
|
if (!body.data) throw new Error("Railway GraphQL response carried no data");
|
|
11023
11060
|
return body.data;
|
|
11024
11061
|
}
|
|
11062
|
+
async function railwayGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
11063
|
+
const known = resolvedRailwayAuthStyle.get(token);
|
|
11064
|
+
const styles = known ? [known] : RAILWAY_AUTH_STYLES;
|
|
11065
|
+
let lastNotAuthorized;
|
|
11066
|
+
for (let i = 0; i < styles.length; i++) {
|
|
11067
|
+
const style = styles[i];
|
|
11068
|
+
try {
|
|
11069
|
+
const data = await railwayGraphQLOnce(apiUrl, style, token, query, variables, accountKey, fetchImpl);
|
|
11070
|
+
resolvedRailwayAuthStyle.set(token, style);
|
|
11071
|
+
return data;
|
|
11072
|
+
} catch (err) {
|
|
11073
|
+
if (isRailwayNotAuthorized(err) && i < styles.length - 1) {
|
|
11074
|
+
lastNotAuthorized = err;
|
|
11075
|
+
continue;
|
|
11076
|
+
}
|
|
11077
|
+
throw err;
|
|
11078
|
+
}
|
|
11079
|
+
}
|
|
11080
|
+
throw lastNotAuthorized ?? new Error("Railway GraphQL: no auth style resolved");
|
|
11081
|
+
}
|
|
11025
11082
|
var HTTP_LOGS_QUERY = `
|
|
11026
11083
|
query HttpLogs($deploymentId: String!, $startDate: String, $endDate: String, $limit: Int) {
|
|
11027
11084
|
httpLogs(deploymentId: $deploymentId, startDate: $startDate, endDate: $endDate, limit: $limit) {
|
|
@@ -12128,6 +12185,27 @@ async function loadConnectorRegistrations(input) {
|
|
|
12128
12185
|
}
|
|
12129
12186
|
return registrations;
|
|
12130
12187
|
}
|
|
12188
|
+
async function startConnectorPolling(input) {
|
|
12189
|
+
const fileConnectors = input.home ? await loadConnectorRegistrations({
|
|
12190
|
+
project: input.project,
|
|
12191
|
+
graph: input.graph,
|
|
12192
|
+
home: input.home,
|
|
12193
|
+
...input.onSkip ? { onSkip: input.onSkip } : {}
|
|
12194
|
+
}) : [];
|
|
12195
|
+
const all = [...input.extra ?? [], ...fileConnectors];
|
|
12196
|
+
const stopFns = all.map(
|
|
12197
|
+
(registration) => startConnectorPollLoop(
|
|
12198
|
+
registration.connector,
|
|
12199
|
+
{ projectDir: input.projectDir, credentials: registration.credentials },
|
|
12200
|
+
input.graph,
|
|
12201
|
+
registration.resolveTarget,
|
|
12202
|
+
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
12203
|
+
)
|
|
12204
|
+
);
|
|
12205
|
+
return () => {
|
|
12206
|
+
for (const stop of stopFns) stop();
|
|
12207
|
+
};
|
|
12208
|
+
}
|
|
12131
12209
|
|
|
12132
12210
|
// src/daemon.ts
|
|
12133
12211
|
init_auth();
|
|
@@ -12333,31 +12411,16 @@ async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
|
12333
12411
|
staleEventsPath: paths.staleEventsPath,
|
|
12334
12412
|
project: entry2.name
|
|
12335
12413
|
});
|
|
12336
|
-
const
|
|
12414
|
+
const stopConnectors = await startConnectorPolling({
|
|
12337
12415
|
project: entry2.name,
|
|
12338
12416
|
graph,
|
|
12339
|
-
|
|
12417
|
+
projectDir: entry2.path,
|
|
12418
|
+
...neatHome4 ? { home: neatHome4 } : {},
|
|
12419
|
+
extra: connectors,
|
|
12340
12420
|
onSkip: (skipped, reason) => console.warn(
|
|
12341
12421
|
`neatd: connector "${skipped.id}" (${skipped.provider}) skipped for project "${entry2.name}" \u2014 ${reason}`
|
|
12342
12422
|
)
|
|
12343
|
-
})
|
|
12344
|
-
const allConnectors = [...connectors, ...fileConnectors];
|
|
12345
|
-
const stopFns = allConnectors.map(
|
|
12346
|
-
(registration) => startConnectorPollLoop(
|
|
12347
|
-
registration.connector,
|
|
12348
|
-
{ projectDir: entry2.path, credentials: registration.credentials },
|
|
12349
|
-
graph,
|
|
12350
|
-
registration.resolveTarget,
|
|
12351
|
-
// `connectorId` is threaded through so every tick lands in the
|
|
12352
|
-
// in-process status tracker the connector-status endpoint reads
|
|
12353
|
-
// (ADR-136). Undefined for a programmatic registration, which records
|
|
12354
|
-
// nothing.
|
|
12355
|
-
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
12356
|
-
)
|
|
12357
|
-
);
|
|
12358
|
-
const stopConnectors = () => {
|
|
12359
|
-
for (const stop of stopFns) stop();
|
|
12360
|
-
};
|
|
12423
|
+
});
|
|
12361
12424
|
await touchLastSeen(entry2.name).catch(() => {
|
|
12362
12425
|
});
|
|
12363
12426
|
return {
|