neon 7.0.6 → 7.0.8
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/dist/api.js +5 -3
- package/dist/commands/databases.js +18 -7
- package/dist/commands/projects.js +95 -26
- package/package.json +4 -4
package/dist/api.js
CHANGED
|
@@ -208,7 +208,8 @@ function validateRequestTimeout(ms) {
|
|
|
208
208
|
}
|
|
209
209
|
const makeTimedFetch = (requestTimeoutMs) => async (input, init) => {
|
|
210
210
|
const timeout = AbortSignal.timeout(requestTimeoutMs);
|
|
211
|
-
const
|
|
211
|
+
const callerSignal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
212
|
+
const signal = callerSignal ? AbortSignal.any([callerSignal, timeout]) : timeout;
|
|
212
213
|
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
213
214
|
const url = input instanceof Request ? input.url : String(input);
|
|
214
215
|
log.debug("%s %s", method.toUpperCase(), url);
|
|
@@ -220,7 +221,7 @@ const makeTimedFetch = (requestTimeoutMs) => async (input, init) => {
|
|
|
220
221
|
log.debug("%d %s", response.status, response.statusText);
|
|
221
222
|
return response;
|
|
222
223
|
} catch (err) {
|
|
223
|
-
if (timeout.aborted && !
|
|
224
|
+
if (timeout.aborted && !callerSignal?.aborted) throw new RequestTimeoutError(requestTimeoutMs, err);
|
|
224
225
|
throw err;
|
|
225
226
|
}
|
|
226
227
|
};
|
|
@@ -365,8 +366,9 @@ const getApiClient = ({ apiKey, apiHost, requestTimeoutMs = REQUEST_TIMEOUT_MS }
|
|
|
365
366
|
client,
|
|
366
367
|
query
|
|
367
368
|
})),
|
|
368
|
-
listSharedProjects: (query = {}) => call(() => raw.listSharedProjects({
|
|
369
|
+
listSharedProjects: (query = {}, options = {}) => call(() => raw.listSharedProjects({
|
|
369
370
|
client,
|
|
371
|
+
signal: options.signal,
|
|
370
372
|
query: {
|
|
371
373
|
...query.cursor !== void 0 ? { cursor: query.cursor } : {},
|
|
372
374
|
...query.limit !== void 0 ? { limit: query.limit } : {},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { retryOnLock } from "../api.js";
|
|
3
3
|
import { writer } from "../writer.js";
|
|
4
|
-
import {
|
|
4
|
+
import { fillSingleProject, resolveBranchFromProps } from "../utils/enrichers.js";
|
|
5
5
|
//#region src/commands/databases.ts
|
|
6
6
|
var databases_exports = /* @__PURE__ */ __exportAll({
|
|
7
7
|
DATABASE_FIELDS: () => DATABASE_FIELDS,
|
|
@@ -46,12 +46,17 @@ const handler = (args) => {
|
|
|
46
46
|
return args;
|
|
47
47
|
};
|
|
48
48
|
const list = async (props) => {
|
|
49
|
-
const branchId = await
|
|
49
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
50
50
|
const { data } = await props.apiClient.listProjectBranchDatabases(props.projectId, branchId);
|
|
51
|
-
|
|
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
|
+
});
|
|
52
57
|
};
|
|
53
58
|
const create = async (props) => {
|
|
54
|
-
const branchId = await
|
|
59
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
55
60
|
const owner = props.ownerName ?? await props.apiClient.listProjectBranchRoles(props.projectId, branchId).then(({ data }) => {
|
|
56
61
|
if (data.roles.length === 0) throw new Error(`No roles found in branch ${branchId}`);
|
|
57
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(", ")}`);
|
|
@@ -62,12 +67,18 @@ const create = async (props) => {
|
|
|
62
67
|
name: props.name,
|
|
63
68
|
owner_name: owner
|
|
64
69
|
} }));
|
|
65
|
-
writer(props).end(data.database, {
|
|
70
|
+
writer(props).end(data.database, {
|
|
71
|
+
fields: DATABASE_FIELDS,
|
|
72
|
+
humanTitle: `Database created on ${branch?.name ?? branchId}`
|
|
73
|
+
});
|
|
66
74
|
};
|
|
67
75
|
const deleteDb = async (props) => {
|
|
68
|
-
const branchId = await
|
|
76
|
+
const { branchId, branch } = await resolveBranchFromProps(props);
|
|
69
77
|
const { data } = await retryOnLock(() => props.apiClient.deleteProjectBranchDatabase(props.projectId, branchId, props.database));
|
|
70
|
-
if (data) writer(props).end(data.database, {
|
|
78
|
+
if (data) writer(props).end(data.database, {
|
|
79
|
+
fields: DATABASE_FIELDS,
|
|
80
|
+
humanTitle: `Database deleted from ${branch?.name ?? branchId}`
|
|
81
|
+
});
|
|
71
82
|
};
|
|
72
83
|
//#endregion
|
|
73
84
|
export { DATABASE_FIELDS, aliases, builder, command, create, deleteDb, describe, handler, list, databases_exports as t };
|
|
@@ -2,12 +2,13 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { isCi } from "../env.js";
|
|
3
3
|
import { log } from "../log.js";
|
|
4
4
|
import { isNeonApiError, messageFromBody } from "../api.js";
|
|
5
|
-
import { updateContextFile } from "../context.js";
|
|
5
|
+
import { readContextFile, updateContextFile } from "../context.js";
|
|
6
6
|
import { getCliName } from "../utils/cli_name.js";
|
|
7
7
|
import { projectCreateRequest, projectUpdateRequest } from "../parameters.gen.js";
|
|
8
8
|
import { writer } from "../writer.js";
|
|
9
9
|
import { getComputeUnits } from "../utils/compute_units.js";
|
|
10
10
|
import { psql } from "../utils/psql.js";
|
|
11
|
+
import chalk from "chalk";
|
|
11
12
|
import prompts from "prompts";
|
|
12
13
|
//#region src/commands/projects.ts
|
|
13
14
|
var projects_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -18,6 +19,7 @@ var projects_exports = /* @__PURE__ */ __exportAll({
|
|
|
18
19
|
builder: () => builder,
|
|
19
20
|
command: () => command,
|
|
20
21
|
describe: () => describe,
|
|
22
|
+
formatDuration: () => formatDuration,
|
|
21
23
|
handler: () => handler
|
|
22
24
|
});
|
|
23
25
|
const PROJECT_FIELDS = [
|
|
@@ -176,37 +178,56 @@ const builder = (argv) => {
|
|
|
176
178
|
const handler = (args) => {
|
|
177
179
|
return args;
|
|
178
180
|
};
|
|
181
|
+
const listAll = async (fetchPage) => {
|
|
182
|
+
const result = [];
|
|
183
|
+
let cursor;
|
|
184
|
+
let end = false;
|
|
185
|
+
while (!end) {
|
|
186
|
+
const { data } = await fetchPage(cursor);
|
|
187
|
+
result.push(...data.projects);
|
|
188
|
+
cursor = data.pagination?.cursor;
|
|
189
|
+
log.debug("Got %d projects, with cursor: %s", data.projects.length, cursor);
|
|
190
|
+
if (data.projects.length < PROJECTS_LIST_LIMIT) end = true;
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
};
|
|
194
|
+
const projectColumns = (props) => {
|
|
195
|
+
const current = readContextFile(props.contextFile).projectId;
|
|
196
|
+
return { name: (project) => project.id === current ? `${chalk.green("[current]")} ${project.name}` : project.name };
|
|
197
|
+
};
|
|
179
198
|
const list = async (props) => {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
const
|
|
199
|
-
const sharedProjects = props.orgId || props.recoverableOnly ? [] : await getList(props.apiClient.listSharedProjects);
|
|
199
|
+
const query = (cursor) => ({
|
|
200
|
+
limit: PROJECTS_LIST_LIMIT,
|
|
201
|
+
org_id: props.orgId,
|
|
202
|
+
recoverable: props.recoverableOnly,
|
|
203
|
+
cursor
|
|
204
|
+
});
|
|
205
|
+
const includeShared = !props.orgId && !props.recoverableOnly;
|
|
206
|
+
const ownedRequest = listAll((cursor) => props.apiClient.listProjects(query(cursor)));
|
|
207
|
+
const sharedAbort = new AbortController();
|
|
208
|
+
const sharedRequest = includeShared ? listAll((cursor) => props.apiClient.listSharedProjects(query(cursor), { signal: sharedAbort.signal })) : void 0;
|
|
209
|
+
sharedRequest?.catch(() => void 0);
|
|
210
|
+
let ownedProjects;
|
|
211
|
+
try {
|
|
212
|
+
ownedProjects = await ownedRequest;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
sharedAbort.abort();
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
const sharedProjects = await sharedRequest ?? [];
|
|
200
218
|
const out = writer(props);
|
|
219
|
+
const renderColumns = projectColumns(props);
|
|
201
220
|
out.write(ownedProjects, {
|
|
202
221
|
fields: props.recoverableOnly ? RECOVERABLE_PROJECT_FIELDS : PROJECT_FIELDS,
|
|
203
222
|
title: "Projects",
|
|
204
|
-
emptyMessage: props.recoverableOnly ? "You don't have any recoverable projects." : "You don't have any projects yet. See how to create a new project:\n> neon projects create --help"
|
|
223
|
+
emptyMessage: props.recoverableOnly ? "You don't have any recoverable projects." : "You don't have any projects yet. See how to create a new project:\n> neon projects create --help",
|
|
224
|
+
renderColumns
|
|
205
225
|
});
|
|
206
|
-
if (
|
|
226
|
+
if (includeShared) out.write(sharedProjects, {
|
|
207
227
|
fields: PROJECT_FIELDS,
|
|
208
228
|
title: "Shared with you",
|
|
209
|
-
emptyMessage: "No projects have been shared with you"
|
|
229
|
+
emptyMessage: "No projects have been shared with you",
|
|
230
|
+
renderColumns
|
|
210
231
|
});
|
|
211
232
|
out.end();
|
|
212
233
|
};
|
|
@@ -303,7 +324,55 @@ const recover = async (props) => {
|
|
|
303
324
|
};
|
|
304
325
|
const get = async (props) => {
|
|
305
326
|
const { data } = await props.apiClient.getProject(props.id);
|
|
306
|
-
|
|
327
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
328
|
+
writer(props).end(data.project, { fields: PROJECT_FIELDS });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
writer(props).end(projectDetails(data.project), {
|
|
332
|
+
fields: PROJECT_DETAIL_FIELDS,
|
|
333
|
+
humanTitle: "Project"
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
const PROJECT_DETAIL_FIELDS = [
|
|
337
|
+
"name",
|
|
338
|
+
"id",
|
|
339
|
+
"region_id",
|
|
340
|
+
"postgres_version",
|
|
341
|
+
"organization",
|
|
342
|
+
"default_compute",
|
|
343
|
+
"history_retention",
|
|
344
|
+
"created_at",
|
|
345
|
+
"updated_at"
|
|
346
|
+
];
|
|
347
|
+
/** Table-only view of a project; the field names become the row labels. */
|
|
348
|
+
const projectDetails = (project) => ({
|
|
349
|
+
name: project.name,
|
|
350
|
+
id: project.id,
|
|
351
|
+
region_id: project.region_id,
|
|
352
|
+
postgres_version: project.pg_version,
|
|
353
|
+
organization: project.org_id,
|
|
354
|
+
default_compute: formatComputeRange(project.default_endpoint_settings),
|
|
355
|
+
history_retention: project.history_retention_seconds === void 0 ? void 0 : formatDuration(project.history_retention_seconds),
|
|
356
|
+
created_at: project.created_at,
|
|
357
|
+
updated_at: project.updated_at
|
|
358
|
+
});
|
|
359
|
+
const formatComputeRange = (settings) => {
|
|
360
|
+
const min = settings?.autoscaling_limit_min_cu;
|
|
361
|
+
const max = settings?.autoscaling_limit_max_cu;
|
|
362
|
+
if (min === void 0 || max === void 0) return;
|
|
363
|
+
return min === max ? `${min} CU` : `${min}-${max} CU`;
|
|
364
|
+
};
|
|
365
|
+
const DURATION_UNITS = [
|
|
366
|
+
["day", 86400],
|
|
367
|
+
["hour", 3600],
|
|
368
|
+
["minute", 60]
|
|
369
|
+
];
|
|
370
|
+
const formatDuration = (seconds) => {
|
|
371
|
+
for (const [unit, size] of DURATION_UNITS) if (seconds > 0 && seconds % size === 0) {
|
|
372
|
+
const count = seconds / size;
|
|
373
|
+
return `${count} ${unit}${count === 1 ? "" : "s"}`;
|
|
374
|
+
}
|
|
375
|
+
return `${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
307
376
|
};
|
|
308
377
|
const handleMissingOrgId = async (args, cmd) => {
|
|
309
378
|
try {
|
|
@@ -367,4 +436,4 @@ const onPromptState = (state) => {
|
|
|
367
436
|
}
|
|
368
437
|
};
|
|
369
438
|
//#endregion
|
|
370
|
-
export { PROJECT_FIELDS, RECOVERABLE_PROJECT_FIELDS, REGIONS, aliases, builder, command, describe, handler, projects_exports as t };
|
|
439
|
+
export { PROJECT_FIELDS, RECOVERABLE_PROJECT_FIELDS, REGIONS, aliases, builder, command, describe, formatDuration, handler, projects_exports as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.8",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"yaml": "^2.9.0",
|
|
65
65
|
"yargs": "17.7.2",
|
|
66
66
|
"yoctocolors": "^2.1.2",
|
|
67
|
-
"@neon/config": "1.8.3",
|
|
68
67
|
"@neon/config-runtime": "1.7.3",
|
|
68
|
+
"@neon/config": "1.8.3",
|
|
69
69
|
"@neon/sdk": "7.0.0"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|
|
@@ -99,10 +99,10 @@
|
|
|
99
99
|
"tsx": "4.22.3",
|
|
100
100
|
"typescript": "^5.9.0",
|
|
101
101
|
"vitest": "^3.0.9",
|
|
102
|
-
"@neon-internals/cli-core": "0.0.0",
|
|
103
102
|
"@neon-internals/env-core": "0.0.26",
|
|
104
103
|
"@neon/e2e-harness": "0.0.0",
|
|
105
|
-
"@neon/functions": "0.11.0"
|
|
104
|
+
"@neon/functions": "0.11.0",
|
|
105
|
+
"@neon-internals/cli-core": "0.0.0"
|
|
106
106
|
},
|
|
107
107
|
"publishConfig": {
|
|
108
108
|
"access": "public",
|