@neat.is/core 0.10.4 → 0.10.5-dev.20261002
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-TUGYIRUT.js → chunk-HHAPD6JD.js} +32 -6
- package/dist/chunk-HHAPD6JD.js.map +1 -0
- package/dist/{chunk-2OQVIBZZ.js → chunk-TONL5O2E.js} +4 -4
- package/dist/chunk-TONL5O2E.js.map +1 -0
- package/dist/cli.cjs +154 -28
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +125 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +33 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +33 -7
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +31 -5
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-2OQVIBZZ.js.map +0 -1
- package/dist/chunk-TUGYIRUT.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -3536,6 +3536,10 @@ function languageForPath(relPath) {
|
|
|
3536
3536
|
return void 0;
|
|
3537
3537
|
}
|
|
3538
3538
|
}
|
|
3539
|
+
var currentSource;
|
|
3540
|
+
function setExtractionSource(source) {
|
|
3541
|
+
currentSource = source;
|
|
3542
|
+
}
|
|
3539
3543
|
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3540
3544
|
let nodesAdded = 0;
|
|
3541
3545
|
let edgesAdded = 0;
|
|
@@ -3548,10 +3552,16 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
3548
3552
|
service: serviceName,
|
|
3549
3553
|
path: relPath,
|
|
3550
3554
|
...language ? { language } : {},
|
|
3555
|
+
...currentSource ? { source: currentSource } : {},
|
|
3551
3556
|
discoveredVia: "static"
|
|
3552
3557
|
};
|
|
3553
3558
|
graph.addNode(fileNodeId, node);
|
|
3554
3559
|
nodesAdded++;
|
|
3560
|
+
} else if (currentSource) {
|
|
3561
|
+
const existing = graph.getNodeAttributes(fileNodeId);
|
|
3562
|
+
if (existing.source !== currentSource) {
|
|
3563
|
+
graph.replaceNodeAttributes(fileNodeId, { ...existing, source: currentSource });
|
|
3564
|
+
}
|
|
3555
3565
|
}
|
|
3556
3566
|
const containsId = (0, import_types5.extractedEdgeId)(serviceNodeId, fileNodeId, import_types5.EdgeType.CONTAINS);
|
|
3557
3567
|
if (!graph.hasEdge(containsId)) {
|
|
@@ -16612,10 +16622,12 @@ init_cjs_shims();
|
|
|
16612
16622
|
var import_node_fs34 = require("fs");
|
|
16613
16623
|
var import_node_path69 = __toESM(require("path"), 1);
|
|
16614
16624
|
var import_types59 = require("@neat.is/types");
|
|
16615
|
-
function dropOrphanedFileNodes(graph) {
|
|
16625
|
+
function dropOrphanedFileNodes(graph, source) {
|
|
16616
16626
|
const orphans = [];
|
|
16617
16627
|
graph.forEachNode((id, attrs) => {
|
|
16618
|
-
|
|
16628
|
+
const node = attrs;
|
|
16629
|
+
if (node.type !== import_types59.NodeType.FileNode) return;
|
|
16630
|
+
if (source && node.source !== source) return;
|
|
16619
16631
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
16620
16632
|
orphans.push(id);
|
|
16621
16633
|
}
|
|
@@ -16636,12 +16648,23 @@ function retireEdgesByFile(graph, file) {
|
|
|
16636
16648
|
dropOrphanedFileNodes(graph);
|
|
16637
16649
|
return toDrop.length;
|
|
16638
16650
|
}
|
|
16639
|
-
function
|
|
16651
|
+
function outOfScope(graph, edge, source) {
|
|
16652
|
+
if (!source) return false;
|
|
16653
|
+
for (const endpoint2 of [edge.source, edge.target]) {
|
|
16654
|
+
if (!graph.hasNode(endpoint2)) continue;
|
|
16655
|
+
const node = graph.getNodeAttributes(endpoint2);
|
|
16656
|
+
if (node.type !== import_types59.NodeType.FileNode) continue;
|
|
16657
|
+
return node.source !== source;
|
|
16658
|
+
}
|
|
16659
|
+
return false;
|
|
16660
|
+
}
|
|
16661
|
+
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = [], source) {
|
|
16640
16662
|
const toDrop = [];
|
|
16641
16663
|
const bases = [scanPath, ...serviceDirs];
|
|
16642
16664
|
graph.forEachEdge((id, attrs) => {
|
|
16643
16665
|
const edge = attrs;
|
|
16644
16666
|
if (edge.provenance !== import_types59.Provenance.EXTRACTED) return;
|
|
16667
|
+
if (outOfScope(graph, edge, source)) return;
|
|
16645
16668
|
const evidenceFile = edge.evidence?.file;
|
|
16646
16669
|
if (!evidenceFile) return;
|
|
16647
16670
|
if (import_node_path69.default.isAbsolute(evidenceFile)) {
|
|
@@ -16652,7 +16675,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
16652
16675
|
if (!found) toDrop.push(id);
|
|
16653
16676
|
});
|
|
16654
16677
|
for (const id of toDrop) graph.dropEdge(id);
|
|
16655
|
-
dropOrphanedFileNodes(graph);
|
|
16678
|
+
dropOrphanedFileNodes(graph, source);
|
|
16656
16679
|
return toDrop.length;
|
|
16657
16680
|
}
|
|
16658
16681
|
|
|
@@ -16661,6 +16684,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16661
16684
|
await ensureCompatLoaded();
|
|
16662
16685
|
drainExtractionErrors();
|
|
16663
16686
|
drainSkippedFiles();
|
|
16687
|
+
setExtractionSource(opts.source);
|
|
16664
16688
|
const services = await discoverServices(scanPath);
|
|
16665
16689
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
16666
16690
|
await addServiceAliases(graph, scanPath, services);
|
|
@@ -16681,7 +16705,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16681
16705
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
16682
16706
|
graph,
|
|
16683
16707
|
scanPath,
|
|
16684
|
-
services.map((s) => s.dir)
|
|
16708
|
+
services.map((s) => s.dir),
|
|
16709
|
+
opts.source
|
|
16685
16710
|
);
|
|
16686
16711
|
const frontiersPromoted = promoteFrontierNodes(graph);
|
|
16687
16712
|
if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph);
|
|
@@ -16716,6 +16741,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16716
16741
|
);
|
|
16717
16742
|
}
|
|
16718
16743
|
}
|
|
16744
|
+
setExtractionSource(void 0);
|
|
16719
16745
|
const result = {
|
|
16720
16746
|
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + actionPhase.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + tableEdges.nodesAdded + phase5.nodesAdded + zodPhase.nodesAdded + firestoreRulesPhase.nodesAdded,
|
|
16721
16747
|
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + actionPhase.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + tableEdges.edgesAdded + phase5.edgesAdded + zodPhase.edgesAdded + firestoreRulesPhase.edgesAdded,
|
|
@@ -30658,6 +30684,52 @@ async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
|
|
|
30658
30684
|
if (deps.signal?.aborted) return { kind: "aborted" };
|
|
30659
30685
|
}
|
|
30660
30686
|
}
|
|
30687
|
+
function projectNameForRepo(repo) {
|
|
30688
|
+
const slug = repo.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
30689
|
+
return slug || "repo";
|
|
30690
|
+
}
|
|
30691
|
+
async function detailOf(res) {
|
|
30692
|
+
const body = await res.text().catch(() => "");
|
|
30693
|
+
try {
|
|
30694
|
+
const parsed = JSON.parse(body);
|
|
30695
|
+
if (parsed.error) return parsed.error;
|
|
30696
|
+
} catch {
|
|
30697
|
+
}
|
|
30698
|
+
return body.trim() || `HTTP ${res.status}`;
|
|
30699
|
+
}
|
|
30700
|
+
async function createProjectForRepo(fetchImpl, cpUrl, accessToken, name) {
|
|
30701
|
+
const headers = { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" };
|
|
30702
|
+
let project;
|
|
30703
|
+
try {
|
|
30704
|
+
const res = await fetchImpl(`${cpUrl}/me/projects`, {
|
|
30705
|
+
method: "POST",
|
|
30706
|
+
headers,
|
|
30707
|
+
body: JSON.stringify({ name }),
|
|
30708
|
+
signal: AbortSignal.timeout(CP_TIMEOUT_MS)
|
|
30709
|
+
});
|
|
30710
|
+
if (!res.ok) return { ok: false, reason: "not-created", detail: await detailOf(res) };
|
|
30711
|
+
project = await res.json();
|
|
30712
|
+
} catch (err) {
|
|
30713
|
+
return { ok: false, reason: "not-created", detail: err instanceof Error ? err.message : String(err) };
|
|
30714
|
+
}
|
|
30715
|
+
try {
|
|
30716
|
+
const res = await fetchImpl(`${cpUrl}/me/projects/${encodeURIComponent(project.id)}/provision`, {
|
|
30717
|
+
method: "POST",
|
|
30718
|
+
headers,
|
|
30719
|
+
signal: AbortSignal.timeout(CP_TIMEOUT_MS)
|
|
30720
|
+
});
|
|
30721
|
+
if (res.status === 402) return { ok: false, reason: "needs-plan", project, detail: await detailOf(res) };
|
|
30722
|
+
if (!res.ok) return { ok: false, reason: "not-provisioned", project, detail: await detailOf(res) };
|
|
30723
|
+
} catch (err) {
|
|
30724
|
+
return {
|
|
30725
|
+
ok: false,
|
|
30726
|
+
reason: "not-provisioned",
|
|
30727
|
+
project,
|
|
30728
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
30729
|
+
};
|
|
30730
|
+
}
|
|
30731
|
+
return { ok: true, project };
|
|
30732
|
+
}
|
|
30661
30733
|
function bindUrl(webUrl, projectId) {
|
|
30662
30734
|
return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
|
|
30663
30735
|
}
|
|
@@ -30683,7 +30755,8 @@ async function runPostLoginConnect(input) {
|
|
|
30683
30755
|
const repo = await detect6(cwd);
|
|
30684
30756
|
out("");
|
|
30685
30757
|
const cmd = commandPrefix();
|
|
30686
|
-
const
|
|
30758
|
+
const pushCmd = input.endpoint ? `${cmd} sync --to ${input.endpoint}${input.pushToken ? ` --token ${input.pushToken}` : ""}` : null;
|
|
30759
|
+
const pushInto = (label) => pushCmd ? [` ${pushCmd}`] : [` Its daemon URL and token are on ${label} in the console: ${input.webUrl}`];
|
|
30687
30760
|
if (!repo) {
|
|
30688
30761
|
out("This directory has no GitHub remote, so it can't be bound to the project.");
|
|
30689
30762
|
const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
|
|
@@ -30693,16 +30766,17 @@ async function runPostLoginConnect(input) {
|
|
|
30693
30766
|
out("");
|
|
30694
30767
|
if (built) {
|
|
30695
30768
|
out(`Now push it to ${input.project.name}:`);
|
|
30696
|
-
out(
|
|
30769
|
+
for (const line of pushInto(input.project.name)) out(line);
|
|
30697
30770
|
} else {
|
|
30698
30771
|
out(`When you're ready, build it and push it to ${input.project.name}:`);
|
|
30699
30772
|
out(` ${cmd}`);
|
|
30700
|
-
out(
|
|
30773
|
+
for (const line of pushInto(input.project.name)) out(line);
|
|
30701
30774
|
}
|
|
30702
30775
|
out(` Manage projects and repos: ${input.webUrl}`);
|
|
30703
30776
|
return;
|
|
30704
30777
|
}
|
|
30705
|
-
out(` Push its graph
|
|
30778
|
+
out(` Push its graph to ${input.project.name}:`);
|
|
30779
|
+
for (const line of pushInto(input.project.name)) out(line);
|
|
30706
30780
|
out(` Manage projects and repos: ${input.webUrl}`);
|
|
30707
30781
|
return;
|
|
30708
30782
|
}
|
|
@@ -30715,39 +30789,87 @@ async function runPostLoginConnect(input) {
|
|
|
30715
30789
|
out(` See the graph: ${input.webUrl}`);
|
|
30716
30790
|
return;
|
|
30717
30791
|
}
|
|
30718
|
-
out(`${slug} isn't
|
|
30792
|
+
out(`${slug} isn't in ${input.project.name} \u2014 logging in connected your account, not this repo.`);
|
|
30719
30793
|
out("");
|
|
30720
|
-
const
|
|
30721
|
-
|
|
30722
|
-
out(
|
|
30723
|
-
out(`
|
|
30794
|
+
const suggested = projectNameForRepo(repo);
|
|
30795
|
+
out("Where should this repo go?");
|
|
30796
|
+
out("");
|
|
30797
|
+
out(`1) A project of its own, called ${suggested}`);
|
|
30798
|
+
out(`2) ${input.project.name} \u2014 the project this login connected to`);
|
|
30799
|
+
for (const line of sharedProjectCaveat(bound)) out(` ${line}`);
|
|
30724
30800
|
out("3) Neither, for now");
|
|
30801
|
+
out("");
|
|
30725
30802
|
const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
|
|
30726
30803
|
if (answer === "1") {
|
|
30727
|
-
const url = bindUrl(input.webUrl, input.project.id);
|
|
30728
|
-
const opened = (d.openBrowser ?? (() => false))(url);
|
|
30729
30804
|
out("");
|
|
30730
|
-
|
|
30731
|
-
|
|
30805
|
+
const outcome = await createProjectForRepo(fetchImpl, input.cpUrl, input.accessToken, suggested);
|
|
30806
|
+
reportCreate(outcome, input.webUrl, cmd, input.project.name, out);
|
|
30732
30807
|
return;
|
|
30733
30808
|
}
|
|
30734
30809
|
if (answer === "2") {
|
|
30735
|
-
const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
|
|
30736
30810
|
out("");
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
|
|
30740
|
-
|
|
30741
|
-
|
|
30811
|
+
out(`Two ways into ${input.project.name} \u2014 either is fine, and you can do both:`);
|
|
30812
|
+
out("");
|
|
30813
|
+
const installed = input.me?.github?.installed;
|
|
30814
|
+
out(
|
|
30815
|
+
installed === false ? " Bind it (install the GitHub App first) \u2014 hosted NEAT keeps it in sync:" : installed === true ? " Bind it \u2014 hosted NEAT clones and extracts it, and keeps it in sync:" : " Bind it through the console \u2014 hosted NEAT keeps it in sync:"
|
|
30816
|
+
);
|
|
30817
|
+
out(` ${bindUrl(input.webUrl, input.project.id)}`);
|
|
30818
|
+
out("");
|
|
30819
|
+
const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
|
|
30820
|
+
if (hasGraph) {
|
|
30821
|
+
out(" Or push the graph on this machine now \u2014 a snapshot, not a subscription:");
|
|
30822
|
+
for (const line of pushInto(input.project.name)) out(` ${line}`);
|
|
30823
|
+
} else {
|
|
30824
|
+
out(" Or build a graph here and push it \u2014 a snapshot, not a subscription:");
|
|
30825
|
+
out(` ${cmd}`);
|
|
30826
|
+
for (const line of pushInto(input.project.name)) out(` ${line}`);
|
|
30742
30827
|
}
|
|
30743
|
-
out("Push the local graph with:");
|
|
30744
|
-
out(` ${syncLine}`);
|
|
30745
30828
|
return;
|
|
30746
30829
|
}
|
|
30747
30830
|
out("");
|
|
30748
30831
|
out("No problem \u2014 when you want this repo in the graph:");
|
|
30749
|
-
out(`
|
|
30750
|
-
out(` Or
|
|
30832
|
+
out(` Its own project: ${input.webUrl}`);
|
|
30833
|
+
out(` Or into ${input.project.name}: ${bindUrl(input.webUrl, input.project.id)}`);
|
|
30834
|
+
}
|
|
30835
|
+
function sharedProjectCaveat(bound) {
|
|
30836
|
+
const hazard = "Two repos in one project can currently retire each other's files (#1294),";
|
|
30837
|
+
if (!bound) return [hazard, "so a graph there may end up short of code until that is fixed."];
|
|
30838
|
+
if (bound.length === 0) return ["Nothing else is bound there yet.", `Adding a second repo later would hit #1294 \u2014 ${hazard.toLowerCase()}`];
|
|
30839
|
+
const names = bound.map((r) => `${r.owner}/${r.name}`).join(", ");
|
|
30840
|
+
return [
|
|
30841
|
+
`${bound.length} already there: ${names}.`,
|
|
30842
|
+
hazard,
|
|
30843
|
+
"so both graphs may end up short of code until that is fixed."
|
|
30844
|
+
];
|
|
30845
|
+
}
|
|
30846
|
+
function reportCreate(outcome, webUrl, cmd, connectedTo, out) {
|
|
30847
|
+
if (outcome.ok) {
|
|
30848
|
+
out(`Created ${outcome.project.name} and started its daemon.`);
|
|
30849
|
+
out("");
|
|
30850
|
+
out(`This CLI is still pointed at ${connectedTo}. Switch it to the new project with:`);
|
|
30851
|
+
out(` ${cmd} login --project ${outcome.project.name}`);
|
|
30852
|
+
out("");
|
|
30853
|
+
out("Then bind this repo to it:");
|
|
30854
|
+
out(` ${bindUrl(webUrl, outcome.project.id)}`);
|
|
30855
|
+
return;
|
|
30856
|
+
}
|
|
30857
|
+
if (outcome.reason === "needs-plan") {
|
|
30858
|
+
out(`Created ${outcome.project.name}, but starting a daemon for it needs a plan.`);
|
|
30859
|
+
out(` Choose one: ${webUrl}/checkout`);
|
|
30860
|
+
out("");
|
|
30861
|
+
out("The project is made and keeps its name, so there's nothing to redo here afterwards.");
|
|
30862
|
+
return;
|
|
30863
|
+
}
|
|
30864
|
+
if (outcome.reason === "not-provisioned") {
|
|
30865
|
+
out(`Created ${outcome.project.name}, but its daemon didn't start: ${outcome.detail}`);
|
|
30866
|
+
out("");
|
|
30867
|
+
out("The project exists \u2014 start it from the console rather than making a second one:");
|
|
30868
|
+
out(` ${webUrl}`);
|
|
30869
|
+
return;
|
|
30870
|
+
}
|
|
30871
|
+
out(`Couldn't create the project: ${outcome.detail}`);
|
|
30872
|
+
out(`Nothing was made, so this is safe to retry, or do it in the console: ${webUrl}`);
|
|
30751
30873
|
}
|
|
30752
30874
|
|
|
30753
30875
|
// src/login-sso.ts
|
|
@@ -30984,6 +31106,10 @@ async function runSsoLogin(opts, deps) {
|
|
|
30984
31106
|
webUrl: opts.webUrl,
|
|
30985
31107
|
accessToken,
|
|
30986
31108
|
project: { id: project.id, name: project.name },
|
|
31109
|
+
// What a push actually needs: `sync --to` takes the daemon's URL, and its
|
|
31110
|
+
// token comes from the flag rather than the profile (#1302).
|
|
31111
|
+
endpoint: cred.endpoint,
|
|
31112
|
+
pushToken: cred.authToken,
|
|
30987
31113
|
me,
|
|
30988
31114
|
deps: {
|
|
30989
31115
|
fetchImpl,
|