neon-init 0.16.1 → 0.16.3
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/cli.js +29 -12
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/interactive.js +27 -20
- package/dist/interactive.js.map +1 -1
- package/dist/lib/auth.js +2 -2
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/bootstrap.d.ts +10 -3
- package/dist/lib/bootstrap.d.ts.map +1 -1
- package/dist/lib/bootstrap.js +67 -22
- package/dist/lib/bootstrap.js.map +1 -1
- package/dist/lib/enrich-output.d.ts +11 -0
- package/dist/lib/enrich-output.d.ts.map +1 -0
- package/dist/lib/enrich-output.js +58 -0
- package/dist/lib/enrich-output.js.map +1 -0
- package/dist/lib/neonctl.d.ts +4 -14
- package/dist/lib/neonctl.d.ts.map +1 -1
- package/dist/lib/neonctl.js +5 -29
- package/dist/lib/neonctl.js.map +1 -1
- package/dist/lib/phases/auth.d.ts.map +1 -1
- package/dist/lib/phases/auth.js +2 -2
- package/dist/lib/phases/auth.js.map +1 -1
- package/dist/lib/phases/getting-started.js +2 -2
- package/dist/lib/phases/getting-started.js.map +1 -1
- package/dist/lib/phases/setup.js +2 -1
- package/dist/lib/phases/setup.js.map +1 -1
- package/dist/lib/phases/status.d.ts +1 -1
- package/dist/lib/phases/status.d.ts.map +1 -1
- package/dist/lib/phases/status.js +5 -5
- package/dist/lib/phases/status.js.map +1 -1
- package/dist/lib/route-command.d.ts +8 -1
- package/dist/lib/route-command.d.ts.map +1 -1
- package/dist/lib/route-command.js +87 -1
- package/dist/lib/route-command.js.map +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/interactive.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive.js","names":[],"sources":["../src/interactive.ts"],"sourcesContent":["/**\n * Interactive v2 CLI — purpose-built guided flow for humans.\n * Uses the same underlying install functions but with a clean clack-based UX.\n */\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n\tconfirm,\n\tisCancel,\n\tlog,\n\tmultiselect,\n\toutro,\n\tselect,\n\tspinner,\n} from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { bold, dim } from \"yoctocolors\";\nimport { ALL_CONFIGURABLE_AGENTS, getAddMcpAgentId } from \"./lib/agents.js\";\nimport { ensureNeonctlAuth, isAuthenticated } from \"./lib/auth.js\";\nimport {\n\ttype BootstrapTemplate,\n\tFALLBACK_TEMPLATES,\n\tfetchTemplates,\n\ttype NeonFeature,\n} from \"./lib/bootstrap.js\";\nimport { detectAgent } from \"./lib/detect-agent.js\";\nimport { detectAvailableEditors } from \"./lib/editors.js\";\nimport {\n\tinstallExtension,\n\tisExtensionInstalled,\n\tusesExtension,\n} from \"./lib/extension.js\";\nimport { inspectProject } from \"./lib/inspect.js\";\nimport { ensureNeonctl } from \"./lib/neonctl.js\";\nimport { installAgentSkills } from \"./lib/skills.js\";\nimport type { Editor } from \"./lib/types.js\";\n\nfunction wordWrap(text: string, width: number): string {\n\treturn text\n\t\t.split(\"\\n\")\n\t\t.map((line) => {\n\t\t\tif (line.length <= width) return line;\n\t\t\tconst words = line.split(\" \");\n\t\t\tconst lines: string[] = [];\n\t\t\tlet current = \"\";\n\t\t\tfor (const word of words) {\n\t\t\t\tif (\n\t\t\t\t\tcurrent.length + word.length + 1 > width &&\n\t\t\t\t\tcurrent.length > 0\n\t\t\t\t) {\n\t\t\t\t\tlines.push(current);\n\t\t\t\t\tcurrent = word;\n\t\t\t\t} else {\n\t\t\t\t\tcurrent = current.length > 0 ? `${current} ${word}` : word;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (current.length > 0) lines.push(current);\n\t\t\treturn lines.join(\"\\n\");\n\t\t})\n\t\t.join(\"\\n\");\n}\n\n// Patch picocolors (used by @clack/prompts) to use Neon green instead of cyan/magenta.\n// clack hardcodes picocolors.cyan() with no theme API, so this is the least invasive override.\nimport picocolors from \"picocolors\";\n\nconst neonGreenFn = (s: string) => `\\x1b[38;2;75;181;120m${s}\\x1b[39m`;\nconst originalCyan = picocolors.cyan;\nconst originalMagenta = picocolors.magenta;\n\nfunction patchClackColors(): () => void {\n\tconst pc = picocolors as unknown as Record<string, unknown>;\n\tpc.cyan = neonGreenFn;\n\tpc.magenta = neonGreenFn;\n\treturn () => {\n\t\tpc.cyan = originalCyan;\n\t\tpc.magenta = originalMagenta;\n\t};\n}\n\nexport interface InteractiveInitOptions {\n\tpreview?: boolean;\n}\n\nexport async function interactiveInit(\n\toptions: InteractiveInitOptions = {},\n): Promise<void> {\n\tconst restoreColors = patchClackColors();\n\ttry {\n\t\tawait interactiveInitInner(options);\n\t} finally {\n\t\trestoreColors();\n\t}\n}\n\nasync function interactiveInitInner(\n\toptions: InteractiveInitOptions,\n): Promise<void> {\n\tconsole.log();\n\tconsole.log(\n\t\t\"\\x1b[38;2;75;181;120m\" +\n\t\t\t[\n\t\t\t\t\" ██╗ ██╗██████╗ ██████╗ ██╗ ██╗\",\n\t\t\t\t\" ███╗ ██║██╔═══╝ ██╔═══██╗███╗ ██║\",\n\t\t\t\t\" ████╗██║██████╗ ██║ ██║████╗██║\",\n\t\t\t\t\" ██╔████║██╔═══╝ ██║ ██║██╔████║\",\n\t\t\t\t\" ██║╚███║██████╗ ╚██████╔╝██║╚███║\",\n\t\t\t\t\" ╚═╝ ╚══╝╚═════╝ ╚═════╝ ╚═╝ ╚══╝\",\n\t\t\t].join(\"\\n\") +\n\t\t\t\"\\x1b[0m\",\n\t);\n\tconsole.log(\n\t\tdim(\n\t\t\twordWrap(\n\t\t\t\t\"\\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\",\n\t\t\t\tprocess.stdout.columns || 80,\n\t\t\t),\n\t\t),\n\t);\n\n\tconst detectedAgentId = detectAgent();\n\tconst detectedEditor = detectedAgentId\n\t\t? agentIdToEditor(detectedAgentId)\n\t\t: null;\n\tlet editorList = detectedEditor ?? \"your editor\";\n\n\t// -----------------------------------------------------------------------\n\t// Step 1: Inspect what's already in place\n\t// -----------------------------------------------------------------------\n\tconst inspectSpinner = spinner();\n\tinspectSpinner.start(\"Checking existing configuration...\");\n\tconst inspection = await inspectProject([\n\t\t{ id: \"has_app\", description: \"\", lookFor: [] },\n\t\t{ id: \"mcp_server\", description: \"\", lookFor: [] },\n\t\t{ id: \"skills\", description: \"\", lookFor: [] },\n\t\t{ id: \"connection_string\", description: \"\", lookFor: [] },\n\t\t{ id: \"project_stack\", description: \"\", lookFor: [] },\n\t\t{ id: \"migrations\", description: \"\", lookFor: [] },\n\t\t{ id: \"ide_type\", description: \"\", lookFor: [] },\n\t]);\n\tinspectSpinner.stop(dim(\"Configuration checked ✓\"));\n\n\tconst hasApp = inspection.hasApp === true;\n\tlet selectedFeatures: NeonFeature[] = [];\n\tlet selectedTemplate: BootstrapTemplate | null = null;\n\n\t// Preview mode: bootstrap from template if no app detected\n\tif (options.preview && !hasApp) {\n\t\tlet templates = FALLBACK_TEMPLATES;\n\t\ttry {\n\t\t\tconst fetched = await fetchTemplates();\n\t\t\tif (fetched && fetched.length > 0) templates = fetched;\n\t\t} catch {}\n\n\t\tconst templateResult = await select({\n\t\t\tmessage:\n\t\t\t\t\"No application detected. Would you like to scaffold a new project from a template?\",\n\t\t\toptions: [\n\t\t\t\t...templates.map((t) => ({\n\t\t\t\t\tvalue: t.id,\n\t\t\t\t\tlabel: t.title,\n\t\t\t\t\thint: t.description,\n\t\t\t\t})),\n\t\t\t\t{\n\t\t\t\t\tvalue: \"none\",\n\t\t\t\t\tlabel: \"No thanks — continue without scaffolding\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tinitialValue: templates[0]?.id ?? \"none\",\n\t\t});\n\t\tif (isCancel(templateResult)) {\n\t\t\toutro(\"Setup cancelled.\");\n\t\t\treturn;\n\t\t}\n\t\tif (templateResult !== \"none\") {\n\t\t\tselectedTemplate =\n\t\t\t\ttemplates.find((t) => t.id === templateResult) ?? null;\n\t\t\tif (selectedTemplate) {\n\t\t\t\tselectedFeatures = selectedTemplate.requires;\n\t\t\t\tconst bootstrapS = spinner();\n\t\t\t\tbootstrapS.start(\n\t\t\t\t\t`Scaffolding project from template \"${selectedTemplate.title}\"...`,\n\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tawait execa(\n\t\t\t\t\t\t\"npx\",\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\"neonctl\",\n\t\t\t\t\t\t\t\"bootstrap\",\n\t\t\t\t\t\t\t\".\",\n\t\t\t\t\t\t\t\"--template\",\n\t\t\t\t\t\t\tselectedTemplate.id,\n\t\t\t\t\t\t\t\"--force\",\n\t\t\t\t\t\t],\n\t\t\t\t\t\t{ stdio: \"pipe\", timeout: 120000 },\n\t\t\t\t\t);\n\t\t\t\t\tbootstrapS.stop(\n\t\t\t\t\t\tdim(\n\t\t\t\t\t\t\t`Scaffolded project from \"${selectedTemplate.title}\" ✓`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst msg =\n\t\t\t\t\t\terr instanceof Error ? err.message : \"Unknown error\";\n\t\t\t\t\tbootstrapS.stop(\"Failed to scaffold project\");\n\t\t\t\t\tlog.error(msg);\n\t\t\t\t\toutro(\"Setup failed.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// For brownfield flows (existing app), ask which features to enable\n\tif (!selectedTemplate && hasApp) {\n\t\tconst featuresResult = await select({\n\t\t\tmessage:\n\t\t\t\t\"Which Neon features would you like to enable for this project?\",\n\t\t\toptions: [\n\t\t\t\t{ value: \"database\", label: \"Database\" },\n\t\t\t\t{\n\t\t\t\t\tvalue: \"database,auth\",\n\t\t\t\t\tlabel: \"Database + Neon Auth (adds authentication via Neon)\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tinitialValue: \"database\",\n\t\t});\n\t\tif (isCancel(featuresResult)) {\n\t\t\toutro(\"Setup cancelled.\");\n\t\t\treturn;\n\t\t}\n\t\tselectedFeatures = (featuresResult as string).split(\n\t\t\t\",\",\n\t\t) as NeonFeature[];\n\t}\n\n\t// Write _init metadata to .neon\n\tif (selectedFeatures.length > 0) {\n\t\tconst neonPath = resolve(process.cwd(), \".neon\");\n\t\tlet existing: Record<string, unknown> = {};\n\t\tif (existsSync(neonPath)) {\n\t\t\ttry {\n\t\t\t\texisting = JSON.parse(readFileSync(neonPath, \"utf-8\"));\n\t\t\t} catch {}\n\t\t}\n\t\texisting._init = { features: selectedFeatures };\n\t\twriteFileSync(neonPath, `${JSON.stringify(existing, null, 2)}\\n`);\n\t}\n\n\tconst mcpAlready = inspection.mcpConfigured === true;\n\t// If we bootstrapped, skills come from the template\n\tconst skillsAlready =\n\t\tinspection.skillsInstalled === true || selectedTemplate !== null;\n\tconst hasNeonConnection = inspection.connectionString === true;\n\tconst needsMcp = !mcpAlready;\n\tconst needsSkills = !skillsAlready;\n\tconst needsInstall = needsMcp || needsSkills;\n\n\t// Check if .neon context file exists\n\tconst neonContextPath = resolve(process.cwd(), \".neon\");\n\tconst hasNeonContext =\n\t\texistsSync(neonContextPath) &&\n\t\t(() => {\n\t\t\ttry {\n\t\t\t\tconst content = JSON.parse(\n\t\t\t\t\treadFileSync(neonContextPath, \"utf-8\"),\n\t\t\t\t);\n\t\t\t\treturn !!content.projectId;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})();\n\n\t// Check if Neon Auth is configured\n\tconst hasNeonAuth = (() => {\n\t\tfor (const envFile of [\".env\", \".env.local\"]) {\n\t\t\tconst envPath = resolve(process.cwd(), envFile);\n\t\t\tif (existsSync(envPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\t\t\tif (/^NEON_AUTH_/m.test(content)) return true;\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t})();\n\n\t// Check if extension is installed for the detected editor\n\tlet extensionAlready = false;\n\tif (detectedEditor && usesExtension(detectedEditor)) {\n\t\textensionAlready = await isExtensionInstalled(detectedEditor);\n\t}\n\n\t// If tooling + database are configured, check if there's anything left to do\n\tif (mcpAlready && skillsAlready && hasNeonConnection && hasNeonContext) {\n\t\tlog.step(dim(\"Neon MCP server already configured ✓\"));\n\t\tlog.step(dim(\"Neon agent skills already installed ✓\"));\n\t\tif (extensionAlready)\n\t\t\tlog.step(dim(\"Neon editor extension installed ✓\"));\n\t\tlog.step(dim(\"Neon database connected ✓\"));\n\n\t\tif (hasNeonAuth) {\n\t\t\tlog.step(dim(\"Neon Auth configured ✓\"));\n\t\t\toutro(\n\t\t\t\tdim(\n\t\t\t\t\t\"Your project is fully configured with Neon. Nothing to do.\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Neon Auth not configured — ask if they want it\n\t\tconst authResult = await select({\n\t\t\tmessage:\n\t\t\t\t\"Would you like to set up Neon Auth for user authentication?\",\n\t\t\toptions: [\n\t\t\t\t{ value: \"yes\", label: \"Yes, set up Neon Auth\" },\n\t\t\t\t{ value: \"no\", label: \"No, skip for now\" },\n\t\t\t],\n\t\t\tinitialValue: \"no\",\n\t\t});\n\n\t\tif (isCancel(authResult) || authResult === \"no\") {\n\t\t\toutro(\n\t\t\t\tdim(\n\t\t\t\t\t\"Your project is configured with Neon. You can set up Neon Auth later by having your agent run: neon-init neon-auth\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Read .neon for project context\n\t\tlet projectId: string | null = null;\n\t\ttry {\n\t\t\tconst neonCtx = JSON.parse(readFileSync(neonContextPath, \"utf-8\"));\n\t\t\tprojectId = neonCtx.projectId ?? null;\n\t\t} catch {}\n\n\t\tlog.step(\"Next steps\");\n\t\tconst promptLines = [\"Set up Neon Auth for this project.\"];\n\t\tif (projectId) promptLines.push(`Project ID: ${projectId}.`);\n\t\tlog.message(dim(\"Copy the following into your agent chat:\"));\n\t\tlog.message(\n\t\t\tpromptLines.map((line) => bold(neonGreenFn(line))).join(\"\\n\"),\n\t\t);\n\t\toutro(dim(\"Have feedback? Email us at feedback@neon.tech\"));\n\t\treturn;\n\t}\n\n\t// Log what's already in place\n\tif (mcpAlready) log.step(dim(\"Neon MCP server already configured ✓\"));\n\tif (skillsAlready) log.step(dim(\"Neon agent skills already installed ✓\"));\n\n\t// -----------------------------------------------------------------------\n\t// Step 3–5: Install what's missing (skip entirely if everything is configured)\n\t// -----------------------------------------------------------------------\n\tif (needsInstall) {\n\t\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\t\tif (!homeDir) {\n\t\t\tlog.error(\"Could not determine home directory.\");\n\t\t\toutro(\"Setup failed.\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet selectedEditors: Editor[];\n\t\tif (detectedEditor) {\n\t\t\tselectedEditors = [detectedEditor];\n\t\t} else {\n\t\t\tconst availableEditors = await detectAvailableEditors(homeDir);\n\t\t\tconst editorResponse = await multiselect({\n\t\t\t\tmessage: \"Which editor(s) would you like to configure?\",\n\t\t\t\toptions: ALL_CONFIGURABLE_AGENTS.map((agent) => ({\n\t\t\t\t\tvalue: agent.editor,\n\t\t\t\t\tlabel: agent.editor,\n\t\t\t\t\thint: agent.hint,\n\t\t\t\t})),\n\t\t\t\tinitialValues: availableEditors,\n\t\t\t\trequired: true,\n\t\t\t});\n\t\t\tif (isCancel(editorResponse)) {\n\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tselectedEditors = editorResponse as Editor[];\n\t\t\tif (selectedEditors.length === 0) {\n\t\t\t\tlog.warn(\"No editors selected.\");\n\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Check extension status\n\t\tconst vscodeEditors = selectedEditors.filter(usesExtension);\n\t\tlet extensionAlreadyInstalled = false;\n\t\tif (vscodeEditors.length > 0) {\n\t\t\tconst checks = await Promise.all(\n\t\t\t\tvscodeEditors.map((e) => isExtensionInstalled(e)),\n\t\t\t);\n\t\t\textensionAlreadyInstalled = checks.every(Boolean);\n\t\t\tif (extensionAlreadyInstalled) {\n\t\t\t\tlog.step(dim(\"Neon editor extension already installed ✓\"));\n\t\t\t}\n\t\t}\n\t\tlet doInstallExtension =\n\t\t\tvscodeEditors.length > 0 && !extensionAlreadyInstalled;\n\n\t\t// Build hint showing only what needs installing\n\t\tconst hintParts: string[] = [];\n\t\tif (needsMcp) hintParts.push(\"MCP server (global)\");\n\t\tif (needsSkills) hintParts.push(\"agent skills (project)\");\n\t\tif (doInstallExtension) hintParts.push(\"editor extension\");\n\n\t\t// Installation preferences\n\t\tlet mcpScope: \"global\" | \"project\" = \"global\";\n\t\tlet skillsScope: \"global\" | \"project\" = \"project\";\n\n\t\tlet modeResult: string;\n\t\twhile (true) {\n\t\t\tconst editorName = selectedEditors.join(\", \");\n\t\t\tconst result = await select({\n\t\t\t\tmessage: `Configure ${editorName} for Neon:`,\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: \"defaults\",\n\t\t\t\t\t\tlabel: \"Install with defaults\",\n\t\t\t\t\t\thint: hintParts.join(\", \"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: \"customize\",\n\t\t\t\t\t\tlabel: \"Customize installation\",\n\t\t\t\t\t\thint: \"choose scopes and options\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: \"change_editor\",\n\t\t\t\t\t\tlabel: \"Configure a different editor\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tinitialValue: \"defaults\",\n\t\t\t});\n\n\t\t\tif (isCancel(result)) {\n\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (result === \"change_editor\") {\n\t\t\t\tconst availableEditors = await detectAvailableEditors(homeDir);\n\t\t\t\tconst editorResponse = await multiselect({\n\t\t\t\t\tmessage: \"Which editor(s) would you like to configure?\",\n\t\t\t\t\toptions: ALL_CONFIGURABLE_AGENTS.map((agent) => ({\n\t\t\t\t\t\tvalue: agent.editor,\n\t\t\t\t\t\tlabel: agent.editor,\n\t\t\t\t\t\thint: agent.hint,\n\t\t\t\t\t})),\n\t\t\t\t\tinitialValues: availableEditors,\n\t\t\t\t\trequired: true,\n\t\t\t\t});\n\t\t\t\tif (isCancel(editorResponse)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tselectedEditors = editorResponse as Editor[];\n\t\t\t\tif (selectedEditors.length === 0) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmodeResult = result as string;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (modeResult === \"customize\") {\n\t\t\tif (needsMcp) {\n\t\t\t\tconst scopeResult = await select({\n\t\t\t\t\tmessage: \"Where should the Neon MCP server be configured?\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"global\",\n\t\t\t\t\t\t\tlabel: \"Global (available in all projects)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"project\",\n\t\t\t\t\t\t\tlabel: \"Project-level (this project only)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t\tif (isCancel(scopeResult)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmcpScope = scopeResult as \"global\" | \"project\";\n\t\t\t}\n\n\t\t\tif (needsSkills) {\n\t\t\t\tconst skillsScopeResult = await select({\n\t\t\t\t\tmessage: \"Where should Neon agent skills be installed?\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"global\",\n\t\t\t\t\t\t\tlabel: \"Global (available in all projects)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"project\",\n\t\t\t\t\t\t\tlabel: \"Project-level (this project only)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tinitialValue: \"project\",\n\t\t\t\t});\n\t\t\t\tif (isCancel(skillsScopeResult)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tskillsScope = skillsScopeResult as \"global\" | \"project\";\n\t\t\t}\n\n\t\t\tif (doInstallExtension) {\n\t\t\t\tconst extResult = await confirm({\n\t\t\t\t\tmessage: `Install the Neon extension for ${vscodeEditors.join(\", \")}?`,\n\t\t\t\t});\n\t\t\t\tif (isCancel(extResult)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdoInstallExtension = extResult;\n\t\t\t}\n\t\t}\n\n\t\t// Auth check before install\n\t\tconst installAuthed = await isAuthenticated();\n\t\tif (!installAuthed) {\n\t\t\tconst authS = spinner();\n\t\t\tauthS.start(\"Authenticating with Neon...\");\n\t\t\tconst authSuccess = await ensureNeonctlAuth();\n\t\t\tif (!authSuccess) {\n\t\t\t\tauthS.stop(\"Authentication failed.\");\n\t\t\t\toutro(\"Run neon-init again after signing in.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tauthS.stop(\"Authenticated.\");\n\t\t}\n\n\t\t// Ensure neonctl CLI is installed and up to date\n\t\tconst nctlS = spinner();\n\t\tnctlS.start(\"Checking neonctl CLI...\");\n\t\tconst nctlResult = await ensureNeonctl();\n\t\tswitch (nctlResult.status) {\n\t\t\tcase \"already_current\":\n\t\t\t\tnctlS.stop(\n\t\t\t\t\tdim(`neonctl CLI is up to date (v${nctlResult.version}) ✓`),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"installed\":\n\t\t\t\tnctlS.stop(\n\t\t\t\t\tdim(`Installed neonctl CLI (v${nctlResult.version}) ✓`),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"updated\":\n\t\t\t\tnctlS.stop(\n\t\t\t\t\tdim(`Updated neonctl CLI to v${nctlResult.version} ✓`),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"failed\":\n\t\t\t\tnctlS.stop(\"Failed to install neonctl CLI\");\n\t\t\t\tlog.warn(\n\t\t\t\t\t\"neonctl could not be installed automatically. The setup will continue using npx.\",\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Install only what's missing\n\t\tfor (const editor of selectedEditors) {\n\t\t\tif (needsMcp) {\n\t\t\t\tconst mcpAgentId = getAddMcpAgentId(editor);\n\t\t\t\tconst mcpArgs = [\n\t\t\t\t\t\"-y\",\n\t\t\t\t\t\"add-mcp\",\n\t\t\t\t\t\"https://mcp.neon.tech/mcp\",\n\t\t\t\t\t\"-n\",\n\t\t\t\t\t\"Neon\",\n\t\t\t\t\t\"-y\",\n\t\t\t\t\t\"-a\",\n\t\t\t\t\tmcpAgentId,\n\t\t\t\t];\n\t\t\t\tif (mcpScope === \"global\") mcpArgs.splice(5, 0, \"-g\");\n\n\t\t\t\tconst mcpS = spinner();\n\t\t\t\tmcpS.start(`Installing Neon MCP server for ${editor}...`);\n\t\t\t\ttry {\n\t\t\t\t\tawait execa(\"npx\", mcpArgs, {\n\t\t\t\t\t\tstdio: \"pipe\",\n\t\t\t\t\t\ttimeout: 60000,\n\t\t\t\t\t});\n\t\t\t\t\tmcpS.stop(\n\t\t\t\t\t\tdim(\n\t\t\t\t\t\t\t`Neon MCP server configured for ${editor} (${mcpScope}) ✓`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst msg =\n\t\t\t\t\t\terr instanceof Error ? err.message : \"Unknown error\";\n\t\t\t\t\tmcpS.stop(`Failed to configure MCP server for ${editor}`);\n\t\t\t\t\tlog.error(msg);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (needsSkills) {\n\t\t\t\tawait installAgentSkills([editor], { scope: skillsScope });\n\t\t\t}\n\n\t\t\tif (doInstallExtension && usesExtension(editor)) {\n\t\t\t\tconst extS = spinner();\n\t\t\t\textS.start(`Installing Neon extension for ${editor}...`);\n\t\t\t\tconst extOk = await installExtension(editor);\n\t\t\t\tif (extOk) {\n\t\t\t\t\textS.stop(dim(`Neon extension installed for ${editor} ✓`));\n\t\t\t\t} else {\n\t\t\t\t\textS.stop(\n\t\t\t\t\t\t`Extension install failed — install manually from the extensions panel.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\teditorList = selectedEditors.join(\", \");\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Step 6: Done — build prompt for the agent to continue\n\t// -----------------------------------------------------------------------\n\n\t// Build the getting-started data payload (same as agent mode)\n\tconst gettingStartedData: Record<string, unknown> = {};\n\tif (hasNeonConnection) gettingStartedData.hasConnectionString = true;\n\tif (inspection.framework && inspection.framework !== \"none\")\n\t\tgettingStartedData.framework = inspection.framework;\n\tif (inspection.orm && inspection.orm !== \"none\")\n\t\tgettingStartedData.orm = inspection.orm;\n\tif (inspection.migrationTool && inspection.migrationTool !== \"none\")\n\t\tgettingStartedData.migrationTool = inspection.migrationTool;\n\tif (inspection.migrationDir && inspection.migrationDir !== \"none\")\n\t\tgettingStartedData.migrationDir = inspection.migrationDir;\n\tif (selectedFeatures.length > 0)\n\t\tgettingStartedData.features = selectedFeatures;\n\tif (options.preview) gettingStartedData.preview = true;\n\n\tconst dataJson = JSON.stringify(gettingStartedData);\n\n\t// Build a natural-language prompt for the user to paste into their agent\n\tconst promptLines: string[] = [\n\t\t\"Continue setting up Neon for this project.\",\n\t\t`Run this command to proceed: neon-init getting-started --json --data '${dataJson}'`,\n\t\t\"Follow the steps it returns to connect a Neon database, pull environment variables, and verify the connection.\",\n\t];\n\tif (selectedFeatures.includes(\"auth\")) {\n\t\tpromptLines.push(\n\t\t\t\"After the database is connected, it will automatically set up Neon Auth.\",\n\t\t);\n\t}\n\n\tlog.step(\"Next steps\");\n\tlog.message(\n\t\tdim(\n\t\t\t`Restart ${editorList} to load the Neon MCP server, then copy the following into your agent chat:`,\n\t\t),\n\t);\n\tlog.message(promptLines.map((line) => bold(neonGreenFn(line))).join(\"\\n\"));\n\toutro(dim(\"Have feedback? Email us at feedback@neon.tech\"));\n}\n\nfunction agentIdToEditor(agentId: string): Editor | null {\n\tswitch (agentId) {\n\t\tcase \"cursor\":\n\t\t\treturn \"Cursor\";\n\t\tcase \"vscode\":\n\t\t\treturn \"VS Code\";\n\t\tcase \"claude-code\":\n\t\t\treturn \"Claude CLI\";\n\t\tcase \"windsurf\":\n\t\t\t// Windsurf not in Editor type yet — fall back to prompt\n\t\t\treturn null;\n\t\tcase \"codex\":\n\t\t\treturn \"Codex\";\n\t\tcase \"cline\":\n\t\t\treturn \"Cline\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"interactive.js","names":[],"sources":["../src/interactive.ts"],"sourcesContent":["/**\n * Interactive v2 CLI — purpose-built guided flow for humans.\n * Uses the same underlying install functions but with a clean clack-based UX.\n */\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n\tconfirm,\n\tisCancel,\n\tlog,\n\tmultiselect,\n\toutro,\n\tselect,\n\tspinner,\n} from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { bold, dim } from \"yoctocolors\";\nimport { ALL_CONFIGURABLE_AGENTS, getAddMcpAgentId } from \"./lib/agents.js\";\nimport { ensureNeonctlAuth, isAuthenticated } from \"./lib/auth.js\";\nimport {\n\ttype BootstrapTemplate,\n\tFALLBACK_TEMPLATES,\n\tfetchTemplates,\n\ttype NeonFeature,\n} from \"./lib/bootstrap.js\";\nimport { detectAgent } from \"./lib/detect-agent.js\";\nimport { detectAvailableEditors } from \"./lib/editors.js\";\nimport {\n\tinstallExtension,\n\tisExtensionInstalled,\n\tusesExtension,\n} from \"./lib/extension.js\";\nimport { inspectProject } from \"./lib/inspect.js\";\nimport { ensureNeonctl } from \"./lib/neonctl.js\";\nimport { installAgentSkills } from \"./lib/skills.js\";\nimport type { Editor } from \"./lib/types.js\";\n\nfunction wordWrap(text: string, width: number): string {\n\treturn text\n\t\t.split(\"\\n\")\n\t\t.map((line) => {\n\t\t\tif (line.length <= width) return line;\n\t\t\tconst words = line.split(\" \");\n\t\t\tconst lines: string[] = [];\n\t\t\tlet current = \"\";\n\t\t\tfor (const word of words) {\n\t\t\t\tif (\n\t\t\t\t\tcurrent.length + word.length + 1 > width &&\n\t\t\t\t\tcurrent.length > 0\n\t\t\t\t) {\n\t\t\t\t\tlines.push(current);\n\t\t\t\t\tcurrent = word;\n\t\t\t\t} else {\n\t\t\t\t\tcurrent = current.length > 0 ? `${current} ${word}` : word;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (current.length > 0) lines.push(current);\n\t\t\treturn lines.join(\"\\n\");\n\t\t})\n\t\t.join(\"\\n\");\n}\n\n// Patch picocolors (used by @clack/prompts) to use Neon green instead of cyan/magenta.\n// clack hardcodes picocolors.cyan() with no theme API, so this is the least invasive override.\nimport picocolors from \"picocolors\";\n\nconst neonGreenFn = (s: string) => `\\x1b[38;2;75;181;120m${s}\\x1b[39m`;\nconst originalCyan = picocolors.cyan;\nconst originalMagenta = picocolors.magenta;\n\nfunction patchClackColors(): () => void {\n\tconst pc = picocolors as unknown as Record<string, unknown>;\n\tpc.cyan = neonGreenFn;\n\tpc.magenta = neonGreenFn;\n\treturn () => {\n\t\tpc.cyan = originalCyan;\n\t\tpc.magenta = originalMagenta;\n\t};\n}\n\nexport interface InteractiveInitOptions {\n\tpreview?: boolean;\n}\n\nexport async function interactiveInit(\n\toptions: InteractiveInitOptions = {},\n): Promise<void> {\n\tconst restoreColors = patchClackColors();\n\ttry {\n\t\tawait interactiveInitInner(options);\n\t} finally {\n\t\trestoreColors();\n\t}\n}\n\nasync function interactiveInitInner(\n\toptions: InteractiveInitOptions,\n): Promise<void> {\n\tconsole.log();\n\tconsole.log(\n\t\t\"\\x1b[38;2;75;181;120m\" +\n\t\t\t[\n\t\t\t\t\" ██╗ ██╗██████╗ ██████╗ ██╗ ██╗\",\n\t\t\t\t\" ███╗ ██║██╔═══╝ ██╔═══██╗███╗ ██║\",\n\t\t\t\t\" ████╗██║██████╗ ██║ ██║████╗██║\",\n\t\t\t\t\" ██╔████║██╔═══╝ ██║ ██║██╔████║\",\n\t\t\t\t\" ██║╚███║██████╗ ╚██████╔╝██║╚███║\",\n\t\t\t\t\" ╚═╝ ╚══╝╚═════╝ ╚═════╝ ╚═╝ ╚══╝\",\n\t\t\t].join(\"\\n\") +\n\t\t\t\"\\x1b[0m\",\n\t);\n\tconsole.log(\n\t\tdim(\n\t\t\twordWrap(\n\t\t\t\t\"\\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\",\n\t\t\t\tprocess.stdout.columns || 80,\n\t\t\t),\n\t\t),\n\t);\n\n\tconst detectedAgentId = detectAgent();\n\tconst detectedEditor = detectedAgentId\n\t\t? agentIdToEditor(detectedAgentId)\n\t\t: null;\n\n\t// -----------------------------------------------------------------------\n\t// Step 1: Inspect what's already in place\n\t// -----------------------------------------------------------------------\n\tconst inspectSpinner = spinner();\n\tinspectSpinner.start(\"Checking existing configuration...\");\n\tconst inspection = await inspectProject([\n\t\t{ id: \"has_app\", description: \"\", lookFor: [] },\n\t\t{ id: \"mcp_server\", description: \"\", lookFor: [] },\n\t\t{ id: \"skills\", description: \"\", lookFor: [] },\n\t\t{ id: \"connection_string\", description: \"\", lookFor: [] },\n\t\t{ id: \"project_stack\", description: \"\", lookFor: [] },\n\t\t{ id: \"migrations\", description: \"\", lookFor: [] },\n\t\t{ id: \"ide_type\", description: \"\", lookFor: [] },\n\t]);\n\tinspectSpinner.stop(dim(\"Configuration checked ✓\"));\n\n\tconst hasApp = inspection.hasApp === true;\n\tlet selectedFeatures: NeonFeature[] = [];\n\tlet selectedTemplate: BootstrapTemplate | null = null;\n\n\t// Preview mode: bootstrap from template if no app detected\n\tif (options.preview && !hasApp) {\n\t\tlet templates = FALLBACK_TEMPLATES;\n\t\ttry {\n\t\t\tconst fetched = await fetchTemplates();\n\t\t\tif (fetched && fetched.length > 0) templates = fetched;\n\t\t} catch {}\n\n\t\tconst templateResult = await select({\n\t\t\tmessage:\n\t\t\t\t\"No application detected. Would you like to scaffold a new project from a template?\",\n\t\t\toptions: [\n\t\t\t\t...templates.map((t) => ({\n\t\t\t\t\tvalue: t.id,\n\t\t\t\t\tlabel: t.title,\n\t\t\t\t\thint: t.description,\n\t\t\t\t})),\n\t\t\t\t{\n\t\t\t\t\tvalue: \"none\",\n\t\t\t\t\tlabel: \"No thanks — continue without scaffolding\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tinitialValue: templates[0]?.id ?? \"none\",\n\t\t});\n\t\tif (isCancel(templateResult)) {\n\t\t\toutro(\"Setup cancelled.\");\n\t\t\treturn;\n\t\t}\n\t\tif (templateResult !== \"none\") {\n\t\t\tselectedTemplate =\n\t\t\t\ttemplates.find((t) => t.id === templateResult) ?? null;\n\t\t\tif (selectedTemplate) {\n\t\t\t\tselectedFeatures = selectedTemplate.requires;\n\t\t\t\tconst bootstrapS = spinner();\n\t\t\t\tbootstrapS.start(\n\t\t\t\t\t`Scaffolding project from template \"${selectedTemplate.title}\"...`,\n\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\t// Pin @latest (and -y) so a stale globally-installed neonctl\n\t\t\t\t\t// can't be picked up by npx — bootstrap's rate-limit fix lives\n\t\t\t\t\t// in recent neonctl.\n\t\t\t\t\tawait execa(\n\t\t\t\t\t\t\"npx\",\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\"-y\",\n\t\t\t\t\t\t\t\"neonctl@latest\",\n\t\t\t\t\t\t\t\"bootstrap\",\n\t\t\t\t\t\t\t\".\",\n\t\t\t\t\t\t\t\"--template\",\n\t\t\t\t\t\t\tselectedTemplate.id,\n\t\t\t\t\t\t\t\"--force\",\n\t\t\t\t\t\t],\n\t\t\t\t\t\t{ stdio: \"pipe\", timeout: 120000 },\n\t\t\t\t\t);\n\t\t\t\t\tbootstrapS.stop(\n\t\t\t\t\t\tdim(\n\t\t\t\t\t\t\t`Scaffolded project from \"${selectedTemplate.title}\" ✓`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst msg =\n\t\t\t\t\t\terr instanceof Error ? err.message : \"Unknown error\";\n\t\t\t\t\tbootstrapS.stop(\"Failed to scaffold project\");\n\t\t\t\t\tlog.error(msg);\n\t\t\t\t\toutro(\"Setup failed.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// For brownfield flows (existing app), ask which features to enable\n\tif (!selectedTemplate && hasApp) {\n\t\tconst featuresResult = await select({\n\t\t\tmessage:\n\t\t\t\t\"Which Neon features would you like to enable for this project?\",\n\t\t\toptions: [\n\t\t\t\t{ value: \"database\", label: \"Database\" },\n\t\t\t\t{\n\t\t\t\t\tvalue: \"database,auth\",\n\t\t\t\t\tlabel: \"Database + Neon Auth (adds authentication via Neon)\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tinitialValue: \"database\",\n\t\t});\n\t\tif (isCancel(featuresResult)) {\n\t\t\toutro(\"Setup cancelled.\");\n\t\t\treturn;\n\t\t}\n\t\tselectedFeatures = (featuresResult as string).split(\n\t\t\t\",\",\n\t\t) as NeonFeature[];\n\t}\n\n\t// Write _init metadata to .neon\n\tif (selectedFeatures.length > 0) {\n\t\tconst neonPath = resolve(process.cwd(), \".neon\");\n\t\tlet existing: Record<string, unknown> = {};\n\t\tif (existsSync(neonPath)) {\n\t\t\ttry {\n\t\t\t\texisting = JSON.parse(readFileSync(neonPath, \"utf-8\"));\n\t\t\t} catch {}\n\t\t}\n\t\texisting._init = { features: selectedFeatures };\n\t\twriteFileSync(neonPath, `${JSON.stringify(existing, null, 2)}\\n`);\n\t}\n\n\tconst mcpAlready = inspection.mcpConfigured === true;\n\t// If we bootstrapped, skills come from the template\n\tconst skillsAlready =\n\t\tinspection.skillsInstalled === true || selectedTemplate !== null;\n\tconst hasNeonConnection = inspection.connectionString === true;\n\tlet needsMcp = !mcpAlready;\n\tconst needsSkills = !skillsAlready;\n\tconst needsInstall = needsMcp || needsSkills;\n\n\t// Check if .neon context file exists\n\tconst neonContextPath = resolve(process.cwd(), \".neon\");\n\tconst hasNeonContext =\n\t\texistsSync(neonContextPath) &&\n\t\t(() => {\n\t\t\ttry {\n\t\t\t\tconst content = JSON.parse(\n\t\t\t\t\treadFileSync(neonContextPath, \"utf-8\"),\n\t\t\t\t);\n\t\t\t\treturn !!content.projectId;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})();\n\n\t// Check if Neon Auth is configured\n\tconst hasNeonAuth = (() => {\n\t\tfor (const envFile of [\".env\", \".env.local\"]) {\n\t\t\tconst envPath = resolve(process.cwd(), envFile);\n\t\t\tif (existsSync(envPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\t\t\tif (/^NEON_AUTH_/m.test(content)) return true;\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t})();\n\n\t// Check if extension is installed for the detected editor\n\tlet extensionAlready = false;\n\tif (detectedEditor && usesExtension(detectedEditor)) {\n\t\textensionAlready = await isExtensionInstalled(detectedEditor);\n\t}\n\n\t// If tooling + database are configured, check if there's anything left to do\n\tif (mcpAlready && skillsAlready && hasNeonConnection && hasNeonContext) {\n\t\tlog.step(dim(\"Neon MCP server already configured ✓\"));\n\t\tlog.step(dim(\"Neon agent skills already installed ✓\"));\n\t\tif (extensionAlready)\n\t\t\tlog.step(dim(\"Neon editor extension installed ✓\"));\n\t\tlog.step(dim(\"Neon database connected ✓\"));\n\n\t\tif (hasNeonAuth) {\n\t\t\tlog.step(dim(\"Neon Auth configured ✓\"));\n\t\t\toutro(\n\t\t\t\tdim(\n\t\t\t\t\t\"Your project is fully configured with Neon. Nothing to do.\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Neon Auth not configured — ask if they want it\n\t\tconst authResult = await select({\n\t\t\tmessage:\n\t\t\t\t\"Would you like to set up Neon Auth for user authentication?\",\n\t\t\toptions: [\n\t\t\t\t{ value: \"yes\", label: \"Yes, set up Neon Auth\" },\n\t\t\t\t{ value: \"no\", label: \"No, skip for now\" },\n\t\t\t],\n\t\t\tinitialValue: \"no\",\n\t\t});\n\n\t\tif (isCancel(authResult) || authResult === \"no\") {\n\t\t\toutro(\n\t\t\t\tdim(\n\t\t\t\t\t`Your project is configured with Neon. You can set up Neon Auth later by having your agent run: neonctl init --agent --json --data '{\"step\":\"neon-auth\"}'`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Read .neon for project context\n\t\tlet projectId: string | null = null;\n\t\ttry {\n\t\t\tconst neonCtx = JSON.parse(readFileSync(neonContextPath, \"utf-8\"));\n\t\t\tprojectId = neonCtx.projectId ?? null;\n\t\t} catch {}\n\n\t\tlog.step(\"Next steps\");\n\t\tconst promptLines = [\"Set up Neon Auth for this project.\"];\n\t\tif (projectId) promptLines.push(`Project ID: ${projectId}.`);\n\t\tlog.message(dim(\"Copy the following into your agent chat:\"));\n\t\tlog.message(\n\t\t\tpromptLines.map((line) => bold(neonGreenFn(line))).join(\"\\n\"),\n\t\t);\n\t\toutro(dim(\"Have feedback? Email us at feedback@neon.tech\"));\n\t\treturn;\n\t}\n\n\t// Log what's already in place\n\tif (mcpAlready) log.step(dim(\"Neon MCP server already configured ✓\"));\n\tif (skillsAlready) log.step(dim(\"Neon agent skills already installed ✓\"));\n\n\t// -----------------------------------------------------------------------\n\t// Step 3–5: Install what's missing (skip entirely if everything is configured)\n\t// -----------------------------------------------------------------------\n\tif (needsInstall) {\n\t\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\t\tif (!homeDir) {\n\t\t\tlog.error(\"Could not determine home directory.\");\n\t\t\toutro(\"Setup failed.\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet selectedEditors: Editor[];\n\t\tif (detectedEditor) {\n\t\t\tselectedEditors = [detectedEditor];\n\t\t} else {\n\t\t\tconst availableEditors = await detectAvailableEditors(homeDir);\n\t\t\tconst editorResponse = await multiselect({\n\t\t\t\tmessage: \"Which editor(s) would you like to configure?\",\n\t\t\t\toptions: ALL_CONFIGURABLE_AGENTS.map((agent) => ({\n\t\t\t\t\tvalue: agent.editor,\n\t\t\t\t\tlabel: agent.editor,\n\t\t\t\t\thint: agent.hint,\n\t\t\t\t})),\n\t\t\t\tinitialValues: availableEditors,\n\t\t\t\trequired: true,\n\t\t\t});\n\t\t\tif (isCancel(editorResponse)) {\n\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tselectedEditors = editorResponse as Editor[];\n\t\t\tif (selectedEditors.length === 0) {\n\t\t\t\tlog.warn(\"No editors selected.\");\n\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Check extension status\n\t\tconst vscodeEditors = selectedEditors.filter(usesExtension);\n\t\tlet extensionAlreadyInstalled = false;\n\t\tif (vscodeEditors.length > 0) {\n\t\t\tconst checks = await Promise.all(\n\t\t\t\tvscodeEditors.map((e) => isExtensionInstalled(e)),\n\t\t\t);\n\t\t\textensionAlreadyInstalled = checks.every(Boolean);\n\t\t\tif (extensionAlreadyInstalled) {\n\t\t\t\tlog.step(dim(\"Neon editor extension already installed ✓\"));\n\t\t\t}\n\t\t}\n\t\tlet doInstallExtension =\n\t\t\tvscodeEditors.length > 0 && !extensionAlreadyInstalled;\n\n\t\t// Build hint showing only what needs installing\n\t\tconst hintParts: string[] = [];\n\t\tif (needsMcp) hintParts.push(\"MCP server (global)\");\n\t\tif (needsSkills) hintParts.push(\"agent skills (project)\");\n\t\tif (doInstallExtension) hintParts.push(\"editor extension\");\n\n\t\t// Installation preferences\n\t\tlet mcpScope: \"global\" | \"project\" | \"none\" = \"global\";\n\t\tlet skillsScope: \"global\" | \"project\" = \"project\";\n\n\t\tlet modeResult: string;\n\t\twhile (true) {\n\t\t\tconst editorName = selectedEditors.join(\", \");\n\t\t\tconst result = await select({\n\t\t\t\tmessage: `Configure ${editorName} for Neon:`,\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: \"defaults\",\n\t\t\t\t\t\tlabel: \"Install with defaults\",\n\t\t\t\t\t\thint: hintParts.join(\", \"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: \"customize\",\n\t\t\t\t\t\tlabel: \"Customize installation\",\n\t\t\t\t\t\thint: \"choose scopes and options\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: \"change_editor\",\n\t\t\t\t\t\tlabel: \"Configure a different editor\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tinitialValue: \"defaults\",\n\t\t\t});\n\n\t\t\tif (isCancel(result)) {\n\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (result === \"change_editor\") {\n\t\t\t\tconst availableEditors = await detectAvailableEditors(homeDir);\n\t\t\t\tconst editorResponse = await multiselect({\n\t\t\t\t\tmessage: \"Which editor(s) would you like to configure?\",\n\t\t\t\t\toptions: ALL_CONFIGURABLE_AGENTS.map((agent) => ({\n\t\t\t\t\t\tvalue: agent.editor,\n\t\t\t\t\t\tlabel: agent.editor,\n\t\t\t\t\t\thint: agent.hint,\n\t\t\t\t\t})),\n\t\t\t\t\tinitialValues: availableEditors,\n\t\t\t\t\trequired: true,\n\t\t\t\t});\n\t\t\t\tif (isCancel(editorResponse)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tselectedEditors = editorResponse as Editor[];\n\t\t\t\tif (selectedEditors.length === 0) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmodeResult = result as string;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (modeResult === \"customize\") {\n\t\t\tif (needsMcp) {\n\t\t\t\tconst scopeResult = await select({\n\t\t\t\t\tmessage: \"Where should the Neon MCP server be configured?\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"global\",\n\t\t\t\t\t\t\tlabel: \"Global (available in all projects)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"project\",\n\t\t\t\t\t\t\tlabel: \"Project-level (this project only)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"none\",\n\t\t\t\t\t\t\tlabel: \"Skip — do not install the MCP server\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t\tif (isCancel(scopeResult)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmcpScope = scopeResult as \"global\" | \"project\" | \"none\";\n\t\t\t\tif (mcpScope === \"none\") needsMcp = false;\n\t\t\t}\n\n\t\t\tif (needsSkills) {\n\t\t\t\tconst skillsScopeResult = await select({\n\t\t\t\t\tmessage: \"Where should Neon agent skills be installed?\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"global\",\n\t\t\t\t\t\t\tlabel: \"Global (available in all projects)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: \"project\",\n\t\t\t\t\t\t\tlabel: \"Project-level (this project only)\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tinitialValue: \"project\",\n\t\t\t\t});\n\t\t\t\tif (isCancel(skillsScopeResult)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tskillsScope = skillsScopeResult as \"global\" | \"project\";\n\t\t\t}\n\n\t\t\tif (doInstallExtension) {\n\t\t\t\tconst extResult = await confirm({\n\t\t\t\t\tmessage: `Install the Neon extension for ${vscodeEditors.join(\", \")}?`,\n\t\t\t\t});\n\t\t\t\tif (isCancel(extResult)) {\n\t\t\t\t\toutro(\"Setup cancelled.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdoInstallExtension = extResult;\n\t\t\t}\n\t\t}\n\n\t\t// Auth check before install\n\t\tconst installAuthed = await isAuthenticated();\n\t\tif (!installAuthed) {\n\t\t\tconst authS = spinner();\n\t\t\tauthS.start(\"Authenticating with Neon...\");\n\t\t\tconst authSuccess = await ensureNeonctlAuth();\n\t\t\tif (!authSuccess) {\n\t\t\t\tauthS.stop(\"Authentication failed.\");\n\t\t\t\toutro(\"Run neon-init again after signing in.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tauthS.stop(\"Authenticated.\");\n\t\t}\n\n\t\t// Ensure neonctl CLI is installed and up to date\n\t\tconst nctlS = spinner();\n\t\tnctlS.start(\"Checking neonctl CLI...\");\n\t\tconst nctlResult = await ensureNeonctl();\n\t\tswitch (nctlResult.status) {\n\t\t\tcase \"already_current\":\n\t\t\t\tnctlS.stop(\n\t\t\t\t\tdim(`neonctl CLI is up to date (v${nctlResult.version}) ✓`),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"installed\":\n\t\t\t\tnctlS.stop(\n\t\t\t\t\tdim(`Installed neonctl CLI (v${nctlResult.version}) ✓`),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"updated\":\n\t\t\t\tnctlS.stop(\n\t\t\t\t\tdim(`Updated neonctl CLI to v${nctlResult.version} ✓`),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"failed\":\n\t\t\t\tnctlS.stop(\"Failed to install neonctl CLI\");\n\t\t\t\tlog.warn(\n\t\t\t\t\t\"neonctl could not be installed automatically. The setup will continue using npx.\",\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Install only what's missing\n\t\tfor (const editor of selectedEditors) {\n\t\t\tif (needsMcp) {\n\t\t\t\tconst mcpAgentId = getAddMcpAgentId(editor);\n\t\t\t\tconst mcpArgs = [\n\t\t\t\t\t\"-y\",\n\t\t\t\t\t\"add-mcp\",\n\t\t\t\t\t\"https://mcp.neon.tech/mcp\",\n\t\t\t\t\t\"-n\",\n\t\t\t\t\t\"Neon\",\n\t\t\t\t\t\"-y\",\n\t\t\t\t\t\"-a\",\n\t\t\t\t\tmcpAgentId,\n\t\t\t\t];\n\t\t\t\tif (mcpScope === \"global\") mcpArgs.splice(5, 0, \"-g\");\n\n\t\t\t\tconst mcpS = spinner();\n\t\t\t\tmcpS.start(`Installing Neon MCP server for ${editor}...`);\n\t\t\t\ttry {\n\t\t\t\t\tawait execa(\"npx\", mcpArgs, {\n\t\t\t\t\t\tstdio: \"pipe\",\n\t\t\t\t\t\ttimeout: 60000,\n\t\t\t\t\t});\n\t\t\t\t\tmcpS.stop(\n\t\t\t\t\t\tdim(\n\t\t\t\t\t\t\t`Neon MCP server configured for ${editor} (${mcpScope}) ✓`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst msg =\n\t\t\t\t\t\terr instanceof Error ? err.message : \"Unknown error\";\n\t\t\t\t\tmcpS.stop(`Failed to configure MCP server for ${editor}`);\n\t\t\t\t\tlog.error(msg);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (needsSkills) {\n\t\t\t\tawait installAgentSkills([editor], { scope: skillsScope });\n\t\t\t}\n\n\t\t\tif (doInstallExtension && usesExtension(editor)) {\n\t\t\t\tconst extS = spinner();\n\t\t\t\textS.start(`Installing Neon extension for ${editor}...`);\n\t\t\t\tconst extOk = await installExtension(editor);\n\t\t\t\tif (extOk) {\n\t\t\t\t\textS.stop(dim(`Neon extension installed for ${editor} ✓`));\n\t\t\t\t} else {\n\t\t\t\t\textS.stop(\n\t\t\t\t\t\t`Extension install failed — install manually from the extensions panel.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Step 6: Done — build prompt for the agent to continue\n\t// -----------------------------------------------------------------------\n\n\t// Build the getting-started data payload (same as agent mode)\n\tconst gettingStartedData: Record<string, unknown> = {};\n\tif (hasNeonConnection) gettingStartedData.hasConnectionString = true;\n\tif (inspection.framework && inspection.framework !== \"none\")\n\t\tgettingStartedData.framework = inspection.framework;\n\tif (inspection.orm && inspection.orm !== \"none\")\n\t\tgettingStartedData.orm = inspection.orm;\n\tif (inspection.migrationTool && inspection.migrationTool !== \"none\")\n\t\tgettingStartedData.migrationTool = inspection.migrationTool;\n\tif (inspection.migrationDir && inspection.migrationDir !== \"none\")\n\t\tgettingStartedData.migrationDir = inspection.migrationDir;\n\tif (selectedFeatures.length > 0)\n\t\tgettingStartedData.features = selectedFeatures;\n\tif (options.preview) gettingStartedData.preview = true;\n\n\t// Build a prompt for the user to paste into their agent chat\n\tconst cmd = `neonctl init --agent --json --data '${JSON.stringify({ step: \"getting-started\", ...gettingStartedData })}'`;\n\t// Account for clack's \"│ \" prefix (3 chars) when wrapping\n\tconst cols = (process.stdout.columns || 80) - 3;\n\tconst promptText = `To finish setting up Neon using Neon's agent-guided onboarding experience, have your agent run this shell command: ${cmd}`;\n\n\tlog.step(\"Next steps\");\n\tlog.message(dim(\"Copy the following into your agent chat:\"));\n\tlog.message(\n\t\twordWrap(promptText, cols)\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => bold(neonGreenFn(line)))\n\t\t\t.join(\"\\n\"),\n\t);\n\toutro(dim(\"Have feedback? Email us at feedback@neon.tech\"));\n}\n\nfunction agentIdToEditor(agentId: string): Editor | null {\n\tswitch (agentId) {\n\t\tcase \"cursor\":\n\t\t\treturn \"Cursor\";\n\t\tcase \"vscode\":\n\t\t\treturn \"VS Code\";\n\t\tcase \"claude-code\":\n\t\t\treturn \"Claude CLI\";\n\t\tcase \"windsurf\":\n\t\t\t// Windsurf not in Editor type yet — fall back to prompt\n\t\t\treturn null;\n\t\tcase \"codex\":\n\t\t\treturn \"Codex\";\n\t\tcase \"cline\":\n\t\t\treturn \"Cline\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n"],"mappings":"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"}
|
package/dist/lib/auth.js
CHANGED
|
@@ -25,7 +25,7 @@ async function ensureNeonctlAuth(options) {
|
|
|
25
25
|
return true;
|
|
26
26
|
} catch (error) {
|
|
27
27
|
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
28
|
-
if (!quiet) if (msg.includes("interactive auth") || msg.includes("CI")) log.error("Auth requires an interactive terminal. Run
|
|
28
|
+
if (!quiet) if (msg.includes("interactive auth") || msg.includes("CI")) log.error("Auth requires an interactive terminal. Run neonctl init in your system terminal (outside the chat) to sign in.");
|
|
29
29
|
else log.error(`Authentication failed: ${msg}`);
|
|
30
30
|
return false;
|
|
31
31
|
}
|
|
@@ -62,7 +62,7 @@ async function createApiKeyFromNeonctl(options) {
|
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
const keyName = `neonctl-init-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}`;
|
|
65
|
-
const apiBase = process.env.NEON_API_HOST ? `${process.env.NEON_API_HOST.
|
|
65
|
+
const apiBase = process.env.NEON_API_HOST ? `${new URL(process.env.NEON_API_HOST).origin}/api/v2` : "https://console.neon.tech/api/v2";
|
|
66
66
|
const response = await fetch(`${apiBase}/api_keys`, {
|
|
67
67
|
method: "POST",
|
|
68
68
|
headers: {
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","names":[],"sources":["../../src/lib/auth.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { log } from \"@clack/prompts\";\nimport { execa } from \"execa\";\n\nexport interface AuthOptions {\n\tjson?: boolean;\n}\n\n/**\n * Ensures neonctl is authenticated by running a command that triggers auth if needed\n * This will automatically start the OAuth flow if the user isn't already authenticated\n */\nexport async function ensureNeonctlAuth(\n\toptions?: AuthOptions,\n): Promise<boolean> {\n\tconst quiet = options?.json === true;\n\n\t// If already authenticated (e.g. ran in a terminal before), we can proceed\n\tconst existingToken = await getNeonctlAccessToken();\n\tif (existingToken) return true;\n\n\ttry {\n\t\t// Use execa to authenticate with neonctl\n\t\tawait execa(\"npx\", [\"-y\", \"neonctl\", \"me\"], {\n\t\t\t// Shows OAuth URL and prompts to the user\n\t\t\tstdio: \"inherit\",\n\t\t\t// Unset CI so neonctl doesn't refuse to open the browser (e.g. when run from agent chat)\n\t\t\tenv: { ...process.env, CI: undefined },\n\t\t});\n\t\treturn true;\n\t} catch (error) {\n\t\tconst msg = error instanceof Error ? error.message : \"Unknown error\";\n\t\tif (!quiet) {\n\t\t\tif (msg.includes(\"interactive auth\") || msg.includes(\"CI\")) {\n\t\t\t\tlog.error(\n\t\t\t\t\t\"Auth requires an interactive terminal. Run
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../../src/lib/auth.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { log } from \"@clack/prompts\";\nimport { execa } from \"execa\";\n\nexport interface AuthOptions {\n\tjson?: boolean;\n}\n\n/**\n * Ensures neonctl is authenticated by running a command that triggers auth if needed\n * This will automatically start the OAuth flow if the user isn't already authenticated\n */\nexport async function ensureNeonctlAuth(\n\toptions?: AuthOptions,\n): Promise<boolean> {\n\tconst quiet = options?.json === true;\n\n\t// If already authenticated (e.g. ran in a terminal before), we can proceed\n\tconst existingToken = await getNeonctlAccessToken();\n\tif (existingToken) return true;\n\n\ttry {\n\t\t// Use execa to authenticate with neonctl\n\t\tawait execa(\"npx\", [\"-y\", \"neonctl\", \"me\"], {\n\t\t\t// Shows OAuth URL and prompts to the user\n\t\t\tstdio: \"inherit\",\n\t\t\t// Unset CI so neonctl doesn't refuse to open the browser (e.g. when run from agent chat)\n\t\t\tenv: { ...process.env, CI: undefined },\n\t\t});\n\t\treturn true;\n\t} catch (error) {\n\t\tconst msg = error instanceof Error ? error.message : \"Unknown error\";\n\t\tif (!quiet) {\n\t\t\tif (msg.includes(\"interactive auth\") || msg.includes(\"CI\")) {\n\t\t\t\tlog.error(\n\t\t\t\t\t\"Auth requires an interactive terminal. Run neonctl init in your system terminal (outside the chat) to sign in.\",\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog.error(`Authentication failed: ${msg}`);\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/**\n * Checks whether neonctl has stored OAuth credentials.\n */\nexport async function isAuthenticated(): Promise<boolean> {\n\tconst token = await getNeonctlAccessToken();\n\treturn token !== null;\n}\n\n/**\n * Gets the OAuth access token from neonctl's stored credentials\n */\nasync function getNeonctlAccessToken(): Promise<string | null> {\n\ttry {\n\t\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\t\tif (!homeDir) return null;\n\n\t\tconst credentialsPath = resolve(\n\t\t\thomeDir,\n\t\t\t\".config\",\n\t\t\t\"neonctl\",\n\t\t\t\"credentials.json\",\n\t\t);\n\t\tif (!existsSync(credentialsPath)) return null;\n\n\t\tconst credentials = JSON.parse(readFileSync(credentialsPath, \"utf-8\"));\n\t\treturn credentials.access_token || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Creates an API key using the Neon API with the OAuth token from neonctl\n */\nexport async function createApiKeyFromNeonctl(\n\toptions?: AuthOptions,\n): Promise<string | null> {\n\tconst quiet = options?.json === true;\n\n\ttry {\n\t\tconst accessToken = await getNeonctlAccessToken();\n\t\tif (!accessToken) {\n\t\t\tif (!quiet) log.error(\"Could not find OAuth token from neonctl\");\n\t\t\treturn null;\n\t\t}\n\n\t\t// Generate a unique key name with timestamp\n\t\tconst timestamp = new Date()\n\t\t\t.toISOString()\n\t\t\t.replace(/[:.]/g, \"-\")\n\t\t\t.slice(0, -5); // e.g., 2024-10-08T15-30-45\n\t\tconst keyName = `neonctl-init-${timestamp}`;\n\n\t\t// Call Neon API to create an API key\n\t\tconst apiBase = process.env.NEON_API_HOST\n\t\t\t? `${new URL(process.env.NEON_API_HOST).origin}/api/v2`\n\t\t\t: \"https://console.neon.tech/api/v2\";\n\t\tconst response = await fetch(`${apiBase}/api_keys`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tkey_name: keyName,\n\t\t\t}),\n\t\t\tsignal: AbortSignal.timeout(30000),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tif (!quiet)\n\t\t\t\tlog.error(\n\t\t\t\t\t`Failed to create API key: ${response.status} ${errorText}`,\n\t\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst data = await response.json();\n\t\treturn data.key || null;\n\t} catch (error) {\n\t\tif (!quiet)\n\t\t\tlog.error(\n\t\t\t\t`Failed to create API key: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t\t);\n\t\treturn null;\n\t}\n}\n"],"mappings":";;;;;;;;;AAaA,eAAsB,kBACrB,SACmB;CACnB,MAAM,QAAQ,SAAS,SAAS;CAIhC,IAAI,MADwB,sBAAsB,GAC/B,OAAO;CAE1B,IAAI;EAEH,MAAM,MAAM,OAAO;GAAC;GAAM;GAAW;EAAI,GAAG;GAE3C,OAAO;GAEP,KAAK;IAAE,GAAG,QAAQ;IAAK,IAAI,KAAA;GAAU;EACtC,CAAC;EACD,OAAO;CACR,SAAS,OAAO;EACf,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU;EACrD,IAAI,CAAC,OACJ,IAAI,IAAI,SAAS,kBAAkB,KAAK,IAAI,SAAS,IAAI,GACxD,IAAI,MACH,gHACD;OAEA,IAAI,MAAM,0BAA0B,KAAK;EAG3C,OAAO;CACR;AACD;;;;AAKA,eAAsB,kBAAoC;CAEzD,OAAO,MADa,sBAAsB,MACzB;AAClB;;;;AAKA,eAAe,wBAAgD;CAC9D,IAAI;EACH,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;EAChD,IAAI,CAAC,SAAS,OAAO;EAErB,MAAM,kBAAkB,QACvB,SACA,WACA,WACA,kBACD;EACA,IAAI,CAAC,WAAW,eAAe,GAAG,OAAO;EAGzC,OADoB,KAAK,MAAM,aAAa,iBAAiB,OAAO,CACnD,CAAC,CAAC,gBAAgB;CACpC,QAAQ;EACP,OAAO;CACR;AACD;;;;AAKA,eAAsB,wBACrB,SACyB;CACzB,MAAM,QAAQ,SAAS,SAAS;CAEhC,IAAI;EACH,MAAM,cAAc,MAAM,sBAAsB;EAChD,IAAI,CAAC,aAAa;GACjB,IAAI,CAAC,OAAO,IAAI,MAAM,yCAAyC;GAC/D,OAAO;EACR;EAOA,MAAM,UAAU,iCAJE,IAAI,KAAK,EAAA,CACzB,YAAY,CAAC,CACb,QAAQ,SAAS,GAAG,CAAC,CACrB,MAAM,GAAG,EAC6B;EAGxC,MAAM,UAAU,QAAQ,IAAI,gBACzB,GAAG,IAAI,IAAI,QAAQ,IAAI,aAAa,CAAC,CAAC,OAAO,WAC7C;EACH,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,YAAY;GACnD,QAAQ;GACR,SAAS;IACR,eAAe,UAAU;IACzB,gBAAgB;GACjB;GACA,MAAM,KAAK,UAAU,EACpB,UAAU,QACX,CAAC;GACD,QAAQ,YAAY,QAAQ,GAAK;EAClC,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GACtC,IAAI,CAAC,OACJ,IAAI,MACH,6BAA6B,SAAS,OAAO,GAAG,WACjD;GACD,OAAO;EACR;EAGA,QAAO,MADY,SAAS,KAAK,EAAA,CACrB,OAAO;CACpB,SAAS,OAAO;EACf,IAAI,CAAC,OACJ,IAAI,MACH,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,iBACvE;EACD,OAAO;CACR;AACD"}
|
package/dist/lib/bootstrap.d.ts
CHANGED
|
@@ -17,12 +17,19 @@ interface BootstrapTemplate {
|
|
|
17
17
|
subdir: string;
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Hardcoded fallback used when every remote manifest source is unreachable.
|
|
22
|
+
* Kept in sync with `neondatabase/examples/bootstrap.yaml` (the source of
|
|
23
|
+
* truth) so that, even fully offline from the manifest, the picker still offers
|
|
24
|
+
* the full set of starters rather than a single template.
|
|
25
|
+
*/
|
|
21
26
|
declare const FALLBACK_TEMPLATES: BootstrapTemplate[];
|
|
22
27
|
declare function parseManifest(text: string): BootstrapTemplate[];
|
|
23
28
|
/**
|
|
24
|
-
* Fetch the template manifest
|
|
25
|
-
*
|
|
29
|
+
* Fetch the template manifest, trying each source in {@link manifestUrls} in
|
|
30
|
+
* order and returning the first that yields a non-empty template list. Falls
|
|
31
|
+
* back to the hardcoded list when every source is unreachable or empty, so the
|
|
32
|
+
* picker never fails just because a host is down.
|
|
26
33
|
*/
|
|
27
34
|
declare function fetchTemplates(): Promise<BootstrapTemplate[]>;
|
|
28
35
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","names":[],"sources":["../../src/lib/bootstrap.ts"],"mappings":";;AAMA;AAUA;
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","names":[],"sources":["../../src/lib/bootstrap.ts"],"mappings":";;AAMA;AAUA;AAoBA;AA0DgB,KAxFJ,WAAA,GAwFiB,UAAgB,GAAA,MAAA,GAAiB,WAAA,GAAA,YAAA,GAAA,gBAAA;AAmDxC,UAjIL,iBAAA,CAiImB;EAAA,EAAA,EAAA,MAAA;OAAY,EAAA,MAAA;aAAR,EAAA,MAAA;EAAO;YA5HpC;;;;;;;;;;;;;;cAeE,oBAAoB;iBA0DjB,aAAA,gBAA6B;;;;;;;iBAmDvB,cAAA,CAAA,GAAkB,QAAQ"}
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -2,20 +2,66 @@ import YAML from "yaml";
|
|
|
2
2
|
//#region src/lib/bootstrap.ts
|
|
3
3
|
/** Default features when a template doesn't specify `requires`. */
|
|
4
4
|
const DEFAULT_REQUIRES = ["database"];
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Hardcoded fallback used when every remote manifest source is unreachable.
|
|
7
|
+
* Kept in sync with `neondatabase/examples/bootstrap.yaml` (the source of
|
|
8
|
+
* truth) so that, even fully offline from the manifest, the picker still offers
|
|
9
|
+
* the full set of starters rather than a single template.
|
|
10
|
+
*/
|
|
11
|
+
const FALLBACK_TEMPLATES = [
|
|
12
|
+
{
|
|
13
|
+
id: "hono",
|
|
14
|
+
title: "Hono API (Drizzle, Neon Postgres) on Neon Functions",
|
|
15
|
+
description: "A Hono API using Drizzle ORM and Neon Postgres, ready to deploy as a Neon Function.",
|
|
16
|
+
requires: ["database", "functions"],
|
|
17
|
+
source: {
|
|
18
|
+
owner: "neondatabase",
|
|
19
|
+
repo: "examples",
|
|
20
|
+
ref: "main",
|
|
21
|
+
subdir: "with-hono"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "ai-sdk",
|
|
26
|
+
title: "AI SDK agent (AI Gateway, object storage, Drizzle) on Neon Functions",
|
|
27
|
+
description: "A Vercel AI SDK agent on Neon Functions: streams chat through the Neon AI Gateway, generates an image with OpenAI image generation, and stores it in Neon object storage indexed in Postgres via Drizzle.",
|
|
28
|
+
requires: [
|
|
29
|
+
"database",
|
|
30
|
+
"functions",
|
|
31
|
+
"object-storage",
|
|
32
|
+
"ai-gateway"
|
|
33
|
+
],
|
|
34
|
+
source: {
|
|
35
|
+
owner: "neondatabase",
|
|
36
|
+
repo: "examples",
|
|
37
|
+
ref: "main",
|
|
38
|
+
subdir: "with-ai-sdk"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "mastra",
|
|
43
|
+
title: "Mastra personal agent (AI Gateway, Mastra Memory) on Neon Functions",
|
|
44
|
+
description: "A Mastra personal-assistant agent on Neon Functions: streams chat through the Neon AI Gateway and uses Mastra Memory — backed by Neon Postgres — to remember the user across conversation threads via resource-scoped working memory.",
|
|
45
|
+
requires: [
|
|
46
|
+
"database",
|
|
47
|
+
"functions",
|
|
48
|
+
"ai-gateway"
|
|
49
|
+
],
|
|
50
|
+
source: {
|
|
51
|
+
owner: "neondatabase",
|
|
52
|
+
repo: "examples",
|
|
53
|
+
ref: "main",
|
|
54
|
+
subdir: "with-mastra"
|
|
55
|
+
}
|
|
16
56
|
}
|
|
17
|
-
|
|
18
|
-
const
|
|
57
|
+
];
|
|
58
|
+
const NEON_MANIFEST_URL = "https://neon.com/bootstrap/templates.yaml";
|
|
59
|
+
const GITHUB_RAW_MANIFEST_URL = "https://raw.githubusercontent.com/neondatabase/examples/main/bootstrap.yaml";
|
|
60
|
+
function manifestUrls() {
|
|
61
|
+
const override = process.env.NEON_BOOTSTRAP_MANIFEST_URL;
|
|
62
|
+
if (override) return [override];
|
|
63
|
+
return [NEON_MANIFEST_URL, GITHUB_RAW_MANIFEST_URL];
|
|
64
|
+
}
|
|
19
65
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
20
66
|
function parseManifest(text) {
|
|
21
67
|
const data = YAML.parse(text);
|
|
@@ -40,20 +86,19 @@ function parseManifest(text) {
|
|
|
40
86
|
return templates;
|
|
41
87
|
}
|
|
42
88
|
/**
|
|
43
|
-
* Fetch the template manifest
|
|
44
|
-
*
|
|
89
|
+
* Fetch the template manifest, trying each source in {@link manifestUrls} in
|
|
90
|
+
* order and returning the first that yields a non-empty template list. Falls
|
|
91
|
+
* back to the hardcoded list when every source is unreachable or empty, so the
|
|
92
|
+
* picker never fails just because a host is down.
|
|
45
93
|
*/
|
|
46
94
|
async function fetchTemplates() {
|
|
47
|
-
const url
|
|
48
|
-
try {
|
|
95
|
+
for (const url of manifestUrls()) try {
|
|
49
96
|
const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
50
97
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
51
98
|
const templates = parseManifest(await res.text());
|
|
52
|
-
if (templates.length
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return FALLBACK_TEMPLATES;
|
|
56
|
-
}
|
|
99
|
+
if (templates.length > 0) return templates;
|
|
100
|
+
} catch {}
|
|
101
|
+
return FALLBACK_TEMPLATES;
|
|
57
102
|
}
|
|
58
103
|
//#endregion
|
|
59
104
|
export { FALLBACK_TEMPLATES, fetchTemplates, parseManifest };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.js","names":[],"sources":["../../src/lib/bootstrap.ts"],"sourcesContent":["import YAML from \"yaml\";\n\n/**\n * Neon features that a template or project may require.\n * Each feature maps to a setup phase that the orchestrator can run.\n */\nexport type NeonFeature =\n\t| \"database\"\n\t| \"auth\"\n\t| \"functions\"\n\t| \"ai-gateway\"\n\t| \"object-storage\";\n\n/** Default features when a template doesn't specify `requires`. */\nconst DEFAULT_REQUIRES: NeonFeature[] = [\"database\"];\n\nexport interface BootstrapTemplate {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\t/** Neon features this template needs (defaults to [\"database\"]) */\n\trequires: NeonFeature[];\n\tsource: {\n\t\towner: string;\n\t\trepo: string;\n\t\tref: string;\n\t\tsubdir: string;\n\t};\n}\n\n
|
|
1
|
+
{"version":3,"file":"bootstrap.js","names":[],"sources":["../../src/lib/bootstrap.ts"],"sourcesContent":["import YAML from \"yaml\";\n\n/**\n * Neon features that a template or project may require.\n * Each feature maps to a setup phase that the orchestrator can run.\n */\nexport type NeonFeature =\n\t| \"database\"\n\t| \"auth\"\n\t| \"functions\"\n\t| \"ai-gateway\"\n\t| \"object-storage\";\n\n/** Default features when a template doesn't specify `requires`. */\nconst DEFAULT_REQUIRES: NeonFeature[] = [\"database\"];\n\nexport interface BootstrapTemplate {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\t/** Neon features this template needs (defaults to [\"database\"]) */\n\trequires: NeonFeature[];\n\tsource: {\n\t\towner: string;\n\t\trepo: string;\n\t\tref: string;\n\t\tsubdir: string;\n\t};\n}\n\n/**\n * Hardcoded fallback used when every remote manifest source is unreachable.\n * Kept in sync with `neondatabase/examples/bootstrap.yaml` (the source of\n * truth) so that, even fully offline from the manifest, the picker still offers\n * the full set of starters rather than a single template.\n */\nexport const FALLBACK_TEMPLATES: BootstrapTemplate[] = [\n\t{\n\t\tid: \"hono\",\n\t\ttitle: \"Hono API (Drizzle, Neon Postgres) on Neon Functions\",\n\t\tdescription:\n\t\t\t\"A Hono API using Drizzle ORM and Neon Postgres, ready to deploy as a Neon Function.\",\n\t\trequires: [\"database\", \"functions\"],\n\t\tsource: {\n\t\t\towner: \"neondatabase\",\n\t\t\trepo: \"examples\",\n\t\t\tref: \"main\",\n\t\t\tsubdir: \"with-hono\",\n\t\t},\n\t},\n\t{\n\t\tid: \"ai-sdk\",\n\t\ttitle: \"AI SDK agent (AI Gateway, object storage, Drizzle) on Neon Functions\",\n\t\tdescription:\n\t\t\t\"A Vercel AI SDK agent on Neon Functions: streams chat through the Neon AI Gateway, generates an image with OpenAI image generation, and stores it in Neon object storage indexed in Postgres via Drizzle.\",\n\t\trequires: [\"database\", \"functions\", \"object-storage\", \"ai-gateway\"],\n\t\tsource: {\n\t\t\towner: \"neondatabase\",\n\t\t\trepo: \"examples\",\n\t\t\tref: \"main\",\n\t\t\tsubdir: \"with-ai-sdk\",\n\t\t},\n\t},\n\t{\n\t\tid: \"mastra\",\n\t\ttitle: \"Mastra personal agent (AI Gateway, Mastra Memory) on Neon Functions\",\n\t\tdescription:\n\t\t\t\"A Mastra personal-assistant agent on Neon Functions: streams chat through the Neon AI Gateway and uses Mastra Memory — backed by Neon Postgres — to remember the user across conversation threads via resource-scoped working memory.\",\n\t\trequires: [\"database\", \"functions\", \"ai-gateway\"],\n\t\tsource: {\n\t\t\towner: \"neondatabase\",\n\t\t\trepo: \"examples\",\n\t\t\tref: \"main\",\n\t\t\tsubdir: \"with-mastra\",\n\t\t},\n\t},\n];\n\n// Primary manifest host is neon.com (CDN-backed, no GitHub rate limiting), with\n// the raw GitHub copy as a fallback and the hardcoded list as the last resort.\n// A single env override (used by tests) short-circuits the chain.\nconst NEON_MANIFEST_URL = \"https://neon.com/bootstrap/templates.yaml\";\nconst GITHUB_RAW_MANIFEST_URL =\n\t\"https://raw.githubusercontent.com/neondatabase/examples/main/bootstrap.yaml\";\n\nfunction manifestUrls(): string[] {\n\tconst override = process.env.NEON_BOOTSTRAP_MANIFEST_URL;\n\tif (override) return [override];\n\treturn [NEON_MANIFEST_URL, GITHUB_RAW_MANIFEST_URL];\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === \"object\" && value !== null;\n\nexport function parseManifest(text: string): BootstrapTemplate[] {\n\tconst data: unknown = YAML.parse(text);\n\tif (!isRecord(data) || !Array.isArray(data.templates)) {\n\t\tthrow new Error(\n\t\t\t'Invalid bootstrap manifest: missing \"templates\" array.',\n\t\t);\n\t}\n\tconst templates: BootstrapTemplate[] = [];\n\tfor (const item of data.templates) {\n\t\tif (\n\t\t\t!isRecord(item) ||\n\t\t\ttypeof item.id !== \"string\" ||\n\t\t\ttypeof item.title !== \"string\" ||\n\t\t\ttypeof item.description !== \"string\" ||\n\t\t\t!isRecord(item.source) ||\n\t\t\ttypeof item.source.owner !== \"string\" ||\n\t\t\ttypeof item.source.repo !== \"string\" ||\n\t\t\ttypeof item.source.ref !== \"string\" ||\n\t\t\ttypeof item.source.subdir !== \"string\"\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\t// Parse requires — accept string array, default to [\"database\"]\n\t\tconst requires: NeonFeature[] =\n\t\t\tArray.isArray(item.requires) &&\n\t\t\titem.requires.every((r: unknown) => typeof r === \"string\")\n\t\t\t\t? (item.requires as NeonFeature[])\n\t\t\t\t: DEFAULT_REQUIRES;\n\n\t\ttemplates.push({\n\t\t\tid: item.id,\n\t\t\ttitle: item.title,\n\t\t\tdescription: item.description,\n\t\t\trequires,\n\t\t\tsource: {\n\t\t\t\towner: item.source.owner,\n\t\t\t\trepo: item.source.repo,\n\t\t\t\tref: item.source.ref,\n\t\t\t\tsubdir: item.source.subdir,\n\t\t\t},\n\t\t});\n\t}\n\treturn templates;\n}\n\n/**\n * Fetch the template manifest, trying each source in {@link manifestUrls} in\n * order and returning the first that yields a non-empty template list. Falls\n * back to the hardcoded list when every source is unreachable or empty, so the\n * picker never fails just because a host is down.\n */\nexport async function fetchTemplates(): Promise<BootstrapTemplate[]> {\n\tfor (const url of manifestUrls()) {\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t});\n\t\t\tif (!res.ok) throw new Error(`HTTP ${res.status}`);\n\t\t\tconst templates = parseManifest(await res.text());\n\t\t\tif (templates.length > 0) return templates;\n\t\t} catch {\n\t\t\t// Try the next source.\n\t\t}\n\t}\n\treturn FALLBACK_TEMPLATES;\n}\n"],"mappings":";;;AAcA,MAAM,mBAAkC,CAAC,UAAU;;;;;;;AAsBnD,MAAa,qBAA0C;CACtD;EACC,IAAI;EACJ,OAAO;EACP,aACC;EACD,UAAU,CAAC,YAAY,WAAW;EAClC,QAAQ;GACP,OAAO;GACP,MAAM;GACN,KAAK;GACL,QAAQ;EACT;CACD;CACA;EACC,IAAI;EACJ,OAAO;EACP,aACC;EACD,UAAU;GAAC;GAAY;GAAa;GAAkB;EAAY;EAClE,QAAQ;GACP,OAAO;GACP,MAAM;GACN,KAAK;GACL,QAAQ;EACT;CACD;CACA;EACC,IAAI;EACJ,OAAO;EACP,aACC;EACD,UAAU;GAAC;GAAY;GAAa;EAAY;EAChD,QAAQ;GACP,OAAO;GACP,MAAM;GACN,KAAK;GACL,QAAQ;EACT;CACD;AACD;AAKA,MAAM,oBAAoB;AAC1B,MAAM,0BACL;AAED,SAAS,eAAyB;CACjC,MAAM,WAAW,QAAQ,IAAI;CAC7B,IAAI,UAAU,OAAO,CAAC,QAAQ;CAC9B,OAAO,CAAC,mBAAmB,uBAAuB;AACnD;AAEA,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,UAAU;AAExC,SAAgB,cAAc,MAAmC;CAChE,MAAM,OAAgB,KAAK,MAAM,IAAI;CACrC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,QAAQ,KAAK,SAAS,GACnD,MAAM,IAAI,MACT,0DACD;CAED,MAAM,YAAiC,CAAC;CACxC,KAAK,MAAM,QAAQ,KAAK,WAAW;EAClC,IACC,CAAC,SAAS,IAAI,KACd,OAAO,KAAK,OAAO,YACnB,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,gBAAgB,YAC5B,CAAC,SAAS,KAAK,MAAM,KACrB,OAAO,KAAK,OAAO,UAAU,YAC7B,OAAO,KAAK,OAAO,SAAS,YAC5B,OAAO,KAAK,OAAO,QAAQ,YAC3B,OAAO,KAAK,OAAO,WAAW,UAE9B;EAGD,MAAM,WACL,MAAM,QAAQ,KAAK,QAAQ,KAC3B,KAAK,SAAS,OAAO,MAAe,OAAO,MAAM,QAAQ,IACrD,KAAK,WACN;EAEJ,UAAU,KAAK;GACd,IAAI,KAAK;GACT,OAAO,KAAK;GACZ,aAAa,KAAK;GAClB;GACA,QAAQ;IACP,OAAO,KAAK,OAAO;IACnB,MAAM,KAAK,OAAO;IAClB,KAAK,KAAK,OAAO;IACjB,QAAQ,KAAK,OAAO;GACrB;EACD,CAAC;CACF;CACA,OAAO;AACR;;;;;;;AAQA,eAAsB,iBAA+C;CACpE,KAAK,MAAM,OAAO,aAAa,GAC9B,IAAI;EACH,MAAM,MAAM,MAAM,MAAM,KAAK,EAC5B,QAAQ,YAAY,QAAQ,GAAM,EACnC,CAAC;EACD,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,MAAM,QAAQ,IAAI,QAAQ;EACjD,MAAM,YAAY,cAAc,MAAM,IAAI,KAAK,CAAC;EAChD,IAAI,UAAU,SAAS,GAAG,OAAO;CAClC,QAAQ,CAER;CAED,OAAO;AACR"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/lib/enrich-output.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Walks a phase response object and:
|
|
4
|
+
* 1. Replaces `args` arrays with `command` strings (neonctl init --data format)
|
|
5
|
+
* 2. Renames `run_neon_init` → `run_shell_command`
|
|
6
|
+
* 3. Adds a description to finalize steps
|
|
7
|
+
*/
|
|
8
|
+
declare function enrichResponse(obj: unknown): unknown;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { enrichResponse };
|
|
11
|
+
//# sourceMappingURL=enrich-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich-output.d.ts","names":[],"sources":["../../src/lib/enrich-output.ts"],"mappings":";;AA8CA;;;;;iBAAgB,cAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/lib/enrich-output.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts neon-init args (e.g. ["neon-auth", "--json", "--setup"]) to a
|
|
4
|
+
* neonctl init --data command using the step routing pattern.
|
|
5
|
+
*/
|
|
6
|
+
function argsToCommand(args) {
|
|
7
|
+
const data = {};
|
|
8
|
+
let i = 0;
|
|
9
|
+
if (args.length > 0 && !args[0].startsWith("-")) {
|
|
10
|
+
data.step = args[0];
|
|
11
|
+
i = 1;
|
|
12
|
+
}
|
|
13
|
+
while (i < args.length) {
|
|
14
|
+
const arg = args[i];
|
|
15
|
+
if (arg.startsWith("--")) {
|
|
16
|
+
const key = arg.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
17
|
+
if (key === "json") {
|
|
18
|
+
i += 1;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const next = args[i + 1];
|
|
22
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
23
|
+
data[key] = next;
|
|
24
|
+
i += 2;
|
|
25
|
+
} else {
|
|
26
|
+
data[key] = true;
|
|
27
|
+
i += 1;
|
|
28
|
+
}
|
|
29
|
+
} else i += 1;
|
|
30
|
+
}
|
|
31
|
+
return `neonctl init --agent --json --data '${JSON.stringify(data)}'`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Walks a phase response object and:
|
|
35
|
+
* 1. Replaces `args` arrays with `command` strings (neonctl init --data format)
|
|
36
|
+
* 2. Renames `run_neon_init` → `run_shell_command`
|
|
37
|
+
* 3. Adds a description to finalize steps
|
|
38
|
+
*/
|
|
39
|
+
function enrichResponse(obj) {
|
|
40
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
41
|
+
if (Array.isArray(obj)) return obj.map(enrichResponse);
|
|
42
|
+
const record = obj;
|
|
43
|
+
const result = {};
|
|
44
|
+
for (const [key, value] of Object.entries(record)) result[key] = enrichResponse(value);
|
|
45
|
+
if (Array.isArray(result.args)) {
|
|
46
|
+
result.command = argsToCommand(result.args);
|
|
47
|
+
delete result.args;
|
|
48
|
+
}
|
|
49
|
+
if (result.type === "run_neon_init") {
|
|
50
|
+
result.type = "run_shell_command";
|
|
51
|
+
if (typeof result.command === "string" && result.command.includes("\"step\":\"finalize\"")) result.description = "Run this command to complete the setup. This is the final step — do not run any other neonctl init commands after this.";
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { enrichResponse };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=enrich-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich-output.js","names":[],"sources":["../../src/lib/enrich-output.ts"],"sourcesContent":["/**\n * Converts neon-init args (e.g. [\"neon-auth\", \"--json\", \"--setup\"]) to a\n * neonctl init --data command using the step routing pattern.\n */\nfunction argsToCommand(args: string[]): string {\n\tconst data: Record<string, unknown> = {};\n\tlet i = 0;\n\n\t// First non-flag arg is the subcommand → step\n\tif (args.length > 0 && !args[0].startsWith(\"-\")) {\n\t\tdata.step = args[0];\n\t\ti = 1;\n\t}\n\n\twhile (i < args.length) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--\")) {\n\t\t\tconst key = arg\n\t\t\t\t.slice(2)\n\t\t\t\t.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n\t\t\tif (key === \"json\") {\n\t\t\t\ti += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst next = args[i + 1];\n\t\t\tif (next !== undefined && !next.startsWith(\"-\")) {\n\t\t\t\tdata[key] = next;\n\t\t\t\ti += 2;\n\t\t\t} else {\n\t\t\t\tdata[key] = true;\n\t\t\t\ti += 1;\n\t\t\t}\n\t\t} else {\n\t\t\ti += 1;\n\t\t}\n\t}\n\n\treturn `neonctl init --agent --json --data '${JSON.stringify(data)}'`;\n}\n\n/**\n * Walks a phase response object and:\n * 1. Replaces `args` arrays with `command` strings (neonctl init --data format)\n * 2. Renames `run_neon_init` → `run_shell_command`\n * 3. Adds a description to finalize steps\n */\nexport function enrichResponse(obj: unknown): unknown {\n\tif (obj === null || typeof obj !== \"object\") return obj;\n\tif (Array.isArray(obj)) return obj.map(enrichResponse);\n\n\tconst record = obj as Record<string, unknown>;\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const [key, value] of Object.entries(record)) {\n\t\tresult[key] = enrichResponse(value);\n\t}\n\n\t// Replace args with command in run_neon_init actions and responseMapping entries\n\tif (Array.isArray(result.args)) {\n\t\tresult.command = argsToCommand(result.args as string[]);\n\t\tdelete result.args;\n\t}\n\n\t// Rename run_neon_init → run_shell_command so agents don't infer subcommand patterns\n\tif (result.type === \"run_neon_init\") {\n\t\tresult.type = \"run_shell_command\";\n\t\t// Help agents understand finalize is the terminal step\n\t\tif (\n\t\t\ttypeof result.command === \"string\" &&\n\t\t\tresult.command.includes('\"step\":\"finalize\"')\n\t\t) {\n\t\t\tresult.description =\n\t\t\t\t\"Run this command to complete the setup. This is the final step — do not run any other neonctl init commands after this.\";\n\t\t}\n\t}\n\n\treturn result;\n}\n"],"mappings":";;;;;AAIA,SAAS,cAAc,MAAwB;CAC9C,MAAM,OAAgC,CAAC;CACvC,IAAI,IAAI;CAGR,IAAI,KAAK,SAAS,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,GAAG,GAAG;EAChD,KAAK,OAAO,KAAK;EACjB,IAAI;CACL;CAEA,OAAO,IAAI,KAAK,QAAQ;EACvB,MAAM,MAAM,KAAK;EACjB,IAAI,IAAI,WAAW,IAAI,GAAG;GACzB,MAAM,MAAM,IACV,MAAM,CAAC,CAAC,CACR,QAAQ,cAAc,GAAG,MAAc,EAAE,YAAY,CAAC;GACxD,IAAI,QAAQ,QAAQ;IACnB,KAAK;IACL;GACD;GACA,MAAM,OAAO,KAAK,IAAI;GACtB,IAAI,SAAS,KAAA,KAAa,CAAC,KAAK,WAAW,GAAG,GAAG;IAChD,KAAK,OAAO;IACZ,KAAK;GACN,OAAO;IACN,KAAK,OAAO;IACZ,KAAK;GACN;EACD,OACC,KAAK;CAEP;CAEA,OAAO,uCAAuC,KAAK,UAAU,IAAI,EAAE;AACpE;;;;;;;AAQA,SAAgB,eAAe,KAAuB;CACrD,IAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU,OAAO;CACpD,IAAI,MAAM,QAAQ,GAAG,GAAG,OAAO,IAAI,IAAI,cAAc;CAErD,MAAM,SAAS;CACf,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC/C,OAAO,OAAO,eAAe,KAAK;CAInC,IAAI,MAAM,QAAQ,OAAO,IAAI,GAAG;EAC/B,OAAO,UAAU,cAAc,OAAO,IAAgB;EACtD,OAAO,OAAO;CACf;CAGA,IAAI,OAAO,SAAS,iBAAiB;EACpC,OAAO,OAAO;EAEd,IACC,OAAO,OAAO,YAAY,YAC1B,OAAO,QAAQ,SAAS,uBAAmB,GAE3C,OAAO,cACN;CAEH;CAEA,OAAO;AACR"}
|
package/dist/lib/neonctl.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
//#region src/lib/neonctl.d.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Returns the neonctl command prefix: "CI= npx -y neonctl".
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* --api-host https://console-stage.neon.build/api/v2
|
|
8
|
-
* --oauth-host https://oauth2-stage.neon.build
|
|
9
|
-
*
|
|
10
|
-
* The oauth host is derived by replacing the "console" prefix in the hostname
|
|
11
|
-
* with "oauth2" (e.g. console-stage.neon.build → oauth2-stage.neon.build).
|
|
12
|
-
*/
|
|
13
|
-
declare function getNeonctlApiFlags(): string;
|
|
14
|
-
/**
|
|
15
|
-
* Returns the neonctl command prefix: "CI= npx -y neonctl" with any
|
|
16
|
-
* --api-host / --oauth-host flags appended when NEON_API_HOST is set.
|
|
5
|
+
* neonctl reads NEON_API_HOST and NEON_OAUTH_HOST from the environment
|
|
6
|
+
* directly, so no extra flags are needed.
|
|
17
7
|
*
|
|
18
8
|
* Usage: `${neonctlCmd()} orgs list --output json`
|
|
19
9
|
*/
|
|
@@ -47,5 +37,5 @@ interface EnsureNeonctlResult {
|
|
|
47
37
|
*/
|
|
48
38
|
declare function ensureNeonctl(): Promise<EnsureNeonctlResult>;
|
|
49
39
|
//#endregion
|
|
50
|
-
export { EnsureNeonctlResult, checkNeonctl, detectPackageManager, ensureNeonctl,
|
|
40
|
+
export { EnsureNeonctlResult, checkNeonctl, detectPackageManager, ensureNeonctl, neonctlCmd };
|
|
51
41
|
//# sourceMappingURL=neonctl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neonctl.d.ts","names":[],"sources":["../../src/lib/neonctl.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"neonctl.d.ts","names":[],"sources":["../../src/lib/neonctl.ts"],"mappings":";;AAUA;AAWA;AAQC;AAkDD;;;;AAA6C,iBArE7B,UAAA,CAAA,CAqE6B,EAAA,MAAA;AAiD7C;AAUA;;;;AAA8C;;iBArH9B,oBAAA,CAAA;UA6BN,aAAA;;;;;;;;;iBA6BY,YAAA,CAAA,GAAgB,QAAQ;UAiD7B,mBAAA;;;;;;;;;iBAUK,aAAA,CAAA,GAAiB,QAAQ"}
|
package/dist/lib/neonctl.js
CHANGED
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
//#region src/lib/neonctl.ts
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Returns the neonctl command prefix: "CI= npx -y neonctl".
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* --api-host https://console-stage.neon.build/api/v2
|
|
9
|
-
* --oauth-host https://oauth2-stage.neon.build
|
|
10
|
-
*
|
|
11
|
-
* The oauth host is derived by replacing the "console" prefix in the hostname
|
|
12
|
-
* with "oauth2" (e.g. console-stage.neon.build → oauth2-stage.neon.build).
|
|
13
|
-
*/
|
|
14
|
-
function getNeonctlApiFlags() {
|
|
15
|
-
const apiHost = process.env.NEON_API_HOST;
|
|
16
|
-
if (!apiHost) return "";
|
|
17
|
-
const apiUrl = `${apiHost.replace(/\/+$/, "")}/api/v2`;
|
|
18
|
-
let oauthUrl = "";
|
|
19
|
-
try {
|
|
20
|
-
const url = new URL(apiHost);
|
|
21
|
-
url.hostname = url.hostname.replace(/^console/, "oauth2");
|
|
22
|
-
oauthUrl = url.origin;
|
|
23
|
-
} catch {}
|
|
24
|
-
const flags = [`--api-host ${apiUrl}`];
|
|
25
|
-
if (oauthUrl) flags.push(`--oauth-host ${oauthUrl}`);
|
|
26
|
-
return flags.join(" ");
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Returns the neonctl command prefix: "CI= npx -y neonctl" with any
|
|
30
|
-
* --api-host / --oauth-host flags appended when NEON_API_HOST is set.
|
|
6
|
+
* neonctl reads NEON_API_HOST and NEON_OAUTH_HOST from the environment
|
|
7
|
+
* directly, so no extra flags are needed.
|
|
31
8
|
*
|
|
32
9
|
* Usage: `${neonctlCmd()} orgs list --output json`
|
|
33
10
|
*/
|
|
34
11
|
function neonctlCmd() {
|
|
35
|
-
|
|
36
|
-
return flags ? `CI= npx -y neonctl ${flags}` : "CI= npx -y neonctl";
|
|
12
|
+
return "CI= npx -y neonctl";
|
|
37
13
|
}
|
|
38
14
|
/**
|
|
39
15
|
* Detects which package manager was used to invoke the current process.
|
|
@@ -179,6 +155,6 @@ async function ensureNeonctl() {
|
|
|
179
155
|
}
|
|
180
156
|
}
|
|
181
157
|
//#endregion
|
|
182
|
-
export { checkNeonctl, detectPackageManager, ensureNeonctl,
|
|
158
|
+
export { checkNeonctl, detectPackageManager, ensureNeonctl, neonctlCmd };
|
|
183
159
|
|
|
184
160
|
//# sourceMappingURL=neonctl.js.map
|
package/dist/lib/neonctl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"neonctl.js","names":[],"sources":["../../src/lib/neonctl.ts"],"sourcesContent":["import { execa } from \"execa\";\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"neonctl.js","names":[],"sources":["../../src/lib/neonctl.ts"],"sourcesContent":["import { execa } from \"execa\";\n\n/**\n * Returns the neonctl command prefix: \"CI= npx -y neonctl\".\n *\n * neonctl reads NEON_API_HOST and NEON_OAUTH_HOST from the environment\n * directly, so no extra flags are needed.\n *\n * Usage: `${neonctlCmd()} orgs list --output json`\n */\nexport function neonctlCmd(): string {\n\treturn \"CI= npx -y neonctl\";\n}\n\n/**\n * Detects which package manager was used to invoke the current process.\n * Reads the `npm_config_user_agent` env var set by npm/pnpm/yarn/bun when\n * they spawn child processes (including via `npx`, `pnpx`, `bunx`, etc.).\n *\n * Falls back to \"npm\" if detection fails.\n */\nexport function detectPackageManager(): \"npm\" | \"pnpm\" | \"yarn\" | \"bun\" {\n\tconst ua = process.env.npm_config_user_agent;\n\tif (ua) {\n\t\tif (ua.startsWith(\"pnpm/\")) return \"pnpm\";\n\t\tif (ua.startsWith(\"yarn/\")) return \"yarn\";\n\t\tif (ua.startsWith(\"bun/\")) return \"bun\";\n\t}\n\treturn \"npm\";\n}\n\n/**\n * Returns the global install command for a given package manager.\n */\nfunction globalInstallArgs(\n\tpm: \"npm\" | \"pnpm\" | \"yarn\" | \"bun\",\n\tpkg: string,\n): { command: string; args: string[] } {\n\tswitch (pm) {\n\t\tcase \"pnpm\":\n\t\t\treturn { command: \"pnpm\", args: [\"add\", \"-g\", pkg] };\n\t\tcase \"yarn\":\n\t\t\treturn { command: \"yarn\", args: [\"global\", \"add\", pkg] };\n\t\tcase \"bun\":\n\t\t\treturn { command: \"bun\", args: [\"add\", \"-g\", pkg] };\n\t\tdefault:\n\t\t\treturn { command: \"npm\", args: [\"install\", \"-g\", pkg] };\n\t}\n}\n\ninterface NeonctlStatus {\n\tinstalled: boolean;\n\tcurrentVersion: string | null;\n\tlatestVersion: string | null;\n\tneedsUpdate: boolean;\n}\n\n/**\n * Gets the currently available neonctl version.\n * Tries the global binary first, then falls back to npx.\n */\nasync function getNeonctlVersion(): Promise<string | null> {\n\t// Try global binary first (fast path)\n\ttry {\n\t\tconst result = await execa(\"neonctl\", [\"--version\"], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 5000,\n\t\t});\n\t\tconst match = result.stdout.trim().match(/(\\d+\\.\\d+\\.\\d+)/);\n\t\tif (match) return match[1];\n\t} catch {\n\t\t// Not globally installed — that's fine\n\t}\n\treturn null;\n}\n\n/**\n * Checks whether the neonctl CLI is globally installed and whether it's up to date.\n */\nexport async function checkNeonctl(): Promise<NeonctlStatus> {\n\tconst currentVersion = await getNeonctlVersion();\n\n\tif (!currentVersion) {\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\tcurrentVersion: null,\n\t\t\tlatestVersion: null,\n\t\t\tneedsUpdate: true,\n\t\t};\n\t}\n\n\t// Check latest version from npm registry\n\tlet latestVersion: string | null = null;\n\ttry {\n\t\tconst result = await execa(\"npm\", [\"view\", \"neonctl\", \"version\"], {\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 10000,\n\t\t});\n\t\tlatestVersion = result.stdout.trim();\n\t} catch {\n\t\t// Can't determine latest — assume current is fine\n\t\treturn {\n\t\t\tinstalled: true,\n\t\t\tcurrentVersion,\n\t\t\tlatestVersion: null,\n\t\t\tneedsUpdate: false,\n\t\t};\n\t}\n\n\tconst needsUpdate =\n\t\tcurrentVersion !== null &&\n\t\tlatestVersion !== null &&\n\t\tcurrentVersion !== latestVersion &&\n\t\tisOlderVersion(currentVersion, latestVersion);\n\n\treturn { installed: true, currentVersion, latestVersion, needsUpdate };\n}\n\nfunction isOlderVersion(current: string, latest: string): boolean {\n\tconst c = current.split(\".\").map(Number);\n\tconst l = latest.split(\".\").map(Number);\n\tfor (let i = 0; i < 3; i++) {\n\t\tif ((c[i] ?? 0) < (l[i] ?? 0)) return true;\n\t\tif ((c[i] ?? 0) > (l[i] ?? 0)) return false;\n\t}\n\treturn false;\n}\n\nexport interface EnsureNeonctlResult {\n\tstatus: \"already_current\" | \"installed\" | \"updated\" | \"failed\";\n\tversion?: string;\n\terror?: string;\n}\n\n/**\n * Ensures neonctl is globally installed and up to date.\n * Uses the same package manager that invoked the init command.\n */\nexport async function ensureNeonctl(): Promise<EnsureNeonctlResult> {\n\tconst check = await checkNeonctl();\n\n\tif (check.installed && !check.needsUpdate) {\n\t\treturn {\n\t\t\tstatus: \"already_current\",\n\t\t\tversion: check.currentVersion ?? undefined,\n\t\t};\n\t}\n\n\tconst pm = detectPackageManager();\n\tconst { command, args } = globalInstallArgs(pm, \"neonctl\");\n\n\ttry {\n\t\tawait execa(command, args, { stdio: \"pipe\", timeout: 60000 });\n\n\t\t// Verify installation\n\t\tconst version = await getNeonctlVersion();\n\t\treturn {\n\t\t\tstatus: check.installed ? \"updated\" : \"installed\",\n\t\t\tversion: version ?? undefined,\n\t\t};\n\t} catch (err) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\terror: err instanceof Error ? err.message : \"Unknown error\",\n\t\t};\n\t}\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,aAAqB;CACpC,OAAO;AACR;;;;;;;;AASA,SAAgB,uBAAwD;CACvE,MAAM,KAAK,QAAQ,IAAI;CACvB,IAAI,IAAI;EACP,IAAI,GAAG,WAAW,OAAO,GAAG,OAAO;EACnC,IAAI,GAAG,WAAW,OAAO,GAAG,OAAO;EACnC,IAAI,GAAG,WAAW,MAAM,GAAG,OAAO;CACnC;CACA,OAAO;AACR;;;;AAKA,SAAS,kBACR,IACA,KACsC;CACtC,QAAQ,IAAR;EACC,KAAK,QACJ,OAAO;GAAE,SAAS;GAAQ,MAAM;IAAC;IAAO;IAAM;GAAG;EAAE;EACpD,KAAK,QACJ,OAAO;GAAE,SAAS;GAAQ,MAAM;IAAC;IAAU;IAAO;GAAG;EAAE;EACxD,KAAK,OACJ,OAAO;GAAE,SAAS;GAAO,MAAM;IAAC;IAAO;IAAM;GAAG;EAAE;EACnD,SACC,OAAO;GAAE,SAAS;GAAO,MAAM;IAAC;IAAW;IAAM;GAAG;EAAE;CACxD;AACD;;;;;AAaA,eAAe,oBAA4C;CAE1D,IAAI;EAKH,MAAM,SAAQ,MAJO,MAAM,WAAW,CAAC,WAAW,GAAG;GACpD,OAAO;GACP,SAAS;EACV,CAAC,EAAA,CACoB,OAAO,KAAK,CAAC,CAAC,MAAM,iBAAiB;EAC1D,IAAI,OAAO,OAAO,MAAM;CACzB,QAAQ,CAER;CACA,OAAO;AACR;;;;AAKA,eAAsB,eAAuC;CAC5D,MAAM,iBAAiB,MAAM,kBAAkB;CAE/C,IAAI,CAAC,gBACJ,OAAO;EACN,WAAW;EACX,gBAAgB;EAChB,eAAe;EACf,aAAa;CACd;CAID,IAAI,gBAA+B;CACnC,IAAI;EAKH,iBAAgB,MAJK,MAAM,OAAO;GAAC;GAAQ;GAAW;EAAS,GAAG;GACjE,OAAO;GACP,SAAS;EACV,CAAC,EAAA,CACsB,OAAO,KAAK;CACpC,QAAQ;EAEP,OAAO;GACN,WAAW;GACX;GACA,eAAe;GACf,aAAa;EACd;CACD;CAEA,MAAM,cACL,mBAAmB,QACnB,kBAAkB,QAClB,mBAAmB,iBACnB,eAAe,gBAAgB,aAAa;CAE7C,OAAO;EAAE,WAAW;EAAM;EAAgB;EAAe;CAAY;AACtE;AAEA,SAAS,eAAe,SAAiB,QAAyB;CACjE,MAAM,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CACvC,MAAM,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CACtC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC3B,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,IAAI,OAAO;EACtC,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,IAAI,OAAO;CACvC;CACA,OAAO;AACR;;;;;AAYA,eAAsB,gBAA8C;CACnE,MAAM,QAAQ,MAAM,aAAa;CAEjC,IAAI,MAAM,aAAa,CAAC,MAAM,aAC7B,OAAO;EACN,QAAQ;EACR,SAAS,MAAM,kBAAkB,KAAA;CAClC;CAID,MAAM,EAAE,SAAS,SAAS,kBADf,qBACkC,GAAG,SAAS;CAEzD,IAAI;EACH,MAAM,MAAM,SAAS,MAAM;GAAE,OAAO;GAAQ,SAAS;EAAM,CAAC;EAG5D,MAAM,UAAU,MAAM,kBAAkB;EACxC,OAAO;GACN,QAAQ,MAAM,YAAY,YAAY;GACtC,SAAS,WAAW,KAAA;EACrB;CACD,SAAS,KAAK;EACb,OAAO;GACN,QAAQ;GACR,OAAO,eAAe,QAAQ,IAAI,UAAU;EAC7C;CACD;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","names":[],"sources":["../../../src/lib/phases/auth.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","names":[],"sources":["../../../src/lib/phases/auth.ts"],"mappings":";;;UAmBiB,gBAAA;;EAAA,MAAA,CAAA,EAAA,UAAgB,GAAA,KAAA;EAMX,MAAA,CAAA,EAAA,OAAA;;AACZ,iBADY,eAAA,CACZ,OAAA,EAAA,gBAAA,CAAA,EACP,OADO,CACC,aADD,CAAA"}
|