neon 4.14.3 → 4.14.5
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 +18 -6
- package/dist/_chunks/{env-CZmstYrY.js → env-DsakkOVZ.js} +138 -2
- package/dist/claimable/state.js +20 -1
- package/dist/commands/claim.js +35 -21
- package/dist/commands/dev.js +7 -2
- package/dist/commands/env.js +26 -7
- package/dist/dev/env.js +2 -2
- package/dist/env_file.js +2 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -105,16 +105,23 @@ sends:
|
|
|
105
105
|
}
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
Object Storage, Functions, and the AI Gateway
|
|
109
|
-
recorded,
|
|
110
|
-
not
|
|
108
|
+
Object Storage, Functions, and the AI Gateway passed with `--service` are sent so
|
|
109
|
+
demand is recorded, then reported as unavailable until the project is claimed. The CLI
|
|
110
|
+
does not drop them from the request.
|
|
111
|
+
|
|
112
|
+
The bundled env pull still follows `neon.ts`. A `neon.ts` that names AI Gateway,
|
|
113
|
+
Functions, or Object Storage fails the pull, and the create is rolled back. Use
|
|
114
|
+
`--config` for a different policy file (registration and that pull), or `--no-env-pull`
|
|
115
|
+
to skip the dotenv write.
|
|
111
116
|
|
|
112
117
|
The command writes:
|
|
113
118
|
|
|
114
119
|
- a `.neon` context that identifies the project and Claimable Neon service;
|
|
115
120
|
- an owner-only identity assertion under the CLI config directory;
|
|
116
|
-
- `DATABASE_URL` and
|
|
117
|
-
(
|
|
121
|
+
- `DATABASE_URL`, `DATABASE_URL_UNPOOLED`, `NEON_BRANCH`, and Auth / Data API variables
|
|
122
|
+
when `neon.ts` declares them (or, with no `neon.ts`, when they are provisioned) to `.env`
|
|
123
|
+
or `.env.local` (disable this with `--no-env-pull`). The write is the same `env pull` used
|
|
124
|
+
after create.
|
|
118
125
|
|
|
119
126
|
Subsequent project commands automatically exchange the assertion for a short-lived agent
|
|
120
127
|
token and send API calls to Claimable Neon. The service decides which operations are
|
|
@@ -125,7 +132,7 @@ neon claim status # lifecycle and transfer status
|
|
|
125
132
|
neon projects get <project-id> # regular CLI command, same agent token
|
|
126
133
|
neon psql --role-name neondb_owner -- -c "select now()"
|
|
127
134
|
neon config plan
|
|
128
|
-
neon env pull
|
|
135
|
+
neon env pull
|
|
129
136
|
|
|
130
137
|
neon claim accept # create a claim code and open the transfer URL
|
|
131
138
|
neon claim delete --yes # permanently delete an unclaimed project
|
|
@@ -497,6 +504,8 @@ The human-readable summary line goes to stderr and the diff body to stdout, so `
|
|
|
497
504
|
2. **`neon.ts`**, when the working directory has one — the policy is the source of truth, same as `neon dev` and `neon deploy`. Declared function URLs are derived from the branch connection host; the function does not have to be deployed.
|
|
498
505
|
3. **Everything the branch has** otherwise — Postgres, Neon Auth, the Data API, object storage, and function invocation URLs read back from the branch, plus the AI Gateway. The gateway has no branch-level state to read back, so a bare `env pull` asks for it rather than detecting it and may mint a branch credential. To leave it out, name only what you do want with `--service` and/or `--env`.
|
|
499
506
|
|
|
507
|
+
On an unclaimed Claimable Neon project, `neon.ts` is still the source of truth when it only declares Postgres, Auth, and the Data API. A `neon.ts` that declares AI Gateway, Functions, or Object Storage fails: those cannot be used until the project is claimed. Without a `neon.ts`, a bare pull writes provisioned Postgres, Auth, and Data API. Naming AI Gateway, Functions, or Object Storage with `--service` / `--env` warns and writes nothing for them. `--config` selects the policy file, the same flag as `claim create` and `config plan`.
|
|
508
|
+
|
|
500
509
|
If the gateway can't be resolved, it is dropped with a warning and the rest of the pull still lands. Gateway variables already in your file for *this* branch are left alone — a pull that couldn't reach the gateway is no evidence the branch has stopped having one — while ones left over from a different branch are pruned like any other stale value.
|
|
501
510
|
|
|
502
511
|
```bash
|
|
@@ -506,6 +515,9 @@ neon env pull
|
|
|
506
515
|
# Pull a specific branch into a specific file
|
|
507
516
|
neon env pull --branch preview --file .env.preview
|
|
508
517
|
|
|
518
|
+
# Use a specific neon.ts
|
|
519
|
+
neon env pull --config ./claimable.ts
|
|
520
|
+
|
|
509
521
|
# Only the AI Gateway
|
|
510
522
|
neon env pull --service ai-gateway
|
|
511
523
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { log } from "../log.js";
|
|
2
2
|
import { getCliName } from "../utils/cli_name.js";
|
|
3
|
+
import { declaredNeonServices } from "../config_services.js";
|
|
3
4
|
import { S as toEntries, _ as functionBaseUrlKey, a as envKeysForSelection, b as previewCredentialScopes, d as servicesForEnvKeys, f as NEON_ENV_VAR_KEYS, g as fetchEnvKeysState, h as credentialName, i as ENV_PULL_SERVICES, m as credentialEnvKeys, p as createApiFromOptions, u as serviceForEnvKey, v as isFunctionBaseUrlKey, x as resolveBranchPolicy, y as policyEnvKeys } from "./env_services-CAWrZTWa.js";
|
|
4
5
|
import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
|
|
5
6
|
import { credentialScopesSatisfied } from "@neon/config/v1";
|
|
@@ -232,6 +233,134 @@ var ServiceNotOnBranchError = class extends Error {
|
|
|
232
233
|
this.name = "ServiceNotOnBranchError";
|
|
233
234
|
}
|
|
234
235
|
};
|
|
236
|
+
const CLAIMABLE_UNSUPPORTED_SERVICES = /* @__PURE__ */ new Set([
|
|
237
|
+
"ai-gateway",
|
|
238
|
+
"functions",
|
|
239
|
+
"object-storage"
|
|
240
|
+
]);
|
|
241
|
+
const CLAIMABLE_RESOLVE_ORDER = [
|
|
242
|
+
"postgres",
|
|
243
|
+
"auth",
|
|
244
|
+
"data-api"
|
|
245
|
+
];
|
|
246
|
+
const emptyClaimableCredential = () => ({
|
|
247
|
+
issued: false,
|
|
248
|
+
keys: [],
|
|
249
|
+
revoked: [],
|
|
250
|
+
superseded: []
|
|
251
|
+
});
|
|
252
|
+
const isClaimableUnsupportedService = (service) => CLAIMABLE_UNSUPPORTED_SERVICES.has(service);
|
|
253
|
+
const dropClaimableUnsupported = (services, envKeys) => {
|
|
254
|
+
const skipped = [];
|
|
255
|
+
const keptServices = [];
|
|
256
|
+
for (const service of services) {
|
|
257
|
+
if (isClaimableUnsupportedService(service)) {
|
|
258
|
+
if (!skipped.includes(service)) skipped.push(service);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
keptServices.push(service);
|
|
262
|
+
}
|
|
263
|
+
const keptKeys = [];
|
|
264
|
+
for (const key of envKeys) {
|
|
265
|
+
const service = serviceForEnvKey(key);
|
|
266
|
+
if (service !== null && isClaimableUnsupportedService(service)) {
|
|
267
|
+
if (!skipped.includes(service)) skipped.push(service);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
keptKeys.push(key);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
services: keptServices,
|
|
274
|
+
envKeys: keptKeys,
|
|
275
|
+
skipped
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
const warnClaimableSkipped = (skipped) => {
|
|
279
|
+
if (skipped.length === 0) return;
|
|
280
|
+
log.warning("Skipped %s: not available on an unclaimed Claimable Neon project. Postgres, Auth, and the Data API can be pulled when they are provisioned.", skipped.join(", "));
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Claimable Neon does not complete concurrent Management API reads.
|
|
284
|
+
* Resolve one allowed service at a time.
|
|
285
|
+
*/
|
|
286
|
+
const resolveClaimableServicesSequentially = async (ctx, services, envKeys) => {
|
|
287
|
+
const leftover = envKeys.filter((key) => serviceForEnvKey(key) === null);
|
|
288
|
+
const parts = [];
|
|
289
|
+
for (const service of CLAIMABLE_RESOLVE_ORDER) {
|
|
290
|
+
const named = services.includes(service);
|
|
291
|
+
const keysForService = [...envKeys.filter((key) => serviceForEnvKey(key) === service), ...service === "postgres" ? leftover : []];
|
|
292
|
+
if (!named && keysForService.length === 0) continue;
|
|
293
|
+
parts.push(await resolveSelectedServices(ctx, named ? [service] : [], keysForService));
|
|
294
|
+
}
|
|
295
|
+
const resolvedPostgresPartition = services.includes("postgres") || envKeys.some((key) => serviceForEnvKey(key) === "postgres");
|
|
296
|
+
if (leftover.length > 0 && !resolvedPostgresPartition) parts.push(await resolveSelectedServices(ctx, [], leftover));
|
|
297
|
+
if (parts.length === 0) return {
|
|
298
|
+
vars: {},
|
|
299
|
+
credential: emptyClaimableCredential()
|
|
300
|
+
};
|
|
301
|
+
const vars = Object.assign({}, ...parts.map((part) => part.vars));
|
|
302
|
+
const skipped = parts.flatMap((part) => part.skipped ?? []);
|
|
303
|
+
return {
|
|
304
|
+
vars,
|
|
305
|
+
credential: emptyClaimableCredential(),
|
|
306
|
+
...skipped.length > 0 ? { skipped } : {}
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
const resolveClaimableLiveEnv = async (ctx) => {
|
|
310
|
+
const { projectId, branchId } = ctx;
|
|
311
|
+
if (!projectId || !branchId) throw new MissingBranchContextError(`No project/branch context found. Link a branch (\`${getCliName()} link\` / \`${getCliName()} checkout\`) or pass --project-id and --branch.`);
|
|
312
|
+
const api = apiFor(ctx);
|
|
313
|
+
const auth = await api.getNeonAuth(projectId, branchId);
|
|
314
|
+
const dataApiEnabled = await readDataApiEnabled(api, projectId, branchId);
|
|
315
|
+
const services = ["postgres"];
|
|
316
|
+
if (auth !== null) services.push("auth");
|
|
317
|
+
if (dataApiEnabled !== false) services.push("data-api");
|
|
318
|
+
return await resolveClaimableServicesSequentially(ctx, services, []);
|
|
319
|
+
};
|
|
320
|
+
const throwPolicyMissingOnBranch = (names, branchId) => {
|
|
321
|
+
throw new DevEnvMismatchError(`Your neon.ts declares ${names} for branch ${branchId}, but the branch does not have it yet, so the matching env vars cannot be injected. Provision it first with \`${getCliName()} deploy\` (or \`${getCliName()} config apply\`), then re-run \`${getCliName()} dev\`.`);
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Honor neon.ts on Claimable Neon without going through `plan` / `fetchEnv`.
|
|
325
|
+
* Those fire concurrent Management API reads, which Claimable does not complete.
|
|
326
|
+
*/
|
|
327
|
+
const resolveClaimablePolicyEnv = async (ctx, config) => {
|
|
328
|
+
const declared = declaredNeonServices(config);
|
|
329
|
+
const unsupported = declared.filter(isClaimableUnsupportedService);
|
|
330
|
+
if (unsupported.length > 0) throw new DevEnvMismatchError(`Your neon.ts declares ${unsupported.join(", ")}, which cannot be used on an unclaimed Claimable Neon project. Remove those services from neon.ts.`);
|
|
331
|
+
const { projectId, branchId } = ctx;
|
|
332
|
+
if (!projectId || !branchId) throw new MissingBranchContextError(`Found a neon.ts but could not resolve the project/branch. Run \`${getCliName()} link\` and \`${getCliName()} checkout <branch>\`, or pass --project-id / --branch.`);
|
|
333
|
+
const api = apiFor(ctx);
|
|
334
|
+
const missing = [];
|
|
335
|
+
if (declared.includes("auth")) {
|
|
336
|
+
if (await api.getNeonAuth(projectId, branchId) === null) missing.push("auth");
|
|
337
|
+
}
|
|
338
|
+
if (declared.includes("data-api")) {
|
|
339
|
+
if (await readDataApiEnabled(api, projectId, branchId) === false) missing.push("data-api");
|
|
340
|
+
}
|
|
341
|
+
if (missing.length > 0) throwPolicyMissingOnBranch(missing.join(", "), branchId);
|
|
342
|
+
return await resolveClaimableServicesSequentially(ctx, ["postgres", ...declared], []);
|
|
343
|
+
};
|
|
344
|
+
const resolveClaimableNeonEnvVars = async (ctx) => {
|
|
345
|
+
if (ctx.services !== void 0 || ctx.envKeys !== void 0) {
|
|
346
|
+
const dropped = dropClaimableUnsupported(ctx.services ?? [], ctx.envKeys ?? []);
|
|
347
|
+
warnClaimableSkipped(dropped.skipped);
|
|
348
|
+
if (dropped.services.length === 0 && dropped.envKeys.length === 0) return {
|
|
349
|
+
vars: {},
|
|
350
|
+
credential: emptyClaimableCredential(),
|
|
351
|
+
...dropped.skipped.length > 0 ? { skipped: dropped.skipped } : {}
|
|
352
|
+
};
|
|
353
|
+
const resolved = await resolveClaimableServicesSequentially(ctx, dropped.services, dropped.envKeys);
|
|
354
|
+
const skipped = [...resolved.skipped ?? [], ...dropped.skipped];
|
|
355
|
+
return {
|
|
356
|
+
...resolved,
|
|
357
|
+
...skipped.length > 0 ? { skipped } : {}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const config = await loadNeonConfig(ctx);
|
|
361
|
+
if (config) return await resolveClaimablePolicyEnv(ctx, config);
|
|
362
|
+
return await resolveClaimableLiveEnv(ctx);
|
|
363
|
+
};
|
|
235
364
|
/**
|
|
236
365
|
* Resolve the branch's Neon env vars (pooled / direct `DATABASE_URL`, plus Auth /
|
|
237
366
|
* Data API when enabled) into a `{ KEY: value }` map. Shared by `neon dev` (which
|
|
@@ -259,10 +388,16 @@ var ServiceNotOnBranchError = class extends Error {
|
|
|
259
388
|
* `pullConfig` cannot read it back.
|
|
260
389
|
* 3. otherwise -> throw {@link MissingBranchContextError}.
|
|
261
390
|
*
|
|
391
|
+
* {@link DevEnvContext.claimable} keeps the same precedence. Credentials are not
|
|
392
|
+
* minted. A neon.ts that names AI Gateway, Functions, or Object Storage fails
|
|
393
|
+
* instead of pulling a subset. The live and policy reads run one service at a
|
|
394
|
+
* time because Claimable Neon does not complete concurrent Management API reads.
|
|
395
|
+
*
|
|
262
396
|
* Unlike {@link resolveDevEnv}, this never swallows errors — callers decide how to
|
|
263
397
|
* handle them.
|
|
264
398
|
*/
|
|
265
399
|
const resolveNeonEnvVars = async (ctx) => {
|
|
400
|
+
if (ctx.claimable) return await resolveClaimableNeonEnvVars(ctx);
|
|
266
401
|
if (ctx.services !== void 0 || ctx.envKeys !== void 0) return await resolveSelectedServices(ctx, ctx.services ?? [], ctx.envKeys ?? []);
|
|
267
402
|
const config = await loadNeonConfig(ctx);
|
|
268
403
|
if (config) {
|
|
@@ -519,7 +654,7 @@ const assertPolicyMatchesBranch = async (config, ctx) => {
|
|
|
519
654
|
...apiOptions(ctx)
|
|
520
655
|
})).applied.filter(isMissingResource);
|
|
521
656
|
if (missing.length === 0) return;
|
|
522
|
-
|
|
657
|
+
throwPolicyMissingOnBranch(missing.map((change) => change.identifier).join(", "), ctx.branchId);
|
|
523
658
|
};
|
|
524
659
|
/**
|
|
525
660
|
* A planned change that provisions a branch-level resource the branch lacks: a
|
|
@@ -583,6 +718,7 @@ const loadNeonConfig = async (ctx) => {
|
|
|
583
718
|
try {
|
|
584
719
|
const { config } = await loadConfigFromFile({
|
|
585
720
|
cwd: ctx.cwd,
|
|
721
|
+
...ctx.config ? { path: ctx.config } : {},
|
|
586
722
|
...ctx.omitUnsetFunctionEnv ? { unsetFunctionEnv: "omit" } : {}
|
|
587
723
|
});
|
|
588
724
|
return config;
|
|
@@ -594,4 +730,4 @@ const loadNeonConfig = async (ctx) => {
|
|
|
594
730
|
}
|
|
595
731
|
};
|
|
596
732
|
//#endregion
|
|
597
|
-
export {
|
|
733
|
+
export { resolveDevEnv as a, dropClaimableUnsupported as i, MissingBranchContextError as n, resolveNeonEnvVars as o, ServiceNotOnBranchError as r, DevEnvMismatchError as t };
|
package/dist/claimable/state.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { t as writeSecretFile } from "../_chunks/secure_file-BucZj4yQ.js";
|
|
2
|
+
import { getAuthContext } from "../auth_context.js";
|
|
3
|
+
import { readContextFile } from "../context.js";
|
|
4
|
+
import { ClaimableClient } from "./api.js";
|
|
2
5
|
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
3
6
|
import { join } from "node:path";
|
|
4
7
|
//#region src/claimable/state.ts
|
|
@@ -88,5 +91,21 @@ const resolveClaimableContext = (context) => {
|
|
|
88
91
|
};
|
|
89
92
|
};
|
|
90
93
|
const shouldUseClaimableCredentials = (inputs, profileFlag, context) => inputs.apiKeyFlag.trim() === "" && inputs.apiKeyEnv.trim() === "" && inputs.profileEnv.trim() === "" && (profileFlag === void 0 || profileFlag.trim() === "") && resolveClaimableContext(context) !== null;
|
|
94
|
+
/** Management API base URL the CLI uses for a Claimable Neon origin. */
|
|
95
|
+
const claimableApiHost = (origin) => `${new ClaimableClient(origin).origin}/v1`;
|
|
96
|
+
/**
|
|
97
|
+
* Whether this invocation is talking to Claimable Neon, so env resolve must not
|
|
98
|
+
* mint credentials or pull services that require claim.
|
|
99
|
+
*
|
|
100
|
+
* Auth source wins so a same-process checkout that rewrote `.neon` without the
|
|
101
|
+
* marker still skips minting. A `.neon` marker plus a production API host is an
|
|
102
|
+
* account override and is not claimable.
|
|
103
|
+
*/
|
|
104
|
+
const isClaimableEnvTarget = (props) => {
|
|
105
|
+
if (getAuthContext()?.source === "claimable") return true;
|
|
106
|
+
const linked = resolveClaimableContext(readContextFile(props.contextFile));
|
|
107
|
+
if (linked === null) return false;
|
|
108
|
+
return props.apiHost.trim().replace(/\/+$/, "") === claimableApiHost(linked.origin);
|
|
109
|
+
};
|
|
91
110
|
//#endregion
|
|
92
|
-
export { assertionHasExpired, claimableCredentialsPath, listClaimableCredentials, readClaimableCredentials, removeClaimableCredentials, resolveClaimableContext, shouldUseClaimableCredentials, writeClaimableCredentials };
|
|
111
|
+
export { assertionHasExpired, claimableApiHost, claimableCredentialsPath, isClaimableEnvTarget, listClaimableCredentials, readClaimableCredentials, removeClaimableCredentials, resolveClaimableContext, shouldUseClaimableCredentials, writeClaimableCredentials };
|
package/dist/commands/claim.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
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
|
-
import {
|
|
4
|
+
import { getApiClient } from "../api.js";
|
|
5
|
+
import { applyContext, contextBranch, readContextFile } from "../context.js";
|
|
5
6
|
import { claimableDataApiCreateBody, declaredNeonServices } from "../config_services.js";
|
|
6
7
|
import { NEON_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
7
|
-
import {
|
|
8
|
+
import { resolveEnvFilePath } from "../env_file.js";
|
|
8
9
|
import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
9
10
|
import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
|
|
10
11
|
import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials, removeClaimableCredentials, resolveClaimableContext, writeClaimableCredentials } from "../claimable/state.js";
|
|
11
12
|
import { writer } from "../writer.js";
|
|
12
13
|
import { noPassthrough } from "../utils/flags.js";
|
|
14
|
+
import { pull } from "./env.js";
|
|
13
15
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
14
16
|
import { dirname, join, resolve } from "node:path";
|
|
15
17
|
import { homedir } from "node:os";
|
|
@@ -25,6 +27,7 @@ var claim_exports = /* @__PURE__ */ __exportAll({
|
|
|
25
27
|
claimCreateFields: () => claimCreateFields,
|
|
26
28
|
claimableCapabilities: () => claimableCapabilities,
|
|
27
29
|
command: () => command,
|
|
30
|
+
create: () => create,
|
|
28
31
|
describe: () => describe,
|
|
29
32
|
findNeonConfig: () => findNeonConfig,
|
|
30
33
|
handler: () => handler
|
|
@@ -84,7 +87,10 @@ const loadCreatePolicy = async (explicitPath) => {
|
|
|
84
87
|
const path = explicitPath ?? findNeonConfig();
|
|
85
88
|
if (!path) return void 0;
|
|
86
89
|
const { config } = await loadConfigFromFile({ path });
|
|
87
|
-
return
|
|
90
|
+
return {
|
|
91
|
+
path,
|
|
92
|
+
config
|
|
93
|
+
};
|
|
88
94
|
};
|
|
89
95
|
const removeFileIfPresent = (path) => {
|
|
90
96
|
try {
|
|
@@ -129,10 +135,10 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
|
|
|
129
135
|
describe: "Target dotenv file. Defaults to an existing .env, otherwise .env.local",
|
|
130
136
|
type: "string"
|
|
131
137
|
}).option("config", {
|
|
132
|
-
describe: "Path to neon.ts. Defaults to walking up from the current directory",
|
|
138
|
+
describe: "Path to neon.ts for registration and the bundled env pull. Defaults to walking up from the current directory",
|
|
133
139
|
type: "string"
|
|
134
140
|
}).option("env-pull", {
|
|
135
|
-
describe: "Write the
|
|
141
|
+
describe: "Write the same Neon env vars `env pull` would",
|
|
136
142
|
type: "boolean",
|
|
137
143
|
default: true
|
|
138
144
|
}).strict().check(noPassthrough("claim create")), async (args) => {
|
|
@@ -143,11 +149,12 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
|
|
|
143
149
|
onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical))
|
|
144
150
|
}) : [];
|
|
145
151
|
const policy = await loadCreatePolicy(typeof args.config === "string" ? args.config : void 0);
|
|
146
|
-
const configuredServices = policy ? declaredNeonServices(policy) : [];
|
|
147
|
-
const dataApi = policy ? claimableDataApiCreateBody(policy) : void 0;
|
|
152
|
+
const configuredServices = policy ? declaredNeonServices(policy.config) : [];
|
|
153
|
+
const dataApi = policy ? claimableDataApiCreateBody(policy.config) : void 0;
|
|
148
154
|
await create({
|
|
149
155
|
...args,
|
|
150
156
|
services: [.../* @__PURE__ */ new Set([...services, ...configuredServices])],
|
|
157
|
+
...policy ? { config: policy.path } : {},
|
|
151
158
|
...dataApi ? { dataApi } : {}
|
|
152
159
|
});
|
|
153
160
|
}).command("status [project-id]", "Show a claimable project's lifecycle and claim status", (y) => y.positional("project-id", {
|
|
@@ -205,7 +212,8 @@ const create = async (props) => {
|
|
|
205
212
|
const existing = readContextFile(props.contextFile);
|
|
206
213
|
if (existing.projectId || existing.orgId || existing.claimable) throw new Error(`${props.contextFile} already links this directory to a Neon project. Run \`neon claim create\` from an unlinked directory.`);
|
|
207
214
|
const contextFileExisted = existsSync(props.contextFile);
|
|
208
|
-
const
|
|
215
|
+
const cwd = props.cwd ?? process.cwd();
|
|
216
|
+
const envFile = props.envPull ? resolveEnvFilePath(cwd, props.file) : void 0;
|
|
209
217
|
const envFileExisted = envFile ? existsSync(envFile) : false;
|
|
210
218
|
const previousEnv = envFileExisted && envFile ? readFileSync(envFile) : void 0;
|
|
211
219
|
const client = new ClaimableClient(props.claimableHost);
|
|
@@ -242,20 +250,26 @@ const create = async (props) => {
|
|
|
242
250
|
contextWritten = true;
|
|
243
251
|
const token = await client.exchange(registration.identityAssertion);
|
|
244
252
|
accessToken = token.accessToken;
|
|
245
|
-
const credentials = await client.credentials(registration.project.id, token.accessToken);
|
|
246
|
-
if (credentials.projectId !== registration.project.id || credentials.branchId !== registration.project.branchId) throw new Error("Claimable Neon returned credentials for a different project. The project was not kept.");
|
|
247
253
|
if (envFile) {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
NEON_AUTH_JWKS_URL: credentials.services.auth.jwksUrl
|
|
254
|
-
} : {}
|
|
255
|
-
};
|
|
254
|
+
const apiHost = `${client.origin}/v1`;
|
|
255
|
+
const apiClient = props.apiClient ?? getApiClient({
|
|
256
|
+
apiKey: token.accessToken,
|
|
257
|
+
apiHost
|
|
258
|
+
});
|
|
256
259
|
envWriteAttempted = true;
|
|
257
|
-
|
|
258
|
-
|
|
260
|
+
if ((await pull({
|
|
261
|
+
apiClient,
|
|
262
|
+
apiKey: token.accessToken,
|
|
263
|
+
apiHost,
|
|
264
|
+
contextFile: props.contextFile,
|
|
265
|
+
output: props.output,
|
|
266
|
+
projectId: registration.project.id,
|
|
267
|
+
branch: registration.project.branchId,
|
|
268
|
+
cwd,
|
|
269
|
+
file: props.file,
|
|
270
|
+
...props.config ? { config: props.config } : {},
|
|
271
|
+
...props.runtimeApi ? { runtimeApi: props.runtimeApi } : {}
|
|
272
|
+
})).status === "empty") throw new Error("No Neon env variables were pulled for this claimable project.");
|
|
259
273
|
}
|
|
260
274
|
const granted = registration.capabilities.filter((decision) => decision.granted).map((decision) => cliServiceName(decision.capability));
|
|
261
275
|
const denied = registration.capabilities.filter((decision) => !decision.granted).map((decision) => ({
|
|
@@ -467,4 +481,4 @@ const finishClaimedContext = (props, projectId, contextMatches) => {
|
|
|
467
481
|
log.info("Dropped the local identity assertion. The next command needs `neon auth` or `neon link`.");
|
|
468
482
|
};
|
|
469
483
|
//#endregion
|
|
470
|
-
export { CLAIM_CREATE_FIELDS, CLAIM_LIST_FIELDS, aliases, builder, claimCreateFields, claimableCapabilities, command, describe, findNeonConfig, handler, claim_exports as t };
|
|
484
|
+
export { CLAIM_CREATE_FIELDS, CLAIM_LIST_FIELDS, aliases, builder, claimCreateFields, claimableCapabilities, command, create, describe, findNeonConfig, handler, claim_exports as t };
|
package/dist/commands/dev.js
CHANGED
|
@@ -3,9 +3,10 @@ import { log } from "../log.js";
|
|
|
3
3
|
import { getCliName } from "../utils/cli_name.js";
|
|
4
4
|
import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
5
5
|
import { _ as functionBaseUrlKey } from "../_chunks/env_services-CAWrZTWa.js";
|
|
6
|
+
import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
6
7
|
import { branchIdResolve } from "../utils/enrichers.js";
|
|
7
8
|
import { bundleEntry } from "../utils/esbuild.js";
|
|
8
|
-
import {
|
|
9
|
+
import { a as resolveDevEnv } from "../_chunks/env-DsakkOVZ.js";
|
|
9
10
|
import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
|
|
10
11
|
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
11
12
|
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -85,7 +86,11 @@ const devEnvContext = (props, branchId, cwd) => {
|
|
|
85
86
|
...props.projectId ? { projectId: props.projectId } : {},
|
|
86
87
|
...branchId ? { branchId } : {},
|
|
87
88
|
...props.apiKey ? { apiKey: props.apiKey } : {},
|
|
88
|
-
...props.apiHost ? { apiHost: props.apiHost } : {}
|
|
89
|
+
...props.apiHost ? { apiHost: props.apiHost } : {},
|
|
90
|
+
...isClaimableEnvTarget({
|
|
91
|
+
apiHost: props.apiHost,
|
|
92
|
+
contextFile: props.contextFile ?? ""
|
|
93
|
+
}) ? { claimable: true } : {}
|
|
89
94
|
};
|
|
90
95
|
};
|
|
91
96
|
/**
|
package/dist/commands/env.js
CHANGED
|
@@ -5,10 +5,11 @@ import { getCliName } from "../utils/cli_name.js";
|
|
|
5
5
|
import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
6
6
|
import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
7
7
|
import { a as envKeysForSelection, c as ownedEnvServiceKeys, f as NEON_ENV_VAR_KEYS, i as ENV_PULL_SERVICES, l as parseEnvPullKeys, o as envPullFlagValue, r as ENV_PULL_KEY_HELP, v as isFunctionBaseUrlKey } from "../_chunks/env_services-CAWrZTWa.js";
|
|
8
|
+
import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
8
9
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
9
10
|
import { warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
10
11
|
import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
11
|
-
import {
|
|
12
|
+
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-DsakkOVZ.js";
|
|
12
13
|
import { existsSync } from "node:fs";
|
|
13
14
|
import chalk from "chalk";
|
|
14
15
|
//#region src/commands/env.ts
|
|
@@ -54,6 +55,10 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
|
|
|
54
55
|
describe: `Pull only these individual variables: ${ENV_PULL_KEY_HELP}. Repeat the flag or comma-separate. Overrides neon.ts. Combines with --service as a union; it never narrows a service bundle.`,
|
|
55
56
|
type: "array",
|
|
56
57
|
string: true
|
|
58
|
+
},
|
|
59
|
+
config: {
|
|
60
|
+
describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
|
|
61
|
+
type: "string"
|
|
57
62
|
}
|
|
58
63
|
}).epilogue([
|
|
59
64
|
"",
|
|
@@ -64,6 +69,14 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
|
|
|
64
69
|
" 3. Otherwise everything the branch has, plus the AI Gateway —",
|
|
65
70
|
" which mints a branch credential for it.",
|
|
66
71
|
"",
|
|
72
|
+
"On an unclaimed Claimable Neon project, neon.ts is still the source",
|
|
73
|
+
"of truth when it only declares Postgres, Auth, and the Data API.",
|
|
74
|
+
"A neon.ts that declares AI Gateway, Functions, or Object Storage",
|
|
75
|
+
"fails: those cannot be used until the project is claimed. Without",
|
|
76
|
+
"a neon.ts, a bare pull writes provisioned Postgres, Auth, and Data",
|
|
77
|
+
"API. Naming unsupported services with --service / --env warns",
|
|
78
|
+
"and writes nothing for them.",
|
|
79
|
+
"",
|
|
67
80
|
"The pull bundled into link / checkout / config apply follows 2 and 3",
|
|
68
81
|
"without the AI Gateway, so it never mints a credential you did not ask",
|
|
69
82
|
"for. Run `env pull` to add it."
|
|
@@ -109,7 +122,11 @@ const NEON_OWNED_ENV_KEYS = [
|
|
|
109
122
|
];
|
|
110
123
|
const pull = async (props, opts = {}) => {
|
|
111
124
|
const cwd = props.cwd ?? process.cwd();
|
|
112
|
-
const
|
|
125
|
+
const claimable = isClaimableEnvTarget(props);
|
|
126
|
+
const dropped = claimable && (props.services !== void 0 || props.envKeys !== void 0) ? dropClaimableUnsupported(props.services ?? [], props.envKeys ?? []) : null;
|
|
127
|
+
const selectionServices = dropped ? dropped.services : props.services ?? [];
|
|
128
|
+
const selectionEnvKeys = dropped ? dropped.envKeys : props.envKeys ?? [];
|
|
129
|
+
const selectedKeys = props.services !== void 0 || props.envKeys !== void 0 ? envKeysForSelection(selectionServices, selectionEnvKeys) : void 0;
|
|
113
130
|
const branch = await resolveBranchRef(props);
|
|
114
131
|
if (opts.announce) announceTargetBranch(props, branch, "Pulling env from branch");
|
|
115
132
|
const branchId = branch.branchId;
|
|
@@ -124,20 +141,22 @@ const pull = async (props, opts = {}) => {
|
|
|
124
141
|
...process.env,
|
|
125
142
|
...existingEnv
|
|
126
143
|
},
|
|
127
|
-
...props.services ? { services: props.services } : {},
|
|
128
|
-
...props.envKeys ? { envKeys: props.envKeys } : {},
|
|
144
|
+
...props.services !== void 0 ? { services: props.services } : {},
|
|
145
|
+
...props.envKeys !== void 0 ? { envKeys: props.envKeys } : {},
|
|
129
146
|
...opts.implyAiGateway ? { implyAiGateway: true } : {},
|
|
130
147
|
omitUnsetFunctionEnv: true,
|
|
148
|
+
...claimable ? { claimable: true } : {},
|
|
149
|
+
...props.config ? { config: props.config } : {},
|
|
131
150
|
...props.apiKey ? { apiKey: props.apiKey } : {},
|
|
132
151
|
...props.apiHost ? { apiHost: props.apiHost } : {},
|
|
133
152
|
...props.runtimeApi ? { api: props.runtimeApi } : {}
|
|
134
153
|
});
|
|
135
|
-
const neonVars = pickSelectedVars(pickNeonVars(vars), selectedKeys, props.services);
|
|
154
|
+
const neonVars = pickSelectedVars(pickNeonVars(vars), selectedKeys, props.services !== void 0 ? selectionServices : void 0);
|
|
136
155
|
if (Object.keys(neonVars).length === 0) {
|
|
137
|
-
log.info("No Neon env variables to pull for this branch (no DATABASE_URL or enabled Auth / Data API).");
|
|
156
|
+
if (!(dropped !== null && dropped.services.length === 0 && dropped.envKeys.length === 0 && dropped.skipped.length > 0)) log.info("No Neon env variables to pull for this branch (no DATABASE_URL or enabled Auth / Data API).");
|
|
138
157
|
return { status: "empty" };
|
|
139
158
|
}
|
|
140
|
-
const { written, removed } = mergeEnvFile(targetPath, neonVars, { managedKeys: managedKeysFor(selectedKeys, unreachedButCurrent(skipped, existingEnv, branchId), existingEnv, props.services, props.envKeys) });
|
|
159
|
+
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) });
|
|
141
160
|
log.info("Pulled %d Neon variable%s into %s: %s", written.length, written.length === 1 ? "" : "s", targetPath, written.join(", "));
|
|
142
161
|
if (removed.length > 0) log.info("Removed %d stale Neon variable%s not enabled on this branch: %s", removed.length, removed.length === 1 ? "" : "s", removed.join(", "));
|
|
143
162
|
if (credential?.issued) {
|
package/dist/dev/env.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { DevEnvMismatchError, MissingBranchContextError, ServiceNotOnBranchError, resolveDevEnv, resolveNeonEnvVars };
|
|
1
|
+
import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-DsakkOVZ.js";
|
|
2
|
+
export { DevEnvMismatchError, MissingBranchContextError, ServiceNotOnBranchError, dropClaimableUnsupported, resolveDevEnv, resolveNeonEnvVars };
|
package/dist/env_file.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
//#region src/env_file.ts
|
|
4
4
|
/**
|
|
5
5
|
* Default dotenv file `env pull` writes to: `.env` when one already exists in the working
|
|
@@ -7,7 +7,7 @@ import { join } from "node:path";
|
|
|
7
7
|
* `vercel env pull` convention. An explicit `--file` always wins over this.
|
|
8
8
|
*/
|
|
9
9
|
const resolveEnvFilePath = (cwd, file) => {
|
|
10
|
-
if (file) return
|
|
10
|
+
if (file) return resolve(cwd, file);
|
|
11
11
|
if (existsSync(join(cwd, ".env"))) return join(cwd, ".env");
|
|
12
12
|
return join(cwd, ".env.local");
|
|
13
13
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "4.14.
|
|
3
|
+
"version": "4.14.5",
|
|
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/
|
|
67
|
-
"@neon/
|
|
68
|
-
"@neon/config": "1.3.
|
|
66
|
+
"@neon/config-runtime": "1.2.3",
|
|
67
|
+
"@neon/sdk": "4.0.0",
|
|
68
|
+
"@neon/config": "1.3.2"
|
|
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",
|
|
100
101
|
"@neon-internals/cli-core": "0.0.0",
|
|
101
|
-
"@neon-internals/env-core": "0.0.9",
|
|
102
102
|
"@neon/e2e-harness": "0.0.0",
|
|
103
103
|
"@neon/functions": "0.9.0"
|
|
104
104
|
},
|