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.
- package/README.md +126 -41
- package/commands/auth.js +9 -0
- package/commands/bootstrap.js +603 -0
- package/commands/branches.js +6 -4
- package/commands/bucket.js +118 -5
- package/commands/checkout.js +25 -8
- package/commands/config.js +98 -10
- package/commands/deploy.js +2 -1
- package/commands/dev.js +11 -57
- package/commands/env.js +9 -2
- package/commands/functions.js +53 -5
- package/commands/index.js +2 -0
- package/commands/link.js +441 -108
- package/commands/projects.js +2 -2
- package/commands/set_context.js +5 -1
- package/config_format.js +8 -2
- package/context.js +33 -5
- package/dev/env.js +38 -0
- package/dev/functions.js +2 -4
- package/dev/runtime.js +2 -2
- package/index.js +1 -0
- package/package.json +5 -5
- package/storage_api.js +34 -0
- package/utils/bootstrap.js +243 -0
- package/utils/esbuild.js +11 -2
package/commands/functions.js
CHANGED
|
@@ -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
|
|
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
|
|
214
|
+
let fn;
|
|
177
215
|
try {
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
];
|