neon 4.14.5 → 4.15.0
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 +12 -7
- package/dist/claimable/state.js +20 -21
- package/dist/commands/auth.js +9 -11
- package/dist/commands/checkout.js +14 -5
- package/dist/commands/claim.js +20 -19
- package/dist/commands/config.js +2 -1
- package/dist/commands/dev.js +2 -1
- package/dist/commands/env.js +5 -1
- package/dist/context.js +23 -5
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -116,22 +116,23 @@ to skip the dotenv write.
|
|
|
116
116
|
|
|
117
117
|
The command writes:
|
|
118
118
|
|
|
119
|
-
- a `.neon` context
|
|
119
|
+
- a `.neon` context with the project id and pinned branch;
|
|
120
120
|
- an owner-only identity assertion under the CLI config directory;
|
|
121
121
|
- `DATABASE_URL`, `DATABASE_URL_UNPOOLED`, `NEON_BRANCH`, and Auth / Data API variables
|
|
122
122
|
when `neon.ts` declares them (or, with no `neon.ts`, when they are provisioned) to `.env`
|
|
123
123
|
or `.env.local` (disable this with `--no-env-pull`). The write is the same `env pull` used
|
|
124
124
|
after create.
|
|
125
125
|
|
|
126
|
-
Subsequent project commands
|
|
127
|
-
token and send API calls to Claimable Neon. The service decides which
|
|
128
|
-
allowed before claim.
|
|
126
|
+
Subsequent project commands find that assertion by the linked project id, exchange it for a
|
|
127
|
+
short-lived agent token, and send API calls to Claimable Neon. The service decides which
|
|
128
|
+
operations are allowed before claim.
|
|
129
129
|
|
|
130
130
|
```bash
|
|
131
131
|
neon claim status # lifecycle and transfer status
|
|
132
132
|
neon projects get <project-id> # regular CLI command, same agent token
|
|
133
133
|
neon psql --role-name neondb_owner -- -c "select now()"
|
|
134
134
|
neon config plan
|
|
135
|
+
neon checkout main
|
|
135
136
|
neon env pull
|
|
136
137
|
|
|
137
138
|
neon claim accept # create a claim code and open the transfer URL
|
|
@@ -439,8 +440,8 @@ The branch argument is **optional**: run `neon checkout` with no branch in an in
|
|
|
439
440
|
|
|
440
441
|
Branch **id vs name** is detected automatically (a `br-…` value is treated as an id):
|
|
441
442
|
|
|
442
|
-
- **id** — matched strictly by id. A non-existent id is a hard "not found" error (ids are server-assigned, so checkout never creates one).
|
|
443
|
-
- **name** — matched by name. If the name doesn't exist,
|
|
443
|
+
- **id** — matched strictly by id. A non-existent id is a hard "not found" error (ids are server-assigned, so checkout never creates one, including with `--create`).
|
|
444
|
+
- **name** — matched by name. If the name doesn't exist, pass `--create` to create it (equivalent to `neon branch create --name <name>`: branched from the project's default branch with a read-write compute, or from `neon.ts` when that file is present), then check it out. `--create` is a no-op when the name already exists. Without `--create`, an interactive terminal offers to create the branch; in CI or with no TTY the error names `--create`.
|
|
444
445
|
|
|
445
446
|
The project is resolved through the standard neon chain, each entry winning over the next:
|
|
446
447
|
|
|
@@ -456,11 +457,15 @@ The resolved branch is then written (by name) to the same `.neon` file `link` us
|
|
|
456
457
|
$ neon checkout main --project-id polished-snowflake-12345678
|
|
457
458
|
INFO: Checked out branch br-main-branch-87654321 on project polished-snowflake-12345678. Updated /path/to/cwd/.neon.
|
|
458
459
|
|
|
460
|
+
$ neon checkout dev --create --project-id polished-snowflake-12345678
|
|
461
|
+
INFO: Created branch dev (br-dev-branch-12345678).
|
|
462
|
+
INFO: Checked out branch br-dev-branch-12345678 on project polished-snowflake-12345678. Updated /path/to/cwd/.neon.
|
|
463
|
+
|
|
459
464
|
$ cat .neon
|
|
460
465
|
{
|
|
461
466
|
"orgId": "org-abc123",
|
|
462
467
|
"projectId": "polished-snowflake-12345678",
|
|
463
|
-
"branch": "
|
|
468
|
+
"branch": "dev"
|
|
464
469
|
}
|
|
465
470
|
```
|
|
466
471
|
|
package/dist/claimable/state.js
CHANGED
|
@@ -77,35 +77,34 @@ const removeClaimableCredentials = (configDir, projectId) => {
|
|
|
77
77
|
throw error;
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return
|
|
88
|
-
origin: marker.origin,
|
|
89
|
-
projectId: context.projectId,
|
|
90
|
-
...nonEmptyString(context.branch) ? { branch: context.branch } : {}
|
|
91
|
-
};
|
|
80
|
+
const readLinkedClaimableCredentials = (configDir, context) => {
|
|
81
|
+
if (!nonEmptyString(context.projectId)) return null;
|
|
82
|
+
if (!PROJECT_ID.test(context.projectId)) return null;
|
|
83
|
+
return readClaimableCredentials(configDir, context.projectId);
|
|
84
|
+
};
|
|
85
|
+
const claimableCredentialsExist = (configDir, projectId) => {
|
|
86
|
+
if (!PROJECT_ID.test(projectId)) return false;
|
|
87
|
+
return existsSync(claimableCredentialsPath(configDir, projectId));
|
|
92
88
|
};
|
|
93
|
-
const shouldUseClaimableCredentials = (inputs, profileFlag, context) => inputs.apiKeyFlag.trim() === "" && inputs.apiKeyEnv.trim() === "" && inputs.profileEnv.trim() === "" && (profileFlag === void 0 || profileFlag.trim() === "") &&
|
|
89
|
+
const shouldUseClaimableCredentials = (inputs, profileFlag, context, configDir) => inputs.apiKeyFlag.trim() === "" && inputs.apiKeyEnv.trim() === "" && inputs.profileEnv.trim() === "" && (profileFlag === void 0 || profileFlag.trim() === "") && readLinkedClaimableCredentials(configDir, context) !== null;
|
|
94
90
|
/** Management API base URL the CLI uses for a Claimable Neon origin. */
|
|
95
91
|
const claimableApiHost = (origin) => `${new ClaimableClient(origin).origin}/v1`;
|
|
96
92
|
/**
|
|
97
93
|
* Whether this invocation is talking to Claimable Neon, so env resolve must not
|
|
98
94
|
* mint credentials or pull services that require claim.
|
|
99
95
|
*
|
|
100
|
-
* Auth source wins so a same-process checkout that rewrote `.neon`
|
|
101
|
-
*
|
|
102
|
-
*
|
|
96
|
+
* Auth source wins so a same-process checkout that rewrote `.neon` still skips
|
|
97
|
+
* minting. A credential file plus a production API host is an account override
|
|
98
|
+
* and is not claimable.
|
|
103
99
|
*/
|
|
104
100
|
const isClaimableEnvTarget = (props) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
101
|
+
const source = getAuthContext()?.source;
|
|
102
|
+
if (source === "claimable") return true;
|
|
103
|
+
if (source !== void 0) return false;
|
|
104
|
+
if (props.configDir.trim() === "") return false;
|
|
105
|
+
const stored = readLinkedClaimableCredentials(props.configDir, readContextFile(props.contextFile));
|
|
106
|
+
if (stored === null) return false;
|
|
107
|
+
return props.apiHost.trim().replace(/\/+$/, "") === claimableApiHost(stored.origin);
|
|
109
108
|
};
|
|
110
109
|
//#endregion
|
|
111
|
-
export { assertionHasExpired, claimableApiHost, claimableCredentialsPath, isClaimableEnvTarget, listClaimableCredentials, readClaimableCredentials,
|
|
110
|
+
export { assertionHasExpired, claimableApiHost, claimableCredentialsExist, claimableCredentialsPath, isClaimableEnvTarget, listClaimableCredentials, readClaimableCredentials, readLinkedClaimableCredentials, removeClaimableCredentials, shouldUseClaimableCredentials, writeClaimableCredentials };
|
package/dist/commands/auth.js
CHANGED
|
@@ -5,13 +5,13 @@ import { log } from "../log.js";
|
|
|
5
5
|
import { getApiClient, isNeonApiError } from "../api.js";
|
|
6
6
|
import { setAuthContext } from "../auth_context.js";
|
|
7
7
|
import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
|
|
8
|
-
import { currentContextFile, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile } from "../context.js";
|
|
8
|
+
import { currentContextFile, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile } from "../context.js";
|
|
9
9
|
import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-CvnFEQyd.js";
|
|
10
10
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
11
|
import { AuthRefreshError, auth, classifyRefreshFailure, refreshToken } from "../auth.js";
|
|
12
12
|
import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
13
13
|
import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
|
|
14
|
-
import { assertionHasExpired,
|
|
14
|
+
import { assertionHasExpired, claimableCredentialsExist, claimableCredentialsPath, readLinkedClaimableCredentials, shouldUseClaimableCredentials } from "../claimable/state.js";
|
|
15
15
|
import { withExclusiveLock } from "../refresh_lock.js";
|
|
16
16
|
import { readOutgoingCredential, retirePreviousCredential } from "../retire_credential.js";
|
|
17
17
|
import { existsSync } from "node:fs";
|
|
@@ -291,6 +291,7 @@ const ensureAuth = async (props) => {
|
|
|
291
291
|
if (isConfigInit(props)) return;
|
|
292
292
|
if (isProfileCommand(props)) return;
|
|
293
293
|
if (isMcpOauth(props)) return;
|
|
294
|
+
if (isInspectDbUrl(props)) return;
|
|
294
295
|
if (props._[0] === "open") return;
|
|
295
296
|
if (isSkillsCommand(props) || isPluginsCommand(props)) return;
|
|
296
297
|
if (isAskCommand(props)) return;
|
|
@@ -316,15 +317,12 @@ const ensureAuth = async (props) => {
|
|
|
316
317
|
const inputs = credentialInputs();
|
|
317
318
|
const contextFile = typeof props.contextFile === "function" ? props.contextFile() : props.contextFile ?? currentContextFile();
|
|
318
319
|
const localContext = readContextFile(contextFile);
|
|
319
|
-
if (shouldUseClaimableCredentials(inputs, props.profile, localContext)) {
|
|
320
|
-
const
|
|
321
|
-
if (
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
if (stored === null) throw new Error(`The linked project is claimable, but its identity assertion is missing from ${path}. Run \`neon claim create\` in a new directory, or \`neon link\` after claiming the project.`);
|
|
325
|
-
if (assertionHasExpired(stored)) throw new Error(`The identity assertion for ${linked.projectId} has expired. Run \`neon claim delete ${linked.projectId} --yes\` to drop the local record.`);
|
|
320
|
+
if (shouldUseClaimableCredentials(inputs, props.profile, localContext, props.configDir)) {
|
|
321
|
+
const stored = readLinkedClaimableCredentials(props.configDir, localContext);
|
|
322
|
+
if (stored === null || localContext.projectId === void 0) throw new Error("The linked Claimable Neon identity assertion could not be read.");
|
|
323
|
+
const path = claimableCredentialsPath(props.configDir, localContext.projectId);
|
|
324
|
+
if (assertionHasExpired(stored)) throw new Error(`The identity assertion for ${localContext.projectId} has expired. Run \`neon claim delete ${localContext.projectId} --yes\` to drop the local record.`);
|
|
326
325
|
const client = new ClaimableClient(stored.origin);
|
|
327
|
-
if (client.origin !== new ClaimableClient(linked.origin).origin) throw new Error(`The linked .neon file and ${path} name different Claimable Neon services. Delete .neon or the assertion file and run \`neon claim create\` in a new directory.`);
|
|
328
326
|
let token;
|
|
329
327
|
try {
|
|
330
328
|
token = await client.exchange(stored.identityAssertion);
|
|
@@ -346,7 +344,7 @@ const ensureAuth = async (props) => {
|
|
|
346
344
|
log.debug("Using the linked Claimable Neon project's short-lived access token");
|
|
347
345
|
return;
|
|
348
346
|
}
|
|
349
|
-
if (localContext.
|
|
347
|
+
if (localContext.projectId !== void 0 && claimableCredentialsExist(props.configDir, localContext.projectId)) log.warning("This directory is linked to a claimable project, but NEON_API_KEY or NEON_PROFILE is set. This command will use that account credential instead of the unclaimed project. Unset them to keep using the unclaimed project.");
|
|
350
348
|
const selection = selectCredential({
|
|
351
349
|
...inputs,
|
|
352
350
|
profileFlag: props.profile
|
|
@@ -28,7 +28,7 @@ const formatCheckoutPolicyFailure = (opts) => {
|
|
|
28
28
|
return [
|
|
29
29
|
`Branch ${opts.branchName} (${opts.branchId}) was created and checked out, but applying neon.ts to it failed: ${opts.failure}`,
|
|
30
30
|
`The branch is usable but does not match the policy, and \`${cli} checkout\` never reconciles a branch that already exists.`,
|
|
31
|
-
`Fix the cause above, then run \`${cli} deploy --update-existing${envArg}\` to apply the policy to it — or, if your policy only configures new branches (keyed on \`!branch.exists\`), delete the branch and check it out again: \`${cli} branches delete ${opts.branchName}\` then \`${cli} checkout ${opts.branchName}${envArg}\`.`
|
|
31
|
+
`Fix the cause above, then run \`${cli} deploy --update-existing${envArg}\` to apply the policy to it — or, if your policy only configures new branches (keyed on \`!branch.exists\`), delete the branch and check it out again: \`${cli} branches delete ${opts.branchName}\` then \`${cli} checkout ${opts.branchName} --create${envArg}\`.`
|
|
32
32
|
].join("\n");
|
|
33
33
|
};
|
|
34
34
|
const builder = (argv) => argv.usage("$0 checkout [id|name] [options]").positional("id", {
|
|
@@ -44,6 +44,11 @@ const builder = (argv) => argv.usage("$0 checkout [id|name] [options]").position
|
|
|
44
44
|
type: "boolean",
|
|
45
45
|
default: true
|
|
46
46
|
},
|
|
47
|
+
create: {
|
|
48
|
+
describe: "Create the named branch if it does not exist, then check it out",
|
|
49
|
+
type: "boolean",
|
|
50
|
+
default: false
|
|
51
|
+
},
|
|
47
52
|
...envFlag,
|
|
48
53
|
env: {
|
|
49
54
|
...envFlag.env,
|
|
@@ -52,12 +57,14 @@ const builder = (argv) => argv.usage("$0 checkout [id|name] [options]").position
|
|
|
52
57
|
}).example([
|
|
53
58
|
["$0 checkout", "Pick a branch interactively from the project in the closest .neon file"],
|
|
54
59
|
["$0 checkout main", "Pin the branch named \"main\" in the closest .neon file"],
|
|
55
|
-
["$0 checkout
|
|
60
|
+
["$0 checkout dev --create", "Create and pin a missing branch named dev"],
|
|
61
|
+
["$0 checkout feat --create --env .env.local", "Create feat from neon.ts, loading Function env from .env.local first"],
|
|
56
62
|
["$0 checkout br-cool-snow-12345678 --project-id project-id-123", "Pin a branch by id for an explicit project"]
|
|
57
63
|
]);
|
|
58
64
|
const handler = async (props) => {
|
|
59
65
|
const previousBranch = contextBranch(readContextFile(props.contextFile));
|
|
60
66
|
if (previousBranch) log.info("%s Currently on branch %s", chalk.dim("→"), chalk.cyan.bold(previousBranch));
|
|
67
|
+
if (props.create && !props.id?.trim()) throw new Error(`No branch specified. Pass a branch name with --create (e.g. \`${getCliName()} checkout dev --create\`).`);
|
|
61
68
|
const projectId = await resolveProjectId(props);
|
|
62
69
|
const { branchId, branchName, created, policyApplied, policyFailure } = await resolveBranchId(props, projectId);
|
|
63
70
|
const orgId = await resolveOrgId(props, projectId);
|
|
@@ -137,8 +144,10 @@ const resolveBranchId = async (props, projectId) => {
|
|
|
137
144
|
created: false,
|
|
138
145
|
policyApplied: false
|
|
139
146
|
};
|
|
140
|
-
if (
|
|
141
|
-
|
|
147
|
+
if (props.create) return createCheckoutBranch(props, projectId, ref, branches);
|
|
148
|
+
const missingName = notFoundMessage(ref, branches, { suggestCreate: true });
|
|
149
|
+
if (isCi() || !process.stdout.isTTY) throw new Error(missingName);
|
|
150
|
+
log.error(missingName);
|
|
142
151
|
const { create } = await prompts({
|
|
143
152
|
type: "confirm",
|
|
144
153
|
name: "create",
|
|
@@ -178,7 +187,7 @@ const createCheckoutBranch = async (props, projectId, name, branches) => {
|
|
|
178
187
|
policyApplied: false
|
|
179
188
|
};
|
|
180
189
|
};
|
|
181
|
-
const notFoundMessage = (ref, branches) => `Branch ${ref} not found
|
|
190
|
+
const notFoundMessage = (ref, branches, opts) => `Branch ${ref} not found.${opts?.suggestCreate ? " Pass --create to create it." : ""}\nAvailable branches: ${branches.map((b) => b.name).join(", ")}`;
|
|
182
191
|
/**
|
|
183
192
|
* Resolve the org id to heal into the context file.
|
|
184
193
|
*
|
package/dist/commands/claim.js
CHANGED
|
@@ -8,7 +8,7 @@ import { NEON_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagVal
|
|
|
8
8
|
import { resolveEnvFilePath } from "../env_file.js";
|
|
9
9
|
import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
10
10
|
import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
|
|
11
|
-
import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials,
|
|
11
|
+
import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials, readLinkedClaimableCredentials, removeClaimableCredentials, writeClaimableCredentials } from "../claimable/state.js";
|
|
12
12
|
import { writer } from "../writer.js";
|
|
13
13
|
import { noPassthrough } from "../utils/flags.js";
|
|
14
14
|
import { pull } from "./env.js";
|
|
@@ -184,19 +184,23 @@ const resolveTarget = (props) => {
|
|
|
184
184
|
rejectExplicitAccountCredential(props);
|
|
185
185
|
const requested = props.projectId?.trim();
|
|
186
186
|
const context = readContextFile(props.contextFile);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
if (requested) {
|
|
188
|
+
const credentials = readClaimableCredentials(props.configDir, requested);
|
|
189
|
+
if (credentials === null) throw new Error(`The identity assertion for ${requested} is missing. The project cannot be managed from this machine; claim it through its existing verification URL or run \`neon link\` after it is claimed.`);
|
|
190
|
+
return {
|
|
191
|
+
projectId: requested,
|
|
192
|
+
credentials,
|
|
193
|
+
client: new ClaimableClient(credentials.origin),
|
|
194
|
+
contextMatches: context.projectId === requested
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const linked = readLinkedClaimableCredentials(props.configDir, context);
|
|
198
|
+
if (linked === null) throw new Error("This directory is not linked to a claimable project. Pass a project id from `neon claim list`, or run `neon claim create` first.");
|
|
195
199
|
return {
|
|
196
|
-
projectId,
|
|
197
|
-
credentials,
|
|
198
|
-
client,
|
|
199
|
-
contextMatches
|
|
200
|
+
projectId: linked.projectId,
|
|
201
|
+
credentials: linked,
|
|
202
|
+
client: new ClaimableClient(linked.origin),
|
|
203
|
+
contextMatches: true
|
|
200
204
|
};
|
|
201
205
|
};
|
|
202
206
|
const requireLiveIdentity = (credentials) => {
|
|
@@ -210,7 +214,7 @@ const clearLocalRecord = (props, projectId, contextMatches) => {
|
|
|
210
214
|
const create = async (props) => {
|
|
211
215
|
rejectExplicitAccountCredential(props);
|
|
212
216
|
const existing = readContextFile(props.contextFile);
|
|
213
|
-
if (existing.projectId || existing.orgId
|
|
217
|
+
if (existing.projectId || existing.orgId) throw new Error(`${props.contextFile} already links this directory to a Neon project. Run \`neon claim create\` from an unlinked directory.`);
|
|
214
218
|
const contextFileExisted = existsSync(props.contextFile);
|
|
215
219
|
const cwd = props.cwd ?? process.cwd();
|
|
216
220
|
const envFile = props.envPull ? resolveEnvFilePath(cwd, props.file) : void 0;
|
|
@@ -241,11 +245,7 @@ const create = async (props) => {
|
|
|
241
245
|
localStateWritten = true;
|
|
242
246
|
applyContext(props.contextFile, {
|
|
243
247
|
projectId: registration.project.id,
|
|
244
|
-
branch: registration.project.branchId
|
|
245
|
-
claimable: {
|
|
246
|
-
version: 1,
|
|
247
|
-
origin: client.origin
|
|
248
|
-
}
|
|
248
|
+
branch: registration.project.branchId
|
|
249
249
|
});
|
|
250
250
|
contextWritten = true;
|
|
251
251
|
const token = await client.exchange(registration.identityAssertion);
|
|
@@ -261,6 +261,7 @@ const create = async (props) => {
|
|
|
261
261
|
apiClient,
|
|
262
262
|
apiKey: token.accessToken,
|
|
263
263
|
apiHost,
|
|
264
|
+
configDir: props.configDir,
|
|
264
265
|
contextFile: props.contextFile,
|
|
265
266
|
output: props.output,
|
|
266
267
|
projectId: registration.project.id,
|
package/dist/commands/config.js
CHANGED
|
@@ -2,7 +2,7 @@ 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 { getApiClient } from "../api.js";
|
|
5
|
-
import { contextBranch, readContextFile } from "../context.js";
|
|
5
|
+
import { contextBranch, ensureDirectoryGitignored, readContextFile } from "../context.js";
|
|
6
6
|
import { getCliName } from "../utils/cli_name.js";
|
|
7
7
|
import { toNeonConfigView } from "../config_format.js";
|
|
8
8
|
import { declaredNeonServices } from "../config_services.js";
|
|
@@ -229,6 +229,7 @@ const initCmd = async (props) => {
|
|
|
229
229
|
const args = installArgs(pm, missing);
|
|
230
230
|
if (props.install === false) log.info("Install the Neon config packages to use neon.ts: %s", formatInstallCommand(pm, missing));
|
|
231
231
|
else {
|
|
232
|
+
ensureDirectoryGitignored(join(cwd, "node_modules"));
|
|
232
233
|
log.info("Installing %s with %s…", missing.join(", "), pm);
|
|
233
234
|
if (!await run(pm, args, cwd)) log.warning("Could not install the config packages automatically. Run by hand: %s", formatInstallCommand(pm, missing));
|
|
234
235
|
}
|
package/dist/commands/dev.js
CHANGED
|
@@ -89,7 +89,8 @@ const devEnvContext = (props, branchId, cwd) => {
|
|
|
89
89
|
...props.apiHost ? { apiHost: props.apiHost } : {},
|
|
90
90
|
...isClaimableEnvTarget({
|
|
91
91
|
apiHost: props.apiHost,
|
|
92
|
-
contextFile: props.contextFile ?? ""
|
|
92
|
+
contextFile: props.contextFile ?? "",
|
|
93
|
+
configDir: props.configDir ?? ""
|
|
93
94
|
}) ? { claimable: true } : {}
|
|
94
95
|
};
|
|
95
96
|
};
|
package/dist/commands/env.js
CHANGED
|
@@ -122,7 +122,11 @@ const NEON_OWNED_ENV_KEYS = [
|
|
|
122
122
|
];
|
|
123
123
|
const pull = async (props, opts = {}) => {
|
|
124
124
|
const cwd = props.cwd ?? process.cwd();
|
|
125
|
-
const claimable = isClaimableEnvTarget(
|
|
125
|
+
const claimable = isClaimableEnvTarget({
|
|
126
|
+
apiHost: props.apiHost,
|
|
127
|
+
contextFile: props.contextFile,
|
|
128
|
+
configDir: props.configDir ?? ""
|
|
129
|
+
});
|
|
126
130
|
const dropped = claimable && (props.services !== void 0 || props.envKeys !== void 0) ? dropClaimableUnsupported(props.services ?? [], props.envKeys ?? []) : null;
|
|
127
131
|
const selectionServices = dropped ? dropped.services : props.services ?? [];
|
|
128
132
|
const selectionEnvKeys = dropped ? dropped.envKeys : props.envKeys ?? [];
|
package/dist/context.js
CHANGED
|
@@ -55,6 +55,12 @@ const isMcpCommand = (args) => args._[0] === "mcp";
|
|
|
55
55
|
const isSkillsCommand = (args) => args._[0] === "skills" || args._[0] === "skill";
|
|
56
56
|
const isPluginsCommand = (args) => args._[0] === "plugins" || args._[0] === "plugin";
|
|
57
57
|
const isAskCommand = (args) => args._[0] === "ask";
|
|
58
|
+
/**
|
|
59
|
+
* `inspect db --db-url` talks to Postgres with that URI. Skip only when the
|
|
60
|
+
* parsed value is nonempty: empty `--db-url` still goes through project
|
|
61
|
+
* resolution and needs an API client.
|
|
62
|
+
*/
|
|
63
|
+
const isInspectDbUrl = (args) => (args._[0] === "inspect" || args._[0] === "inspection") && typeof args.dbUrl === "string" && args.dbUrl.length > 0;
|
|
58
64
|
/** Raw argv is required because auth middleware runs before MCP flags are parsed. */
|
|
59
65
|
const isMcpOauth = (args) => isMcpCommand(args) && argvEnablesMcpOauth(process.argv);
|
|
60
66
|
const OAUTH_FALSE = /* @__PURE__ */ new Set([
|
|
@@ -193,21 +199,33 @@ const setContext = (file, context) => {
|
|
|
193
199
|
* must not be blocked by a `.gitignore` write error.
|
|
194
200
|
*/
|
|
195
201
|
const ensureGitignored = (file) => {
|
|
202
|
+
ensureGitignoreEntry(file, basenameOf(file));
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Make sure a directory is ignored by the `.gitignore` in its parent. The written entry keeps
|
|
206
|
+
* the conventional trailing slash, while coverage checks also recognize an existing entry
|
|
207
|
+
* without one.
|
|
208
|
+
*/
|
|
209
|
+
const ensureDirectoryGitignored = (directory) => {
|
|
210
|
+
const normalized = directory.replace(/[\\/]+$/, "");
|
|
211
|
+
ensureGitignoreEntry(directory, `${basenameOf(normalized)}/`);
|
|
212
|
+
};
|
|
213
|
+
const ensureGitignoreEntry = (path, entry) => {
|
|
196
214
|
try {
|
|
197
|
-
const dir = dirname(
|
|
198
|
-
const entry = basenameOf(file);
|
|
215
|
+
const dir = dirname(path);
|
|
199
216
|
const gitignorePath = resolve(dir, GITIGNORE_FILE);
|
|
217
|
+
const coveredEntry = entry.replace(/\/$/, "");
|
|
200
218
|
if (!existsSync(gitignorePath)) {
|
|
201
219
|
writeFileSync(gitignorePath, `${entry}\n`);
|
|
202
220
|
return;
|
|
203
221
|
}
|
|
204
222
|
const current = readFileSync(gitignorePath, "utf-8");
|
|
205
|
-
if (hasGitignoreEntry(current,
|
|
223
|
+
if (hasGitignoreEntry(current, coveredEntry)) return;
|
|
206
224
|
const addition = `${current.length > 0 && !current.endsWith("\n") ? "\n" : ""}${entry}\n`;
|
|
207
225
|
writeFileSync(gitignorePath, current + addition);
|
|
208
226
|
} catch (err) {
|
|
209
227
|
const message = err instanceof Error ? err.message : String(err);
|
|
210
|
-
log.debug("Failed to update .gitignore next to %s: %s",
|
|
228
|
+
log.debug("Failed to update .gitignore next to %s: %s", path, message);
|
|
211
229
|
}
|
|
212
230
|
};
|
|
213
231
|
const basenameOf = (file) => {
|
|
@@ -242,4 +260,4 @@ const globToRegExp = (pattern) => {
|
|
|
242
260
|
return new RegExp(`^${source}$`);
|
|
243
261
|
};
|
|
244
262
|
//#endregion
|
|
245
|
-
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
|
263
|
+
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureDirectoryGitignored, ensureGitignored, isApiKeysCommand, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"yaml": "^2.9.0",
|
|
64
64
|
"yargs": "17.7.2",
|
|
65
65
|
"yoctocolors": "^2.1.2",
|
|
66
|
-
"@neon/config
|
|
67
|
-
"@neon/
|
|
68
|
-
"@neon/
|
|
66
|
+
"@neon/config": "1.3.3",
|
|
67
|
+
"@neon/config-runtime": "1.2.4",
|
|
68
|
+
"@neon/sdk": "4.1.0"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
71
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
"tsx": "4.22.3",
|
|
98
98
|
"typescript": "^5.9.0",
|
|
99
99
|
"vitest": "^3.0.9",
|
|
100
|
-
"@neon-internals/env-core": "0.0.10",
|
|
101
100
|
"@neon-internals/cli-core": "0.0.0",
|
|
101
|
+
"@neon-internals/env-core": "0.0.11",
|
|
102
102
|
"@neon/e2e-harness": "0.0.0",
|
|
103
103
|
"@neon/functions": "0.9.0"
|
|
104
104
|
},
|