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
package/dist/commands/env.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { NEON_ENV_VAR_KEYS } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
export const command =
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { NEON_ENV_VAR_KEYS } from "@neon/env";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { resolveNeonEnvVars } from "../dev/env.js";
|
|
5
|
+
import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
6
|
+
import { log } from "../log.js";
|
|
7
|
+
import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
8
|
+
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
9
|
+
export const command = "env";
|
|
10
10
|
export const describe = "Manage a branch's Neon env variables locally";
|
|
11
11
|
/**
|
|
12
12
|
* Shown (to stderr) when `link` / `checkout` skip the bundled env pull because the user passed
|
|
13
13
|
* `--no-env-pull`. Names the two ways to get the branch's vars without an on-disk file written
|
|
14
14
|
* eagerly: an explicit `neonctl env pull`, or runtime injection via `neon-env run`.
|
|
15
15
|
*/
|
|
16
|
-
export const ENV_PULL_SKIPPED_HINT =
|
|
17
|
-
|
|
16
|
+
export const ENV_PULL_SKIPPED_HINT = "Skipped env pull (--no-env-pull). Run `neonctl env pull` to write this branch’s env vars " +
|
|
17
|
+
"(DATABASE_URL, …) into a local .env, or inject them at runtime with `neon-env run -- <your dev command>`.";
|
|
18
18
|
export const builder = (argv) => argv
|
|
19
|
-
.usage(
|
|
19
|
+
.usage("$0 env <sub-command> [options]")
|
|
20
20
|
.options({
|
|
21
|
-
|
|
22
|
-
branch: { describe:
|
|
21
|
+
"project-id": { describe: "Project ID", type: "string" },
|
|
22
|
+
branch: { describe: "Branch ID or name", type: "string" },
|
|
23
23
|
})
|
|
24
24
|
.middleware(fillSingleProject)
|
|
25
|
-
.command(
|
|
26
|
-
.usage(
|
|
25
|
+
.command("pull", "Write the branch's Neon env variables to a local .env file", (yargs) => yargs
|
|
26
|
+
.usage("$0 env pull [options]")
|
|
27
27
|
.options({
|
|
28
28
|
file: {
|
|
29
|
-
describe:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
type:
|
|
29
|
+
describe: "Target .env file to write. Defaults to an existing .env, " +
|
|
30
|
+
"otherwise .env.local. Only Neon variables are updated; other " +
|
|
31
|
+
"lines are preserved.",
|
|
32
|
+
type: "string",
|
|
33
33
|
},
|
|
34
34
|
})
|
|
35
|
-
.example(
|
|
36
|
-
.example(
|
|
35
|
+
.example("$0 env pull", "Write the linked branch's Neon vars into .env.local (or .env if present)")
|
|
36
|
+
.example("$0 env pull --branch preview --file .env.preview", "Pull a specific branch into a specific file"), async (args) => {
|
|
37
37
|
// Explicit `env pull` announces the branch it's reading from up front so the user
|
|
38
38
|
// can catch "pulled env from the wrong branch" before it overwrites their .env. The
|
|
39
39
|
// bundled auto-pull (link / checkout / apply) stays quiet — those already report the
|
|
@@ -42,7 +42,7 @@ export const builder = (argv) => argv
|
|
|
42
42
|
})
|
|
43
43
|
.demandCommand(1);
|
|
44
44
|
export const handler = (args) => args;
|
|
45
|
-
/** Every OS-level env var name `@
|
|
45
|
+
/** Every OS-level env var name `@neon/env` can emit, used only for reporting. */
|
|
46
46
|
const NEON_VAR_NAMES = Object.values(NEON_ENV_VAR_KEYS).flatMap((group) => Object.values(group));
|
|
47
47
|
/**
|
|
48
48
|
* The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
|
|
@@ -68,7 +68,7 @@ export const pull = async (props, opts = {}) => {
|
|
|
68
68
|
const cwd = props.cwd ?? process.cwd();
|
|
69
69
|
const branch = await resolveBranchRef(props);
|
|
70
70
|
if (opts.announce) {
|
|
71
|
-
announceTargetBranch(props, branch,
|
|
71
|
+
announceTargetBranch(props, branch, "Pulling env from branch");
|
|
72
72
|
}
|
|
73
73
|
const branchId = branch.branchId;
|
|
74
74
|
// Resolve the target file first and layer its current contents under the resolver's env
|
|
@@ -91,9 +91,9 @@ export const pull = async (props, opts = {}) => {
|
|
|
91
91
|
});
|
|
92
92
|
const neonVars = pickNeonVars(vars);
|
|
93
93
|
if (Object.keys(neonVars).length === 0) {
|
|
94
|
-
log.info(
|
|
95
|
-
|
|
96
|
-
return { status:
|
|
94
|
+
log.info("No Neon env variables to pull for this branch (no DATABASE_URL or " +
|
|
95
|
+
"enabled Auth / Data API).");
|
|
96
|
+
return { status: "empty" };
|
|
97
97
|
}
|
|
98
98
|
// Reconcile rather than blindly merge: write the branch's current Neon vars and prune any
|
|
99
99
|
// Neon-owned vars the branch no longer has (e.g. NEON_AUTH_* / NEON_DATA_API_* carried over
|
|
@@ -101,11 +101,11 @@ export const pull = async (props, opts = {}) => {
|
|
|
101
101
|
const { written, removed } = mergeEnvFile(targetPath, neonVars, {
|
|
102
102
|
managedKeys: NEON_OWNED_ENV_KEYS,
|
|
103
103
|
});
|
|
104
|
-
log.info(
|
|
104
|
+
log.info("Pulled %d Neon variable%s into %s: %s", written.length, written.length === 1 ? "" : "s", targetPath, written.join(", "));
|
|
105
105
|
if (removed.length > 0) {
|
|
106
|
-
log.info(
|
|
106
|
+
log.info("Removed %d stale Neon variable%s not enabled on this branch: %s", removed.length, removed.length === 1 ? "" : "s", removed.join(", "));
|
|
107
107
|
}
|
|
108
|
-
return { status:
|
|
108
|
+
return { status: "written", written, file: targetPath };
|
|
109
109
|
};
|
|
110
110
|
/**
|
|
111
111
|
* Pull a freshly-pinned branch's Neon env vars into a local `.env`, bundled into `link` and
|
|
@@ -120,17 +120,17 @@ export const pull = async (props, opts = {}) => {
|
|
|
120
120
|
export const autoPullEnvAfterPin = async (props) => {
|
|
121
121
|
if (!props.envPull) {
|
|
122
122
|
log.info(chalk.dim(ENV_PULL_SKIPPED_HINT));
|
|
123
|
-
return { status:
|
|
123
|
+
return { status: "skipped" };
|
|
124
124
|
}
|
|
125
125
|
try {
|
|
126
126
|
return await pull(props);
|
|
127
127
|
}
|
|
128
128
|
catch (err) {
|
|
129
129
|
const message = err instanceof Error ? err.message : String(err);
|
|
130
|
-
log.warning(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return { status:
|
|
130
|
+
log.warning("Branch pinned, but pulling its Neon env vars failed: %s\n" +
|
|
131
|
+
"Run `neonctl env pull` once resolved (e.g. `neonctl deploy` if a declared service " +
|
|
132
|
+
"is missing), or inject them at runtime with `neon-env run -- <your dev command>`.", message);
|
|
133
|
+
return { status: "failed", message };
|
|
134
134
|
}
|
|
135
135
|
};
|
|
136
136
|
/**
|
|
@@ -139,14 +139,14 @@ export const autoPullEnvAfterPin = async (props) => {
|
|
|
139
139
|
*/
|
|
140
140
|
export const renderAgentPullNote = (result) => {
|
|
141
141
|
switch (result.status) {
|
|
142
|
-
case
|
|
143
|
-
return ` Pulled ${result.written.length} Neon env var${result.written.length === 1 ?
|
|
144
|
-
case
|
|
145
|
-
return
|
|
146
|
-
case
|
|
147
|
-
return (
|
|
148
|
-
|
|
149
|
-
case
|
|
142
|
+
case "written":
|
|
143
|
+
return ` Pulled ${result.written.length} Neon env var${result.written.length === 1 ? "" : "s"} into ${result.file}.`;
|
|
144
|
+
case "empty":
|
|
145
|
+
return " No Neon env vars to pull for this branch yet.";
|
|
146
|
+
case "skipped":
|
|
147
|
+
return (" Skipped env pull (--no-env-pull); run `neonctl env pull` later, " +
|
|
148
|
+
"or inject env at runtime with `neon-env run -- <your dev command>`.");
|
|
149
|
+
case "failed":
|
|
150
150
|
return ` Could not pull env vars (${result.message}); run \`neonctl env pull\` once resolved.`;
|
|
151
151
|
}
|
|
152
152
|
};
|
|
@@ -1,134 +1,133 @@
|
|
|
1
|
-
import { existsSync, statSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { log } from
|
|
6
|
-
import { branchIdFromProps, fillSingleProject } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { writer } from
|
|
10
|
-
import { createDeployment, deleteFunction, getFunction, listFunctions, } from '../functions_api.js';
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { isNeonApiError, retryOnLock } from "../api.js";
|
|
4
|
+
import { createDeployment, deleteFunction, getFunction, listFunctions, } from "../functions_api.js";
|
|
5
|
+
import { log } from "../log.js";
|
|
6
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
7
|
+
import { bundleEntry } from "../utils/esbuild.js";
|
|
8
|
+
import { zipBundle } from "../utils/zip.js";
|
|
9
|
+
import { writer } from "../writer.js";
|
|
11
10
|
const FUNCTION_FIELDS = [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
"slug",
|
|
12
|
+
"name",
|
|
13
|
+
"invocation_url",
|
|
14
|
+
"created_at",
|
|
16
15
|
];
|
|
17
16
|
const FUNCTIONS_LIST_LIMIT = 100;
|
|
18
17
|
// Table columns for `functions list`. `status` is a derived field (the
|
|
19
18
|
// table writer reads flat fields only): the current deployment's status.
|
|
20
19
|
const LIST_TABLE_FIELDS = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
"slug",
|
|
21
|
+
"name",
|
|
22
|
+
"status",
|
|
23
|
+
"invocation_url",
|
|
24
|
+
"created_at",
|
|
26
25
|
];
|
|
27
26
|
const DEPLOYMENT_FIELDS = [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
"id",
|
|
28
|
+
"status",
|
|
29
|
+
"runtime",
|
|
30
|
+
"memory_mib",
|
|
31
|
+
"created_at",
|
|
33
32
|
];
|
|
34
33
|
// Deploy emits the resolved deployment plus the function's invocation_url, so a
|
|
35
34
|
// successful `functions deploy` tells the user exactly where to call the function.
|
|
36
35
|
const DEPLOY_RESULT_FIELDS = [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
"id",
|
|
37
|
+
"status",
|
|
38
|
+
"invocation_url",
|
|
39
|
+
"runtime",
|
|
40
|
+
"memory_mib",
|
|
41
|
+
"created_at",
|
|
43
42
|
];
|
|
44
43
|
// In table mode a failed build's reason gets its own "deployment error"
|
|
45
44
|
// section after the deployment table; json/yaml carry the raw `error` field.
|
|
46
45
|
const writeDeploymentErrorSection = (out, dep) => {
|
|
47
|
-
if (dep.status ===
|
|
48
|
-
out.write({ reason: dep.error }, { fields: [
|
|
46
|
+
if (dep.status === "failed" && dep.error) {
|
|
47
|
+
out.write({ reason: dep.error }, { fields: ["reason"], title: "deployment error" });
|
|
49
48
|
}
|
|
50
49
|
};
|
|
51
50
|
const SLUG_PATTERN = /^[a-z0-9]{1,20}$/;
|
|
52
|
-
const SLUG_HELP =
|
|
51
|
+
const SLUG_HELP = "Use 1-20 lowercase letters and digits (no hyphens or other characters).";
|
|
53
52
|
// Entry-point discovery order inside --src.
|
|
54
|
-
const ENTRY_CANDIDATES = [
|
|
53
|
+
const ENTRY_CANDIDATES = ["index.ts", "index.mjs", "index.js"];
|
|
55
54
|
// Overridable so tests can poll fast; defaults to 2s in real use.
|
|
56
55
|
const POLL_INTERVAL_MS = Number(process.env.NEON_FUNCTIONS_POLL_INTERVAL_MS) || 2000;
|
|
57
56
|
// Upper bound on --wait polling so the CLI never hangs (e.g. if our deployment
|
|
58
57
|
// never shows up as current_deployment). Overridable so tests can time out fast;
|
|
59
58
|
// defaults to 10 minutes in real use.
|
|
60
59
|
const POLL_TIMEOUT_MS = Number(process.env.NEON_FUNCTIONS_POLL_TIMEOUT_MS) || 600000;
|
|
61
|
-
export const command =
|
|
62
|
-
export const describe =
|
|
63
|
-
export const aliases = [
|
|
60
|
+
export const command = "functions";
|
|
61
|
+
export const describe = "Manage Neon Functions";
|
|
62
|
+
export const aliases = ["function"];
|
|
64
63
|
export const builder = (argv) => argv
|
|
65
|
-
.usage(
|
|
64
|
+
.usage("$0 function <sub-command> [options]")
|
|
66
65
|
.options({
|
|
67
|
-
|
|
68
|
-
describe:
|
|
69
|
-
type:
|
|
66
|
+
"project-id": {
|
|
67
|
+
describe: "Project ID",
|
|
68
|
+
type: "string",
|
|
70
69
|
},
|
|
71
70
|
branch: {
|
|
72
|
-
describe:
|
|
73
|
-
type:
|
|
71
|
+
describe: "Branch ID or name",
|
|
72
|
+
type: "string",
|
|
74
73
|
},
|
|
75
74
|
})
|
|
76
75
|
.middleware(fillSingleProject)
|
|
77
|
-
.command(
|
|
78
|
-
.positional(
|
|
79
|
-
describe:
|
|
80
|
-
type:
|
|
76
|
+
.command("deploy <slug>", "Deploy a function from a local directory", (yargs) => yargs
|
|
77
|
+
.positional("slug", {
|
|
78
|
+
describe: "Function slug (1-20 lowercase letters and digits)",
|
|
79
|
+
type: "string",
|
|
81
80
|
demandOption: true,
|
|
82
81
|
})
|
|
83
82
|
.options({
|
|
84
83
|
src: {
|
|
85
|
-
describe:
|
|
86
|
-
type:
|
|
84
|
+
describe: "Function source: a directory containing index.ts, index.mjs, or index.js, or a path to the entry file",
|
|
85
|
+
type: "string",
|
|
87
86
|
},
|
|
88
87
|
// Removed flags, kept hidden so old invocations fail loudly instead
|
|
89
88
|
// of being silently ignored (the CLI has no .strictOptions()).
|
|
90
89
|
path: {
|
|
91
|
-
type:
|
|
90
|
+
type: "string",
|
|
92
91
|
hidden: true,
|
|
93
92
|
},
|
|
94
93
|
entry: {
|
|
95
|
-
type:
|
|
94
|
+
type: "string",
|
|
96
95
|
hidden: true,
|
|
97
96
|
},
|
|
98
97
|
runtime: {
|
|
99
|
-
describe:
|
|
100
|
-
type:
|
|
101
|
-
choices: [
|
|
98
|
+
describe: "Function runtime",
|
|
99
|
+
type: "string",
|
|
100
|
+
choices: ["nodejs24"],
|
|
102
101
|
},
|
|
103
102
|
env: {
|
|
104
|
-
describe:
|
|
105
|
-
type:
|
|
103
|
+
describe: "Environment variable as KEY=VALUE (repeatable)",
|
|
104
|
+
type: "string",
|
|
106
105
|
array: true,
|
|
107
106
|
},
|
|
108
107
|
wait: {
|
|
109
|
-
describe:
|
|
110
|
-
type:
|
|
108
|
+
describe: "Wait for the deployment to finish building",
|
|
109
|
+
type: "boolean",
|
|
111
110
|
default: true,
|
|
112
111
|
},
|
|
113
112
|
}), (args) => deploy(args))
|
|
114
|
-
.command(
|
|
115
|
-
.command(
|
|
116
|
-
.positional(
|
|
117
|
-
describe:
|
|
118
|
-
type:
|
|
113
|
+
.command("list", "List functions on the branch", (yargs) => yargs, (args) => list(args))
|
|
114
|
+
.command("get <slug>", "Show a function's details", (yargs) => yargs
|
|
115
|
+
.positional("slug", {
|
|
116
|
+
describe: "Function slug",
|
|
117
|
+
type: "string",
|
|
119
118
|
demandOption: true,
|
|
120
119
|
})
|
|
121
120
|
.options({
|
|
122
|
-
|
|
123
|
-
describe:
|
|
124
|
-
type:
|
|
125
|
-
alias:
|
|
121
|
+
"list-env-variables": {
|
|
122
|
+
describe: "List the environment variable names of the active deployment",
|
|
123
|
+
type: "boolean",
|
|
124
|
+
alias: "E",
|
|
126
125
|
default: false,
|
|
127
126
|
},
|
|
128
127
|
}), (args) => get(args))
|
|
129
|
-
.command(
|
|
130
|
-
describe:
|
|
131
|
-
type:
|
|
128
|
+
.command("delete <slug>", "Delete a function on the branch", (yargs) => yargs.positional("slug", {
|
|
129
|
+
describe: "Function slug",
|
|
130
|
+
type: "string",
|
|
132
131
|
demandOption: true,
|
|
133
132
|
}), (args) => deleteFn(args));
|
|
134
133
|
export const handler = (args) => {
|
|
@@ -139,7 +138,7 @@ const parseEnv = (entries) => {
|
|
|
139
138
|
return undefined;
|
|
140
139
|
const map = {};
|
|
141
140
|
for (const entry of entries) {
|
|
142
|
-
const eq = entry.indexOf(
|
|
141
|
+
const eq = entry.indexOf("=");
|
|
143
142
|
if (eq <= 0) {
|
|
144
143
|
throw new Error(`Invalid --env value "${entry}". Expected KEY=VALUE.`);
|
|
145
144
|
}
|
|
@@ -152,21 +151,19 @@ const statusHint = (slug, projectId, branchId) => `Check status with: neonctl fu
|
|
|
152
151
|
// deploy output shows where the function is reachable (not just the deployment id).
|
|
153
152
|
const emitDeployResult = (props, deployment, fn) => {
|
|
154
153
|
const out = writer(props).write({ ...deployment, invocation_url: fn?.invocation_url }, { fields: DEPLOY_RESULT_FIELDS });
|
|
155
|
-
if (props.output !==
|
|
154
|
+
if (props.output !== "json" && props.output !== "yaml") {
|
|
156
155
|
writeDeploymentErrorSection(out, deployment);
|
|
157
156
|
}
|
|
158
157
|
out.end();
|
|
159
158
|
};
|
|
160
159
|
// A poll error worth retrying: a network error (no HTTP response), a 5xx, or a
|
|
161
160
|
// 404 from eventual consistency. Anything else (e.g. 401/403) is surfaced.
|
|
162
|
-
const isTransient = (err) =>
|
|
163
|
-
(err.
|
|
164
|
-
err.response.status === 404 ||
|
|
165
|
-
err.response.status >= 500);
|
|
161
|
+
const isTransient = (err) => isNeonApiError(err) &&
|
|
162
|
+
(err.status === undefined || err.status === 404 || err.status >= 500);
|
|
166
163
|
const deploy = async (props) => {
|
|
167
164
|
if (props.path !== undefined || props.entry !== undefined) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
|
|
165
|
+
throw new Error("--path and --entry were removed. Use --src <dir>; the entry point " +
|
|
166
|
+
"is discovered as index.ts, index.mjs, or index.js in that directory.");
|
|
170
167
|
}
|
|
171
168
|
// At least one deploy option must be passed (--wait is excluded: it controls
|
|
172
169
|
// output, not what gets deployed).
|
|
@@ -174,15 +171,15 @@ const deploy = async (props) => {
|
|
|
174
171
|
props.env !== undefined ||
|
|
175
172
|
props.runtime !== undefined;
|
|
176
173
|
if (!hasOption) {
|
|
177
|
-
throw new Error(
|
|
178
|
-
|
|
174
|
+
throw new Error("Provide at least one option to deploy, e.g. --src or --env. " +
|
|
175
|
+
"See: neonctl function deploy --help.");
|
|
179
176
|
}
|
|
180
177
|
// Cheap, offline validation first - fail before any network round-trip.
|
|
181
178
|
if (!SLUG_PATTERN.test(props.slug)) {
|
|
182
179
|
throw new Error(`Invalid function slug "${props.slug}". ${SLUG_HELP}`);
|
|
183
180
|
}
|
|
184
|
-
const src = props.src ??
|
|
185
|
-
const runtime = props.runtime ??
|
|
181
|
+
const src = props.src ?? ".";
|
|
182
|
+
const runtime = props.runtime ?? "nodejs24";
|
|
186
183
|
const environment = parseEnv(props.env);
|
|
187
184
|
const srcStat = statSync(src, { throwIfNoEntry: false });
|
|
188
185
|
if (srcStat === undefined) {
|
|
@@ -193,7 +190,7 @@ const deploy = async (props) => {
|
|
|
193
190
|
? src
|
|
194
191
|
: ENTRY_CANDIDATES.map((name) => join(src, name)).find((p) => existsSync(p));
|
|
195
192
|
if (source === undefined) {
|
|
196
|
-
throw new Error(`No entry file found in ${src}. Expected one of: ${ENTRY_CANDIDATES.join(
|
|
193
|
+
throw new Error(`No entry file found in ${src}. Expected one of: ${ENTRY_CANDIDATES.join(", ")}.`);
|
|
197
194
|
}
|
|
198
195
|
// Bundle before any network round-trip so a bundling failure fails fast.
|
|
199
196
|
const zip = zipBundle(await bundleEntry(source));
|
|
@@ -206,7 +203,7 @@ const deploy = async (props) => {
|
|
|
206
203
|
before = fn.current_deployment?.id;
|
|
207
204
|
}
|
|
208
205
|
catch (err) {
|
|
209
|
-
if (!(
|
|
206
|
+
if (!(isNeonApiError(err) && err.status === 404))
|
|
210
207
|
throw err;
|
|
211
208
|
}
|
|
212
209
|
await retryOnLock(() => createDeployment(props.apiClient, props.projectId, branchId, props.slug, {
|
|
@@ -221,8 +218,8 @@ const deploy = async (props) => {
|
|
|
221
218
|
const onSignal = () => {
|
|
222
219
|
interrupted = true;
|
|
223
220
|
};
|
|
224
|
-
process.once(
|
|
225
|
-
process.once(
|
|
221
|
+
process.once("SIGINT", onSignal);
|
|
222
|
+
process.once("SIGTERM", onSignal);
|
|
226
223
|
// Poll until a NEW version appears (id greater than the snapshot, or
|
|
227
224
|
// any version if there was none). --no-wait stops there; --wait stops at a
|
|
228
225
|
// terminal status. Bounded by POLL_TIMEOUT_MS so it never hangs.
|
|
@@ -254,14 +251,14 @@ const deploy = async (props) => {
|
|
|
254
251
|
resolvedFn = fn;
|
|
255
252
|
if (!props.wait)
|
|
256
253
|
break;
|
|
257
|
-
if (dep.status ===
|
|
254
|
+
if (dep.status === "completed" || dep.status === "failed")
|
|
258
255
|
break;
|
|
259
256
|
}
|
|
260
257
|
}
|
|
261
258
|
}
|
|
262
259
|
finally {
|
|
263
|
-
process.removeListener(
|
|
264
|
-
process.removeListener(
|
|
260
|
+
process.removeListener("SIGINT", onSignal);
|
|
261
|
+
process.removeListener("SIGTERM", onSignal);
|
|
265
262
|
}
|
|
266
263
|
if (interrupted) {
|
|
267
264
|
log.info(statusHint(props.slug, props.projectId, branchId));
|
|
@@ -278,11 +275,11 @@ const deploy = async (props) => {
|
|
|
278
275
|
log.info(statusHint(props.slug, props.projectId, branchId));
|
|
279
276
|
return;
|
|
280
277
|
}
|
|
281
|
-
if (resolved.status ===
|
|
278
|
+
if (resolved.status === "completed") {
|
|
282
279
|
log.info(`Function deployment ${props.slug}/${resolved.id} completed.`);
|
|
283
280
|
return;
|
|
284
281
|
}
|
|
285
|
-
if (resolved.status ===
|
|
282
|
+
if (resolved.status === "failed") {
|
|
286
283
|
throw new Error(`Function deployment ${props.slug}/${resolved.id} failed.`);
|
|
287
284
|
}
|
|
288
285
|
// --wait, new version appeared but the deadline hit before it finished.
|
|
@@ -292,20 +289,20 @@ const deploy = async (props) => {
|
|
|
292
289
|
const get = async (props) => {
|
|
293
290
|
const branchId = await branchIdFromProps(props);
|
|
294
291
|
const fn = await getFunction(props.apiClient, props.projectId, branchId, props.slug);
|
|
295
|
-
if (props.output ===
|
|
292
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
296
293
|
writer(props).end(fn, { fields: FUNCTION_FIELDS });
|
|
297
294
|
return;
|
|
298
295
|
}
|
|
299
296
|
const out = writer(props).write(fn, {
|
|
300
297
|
fields: FUNCTION_FIELDS,
|
|
301
|
-
title:
|
|
298
|
+
title: "function",
|
|
302
299
|
});
|
|
303
300
|
const current = fn.current_deployment;
|
|
304
301
|
const active = fn.active_deployment;
|
|
305
302
|
if (current && active && current.id === active.id) {
|
|
306
303
|
out.write(current, {
|
|
307
304
|
fields: DEPLOYMENT_FIELDS,
|
|
308
|
-
title:
|
|
305
|
+
title: "deployment (current, active)",
|
|
309
306
|
});
|
|
310
307
|
writeDeploymentErrorSection(out, current);
|
|
311
308
|
}
|
|
@@ -313,7 +310,7 @@ const get = async (props) => {
|
|
|
313
310
|
if (current) {
|
|
314
311
|
out.write(current, {
|
|
315
312
|
fields: DEPLOYMENT_FIELDS,
|
|
316
|
-
title:
|
|
313
|
+
title: "current deployment",
|
|
317
314
|
});
|
|
318
315
|
// The failure reason is shown only for the current deployment;
|
|
319
316
|
// the active one completed successfully by definition.
|
|
@@ -322,15 +319,15 @@ const get = async (props) => {
|
|
|
322
319
|
if (active) {
|
|
323
320
|
out.write(active, {
|
|
324
321
|
fields: DEPLOYMENT_FIELDS,
|
|
325
|
-
title:
|
|
322
|
+
title: "active deployment",
|
|
326
323
|
});
|
|
327
324
|
}
|
|
328
325
|
}
|
|
329
326
|
if (props.listEnvVariables) {
|
|
330
327
|
out.write((fn.active_deployment?.environment ?? []).map((name) => ({ name })), {
|
|
331
|
-
fields: [
|
|
332
|
-
title:
|
|
333
|
-
emptyMessage:
|
|
328
|
+
fields: ["name"],
|
|
329
|
+
title: "environment",
|
|
330
|
+
emptyMessage: "No environment variables on the active deployment.",
|
|
334
331
|
});
|
|
335
332
|
}
|
|
336
333
|
out.end();
|
|
@@ -341,7 +338,7 @@ const deleteFn = async (props) => {
|
|
|
341
338
|
await retryOnLock(() => deleteFunction(props.apiClient, props.projectId, branchId, props.slug));
|
|
342
339
|
}
|
|
343
340
|
catch (err) {
|
|
344
|
-
if (
|
|
341
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
345
342
|
throw new Error(`Function "${props.slug}" not found on branch ${branchId}.`);
|
|
346
343
|
}
|
|
347
344
|
throw err;
|
|
@@ -355,22 +352,22 @@ const list = async (props) => {
|
|
|
355
352
|
for (;;) {
|
|
356
353
|
const page = await listFunctions(props.apiClient, props.projectId, branchId, { cursor, limit: FUNCTIONS_LIST_LIMIT });
|
|
357
354
|
functions.push(...page.functions);
|
|
358
|
-
log.debug(
|
|
355
|
+
log.debug("Got %d functions, next cursor: %s", page.functions.length, page.next);
|
|
359
356
|
// A server echoing the same cursor would loop forever; treat it as
|
|
360
357
|
// the end of the list.
|
|
361
358
|
if (!page.next || page.next === cursor)
|
|
362
359
|
break;
|
|
363
360
|
cursor = page.next;
|
|
364
361
|
}
|
|
365
|
-
if (props.output ===
|
|
362
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
366
363
|
writer(props).end(functions, { fields: FUNCTION_FIELDS });
|
|
367
364
|
return;
|
|
368
365
|
}
|
|
369
366
|
writer(props).end(functions.map((fn) => ({
|
|
370
367
|
...fn,
|
|
371
|
-
status: fn.current_deployment?.status ??
|
|
368
|
+
status: fn.current_deployment?.status ?? "",
|
|
372
369
|
})), {
|
|
373
370
|
fields: LIST_TABLE_FIELDS,
|
|
374
|
-
emptyMessage:
|
|
371
|
+
emptyMessage: "No functions found on this branch.",
|
|
375
372
|
});
|
|
376
373
|
};
|
package/dist/commands/index.js
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
import * as auth from
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
7
|
-
import * as
|
|
8
|
-
import * as
|
|
9
|
-
import * as
|
|
10
|
-
import * as
|
|
11
|
-
import * as
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
14
|
-
import * as
|
|
15
|
-
import * as
|
|
16
|
-
import * as
|
|
17
|
-
import * as
|
|
18
|
-
import * as
|
|
19
|
-
import * as
|
|
20
|
-
import * as
|
|
21
|
-
import * as
|
|
22
|
-
import * as
|
|
23
|
-
import * as
|
|
24
|
-
import * as
|
|
25
|
-
import * as
|
|
1
|
+
import * as auth from "./auth.js";
|
|
2
|
+
import * as bootstrap from "./bootstrap.js";
|
|
3
|
+
import * as branches from "./branches.js";
|
|
4
|
+
import * as bucket from "./bucket.js";
|
|
5
|
+
import * as checkout from "./checkout.js";
|
|
6
|
+
import * as config from "./config.js";
|
|
7
|
+
import * as cs from "./connection_string.js";
|
|
8
|
+
import * as dataApi from "./data_api.js";
|
|
9
|
+
import * as databases from "./databases.js";
|
|
10
|
+
import * as deploy from "./deploy.js";
|
|
11
|
+
import * as dev from "./dev.js";
|
|
12
|
+
import * as env from "./env.js";
|
|
13
|
+
import * as functions from "./functions.js";
|
|
14
|
+
import * as init from "./init.js";
|
|
15
|
+
import * as ipAllow from "./ip_allow.js";
|
|
16
|
+
import * as link from "./link.js";
|
|
17
|
+
import * as neonAuth from "./neon_auth.js";
|
|
18
|
+
import * as operations from "./operations.js";
|
|
19
|
+
import * as orgs from "./orgs.js";
|
|
20
|
+
import * as projects from "./projects.js";
|
|
21
|
+
import * as psql from "./psql.js";
|
|
22
|
+
import * as roles from "./roles.js";
|
|
23
|
+
import * as setContext from "./set_context.js";
|
|
24
|
+
import * as status from "./status.js";
|
|
25
|
+
import * as users from "./user.js";
|
|
26
|
+
import * as vpcEndpoints from "./vpc_endpoints.js";
|
|
26
27
|
export default [
|
|
27
28
|
auth,
|
|
28
29
|
users,
|
|
@@ -45,6 +46,7 @@ export default [
|
|
|
45
46
|
functions,
|
|
46
47
|
dev,
|
|
47
48
|
config,
|
|
49
|
+
status,
|
|
48
50
|
deploy,
|
|
49
51
|
env,
|
|
50
52
|
bucket,
|