@neat.is/core 0.5.0 → 0.5.1-dev.20260719
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-QQDZSIY3.js → chunk-BI3XKGVG.js} +62 -2
- package/dist/chunk-BI3XKGVG.js.map +1 -0
- package/dist/{chunk-5MGHWP6R.js → chunk-O4PCNQFG.js} +39 -12
- package/dist/chunk-O4PCNQFG.js.map +1 -0
- package/dist/cli.cjs +96 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +111 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +111 -24
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +61 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5MGHWP6R.js.map +0 -1
- package/dist/chunk-QQDZSIY3.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveNeatVersion,
|
|
10
10
|
validateConnectorEntry,
|
|
11
11
|
writeDaemonRecord
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-O4PCNQFG.js";
|
|
13
13
|
import {
|
|
14
14
|
buildSearchIndex
|
|
15
15
|
} from "./chunk-BIY46Q6U.js";
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
startPersistLoop,
|
|
69
69
|
startStalenessLoop,
|
|
70
70
|
upsertConnectorEntry
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-BI3XKGVG.js";
|
|
72
72
|
import {
|
|
73
73
|
startOtelGrpcReceiver
|
|
74
74
|
} from "./chunk-I72HTUOG.js";
|
package/dist/index.cjs
CHANGED
|
@@ -2664,6 +2664,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
2664
2664
|
}
|
|
2665
2665
|
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
2666
2666
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
2667
|
+
const grain = source.startsWith("file:") ? "file" : "service";
|
|
2667
2668
|
const id = makeObservedEdgeId(type, source, target);
|
|
2668
2669
|
if (graph.hasEdge(id)) {
|
|
2669
2670
|
const existing = graph.getEdgeAttributes(id);
|
|
@@ -2680,7 +2681,9 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
2680
2681
|
lastObserved: ts,
|
|
2681
2682
|
callCount: newSpanCount,
|
|
2682
2683
|
signal: newSignal,
|
|
2683
|
-
confidence: (0, import_types3.confidenceForObservedSignal)(newSignal)
|
|
2684
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(newSignal),
|
|
2685
|
+
grain
|
|
2686
|
+
// backfills legacy edges that predate ADR-142
|
|
2684
2687
|
};
|
|
2685
2688
|
graph.replaceEdgeAttributes(id, updated);
|
|
2686
2689
|
return { edge: updated, created: false };
|
|
@@ -2700,6 +2703,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
2700
2703
|
lastObserved: ts,
|
|
2701
2704
|
callCount: 1,
|
|
2702
2705
|
signal,
|
|
2706
|
+
grain,
|
|
2703
2707
|
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2704
2708
|
// Only set when code.filepath was present on the span — never fabricated.
|
|
2705
2709
|
...evidence ? { evidence } : {}
|
|
@@ -4786,6 +4790,15 @@ async function parse4(serviceDir) {
|
|
|
4786
4790
|
const config = parseConnectionString(urlMatch[1]);
|
|
4787
4791
|
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4788
4792
|
}
|
|
4793
|
+
const urlEnvMatch = content.match(
|
|
4794
|
+
/(?:url|connectionString)\s*:\s*process\.env(?:\.([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])/
|
|
4795
|
+
);
|
|
4796
|
+
if (urlEnvMatch) {
|
|
4797
|
+
const varName = urlEnvMatch[1] ?? urlEnvMatch[2];
|
|
4798
|
+
const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null;
|
|
4799
|
+
const config = resolved ? parseConnectionString(resolved) : null;
|
|
4800
|
+
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4801
|
+
}
|
|
4789
4802
|
const hostMatch = content.match(/host\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
4790
4803
|
if (hostMatch) {
|
|
4791
4804
|
const portMatch = content.match(/port\s*:\s*(\d+)/);
|
|
@@ -4839,6 +4852,15 @@ async function parse5(serviceDir) {
|
|
|
4839
4852
|
const config = parseConnectionString(urlMatch[1]);
|
|
4840
4853
|
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4841
4854
|
}
|
|
4855
|
+
const urlEnvMatch = content.match(
|
|
4856
|
+
/connection\s*:\s*process\.env(?:\.([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])/
|
|
4857
|
+
);
|
|
4858
|
+
if (urlEnvMatch) {
|
|
4859
|
+
const varName = urlEnvMatch[1] ?? urlEnvMatch[2];
|
|
4860
|
+
const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null;
|
|
4861
|
+
const config = resolved ? parseConnectionString(resolved) : null;
|
|
4862
|
+
if (config) return [{ ...config, sourceFile: filePath }];
|
|
4863
|
+
}
|
|
4842
4864
|
const host = content.match(/host\s*:\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
4843
4865
|
if (host) {
|
|
4844
4866
|
const port = content.match(/port\s*:\s*(\d+)/)?.[1];
|
|
@@ -6387,6 +6409,16 @@ function constructorMatchesImport(name, ctx) {
|
|
|
6387
6409
|
if (name === "createClient") return ctx.hasSupabaseJs;
|
|
6388
6410
|
return ctx.hasSupabaseSsr;
|
|
6389
6411
|
}
|
|
6412
|
+
var SUPABASE_CLIENT_ASSIGN_RE = /(?:const|let|var)\s+(\w+)\s*=\s*(?:await\s+)?(createClient|createServerClient|createBrowserClient)\s*\(/g;
|
|
6413
|
+
function supabaseClientVars(content, ctx) {
|
|
6414
|
+
const vars = /* @__PURE__ */ new Set();
|
|
6415
|
+
SUPABASE_CLIENT_ASSIGN_RE.lastIndex = 0;
|
|
6416
|
+
let m;
|
|
6417
|
+
while ((m = SUPABASE_CLIENT_ASSIGN_RE.exec(content)) !== null) {
|
|
6418
|
+
if (constructorMatchesImport(m[2], ctx)) vars.add(m[1]);
|
|
6419
|
+
}
|
|
6420
|
+
return vars;
|
|
6421
|
+
}
|
|
6390
6422
|
function supabaseEndpointsFromFile(file, serviceDir) {
|
|
6391
6423
|
const ctx = readImports2(file.content);
|
|
6392
6424
|
if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return [];
|
|
@@ -6419,6 +6451,34 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
6419
6451
|
}
|
|
6420
6452
|
});
|
|
6421
6453
|
}
|
|
6454
|
+
const clientVars = supabaseClientVars(file.content, ctx);
|
|
6455
|
+
for (const clientVar of clientVars) {
|
|
6456
|
+
const accessRe = new RegExp(
|
|
6457
|
+
`\\b${clientVar}\\s*\\.\\s*(from|rpc)\\s*\\(\\s*['"\`]([\\w.-]+)['"\`]`,
|
|
6458
|
+
"g"
|
|
6459
|
+
);
|
|
6460
|
+
let am;
|
|
6461
|
+
while ((am = accessRe.exec(file.content)) !== null) {
|
|
6462
|
+
const kind = am[1] === "rpc" ? "supabase-rpc" : "supabase-table";
|
|
6463
|
+
const resource = am[2];
|
|
6464
|
+
const key = `${kind}/${resource}`;
|
|
6465
|
+
if (seen.has(key)) continue;
|
|
6466
|
+
seen.add(key);
|
|
6467
|
+
const line = lineOf(file.content, am[0]);
|
|
6468
|
+
out.push({
|
|
6469
|
+
infraId: (0, import_types19.infraId)(kind, resource),
|
|
6470
|
+
name: resource,
|
|
6471
|
+
kind,
|
|
6472
|
+
edgeType: "CALLS",
|
|
6473
|
+
confidenceKind: "verified-call-site",
|
|
6474
|
+
evidence: {
|
|
6475
|
+
file: import_node_path30.default.relative(serviceDir, file.path),
|
|
6476
|
+
line,
|
|
6477
|
+
snippet: snippet(file.content, line)
|
|
6478
|
+
}
|
|
6479
|
+
});
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6422
6482
|
return out;
|
|
6423
6483
|
}
|
|
6424
6484
|
|
|
@@ -9664,7 +9724,30 @@ init_otel();
|
|
|
9664
9724
|
|
|
9665
9725
|
// src/connectors/index.ts
|
|
9666
9726
|
init_cjs_shims();
|
|
9727
|
+
var import_types34 = require("@neat.is/types");
|
|
9667
9728
|
var NO_ENV = "unknown";
|
|
9729
|
+
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
9730
|
+
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
9731
|
+
const sites = [];
|
|
9732
|
+
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
9733
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
9734
|
+
if (edge.provenance !== import_types34.Provenance.EXTRACTED) continue;
|
|
9735
|
+
const parsed = (0, import_types34.parseFileId)(edge.source);
|
|
9736
|
+
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
9737
|
+
const site = { relPath: edge.evidence.file };
|
|
9738
|
+
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
9739
|
+
sites.push(site);
|
|
9740
|
+
}
|
|
9741
|
+
return sites.length === 1 ? sites[0] : void 0;
|
|
9742
|
+
}
|
|
9743
|
+
function routeCallSiteFor(graph, targetNodeId) {
|
|
9744
|
+
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
9745
|
+
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
9746
|
+
if (attrs.type !== import_types34.NodeType.RouteNode || !attrs.path) return void 0;
|
|
9747
|
+
const site = { relPath: attrs.path };
|
|
9748
|
+
if (attrs.line !== void 0) site.line = attrs.line;
|
|
9749
|
+
return site;
|
|
9750
|
+
}
|
|
9668
9751
|
async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
9669
9752
|
const signals = await connector.poll(ctx);
|
|
9670
9753
|
let edgesCreated = 0;
|
|
@@ -9681,7 +9764,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
9681
9764
|
ensureInfraNode(graph, kind, name, provider);
|
|
9682
9765
|
}
|
|
9683
9766
|
const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
9684
|
-
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } :
|
|
9767
|
+
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : routeCallSiteFor(graph, resolved.targetNodeId) ?? staticCallSiteFor(graph, resolved.serviceName, resolved.targetNodeId);
|
|
9685
9768
|
const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
|
|
9686
9769
|
const evidence = callSite ? {
|
|
9687
9770
|
file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),
|
|
@@ -10232,19 +10315,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
10232
10315
|
|
|
10233
10316
|
// src/connectors/supabase/resolve.ts
|
|
10234
10317
|
init_cjs_shims();
|
|
10235
|
-
var
|
|
10318
|
+
var import_types36 = require("@neat.is/types");
|
|
10236
10319
|
function createSupabaseResolveTarget(graph, config) {
|
|
10237
10320
|
return (signal, _ctx) => {
|
|
10238
10321
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
10239
10322
|
return null;
|
|
10240
10323
|
}
|
|
10241
|
-
const subResourceId = (0,
|
|
10324
|
+
const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
10242
10325
|
if (graph.hasNode(subResourceId)) {
|
|
10243
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10326
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
10327
|
+
}
|
|
10328
|
+
const bareResourceId = (0, import_types36.infraId)(signal.targetKind, signal.targetName);
|
|
10329
|
+
if (graph.hasNode(bareResourceId)) {
|
|
10330
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
10244
10331
|
}
|
|
10245
|
-
const projectLevelId = (0,
|
|
10332
|
+
const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
|
|
10246
10333
|
if (graph.hasNode(projectLevelId)) {
|
|
10247
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10334
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
10248
10335
|
}
|
|
10249
10336
|
return null;
|
|
10250
10337
|
};
|
|
@@ -10337,7 +10424,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
10337
10424
|
|
|
10338
10425
|
// src/connectors/railway/index.ts
|
|
10339
10426
|
init_cjs_shims();
|
|
10340
|
-
var
|
|
10427
|
+
var import_types40 = require("@neat.is/types");
|
|
10341
10428
|
|
|
10342
10429
|
// src/connectors/railway/client.ts
|
|
10343
10430
|
init_cjs_shims();
|
|
@@ -10463,7 +10550,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
10463
10550
|
const out = [];
|
|
10464
10551
|
graph.forEachNode((_id, attrs) => {
|
|
10465
10552
|
const node = attrs;
|
|
10466
|
-
if (node.type !==
|
|
10553
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10467
10554
|
const route = attrs;
|
|
10468
10555
|
if (route.service !== serviceName) return;
|
|
10469
10556
|
out.push({
|
|
@@ -10559,12 +10646,12 @@ function createRailwayResolveTarget(config) {
|
|
|
10559
10646
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
10560
10647
|
if (!serviceName) return null;
|
|
10561
10648
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
10562
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10649
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
|
|
10563
10650
|
}
|
|
10564
10651
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
10565
10652
|
const peerName = config.serviceNameById[signal.targetName];
|
|
10566
10653
|
if (!peerName) return null;
|
|
10567
|
-
return { targetNodeId: (0,
|
|
10654
|
+
return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
|
|
10568
10655
|
}
|
|
10569
10656
|
return null;
|
|
10570
10657
|
};
|
|
@@ -10751,7 +10838,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10751
10838
|
|
|
10752
10839
|
// src/connectors/firebase/resolve.ts
|
|
10753
10840
|
init_cjs_shims();
|
|
10754
|
-
var
|
|
10841
|
+
var import_types41 = require("@neat.is/types");
|
|
10755
10842
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10756
10843
|
switch (resourceType) {
|
|
10757
10844
|
case "cloud_function":
|
|
@@ -10766,7 +10853,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10766
10853
|
const entries = [];
|
|
10767
10854
|
graph.forEachNode((_id, attrs) => {
|
|
10768
10855
|
const node = attrs;
|
|
10769
|
-
if (node.type !==
|
|
10856
|
+
if (node.type !== import_types41.NodeType.RouteNode) return;
|
|
10770
10857
|
const route = attrs;
|
|
10771
10858
|
if (route.service !== serviceName) return;
|
|
10772
10859
|
entries.push({
|
|
@@ -10798,7 +10885,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10798
10885
|
return {
|
|
10799
10886
|
targetNodeId: match.routeNodeId,
|
|
10800
10887
|
serviceName,
|
|
10801
|
-
edgeType:
|
|
10888
|
+
edgeType: import_types41.EdgeType.CALLS
|
|
10802
10889
|
};
|
|
10803
10890
|
};
|
|
10804
10891
|
}
|
|
@@ -10825,7 +10912,7 @@ init_cjs_shims();
|
|
|
10825
10912
|
|
|
10826
10913
|
// src/connectors/cloudflare/connector.ts
|
|
10827
10914
|
init_cjs_shims();
|
|
10828
|
-
var
|
|
10915
|
+
var import_types43 = require("@neat.is/types");
|
|
10829
10916
|
|
|
10830
10917
|
// src/connectors/cloudflare/client.ts
|
|
10831
10918
|
init_cjs_shims();
|
|
@@ -10986,7 +11073,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
10986
11073
|
graph.forEachNode((id, attrs) => {
|
|
10987
11074
|
if (found) return;
|
|
10988
11075
|
const a = attrs;
|
|
10989
|
-
if (a.type ===
|
|
11076
|
+
if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
10990
11077
|
found = id;
|
|
10991
11078
|
}
|
|
10992
11079
|
});
|
|
@@ -10998,7 +11085,7 @@ function findMatchingRouteNode(graph, serviceName, method, path51) {
|
|
|
10998
11085
|
graph.forEachNode((id, attrs) => {
|
|
10999
11086
|
if (found) return;
|
|
11000
11087
|
const a = attrs;
|
|
11001
|
-
if (a.type !==
|
|
11088
|
+
if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
|
|
11002
11089
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
11003
11090
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
11004
11091
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -11017,11 +11104,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11017
11104
|
};
|
|
11018
11105
|
const mapping = config.workers?.[scriptName];
|
|
11019
11106
|
if (mapping) {
|
|
11020
|
-
const wholeFileId = (0,
|
|
11107
|
+
const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
|
|
11021
11108
|
return {
|
|
11022
11109
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
11023
11110
|
serviceName: mapping.service,
|
|
11024
|
-
edgeType:
|
|
11111
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
11025
11112
|
};
|
|
11026
11113
|
}
|
|
11027
11114
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -11030,13 +11117,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11030
11117
|
return {
|
|
11031
11118
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
11032
11119
|
serviceName: fileNode.service,
|
|
11033
|
-
edgeType:
|
|
11120
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
11034
11121
|
};
|
|
11035
11122
|
}
|
|
11036
11123
|
return {
|
|
11037
|
-
targetNodeId: (0,
|
|
11124
|
+
targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
|
|
11038
11125
|
serviceName: scriptName,
|
|
11039
|
-
edgeType:
|
|
11126
|
+
edgeType: import_types43.EdgeType.CALLS,
|
|
11040
11127
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
11041
11128
|
};
|
|
11042
11129
|
};
|
|
@@ -11295,7 +11382,7 @@ function unroutedErrorsPath(neatHome3) {
|
|
|
11295
11382
|
}
|
|
11296
11383
|
|
|
11297
11384
|
// src/daemon.ts
|
|
11298
|
-
var
|
|
11385
|
+
var import_types46 = require("@neat.is/types");
|
|
11299
11386
|
function daemonJsonPath(scanPath) {
|
|
11300
11387
|
return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
|
|
11301
11388
|
}
|
|
@@ -11416,7 +11503,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
11416
11503
|
if (!serviceName) return true;
|
|
11417
11504
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
11418
11505
|
return graph.someNode(
|
|
11419
|
-
(_id, attrs) => attrs.type ===
|
|
11506
|
+
(_id, attrs) => attrs.type === import_types46.NodeType.ServiceNode && attrs.name === serviceName
|
|
11420
11507
|
);
|
|
11421
11508
|
}
|
|
11422
11509
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|