neon 8.11.1 → 8.11.2

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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/_chunks/api-GbEaBZJ5.js +388 -0
  3. package/dist/_chunks/api_keys-CNnnugbQ.js +281 -0
  4. package/dist/_chunks/ask-DVwy0eUL.js +155 -0
  5. package/dist/_chunks/auth-CIUtXByB.js +495 -0
  6. package/dist/_chunks/bootstrap-wPZWa1sT.js +499 -0
  7. package/dist/_chunks/branches-BNa1xX1L.js +460 -0
  8. package/dist/_chunks/bucket-BCdryTkD.js +409 -0
  9. package/dist/_chunks/checkout-CXIOydqZ.js +348 -0
  10. package/dist/_chunks/claim-DqAvAvDm.js +473 -0
  11. package/dist/_chunks/config-BH_qRHiG.js +1113 -0
  12. package/dist/_chunks/connection_string-DB4awPzZ.js +154 -0
  13. package/dist/_chunks/credentials-BSjE9fRR.js +146 -0
  14. package/dist/_chunks/data_api-Djxj5ZQD.js +263 -0
  15. package/dist/_chunks/databases-Dok4wru7.js +84 -0
  16. package/dist/_chunks/deploy-D7B0Xfjg.js +33 -0
  17. package/dist/_chunks/dev-CSvxAecE.js +945 -0
  18. package/dist/_chunks/diff-DhxxbdQE.js +178 -0
  19. package/dist/_chunks/docs-CrVybkTP.js +197 -0
  20. package/dist/_chunks/env-5HS5qQGd.js +284 -0
  21. package/dist/_chunks/feedback-CMayyTa8.js +88 -0
  22. package/dist/_chunks/functions-Q0zuvtnk.js +464 -0
  23. package/dist/_chunks/git-H32TNt6M.js +356 -0
  24. package/dist/_chunks/init-BkA6RilG.js +111 -0
  25. package/dist/_chunks/inspect-g0wekylj.js +90 -0
  26. package/dist/_chunks/ip_allow-5lLFvIuE.js +106 -0
  27. package/dist/_chunks/link-BgtkOBws.js +936 -0
  28. package/dist/_chunks/logs-DWkpjzIH.js +291 -0
  29. package/dist/_chunks/mcp-Bo-7TKc7.js +253 -0
  30. package/dist/_chunks/neon_auth-CBs1_3qu.js +822 -0
  31. package/dist/_chunks/open-CX3BT3Tp.js +33 -0
  32. package/dist/_chunks/operations-D4I-AxVs.js +37 -0
  33. package/dist/_chunks/orgs-xGfANwHQ.js +52 -0
  34. package/dist/_chunks/plugins-DqD7BDO1.js +169 -0
  35. package/dist/_chunks/profile-yXOiKk9R.js +672 -0
  36. package/dist/_chunks/projects-BwI2Uo0z.js +439 -0
  37. package/dist/_chunks/psql-BM-doe8e.js +64 -0
  38. package/dist/_chunks/realtime-BRqIyGR-.js +79 -0
  39. package/dist/_chunks/roles-B4w3YT_7.js +79 -0
  40. package/dist/_chunks/set_context-DbrwoImy.js +38 -0
  41. package/dist/_chunks/skills-DyiBXKS3.js +255 -0
  42. package/dist/_chunks/snapshots-RJBzW8GM.js +338 -0
  43. package/dist/_chunks/status-DV8Z2kw3.js +44 -0
  44. package/dist/_chunks/triggers-BrRo1z4b.js +302 -0
  45. package/dist/_chunks/user-dlOGXbX9.js +60 -0
  46. package/dist/_chunks/vpc_endpoints-CNb0YfMo.js +105 -0
  47. package/dist/auth_recovery.js +1 -1
  48. package/dist/commands/api.js +1 -388
  49. package/dist/commands/api_keys.js +1 -281
  50. package/dist/commands/ask.js +1 -155
  51. package/dist/commands/auth.js +1 -495
  52. package/dist/commands/bootstrap.js +1 -499
  53. package/dist/commands/branches.js +1 -460
  54. package/dist/commands/bucket.js +1 -409
  55. package/dist/commands/checkout.js +1 -348
  56. package/dist/commands/claim.js +1 -473
  57. package/dist/commands/config.js +1 -1113
  58. package/dist/commands/connection_string.js +1 -154
  59. package/dist/commands/credentials.js +1 -146
  60. package/dist/commands/data_api.js +1 -263
  61. package/dist/commands/databases.js +1 -84
  62. package/dist/commands/deploy.js +1 -33
  63. package/dist/commands/dev.js +1 -945
  64. package/dist/commands/diff.js +1 -178
  65. package/dist/commands/docs.js +1 -197
  66. package/dist/commands/env.js +1 -284
  67. package/dist/commands/feedback.js +1 -88
  68. package/dist/commands/functions.js +1 -464
  69. package/dist/commands/functions_new.js +1 -1
  70. package/dist/commands/git.js +1 -356
  71. package/dist/commands/index.js +45 -45
  72. package/dist/commands/init.js +1 -111
  73. package/dist/commands/inspect.js +1 -90
  74. package/dist/commands/ip_allow.js +1 -106
  75. package/dist/commands/link.js +1 -936
  76. package/dist/commands/logs.js +1 -291
  77. package/dist/commands/mcp.js +1 -253
  78. package/dist/commands/neon_auth.js +1 -822
  79. package/dist/commands/open.js +1 -33
  80. package/dist/commands/operations.js +1 -37
  81. package/dist/commands/orgs.js +1 -52
  82. package/dist/commands/plugins.js +1 -169
  83. package/dist/commands/profile.js +1 -672
  84. package/dist/commands/projects.js +1 -439
  85. package/dist/commands/psql.js +1 -64
  86. package/dist/commands/realtime.js +1 -79
  87. package/dist/commands/roles.js +1 -79
  88. package/dist/commands/set_context.js +1 -38
  89. package/dist/commands/skills.js +1 -255
  90. package/dist/commands/snapshots.js +1 -338
  91. package/dist/commands/status.js +1 -44
  92. package/dist/commands/subscribe.js +1 -1
  93. package/dist/commands/triggers.js +1 -302
  94. package/dist/commands/user.js +1 -60
  95. package/dist/commands/vpc_endpoints.js +1 -105
  96. package/dist/functions/registry.js +29 -151
  97. package/dist/functions/source.js +42 -2
  98. package/dist/index.js +1 -1
  99. package/dist/init/auth.js +3 -3
  100. package/dist/init/link.js +2 -2
  101. package/dist/init/run.js +3 -3
  102. package/dist/init/tooling.js +3 -3
  103. package/dist/mcp/mint.js +1 -1
  104. package/dist/test_utils/registry.js +1 -2
  105. package/package.json +8 -6
@@ -0,0 +1,356 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { isCi } from "../env.js";
3
+ import { log } from "../log.js";
4
+ import { CONTEXT_FILE, contextBranch, gitBranchMap, gitBranchMapping, isUnfollowedGitHookSync, readContextFile, setGitBranchMap, setGitBranchMapping, setGitFollow } from "../context.js";
5
+ import { writer } from "../writer.js";
6
+ import { neonSafeBranchName } from "../utils/branch_name.js";
7
+ import { currentGitBranch, gitPull, gitRepoRoot, hasUpstream, installPostCheckoutHook, isGitRepo, isManagedHook, localGitBranches, postCheckoutHookPath, readGitContext, removePostCheckoutHook } from "../utils/git.js";
8
+ import { o as quoteFlagValue } from "./link-BgtkOBws.js";
9
+ import { o as handler$1 } from "./checkout-CXIOydqZ.js";
10
+ import { existsSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import chalk from "chalk";
13
+ import prompts from "prompts";
14
+ //#region src/commands/git.ts
15
+ var git_exports = /* @__PURE__ */ __exportAll({
16
+ aliases: () => aliases,
17
+ builder: () => builder,
18
+ cleanup: () => cleanup,
19
+ command: () => "git",
20
+ describe: () => describe,
21
+ handler: () => handler,
22
+ install: () => install,
23
+ partitionBranchesToPrune: () => partitionBranchesToPrune,
24
+ status: () => status,
25
+ sync: () => sync,
26
+ uninstall: () => uninstall
27
+ });
28
+ const command = "git";
29
+ const describe = "Sync the checked-out Neon branch to your git branch (Preview)";
30
+ const aliases = [];
31
+ const builder = (argv) => argv.usage("$0 git <sub-command> [options]").command("install", "Install a git post-checkout hook that syncs the Neon branch on `git checkout`", (yargs) => yargs.epilogue("Requires a project already linked at the repository root — run `neon link` or `neon checkout <branch>` there first."), (args) => {
32
+ install(args);
33
+ }).command("uninstall", "Remove the git post-checkout hook installed by `git install`", (yargs) => yargs, (args) => {
34
+ uninstall(args);
35
+ }).command("sync", "Check out the Neon branch mapped to the current git branch (run by the hook)", (yargs) => yargs.options({
36
+ "env-pull": {
37
+ describe: "Pull the branch's Neon env vars into a local .env after sync. On by default.",
38
+ type: "boolean",
39
+ default: true
40
+ },
41
+ pull: {
42
+ describe: "Run `git pull --ff-only` before syncing so local files (incl. migration files) match the branch before any checkout.after migration runs. Without the flag, prompts when run manually in a TTY and skips in the hook / CI.",
43
+ type: "boolean"
44
+ },
45
+ quiet: {
46
+ describe: "Suppress sync's own routine status lines (git pull result, detached HEAD notice). Warnings and the delegated checkout's own output are unaffected. Used by the installed git hook.",
47
+ type: "boolean",
48
+ default: false
49
+ }
50
+ }), (args) => sync(args)).command("status", "Show the git context, hook state, and current git → Neon mapping", (yargs) => yargs, (args) => {
51
+ status(args);
52
+ }).command("cleanup", "Prune git → Neon mappings whose local git branch is gone; optionally delete the orphaned Neon branches", (yargs) => yargs.options({
53
+ "prune-neon-branches": {
54
+ describe: "Also delete the orphaned Neon branches (never the default or a protected branch).",
55
+ type: "boolean",
56
+ default: false
57
+ },
58
+ yes: {
59
+ describe: "Skip the confirmation prompt before deleting Neon branches.",
60
+ type: "boolean",
61
+ default: false
62
+ }
63
+ }), (args) => cleanup(args));
64
+ const handler = (args) => args;
65
+ const requireRepoRoot = () => {
66
+ const cwd = process.cwd();
67
+ if (!isGitRepo(cwd)) throw new Error("Not inside a git repository. Run `neon git` from a git work tree.");
68
+ const repoRoot = gitRepoRoot(cwd);
69
+ if (!repoRoot) throw new Error("Could not resolve the git repository root.");
70
+ return repoRoot;
71
+ };
72
+ /**
73
+ * `.neon` at exactly the repo root — never walk-up-resolved. The single source of truth for
74
+ * all git-hooks state: `git.follow` and the git → Neon map. A `post-checkout` hook always
75
+ * runs with `cwd` at the repo root, so every function here (`install`, `uninstall`, `sync`,
76
+ * `status`, `cleanup`) reads and writes this exact path, never `props.contextFile`'s
77
+ * walk-up resolution — an ancestor directory's unrelated `.neon` (e.g. a parent monorepo
78
+ * folder) must never be mistaken for this repo's own state.
79
+ *
80
+ * Deliberately never *creates* this file: `install` requires it to already exist (from a
81
+ * prior `neon link` / `neon checkout` run at the repo root), and `sync` skips persisting a
82
+ * mapping when it's absent. Auto-creating a bare `{ git: {...} }` file with no `projectId`
83
+ * would plant a **new**, closer `.neon` that shadows a real project link recorded in an
84
+ * ancestor directory (or a linked subdirectory) for every other command's normal
85
+ * context resolution — silently breaking `checkout` / `deploy` / `status` in that
86
+ * directory. Requiring the link first also keeps this file and the project context file
87
+ * the same file whenever hooks state exists, which is what makes `cleanup`'s project-match
88
+ * check (see below) actually correct.
89
+ */
90
+ const repoRootContextFile = (repoRoot) => join(repoRoot, CONTEXT_FILE);
91
+ const install = (_props) => {
92
+ const repoRoot = requireRepoRoot();
93
+ const gitStateFile = repoRootContextFile(repoRoot);
94
+ if (!readContextFile(gitStateFile).projectId) {
95
+ const quoted = quoteFlagValue(gitStateFile);
96
+ throw new Error(`No project linked at the repository root (${gitStateFile}).\nRun \`neon link --context-file ${quoted}\` (an explicit path, since a plain \`neon link\` walks up and may update a different, ancestor \`.neon\` instead) or \`neon checkout <branch> --context-file ${quoted}\` first, then re-run \`neon git install\`.`);
97
+ }
98
+ const result = installPostCheckoutHook(repoRoot);
99
+ if (result.status === "conflict") throw new Error(`A non-neon \`post-checkout\` hook already exists at ${result.hookPath}.\nRemove or rename it first, then re-run \`neon git install\`.`);
100
+ setGitFollow(gitStateFile, true);
101
+ log.info("Git → Neon sync %s. `git checkout <branch>` will now check out the mapped Neon branch.\nHook: %s", result.status === "installed" ? "installed" : "updated", postCheckoutHookPath(repoRoot));
102
+ };
103
+ const uninstall = (_props) => {
104
+ const repoRoot = requireRepoRoot();
105
+ const result = removePostCheckoutHook(repoRoot);
106
+ const gitStateFile = repoRootContextFile(repoRoot);
107
+ if (existsSync(gitStateFile)) setGitFollow(gitStateFile, false);
108
+ switch (result) {
109
+ case "removed":
110
+ log.info("Removed the neon git post-checkout hook. Git → Neon sync is off.");
111
+ break;
112
+ case "foreign":
113
+ log.info("Left the existing `post-checkout` hook in place (not managed by neon). Git → Neon sync flag cleared.");
114
+ break;
115
+ case "absent": log.info("No neon git hook was installed. Git → Neon sync flag cleared.");
116
+ }
117
+ };
118
+ /**
119
+ * Check out the Neon branch that corresponds to the current git branch. Invoked by the
120
+ * installed `post-checkout` hook (with `NEON_GIT_HOOK=1`) and runnable by hand. Resolves the
121
+ * Neon branch name via the persisted map (sticky), delegates to `neon checkout` (whose
122
+ * `checkout.before` hook may further map the name and whose `checkout.after` hook runs
123
+ * migrations, etc.), then records the resulting git → Neon mapping so it stays stable.
124
+ */
125
+ /** `sync`'s own routine status lines (pull result, detached HEAD) — skipped under
126
+ * `--quiet`. Warnings, errors, and the delegated `checkout`'s own output are unaffected. */
127
+ const syncLog = (props, ...args) => {
128
+ if (props.quiet) return;
129
+ log.info(...args);
130
+ };
131
+ const sync = async (props) => {
132
+ if (isUnfollowedGitHookSync({ _: ["git", "sync"] })) {
133
+ log.debug("Skipping git sync: this repo has not run `neon git install` (git.follow is not set).");
134
+ return;
135
+ }
136
+ const cwd = process.cwd();
137
+ if (!isGitRepo(cwd)) throw new Error("Not inside a git repository.");
138
+ const repoRoot = gitRepoRoot(cwd) ?? cwd;
139
+ const gitStateFile = repoRootContextFile(repoRoot);
140
+ const context = readContextFile(gitStateFile);
141
+ const gitBranch = currentGitBranch(cwd);
142
+ if (!gitBranch) {
143
+ syncLog(props, "Detached HEAD — no git branch to sync. Skipping.");
144
+ return;
145
+ }
146
+ if (await resolveShouldPull(props, cwd, gitBranch)) {
147
+ const outcome = gitPull(cwd);
148
+ switch (outcome.status) {
149
+ case "pulled":
150
+ syncLog(props, "%s git pull --ff-only (%s)", chalk.dim("→"), gitBranch);
151
+ break;
152
+ case "no-upstream":
153
+ syncLog(props, "No upstream for %s — skipping git pull.", gitBranch);
154
+ break;
155
+ case "failed": log.warning("git pull --ff-only failed (continuing with sync): %s", outcome.detail);
156
+ }
157
+ }
158
+ const inputName = gitBranchMapping(context, gitBranch) ?? neonSafeBranchName(gitBranch);
159
+ await handler$1({
160
+ ...props,
161
+ id: inputName,
162
+ envPull: props.envPull ?? true
163
+ });
164
+ const rootProjectId = readContextFile(gitStateFile).projectId;
165
+ const pinnedContext = readContextFile(props.contextFile);
166
+ if (!rootProjectId || pinnedContext.projectId !== rootProjectId) log.warning("Not persisting the git → Neon mapping: this checkout targeted project %s, not a project linked at the repo root (%s).", pinnedContext.projectId ?? "(unknown)", rootProjectId ?? "none");
167
+ else {
168
+ const resolved = contextBranch(pinnedContext);
169
+ if (resolved) setGitBranchMapping(gitStateFile, gitBranch, resolved);
170
+ }
171
+ };
172
+ /**
173
+ * Decide whether `git sync` should `git pull` first. Mirrors the rest of the CLI's
174
+ * auto-vs-interactive philosophy (cf. `checkout` / `link`): an explicit flag always wins;
175
+ * otherwise we only *prompt* in a real manual TTY, and never auto-pull in the post-checkout
176
+ * hook or CI (no network surprises in automation). Skips when there's nothing upstream.
177
+ */
178
+ const resolveShouldPull = async (props, cwd, gitBranch) => {
179
+ if (props.pull === true) return true;
180
+ if (props.pull === false) return false;
181
+ if (!hasUpstream(cwd)) return false;
182
+ if (process.env["NEON_GIT_HOOK"] === "1" || isCi() || !process.stdout.isTTY) return false;
183
+ const { pull } = await prompts({
184
+ type: "confirm",
185
+ name: "pull",
186
+ message: `Pull latest for "${gitBranch}" (git pull --ff-only) before syncing?`,
187
+ initial: false
188
+ });
189
+ return Boolean(pull);
190
+ };
191
+ const GIT_STATUS_FIELDS = [
192
+ "gitBranch",
193
+ "hookInstalled",
194
+ "followOnCheckout",
195
+ "mappedNeonBranch",
196
+ "mappings"
197
+ ];
198
+ const status = (props) => {
199
+ const cwd = process.cwd();
200
+ const git = readGitContext(cwd);
201
+ if (!git.available) throw new Error("Not inside a git repository.");
202
+ const repoRoot = git.repoRoot ?? cwd;
203
+ const hookPath = postCheckoutHookPath(repoRoot);
204
+ const context = readContextFile(repoRootContextFile(repoRoot));
205
+ const currentBranch = git.branch;
206
+ writer(props).end({
207
+ gitBranch: currentBranch ?? null,
208
+ hookInstalled: isManagedHook(hookPath),
209
+ followOnCheckout: context.git?.follow === true,
210
+ mappedNeonBranch: currentBranch ? gitBranchMapping(context, currentBranch) ?? null : null,
211
+ mappings: context.git?.map ?? {}
212
+ }, {
213
+ fields: GIT_STATUS_FIELDS,
214
+ renderColumns: {
215
+ gitBranch: (v) => v.gitBranch ?? "(detached)",
216
+ mappedNeonBranch: (v) => v.gitBranch === null ? "(detached — sync skips detached HEAD)" : v.mappedNeonBranch ?? "(unmapped — will derive on next sync)",
217
+ mappings: (v) => Object.entries(v.mappings).map(([gitBranch, neonBranch]) => `${gitBranch} → ${neonBranch}`).join("\n") || "(none)"
218
+ }
219
+ });
220
+ };
221
+ /**
222
+ * Split the orphaned Neon branches (those mapped from now-deleted git branches) into the ones
223
+ * safe to delete and the ones to skip. Default and protected branches are **never** deleted.
224
+ * Pure (no I/O) so it's unit-testable.
225
+ */
226
+ const partitionBranchesToPrune = (branches, orphanNeonNames) => {
227
+ const toDelete = [];
228
+ const skipped = [];
229
+ for (const branch of branches) {
230
+ if (!orphanNeonNames.has(branch.name)) continue;
231
+ if (branch.default) skipped.push({
232
+ name: branch.name,
233
+ reason: "default branch"
234
+ });
235
+ else if (branch.protected) skipped.push({
236
+ name: branch.name,
237
+ reason: "protected"
238
+ });
239
+ else toDelete.push(branch);
240
+ }
241
+ return {
242
+ toDelete,
243
+ skipped
244
+ };
245
+ };
246
+ /**
247
+ * Prune the git → Neon workflow state. By default this only cleans `.neon`: mapping entries
248
+ * whose git branch no longer exists locally are dropped. With `--prune-neon-branches` it also
249
+ * deletes the orphaned Neon branches — never the default branch and never a protected one.
250
+ */
251
+ const cleanup = async (props) => {
252
+ const cwd = process.cwd();
253
+ if (!isGitRepo(cwd)) throw new Error("Not inside a git repository.");
254
+ const repoRoot = gitRepoRoot(cwd) ?? cwd;
255
+ const gitStateFile = repoRootContextFile(repoRoot);
256
+ const context = readContextFile(gitStateFile);
257
+ const map = gitBranchMap(context);
258
+ const entries = Object.entries(map);
259
+ if (entries.length === 0) {
260
+ log.info("No git → Neon mappings to clean up.");
261
+ return;
262
+ }
263
+ const local = new Set(localGitBranches(cwd));
264
+ const stale = entries.filter(([gitBranch]) => !local.has(gitBranch));
265
+ if (stale.length === 0) {
266
+ log.info("All %d mapping(s) still have a local git branch — nothing to prune.", entries.length);
267
+ return;
268
+ }
269
+ const kept = Object.fromEntries(entries.filter(([gitBranch]) => local.has(gitBranch)));
270
+ const keptNeonNames = new Set(Object.values(kept));
271
+ const staleButShared = stale.filter(([, neonBranch]) => keptNeonNames.has(neonBranch));
272
+ const orphaned = stale.filter(([, neonBranch]) => !keptNeonNames.has(neonBranch));
273
+ if (!props.pruneNeonBranches) {
274
+ setGitBranchMap(gitStateFile, kept);
275
+ log.info("Pruned %d stale mapping(s) from .neon:", stale.length);
276
+ for (const [gitBranch, neonBranch] of stale) log.info(" %s → %s", gitBranch, neonBranch);
277
+ if (orphaned.length > 0) log.info("These Neon branch(es) are no longer mapped and were not deleted: %s. Delete them with `neon branches delete <name>`, or run `neon git cleanup --prune-neon-branches` next time before a mapping-only cleanup.", orphaned.map(([, neonBranch]) => neonBranch).join(", "));
278
+ return;
279
+ }
280
+ if (staleButShared.length > 0) {
281
+ setGitBranchMap(gitStateFile, {
282
+ ...kept,
283
+ ...Object.fromEntries(orphaned)
284
+ });
285
+ log.info("Pruned %d stale mapping(s) whose Neon branch is still in use elsewhere:", staleButShared.length);
286
+ for (const [gitBranch, neonBranch] of staleButShared) log.info(" %s → %s", gitBranch, neonBranch);
287
+ }
288
+ if (!props.projectId) throw new Error("Cannot delete Neon branches: no project in context. Run `neon link` first.");
289
+ if (!context.projectId) throw new Error(`Cannot delete Neon branches: the git → Neon map has no recorded project (the repo-root .neon at ${gitStateFile} has no projectId). Run \`neon link --context-file ${quoteFlagValue(gitStateFile)}\` to establish it, or \`neon git cleanup\` with no --prune-neon-branches to only prune the local mapping.`);
290
+ if (context.projectId !== props.projectId) throw new Error(`Cannot delete Neon branches: this git → Neon map was recorded against project ${context.projectId}, but the active project is ${props.projectId}. Run \`neon link\` (or drop --project-id) to target ${context.projectId}, or \`neon git cleanup\` with no --prune-neon-branches to only prune the local mapping.`);
291
+ if (orphaned.length === 0) {
292
+ log.info("No orphaned Neon branches to delete.");
293
+ return;
294
+ }
295
+ const orphanNeonNames = new Set(orphaned.map(([, neonBranch]) => neonBranch));
296
+ const branches = (await props.apiClient.listProjectBranches({ projectId: props.projectId })).data.branches;
297
+ const { toDelete, skipped } = partitionBranchesToPrune(branches, orphanNeonNames);
298
+ for (const { name, reason } of skipped) log.info("Keeping Neon branch %s (%s) — never auto-deleted.", name, reason);
299
+ if (toDelete.length === 0) {
300
+ log.info("No orphaned Neon branches to delete.");
301
+ return;
302
+ }
303
+ if (!await confirmPrune(props, toDelete)) {
304
+ log.info("Aborted — no Neon branches were deleted, and no mappings were pruned.");
305
+ return;
306
+ }
307
+ const currentMap = {
308
+ ...kept,
309
+ ...Object.fromEntries(orphaned)
310
+ };
311
+ const failed = [];
312
+ let prunedCount = 0;
313
+ for (const branch of toDelete) {
314
+ const branchMappings = orphaned.filter(([, neonBranch]) => neonBranch === branch.name);
315
+ try {
316
+ await props.apiClient.deleteProjectBranch(props.projectId, branch.id);
317
+ log.info("Deleted Neon branch %s (%s).", branch.name, branch.id);
318
+ prunedCount += branchMappings.length;
319
+ for (const [gitBranch, neonBranch] of branchMappings) {
320
+ log.info(" Pruned %s → %s", gitBranch, neonBranch);
321
+ delete currentMap[gitBranch];
322
+ }
323
+ } catch (err) {
324
+ const message = err instanceof Error ? err.message : String(err);
325
+ log.error("Failed to delete Neon branch %s: %s", branch.name, message);
326
+ failed.push({
327
+ name: branch.name,
328
+ error: message
329
+ });
330
+ }
331
+ setGitBranchMap(gitStateFile, currentMap);
332
+ }
333
+ if (prunedCount > 0) log.info("Pruned %d stale mapping(s) from .neon.", prunedCount);
334
+ if (failed.length > 0) throw new Error(`Failed to delete ${failed.length} Neon branch(es): ${failed.map((f) => `${f.name} (${f.error})`).join(", ")}. Re-run \`neon git cleanup --prune-neon-branches\` to retry.`);
335
+ };
336
+ /**
337
+ * Confirm deleting the orphaned Neon branches. Deletion is destructive, so an explicit
338
+ * `--yes` wins; otherwise prompt in an interactive terminal, and refuse (with a warning) in
339
+ * CI / non-interactive contexts rather than deleting without consent.
340
+ */
341
+ const confirmPrune = async (props, toDelete) => {
342
+ if (props.yes) return true;
343
+ if (isCi() || !process.stdout.isTTY) {
344
+ log.warning("Refusing to delete %d Neon branch(es) non-interactively. Re-run with --yes to confirm.", toDelete.length);
345
+ return false;
346
+ }
347
+ const { ok } = await prompts({
348
+ type: "confirm",
349
+ name: "ok",
350
+ message: `Delete ${toDelete.length} orphaned Neon branch(es): ${toDelete.map((branch) => branch.name).join(", ")}?`,
351
+ initial: false
352
+ });
353
+ return Boolean(ok);
354
+ };
355
+ //#endregion
356
+ export { describe as a, install as c, sync as d, uninstall as f, command as i, partitionBranchesToPrune as l, builder as n, git_exports as o, cleanup as r, handler as s, aliases as t, status as u };
@@ -0,0 +1,111 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { getCliName } from "../utils/cli_name.js";
3
+ import { helpCsv, helpEpilogue } from "../utils/help_text.js";
4
+ import { NEON_SERVICES, servicesOption } from "../neon_services.js";
5
+ import { coerceAgentFlag } from "../utils/agent_flag.js";
6
+ import { initPluginAgents, initSkillsMcpAgents } from "../init/plan.js";
7
+ import { listSkillIds } from "../skills/catalog.js";
8
+ import { runInit } from "../init/run.js";
9
+ //#region src/commands/init.ts
10
+ var init_exports = /* @__PURE__ */ __exportAll({
11
+ builder: () => builder,
12
+ command: () => command,
13
+ describe: () => describe,
14
+ handler: () => handler
15
+ });
16
+ const command = "init";
17
+ const describe = "Set up coding agents and this directory for Neon. -y is Recommended. -y with Custom flags applies those answers without prompts.";
18
+ const removedProtocol = () => `\`${getCliName()} init --data\` was removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
19
+ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file", {
20
+ hidden: true,
21
+ type: "string",
22
+ default: ".neon"
23
+ }).option("yes", {
24
+ alias: "y",
25
+ type: "boolean",
26
+ default: false,
27
+ describe: "Skip prompts. Alone: Recommended (detected agents, link when authenticated, default neon.ts). With --skill, MCP flags, --no-agent-setup, or --claimable: Custom using those flags"
28
+ }).option("agent-setup", {
29
+ type: "boolean",
30
+ default: true,
31
+ describe: "Install Neon into coding agents. Use --no-agent-setup to skip. Plugin vs skills is inferred from --skill and MCP flags"
32
+ }).option("claimable", {
33
+ type: "boolean",
34
+ default: false,
35
+ describe: "Create a claimable project that expires in 72 hours unless claimed. Selects Custom"
36
+ }).option("package-manager", {
37
+ type: "string",
38
+ choices: [
39
+ "npm",
40
+ "pnpm",
41
+ "yarn",
42
+ "bun"
43
+ ],
44
+ describe: "Package manager for neon.ts dependencies. -y uses this without asking. Custom asks only when none is detected"
45
+ }).option("link", {
46
+ type: "boolean",
47
+ default: true,
48
+ describe: "Link a Neon project during setup. Use --no-link to skip without being asked"
49
+ }).option("config", {
50
+ type: "boolean",
51
+ describe: "Create neon.ts after linking. Use --no-config to skip. Omitted in Custom: you will be asked"
52
+ }).option("services", servicesOption({
53
+ key: "services",
54
+ allowed: NEON_SERVICES,
55
+ describe: "Services to declare in neon.ts",
56
+ also: "postgres alone writes the starter policy; every branch has Postgres. Implies creating neon.ts. Cannot be combined with --no-config."
57
+ })).option("org-id", {
58
+ describe: "Forwarded to link: organization ID to link to",
59
+ type: "string"
60
+ }).option("project-id", {
61
+ describe: "Forwarded to link: existing project ID to link to",
62
+ type: "string"
63
+ }).option("project-name", {
64
+ describe: "Forwarded to link: name for a new project",
65
+ type: "string"
66
+ }).option("region-id", {
67
+ describe: "Region for a new project, forwarded to link or to the claimable project",
68
+ type: "string"
69
+ }).option("branch", {
70
+ alias: "branch-id",
71
+ describe: "Forwarded to link: branch name or ID to pin",
72
+ type: "string"
73
+ }).option("agent", {
74
+ alias: "a",
75
+ type: "array",
76
+ string: true,
77
+ describe: "Coding agent to install into (repeatable). Skips agent selection. Values listed below",
78
+ coerce: coerceAgentFlag
79
+ }).option("skill", {
80
+ type: "array",
81
+ string: true,
82
+ describe: "Neon skill to install (repeatable). Selects skills setup (not the plugin) and skips the skills picker. With MCP flags, also configures MCP. Values listed below"
83
+ }).option("mcp-config-location", {
84
+ type: "string",
85
+ choices: ["global", "project"],
86
+ describe: "Where to configure the Neon MCP server: global (user config, same as neon mcp) or project (this directory, same as neon mcp --project). Selects skills and MCP setup"
87
+ }).option("mcp-auth", {
88
+ type: "string",
89
+ choices: ["oauth", "api-key"],
90
+ describe: "MCP authentication. Selects skills and MCP setup. oauth is neon mcp --oauth"
91
+ }).option("mcp-project-scoped", {
92
+ type: "boolean",
93
+ default: false,
94
+ describe: "Limit MCP tools to the linked project, same as neon mcp --project-id. Selects skills and MCP setup"
95
+ }).option("data", {
96
+ hidden: true,
97
+ type: "string"
98
+ }).option("output", {
99
+ alias: "o",
100
+ hidden: true,
101
+ describe: "Not supported; the commands init runs print their own output"
102
+ }).example("$0 init", "Recommended or Custom setup").example("$0 init -y", "Recommended setup without prompts").example("$0 init -y --skill neon --mcp-auth oauth --mcp-config-location project", "Custom: those skills and MCP, Recommended defaults for the rest").example("$0 init --no-agent-setup --claimable", "Skip agent setup and create a claimable project").example("$0 init --agent cursor --agent claude-code", "Skip agent selection; install tooling for those agents").epilogue(helpEpilogue("Recommended setup installs tooling for detected coding agents, links a Neon project when the CLI is authenticated or the session is interactive, and writes a default neon.ts.", "init sets up the current directory in place. It does not scaffold starter files.", "-y alone is Recommended. -y with --skill, MCP flags, --no-agent-setup, or --claimable is Custom: those flags, Recommended defaults for unanswered questions. -y never opens a browser.", "--skill selects skills (not the plugin). MCP flags select skills and MCP. --no-agent-setup skips agent setup. --agent without those flags installs the plugin (and skills/MCP for agents the plugin cannot cover).", "Without a TTY, pass -y or enough flags to answer every question.", "--agent / -a is forwarded to plugins, or to skills and mcp. It skips agent selection, including with -y.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()), helpCsv("Skills", listSkillIds()))).check((argv) => {
103
+ if (argv.data !== void 0) throw new Error(removedProtocol());
104
+ if (argv.help !== true && (argv.output === "json" || argv.output === "yaml")) throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
105
+ return true;
106
+ }).strict();
107
+ const handler = async (props) => {
108
+ await runInit(props);
109
+ };
110
+ //#endregion
111
+ export { init_exports as a, handler as i, command as n, describe as r, builder as t };
@@ -0,0 +1,90 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { writer } from "../writer.js";
3
+ import { fillSingleProject } from "../utils/enrichers.js";
4
+ import { formatInspectQueryError, resolveInspectTargets, runInspectQuery } from "../utils/inspect_db.js";
5
+ import { INSPECT_QUERIES } from "../utils/inspect_queries.js";
6
+ //#region src/commands/inspect.ts
7
+ var inspect_exports = /* @__PURE__ */ __exportAll({
8
+ aliases: () => aliases,
9
+ builder: () => builder,
10
+ command: () => command,
11
+ describe: () => describe,
12
+ handler: () => handler
13
+ });
14
+ const command = "inspect";
15
+ const describe = "Inspect a branch's Postgres health and configuration";
16
+ const aliases = ["inspection"];
17
+ /**
18
+ * `fillSingleProject` hits the API to auto-resolve a project. When the user
19
+ * passes `--db-url` we bypass the Neon API entirely, so skip it.
20
+ */
21
+ const fillSingleProjectUnlessDbUrl = async (props) => {
22
+ if (props.dbUrl) return props;
23
+ return fillSingleProject(props);
24
+ };
25
+ const runSubcommand = async (name, props) => {
26
+ const query = INSPECT_QUERIES[name];
27
+ const { targets, includeDatabaseColumn, branchDatabaseCount } = await resolveInspectTargets(props, query.scope);
28
+ const rows = [];
29
+ for (const target of targets) {
30
+ let batch;
31
+ try {
32
+ batch = await runInspectQuery(target.connectionUri, query.sql, { requiresExtension: query.requiresExtension });
33
+ } catch (err) {
34
+ const reason = err instanceof Error ? err.message : String(err);
35
+ const wrapped = formatInspectQueryError({
36
+ reason,
37
+ database: target.database,
38
+ dbUrl: props.dbUrl,
39
+ databaseName: props.databaseName,
40
+ offerDatabaseNameHint: branchDatabaseCount > 1,
41
+ scope: query.scope,
42
+ requiresExtension: query.requiresExtension
43
+ });
44
+ if (wrapped === void 0) throw err instanceof Error ? err : new Error(reason);
45
+ throw new Error(wrapped);
46
+ }
47
+ if (includeDatabaseColumn) rows.push(...batch.map((row) => ({
48
+ database: target.database,
49
+ ...row
50
+ })));
51
+ else rows.push(...batch);
52
+ }
53
+ const fields = includeDatabaseColumn ? ["database", ...query.fields] : query.fields;
54
+ writer(props).end(rows, {
55
+ fields,
56
+ emptyMessage: includeDatabaseColumn ? query.emptyMessageAll ?? query.emptyMessage : query.emptyMessage
57
+ });
58
+ };
59
+ const dbBuilder = (argv) => {
60
+ let builder = argv.usage("$0 inspect db <sub-command> [options]").options({
61
+ "project-id": {
62
+ describe: "Project ID",
63
+ type: "string"
64
+ },
65
+ branch: {
66
+ describe: "Branch ID or name",
67
+ type: "string"
68
+ },
69
+ "database-name": {
70
+ describe: "Database to inspect. Omit to cover every database the API lists for the branch. Ranking and row limits stay per database. One failing database fails the whole run. Compute-wide checks run once against the first listed database. Ignored with --db-url.",
71
+ type: "string"
72
+ },
73
+ "role-name": {
74
+ describe: "Role name. Defaults to neondb_owner, else the only role besides the Data API and Neon Auth roles",
75
+ type: "string"
76
+ },
77
+ "db-url": {
78
+ describe: "Inspect any Postgres via a connection string, bypassing the Neon API (project/branch resolution is skipped)",
79
+ type: "string"
80
+ }
81
+ }).middleware(fillSingleProjectUnlessDbUrl);
82
+ for (const name of Object.keys(INSPECT_QUERIES)) builder = builder.command(name, INSPECT_QUERIES[name].describe, (yargs) => yargs, (args) => runSubcommand(name, args));
83
+ return builder;
84
+ };
85
+ const builder = (argv) => argv.usage("$0 inspect <sub-command> [options]").command("db", "Run a diagnostic query against a branch's Postgres", dbBuilder);
86
+ const handler = (args) => {
87
+ return args;
88
+ };
89
+ //#endregion
90
+ export { handler as a, describe as i, builder as n, inspect_exports as o, command as r, aliases as t };
@@ -0,0 +1,106 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { log } from "../log.js";
3
+ import { getCliName } from "../utils/cli_name.js";
4
+ import { projectUpdateRequest } from "../parameters.gen.js";
5
+ import { writer } from "../writer.js";
6
+ import { fillSingleProject } from "../utils/enrichers.js";
7
+ //#region src/commands/ip_allow.ts
8
+ var ip_allow_exports = /* @__PURE__ */ __exportAll({
9
+ builder: () => builder,
10
+ command: () => command,
11
+ describe: () => describe,
12
+ handler: () => handler
13
+ });
14
+ const IP_ALLOW_FIELDS = [
15
+ "id",
16
+ "name",
17
+ "IP_addresses",
18
+ "protected_branches_only"
19
+ ];
20
+ const command = "ip-allow";
21
+ const describe = "Manage IP Allow";
22
+ const builder = (argv) => {
23
+ return argv.usage("$0 ip-allow <sub-command> [options]").options({ "project-id": {
24
+ describe: "Project ID",
25
+ type: "string"
26
+ } }).middleware(fillSingleProject).command("list", "List the IP allowlist", (yargs) => yargs, async (args) => {
27
+ await list(args);
28
+ }).command("add [ips...]", "Add IP addresses to the IP allowlist", (yargs) => yargs.usage("$0 ip-allow add [ips...]").positional("ips", {
29
+ describe: "The list of IP addresses to add",
30
+ type: "string",
31
+ default: [],
32
+ array: true
33
+ }).options({ "protected-only": {
34
+ describe: projectUpdateRequest["project.settings.allowed_ips.protected_branches_only"].description,
35
+ type: "boolean"
36
+ } }), async (args) => {
37
+ await add(args);
38
+ }).command("remove [ips...]", "Remove IP addresses from the IP allowlist", (yargs) => yargs.usage("$0 ip-allow remove [ips...]").positional("ips", {
39
+ describe: "The list of IP addresses to remove",
40
+ type: "string",
41
+ default: [],
42
+ array: true
43
+ }), async (args) => {
44
+ await remove(args);
45
+ }).command("reset [ips...]", "Reset the IP allowlist", (yargs) => yargs.usage("$0 ip-allow reset [ips...]").positional("ips", {
46
+ describe: "The list of IP addresses to reset",
47
+ type: "string",
48
+ default: [],
49
+ array: true
50
+ }), async (args) => {
51
+ await reset(args);
52
+ });
53
+ };
54
+ const handler = (args) => {
55
+ return args;
56
+ };
57
+ const list = async (props) => {
58
+ const { data } = await props.apiClient.getProject(props.projectId);
59
+ writer(props).end(parse(data.project), { fields: IP_ALLOW_FIELDS });
60
+ };
61
+ const add = async (props) => {
62
+ if (props.ips.length <= 0) throw new Error(`Enter individual IP addresses, define ranges with a dash, or use CIDR notation for more flexibility.
63
+ Example: ${getCliName()} ip-allow add 192.168.1.1, 192.168.1.20-192.168.1.50, 192.168.1.0/24 --project-id <id>`);
64
+ const project = {};
65
+ const { data } = await props.apiClient.getProject(props.projectId);
66
+ const existingAllowedIps = data.project.settings?.allowed_ips;
67
+ project.settings = { allowed_ips: {
68
+ ips: [...new Set(props.ips.concat(existingAllowedIps?.ips ?? []))],
69
+ protected_branches_only: props.protectedOnly ?? existingAllowedIps?.protected_branches_only ?? false
70
+ } };
71
+ const { data: response } = await props.apiClient.updateProject(props.projectId, { project });
72
+ writer(props).end(parse(response.project), { fields: IP_ALLOW_FIELDS });
73
+ };
74
+ const remove = async (props) => {
75
+ if (props.ips.length <= 0) throw new Error(`Remove individual IP addresses and ranges. Example: ${getCliName()} ip-allow remove 192.168.1.1 --project-id <id>`);
76
+ const project = {};
77
+ const { data } = await props.apiClient.getProject(props.projectId);
78
+ const existingAllowedIps = data.project.settings?.allowed_ips;
79
+ project.settings = { allowed_ips: {
80
+ ips: existingAllowedIps?.ips?.filter((ip) => !props.ips.includes(ip)) ?? [],
81
+ protected_branches_only: existingAllowedIps?.protected_branches_only ?? false
82
+ } };
83
+ const { data: response } = await props.apiClient.updateProject(props.projectId, { project });
84
+ writer(props).end(parse(response.project), { fields: IP_ALLOW_FIELDS });
85
+ };
86
+ const reset = async (props) => {
87
+ const project = {};
88
+ project.settings = { allowed_ips: {
89
+ ips: props.ips,
90
+ protected_branches_only: false
91
+ } };
92
+ const { data } = await props.apiClient.updateProject(props.projectId, { project });
93
+ writer(props).end(parse(data.project), { fields: IP_ALLOW_FIELDS });
94
+ if (props.ips.length <= 0) log.info(`The IP allowlist has been reset. All databases on project "${data.project.name}" are now exposed to the internet`);
95
+ };
96
+ const parse = (project) => {
97
+ const ips = project.settings?.allowed_ips?.ips ?? [];
98
+ return {
99
+ id: project.id,
100
+ name: project.name,
101
+ IP_addresses: ips,
102
+ protected_branches_only: project.settings?.allowed_ips?.protected_branches_only ?? false
103
+ };
104
+ };
105
+ //#endregion
106
+ export { ip_allow_exports as a, handler as i, command as n, describe as r, builder as t };