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.
- package/README.md +35 -3
- package/dist/analytics.js +52 -34
- package/dist/api.js +643 -13
- package/dist/auth.js +50 -44
- package/dist/cli.js +8 -1
- package/dist/commands/auth.js +64 -51
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +160 -150
- package/dist/commands/bucket.js +183 -146
- package/dist/commands/checkout.js +51 -51
- package/dist/commands/config.js +228 -82
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +100 -101
- package/dist/commands/databases.js +29 -26
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +101 -104
- package/dist/commands/index.js +27 -25
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +232 -182
- package/dist/commands/neon_auth.js +385 -370
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +103 -101
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +27 -24
- package/dist/commands/schema_diff.js +25 -26
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +37 -14
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +68 -5
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +110 -107
- package/dist/log.js +2 -2
- package/dist/parameters.gen.js +14 -14
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +22 -23
- package/dist/test_utils/fixtures.js +74 -41
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +28 -16
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +10 -12
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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(
|
|
25
|
+
const comparingWithParent = props.compareSource.startsWith("^parent");
|
|
26
26
|
let baseBranchPoint = {
|
|
27
27
|
branchId: baseBranch,
|
|
28
|
-
tag:
|
|
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 (
|
|
72
|
-
|
|
73
|
-
|
|
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,
|
|
83
|
-
.replace(/^-.*/gm, colorizer(
|
|
84
|
-
.replace(/^\+.*/gm, colorizer(
|
|
85
|
-
.replace(/^@@.+@@.*/gm, colorizer(
|
|
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
|
|
92
|
+
case "timestamp":
|
|
94
93
|
return {
|
|
95
94
|
timestamp: pointInTime.timestamp,
|
|
96
95
|
};
|
|
97
|
-
case
|
|
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
|
|
107
|
+
case "timestamp":
|
|
109
108
|
return `${header} at ${pointInTime.timestamp})`;
|
|
110
|
-
case
|
|
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 =
|
|
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(
|
|
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 =
|
|
147
|
+
props.compareSource = "^parent";
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
150
|
return props;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { applyContext } from
|
|
2
|
-
import { log } from
|
|
3
|
-
export const command =
|
|
4
|
-
export const describe =
|
|
5
|
-
export const builder = (argv) => argv.usage(
|
|
6
|
-
|
|
7
|
-
describe:
|
|
8
|
-
type:
|
|
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
|
-
|
|
11
|
-
describe:
|
|
12
|
-
type:
|
|
10
|
+
"org-id": {
|
|
11
|
+
describe: "Organization ID",
|
|
12
|
+
type: "string",
|
|
13
13
|
},
|
|
14
|
-
|
|
15
|
-
describe:
|
|
16
|
-
type:
|
|
14
|
+
"branch-id": {
|
|
15
|
+
describe: "Branch ID",
|
|
16
|
+
type: "string",
|
|
17
17
|
},
|
|
18
18
|
});
|
|
19
19
|
export const handler = (props) => {
|
|
20
|
-
log.warning(
|
|
21
|
-
|
|
22
|
-
|
|
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);
|
package/dist/commands/user.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { writer } from
|
|
2
|
-
export const command =
|
|
3
|
-
export const describe =
|
|
4
|
-
export const builder = (yargs) => yargs.option(
|
|
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: [
|
|
13
|
+
fields: ["login", "email", "name", "projects_limit"],
|
|
14
14
|
});
|
|
15
15
|
};
|
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
const VPC_ENDPOINT_FIELDS = [
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
"vpc_endpoint_id",
|
|
8
|
+
"label",
|
|
9
|
+
"state",
|
|
10
|
+
"num_restricted_projects",
|
|
11
|
+
"example_restricted_projects",
|
|
12
12
|
];
|
|
13
|
-
export const command =
|
|
14
|
-
export const describe =
|
|
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(
|
|
18
|
-
.command(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
describe:
|
|
27
|
-
type:
|
|
25
|
+
"org-id": {
|
|
26
|
+
describe: "Organization ID",
|
|
27
|
+
type: "string",
|
|
28
28
|
},
|
|
29
|
-
|
|
30
|
-
describe: `The region ID. Possible values: ${REGIONS.join(
|
|
31
|
-
type:
|
|
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(
|
|
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:
|
|
41
|
-
aliases: [
|
|
42
|
-
describe:
|
|
43
|
-
|
|
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:
|
|
47
|
-
type:
|
|
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(
|
|
54
|
+
.command("remove <id>", "Remove a VPC endpoint from this organization.", (yargs) => yargs, async (args) => {
|
|
55
55
|
await removeOrg(args);
|
|
56
56
|
})
|
|
57
|
-
.command(
|
|
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(
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
describe:
|
|
68
|
-
type:
|
|
66
|
+
"project-id": {
|
|
67
|
+
describe: "Project ID",
|
|
68
|
+
type: "string",
|
|
69
69
|
},
|
|
70
70
|
})
|
|
71
71
|
.middleware(fillSingleProject)
|
|
72
|
-
.command(
|
|
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:
|
|
77
|
-
aliases: [
|
|
78
|
-
describe:
|
|
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:
|
|
82
|
-
type:
|
|
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(
|
|
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(
|
|
107
|
-
log.info(
|
|
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 {
|
|
2
|
-
import { homedir } from
|
|
3
|
-
import {
|
|
4
|
-
import { isCi } from
|
|
5
|
-
export const CREDENTIALS_FILE =
|
|
6
|
-
export const defaultDir = join(process.env.XDG_CONFIG_HOME || join(homedir(),
|
|
7
|
-
export const ensureConfigDir = ({
|
|
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
|
}
|
package/dist/config_format.js
CHANGED
|
@@ -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 `@
|
|
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
|
-
[
|
|
10
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[
|
|
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
|
|
2
|
-
import { homedir } from
|
|
3
|
-
import { dirname, normalize, resolve } from
|
|
4
|
-
import { log } from
|
|
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
|
-
|
|
12
|
-
|
|
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,
|
|
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] ===
|
|
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,
|
|
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(
|
|
140
|
-
const addition = `${needsLeadingNewline ?
|
|
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(
|
|
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
|
+
};
|