neon 2.45.0 → 2.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +111 -4
  2. package/dist/_shared/paths.js +3 -4
  3. package/dist/analytics.js +82 -25
  4. package/dist/commands/bootstrap.js +12 -11
  5. package/dist/commands/checkout.js +7 -6
  6. package/dist/commands/config.js +26 -11
  7. package/dist/commands/data_api.js +4 -3
  8. package/dist/commands/dev.js +67 -14
  9. package/dist/commands/env.js +131 -9
  10. package/dist/commands/functions.js +3 -2
  11. package/dist/commands/init.js +82 -37
  12. package/dist/commands/ip_allow.js +3 -2
  13. package/dist/commands/link.js +17 -16
  14. package/dist/commands/projects.js +3 -2
  15. package/dist/commands/set_context.js +5 -4
  16. package/dist/config_template.js +20 -42
  17. package/dist/current_branch_fast_path.js +2 -1
  18. package/dist/dev/env.js +214 -10
  19. package/dist/env_services.js +51 -0
  20. package/dist/index.js +2 -2
  21. package/dist/init/agents.js +127 -0
  22. package/dist/init/auth.js +77 -0
  23. package/dist/init/bootstrap.js +448 -0
  24. package/dist/init/build_config.js +2 -0
  25. package/dist/init/detect_agent.js +108 -0
  26. package/dist/init/editors.js +62 -0
  27. package/dist/init/enrich_output.js +71 -0
  28. package/dist/init/extension.js +191 -0
  29. package/dist/init/inspect.js +287 -0
  30. package/dist/init/interactive.js +651 -0
  31. package/dist/init/neonctl.js +184 -0
  32. package/dist/init/orchestrate.js +190 -0
  33. package/dist/init/phases/auth.js +209 -0
  34. package/dist/init/phases/cleanup.js +27 -0
  35. package/dist/init/phases/db.js +283 -0
  36. package/dist/init/phases/getting_started.js +228 -0
  37. package/dist/init/phases/mcp.js +227 -0
  38. package/dist/init/phases/migrations.js +251 -0
  39. package/dist/init/phases/neon_auth.js +135 -0
  40. package/dist/init/phases/setup.js +729 -0
  41. package/dist/init/phases/skills.js +89 -0
  42. package/dist/init/phases/status.js +70 -0
  43. package/dist/init/resolve_context.js +107 -0
  44. package/dist/init/route_command.js +100 -0
  45. package/dist/init/skills.js +248 -0
  46. package/dist/init/types.js +1 -0
  47. package/dist/init/vsix.js +111 -0
  48. package/dist/neon_services.js +143 -0
  49. package/dist/psql/command/cmd_meta.js +2 -2
  50. package/dist/psql/core/mainloop.js +1 -1
  51. package/dist/psql/core/startup.js +1 -1
  52. package/dist/psql/core/syncVars.js +3 -3
  53. package/dist/psql/index.js +1 -1
  54. package/dist/utils/cli_name.js +14 -0
  55. package/dist/utils/esbuild.js +1 -1
  56. package/dist/utils/package_manager.js +51 -4
  57. package/dist/utils/service_picker.js +6 -6
  58. package/dist/utils/write_sync.js +39 -0
  59. package/package.json +18 -12
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Shared VSIX download utilities for extension installation.
3
+ * Supports corporate proxy via NEON_VSX_GALLERY_URL env var.
4
+ */
5
+ import { createWriteStream } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { pipeline } from "node:stream/promises";
9
+ import { INTERNAL_VSX_GALLERY } from "./build_config.js";
10
+ export const NEON_EXTENSION_ID = "databricks.neon-local-connect";
11
+ const OPEN_VSX_API = "https://open-vsx.org/api";
12
+ /**
13
+ * Downloads a .vsix file for the Neon extension.
14
+ *
15
+ * Strategy:
16
+ * 1. If NEON_VSX_GALLERY_URL is set, download from the corporate proxy gallery
17
+ * 2. Otherwise, download from the public Open VSX API
18
+ *
19
+ * Returns the path to the temp .vsix file, or null on failure.
20
+ */
21
+ export async function downloadVsix() {
22
+ // Runtime env var takes priority, then build-time baked value
23
+ const proxyGallery = process.env.NEON_VSX_GALLERY_URL || INTERNAL_VSX_GALLERY || "";
24
+ if (proxyGallery) {
25
+ return downloadFromGallery(proxyGallery);
26
+ }
27
+ return downloadFromOpenVsx();
28
+ }
29
+ /**
30
+ * Downloads from a VS Code marketplace-compatible gallery API (corporate proxy).
31
+ * Uses the VS Code extensionquery POST API to find the VSIX download URL.
32
+ */
33
+ async function downloadFromGallery(galleryUrl) {
34
+ const [publisher, name] = NEON_EXTENSION_ID.split(".");
35
+ const baseUrl = galleryUrl.replace(/\/+$/, "");
36
+ const queryUrl = `${baseUrl}/extensionquery`;
37
+ try {
38
+ // Query the marketplace API for the extension
39
+ const queryRes = await fetch(queryUrl, {
40
+ method: "POST",
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ Accept: "application/json;api-version=6.1-preview.1",
44
+ },
45
+ body: JSON.stringify({
46
+ filters: [
47
+ {
48
+ criteria: [
49
+ { filterType: 7, value: `${publisher}.${name}` },
50
+ ],
51
+ },
52
+ ],
53
+ flags: 914,
54
+ }),
55
+ signal: AbortSignal.timeout(15000),
56
+ });
57
+ if (!queryRes.ok)
58
+ return null;
59
+ const data = (await queryRes.json());
60
+ // Find the VSIX download URL from the response
61
+ const extension = data.results?.[0]?.extensions?.[0];
62
+ const latestVersion = extension?.versions?.[0];
63
+ const vsixFile = latestVersion?.files?.find((f) => f.assetType === "Microsoft.VisualStudio.Services.VSIXPackage");
64
+ if (!vsixFile?.source)
65
+ return null;
66
+ // Download the VSIX
67
+ const vsixRes = await fetch(vsixFile.source, {
68
+ signal: AbortSignal.timeout(30000),
69
+ redirect: "follow",
70
+ });
71
+ if (!vsixRes.ok || !vsixRes.body)
72
+ return null;
73
+ const tmpPath = join(tmpdir(), `${NEON_EXTENSION_ID}-proxy.vsix`);
74
+ const fileStream = createWriteStream(tmpPath);
75
+ await pipeline(vsixRes.body, fileStream);
76
+ return tmpPath;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Downloads from the public Open VSX API.
84
+ */
85
+ async function downloadFromOpenVsx() {
86
+ const [publisher, name] = NEON_EXTENSION_ID.split(".");
87
+ const metaUrl = `${OPEN_VSX_API}/${publisher}/${name}/latest`;
88
+ try {
89
+ const metaRes = await fetch(metaUrl, {
90
+ signal: AbortSignal.timeout(10000),
91
+ });
92
+ if (!metaRes.ok)
93
+ return null;
94
+ const meta = (await metaRes.json());
95
+ const downloadUrl = meta.files?.download;
96
+ if (!downloadUrl)
97
+ return null;
98
+ const vsixRes = await fetch(downloadUrl, {
99
+ signal: AbortSignal.timeout(30000),
100
+ });
101
+ if (!vsixRes.ok || !vsixRes.body)
102
+ return null;
103
+ const tmpPath = join(tmpdir(), `${NEON_EXTENSION_ID}-${meta.version ?? "latest"}.vsix`);
104
+ const fileStream = createWriteStream(tmpPath);
105
+ await pipeline(vsixRes.body, fileStream);
106
+ return tmpPath;
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Every Neon service a `--service` flag can name, spelled the way a user types it — one
3
+ * vocabulary for the whole CLI.
4
+ *
5
+ * Kebab-case rather than the `neon.ts` field names (`aiGateway`, `buckets`) so a flag reads
6
+ * like a flag, and the full product name rather than a shortening (`object-storage`, not
7
+ * `storage`) so nothing is ambiguous when read on its own.
8
+ *
9
+ * Commands take a **subset** of this via {@link ParseServicesOptions.allowed} — `config init`
10
+ * can only declare what a `neon.ts` has a field for, `env pull` can only pull what produces
11
+ * env vars — but the spelling of a service never varies between them. The order here is the
12
+ * canonical one: parsing sorts into it, so a command's output never depends on the order the
13
+ * flags were typed in.
14
+ *
15
+ * Not to be confused with `NeonFeature` in `init/bootstrap.ts`, which is what a *template*
16
+ * requires. That list comes from remote manifests (`neondatabase/examples/bootstrap.yaml`),
17
+ * spells Postgres `database`, and is not ours to rename.
18
+ */
19
+ export const NEON_SERVICES = [
20
+ "postgres",
21
+ "auth",
22
+ "data-api",
23
+ "functions",
24
+ "object-storage",
25
+ "ai-gateway",
26
+ ];
27
+ /**
28
+ * Spellings that used to be canonical, and the service they now mean. Accepted so a scripted
29
+ * `--services storage` keeps working, warned about so it does not quietly become a second
30
+ * vocabulary, and absent from help text, errors, and docs so nobody learns it fresh.
31
+ */
32
+ const DEPRECATED_SERVICE_ALIASES = {
33
+ // `config init --services storage` shipped before the vocabulary was unified.
34
+ storage: "object-storage",
35
+ };
36
+ /** An explicit empty selection, for commands where "declare nothing" is a real answer. */
37
+ export const NO_SERVICES = "none";
38
+ /**
39
+ * What to tell someone still using a retired spelling. A message rather than a log call, so
40
+ * the parser stays free of the CLI's writer and each command can surface it in its own voice.
41
+ */
42
+ export const deprecatedServiceMessage = (used, canonical) => `"${used}" is the old name for "${canonical}" and still works, but it will be removed. ` +
43
+ `Use "${canonical}".`;
44
+ /**
45
+ * Parse the raw values of a services flag into a canonical selection.
46
+ *
47
+ * Accepts the flag repeated (`-s auth -s postgres`) and comma-separated
48
+ * (`-s auth,postgres`), since both read naturally and users will try either. The result is
49
+ * deduplicated and sorted into {@link NEON_SERVICES} order, so what a command does never
50
+ * depends on typing order.
51
+ *
52
+ * An unrecognized name is rejected rather than dropped: a typo would otherwise act on
53
+ * everything *except* the service that was asked for, and report success. A name that is a
54
+ * real service but not one this command supports says so specifically — "functions has no env
55
+ * variables" is a different problem from a typo, and has a different fix.
56
+ */
57
+ export const parseServices = (raw, options) => {
58
+ const { allowed, flag, noneMeans, whyUnavailable = {}, onDeprecated, } = options;
59
+ const supported = `Supported values: ${allowed.join(", ")}${noneMeans !== undefined ? `, ${NO_SERVICES}` : ""}.`;
60
+ const names = raw
61
+ .flatMap((value) => value.split(","))
62
+ .map((name) => name.trim())
63
+ .filter((name) => name !== "");
64
+ if (names.length === 0) {
65
+ throw new Error(`${flag} needs at least one service. ${supported}`);
66
+ }
67
+ if (noneMeans !== undefined && names.includes(NO_SERVICES)) {
68
+ // Deduplicate before deciding it was combined with something: a repeated value is
69
+ // a no-op everywhere else in this parser, so `-s none -s none` must be too.
70
+ if (new Set(names).size > 1) {
71
+ throw new Error(`${flag} ${NO_SERVICES} cannot be combined with other services.`);
72
+ }
73
+ return [];
74
+ }
75
+ // Canonicalize first and unconditionally, so a retired spelling is reported against the
76
+ // service it means rather than as a word nobody recognizes.
77
+ const deprecated = new Map();
78
+ const resolved = names.map((name) => {
79
+ const canonical = DEPRECATED_SERVICE_ALIASES[name];
80
+ if (canonical === undefined)
81
+ return name;
82
+ deprecated.set(name, canonical);
83
+ return canonical;
84
+ });
85
+ const unsupported = resolved.filter((name) => !allowed.some((service) => service === name));
86
+ if (unsupported.length > 0) {
87
+ throw new Error(`${unsupportedMessage(unsupported, flag, whyUnavailable)} ${supported}`);
88
+ }
89
+ // Warned only once the selection is valid: a run that fails validation should not also
90
+ // carry a "still works" claim about a value that never took effect.
91
+ for (const [used, canonical] of deprecated)
92
+ onDeprecated?.(used, canonical);
93
+ return NEON_SERVICES.filter((service) => allowed.includes(service) && resolved.includes(service));
94
+ };
95
+ /**
96
+ * The sentences explaining why a selection was refused. A real Neon service this command
97
+ * cannot act on is a different mistake from a typo — different cause, different fix — so the
98
+ * two are never answered with the same word, and each service carries its reason where the
99
+ * command supplied one.
100
+ */
101
+ const unsupportedMessage = (unsupported, flag, whyUnavailable) => {
102
+ const known = unsupported.filter((name) => NEON_SERVICES.some((service) => service === name));
103
+ const unknown = unsupported.filter((name) => !known.some((service) => service === name));
104
+ return [
105
+ unknown.length > 0
106
+ ? `Unknown service${unknown.length === 1 ? "" : "s"} ${unknown.join(", ")}.`
107
+ : undefined,
108
+ ...known.map((service) => {
109
+ const why = whyUnavailable[service];
110
+ return `${service} is not something ${flag} can select${why ? `: ${why}` : ""}.`;
111
+ }),
112
+ ]
113
+ .filter((part) => part !== undefined)
114
+ .join(" ");
115
+ };
116
+ /** Every spelling of the services flag, so a habit picked up on one command works on another. */
117
+ const SERVICE_FLAG_NAMES = ["s", "service", "services"];
118
+ /**
119
+ * The yargs option for a services flag, so every command that has one accepts the same
120
+ * spellings (`-s`, `--service`, `--services`) and the same value syntax. `key` is the name the
121
+ * command reads off `argv`; the rest become aliases.
122
+ */
123
+ export const servicesOption = (params) => ({
124
+ alias: SERVICE_FLAG_NAMES.filter((name) => name !== params.key),
125
+ describe: [
126
+ `${params.describe}: ${params.allowed.join(", ")}.`,
127
+ params.noneMeans !== undefined
128
+ ? `Pass "${NO_SERVICES}" for ${params.noneMeans}.`
129
+ : undefined,
130
+ "Repeat the flag or comma-separate.",
131
+ params.also,
132
+ ]
133
+ .filter((part) => part !== undefined)
134
+ .join(" "),
135
+ type: "array",
136
+ string: true,
137
+ });
138
+ /**
139
+ * Narrow a yargs value for a services flag to the raw strings, or `undefined` when the flag
140
+ * was not given. `argv` is untyped at the handler, and `string: true` only guarantees the
141
+ * element type when the flag was actually parsed as an array.
142
+ */
143
+ export const servicesFlagValue = (value) => Array.isArray(value) ? value.map(String) : undefined;
@@ -754,12 +754,12 @@ PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
754
754
  * here on purpose (see the project's LICENSE file for terms).
755
755
  */
756
756
  const NEON_NOTICE = `
757
- This is an embedded psql reimplementation that ships with neonctl, the
757
+ This is an embedded psql reimplementation that ships with neon, the
758
758
  command-line interface for Neon (https://neon.tech). Neon is part of
759
759
  Databricks (https://www.databricks.com).
760
760
 
761
761
  It is an independent reimplementation of psql and is not affiliated with
762
- or endorsed by the PostgreSQL Global Development Group. See the neonctl
762
+ or endorsed by the PostgreSQL Global Development Group. See the neon
763
763
  LICENSE file for distribution terms.
764
764
  `;
765
765
  /**
@@ -246,7 +246,7 @@ const isHelpKeyword = (line) => {
246
246
  const stripped = trimmed.replace(/;+\s*$/u, "").trimEnd();
247
247
  return stripped === "help";
248
248
  };
249
- const HELP_TEXT = "You are using psql-ts, the embedded TypeScript psql in neonctl.\n" +
249
+ const HELP_TEXT = "You are using psql-ts, the embedded TypeScript psql in neon.\n" +
250
250
  "Type: \\copyright for distribution terms\n" +
251
251
  " \\h for help with SQL commands\n" +
252
252
  " \\? for help with psql commands\n" +
@@ -223,7 +223,7 @@ export const CLIENT_VERSION = "1.0.0";
223
223
  // `embedded-ts` label in place of a real PG build number since this is a
224
224
  // reimplementation, not a compiled psql. Distinct from the `:VERSION` psql
225
225
  // variable (the client-identity string seeded from CLIENT_VERSION).
226
- const VERSION_STRING = `psql (PostgreSQL) embedded-ts (neonctl ${CLIENT_VERSION})`;
226
+ const VERSION_STRING = `psql (PostgreSQL) embedded-ts (neon ${CLIENT_VERSION})`;
227
227
  const pushAction = (acts, a) => {
228
228
  acts.push(a);
229
229
  };
@@ -85,16 +85,16 @@ export const syncConnectionVars = (vars, conn) => {
85
85
  * identifier is real and traceable to the shipped binary, while keeping
86
86
  * upstream's variable *shapes*:
87
87
  *
88
- * - `VERSION` → `psql-ts (neonctl) <clientVersion>` — a banner that
88
+ * - `VERSION` → `psql-ts (neon) <clientVersion>` — a banner that
89
89
  * names the implementation so users can tell they are on the embedded
90
- * TS port, mirroring the startup banner's `psql-ts (neonctl, …)` shape.
90
+ * TS port, mirroring the startup banner's `psql-ts (neon, …)` shape.
91
91
  * - `VERSION_NAME` → `<clientVersion>` (e.g. `2.22.0`).
92
92
  * - `VERSION_NUM` → the same version mapped into PG's NNMMPP integer form
93
93
  * (`2.22.0` → `22200`) via {@link clientVersionNum}, so a script doing a
94
94
  * numeric `:VERSION_NUM` comparison gets a monotonic integer.
95
95
  */
96
96
  export const setStartupVars = (vars, clientVersion) => {
97
- vars.set("VERSION", `psql-ts (neonctl) ${clientVersion}`);
97
+ vars.set("VERSION", `psql-ts (neon) ${clientVersion}`);
98
98
  vars.set("VERSION_NAME", clientVersion);
99
99
  vars.set("VERSION_NUM", String(clientVersionNum(clientVersion)));
100
100
  };
@@ -337,7 +337,7 @@ const writeStartupBanner = (connection, out) => {
337
337
  // Client identifier. Matches upstream's `psql (18.4, server X.Y)` shape
338
338
  // but signals that this is the embedded TS implementation so users can tell
339
339
  // when they're on the fallback path.
340
- out.write(`psql-ts (neonctl, server ${serverVersion})\n`);
340
+ out.write(`psql-ts (neon, server ${serverVersion})\n`);
341
341
  const tls = connection.getTlsInfo();
342
342
  if (tls) {
343
343
  const parts = [
@@ -0,0 +1,14 @@
1
+ import { basename } from "node:path";
2
+ /**
3
+ * The name this CLI was invoked as: `neon` (current) or `neonctl` (legacy alias).
4
+ *
5
+ * Use this for any user-facing string that suggests a command to run — help text,
6
+ * error hints, and especially `--agent` `next_command_template` values, which an agent
7
+ * executes verbatim. Hardcoding `neonctl` breaks those on installs of the `neon` package,
8
+ * which no longer ships a `neonctl` binary (removed in `neon@2.38.0`).
9
+ *
10
+ * Derived from `process.argv[1]`, which is fixed for the process lifetime, so the result
11
+ * is stable whether this is called at module load or per invocation. Mirrors the name used
12
+ * for yargs `.scriptName()` in `index.ts`.
13
+ */
14
+ export const getCliName = () => basename(process.argv[1] ?? "") === "neonctl" ? "neonctl" : "neon";
@@ -3,7 +3,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { basename, join } from "node:path";
5
5
  import which from "which";
6
- const NOT_FOUND = "esbuild not found. neonctl ships esbuild for most platforms; if you see " +
6
+ const NOT_FOUND = "esbuild not found. neon ships esbuild for most platforms; if you see " +
7
7
  "this, install esbuild and ensure it is on your PATH (e.g. `npm i -g " +
8
8
  "esbuild`), or set NEON_ESBUILD_PATH to an esbuild binary.";
9
9
  // Prepended to the ESM bundle. Bundled dependencies are frequently CommonJS, but an ESM
@@ -1,4 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
2
4
  import which from "which";
3
5
  import { log } from "../log.js";
4
6
  // npm first so it's the default/preselected choice; the rest follow in rough
@@ -9,6 +11,42 @@ export const PACKAGE_MANAGERS = [
9
11
  "yarn",
10
12
  "bun",
11
13
  ];
14
+ /**
15
+ * Lockfiles, and the package manager each one belongs to. npm is last on
16
+ * purpose: a repo with both a pnpm lockfile and a leftover `package-lock.json`
17
+ * (which a failed run like the one this fixes can leave behind) is a pnpm repo.
18
+ */
19
+ const LOCKFILES = [
20
+ ["pnpm-lock.yaml", "pnpm"],
21
+ ["yarn.lock", "yarn"],
22
+ // bun 1.2+ writes the text `bun.lock`; older versions the binary `bun.lockb`.
23
+ ["bun.lock", "bun"],
24
+ ["bun.lockb", "bun"],
25
+ ["package-lock.json", "npm"],
26
+ ];
27
+ /**
28
+ * The package manager the project at `cwd` uses, from its lockfile. Searches
29
+ * `cwd` and then each parent up to the repo root: in a monorepo the lockfile
30
+ * sits at the root while we scaffold into a package. Stopping at the root keeps
31
+ * a stray lockfile above the repository from deciding how we install into it.
32
+ */
33
+ export const detectProjectPackageManager = (cwd) => {
34
+ let dir = cwd;
35
+ for (;;) {
36
+ for (const [file, pm] of LOCKFILES) {
37
+ if (existsSync(join(dir, file)))
38
+ return pm;
39
+ }
40
+ // After the lockfiles, not before: the repo root's own lockfile counts.
41
+ // `.git` is a file rather than a directory in a worktree or submodule.
42
+ if (existsSync(join(dir, ".git")))
43
+ return undefined;
44
+ const parent = dirname(dir);
45
+ if (parent === dir)
46
+ return undefined;
47
+ dir = parent;
48
+ }
49
+ };
12
50
  /**
13
51
  * The package manager the CLI was invoked through, read from the
14
52
  * `npm_config_user_agent` npm sets for `npm exec`/`npx`, `pnpm dlx`, `yarn
@@ -32,11 +70,20 @@ export const detectPackageManager = () => {
32
70
  /** The package managers actually on PATH, in {@link PACKAGE_MANAGERS} order. */
33
71
  export const installedPackageManagers = () => PACKAGE_MANAGERS.filter((pm) => which.sync(pm, { nothrow: true }) !== null);
34
72
  /**
35
- * Pick a package manager without prompting: the one the CLI was invoked through,
36
- * else the first one installed, else npm. Used by non-interactive flows (e.g.
37
- * `config init`) where there's no scaffold prompt to hang a picker off.
73
+ * Pick a package manager without prompting: the one the project at `cwd` uses,
74
+ * else the one the CLI was invoked through, else the first one installed, else
75
+ * npm. Used by non-interactive flows (e.g. `config init`) where there's no
76
+ * scaffold prompt to hang a picker off.
77
+ *
78
+ * The project wins over the invocation on purpose. `npx neon …` inside a pnpm
79
+ * repo should still install with pnpm — which tool launched us says nothing
80
+ * about which one owns that project's `node_modules`, and running npm against
81
+ * pnpm's symlinked tree is what this ordering exists to prevent.
38
82
  */
39
- export const resolvePackageManager = () => detectPackageManager() ?? installedPackageManagers()[0] ?? "npm";
83
+ export const resolvePackageManager = (cwd) => detectProjectPackageManager(cwd) ??
84
+ detectPackageManager() ??
85
+ installedPackageManagers()[0] ??
86
+ "npm";
40
87
  /**
41
88
  * The argv that adds `packages` as runtime dependencies with `pm`. npm spells it
42
89
  * `install`; pnpm/yarn/bun use `add`.
@@ -1,8 +1,8 @@
1
1
  import prompts from "prompts";
2
- import { NEON_SERVICES } from "../config_template.js";
2
+ import { CONFIG_INIT_SERVICES } from "../config_template.js";
3
3
  /**
4
- * The picker's rows, in {@link NEON_SERVICES} order. Titles use the product names from the
5
- * CLI's README ("Managed Better Auth", "Object Storage") rather than the `neon.ts` field
4
+ * The picker's rows, in {@link CONFIG_INIT_SERVICES} order. Titles use the product names from
5
+ * the CLI's README ("Managed Better Auth", "Object Storage") rather than the `neon.ts` field
6
6
  * names, since this is the list a user reads before they've seen a policy.
7
7
  */
8
8
  const CHOICES = [
@@ -17,7 +17,7 @@ const CHOICES = [
17
17
  description: "Long-running, without timeouts, and closer to your database.",
18
18
  },
19
19
  {
20
- value: "storage",
20
+ value: "object-storage",
21
21
  title: "Object Storage",
22
22
  description: "S3-compatible blob storage that branches with your projects.",
23
23
  },
@@ -58,7 +58,7 @@ export const pickServicesInteractively = async () => {
58
58
  if (!Array.isArray(services)) {
59
59
  throw new Error("Aborted: no services selected.");
60
60
  }
61
- // Order by NEON_SERVICES rather than selection order so the rendered neon.ts is
61
+ // Order canonically rather than by selection order so the rendered neon.ts is
62
62
  // independent of the order the rows were toggled in.
63
- return NEON_SERVICES.filter((service) => services.includes(service));
63
+ return CONFIG_INIT_SERVICES.filter((service) => services.includes(service));
64
64
  };
@@ -0,0 +1,39 @@
1
+ import { writeSync } from "node:fs";
2
+ /**
3
+ * Write `text` to a file descriptor and do not return until all of it is out.
4
+ *
5
+ * `process.stdout.write` is asynchronous when stdout is a pipe — which it is whenever the
6
+ * caller is an agent or a script capturing output — so a `process.exit()` on the next line
7
+ * discards whatever has not been flushed. The caller sees an exit code and empty stdout.
8
+ *
9
+ * Use this for any final message that is followed by `process.exit`.
10
+ */
11
+ export function writeAllSync(fd, text) {
12
+ let buffer = Buffer.from(text, "utf8");
13
+ while (buffer.length > 0) {
14
+ try {
15
+ buffer = buffer.subarray(writeSync(fd, buffer));
16
+ }
17
+ catch (err) {
18
+ const code = err.code;
19
+ if (code === "EAGAIN") {
20
+ // Wait for the reader rather than spinning on it. A pipe nobody is draining
21
+ // would otherwise burn a core until it is; failure output is small enough
22
+ // that this should never be reached, which is the reason to bound it rather
23
+ // than assume so.
24
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
25
+ continue;
26
+ }
27
+ // The reader closed the pipe (`neon … | head`). There is nobody left to tell,
28
+ // and this is the last thing the process does, so stop rather than throw.
29
+ if (code === "EPIPE")
30
+ return;
31
+ // Anything else means the write did not happen and the caller is about to exit
32
+ // as though it had. Swallowing it turns a lost payload into an apparent success.
33
+ throw err;
34
+ }
35
+ }
36
+ }
37
+ /** File descriptors, named so call sites read as the stream they mean. */
38
+ export const STDOUT_FD = 1;
39
+ export const STDERR_FD = 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "2.45.0",
3
+ "version": "2.47.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -39,6 +39,8 @@
39
39
  "node": ">=20.19.0"
40
40
  },
41
41
  "dependencies": {
42
+ "@clack/core": "0.4.2",
43
+ "@clack/prompts": "0.10.1",
42
44
  "@hono/node-server": "2.0.4",
43
45
  "@segment/analytics-node": "1.3.0",
44
46
  "chalk": "5.3.0",
@@ -46,20 +48,22 @@
46
48
  "cli-table": "0.3.11",
47
49
  "cliui": "8.0.1",
48
50
  "diff": "5.2.0",
51
+ "execa": "^9.5.2",
49
52
  "fflate": "^0.8.3",
50
53
  "open": "^10.2.0",
51
54
  "openid-client": "6.8.1",
52
55
  "pg-protocol": "^1.14.0",
56
+ "picocolors": "^1.1.1",
53
57
  "prompts": "2.4.2",
54
58
  "undici": "^7.28.0",
55
59
  "which": "3.0.1",
56
60
  "yaml": "^2.9.0",
57
61
  "yargs": "17.7.2",
62
+ "yoctocolors": "^2.1.2",
58
63
  "@neon/sdk": "1.5.0",
59
- "@neon/config": "0.14.0",
60
- "@neon/config-runtime": "0.12.4",
61
- "neon-init": "0.20.7",
62
- "@neon/env": "0.14.0"
64
+ "@neon/config": "0.14.1",
65
+ "@neon/env": "0.15.0",
66
+ "@neon/config-runtime": "0.12.5"
63
67
  },
64
68
  "optionalDependencies": {
65
69
  "esbuild": "0.28.1"
@@ -111,15 +115,17 @@
111
115
  },
112
116
  "scripts": {
113
117
  "generateParams": "tsx generateOptionsFromSpec.ts",
118
+ "codegen": "node ../../scripts/sync-shared.mjs . && node scripts/set-vsx-gallery.mjs",
114
119
  "clean": "rm -rf dist",
115
- "build": "node ../../scripts/sync-shared.mjs . && pnpm generateParams && pnpm clean && tsc -p tsconfig.build.json && cp src/*.html ./dist",
120
+ "build": "pnpm codegen && pnpm generateParams && pnpm clean && tsc -p tsconfig.build.json && cp src/*.html ./dist",
121
+ "build:internal": "node ../../scripts/sync-shared.mjs . && node scripts/set-vsx-gallery.mjs https://cursor-vsx-proxy.cloud.databricks.com/gallery && pnpm generateParams && pnpm clean && tsc -p tsconfig.build.json && cp src/*.html ./dist",
116
122
  "bundle": "node pkg.js",
117
- "typecheck": "node ../../scripts/sync-shared.mjs . && tsc --noEmit",
118
- "lint": "node ../../scripts/sync-shared.mjs . && pnpm typecheck && biome check src",
123
+ "typecheck": "pnpm codegen && tsc --noEmit",
124
+ "lint": "pnpm codegen && pnpm typecheck && biome check src",
119
125
  "lint:fix": "pnpm typecheck && biome check src --write",
120
- "test": "node ../../scripts/sync-shared.mjs . && pnpm --filter neonctl... build && vitest run",
121
- "test:ci": "node ../../scripts/sync-shared.mjs . && pnpm build && vitest run",
122
- "test:e2e": "node ../../scripts/sync-shared.mjs . && pnpm build && vitest run --config vitest.e2e.config.ts",
123
- "test:conformance": "node ../../scripts/sync-shared.mjs . && vitest run --config tests/psql-conformance/vitest.config.ts"
126
+ "test": "pnpm codegen && pnpm --filter neonctl... build && vitest run",
127
+ "test:ci": "pnpm codegen && pnpm build && vitest run",
128
+ "test:e2e": "pnpm codegen && pnpm build && vitest run --config vitest.e2e.config.ts",
129
+ "test:conformance": "pnpm codegen && vitest run --config tests/psql-conformance/vitest.config.ts"
124
130
  }
125
131
  }