neon 8.11.0 → 8.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +6 -6
- 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/dist/utils/package_manager.js +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { n as defaultConnectionRole } from "./roles-C3M4q9DS.js";
|
|
3
|
+
import { writer } from "../writer.js";
|
|
4
|
+
import { EndpointType } from "../utils/api_enums.js";
|
|
5
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
6
|
+
import { psql } from "../utils/psql.js";
|
|
7
|
+
import { parsePITBranch } from "../utils/point_in_time.js";
|
|
8
|
+
//#region src/commands/connection_string.ts
|
|
9
|
+
var connection_string_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
SSL_MODES: () => SSL_MODES,
|
|
11
|
+
aliases: () => aliases,
|
|
12
|
+
builder: () => builder,
|
|
13
|
+
command: () => command,
|
|
14
|
+
describe: () => describe,
|
|
15
|
+
handler: () => handler
|
|
16
|
+
});
|
|
17
|
+
const SSL_MODES = [
|
|
18
|
+
"require",
|
|
19
|
+
"verify-ca",
|
|
20
|
+
"verify-full",
|
|
21
|
+
"omit"
|
|
22
|
+
];
|
|
23
|
+
const command = "connection-string [branch]";
|
|
24
|
+
const aliases = ["cs"];
|
|
25
|
+
const describe = "Get connection string";
|
|
26
|
+
const builder = (argv) => {
|
|
27
|
+
return argv.usage("$0 connection-string [branch] [options]").example("$0 cs main", "Get connection string for the main branch").example("$0 cs main@2024-01-01T00:00:00Z", "Get connection string for the main branch at a specific point in time").example("$0 cs main@0/234235", "Get connection string for the main branch at a specific LSN").positional("branch", {
|
|
28
|
+
describe: `Branch name or id. Defaults to the default branch if omitted. Can be written in the point-in-time format: "branch@timestamp" or "branch@lsn"`,
|
|
29
|
+
type: "string"
|
|
30
|
+
}).options({
|
|
31
|
+
"project-id": {
|
|
32
|
+
type: "string",
|
|
33
|
+
describe: "Project ID"
|
|
34
|
+
},
|
|
35
|
+
"role-name": {
|
|
36
|
+
type: "string",
|
|
37
|
+
describe: "Role name. Defaults to neondb_owner, else the only role besides the Data API and Neon Auth roles"
|
|
38
|
+
},
|
|
39
|
+
"database-name": {
|
|
40
|
+
type: "string",
|
|
41
|
+
describe: "Database name"
|
|
42
|
+
},
|
|
43
|
+
pooled: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
describe: "Use pooled connection",
|
|
46
|
+
default: false
|
|
47
|
+
},
|
|
48
|
+
prisma: {
|
|
49
|
+
type: "boolean",
|
|
50
|
+
describe: "Use connection string for Prisma setup",
|
|
51
|
+
default: false
|
|
52
|
+
},
|
|
53
|
+
"endpoint-type": {
|
|
54
|
+
type: "string",
|
|
55
|
+
choices: Object.values(EndpointType),
|
|
56
|
+
describe: "Endpoint type"
|
|
57
|
+
},
|
|
58
|
+
extended: {
|
|
59
|
+
type: "boolean",
|
|
60
|
+
describe: "Show extended information"
|
|
61
|
+
},
|
|
62
|
+
psql: {
|
|
63
|
+
type: "boolean",
|
|
64
|
+
describe: "Connect to a database via psql using connection string",
|
|
65
|
+
default: false
|
|
66
|
+
},
|
|
67
|
+
fallback: {
|
|
68
|
+
type: "boolean",
|
|
69
|
+
describe: "Force the embedded TypeScript psql fallback (for testing)",
|
|
70
|
+
default: false,
|
|
71
|
+
hidden: true
|
|
72
|
+
},
|
|
73
|
+
ssl: {
|
|
74
|
+
type: "string",
|
|
75
|
+
choices: SSL_MODES,
|
|
76
|
+
default: "require",
|
|
77
|
+
describe: "SSL mode"
|
|
78
|
+
}
|
|
79
|
+
}).middleware(fillSingleProject);
|
|
80
|
+
};
|
|
81
|
+
const handler = async (props) => {
|
|
82
|
+
const projectId = props.projectId;
|
|
83
|
+
const parsedPIT = props.branch ? parsePITBranch(props.branch) : {
|
|
84
|
+
tag: "head",
|
|
85
|
+
branch: ""
|
|
86
|
+
};
|
|
87
|
+
if (props.branch) props.branch = parsedPIT.branch;
|
|
88
|
+
const branchId = await branchIdFromProps(props);
|
|
89
|
+
const endpointsRequest = props.apiClient.listProjectBranchEndpoints(projectId, branchId);
|
|
90
|
+
const rolesRequest = props.roleName ? void 0 : props.apiClient.listProjectBranchRoles(projectId, branchId);
|
|
91
|
+
const databasesRequest = props.apiClient.listProjectBranchDatabases(projectId, branchId);
|
|
92
|
+
for (const request of [
|
|
93
|
+
endpointsRequest,
|
|
94
|
+
rolesRequest,
|
|
95
|
+
databasesRequest
|
|
96
|
+
]) request?.catch(() => void 0);
|
|
97
|
+
const { data: { endpoints } } = await endpointsRequest;
|
|
98
|
+
const matchEndpointType = props.endpointType ?? EndpointType.ReadWrite;
|
|
99
|
+
let endpoint = endpoints.find((e) => e.type === matchEndpointType);
|
|
100
|
+
if (!endpoint && props.endpointType == null) endpoint = endpoints[0];
|
|
101
|
+
if (!endpoint) throw new Error(`No ${props.endpointType ?? ""} endpoint found for the branch: ${branchId}`);
|
|
102
|
+
const role = !rolesRequest ? props.roleName : await rolesRequest.then(({ data }) => {
|
|
103
|
+
if (data.roles.length === 0) throw new Error(`No roles found for the branch: ${branchId}`);
|
|
104
|
+
const picked = defaultConnectionRole(data.roles.map((r) => r.name));
|
|
105
|
+
if (picked) return picked;
|
|
106
|
+
throw new Error(`Multiple roles found for the branch, please provide one with the --role-name option: ${data.roles.map((r) => r.name).join(", ")}`);
|
|
107
|
+
});
|
|
108
|
+
const { data: { databases: branchDatabases } } = await databasesRequest;
|
|
109
|
+
const database = props.databaseName || (() => {
|
|
110
|
+
if (branchDatabases.length === 0) throw new Error(`No databases found for the branch: ${branchId}`);
|
|
111
|
+
if (branchDatabases.length === 1) return branchDatabases[0].name;
|
|
112
|
+
throw new Error(`Multiple databases found for the branch, please provide one with the --database-name option: ${branchDatabases.map((d) => d.name).join(", ")}`);
|
|
113
|
+
})();
|
|
114
|
+
if (!branchDatabases.find((d) => d.name === database)) throw new Error(`Database not found: ${database}`);
|
|
115
|
+
const { data: { password } } = await props.apiClient.getProjectBranchRolePassword(props.projectId, endpoint.branch_id, role);
|
|
116
|
+
let host = props.pooled ? endpoint.host.replace(endpoint.id, `${endpoint.id}-pooler`) : endpoint.host;
|
|
117
|
+
if (parsedPIT.tag !== "head") host = endpoint.host.replace(endpoint.id, endpoint.branch_id);
|
|
118
|
+
const connectionString = new URL(`postgresql://${host}`);
|
|
119
|
+
connectionString.pathname = `/${encodeURIComponent(database)}`;
|
|
120
|
+
connectionString.username = role;
|
|
121
|
+
connectionString.password = password;
|
|
122
|
+
if (props.prisma) {
|
|
123
|
+
connectionString.searchParams.set("connect_timeout", "30");
|
|
124
|
+
if (props.pooled) {
|
|
125
|
+
connectionString.searchParams.set("pool_timeout", "30");
|
|
126
|
+
connectionString.searchParams.set("pgbouncer", "true");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (props.ssl !== "omit") {
|
|
130
|
+
connectionString.searchParams.set("sslmode", props.ssl);
|
|
131
|
+
connectionString.searchParams.set("channel_binding", "require");
|
|
132
|
+
}
|
|
133
|
+
if (parsedPIT.tag === "lsn") connectionString.searchParams.set("options", `neon_lsn:${parsedPIT.lsn}`);
|
|
134
|
+
else if (parsedPIT.tag === "timestamp") connectionString.searchParams.set("options", `neon_timestamp:${parsedPIT.timestamp}`);
|
|
135
|
+
if (props.psql) {
|
|
136
|
+
const psqlArgs = props["--"];
|
|
137
|
+
await psql(connectionString.toString(), psqlArgs, { mode: props.fallback ? "ts" : "auto" });
|
|
138
|
+
} else if (props.extended) writer(props).end({
|
|
139
|
+
connection_string: connectionString.toString(),
|
|
140
|
+
host,
|
|
141
|
+
role,
|
|
142
|
+
password,
|
|
143
|
+
database,
|
|
144
|
+
options: connectionString.searchParams.toString()
|
|
145
|
+
}, { fields: [
|
|
146
|
+
"host",
|
|
147
|
+
"role",
|
|
148
|
+
"password",
|
|
149
|
+
"database"
|
|
150
|
+
] });
|
|
151
|
+
else process.stdout.write(connectionString.toString() + "\n");
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
export { connection_string_exports as a, command as i, aliases as n, describe as o, builder as r, handler as s, SSL_MODES as t };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { log } from "../log.js";
|
|
3
|
+
import { isNeonApiError, messageFromBody, retryOnLock } from "../api.js";
|
|
4
|
+
import { writer } from "../writer.js";
|
|
5
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
6
|
+
//#region src/commands/credentials.ts
|
|
7
|
+
var credentials_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
+
aliases: () => aliases,
|
|
9
|
+
builder: () => builder,
|
|
10
|
+
command: () => command,
|
|
11
|
+
create: () => create,
|
|
12
|
+
describe: () => describe,
|
|
13
|
+
handler: () => handler,
|
|
14
|
+
list: () => list,
|
|
15
|
+
reveal: () => reveal,
|
|
16
|
+
revoke: () => revoke,
|
|
17
|
+
rotate: () => rotate
|
|
18
|
+
});
|
|
19
|
+
const CREDENTIAL_SCOPES = [
|
|
20
|
+
"storage:read",
|
|
21
|
+
"storage:write",
|
|
22
|
+
"ai_gateway:invoke",
|
|
23
|
+
"functions:invoke"
|
|
24
|
+
];
|
|
25
|
+
const LIST_FIELDS = [
|
|
26
|
+
"token_id",
|
|
27
|
+
"name",
|
|
28
|
+
"principal_type",
|
|
29
|
+
"scopes",
|
|
30
|
+
"created_at"
|
|
31
|
+
];
|
|
32
|
+
const SECRET_META_FIELDS = [
|
|
33
|
+
"token_id",
|
|
34
|
+
"name",
|
|
35
|
+
"scopes"
|
|
36
|
+
];
|
|
37
|
+
const renderColumns = { scopes: (c) => (c.scopes ?? []).join(", ") };
|
|
38
|
+
/** Translate a token-id 404 into a resource-specific message; re-throw everything else. */
|
|
39
|
+
async function withCredentialNotFound(tokenId, branchId, run) {
|
|
40
|
+
try {
|
|
41
|
+
return await run();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
44
|
+
if ((messageFromBody(err.data) ?? "").includes("platform credentials not available")) throw err;
|
|
45
|
+
throw new Error(`Credential ${tokenId} not found on branch ${branchId}.`);
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Secrets are shown only once (create/rotate) or on demand (reveal). Table
|
|
52
|
+
* output keeps them off the grid so they stay one selectable line; json/yaml
|
|
53
|
+
* keep them on the object for scripts.
|
|
54
|
+
*/
|
|
55
|
+
const reportSecrets = (props, data, warning) => {
|
|
56
|
+
const out = writer(props);
|
|
57
|
+
if (props.output === "table") {
|
|
58
|
+
out.write(data, { fields: SECRET_META_FIELDS });
|
|
59
|
+
out.end();
|
|
60
|
+
const apiToken = "api_token" in data && typeof data.api_token === "string" ? data.api_token : "";
|
|
61
|
+
const secretKey = "s3_secret_access_key" in data && typeof data.s3_secret_access_key === "string" ? data.s3_secret_access_key : "";
|
|
62
|
+
out.text(`api_token: ${apiToken}\n`);
|
|
63
|
+
out.text(`s3_secret_access_key: ${secretKey}\n`);
|
|
64
|
+
} else out.end(data, { fields: [
|
|
65
|
+
"token_id",
|
|
66
|
+
"name",
|
|
67
|
+
"scopes",
|
|
68
|
+
"api_token",
|
|
69
|
+
"s3_secret_access_key"
|
|
70
|
+
] });
|
|
71
|
+
log.warning(warning);
|
|
72
|
+
};
|
|
73
|
+
const command = "credentials";
|
|
74
|
+
const describe = "Manage branch credentials";
|
|
75
|
+
const aliases = ["credential"];
|
|
76
|
+
const builder = (argv) => argv.usage("$0 credentials <sub-command> [options]").options({
|
|
77
|
+
"project-id": {
|
|
78
|
+
describe: "Project ID",
|
|
79
|
+
type: "string"
|
|
80
|
+
},
|
|
81
|
+
branch: {
|
|
82
|
+
describe: "Branch ID or name",
|
|
83
|
+
type: "string"
|
|
84
|
+
}
|
|
85
|
+
}).middleware(fillSingleProject).command("list", "List credentials on the branch", (yargs) => yargs, (args) => list(args)).command("create", "Issue a scoped credential on the branch", (yargs) => yargs.options({
|
|
86
|
+
name: {
|
|
87
|
+
describe: "Label for the credential",
|
|
88
|
+
type: "string"
|
|
89
|
+
},
|
|
90
|
+
scope: {
|
|
91
|
+
describe: "Capability to grant. Repeatable. Values: storage:read, storage:write, ai_gateway:invoke, functions:invoke",
|
|
92
|
+
type: "string",
|
|
93
|
+
array: true,
|
|
94
|
+
choices: CREDENTIAL_SCOPES,
|
|
95
|
+
demandOption: true
|
|
96
|
+
}
|
|
97
|
+
}), (args) => create(args)).command("reveal <tokenId>", "Show a credential's api_token and s3_secret_access_key", (yargs) => yargs.positional("tokenId", {
|
|
98
|
+
describe: "Credential token id (nak_live_<32hex>)",
|
|
99
|
+
type: "string"
|
|
100
|
+
}), (args) => reveal(args)).command("rotate <tokenId>", "Replace a credential's secrets in place. The token id is unchanged.", (yargs) => yargs.positional("tokenId", {
|
|
101
|
+
describe: "Credential token id (nak_live_<32hex>)",
|
|
102
|
+
type: "string"
|
|
103
|
+
}), (args) => rotate(args)).command("revoke <tokenId>", "Revoke a credential", (yargs) => yargs.positional("tokenId", {
|
|
104
|
+
describe: "Credential token id (nak_live_<32hex>)",
|
|
105
|
+
type: "string"
|
|
106
|
+
}), (args) => revoke(args));
|
|
107
|
+
const handler = (args) => {
|
|
108
|
+
return args;
|
|
109
|
+
};
|
|
110
|
+
const list = async (props) => {
|
|
111
|
+
const branchId = await branchIdFromProps(props);
|
|
112
|
+
const { data } = await props.apiClient.listCredentials(props.projectId, branchId);
|
|
113
|
+
writer(props).end(data.credentials, {
|
|
114
|
+
fields: LIST_FIELDS,
|
|
115
|
+
renderColumns,
|
|
116
|
+
emptyMessage: "No credentials found on this branch."
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const create = async (props) => {
|
|
120
|
+
const branchId = await branchIdFromProps(props);
|
|
121
|
+
const scopes = (Array.isArray(props.scope) ? props.scope : [props.scope]).filter((scope) => CREDENTIAL_SCOPES.some((allowed) => allowed === scope));
|
|
122
|
+
if (scopes.length === 0) throw new Error("Pass at least one --scope. Values: storage:read, storage:write, ai_gateway:invoke, functions:invoke");
|
|
123
|
+
const { data } = await retryOnLock(() => props.apiClient.createCredential(props.projectId, branchId, {
|
|
124
|
+
scopes,
|
|
125
|
+
principal_type: "user",
|
|
126
|
+
...props.name !== void 0 ? { name: props.name } : {}
|
|
127
|
+
}));
|
|
128
|
+
reportSecrets(props, data, "Store these secrets now: they are not shown again unless you run neon credentials reveal.");
|
|
129
|
+
};
|
|
130
|
+
const reveal = async (props) => {
|
|
131
|
+
const branchId = await branchIdFromProps(props);
|
|
132
|
+
const { data } = await withCredentialNotFound(props.tokenId, branchId, () => props.apiClient.revealCredential(props.projectId, branchId, props.tokenId));
|
|
133
|
+
reportSecrets(props, data, "These are live secrets. Treat them like a password.");
|
|
134
|
+
};
|
|
135
|
+
const rotate = async (props) => {
|
|
136
|
+
const branchId = await branchIdFromProps(props);
|
|
137
|
+
const { data } = await withCredentialNotFound(props.tokenId, branchId, () => retryOnLock(() => props.apiClient.rotateCredential(props.projectId, branchId, props.tokenId)));
|
|
138
|
+
reportSecrets(props, data, "Store the new secrets now: a retry mints another pair and does not recover a lost response. A replica may briefly accept the previous secret.");
|
|
139
|
+
};
|
|
140
|
+
const revoke = async (props) => {
|
|
141
|
+
const branchId = await branchIdFromProps(props);
|
|
142
|
+
await withCredentialNotFound(props.tokenId, branchId, () => retryOnLock(() => props.apiClient.revokeCredential(props.projectId, branchId, props.tokenId)));
|
|
143
|
+
log.info(`Credential ${props.tokenId} revoked`);
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
export { credentials_exports as a, list as c, rotate as d, create as i, reveal as l, builder as n, describe as o, command as r, handler as s, aliases as t, revoke as u };
|
|
@@ -0,0 +1,263 @@
|
|
|
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 { writer } from "../writer.js";
|
|
6
|
+
import { branchIdFromProps, fillSingleProject, resolveSingleDatabase } from "../utils/enrichers.js";
|
|
7
|
+
//#region src/commands/data_api.ts
|
|
8
|
+
var data_api_exports = /* @__PURE__ */ __exportAll({
|
|
9
|
+
builder: () => builder,
|
|
10
|
+
command: () => command,
|
|
11
|
+
describe: () => describe,
|
|
12
|
+
handler: () => handler
|
|
13
|
+
});
|
|
14
|
+
const SETTINGS_FIELDS = [
|
|
15
|
+
"db_aggregates_enabled",
|
|
16
|
+
"db_anon_role",
|
|
17
|
+
"db_extra_search_path",
|
|
18
|
+
"db_max_rows",
|
|
19
|
+
"db_schemas",
|
|
20
|
+
"jwt_role_claim_key",
|
|
21
|
+
"jwt_cache_max_lifetime",
|
|
22
|
+
"openapi_mode",
|
|
23
|
+
"server_cors_allowed_origins",
|
|
24
|
+
"server_timing_enabled"
|
|
25
|
+
];
|
|
26
|
+
const settingsFlags = {
|
|
27
|
+
"db-aggregates-enabled": {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
describe: "Enable aggregate functions in queries"
|
|
30
|
+
},
|
|
31
|
+
"db-anon-role": {
|
|
32
|
+
type: "string",
|
|
33
|
+
describe: "Database role used for anonymous (unauthenticated) requests"
|
|
34
|
+
},
|
|
35
|
+
"db-extra-search-path": {
|
|
36
|
+
type: "string",
|
|
37
|
+
describe: "Extra schemas appended to the search path"
|
|
38
|
+
},
|
|
39
|
+
"db-max-rows": {
|
|
40
|
+
type: "number",
|
|
41
|
+
describe: "Maximum number of rows returned by a single request"
|
|
42
|
+
},
|
|
43
|
+
"db-schemas": {
|
|
44
|
+
type: "string",
|
|
45
|
+
describe: "Comma-separated list of schemas exposed via the Data API"
|
|
46
|
+
},
|
|
47
|
+
"jwt-role-claim-key": {
|
|
48
|
+
type: "string",
|
|
49
|
+
describe: "JWT claim path used to extract the role"
|
|
50
|
+
},
|
|
51
|
+
"jwt-cache-max-lifetime": {
|
|
52
|
+
type: "number",
|
|
53
|
+
describe: "Maximum JWT cache lifetime in seconds"
|
|
54
|
+
},
|
|
55
|
+
"openapi-mode": {
|
|
56
|
+
type: "string",
|
|
57
|
+
choices: ["ignore-privileges", "disabled"],
|
|
58
|
+
describe: "OpenAPI mode"
|
|
59
|
+
},
|
|
60
|
+
"server-cors-allowed-origins": {
|
|
61
|
+
type: "string",
|
|
62
|
+
describe: "CORS allowed origins"
|
|
63
|
+
},
|
|
64
|
+
"server-timing-enabled": {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
describe: "Enable Server-Timing response headers"
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const command = "data-api";
|
|
70
|
+
const describe = "Manage the Neon Data API for a database";
|
|
71
|
+
const builder = (argv) => argv.usage("$0 data-api <sub-command> [options]").options({
|
|
72
|
+
"project-id": {
|
|
73
|
+
describe: "Project ID",
|
|
74
|
+
type: "string"
|
|
75
|
+
},
|
|
76
|
+
branch: {
|
|
77
|
+
describe: "Branch ID or name",
|
|
78
|
+
type: "string"
|
|
79
|
+
},
|
|
80
|
+
database: {
|
|
81
|
+
describe: "Database name",
|
|
82
|
+
type: "string"
|
|
83
|
+
}
|
|
84
|
+
}).middleware(fillSingleProject).command("create", "Provision the Neon Data API for a database", (yargs) => yargs.options({
|
|
85
|
+
"auth-provider": {
|
|
86
|
+
type: "string",
|
|
87
|
+
choices: ["neon_auth", "external"],
|
|
88
|
+
describe: "Authentication provider"
|
|
89
|
+
},
|
|
90
|
+
"jwks-url": {
|
|
91
|
+
type: "string",
|
|
92
|
+
describe: "URL that lists the JWKS (used with external auth)"
|
|
93
|
+
},
|
|
94
|
+
"provider-name": {
|
|
95
|
+
type: "string",
|
|
96
|
+
describe: "Name of the auth provider (e.g. Clerk, Stytch, Auth0)"
|
|
97
|
+
},
|
|
98
|
+
"jwt-audience": {
|
|
99
|
+
type: "string",
|
|
100
|
+
describe: "Expected JWT audience claim"
|
|
101
|
+
},
|
|
102
|
+
"add-default-grants": {
|
|
103
|
+
type: "boolean",
|
|
104
|
+
describe: "Grant all permissions on tables in the public schema to authenticated users"
|
|
105
|
+
},
|
|
106
|
+
"skip-auth-schema": {
|
|
107
|
+
type: "boolean",
|
|
108
|
+
describe: "Skip creating the auth schema and RLS functions"
|
|
109
|
+
},
|
|
110
|
+
...settingsFlags
|
|
111
|
+
}), (args) => create(args)).command("get", "Show the Neon Data API status and settings", (yargs) => yargs, (args) => get(args)).command("update", "Update Neon Data API settings (merges with current settings by default)", (yargs) => yargs.options({
|
|
112
|
+
replace: {
|
|
113
|
+
type: "boolean",
|
|
114
|
+
default: false,
|
|
115
|
+
describe: "Replace settings with only the flags provided. Omitted settings revert to server defaults."
|
|
116
|
+
},
|
|
117
|
+
...settingsFlags
|
|
118
|
+
}), (args) => update(args)).command("refresh-schema", "Refresh the Data API schema cache without changing settings", (yargs) => yargs, (args) => refreshSchema(args)).command("delete", "Tear down the Neon Data API for a database", (yargs) => yargs, (args) => deleteDataApi(args));
|
|
119
|
+
const handler = (args) => {
|
|
120
|
+
return args;
|
|
121
|
+
};
|
|
122
|
+
const TOP_LEVEL_CREATE_FIELDS = [
|
|
123
|
+
"auth_provider",
|
|
124
|
+
"jwks_url",
|
|
125
|
+
"provider_name",
|
|
126
|
+
"jwt_audience",
|
|
127
|
+
"add_default_grants",
|
|
128
|
+
"skip_auth_schema"
|
|
129
|
+
];
|
|
130
|
+
const argKey = (snake) => snake.replace(/_/g, "-");
|
|
131
|
+
const buildSettings = (args) => {
|
|
132
|
+
const settings = {};
|
|
133
|
+
for (const field of SETTINGS_FIELDS) {
|
|
134
|
+
const value = args[argKey(field)];
|
|
135
|
+
if (value === void 0) continue;
|
|
136
|
+
if (field === "db_schemas" && typeof value === "string") settings[field] = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
137
|
+
else settings[field] = value;
|
|
138
|
+
}
|
|
139
|
+
return Object.keys(settings).length > 0 ? settings : void 0;
|
|
140
|
+
};
|
|
141
|
+
const buildCreateBody = (args) => {
|
|
142
|
+
const body = {};
|
|
143
|
+
for (const field of TOP_LEVEL_CREATE_FIELDS) {
|
|
144
|
+
const value = args[argKey(field)];
|
|
145
|
+
if (value !== void 0) body[field] = value;
|
|
146
|
+
}
|
|
147
|
+
const settings = buildSettings(args);
|
|
148
|
+
if (settings) body.settings = settings;
|
|
149
|
+
return body;
|
|
150
|
+
};
|
|
151
|
+
const create = async (props) => {
|
|
152
|
+
const branchId = await branchIdFromProps(props);
|
|
153
|
+
const database = await resolveSingleDatabase({
|
|
154
|
+
apiClient: props.apiClient,
|
|
155
|
+
projectId: props.projectId,
|
|
156
|
+
branchId,
|
|
157
|
+
database: props.database
|
|
158
|
+
});
|
|
159
|
+
const body = buildCreateBody(props);
|
|
160
|
+
const { data } = await retryOnLock(() => props.apiClient.createProjectBranchDataApi(props.projectId, branchId, database, body));
|
|
161
|
+
writer(props).end(data, { fields: ["url"] });
|
|
162
|
+
};
|
|
163
|
+
const GET_FIELDS = [
|
|
164
|
+
"url",
|
|
165
|
+
"status",
|
|
166
|
+
"db_schemas"
|
|
167
|
+
];
|
|
168
|
+
const get = async (props) => {
|
|
169
|
+
const branchId = await branchIdFromProps(props);
|
|
170
|
+
const database = await resolveSingleDatabase({
|
|
171
|
+
apiClient: props.apiClient,
|
|
172
|
+
projectId: props.projectId,
|
|
173
|
+
branchId,
|
|
174
|
+
database: props.database
|
|
175
|
+
});
|
|
176
|
+
const { data } = await props.apiClient.getProjectBranchDataApi(props.projectId, branchId, database);
|
|
177
|
+
const { available_schemas: _ignored, ...publicData } = data;
|
|
178
|
+
const tableRow = {
|
|
179
|
+
url: publicData.url,
|
|
180
|
+
status: publicData.status,
|
|
181
|
+
db_schemas: (publicData.settings?.db_schemas ?? []).join(", ")
|
|
182
|
+
};
|
|
183
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
184
|
+
writer(props).end(publicData, { fields: [
|
|
185
|
+
"url",
|
|
186
|
+
"status",
|
|
187
|
+
"settings"
|
|
188
|
+
] });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
writer(props).end(tableRow, { fields: GET_FIELDS });
|
|
192
|
+
};
|
|
193
|
+
const update = async (props) => {
|
|
194
|
+
const branchId = await branchIdFromProps(props);
|
|
195
|
+
const database = await resolveSingleDatabase({
|
|
196
|
+
apiClient: props.apiClient,
|
|
197
|
+
projectId: props.projectId,
|
|
198
|
+
branchId,
|
|
199
|
+
database: props.database
|
|
200
|
+
});
|
|
201
|
+
const userSettings = buildSettings(props);
|
|
202
|
+
if (!userSettings) throw new Error("No settings flags provided. Pass at least one setting flag to update, or use `data-api refresh-schema` to refresh the schema cache without changing settings.");
|
|
203
|
+
let settings;
|
|
204
|
+
if (props.replace) settings = userSettings;
|
|
205
|
+
else {
|
|
206
|
+
let current;
|
|
207
|
+
try {
|
|
208
|
+
const { data } = await props.apiClient.getProjectBranchDataApi(props.projectId, branchId, database);
|
|
209
|
+
current = data.settings ?? void 0;
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`${getCliName()} data-api create\` first.`);
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
if (!current) throw new Error(`Could not read current Data API settings for ${database} on branch ${branchId}. Retry, or pass --replace to overwrite.`);
|
|
215
|
+
settings = {
|
|
216
|
+
...current,
|
|
217
|
+
...userSettings ?? {}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const body = {};
|
|
221
|
+
if (settings) body.settings = settings;
|
|
222
|
+
try {
|
|
223
|
+
await retryOnLock(() => props.apiClient.updateProjectBranchDataApi(props.projectId, branchId, database, body));
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`${getCliName()} data-api create\` first.`);
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
log.info(`Data API settings updated for ${database} on branch ${branchId}`);
|
|
229
|
+
};
|
|
230
|
+
const refreshSchema = async (props) => {
|
|
231
|
+
const branchId = await branchIdFromProps(props);
|
|
232
|
+
const database = await resolveSingleDatabase({
|
|
233
|
+
apiClient: props.apiClient,
|
|
234
|
+
projectId: props.projectId,
|
|
235
|
+
branchId,
|
|
236
|
+
database: props.database
|
|
237
|
+
});
|
|
238
|
+
try {
|
|
239
|
+
await retryOnLock(() => props.apiClient.updateProjectBranchDataApi(props.projectId, branchId, database, {}));
|
|
240
|
+
} catch (err) {
|
|
241
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`${getCliName()} data-api create\` first.`);
|
|
242
|
+
throw err;
|
|
243
|
+
}
|
|
244
|
+
log.info(`Data API schema cache refreshed for ${database} on branch ${branchId}`);
|
|
245
|
+
};
|
|
246
|
+
const deleteDataApi = async (props) => {
|
|
247
|
+
const branchId = await branchIdFromProps(props);
|
|
248
|
+
const database = await resolveSingleDatabase({
|
|
249
|
+
apiClient: props.apiClient,
|
|
250
|
+
projectId: props.projectId,
|
|
251
|
+
branchId,
|
|
252
|
+
database: props.database
|
|
253
|
+
});
|
|
254
|
+
try {
|
|
255
|
+
await retryOnLock(() => props.apiClient.deleteProjectBranchDataApi(props.projectId, branchId, database));
|
|
256
|
+
} catch (err) {
|
|
257
|
+
if (isNeonApiError(err) && err.status === 404) throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}.`);
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
260
|
+
log.info(`Data API deleted for ${database} on branch ${branchId}`);
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
export { handler as a, describe as i, command as n, data_api_exports as r, builder as t };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { retryOnLock } from "../api.js";
|
|
3
|
+
import { writer } from "../writer.js";
|
|
4
|
+
import { fillSingleProject, resolveBranchFromProps } from "../utils/enrichers.js";
|
|
5
|
+
//#region src/commands/databases.ts
|
|
6
|
+
var databases_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
DATABASE_FIELDS: () => DATABASE_FIELDS,
|
|
8
|
+
aliases: () => aliases,
|
|
9
|
+
builder: () => builder,
|
|
10
|
+
command: () => command,
|
|
11
|
+
create: () => create,
|
|
12
|
+
deleteDb: () => deleteDb,
|
|
13
|
+
describe: () => describe,
|
|
14
|
+
handler: () => handler,
|
|
15
|
+
list: () => list
|
|
16
|
+
});
|
|
17
|
+
const DATABASE_FIELDS = [
|
|
18
|
+
"name",
|
|
19
|
+
"owner_name",
|
|
20
|
+
"created_at"
|
|
21
|
+
];
|
|
22
|
+
const command = "databases";
|
|
23
|
+
const describe = "Manage databases";
|
|
24
|
+
const aliases = ["database", "db"];
|
|
25
|
+
const builder = (argv) => argv.usage("$0 databases <sub-command> [options]").options({
|
|
26
|
+
"project-id": {
|
|
27
|
+
describe: "Project ID",
|
|
28
|
+
type: "string"
|
|
29
|
+
},
|
|
30
|
+
branch: {
|
|
31
|
+
describe: "Branch ID or name",
|
|
32
|
+
type: "string"
|
|
33
|
+
}
|
|
34
|
+
}).middleware(fillSingleProject).command("list", "List databases", (yargs) => yargs, (args) => list(args)).command("create", "Create a database", (yargs) => yargs.options({
|
|
35
|
+
name: {
|
|
36
|
+
describe: "Database name",
|
|
37
|
+
type: "string",
|
|
38
|
+
demandOption: true
|
|
39
|
+
},
|
|
40
|
+
"owner-name": {
|
|
41
|
+
describe: "Owner name",
|
|
42
|
+
type: "string"
|
|
43
|
+
}
|
|
44
|
+
}), (args) => create(args)).command("delete <database>", "Delete a database", (yargs) => yargs, (args) => deleteDb(args));
|
|
45
|
+
const handler = (args) => {
|
|
46
|
+
return args;
|
|
47
|
+
};
|
|
48
|
+
const list = async (props) => {
|
|
49
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
50
|
+
const { data } = await props.apiClient.listProjectBranchDatabases(props.projectId, branchId);
|
|
51
|
+
const branchLabel = branch?.name ?? branchId;
|
|
52
|
+
writer(props).end(data.databases, {
|
|
53
|
+
fields: DATABASE_FIELDS,
|
|
54
|
+
humanTitle: `Databases on ${branchLabel}`,
|
|
55
|
+
emptyMessage: `No databases on ${branchLabel}.`
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const create = async (props) => {
|
|
59
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
60
|
+
const owner = props.ownerName ?? await props.apiClient.listProjectBranchRoles(props.projectId, branchId).then(({ data }) => {
|
|
61
|
+
if (data.roles.length === 0) throw new Error(`No roles found in branch ${branchId}`);
|
|
62
|
+
if (data.roles.length > 1) throw new Error(`More than one role found in branch ${branchId}. Please specify the owner name. Roles: ${data.roles.map((r) => r.name).join(", ")}`);
|
|
63
|
+
return data.roles[0].name;
|
|
64
|
+
});
|
|
65
|
+
if (!owner) throw new Error("No owner found");
|
|
66
|
+
const { data } = await retryOnLock(() => props.apiClient.createProjectBranchDatabase(props.projectId, branchId, { database: {
|
|
67
|
+
name: props.name,
|
|
68
|
+
owner_name: owner
|
|
69
|
+
} }));
|
|
70
|
+
writer(props).end(data.database, {
|
|
71
|
+
fields: DATABASE_FIELDS,
|
|
72
|
+
humanTitle: `Database created on ${branch?.name ?? branchId}`
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const deleteDb = async (props) => {
|
|
76
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
77
|
+
const { data } = await retryOnLock(() => props.apiClient.deleteProjectBranchDatabase(props.projectId, branchId, props.database));
|
|
78
|
+
if (data) writer(props).end(data.database, {
|
|
79
|
+
fields: DATABASE_FIELDS,
|
|
80
|
+
humanTitle: `Database deleted from ${branch?.name ?? branchId}`
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { create as a, describe as c, command as i, handler as l, aliases as n, databases_exports as o, builder as r, deleteDb as s, DATABASE_FIELDS as t, list as u };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { fillSingleProject } from "../utils/enrichers.js";
|
|
3
|
+
import { f as envFlag, i as applyFlags, p as envPullFlag, r as applyCmd } from "./config-BH_qRHiG.js";
|
|
4
|
+
import { DEPLOY_COMMANDS_EPILOGUE } from "../commands/deploy_help.js";
|
|
5
|
+
//#region src/commands/deploy.ts
|
|
6
|
+
var deploy_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
builder: () => builder,
|
|
8
|
+
command: () => command,
|
|
9
|
+
describe: () => describe,
|
|
10
|
+
handler: () => handler
|
|
11
|
+
});
|
|
12
|
+
const command = "deploy";
|
|
13
|
+
const describe = "Apply a neon.ts policy to a branch (alias for `config apply`)";
|
|
14
|
+
const builder = (argv) => argv.usage("$0 deploy [options]").options({
|
|
15
|
+
"project-id": {
|
|
16
|
+
describe: "Project ID",
|
|
17
|
+
type: "string"
|
|
18
|
+
},
|
|
19
|
+
branch: {
|
|
20
|
+
describe: "Branch ID or name",
|
|
21
|
+
type: "string"
|
|
22
|
+
},
|
|
23
|
+
config: {
|
|
24
|
+
describe: "Path to a neon.ts policy (defaults to the neon.ts next to .neon, or in cwd without one)",
|
|
25
|
+
type: "string"
|
|
26
|
+
},
|
|
27
|
+
...envFlag,
|
|
28
|
+
...applyFlags,
|
|
29
|
+
...envPullFlag
|
|
30
|
+
}).middleware(fillSingleProject).epilogue(DEPLOY_COMMANDS_EPILOGUE).strict();
|
|
31
|
+
const handler = (props) => applyCmd(props);
|
|
32
|
+
//#endregion
|
|
33
|
+
export { handler as a, describe as i, command as n, deploy_exports as r, builder as t };
|