neon-init 0.11.0 → 0.11.1

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.
@@ -8,6 +8,11 @@ import { Editor } from "./types.js";
8
8
  * Falls back to the simple command name if no full path is found (in case it's in PATH)
9
9
  */
10
10
  declare function findEditorCommand(editor: Editor): Promise<string | null>;
11
+ /**
12
+ * Waits for the extension to appear in the installed extensions list
13
+ * This ensures the extension is fully installed and activated before we try to configure it
14
+ */
15
+ declare function waitForExtensionInstalled(editor: Editor, maxAttempts?: number, delayMs?: number): Promise<boolean>;
11
16
  /**
12
17
  * Installs the Neon Local Connect extension for VS Code or Cursor
13
18
  * Returns success only if installation succeeds, fails silently otherwise
@@ -23,5 +28,5 @@ declare function configureExtension(editor: Editor, apiKey: string): Promise<boo
23
28
  */
24
29
  declare function usesExtension(editor: Editor): boolean;
25
30
  //#endregion
26
- export { configureExtension, findEditorCommand, installExtension, usesExtension };
31
+ export { configureExtension, findEditorCommand, installExtension, usesExtension, waitForExtensionInstalled };
27
32
  //# sourceMappingURL=extension.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.ts","names":[],"sources":["../../src/lib/extension.ts"],"sourcesContent":[],"mappings":";;;;;;AAuGA;;;AAEG,iBAFmB,iBAAA,CAEnB,MAAA,EADM,MACN,CAAA,EAAA,OAAA,CAAA,MAAA,GAAA,IAAA,CAAA;;AAsDH;;;AAAwD,iBAAlC,gBAAA,CAAkC,MAAA,EAAT,MAAS,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAsBxD;;;AAGG,iBAHmB,kBAAA,CAGnB,MAAA,EAFM,MAEN,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAoCH;;iBAAgB,aAAA,SAAsB"}
1
+ {"version":3,"file":"extension.d.ts","names":[],"sources":["../../src/lib/extension.ts"],"sourcesContent":[],"mappings":";;;;;;AAuGA;;;AAEG,iBAFmB,iBAAA,CAEnB,MAAA,EADM,MACN,CAAA,EAAA,OAAA,CAAA,MAAA,GAAA,IAAA,CAAA;;AAyEH;;;AAIG,iBAJmB,yBAAA,CAInB,MAAA,EAHM,MAGN,EAAA,WAAA,CAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAuBH;;;AAAwD,iBAAlC,gBAAA,CAAkC,MAAA,EAAT,MAAS,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAsBxD;;;AAGG,iBAHmB,kBAAA,CAGnB,MAAA,EAFM,MAEN,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAA,OAAA,CAAA;;AAoCH;;iBAAgB,aAAA,SAAsB"}
@@ -107,6 +107,32 @@ function getEditorUriScheme(editor) {
107
107
  return null;
108
108
  }
109
109
  /**
110
+ * Checks if the extension is installed by querying the editor's extension list
111
+ */
112
+ async function isExtensionInList(editor) {
113
+ const command = await findEditorCommand(editor);
114
+ if (!command) return false;
115
+ try {
116
+ return (await execa(command, ["--list-extensions"], { timeout: 5e3 })).stdout.includes(NEON_EXTENSION_ID);
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+ /**
122
+ * Waits for the extension to appear in the installed extensions list
123
+ * This ensures the extension is fully installed and activated before we try to configure it
124
+ */
125
+ async function waitForExtensionInstalled(editor, maxAttempts = 10, delayMs = 1e3) {
126
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
127
+ if (await isExtensionInList(editor)) {
128
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
129
+ return true;
130
+ }
131
+ if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, delayMs));
132
+ }
133
+ return false;
134
+ }
135
+ /**
110
136
  * Installs the Neon Local Connect extension for VS Code or Cursor
111
137
  * Returns success only if installation succeeds, fails silently otherwise
112
138
  */
@@ -156,5 +182,5 @@ function usesExtension(editor) {
156
182
  }
157
183
 
158
184
  //#endregion
159
- export { configureExtension, findEditorCommand, installExtension, usesExtension };
185
+ export { configureExtension, findEditorCommand, installExtension, usesExtension, waitForExtensionInstalled };
160
186
  //# sourceMappingURL=extension.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.js","names":[],"sources":["../../src/lib/extension.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { execa } from \"execa\";\nimport type { Editor } from \"./types.js\";\n\nconst NEON_EXTENSION_ID = \"databricks.neon-local-connect\";\n\n/**\n * Uses macOS mdfind to locate an app by bundle identifier\n */\nasync function findAppWithMdfind(bundleId: string): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa(\n\t\t\t\"mdfind\",\n\t\t\t[`kMDItemCFBundleIdentifier == '${bundleId}'`],\n\t\t\t{ timeout: 5000 },\n\t\t);\n\t\tconst paths = result.stdout.trim().split(\"\\n\").filter(Boolean);\n\t\treturn paths[0] || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Known installation paths for VS Code CLI\n */\nfunction getVSCodePaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code\",\n\t\t\t\"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders\",\n\t\t\t`${home}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t\t`${home}/Downloads/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/code/bin/code\",\n\t\t\t\"/usr/bin/code\",\n\t\t\t\"/snap/bin/code\",\n\t\t\t\"/usr/share/code-insiders/bin/code-insiders\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${programFiles}\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code Insiders\\\\bin\\\\code-insiders.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Known installation paths for Cursor CLI\n */\nfunction getCursorPaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Cursor.app/Contents/Resources/app/bin/cursor\",\n\t\t\t`${home}/Applications/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t\t`${home}/Downloads/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/cursor/bin/cursor\",\n\t\t\t\"/usr/bin/cursor\",\n\t\t\t`${home}/.local/bin/cursor`,\n\t\t\t\"/opt/cursor/bin/cursor\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t\t`${localAppData}\\\\cursor\\\\Cursor.exe`,\n\t\t\t`${programFiles}\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Finds the CLI command for an editor by checking known installation paths\n * On macOS, also uses mdfind to locate the app if standard paths fail\n * Falls back to the simple command name if no full path is found (in case it's in PATH)\n */\nexport async function findEditorCommand(\n\teditor: Editor,\n): Promise<string | null> {\n\tlet paths: string[];\n\tlet fallbackCommand: string;\n\tlet bundleId: string | null = null;\n\n\tif (editor === \"VS Code\") {\n\t\tpaths = getVSCodePaths();\n\t\tfallbackCommand = \"code\";\n\t\tbundleId = \"com.microsoft.VSCode\";\n\t} else if (editor === \"Cursor\") {\n\t\tpaths = getCursorPaths();\n\t\tfallbackCommand = \"cursor\";\n\t\tbundleId = \"com.todesktop.230313mzl4w4u92\";\n\t} else {\n\t\treturn null;\n\t}\n\n\tfor (const path of paths) {\n\t\tif (existsSync(path)) {\n\t\t\treturn path;\n\t\t}\n\t}\n\n\t// On macOS, try mdfind to locate the app dynamically\n\tif (process.platform === \"darwin\" && bundleId) {\n\t\tconst appPath = await findAppWithMdfind(bundleId);\n\t\tif (appPath) {\n\t\t\tconst cliPath = `${appPath}/Contents/Resources/app/bin/${fallbackCommand}`;\n\t\t\tif (existsSync(cliPath)) {\n\t\t\t\treturn cliPath;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fallbackCommand;\n}\n\n/**\n * Gets the URI scheme for an editor\n */\nfunction getEditorUriScheme(editor: Editor): string | null {\n\tif (editor === \"VS Code\") {\n\t\treturn \"vscode\";\n\t}\n\tif (editor === \"Cursor\") {\n\t\treturn \"cursor\";\n\t}\n\treturn null;\n}\n\n/**\n * Installs the Neon Local Connect extension for VS Code or Cursor\n * Returns success only if installation succeeds, fails silently otherwise\n */\nexport async function installExtension(editor: Editor): Promise<boolean> {\n\tconst command = await findEditorCommand(editor);\n\tif (!command) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tawait execa(command, [\n\t\t\t\"--install-extension\",\n\t\t\tNEON_EXTENSION_ID,\n\t\t\t\"--pre-release\",\n\t\t]);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Configures the Neon Local Connect extension with the API key\n * Uses the extension's URI handler to trigger the import-api-key command\n */\nexport async function configureExtension(\n\teditor: Editor,\n\tapiKey: string,\n): Promise<boolean> {\n\tconst scheme = getEditorUriScheme(editor);\n\tif (!scheme) {\n\t\treturn false;\n\t}\n\n\t// Build the URI to trigger the extension's import-api-key handler\n\t// Format: vscode://databricks.neon-local-connect/import-api-key?token=xxx\n\tconst encodedApiKey = encodeURIComponent(apiKey);\n\tconst uri = `${scheme}://${NEON_EXTENSION_ID}/import-api-key?token=${encodedApiKey}`;\n\n\ttry {\n\t\tconst platform = process.platform;\n\n\t\tif (platform === \"darwin\") {\n\t\t\t// macOS: use 'open' command\n\t\t\tawait execa(\"open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"linux\") {\n\t\t\t// Linux: use 'xdg-open' command\n\t\t\tawait execa(\"xdg-open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"win32\") {\n\t\t\t// Windows: use 'start' command\n\t\t\tawait execa(\"cmd\", [\"/c\", \"start\", \"\", uri], { timeout: 10000 });\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Returns the editor types that should use extension installation (vs MCP)\n */\nexport function usesExtension(editor: Editor): boolean {\n\treturn editor === \"VS Code\" || editor === \"Cursor\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.js","names":[],"sources":["../../src/lib/extension.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { execa } from \"execa\";\nimport type { Editor } from \"./types.js\";\n\nconst NEON_EXTENSION_ID = \"databricks.neon-local-connect\";\n\n/**\n * Uses macOS mdfind to locate an app by bundle identifier\n */\nasync function findAppWithMdfind(bundleId: string): Promise<string | null> {\n\ttry {\n\t\tconst result = await execa(\n\t\t\t\"mdfind\",\n\t\t\t[`kMDItemCFBundleIdentifier == '${bundleId}'`],\n\t\t\t{ timeout: 5000 },\n\t\t);\n\t\tconst paths = result.stdout.trim().split(\"\\n\").filter(Boolean);\n\t\treturn paths[0] || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Known installation paths for VS Code CLI\n */\nfunction getVSCodePaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code\",\n\t\t\t\"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders\",\n\t\t\t`${home}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t\t`${home}/Downloads/Visual Studio Code.app/Contents/Resources/app/bin/code`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/code/bin/code\",\n\t\t\t\"/usr/bin/code\",\n\t\t\t\"/snap/bin/code\",\n\t\t\t\"/usr/share/code-insiders/bin/code-insiders\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${programFiles}\\\\Microsoft VS Code\\\\bin\\\\code.cmd`,\n\t\t\t`${localAppData}\\\\Programs\\\\Microsoft VS Code Insiders\\\\bin\\\\code-insiders.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Known installation paths for Cursor CLI\n */\nfunction getCursorPaths(): string[] {\n\tconst platform = process.platform;\n\tconst home = process.env.HOME || \"\";\n\n\tif (platform === \"darwin\") {\n\t\treturn [\n\t\t\t\"/Applications/Cursor.app/Contents/Resources/app/bin/cursor\",\n\t\t\t`${home}/Applications/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t\t`${home}/Downloads/Cursor.app/Contents/Resources/app/bin/cursor`,\n\t\t];\n\t}\n\n\tif (platform === \"linux\") {\n\t\treturn [\n\t\t\t\"/usr/share/cursor/bin/cursor\",\n\t\t\t\"/usr/bin/cursor\",\n\t\t\t`${home}/.local/bin/cursor`,\n\t\t\t\"/opt/cursor/bin/cursor\",\n\t\t];\n\t}\n\n\tif (platform === \"win32\") {\n\t\tconst localAppData = process.env.LOCALAPPDATA || \"\";\n\t\tconst programFiles = process.env.PROGRAMFILES || \"C:\\\\Program Files\";\n\t\treturn [\n\t\t\t`${localAppData}\\\\Programs\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t\t`${localAppData}\\\\cursor\\\\Cursor.exe`,\n\t\t\t`${programFiles}\\\\Cursor\\\\resources\\\\app\\\\bin\\\\cursor.cmd`,\n\t\t];\n\t}\n\n\treturn [];\n}\n\n/**\n * Finds the CLI command for an editor by checking known installation paths\n * On macOS, also uses mdfind to locate the app if standard paths fail\n * Falls back to the simple command name if no full path is found (in case it's in PATH)\n */\nexport async function findEditorCommand(\n\teditor: Editor,\n): Promise<string | null> {\n\tlet paths: string[];\n\tlet fallbackCommand: string;\n\tlet bundleId: string | null = null;\n\n\tif (editor === \"VS Code\") {\n\t\tpaths = getVSCodePaths();\n\t\tfallbackCommand = \"code\";\n\t\tbundleId = \"com.microsoft.VSCode\";\n\t} else if (editor === \"Cursor\") {\n\t\tpaths = getCursorPaths();\n\t\tfallbackCommand = \"cursor\";\n\t\tbundleId = \"com.todesktop.230313mzl4w4u92\";\n\t} else {\n\t\treturn null;\n\t}\n\n\tfor (const path of paths) {\n\t\tif (existsSync(path)) {\n\t\t\treturn path;\n\t\t}\n\t}\n\n\t// On macOS, try mdfind to locate the app dynamically\n\tif (process.platform === \"darwin\" && bundleId) {\n\t\tconst appPath = await findAppWithMdfind(bundleId);\n\t\tif (appPath) {\n\t\t\tconst cliPath = `${appPath}/Contents/Resources/app/bin/${fallbackCommand}`;\n\t\t\tif (existsSync(cliPath)) {\n\t\t\t\treturn cliPath;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fallbackCommand;\n}\n\n/**\n * Gets the URI scheme for an editor\n */\nfunction getEditorUriScheme(editor: Editor): string | null {\n\tif (editor === \"VS Code\") {\n\t\treturn \"vscode\";\n\t}\n\tif (editor === \"Cursor\") {\n\t\treturn \"cursor\";\n\t}\n\treturn null;\n}\n\n/**\n * Checks if the extension is installed by querying the editor's extension list\n */\nasync function isExtensionInList(editor: Editor): Promise<boolean> {\n\tconst command = await findEditorCommand(editor);\n\tif (!command) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tconst result = await execa(command, [\"--list-extensions\"], {\n\t\t\ttimeout: 5000,\n\t\t});\n\t\treturn result.stdout.includes(NEON_EXTENSION_ID);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Waits for the extension to appear in the installed extensions list\n * This ensures the extension is fully installed and activated before we try to configure it\n */\nexport async function waitForExtensionInstalled(\n\teditor: Editor,\n\tmaxAttempts = 10,\n\tdelayMs = 1000,\n): Promise<boolean> {\n\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\tconst isInstalled = await isExtensionInList(editor);\n\n\t\tif (isInstalled) {\n\t\t\t// Give the extension a moment to fully activate and register URI handlers\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 1000));\n\t\t\treturn true;\n\t\t}\n\n\t\t// Wait before checking again (unless this is the last attempt)\n\t\tif (attempt < maxAttempts - 1) {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, delayMs));\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Installs the Neon Local Connect extension for VS Code or Cursor\n * Returns success only if installation succeeds, fails silently otherwise\n */\nexport async function installExtension(editor: Editor): Promise<boolean> {\n\tconst command = await findEditorCommand(editor);\n\tif (!command) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tawait execa(command, [\n\t\t\t\"--install-extension\",\n\t\t\tNEON_EXTENSION_ID,\n\t\t\t\"--pre-release\",\n\t\t]);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Configures the Neon Local Connect extension with the API key\n * Uses the extension's URI handler to trigger the import-api-key command\n */\nexport async function configureExtension(\n\teditor: Editor,\n\tapiKey: string,\n): Promise<boolean> {\n\tconst scheme = getEditorUriScheme(editor);\n\tif (!scheme) {\n\t\treturn false;\n\t}\n\n\t// Build the URI to trigger the extension's import-api-key handler\n\t// Format: vscode://databricks.neon-local-connect/import-api-key?token=xxx\n\tconst encodedApiKey = encodeURIComponent(apiKey);\n\tconst uri = `${scheme}://${NEON_EXTENSION_ID}/import-api-key?token=${encodedApiKey}`;\n\n\ttry {\n\t\tconst platform = process.platform;\n\n\t\tif (platform === \"darwin\") {\n\t\t\t// macOS: use 'open' command\n\t\t\tawait execa(\"open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"linux\") {\n\t\t\t// Linux: use 'xdg-open' command\n\t\t\tawait execa(\"xdg-open\", [uri], { timeout: 10000 });\n\t\t} else if (platform === \"win32\") {\n\t\t\t// Windows: use 'start' command\n\t\t\tawait execa(\"cmd\", [\"/c\", \"start\", \"\", uri], { timeout: 10000 });\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Returns the editor types that should use extension installation (vs MCP)\n */\nexport function usesExtension(editor: Editor): boolean {\n\treturn editor === \"VS Code\" || editor === \"Cursor\";\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":[],"mappings":";;;;;;AA2FA;AAAiC,iBAAX,WAAA,CAAW,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,eAAA,EAGf,MAHe,EAAA,CAAA,EAI9B,OAJ8B,CAItB,GAJsB,CAIlB,MAJkB,EAIV,aAJU,CAAA,CAAA"}
1
+ {"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":[],"mappings":";;;;;;AA4FA;AAAiC,iBAAX,WAAA,CAAW,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,eAAA,EAGf,MAHe,EAAA,CAAA,EAI9B,OAJ8B,CAItB,GAJsB,CAIlB,MAJkB,EAIV,aAJU,CAAA,CAAA"}
@@ -1,4 +1,4 @@
1
- import { configureExtension, installExtension, usesExtension } from "./extension.js";
1
+ import { configureExtension, installExtension, usesExtension, waitForExtensionInstalled } from "./extension.js";
2
2
  import { createApiKeyFromNeonctl, ensureNeonctlAuth } from "./auth.js";
3
3
  import { getMCPConfig, writeMCPConfig } from "./mcp-config.js";
4
4
  import { confirm, isCancel, log, spinner } from "@clack/prompts";
@@ -80,6 +80,10 @@ async function installNeon(homeDir, workspaceDir, selectedEditors) {
80
80
  results.set(editor, "failed");
81
81
  continue;
82
82
  }
83
+ if (!await waitForExtensionInstalled(editor)) {
84
+ results.set(editor, "failed");
85
+ continue;
86
+ }
83
87
  if (await configureExtension(editor, apiKey)) results.set(editor, "success");
84
88
  else results.set(editor, "success");
85
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { confirm, isCancel, log, spinner } from \"@clack/prompts\";\nimport { createApiKeyFromNeonctl, ensureNeonctlAuth } from \"./auth.js\";\nimport {\n\tconfigureExtension,\n\tinstallExtension,\n\tusesExtension,\n} from \"./extension.js\";\nimport { getMCPConfig, writeMCPConfig } from \"./mcp-config.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\n/**\n * Checks if an editor needs MCP configuration\n * Returns true if configuration is needed, false otherwise\n */\nasync function shouldConfigureMCP(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n): Promise<boolean> {\n\tconst { config } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Check if already configured\n\tconst serverKey = editor === \"VS Code\" ? config.servers : config.mcpServers;\n\tconst alreadyConfigured = Boolean(serverKey?.Neon);\n\n\tif (alreadyConfigured) {\n\t\tconst response = await confirm({\n\t\t\tmessage: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(response)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst shouldReconfigure = response as boolean;\n\n\t\tif (!shouldReconfigure) {\n\t\t\tlog.info(\n\t\t\t\t`Keeping existing MCP server configuration for ${editor}.`,\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Installs Neon's MCP Server for specific editors\n */\nasync function installMCPServerForEditor(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n\tapiKey: string,\n): Promise<InstallStatus> {\n\tconst { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Configure Neon MCP Server\n\t// Using remote MCP server with API key authentication\n\t// Ref: https://neon.com/docs/ai/neon-mcp-server#api-key-based-authentication\n\tconst neonServerConfig = {\n\t\ttype: \"http\",\n\t\turl: \"https://mcp.neon.tech/mcp\",\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t},\n\t};\n\n\t// Claude CLI uses \"mcpServers\" key\n\tif (!config.mcpServers) {\n\t\tconfig.mcpServers = {};\n\t}\n\tconfig.mcpServers.Neon = neonServerConfig;\n\n\t// Write configuration\n\ttry {\n\t\twriteMCPConfig(configPath, config);\n\t\treturn \"success\";\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to write configuration for ${editor}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn \"failed\";\n\t}\n}\n\n/**\n * Installs Neon's Local Connect extension or MCP Server for specific editors\n */\nexport async function installNeon(\n\thomeDir: string,\n\tworkspaceDir: string,\n\tselectedEditors: Editor[],\n): Promise<Map<Editor, InstallStatus>> {\n\tconst results = new Map<Editor, InstallStatus>();\n\n\tconst extensionEditors = selectedEditors.filter(usesExtension);\n\tconst mcpEditors = selectedEditors.filter((e) => !usesExtension(e));\n\n\t// Check which MCP editors need configuration\n\tconst mcpEditorsToConfigureMap = new Map<Editor, boolean>();\n\tfor (const editor of mcpEditors) {\n\t\tconst needsConfig = await shouldConfigureMCP(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t);\n\t\tmcpEditorsToConfigureMap.set(editor, needsConfig);\n\n\t\tif (!needsConfig) {\n\t\t\tresults.set(editor, \"success\");\n\t\t}\n\t}\n\n\tconst mcpToConfigure = mcpEditors.filter(\n\t\t(editor) => mcpEditorsToConfigureMap.get(editor) === true,\n\t);\n\n\t// Extension editors always get processed (silent installation)\n\tconst extensionsToConfigure = extensionEditors;\n\n\t// If nothing needs configuration, return early\n\tif (extensionsToConfigure.length === 0 && mcpToConfigure.length === 0) {\n\t\treturn results;\n\t}\n\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth();\n\n\tif (!authSuccess) {\n\t\tauthSpinner.stop(\"Authentication failed\");\n\t\t// Mark all editors that need configuration as failed\n\t\tfor (const editor of [...extensionsToConfigure, ...mcpToConfigure]) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Create API key using the OAuth token\n\tconst apiKey = await createApiKeyFromNeonctl();\n\n\tif (!apiKey) {\n\t\tlog.error(\"Could not create API key after authentication.\");\n\t\tlog.info(\n\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t);\n\t\t// Mark all editors that need configuration as failed\n\t\tfor (const editor of [...extensionsToConfigure, ...mcpToConfigure]) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\t// Install and configure extension for Cursor/VS Code (silently)\n\tfor (const editor of extensionsToConfigure) {\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\t// Configure the extension with the API key\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\t// Extension installed but auth failed but user can manually configure later\n\t\t\tresults.set(editor, \"success\");\n\t\t}\n\t}\n\n\tfor (const editor of mcpToConfigure) {\n\t\tconst status = await installMCPServerForEditor(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t\tapiKey,\n\t\t);\n\t\tresults.set(editor, status);\n\t}\n\n\treturn results;\n}\n"],"mappings":";;;;;;;;;;AAcA,eAAe,mBACd,SACA,cACA,QACmB;CACnB,MAAM,EAAE,WAAW,aAAa,SAAS,cAAc,OAAO;CAG9D,MAAM,YAAY,WAAW,YAAY,OAAO,UAAU,OAAO;AAGjE,KAF0B,QAAQ,WAAW,KAAK,EAE3B;EACtB,MAAM,WAAW,MAAM,QAAQ;GAC9B,SAAS,6CAA6C,OAAO;GAC7D,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,SAAS,CACrB,QAAO;AAKR,MAAI,CAFsB,UAEF;AACvB,OAAI,KACH,iDAAiD,OAAO,GACxD;AACD,UAAO;;;AAIT,QAAO;;;;;AAMR,eAAe,0BACd,SACA,cACA,QACA,QACyB;CACzB,MAAM,EAAE,QAAQ,eAAe,aAAa,SAAS,cAAc,OAAO;CAK1E,MAAM,mBAAmB;EACxB,MAAM;EACN,KAAK;EACL,SAAS,EACR,eAAe,UAAU,UACzB;EACD;AAGD,KAAI,CAAC,OAAO,WACX,QAAO,aAAa,EAAE;AAEvB,QAAO,WAAW,OAAO;AAGzB,KAAI;AACH,iBAAe,YAAY,OAAO;AAClC,SAAO;UACC,OAAO;AACf,MAAI,MACH,qCAAqC,OAAO,IAAI,iBAAiB,QAAQ,MAAM,UAAU,kBACzF;AACD,SAAO;;;;;;AAOT,eAAsB,YACrB,SACA,cACA,iBACsC;CACtC,MAAM,0BAAU,IAAI,KAA4B;CAEhD,MAAM,mBAAmB,gBAAgB,OAAO,cAAc;CAC9D,MAAM,aAAa,gBAAgB,QAAQ,MAAM,CAAC,cAAc,EAAE,CAAC;CAGnE,MAAM,2CAA2B,IAAI,KAAsB;AAC3D,MAAK,MAAM,UAAU,YAAY;EAChC,MAAM,cAAc,MAAM,mBACzB,SACA,cACA,OACA;AACD,2BAAyB,IAAI,QAAQ,YAAY;AAEjD,MAAI,CAAC,YACJ,SAAQ,IAAI,QAAQ,UAAU;;CAIhC,MAAM,iBAAiB,WAAW,QAChC,WAAW,yBAAyB,IAAI,OAAO,KAAK,KACrD;CAGD,MAAM,wBAAwB;AAG9B,KAAI,sBAAsB,WAAW,KAAK,eAAe,WAAW,EACnE,QAAO;CAGR,MAAM,cAAc,SAAS;AAC7B,aAAY,MAAM,oBAAoB;AAItC,KAAI,CAFgB,MAAM,mBAAmB,EAE3B;AACjB,cAAY,KAAK,wBAAwB;AAEzC,OAAK,MAAM,UAAU,CAAC,GAAG,uBAAuB,GAAG,eAAe,CACjE,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAGR,aAAY,KAAK,8BAA8B;CAG/C,MAAM,SAAS,MAAM,yBAAyB;AAE9C,KAAI,CAAC,QAAQ;AACZ,MAAI,MAAM,iDAAiD;AAC3D,MAAI,KACH,kFACA;AAED,OAAK,MAAM,UAAU,CAAC,GAAG,uBAAuB,GAAG,eAAe,CACjE,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAIR,MAAK,MAAM,UAAU,uBAAuB;AAG3C,MAAI,CAFmB,MAAM,iBAAiB,OAAO,EAEhC;AACpB,WAAQ,IAAI,QAAQ,SAAS;AAC7B;;AAMD,MAFsB,MAAM,mBAAmB,QAAQ,OAAO,CAG7D,SAAQ,IAAI,QAAQ,UAAU;MAG9B,SAAQ,IAAI,QAAQ,UAAU;;AAIhC,MAAK,MAAM,UAAU,gBAAgB;EACpC,MAAM,SAAS,MAAM,0BACpB,SACA,cACA,QACA,OACA;AACD,UAAQ,IAAI,QAAQ,OAAO;;AAG5B,QAAO"}
1
+ {"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { confirm, isCancel, log, spinner } from \"@clack/prompts\";\nimport { createApiKeyFromNeonctl, ensureNeonctlAuth } from \"./auth.js\";\nimport {\n\tconfigureExtension,\n\tinstallExtension,\n\tusesExtension,\n\twaitForExtensionInstalled,\n} from \"./extension.js\";\nimport { getMCPConfig, writeMCPConfig } from \"./mcp-config.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\n/**\n * Checks if an editor needs MCP configuration\n * Returns true if configuration is needed, false otherwise\n */\nasync function shouldConfigureMCP(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n): Promise<boolean> {\n\tconst { config } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Check if already configured\n\tconst serverKey = editor === \"VS Code\" ? config.servers : config.mcpServers;\n\tconst alreadyConfigured = Boolean(serverKey?.Neon);\n\n\tif (alreadyConfigured) {\n\t\tconst response = await confirm({\n\t\t\tmessage: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(response)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst shouldReconfigure = response as boolean;\n\n\t\tif (!shouldReconfigure) {\n\t\t\tlog.info(\n\t\t\t\t`Keeping existing MCP server configuration for ${editor}.`,\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Installs Neon's MCP Server for specific editors\n */\nasync function installMCPServerForEditor(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n\tapiKey: string,\n): Promise<InstallStatus> {\n\tconst { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Configure Neon MCP Server\n\t// Using remote MCP server with API key authentication\n\t// Ref: https://neon.com/docs/ai/neon-mcp-server#api-key-based-authentication\n\tconst neonServerConfig = {\n\t\ttype: \"http\",\n\t\turl: \"https://mcp.neon.tech/mcp\",\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t},\n\t};\n\n\t// Claude CLI uses \"mcpServers\" key\n\tif (!config.mcpServers) {\n\t\tconfig.mcpServers = {};\n\t}\n\tconfig.mcpServers.Neon = neonServerConfig;\n\n\t// Write configuration\n\ttry {\n\t\twriteMCPConfig(configPath, config);\n\t\treturn \"success\";\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to write configuration for ${editor}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn \"failed\";\n\t}\n}\n\n/**\n * Installs Neon's Local Connect extension or MCP Server for specific editors\n */\nexport async function installNeon(\n\thomeDir: string,\n\tworkspaceDir: string,\n\tselectedEditors: Editor[],\n): Promise<Map<Editor, InstallStatus>> {\n\tconst results = new Map<Editor, InstallStatus>();\n\n\tconst extensionEditors = selectedEditors.filter(usesExtension);\n\tconst mcpEditors = selectedEditors.filter((e) => !usesExtension(e));\n\n\t// Check which MCP editors need configuration\n\tconst mcpEditorsToConfigureMap = new Map<Editor, boolean>();\n\tfor (const editor of mcpEditors) {\n\t\tconst needsConfig = await shouldConfigureMCP(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t);\n\t\tmcpEditorsToConfigureMap.set(editor, needsConfig);\n\n\t\tif (!needsConfig) {\n\t\t\tresults.set(editor, \"success\");\n\t\t}\n\t}\n\n\tconst mcpToConfigure = mcpEditors.filter(\n\t\t(editor) => mcpEditorsToConfigureMap.get(editor) === true,\n\t);\n\n\t// Extension editors always get processed (silent installation)\n\tconst extensionsToConfigure = extensionEditors;\n\n\t// If nothing needs configuration, return early\n\tif (extensionsToConfigure.length === 0 && mcpToConfigure.length === 0) {\n\t\treturn results;\n\t}\n\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth();\n\n\tif (!authSuccess) {\n\t\tauthSpinner.stop(\"Authentication failed\");\n\t\t// Mark all editors that need configuration as failed\n\t\tfor (const editor of [...extensionsToConfigure, ...mcpToConfigure]) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Create API key using the OAuth token\n\tconst apiKey = await createApiKeyFromNeonctl();\n\n\tif (!apiKey) {\n\t\tlog.error(\"Could not create API key after authentication.\");\n\t\tlog.info(\n\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t);\n\t\t// Mark all editors that need configuration as failed\n\t\tfor (const editor of [...extensionsToConfigure, ...mcpToConfigure]) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\t// Install and configure extension for Cursor/VS Code (silently)\n\tfor (const editor of extensionsToConfigure) {\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\t// Extension install command succeeded but extension didn't appear in list\n\t\t\tresults.set(editor, \"failed\");\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Configure the extension with the API key\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\t// Extension installed but auth failed but user can manually configure later\n\t\t\tresults.set(editor, \"success\");\n\t\t}\n\t}\n\n\tfor (const editor of mcpToConfigure) {\n\t\tconst status = await installMCPServerForEditor(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t\tapiKey,\n\t\t);\n\t\tresults.set(editor, status);\n\t}\n\n\treturn results;\n}\n"],"mappings":";;;;;;;;;;AAeA,eAAe,mBACd,SACA,cACA,QACmB;CACnB,MAAM,EAAE,WAAW,aAAa,SAAS,cAAc,OAAO;CAG9D,MAAM,YAAY,WAAW,YAAY,OAAO,UAAU,OAAO;AAGjE,KAF0B,QAAQ,WAAW,KAAK,EAE3B;EACtB,MAAM,WAAW,MAAM,QAAQ;GAC9B,SAAS,6CAA6C,OAAO;GAC7D,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,SAAS,CACrB,QAAO;AAKR,MAAI,CAFsB,UAEF;AACvB,OAAI,KACH,iDAAiD,OAAO,GACxD;AACD,UAAO;;;AAIT,QAAO;;;;;AAMR,eAAe,0BACd,SACA,cACA,QACA,QACyB;CACzB,MAAM,EAAE,QAAQ,eAAe,aAAa,SAAS,cAAc,OAAO;CAK1E,MAAM,mBAAmB;EACxB,MAAM;EACN,KAAK;EACL,SAAS,EACR,eAAe,UAAU,UACzB;EACD;AAGD,KAAI,CAAC,OAAO,WACX,QAAO,aAAa,EAAE;AAEvB,QAAO,WAAW,OAAO;AAGzB,KAAI;AACH,iBAAe,YAAY,OAAO;AAClC,SAAO;UACC,OAAO;AACf,MAAI,MACH,qCAAqC,OAAO,IAAI,iBAAiB,QAAQ,MAAM,UAAU,kBACzF;AACD,SAAO;;;;;;AAOT,eAAsB,YACrB,SACA,cACA,iBACsC;CACtC,MAAM,0BAAU,IAAI,KAA4B;CAEhD,MAAM,mBAAmB,gBAAgB,OAAO,cAAc;CAC9D,MAAM,aAAa,gBAAgB,QAAQ,MAAM,CAAC,cAAc,EAAE,CAAC;CAGnE,MAAM,2CAA2B,IAAI,KAAsB;AAC3D,MAAK,MAAM,UAAU,YAAY;EAChC,MAAM,cAAc,MAAM,mBACzB,SACA,cACA,OACA;AACD,2BAAyB,IAAI,QAAQ,YAAY;AAEjD,MAAI,CAAC,YACJ,SAAQ,IAAI,QAAQ,UAAU;;CAIhC,MAAM,iBAAiB,WAAW,QAChC,WAAW,yBAAyB,IAAI,OAAO,KAAK,KACrD;CAGD,MAAM,wBAAwB;AAG9B,KAAI,sBAAsB,WAAW,KAAK,eAAe,WAAW,EACnE,QAAO;CAGR,MAAM,cAAc,SAAS;AAC7B,aAAY,MAAM,oBAAoB;AAItC,KAAI,CAFgB,MAAM,mBAAmB,EAE3B;AACjB,cAAY,KAAK,wBAAwB;AAEzC,OAAK,MAAM,UAAU,CAAC,GAAG,uBAAuB,GAAG,eAAe,CACjE,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAGR,aAAY,KAAK,8BAA8B;CAG/C,MAAM,SAAS,MAAM,yBAAyB;AAE9C,KAAI,CAAC,QAAQ;AACZ,MAAI,MAAM,iDAAiD;AAC3D,MAAI,KACH,kFACA;AAED,OAAK,MAAM,UAAU,CAAC,GAAG,uBAAuB,GAAG,eAAe,CACjE,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAIR,MAAK,MAAM,UAAU,uBAAuB;AAG3C,MAAI,CAFmB,MAAM,iBAAiB,OAAO,EAEhC;AACpB,WAAQ,IAAI,QAAQ,SAAS;AAC7B;;AAID,MAAI,CADY,MAAM,0BAA0B,OAAO,EACzC;AAEb,WAAQ,IAAI,QAAQ,SAAS;AAC7B;;AAMD,MAFsB,MAAM,mBAAmB,QAAQ,OAAO,CAG7D,SAAQ,IAAI,QAAQ,UAAU;MAG9B,SAAQ,IAAI,QAAQ,UAAU;;AAIhC,MAAK,MAAM,UAAU,gBAAgB;EACpC,MAAM,SAAS,MAAM,0BACpB,SACA,cACA,QACA,OACA;AACD,UAAQ,IAAI,QAAQ,OAAO;;AAG5B,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon-init",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Initialize Neon projects",
5
5
  "keywords": [
6
6
  "neon",
@@ -47,7 +47,7 @@
47
47
  "yoctocolors": "^2.1.2"
48
48
  },
49
49
  "engines": {
50
- "node": "22"
50
+ "node": ">=22"
51
51
  },
52
52
  "publishConfig": {
53
53
  "provenance": false