@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.cjs
CHANGED
|
@@ -5671,7 +5671,7 @@ function keywordArrayStrings(argsNode, key) {
|
|
|
5671
5671
|
}
|
|
5672
5672
|
return [];
|
|
5673
5673
|
}
|
|
5674
|
-
function
|
|
5674
|
+
function collectPythonRouterPrefixes(root) {
|
|
5675
5675
|
const prefixes = /* @__PURE__ */ new Map();
|
|
5676
5676
|
walk(root, (node) => {
|
|
5677
5677
|
if (node.type !== "assignment") return;
|
|
@@ -5680,7 +5680,8 @@ function collectApiRouterPrefixes(root) {
|
|
|
5680
5680
|
const fn = right.childForFieldName("function");
|
|
5681
5681
|
if (!fn) return;
|
|
5682
5682
|
const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
|
|
5683
|
-
|
|
5683
|
+
const prefixKey = ctor === "APIRouter" ? "prefix" : ctor === "Blueprint" ? "url_prefix" : null;
|
|
5684
|
+
if (!prefixKey) return;
|
|
5684
5685
|
const left = node.childForFieldName("left");
|
|
5685
5686
|
if (!left || left.type !== "identifier") return;
|
|
5686
5687
|
const args = right.childForFieldName("arguments");
|
|
@@ -5688,7 +5689,7 @@ function collectApiRouterPrefixes(root) {
|
|
|
5688
5689
|
for (let i = 0; i < args.namedChildCount; i++) {
|
|
5689
5690
|
const arg = args.namedChild(i);
|
|
5690
5691
|
if (arg?.type !== "keyword_argument") continue;
|
|
5691
|
-
if (arg.childForFieldName("name")?.text !==
|
|
5692
|
+
if (arg.childForFieldName("name")?.text !== prefixKey) continue;
|
|
5692
5693
|
const val = arg.childForFieldName("value");
|
|
5693
5694
|
const p = val ? pyStaticStringText(val) : null;
|
|
5694
5695
|
if (p !== null) prefixes.set(left.text, p);
|
|
@@ -5696,9 +5697,9 @@ function collectApiRouterPrefixes(root) {
|
|
|
5696
5697
|
});
|
|
5697
5698
|
return prefixes;
|
|
5698
5699
|
}
|
|
5699
|
-
function
|
|
5700
|
+
function pythonRoutesFromSource(source, parser, framework) {
|
|
5700
5701
|
const tree = parseSource2(parser, source);
|
|
5701
|
-
const prefixes =
|
|
5702
|
+
const prefixes = collectPythonRouterPrefixes(tree.rootNode);
|
|
5702
5703
|
const out = [];
|
|
5703
5704
|
walk(tree.rootNode, (node) => {
|
|
5704
5705
|
if (node.type !== "decorator") return;
|
|
@@ -5709,7 +5710,9 @@ function fastapiRoutesFromSource(source, parser) {
|
|
|
5709
5710
|
const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
|
|
5710
5711
|
if (!method) return;
|
|
5711
5712
|
const isVerb = FASTAPI_METHODS.has(method);
|
|
5712
|
-
|
|
5713
|
+
const isFlaskRoute = method === "route";
|
|
5714
|
+
const isApiRoute = method === "api_route";
|
|
5715
|
+
if (!isVerb && !isFlaskRoute && !isApiRoute) return;
|
|
5713
5716
|
const args = call.childForFieldName("arguments");
|
|
5714
5717
|
const first = args?.namedChild(0);
|
|
5715
5718
|
if (!first || first.type !== "string") return;
|
|
@@ -5720,17 +5723,41 @@ function fastapiRoutesFromSource(source, parser) {
|
|
|
5720
5723
|
const pathTemplate = canonicalizeTemplate(prefix + rawPath);
|
|
5721
5724
|
const line = node.startPosition.row + 1;
|
|
5722
5725
|
if (isVerb) {
|
|
5723
|
-
out.push({ method: method.toUpperCase(), pathTemplate, line, framework
|
|
5726
|
+
out.push({ method: method.toUpperCase(), pathTemplate, line, framework });
|
|
5724
5727
|
return;
|
|
5725
5728
|
}
|
|
5726
5729
|
const methods = keywordArrayStrings(args, "methods");
|
|
5727
|
-
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5730
|
+
const list = methods.length > 0 ? methods : isFlaskRoute ? ["GET"] : ["ALL"];
|
|
5728
5731
|
for (const m of list) {
|
|
5732
|
+
out.push({ method: m === "ALL" ? "ALL" : m.toUpperCase(), pathTemplate, line, framework });
|
|
5733
|
+
}
|
|
5734
|
+
});
|
|
5735
|
+
return out;
|
|
5736
|
+
}
|
|
5737
|
+
function djangoRoutesFromSource(source, parser) {
|
|
5738
|
+
const tree = parseSource2(parser, source);
|
|
5739
|
+
const out = [];
|
|
5740
|
+
walk(tree.rootNode, (node) => {
|
|
5741
|
+
if (node.type !== "assignment") return;
|
|
5742
|
+
if (node.childForFieldName("left")?.text !== "urlpatterns") return;
|
|
5743
|
+
const list = node.childForFieldName("right");
|
|
5744
|
+
if (!list || list.type !== "list") return;
|
|
5745
|
+
for (let i = 0; i < list.namedChildCount; i++) {
|
|
5746
|
+
const el = list.namedChild(i);
|
|
5747
|
+
if (el?.type !== "call") continue;
|
|
5748
|
+
if (el.childForFieldName("function")?.text !== "path") continue;
|
|
5749
|
+
const args = el.childForFieldName("arguments");
|
|
5750
|
+
const first = args?.namedChild(0);
|
|
5751
|
+
if (first?.type !== "string") continue;
|
|
5752
|
+
const raw = pyStaticStringText(first);
|
|
5753
|
+
if (raw === null) continue;
|
|
5754
|
+
const second = args?.namedChild(1);
|
|
5755
|
+
if (second?.type === "call" && second.childForFieldName("function")?.text === "include") continue;
|
|
5729
5756
|
out.push({
|
|
5730
|
-
method:
|
|
5731
|
-
pathTemplate,
|
|
5732
|
-
line,
|
|
5733
|
-
framework: "
|
|
5757
|
+
method: "ALL",
|
|
5758
|
+
pathTemplate: canonicalizeTemplate(raw),
|
|
5759
|
+
line: el.startPosition.row + 1,
|
|
5760
|
+
framework: "django"
|
|
5734
5761
|
});
|
|
5735
5762
|
}
|
|
5736
5763
|
});
|
|
@@ -5751,7 +5778,10 @@ async function addRoutes(graph, services) {
|
|
|
5751
5778
|
const hasHono = deps["hono"] !== void 0;
|
|
5752
5779
|
const hasNext = deps["next"] !== void 0;
|
|
5753
5780
|
const hasFastapi = deps["fastapi"] !== void 0;
|
|
5754
|
-
|
|
5781
|
+
const hasFlask = deps["flask"] !== void 0;
|
|
5782
|
+
const hasDjango = deps["django"] !== void 0;
|
|
5783
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi && !hasFlask && !hasDjango)
|
|
5784
|
+
continue;
|
|
5755
5785
|
const files = await loadSourceFiles(service.dir);
|
|
5756
5786
|
for (const file of files) {
|
|
5757
5787
|
if (isTestPath(file.path)) continue;
|
|
@@ -5762,7 +5792,8 @@ async function addRoutes(graph, services) {
|
|
|
5762
5792
|
let routes;
|
|
5763
5793
|
try {
|
|
5764
5794
|
if (isPy) {
|
|
5765
|
-
routes = hasFastapi ?
|
|
5795
|
+
routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
|
|
5796
|
+
if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
|
|
5766
5797
|
} else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5767
5798
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5768
5799
|
} else if (hasExpress || hasFastify || hasHono) {
|
|
@@ -10373,6 +10404,9 @@ var import_node_path52 = __toESM(require("path"), 1);
|
|
|
10373
10404
|
var import_node_module = require("module");
|
|
10374
10405
|
init_otel();
|
|
10375
10406
|
|
|
10407
|
+
// src/connectors/registry.ts
|
|
10408
|
+
init_cjs_shims();
|
|
10409
|
+
|
|
10376
10410
|
// src/connectors/index.ts
|
|
10377
10411
|
init_cjs_shims();
|
|
10378
10412
|
var import_types36 = require("@neat.is/types");
|
|
@@ -10484,6 +10518,7 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
10484
10518
|
}
|
|
10485
10519
|
})();
|
|
10486
10520
|
};
|
|
10521
|
+
tick();
|
|
10487
10522
|
const interval = setInterval(tick, intervalMs);
|
|
10488
10523
|
if (typeof interval.unref === "function") interval.unref();
|
|
10489
10524
|
return () => {
|
|
@@ -10492,9 +10527,6 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
10492
10527
|
};
|
|
10493
10528
|
}
|
|
10494
10529
|
|
|
10495
|
-
// src/connectors/registry.ts
|
|
10496
|
-
init_cjs_shims();
|
|
10497
|
-
|
|
10498
10530
|
// src/connectors/junction.ts
|
|
10499
10531
|
init_cjs_shims();
|
|
10500
10532
|
var buckets = /* @__PURE__ */ new Map();
|
|
@@ -11102,17 +11134,22 @@ function readRailwayToken(credentials) {
|
|
|
11102
11134
|
}
|
|
11103
11135
|
return token;
|
|
11104
11136
|
}
|
|
11105
|
-
|
|
11106
|
-
|
|
11137
|
+
var RAILWAY_AUTH_STYLES = ["bearer", "project-access-token"];
|
|
11138
|
+
function railwayAuthHeader(style, token) {
|
|
11139
|
+
return style === "bearer" ? { Authorization: `Bearer ${token}` } : { "Project-Access-Token": token };
|
|
11107
11140
|
}
|
|
11108
|
-
|
|
11141
|
+
var resolvedRailwayAuthStyle = /* @__PURE__ */ new Map();
|
|
11142
|
+
function isRailwayNotAuthorized(err) {
|
|
11143
|
+
return err instanceof Error && /not authorized/i.test(err.message);
|
|
11144
|
+
}
|
|
11145
|
+
async function railwayGraphQLOnce(apiUrl, style, token, query, variables, accountKey, fetchImpl) {
|
|
11109
11146
|
const res = await junctionFetch(
|
|
11110
11147
|
apiUrl,
|
|
11111
11148
|
{
|
|
11112
11149
|
method: "POST",
|
|
11113
11150
|
headers: {
|
|
11114
11151
|
"Content-Type": "application/json",
|
|
11115
|
-
...
|
|
11152
|
+
...railwayAuthHeader(style, token)
|
|
11116
11153
|
},
|
|
11117
11154
|
body: JSON.stringify({ query, variables })
|
|
11118
11155
|
},
|
|
@@ -11128,6 +11165,26 @@ async function railwayGraphQL(apiUrl, token, query, variables, accountKey, fetch
|
|
|
11128
11165
|
if (!body.data) throw new Error("Railway GraphQL response carried no data");
|
|
11129
11166
|
return body.data;
|
|
11130
11167
|
}
|
|
11168
|
+
async function railwayGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
11169
|
+
const known = resolvedRailwayAuthStyle.get(token);
|
|
11170
|
+
const styles = known ? [known] : RAILWAY_AUTH_STYLES;
|
|
11171
|
+
let lastNotAuthorized;
|
|
11172
|
+
for (let i = 0; i < styles.length; i++) {
|
|
11173
|
+
const style = styles[i];
|
|
11174
|
+
try {
|
|
11175
|
+
const data = await railwayGraphQLOnce(apiUrl, style, token, query, variables, accountKey, fetchImpl);
|
|
11176
|
+
resolvedRailwayAuthStyle.set(token, style);
|
|
11177
|
+
return data;
|
|
11178
|
+
} catch (err) {
|
|
11179
|
+
if (isRailwayNotAuthorized(err) && i < styles.length - 1) {
|
|
11180
|
+
lastNotAuthorized = err;
|
|
11181
|
+
continue;
|
|
11182
|
+
}
|
|
11183
|
+
throw err;
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
throw lastNotAuthorized ?? new Error("Railway GraphQL: no auth style resolved");
|
|
11187
|
+
}
|
|
11131
11188
|
var HTTP_LOGS_QUERY = `
|
|
11132
11189
|
query HttpLogs($deploymentId: String!, $startDate: String, $endDate: String, $limit: Int) {
|
|
11133
11190
|
httpLogs(deploymentId: $deploymentId, startDate: $startDate, endDate: $endDate, limit: $limit) {
|
|
@@ -12234,6 +12291,27 @@ async function loadConnectorRegistrations(input) {
|
|
|
12234
12291
|
}
|
|
12235
12292
|
return registrations;
|
|
12236
12293
|
}
|
|
12294
|
+
async function startConnectorPolling(input) {
|
|
12295
|
+
const fileConnectors = input.home ? await loadConnectorRegistrations({
|
|
12296
|
+
project: input.project,
|
|
12297
|
+
graph: input.graph,
|
|
12298
|
+
home: input.home,
|
|
12299
|
+
...input.onSkip ? { onSkip: input.onSkip } : {}
|
|
12300
|
+
}) : [];
|
|
12301
|
+
const all = [...input.extra ?? [], ...fileConnectors];
|
|
12302
|
+
const stopFns = all.map(
|
|
12303
|
+
(registration) => startConnectorPollLoop(
|
|
12304
|
+
registration.connector,
|
|
12305
|
+
{ projectDir: input.projectDir, credentials: registration.credentials },
|
|
12306
|
+
input.graph,
|
|
12307
|
+
registration.resolveTarget,
|
|
12308
|
+
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
12309
|
+
)
|
|
12310
|
+
);
|
|
12311
|
+
return () => {
|
|
12312
|
+
for (const stop of stopFns) stop();
|
|
12313
|
+
};
|
|
12314
|
+
}
|
|
12237
12315
|
|
|
12238
12316
|
// src/daemon.ts
|
|
12239
12317
|
init_auth();
|
|
@@ -12425,31 +12503,16 @@ async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
|
12425
12503
|
staleEventsPath: paths.staleEventsPath,
|
|
12426
12504
|
project: entry.name
|
|
12427
12505
|
});
|
|
12428
|
-
const
|
|
12506
|
+
const stopConnectors = await startConnectorPolling({
|
|
12429
12507
|
project: entry.name,
|
|
12430
12508
|
graph,
|
|
12431
|
-
|
|
12509
|
+
projectDir: entry.path,
|
|
12510
|
+
...neatHome3 ? { home: neatHome3 } : {},
|
|
12511
|
+
extra: connectors,
|
|
12432
12512
|
onSkip: (skipped, reason) => console.warn(
|
|
12433
12513
|
`neatd: connector "${skipped.id}" (${skipped.provider}) skipped for project "${entry.name}" \u2014 ${reason}`
|
|
12434
12514
|
)
|
|
12435
|
-
})
|
|
12436
|
-
const allConnectors = [...connectors, ...fileConnectors];
|
|
12437
|
-
const stopFns = allConnectors.map(
|
|
12438
|
-
(registration) => startConnectorPollLoop(
|
|
12439
|
-
registration.connector,
|
|
12440
|
-
{ projectDir: entry.path, credentials: registration.credentials },
|
|
12441
|
-
graph,
|
|
12442
|
-
registration.resolveTarget,
|
|
12443
|
-
// `connectorId` is threaded through so every tick lands in the
|
|
12444
|
-
// in-process status tracker the connector-status endpoint reads
|
|
12445
|
-
// (ADR-136). Undefined for a programmatic registration, which records
|
|
12446
|
-
// nothing.
|
|
12447
|
-
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
12448
|
-
)
|
|
12449
|
-
);
|
|
12450
|
-
const stopConnectors = () => {
|
|
12451
|
-
for (const stop of stopFns) stop();
|
|
12452
|
-
};
|
|
12515
|
+
});
|
|
12453
12516
|
await touchLastSeen(entry.name).catch(() => {
|
|
12454
12517
|
});
|
|
12455
12518
|
return {
|