neonctl 2.27.1 → 2.29.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 (136) hide show
  1. package/README.md +35 -3
  2. package/dist/analytics.js +52 -34
  3. package/dist/api.js +643 -13
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +8 -1
  6. package/dist/commands/auth.js +64 -51
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +160 -150
  9. package/dist/commands/bucket.js +183 -146
  10. package/dist/commands/checkout.js +51 -51
  11. package/dist/commands/config.js +228 -82
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +100 -101
  14. package/dist/commands/databases.js +29 -26
  15. package/dist/commands/deploy.js +12 -12
  16. package/dist/commands/dev.js +114 -114
  17. package/dist/commands/env.js +43 -43
  18. package/dist/commands/functions.js +101 -104
  19. package/dist/commands/index.js +27 -25
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +232 -182
  23. package/dist/commands/neon_auth.js +385 -370
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +103 -101
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +27 -24
  29. package/dist/commands/schema_diff.js +25 -26
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +40 -0
  32. package/dist/commands/user.js +5 -5
  33. package/dist/commands/vpc_endpoints.js +50 -50
  34. package/dist/config.js +7 -7
  35. package/dist/config_format.js +5 -5
  36. package/dist/context.js +37 -14
  37. package/dist/current_branch_fast_path.js +55 -0
  38. package/dist/dev/env.js +33 -33
  39. package/dist/dev/functions.js +4 -4
  40. package/dist/dev/inputs.js +6 -6
  41. package/dist/dev/runtime.js +25 -25
  42. package/dist/env.js +14 -14
  43. package/dist/env_file.js +13 -13
  44. package/dist/errors.js +68 -5
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +110 -107
  48. package/dist/log.js +2 -2
  49. package/dist/parameters.gen.js +14 -14
  50. package/dist/pkg.js +5 -5
  51. package/dist/psql/cli.js +4 -2
  52. package/dist/psql/command/cmd_cond.js +61 -61
  53. package/dist/psql/command/cmd_connect.js +159 -154
  54. package/dist/psql/command/cmd_copy.js +107 -97
  55. package/dist/psql/command/cmd_describe.js +368 -363
  56. package/dist/psql/command/cmd_format.js +276 -263
  57. package/dist/psql/command/cmd_io.js +269 -263
  58. package/dist/psql/command/cmd_lo.js +74 -66
  59. package/dist/psql/command/cmd_meta.js +148 -148
  60. package/dist/psql/command/cmd_misc.js +17 -17
  61. package/dist/psql/command/cmd_pipeline.js +142 -135
  62. package/dist/psql/command/cmd_restrict.js +25 -25
  63. package/dist/psql/command/cmd_show.js +183 -168
  64. package/dist/psql/command/dispatch.js +26 -26
  65. package/dist/psql/command/shared.js +14 -14
  66. package/dist/psql/complete/filenames.js +16 -16
  67. package/dist/psql/complete/index.js +4 -4
  68. package/dist/psql/complete/matcher.js +33 -32
  69. package/dist/psql/complete/psqlVars.js +173 -173
  70. package/dist/psql/complete/queries.js +5 -3
  71. package/dist/psql/complete/rules.js +900 -863
  72. package/dist/psql/core/common.js +136 -133
  73. package/dist/psql/core/help.js +343 -343
  74. package/dist/psql/core/mainloop.js +160 -153
  75. package/dist/psql/core/prompt.js +126 -123
  76. package/dist/psql/core/settings.js +111 -111
  77. package/dist/psql/core/sqlHelp.js +150 -150
  78. package/dist/psql/core/startup.js +211 -205
  79. package/dist/psql/core/syncVars.js +14 -14
  80. package/dist/psql/core/variables.js +24 -24
  81. package/dist/psql/describe/formatters.js +302 -289
  82. package/dist/psql/describe/processNamePattern.js +28 -28
  83. package/dist/psql/describe/queries.js +656 -651
  84. package/dist/psql/index.js +436 -411
  85. package/dist/psql/io/history.js +36 -36
  86. package/dist/psql/io/input.js +15 -15
  87. package/dist/psql/io/lineEditor/buffer.js +27 -25
  88. package/dist/psql/io/lineEditor/complete.js +15 -15
  89. package/dist/psql/io/lineEditor/filename.js +22 -22
  90. package/dist/psql/io/lineEditor/index.js +65 -62
  91. package/dist/psql/io/lineEditor/keymap.js +325 -318
  92. package/dist/psql/io/lineEditor/vt100.js +60 -60
  93. package/dist/psql/io/pgpass.js +18 -18
  94. package/dist/psql/io/pgservice.js +14 -14
  95. package/dist/psql/io/psqlrc.js +46 -46
  96. package/dist/psql/print/aligned.js +175 -166
  97. package/dist/psql/print/asciidoc.js +51 -51
  98. package/dist/psql/print/crosstab.js +34 -31
  99. package/dist/psql/print/csv.js +25 -22
  100. package/dist/psql/print/html.js +54 -54
  101. package/dist/psql/print/json.js +12 -12
  102. package/dist/psql/print/latex.js +118 -118
  103. package/dist/psql/print/pager.js +28 -26
  104. package/dist/psql/print/troff.js +48 -48
  105. package/dist/psql/print/unaligned.js +15 -14
  106. package/dist/psql/print/units.js +17 -17
  107. package/dist/psql/scanner/slash.js +48 -46
  108. package/dist/psql/scanner/sql.js +88 -84
  109. package/dist/psql/scanner/stringutils.js +21 -17
  110. package/dist/psql/types/index.js +7 -7
  111. package/dist/psql/types/scanner.js +8 -8
  112. package/dist/psql/wire/connection.js +341 -327
  113. package/dist/psql/wire/copy.js +7 -7
  114. package/dist/psql/wire/pipeline.js +26 -24
  115. package/dist/psql/wire/protocol.js +102 -102
  116. package/dist/psql/wire/sasl.js +62 -62
  117. package/dist/psql/wire/tls.js +79 -73
  118. package/dist/storage_api.js +22 -23
  119. package/dist/test_utils/fixtures.js +74 -41
  120. package/dist/test_utils/oauth_server.js +5 -5
  121. package/dist/utils/api_enums.js +33 -0
  122. package/dist/utils/branch_notice.js +5 -5
  123. package/dist/utils/branch_picker.js +26 -26
  124. package/dist/utils/compute_units.js +4 -4
  125. package/dist/utils/enrichers.js +28 -16
  126. package/dist/utils/esbuild.js +28 -28
  127. package/dist/utils/formats.js +1 -1
  128. package/dist/utils/middlewares.js +3 -3
  129. package/dist/utils/package_manager.js +68 -0
  130. package/dist/utils/point_in_time.js +12 -12
  131. package/dist/utils/psql.js +30 -30
  132. package/dist/utils/string.js +2 -2
  133. package/dist/utils/ui.js +9 -9
  134. package/dist/utils/zip.js +1 -1
  135. package/dist/writer.js +17 -17
  136. package/package.json +10 -12
@@ -1,11 +1,11 @@
1
- import { createPatch } from 'diff';
2
- import chalk from 'chalk';
3
- import { writer } from '../writer.js';
4
- import { branchIdFromProps } from '../utils/enrichers.js';
5
- import { parsePointInTime, } from '../utils/point_in_time.js';
6
- import { isAxiosError } from 'axios';
7
- import { sendError } from '../analytics.js';
8
- import { log } from '../log.js';
1
+ import chalk from "chalk";
2
+ import { createPatch } from "diff";
3
+ import { sendError } from "../analytics.js";
4
+ import { isNeonApiError, messageFromBody } from "../api.js";
5
+ import { log } from "../log.js";
6
+ import { branchIdFromProps } from "../utils/enrichers.js";
7
+ import { parsePointInTime, } from "../utils/point_in_time.js";
8
+ import { writer } from "../writer.js";
9
9
  const COLORS = {
10
10
  added: chalk.green,
11
11
  removed: chalk.red,
@@ -22,10 +22,10 @@ export const schemaDiff = async (props) => {
22
22
  api: props.apiClient,
23
23
  });
24
24
  // Swap base and compare points if comparing with parent branch
25
- const comparingWithParent = props.compareSource.startsWith('^parent');
25
+ const comparingWithParent = props.compareSource.startsWith("^parent");
26
26
  let baseBranchPoint = {
27
27
  branchId: baseBranch,
28
- tag: 'head',
28
+ tag: "head",
29
29
  };
30
30
  [baseBranchPoint, pointInTime] = comparingWithParent
31
31
  ? [pointInTime, baseBranchPoint]
@@ -65,13 +65,12 @@ const fetchSchema = async (pointInTime, database, props) => {
65
65
  db_name: database.name,
66
66
  ...pointInTimeParams(pointInTime),
67
67
  });
68
- return response.data.sql ?? '';
68
+ return response.data.sql ?? "";
69
69
  }
70
70
  catch (error) {
71
- if (isAxiosError(error)) {
72
- const data = error.response?.data;
73
- sendError(error, 'API_ERROR');
74
- throw new Error(data.message ??
71
+ if (isNeonApiError(error)) {
72
+ sendError(error, "API_ERROR");
73
+ throw new Error(messageFromBody(error.data) ??
75
74
  `Error while fetching schema for branch ${pointInTime.branchId}`);
76
75
  }
77
76
  throw error;
@@ -79,10 +78,10 @@ const fetchSchema = async (pointInTime, database, props) => {
79
78
  };
80
79
  const colorize = (patch) => {
81
80
  return patch
82
- .replace(/^([^\n]+)\n([^\n]+)\n/m, '') // Remove first two lines
83
- .replace(/^-.*/gm, colorizer('removed'))
84
- .replace(/^\+.*/gm, colorizer('added'))
85
- .replace(/^@@.+@@.*/gm, colorizer('section'));
81
+ .replace(/^([^\n]+)\n([^\n]+)\n/m, "") // Remove first two lines
82
+ .replace(/^-.*/gm, colorizer("removed"))
83
+ .replace(/^\+.*/gm, colorizer("added"))
84
+ .replace(/^@@.+@@.*/gm, colorizer("section"));
86
85
  };
87
86
  const colorizer = (colorId) => {
88
87
  const color = COLORS[colorId];
@@ -90,11 +89,11 @@ const colorizer = (colorId) => {
90
89
  };
91
90
  const pointInTimeParams = (pointInTime) => {
92
91
  switch (pointInTime.tag) {
93
- case 'timestamp':
92
+ case "timestamp":
94
93
  return {
95
94
  timestamp: pointInTime.timestamp,
96
95
  };
97
- case 'lsn':
96
+ case "lsn":
98
97
  return {
99
98
  lsn: pointInTime.lsn ?? undefined,
100
99
  };
@@ -105,9 +104,9 @@ const pointInTimeParams = (pointInTime) => {
105
104
  const generateHeader = (pointInTime) => {
106
105
  const header = `(Branch: ${pointInTime.branchId}`;
107
106
  switch (pointInTime.tag) {
108
- case 'timestamp':
107
+ case "timestamp":
109
108
  return `${header} at ${pointInTime.timestamp})`;
110
- case 'lsn':
109
+ case "lsn":
111
110
  return `${header} at ${pointInTime.lsn})`;
112
111
  default:
113
112
  return `${header})`;
@@ -134,7 +133,7 @@ export const parseSchemaDiffParams = async (props) => {
134
133
  throw new Error(`No branch specified. Your context branch (${props.branch}) has no parent, so no comparison is possible.`);
135
134
  }
136
135
  log.info(`No branches specified. Comparing your context branch '${props.branch}' with its parent`);
137
- props.compareSource = '^parent';
136
+ props.compareSource = "^parent";
138
137
  }
139
138
  else {
140
139
  const { data } = await props.apiClient.listProjectBranches({
@@ -142,10 +141,10 @@ export const parseSchemaDiffParams = async (props) => {
142
141
  });
143
142
  const defaultBranch = data.branches.find((b) => b.default);
144
143
  if (defaultBranch?.parent_id == undefined) {
145
- throw new Error('No branch specified. Include a base branch or add a set-context branch to continue. Your default branch has no parent, so no comparison is possible.');
144
+ throw new Error("No branch specified. Include a base branch or add a set-context branch to continue. Your default branch has no parent, so no comparison is possible.");
146
145
  }
147
146
  log.info(`No branches specified. Comparing default branch with its parent`);
148
- props.compareSource = '^parent';
147
+ props.compareSource = "^parent";
149
148
  }
150
149
  }
151
150
  return props;
@@ -1,25 +1,25 @@
1
- import { applyContext } from '../context.js';
2
- import { log } from '../log.js';
3
- export const command = 'set-context';
4
- export const describe = 'Deprecated: use `neonctl link`. Set the .neon context (raw write).';
5
- export const builder = (argv) => argv.usage('$0 set-context [options]').options({
6
- 'project-id': {
7
- describe: 'Project ID',
8
- type: 'string',
1
+ import { applyContext } from "../context.js";
2
+ import { log } from "../log.js";
3
+ export const command = "set-context";
4
+ export const describe = "Deprecated: use `neonctl link`. Set the .neon context (raw write).";
5
+ export const builder = (argv) => argv.usage("$0 set-context [options]").options({
6
+ "project-id": {
7
+ describe: "Project ID",
8
+ type: "string",
9
9
  },
10
- 'org-id': {
11
- describe: 'Organization ID',
12
- type: 'string',
10
+ "org-id": {
11
+ describe: "Organization ID",
12
+ type: "string",
13
13
  },
14
- 'branch-id': {
15
- describe: 'Branch ID',
16
- type: 'string',
14
+ "branch-id": {
15
+ describe: "Branch ID",
16
+ type: "string",
17
17
  },
18
18
  });
19
19
  export const handler = (props) => {
20
- log.warning('`neonctl set-context` is deprecated and will be removed in a future release. ' +
21
- 'Use `neonctl link` instead — it verifies inputs and infers the org for you ' +
22
- '(or `neonctl link --no-checks` for the same write-without-checks behavior).');
20
+ log.warning("`neonctl set-context` is deprecated and will be removed in a future release. " +
21
+ "Use `neonctl link` instead — it verifies inputs and infers the org for you " +
22
+ "(or `neonctl link --no-checks` for the same write-without-checks behavior).");
23
23
  const context = {
24
24
  projectId: props.projectId,
25
25
  orgId: props.orgId,
@@ -0,0 +1,40 @@
1
+ import { fillSingleProject } from "../utils/enrichers.js";
2
+ import { status } from "./config.js";
3
+ /**
4
+ * `neon status` is a top-level alias for `neon config status` — the most-reached-for
5
+ * config subcommand. It mirrors that command's options (including `--current-branch`,
6
+ * the offline branch probe) and delegates to the same `status` handler.
7
+ *
8
+ * Because it has a handler but no subcommands, `status` must also be listed in
9
+ * `NO_SUBCOMMANDS_VERBS` (see index.ts) so the help-fallback middleware doesn't
10
+ * intercept a bare `neon status`.
11
+ */
12
+ export const command = "status";
13
+ export const describe = "Show the branch's live Neon state (alias of `config status`)";
14
+ export const builder = (argv) => argv
15
+ .usage("$0 status [options]")
16
+ .options({
17
+ "project-id": {
18
+ describe: "Project ID",
19
+ type: "string",
20
+ },
21
+ branch: {
22
+ describe: "Branch ID or name",
23
+ type: "string",
24
+ },
25
+ "config-json": {
26
+ describe: "Print only the branch's live config as neon.ts-shaped JSON " +
27
+ "(services + branch tuning + preview), to stdout. Useful for " +
28
+ "scripting or copying into a neon.ts.",
29
+ type: "boolean",
30
+ default: false,
31
+ },
32
+ "current-branch": {
33
+ describe: "Print only the linked branch name from the local .neon file " +
34
+ "(no network). Exits non-zero when no branch is pinned.",
35
+ type: "boolean",
36
+ default: false,
37
+ },
38
+ })
39
+ .middleware(fillSingleProject);
40
+ export const handler = (args) => status(args);
@@ -1,7 +1,7 @@
1
- import { writer } from '../writer.js';
2
- export const command = 'me';
3
- export const describe = 'Show current user';
4
- export const builder = (yargs) => yargs.option('context-file', {
1
+ import { writer } from "../writer.js";
2
+ export const command = "me";
3
+ export const describe = "Show current user";
4
+ export const builder = (yargs) => yargs.option("context-file", {
5
5
  hidden: true,
6
6
  });
7
7
  export const handler = async (args) => {
@@ -10,6 +10,6 @@ export const handler = async (args) => {
10
10
  const me = async (props) => {
11
11
  const { data } = await props.apiClient.getCurrentUserInfo();
12
12
  writer(props).end(data, {
13
- fields: ['login', 'email', 'name', 'projects_limit'],
13
+ fields: ["login", "email", "name", "projects_limit"],
14
14
  });
15
15
  };
@@ -1,92 +1,92 @@
1
- import { writer } from '../writer.js';
2
- import { fillSingleProject, fillSingleOrg } from '../utils/enrichers.js';
3
- import { REGIONS } from './projects.js';
4
- import { log } from '../log.js';
5
- const VPC_ENDPOINT_FIELDS = ['vpc_endpoint_id', 'label'];
1
+ import { log } from "../log.js";
2
+ import { fillSingleOrg, fillSingleProject } from "../utils/enrichers.js";
3
+ import { writer } from "../writer.js";
4
+ import { REGIONS } from "./projects.js";
5
+ const VPC_ENDPOINT_FIELDS = ["vpc_endpoint_id", "label"];
6
6
  const VPC_ENDPOINT_DETAILS_FIELDS = [
7
- 'vpc_endpoint_id',
8
- 'label',
9
- 'state',
10
- 'num_restricted_projects',
11
- 'example_restricted_projects',
7
+ "vpc_endpoint_id",
8
+ "label",
9
+ "state",
10
+ "num_restricted_projects",
11
+ "example_restricted_projects",
12
12
  ];
13
- export const command = 'vpc';
14
- export const describe = 'Manage VPC endpoints and project VPC restrictions';
13
+ export const command = "vpc";
14
+ export const describe = "Manage VPC endpoints and project VPC restrictions";
15
15
  export const builder = (argv) => {
16
16
  return argv
17
- .usage('$0 vpc <sub-command> [options]')
18
- .command('endpoint', 'Manage VPC endpoints.\n' +
19
- 'See: https://neon.tech/docs/guides/neon-private-networking\n' +
20
- 'After adding an endpoint to an organization, client connections will be accepted\n' +
21
- 'from the corresponding VPC for all projects in the organization, unless overridden\n' +
22
- 'by a project-level VPC endpoint restriction.', (yargs) => {
17
+ .usage("$0 vpc <sub-command> [options]")
18
+ .command("endpoint", "Manage VPC endpoints.\n" +
19
+ "See: https://neon.tech/docs/guides/neon-private-networking\n" +
20
+ "After adding an endpoint to an organization, client connections will be accepted\n" +
21
+ "from the corresponding VPC for all projects in the organization, unless overridden\n" +
22
+ "by a project-level VPC endpoint restriction.", (yargs) => {
23
23
  return yargs
24
24
  .options({
25
- 'org-id': {
26
- describe: 'Organization ID',
27
- type: 'string',
25
+ "org-id": {
26
+ describe: "Organization ID",
27
+ type: "string",
28
28
  },
29
- 'region-id': {
30
- describe: `The region ID. Possible values: ${REGIONS.join(', ')}`,
31
- type: 'string',
29
+ "region-id": {
30
+ describe: `The region ID. Possible values: ${REGIONS.join(", ")}`,
31
+ type: "string",
32
32
  demandOption: true,
33
33
  },
34
34
  })
35
35
  .middleware(fillSingleOrg)
36
- .command('list', 'List configured VPC endpoints for this organization.', (yargs) => yargs, async (args) => {
36
+ .command("list", "List configured VPC endpoints for this organization.", (yargs) => yargs, async (args) => {
37
37
  await listOrg(args);
38
38
  })
39
39
  .command({
40
- command: 'assign <id>',
41
- aliases: ['update <id>', 'add <id>'],
42
- describe: 'Add or update a VPC endpoint for this organization.\n' +
43
- 'Note: Azure regions are not yet supported.',
40
+ command: "assign <id>",
41
+ aliases: ["update <id>", "add <id>"],
42
+ describe: "Add or update a VPC endpoint for this organization.\n" +
43
+ "Note: Azure regions are not yet supported.",
44
44
  builder: (yargs) => yargs.options({
45
45
  label: {
46
- describe: 'An optional descriptive label for the VPC endpoint',
47
- type: 'string',
46
+ describe: "An optional descriptive label for the VPC endpoint",
47
+ type: "string",
48
48
  },
49
49
  }),
50
50
  handler: async (args) => {
51
51
  await assignOrg(args);
52
52
  },
53
53
  })
54
- .command('remove <id>', 'Remove a VPC endpoint from this organization.', (yargs) => yargs, async (args) => {
54
+ .command("remove <id>", "Remove a VPC endpoint from this organization.", (yargs) => yargs, async (args) => {
55
55
  await removeOrg(args);
56
56
  })
57
- .command('status <id>', 'Get the status of a VPC endpoint for this organization.', (yargs) => yargs, async (args) => {
57
+ .command("status <id>", "Get the status of a VPC endpoint for this organization.", (yargs) => yargs, async (args) => {
58
58
  await statusOrg(args);
59
59
  });
60
60
  })
61
- .command('project', 'Manage project-level VPC endpoint restrictions.\n' +
62
- 'By default, connections are accepted from any VPC configured at the organization level.\n' +
63
- 'A project-level VPC endpoint restriction can be used to restrict connections to a specific VPC.', (yargs) => {
61
+ .command("project", "Manage project-level VPC endpoint restrictions.\n" +
62
+ "By default, connections are accepted from any VPC configured at the organization level.\n" +
63
+ "A project-level VPC endpoint restriction can be used to restrict connections to a specific VPC.", (yargs) => {
64
64
  return yargs
65
65
  .options({
66
- 'project-id': {
67
- describe: 'Project ID',
68
- type: 'string',
66
+ "project-id": {
67
+ describe: "Project ID",
68
+ type: "string",
69
69
  },
70
70
  })
71
71
  .middleware(fillSingleProject)
72
- .command('list', 'List VPC endpoint restrictions for this project.', (yargs) => yargs, async (args) => {
72
+ .command("list", "List VPC endpoint restrictions for this project.", (yargs) => yargs, async (args) => {
73
73
  await listProject(args);
74
74
  })
75
75
  .command({
76
- command: 'restrict <id>',
77
- aliases: ['update <id>'],
78
- describe: 'Configure or update a VPC endpoint restriction for this project.',
76
+ command: "restrict <id>",
77
+ aliases: ["update <id>"],
78
+ describe: "Configure or update a VPC endpoint restriction for this project.",
79
79
  builder: (yargs) => yargs.options({
80
80
  label: {
81
- describe: 'An optional descriptive label for the VPC endpoint restriction',
82
- type: 'string',
81
+ describe: "An optional descriptive label for the VPC endpoint restriction",
82
+ type: "string",
83
83
  },
84
84
  }),
85
85
  handler: async (args) => {
86
86
  await assignProject(args);
87
87
  },
88
88
  })
89
- .command('remove <id>', 'Remove a VPC endpoint restriction from this project.', (yargs) => yargs, async (args) => {
89
+ .command("remove <id>", "Remove a VPC endpoint restriction from this project.", (yargs) => yargs, async (args) => {
90
90
  await removeProject(args);
91
91
  });
92
92
  });
@@ -99,12 +99,12 @@ const listOrg = async (props) => {
99
99
  };
100
100
  const assignOrg = async (props) => {
101
101
  const vpcEndpointAssignment = {
102
- label: props.label || '',
102
+ label: props.label || "",
103
103
  };
104
104
  const { data } = await props.apiClient.assignOrganizationVpcEndpoint(props.orgId, props.regionId, props.id, vpcEndpointAssignment);
105
105
  writer(props).end(data, { fields: [] });
106
- if (props.regionId.startsWith('azure')) {
107
- log.info('VPC endpoint configuration is not supported for Azure regions');
106
+ if (props.regionId.startsWith("azure")) {
107
+ log.info("VPC endpoint configuration is not supported for Azure regions");
108
108
  }
109
109
  };
110
110
  const removeOrg = async (props) => {
@@ -123,7 +123,7 @@ const listProject = async (props) => {
123
123
  };
124
124
  const assignProject = async (props) => {
125
125
  const vpcEndpointAssignment = {
126
- label: props.label || '',
126
+ label: props.label || "",
127
127
  };
128
128
  const { data } = await props.apiClient.assignProjectVpcEndpoint(props.projectId, props.id, vpcEndpointAssignment);
129
129
  writer(props).end(data, { fields: [] });
package/dist/config.js CHANGED
@@ -1,10 +1,10 @@
1
- import { join } from 'node:path';
2
- import { homedir } from 'node:os';
3
- import { existsSync, mkdirSync } from 'node:fs';
4
- import { isCi } from './env.js';
5
- export const CREDENTIALS_FILE = 'credentials.json';
6
- export const defaultDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'neonctl');
7
- export const ensureConfigDir = ({ 'config-dir': configDir, 'force-auth': forceAuth, }) => {
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { isCi } from "./env.js";
5
+ export const CREDENTIALS_FILE = "credentials.json";
6
+ export const defaultDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "neonctl");
7
+ export const ensureConfigDir = ({ "config-dir": configDir, "force-auth": forceAuth, }) => {
8
8
  if (!existsSync(configDir) && (!isCi() || forceAuth)) {
9
9
  mkdirSync(configDir, { recursive: true });
10
10
  }
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Render a TTL in whole seconds back to the canonical `neon.ts` duration string (e.g.
3
3
  * `604800` -> `"7d"`), falling back to seconds when no clean unit boundary matches. Mirrors
4
- * the formatter `@neondatabase/config` uses when it emits a TTL, so `config status` shows
4
+ * the formatter `@neon/config` uses when it emits a TTL, so `config status` shows
5
5
  * the same value a user would write in `neon.ts`.
6
6
  */
7
7
  export const formatDurationSeconds = (totalSeconds) => {
8
8
  const units = [
9
- ['w', 7 * 24 * 60 * 60],
10
- ['d', 24 * 60 * 60],
11
- ['h', 60 * 60],
12
- ['m', 60],
9
+ ["w", 7 * 24 * 60 * 60],
10
+ ["d", 24 * 60 * 60],
11
+ ["h", 60 * 60],
12
+ ["m", 60],
13
13
  ];
14
14
  for (const [unit, perUnit] of units) {
15
15
  if (totalSeconds % perUnit === 0)
package/dist/context.js CHANGED
@@ -1,15 +1,38 @@
1
- import { accessSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { dirname, normalize, resolve } from 'node:path';
4
- import { log } from './log.js';
1
+ import { accessSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, normalize, resolve } from "node:path";
4
+ import { log } from "./log.js";
5
5
  /**
6
6
  * The branch pinned in a context, reading the current `branch` field and
7
7
  * falling back to the legacy `branchId` so pre-migration `.neon` files keep
8
8
  * working.
9
9
  */
10
10
  export const contextBranch = (context) => context.branch ?? context.branchId;
11
- const CONTEXT_FILE = '.neon';
12
- const GITIGNORE_FILE = '.gitignore';
11
+ /**
12
+ * True when the invocation is the offline "current branch" probe:
13
+ * `(config) status --current-branch`. This mode only reads the pinned branch
14
+ * from the local `.neon` file (for shell prompts like starship), so it MUST
15
+ * NOT touch the network — several middlewares (auth, analytics, single-project
16
+ * resolution) consult this to early-return and skip their API calls / login.
17
+ *
18
+ * Gated on the exact command as well as the flag so an accidental
19
+ * `--current-branch` on an unrelated command (e.g. `config plan`, where the flag
20
+ * is undefined but non-strict yargs still parses it) can't silently skip
21
+ * auth/analytics. The probe is only `status` (the top-level alias) or
22
+ * `config status` (`_ = ['config', 'status']`).
23
+ */
24
+ export const isCurrentBranchProbe = (args) => args.currentBranch === true &&
25
+ (args._[0] === "status" ||
26
+ (args._[0] === "config" && args._[1] === "status"));
27
+ /**
28
+ * `config init` only scaffolds a local `neon.ts` and installs npm packages — it
29
+ * never calls the Neon API. Gated on the exact command path so the global auth
30
+ * middleware and the single-project resolver can skip it (it runs with no API
31
+ * client), mirroring {@link isCurrentBranchProbe}.
32
+ */
33
+ export const isConfigInit = (args) => args._[0] === "config" && args._[1] === "init";
34
+ const CONTEXT_FILE = ".neon";
35
+ const GITIGNORE_FILE = ".gitignore";
13
36
  const wrapWithContextFile = (dir) => resolve(dir, CONTEXT_FILE);
14
37
  /**
15
38
  * Resolve the default `.neon` path for the current working directory.
@@ -31,7 +54,7 @@ const wrapWithContextFile = (dir) => resolve(dir, CONTEXT_FILE);
31
54
  */
32
55
  export const currentContextFile = (cwd = process.cwd()) => {
33
56
  let currentDir = cwd;
34
- const root = normalize('/');
57
+ const root = normalize("/");
35
58
  const home = homedir();
36
59
  while (currentDir !== root && currentDir !== home) {
37
60
  try {
@@ -41,13 +64,13 @@ export const currentContextFile = (cwd = process.cwd()) => {
41
64
  catch {
42
65
  // ignore
43
66
  }
44
- currentDir = resolve(currentDir, '..');
67
+ currentDir = resolve(currentDir, "..");
45
68
  }
46
69
  return wrapWithContextFile(cwd);
47
70
  };
48
71
  export const readContextFile = (file) => {
49
72
  try {
50
- return JSON.parse(readFileSync(file, 'utf-8'));
73
+ return JSON.parse(readFileSync(file, "utf-8"));
51
74
  }
52
75
  catch {
53
76
  return {};
@@ -57,7 +80,7 @@ export const enrichFromContext = (args) => {
57
80
  // `link` and the deprecated `set-context` manage the context file themselves
58
81
  // and must see the raw flags rather than values pre-filled from an existing
59
82
  // `.neon`, so skip enrichment for both.
60
- if (args._[0] === 'link' || args._[0] === 'set-context') {
83
+ if (args._[0] === "link" || args._[0] === "set-context") {
61
84
  return;
62
85
  }
63
86
  const context = readContextFile(args.contextFile);
@@ -132,17 +155,17 @@ export const ensureGitignored = (file) => {
132
155
  writeFileSync(gitignorePath, `${entry}\n`);
133
156
  return;
134
157
  }
135
- const current = readFileSync(gitignorePath, 'utf-8');
158
+ const current = readFileSync(gitignorePath, "utf-8");
136
159
  if (hasGitignoreEntry(current, entry)) {
137
160
  return;
138
161
  }
139
- const needsLeadingNewline = current.length > 0 && !current.endsWith('\n');
140
- const addition = `${needsLeadingNewline ? '\n' : ''}${entry}\n`;
162
+ const needsLeadingNewline = current.length > 0 && !current.endsWith("\n");
163
+ const addition = `${needsLeadingNewline ? "\n" : ""}${entry}\n`;
141
164
  writeFileSync(gitignorePath, current + addition);
142
165
  }
143
166
  catch (err) {
144
167
  const message = err instanceof Error ? err.message : String(err);
145
- log.debug('Failed to update .gitignore next to %s: %s', file, message);
168
+ log.debug("Failed to update .gitignore next to %s: %s", file, message);
146
169
  }
147
170
  };
148
171
  const basenameOf = (file) => {
@@ -0,0 +1,55 @@
1
+ import { contextBranch, currentContextFile, readContextFile, } from "./context.js";
2
+ import { log } from "./log.js";
3
+ /**
4
+ * Offline fast path for `(config) status --current-branch` (used by shell prompts).
5
+ *
6
+ * Reading the pinned branch out of the local `.neon` file does not need the CLI's
7
+ * full command tree, `@neondatabase/api-client`, or yargs — importing those is ~200ms,
8
+ * which dwarfs the actual work. So the entry point ({@link file://./cli.ts}) calls this
9
+ * BEFORE importing `index.js`, and only falls through to the full CLI when this returns
10
+ * `false`. On the fast path the process loads only this module + `context.js`/`log.js`
11
+ * (~25ms total incl. Node startup) instead of ~230ms.
12
+ *
13
+ * It mirrors the `--current-branch` short-circuit in `status()` (commands/config.ts):
14
+ * print the pinned branch to stdout and exit 0, or print nothing + a `neonctl checkout`
15
+ * hint on stderr and exit non-zero when no branch is pinned.
16
+ *
17
+ * Deliberately conservative: only the EXACT bare invocation is handled —
18
+ * `status --current-branch` or `config status --current-branch` with no other args.
19
+ * Anything else (extra flags like `--context-file`/`--output`, more args, etc.) returns
20
+ * `false` and flows through the normal yargs pipeline, so behavior can never diverge —
21
+ * the worst case is "not faster", never "wrong".
22
+ *
23
+ * @returns `true` if it handled the invocation (caller should NOT load the full CLI).
24
+ */
25
+ export const tryCurrentBranchFastPath = (argv,
26
+ // `cwd` is overridable so tests can exercise the `.neon` walk-up without mutating
27
+ // `process.cwd()` (which isn't allowed in vitest workers), mirroring currentContextFile.
28
+ cwd = process.cwd()) => {
29
+ // argv is [execPath, scriptPath, ...userArgs].
30
+ if (!isExactCurrentBranchInvocation(argv.slice(2))) {
31
+ return false;
32
+ }
33
+ const branch = contextBranch(readContextFile(currentContextFile(cwd)));
34
+ if (branch) {
35
+ process.stdout.write(`${branch}\n`);
36
+ }
37
+ else {
38
+ log.info("No branch pinned. Run `neonctl checkout <branch>` to pin a branch and pull its env vars.");
39
+ process.exitCode = 1;
40
+ }
41
+ return true;
42
+ };
43
+ /**
44
+ * True only for `status --current-branch` or `config status --current-branch` with no
45
+ * other arguments. Any extra token (another flag, `--context-file`, `=`-style flags,
46
+ * positional args) makes this false so the full CLI handles it.
47
+ */
48
+ const isExactCurrentBranchInvocation = (args) => {
49
+ const rest = args[0] === "status"
50
+ ? args.slice(1)
51
+ : args[0] === "config" && args[1] === "status"
52
+ ? args.slice(2)
53
+ : null;
54
+ return rest !== null && rest.length === 1 && rest[0] === "--current-branch";
55
+ };