neonctl 2.24.2 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,23 @@ const DEPLOYMENT_FIELDS = [
21
21
  'memory_mib',
22
22
  'created_at',
23
23
  ];
24
+ // Deploy emits the resolved deployment plus the function's invocation_url, so a
25
+ // successful `functions deploy` tells the user exactly where to call the function.
26
+ const DEPLOY_RESULT_FIELDS = [
27
+ 'id',
28
+ 'status',
29
+ 'invocation_url',
30
+ 'runtime',
31
+ 'memory_mib',
32
+ 'created_at',
33
+ ];
34
+ // In table mode a failed build's reason gets its own "deployment error"
35
+ // section after the deployment table; json/yaml carry the raw `error` field.
36
+ const writeDeploymentErrorSection = (out, dep) => {
37
+ if (dep.status === 'failed' && dep.error) {
38
+ out.write({ reason: dep.error }, { fields: ['reason'], title: 'deployment error' });
39
+ }
40
+ };
24
41
  const SLUG_PATTERN = /^[a-z0-9]{1,20}$/;
25
42
  const SLUG_HELP = 'Use 1-20 lowercase letters and digits (no hyphens or other characters).';
26
43
  // Overridable so tests can poll fast; defaults to 2s in real use.
@@ -77,10 +94,19 @@ export const builder = (argv) => argv
77
94
  },
78
95
  }), (args) => deploy(args))
79
96
  .command('list', 'List functions on the branch', (yargs) => yargs, (args) => list(args))
80
- .command('get <slug>', "Show a function's details", (yargs) => yargs.positional('slug', {
97
+ .command('get <slug>', "Show a function's details", (yargs) => yargs
98
+ .positional('slug', {
81
99
  describe: 'Function slug',
82
100
  type: 'string',
83
101
  demandOption: true,
102
+ })
103
+ .options({
104
+ 'list-env-variables': {
105
+ describe: 'List the environment variable names of the active deployment',
106
+ type: 'boolean',
107
+ alias: 'E',
108
+ default: false,
109
+ },
84
110
  }), (args) => get(args))
85
111
  .command('delete <slug>', 'Delete a function on the branch', (yargs) => yargs.positional('slug', {
86
112
  describe: 'Function slug',
@@ -104,6 +130,15 @@ const parseEnv = (entries) => {
104
130
  return JSON.stringify(map);
105
131
  };
106
132
  const statusHint = (slug, projectId, branchId) => `Check status with: neonctl functions get ${slug} --project-id ${projectId} --branch ${branchId}`;
133
+ // Emit the resolved deployment together with the function's invocation_url, so the
134
+ // deploy output shows where the function is reachable (not just the deployment id).
135
+ const emitDeployResult = (props, deployment, fn) => {
136
+ const out = writer(props).write({ ...deployment, invocation_url: fn?.invocation_url }, { fields: DEPLOY_RESULT_FIELDS });
137
+ if (props.output !== 'json' && props.output !== 'yaml') {
138
+ writeDeploymentErrorSection(out, deployment);
139
+ }
140
+ out.end();
141
+ };
107
142
  // A poll error worth retrying: a network error (no HTTP response), a 5xx, or a
108
143
  // 404 from eventual consistency. Anything else (e.g. 401/403) is surfaced.
109
144
  const isTransient = (err) => isAxiosError(err) &&
@@ -165,6 +200,9 @@ const deploy = async (props) => {
165
200
  // any version if there was none). --no-wait stops there; --wait stops at a
166
201
  // terminal status. Bounded by POLL_TIMEOUT_MS so it never hangs.
167
202
  let resolved;
203
+ // The function carries the invocation_url; keep the whole record (not just its
204
+ // active_deployment) so we can surface that URL on success.
205
+ let resolvedFn;
168
206
  const deadline = Date.now() + POLL_TIMEOUT_MS;
169
207
  try {
170
208
  while (!interrupted && Date.now() < deadline) {
@@ -173,18 +211,20 @@ const deploy = async (props) => {
173
211
  break;
174
212
  // The deploy already succeeded server-side; tolerate transient poll
175
213
  // failures and retry on the next interval. Surface anything else.
176
- let dep;
214
+ let fn;
177
215
  try {
178
- dep = (await getFunction(props.apiClient, props.projectId, branchId, props.slug)).active_deployment;
216
+ fn = await getFunction(props.apiClient, props.projectId, branchId, props.slug);
179
217
  }
180
218
  catch (err) {
181
219
  if (isTransient(err))
182
220
  continue;
183
221
  throw err;
184
222
  }
223
+ const dep = fn.active_deployment;
185
224
  const isNew = dep !== undefined && (before === undefined || dep.id > before);
186
225
  if (isNew && dep) {
187
226
  resolved = dep;
227
+ resolvedFn = fn;
188
228
  if (!props.wait)
189
229
  break;
190
230
  if (dep.status === 'completed' || dep.status === 'failed')
@@ -199,14 +239,14 @@ const deploy = async (props) => {
199
239
  if (interrupted) {
200
240
  log.info(statusHint(props.slug, props.projectId, branchId));
201
241
  if (resolved)
202
- writer(props).end(resolved, { fields: DEPLOYMENT_FIELDS });
242
+ emitDeployResult(props, resolved, resolvedFn);
203
243
  return;
204
244
  }
205
245
  if (resolved === undefined) {
206
246
  log.info(statusHint(props.slug, props.projectId, branchId));
207
247
  throw new Error(`Timed out waiting for the deployment of ${props.slug} to start. It may still be in progress.`);
208
248
  }
209
- writer(props).end(resolved, { fields: DEPLOYMENT_FIELDS });
249
+ emitDeployResult(props, resolved, resolvedFn);
210
250
  if (!props.wait) {
211
251
  log.info(statusHint(props.slug, props.projectId, branchId));
212
252
  return;
@@ -238,6 +278,14 @@ const get = async (props) => {
238
278
  fields: DEPLOYMENT_FIELDS,
239
279
  title: 'active deployment',
240
280
  });
281
+ writeDeploymentErrorSection(out, fn.active_deployment);
282
+ }
283
+ if (props.listEnvVariables) {
284
+ out.write((fn.active_deployment?.environment ?? []).map((name) => ({ name })), {
285
+ fields: ['name'],
286
+ title: 'environment',
287
+ emptyMessage: 'No environment variables on the active deployment.',
288
+ });
241
289
  }
242
290
  out.end();
243
291
  };
package/commands/index.js CHANGED
@@ -22,6 +22,7 @@ import * as config from './config.js';
22
22
  import * as deploy from './deploy.js';
23
23
  import * as env from './env.js';
24
24
  import * as bucket from './bucket.js';
25
+ import * as bootstrap from './bootstrap.js';
25
26
  export default [
26
27
  auth,
27
28
  users,
@@ -47,4 +48,5 @@ export default [
47
48
  deploy,
48
49
  env,
49
50
  bucket,
51
+ bootstrap,
50
52
  ];