neon 8.3.0 → 8.3.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 +3 -1
- package/dist/commands/snapshots.js +38 -28
- package/dist/error_reporting.js +4043 -2
- package/dist/init/copy.js +25 -3
- package/dist/init/run.js +137 -67
- package/dist/init/tooling.js +13 -3
- package/dist/mcp/mint.js +8 -3
- package/dist/utils/formats.js +2 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -839,7 +839,9 @@ The target directory must be empty unless you pass `--force` (a lone `.git` is i
|
|
|
839
839
|
|
|
840
840
|
Recommended installs the Neon plugin for every detected agent (global config, project folders, and the host CLI). Plugin-capable agents get the plugin; the rest get skills and MCP. If none are detected, it installs the default Neon skills for Cursor and Codex in this directory (`./.agents/skills`) and does not configure MCP. It then links a project when the CLI is authenticated, and writes a default `neon.ts` (Postgres only) using the package manager already in the directory.
|
|
841
841
|
|
|
842
|
-
Unauthenticated `-y` skips linking and prints the next step: sign up at https://neon.com/signup, then `neon login`, `neon link`, or `neon claim create` (no account, expires in 72 hours unless claimed). `-y` never opens a browser. `-y` with `--project-id` or other account flags links when the CLI is already signed in;
|
|
842
|
+
Unauthenticated `-y` skips linking and prints the next step: sign up at https://neon.com/signup, then `neon login`, `neon link`, or `neon claim create` (no account, expires in 72 hours unless claimed). `-y` never opens a browser. `-y` with `--project-id` or other account flags links when the CLI is already signed in. Without credentials, `--org-id`, `--project-id`, and `--branch` together write `.neon` offline (as `neon link --no-checks` does), and init prints `neon login` as the next step; Neon checks the IDs on the first command that reaches it. With only some of those flags, init still installs agent tooling and writes `neon.ts`, then prints `neon login` and the `neon link` command with those flags.
|
|
843
|
+
|
|
844
|
+
When init picks MCP auth (no `--mcp-auth`) and the CLI credential is an organization or project-scoped API key, which cannot mint the MCP server's key, init configures MCP with OAuth instead and continues. With `--mcp-auth api-key`, init skips MCP, finishes linking and `neon.ts`, then reports `Neon setup failed.` with the `neon mcp` commands to retry and exits 1.
|
|
843
845
|
|
|
844
846
|
Custom asks how to add Neon to coding agents (plugin, skills and MCP separately, or skip), then how to get a project (sign in and link, or a claimable project when you are not signed in), then which services `neon.ts` should declare. Skills and MCP use separate agent pickers, and either can be skipped. Postgres is listed first as always included. The package manager is inferred from the directory; Custom asks only when none is detected. `--skill` selects skills (not the plugin) and skips that picker. MCP flags (`--mcp-config-location`, `--mcp-auth`, `--mcp-project-scoped`) select skills and MCP. `--mcp-config-location` is where the config is written: `global` maps to `neon mcp`, and `project` maps to `neon mcp --project`. With `-y`, the location defaults to `global`; interactive Custom asks. If some selected agents cannot write that location, init warns and skips MCP for those agents while continuing with the supported agents. It fails when none of the selected agents support the location. `--mcp-project-scoped` limits MCP tools to the linked Neon project by passing its ID to `neon mcp --project-id`. Omitted is false, including in Custom; there is no prompt. `--no-agent-setup` skips agent setup.
|
|
845
847
|
|
|
@@ -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, toIso } 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
|
|
@@ -166,6 +166,25 @@ const resolveSnapshot = async (props) => {
|
|
|
166
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")}`);
|
|
167
167
|
throw new Error(`Snapshot "${props.id}" not found.\nAvailable snapshots: ${snapshots.map((s) => `${s.name} (${s.id})`).join(", ") || "none"}`);
|
|
168
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;
|
|
169
188
|
const list = async (props) => {
|
|
170
189
|
const { data: { snapshots } } = await props.apiClient.listSnapshots(props.projectId);
|
|
171
190
|
writer(props).end(snapshots, {
|
|
@@ -185,10 +204,7 @@ const get = async (props) => {
|
|
|
185
204
|
const create = async (props) => {
|
|
186
205
|
if (props.lsn !== void 0 && !looksLikeLSN(props.lsn)) throw new Error(`Invalid --lsn value: "${props.lsn}". Expected an LSN like 0/1F3C8A0.`);
|
|
187
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.`);
|
|
188
|
-
const
|
|
189
|
-
...props,
|
|
190
|
-
branch: props.branch
|
|
191
|
-
});
|
|
207
|
+
const branchId = await snapshotBranchId(props);
|
|
192
208
|
const { data } = await retryOnLock(() => props.apiClient.createSnapshot(props.projectId, branchId, {
|
|
193
209
|
name: props.name,
|
|
194
210
|
timestamp: props.timestamp,
|
|
@@ -203,12 +219,11 @@ const create = async (props) => {
|
|
|
203
219
|
};
|
|
204
220
|
const update = async (props) => {
|
|
205
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.");
|
|
206
|
-
const snapshot = await resolveSnapshot(props);
|
|
207
222
|
const expiresAt = props.clearExpiration ? null : props.expiresAt !== void 0 ? toIso(props.expiresAt, "--expires-at") : void 0;
|
|
208
|
-
const { data } = await retryOnLock(() => props.apiClient.updateSnapshot(props.projectId,
|
|
223
|
+
const { data } = await withSnapshotId(props, (snapshotId) => retryOnLock(() => props.apiClient.updateSnapshot(props.projectId, snapshotId, { snapshot: {
|
|
209
224
|
name: props.name,
|
|
210
225
|
expires_at: expiresAt
|
|
211
|
-
} }));
|
|
226
|
+
} })));
|
|
212
227
|
writer(props).end(data.snapshot, {
|
|
213
228
|
fields: SNAPSHOT_FIELDS,
|
|
214
229
|
renderColumns: { expires_at: (s) => s.expires_at || "never" }
|
|
@@ -223,17 +238,18 @@ const deleteSnapshot = async (props) => {
|
|
|
223
238
|
});
|
|
224
239
|
};
|
|
225
240
|
const restore = async (props) => {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
+
});
|
|
237
253
|
const out = writer(props).write(data.branch, {
|
|
238
254
|
fields: BRANCH_FIELDS,
|
|
239
255
|
title: "restored branch"
|
|
@@ -259,10 +275,7 @@ const finalize = async (props) => {
|
|
|
259
275
|
});
|
|
260
276
|
};
|
|
261
277
|
const scheduleGet = async (props) => {
|
|
262
|
-
const
|
|
263
|
-
...props,
|
|
264
|
-
branch: props.branch
|
|
265
|
-
});
|
|
278
|
+
const branchId = await snapshotBranchId(props);
|
|
266
279
|
const { data } = await props.apiClient.getSnapshotSchedule(props.projectId, branchId);
|
|
267
280
|
writer(props).end(data.schedule, {
|
|
268
281
|
fields: SCHEDULE_FIELDS,
|
|
@@ -304,10 +317,6 @@ const parseScheduleJson = (raw) => {
|
|
|
304
317
|
});
|
|
305
318
|
};
|
|
306
319
|
const scheduleSet = async (props) => {
|
|
307
|
-
const { branchId } = await resolveBranchRef({
|
|
308
|
-
...props,
|
|
309
|
-
branch: props.branch
|
|
310
|
-
});
|
|
311
320
|
let schedule;
|
|
312
321
|
if (props.schedule) schedule = parseScheduleJson(props.schedule);
|
|
313
322
|
else if (props.frequency) {
|
|
@@ -318,6 +327,7 @@ const scheduleSet = async (props) => {
|
|
|
318
327
|
if (props.retention !== void 0) item.retention_seconds = props.retention;
|
|
319
328
|
schedule = [item];
|
|
320
329
|
} else throw new Error("Provide --frequency (optionally with --hour/--day/--month/--retention) or --schedule <json>.");
|
|
330
|
+
const branchId = await snapshotBranchId(props);
|
|
321
331
|
await retryOnLock(() => props.apiClient.setSnapshotSchedule(props.projectId, branchId, { schedule }));
|
|
322
332
|
writer(props).end(schedule, {
|
|
323
333
|
fields: SCHEDULE_FIELDS,
|