neon 8.0.8 → 8.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{env-HVRMArlT.js → env-BWqJqBDJ.js} +30 -8
- package/dist/_chunks/{env-Dkl0TY44.js → env-HYNFtxu5.js} +39 -15
- package/dist/commands/config.js +2 -12
- package/dist/commands/dev.js +4 -4
- package/dist/commands/env.js +11 -9
- package/dist/commands/env_output.js +64 -0
- package/dist/commands/link.js +86 -44
- package/dist/commands/link_output.js +17 -0
- package/dist/dev/env.js +1 -1
- package/dist/env_services.js +1 -1
- package/dist/neon_services.js +10 -1
- package/dist/utils/ai_gateway_notice.js +2 -1
- package/dist/utils/hooks.js +1 -1
- package/package.json +5 -5
|
@@ -2,7 +2,7 @@ import { log } from "../log.js";
|
|
|
2
2
|
import { getCliName } from "../utils/cli_name.js";
|
|
3
3
|
import { declaredNeonServices } from "../config_services.js";
|
|
4
4
|
import { loadProjectConfig } from "../project.js";
|
|
5
|
-
import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-
|
|
5
|
+
import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-HYNFtxu5.js";
|
|
6
6
|
import { ENV_PULL_SERVICES, envKeysForSelection, serviceForEnvKey, servicesForEnvKeys } from "../env_services.js";
|
|
7
7
|
import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
|
|
8
8
|
import { ErrorCode, createNeonApiFromOptions, isPlatformError } from "@neon/config";
|
|
@@ -42,9 +42,9 @@ import { plan, pullConfig } from "@neon/config-runtime";
|
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
async function fetchEnvReusingSecrets(config, options) {
|
|
45
|
-
const { env: source = process.env, keys: requestedKeys, revokeSuperseded = true, ...fetchOptions } = options;
|
|
45
|
+
const { env: source = process.env, keys: requestedKeys, revokeSuperseded = true, resolvedBranch, ...fetchOptions } = options;
|
|
46
46
|
const api = options.api ?? createApiFromOptions(options);
|
|
47
|
-
const { branch, desired } = await resolveBranchPolicy(config, options, api);
|
|
47
|
+
const { branch, desired } = await resolveBranchPolicy(config, options, api, resolvedBranch);
|
|
48
48
|
const allPolicyKeys = policyEnvKeys(desired);
|
|
49
49
|
const requested = requestedKeys ? new Set(requestedKeys) : null;
|
|
50
50
|
const selectedPolicyKeys = requested === null ? allPolicyKeys : [...allPolicyKeys.filter((key) => requested.has(key)), ...[...requested].filter(isFunctionBaseUrlKey).sort()];
|
|
@@ -66,6 +66,7 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
66
66
|
credential: {
|
|
67
67
|
issued: false,
|
|
68
68
|
keys: [],
|
|
69
|
+
fresh: [],
|
|
69
70
|
revoked: [],
|
|
70
71
|
superseded: []
|
|
71
72
|
},
|
|
@@ -111,6 +112,7 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
111
112
|
credential: {
|
|
112
113
|
issued: false,
|
|
113
114
|
keys: secretKeys,
|
|
115
|
+
fresh: [],
|
|
114
116
|
revoked: [],
|
|
115
117
|
superseded: []
|
|
116
118
|
},
|
|
@@ -124,6 +126,7 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
124
126
|
credential: {
|
|
125
127
|
issued: true,
|
|
126
128
|
keys: secretKeys,
|
|
129
|
+
fresh: secretKeys.filter((key) => !keptSecretKeys.includes(key)),
|
|
127
130
|
revoked: revokeSuperseded ? [...ours] : [],
|
|
128
131
|
superseded: revokeSuperseded ? [] : [...ours]
|
|
129
132
|
},
|
|
@@ -245,6 +248,7 @@ const CLAIMABLE_RESOLVE_ORDER = [
|
|
|
245
248
|
const emptyClaimableCredential = () => ({
|
|
246
249
|
issued: false,
|
|
247
250
|
keys: [],
|
|
251
|
+
fresh: [],
|
|
248
252
|
revoked: [],
|
|
249
253
|
superseded: []
|
|
250
254
|
});
|
|
@@ -410,10 +414,21 @@ const resolveNeonEnvVars = async (ctx) => {
|
|
|
410
414
|
branchId: ctx.branchId,
|
|
411
415
|
...apiOptions(ctx)
|
|
412
416
|
});
|
|
413
|
-
|
|
417
|
+
const resolvedBranch = {
|
|
418
|
+
id: pulled.branch.id,
|
|
419
|
+
name: pulled.branch.name,
|
|
420
|
+
isDefault: pulled.branch.isDefault,
|
|
421
|
+
protected: pulled.branch.protected,
|
|
422
|
+
...pulled.branch.expiresAt ? { expiresAt: pulled.branch.expiresAt } : {}
|
|
423
|
+
};
|
|
424
|
+
if (!ctx.implyAiGateway) return await fetchAndProject(pulled.config, ctx, {
|
|
425
|
+
functionUrls: "all-live",
|
|
426
|
+
resolvedBranch
|
|
427
|
+
});
|
|
414
428
|
return await resolveWithImpliedGateway(pulled.config, ctx, {
|
|
415
429
|
projectId: ctx.projectId,
|
|
416
|
-
branchId: ctx.branchId
|
|
430
|
+
branchId: ctx.branchId,
|
|
431
|
+
resolvedBranch
|
|
417
432
|
});
|
|
418
433
|
}
|
|
419
434
|
throw new MissingBranchContextError(`No project/branch context found. Link a branch (\`${getCliName()} link\` / \`${getCliName()} checkout\`) or pass --project-id and --branch.`);
|
|
@@ -450,9 +465,15 @@ const withAiGateway = (config) => ({
|
|
|
450
465
|
*/
|
|
451
466
|
const resolveWithImpliedGateway = async (config, ctx, branch) => {
|
|
452
467
|
const unreachable = await credentialsUnreachable(ctx, branch);
|
|
453
|
-
if (unreachable === null) return await fetchAndProject(withAiGateway(config), ctx, {
|
|
468
|
+
if (unreachable === null) return await fetchAndProject(withAiGateway(config), ctx, {
|
|
469
|
+
functionUrls: "all-live",
|
|
470
|
+
resolvedBranch: branch.resolvedBranch
|
|
471
|
+
});
|
|
454
472
|
log.warning(`Could not reach the AI Gateway's credentials, so %s were not resolved. Everything else was. Either this project does not have the AI Gateway, or the call failed — \`${getCliName()} env pull -s ai-gateway\` will say which.\nDetails: %s`, [NEON_ENV_VAR_KEYS.aiGateway.apiKey, NEON_ENV_VAR_KEYS.aiGateway.baseUrl].join(" and "), unreachable);
|
|
455
|
-
const pulled = await fetchAndProject(config, ctx, {
|
|
473
|
+
const pulled = await fetchAndProject(config, ctx, {
|
|
474
|
+
functionUrls: "all-live",
|
|
475
|
+
resolvedBranch: branch.resolvedBranch
|
|
476
|
+
});
|
|
456
477
|
return {
|
|
457
478
|
...pulled,
|
|
458
479
|
skipped: [...pulled.skipped ?? [], "ai-gateway"]
|
|
@@ -670,7 +691,8 @@ const fetchAndProject = async (config, ctx, opts = {}) => {
|
|
|
670
691
|
...opts.keys ? { keys: opts.keys } : {},
|
|
671
692
|
...opts.revokeSuperseded === false ? { revokeSuperseded: false } : {},
|
|
672
693
|
...opts.functionUrls ? { functionUrls: opts.functionUrls } : {},
|
|
673
|
-
...opts.listedFunctions ? { listedFunctions: opts.listedFunctions } : {}
|
|
694
|
+
...opts.listedFunctions ? { listedFunctions: opts.listedFunctions } : {},
|
|
695
|
+
...opts.resolvedBranch ? { resolvedBranch: opts.resolvedBranch } : {}
|
|
674
696
|
});
|
|
675
697
|
return {
|
|
676
698
|
vars: result.vars,
|
|
@@ -96,6 +96,22 @@ function parseFunctionBaseUrlKey(key) {
|
|
|
96
96
|
function isFunctionBaseUrlKey(key) {
|
|
97
97
|
return parseFunctionBaseUrlKey(key) !== null;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Starts a read now and keeps an unread rejection from crashing the process; the caller
|
|
101
|
+
* awaits it (and sees the error) when its turn comes. The read is called synchronously and
|
|
102
|
+
* its own promise returned, so errors surface in the same order as a direct call; a
|
|
103
|
+
* synchronous throw becomes the returned rejection.
|
|
104
|
+
*/
|
|
105
|
+
function started(read) {
|
|
106
|
+
let promise;
|
|
107
|
+
try {
|
|
108
|
+
promise = read();
|
|
109
|
+
} catch (error) {
|
|
110
|
+
promise = Promise.reject(error);
|
|
111
|
+
}
|
|
112
|
+
promise.catch(() => void 0);
|
|
113
|
+
return promise;
|
|
114
|
+
}
|
|
99
115
|
/** Fail loudly when selected-key dependency planning and execution disagree. */
|
|
100
116
|
function requiredValue(value, description) {
|
|
101
117
|
if (value === null) throw new Error(`fetchEnv: missing ${description}.`);
|
|
@@ -141,6 +157,12 @@ async function fetchEnvKeysState(config, options, keys, policy) {
|
|
|
141
157
|
selectedFunctionKeys,
|
|
142
158
|
wants
|
|
143
159
|
});
|
|
160
|
+
const wantsAnyFunctionUrl = selection === null || selectedFunctionKeys.length > 0;
|
|
161
|
+
const storageEnabled = (desired.preview?.buckets.length ?? 0) > 0;
|
|
162
|
+
const wantsStorage = storageEnabled && (wants(K.storage.accessKeyId) || wants(K.storage.secretAccessKey) || wants(K.storage.endpoint) || wants(K.storage.region));
|
|
163
|
+
const functionsRead = functionUrlMode === "all-live" && wantsAnyFunctionUrl && options.listedFunctions === void 0 ? listFunctionInvocationUrls(api, projectId, branch.id) : null;
|
|
164
|
+
functionsRead?.catch(() => void 0);
|
|
165
|
+
const storageRead = wantsStorage ? started(() => api.getProjectBranchStorage(projectId, branch.id)) : null;
|
|
144
166
|
const needsUnpooled = wantsUnpooled || gatewayEnabled && wants(K.aiGateway.baseUrl) || constructSlugs.length > 0;
|
|
145
167
|
const needsConnectionTarget = wantsPooled || needsUnpooled;
|
|
146
168
|
const needsDatabase = needsConnectionTarget || wantsDataApi;
|
|
@@ -190,8 +212,6 @@ async function fetchEnvKeysState(config, options, keys, policy) {
|
|
|
190
212
|
}
|
|
191
213
|
result.dataApi = { url: dataApiSnapshot.url };
|
|
192
214
|
}
|
|
193
|
-
const storageEnabled = (desired.preview?.buckets.length ?? 0) > 0;
|
|
194
|
-
const wantsStorage = storageEnabled && (wants(K.storage.accessKeyId) || wants(K.storage.secretAccessKey) || wants(K.storage.endpoint) || wants(K.storage.region));
|
|
195
215
|
const wantsGateway = gatewayEnabled && (wants(K.aiGateway.apiKey) || wants(K.aiGateway.baseUrl));
|
|
196
216
|
const wantsStorageCredential = storageEnabled && (wants(K.storage.accessKeyId) || wants(K.storage.secretAccessKey));
|
|
197
217
|
const wantsGatewayCredential = gatewayEnabled && wants(K.aiGateway.apiKey);
|
|
@@ -199,7 +219,7 @@ async function fetchEnvKeysState(config, options, keys, policy) {
|
|
|
199
219
|
if (wantsStorage || wantsGateway) {
|
|
200
220
|
let storage = null;
|
|
201
221
|
if (wantsStorage) {
|
|
202
|
-
storage = await
|
|
222
|
+
storage = await requiredValue(storageRead, "storage read");
|
|
203
223
|
if (!storage) throw new PlatformError(ErrorCode.NotFound, [`fetchEnv: branch policy declares object storage (buckets) but storage is not enabled on branch ${branch.name} (${branch.id}).`, "Enable it via `apply(config, { projectId, branchId })` (or in the Neon Console) — then re-run fetchEnv. Or remove buckets."].join(" "), { details: {
|
|
204
224
|
projectId,
|
|
205
225
|
branchId: branch.id
|
|
@@ -230,11 +250,10 @@ async function fetchEnvKeysState(config, options, keys, policy) {
|
|
|
230
250
|
result.aiGateway = gateway;
|
|
231
251
|
}
|
|
232
252
|
}
|
|
233
|
-
const wantsAnyFunctionUrl = selection === null || selectedFunctionKeys.length > 0;
|
|
234
253
|
const functions = {};
|
|
235
254
|
let functionUrlsUnavailable = false;
|
|
236
255
|
if (functionUrlMode === "all-live" && wantsAnyFunctionUrl) {
|
|
237
|
-
const listed = options.listedFunctions === void 0 ? await
|
|
256
|
+
const listed = options.listedFunctions === void 0 ? await requiredValue(functionsRead, "function listing") : listedFromSnapshots(options.listedFunctions);
|
|
238
257
|
if (listed.status === "unavailable") {
|
|
239
258
|
if (selection === null) functionUrlsUnavailable = true;
|
|
240
259
|
} else for (const fn of listed.functions) {
|
|
@@ -297,13 +316,8 @@ async function listFunctionInvocationUrls(api, projectId, branchId) {
|
|
|
297
316
|
* branch-scoped operation needs. Shared by {@link fetchEnv} and {@link fetchEnvReusingSecrets}
|
|
298
317
|
* so the two agree on which branch they're talking about and what it has enabled.
|
|
299
318
|
*/
|
|
300
|
-
async function resolveBranchPolicy(config, options, api) {
|
|
301
|
-
const
|
|
302
|
-
const branches = await api.listBranches(projectId);
|
|
303
|
-
if (branches.length === 0) throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: project ${projectId} has no branches.`, "Deploy your neon.ts policy (or create a branch) first, or pick a different project id."].join(" "), { details: { projectId } });
|
|
304
|
-
const branchRef = options.branch ?? options.branchId;
|
|
305
|
-
if (!branchRef) throw new PlatformError(ErrorCode.BranchNotFound, ["fetchEnv: no branch provided.", "Pass `branch` with a branch name (e.g. `main`) or id (`br-…`)."].join(" "), { details: { projectId } });
|
|
306
|
-
const branch = resolveBranch(branchRef, branches);
|
|
319
|
+
async function resolveBranchPolicy(config, options, api, resolved) {
|
|
320
|
+
const branch = resolved ?? await listAndResolveBranch(options, api);
|
|
307
321
|
return {
|
|
308
322
|
branch,
|
|
309
323
|
desired: resolveConfig(config, {
|
|
@@ -317,6 +331,14 @@ async function resolveBranchPolicy(config, options, api) {
|
|
|
317
331
|
})
|
|
318
332
|
};
|
|
319
333
|
}
|
|
334
|
+
async function listAndResolveBranch(options, api) {
|
|
335
|
+
const projectId = options.projectId;
|
|
336
|
+
const branches = await api.listBranches(projectId);
|
|
337
|
+
if (branches.length === 0) throw new PlatformError(ErrorCode.BranchNotFound, [`fetchEnv: project ${projectId} has no branches.`, "Deploy your neon.ts policy (or create a branch) first, or pick a different project id."].join(" "), { details: { projectId } });
|
|
338
|
+
const branchRef = options.branch ?? options.branchId;
|
|
339
|
+
if (!branchRef) throw new PlatformError(ErrorCode.BranchNotFound, ["fetchEnv: no branch provided.", "Pass `branch` with a branch name (e.g. `main`) or id (`br-…`)."].join(" "), { details: { projectId } });
|
|
340
|
+
return resolveBranch(branchRef, branches);
|
|
341
|
+
}
|
|
320
342
|
/**
|
|
321
343
|
* Scopes a minted fallback credential should carry. Only object storage and the AI Gateway
|
|
322
344
|
* *require* secrets; functions never force a credential. `functions:invoke` rides along only
|
|
@@ -402,12 +424,14 @@ async function resolveBranchCredentialSecrets(args) {
|
|
|
402
424
|
secretAccessKey: "",
|
|
403
425
|
apiToken: ""
|
|
404
426
|
};
|
|
405
|
-
|
|
406
|
-
|
|
427
|
+
const storageReveal = needsStorage && storageDefault ? started(() => args.api.revealCredential(args.projectId, args.branchId, storageDefault.tokenId)) : null;
|
|
428
|
+
const gatewayReveal = needsGateway && gatewayDefault ? started(() => args.api.revealCredential(args.projectId, args.branchId, gatewayDefault.tokenId)) : null;
|
|
429
|
+
if (storageReveal) {
|
|
430
|
+
const revealed = await storageReveal;
|
|
407
431
|
secrets.accessKeyId = revealed.tokenId;
|
|
408
432
|
secrets.secretAccessKey = revealed.s3SecretAccessKey;
|
|
409
433
|
}
|
|
410
|
-
if (
|
|
434
|
+
if (gatewayReveal) secrets.apiToken = (await gatewayReveal).apiToken;
|
|
411
435
|
const missingStorage = needsStorage && storageDefault === null;
|
|
412
436
|
const missingGateway = needsGateway && gatewayDefault === null;
|
|
413
437
|
if (missingStorage || missingGateway) {
|
package/dist/commands/config.js
CHANGED
|
@@ -4,7 +4,7 @@ import { log } from "../log.js";
|
|
|
4
4
|
import { getApiClient } from "../api.js";
|
|
5
5
|
import { contextBranch, ensureDirectoryGitignored, readContextFile } from "../context.js";
|
|
6
6
|
import { getCliName } from "../utils/cli_name.js";
|
|
7
|
-
import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
7
|
+
import { NEON_SERVICE_LABELS, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
8
8
|
import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE, FUNCTION_FILENAME, FUNCTION_SLUG, FUNCTION_TEMPLATE, REQUIRED_PACKAGES, renderFunctionSource, renderNeonConfig, renderNeonConfigFromView } from "../config_template.js";
|
|
9
9
|
import { editNeonConfig } from "../config_edit.js";
|
|
10
10
|
import { toNeonConfigView } from "../config_format.js";
|
|
@@ -820,17 +820,7 @@ const applyCmd = async (props) => {
|
|
|
820
820
|
* mistaken for being silently dropped. Service enablement is static top-level config (it never
|
|
821
821
|
* lives in the per-branch closure), so reading it straight off `config` is accurate.
|
|
822
822
|
*/
|
|
823
|
-
const utilizedServices = (config) =>
|
|
824
|
-
const labels = {
|
|
825
|
-
postgres: "Postgres",
|
|
826
|
-
auth: "Neon Auth",
|
|
827
|
-
"data-api": "Data API",
|
|
828
|
-
"object-storage": "Object Storage",
|
|
829
|
-
functions: "Functions",
|
|
830
|
-
"ai-gateway": "AI Gateway"
|
|
831
|
-
};
|
|
832
|
-
return [labels.postgres, ...declaredNeonServices(config).map((service) => labels[service])];
|
|
833
|
-
};
|
|
823
|
+
const utilizedServices = (config) => [NEON_SERVICE_LABELS.postgres, ...declaredNeonServices(config).map((service) => NEON_SERVICE_LABELS[service])];
|
|
834
824
|
/**
|
|
835
825
|
* Render a {@link PushResult}. JSON/YAML output emits the raw result (plus a `services`
|
|
836
826
|
* summary) verbatim so it can be piped; the human-readable path renders the actual changes
|
package/dist/commands/dev.js
CHANGED
|
@@ -3,12 +3,12 @@ import { log } from "../log.js";
|
|
|
3
3
|
import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
4
4
|
import { getCliName } from "../utils/cli_name.js";
|
|
5
5
|
import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
6
|
-
import { l as functionBaseUrlKey } from "../_chunks/env-
|
|
7
|
-
import { a as resolveDevEnv } from "../_chunks/env-HVRMArlT.js";
|
|
8
|
-
import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
|
|
9
|
-
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
6
|
+
import { l as functionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
|
|
10
7
|
import { branchIdResolve } from "../utils/enrichers.js";
|
|
11
8
|
import { bundleEntry } from "../utils/esbuild.js";
|
|
9
|
+
import { a as resolveDevEnv } from "../_chunks/env-BWqJqBDJ.js";
|
|
10
|
+
import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
|
|
11
|
+
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
12
12
|
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
package/dist/commands/env.js
CHANGED
|
@@ -5,12 +5,13 @@ import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
|
5
5
|
import { getCliName } from "../utils/cli_name.js";
|
|
6
6
|
import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
7
7
|
import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
8
|
-
import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-
|
|
8
|
+
import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
|
|
9
9
|
import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
|
|
10
|
-
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-HVRMArlT.js";
|
|
11
10
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
12
11
|
import { warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
13
12
|
import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
13
|
+
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-BWqJqBDJ.js";
|
|
14
|
+
import { formatPulledEnv } from "./env_output.js";
|
|
14
15
|
import { existsSync } from "node:fs";
|
|
15
16
|
import chalk from "chalk";
|
|
16
17
|
//#region src/commands/env.ts
|
|
@@ -167,13 +168,13 @@ const pull = async (props, opts = {}) => {
|
|
|
167
168
|
return { status: "empty" };
|
|
168
169
|
}
|
|
169
170
|
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) });
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
171
|
+
process.stderr.write(formatPulledEnv({
|
|
172
|
+
status: "written",
|
|
173
|
+
written,
|
|
174
|
+
removed,
|
|
175
|
+
file: targetPath,
|
|
176
|
+
...credential && credential.keys.length > 0 ? { credential } : {}
|
|
177
|
+
}, cwd));
|
|
177
178
|
if (!fileExisted) ensureGitignored(targetPath);
|
|
178
179
|
const gatewayBaseUrl = neonVars.NEON_AI_GATEWAY_BASE_URL;
|
|
179
180
|
const gatewayToken = neonVars.NEON_AI_GATEWAY_TOKEN;
|
|
@@ -189,6 +190,7 @@ const pull = async (props, opts = {}) => {
|
|
|
189
190
|
return {
|
|
190
191
|
status: "written",
|
|
191
192
|
written,
|
|
193
|
+
removed,
|
|
192
194
|
file: targetPath,
|
|
193
195
|
...credential && credential.keys.length > 0 ? { credential } : {},
|
|
194
196
|
...skipped && skipped.length > 0 ? { skipped } : {}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { NEON_SERVICES, NEON_SERVICE_LABELS } from "../neon_services.js";
|
|
2
|
+
import { u as isFunctionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
|
|
3
|
+
import { BRANCH_ENV_KEY, ENV_PULL_KEYS, serviceForEnvKey } from "../env_services.js";
|
|
4
|
+
import { isAbsolute, relative } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
//#region src/commands/env_output.ts
|
|
7
|
+
/** Wide enough for the longest label ("Object Storage") plus a gutter, so blocks align. */
|
|
8
|
+
const LABEL_WIDTH = 16;
|
|
9
|
+
const row = (label, value) => ` ${label.padEnd(LABEL_WIDTH)}${value}`;
|
|
10
|
+
/** Relative to `cwd` when the file is inside it, else absolute with the home directory as `~`. */
|
|
11
|
+
const displayPath = (file, cwd) => {
|
|
12
|
+
const rel = relative(cwd, file);
|
|
13
|
+
if (rel !== "" && !rel.startsWith("..") && !isAbsolute(rel)) return rel;
|
|
14
|
+
const home = homedir();
|
|
15
|
+
return file.startsWith(`${home}/`) ? `~${file.slice(home.length)}` : file;
|
|
16
|
+
};
|
|
17
|
+
const BRANCH_GROUP = "Branch";
|
|
18
|
+
const OTHER_GROUP = "Other";
|
|
19
|
+
const GROUP_ORDER = [
|
|
20
|
+
NEON_SERVICE_LABELS.postgres,
|
|
21
|
+
BRANCH_GROUP,
|
|
22
|
+
...NEON_SERVICES.filter((service) => service !== "postgres").map((service) => NEON_SERVICE_LABELS[service]),
|
|
23
|
+
OTHER_GROUP
|
|
24
|
+
];
|
|
25
|
+
const groupOf = (key) => {
|
|
26
|
+
if (key === BRANCH_ENV_KEY) return BRANCH_GROUP;
|
|
27
|
+
if (isFunctionBaseUrlKey(key)) return NEON_SERVICE_LABELS.functions;
|
|
28
|
+
const pullKey = ENV_PULL_KEYS.find((candidate) => candidate === key);
|
|
29
|
+
const service = pullKey ? serviceForEnvKey(pullKey) : null;
|
|
30
|
+
return service ? NEON_SERVICE_LABELS[service] : OTHER_GROUP;
|
|
31
|
+
};
|
|
32
|
+
/** Canonical key order, so a row reads the same whether a key was rewritten or reused. */
|
|
33
|
+
const keyRank = (key) => {
|
|
34
|
+
const index = ENV_PULL_KEYS.findIndex((candidate) => candidate === key);
|
|
35
|
+
return index === -1 ? ENV_PULL_KEYS.length : index;
|
|
36
|
+
};
|
|
37
|
+
const byKeyRank = (a, b) => keyRank(a) - keyRank(b) || a.localeCompare(b);
|
|
38
|
+
const groupKeys = (keys) => {
|
|
39
|
+
const groups = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const key of [...keys].sort(byKeyRank)) {
|
|
41
|
+
const group = groupOf(key);
|
|
42
|
+
groups.set(group, [...groups.get(group) ?? [], key]);
|
|
43
|
+
}
|
|
44
|
+
return groups;
|
|
45
|
+
};
|
|
46
|
+
const formatPulledEnv = (outcome, cwd) => {
|
|
47
|
+
const fresh = new Set(outcome.credential?.fresh ?? []);
|
|
48
|
+
const count = outcome.written.length;
|
|
49
|
+
const lines = [`Pulled ${count} Neon variable${count === 1 ? "" : "s"} into ${displayPath(outcome.file, cwd)}`];
|
|
50
|
+
const groups = groupKeys(outcome.written);
|
|
51
|
+
for (const group of GROUP_ORDER) {
|
|
52
|
+
const keys = groups.get(group);
|
|
53
|
+
if (!keys) continue;
|
|
54
|
+
lines.push(row(group, keys.map((key) => fresh.has(key) ? `${key}*` : key).join(", ")));
|
|
55
|
+
}
|
|
56
|
+
if (outcome.removed.length > 0) lines.push(row("Removed", `${outcome.removed.join(", ")} (not produced by this pull)`));
|
|
57
|
+
if (fresh.size > 0) lines.push(" * new credential value");
|
|
58
|
+
const credential = outcome.credential;
|
|
59
|
+
if (credential?.issued && credential.revoked.length > 0) lines.push(`Revoked the credential it replaced (${credential.revoked.join(", ")}).`);
|
|
60
|
+
else if (credential?.issued && credential.superseded.length > 0) lines.push(`Left the credential it replaced live (${credential.superseded.join(", ")}): an explicitly scoped pull can't tell which other variables still use it. Revoke it in the Neon Console if nothing does.`);
|
|
61
|
+
return `${lines.join("\n")}\n`;
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
export { displayPath, formatPulledEnv, row };
|
package/dist/commands/link.js
CHANGED
|
@@ -5,6 +5,7 @@ import { isNeonApiError, messageFromBody } from "../api.js";
|
|
|
5
5
|
import { r as defaultDir } from "../_chunks/paths-DMq0Lt7a.js";
|
|
6
6
|
import "../config.js";
|
|
7
7
|
import { applyContext, contextBranch, readContextFile, setContext, updateContextFile } from "../context.js";
|
|
8
|
+
import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
8
9
|
import { getCliName } from "../utils/cli_name.js";
|
|
9
10
|
import { helpEpilogue } from "../utils/help_text.js";
|
|
10
11
|
import { hasNeonConfigFile, projectDir } from "../project.js";
|
|
@@ -14,6 +15,7 @@ import { looksLikeBranchId } from "../utils/formats.js";
|
|
|
14
15
|
import { listAllProjectBranches } from "../utils/enrichers.js";
|
|
15
16
|
import { autoPullEnvAfterPin } from "./env.js";
|
|
16
17
|
import { initCmd } from "./config.js";
|
|
18
|
+
import { formatLinkSummary } from "./link_output.js";
|
|
17
19
|
import { REGIONS } from "./projects.js";
|
|
18
20
|
import prompts from "prompts";
|
|
19
21
|
//#region src/commands/link.ts
|
|
@@ -304,13 +306,28 @@ const failWithBranchCandidates = (props, projectId, branches, reason) => {
|
|
|
304
306
|
};
|
|
305
307
|
const listAllBranches = async (props, projectId) => listAllProjectBranches(props.apiClient, projectId);
|
|
306
308
|
/**
|
|
309
|
+
* The branch listing for {@link resolvePinnedBranch}, started alongside the project check
|
|
310
|
+
* except on Claimable Neon, which does not complete concurrent Management API reads. Only
|
|
311
|
+
* the listing starts early: branch selection, which can prompt or create a branch, still
|
|
312
|
+
* waits for the check, and a listing error is reported only after the check passes.
|
|
313
|
+
*/
|
|
314
|
+
const branchListing = (props, projectId) => {
|
|
315
|
+
if (isClaimableEnvTarget({
|
|
316
|
+
apiHost: props.apiHost,
|
|
317
|
+
contextFile: props.contextFile,
|
|
318
|
+
configDir: props.configDir ?? ""
|
|
319
|
+
})) return () => listAllBranches(props, projectId);
|
|
320
|
+
const listing = listAllBranches(props, projectId);
|
|
321
|
+
listing.catch(() => void 0);
|
|
322
|
+
return () => listing;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
307
325
|
* Resolve a branch reference (name *or* id) to the matching branch, while
|
|
308
326
|
* confirming it actually exists in the project. Unlike the shared
|
|
309
327
|
* `branchIdResolve`, this also verifies references that already look like ids
|
|
310
328
|
* (so a typo'd `br-…` doesn't silently get written).
|
|
311
329
|
*/
|
|
312
|
-
const resolveBranchRef =
|
|
313
|
-
const branches = await listAllBranches(props, projectId);
|
|
330
|
+
const resolveBranchRef = (props, projectId, branchRef, branches) => {
|
|
314
331
|
const match = branches.find((b) => b.id === branchRef) ?? branches.find((b) => b.name === branchRef);
|
|
315
332
|
if (match) return match;
|
|
316
333
|
return failWithBranchCandidates(props, projectId, branches, `Branch '${branchRef}' not found in project '${projectId}'. Pass --branch with a name or ID from the list:`);
|
|
@@ -336,43 +353,54 @@ const branchPersistValue = (branch) => {
|
|
|
336
353
|
* the field is simply omitted.
|
|
337
354
|
*/
|
|
338
355
|
const resolveOrgForProject = async (props, inputs, projectId) => {
|
|
339
|
-
const
|
|
356
|
+
const project = await fetchProjectOrThrow(props, projectId);
|
|
357
|
+
const projectOrg = project.org_id ?? void 0;
|
|
358
|
+
const projectName = project.name;
|
|
340
359
|
if (inputs.orgId) {
|
|
341
360
|
if (projectOrg && projectOrg !== inputs.orgId) throw new LinkInputError(`Project '${projectId}' belongs to organization '${projectOrg}', not '${inputs.orgId}'. Omit --org-id to use the project's own org, or pass the matching ID.`);
|
|
342
361
|
if (!projectOrg) throw new LinkInputError(`Project '${projectId}' does not report an organization matching --org-id ${inputs.orgId}. Omit --org-id to link using the project's own context.`);
|
|
343
|
-
return
|
|
362
|
+
return {
|
|
363
|
+
orgId: inputs.orgId,
|
|
364
|
+
projectName
|
|
365
|
+
};
|
|
344
366
|
}
|
|
345
|
-
|
|
367
|
+
return {
|
|
368
|
+
orgId: projectOrg,
|
|
369
|
+
projectName
|
|
370
|
+
};
|
|
346
371
|
};
|
|
372
|
+
const pinnedRef = (branch) => ({
|
|
373
|
+
branchId: branch.id,
|
|
374
|
+
branchName: branch.name ?? branch.id,
|
|
375
|
+
usedDefault: false
|
|
376
|
+
});
|
|
377
|
+
const resolution = (branch) => ({
|
|
378
|
+
branch: branchPersistValue(branch),
|
|
379
|
+
ref: pinnedRef(branch)
|
|
380
|
+
});
|
|
347
381
|
/**
|
|
348
382
|
* Keep a same-project pin only after it still resolves on the project.
|
|
349
383
|
*/
|
|
350
|
-
const resolvePinnedBranch = async (props, inputs, existing, projectId) => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return { branch: branchPersistValue(branch) };
|
|
354
|
-
}
|
|
384
|
+
const resolvePinnedBranch = async (props, inputs, existing, projectId, listBranches) => {
|
|
385
|
+
const branches = await listBranches();
|
|
386
|
+
if (inputs.branch) return resolution(resolveBranchRef(props, projectId, inputs.branch, branches));
|
|
355
387
|
if (projectId === existing.projectId) {
|
|
356
388
|
const pinned = contextBranch(existing);
|
|
357
|
-
if (pinned)
|
|
358
|
-
const branch = await resolveBranchRef(props, projectId, pinned);
|
|
359
|
-
return { branch: branchPersistValue(branch) };
|
|
360
|
-
}
|
|
389
|
+
if (pinned) return resolution(resolveBranchRef(props, projectId, pinned, branches));
|
|
361
390
|
}
|
|
362
|
-
return resolveBranchFromList(props, projectId);
|
|
391
|
+
return resolveBranchFromList(props, projectId, branches);
|
|
363
392
|
};
|
|
364
|
-
const resolveBranchFromList = async (props, projectId) => {
|
|
365
|
-
const branches = await listAllBranches(props, projectId);
|
|
393
|
+
const resolveBranchFromList = async (props, projectId, branches) => {
|
|
366
394
|
if (branches.length === 0) throw new LinkInputError(`Project '${projectId}' has no branches to link. Create or restore a branch before retrying, or select another project with --project-id.`);
|
|
367
395
|
if (branches.length === 1) {
|
|
368
396
|
const [only] = branches;
|
|
369
397
|
if (!only) throw new LinkInputError(`Project '${projectId}' has no branches to link. Create or restore a branch before retrying, or select another project with --project-id.`);
|
|
370
|
-
return
|
|
398
|
+
return resolution(only);
|
|
371
399
|
}
|
|
372
400
|
if (props.yes) {
|
|
373
401
|
const def = branches.find((b) => b.default);
|
|
374
402
|
if (!def) return failWithBranchCandidates(props, projectId, branches, `Project '${projectId}' has no default branch. Pass --branch with a name or ID from the list:`);
|
|
375
|
-
return
|
|
403
|
+
return resolution(def);
|
|
376
404
|
}
|
|
377
405
|
if (canPromptInteractively()) {
|
|
378
406
|
const picked = await pickBranchInteractively(branches, {
|
|
@@ -381,13 +409,19 @@ const resolveBranchFromList = async (props, projectId) => {
|
|
|
381
409
|
});
|
|
382
410
|
if (picked.kind === "existing") {
|
|
383
411
|
const existing = branches.find((b) => b.id === picked.branchId);
|
|
384
|
-
return
|
|
412
|
+
return existing ? resolution(existing) : {
|
|
413
|
+
branch: picked.branchId,
|
|
414
|
+
ref: pinnedRef({ id: picked.branchId })
|
|
415
|
+
};
|
|
385
416
|
}
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
id: created,
|
|
417
|
+
const createdBranch = {
|
|
418
|
+
id: await createBranch(props.apiClient, projectId, picked.name, branches),
|
|
389
419
|
name: picked.name
|
|
390
|
-
}
|
|
420
|
+
};
|
|
421
|
+
return {
|
|
422
|
+
branch: branchPersistValue(createdBranch),
|
|
423
|
+
ref: pinnedRef(createdBranch)
|
|
424
|
+
};
|
|
391
425
|
}
|
|
392
426
|
return failWithBranchCandidates(props, projectId, branches, `Project '${projectId}' has multiple branches. Pass --branch <name-or-id>, or -y to pin its default branch.`);
|
|
393
427
|
};
|
|
@@ -410,6 +444,10 @@ const runNonInteractive = async (props, inputs, existing) => {
|
|
|
410
444
|
orgId,
|
|
411
445
|
projectId: created.project.id,
|
|
412
446
|
branch: created.branchName,
|
|
447
|
+
branchRef: pinnedRef({
|
|
448
|
+
id: created.branchId,
|
|
449
|
+
name: created.branchName
|
|
450
|
+
}),
|
|
413
451
|
created: true,
|
|
414
452
|
projectName: created.project.name,
|
|
415
453
|
regionId: created.project.region_id
|
|
@@ -417,8 +455,9 @@ const runNonInteractive = async (props, inputs, existing) => {
|
|
|
417
455
|
return;
|
|
418
456
|
}
|
|
419
457
|
if (inputs.projectId) {
|
|
420
|
-
const
|
|
421
|
-
const
|
|
458
|
+
const listBranches = branchListing(props, inputs.projectId);
|
|
459
|
+
const { orgId, projectName } = await resolveOrgForProject(props, inputs, inputs.projectId);
|
|
460
|
+
const resolved = await resolvePinnedBranch(props, inputs, existing, inputs.projectId, listBranches);
|
|
422
461
|
applyContext(props.contextFile, {
|
|
423
462
|
orgId,
|
|
424
463
|
projectId: inputs.projectId,
|
|
@@ -428,15 +467,18 @@ const runNonInteractive = async (props, inputs, existing) => {
|
|
|
428
467
|
contextFile: props.contextFile,
|
|
429
468
|
orgId,
|
|
430
469
|
projectId: inputs.projectId,
|
|
470
|
+
projectName,
|
|
431
471
|
branch: resolved.branch,
|
|
472
|
+
branchRef: resolved.ref,
|
|
432
473
|
created: false
|
|
433
474
|
});
|
|
434
475
|
return;
|
|
435
476
|
}
|
|
436
477
|
if (inputs.branch && existing.projectId) {
|
|
437
478
|
const projectId = existing.projectId;
|
|
438
|
-
const
|
|
439
|
-
const
|
|
479
|
+
const listBranches = branchListing(props, projectId);
|
|
480
|
+
const { orgId, projectName } = await resolveOrgForProject(props, inputs, projectId);
|
|
481
|
+
const resolved = await resolvePinnedBranch(props, inputs, existing, projectId, listBranches);
|
|
440
482
|
applyContext(props.contextFile, {
|
|
441
483
|
orgId,
|
|
442
484
|
projectId,
|
|
@@ -446,7 +488,9 @@ const runNonInteractive = async (props, inputs, existing) => {
|
|
|
446
488
|
contextFile: props.contextFile,
|
|
447
489
|
orgId,
|
|
448
490
|
projectId,
|
|
491
|
+
projectName,
|
|
449
492
|
branch: resolved.branch,
|
|
493
|
+
branchRef: resolved.ref,
|
|
450
494
|
created: false
|
|
451
495
|
});
|
|
452
496
|
return;
|
|
@@ -481,6 +525,10 @@ const runInteractive = async (props, inputs) => {
|
|
|
481
525
|
orgId,
|
|
482
526
|
projectId: created.project.id,
|
|
483
527
|
branch: created.branchName,
|
|
528
|
+
branchRef: pinnedRef({
|
|
529
|
+
id: created.branchId,
|
|
530
|
+
name: created.branchName
|
|
531
|
+
}),
|
|
484
532
|
created: true,
|
|
485
533
|
projectName: created.project.name,
|
|
486
534
|
regionId: created.project.region_id
|
|
@@ -491,7 +539,7 @@ const runInteractive = async (props, inputs) => {
|
|
|
491
539
|
const action = await promptProjectChoice(projects, inputs.projectName, { allowCreate: !inputs.branch });
|
|
492
540
|
if (action.type === "existing") {
|
|
493
541
|
const existing = readContextFile(props.contextFile);
|
|
494
|
-
const resolved = await resolvePinnedBranch(props, inputs, existing, action.projectId);
|
|
542
|
+
const resolved = await resolvePinnedBranch(props, inputs, existing, action.projectId, () => listAllBranches(props, action.projectId));
|
|
495
543
|
applyContext(props.contextFile, {
|
|
496
544
|
orgId,
|
|
497
545
|
projectId: action.projectId,
|
|
@@ -502,6 +550,7 @@ const runInteractive = async (props, inputs) => {
|
|
|
502
550
|
orgId,
|
|
503
551
|
projectId: action.projectId,
|
|
504
552
|
branch: resolved.branch,
|
|
553
|
+
branchRef: resolved.ref,
|
|
505
554
|
created: false,
|
|
506
555
|
projectName: action.name,
|
|
507
556
|
regionId: action.regionId
|
|
@@ -526,6 +575,10 @@ const runInteractive = async (props, inputs) => {
|
|
|
526
575
|
orgId,
|
|
527
576
|
projectId: created.project.id,
|
|
528
577
|
branch: created.branchName,
|
|
578
|
+
branchRef: pinnedRef({
|
|
579
|
+
id: created.branchId,
|
|
580
|
+
name: created.branchName
|
|
581
|
+
}),
|
|
529
582
|
created: true,
|
|
530
583
|
projectName: created.project.name,
|
|
531
584
|
regionId: created.project.region_id
|
|
@@ -800,19 +853,8 @@ const createProject = async (props, args) => {
|
|
|
800
853
|
branchName: data.branch.name ?? data.branch.id
|
|
801
854
|
};
|
|
802
855
|
};
|
|
803
|
-
const printSummary = (
|
|
804
|
-
|
|
805
|
-
if (summary.created) lines.push(`Created project ${summary.projectId}${summary.projectName ? ` ("${summary.projectName}")` : ""}${summary.regionId ? ` in ${summary.regionId}` : ""}.`);
|
|
806
|
-
lines.push(`Linked ${summary.contextFile}:`);
|
|
807
|
-
if (summary.orgId) lines.push(` orgId: ${summary.orgId}`);
|
|
808
|
-
lines.push(` projectId: ${summary.projectId}`);
|
|
809
|
-
lines.push(` branch: ${summary.branch}`);
|
|
810
|
-
if (summary.noChecks) {
|
|
811
|
-
lines.push("");
|
|
812
|
-
lines.push("Written offline (--no-checks): nothing was verified.");
|
|
813
|
-
}
|
|
814
|
-
lines.push("");
|
|
815
|
-
process.stdout.write(`${lines.join("\n")}\n`);
|
|
856
|
+
const printSummary = (props, summary) => {
|
|
857
|
+
process.stdout.write(`${formatLinkSummary(summary, props.cwd ?? process.cwd())}\n`);
|
|
816
858
|
};
|
|
817
859
|
/**
|
|
818
860
|
* Print the link summary, then run the bundled `env pull` so a completed `link` ends with
|
|
@@ -830,7 +872,7 @@ const finalizeLink = async (props, summary) => {
|
|
|
830
872
|
projectId: summary.projectId,
|
|
831
873
|
branch: summary.branch,
|
|
832
874
|
envPull: props.envPull
|
|
833
|
-
});
|
|
875
|
+
}, summary.branchRef);
|
|
834
876
|
};
|
|
835
877
|
/**
|
|
836
878
|
* Interactive `link` finalize: the shared {@link finalizeLink} (summary + env
|
|
@@ -876,7 +918,7 @@ const maybeOfferConfigInit = async (props, summary) => {
|
|
|
876
918
|
projectId: summary.projectId,
|
|
877
919
|
branch: summary.branch,
|
|
878
920
|
envPull: props.envPull
|
|
879
|
-
});
|
|
921
|
+
}, summary.branchRef);
|
|
880
922
|
}
|
|
881
923
|
};
|
|
882
924
|
const onPromptState = (state) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { displayPath, row } from "./env_output.js";
|
|
2
|
+
//#region src/commands/link_output.ts
|
|
3
|
+
const formatLinkSummary = (summary, cwd) => {
|
|
4
|
+
const lines = [];
|
|
5
|
+
if (summary.created) lines.push(`Created project ${summary.projectName ?? summary.projectId}${summary.regionId ? ` in ${summary.regionId}` : ""}`);
|
|
6
|
+
lines.push(`Linked ${displayPath(summary.contextFile, cwd)}`);
|
|
7
|
+
lines.push(row("Project", summary.projectName ? `${summary.projectName} (${summary.projectId})` : summary.projectId));
|
|
8
|
+
lines.push(row("Branch", summary.branch));
|
|
9
|
+
if (summary.orgId) lines.push(row("Org", summary.orgId));
|
|
10
|
+
if (summary.noChecks) {
|
|
11
|
+
lines.push("");
|
|
12
|
+
lines.push("Written offline (--no-checks): nothing was verified.");
|
|
13
|
+
}
|
|
14
|
+
return `${lines.join("\n")}\n`;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { formatLinkSummary };
|
package/dist/dev/env.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-
|
|
1
|
+
import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-BWqJqBDJ.js";
|
|
2
2
|
export { DevEnvMismatchError, MissingBranchContextError, ServiceNotOnBranchError, dropClaimableUnsupported, resolveDevEnv, resolveNeonEnvVars };
|
package/dist/env_services.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NEON_SERVICES } from "./neon_services.js";
|
|
2
|
-
import { f as parseFunctionBaseUrlKey, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./_chunks/env-
|
|
2
|
+
import { f as parseFunctionBaseUrlKey, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./_chunks/env-HYNFtxu5.js";
|
|
3
3
|
//#region src/env_services.ts
|
|
4
4
|
const ENV_PULL_SERVICES = NEON_SERVICES;
|
|
5
5
|
const ENV_PULL_KEYS = [
|
package/dist/neon_services.js
CHANGED
|
@@ -25,6 +25,15 @@ const NEON_SERVICES = [
|
|
|
25
25
|
"object-storage",
|
|
26
26
|
"ai-gateway"
|
|
27
27
|
];
|
|
28
|
+
/** How output names each service. */
|
|
29
|
+
const NEON_SERVICE_LABELS = {
|
|
30
|
+
postgres: "Postgres",
|
|
31
|
+
auth: "Neon Auth",
|
|
32
|
+
"data-api": "Data API",
|
|
33
|
+
"object-storage": "Object Storage",
|
|
34
|
+
functions: "Functions",
|
|
35
|
+
"ai-gateway": "AI Gateway"
|
|
36
|
+
};
|
|
28
37
|
/**
|
|
29
38
|
* Spellings that used to be canonical, and the service they now mean. Accepted so a scripted
|
|
30
39
|
* `--services storage` keeps working, warned about so it does not quietly become a second
|
|
@@ -115,4 +124,4 @@ const servicesOption = (params) => ({
|
|
|
115
124
|
*/
|
|
116
125
|
const servicesFlagValue = (value) => Array.isArray(value) ? value.map(String) : void 0;
|
|
117
126
|
//#endregion
|
|
118
|
-
export { NEON_SERVICES, NO_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption };
|
|
127
|
+
export { NEON_SERVICES, NEON_SERVICE_LABELS, NO_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption };
|
|
@@ -131,11 +131,12 @@ const assertAiGatewayProvisionable = async (params) => {
|
|
|
131
131
|
* fetching the plan or catalog is swallowed so it can never break the underlying command.
|
|
132
132
|
*/
|
|
133
133
|
const warnAiGateway = async (params) => {
|
|
134
|
+
const modelsRead = params.gateway ? fetchGatewayModelIds(params.gateway.baseUrl, params.gateway.token) : null;
|
|
134
135
|
try {
|
|
135
136
|
const { data } = await params.apiClient.getProject(params.projectId);
|
|
136
137
|
const subscriptionType = data.project.owner?.subscription_type;
|
|
137
138
|
const orgId = data.project.org_id ?? void 0;
|
|
138
|
-
const modelIds =
|
|
139
|
+
const modelIds = modelsRead ? await modelsRead ?? void 0 : void 0;
|
|
139
140
|
const notice = buildAiGatewayNotice({
|
|
140
141
|
subscriptionType,
|
|
141
142
|
modelIds,
|
package/dist/utils/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { log } from "../log.js";
|
|
2
2
|
import { loadProjectConfig } from "../project.js";
|
|
3
|
-
import { s as fetchEnvKeys } from "../_chunks/env-
|
|
3
|
+
import { s as fetchEnvKeys } from "../_chunks/env-HYNFtxu5.js";
|
|
4
4
|
import { runHook } from "@neon/config-runtime";
|
|
5
5
|
//#region src/utils/hooks.ts
|
|
6
6
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.10",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"yaml": "^2.9.0",
|
|
65
65
|
"yargs": "17.7.2",
|
|
66
66
|
"yoctocolors": "^2.1.2",
|
|
67
|
-
"@neon/
|
|
68
|
-
"@neon/config-runtime": "1.7.
|
|
69
|
-
"@neon/
|
|
67
|
+
"@neon/config": "1.8.4",
|
|
68
|
+
"@neon/config-runtime": "1.7.6",
|
|
69
|
+
"@neon/sdk": "7.0.1"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|
|
72
72
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
"typescript": "^5.9.0",
|
|
101
101
|
"vitest": "^3.0.9",
|
|
102
102
|
"@neon-internals/cli-core": "0.0.0",
|
|
103
|
-
"@neon-internals/env-core": "0.0.26",
|
|
104
103
|
"@neon/e2e-harness": "0.0.0",
|
|
104
|
+
"@neon-internals/env-core": "0.0.27",
|
|
105
105
|
"@neon/functions": "0.11.0"
|
|
106
106
|
},
|
|
107
107
|
"publishConfig": {
|