neon 8.2.0 → 8.3.1
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/analytics.js +26 -0
- package/dist/auth.js +1 -1
- package/dist/commands/branches.js +13 -4
- package/dist/commands/env.js +4 -5
- package/dist/commands/env_output.js +6 -3
- package/dist/commands/snapshots.js +38 -34
- package/dist/error_reporting.js +4158 -0
- package/dist/errors.js +7 -1
- package/dist/index.js +1 -1
- package/dist/init/copy.js +2 -1
- package/dist/init/mode.js +2 -3
- package/dist/utils/formats.js +8 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1488,7 +1488,7 @@ Global options are supported with any Neon CLI command.
|
|
|
1488
1488
|
|
|
1489
1489
|
- <a id="analytics"></a>`--analytics`
|
|
1490
1490
|
|
|
1491
|
-
Analytics are enabled by default to gather information about the CLI commands and options that are used by our customers. This data collection assists in offering support, and allows for a better understanding of typical usage patterns so that we can improve user experience. Neon does not collect user-defined data, such as project IDs or command payloads, except the question passed to `neon ask --prompt`. To opt-out of analytics data collection, specify `--no-analytics` or `--analytics false`.
|
|
1491
|
+
Analytics are enabled by default to gather information about the CLI commands and options that are used by our customers. This data collection assists in offering support, and allows for a better understanding of typical usage patterns so that we can improve user experience. Neon does not collect user-defined data, such as project IDs or command payloads, except the question passed to `neon ask --prompt`. When the CLI fails on an internal bug (a JavaScript `TypeError`, `ReferenceError`, `RangeError`, or `SyntaxError`), it also sends that error to Neon's Sentry: the error type, the message only when it is a standard JavaScript message that names nothing but code (any other message is replaced), the stack trace with file paths cut to the package (`neon/dist/...`), the CLI and Node.js versions, and whether it ran in CI or a coding agent. To opt-out of analytics data collection and error reports, specify `--no-analytics` or `--analytics false`.
|
|
1492
1492
|
|
|
1493
1493
|
- <a id="version"></a>`-v, --version`
|
|
1494
1494
|
|
package/dist/analytics.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getAuthContext } from "./auth_context.js";
|
|
|
7
7
|
import { n as credentialsPath } from "./_chunks/paths-DMq0Lt7a.js";
|
|
8
8
|
import "./config.js";
|
|
9
9
|
import { isCurrentBranchProbe } from "./context.js";
|
|
10
|
+
import { isUnexpectedError } from "./errors.js";
|
|
10
11
|
import { Analytics } from "@segment/analytics-node";
|
|
11
12
|
//#region src/analytics.ts
|
|
12
13
|
const WRITE_KEY = "3SQXn5ejjXWLEJ8xU2PRYhAotLtTaeeV";
|
|
@@ -190,6 +191,7 @@ const analyticsMiddleware = (args) => {
|
|
|
190
191
|
/** How long closing waits for an unfinished account lookup before aborting it. */
|
|
191
192
|
const ATTRIBUTION_WAIT_MS = 1e3;
|
|
192
193
|
let closing;
|
|
194
|
+
const errorReports = [];
|
|
193
195
|
/**
|
|
194
196
|
* Send queued events, then close the client and flush. Later calls share the first close: the
|
|
195
197
|
* psql launcher starts one while psql runs, and a second `closeAndFlush` would only make the SDK
|
|
@@ -198,6 +200,7 @@ let closing;
|
|
|
198
200
|
const closeAnalytics = (opts) => {
|
|
199
201
|
const analytics = client;
|
|
200
202
|
if (!analytics) return Promise.resolve();
|
|
203
|
+
if (closing && opts?.timeout !== void 0) return untilDeadline(closing, Date.now() + opts.timeout);
|
|
201
204
|
if (!closing) {
|
|
202
205
|
const started = Date.now();
|
|
203
206
|
closing = (async () => {
|
|
@@ -215,11 +218,27 @@ const closeAnalytics = (opts) => {
|
|
|
215
218
|
log.debug("Flushing CLI analytics");
|
|
216
219
|
const flush = opts?.timeout === void 0 ? void 0 : { timeout: Math.max(1, opts.timeout - (Date.now() - started)) };
|
|
217
220
|
await analytics.closeAndFlush(flush);
|
|
221
|
+
await settleErrorReports(opts?.timeout === void 0 ? void 0 : started + opts.timeout);
|
|
218
222
|
log.debug("Flushed CLI analytics");
|
|
219
223
|
})();
|
|
220
224
|
}
|
|
221
225
|
return closing;
|
|
222
226
|
};
|
|
227
|
+
const untilDeadline = async (work, deadline) => {
|
|
228
|
+
let timer;
|
|
229
|
+
await Promise.race([work, new Promise((resolve) => {
|
|
230
|
+
timer = setTimeout(resolve, Math.max(0, deadline - Date.now()));
|
|
231
|
+
})]);
|
|
232
|
+
clearTimeout(timer);
|
|
233
|
+
};
|
|
234
|
+
const settleErrorReports = async (deadline) => {
|
|
235
|
+
const reports = Promise.all(errorReports);
|
|
236
|
+
if (deadline === void 0) {
|
|
237
|
+
await reports;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
await untilDeadline(reports, deadline);
|
|
241
|
+
};
|
|
223
242
|
const getErrorAnalyticsEventContext = (_args) => ({
|
|
224
243
|
version: pkg_default.version,
|
|
225
244
|
ci: isCi(),
|
|
@@ -252,6 +271,13 @@ const sendError = (err, errCode) => {
|
|
|
252
271
|
});
|
|
253
272
|
});
|
|
254
273
|
log.debug("Sent CLI error event: %s", errCode);
|
|
274
|
+
if (errCode === "UNKNOWN_ERROR" && isUnexpectedError(err)) {
|
|
275
|
+
const report = import("./error_reporting.js").then(({ reportUnexpectedError }) => reportUnexpectedError(err, redactCredentialTokenIds)).catch((reportError) => {
|
|
276
|
+
log.debug("Could not report the error to Sentry: %s", reportError);
|
|
277
|
+
});
|
|
278
|
+
errorReports.push(report);
|
|
279
|
+
if (closing) closing = closing.then(() => report);
|
|
280
|
+
}
|
|
255
281
|
};
|
|
256
282
|
const trackEvent = (event, properties) => {
|
|
257
283
|
if (!client) return;
|
package/dist/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { log } from "./log.js";
|
|
2
|
-
import { sendError } from "./analytics.js";
|
|
3
2
|
import { matchErrorCode } from "./errors.js";
|
|
3
|
+
import { sendError } from "./analytics.js";
|
|
4
4
|
import { extendTokenSet } from "./utils/auth.js";
|
|
5
5
|
import { createReadStream } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -5,7 +5,7 @@ import { contextBranch, readContextFile } from "../context.js";
|
|
|
5
5
|
import { branchCreateRequest } from "../parameters.gen.js";
|
|
6
6
|
import { writer } from "../writer.js";
|
|
7
7
|
import { EndpointType } from "../utils/api_enums.js";
|
|
8
|
-
import { looksLikeBranchId, looksLikeLSN, looksLikeTimestamp } from "../utils/formats.js";
|
|
8
|
+
import { looksLikeBranchId, looksLikeLSN, looksLikeTimestamp, toIso } from "../utils/formats.js";
|
|
9
9
|
import { branchIdFromProps, fillSingleProject, resolveBranchFromProps } from "../utils/enrichers.js";
|
|
10
10
|
import { getComputeUnits } from "../utils/compute_units.js";
|
|
11
11
|
import { psql } from "../utils/psql.js";
|
|
@@ -230,6 +230,14 @@ const list = async (props) => {
|
|
|
230
230
|
})
|
|
231
231
|
});
|
|
232
232
|
};
|
|
233
|
+
const parseAnnotation = (value) => {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(value);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
238
|
+
throw new Error(`Failed to parse --annotation JSON: ${message}`);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
233
241
|
const create = async (props) => {
|
|
234
242
|
const branches = await props.apiClient.listProjectBranches({ projectId: props.projectId }).then(({ data }) => data.branches);
|
|
235
243
|
const parentProps = (() => {
|
|
@@ -245,6 +253,7 @@ const create = async (props) => {
|
|
|
245
253
|
if (!branch) throw new Error(`Branch ${props.parent} not found`);
|
|
246
254
|
return { parent_id: branch.id };
|
|
247
255
|
})();
|
|
256
|
+
const annotationValue = props.annotation ? parseAnnotation(props.annotation) : void 0;
|
|
248
257
|
if (props.schemaOnly) {
|
|
249
258
|
if (!props.compute) throw new Error("Schema-only branches require a compute endpoint");
|
|
250
259
|
if (props.type !== EndpointType.ReadWrite) throw new Error("Schema-only branches require a read-write compute endpoint");
|
|
@@ -254,7 +263,7 @@ const create = async (props) => {
|
|
|
254
263
|
name: props.name,
|
|
255
264
|
...parentProps,
|
|
256
265
|
...props.schemaOnly ? { init_source: "schema-only" } : {},
|
|
257
|
-
...props["expires-at"] ? { expires_at:
|
|
266
|
+
...props["expires-at"] ? { expires_at: toIso(props["expires-at"], "--expires-at") } : {},
|
|
258
267
|
...props.protected !== void 0 ? { protected: props.protected } : {}
|
|
259
268
|
},
|
|
260
269
|
endpoints: props.compute ? [{
|
|
@@ -262,7 +271,7 @@ const create = async (props) => {
|
|
|
262
271
|
suspend_timeout_seconds: props.suspendTimeout === 0 ? void 0 : props.suspendTimeout,
|
|
263
272
|
...props.cu ? getComputeUnits(props.cu) : void 0
|
|
264
273
|
}] : [],
|
|
265
|
-
annotation_value:
|
|
274
|
+
annotation_value: annotationValue
|
|
266
275
|
}));
|
|
267
276
|
if (branches.find((b) => b.id === data.branch.parent_id)?.protected) log.warning("The parent branch is protected; a unique role password has been generated for the new branch.");
|
|
268
277
|
const out = writer(props);
|
|
@@ -437,7 +446,7 @@ const setExpiration = async (props) => {
|
|
|
437
446
|
...props,
|
|
438
447
|
id: props.branchId
|
|
439
448
|
});
|
|
440
|
-
const expiresAt = typeof props.expiresAt === "string" ?
|
|
449
|
+
const expiresAt = typeof props.expiresAt === "string" ? toIso(props.expiresAt, "--expires-at") : null;
|
|
441
450
|
const { data } = await retryOnLock(() => props.apiClient.updateProjectBranch(props.projectId, branchId, { branch: { expires_at: expiresAt } }));
|
|
442
451
|
writer(props).end(data.branch, {
|
|
443
452
|
fields: BRANCH_FIELDS,
|
package/dist/commands/env.js
CHANGED
|
@@ -9,9 +9,8 @@ import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/en
|
|
|
9
9
|
import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
|
|
10
10
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
11
11
|
import { warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
12
|
-
import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
13
12
|
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BWqJqBDJ.js";
|
|
14
|
-
import { formatPulledEnv } from "./env_output.js";
|
|
13
|
+
import { formatPulledEnv, pulledBranch } from "./env_output.js";
|
|
15
14
|
import { existsSync } from "node:fs";
|
|
16
15
|
import chalk from "chalk";
|
|
17
16
|
//#region src/commands/env.ts
|
|
@@ -135,7 +134,7 @@ const pull = async (props, opts = {}) => {
|
|
|
135
134
|
const selectionEnvKeys = dropped ? dropped.envKeys : props.envKeys ?? [];
|
|
136
135
|
const selectedKeys = props.services !== void 0 || props.envKeys !== void 0 ? envKeysForSelection(selectionServices, selectionEnvKeys) : void 0;
|
|
137
136
|
const branch = opts.branch ?? await resolveBranchRef(props);
|
|
138
|
-
|
|
137
|
+
const namedBranch = opts.announce ? branch : void 0;
|
|
139
138
|
const branchId = branch.branchId;
|
|
140
139
|
const location = {
|
|
141
140
|
cwd,
|
|
@@ -164,7 +163,7 @@ const pull = async (props, opts = {}) => {
|
|
|
164
163
|
});
|
|
165
164
|
const neonVars = pickSelectedVars(pickNeonVars(vars), selectedKeys, props.services !== void 0 ? selectionServices : void 0);
|
|
166
165
|
if (Object.keys(neonVars).length === 0) {
|
|
167
|
-
if (!(dropped !== null && dropped.services.length === 0 && dropped.envKeys.length === 0 && dropped.skipped.length > 0)) log.info(
|
|
166
|
+
if (!(dropped !== null && dropped.services.length === 0 && dropped.envKeys.length === 0 && dropped.skipped.length > 0)) log.info(`No Neon env variables to pull from ${namedBranch ? pulledBranch(namedBranch) : "this branch"}: no DATABASE_URL or enabled Auth / Data API.`);
|
|
168
167
|
return { status: "empty" };
|
|
169
168
|
}
|
|
170
169
|
const { written, removed } = mergeEnvFile(targetPath, neonVars, { managedKeys: managedKeysFor(selectedKeys, unreachedButCurrent(skipped, existingEnv, branchId), existingEnv, props.services !== void 0 ? selectionServices : void 0, props.envKeys !== void 0 ? selectionEnvKeys : void 0) });
|
|
@@ -174,7 +173,7 @@ const pull = async (props, opts = {}) => {
|
|
|
174
173
|
removed,
|
|
175
174
|
file: targetPath,
|
|
176
175
|
...credential && credential.keys.length > 0 ? { credential } : {}
|
|
177
|
-
}, cwd));
|
|
176
|
+
}, cwd, namedBranch));
|
|
178
177
|
if (!fileExisted) ensureGitignored(targetPath);
|
|
179
178
|
const gatewayBaseUrl = neonVars.NEON_AI_GATEWAY_BASE_URL;
|
|
180
179
|
const gatewayToken = neonVars.NEON_AI_GATEWAY_TOKEN;
|
|
@@ -3,6 +3,7 @@ import { u as isFunctionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
|
|
|
3
3
|
import { BRANCH_ENV_KEY, ENV_PULL_KEYS, serviceForEnvKey } from "../env_services.js";
|
|
4
4
|
import { isAbsolute, relative } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
+
import chalk from "chalk";
|
|
6
7
|
//#region src/commands/env_output.ts
|
|
7
8
|
/** Wide enough for the longest label ("Object Storage") plus a gutter, so blocks align. */
|
|
8
9
|
const LABEL_WIDTH = 16;
|
|
@@ -43,10 +44,12 @@ const groupKeys = (keys) => {
|
|
|
43
44
|
}
|
|
44
45
|
return groups;
|
|
45
46
|
};
|
|
46
|
-
const
|
|
47
|
+
const pulledBranch = (branch) => `branch ${chalk.cyan.bold(branch.branchName)} ${chalk.dim(`(${branch.branchId}${branch.usedDefault ? ", project default" : ""})`)}`;
|
|
48
|
+
const formatPulledEnv = (outcome, cwd, branch) => {
|
|
47
49
|
const fresh = new Set(outcome.credential?.fresh ?? []);
|
|
48
50
|
const count = outcome.written.length;
|
|
49
|
-
const
|
|
51
|
+
const source = branch ? ` from ${pulledBranch(branch)}` : "";
|
|
52
|
+
const lines = [`Pulled ${count} Neon variable${count === 1 ? "" : "s"} into ${displayPath(outcome.file, cwd)}${source}`];
|
|
50
53
|
const groups = groupKeys(outcome.written);
|
|
51
54
|
for (const group of GROUP_ORDER) {
|
|
52
55
|
const keys = groups.get(group);
|
|
@@ -61,4 +64,4 @@ const formatPulledEnv = (outcome, cwd) => {
|
|
|
61
64
|
return `${lines.join("\n")}\n`;
|
|
62
65
|
};
|
|
63
66
|
//#endregion
|
|
64
|
-
export { displayPath, formatPulledEnv, row };
|
|
67
|
+
export { displayPath, formatPulledEnv, pulledBranch, row };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
|
-
import { retryOnLock } from "../api.js";
|
|
3
|
+
import { isNeonApiError, retryOnLock } from "../api.js";
|
|
4
4
|
import { writer } from "../writer.js";
|
|
5
|
-
import { looksLikeLSN, looksLikeTimestamp } from "../utils/formats.js";
|
|
5
|
+
import { looksLikeBranchId, looksLikeLSN, looksLikeSnapshotId, looksLikeTimestamp, toIso } from "../utils/formats.js";
|
|
6
6
|
import { branchIdResolve, fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
7
7
|
import { BRANCH_FIELDS } from "./branches.js";
|
|
8
8
|
//#region src/commands/snapshots.ts
|
|
@@ -152,12 +152,6 @@ const handler = (args) => {
|
|
|
152
152
|
};
|
|
153
153
|
/** Narrow an unknown parsed JSON value to a plain object without type casting. */
|
|
154
154
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
155
|
-
/** Normalize a user-supplied date to an ISO 8601 string, or throw a friendly error. */
|
|
156
|
-
const toIso = (value, flag) => {
|
|
157
|
-
const ms = Date.parse(value);
|
|
158
|
-
if (Number.isNaN(ms)) throw new Error(`Invalid ${flag} value: "${value}". Use an RFC 3339 timestamp, e.g. 2025-12-31T23:59:59Z.`);
|
|
159
|
-
return new Date(ms).toISOString();
|
|
160
|
-
};
|
|
161
155
|
/**
|
|
162
156
|
* Resolve a snapshot from an id **or** a name. Snapshot names are not guaranteed
|
|
163
157
|
* unique, so an id match wins; a name that resolves to more than one snapshot is a
|
|
@@ -172,6 +166,25 @@ const resolveSnapshot = async (props) => {
|
|
|
172
166
|
if (byName.length > 1) throw new Error(`Multiple snapshots are named "${props.id}". Re-run with the snapshot id:\n${byName.map((s) => ` ${s.id}`).join("\n")}`);
|
|
173
167
|
throw new Error(`Snapshot "${props.id}" not found.\nAvailable snapshots: ${snapshots.map((s) => `${s.name} (${s.id})`).join(", ") || "none"}`);
|
|
174
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* Runs `call` on the snapshot `props.id` names. An id goes straight to the API without the
|
|
171
|
+
* listing; a 404 sends it through {@link resolveSnapshot}, since a name can look like an id.
|
|
172
|
+
* When the listing has that id, the 404 was about something else (a target branch), so it stands.
|
|
173
|
+
*/
|
|
174
|
+
const withSnapshotId = async (props, call) => {
|
|
175
|
+
let notFound;
|
|
176
|
+
if (looksLikeSnapshotId(props.id)) try {
|
|
177
|
+
return await call(props.id);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (!(isNeonApiError(err) && err.status === 404)) throw err;
|
|
180
|
+
notFound = err;
|
|
181
|
+
}
|
|
182
|
+
const snapshot = await resolveSnapshot(props);
|
|
183
|
+
if (notFound && snapshot.id === props.id) throw notFound;
|
|
184
|
+
return call(snapshot.id);
|
|
185
|
+
};
|
|
186
|
+
/** A `br-…` id needs no branch listing; a name or the context/default branch does. */
|
|
187
|
+
const snapshotBranchId = async (props) => props.branch && looksLikeBranchId(props.branch) ? props.branch : (await resolveBranchRef(props)).branchId;
|
|
175
188
|
const list = async (props) => {
|
|
176
189
|
const { data: { snapshots } } = await props.apiClient.listSnapshots(props.projectId);
|
|
177
190
|
writer(props).end(snapshots, {
|
|
@@ -191,10 +204,7 @@ const get = async (props) => {
|
|
|
191
204
|
const create = async (props) => {
|
|
192
205
|
if (props.lsn !== void 0 && !looksLikeLSN(props.lsn)) throw new Error(`Invalid --lsn value: "${props.lsn}". Expected an LSN like 0/1F3C8A0.`);
|
|
193
206
|
if (props.timestamp !== void 0 && !looksLikeTimestamp(props.timestamp)) throw new Error(`Invalid --timestamp value: "${props.timestamp}". Use an RFC 3339 timestamp, e.g. 2025-01-01T00:00:00Z.`);
|
|
194
|
-
const
|
|
195
|
-
...props,
|
|
196
|
-
branch: props.branch
|
|
197
|
-
});
|
|
207
|
+
const branchId = await snapshotBranchId(props);
|
|
198
208
|
const { data } = await retryOnLock(() => props.apiClient.createSnapshot(props.projectId, branchId, {
|
|
199
209
|
name: props.name,
|
|
200
210
|
timestamp: props.timestamp,
|
|
@@ -209,12 +219,11 @@ const create = async (props) => {
|
|
|
209
219
|
};
|
|
210
220
|
const update = async (props) => {
|
|
211
221
|
if (props.name === void 0 && props.expiresAt === void 0 && !props.clearExpiration) throw new Error("Nothing to update. Pass --name, --expires-at, or --clear-expiration.");
|
|
212
|
-
const snapshot = await resolveSnapshot(props);
|
|
213
222
|
const expiresAt = props.clearExpiration ? null : props.expiresAt !== void 0 ? toIso(props.expiresAt, "--expires-at") : void 0;
|
|
214
|
-
const { data } = await retryOnLock(() => props.apiClient.updateSnapshot(props.projectId,
|
|
223
|
+
const { data } = await withSnapshotId(props, (snapshotId) => retryOnLock(() => props.apiClient.updateSnapshot(props.projectId, snapshotId, { snapshot: {
|
|
215
224
|
name: props.name,
|
|
216
225
|
expires_at: expiresAt
|
|
217
|
-
} }));
|
|
226
|
+
} })));
|
|
218
227
|
writer(props).end(data.snapshot, {
|
|
219
228
|
fields: SNAPSHOT_FIELDS,
|
|
220
229
|
renderColumns: { expires_at: (s) => s.expires_at || "never" }
|
|
@@ -229,17 +238,18 @@ const deleteSnapshot = async (props) => {
|
|
|
229
238
|
});
|
|
230
239
|
};
|
|
231
240
|
const restore = async (props) => {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
241
|
+
const { data } = await withSnapshotId(props, async (snapshotId) => {
|
|
242
|
+
const targetBranchId = props.targetBranch ? await branchIdResolve({
|
|
243
|
+
branch: props.targetBranch,
|
|
244
|
+
projectId: props.projectId,
|
|
245
|
+
apiClient: props.apiClient
|
|
246
|
+
}) : void 0;
|
|
247
|
+
return retryOnLock(() => props.apiClient.restoreSnapshot(props.projectId, snapshotId, {
|
|
248
|
+
name: props.name,
|
|
249
|
+
target_branch_id: targetBranchId,
|
|
250
|
+
finalize_restore: props.finalize
|
|
251
|
+
}));
|
|
252
|
+
});
|
|
243
253
|
const out = writer(props).write(data.branch, {
|
|
244
254
|
fields: BRANCH_FIELDS,
|
|
245
255
|
title: "restored branch"
|
|
@@ -265,10 +275,7 @@ const finalize = async (props) => {
|
|
|
265
275
|
});
|
|
266
276
|
};
|
|
267
277
|
const scheduleGet = async (props) => {
|
|
268
|
-
const
|
|
269
|
-
...props,
|
|
270
|
-
branch: props.branch
|
|
271
|
-
});
|
|
278
|
+
const branchId = await snapshotBranchId(props);
|
|
272
279
|
const { data } = await props.apiClient.getSnapshotSchedule(props.projectId, branchId);
|
|
273
280
|
writer(props).end(data.schedule, {
|
|
274
281
|
fields: SCHEDULE_FIELDS,
|
|
@@ -310,10 +317,6 @@ const parseScheduleJson = (raw) => {
|
|
|
310
317
|
});
|
|
311
318
|
};
|
|
312
319
|
const scheduleSet = async (props) => {
|
|
313
|
-
const { branchId } = await resolveBranchRef({
|
|
314
|
-
...props,
|
|
315
|
-
branch: props.branch
|
|
316
|
-
});
|
|
317
320
|
let schedule;
|
|
318
321
|
if (props.schedule) schedule = parseScheduleJson(props.schedule);
|
|
319
322
|
else if (props.frequency) {
|
|
@@ -324,6 +327,7 @@ const scheduleSet = async (props) => {
|
|
|
324
327
|
if (props.retention !== void 0) item.retention_seconds = props.retention;
|
|
325
328
|
schedule = [item];
|
|
326
329
|
} else throw new Error("Provide --frequency (optionally with --hour/--day/--month/--retention) or --schedule <json>.");
|
|
330
|
+
const branchId = await snapshotBranchId(props);
|
|
327
331
|
await retryOnLock(() => props.apiClient.setSnapshotSchedule(props.projectId, branchId, { schedule }));
|
|
328
332
|
writer(props).end(schedule, {
|
|
329
333
|
fields: SCHEDULE_FIELDS,
|