neon-init 0.14.0 → 0.16.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.
- package/dist/cli.js +366 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +218 -13
- package/dist/index.js.map +1 -1
- package/dist/interactive.d.ts +12 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +495 -0
- package/dist/interactive.js.map +1 -0
- package/dist/lib/agents.d.ts +6 -1
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +62 -1
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/auth.d.ts +10 -3
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +21 -12
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/bootstrap.d.ts +30 -0
- package/dist/lib/bootstrap.d.ts.map +1 -0
- package/dist/lib/bootstrap.js +61 -0
- package/dist/lib/bootstrap.js.map +1 -0
- package/dist/lib/build-config.d.ts +5 -0
- package/dist/lib/build-config.d.ts.map +1 -0
- package/dist/lib/build-config.js +6 -0
- package/dist/lib/build-config.js.map +1 -0
- package/dist/lib/detect-agent.d.ts +22 -0
- package/dist/lib/detect-agent.d.ts.map +1 -0
- package/dist/lib/detect-agent.js +65 -0
- package/dist/lib/detect-agent.js.map +1 -0
- package/dist/lib/editors.d.ts.map +1 -1
- package/dist/lib/editors.js.map +1 -1
- package/dist/lib/extension.d.ts +11 -3
- package/dist/lib/extension.d.ts.map +1 -1
- package/dist/lib/extension.js +28 -7
- package/dist/lib/extension.js.map +1 -1
- package/dist/lib/inspect.d.ts +28 -0
- package/dist/lib/inspect.d.ts.map +1 -0
- package/dist/lib/inspect.js +190 -0
- package/dist/lib/inspect.js.map +1 -0
- package/dist/lib/install.d.ts +10 -4
- package/dist/lib/install.d.ts.map +1 -1
- package/dist/lib/install.js +37 -20
- package/dist/lib/install.js.map +1 -1
- package/dist/lib/neonctl.d.ts +51 -0
- package/dist/lib/neonctl.d.ts.map +1 -0
- package/dist/lib/neonctl.js +184 -0
- package/dist/lib/neonctl.js.map +1 -0
- package/dist/lib/phases/auth.d.ts +12 -0
- package/dist/lib/phases/auth.d.ts.map +1 -0
- package/dist/lib/phases/auth.js +197 -0
- package/dist/lib/phases/auth.js.map +1 -0
- package/dist/lib/phases/cleanup.d.ts +12 -0
- package/dist/lib/phases/cleanup.d.ts.map +1 -0
- package/dist/lib/phases/cleanup.js +29 -0
- package/dist/lib/phases/cleanup.js.map +1 -0
- package/dist/lib/phases/db.d.ts +17 -0
- package/dist/lib/phases/db.d.ts.map +1 -0
- package/dist/lib/phases/db.js +259 -0
- package/dist/lib/phases/db.js.map +1 -0
- package/dist/lib/phases/getting-started.d.ts +26 -0
- package/dist/lib/phases/getting-started.d.ts.map +1 -0
- package/dist/lib/phases/getting-started.js +196 -0
- package/dist/lib/phases/getting-started.js.map +1 -0
- package/dist/lib/phases/mcp.d.ts +15 -0
- package/dist/lib/phases/mcp.d.ts.map +1 -0
- package/dist/lib/phases/mcp.js +179 -0
- package/dist/lib/phases/mcp.js.map +1 -0
- package/dist/lib/phases/migrations.d.ts +14 -0
- package/dist/lib/phases/migrations.d.ts.map +1 -0
- package/dist/lib/phases/migrations.js +239 -0
- package/dist/lib/phases/migrations.js.map +1 -0
- package/dist/lib/phases/neon-auth.d.ts +13 -0
- package/dist/lib/phases/neon-auth.d.ts.map +1 -0
- package/dist/lib/phases/neon-auth.js +118 -0
- package/dist/lib/phases/neon-auth.js.map +1 -0
- package/dist/lib/phases/setup.d.ts +41 -0
- package/dist/lib/phases/setup.d.ts.map +1 -0
- package/dist/lib/phases/setup.js +689 -0
- package/dist/lib/phases/setup.js.map +1 -0
- package/dist/lib/phases/skills.d.ts +14 -0
- package/dist/lib/phases/skills.d.ts.map +1 -0
- package/dist/lib/phases/skills.js +80 -0
- package/dist/lib/phases/skills.js.map +1 -0
- package/dist/lib/phases/status.d.ts +10 -0
- package/dist/lib/phases/status.d.ts.map +1 -0
- package/dist/lib/phases/status.js +65 -0
- package/dist/lib/phases/status.js.map +1 -0
- package/dist/lib/resolve-context.d.ts +19 -0
- package/dist/lib/resolve-context.d.ts.map +1 -0
- package/dist/lib/resolve-context.js +112 -0
- package/dist/lib/resolve-context.js.map +1 -0
- package/dist/lib/route-command.d.ts +8 -0
- package/dist/lib/route-command.d.ts.map +1 -0
- package/dist/lib/route-command.js +195 -0
- package/dist/lib/route-command.js.map +1 -0
- package/dist/lib/skills.d.ts +20 -3
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +116 -12
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/types.d.ts +150 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/vsix.d.ts +15 -0
- package/dist/lib/vsix.d.ts.map +1 -0
- package/dist/lib/vsix.js +91 -0
- package/dist/lib/vsix.js.map +1 -0
- package/dist/v2.d.ts +31 -0
- package/dist/v2.d.ts.map +1 -0
- package/dist/v2.js +147 -0
- package/dist/v2.js.map +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AgentCheck } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/inspect.d.ts
|
|
4
|
+
interface InspectionResults {
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
mcpConfigured?: boolean;
|
|
7
|
+
skillsInstalled?: boolean;
|
|
8
|
+
/** True if a Neon-specific connection string (DATABASE_URL with "neon" or PGHOST with "neon") is found */
|
|
9
|
+
connectionString?: boolean;
|
|
10
|
+
/** True if any DATABASE_URL is set in .env (regardless of provider) */
|
|
11
|
+
databaseUrl?: boolean;
|
|
12
|
+
framework?: string;
|
|
13
|
+
orm?: string;
|
|
14
|
+
migrationTool?: string;
|
|
15
|
+
migrationDir?: string;
|
|
16
|
+
isVscodeIde?: boolean;
|
|
17
|
+
agent?: string;
|
|
18
|
+
/** True if the directory contains an application (package.json with deps, or source files) */
|
|
19
|
+
hasApp?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs filesystem checks based on the agent_check descriptors.
|
|
23
|
+
* Maps check IDs to concrete inspection functions.
|
|
24
|
+
*/
|
|
25
|
+
declare function inspectProject(checks: AgentCheck[]): Promise<InspectionResults>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { InspectionResults, inspectProject };
|
|
28
|
+
//# sourceMappingURL=inspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","names":[],"sources":["../../src/lib/inspect.ts"],"mappings":";;;UASiB,iBAAA;;EAAA,aAAA,CAAA,EAAA,OAAiB;EAsBZ,eAAA,CAAA,EAAc,OAAA;EAAA;kBAC3B,CAAA,EAAA,OAAA;;aACN,CAAA,EAAA,OAAA;EAAO,SAAA,CAAA,EAAA,MAAA;;;;;;;;;;;;;iBAFY,cAAA,SACb,eACN,QAAQ"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
//#region src/lib/inspect.ts
|
|
4
|
+
/**
|
|
5
|
+
* Filesystem-based project inspection for interactive (agentless) mode.
|
|
6
|
+
* Replaces the "agent_check" pattern — instead of asking an agent to look,
|
|
7
|
+
* we examine the filesystem directly.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Runs filesystem checks based on the agent_check descriptors.
|
|
11
|
+
* Maps check IDs to concrete inspection functions.
|
|
12
|
+
*/
|
|
13
|
+
async function inspectProject(checks) {
|
|
14
|
+
const results = {};
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
for (const check of checks) switch (check.id) {
|
|
17
|
+
case "mcp_server":
|
|
18
|
+
results.mcpConfigured = checkMcpServer(cwd);
|
|
19
|
+
break;
|
|
20
|
+
case "connection_string":
|
|
21
|
+
results.connectionString = checkConnectionString(cwd);
|
|
22
|
+
break;
|
|
23
|
+
case "database_url":
|
|
24
|
+
results.databaseUrl = checkDatabaseUrl(cwd);
|
|
25
|
+
break;
|
|
26
|
+
case "project_stack": {
|
|
27
|
+
const stack = detectProjectStack(cwd);
|
|
28
|
+
results.framework = stack.framework;
|
|
29
|
+
results.orm = stack.orm;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case "migrations": {
|
|
33
|
+
const migrations = detectMigrations(cwd);
|
|
34
|
+
results.migrationTool = migrations.tool;
|
|
35
|
+
results.migrationDir = migrations.dir;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "skills":
|
|
39
|
+
results.skillsInstalled = checkSkillsInstalled(cwd);
|
|
40
|
+
break;
|
|
41
|
+
case "ide_type":
|
|
42
|
+
results.isVscodeIde = checkVscodeIde();
|
|
43
|
+
break;
|
|
44
|
+
case "has_app":
|
|
45
|
+
results.hasApp = checkHasApp(cwd);
|
|
46
|
+
break;
|
|
47
|
+
case "agent_type": break;
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
function checkMcpServer(cwd) {
|
|
52
|
+
const cursorMcp = resolve(cwd, ".cursor", "mcp.json");
|
|
53
|
+
if (existsSync(cursorMcp)) try {
|
|
54
|
+
const content = readFileSync(cursorMcp, "utf-8");
|
|
55
|
+
if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
|
|
56
|
+
} catch {}
|
|
57
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
58
|
+
const globalCursorMcp = resolve(home, ".cursor", "mcp.json");
|
|
59
|
+
if (existsSync(globalCursorMcp)) try {
|
|
60
|
+
const content = readFileSync(globalCursorMcp, "utf-8");
|
|
61
|
+
if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
|
|
62
|
+
} catch {}
|
|
63
|
+
for (const claudeFile of ["settings.local.json", "settings.json"]) {
|
|
64
|
+
const claudeConfig = resolve(home, ".claude", claudeFile);
|
|
65
|
+
if (existsSync(claudeConfig)) try {
|
|
66
|
+
const content = readFileSync(claudeConfig, "utf-8");
|
|
67
|
+
if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
const vscodeSettings = resolve(cwd, ".vscode", "settings.json");
|
|
71
|
+
if (existsSync(vscodeSettings)) try {
|
|
72
|
+
const content = readFileSync(vscodeSettings, "utf-8");
|
|
73
|
+
if (content.includes("neon") || content.includes("mcp.neon.tech")) return true;
|
|
74
|
+
} catch {}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
function checkConnectionString(cwd) {
|
|
78
|
+
for (const envFile of [".env", ".env.local"]) {
|
|
79
|
+
const envPath = resolve(cwd, envFile);
|
|
80
|
+
if (existsSync(envPath)) try {
|
|
81
|
+
const content = readFileSync(envPath, "utf-8");
|
|
82
|
+
if (/^DATABASE_URL=.*neon/m.test(content) || /^PGHOST=.*neon/m.test(content)) return true;
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function detectProjectStack(cwd) {
|
|
88
|
+
const result = {
|
|
89
|
+
framework: "none",
|
|
90
|
+
orm: "none"
|
|
91
|
+
};
|
|
92
|
+
const pkgPath = resolve(cwd, "package.json");
|
|
93
|
+
if (!existsSync(pkgPath)) return result;
|
|
94
|
+
try {
|
|
95
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
96
|
+
const allDeps = {
|
|
97
|
+
...pkg.dependencies,
|
|
98
|
+
...pkg.devDependencies
|
|
99
|
+
};
|
|
100
|
+
if (allDeps.next) result.framework = "next";
|
|
101
|
+
else if (allDeps.remix || allDeps["@remix-run/node"]) result.framework = "remix";
|
|
102
|
+
else if (allDeps.express) result.framework = "express";
|
|
103
|
+
else if (allDeps.hono) result.framework = "hono";
|
|
104
|
+
else if (allDeps.fastify) result.framework = "fastify";
|
|
105
|
+
else if (allDeps["@sveltejs/kit"]) result.framework = "sveltekit";
|
|
106
|
+
else if (allDeps.nuxt) result.framework = "nuxt";
|
|
107
|
+
else if (allDeps.astro) result.framework = "astro";
|
|
108
|
+
if (allDeps.prisma || allDeps["@prisma/client"]) result.orm = "prisma";
|
|
109
|
+
else if (allDeps["drizzle-orm"]) result.orm = "drizzle";
|
|
110
|
+
else if (allDeps.knex) result.orm = "knex";
|
|
111
|
+
else if (allDeps.typeorm) result.orm = "typeorm";
|
|
112
|
+
else if (allDeps.sequelize) result.orm = "sequelize";
|
|
113
|
+
else if (allDeps["@neondatabase/serverless"]) result.orm = "neon-serverless";
|
|
114
|
+
} catch {}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function detectMigrations(cwd) {
|
|
118
|
+
if (existsSync(resolve(cwd, "prisma", "schema.prisma"))) return {
|
|
119
|
+
tool: "prisma",
|
|
120
|
+
dir: existsSync(resolve(cwd, "prisma", "migrations")) ? "prisma/migrations" : "none"
|
|
121
|
+
};
|
|
122
|
+
if (existsSync(resolve(cwd, "drizzle.config.ts")) || existsSync(resolve(cwd, "drizzle.config.js"))) return {
|
|
123
|
+
tool: "drizzle",
|
|
124
|
+
dir: existsSync(resolve(cwd, "drizzle")) ? "drizzle" : "none"
|
|
125
|
+
};
|
|
126
|
+
if (existsSync(resolve(cwd, "knexfile.js")) || existsSync(resolve(cwd, "knexfile.ts"))) return {
|
|
127
|
+
tool: "knex",
|
|
128
|
+
dir: existsSync(resolve(cwd, "migrations")) ? "migrations" : "none"
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
tool: "none",
|
|
132
|
+
dir: "none"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function checkDatabaseUrl(cwd) {
|
|
136
|
+
const envPath = resolve(cwd, ".env");
|
|
137
|
+
if (existsSync(envPath)) try {
|
|
138
|
+
const content = readFileSync(envPath, "utf-8");
|
|
139
|
+
if (/^DATABASE_URL=/m.test(content)) return true;
|
|
140
|
+
} catch {}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
function checkSkillsInstalled(cwd) {
|
|
144
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
145
|
+
const skillsDirs = [
|
|
146
|
+
resolve(cwd, ".cursor", "skills"),
|
|
147
|
+
resolve(cwd, ".claude", "skills"),
|
|
148
|
+
resolve(cwd, ".agents", "skills"),
|
|
149
|
+
resolve(home, ".cursor", "skills"),
|
|
150
|
+
resolve(home, ".claude", "skills")
|
|
151
|
+
];
|
|
152
|
+
for (const dir of skillsDirs) if (existsSync(resolve(dir, "neon-postgres", "SKILL.md"))) return true;
|
|
153
|
+
const claudeMd = resolve(cwd, "CLAUDE.md");
|
|
154
|
+
if (existsSync(claudeMd)) try {
|
|
155
|
+
if (readFileSync(claudeMd, "utf-8").includes("neon-postgres")) return true;
|
|
156
|
+
} catch {}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
function checkVscodeIde() {
|
|
160
|
+
const env = process.env;
|
|
161
|
+
return !!(env.TERM_PROGRAM === "vscode" || env.TERM_PROGRAM === "cursor" || env.TERM_PROGRAM === "windsurf" || env.VSCODE_PID || env.VSCODE_CWD);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Detects whether the current directory contains an application.
|
|
165
|
+
* Returns true if package.json has dependencies or common source directories exist.
|
|
166
|
+
*/
|
|
167
|
+
function checkHasApp(cwd) {
|
|
168
|
+
const pkgPath = resolve(cwd, "package.json");
|
|
169
|
+
if (existsSync(pkgPath)) try {
|
|
170
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
171
|
+
const deps = Object.keys(pkg.dependencies ?? {});
|
|
172
|
+
const devDeps = Object.keys(pkg.devDependencies ?? {});
|
|
173
|
+
if (deps.length > 0 || devDeps.length > 0) return true;
|
|
174
|
+
} catch {}
|
|
175
|
+
for (const indicator of [
|
|
176
|
+
"src",
|
|
177
|
+
"app",
|
|
178
|
+
"pages",
|
|
179
|
+
"lib",
|
|
180
|
+
"index.ts",
|
|
181
|
+
"index.js",
|
|
182
|
+
"main.ts",
|
|
183
|
+
"main.js"
|
|
184
|
+
]) if (existsSync(resolve(cwd, indicator))) return true;
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
export { inspectProject };
|
|
189
|
+
|
|
190
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.js","names":[],"sources":["../../src/lib/inspect.ts"],"sourcesContent":["/**\n * Filesystem-based project inspection for interactive (agentless) mode.\n * Replaces the \"agent_check\" pattern — instead of asking an agent to look,\n * we examine the filesystem directly.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { AgentCheck } from \"./types.js\";\n\nexport interface InspectionResults {\n\t[key: string]: unknown;\n\tmcpConfigured?: boolean;\n\tskillsInstalled?: boolean;\n\t/** True if a Neon-specific connection string (DATABASE_URL with \"neon\" or PGHOST with \"neon\") is found */\n\tconnectionString?: boolean;\n\t/** True if any DATABASE_URL is set in .env (regardless of provider) */\n\tdatabaseUrl?: boolean;\n\tframework?: string;\n\torm?: string;\n\tmigrationTool?: string;\n\tmigrationDir?: string;\n\tisVscodeIde?: boolean;\n\tagent?: string;\n\t/** True if the directory contains an application (package.json with deps, or source files) */\n\thasApp?: boolean;\n}\n\n/**\n * Runs filesystem checks based on the agent_check descriptors.\n * Maps check IDs to concrete inspection functions.\n */\nexport async function inspectProject(\n\tchecks: AgentCheck[],\n): Promise<InspectionResults> {\n\tconst results: InspectionResults = {};\n\tconst cwd = process.cwd();\n\n\tfor (const check of checks) {\n\t\tswitch (check.id) {\n\t\t\tcase \"mcp_server\":\n\t\t\t\tresults.mcpConfigured = checkMcpServer(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"connection_string\":\n\t\t\t\tresults.connectionString = checkConnectionString(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"database_url\":\n\t\t\t\tresults.databaseUrl = checkDatabaseUrl(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"project_stack\": {\n\t\t\t\tconst stack = detectProjectStack(cwd);\n\t\t\t\tresults.framework = stack.framework;\n\t\t\t\tresults.orm = stack.orm;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"migrations\": {\n\t\t\t\tconst migrations = detectMigrations(cwd);\n\t\t\t\tresults.migrationTool = migrations.tool;\n\t\t\t\tresults.migrationDir = migrations.dir;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"skills\":\n\t\t\t\tresults.skillsInstalled = checkSkillsInstalled(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"ide_type\":\n\t\t\t\tresults.isVscodeIde = checkVscodeIde();\n\t\t\t\tbreak;\n\t\t\tcase \"has_app\":\n\t\t\t\tresults.hasApp = checkHasApp(cwd);\n\t\t\t\tbreak;\n\t\t\tcase \"agent_type\":\n\t\t\t\t// Handled by the interactive runner (prompt or env detection)\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn results;\n}\n\n// ---------------------------------------------------------------------------\n// Individual check implementations\n// ---------------------------------------------------------------------------\n\nfunction checkMcpServer(cwd: string): boolean {\n\t// Check project-level Cursor config\n\tconst cursorMcp = resolve(cwd, \".cursor\", \"mcp.json\");\n\tif (existsSync(cursorMcp)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(cursorMcp, \"utf-8\");\n\t\t\tif (content.includes(\"neon\") || content.includes(\"mcp.neon.tech\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\t// Check global Cursor config\n\tconst home = process.env.HOME || process.env.USERPROFILE || \"\";\n\tconst globalCursorMcp = resolve(home, \".cursor\", \"mcp.json\");\n\tif (existsSync(globalCursorMcp)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(globalCursorMcp, \"utf-8\");\n\t\t\tif (content.includes(\"neon\") || content.includes(\"mcp.neon.tech\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\t// Check Claude Code config (add-mcp writes to settings.local.json)\n\tfor (const claudeFile of [\"settings.local.json\", \"settings.json\"]) {\n\t\tconst claudeConfig = resolve(home, \".claude\", claudeFile);\n\t\tif (existsSync(claudeConfig)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(claudeConfig, \"utf-8\");\n\t\t\t\tif (\n\t\t\t\t\tcontent.includes(\"neon\") ||\n\t\t\t\t\tcontent.includes(\"mcp.neon.tech\")\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// Check VS Code settings\n\tconst vscodeSettings = resolve(cwd, \".vscode\", \"settings.json\");\n\tif (existsSync(vscodeSettings)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(vscodeSettings, \"utf-8\");\n\t\t\tif (content.includes(\"neon\") || content.includes(\"mcp.neon.tech\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\treturn false;\n}\n\nfunction checkConnectionString(cwd: string): boolean {\n\tfor (const envFile of [\".env\", \".env.local\"]) {\n\t\tconst envPath = resolve(cwd, envFile);\n\t\tif (existsSync(envPath)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\t\tif (\n\t\t\t\t\t/^DATABASE_URL=.*neon/m.test(content) ||\n\t\t\t\t\t/^PGHOST=.*neon/m.test(content)\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn false;\n}\n\ninterface StackResult {\n\tframework: string;\n\torm: string;\n}\n\nfunction detectProjectStack(cwd: string): StackResult {\n\tconst result: StackResult = { framework: \"none\", orm: \"none\" };\n\n\tconst pkgPath = resolve(cwd, \"package.json\");\n\tif (!existsSync(pkgPath)) return result;\n\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\tconst allDeps = {\n\t\t\t...pkg.dependencies,\n\t\t\t...pkg.devDependencies,\n\t\t};\n\n\t\t// Framework detection\n\t\tif (allDeps.next) result.framework = \"next\";\n\t\telse if (allDeps.remix || allDeps[\"@remix-run/node\"])\n\t\t\tresult.framework = \"remix\";\n\t\telse if (allDeps.express) result.framework = \"express\";\n\t\telse if (allDeps.hono) result.framework = \"hono\";\n\t\telse if (allDeps.fastify) result.framework = \"fastify\";\n\t\telse if (allDeps[\"@sveltejs/kit\"]) result.framework = \"sveltekit\";\n\t\telse if (allDeps.nuxt) result.framework = \"nuxt\";\n\t\telse if (allDeps.astro) result.framework = \"astro\";\n\n\t\t// ORM detection\n\t\tif (allDeps.prisma || allDeps[\"@prisma/client\"]) result.orm = \"prisma\";\n\t\telse if (allDeps[\"drizzle-orm\"]) result.orm = \"drizzle\";\n\t\telse if (allDeps.knex) result.orm = \"knex\";\n\t\telse if (allDeps.typeorm) result.orm = \"typeorm\";\n\t\telse if (allDeps.sequelize) result.orm = \"sequelize\";\n\t\telse if (allDeps[\"@neondatabase/serverless\"])\n\t\t\tresult.orm = \"neon-serverless\";\n\t} catch {}\n\n\treturn result;\n}\n\ninterface MigrationResult {\n\ttool: string;\n\tdir: string;\n}\n\nfunction detectMigrations(cwd: string): MigrationResult {\n\t// Prisma\n\tif (existsSync(resolve(cwd, \"prisma\", \"schema.prisma\"))) {\n\t\tconst migrationsDir = resolve(cwd, \"prisma\", \"migrations\");\n\t\treturn {\n\t\t\ttool: \"prisma\",\n\t\t\tdir: existsSync(migrationsDir) ? \"prisma/migrations\" : \"none\",\n\t\t};\n\t}\n\n\t// Drizzle\n\tif (\n\t\texistsSync(resolve(cwd, \"drizzle.config.ts\")) ||\n\t\texistsSync(resolve(cwd, \"drizzle.config.js\"))\n\t) {\n\t\tconst drizzleDir = resolve(cwd, \"drizzle\");\n\t\treturn {\n\t\t\ttool: \"drizzle\",\n\t\t\tdir: existsSync(drizzleDir) ? \"drizzle\" : \"none\",\n\t\t};\n\t}\n\n\t// Knex\n\tif (\n\t\texistsSync(resolve(cwd, \"knexfile.js\")) ||\n\t\texistsSync(resolve(cwd, \"knexfile.ts\"))\n\t) {\n\t\tconst migrationsDir = resolve(cwd, \"migrations\");\n\t\treturn {\n\t\t\ttool: \"knex\",\n\t\t\tdir: existsSync(migrationsDir) ? \"migrations\" : \"none\",\n\t\t};\n\t}\n\n\treturn { tool: \"none\", dir: \"none\" };\n}\n\nfunction checkDatabaseUrl(cwd: string): boolean {\n\tconst envPath = resolve(cwd, \".env\");\n\tif (existsSync(envPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(envPath, \"utf-8\");\n\t\t\tif (/^DATABASE_URL=/m.test(content)) return true;\n\t\t} catch {}\n\t}\n\treturn false;\n}\n\nfunction checkSkillsInstalled(cwd: string): boolean {\n\tconst home = process.env.HOME || process.env.USERPROFILE || \"\";\n\n\t// Check for neon-postgres SKILL.md in known skill directories (project and global)\n\tconst skillsDirs = [\n\t\tresolve(cwd, \".cursor\", \"skills\"),\n\t\tresolve(cwd, \".claude\", \"skills\"),\n\t\tresolve(cwd, \".agents\", \"skills\"),\n\t\tresolve(home, \".cursor\", \"skills\"),\n\t\tresolve(home, \".claude\", \"skills\"),\n\t];\n\tfor (const dir of skillsDirs) {\n\t\t// Verify the actual skill content file exists, not just the directory\n\t\tconst skillMd = resolve(dir, \"neon-postgres\", \"SKILL.md\");\n\t\tif (existsSync(skillMd)) return true;\n\t}\n\n\t// Check CLAUDE.md for neon skill references (injected by skills CLI)\n\tconst claudeMd = resolve(cwd, \"CLAUDE.md\");\n\tif (existsSync(claudeMd)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(claudeMd, \"utf-8\");\n\t\t\tif (content.includes(\"neon-postgres\")) return true;\n\t\t} catch {}\n\t}\n\n\treturn false;\n}\n\nfunction checkVscodeIde(): boolean {\n\tconst env = process.env;\n\treturn !!(\n\t\tenv.TERM_PROGRAM === \"vscode\" ||\n\t\tenv.TERM_PROGRAM === \"cursor\" ||\n\t\tenv.TERM_PROGRAM === \"windsurf\" ||\n\t\tenv.VSCODE_PID ||\n\t\tenv.VSCODE_CWD\n\t);\n}\n\n/**\n * Detects whether the current directory contains an application.\n * Returns true if package.json has dependencies or common source directories exist.\n */\nfunction checkHasApp(cwd: string): boolean {\n\tconst pkgPath = resolve(cwd, \"package.json\");\n\tif (existsSync(pkgPath)) {\n\t\ttry {\n\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\t\t\tconst deps = Object.keys(pkg.dependencies ?? {});\n\t\t\tconst devDeps = Object.keys(pkg.devDependencies ?? {});\n\t\t\tif (deps.length > 0 || devDeps.length > 0) return true;\n\t\t} catch {}\n\t}\n\n\t// Check for common source directories / entry files\n\tconst indicators = [\n\t\t\"src\",\n\t\t\"app\",\n\t\t\"pages\",\n\t\t\"lib\",\n\t\t\"index.ts\",\n\t\t\"index.js\",\n\t\t\"main.ts\",\n\t\t\"main.js\",\n\t];\n\tfor (const indicator of indicators) {\n\t\tif (existsSync(resolve(cwd, indicator))) return true;\n\t}\n\n\treturn false;\n}\n"],"mappings":"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"}
|
package/dist/lib/install.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { Editor, InstallStatus } from "./types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/lib/install.d.ts
|
|
4
|
-
|
|
4
|
+
interface InstallNeonOptions {
|
|
5
|
+
json?: boolean;
|
|
6
|
+
}
|
|
5
7
|
/**
|
|
6
|
-
* Installs Neon's Local Connect extension or MCP Server for specific editors
|
|
8
|
+
* Installs Neon's Local Connect extension or MCP Server for specific editors.
|
|
9
|
+
* Returns a map of editor → install status and whether auth succeeded.
|
|
7
10
|
*/
|
|
8
|
-
declare function installNeon(selectedEditors: Editor[]): Promise<
|
|
11
|
+
declare function installNeon(selectedEditors: Editor[], options?: InstallNeonOptions): Promise<{
|
|
12
|
+
results: Map<Editor, InstallStatus>;
|
|
13
|
+
authSuccess: boolean;
|
|
14
|
+
}>;
|
|
9
15
|
//#endregion
|
|
10
|
-
export { installNeon };
|
|
16
|
+
export { InstallNeonOptions, installNeon };
|
|
11
17
|
//# sourceMappingURL=install.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"
|
|
1
|
+
{"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"mappings":";;;UAkDiB,kBAAA;;AAAjB;AAQA;;;;AAG0B,iBAHJ,WAAA,CAGI,eAAA,EAFR,MAEQ,EAAA,EAAA,OAAA,CAAA,EADf,kBACe,CAAA,EAAvB,OAAuB,CAAA;SAAQ,EAAZ,GAAY,CAAR,MAAQ,EAAA,aAAA,CAAA;aAAZ,EAAA,OAAA"}
|
package/dist/lib/install.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getAddMcpAgentId } from "./agents.js";
|
|
2
|
-
import { configureExtension, installExtension, usesExtension, waitForExtensionInstalled } from "./extension.js";
|
|
3
2
|
import { createApiKeyFromNeonctl, ensureNeonctlAuth } from "./auth.js";
|
|
3
|
+
import { configureExtension, installExtension, usesExtension, waitForExtensionInstalled } from "./extension.js";
|
|
4
4
|
import { log, spinner } from "@clack/prompts";
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
//#region src/lib/install.ts
|
|
@@ -29,27 +29,41 @@ async function installMCPServerViaAddMcp(editor, apiKey) {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Installs Neon's Local Connect extension or MCP Server for specific editors
|
|
32
|
+
* Installs Neon's Local Connect extension or MCP Server for specific editors.
|
|
33
|
+
* Returns a map of editor → install status and whether auth succeeded.
|
|
33
34
|
*/
|
|
34
|
-
async function installNeon(selectedEditors) {
|
|
35
|
+
async function installNeon(selectedEditors, options) {
|
|
36
|
+
const quiet = options?.json === true;
|
|
37
|
+
const authOptions = { json: quiet };
|
|
35
38
|
const results = /* @__PURE__ */ new Map();
|
|
36
39
|
const extensionEditors = selectedEditors.filter(usesExtension);
|
|
37
40
|
const mcpEditors = selectedEditors.filter((e) => !usesExtension(e));
|
|
38
|
-
if (extensionEditors.length === 0 && mcpEditors.length === 0) return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if (extensionEditors.length === 0 && mcpEditors.length === 0) return {
|
|
42
|
+
results,
|
|
43
|
+
authSuccess: false
|
|
44
|
+
};
|
|
45
|
+
const authSpinner = quiet ? null : spinner();
|
|
46
|
+
authSpinner?.start("Authenticating...");
|
|
47
|
+
if (!await ensureNeonctlAuth(authOptions)) {
|
|
48
|
+
authSpinner?.stop("Authentication failed");
|
|
43
49
|
for (const editor of selectedEditors) results.set(editor, "failed");
|
|
44
|
-
return
|
|
50
|
+
return {
|
|
51
|
+
results,
|
|
52
|
+
authSuccess: false
|
|
53
|
+
};
|
|
45
54
|
}
|
|
46
|
-
authSpinner
|
|
47
|
-
const apiKey = await createApiKeyFromNeonctl();
|
|
55
|
+
authSpinner?.stop("Authentication successful ✓");
|
|
56
|
+
const apiKey = await createApiKeyFromNeonctl(authOptions);
|
|
48
57
|
if (!apiKey) {
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
if (!quiet) {
|
|
59
|
+
log.error("Could not create API key after authentication.");
|
|
60
|
+
log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
|
|
61
|
+
}
|
|
51
62
|
for (const editor of selectedEditors) results.set(editor, "failed");
|
|
52
|
-
return
|
|
63
|
+
return {
|
|
64
|
+
results,
|
|
65
|
+
authSuccess: true
|
|
66
|
+
};
|
|
53
67
|
}
|
|
54
68
|
for (const editor of extensionEditors) {
|
|
55
69
|
if (!await installExtension(editor)) {
|
|
@@ -61,11 +75,11 @@ async function installNeon(selectedEditors) {
|
|
|
61
75
|
continue;
|
|
62
76
|
}
|
|
63
77
|
if (await configureExtension(editor, apiKey)) results.set(editor, "success");
|
|
64
|
-
else results.set(editor, "
|
|
78
|
+
else results.set(editor, "failed");
|
|
65
79
|
}
|
|
66
80
|
if (mcpEditors.length > 0) {
|
|
67
|
-
const mcpSpinner = spinner();
|
|
68
|
-
mcpSpinner
|
|
81
|
+
const mcpSpinner = quiet ? null : spinner();
|
|
82
|
+
mcpSpinner?.start("Installing and configuring Neon MCP Server...");
|
|
69
83
|
let mcpSuccessCount = 0;
|
|
70
84
|
for (const editor of mcpEditors) try {
|
|
71
85
|
await installMCPServerViaAddMcp(editor, apiKey);
|
|
@@ -73,11 +87,14 @@ async function installNeon(selectedEditors) {
|
|
|
73
87
|
mcpSuccessCount++;
|
|
74
88
|
} catch (err) {
|
|
75
89
|
results.set(editor, "failed");
|
|
76
|
-
if (err && typeof err === "object" && "stderr" in err && err.stderr) log.error(String(err.stderr).trim() || "failed to install MCP server via add-mcp");
|
|
90
|
+
if (!quiet && err && typeof err === "object" && "stderr" in err && err.stderr) log.error(String(err.stderr).trim() || "failed to install MCP server via add-mcp");
|
|
77
91
|
}
|
|
78
|
-
mcpSpinner
|
|
92
|
+
mcpSpinner?.stop(mcpSuccessCount > 0 ? "Neon MCP Server configuration complete ✓" : "Failed to configure Neon MCP Server");
|
|
79
93
|
}
|
|
80
|
-
return
|
|
94
|
+
return {
|
|
95
|
+
results,
|
|
96
|
+
authSuccess: true
|
|
97
|
+
};
|
|
81
98
|
}
|
|
82
99
|
//#endregion
|
|
83
100
|
export { installNeon };
|
package/dist/lib/install.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { log, spinner } from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { getAddMcpAgentId } from \"./agents.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { log, spinner } from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { getAddMcpAgentId } from \"./agents.js\";\nimport {\n\ttype AuthOptions,\n\tcreateApiKeyFromNeonctl,\n\tensureNeonctlAuth,\n} from \"./auth.js\";\nimport {\n\tconfigureExtension,\n\tinstallExtension,\n\tusesExtension,\n\twaitForExtensionInstalled,\n} from \"./extension.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\nconst NEON_MCP_SERVER_URL = \"https://mcp.neon.tech/mcp\";\n\n/**\n * Installs Neon MCP Server for a single editor via the add-mcp CLI.\n * Uses API key authentication via the Authorization header.\n */\nasync function installMCPServerViaAddMcp(\n\teditor: Editor,\n\tapiKey: string,\n): Promise<void> {\n\tconst agentId = getAddMcpAgentId(editor);\n\n\tawait execa(\n\t\t\"npx\",\n\t\t[\n\t\t\t\"-y\",\n\t\t\t\"add-mcp\",\n\t\t\tNEON_MCP_SERVER_URL,\n\t\t\t\"--header\",\n\t\t\t`Authorization: Bearer ${apiKey}`,\n\t\t\t\"-g\",\n\t\t\t\"-n\",\n\t\t\t\"Neon\",\n\t\t\t\"-y\",\n\t\t\t\"-a\",\n\t\t\tagentId,\n\t\t],\n\t\t{\n\t\t\tstdio: \"pipe\",\n\t\t\ttimeout: 60000,\n\t\t},\n\t);\n}\n\nexport interface InstallNeonOptions {\n\tjson?: boolean;\n}\n\n/**\n * Installs Neon's Local Connect extension or MCP Server for specific editors.\n * Returns a map of editor → install status and whether auth succeeded.\n */\nexport async function installNeon(\n\tselectedEditors: Editor[],\n\toptions?: InstallNeonOptions,\n): Promise<{ results: Map<Editor, InstallStatus>; authSuccess: boolean }> {\n\tconst quiet = options?.json === true;\n\tconst authOptions: AuthOptions = { json: quiet };\n\tconst results = new Map<Editor, InstallStatus>();\n\n\tconst extensionEditors = selectedEditors.filter(usesExtension);\n\tconst mcpEditors = selectedEditors.filter((e) => !usesExtension(e));\n\n\tif (extensionEditors.length === 0 && mcpEditors.length === 0) {\n\t\treturn { results, authSuccess: false };\n\t}\n\n\tconst authSpinner = quiet ? null : spinner();\n\tauthSpinner?.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth(authOptions);\n\n\tif (!authSuccess) {\n\t\tauthSpinner?.stop(\"Authentication failed\");\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn { results, authSuccess: false };\n\t}\n\n\tauthSpinner?.stop(\"Authentication successful ✓\");\n\n\tconst apiKey = await createApiKeyFromNeonctl(authOptions);\n\n\tif (!apiKey) {\n\t\tif (!quiet) {\n\t\t\tlog.error(\"Could not create API key after authentication.\");\n\t\t\tlog.info(\n\t\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t\t);\n\t\t}\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn { results, authSuccess: true };\n\t}\n\n\tfor (const editor of extensionEditors) {\n\t\tconst installSuccess = await installExtension(editor);\n\n\t\tif (!installSuccess) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst isReady = await waitForExtensionInstalled(editor);\n\t\tif (!isReady) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst configSuccess = await configureExtension(editor, apiKey);\n\n\t\tif (configSuccess) {\n\t\t\tresults.set(editor, \"success\");\n\t\t} else {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t}\n\n\tif (mcpEditors.length > 0) {\n\t\tconst mcpSpinner = quiet ? null : spinner();\n\t\tmcpSpinner?.start(\"Installing and configuring Neon MCP Server...\");\n\n\t\tlet mcpSuccessCount = 0;\n\t\tfor (const editor of mcpEditors) {\n\t\t\ttry {\n\t\t\t\tawait installMCPServerViaAddMcp(editor, apiKey);\n\t\t\t\tresults.set(editor, \"success\");\n\t\t\t\tmcpSuccessCount++;\n\t\t\t} catch (err) {\n\t\t\t\tresults.set(editor, \"failed\");\n\t\t\t\tif (\n\t\t\t\t\t!quiet &&\n\t\t\t\t\terr &&\n\t\t\t\t\ttypeof err === \"object\" &&\n\t\t\t\t\t\"stderr\" in err &&\n\t\t\t\t\terr.stderr\n\t\t\t\t) {\n\t\t\t\t\tlog.error(\n\t\t\t\t\t\tString(err.stderr).trim() ||\n\t\t\t\t\t\t\t\"failed to install MCP server via add-mcp\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmcpSpinner?.stop(\n\t\t\tmcpSuccessCount > 0\n\t\t\t\t? \"Neon MCP Server configuration complete ✓\"\n\t\t\t\t: \"Failed to configure Neon MCP Server\",\n\t\t);\n\t}\n\n\treturn { results, authSuccess: true };\n}\n"],"mappings":";;;;;;AAgBA,MAAM,sBAAsB;;;;;AAM5B,eAAe,0BACd,QACA,QACgB;CAChB,MAAM,UAAU,iBAAiB,MAAM;CAEvC,MAAM,MACL,OACA;EACC;EACA;EACA;EACA;EACA,yBAAyB;EACzB;EACA;EACA;EACA;EACA;EACA;CACD,GACA;EACC,OAAO;EACP,SAAS;CACV,CACD;AACD;;;;;AAUA,eAAsB,YACrB,iBACA,SACyE;CACzE,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,cAA2B,EAAE,MAAM,MAAM;CAC/C,MAAM,0BAAU,IAAI,IAA2B;CAE/C,MAAM,mBAAmB,gBAAgB,OAAO,aAAa;CAC7D,MAAM,aAAa,gBAAgB,QAAQ,MAAM,CAAC,cAAc,CAAC,CAAC;CAElE,IAAI,iBAAiB,WAAW,KAAK,WAAW,WAAW,GAC1D,OAAO;EAAE;EAAS,aAAa;CAAM;CAGtC,MAAM,cAAc,QAAQ,OAAO,QAAQ;CAC3C,aAAa,MAAM,mBAAmB;CAItC,IAAI,CAAC,MAFqB,kBAAkB,WAAW,GAErC;EACjB,aAAa,KAAK,uBAAuB;EACzC,KAAK,MAAM,UAAU,iBACpB,QAAQ,IAAI,QAAQ,QAAQ;EAE7B,OAAO;GAAE;GAAS,aAAa;EAAM;CACtC;CAEA,aAAa,KAAK,6BAA6B;CAE/C,MAAM,SAAS,MAAM,wBAAwB,WAAW;CAExD,IAAI,CAAC,QAAQ;EACZ,IAAI,CAAC,OAAO;GACX,IAAI,MAAM,gDAAgD;GAC1D,IAAI,KACH,iFACD;EACD;EACA,KAAK,MAAM,UAAU,iBACpB,QAAQ,IAAI,QAAQ,QAAQ;EAE7B,OAAO;GAAE;GAAS,aAAa;EAAK;CACrC;CAEA,KAAK,MAAM,UAAU,kBAAkB;EAGtC,IAAI,CAAC,MAFwB,iBAAiB,MAAM,GAE/B;GACpB,QAAQ,IAAI,QAAQ,QAAQ;GAC5B;EACD;EAGA,IAAI,CAAC,MADiB,0BAA0B,MAAM,GACxC;GACb,QAAQ,IAAI,QAAQ,QAAQ;GAC5B;EACD;EAIA,IAAI,MAFwB,mBAAmB,QAAQ,MAAM,GAG5D,QAAQ,IAAI,QAAQ,SAAS;OAE7B,QAAQ,IAAI,QAAQ,QAAQ;CAE9B;CAEA,IAAI,WAAW,SAAS,GAAG;EAC1B,MAAM,aAAa,QAAQ,OAAO,QAAQ;EAC1C,YAAY,MAAM,+CAA+C;EAEjE,IAAI,kBAAkB;EACtB,KAAK,MAAM,UAAU,YACpB,IAAI;GACH,MAAM,0BAA0B,QAAQ,MAAM;GAC9C,QAAQ,IAAI,QAAQ,SAAS;GAC7B;EACD,SAAS,KAAK;GACb,QAAQ,IAAI,QAAQ,QAAQ;GAC5B,IACC,CAAC,SACD,OACA,OAAO,QAAQ,YACf,YAAY,OACZ,IAAI,QAEJ,IAAI,MACH,OAAO,IAAI,MAAM,CAAC,CAAC,KAAK,KACvB,0CACF;EAEF;EAGD,YAAY,KACX,kBAAkB,IACf,6CACA,qCACJ;CACD;CAEA,OAAO;EAAE;EAAS,aAAa;CAAK;AACrC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/lib/neonctl.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Derives --api-host and --oauth-host flags from the NEON_API_HOST env var.
|
|
4
|
+
*
|
|
5
|
+
* When NEON_API_HOST is set (e.g. "https://console-stage.neon.build"), neonctl
|
|
6
|
+
* commands need explicit flags:
|
|
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.
|
|
17
|
+
*
|
|
18
|
+
* Usage: `${neonctlCmd()} orgs list --output json`
|
|
19
|
+
*/
|
|
20
|
+
declare function neonctlCmd(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Detects which package manager was used to invoke the current process.
|
|
23
|
+
* Reads the `npm_config_user_agent` env var set by npm/pnpm/yarn/bun when
|
|
24
|
+
* they spawn child processes (including via `npx`, `pnpx`, `bunx`, etc.).
|
|
25
|
+
*
|
|
26
|
+
* Falls back to "npm" if detection fails.
|
|
27
|
+
*/
|
|
28
|
+
declare function detectPackageManager(): "npm" | "pnpm" | "yarn" | "bun";
|
|
29
|
+
interface NeonctlStatus {
|
|
30
|
+
installed: boolean;
|
|
31
|
+
currentVersion: string | null;
|
|
32
|
+
latestVersion: string | null;
|
|
33
|
+
needsUpdate: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks whether the neonctl CLI is globally installed and whether it's up to date.
|
|
37
|
+
*/
|
|
38
|
+
declare function checkNeonctl(): Promise<NeonctlStatus>;
|
|
39
|
+
interface EnsureNeonctlResult {
|
|
40
|
+
status: "already_current" | "installed" | "updated" | "failed";
|
|
41
|
+
version?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Ensures neonctl is globally installed and up to date.
|
|
46
|
+
* Uses the same package manager that invoked the init command.
|
|
47
|
+
*/
|
|
48
|
+
declare function ensureNeonctl(): Promise<EnsureNeonctlResult>;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { EnsureNeonctlResult, checkNeonctl, detectPackageManager, ensureNeonctl, getNeonctlApiFlags, neonctlCmd };
|
|
51
|
+
//# sourceMappingURL=neonctl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neonctl.d.ts","names":[],"sources":["../../src/lib/neonctl.ts"],"mappings":";;AAaA;AA2BA;AAYA;AAQC;AAkDD;;;;AAA6C;AAiD7C;AAUsB,iBA5JN,kBAAA,CAAA,CA4JmB,EAAA,MAAA;;;;AAAW;;;iBAjI9B,UAAA,CAAA;;;;;;;;iBAYA,oBAAA,CAAA;UA6BN,aAAA;;;;;;;;;iBA6BY,YAAA,CAAA,GAAgB,QAAQ;UAiD7B,mBAAA;;;;;;;;;iBAUK,aAAA,CAAA,GAAiB,QAAQ"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
//#region src/lib/neonctl.ts
|
|
3
|
+
/**
|
|
4
|
+
* Derives --api-host and --oauth-host flags from the NEON_API_HOST env var.
|
|
5
|
+
*
|
|
6
|
+
* When NEON_API_HOST is set (e.g. "https://console-stage.neon.build"), neonctl
|
|
7
|
+
* commands need explicit flags:
|
|
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.
|
|
31
|
+
*
|
|
32
|
+
* Usage: `${neonctlCmd()} orgs list --output json`
|
|
33
|
+
*/
|
|
34
|
+
function neonctlCmd() {
|
|
35
|
+
const flags = getNeonctlApiFlags();
|
|
36
|
+
return flags ? `CI= npx -y neonctl ${flags}` : "CI= npx -y neonctl";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Detects which package manager was used to invoke the current process.
|
|
40
|
+
* Reads the `npm_config_user_agent` env var set by npm/pnpm/yarn/bun when
|
|
41
|
+
* they spawn child processes (including via `npx`, `pnpx`, `bunx`, etc.).
|
|
42
|
+
*
|
|
43
|
+
* Falls back to "npm" if detection fails.
|
|
44
|
+
*/
|
|
45
|
+
function detectPackageManager() {
|
|
46
|
+
const ua = process.env.npm_config_user_agent;
|
|
47
|
+
if (ua) {
|
|
48
|
+
if (ua.startsWith("pnpm/")) return "pnpm";
|
|
49
|
+
if (ua.startsWith("yarn/")) return "yarn";
|
|
50
|
+
if (ua.startsWith("bun/")) return "bun";
|
|
51
|
+
}
|
|
52
|
+
return "npm";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns the global install command for a given package manager.
|
|
56
|
+
*/
|
|
57
|
+
function globalInstallArgs(pm, pkg) {
|
|
58
|
+
switch (pm) {
|
|
59
|
+
case "pnpm": return {
|
|
60
|
+
command: "pnpm",
|
|
61
|
+
args: [
|
|
62
|
+
"add",
|
|
63
|
+
"-g",
|
|
64
|
+
pkg
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
case "yarn": return {
|
|
68
|
+
command: "yarn",
|
|
69
|
+
args: [
|
|
70
|
+
"global",
|
|
71
|
+
"add",
|
|
72
|
+
pkg
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
case "bun": return {
|
|
76
|
+
command: "bun",
|
|
77
|
+
args: [
|
|
78
|
+
"add",
|
|
79
|
+
"-g",
|
|
80
|
+
pkg
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
default: return {
|
|
84
|
+
command: "npm",
|
|
85
|
+
args: [
|
|
86
|
+
"install",
|
|
87
|
+
"-g",
|
|
88
|
+
pkg
|
|
89
|
+
]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Gets the currently available neonctl version.
|
|
95
|
+
* Tries the global binary first, then falls back to npx.
|
|
96
|
+
*/
|
|
97
|
+
async function getNeonctlVersion() {
|
|
98
|
+
try {
|
|
99
|
+
const match = (await execa("neonctl", ["--version"], {
|
|
100
|
+
stdio: "pipe",
|
|
101
|
+
timeout: 5e3
|
|
102
|
+
})).stdout.trim().match(/(\d+\.\d+\.\d+)/);
|
|
103
|
+
if (match) return match[1];
|
|
104
|
+
} catch {}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Checks whether the neonctl CLI is globally installed and whether it's up to date.
|
|
109
|
+
*/
|
|
110
|
+
async function checkNeonctl() {
|
|
111
|
+
const currentVersion = await getNeonctlVersion();
|
|
112
|
+
if (!currentVersion) return {
|
|
113
|
+
installed: false,
|
|
114
|
+
currentVersion: null,
|
|
115
|
+
latestVersion: null,
|
|
116
|
+
needsUpdate: true
|
|
117
|
+
};
|
|
118
|
+
let latestVersion = null;
|
|
119
|
+
try {
|
|
120
|
+
latestVersion = (await execa("npm", [
|
|
121
|
+
"view",
|
|
122
|
+
"neonctl",
|
|
123
|
+
"version"
|
|
124
|
+
], {
|
|
125
|
+
stdio: "pipe",
|
|
126
|
+
timeout: 1e4
|
|
127
|
+
})).stdout.trim();
|
|
128
|
+
} catch {
|
|
129
|
+
return {
|
|
130
|
+
installed: true,
|
|
131
|
+
currentVersion,
|
|
132
|
+
latestVersion: null,
|
|
133
|
+
needsUpdate: false
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const needsUpdate = currentVersion !== null && latestVersion !== null && currentVersion !== latestVersion && isOlderVersion(currentVersion, latestVersion);
|
|
137
|
+
return {
|
|
138
|
+
installed: true,
|
|
139
|
+
currentVersion,
|
|
140
|
+
latestVersion,
|
|
141
|
+
needsUpdate
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function isOlderVersion(current, latest) {
|
|
145
|
+
const c = current.split(".").map(Number);
|
|
146
|
+
const l = latest.split(".").map(Number);
|
|
147
|
+
for (let i = 0; i < 3; i++) {
|
|
148
|
+
if ((c[i] ?? 0) < (l[i] ?? 0)) return true;
|
|
149
|
+
if ((c[i] ?? 0) > (l[i] ?? 0)) return false;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Ensures neonctl is globally installed and up to date.
|
|
155
|
+
* Uses the same package manager that invoked the init command.
|
|
156
|
+
*/
|
|
157
|
+
async function ensureNeonctl() {
|
|
158
|
+
const check = await checkNeonctl();
|
|
159
|
+
if (check.installed && !check.needsUpdate) return {
|
|
160
|
+
status: "already_current",
|
|
161
|
+
version: check.currentVersion ?? void 0
|
|
162
|
+
};
|
|
163
|
+
const { command, args } = globalInstallArgs(detectPackageManager(), "neonctl");
|
|
164
|
+
try {
|
|
165
|
+
await execa(command, args, {
|
|
166
|
+
stdio: "pipe",
|
|
167
|
+
timeout: 6e4
|
|
168
|
+
});
|
|
169
|
+
const version = await getNeonctlVersion();
|
|
170
|
+
return {
|
|
171
|
+
status: check.installed ? "updated" : "installed",
|
|
172
|
+
version: version ?? void 0
|
|
173
|
+
};
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return {
|
|
176
|
+
status: "failed",
|
|
177
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { checkNeonctl, detectPackageManager, ensureNeonctl, getNeonctlApiFlags, neonctlCmd };
|
|
183
|
+
|
|
184
|
+
//# sourceMappingURL=neonctl.js.map
|