neon 4.7.0 → 4.8.0

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.
Files changed (56) hide show
  1. package/README.md +37 -59
  2. package/dist/_chunks/{auth_selection-ktL6uoEe.js → auth_selection-pitMFzDe.js} +1 -1
  3. package/dist/_chunks/{credential_io-B1JxWELN.js → credential_io-YeAxg9Xn.js} +2 -2
  4. package/dist/_chunks/{paths-g3DRqJiD.js → paths-DMq0Lt7a.js} +1 -1
  5. package/dist/_chunks/{profiles-D95AeZ5v.js → profiles-CvnFEQyd.js} +1 -1
  6. package/dist/analytics.js +2 -2
  7. package/dist/auth_context.js +1 -1
  8. package/dist/commands/auth.js +12 -9
  9. package/dist/commands/bootstrap.js +72 -43
  10. package/dist/commands/claim.js +1 -1
  11. package/dist/commands/init.js +80 -79
  12. package/dist/commands/link.js +78 -63
  13. package/dist/commands/mcp.js +1 -1
  14. package/dist/commands/plugins.js +4 -4
  15. package/dist/commands/profile.js +4 -4
  16. package/dist/commands/skills.js +1 -1
  17. package/dist/config.js +1 -1
  18. package/dist/credential_io.js +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/init/child.js +36 -0
  21. package/dist/init/plan.js +47 -0
  22. package/dist/init/tooling.js +43 -0
  23. package/dist/init/wizard.js +42 -0
  24. package/dist/mcp/targets.js +1 -1
  25. package/dist/plugins/plan.js +1 -1
  26. package/dist/plugins/targets.js +5 -1
  27. package/dist/retire_credential.js +1 -1
  28. package/dist/utils/middlewares.js +1 -1
  29. package/dist/utils/package_manager.js +10 -5
  30. package/package.json +13 -14
  31. package/dist/init/auth.js +0 -53
  32. package/dist/init/build_config.js +0 -4
  33. package/dist/init/detect_agent.js +0 -63
  34. package/dist/init/editors.js +0 -19
  35. package/dist/init/enrich_output.js +0 -61
  36. package/dist/init/extension.js +0 -154
  37. package/dist/init/inspect.js +0 -249
  38. package/dist/init/interactive.js +0 -529
  39. package/dist/init/neonctl.js +0 -139
  40. package/dist/init/orchestrate.js +0 -189
  41. package/dist/init/phases/auth.js +0 -195
  42. package/dist/init/phases/cleanup.js +0 -27
  43. package/dist/init/phases/db.js +0 -257
  44. package/dist/init/phases/getting_started.js +0 -218
  45. package/dist/init/phases/mcp.js +0 -219
  46. package/dist/init/phases/migrations.js +0 -250
  47. package/dist/init/phases/neon_auth.js +0 -116
  48. package/dist/init/phases/setup.js +0 -582
  49. package/dist/init/phases/skills.js +0 -86
  50. package/dist/init/phases/status.js +0 -82
  51. package/dist/init/profile_cli.js +0 -56
  52. package/dist/init/resolve_context.js +0 -105
  53. package/dist/init/route_command.js +0 -93
  54. package/dist/init/skills.js +0 -206
  55. package/dist/init/types.js +0 -1
  56. package/dist/init/vsix.js +0 -89
@@ -1,529 +0,0 @@
1
- import { FALLBACK_TEMPLATES, fetchTemplates, scaffoldTemplate } from "./bootstrap.js";
2
- import { detectAgent, detectIde } from "./detect_agent.js";
3
- import { neonInitAgentCmd } from "./profile_cli.js";
4
- import { installNeonMcpServer } from "../mcp/install.js";
5
- import { agentSupportsProjectMcp, detectInstalledAgents, getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
6
- import { getSkillsAgentName, mcpPickerOptions } from "./agents.js";
7
- import { ensureNeonctlAuth, isAuthenticated } from "./auth.js";
8
- import { installExtension, isExtensionInstalled } from "./extension.js";
9
- import { inspectProject } from "./inspect.js";
10
- import { ensureNeonctl } from "./neonctl.js";
11
- import { ensureSkillsUpToDate, installAgentSkills, skillsInstalledForAgent } from "./skills.js";
12
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
13
- import { resolve } from "node:path";
14
- import which from "which";
15
- import { SelectPrompt } from "@clack/core";
16
- import { confirm, isCancel, log, multiselect, outro, select, spinner } from "@clack/prompts";
17
- import { bold, dim, gray, italic } from "yoctocolors";
18
- import picocolors from "picocolors";
19
- //#region src/init/interactive.ts
20
- /**
21
- * Interactive v2 CLI — purpose-built guided flow for humans.
22
- * Uses the same underlying install functions but with a clean clack-based UX.
23
- */
24
- function wordWrap(text, width) {
25
- return text.split("\n").map((line) => {
26
- if (line.length <= width) return line;
27
- const words = line.split(" ");
28
- const lines = [];
29
- let current = "";
30
- for (const word of words) if (current.length + word.length + 1 > width && current.length > 0) {
31
- lines.push(current);
32
- current = word;
33
- } else current = current.length > 0 ? `${current} ${word}` : word;
34
- if (current.length > 0) lines.push(current);
35
- return lines.join("\n");
36
- }).join("\n");
37
- }
38
- const neonGreenFn = (s) => `\x1b[38;2;75;181;120m${s}\x1b[39m`;
39
- const originalCyan = picocolors.cyan;
40
- const originalMagenta = picocolors.magenta;
41
- function patchClackColors() {
42
- const pc = picocolors;
43
- pc.cyan = neonGreenFn;
44
- pc.magenta = neonGreenFn;
45
- return () => {
46
- pc.cyan = originalCyan;
47
- pc.magenta = originalMagenta;
48
- };
49
- }
50
- const S_BAR = "│";
51
- const S_BAR_END = "└";
52
- const S_STEP_ACTIVE = "◆";
53
- const S_STEP_SUBMIT = "◇";
54
- const S_STEP_CANCEL = "■";
55
- const S_RADIO_ACTIVE = "●";
56
- const S_RADIO_INACTIVE = "○";
57
- const SENTINEL_NONE = "none";
58
- /**
59
- * Bespoke template picker built on @clack/core's `SelectPrompt`.
60
- *
61
- * The stock `@clack/prompts` `select` can only render an option on a single
62
- * line (title + a parenthesized hint on the focused row), which can't express
63
- * what we want here: a clean title-only list, and a focused row that expands to
64
- * `Title (tools)` with the description on its own italic line beneath. Driving
65
- * the core prompt directly lets us own the frame, so we emit a second
66
- * gutter-aligned line for the active option only.
67
- */
68
- async function selectTemplate(templates, message) {
69
- const options = [...templates.map((t) => ({
70
- value: t.id,
71
- label: t.title,
72
- tools: t.tools,
73
- description: t.description
74
- })), {
75
- value: SENTINEL_NONE,
76
- label: "No thanks — continue without scaffolding"
77
- }];
78
- const green = neonGreenFn;
79
- const descIndent = " ";
80
- const descWidth = Math.max(24, (process.stdout.columns || 80) - 8);
81
- const renderActive = (option) => {
82
- const tools = option.tools && option.tools.length > 0 ? ` ${dim(`(${option.tools.join(", ")})`)}` : "";
83
- const head = `${green(S_RADIO_ACTIVE)} ${option.label}${tools}`;
84
- if (!option.description) return head;
85
- return `${head}\n${wordWrap(option.description, descWidth).split("\n").map((line) => `${green(S_BAR)}${descIndent}${italic(dim(line))}`).join("\n")}`;
86
- };
87
- return new SelectPrompt({
88
- options,
89
- initialValue: options[0]?.value,
90
- render() {
91
- const active = this.options[this.cursor];
92
- const heading = (symbol) => `${gray(S_BAR)}\n${symbol} ${message}\n`;
93
- if (this.state === "submit") return `${heading(green(S_STEP_SUBMIT))}${gray(S_BAR)} ${dim(active?.label ?? "")}`;
94
- if (this.state === "cancel") return `${heading(green(S_STEP_CANCEL))}${gray(S_BAR)} ${dim(active?.label ?? "")}\n${gray(S_BAR)}`;
95
- const body = this.options.map((option) => option === active ? renderActive(option) : `${dim(S_RADIO_INACTIVE)} ${dim(option.label)}`).join(`\n${green(S_BAR)} `);
96
- return `${heading(green(S_STEP_ACTIVE))}${green(S_BAR)} ${body}\n${green(S_BAR_END)}\n`;
97
- }
98
- }).prompt();
99
- }
100
- async function interactiveInit(options = {}) {
101
- const restoreColors = patchClackColors();
102
- try {
103
- await interactiveInitInner(options);
104
- } finally {
105
- restoreColors();
106
- }
107
- }
108
- async function interactiveInitInner(options) {
109
- process.stdout.write(`\n\x1b[38;2;75;181;120m${[
110
- " ██╗ ██╗██████╗ ██████╗ ██╗ ██╗",
111
- " ███╗ ██║██╔═══╝ ██╔═══██╗███╗ ██║",
112
- " ████╗██║██████╗ ██║ ██║████╗██║",
113
- " ██╔████║██╔═══╝ ██║ ██║██╔████║",
114
- " ██║╚███║██████╗ ╚██████╔╝██║╚███║",
115
- " ╚═╝ ╚══╝╚═════╝ ╚═════╝ ╚═╝ ╚══╝"
116
- ].join("\n")}\x1b[0m\n`);
117
- process.stdout.write(`${dim(wordWrap("\nLet's get your project set up with Neon. We'll install the MCP server, agent skills, and IDE extension, then connect your app to a database.\n", process.stdout.columns || 80))}\n`);
118
- const detectedAgent = (() => {
119
- const raw = detectAgent();
120
- return raw ? tryResolveAddMcpAgentId(raw) : void 0;
121
- })();
122
- const inspectSpinner = spinner();
123
- inspectSpinner.start("Checking existing configuration...");
124
- const inspection = await inspectProject([
125
- {
126
- id: "has_app",
127
- description: "",
128
- lookFor: []
129
- },
130
- {
131
- id: "mcp_server",
132
- description: "",
133
- lookFor: []
134
- },
135
- {
136
- id: "skills",
137
- description: "",
138
- lookFor: []
139
- },
140
- {
141
- id: "connection_string",
142
- description: "",
143
- lookFor: []
144
- },
145
- {
146
- id: "project_stack",
147
- description: "",
148
- lookFor: []
149
- },
150
- {
151
- id: "migrations",
152
- description: "",
153
- lookFor: []
154
- },
155
- {
156
- id: "ide_type",
157
- description: "",
158
- lookFor: []
159
- }
160
- ]);
161
- inspectSpinner.stop(dim("Configuration checked ✓"));
162
- const hasApp = inspection.hasApp === true;
163
- let selectedFeatures = [];
164
- let selectedTemplate = null;
165
- if (options.preview && !hasApp) {
166
- let templates = FALLBACK_TEMPLATES;
167
- try {
168
- const fetched = await fetchTemplates();
169
- if (fetched && fetched.length > 0) templates = fetched;
170
- } catch {}
171
- const templateResult = await selectTemplate(templates, "No application detected. Would you like to scaffold a new project from a template?");
172
- if (isCancel(templateResult)) {
173
- outro("Setup cancelled.");
174
- return;
175
- }
176
- if (templateResult !== "none") {
177
- selectedTemplate = templates.find((t) => t.id === templateResult) ?? null;
178
- if (selectedTemplate) {
179
- selectedFeatures = selectedTemplate.requires;
180
- const bootstrapS = spinner();
181
- bootstrapS.start(`Scaffolding project from template "${selectedTemplate.title}"...`);
182
- try {
183
- await scaffoldTemplate(selectedTemplate, ".", { onWarn: (message) => log.warn(message) });
184
- bootstrapS.stop(dim(`Scaffolded project from "${selectedTemplate.title}" ✓`));
185
- } catch (err) {
186
- const msg = err instanceof Error ? err.message : "Unknown error";
187
- bootstrapS.stop("Failed to scaffold project");
188
- log.error(msg);
189
- outro("Setup failed.");
190
- return;
191
- }
192
- }
193
- }
194
- }
195
- if (!selectedTemplate && hasApp) {
196
- const featuresResult = await select({
197
- message: "Which Neon features would you like to enable for this project?",
198
- options: [{
199
- value: "database",
200
- label: "Database"
201
- }, {
202
- value: "database,auth",
203
- label: "Database + Neon Auth (adds authentication via Neon)"
204
- }],
205
- initialValue: "database"
206
- });
207
- if (isCancel(featuresResult)) {
208
- outro("Setup cancelled.");
209
- return;
210
- }
211
- selectedFeatures = featuresResult.split(",");
212
- }
213
- if (selectedFeatures.length > 0) {
214
- const neonPath = resolve(process.cwd(), ".neon");
215
- let existing = {};
216
- if (existsSync(neonPath)) try {
217
- existing = JSON.parse(readFileSync(neonPath, "utf-8"));
218
- } catch {}
219
- existing._init = { features: selectedFeatures };
220
- writeFileSync(neonPath, `${JSON.stringify(existing, null, 2)}\n`);
221
- }
222
- const mcpHits = inspection.mcpAgents ?? [];
223
- const detectedHasMcp = detectedAgent ? mcpHits.some((hit) => hit.agent === detectedAgent) : false;
224
- const mcpAlready = detectedAgent ? detectedHasMcp : inspection.mcpConfigured === true;
225
- const skillsAlready = selectedTemplate !== null || (detectedAgent ? skillsInstalledForAgent(detectedAgent, process.cwd(), options.preview) : inspection.skillsInstalled === true);
226
- const hasNeonConnection = inspection.connectionString === true;
227
- const needsInstall = !mcpAlready || !skillsAlready;
228
- const neonContextPath = resolve(process.cwd(), ".neon");
229
- const hasNeonContext = existsSync(neonContextPath) && (() => {
230
- try {
231
- return !!JSON.parse(readFileSync(neonContextPath, "utf-8")).projectId;
232
- } catch {
233
- return false;
234
- }
235
- })();
236
- const hasNeonAuth = (() => {
237
- for (const envFile of [".env", ".env.local"]) {
238
- const envPath = resolve(process.cwd(), envFile);
239
- if (existsSync(envPath)) try {
240
- const content = readFileSync(envPath, "utf-8");
241
- if (/^NEON_AUTH_/m.test(content)) return true;
242
- } catch {}
243
- }
244
- return false;
245
- })();
246
- let extensionAlready = false;
247
- const detectedIdeEditor = extensionEditorForAgent(detectedAgent);
248
- if (detectedIdeEditor) extensionAlready = await isExtensionInstalled(detectedIdeEditor);
249
- if (mcpAlready && skillsAlready && hasNeonConnection && hasNeonContext) {
250
- log.step(dim(`Neon MCP server already configured (${inspection.mcpScope || "detected"}) ✓`));
251
- log.step(dim(`Neon agent skills already installed (${inspection.skillsScope || "detected"}) ✓`));
252
- if (extensionAlready) log.step(dim("Neon editor extension installed ✓"));
253
- log.step(dim("Neon database connected ✓"));
254
- if (hasNeonAuth) {
255
- log.step(dim("Neon Auth configured ✓"));
256
- outro(dim("Your project is fully configured with Neon. Nothing to do."));
257
- return;
258
- }
259
- const authResult = await select({
260
- message: "Would you like to set up Neon Auth for user authentication?",
261
- options: [{
262
- value: "yes",
263
- label: "Yes, set up Neon Auth"
264
- }, {
265
- value: "no",
266
- label: "No, skip for now"
267
- }],
268
- initialValue: "no"
269
- });
270
- if (isCancel(authResult) || authResult === "no") {
271
- outro(dim(`Your project is configured with Neon. You can set up Neon Auth later by having your agent run: ${neonInitAgentCmd({ step: "neon-auth" })}`));
272
- return;
273
- }
274
- let projectId = null;
275
- try {
276
- projectId = JSON.parse(readFileSync(neonContextPath, "utf-8")).projectId ?? null;
277
- } catch {}
278
- log.step("Next steps");
279
- const promptLines = ["Set up Neon Auth for this project."];
280
- if (projectId) promptLines.push(`Project ID: ${projectId}.`);
281
- log.message(dim("Copy the following into your agent chat:"));
282
- log.message(promptLines.map((line) => bold(neonGreenFn(line))).join("\n"));
283
- outro(dim("Have feedback? Email us at feedback@neon.tech"));
284
- return;
285
- }
286
- if (mcpAlready) log.step(dim(`Neon MCP server already configured (${inspection.mcpScope || "detected"}) ✓`));
287
- if (skillsAlready) log.step(dim(`Neon agent skills already installed (${inspection.skillsScope || "detected"}) ✓`));
288
- if (needsInstall) {
289
- if (!(process.env.HOME || process.env.USERPROFILE)) {
290
- log.error("Could not determine home directory.");
291
- outro("Setup failed.");
292
- return;
293
- }
294
- let selectedAgents;
295
- if (detectedAgent) selectedAgents = [detectedAgent];
296
- else {
297
- const picked = await pickAgents();
298
- if (!picked) {
299
- outro("Setup cancelled.");
300
- return;
301
- }
302
- selectedAgents = picked;
303
- }
304
- let vscodeEditors = extensionEditorsFor(selectedAgents);
305
- let extensionAlreadyInstalled = false;
306
- if (vscodeEditors.length > 0) {
307
- extensionAlreadyInstalled = (await Promise.all(vscodeEditors.map((e) => isExtensionInstalled(e)))).every(Boolean);
308
- if (extensionAlreadyInstalled) log.step(dim("Neon editor extension already installed ✓"));
309
- }
310
- let canInstallExtension = vscodeEditors.length > 0 && !extensionAlreadyInstalled;
311
- let doInstallExtension = false;
312
- let mcpScope = "global";
313
- let skillsScope = "project";
314
- let modeResult;
315
- while (true) {
316
- const hintParts = [];
317
- if (selectedAgents.some((agent) => !mcpHits.some((hit) => hit.agent === agent))) hintParts.push("MCP server (global)");
318
- if (selectedTemplate === null && selectedAgents.some((agent) => getSkillsAgentName(agent) && !skillsInstalledForAgent(agent, process.cwd(), options.preview))) hintParts.push("agent skills (project)");
319
- const agentNames = selectedAgents.map((id) => getAgentDisplayName(id)).join(", ");
320
- const result = await select({
321
- message: `Configure ${agentNames} for Neon:`,
322
- options: [
323
- {
324
- value: "defaults",
325
- label: "Install with defaults",
326
- hint: hintParts.join(", ")
327
- },
328
- {
329
- value: "customize",
330
- label: "Customize installation",
331
- hint: canInstallExtension ? "choose scopes and optional editor extension" : "choose scopes and options"
332
- },
333
- {
334
- value: "change_editor",
335
- label: "Configure a different agent"
336
- }
337
- ],
338
- initialValue: "defaults"
339
- });
340
- if (isCancel(result)) {
341
- outro("Setup cancelled.");
342
- return;
343
- }
344
- if (result === "change_editor") {
345
- const picked = await pickAgents();
346
- if (!picked) {
347
- outro("Setup cancelled.");
348
- return;
349
- }
350
- selectedAgents = picked;
351
- vscodeEditors = extensionEditorsFor(selectedAgents);
352
- canInstallExtension = vscodeEditors.length > 0 && !extensionAlreadyInstalled;
353
- continue;
354
- }
355
- modeResult = result;
356
- break;
357
- }
358
- if (modeResult === "customize") {
359
- if (selectedAgents.some((agent) => !mcpHits.some((hit) => hit.agent === agent))) {
360
- const scopeResult = await select({
361
- message: "Where should the Neon MCP server be configured?",
362
- options: [
363
- {
364
- value: "global",
365
- label: "Global (available in all projects)"
366
- },
367
- ...selectedAgents.every(agentSupportsProjectMcp) ? [{
368
- value: "project",
369
- label: "Project-level (this project only)"
370
- }] : [],
371
- {
372
- value: "none",
373
- label: "Skip — do not install the MCP server"
374
- }
375
- ]
376
- });
377
- if (isCancel(scopeResult)) {
378
- outro("Setup cancelled.");
379
- return;
380
- }
381
- mcpScope = scopeResult;
382
- }
383
- if (selectedTemplate === null && selectedAgents.some((agent) => getSkillsAgentName(agent) && !skillsInstalledForAgent(agent, process.cwd(), options.preview))) {
384
- const skillsScopeResult = await select({
385
- message: "Where should Neon agent skills be installed?",
386
- options: [{
387
- value: "global",
388
- label: "Global (available in all projects)"
389
- }, {
390
- value: "project",
391
- label: "Project-level (this project only)"
392
- }],
393
- initialValue: "project"
394
- });
395
- if (isCancel(skillsScopeResult)) {
396
- outro("Setup cancelled.");
397
- return;
398
- }
399
- skillsScope = skillsScopeResult;
400
- }
401
- if (canInstallExtension) {
402
- const extResult = await confirm({ message: `Install the Neon extension for ${vscodeEditors.join(", ")}?` });
403
- if (isCancel(extResult)) {
404
- outro("Setup cancelled.");
405
- return;
406
- }
407
- doInstallExtension = extResult;
408
- }
409
- }
410
- if (!await isAuthenticated()) {
411
- const authS = spinner();
412
- authS.start("Authenticating with Neon...");
413
- if (!await ensureNeonctlAuth()) {
414
- authS.stop("Authentication failed.");
415
- outro("Run `neon init` again after signing in.");
416
- return;
417
- }
418
- authS.stop("Authenticated.");
419
- }
420
- const nctlS = spinner();
421
- nctlS.start("Checking Neon CLI...");
422
- const nctlResult = await ensureNeonctl((phase) => {
423
- nctlS.message(phase === "installing" ? "Installing Neon CLI..." : "Updating Neon CLI...");
424
- });
425
- switch (nctlResult.status) {
426
- case "already_current":
427
- nctlS.stop(dim(`Neon CLI is up to date (v${nctlResult.version}) ✓`));
428
- break;
429
- case "installed":
430
- nctlS.stop(dim(`Installed Neon CLI (v${nctlResult.version}) ✓`));
431
- break;
432
- case "updated":
433
- nctlS.stop(dim(`Updated Neon CLI to v${nctlResult.version} ✓`));
434
- break;
435
- case "failed":
436
- nctlS.stop(nctlResult.action === "updating" ? "Failed to update Neon CLI" : "Failed to install Neon CLI");
437
- log.warn(nctlResult.error ?? "The Neon CLI could not be installed automatically.");
438
- if (which.sync("npx", { nothrow: true })) log.warn("Setup will continue using npx.");
439
- }
440
- for (const agent of selectedAgents) {
441
- const label = getAgentDisplayName(agent);
442
- if (mcpScope !== "none" && !mcpHits.some((hit) => hit.agent === agent)) {
443
- const mcpS = spinner();
444
- mcpS.start(`Installing Neon MCP server for ${label}...`);
445
- const installed = installNeonMcpServer({
446
- agent,
447
- scope: mcpScope === "project" ? "project" : "global",
448
- cwd: process.cwd()
449
- });
450
- if (installed.ok) mcpS.stop(dim(`Neon MCP server configured for ${label} (${mcpScope}) ✓`));
451
- else if (installed.unsupported) {
452
- mcpS.stop(`Could not write MCP config for ${label}`);
453
- log.warn(installed.error);
454
- } else {
455
- mcpS.stop(`Failed to configure MCP server for ${label}`);
456
- log.error(installed.error);
457
- }
458
- }
459
- if (selectedTemplate === null && getSkillsAgentName(agent) && !skillsInstalledForAgent(agent, process.cwd(), options.preview)) await installAgentSkills([agent], {
460
- scope: skillsScope,
461
- preview: options.preview
462
- });
463
- }
464
- if (doInstallExtension) for (const editor of vscodeEditors) {
465
- const extS = spinner();
466
- extS.start(`Installing Neon extension for ${editor}...`);
467
- if (await installExtension(editor)) extS.stop(dim(`Neon extension installed for ${editor} ✓`));
468
- else extS.stop(`Extension install failed — install manually from the extensions panel.`);
469
- }
470
- }
471
- const ide = detectIde();
472
- const agentForSkills = detectAgent() ?? (ide === "Cursor" ? "cursor" : ide === "VS Code" ? "vscode" : ide === "Windsurf" ? "windsurf" : null);
473
- if (agentForSkills) {
474
- const detectedSkillsScope = inspection.skillsScope === "global" ? "global" : void 0;
475
- await ensureSkillsUpToDate(agentForSkills, detectedSkillsScope, options.preview);
476
- }
477
- const gettingStartedData = {};
478
- if (hasNeonConnection) gettingStartedData.hasConnectionString = true;
479
- if (inspection.framework && inspection.framework !== "none") gettingStartedData.framework = inspection.framework;
480
- if (inspection.orm && inspection.orm !== "none") gettingStartedData.orm = inspection.orm;
481
- if (inspection.migrationTool && inspection.migrationTool !== "none") gettingStartedData.migrationTool = inspection.migrationTool;
482
- if (inspection.migrationDir && inspection.migrationDir !== "none") gettingStartedData.migrationDir = inspection.migrationDir;
483
- if (selectedFeatures.length > 0) gettingStartedData.features = selectedFeatures;
484
- if (options.preview) gettingStartedData.preview = true;
485
- const cmd = neonInitAgentCmd({
486
- step: "getting-started",
487
- ...gettingStartedData
488
- });
489
- const cols = (process.stdout.columns || 80) - 3;
490
- const promptText = `To finish setting up Neon using Neon's agent-guided onboarding experience, have your agent run this shell command: ${cmd}`;
491
- log.step("Next steps");
492
- log.message(dim("Copy the following into your agent chat:"));
493
- log.message(wordWrap(promptText, cols).split("\n").map((line) => bold(neonGreenFn(line))).join("\n"));
494
- outro(dim("Have feedback? Email us at feedback@neon.tech"));
495
- }
496
- function extensionEditorForAgent(agent) {
497
- if (agent === "cursor") return "Cursor";
498
- if (agent === "vscode") return "VS Code";
499
- const ide = detectIde();
500
- if (ide === "Cursor" || ide === "VS Code") return ide;
501
- return null;
502
- }
503
- function extensionEditorsFor(selected) {
504
- const out = [];
505
- if (selected.includes("cursor")) out.push("Cursor");
506
- if (selected.includes("vscode")) out.push("VS Code");
507
- const ide = detectIde();
508
- if ((ide === "Cursor" || ide === "VS Code") && !out.includes(ide)) out.push(ide);
509
- return out;
510
- }
511
- async function pickAgents() {
512
- const options = mcpPickerOptions();
513
- const installed = await detectInstalledAgents();
514
- const response = await multiselect({
515
- message: "Which agent(s) would you like to configure?",
516
- options,
517
- initialValues: installed.filter((id) => options.some((option) => option.value === id)),
518
- required: true
519
- });
520
- if (isCancel(response)) return null;
521
- const selected = response;
522
- if (selected.length === 0) {
523
- log.warn("No agents selected.");
524
- return null;
525
- }
526
- return selected;
527
- }
528
- //#endregion
529
- export { interactiveInit };
@@ -1,139 +0,0 @@
1
- import { globalInstallCommand, resolveInvokingPackageManager } from "../utils/package_manager.js";
2
- import { explicitConfigDirCli, explicitProfileCli } from "./profile_cli.js";
3
- import { lstatSync } from "node:fs";
4
- import { execa } from "execa";
5
- //#region src/init/neonctl.ts
6
- /**
7
- * `npx -y neon` avoids global-install assumptions, and `CI=` keeps emitted calls
8
- * non-interactive. API and OAuth hosts stay ambient because the CLI reads them directly.
9
- */
10
- function neonctlCmd() {
11
- return `CI= npx -y neon${explicitProfileCli()}${explicitConfigDirCli()}`;
12
- }
13
- /**
14
- * The global binaries that indicate the Neon CLI is installed. The current
15
- * package ships `neon`; `neonctl` is the legacy alias, kept in the probe so an
16
- * older global install still counts as "installed" (and isn't reinstalled).
17
- */
18
- const NEON_CLI_BINARIES = ["neon", "neonctl"];
19
- /**
20
- * Gets the currently installed Neon CLI version, probing the `neon` binary
21
- * first and falling back to the legacy `neonctl` alias. Returns null when the
22
- * CLI isn't on PATH.
23
- */
24
- async function getNeonCliVersion() {
25
- for (const bin of NEON_CLI_BINARIES) try {
26
- const match = (await execa(bin, ["--version"], {
27
- stdio: "pipe",
28
- timeout: 5e3
29
- })).stdout.trim().match(/(\d+\.\d+\.\d+)/);
30
- if (match) return match[1];
31
- } catch {}
32
- return null;
33
- }
34
- /**
35
- * Checks whether the Neon CLI is globally installed and whether it's up to date.
36
- */
37
- async function checkNeonctl() {
38
- const currentVersion = await getNeonCliVersion();
39
- if (!currentVersion) return {
40
- installed: false,
41
- currentVersion: null,
42
- latestVersion: null,
43
- needsUpdate: true
44
- };
45
- let latestVersion = null;
46
- try {
47
- latestVersion = (await execa("npm", [
48
- "view",
49
- "neon",
50
- "version"
51
- ], {
52
- stdio: "pipe",
53
- timeout: 1e4
54
- })).stdout.trim();
55
- } catch {
56
- return {
57
- installed: true,
58
- currentVersion,
59
- latestVersion: null,
60
- needsUpdate: false
61
- };
62
- }
63
- const needsUpdate = currentVersion !== null && latestVersion !== null && currentVersion !== latestVersion && isOlderVersion(currentVersion, latestVersion);
64
- return {
65
- installed: true,
66
- currentVersion,
67
- latestVersion,
68
- needsUpdate
69
- };
70
- }
71
- function isOlderVersion(current, latest) {
72
- const c = current.split(".").map(Number);
73
- const l = latest.split(".").map(Number);
74
- for (let i = 0; i < 3; i++) {
75
- if ((c[i] ?? 0) < (l[i] ?? 0)) return true;
76
- if ((c[i] ?? 0) > (l[i] ?? 0)) return false;
77
- }
78
- return false;
79
- }
80
- /**
81
- * Checks if neonctl is installed via a local dev symlink.
82
- * If so, skip install/update — the developer manages it manually.
83
- */
84
- function isLocalDevSymlink() {
85
- try {
86
- const home = process.env.HOME || process.env.USERPROFILE || "";
87
- const roots = [`${process.env.NVM_DIR || `${home}/.nvm`}/versions/node/${process.version}/lib/node_modules`, `${home}/.nvm/versions/node/${process.version}/lib/node_modules`];
88
- for (const root of roots) for (const pkg of NEON_CLI_BINARIES) try {
89
- if (lstatSync(`${root}/${pkg}`).isSymbolicLink()) return true;
90
- } catch {}
91
- return false;
92
- } catch {
93
- return false;
94
- }
95
- }
96
- /**
97
- * Ensures neonctl is globally installed and up to date.
98
- * Uses the same package manager that invoked the init command.
99
- */
100
- async function ensureNeonctl(onProgress) {
101
- if (isLocalDevSymlink()) return {
102
- status: "already_current",
103
- version: await getNeonCliVersion() ?? "dev"
104
- };
105
- const check = await checkNeonctl();
106
- if (check.installed && !check.needsUpdate) return {
107
- status: "already_current",
108
- version: check.currentVersion ?? void 0
109
- };
110
- const pm = resolveInvokingPackageManager();
111
- const install = globalInstallCommand(pm, "neon");
112
- const action = check.installed ? "updating" : "installing";
113
- if (!install) return {
114
- status: "failed",
115
- action,
116
- error: `Could not ${action === "updating" ? "update" : "install"} the Neon CLI: this machine has no package manager that can perform a global install. npm, pnpm and bun are not on PATH (yarn Berry has no global install). Install npm, pnpm or bun, then run this again.`
117
- };
118
- const { command, args } = install;
119
- onProgress?.(action);
120
- try {
121
- await execa(command, args, {
122
- stdio: "pipe",
123
- timeout: 6e4
124
- });
125
- const version = await getNeonCliVersion();
126
- return {
127
- status: check.installed ? "updated" : "installed",
128
- version: version ?? void 0
129
- };
130
- } catch (err) {
131
- return {
132
- status: "failed",
133
- action,
134
- error: err instanceof Error ? err.message : "Unknown error"
135
- };
136
- }
137
- }
138
- //#endregion
139
- export { checkNeonctl, ensureNeonctl, neonctlCmd };