@neat.is/core 0.10.3 → 0.10.5-dev.20261001
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-M7CGATVC.js → chunk-EFBWQIKG.js} +12 -2
- package/dist/chunk-EFBWQIKG.js.map +1 -0
- package/dist/{chunk-LNIVRE4L.js → chunk-HHAPD6JD.js} +82 -7
- package/dist/chunk-HHAPD6JD.js.map +1 -0
- package/dist/{chunk-5SJHA5ZH.js → chunk-LXQCL4MP.js} +2 -2
- package/dist/{chunk-DLQNMU3R.js → chunk-TONL5O2E.js} +61 -24
- package/dist/chunk-TONL5O2E.js.map +1 -0
- package/dist/cli.cjs +212 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +127 -27
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +147 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +147 -27
- 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 +93 -7
- 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-LNIVRE4L.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/cli.cjs
CHANGED
|
@@ -51,6 +51,10 @@ function assertBindAuthority(host, token) {
|
|
|
51
51
|
if (isLoopbackHost2(host)) return;
|
|
52
52
|
throw new BindAuthorityError(host);
|
|
53
53
|
}
|
|
54
|
+
function isBearerDelegated(req) {
|
|
55
|
+
const config = req.routeOptions?.config;
|
|
56
|
+
return config?.bearerDelegated === true;
|
|
57
|
+
}
|
|
54
58
|
function mountBearerAuth(app, opts) {
|
|
55
59
|
if (!opts.token || opts.token.length === 0) return;
|
|
56
60
|
if (opts.trustProxy) return;
|
|
@@ -66,6 +70,10 @@ function mountBearerAuth(app, opts) {
|
|
|
66
70
|
done();
|
|
67
71
|
return;
|
|
68
72
|
}
|
|
73
|
+
if (isBearerDelegated(req)) {
|
|
74
|
+
done();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
69
77
|
if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
|
|
70
78
|
done();
|
|
71
79
|
return;
|
|
@@ -99,7 +107,7 @@ function readAuthEnv(env = process.env) {
|
|
|
99
107
|
publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
|
|
100
108
|
};
|
|
101
109
|
}
|
|
102
|
-
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN;
|
|
110
|
+
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN, BEARER_DELEGATED;
|
|
103
111
|
var init_auth = __esm({
|
|
104
112
|
"src/auth.ts"() {
|
|
105
113
|
"use strict";
|
|
@@ -129,6 +137,7 @@ var init_auth = __esm({
|
|
|
129
137
|
PROJECT_SCOPED_UNAUTH_PATTERN = new RegExp(
|
|
130
138
|
`^/projects/[^/]+/(?:${DEFAULT_UNAUTH_PATHS.map((p) => p.slice(1)).join("|")})$`
|
|
131
139
|
);
|
|
140
|
+
BEARER_DELEGATED = { bearerDelegated: true };
|
|
132
141
|
}
|
|
133
142
|
});
|
|
134
143
|
|
|
@@ -3527,6 +3536,10 @@ function languageForPath(relPath) {
|
|
|
3527
3536
|
return void 0;
|
|
3528
3537
|
}
|
|
3529
3538
|
}
|
|
3539
|
+
var currentSource;
|
|
3540
|
+
function setExtractionSource(source) {
|
|
3541
|
+
currentSource = source;
|
|
3542
|
+
}
|
|
3530
3543
|
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3531
3544
|
let nodesAdded = 0;
|
|
3532
3545
|
let edgesAdded = 0;
|
|
@@ -3539,10 +3552,16 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
3539
3552
|
service: serviceName,
|
|
3540
3553
|
path: relPath,
|
|
3541
3554
|
...language ? { language } : {},
|
|
3555
|
+
...currentSource ? { source: currentSource } : {},
|
|
3542
3556
|
discoveredVia: "static"
|
|
3543
3557
|
};
|
|
3544
3558
|
graph.addNode(fileNodeId, node);
|
|
3545
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
|
+
}
|
|
3546
3565
|
}
|
|
3547
3566
|
const containsId = (0, import_types5.extractedEdgeId)(serviceNodeId, fileNodeId, import_types5.EdgeType.CONTAINS);
|
|
3548
3567
|
if (!graph.hasEdge(containsId)) {
|
|
@@ -16603,10 +16622,12 @@ init_cjs_shims();
|
|
|
16603
16622
|
var import_node_fs34 = require("fs");
|
|
16604
16623
|
var import_node_path69 = __toESM(require("path"), 1);
|
|
16605
16624
|
var import_types59 = require("@neat.is/types");
|
|
16606
|
-
function dropOrphanedFileNodes(graph) {
|
|
16625
|
+
function dropOrphanedFileNodes(graph, source) {
|
|
16607
16626
|
const orphans = [];
|
|
16608
16627
|
graph.forEachNode((id, attrs) => {
|
|
16609
|
-
|
|
16628
|
+
const node = attrs;
|
|
16629
|
+
if (node.type !== import_types59.NodeType.FileNode) return;
|
|
16630
|
+
if (source && node.source !== source) return;
|
|
16610
16631
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
16611
16632
|
orphans.push(id);
|
|
16612
16633
|
}
|
|
@@ -16627,12 +16648,23 @@ function retireEdgesByFile(graph, file) {
|
|
|
16627
16648
|
dropOrphanedFileNodes(graph);
|
|
16628
16649
|
return toDrop.length;
|
|
16629
16650
|
}
|
|
16630
|
-
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) {
|
|
16631
16662
|
const toDrop = [];
|
|
16632
16663
|
const bases = [scanPath, ...serviceDirs];
|
|
16633
16664
|
graph.forEachEdge((id, attrs) => {
|
|
16634
16665
|
const edge = attrs;
|
|
16635
16666
|
if (edge.provenance !== import_types59.Provenance.EXTRACTED) return;
|
|
16667
|
+
if (outOfScope(graph, edge, source)) return;
|
|
16636
16668
|
const evidenceFile = edge.evidence?.file;
|
|
16637
16669
|
if (!evidenceFile) return;
|
|
16638
16670
|
if (import_node_path69.default.isAbsolute(evidenceFile)) {
|
|
@@ -16643,7 +16675,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
16643
16675
|
if (!found) toDrop.push(id);
|
|
16644
16676
|
});
|
|
16645
16677
|
for (const id of toDrop) graph.dropEdge(id);
|
|
16646
|
-
dropOrphanedFileNodes(graph);
|
|
16678
|
+
dropOrphanedFileNodes(graph, source);
|
|
16647
16679
|
return toDrop.length;
|
|
16648
16680
|
}
|
|
16649
16681
|
|
|
@@ -16652,6 +16684,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16652
16684
|
await ensureCompatLoaded();
|
|
16653
16685
|
drainExtractionErrors();
|
|
16654
16686
|
drainSkippedFiles();
|
|
16687
|
+
setExtractionSource(opts.source);
|
|
16655
16688
|
const services = await discoverServices(scanPath);
|
|
16656
16689
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
16657
16690
|
await addServiceAliases(graph, scanPath, services);
|
|
@@ -16672,7 +16705,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16672
16705
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
16673
16706
|
graph,
|
|
16674
16707
|
scanPath,
|
|
16675
|
-
services.map((s) => s.dir)
|
|
16708
|
+
services.map((s) => s.dir),
|
|
16709
|
+
opts.source
|
|
16676
16710
|
);
|
|
16677
16711
|
const frontiersPromoted = promoteFrontierNodes(graph);
|
|
16678
16712
|
if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph);
|
|
@@ -16707,6 +16741,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16707
16741
|
);
|
|
16708
16742
|
}
|
|
16709
16743
|
}
|
|
16744
|
+
setExtractionSource(void 0);
|
|
16710
16745
|
const result = {
|
|
16711
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,
|
|
16712
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,
|
|
@@ -24226,6 +24261,19 @@ function registerRoutes(scope, ctx) {
|
|
|
24226
24261
|
});
|
|
24227
24262
|
}
|
|
24228
24263
|
});
|
|
24264
|
+
scope.post("/repo-sync", async (req, reply) => {
|
|
24265
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
24266
|
+
if (!proj) return;
|
|
24267
|
+
const syncNow = ctx.repoSync?.(proj.name);
|
|
24268
|
+
if (!syncNow) {
|
|
24269
|
+
return reply.code(404).send({
|
|
24270
|
+
error: "repo-sync is not running for this project",
|
|
24271
|
+
project: proj.name,
|
|
24272
|
+
hint: "Only a hosted daemon syncs bound repos. On a local daemon, POST /graph/scan re-extracts the project path."
|
|
24273
|
+
});
|
|
24274
|
+
}
|
|
24275
|
+
return reply.code(202).send({ project: proj.name, ...syncNow() });
|
|
24276
|
+
});
|
|
24229
24277
|
scope.post("/graph/scan", async (req, reply) => {
|
|
24230
24278
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
24231
24279
|
if (!proj) return;
|
|
@@ -24473,6 +24521,39 @@ function registerRoutes(scope, ctx) {
|
|
|
24473
24521
|
}
|
|
24474
24522
|
});
|
|
24475
24523
|
}
|
|
24524
|
+
var OTLP_BODY_LIMIT = 16 * 1024 * 1024;
|
|
24525
|
+
function otlpOnRest(getReceiver) {
|
|
24526
|
+
return async function plugin(scope) {
|
|
24527
|
+
scope.removeAllContentTypeParsers();
|
|
24528
|
+
scope.addContentTypeParser(
|
|
24529
|
+
"*",
|
|
24530
|
+
{ parseAs: "buffer", bodyLimit: OTLP_BODY_LIMIT },
|
|
24531
|
+
(_req, body, done) => done(null, body)
|
|
24532
|
+
);
|
|
24533
|
+
const relay = async (req, reply) => {
|
|
24534
|
+
const receiver = getReceiver();
|
|
24535
|
+
if (!receiver) {
|
|
24536
|
+
return reply.code(503).header("retry-after", "1").send({ error: "OTLP receiver is starting" });
|
|
24537
|
+
}
|
|
24538
|
+
const headers = { ...req.headers };
|
|
24539
|
+
delete headers["content-length"];
|
|
24540
|
+
delete headers["transfer-encoding"];
|
|
24541
|
+
delete headers.connection;
|
|
24542
|
+
const res = await receiver.inject({
|
|
24543
|
+
method: "POST",
|
|
24544
|
+
url: req.url,
|
|
24545
|
+
headers,
|
|
24546
|
+
payload: req.body
|
|
24547
|
+
});
|
|
24548
|
+
const contentType = res.headers["content-type"];
|
|
24549
|
+
if (contentType !== void 0) void reply.header("content-type", contentType);
|
|
24550
|
+
return reply.code(res.statusCode).send(res.rawPayload);
|
|
24551
|
+
};
|
|
24552
|
+
const route = { config: BEARER_DELEGATED, bodyLimit: OTLP_BODY_LIMIT };
|
|
24553
|
+
scope.post("/v1/traces", route, relay);
|
|
24554
|
+
scope.post("/projects/:project/v1/traces", route, relay);
|
|
24555
|
+
};
|
|
24556
|
+
}
|
|
24476
24557
|
async function buildApi(opts) {
|
|
24477
24558
|
const app = (0, import_fastify2.default)({ logger: false, routerOptions: { maxParamLength: 1024 } });
|
|
24478
24559
|
await app.register(import_cors.default, { origin: true });
|
|
@@ -24491,6 +24572,7 @@ async function buildApi(opts) {
|
|
|
24491
24572
|
authProxy: trustProxy === true,
|
|
24492
24573
|
requiresAuth
|
|
24493
24574
|
}));
|
|
24575
|
+
if (opts.otlpReceiver) await app.register(otlpOnRest(opts.otlpReceiver));
|
|
24494
24576
|
const startedAt = opts.startedAt ?? Date.now();
|
|
24495
24577
|
const registry = buildLegacyRegistry(opts);
|
|
24496
24578
|
const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
|
|
@@ -24521,6 +24603,7 @@ async function buildApi(opts) {
|
|
|
24521
24603
|
bootstrap: opts.bootstrap,
|
|
24522
24604
|
singleProject: opts.singleProject?.name,
|
|
24523
24605
|
connectorsHome: opts.connectorsHome,
|
|
24606
|
+
repoSync: opts.repoSync,
|
|
24524
24607
|
runPoll: opts.runPoll ?? runConnectorPoll
|
|
24525
24608
|
};
|
|
24526
24609
|
app.get("/health", async () => {
|
|
@@ -30601,6 +30684,52 @@ async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
|
|
|
30601
30684
|
if (deps.signal?.aborted) return { kind: "aborted" };
|
|
30602
30685
|
}
|
|
30603
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
|
+
}
|
|
30604
30733
|
function bindUrl(webUrl, projectId) {
|
|
30605
30734
|
return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
|
|
30606
30735
|
}
|
|
@@ -30626,7 +30755,8 @@ async function runPostLoginConnect(input) {
|
|
|
30626
30755
|
const repo = await detect6(cwd);
|
|
30627
30756
|
out("");
|
|
30628
30757
|
const cmd = commandPrefix();
|
|
30629
|
-
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}`];
|
|
30630
30760
|
if (!repo) {
|
|
30631
30761
|
out("This directory has no GitHub remote, so it can't be bound to the project.");
|
|
30632
30762
|
const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
|
|
@@ -30636,16 +30766,17 @@ async function runPostLoginConnect(input) {
|
|
|
30636
30766
|
out("");
|
|
30637
30767
|
if (built) {
|
|
30638
30768
|
out(`Now push it to ${input.project.name}:`);
|
|
30639
|
-
out(
|
|
30769
|
+
for (const line of pushInto(input.project.name)) out(line);
|
|
30640
30770
|
} else {
|
|
30641
30771
|
out(`When you're ready, build it and push it to ${input.project.name}:`);
|
|
30642
30772
|
out(` ${cmd}`);
|
|
30643
|
-
out(
|
|
30773
|
+
for (const line of pushInto(input.project.name)) out(line);
|
|
30644
30774
|
}
|
|
30645
30775
|
out(` Manage projects and repos: ${input.webUrl}`);
|
|
30646
30776
|
return;
|
|
30647
30777
|
}
|
|
30648
|
-
out(` Push its graph
|
|
30778
|
+
out(` Push its graph to ${input.project.name}:`);
|
|
30779
|
+
for (const line of pushInto(input.project.name)) out(line);
|
|
30649
30780
|
out(` Manage projects and repos: ${input.webUrl}`);
|
|
30650
30781
|
return;
|
|
30651
30782
|
}
|
|
@@ -30658,39 +30789,87 @@ async function runPostLoginConnect(input) {
|
|
|
30658
30789
|
out(` See the graph: ${input.webUrl}`);
|
|
30659
30790
|
return;
|
|
30660
30791
|
}
|
|
30661
|
-
out(`${slug} isn't
|
|
30792
|
+
out(`${slug} isn't in ${input.project.name} \u2014 logging in connected your account, not this repo.`);
|
|
30793
|
+
out("");
|
|
30794
|
+
const suggested = projectNameForRepo(repo);
|
|
30795
|
+
out("Where should this repo go?");
|
|
30662
30796
|
out("");
|
|
30663
|
-
|
|
30664
|
-
|
|
30665
|
-
out(
|
|
30666
|
-
out(`2) Push the graph on this machine now \u2014 a snapshot, not a subscription`);
|
|
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}`);
|
|
30667
30800
|
out("3) Neither, for now");
|
|
30801
|
+
out("");
|
|
30668
30802
|
const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
|
|
30669
30803
|
if (answer === "1") {
|
|
30670
|
-
const url = bindUrl(input.webUrl, input.project.id);
|
|
30671
|
-
const opened = (d.openBrowser ?? (() => false))(url);
|
|
30672
30804
|
out("");
|
|
30673
|
-
|
|
30674
|
-
|
|
30805
|
+
const outcome = await createProjectForRepo(fetchImpl, input.cpUrl, input.accessToken, suggested);
|
|
30806
|
+
reportCreate(outcome, input.webUrl, cmd, input.project.name, out);
|
|
30675
30807
|
return;
|
|
30676
30808
|
}
|
|
30677
30809
|
if (answer === "2") {
|
|
30678
|
-
const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
|
|
30679
30810
|
out("");
|
|
30680
|
-
|
|
30681
|
-
|
|
30682
|
-
|
|
30683
|
-
|
|
30684
|
-
|
|
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}`);
|
|
30685
30827
|
}
|
|
30686
|
-
out("Push the local graph with:");
|
|
30687
|
-
out(` ${syncLine}`);
|
|
30688
30828
|
return;
|
|
30689
30829
|
}
|
|
30690
30830
|
out("");
|
|
30691
30831
|
out("No problem \u2014 when you want this repo in the graph:");
|
|
30692
|
-
out(`
|
|
30693
|
-
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}`);
|
|
30694
30873
|
}
|
|
30695
30874
|
|
|
30696
30875
|
// src/login-sso.ts
|
|
@@ -30927,6 +31106,10 @@ async function runSsoLogin(opts, deps) {
|
|
|
30927
31106
|
webUrl: opts.webUrl,
|
|
30928
31107
|
accessToken,
|
|
30929
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,
|
|
30930
31113
|
me,
|
|
30931
31114
|
deps: {
|
|
30932
31115
|
fetchImpl,
|