@xano/cli 1.0.4-beta.1 → 1.0.4-beta.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/README.md +32 -1
- package/dist/commands/auth/index.d.ts +38 -1
- package/dist/commands/auth/index.js +135 -40
- package/dist/commands/function/run/index.d.ts +40 -0
- package/dist/commands/function/run/index.js +244 -0
- package/dist/commands/sandbox/pull/index.js +12 -2
- package/dist/commands/sandbox/push/index.d.ts +1 -1
- package/dist/commands/sandbox/push/index.js +17 -13
- package/dist/commands/static_host/build/push/index.d.ts +1 -1
- package/dist/commands/static_host/build/push/index.js +20 -17
- package/dist/commands/tenant/unit_test/run/index.d.ts +1 -0
- package/dist/commands/tenant/unit_test/run/index.js +7 -0
- package/dist/commands/tenant/unit_test/run_all/index.js +7 -3
- package/dist/commands/unit_test/run/index.d.ts +1 -0
- package/dist/commands/unit_test/run/index.js +8 -0
- package/dist/commands/unit_test/run_all/index.js +7 -3
- package/dist/commands/workspace/pull/index.js +13 -2
- package/dist/commands/workspace/push/index.d.ts +1 -1
- package/dist/commands/workspace/push/index.js +15 -5
- package/dist/utils/function-input.d.ts +77 -0
- package/dist/utils/function-input.js +148 -0
- package/dist/utils/knowledge-sync.d.ts +113 -0
- package/dist/utils/knowledge-sync.js +404 -0
- package/dist/utils/multidoc-push.d.ts +22 -0
- package/dist/utils/multidoc-push.js +242 -91
- package/dist/utils/static-host-files.d.ts +21 -0
- package/dist/utils/static-host-files.js +41 -0
- package/oclif.manifest.json +2106 -1910
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ containers, or locked-down networks where the browser can't reach the CLI's
|
|
|
62
62
|
loopback address, use `--no-browser`: the CLI prints a login URL, you open it
|
|
63
63
|
in any browser, and paste back the code it displays. No local server required.
|
|
64
64
|
|
|
65
|
-
Each picker can be pre-answered with a flag: `-i/--instance` (instance name),
|
|
65
|
+
Each picker can be pre-answered with a flag: `-i/--instance` (instance name, or numeric instance ID),
|
|
66
66
|
`-w/--workspace` (workspace ID or name), `-b/--branch` (branch label), and
|
|
67
67
|
`-p/--profile` (profile name to save). An empty value (`""`) takes the
|
|
68
68
|
picker's default answer: `-w ""` skips workspace selection, `-b ""` skips and
|
|
@@ -255,8 +255,35 @@ xano function edit <function_id> -f new.xs # Update from file
|
|
|
255
255
|
xano function edit <function_id> -f new.xs --edit # Open in $EDITOR before updating
|
|
256
256
|
cat function.xs | xano function edit <function_id> --stdin # Update from stdin
|
|
257
257
|
xano function edit <function_id> --no-publish # Edit without publishing
|
|
258
|
+
|
|
259
|
+
# Run (execute) a function by name
|
|
260
|
+
xano function run <name> # Prompts for declared inputs (on a TTY)
|
|
261
|
+
xano function run <name> --data email=jo@x.com --data age:=30 --data active:=true
|
|
262
|
+
xano function run <name> --json @payload.json --data env=staging # base payload + override
|
|
263
|
+
echo '{"email":"jo@x.com"}' | xano function run <name> --stdin -o json | jq .result
|
|
264
|
+
xano function run <name> --branch dev --logs # run on a branch, show execution logs
|
|
258
265
|
```
|
|
259
266
|
|
|
267
|
+
Input flexibility for `function run` (assembled into one JSON `input` object):
|
|
268
|
+
|
|
269
|
+
| Form | Meaning |
|
|
270
|
+
| --- | --- |
|
|
271
|
+
| `--data key=value` | string field |
|
|
272
|
+
| `--data key:=<json>` | raw JSON field (`age:=30`, `active:=true`, `tags:='["a","b"]'`) |
|
|
273
|
+
| `--data key@file` | field value read from a file |
|
|
274
|
+
| `--json '<inline>'` / `--json @file.json` / `--json -` | a base JSON object (stdin with `-`) |
|
|
275
|
+
| `--stdin` | read the JSON object from stdin (same as `--json -`) |
|
|
276
|
+
|
|
277
|
+
Merge order is JSON base first, then `--data` overrides. Missing required inputs are
|
|
278
|
+
prompted for on an interactive terminal; in a non-TTY (CI) context the command fails
|
|
279
|
+
listing them. Output defaults to the raw `result` (`-o json`); the exit code is non-zero
|
|
280
|
+
when the function returns an error status.
|
|
281
|
+
|
|
282
|
+
> **Permissions:** running a function requires **Function** (read) plus the **run/debug**
|
|
283
|
+
> action on your workspace role. If your access token's role lacks run/debug, the call is
|
|
284
|
+
> denied with an access error — grant run/debug to the role (or use a token whose role has
|
|
285
|
+
> it). `function list`/`get`/`create`/`edit` only need the Function permission.
|
|
286
|
+
|
|
260
287
|
### Releases
|
|
261
288
|
|
|
262
289
|
All release commands use **release names** (e.g., `v1.0`), not IDs.
|
|
@@ -326,6 +353,7 @@ xano unit_test list --branch dev --obj-type function
|
|
|
326
353
|
|
|
327
354
|
# Run a single unit test
|
|
328
355
|
xano unit_test run <unit_test_id>
|
|
356
|
+
xano unit_test run <unit_test_id> --branch dev # run the test from a specific branch
|
|
329
357
|
|
|
330
358
|
# Run all unit tests
|
|
331
359
|
xano unit_test run_all
|
|
@@ -577,11 +605,14 @@ xano static_host build pull default --env prod -d ./prod-release
|
|
|
577
605
|
|
|
578
606
|
# Push a build (name optional — auto-generated from the timestamp if omitted).
|
|
579
607
|
# Accepts a directory (-d) or a zip file (-f). Defaults to the current directory.
|
|
608
|
+
# When pushing a directory, files matched by its .gitignore are skipped by default
|
|
609
|
+
# (the .git/ folder is always excluded); use --no-gitignore to push everything.
|
|
580
610
|
# For package.json builds, the CLI waits for the build to finish (--no-wait to skip).
|
|
581
611
|
xano static_host build push default -d ./dist -n "v1.0.0"
|
|
582
612
|
xano static_host build push default # current dir, auto-name
|
|
583
613
|
xano static_host build push default -f ./build.zip -n "v1.0.0" # from zip file
|
|
584
614
|
xano static_host build push default -n "release" --description "Production build"
|
|
615
|
+
xano static_host build push default -d ./static --no-gitignore # push gitignored files too
|
|
585
616
|
|
|
586
617
|
# Delete a build (prompts for confirmation; --force to skip)
|
|
587
618
|
xano static_host build delete default --build_id 52
|
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
export interface Instance {
|
|
3
|
+
display: string;
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
origin: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthResult {
|
|
9
|
+
branch: null | string;
|
|
10
|
+
credentialsPath: string;
|
|
11
|
+
instance: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
origin: string;
|
|
15
|
+
};
|
|
16
|
+
profile: string;
|
|
17
|
+
user: {
|
|
18
|
+
email: string;
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
};
|
|
22
|
+
workspace: null | {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Match a user-supplied --instance value against the instance list:
|
|
29
|
+
* numeric values match by ID, URL/hostname values match by the instance
|
|
30
|
+
* origin's hostname, anything else matches by name. Exported for tests.
|
|
31
|
+
*/
|
|
32
|
+
export declare function matchInstance(instances: Instance[], query: string): Instance | undefined;
|
|
2
33
|
export default class Auth extends Command {
|
|
3
34
|
static description: string;
|
|
35
|
+
static enableJsonFlag: boolean;
|
|
4
36
|
static examples: string[];
|
|
5
37
|
static flags: {
|
|
6
38
|
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
39
|
+
code: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
40
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
41
|
insecure: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
42
|
instance: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -12,7 +45,9 @@ export default class Auth extends Command {
|
|
|
12
45
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
46
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
47
|
};
|
|
15
|
-
run(): Promise<
|
|
48
|
+
run(): Promise<AuthResult>;
|
|
49
|
+
protected toErrorJson(err: unknown): unknown;
|
|
50
|
+
private acquireToken;
|
|
16
51
|
private getHeaders;
|
|
17
52
|
private fetchBranches;
|
|
18
53
|
private fetchInstances;
|
|
@@ -22,7 +57,9 @@ export default class Auth extends Command {
|
|
|
22
57
|
private readTokenFromStdin;
|
|
23
58
|
private resolveBranch;
|
|
24
59
|
private resolveInstance;
|
|
60
|
+
private resolveProfileName;
|
|
25
61
|
private resolveWorkspace;
|
|
62
|
+
private resolveWorkspaceAndBranch;
|
|
26
63
|
private saveProfile;
|
|
27
64
|
private selectBranch;
|
|
28
65
|
private selectInstance;
|
|
@@ -6,9 +6,37 @@ import * as http from 'node:http';
|
|
|
6
6
|
import { dirname } from 'node:path';
|
|
7
7
|
import open from 'open';
|
|
8
8
|
import { buildUserAgent, resolveCredentialsPath } from '../../base-command.js';
|
|
9
|
+
function originHostname(value) {
|
|
10
|
+
try {
|
|
11
|
+
return new URL(value.includes('://') ? value : `https://${value}`).hostname.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Match a user-supplied --instance value against the instance list:
|
|
19
|
+
* numeric values match by ID, URL/hostname values match by the instance
|
|
20
|
+
* origin's hostname, anything else matches by name. Exported for tests.
|
|
21
|
+
*/
|
|
22
|
+
export function matchInstance(instances, query) {
|
|
23
|
+
const q = query.trim();
|
|
24
|
+
if (/^\d+$/.test(q)) {
|
|
25
|
+
return instances.find((inst) => inst.id === q);
|
|
26
|
+
}
|
|
27
|
+
if (q.includes('://') || q.includes('.')) {
|
|
28
|
+
const queryHost = originHostname(q);
|
|
29
|
+
const match = queryHost ? instances.find((inst) => originHostname(inst.origin) === queryHost) : undefined;
|
|
30
|
+
if (match) {
|
|
31
|
+
return match;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return instances.find((inst) => inst.name === q);
|
|
35
|
+
}
|
|
9
36
|
const AUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
10
37
|
export default class Auth extends Command {
|
|
11
38
|
static description = 'Authenticate with Xano via browser login';
|
|
39
|
+
static enableJsonFlag = true;
|
|
12
40
|
static examples = [
|
|
13
41
|
`$ xano auth
|
|
14
42
|
Opening browser for Xano login...
|
|
@@ -23,10 +51,12 @@ Opening browser for Xano login at https://custom.xano.com...`,
|
|
|
23
51
|
To authenticate, open the following URL in any browser:
|
|
24
52
|
https://app.xano.com/login?dest=cli&display=code
|
|
25
53
|
? Paste the code shown in your browser: ****`,
|
|
26
|
-
`$ xano auth --
|
|
27
|
-
(non-interactive:
|
|
54
|
+
`$ xano auth --code "$CODE" --instance https://my-instance.xano.io --workspace 5
|
|
55
|
+
(fully non-interactive: no browser, no prompts; missing --branch/--profile fall back to defaults)`,
|
|
28
56
|
`$ echo "$CODE" | xano auth --no-browser --instance my-instance --workspace 5 --branch dev --profile staging
|
|
29
57
|
(fully scripted: the code is read from piped stdin, no prompt at all)`,
|
|
58
|
+
`$ xano auth --code "$CODE" --instance 42 --workspace 5 --json
|
|
59
|
+
(machine-readable: prints the created profile as JSON)`,
|
|
30
60
|
];
|
|
31
61
|
static flags = {
|
|
32
62
|
branch: Flags.string({
|
|
@@ -34,6 +64,11 @@ To authenticate, open the following URL in any browser:
|
|
|
34
64
|
description: 'Pre-select a branch by label (skips the branch picker); pass "" to skip and use the live branch',
|
|
35
65
|
required: false,
|
|
36
66
|
}),
|
|
67
|
+
code: Flags.string({
|
|
68
|
+
description: 'Login code copied from the browser (implies --no-browser and runs fully non-interactively). Get the code at <origin>/login?dest=cli&display=code',
|
|
69
|
+
env: 'XANO_AUTH_CODE',
|
|
70
|
+
required: false,
|
|
71
|
+
}),
|
|
37
72
|
config: Flags.string({
|
|
38
73
|
char: 'c',
|
|
39
74
|
description: 'Path to credentials file (default: ~/.xano/credentials.yaml)',
|
|
@@ -47,7 +82,7 @@ To authenticate, open the following URL in any browser:
|
|
|
47
82
|
}),
|
|
48
83
|
instance: Flags.string({
|
|
49
84
|
char: 'i',
|
|
50
|
-
description: 'Pre-select an instance by name (skips the instance picker)',
|
|
85
|
+
description: 'Pre-select an instance by name, numeric ID, or instance URL/hostname (skips the instance picker)',
|
|
51
86
|
required: false,
|
|
52
87
|
}),
|
|
53
88
|
'no-browser': Flags.boolean({
|
|
@@ -76,12 +111,13 @@ To authenticate, open the following URL in any browser:
|
|
|
76
111
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
77
112
|
this.warn('TLS certificate verification is disabled (insecure mode)');
|
|
78
113
|
}
|
|
114
|
+
// A supplied code (flag or env), piped stdin, or --json output means no
|
|
115
|
+
// prompt can ever open: every unanswered picker falls back to its default.
|
|
116
|
+
const nonInteractive = flags.code !== undefined || (flags['no-browser'] && !process.stdin.isTTY) || this.jsonEnabled();
|
|
79
117
|
try {
|
|
80
|
-
// Step 1: Get token via browser auth
|
|
118
|
+
// Step 1: Get token via supplied code or browser auth
|
|
81
119
|
this.log('Starting authentication flow...');
|
|
82
|
-
const token = flags['no-browser']
|
|
83
|
-
? await this.promptForToken(flags.origin)
|
|
84
|
-
: await this.startAuthServer(flags.origin);
|
|
120
|
+
const token = await this.acquireToken(flags.code, flags['no-browser'], flags.origin);
|
|
85
121
|
// Step 2: Validate token and get user info
|
|
86
122
|
this.log('');
|
|
87
123
|
this.log('Validating authentication...');
|
|
@@ -110,36 +146,15 @@ To authenticate, open the following URL in any browser:
|
|
|
110
146
|
if (instances.length === 0) {
|
|
111
147
|
this.error('No instances found. Please check your account.');
|
|
112
148
|
}
|
|
113
|
-
instance = await this.resolveInstance(instances, flags.instance);
|
|
114
|
-
}
|
|
115
|
-
// Step 4: Workspace selection
|
|
116
|
-
let workspace;
|
|
117
|
-
let branch;
|
|
118
|
-
this.log('');
|
|
119
|
-
this.log('Fetching available workspaces...');
|
|
120
|
-
const workspaces = await this.fetchWorkspaces(token, instance.origin);
|
|
121
|
-
if (workspaces.length > 0) {
|
|
122
|
-
workspace = await this.resolveWorkspace(workspaces, flags.workspace);
|
|
123
|
-
if (workspace) {
|
|
124
|
-
// Step 5: Branch selection
|
|
125
|
-
this.log('');
|
|
126
|
-
this.log('Fetching available branches...');
|
|
127
|
-
const branches = await this.fetchBranches(token, instance.origin, workspace.id);
|
|
128
|
-
branch = await this.resolveBranch(branches, flags.branch);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else if (flags.workspace) {
|
|
132
|
-
this.error(`Workspace '${flags.workspace}' not found: no workspaces are available on this instance.`);
|
|
133
|
-
}
|
|
134
|
-
if (flags.branch && !workspace) {
|
|
135
|
-
this.warn('Ignoring --branch: no workspace selected.');
|
|
149
|
+
instance = await this.resolveInstance(instances, flags.instance, nonInteractive);
|
|
136
150
|
}
|
|
151
|
+
// Steps 4 + 5: Workspace and branch selection
|
|
152
|
+
const { branch, workspace } = await this.resolveWorkspaceAndBranch(token, instance.origin, flags, nonInteractive);
|
|
137
153
|
// Step 6: Profile name
|
|
138
154
|
this.log('');
|
|
139
|
-
|
|
140
|
-
const profileName = flags.profile === undefined ? await this.promptProfileName() : flags.profile.trim() || 'default';
|
|
155
|
+
const profileName = await this.resolveProfileName(flags.profile, nonInteractive);
|
|
141
156
|
// Step 7: Save profile
|
|
142
|
-
await this.saveProfile({
|
|
157
|
+
const credentialsPath = await this.saveProfile({
|
|
143
158
|
access_token: token,
|
|
144
159
|
account_origin: flags.origin,
|
|
145
160
|
branch,
|
|
@@ -150,6 +165,17 @@ To authenticate, open the following URL in any browser:
|
|
|
150
165
|
}, flags.config);
|
|
151
166
|
this.log('');
|
|
152
167
|
this.log(`Profile '${profileName}' created successfully!`);
|
|
168
|
+
const result = {
|
|
169
|
+
branch: branch ?? null,
|
|
170
|
+
credentialsPath,
|
|
171
|
+
instance: { id: instance.id, name: instance.name, origin: instance.origin },
|
|
172
|
+
profile: profileName,
|
|
173
|
+
user: { email: user.email, id: user.id, name: user.name },
|
|
174
|
+
workspace: workspace ? { id: workspace.id, name: workspace.name } : null,
|
|
175
|
+
};
|
|
176
|
+
if (this.jsonEnabled()) {
|
|
177
|
+
this.logJson(result);
|
|
178
|
+
}
|
|
153
179
|
// Ensure clean exit (the open() call can keep the event loop alive)
|
|
154
180
|
process.exit(0);
|
|
155
181
|
}
|
|
@@ -159,11 +185,33 @@ To authenticate, open the following URL in any browser:
|
|
|
159
185
|
// @inquirer/core, so the thrown class won't match an imported one.
|
|
160
186
|
if (error?.name === 'ExitPromptError') {
|
|
161
187
|
this.log('Authentication cancelled.');
|
|
162
|
-
|
|
188
|
+
this.exit(0);
|
|
163
189
|
}
|
|
164
190
|
throw error;
|
|
165
191
|
}
|
|
166
192
|
}
|
|
193
|
+
// oclif's default toErrorJson serializes Error objects to {} (message is a
|
|
194
|
+
// non-enumerable property), leaving --json consumers with no error detail.
|
|
195
|
+
toErrorJson(err) {
|
|
196
|
+
const error = err;
|
|
197
|
+
return {
|
|
198
|
+
error: {
|
|
199
|
+
...(error.code ? { code: error.code } : {}),
|
|
200
|
+
message: error.message ?? String(err),
|
|
201
|
+
...(error.suggestions?.length ? { suggestions: error.suggestions } : {}),
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async acquireToken(code, noBrowser, origin) {
|
|
206
|
+
if (code !== undefined) {
|
|
207
|
+
const token = code.trim();
|
|
208
|
+
if (token === '') {
|
|
209
|
+
this.error(`No code provided. Copy it from ${origin}/login?dest=cli&display=code and pass it via --code or XANO_AUTH_CODE.`);
|
|
210
|
+
}
|
|
211
|
+
return token;
|
|
212
|
+
}
|
|
213
|
+
return noBrowser ? this.promptForToken(origin) : this.startAuthServer(origin);
|
|
214
|
+
}
|
|
167
215
|
getHeaders(accessToken) {
|
|
168
216
|
return {
|
|
169
217
|
'User-Agent': buildUserAgent(this.config.version),
|
|
@@ -211,7 +259,7 @@ To authenticate, open the following URL in any browser:
|
|
|
211
259
|
if (Array.isArray(data)) {
|
|
212
260
|
return data.map((inst) => ({
|
|
213
261
|
display: inst.display,
|
|
214
|
-
id: inst.id
|
|
262
|
+
id: String(inst.id ?? inst.name),
|
|
215
263
|
name: inst.name,
|
|
216
264
|
origin: new URL(inst.meta_api).origin,
|
|
217
265
|
}));
|
|
@@ -308,7 +356,7 @@ To authenticate, open the following URL in any browser:
|
|
|
308
356
|
process.stdin.on('error', () => resolve(data.trim()));
|
|
309
357
|
});
|
|
310
358
|
}
|
|
311
|
-
async resolveBranch(branches, flagValue) {
|
|
359
|
+
async resolveBranch(branches, flagValue, nonInteractive) {
|
|
312
360
|
if (flagValue !== undefined) {
|
|
313
361
|
// An empty value means "skip and use live branch" (same as the picker's skip option)
|
|
314
362
|
if (flagValue.trim() === '') {
|
|
@@ -322,20 +370,39 @@ To authenticate, open the following URL in any browser:
|
|
|
322
370
|
this.log(`Using branch: ${match.label}`);
|
|
323
371
|
return match.id;
|
|
324
372
|
}
|
|
373
|
+
if (nonInteractive) {
|
|
374
|
+
this.log('Using live branch (non-interactive; pass --branch to select one)');
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
325
377
|
return branches.length > 1 ? this.selectBranch(branches) : undefined;
|
|
326
378
|
}
|
|
327
|
-
async resolveInstance(instances, flagValue) {
|
|
379
|
+
async resolveInstance(instances, flagValue, nonInteractive) {
|
|
328
380
|
if (flagValue) {
|
|
329
|
-
const match = instances
|
|
381
|
+
const match = matchInstance(instances, flagValue);
|
|
330
382
|
if (!match) {
|
|
331
|
-
this.error(`Instance '${flagValue}' not found. Available instances: ${instances.map((inst) => inst.name).join(', ')}`);
|
|
383
|
+
this.error(`Instance '${flagValue}' not found (match by name, numeric ID, or instance URL). Available instances: ${instances.map((inst) => `${inst.name} (${inst.id})`).join(', ')}`);
|
|
332
384
|
}
|
|
333
385
|
this.log(`Using instance: ${match.name} (${match.display})`);
|
|
334
386
|
return match;
|
|
335
387
|
}
|
|
388
|
+
if (nonInteractive) {
|
|
389
|
+
if (instances.length === 1) {
|
|
390
|
+
this.log(`Using instance: ${instances[0].name} (${instances[0].display})`);
|
|
391
|
+
return instances[0];
|
|
392
|
+
}
|
|
393
|
+
this.error(`Multiple instances available; pass --instance to select one. Available instances: ${instances.map((inst) => `${inst.name} (${inst.id})`).join(', ')}`);
|
|
394
|
+
}
|
|
336
395
|
return this.selectInstance(instances);
|
|
337
396
|
}
|
|
338
|
-
async
|
|
397
|
+
async resolveProfileName(flagValue, nonInteractive) {
|
|
398
|
+
// An empty --profile value means "use the default name" (same as accepting the prompt's default);
|
|
399
|
+
// non-interactive runs with no --profile fall back to the default name too.
|
|
400
|
+
if (flagValue !== undefined) {
|
|
401
|
+
return flagValue.trim() || 'default';
|
|
402
|
+
}
|
|
403
|
+
return nonInteractive ? 'default' : this.promptProfileName();
|
|
404
|
+
}
|
|
405
|
+
async resolveWorkspace(workspaces, flagValue, nonInteractive) {
|
|
339
406
|
if (flagValue !== undefined) {
|
|
340
407
|
// An empty value means "skip workspace" (same as the picker's skip option)
|
|
341
408
|
if (flagValue.trim() === '') {
|
|
@@ -349,8 +416,35 @@ To authenticate, open the following URL in any browser:
|
|
|
349
416
|
this.log(`Using workspace: ${match.name} (${match.id})`);
|
|
350
417
|
return match;
|
|
351
418
|
}
|
|
419
|
+
if (nonInteractive) {
|
|
420
|
+
this.log('Skipping workspace selection (non-interactive; pass --workspace to select one)');
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
352
423
|
return this.selectWorkspace(workspaces);
|
|
353
424
|
}
|
|
425
|
+
async resolveWorkspaceAndBranch(token, instanceOrigin, flags, nonInteractive) {
|
|
426
|
+
let workspace;
|
|
427
|
+
let branch;
|
|
428
|
+
this.log('');
|
|
429
|
+
this.log('Fetching available workspaces...');
|
|
430
|
+
const workspaces = await this.fetchWorkspaces(token, instanceOrigin);
|
|
431
|
+
if (workspaces.length > 0) {
|
|
432
|
+
workspace = await this.resolveWorkspace(workspaces, flags.workspace, nonInteractive);
|
|
433
|
+
if (workspace) {
|
|
434
|
+
this.log('');
|
|
435
|
+
this.log('Fetching available branches...');
|
|
436
|
+
const branches = await this.fetchBranches(token, instanceOrigin, workspace.id);
|
|
437
|
+
branch = await this.resolveBranch(branches, flags.branch, nonInteractive);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
else if (flags.workspace) {
|
|
441
|
+
this.error(`Workspace '${flags.workspace}' not found: no workspaces are available on this instance.`);
|
|
442
|
+
}
|
|
443
|
+
if (flags.branch && !workspace) {
|
|
444
|
+
this.warn('Ignoring --branch: no workspace selected.');
|
|
445
|
+
}
|
|
446
|
+
return { branch, workspace };
|
|
447
|
+
}
|
|
354
448
|
async saveProfile(profile, configPath) {
|
|
355
449
|
const credentialsPath = resolveCredentialsPath(configPath);
|
|
356
450
|
const credDir = dirname(credentialsPath);
|
|
@@ -390,6 +484,7 @@ To authenticate, open the following URL in any browser:
|
|
|
390
484
|
noRefs: true,
|
|
391
485
|
});
|
|
392
486
|
fs.writeFileSync(credentialsPath, yamlContent, 'utf8');
|
|
487
|
+
return credentialsPath;
|
|
393
488
|
}
|
|
394
489
|
async selectBranch(branches) {
|
|
395
490
|
const { selectedBranch } = await inquirer.prompt([
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class FunctionRun extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
data: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
logs: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
'no-input-check': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
};
|
|
22
|
+
run(): Promise<void>;
|
|
23
|
+
private emitResult;
|
|
24
|
+
/**
|
|
25
|
+
* Fetch a single function record by name via the list endpoint's search,
|
|
26
|
+
* returning the exact-name match (so `calc` doesn't accidentally match
|
|
27
|
+
* `calcScore`). Best-effort: returns undefined and warns on any failure so a
|
|
28
|
+
* transient introspection problem never blocks an otherwise-valid run.
|
|
29
|
+
*/
|
|
30
|
+
private fetchFunctionByName;
|
|
31
|
+
/**
|
|
32
|
+
* Prompt for each missing required input on an interactive TTY. Values are
|
|
33
|
+
* read as raw JSON when they parse as such (so numbers/bools/objects work),
|
|
34
|
+
* otherwise treated as a string — mirroring the `key:=`/`key=` distinction.
|
|
35
|
+
* Non-TTY: fail fast with the list of missing params (never hang on a prompt).
|
|
36
|
+
*/
|
|
37
|
+
private promptForMissing;
|
|
38
|
+
private readStdinSync;
|
|
39
|
+
private resolveWorkspaceId;
|
|
40
|
+
}
|