neon 8.11.1 → 8.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/_chunks/api-GbEaBZJ5.js +388 -0
- package/dist/_chunks/api_keys-CNnnugbQ.js +281 -0
- package/dist/_chunks/ask-DVwy0eUL.js +155 -0
- package/dist/_chunks/auth-CIUtXByB.js +495 -0
- package/dist/_chunks/bootstrap-wPZWa1sT.js +499 -0
- package/dist/_chunks/branches-BNa1xX1L.js +460 -0
- package/dist/_chunks/bucket-BCdryTkD.js +409 -0
- package/dist/_chunks/checkout-CXIOydqZ.js +348 -0
- package/dist/_chunks/claim-DqAvAvDm.js +473 -0
- package/dist/_chunks/config-BH_qRHiG.js +1113 -0
- package/dist/_chunks/connection_string-DB4awPzZ.js +154 -0
- package/dist/_chunks/credentials-BSjE9fRR.js +146 -0
- package/dist/_chunks/data_api-Djxj5ZQD.js +263 -0
- package/dist/_chunks/databases-Dok4wru7.js +84 -0
- package/dist/_chunks/deploy-D7B0Xfjg.js +33 -0
- package/dist/_chunks/dev-CSvxAecE.js +945 -0
- package/dist/_chunks/diff-DhxxbdQE.js +178 -0
- package/dist/_chunks/docs-CrVybkTP.js +197 -0
- package/dist/_chunks/env-5HS5qQGd.js +284 -0
- package/dist/_chunks/feedback-CMayyTa8.js +88 -0
- package/dist/_chunks/functions-Q0zuvtnk.js +464 -0
- package/dist/_chunks/git-H32TNt6M.js +356 -0
- package/dist/_chunks/init-BkA6RilG.js +111 -0
- package/dist/_chunks/inspect-g0wekylj.js +90 -0
- package/dist/_chunks/ip_allow-5lLFvIuE.js +106 -0
- package/dist/_chunks/link-BgtkOBws.js +936 -0
- package/dist/_chunks/logs-DWkpjzIH.js +291 -0
- package/dist/_chunks/mcp-Bo-7TKc7.js +253 -0
- package/dist/_chunks/neon_auth-CBs1_3qu.js +822 -0
- package/dist/_chunks/open-CX3BT3Tp.js +33 -0
- package/dist/_chunks/operations-D4I-AxVs.js +37 -0
- package/dist/_chunks/orgs-xGfANwHQ.js +52 -0
- package/dist/_chunks/plugins-DqD7BDO1.js +169 -0
- package/dist/_chunks/profile-yXOiKk9R.js +672 -0
- package/dist/_chunks/projects-BwI2Uo0z.js +439 -0
- package/dist/_chunks/psql-BM-doe8e.js +64 -0
- package/dist/_chunks/realtime-BRqIyGR-.js +79 -0
- package/dist/_chunks/roles-B4w3YT_7.js +79 -0
- package/dist/_chunks/set_context-DbrwoImy.js +38 -0
- package/dist/_chunks/skills-DyiBXKS3.js +255 -0
- package/dist/_chunks/snapshots-RJBzW8GM.js +338 -0
- package/dist/_chunks/status-DV8Z2kw3.js +44 -0
- package/dist/_chunks/triggers-BrRo1z4b.js +302 -0
- package/dist/_chunks/user-dlOGXbX9.js +60 -0
- package/dist/_chunks/vpc_endpoints-CNb0YfMo.js +105 -0
- package/dist/auth_recovery.js +1 -1
- package/dist/commands/api.js +1 -388
- package/dist/commands/api_keys.js +1 -281
- package/dist/commands/ask.js +1 -155
- package/dist/commands/auth.js +1 -495
- package/dist/commands/bootstrap.js +1 -499
- package/dist/commands/branches.js +1 -460
- package/dist/commands/bucket.js +1 -409
- package/dist/commands/checkout.js +1 -348
- package/dist/commands/claim.js +1 -473
- package/dist/commands/config.js +1 -1113
- package/dist/commands/connection_string.js +1 -154
- package/dist/commands/credentials.js +1 -146
- package/dist/commands/data_api.js +1 -263
- package/dist/commands/databases.js +1 -84
- package/dist/commands/deploy.js +1 -33
- package/dist/commands/dev.js +1 -945
- package/dist/commands/diff.js +1 -178
- package/dist/commands/docs.js +1 -197
- package/dist/commands/env.js +1 -284
- package/dist/commands/feedback.js +1 -88
- package/dist/commands/functions.js +1 -464
- package/dist/commands/functions_new.js +1 -1
- package/dist/commands/git.js +1 -356
- package/dist/commands/index.js +45 -45
- package/dist/commands/init.js +1 -111
- package/dist/commands/inspect.js +1 -90
- package/dist/commands/ip_allow.js +1 -106
- package/dist/commands/link.js +1 -936
- package/dist/commands/logs.js +1 -291
- package/dist/commands/mcp.js +1 -253
- package/dist/commands/neon_auth.js +1 -822
- package/dist/commands/open.js +1 -33
- package/dist/commands/operations.js +1 -37
- package/dist/commands/orgs.js +1 -52
- package/dist/commands/plugins.js +1 -169
- package/dist/commands/profile.js +1 -672
- package/dist/commands/projects.js +1 -439
- package/dist/commands/psql.js +1 -64
- package/dist/commands/realtime.js +1 -79
- package/dist/commands/roles.js +1 -79
- package/dist/commands/set_context.js +1 -38
- package/dist/commands/skills.js +1 -255
- package/dist/commands/snapshots.js +1 -338
- package/dist/commands/status.js +1 -44
- package/dist/commands/subscribe.js +1 -1
- package/dist/commands/triggers.js +1 -302
- package/dist/commands/user.js +1 -60
- package/dist/commands/vpc_endpoints.js +1 -105
- package/dist/functions/registry.js +29 -151
- package/dist/functions/source.js +42 -2
- package/dist/index.js +1 -1
- package/dist/init/auth.js +3 -3
- package/dist/init/link.js +2 -2
- package/dist/init/run.js +3 -3
- package/dist/init/tooling.js +3 -3
- package/dist/mcp/mint.js +1 -1
- package/dist/test_utils/registry.js +1 -2
- package/package.json +8 -6
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { isNetworkError } from "../errors.js";
|
|
3
|
+
import { writer } from "../writer.js";
|
|
4
|
+
import { noPassthrough, single } from "../utils/flags.js";
|
|
5
|
+
//#region src/commands/feedback.ts
|
|
6
|
+
var feedback_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
DEFAULT_FEEDBACK_URL: () => DEFAULT_FEEDBACK_URL,
|
|
8
|
+
MAX_FEEDBACK_LENGTH: () => MAX_FEEDBACK_LENGTH,
|
|
9
|
+
builder: () => builder,
|
|
10
|
+
command: () => command,
|
|
11
|
+
describe: () => describe,
|
|
12
|
+
handler: () => handler,
|
|
13
|
+
resolveFeedbackUrl: () => resolveFeedbackUrl
|
|
14
|
+
});
|
|
15
|
+
const DEFAULT_FEEDBACK_URL = "https://feedback.neon.tech/";
|
|
16
|
+
/** The feedback service truncates longer messages, so the CLI rejects them instead. */
|
|
17
|
+
const MAX_FEEDBACK_LENGTH = 1e4;
|
|
18
|
+
const FEEDBACK_SOURCE = "neon_cli";
|
|
19
|
+
const FEEDBACK_TIMEOUT_MS = 3e4;
|
|
20
|
+
const command = "feedback";
|
|
21
|
+
const describe = "Send feedback to Neon";
|
|
22
|
+
const builder = (argv) => argv.usage("$0 feedback --message <feedback>").option("message", {
|
|
23
|
+
describe: "The feedback to send. Do not include passwords, API keys, or connection strings",
|
|
24
|
+
type: "string",
|
|
25
|
+
demandOption: true,
|
|
26
|
+
coerce: single("message", { required: true })
|
|
27
|
+
}).option("url", {
|
|
28
|
+
describe: "Override the feedback URL",
|
|
29
|
+
type: "string",
|
|
30
|
+
hidden: true,
|
|
31
|
+
coerce: single("url")
|
|
32
|
+
}).strict().check(noPassthrough("feedback")).check((args) => {
|
|
33
|
+
if ((typeof args.message === "string" ? args.message : "").trim().length > 1e4) throw new Error(`--message must be ${MAX_FEEDBACK_LENGTH.toLocaleString("en-US")} characters or fewer`);
|
|
34
|
+
return true;
|
|
35
|
+
}).example("$0 feedback --message \"The branch docs were unclear\"", describe);
|
|
36
|
+
function resolveFeedbackUrl(opts) {
|
|
37
|
+
const fromFlag = opts.url?.trim();
|
|
38
|
+
if (fromFlag) return fromFlag;
|
|
39
|
+
const fromEnv = opts.envUrl?.trim();
|
|
40
|
+
if (fromEnv) return fromEnv;
|
|
41
|
+
return DEFAULT_FEEDBACK_URL;
|
|
42
|
+
}
|
|
43
|
+
const handler = async (props) => {
|
|
44
|
+
await sendFeedback({
|
|
45
|
+
message: props.message.trim(),
|
|
46
|
+
url: resolveFeedbackUrl({
|
|
47
|
+
url: props.url,
|
|
48
|
+
envUrl: process.env.NEON_FEEDBACK_URL
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
52
|
+
writer(props).end({ received: true }, { fields: ["received"] });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
writer(props).text("Feedback received. Thank you!\n");
|
|
56
|
+
};
|
|
57
|
+
function isTimeout(error) {
|
|
58
|
+
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
59
|
+
}
|
|
60
|
+
async function feedbackErrorMessage(response) {
|
|
61
|
+
if (response.status === 429) return "Too many feedback requests. Wait a minute and try again.";
|
|
62
|
+
try {
|
|
63
|
+
const body = await response.json();
|
|
64
|
+
if (typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" && body.error.trim() !== "") return body.error;
|
|
65
|
+
} catch {}
|
|
66
|
+
return `The Neon feedback service returned ${response.status}.`;
|
|
67
|
+
}
|
|
68
|
+
async function sendFeedback(opts) {
|
|
69
|
+
let response;
|
|
70
|
+
try {
|
|
71
|
+
response = await fetch(opts.url, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "Content-Type": "application/json" },
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
feedback: opts.message,
|
|
76
|
+
source: FEEDBACK_SOURCE
|
|
77
|
+
}),
|
|
78
|
+
signal: AbortSignal.timeout(FEEDBACK_TIMEOUT_MS)
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (isTimeout(error)) throw new Error("The Neon feedback service did not respond in time.");
|
|
82
|
+
if (isNetworkError(error)) throw new Error("Could not reach the Neon feedback service. Check your internet connection and try again.");
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (!response.ok) throw new Error(await feedbackErrorMessage(response));
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { describe as a, resolveFeedbackUrl as c, command as i, MAX_FEEDBACK_LENGTH as n, feedback_exports as o, builder as r, handler as s, DEFAULT_FEEDBACK_URL as t };
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { log } from "../log.js";
|
|
3
|
+
import { isNeonApiError, retryOnLock } from "../api.js";
|
|
4
|
+
import { getCliName } from "../utils/cli_name.js";
|
|
5
|
+
import { deleteCustomDomain, listCustomDomains, registerCustomDomain } from "../custom_domains_api.js";
|
|
6
|
+
import { createDeployment, deleteFunction, getFunction, listFunctions } from "../functions_api.js";
|
|
7
|
+
import { writer } from "../writer.js";
|
|
8
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
9
|
+
import { bundleEntry } from "../utils/esbuild.js";
|
|
10
|
+
import { FUNCTION_SLUG_HELP, FUNCTION_SLUG_PATTERN } from "../utils/function_slug.js";
|
|
11
|
+
import { zipBundle } from "../utils/zip.js";
|
|
12
|
+
import { DEPLOY_COMMANDS_EPILOGUE } from "../commands/deploy_help.js";
|
|
13
|
+
import { handleNew, listTemplates } from "../commands/functions_new.js";
|
|
14
|
+
import { statSync } from "node:fs";
|
|
15
|
+
import { dirname } from "node:path";
|
|
16
|
+
import { FUNCTION_SOURCE_ENTRIES } from "@neon/config";
|
|
17
|
+
import { bundleAsIs, describeNativeFinding, findUndeclaredNativePackages, resolveEsbuildEntry, zipFunctionBundle } from "@neon/config-runtime";
|
|
18
|
+
//#region src/commands/functions.ts
|
|
19
|
+
var functions_exports = /* @__PURE__ */ __exportAll({
|
|
20
|
+
aliases: () => aliases,
|
|
21
|
+
builder: () => builder,
|
|
22
|
+
command: () => command,
|
|
23
|
+
deploymentPollDelay: () => deploymentPollDelay,
|
|
24
|
+
describe: () => describe,
|
|
25
|
+
handler: () => handler
|
|
26
|
+
});
|
|
27
|
+
const FUNCTION_FIELDS = [
|
|
28
|
+
"slug",
|
|
29
|
+
"name",
|
|
30
|
+
"invocation_url",
|
|
31
|
+
"created_at"
|
|
32
|
+
];
|
|
33
|
+
const FUNCTIONS_LIST_LIMIT = 100;
|
|
34
|
+
const CUSTOM_DOMAINS_LIST_LIMIT = 100;
|
|
35
|
+
const CUSTOM_DOMAIN_FIELDS = [
|
|
36
|
+
"domain",
|
|
37
|
+
"entity_type",
|
|
38
|
+
"entity_id",
|
|
39
|
+
"cname_target"
|
|
40
|
+
];
|
|
41
|
+
const LIST_TABLE_FIELDS = [
|
|
42
|
+
"slug",
|
|
43
|
+
"name",
|
|
44
|
+
"status",
|
|
45
|
+
"URL",
|
|
46
|
+
"created_at"
|
|
47
|
+
];
|
|
48
|
+
const DEPLOY_RESULT_FIELDS = [
|
|
49
|
+
"id",
|
|
50
|
+
"status",
|
|
51
|
+
"invocation_url",
|
|
52
|
+
"runtime",
|
|
53
|
+
"memory_mib",
|
|
54
|
+
"created_at"
|
|
55
|
+
];
|
|
56
|
+
const DEPLOYMENT_TABLE_FIELDS = [
|
|
57
|
+
"deployment_id",
|
|
58
|
+
"status",
|
|
59
|
+
"runtime",
|
|
60
|
+
"memory",
|
|
61
|
+
"created_at"
|
|
62
|
+
];
|
|
63
|
+
const deploymentTableView = (dep) => ({
|
|
64
|
+
deployment_id: dep.id,
|
|
65
|
+
status: dep.status,
|
|
66
|
+
runtime: dep.runtime,
|
|
67
|
+
memory: `${dep.memory_mib} MiB`,
|
|
68
|
+
created_at: dep.created_at
|
|
69
|
+
});
|
|
70
|
+
const FUNCTION_TABLE_FIELDS = [
|
|
71
|
+
"slug",
|
|
72
|
+
"name",
|
|
73
|
+
"URL",
|
|
74
|
+
"created_at"
|
|
75
|
+
];
|
|
76
|
+
const writeDeploymentErrorSection = (out, dep) => {
|
|
77
|
+
if (dep.status === "failed" && dep.error) out.write({ reason: dep.error }, {
|
|
78
|
+
fields: ["reason"],
|
|
79
|
+
title: "Deployment error"
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
const POLL_INTERVAL_OVERRIDE_MS = Number(process.env.NEON_FUNCTIONS_POLL_INTERVAL_MS) || void 0;
|
|
83
|
+
const FIRST_POLL_DELAY_MS = 200;
|
|
84
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
85
|
+
/**
|
|
86
|
+
* Delay before poll `attempt` (zero-based). A small function's deployment is usually done
|
|
87
|
+
* within a few hundred milliseconds of the POST, so the first check comes soon and longer
|
|
88
|
+
* builds back off to the steady interval.
|
|
89
|
+
*/
|
|
90
|
+
const deploymentPollDelay = (attempt, override = POLL_INTERVAL_OVERRIDE_MS) => override ?? Math.min(FIRST_POLL_DELAY_MS * 2 ** attempt, POLL_INTERVAL_MS);
|
|
91
|
+
const POLL_TIMEOUT_MS = Number(process.env.NEON_FUNCTIONS_POLL_TIMEOUT_MS) || 6e5;
|
|
92
|
+
const command = "functions";
|
|
93
|
+
const describe = "Manage Neon Functions";
|
|
94
|
+
const aliases = ["function"];
|
|
95
|
+
const builder = (argv) => argv.usage("$0 function <sub-command> [options]").options({
|
|
96
|
+
"project-id": {
|
|
97
|
+
describe: "Project ID",
|
|
98
|
+
type: "string"
|
|
99
|
+
},
|
|
100
|
+
branch: {
|
|
101
|
+
describe: "Branch ID or name",
|
|
102
|
+
type: "string"
|
|
103
|
+
}
|
|
104
|
+
}).middleware(fillSingleProject).command("new [template]", "Create a function from a Function Registry template", (yargs) => yargs.strict().positional("template", {
|
|
105
|
+
describe: "Template ID. Omit it in a terminal to pick one; `neon functions templates list` shows them all.",
|
|
106
|
+
type: "string"
|
|
107
|
+
}).options({
|
|
108
|
+
"project-id": { hidden: true },
|
|
109
|
+
branch: { hidden: true },
|
|
110
|
+
slug: {
|
|
111
|
+
describe: "Function slug, also used as the directory name. A separate template keeps its own slugs and uses this only for the directory. Defaults to the template ID",
|
|
112
|
+
type: "string"
|
|
113
|
+
},
|
|
114
|
+
operation: {
|
|
115
|
+
describe: "Operation to include (repeatable). Without it, a TTY asks and anything else uses the recommended operations.",
|
|
116
|
+
type: "string",
|
|
117
|
+
array: true
|
|
118
|
+
},
|
|
119
|
+
"all-operations": {
|
|
120
|
+
describe: "Include every operation. Conflicts with --operation.",
|
|
121
|
+
type: "boolean"
|
|
122
|
+
},
|
|
123
|
+
yes: {
|
|
124
|
+
alias: ["y", "default"],
|
|
125
|
+
describe: "Skip prompts: use the recommended operations and install dependencies",
|
|
126
|
+
type: "boolean"
|
|
127
|
+
},
|
|
128
|
+
dir: {
|
|
129
|
+
describe: "Destination directory (defaults to functions/<slug> next to neon.ts)",
|
|
130
|
+
type: "string"
|
|
131
|
+
},
|
|
132
|
+
force: {
|
|
133
|
+
describe: "Overwrite differing files and keep an existing neon.ts declaration",
|
|
134
|
+
type: "boolean"
|
|
135
|
+
},
|
|
136
|
+
install: {
|
|
137
|
+
describe: "Install the template's dependencies, plus @neon/config and @neon/env when neon.ts is created. A TTY asks when omitted, --yes installs, and anything else leaves the install command in Next.",
|
|
138
|
+
type: "boolean"
|
|
139
|
+
},
|
|
140
|
+
"add-to-config": {
|
|
141
|
+
describe: "Declare the function in neon.ts, creating it when missing. On by default; use --no-add-to-config to skip.",
|
|
142
|
+
type: "boolean"
|
|
143
|
+
},
|
|
144
|
+
config: {
|
|
145
|
+
describe: "Path to the neon.ts to edit (defaults to the neon.ts next to .neon, or in cwd without one; created there when missing)",
|
|
146
|
+
type: "string"
|
|
147
|
+
}
|
|
148
|
+
}), (args) => handleNew(args)).command("templates", "Browse Function Registry templates", (yargs) => yargs.command("list", "List function templates", (yargs) => yargs.strict().options({
|
|
149
|
+
"project-id": { hidden: true },
|
|
150
|
+
branch: { hidden: true }
|
|
151
|
+
}), (args) => listTemplates(args))).command("deploy <slug>", "Deploy a function from a local directory", (yargs) => yargs.positional("slug", {
|
|
152
|
+
describe: "Function slug (1-20 lowercase letters and digits)",
|
|
153
|
+
type: "string",
|
|
154
|
+
demandOption: true
|
|
155
|
+
}).options({
|
|
156
|
+
src: {
|
|
157
|
+
describe: "Function source: a directory containing index.ts, index.js, or index.mjs (first file match is the entry), or a path to the entry file",
|
|
158
|
+
type: "string"
|
|
159
|
+
},
|
|
160
|
+
path: {
|
|
161
|
+
type: "string",
|
|
162
|
+
hidden: true
|
|
163
|
+
},
|
|
164
|
+
entry: {
|
|
165
|
+
type: "string",
|
|
166
|
+
hidden: true
|
|
167
|
+
},
|
|
168
|
+
runtime: {
|
|
169
|
+
describe: "Function runtime",
|
|
170
|
+
type: "string",
|
|
171
|
+
choices: ["nodejs24"]
|
|
172
|
+
},
|
|
173
|
+
env: {
|
|
174
|
+
describe: "Environment variable as KEY=VALUE (repeatable)",
|
|
175
|
+
type: "string",
|
|
176
|
+
array: true
|
|
177
|
+
},
|
|
178
|
+
wait: {
|
|
179
|
+
describe: "Wait for the deployment to finish building",
|
|
180
|
+
type: "boolean",
|
|
181
|
+
default: true
|
|
182
|
+
},
|
|
183
|
+
bundle: {
|
|
184
|
+
describe: "Bundle --src with esbuild. Use --no-bundle to zip a prebuilt directory (root must contain index.mjs or index.js) or a file of that name.",
|
|
185
|
+
type: "boolean",
|
|
186
|
+
default: true
|
|
187
|
+
}
|
|
188
|
+
}).epilogue(DEPLOY_COMMANDS_EPILOGUE), (args) => deploy(args)).command("list", "List functions on the branch", (yargs) => yargs, (args) => list(args)).command("get <slug>", "Show a function's details", (yargs) => yargs.positional("slug", {
|
|
189
|
+
describe: "Function slug",
|
|
190
|
+
type: "string",
|
|
191
|
+
demandOption: true
|
|
192
|
+
}).options({ "list-env-variables": {
|
|
193
|
+
describe: "List the environment variable names of the active deployment",
|
|
194
|
+
type: "boolean",
|
|
195
|
+
alias: "E",
|
|
196
|
+
default: false
|
|
197
|
+
} }), (args) => get(args)).command("delete <slug>", "Delete a function on the branch", (yargs) => yargs.positional("slug", {
|
|
198
|
+
describe: "Function slug",
|
|
199
|
+
type: "string",
|
|
200
|
+
demandOption: true
|
|
201
|
+
}), (args) => deleteFn(args)).command(["domains", "domain"], "Manage custom domains on the branch (beta)", (yargs) => yargs.command("list", "List custom domains on the branch", (yargs) => yargs, (args) => listDomains(args)).command("register <domain>", "Point a domain you already own at a function on the branch", (yargs) => yargs.positional("domain", {
|
|
202
|
+
describe: "Domain you already own (for example docs.example.com)",
|
|
203
|
+
type: "string",
|
|
204
|
+
demandOption: true
|
|
205
|
+
}).options({ slug: {
|
|
206
|
+
describe: "Function slug this domain should point at",
|
|
207
|
+
type: "string",
|
|
208
|
+
demandOption: true
|
|
209
|
+
} }), (args) => registerDomain(args)).command("delete <domain>", "Delete a custom domain from the branch", (yargs) => yargs.positional("domain", {
|
|
210
|
+
describe: "Custom domain",
|
|
211
|
+
type: "string",
|
|
212
|
+
demandOption: true
|
|
213
|
+
}), (args) => deleteDomain(args)));
|
|
214
|
+
const handler = (args) => {
|
|
215
|
+
return args;
|
|
216
|
+
};
|
|
217
|
+
const parseEnv = (entries) => {
|
|
218
|
+
if (!entries || entries.length === 0) return void 0;
|
|
219
|
+
const map = {};
|
|
220
|
+
for (const entry of entries) {
|
|
221
|
+
const eq = entry.indexOf("=");
|
|
222
|
+
if (eq <= 0) throw new Error(`Invalid --env value "${entry}". Expected KEY=VALUE.`);
|
|
223
|
+
map[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
224
|
+
}
|
|
225
|
+
return JSON.stringify(map);
|
|
226
|
+
};
|
|
227
|
+
const statusHint = (slug, projectId, branchId) => `Check status with: ${getCliName()} function get ${slug} --project-id ${projectId} --branch ${branchId}`;
|
|
228
|
+
const emitDeployResult = (props, deployment, fn) => {
|
|
229
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
230
|
+
writer(props).write({
|
|
231
|
+
...deployment,
|
|
232
|
+
invocation_url: fn?.invocation_url
|
|
233
|
+
}, { fields: DEPLOY_RESULT_FIELDS }).end();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const out = writer(props).write({
|
|
237
|
+
URL: fn?.invocation_url,
|
|
238
|
+
...deploymentTableView(deployment)
|
|
239
|
+
}, {
|
|
240
|
+
fields: ["URL", ...DEPLOYMENT_TABLE_FIELDS],
|
|
241
|
+
title: `Function ${props.slug}`
|
|
242
|
+
});
|
|
243
|
+
writeDeploymentErrorSection(out, deployment);
|
|
244
|
+
out.end();
|
|
245
|
+
};
|
|
246
|
+
const isTransient = (err) => isNeonApiError(err) && (err.status === void 0 || err.status === 404 || err.status >= 500);
|
|
247
|
+
const deploy = async (props) => {
|
|
248
|
+
if (props.path !== void 0 || props.entry !== void 0) throw new Error(`--path and --entry were removed. Use --src <dir>; the entry point is discovered as ${FUNCTION_SOURCE_ENTRIES.join(", ")} in that directory.`);
|
|
249
|
+
if (!(props.src !== void 0 || props.env !== void 0 || props.runtime !== void 0 || props.bundle === false)) throw new Error(`Provide at least one option to deploy, e.g. --src, --env, or --no-bundle. See: ${getCliName()} function deploy --help.`);
|
|
250
|
+
if (!FUNCTION_SLUG_PATTERN.test(props.slug)) throw new Error(`Invalid function slug "${props.slug}". ${FUNCTION_SLUG_HELP}`);
|
|
251
|
+
const src = props.src ?? ".";
|
|
252
|
+
const runtime = props.runtime ?? "nodejs24";
|
|
253
|
+
const environment = parseEnv(props.env);
|
|
254
|
+
if (statSync(src, { throwIfNoEntry: false }) === void 0) throw new Error(`--src path not found: ${src}.`);
|
|
255
|
+
let zip;
|
|
256
|
+
if (!props.bundle) zip = await zipFunctionBundle(props.slug, await bundleAsIs({
|
|
257
|
+
slug: props.slug,
|
|
258
|
+
name: props.slug,
|
|
259
|
+
source: src,
|
|
260
|
+
env: {},
|
|
261
|
+
runtime: "nodejs24",
|
|
262
|
+
bundler: "none"
|
|
263
|
+
}, { via: "no-bundle" }));
|
|
264
|
+
else {
|
|
265
|
+
const source = await resolveEsbuildEntry(src);
|
|
266
|
+
const bundled = await bundleEntry(source);
|
|
267
|
+
for (const warning of bundled.warnings) log.warning(warning);
|
|
268
|
+
for (const finding of findUndeclaredNativePackages({
|
|
269
|
+
metafile: bundled.metafile,
|
|
270
|
+
declared: [],
|
|
271
|
+
projectDir: dirname(source)
|
|
272
|
+
})) log.warning(describeNativeFinding(props.slug, finding));
|
|
273
|
+
zip = zipBundle(bundled.files);
|
|
274
|
+
}
|
|
275
|
+
const branchId = await branchIdFromProps(props);
|
|
276
|
+
let before;
|
|
277
|
+
try {
|
|
278
|
+
before = (await getFunction(props.apiClient, props.projectId, branchId, props.slug)).current_deployment?.id;
|
|
279
|
+
} catch (err) {
|
|
280
|
+
if (!(isNeonApiError(err) && err.status === 404)) throw err;
|
|
281
|
+
}
|
|
282
|
+
await retryOnLock(() => createDeployment(props.apiClient, props.projectId, branchId, props.slug, {
|
|
283
|
+
zip,
|
|
284
|
+
runtime,
|
|
285
|
+
environment
|
|
286
|
+
}));
|
|
287
|
+
log.info(`Function deployment triggered for function ${props.slug}.`);
|
|
288
|
+
let interrupted = false;
|
|
289
|
+
const onSignal = () => {
|
|
290
|
+
interrupted = true;
|
|
291
|
+
};
|
|
292
|
+
process.once("SIGINT", onSignal);
|
|
293
|
+
process.once("SIGTERM", onSignal);
|
|
294
|
+
let resolved;
|
|
295
|
+
let resolvedFn;
|
|
296
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
297
|
+
let attempt = 0;
|
|
298
|
+
try {
|
|
299
|
+
while (!interrupted && Date.now() < deadline) {
|
|
300
|
+
await new Promise((r) => setTimeout(r, deploymentPollDelay(attempt)));
|
|
301
|
+
attempt += 1;
|
|
302
|
+
if (interrupted) break;
|
|
303
|
+
let fn;
|
|
304
|
+
try {
|
|
305
|
+
fn = await getFunction(props.apiClient, props.projectId, branchId, props.slug);
|
|
306
|
+
} catch (err) {
|
|
307
|
+
if (isTransient(err)) continue;
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
const dep = fn.current_deployment;
|
|
311
|
+
if (dep !== void 0 && (before === void 0 || dep.id > before) && dep) {
|
|
312
|
+
resolved = dep;
|
|
313
|
+
resolvedFn = fn;
|
|
314
|
+
if (!props.wait) break;
|
|
315
|
+
if (dep.status === "completed" || dep.status === "failed") break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} finally {
|
|
319
|
+
process.removeListener("SIGINT", onSignal);
|
|
320
|
+
process.removeListener("SIGTERM", onSignal);
|
|
321
|
+
}
|
|
322
|
+
if (interrupted) {
|
|
323
|
+
log.info(statusHint(props.slug, props.projectId, branchId));
|
|
324
|
+
if (resolved) emitDeployResult(props, resolved, resolvedFn);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (resolved === void 0) {
|
|
328
|
+
log.info(statusHint(props.slug, props.projectId, branchId));
|
|
329
|
+
throw new Error(`Timed out waiting for the deployment of ${props.slug} to start. It may still be in progress.`);
|
|
330
|
+
}
|
|
331
|
+
emitDeployResult(props, resolved, resolvedFn);
|
|
332
|
+
if (!props.wait) {
|
|
333
|
+
log.info(statusHint(props.slug, props.projectId, branchId));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (resolved.status === "completed") {
|
|
337
|
+
if (props.output === "json" || props.output === "yaml") log.info(`Function deployment ${props.slug}/${resolved.id} completed.`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (resolved.status === "failed") throw new Error(`Function deployment ${props.slug}/${resolved.id} failed.`);
|
|
341
|
+
log.info(statusHint(props.slug, props.projectId, branchId));
|
|
342
|
+
throw new Error(`Timed out waiting for function deployment ${props.slug}/${resolved.id} to finish. It may still be building.`);
|
|
343
|
+
};
|
|
344
|
+
const get = async (props) => {
|
|
345
|
+
const branchId = await branchIdFromProps(props);
|
|
346
|
+
const fn = await getFunction(props.apiClient, props.projectId, branchId, props.slug);
|
|
347
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
348
|
+
writer(props).end(fn, { fields: FUNCTION_FIELDS });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const out = writer(props).write({
|
|
352
|
+
...fn,
|
|
353
|
+
URL: fn.invocation_url
|
|
354
|
+
}, {
|
|
355
|
+
fields: FUNCTION_TABLE_FIELDS,
|
|
356
|
+
title: "Function"
|
|
357
|
+
});
|
|
358
|
+
const current = fn.current_deployment;
|
|
359
|
+
const active = fn.active_deployment;
|
|
360
|
+
if (current && active && current.id === active.id) {
|
|
361
|
+
out.write(deploymentTableView(current), {
|
|
362
|
+
fields: DEPLOYMENT_TABLE_FIELDS,
|
|
363
|
+
title: "Deployment (current, active)"
|
|
364
|
+
});
|
|
365
|
+
writeDeploymentErrorSection(out, current);
|
|
366
|
+
} else {
|
|
367
|
+
if (current) {
|
|
368
|
+
out.write(deploymentTableView(current), {
|
|
369
|
+
fields: DEPLOYMENT_TABLE_FIELDS,
|
|
370
|
+
title: "Current deployment"
|
|
371
|
+
});
|
|
372
|
+
writeDeploymentErrorSection(out, current);
|
|
373
|
+
}
|
|
374
|
+
if (active) out.write(deploymentTableView(active), {
|
|
375
|
+
fields: DEPLOYMENT_TABLE_FIELDS,
|
|
376
|
+
title: "Active deployment"
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
if (props.listEnvVariables) out.write((fn.active_deployment?.environment ?? []).map((name) => ({ name })), {
|
|
380
|
+
fields: ["name"],
|
|
381
|
+
title: "Environment",
|
|
382
|
+
emptyMessage: "No environment variables on the active deployment."
|
|
383
|
+
});
|
|
384
|
+
out.end();
|
|
385
|
+
};
|
|
386
|
+
const deleteFn = async (props) => {
|
|
387
|
+
const branchId = await branchIdFromProps(props);
|
|
388
|
+
try {
|
|
389
|
+
await retryOnLock(() => deleteFunction(props.apiClient, props.projectId, branchId, props.slug));
|
|
390
|
+
} catch (err) {
|
|
391
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(`Function "${props.slug}" not found on branch ${branchId}.`);
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
394
|
+
log.info(`Function ${props.slug} deleted from branch ${branchId}`);
|
|
395
|
+
};
|
|
396
|
+
const list = async (props) => {
|
|
397
|
+
const branchId = await branchIdFromProps(props);
|
|
398
|
+
const functions = [];
|
|
399
|
+
let cursor;
|
|
400
|
+
for (;;) {
|
|
401
|
+
const page = await listFunctions(props.apiClient, props.projectId, branchId, {
|
|
402
|
+
cursor,
|
|
403
|
+
limit: FUNCTIONS_LIST_LIMIT
|
|
404
|
+
});
|
|
405
|
+
functions.push(...page.functions);
|
|
406
|
+
log.debug("Got %d functions, next cursor: %s", page.functions.length, page.next);
|
|
407
|
+
if (!page.next || page.next === cursor) break;
|
|
408
|
+
cursor = page.next;
|
|
409
|
+
}
|
|
410
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
411
|
+
writer(props).end(functions, { fields: FUNCTION_FIELDS });
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
writer(props).end(functions.map((fn) => ({
|
|
415
|
+
...fn,
|
|
416
|
+
status: fn.current_deployment?.status ?? "",
|
|
417
|
+
URL: fn.invocation_url
|
|
418
|
+
})), {
|
|
419
|
+
fields: LIST_TABLE_FIELDS,
|
|
420
|
+
emptyMessage: "No functions found on this branch."
|
|
421
|
+
});
|
|
422
|
+
};
|
|
423
|
+
const listDomains = async (props) => {
|
|
424
|
+
const branchId = await branchIdFromProps(props);
|
|
425
|
+
const domains = [];
|
|
426
|
+
let cursor;
|
|
427
|
+
for (;;) {
|
|
428
|
+
const page = await listCustomDomains(props.apiClient, props.projectId, branchId, {
|
|
429
|
+
cursor,
|
|
430
|
+
limit: CUSTOM_DOMAINS_LIST_LIMIT
|
|
431
|
+
});
|
|
432
|
+
domains.push(...page.custom_domains);
|
|
433
|
+
if (!page.next || page.next === cursor) break;
|
|
434
|
+
cursor = page.next;
|
|
435
|
+
}
|
|
436
|
+
writer(props).end(domains, {
|
|
437
|
+
fields: CUSTOM_DOMAIN_FIELDS,
|
|
438
|
+
emptyMessage: "No custom domains found on this branch."
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
const registerDomain = async (props) => {
|
|
442
|
+
if (!FUNCTION_SLUG_PATTERN.test(props.slug)) throw new Error(`Invalid function slug "${props.slug}". ${FUNCTION_SLUG_HELP}`);
|
|
443
|
+
const branchId = await branchIdFromProps(props);
|
|
444
|
+
const registered = await retryOnLock(() => registerCustomDomain(props.apiClient, props.projectId, branchId, {
|
|
445
|
+
domain: props.domain,
|
|
446
|
+
entity_type: "function",
|
|
447
|
+
entity_id: props.slug
|
|
448
|
+
}));
|
|
449
|
+
writer(props).end(registered, { fields: CUSTOM_DOMAIN_FIELDS });
|
|
450
|
+
if (registered.cname_target) log.info(`CNAME ${registered.domain} to ${registered.cname_target}`);
|
|
451
|
+
else log.info(`No CNAME target for ${registered.domain}; this region has no custom-domains front door.`);
|
|
452
|
+
};
|
|
453
|
+
const deleteDomain = async (props) => {
|
|
454
|
+
const branchId = await branchIdFromProps(props);
|
|
455
|
+
try {
|
|
456
|
+
await retryOnLock(() => deleteCustomDomain(props.apiClient, props.projectId, branchId, props.domain));
|
|
457
|
+
} catch (err) {
|
|
458
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(`Custom domain "${props.domain}" not found on branch ${branchId}.`);
|
|
459
|
+
throw err;
|
|
460
|
+
}
|
|
461
|
+
log.info(`Custom domain ${props.domain} deleted from branch ${branchId}`);
|
|
462
|
+
};
|
|
463
|
+
//#endregion
|
|
464
|
+
export { describe as a, deploymentPollDelay as i, builder as n, functions_exports as o, command as r, handler as s, aliases as t };
|