neon 8.0.9 → 8.0.11

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.
@@ -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-C3uXp-nh.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-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";
@@ -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}.`);
@@ -142,8 +158,11 @@ async function fetchEnvKeysState(config, options, keys, policy) {
142
158
  wants
143
159
  });
144
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));
145
163
  const functionsRead = functionUrlMode === "all-live" && wantsAnyFunctionUrl && options.listedFunctions === void 0 ? listFunctionInvocationUrls(api, projectId, branch.id) : null;
146
164
  functionsRead?.catch(() => void 0);
165
+ const storageRead = wantsStorage ? started(() => api.getProjectBranchStorage(projectId, branch.id)) : null;
147
166
  const needsUnpooled = wantsUnpooled || gatewayEnabled && wants(K.aiGateway.baseUrl) || constructSlugs.length > 0;
148
167
  const needsConnectionTarget = wantsPooled || needsUnpooled;
149
168
  const needsDatabase = needsConnectionTarget || wantsDataApi;
@@ -193,8 +212,6 @@ async function fetchEnvKeysState(config, options, keys, policy) {
193
212
  }
194
213
  result.dataApi = { url: dataApiSnapshot.url };
195
214
  }
196
- const storageEnabled = (desired.preview?.buckets.length ?? 0) > 0;
197
- const wantsStorage = storageEnabled && (wants(K.storage.accessKeyId) || wants(K.storage.secretAccessKey) || wants(K.storage.endpoint) || wants(K.storage.region));
198
215
  const wantsGateway = gatewayEnabled && (wants(K.aiGateway.apiKey) || wants(K.aiGateway.baseUrl));
199
216
  const wantsStorageCredential = storageEnabled && (wants(K.storage.accessKeyId) || wants(K.storage.secretAccessKey));
200
217
  const wantsGatewayCredential = gatewayEnabled && wants(K.aiGateway.apiKey);
@@ -202,7 +219,7 @@ async function fetchEnvKeysState(config, options, keys, policy) {
202
219
  if (wantsStorage || wantsGateway) {
203
220
  let storage = null;
204
221
  if (wantsStorage) {
205
- storage = await api.getProjectBranchStorage(projectId, branch.id);
222
+ storage = await requiredValue(storageRead, "storage read");
206
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: {
207
224
  projectId,
208
225
  branchId: branch.id
@@ -407,12 +424,14 @@ async function resolveBranchCredentialSecrets(args) {
407
424
  secretAccessKey: "",
408
425
  apiToken: ""
409
426
  };
410
- if (needsStorage && storageDefault) {
411
- const revealed = await args.api.revealCredential(args.projectId, args.branchId, storageDefault.tokenId);
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;
412
431
  secrets.accessKeyId = revealed.tokenId;
413
432
  secrets.secretAccessKey = revealed.s3SecretAccessKey;
414
433
  }
415
- if (needsGateway && gatewayDefault) secrets.apiToken = (await args.api.revealCredential(args.projectId, args.branchId, gatewayDefault.tokenId)).apiToken;
434
+ if (gatewayReveal) secrets.apiToken = (await gatewayReveal).apiToken;
416
435
  const missingStorage = needsStorage && storageDefault === null;
417
436
  const missingGateway = needsGateway && gatewayDefault === null;
418
437
  if (missingStorage || missingGateway) {
@@ -3,10 +3,10 @@ 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-C3uXp-nh.js";
6
+ import { l as functionBaseUrlKey } from "../_chunks/env-HYNFtxu5.js";
7
7
  import { branchIdResolve } from "../utils/enrichers.js";
8
8
  import { bundleEntry } from "../utils/esbuild.js";
9
- import { a as resolveDevEnv } from "../_chunks/env-ur81YnUp.js";
9
+ import { a as resolveDevEnv } from "../_chunks/env-BWqJqBDJ.js";
10
10
  import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
11
11
  import { resolveWatchInputs } from "../dev/inputs.js";
12
12
  import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -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-C3uXp-nh.js";
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
10
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
11
11
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
12
12
  import { announceTargetBranch } from "../utils/branch_notice.js";
13
- import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-ur81YnUp.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,15 +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
- if (opts.report !== false) {
171
- log.info("Pulled %d Neon variable%s into %s: %s", written.length, written.length === 1 ? "" : "s", targetPath, written.join(", "));
172
- 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(", "));
173
- if (credential?.issued) {
174
- log.info("Wrote credential secrets — these now hold fresh values: %s", credential.keys.join(", "));
175
- if (credential.revoked.length > 0) log.info("Revoked the credential it replaced (%s).", credential.revoked.join(", "));
176
- else if (credential.superseded.length > 0) log.info("Left the credential it replaced live (%s): an explicitly scoped pull can't tell which other variables still use it. Revoke it in the Neon Console if nothing does.", credential.superseded.join(", "));
177
- }
178
- }
171
+ process.stderr.write(formatPulledEnv({
172
+ status: "written",
173
+ written,
174
+ removed,
175
+ file: targetPath,
176
+ ...credential && credential.keys.length > 0 ? { credential } : {}
177
+ }, cwd));
179
178
  if (!fileExisted) ensureGitignored(targetPath);
180
179
  const gatewayBaseUrl = neonVars.NEON_AI_GATEWAY_BASE_URL;
181
180
  const gatewayToken = neonVars.NEON_AI_GATEWAY_TOKEN;
@@ -254,16 +253,13 @@ const pickSelectedVars = (vars, selectedKeys, services) => {
254
253
  * command's primary effect and has already succeeded by the time this runs, so a pull failure
255
254
  * degrades to a warning rather than failing the command.
256
255
  */
257
- const autoPullEnvAfterPin = async (props, branch, opts = {}) => {
256
+ const autoPullEnvAfterPin = async (props, branch) => {
258
257
  if (!props.envPull) {
259
258
  log.info(chalk.dim(ENV_PULL_SKIPPED_HINT));
260
259
  return { status: "skipped" };
261
260
  }
262
261
  try {
263
- return await pull(props, {
264
- ...branch ? { branch } : {},
265
- ...opts.report === false ? { report: false } : {}
266
- });
262
+ return await pull(props, branch ? { branch } : {});
267
263
  } catch (err) {
268
264
  const message = err instanceof Error ? err.message : String(err);
269
265
  log.warning(`Branch pinned, but pulling its Neon env vars failed: %s
@@ -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 };
@@ -18,6 +18,7 @@ var functions_exports = /* @__PURE__ */ __exportAll({
18
18
  aliases: () => aliases,
19
19
  builder: () => builder,
20
20
  command: () => command,
21
+ deploymentPollDelay: () => deploymentPollDelay,
21
22
  describe: () => describe,
22
23
  handler: () => handler
23
24
  });
@@ -39,33 +40,54 @@ const LIST_TABLE_FIELDS = [
39
40
  "slug",
40
41
  "name",
41
42
  "status",
42
- "invocation_url",
43
+ "URL",
43
44
  "created_at"
44
45
  ];
45
- const DEPLOYMENT_FIELDS = [
46
+ const DEPLOY_RESULT_FIELDS = [
46
47
  "id",
47
48
  "status",
49
+ "invocation_url",
48
50
  "runtime",
49
51
  "memory_mib",
50
52
  "created_at"
51
53
  ];
52
- const DEPLOY_RESULT_FIELDS = [
53
- "id",
54
+ const DEPLOYMENT_TABLE_FIELDS = [
55
+ "deployment_id",
54
56
  "status",
55
- "invocation_url",
56
57
  "runtime",
57
- "memory_mib",
58
+ "memory",
59
+ "created_at"
60
+ ];
61
+ const deploymentTableView = (dep) => ({
62
+ deployment_id: dep.id,
63
+ status: dep.status,
64
+ runtime: dep.runtime,
65
+ memory: `${dep.memory_mib} MiB`,
66
+ created_at: dep.created_at
67
+ });
68
+ const FUNCTION_TABLE_FIELDS = [
69
+ "slug",
70
+ "name",
71
+ "URL",
58
72
  "created_at"
59
73
  ];
60
74
  const writeDeploymentErrorSection = (out, dep) => {
61
75
  if (dep.status === "failed" && dep.error) out.write({ reason: dep.error }, {
62
76
  fields: ["reason"],
63
- title: "deployment error"
77
+ title: "Deployment error"
64
78
  });
65
79
  };
66
80
  const SLUG_PATTERN = /^[a-z0-9]{1,20}$/;
67
81
  const SLUG_HELP = "Use 1-20 lowercase letters and digits (no hyphens or other characters).";
68
- const POLL_INTERVAL_MS = Number(process.env.NEON_FUNCTIONS_POLL_INTERVAL_MS) || 2e3;
82
+ const POLL_INTERVAL_OVERRIDE_MS = Number(process.env.NEON_FUNCTIONS_POLL_INTERVAL_MS) || void 0;
83
+ const FIRST_POLL_DELAY_MS = 200;
84
+ const POLL_INTERVAL_MS = 2e3;
85
+ /**
86
+ * Delay before poll `attempt` (zero-based). A small function's deployment is usually done
87
+ * within a few hundred milliseconds of the POST, so the first check comes soon and longer
88
+ * builds back off to the steady interval.
89
+ */
90
+ const deploymentPollDelay = (attempt, override = POLL_INTERVAL_OVERRIDE_MS) => override ?? Math.min(FIRST_POLL_DELAY_MS * 2 ** attempt, POLL_INTERVAL_MS);
69
91
  const POLL_TIMEOUT_MS = Number(process.env.NEON_FUNCTIONS_POLL_TIMEOUT_MS) || 6e5;
70
92
  const command = "functions";
71
93
  const describe = "Manage Neon Functions";
@@ -157,11 +179,21 @@ const parseEnv = (entries) => {
157
179
  };
158
180
  const statusHint = (slug, projectId, branchId) => `Check status with: ${getCliName()} function get ${slug} --project-id ${projectId} --branch ${branchId}`;
159
181
  const emitDeployResult = (props, deployment, fn) => {
182
+ if (props.output === "json" || props.output === "yaml") {
183
+ writer(props).write({
184
+ ...deployment,
185
+ invocation_url: fn?.invocation_url
186
+ }, { fields: DEPLOY_RESULT_FIELDS }).end();
187
+ return;
188
+ }
160
189
  const out = writer(props).write({
161
- ...deployment,
162
- invocation_url: fn?.invocation_url
163
- }, { fields: DEPLOY_RESULT_FIELDS });
164
- if (props.output !== "json" && props.output !== "yaml") writeDeploymentErrorSection(out, deployment);
190
+ URL: fn?.invocation_url,
191
+ ...deploymentTableView(deployment)
192
+ }, {
193
+ fields: ["URL", ...DEPLOYMENT_TABLE_FIELDS],
194
+ title: `Function ${props.slug}`
195
+ });
196
+ writeDeploymentErrorSection(out, deployment);
165
197
  out.end();
166
198
  };
167
199
  const isTransient = (err) => isNeonApiError(err) && (err.status === void 0 || err.status === 404 || err.status >= 500);
@@ -215,9 +247,11 @@ const deploy = async (props) => {
215
247
  let resolved;
216
248
  let resolvedFn;
217
249
  const deadline = Date.now() + POLL_TIMEOUT_MS;
250
+ let attempt = 0;
218
251
  try {
219
252
  while (!interrupted && Date.now() < deadline) {
220
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
253
+ await new Promise((r) => setTimeout(r, deploymentPollDelay(attempt)));
254
+ attempt += 1;
221
255
  if (interrupted) break;
222
256
  let fn;
223
257
  try {
@@ -253,7 +287,7 @@ const deploy = async (props) => {
253
287
  return;
254
288
  }
255
289
  if (resolved.status === "completed") {
256
- log.info(`Function deployment ${props.slug}/${resolved.id} completed.`);
290
+ if (props.output === "json" || props.output === "yaml") log.info(`Function deployment ${props.slug}/${resolved.id} completed.`);
257
291
  return;
258
292
  }
259
293
  if (resolved.status === "failed") throw new Error(`Function deployment ${props.slug}/${resolved.id} failed.`);
@@ -267,34 +301,37 @@ const get = async (props) => {
267
301
  writer(props).end(fn, { fields: FUNCTION_FIELDS });
268
302
  return;
269
303
  }
270
- const out = writer(props).write(fn, {
271
- fields: FUNCTION_FIELDS,
272
- title: "function"
304
+ const out = writer(props).write({
305
+ ...fn,
306
+ URL: fn.invocation_url
307
+ }, {
308
+ fields: FUNCTION_TABLE_FIELDS,
309
+ title: "Function"
273
310
  });
274
311
  const current = fn.current_deployment;
275
312
  const active = fn.active_deployment;
276
313
  if (current && active && current.id === active.id) {
277
- out.write(current, {
278
- fields: DEPLOYMENT_FIELDS,
279
- title: "deployment (current, active)"
314
+ out.write(deploymentTableView(current), {
315
+ fields: DEPLOYMENT_TABLE_FIELDS,
316
+ title: "Deployment (current, active)"
280
317
  });
281
318
  writeDeploymentErrorSection(out, current);
282
319
  } else {
283
320
  if (current) {
284
- out.write(current, {
285
- fields: DEPLOYMENT_FIELDS,
286
- title: "current deployment"
321
+ out.write(deploymentTableView(current), {
322
+ fields: DEPLOYMENT_TABLE_FIELDS,
323
+ title: "Current deployment"
287
324
  });
288
325
  writeDeploymentErrorSection(out, current);
289
326
  }
290
- if (active) out.write(active, {
291
- fields: DEPLOYMENT_FIELDS,
292
- title: "active deployment"
327
+ if (active) out.write(deploymentTableView(active), {
328
+ fields: DEPLOYMENT_TABLE_FIELDS,
329
+ title: "Active deployment"
293
330
  });
294
331
  }
295
332
  if (props.listEnvVariables) out.write((fn.active_deployment?.environment ?? []).map((name) => ({ name })), {
296
333
  fields: ["name"],
297
- title: "environment",
334
+ title: "Environment",
298
335
  emptyMessage: "No environment variables on the active deployment."
299
336
  });
300
337
  out.end();
@@ -329,7 +366,8 @@ const list = async (props) => {
329
366
  }
330
367
  writer(props).end(functions.map((fn) => ({
331
368
  ...fn,
332
- status: fn.current_deployment?.status ?? ""
369
+ status: fn.current_deployment?.status ?? "",
370
+ URL: fn.invocation_url
333
371
  })), {
334
372
  fields: LIST_TABLE_FIELDS,
335
373
  emptyMessage: "No functions found on this branch."
@@ -376,4 +414,4 @@ const deleteDomain = async (props) => {
376
414
  log.info(`Custom domain ${props.domain} deleted from branch ${branchId}`);
377
415
  };
378
416
  //#endregion
379
- export { aliases, builder, command, describe, handler, functions_exports as t };
417
+ export { aliases, builder, command, deploymentPollDelay, describe, handler, functions_exports as t };
@@ -15,7 +15,7 @@ import { looksLikeBranchId } from "../utils/formats.js";
15
15
  import { listAllProjectBranches } from "../utils/enrichers.js";
16
16
  import { autoPullEnvAfterPin } from "./env.js";
17
17
  import { initCmd } from "./config.js";
18
- import { formatLinkSummary, formatPulledEnv } from "./link_output.js";
18
+ import { formatLinkSummary } from "./link_output.js";
19
19
  import { REGIONS } from "./projects.js";
20
20
  import prompts from "prompts";
21
21
  //#region src/commands/link.ts
@@ -856,11 +856,6 @@ const createProject = async (props, args) => {
856
856
  const printSummary = (props, summary) => {
857
857
  process.stdout.write(`${formatLinkSummary(summary, props.cwd ?? process.cwd())}\n`);
858
858
  };
859
- /** The env block `link` prints under its summary, once the bundled pull has written the file. */
860
- const printPulledEnv = (props, result) => {
861
- if (result.status !== "written") return;
862
- process.stdout.write(formatPulledEnv(result, props.cwd ?? process.cwd()));
863
- };
864
859
  /**
865
860
  * Print the link summary, then run the bundled `env pull` so a completed `link` ends with
866
861
  * the pinned branch's connection string on disk. `--no-checks` skips the pull because
@@ -871,14 +866,13 @@ const finalizeLink = async (props, summary) => {
871
866
  printSummary(props, summary);
872
867
  if (!summary.branch || !summary.projectId || summary.noChecks) return;
873
868
  const { config: _offerConfig, ...rest } = props;
874
- const pulled = await autoPullEnvAfterPin({
869
+ await autoPullEnvAfterPin({
875
870
  ...rest,
876
871
  ...props.cwd ? { cwd: props.cwd } : {},
877
872
  projectId: summary.projectId,
878
873
  branch: summary.branch,
879
874
  envPull: props.envPull
880
- }, summary.branchRef, { report: false });
881
- printPulledEnv(props, pulled);
875
+ }, summary.branchRef);
882
876
  };
883
877
  /**
884
878
  * Interactive `link` finalize: the shared {@link finalizeLink} (summary + env
@@ -919,13 +913,12 @@ const maybeOfferConfigInit = async (props, summary) => {
919
913
  });
920
914
  if (summary.branch && summary.projectId) {
921
915
  const { config: _offerConfig, ...rest } = props;
922
- const pulled = await autoPullEnvAfterPin({
916
+ await autoPullEnvAfterPin({
923
917
  ...rest,
924
918
  projectId: summary.projectId,
925
919
  branch: summary.branch,
926
920
  envPull: props.envPull
927
- }, summary.branchRef, { report: false });
928
- printPulledEnv(props, pulled);
921
+ }, summary.branchRef);
929
922
  }
930
923
  };
931
924
  const onPromptState = (state) => {
@@ -1,19 +1,5 @@
1
- import { NEON_SERVICES, NEON_SERVICE_LABELS } from "../neon_services.js";
2
- import { u as isFunctionBaseUrlKey } from "../_chunks/env-C3uXp-nh.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";
1
+ import { displayPath, row } from "./env_output.js";
6
2
  //#region src/commands/link_output.ts
7
- /** Wide enough for the longest label ("Object Storage") plus a gutter, so both 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
3
  const formatLinkSummary = (summary, cwd) => {
18
4
  const lines = [];
19
5
  if (summary.created) lines.push(`Created project ${summary.projectName ?? summary.projectId}${summary.regionId ? ` in ${summary.regionId}` : ""}`);
@@ -27,51 +13,5 @@ const formatLinkSummary = (summary, cwd) => {
27
13
  }
28
14
  return `${lines.join("\n")}\n`;
29
15
  };
30
- const BRANCH_GROUP = "Branch";
31
- const OTHER_GROUP = "Other";
32
- const GROUP_ORDER = [
33
- NEON_SERVICE_LABELS.postgres,
34
- BRANCH_GROUP,
35
- ...NEON_SERVICES.filter((service) => service !== "postgres").map((service) => NEON_SERVICE_LABELS[service]),
36
- OTHER_GROUP
37
- ];
38
- const groupOf = (key) => {
39
- if (key === BRANCH_ENV_KEY) return BRANCH_GROUP;
40
- if (isFunctionBaseUrlKey(key)) return NEON_SERVICE_LABELS.functions;
41
- const pullKey = ENV_PULL_KEYS.find((candidate) => candidate === key);
42
- const service = pullKey ? serviceForEnvKey(pullKey) : null;
43
- return service ? NEON_SERVICE_LABELS[service] : OTHER_GROUP;
44
- };
45
- /** Canonical key order, so a row reads the same whether a key was rewritten or reused. */
46
- const keyRank = (key) => {
47
- const index = ENV_PULL_KEYS.findIndex((candidate) => candidate === key);
48
- return index === -1 ? ENV_PULL_KEYS.length : index;
49
- };
50
- const byKeyRank = (a, b) => keyRank(a) - keyRank(b) || a.localeCompare(b);
51
- const groupKeys = (keys) => {
52
- const groups = /* @__PURE__ */ new Map();
53
- for (const key of [...keys].sort(byKeyRank)) {
54
- const group = groupOf(key);
55
- groups.set(group, [...groups.get(group) ?? [], key]);
56
- }
57
- return groups;
58
- };
59
- const formatPulledEnv = (outcome, cwd) => {
60
- const fresh = new Set(outcome.credential?.fresh ?? []);
61
- const count = outcome.written.length;
62
- const lines = [`Pulled ${count} Neon variable${count === 1 ? "" : "s"} into ${displayPath(outcome.file, cwd)}`];
63
- const groups = groupKeys(outcome.written);
64
- for (const group of GROUP_ORDER) {
65
- const keys = groups.get(group);
66
- if (!keys) continue;
67
- lines.push(row(group, keys.map((key) => fresh.has(key) ? `${key}*` : key).join(", ")));
68
- }
69
- if (outcome.removed.length > 0) lines.push(row("Removed", `${outcome.removed.join(", ")} (not enabled on this branch)`));
70
- if (fresh.size > 0) lines.push(" * new credential value");
71
- const credential = outcome.credential;
72
- if (credential?.issued && credential.revoked.length > 0) lines.push(`Revoked the credential it replaced (${credential.revoked.join(", ")}).`);
73
- 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.`);
74
- return `${lines.join("\n")}\n`;
75
- };
76
16
  //#endregion
77
- export { displayPath, formatLinkSummary, formatPulledEnv };
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-ur81YnUp.js";
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 };
@@ -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-C3uXp-nh.js";
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 = [
@@ -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 = params.gateway ? await fetchGatewayModelIds(params.gateway.baseUrl, params.gateway.token) ?? void 0 : void 0;
139
+ const modelIds = modelsRead ? await modelsRead ?? void 0 : void 0;
139
140
  const notice = buildAiGatewayNotice({
140
141
  subscriptionType,
141
142
  modelIds,
@@ -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-C3uXp-nh.js";
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.9",
3
+ "version": "8.0.11",
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/config": "1.8.3",
68
- "@neon/sdk": "7.0.0",
69
- "@neon/config-runtime": "1.7.5"
67
+ "@neon/config": "1.8.4",
68
+ "@neon/sdk": "7.0.1",
69
+ "@neon/config-runtime": "1.7.6"
70
70
  },
71
71
  "optionalDependencies": {
72
72
  "@napi-rs/keyring": "1.3.0",
@@ -100,7 +100,7 @@
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",
103
+ "@neon-internals/env-core": "0.0.27",
104
104
  "@neon/e2e-harness": "0.0.0",
105
105
  "@neon/functions": "0.11.0"
106
106
  },