neon 8.11.0 → 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 (106) 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 +6 -6
  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/dist/utils/package_manager.js +1 -0
  106. package/package.json +4 -2
@@ -1,936 +1 @@
1
- import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { isInteractiveTerminal } from "../env.js";
3
- import { log } from "../log.js";
4
- import { isNeonApiError, messageFromBody } from "../api.js";
5
- import { r as defaultDir } from "../_chunks/paths-DMq0Lt7a.js";
6
- import "../config.js";
7
- import { applyContext, contextBranch, readContextFile, setContext, updateContextFile } from "../context.js";
8
- import { isClaimableEnvTarget } from "../claimable/state.js";
9
- import { getCliName } from "../utils/cli_name.js";
10
- import { helpEpilogue } from "../utils/help_text.js";
11
- import { hasNeonConfigFile, projectDir } from "../project.js";
12
- import { writer } from "../writer.js";
13
- import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
14
- import { looksLikeBranchId } from "../utils/formats.js";
15
- import { listAllProjectBranches } from "../utils/enrichers.js";
16
- import { autoPullEnvAfterPin } from "./env.js";
17
- import { initCmd } from "./config.js";
18
- import { formatLinkSummary } from "./link_output.js";
19
- import { REGIONS } from "./projects.js";
20
- import prompts from "prompts";
21
- //#region src/commands/link.ts
22
- var link_exports = /* @__PURE__ */ __exportAll({
23
- builder: () => builder,
24
- command: () => command,
25
- describe: () => describe,
26
- handler: () => handler,
27
- quoteFlagValue: () => quoteFlagValue,
28
- runLink: () => runLink,
29
- shouldOfferConfigInit: () => shouldOfferConfigInit
30
- });
31
- const PROJECTS_LIST_LIMIT = 100;
32
- const CREATE_NEW_SENTINEL = "__create_new__";
33
- const canPromptInteractively = () => isInteractiveTerminal();
34
- const nonInteractiveLinkCommands = () => {
35
- const cli = getCliName();
36
- return [
37
- `${cli} link -y`,
38
- `${cli} orgs list --output json`,
39
- `${cli} projects list --org-id <org-id> --output json`,
40
- `${cli} link --project-id <project-id> [--branch <name> | -y]`,
41
- `${cli} link --org-id <org-id> --project-name <name> --region-id aws-us-east-2`
42
- ];
43
- };
44
- const nonInteractiveLinkHelp = () => nonInteractiveLinkCommands().map((command) => ` ${command}`).join("\n");
45
- const orgScopedKeyHint = "Organization-scoped API keys cannot list orgs; pass --org-id.";
46
- const command = "link";
47
- const describe = "Link the current directory to a Neon project";
48
- const builder = (argv) => argv.usage("$0 link [options]").options({
49
- "org-id": {
50
- describe: "Organization ID to link to",
51
- type: "string"
52
- },
53
- "project-id": {
54
- describe: "Existing project ID to link to",
55
- type: "string"
56
- },
57
- "project-name": {
58
- describe: "Name for a new project to create and link to",
59
- type: "string"
60
- },
61
- "region-id": {
62
- describe: "Region ID for a new project (e.g. aws-us-east-2). Required with --project-name.",
63
- type: "string"
64
- },
65
- branch: {
66
- alias: "branch-id",
67
- describe: "Branch name or ID to pin in the context (resolved to its name before writing).",
68
- type: "string"
69
- },
70
- params: {
71
- describe: "JSON object with link parameters, e.g. '{\"orgId\":\"...\",\"projectId\":\"...\"}' or '{\"orgId\":\"...\",\"projectName\":\"...\",\"regionId\":\"...\"}'. Flags take precedence over fields in --params.",
72
- type: "string"
73
- },
74
- yes: {
75
- alias: "y",
76
- describe: "Skip prompts. Select the only organization and project, or print IDs and the flag to pass. Pin the default branch when several exist. Does not create a project unless --project-name and --region-id are set.",
77
- type: "boolean",
78
- default: false
79
- },
80
- clear: {
81
- describe: "Remove the org/project/branch context (writes an empty context file) instead of linking.",
82
- type: "boolean",
83
- default: false
84
- },
85
- checks: {
86
- describe: "Verify the org/project/branch exist (and resolve the org from the project) before writing. On by default; use --no-checks to write the context offline with no API calls — it then requires --org-id, --project-id, and --branch, and skips env pull.",
87
- type: "boolean",
88
- default: true
89
- },
90
- "env-pull": {
91
- describe: "Pull the linked branch's Neon env vars (DATABASE_URL, …) into a local .env after linking. On by default; use --no-env-pull to skip (e.g. when injecting env at runtime with `neon-env run` / `neon dev`). Only runs when a branch is pinned.",
92
- type: "boolean",
93
- default: true
94
- },
95
- config: {
96
- describe: "Offer to create neon.ts after interactive linking. Use --no-config to skip the offer",
97
- type: "boolean",
98
- default: true
99
- }
100
- }).example([
101
- ["$0 link -y", "Select the only organization and project, or print the --org-id / --project-id to pass"],
102
- ["$0 link --project-id polished-snowflake-12345678", "Link an existing project (org is inferred). Pins the only branch; several prompt in a TTY, or -y pins the default"],
103
- ["$0 link --project-id polished-snowflake-12345678 -y", "Same, pinning the project's default branch when several exist"],
104
- ["$0 link --org-id org-… --project-name my-app --region-id aws-us-east-2", "Create a new project and link it"],
105
- ["$0 link --branch-id br-…", "Pin a branch in the already-linked project"],
106
- ["$0 link --no-checks --org-id org-… --project-id polished-snowflake-12345678 --branch main", "Write the context offline (no API calls, no verification)"],
107
- ["$0 link --clear", "Forget the current org/project/branch context"]
108
- ]).epilogue(helpEpilogue("Non-interactive (CI, scripts, agents):", ...nonInteractiveLinkCommands().map((command) => ` ${command}`), orgScopedKeyHint)).strict();
109
- const runLink = async (props) => {
110
- if (props.clear) {
111
- clearContext(props.contextFile);
112
- return;
113
- }
114
- if (!props.checks) {
115
- runWithoutChecks(props);
116
- return;
117
- }
118
- const inputs = parseInputs(props);
119
- validateInputs(inputs);
120
- const existing = readContextFile(props.contextFile);
121
- if (props.yes && hasIncompleteCreationInputs(inputs)) throw incompleteCreationError(props, inputs);
122
- if (canResolveNonInteractively(inputs, existing)) {
123
- await runNonInteractive(props, inputs, existing);
124
- return;
125
- }
126
- if (props.yes) {
127
- const resolved = await resolveYesInputs(props, inputs);
128
- await runNonInteractive(props, resolved, existing);
129
- return;
130
- }
131
- if (!canPromptInteractively()) {
132
- if (missingProjectForOrg(inputs)) throw orgNeedsProjectError(props, inputs);
133
- throw new LinkInputError([
134
- "Missing inputs and no interactive terminal for prompts.",
135
- "",
136
- "Use:",
137
- nonInteractiveLinkHelp(),
138
- orgScopedKeyHint
139
- ].join("\n"));
140
- }
141
- await runInteractive(props, inputs);
142
- };
143
- const handler = runLink;
144
- const parseInputs = (props) => {
145
- let fromParams = {};
146
- if (props.params !== void 0 && props.params !== "") {
147
- let parsed;
148
- try {
149
- parsed = JSON.parse(props.params);
150
- } catch (err) {
151
- const message = err instanceof Error ? err.message : String(err);
152
- throw new Error(`Failed to parse --params JSON: ${message}`);
153
- }
154
- fromParams = extractParams(parsed);
155
- }
156
- return {
157
- orgId: props.orgId ?? fromParams.orgId,
158
- projectId: props.projectId ?? fromParams.projectId,
159
- projectName: props.projectName ?? fromParams.projectName,
160
- regionId: props.regionId ?? fromParams.regionId,
161
- branch: props.branch ?? fromParams.branch
162
- };
163
- };
164
- const extractParams = (raw) => {
165
- if (raw === null || typeof raw !== "object") throw new Error("--params must be a JSON object");
166
- const obj = raw;
167
- const pickString = (key) => {
168
- const value = obj[key];
169
- if (value === void 0 || value === null) return void 0;
170
- if (typeof value !== "string") throw new Error(`--params.${key} must be a string`);
171
- return value;
172
- };
173
- return {
174
- orgId: pickString("orgId"),
175
- projectId: pickString("projectId"),
176
- projectName: pickString("projectName"),
177
- regionId: pickString("regionId"),
178
- branch: pickString("branch") ?? pickString("branchId")
179
- };
180
- };
181
- const validateInputs = (inputs) => {
182
- if (inputs.projectId && (inputs.projectName || inputs.regionId)) throw new Error("Conflicting inputs: --project-id selects an existing project; --project-name and --region-id describe a new one. Pass only one set.");
183
- if (inputs.projectName && inputs.branch) throw new Error(`Conflicting inputs: --branch pins a branch of an existing project, but --project-name creates a new one. Create the project first, then \`${getCliName()} checkout <branch>\`.`);
184
- };
185
- const hasIncompleteCreationInputs = (inputs) => Boolean(inputs.projectName) !== Boolean(inputs.regionId);
186
- const missingProjectForOrg = (inputs) => Boolean(inputs.orgId) && !inputs.projectId && !inputs.projectName;
187
- const orgNeedsProjectError = (props, inputs) => {
188
- const orgId = inputs.orgId ?? "<org-id>";
189
- const branchFlag = inputs.branch ? ` --branch ${quoteFlagValue(inputs.branch)}` : "";
190
- const session = sessionRetryFlags(props);
191
- return new LinkInputError([
192
- "No project selected. Pass --project-id, or use -y to select the only project:",
193
- ` ${getCliName()} link -y --org-id ${quoteFlagValue(orgId)}${branchFlag}${session}`,
194
- ` ${getCliName()} link --org-id ${quoteFlagValue(orgId)} --project-id <project-id>${inputs.branch ? ` --branch ${quoteFlagValue(inputs.branch)}` : " --branch <name-or-id>"}${session}`
195
- ].join("\n"));
196
- };
197
- const quoteFlagValue = (value) => {
198
- if (/^[A-Za-z0-9_./:@-]+$/.test(value)) return value;
199
- return `'${value.replace(/'/g, `'\\''`)}'`;
200
- };
201
- const sessionRetryFlags = (props) => {
202
- const flags = [];
203
- if (props.contextFile) flags.push(`--context-file ${quoteFlagValue(props.contextFile)}`);
204
- if (props.configDir && props.configDir !== defaultDir) flags.push(`--config-dir ${quoteFlagValue(props.configDir)}`);
205
- if (props.output !== "table") flags.push(`--output ${props.output}`);
206
- if (!props.envPull) flags.push("--no-env-pull");
207
- if (props.profile) flags.push(`--profile ${quoteFlagValue(props.profile)}`);
208
- return flags.length > 0 ? ` ${flags.join(" ")}` : "";
209
- };
210
- const incompleteCreationError = (props, inputs) => {
211
- const orgFlag = inputs.orgId ? `--org-id ${quoteFlagValue(inputs.orgId)}` : "--org-id <org-id>";
212
- const nameFlag = inputs.projectName ? `--project-name ${quoteFlagValue(inputs.projectName)}` : "--project-name <name>";
213
- const regionFlag = inputs.regionId ? `--region-id ${quoteFlagValue(inputs.regionId)}` : "--region-id aws-us-east-2";
214
- const example = `${getCliName()} link -y ${orgFlag} ${nameFlag} ${regionFlag}${sessionRetryFlags(props)}`;
215
- if (inputs.projectName) return new LinkInputError(`--project-name requires --region-id. Example:\n ${example}`);
216
- return new LinkInputError(`--region-id requires --project-name. Example:\n ${example}`);
217
- };
218
- /**
219
- * Branch selection may still prompt after org and project resolve without the
220
- * org/project wizard.
221
- */
222
- const canResolveNonInteractively = (inputs, existing) => {
223
- if (inputs.projectId) return true;
224
- if (inputs.orgId && inputs.projectName && inputs.regionId) return true;
225
- if (inputs.branch && existing.projectId) return true;
226
- return false;
227
- };
228
- const clearContext = (contextFile) => {
229
- updateContextFile(contextFile, {});
230
- process.stdout.write(`Cleared ${contextFile}. The directory is no longer linked to a Neon org/project/branch.\n`);
231
- };
232
- /**
233
- * `--no-checks`: write the context offline. Makes no API calls — so no org
234
- * inference, no existence/access verification, and no env pull — which means
235
- * the caller must supply `--org-id`, `--project-id`, and `--branch`. This is
236
- * the CLI surface over {@link setContext}, useful for scripted/offline setups
237
- * and for re-creating a `.neon` from values you already trust.
238
- */
239
- const runWithoutChecks = (props) => {
240
- const inputs = parseInputs(props);
241
- validateInputs(inputs);
242
- if (inputs.projectName) throw new Error("--no-checks can't create a project (that needs API access). Pass --org-id and --project-id for an existing project, or drop --no-checks.");
243
- if (!inputs.orgId || !inputs.projectId || !inputs.branch) throw new Error("--no-checks requires --org-id, --project-id, and --branch because identifiers cannot be resolved offline.");
244
- setContext(props.contextFile, {
245
- orgId: inputs.orgId,
246
- projectId: inputs.projectId,
247
- branch: inputs.branch
248
- });
249
- printSummary(props, {
250
- contextFile: props.contextFile,
251
- orgId: inputs.orgId,
252
- projectId: inputs.projectId,
253
- branch: inputs.branch,
254
- created: false,
255
- noChecks: true
256
- });
257
- };
258
- var LinkInputError = class extends Error {
259
- constructor(message) {
260
- super(message);
261
- this.name = "LinkInputError";
262
- }
263
- };
264
- const httpStatus = (err) => isNeonApiError(err) ? err.status : void 0;
265
- /** 401 must reach the global handler so it can refresh credentials. */
266
- const fetchProjectOrThrow = async (props, projectId) => {
267
- try {
268
- const { data } = await props.apiClient.getProject(projectId);
269
- return data.project;
270
- } catch (err) {
271
- const status = httpStatus(err);
272
- if (status === 401) throw err;
273
- if (status === 403) throw new LinkInputError(`You don't have access to project '${projectId}'. Check that your API key's account or organization can see it.`);
274
- if (status === 404) throw new LinkInputError(`Project '${projectId}' not found. Double-check the project ID — or that your API key has access to it.`);
275
- throw err;
276
- }
277
- };
278
- /**
279
- * Confirm the org exists and is reachable with the current API key by listing
280
- * its projects (allowed for both user and org-scoped keys). Maps 403/404 to a
281
- * clear message; 401 is rethrown for credential refresh.
282
- */
283
- const verifyOrgAccess = async (props, orgId) => {
284
- try {
285
- await props.apiClient.listProjects({
286
- org_id: orgId,
287
- limit: PROJECTS_LIST_LIMIT
288
- });
289
- } catch (err) {
290
- const status = httpStatus(err);
291
- if (status === 401) throw err;
292
- if (status === 403 || status === 404) throw new LinkInputError(`Organization '${orgId}' not found, or your API key doesn't have access to it. Find your org ID in the Neon Console under Settings.`);
293
- throw err;
294
- }
295
- };
296
- const failWithBranchCandidates = (props, projectId, branches, reason) => {
297
- writer(props).end(branches.map((b) => ({
298
- id: b.id,
299
- name: b.name ?? b.id
300
- })), {
301
- fields: ["id", "name"],
302
- title: "Branches"
303
- });
304
- const orgFlag = props.orgId ? ` --org-id ${quoteFlagValue(props.orgId)}` : "";
305
- throw new LinkInputError(`${reason}\n ${getCliName()} link -y${orgFlag} --project-id ${quoteFlagValue(projectId)} --branch <name-or-id>${sessionRetryFlags(props)}`);
306
- };
307
- const listAllBranches = async (props, projectId) => listAllProjectBranches(props.apiClient, projectId);
308
- /**
309
- * The branch listing for {@link resolvePinnedBranch}, started alongside the project check
310
- * except on Claimable Neon, which does not complete concurrent Management API reads. Only
311
- * the listing starts early: branch selection, which can prompt or create a branch, still
312
- * waits for the check, and a listing error is reported only after the check passes.
313
- */
314
- const branchListing = (props, projectId) => {
315
- if (isClaimableEnvTarget({
316
- apiHost: props.apiHost,
317
- contextFile: props.contextFile,
318
- configDir: props.configDir ?? ""
319
- })) return () => listAllBranches(props, projectId);
320
- const listing = listAllBranches(props, projectId);
321
- listing.catch(() => void 0);
322
- return () => listing;
323
- };
324
- /**
325
- * Resolve a branch reference (name *or* id) to the matching branch, while
326
- * confirming it actually exists in the project. Unlike the shared
327
- * `branchIdResolve`, this also verifies references that already look like ids
328
- * (so a typo'd `br-…` doesn't silently get written).
329
- */
330
- const resolveBranchRef = (props, projectId, branchRef, branches) => {
331
- const match = branches.find((b) => b.id === branchRef) ?? branches.find((b) => b.name === branchRef);
332
- if (match) return match;
333
- return failWithBranchCandidates(props, projectId, branches, `Branch '${branchRef}' not found in project '${projectId}'. Pass --branch with a name or ID from the list:`);
334
- };
335
- /**
336
- * Persist the name when later commands will look it up as a name. A name that
337
- * already looks like a branch id is trusted as an id without listing, so keep
338
- * the real id in that case.
339
- */
340
- const branchPersistValue = (branch) => {
341
- if (branch.name && !looksLikeBranchId(branch.name)) return branch.name;
342
- return branch.id;
343
- };
344
- /**
345
- * Verify the project (and the org, when supplied) and resolve the org id to
346
- * persist.
347
- *
348
- * The project is always fetched, which both validates it and yields its
349
- * `org_id`. When `--org-id` is passed too: if the project reports an org it must
350
- * match (else a clear mismatch error); if it reports none, fail rather than
351
- * attaching an unrelated org. Without `--org-id` the project's own org is used.
352
- * Projects on a personal account have no org, so `undefined` is a valid result —
353
- * the field is simply omitted.
354
- */
355
- const resolveOrgForProject = async (props, inputs, projectId) => {
356
- const project = await fetchProjectOrThrow(props, projectId);
357
- const projectOrg = project.org_id ?? void 0;
358
- const projectName = project.name;
359
- if (inputs.orgId) {
360
- if (projectOrg && projectOrg !== inputs.orgId) throw new LinkInputError(`Project '${projectId}' belongs to organization '${projectOrg}', not '${inputs.orgId}'. Omit --org-id to use the project's own org, or pass the matching ID.`);
361
- if (!projectOrg) throw new LinkInputError(`Project '${projectId}' does not report an organization matching --org-id ${inputs.orgId}. Omit --org-id to link using the project's own context.`);
362
- return {
363
- orgId: inputs.orgId,
364
- projectName
365
- };
366
- }
367
- return {
368
- orgId: projectOrg,
369
- projectName
370
- };
371
- };
372
- const pinnedRef = (branch) => ({
373
- branchId: branch.id,
374
- branchName: branch.name ?? branch.id,
375
- usedDefault: false
376
- });
377
- const resolution = (branch) => ({
378
- branch: branchPersistValue(branch),
379
- ref: pinnedRef(branch)
380
- });
381
- /**
382
- * Keep a same-project pin only after it still resolves on the project.
383
- */
384
- const resolvePinnedBranch = async (props, inputs, existing, projectId, listBranches) => {
385
- const branches = await listBranches();
386
- if (inputs.branch) return resolution(resolveBranchRef(props, projectId, inputs.branch, branches));
387
- if (projectId === existing.projectId) {
388
- const pinned = contextBranch(existing);
389
- if (pinned) return resolution(resolveBranchRef(props, projectId, pinned, branches));
390
- }
391
- return resolveBranchFromList(props, projectId, branches);
392
- };
393
- const resolveBranchFromList = async (props, projectId, branches) => {
394
- if (branches.length === 0) throw new LinkInputError(`Project '${projectId}' has no branches to link. Create or restore a branch before retrying, or select another project with --project-id.`);
395
- if (branches.length === 1) {
396
- const [only] = branches;
397
- if (!only) throw new LinkInputError(`Project '${projectId}' has no branches to link. Create or restore a branch before retrying, or select another project with --project-id.`);
398
- return resolution(only);
399
- }
400
- if (props.yes) {
401
- const def = branches.find((b) => b.default);
402
- if (!def) return failWithBranchCandidates(props, projectId, branches, `Project '${projectId}' has no default branch. Pass --branch with a name or ID from the list:`);
403
- return resolution(def);
404
- }
405
- if (canPromptInteractively()) {
406
- const picked = await pickBranchInteractively(branches, {
407
- message: "Which branch would you like to link?",
408
- nonInteractiveMessage: "No branch could be selected without an interactive terminal. Pass --branch <name-or-id>, or -y to pin the default branch."
409
- });
410
- if (picked.kind === "existing") {
411
- const existing = branches.find((b) => b.id === picked.branchId);
412
- return existing ? resolution(existing) : {
413
- branch: picked.branchId,
414
- ref: pinnedRef({ id: picked.branchId })
415
- };
416
- }
417
- const createdBranch = {
418
- id: await createBranch(props.apiClient, projectId, picked.name, branches),
419
- name: picked.name
420
- };
421
- return {
422
- branch: branchPersistValue(createdBranch),
423
- ref: pinnedRef(createdBranch)
424
- };
425
- }
426
- return failWithBranchCandidates(props, projectId, branches, `Project '${projectId}' has multiple branches. Pass --branch <name-or-id>, or -y to pin its default branch.`);
427
- };
428
- const runNonInteractive = async (props, inputs, existing) => {
429
- if (inputs.projectName) {
430
- const orgId = mustString(inputs.orgId, "orgId");
431
- await verifyOrgAccess(props, orgId);
432
- const created = await createProject(props, {
433
- orgId,
434
- name: inputs.projectName,
435
- regionId: mustString(inputs.regionId, "regionId")
436
- });
437
- applyContext(props.contextFile, {
438
- orgId,
439
- projectId: created.project.id,
440
- branch: created.branchName
441
- });
442
- await finalizeLink(props, {
443
- contextFile: props.contextFile,
444
- orgId,
445
- projectId: created.project.id,
446
- branch: created.branchName,
447
- branchRef: pinnedRef({
448
- id: created.branchId,
449
- name: created.branchName
450
- }),
451
- created: true,
452
- projectName: created.project.name,
453
- regionId: created.project.region_id
454
- });
455
- return;
456
- }
457
- if (inputs.projectId) {
458
- const listBranches = branchListing(props, inputs.projectId);
459
- const { orgId, projectName } = await resolveOrgForProject(props, inputs, inputs.projectId);
460
- const resolved = await resolvePinnedBranch(props, inputs, existing, inputs.projectId, listBranches);
461
- applyContext(props.contextFile, {
462
- orgId,
463
- projectId: inputs.projectId,
464
- branch: resolved.branch
465
- });
466
- await finalizeLink(props, {
467
- contextFile: props.contextFile,
468
- orgId,
469
- projectId: inputs.projectId,
470
- projectName,
471
- branch: resolved.branch,
472
- branchRef: resolved.ref,
473
- created: false
474
- });
475
- return;
476
- }
477
- if (inputs.branch && existing.projectId) {
478
- const projectId = existing.projectId;
479
- const listBranches = branchListing(props, projectId);
480
- const { orgId, projectName } = await resolveOrgForProject(props, inputs, projectId);
481
- const resolved = await resolvePinnedBranch(props, inputs, existing, projectId, listBranches);
482
- applyContext(props.contextFile, {
483
- orgId,
484
- projectId,
485
- branch: resolved.branch
486
- });
487
- await finalizeLink(props, {
488
- contextFile: props.contextFile,
489
- orgId,
490
- projectId,
491
- projectName,
492
- branch: resolved.branch,
493
- branchRef: resolved.ref,
494
- created: false
495
- });
496
- return;
497
- }
498
- throw orgNeedsProjectError(props, inputs);
499
- };
500
- const runInteractive = async (props, inputs) => {
501
- if (!props.yes) {
502
- if (!await confirmRelinkIfNeeded(props)) return;
503
- }
504
- const orgResolution = await resolveOrg(props, inputs.orgId);
505
- let orgId;
506
- if (orgResolution.kind === "resolved") {
507
- orgId = orgResolution.orgId;
508
- if (orgResolution.autoDetected) log.info(`Detected organization ${orgId} from your existing projects (organization-scoped API key).`);
509
- } else if (orgResolution.orgKeyLimited) throw new Error("This API key is organization-scoped, so the CLI cannot list your organizations, and no existing project was found in this org to auto-detect the ID. Re-run with `--org-id <your_org_id>` (find it in the Neon Console under Settings).");
510
- else orgId = await promptOrgFromList(orgResolution.orgs);
511
- if (inputs.projectName && inputs.regionId) {
512
- if (inputs.branch) throw new Error(`Conflicting inputs: --branch pins a branch of an existing project, but --project-name creates a new one. Create the project first, then \`${getCliName()} checkout <branch>\`.`);
513
- const created = await createProject(props, {
514
- orgId,
515
- name: inputs.projectName,
516
- regionId: inputs.regionId
517
- });
518
- applyContext(props.contextFile, {
519
- orgId,
520
- projectId: created.project.id,
521
- branch: created.branchName
522
- });
523
- await finalizeInteractiveLink(props, {
524
- contextFile: props.contextFile,
525
- orgId,
526
- projectId: created.project.id,
527
- branch: created.branchName,
528
- branchRef: pinnedRef({
529
- id: created.branchId,
530
- name: created.branchName
531
- }),
532
- created: true,
533
- projectName: created.project.name,
534
- regionId: created.project.region_id
535
- });
536
- return;
537
- }
538
- const projects = await listAllProjects(props, orgId);
539
- const action = await promptProjectChoice(projects, inputs.projectName, { allowCreate: !inputs.branch });
540
- if (action.type === "existing") {
541
- const existing = readContextFile(props.contextFile);
542
- const resolved = await resolvePinnedBranch(props, inputs, existing, action.projectId, () => listAllBranches(props, action.projectId));
543
- applyContext(props.contextFile, {
544
- orgId,
545
- projectId: action.projectId,
546
- branch: resolved.branch
547
- });
548
- await finalizeInteractiveLink(props, {
549
- contextFile: props.contextFile,
550
- orgId,
551
- projectId: action.projectId,
552
- branch: resolved.branch,
553
- branchRef: resolved.ref,
554
- created: false,
555
- projectName: action.name,
556
- regionId: action.regionId
557
- });
558
- return;
559
- }
560
- if (inputs.branch) throw new Error(`Conflicting inputs: --branch pins a branch of an existing project, but creating a project starts from its default branch. Link an existing project, or omit --branch and create first.`);
561
- const projectName = inputs.projectName ?? await promptProjectName(action.suggestedName);
562
- const regionId = inputs.regionId ?? await promptRegion(props);
563
- const created = await createProject(props, {
564
- orgId,
565
- name: projectName,
566
- regionId
567
- });
568
- applyContext(props.contextFile, {
569
- orgId,
570
- projectId: created.project.id,
571
- branch: created.branchName
572
- });
573
- await finalizeInteractiveLink(props, {
574
- contextFile: props.contextFile,
575
- orgId,
576
- projectId: created.project.id,
577
- branch: created.branchName,
578
- branchRef: pinnedRef({
579
- id: created.branchId,
580
- name: created.branchName
581
- }),
582
- created: true,
583
- projectName: created.project.name,
584
- regionId: created.project.region_id
585
- });
586
- };
587
- const confirmRelinkIfNeeded = async (props) => {
588
- const existing = readContextFile(props.contextFile);
589
- if (!existing.projectId) return true;
590
- const { proceed } = await prompts({
591
- onState: onPromptState,
592
- type: "confirm",
593
- name: "proceed",
594
- message: existing.orgId ? `${props.contextFile} is already linked to project ${existing.projectId} (org ${existing.orgId}). Re-link?` : `${props.contextFile} is already linked to project ${existing.projectId}. Re-link?`,
595
- initial: true
596
- });
597
- if (!proceed) {
598
- process.stdout.write("Aborted. Existing link preserved.\n");
599
- return false;
600
- }
601
- return true;
602
- };
603
- const promptOrgFromList = async (orgs) => {
604
- if (!orgs.length) throw new Error(`You don't belong to any organizations. Create one in the Neon Console first: https://console.neon.tech/`);
605
- if (orgs.length === 1) {
606
- const [only] = orgs;
607
- log.info(`Linking organization ${only.name} (${only.id}).`);
608
- return only.id;
609
- }
610
- const { orgId } = await prompts({
611
- onState: onPromptState,
612
- type: "select",
613
- name: "orgId",
614
- message: "Which organization would you like to link?",
615
- choices: orgs.map((org) => ({
616
- title: `${org.name} (${org.id})`,
617
- value: org.id
618
- })),
619
- initial: 0
620
- });
621
- return orgId;
622
- };
623
- const promptProjectChoice = async (projects, suggestedName, opts) => {
624
- const choices = [...opts.allowCreate ? [{
625
- title: "+ Create new project…",
626
- value: CREATE_NEW_SENTINEL
627
- }] : [], ...projects.map((project) => ({
628
- title: `${project.name} (${project.id})`,
629
- value: project.id
630
- }))];
631
- if (choices.length === 0) throw new LinkInputError("No projects are available to link. Omit --branch to create a project, or pass --project-id for an existing one.");
632
- const createOffset = opts.allowCreate ? 1 : 0;
633
- const { selection } = await prompts({
634
- onState: onPromptState,
635
- type: "select",
636
- name: "selection",
637
- message: "Which project would you like to link?",
638
- choices,
639
- initial: projects.length > 0 ? createOffset : 0
640
- });
641
- if (selection === CREATE_NEW_SENTINEL) return {
642
- type: "create",
643
- suggestedName
644
- };
645
- const project = projects.find((p) => p.id === selection);
646
- return {
647
- type: "existing",
648
- projectId: selection,
649
- name: project?.name,
650
- regionId: project?.region_id
651
- };
652
- };
653
- const promptProjectName = async (suggestedName) => {
654
- const { name } = await prompts({
655
- onState: onPromptState,
656
- type: "text",
657
- name: "name",
658
- message: "Name for the new project:",
659
- initial: suggestedName,
660
- validate: (value) => value && value.trim().length > 0 ? true : "Project name is required"
661
- });
662
- return String(name).trim();
663
- };
664
- const promptRegion = async (props) => {
665
- const regions = await fetchRegions(props);
666
- const defaultIndex = Math.max(0, regions.findIndex((r) => r.default));
667
- const { regionId } = await prompts({
668
- onState: onPromptState,
669
- type: "select",
670
- name: "regionId",
671
- message: "Which region should the new project run in?",
672
- choices: regions.map((region) => ({
673
- title: `${region.name} (${region.region_id})`,
674
- value: region.region_id
675
- })),
676
- initial: defaultIndex
677
- });
678
- return regionId;
679
- };
680
- const ORG_KEY_LIMITED_FRAGMENT = "not allowed for organization API keys";
681
- const isOrgKeyLimitedError = (err) => {
682
- if (!isNeonApiError(err)) return false;
683
- const message = messageFromBody(err.data);
684
- return typeof message === "string" && message.includes(ORG_KEY_LIMITED_FRAGMENT);
685
- };
686
- const fetchOrganizations = async (props) => {
687
- const { data } = await props.apiClient.getCurrentUserOrganizations();
688
- return data.organizations ?? [];
689
- };
690
- /**
691
- * Resolves the org id from the explicit flag, falling back to listing user orgs.
692
- *
693
- * For organization-scoped API keys, `getCurrentUserOrganizations` is forbidden;
694
- * in that case we try to auto-detect the org from the first existing project
695
- * (since all projects of an org key live in the same org). If no project exists
696
- * yet, we return `needs_selection` with `orgKeyLimited: true` so callers can
697
- * give a precise instruction to the user.
698
- */
699
- const resolveOrg = async (props, given) => {
700
- if (given) return {
701
- kind: "resolved",
702
- orgId: given,
703
- autoDetected: false
704
- };
705
- try {
706
- return {
707
- kind: "needs_selection",
708
- orgs: await fetchOrganizations(props),
709
- orgKeyLimited: false
710
- };
711
- } catch (err) {
712
- if (!isOrgKeyLimitedError(err)) throw err;
713
- log.debug("getCurrentUserOrganizations not allowed (org-scoped API key); attempting to derive org from existing projects.");
714
- }
715
- const detected = await detectOrgIdFromProjects(props);
716
- if (detected) return {
717
- kind: "resolved",
718
- orgId: detected,
719
- autoDetected: true
720
- };
721
- return {
722
- kind: "needs_selection",
723
- orgs: [],
724
- orgKeyLimited: true
725
- };
726
- };
727
- const detectOrgIdFromProjects = async (props) => {
728
- try {
729
- const { data } = await props.apiClient.listProjects({ limit: 1 });
730
- return data.projects[0]?.org_id ?? void 0;
731
- } catch (err) {
732
- const message = err instanceof Error ? err.message : String(err);
733
- log.debug("detectOrgIdFromProjects failed: %s", message);
734
- return;
735
- }
736
- };
737
- const listAllProjects = async (props, orgId) => {
738
- const result = [];
739
- let cursor;
740
- while (true) {
741
- const { data } = await props.apiClient.listProjects({
742
- limit: PROJECTS_LIST_LIMIT,
743
- org_id: orgId,
744
- cursor
745
- });
746
- result.push(...data.projects);
747
- cursor = data.pagination?.cursor;
748
- if (data.projects.length < PROJECTS_LIST_LIMIT) break;
749
- }
750
- return result;
751
- };
752
- const CANDIDATE_FIELDS = ["id", "name"];
753
- const extraYesFlags = (props, inputs) => {
754
- const flags = [];
755
- if (inputs.projectName) flags.push(`--project-name ${quoteFlagValue(inputs.projectName)}`);
756
- if (inputs.regionId) flags.push(`--region-id ${quoteFlagValue(inputs.regionId)}`);
757
- if (inputs.branch) flags.push(`--branch ${quoteFlagValue(inputs.branch)}`);
758
- return `${flags.length > 0 ? ` ${flags.join(" ")}` : ""}${sessionRetryFlags(props)}`;
759
- };
760
- const yesProjectIdCommand = (props, inputs, orgId = inputs.orgId) => {
761
- const orgFlag = orgId ? ` --org-id ${quoteFlagValue(orgId)}` : "";
762
- const branchFlag = inputs.branch ? ` --branch ${quoteFlagValue(inputs.branch)}` : "";
763
- return `${getCliName()} link -y${orgFlag} --project-id <project-id>${branchFlag}${sessionRetryFlags(props)}`;
764
- };
765
- const printNamedCandidates = (props, title, items) => {
766
- writer(props).end(items, {
767
- fields: CANDIDATE_FIELDS,
768
- title
769
- });
770
- };
771
- const orgScopedKeyNeedsOrgId = () => new LinkInputError("This API key is organization-scoped, so the CLI cannot list your organizations, and no existing project was found in this org to auto-detect the ID. Re-run with `--org-id <your_org_id>` (find it in the Neon Console under Settings).");
772
- const resolveYesOrgId = async (props, inputs) => {
773
- const orgResolution = await resolveOrg(props, inputs.orgId);
774
- if (orgResolution.kind === "resolved") {
775
- if (orgResolution.autoDetected) log.info(`Detected organization ${orgResolution.orgId} from your existing projects (organization-scoped API key).`);
776
- return orgResolution.orgId;
777
- }
778
- if (orgResolution.orgKeyLimited) throw orgScopedKeyNeedsOrgId();
779
- const orgs = orgResolution.orgs;
780
- if (orgs.length === 0) throw new LinkInputError(["No organizations were returned for this account. Pass --project-id for a project you can access:", ` ${yesProjectIdCommand(props, inputs)}`].join("\n"));
781
- if (orgs.length === 1) {
782
- const [only] = orgs;
783
- return only.id;
784
- }
785
- printNamedCandidates(props, "Organizations", orgs.map((org) => ({
786
- id: org.id,
787
- name: org.name
788
- })));
789
- throw new LinkInputError(["Multiple organizations are available. Pass --org-id with an ID from the list:", ` ${getCliName()} link -y --org-id <org-id>${extraYesFlags(props, inputs)}`].join("\n"));
790
- };
791
- const resolveYesInputs = async (props, inputs) => {
792
- const orgId = await resolveYesOrgId(props, inputs);
793
- if (inputs.projectName && inputs.regionId) return {
794
- ...inputs,
795
- orgId
796
- };
797
- const projects = await listAllProjects(props, orgId);
798
- if (projects.length === 0) throw new LinkInputError([
799
- `No projects are available in organization '${orgId}'.`,
800
- "To create and link a project, pass --project-name and --region-id:",
801
- ` ${getCliName()} link -y --org-id ${quoteFlagValue(orgId)} --project-name <name> --region-id aws-us-east-2${sessionRetryFlags(props)}`
802
- ].join("\n"));
803
- if (projects.length === 1) {
804
- const [only] = projects;
805
- return {
806
- ...inputs,
807
- orgId,
808
- projectId: only.id
809
- };
810
- }
811
- printNamedCandidates(props, "Projects", projects.map((project) => ({
812
- id: project.id,
813
- name: project.name
814
- })));
815
- throw new LinkInputError([`Multiple projects are available in organization '${orgId}'. Pass --project-id with an ID from the list:`, ` ${yesProjectIdCommand(props, inputs, orgId)}`].join("\n"));
816
- };
817
- const fetchRegions = async (props) => {
818
- try {
819
- const { data } = await props.apiClient.getActiveRegions();
820
- if (data.regions && data.regions.length > 0) return data.regions;
821
- } catch (err) {
822
- if (isNeonApiError(err)) log.debug("getActiveRegions failed (%s), falling back to the static region list.", err.status ?? err.code ?? err.message);
823
- else {
824
- const message = err instanceof Error ? err.message : String(err);
825
- log.debug("getActiveRegions failed (%s), falling back to the static region list.", message);
826
- }
827
- }
828
- return staticRegionsFallback();
829
- };
830
- const staticRegionsFallback = () => REGIONS.map((id) => ({
831
- region_id: id,
832
- name: id,
833
- default: id === "aws-us-east-2",
834
- geo_lat: "",
835
- geo_long: ""
836
- }));
837
- const createProject = async (props, args) => {
838
- const project = {
839
- name: args.name,
840
- region_id: args.regionId,
841
- org_id: args.orgId,
842
- branch: {}
843
- };
844
- const { data } = await props.apiClient.createProject({ project });
845
- if (!data.branch?.id) throw new Error("Project was created but the API response did not include a default branch id.");
846
- return {
847
- project: {
848
- id: data.project.id,
849
- name: data.project.name,
850
- region_id: data.project.region_id
851
- },
852
- branchId: data.branch.id,
853
- branchName: data.branch.name ?? data.branch.id
854
- };
855
- };
856
- const printSummary = (props, summary) => {
857
- process.stdout.write(`${formatLinkSummary(summary, props.cwd ?? process.cwd())}\n`);
858
- };
859
- /**
860
- * Print the link summary, then run the bundled `env pull` so a completed `link` ends with
861
- * the pinned branch's connection string on disk. `--no-checks` skips the pull because
862
- * nothing was verified. `--no-env-pull` opts out (env pull's own status / skip hint is
863
- * logged to stderr).
864
- */
865
- const finalizeLink = async (props, summary) => {
866
- printSummary(props, summary);
867
- if (!summary.branch || !summary.projectId || summary.noChecks || props.deferEnvPull === true) return;
868
- const { config: _offerConfig, ...rest } = props;
869
- await autoPullEnvAfterPin({
870
- ...rest,
871
- ...props.cwd ? { cwd: props.cwd } : {},
872
- projectId: summary.projectId,
873
- branch: summary.branch,
874
- envPull: props.envPull
875
- }, summary.branchRef);
876
- };
877
- /**
878
- * Interactive `link` finalize: the shared {@link finalizeLink} (summary + env
879
- * pull), then — as the last step — offer to manage the project's Neon setup as
880
- * code with a `neon.ts`. Kept out of {@link finalizeLink} so the non-interactive
881
- * paths never prompt.
882
- */
883
- const finalizeInteractiveLink = async (props, summary) => {
884
- await finalizeLink(props, summary);
885
- await maybeOfferConfigInit(props, summary);
886
- };
887
- /**
888
- * Interactive `link` offers neon.ts only when the directory has none and the
889
- * caller did not pass --no-config. Init always passes --no-config so it can ask
890
- * once after linking.
891
- */
892
- const shouldOfferConfigInit = (input) => !input.hasConfig && input.offer;
893
- const maybeOfferConfigInit = async (props, summary) => {
894
- const dir = projectDir({
895
- ...props.cwd ? { cwd: props.cwd } : {},
896
- contextFile: props.contextFile
897
- });
898
- if (!shouldOfferConfigInit({
899
- hasConfig: hasNeonConfigFile(dir),
900
- offer: props.config !== false
901
- })) return;
902
- const { value } = await prompts({
903
- onState: onPromptState,
904
- type: "confirm",
905
- name: "value",
906
- message: "Manage this project's Neon setup as code? Adds a neon.ts you can edit and apply with `neon config apply`.",
907
- initial: true
908
- });
909
- if (value !== true) return;
910
- await initCmd({
911
- cwd: dir,
912
- install: true
913
- });
914
- if (summary.branch && summary.projectId) {
915
- const { config: _offerConfig, ...rest } = props;
916
- await autoPullEnvAfterPin({
917
- ...rest,
918
- projectId: summary.projectId,
919
- branch: summary.branch,
920
- envPull: props.envPull
921
- }, summary.branchRef);
922
- }
923
- };
924
- const onPromptState = (state) => {
925
- if (state.aborted) {
926
- process.stdout.write("\x1B[?25h");
927
- process.stdout.write("\n");
928
- process.exit(1);
929
- }
930
- };
931
- const mustString = (value, name) => {
932
- if (value === void 0) throw new Error(`Internal error: expected ${name} to be set.`);
933
- return value;
934
- };
935
- //#endregion
936
- export { builder, command, describe, handler, quoteFlagValue, runLink, shouldOfferConfigInit, link_exports as t };
1
+ export { t as builder, n as command, r as describe, i as handler, o as quoteFlagValue, s as runLink, c as shouldOfferConfigInit } from "../_chunks/link-BgtkOBws.js";