@xano/cli 1.0.6-beta.2 → 1.0.6-beta.20
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 +217 -6
- package/dist/base-command.d.ts +5 -0
- package/dist/base-command.js +61 -4
- package/dist/commands/branch/list/index.js +5 -1
- package/dist/commands/ephemeral/create/index.js +3 -3
- package/dist/commands/ephemeral/list/index.js +2 -1
- package/dist/commands/ephemeral/pull/index.d.ts +0 -6
- package/dist/commands/ephemeral/pull/index.js +10 -95
- package/dist/commands/ephemeral/push/index.d.ts +10 -0
- package/dist/commands/ephemeral/push/index.js +77 -0
- package/dist/commands/ephemeral/static_host/build/list/index.d.ts +2 -3
- package/dist/commands/ephemeral/static_host/build/list/index.js +19 -40
- package/dist/commands/ephemeral/static_host/list/index.d.ts +2 -3
- package/dist/commands/ephemeral/static_host/list/index.js +20 -40
- package/dist/commands/flatten/index.d.ts +34 -0
- package/dist/commands/flatten/index.js +140 -0
- package/dist/commands/function/list/index.d.ts +4 -4
- package/dist/commands/function/list/index.js +30 -38
- package/dist/commands/function/run/index.d.ts +1 -0
- package/dist/commands/function/run/index.js +19 -5
- package/dist/commands/knowledge/list/index.js +2 -1
- package/dist/commands/platform/list/index.js +13 -14
- package/dist/commands/release/list/index.js +13 -14
- package/dist/commands/release/pull/index.d.ts +0 -6
- package/dist/commands/release/pull/index.js +10 -95
- package/dist/commands/sandbox/env/list/index.js +4 -0
- package/dist/commands/sandbox/pull/index.d.ts +0 -1
- package/dist/commands/sandbox/pull/index.js +10 -77
- package/dist/commands/sandbox/unit_test/list/index.d.ts +2 -0
- package/dist/commands/sandbox/unit_test/list/index.js +9 -14
- package/dist/commands/sandbox/unit_test/run/index.js +17 -11
- package/dist/commands/sandbox/unit_test/run_all/index.d.ts +1 -0
- package/dist/commands/sandbox/unit_test/run_all/index.js +76 -38
- package/dist/commands/sandbox/workflow_test/list/index.d.ts +2 -0
- package/dist/commands/sandbox/workflow_test/list/index.js +9 -14
- package/dist/commands/sandbox/workflow_test/run/index.js +12 -9
- package/dist/commands/sandbox/workflow_test/run_all/index.d.ts +1 -0
- package/dist/commands/sandbox/workflow_test/run_all/index.js +74 -36
- package/dist/commands/static_host/build/list/index.d.ts +2 -3
- package/dist/commands/static_host/build/list/index.js +19 -40
- package/dist/commands/static_host/build/pull/index.js +12 -9
- package/dist/commands/static_host/list/index.d.ts +2 -3
- package/dist/commands/static_host/list/index.js +20 -40
- package/dist/commands/static_host/migrate/index.js +14 -10
- package/dist/commands/tenant/backup/list/index.js +13 -14
- package/dist/commands/tenant/cluster/list/index.js +9 -10
- package/dist/commands/tenant/create/index.d.ts +3 -0
- package/dist/commands/tenant/create/index.js +21 -0
- package/dist/commands/tenant/deploy_release/index.d.ts +21 -0
- package/dist/commands/tenant/deploy_release/index.js +94 -2
- package/dist/commands/tenant/edit/index.d.ts +2 -0
- package/dist/commands/tenant/edit/index.js +39 -4
- package/dist/commands/tenant/env/list/index.js +4 -0
- package/dist/commands/tenant/list/index.js +19 -20
- package/dist/commands/tenant/pull/index.d.ts +0 -6
- package/dist/commands/tenant/pull/index.js +10 -106
- package/dist/commands/tenant/snapshot/list/index.js +5 -1
- package/dist/commands/tenant/unit_test/list/index.d.ts +4 -2
- package/dist/commands/tenant/unit_test/list/index.js +9 -14
- package/dist/commands/tenant/unit_test/run/index.js +19 -11
- package/dist/commands/tenant/unit_test/run_all/index.d.ts +1 -0
- package/dist/commands/tenant/unit_test/run_all/index.js +80 -38
- package/dist/commands/tenant/workflow_test/list/index.d.ts +4 -2
- package/dist/commands/tenant/workflow_test/list/index.js +9 -14
- package/dist/commands/tenant/workflow_test/run/index.js +14 -9
- package/dist/commands/tenant/workflow_test/run_all/index.d.ts +1 -0
- package/dist/commands/tenant/workflow_test/run_all/index.js +78 -36
- package/dist/commands/tenant_deploy_request/bypass/index.d.ts +20 -0
- package/dist/commands/tenant_deploy_request/bypass/index.js +79 -0
- package/dist/commands/tenant_deploy_request/create/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/create/index.js +108 -0
- package/dist/commands/tenant_deploy_request/edit/index.d.ts +22 -0
- package/dist/commands/tenant_deploy_request/edit/index.js +103 -0
- package/dist/commands/tenant_deploy_request/get/index.d.ts +19 -0
- package/dist/commands/tenant_deploy_request/get/index.js +75 -0
- package/dist/commands/tenant_deploy_request/list/index.d.ts +20 -0
- package/dist/commands/tenant_deploy_request/list/index.js +122 -0
- package/dist/commands/tenant_deploy_request/revision/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/revision/index.js +83 -0
- package/dist/commands/tenant_deploy_request/set_status/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/set_status/index.js +112 -0
- package/dist/commands/unit_test/list/index.d.ts +3 -1
- package/dist/commands/unit_test/list/index.js +9 -14
- package/dist/commands/unit_test/run/index.js +19 -11
- package/dist/commands/unit_test/run_all/index.d.ts +1 -0
- package/dist/commands/unit_test/run_all/index.js +81 -41
- package/dist/commands/workflow_test/list/index.d.ts +3 -1
- package/dist/commands/workflow_test/list/index.js +10 -13
- package/dist/commands/workflow_test/run/index.js +15 -10
- package/dist/commands/workflow_test/run_all/index.d.ts +1 -0
- package/dist/commands/workflow_test/run_all/index.js +82 -39
- package/dist/commands/workspace/git/pull/index.js +48 -7
- package/dist/commands/workspace/list/index.js +7 -13
- package/dist/commands/workspace/pull/index.d.ts +0 -6
- package/dist/commands/workspace/pull/index.js +10 -115
- package/dist/utils/concurrency.d.ts +29 -0
- package/dist/utils/concurrency.js +54 -0
- package/dist/utils/document-parser.d.ts +90 -0
- package/dist/utils/document-parser.js +273 -1
- package/dist/utils/flatten.d.ts +36 -0
- package/dist/utils/flatten.js +68 -0
- package/dist/utils/knowledge-sync.d.ts +1 -0
- package/dist/utils/knowledge-sync.js +11 -5
- package/dist/utils/microservice-wait.d.ts +53 -0
- package/dist/utils/microservice-wait.js +83 -0
- package/dist/utils/multidoc-push.d.ts +88 -0
- package/dist/utils/multidoc-push.js +377 -8
- package/dist/utils/paging.d.ts +145 -0
- package/dist/utils/paging.js +199 -0
- package/dist/utils/reference-checker.js +13 -0
- package/oclif.manifest.json +4999 -3973
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
+
import { createOrderedEmitter, mapWithConcurrency } from '../../../../utils/concurrency.js';
|
|
4
|
+
import { collectAllPages, INTERNAL_PAGE_SIZE, normalizeListResponse } from '../../../../utils/paging.js';
|
|
3
5
|
export default class TenantWorkflowTestRunAll extends BaseCommand {
|
|
4
6
|
static description = 'Run all workflow tests for a tenant';
|
|
5
7
|
static examples = [
|
|
@@ -21,6 +23,11 @@ Results: 2 passed, 1 failed
|
|
|
21
23
|
description: 'Filter by branch name',
|
|
22
24
|
required: false,
|
|
23
25
|
}),
|
|
26
|
+
concurrency: Flags.integer({
|
|
27
|
+
default: 1,
|
|
28
|
+
description: 'Run this many tests in parallel. Tests share the workspace database, so raise this only when your tests do not depend on shared state.',
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
24
31
|
output: Flags.string({
|
|
25
32
|
char: 'o',
|
|
26
33
|
default: 'summary',
|
|
@@ -49,41 +56,59 @@ Results: 2 passed, 1 failed
|
|
|
49
56
|
const tenantName = encodeURIComponent(flags.tenant);
|
|
50
57
|
const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/workflow_test`;
|
|
51
58
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// Page the list rather than asking for everything at once: 100 per
|
|
60
|
+
// request is plenty for typical suites and does not make the instance
|
|
61
|
+
// build one enormous response. Termination is the server's own nextPage,
|
|
62
|
+
// never "the page came back full".
|
|
63
|
+
const branchValue = flags.branch;
|
|
64
|
+
const tests = await collectAllPages(async (page) => {
|
|
65
|
+
const listParams = new URLSearchParams();
|
|
66
|
+
listParams.set('page', String(page));
|
|
67
|
+
listParams.set('per_page', String(INTERNAL_PAGE_SIZE));
|
|
68
|
+
if (branchValue)
|
|
69
|
+
listParams.set('branch', branchValue);
|
|
70
|
+
const listResponse = await this.verboseFetch(`${baseUrl}?${listParams}`, {
|
|
71
|
+
headers: {
|
|
72
|
+
accept: 'application/json',
|
|
73
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
74
|
+
},
|
|
75
|
+
method: 'GET',
|
|
76
|
+
}, flags.verbose, profile.access_token);
|
|
77
|
+
if (!listResponse.ok) {
|
|
78
|
+
const errorText = await listResponse.text();
|
|
79
|
+
this.error(`Failed to list workflow tests: ${listResponse.status}: ${listResponse.statusText}\n${errorText}`);
|
|
80
|
+
}
|
|
81
|
+
return normalizeListResponse(await listResponse.json());
|
|
82
|
+
}, {
|
|
83
|
+
onTruncate: (collected) => {
|
|
84
|
+
this.warn(`Stopped after ${collected} workflow tests — the list did not terminate. Some tests may not have run.`);
|
|
60
85
|
},
|
|
61
|
-
|
|
62
|
-
}, flags.verbose, profile.access_token);
|
|
63
|
-
if (!listResponse.ok) {
|
|
64
|
-
const errorText = await listResponse.text();
|
|
65
|
-
this.error(`Failed to list workflow tests: ${listResponse.status}: ${listResponse.statusText}\n${errorText}`);
|
|
66
|
-
}
|
|
67
|
-
const data = (await listResponse.json());
|
|
68
|
-
let tests;
|
|
69
|
-
if (Array.isArray(data)) {
|
|
70
|
-
tests = data;
|
|
71
|
-
}
|
|
72
|
-
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
73
|
-
tests = data.items;
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
this.error('Unexpected API response format');
|
|
77
|
-
}
|
|
86
|
+
});
|
|
78
87
|
if (tests.length === 0) {
|
|
79
|
-
|
|
88
|
+
// An empty suite is a normal state (fresh branches have no tests) and
|
|
89
|
+
// is a success, not a failure. Under -o json it must still be parseable,
|
|
90
|
+
// with the same keys the populated path emits below.
|
|
91
|
+
if (flags.output === 'json') {
|
|
92
|
+
this.log(JSON.stringify({ failed: 0, passed: 0, results: [] }, null, 2));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.log('No workflow tests found');
|
|
96
|
+
}
|
|
80
97
|
return;
|
|
81
98
|
}
|
|
82
99
|
if (flags.output === 'summary') {
|
|
83
100
|
this.log(`Running ${tests.length} workflow test${tests.length === 1 ? '' : 's'}...\n`);
|
|
84
101
|
}
|
|
85
102
|
const results = [];
|
|
86
|
-
|
|
103
|
+
const runOne = async (test) => {
|
|
104
|
+
// Shadowed accumulators: the body below is the original sequential
|
|
105
|
+
// logic, but it records output instead of printing it so a concurrent
|
|
106
|
+
// run can still emit in input order.
|
|
107
|
+
const results = [];
|
|
108
|
+
const lines = [];
|
|
109
|
+
const log = (message) => {
|
|
110
|
+
lines.push(message);
|
|
111
|
+
};
|
|
87
112
|
const runUrl = `${baseUrl}/${test.id}/run`;
|
|
88
113
|
try {
|
|
89
114
|
const runResponse = await this.verboseFetch(runUrl, {
|
|
@@ -102,10 +127,10 @@ Results: 2 passed, 1 failed
|
|
|
102
127
|
status: 'fail',
|
|
103
128
|
});
|
|
104
129
|
if (flags.output === 'summary') {
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
log(`FAIL ${test.name}`);
|
|
131
|
+
log(` Error: API error ${runResponse.status}`);
|
|
107
132
|
}
|
|
108
|
-
|
|
133
|
+
return { lines, results };
|
|
109
134
|
}
|
|
110
135
|
const runResult = (await runResponse.json());
|
|
111
136
|
const passed = runResult.status === 'ok';
|
|
@@ -118,17 +143,19 @@ Results: 2 passed, 1 failed
|
|
|
118
143
|
if (flags.output === 'summary') {
|
|
119
144
|
const timing = runResult.timing ? ` (${runResult.timing}s)` : '';
|
|
120
145
|
if (passed) {
|
|
121
|
-
|
|
146
|
+
log(`PASS ${test.name}${timing}`);
|
|
122
147
|
}
|
|
123
148
|
else {
|
|
124
|
-
|
|
149
|
+
log(`FAIL ${test.name}${timing}`);
|
|
125
150
|
if (runResult.message) {
|
|
126
|
-
|
|
151
|
+
log(` Error: ${runResult.message}`);
|
|
127
152
|
}
|
|
128
153
|
}
|
|
129
154
|
}
|
|
130
155
|
}
|
|
131
156
|
catch (error) {
|
|
157
|
+
if (error instanceof Error && 'oclif' in error)
|
|
158
|
+
throw error;
|
|
132
159
|
const message = error instanceof Error ? error.message : String(error);
|
|
133
160
|
results.push({
|
|
134
161
|
message,
|
|
@@ -136,11 +163,24 @@ Results: 2 passed, 1 failed
|
|
|
136
163
|
status: 'fail',
|
|
137
164
|
});
|
|
138
165
|
if (flags.output === 'summary') {
|
|
139
|
-
|
|
140
|
-
|
|
166
|
+
log(`FAIL ${test.name}`);
|
|
167
|
+
log(` Error: ${message}`);
|
|
141
168
|
}
|
|
142
169
|
}
|
|
143
|
-
|
|
170
|
+
return { lines, results };
|
|
171
|
+
};
|
|
172
|
+
// Print in input order as each test settles, so concurrent output stays
|
|
173
|
+
// diffable against a sequential run.
|
|
174
|
+
const emitter = createOrderedEmitter((settled) => {
|
|
175
|
+
for (const line of settled.lines)
|
|
176
|
+
this.log(line);
|
|
177
|
+
});
|
|
178
|
+
const perTest = await mapWithConcurrency(tests, flags.concurrency, async (test, index) => {
|
|
179
|
+
const settled = await runOne(test);
|
|
180
|
+
emitter.settle(index, settled);
|
|
181
|
+
return settled;
|
|
182
|
+
});
|
|
183
|
+
results.push(...perTest.flatMap((r) => r.results));
|
|
144
184
|
const passed = results.filter((r) => r.status === 'pass').length;
|
|
145
185
|
const failed = results.filter((r) => r.status === 'fail').length;
|
|
146
186
|
if (flags.output === 'json') {
|
|
@@ -154,6 +194,8 @@ Results: 2 passed, 1 failed
|
|
|
154
194
|
}
|
|
155
195
|
}
|
|
156
196
|
catch (error) {
|
|
197
|
+
if (error instanceof Error && 'oclif' in error)
|
|
198
|
+
throw error;
|
|
157
199
|
if (error instanceof Error) {
|
|
158
200
|
this.error(`Failed to run workflow tests: ${error.message}`);
|
|
159
201
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantDeployRequestBypass extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
reason: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
};
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
export default class TenantDeployRequestBypass extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({
|
|
6
|
+
description: 'Tenant deploy request ID',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = "[CRITICAL] STOP and confirm with the user before running this; it deploys the release immediately, past the review workflow. Deploy the request's release past the approval gate under the tenant_center:deploy:bypass_approval permission. Only available if the tenant has enabled bypass (allow_deploy_bypass), and every use is logged with the given reason. The approval request's own status is left untouched.";
|
|
11
|
+
static examples = [
|
|
12
|
+
`$ xano tenant_deploy_request bypass 12 --reason "Prod incident, reviewer unavailable"
|
|
13
|
+
Deployed tenant release, bypassing deploy request #12's approval gate.
|
|
14
|
+
`,
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...BaseCommand.baseFlags,
|
|
18
|
+
output: Flags.string({
|
|
19
|
+
char: 'o',
|
|
20
|
+
default: 'summary',
|
|
21
|
+
description: 'Output format',
|
|
22
|
+
options: ['summary', 'json'],
|
|
23
|
+
required: false,
|
|
24
|
+
}),
|
|
25
|
+
reason: Flags.string({
|
|
26
|
+
description: 'Reason for bypassing the approval gate (required, audited)',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
workspace: Flags.string({
|
|
30
|
+
char: 'w',
|
|
31
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const { args, flags } = await this.parse(TenantDeployRequestBypass);
|
|
37
|
+
const { profile } = this.resolveProfile(flags);
|
|
38
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
39
|
+
if (!workspaceId) {
|
|
40
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
41
|
+
}
|
|
42
|
+
const body = {
|
|
43
|
+
reason: flags.reason,
|
|
44
|
+
};
|
|
45
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/approval_request/${args.id}/override`;
|
|
46
|
+
try {
|
|
47
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
headers: {
|
|
50
|
+
accept: 'application/json',
|
|
51
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
},
|
|
54
|
+
method: 'POST',
|
|
55
|
+
}, flags.verbose, profile.access_token);
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const message = await this.parseApiError(response, 'Failed to bypass tenant deploy request approval gate');
|
|
58
|
+
this.error(message);
|
|
59
|
+
}
|
|
60
|
+
const deploy = (await response.json());
|
|
61
|
+
if (flags.output === 'json') {
|
|
62
|
+
this.log(JSON.stringify(deploy, null, 2));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.log(`Deployed tenant release, bypassing deploy request #${args.id}'s approval gate.`);
|
|
66
|
+
if (deploy.duration_ms !== undefined)
|
|
67
|
+
this.log(` Time: ${(deploy.duration_ms / 1000).toFixed(1)}s`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof Error) {
|
|
72
|
+
this.error(`Failed to bypass tenant deploy request approval gate: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
this.error(`Failed to bypass tenant deploy request approval gate: ${String(error)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantDeployRequestCreate extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
title: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
release: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
reviewers: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
};
|
|
20
|
+
run(): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
export default class TenantDeployRequestCreate extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
title: Args.string({
|
|
6
|
+
description: 'Short title for the deploy request',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Open a tenant deploy request asking named reviewers to approve deploying a release to a tenant. Submits for review immediately unless --draft is passed.';
|
|
11
|
+
static examples = [
|
|
12
|
+
`$ xano tenant_deploy_request create "Deploy v1.2 to prod" --tenant prod --release v1.2 --reviewers 12,45
|
|
13
|
+
Created deploy request #12: "Deploy v1.2 to prod" [pending]
|
|
14
|
+
`,
|
|
15
|
+
`$ xano tenant_deploy_request create "Deploy v1.2 to prod" --tenant prod --release v1.2 --draft`,
|
|
16
|
+
`$ xano tenant_deploy_request create "$PR_TITLE" --tenant prod --release v1.2 --description "$PR_BODY" --reviewers 12,45 -o json`,
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...BaseCommand.baseFlags,
|
|
20
|
+
description: Flags.string({
|
|
21
|
+
char: 'd',
|
|
22
|
+
description: 'Longer description of what is being deployed and why',
|
|
23
|
+
required: false,
|
|
24
|
+
}),
|
|
25
|
+
draft: Flags.boolean({
|
|
26
|
+
default: false,
|
|
27
|
+
description: 'Save as a draft instead of submitting immediately (reviewers are not notified until submitted)',
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
output: Flags.string({
|
|
31
|
+
char: 'o',
|
|
32
|
+
default: 'summary',
|
|
33
|
+
description: 'Output format',
|
|
34
|
+
options: ['summary', 'json'],
|
|
35
|
+
required: false,
|
|
36
|
+
}),
|
|
37
|
+
release: Flags.string({
|
|
38
|
+
char: 'r',
|
|
39
|
+
description: 'Name of the release to deploy',
|
|
40
|
+
required: true,
|
|
41
|
+
}),
|
|
42
|
+
reviewers: Flags.string({
|
|
43
|
+
description: 'Comma-separated user IDs to request review from',
|
|
44
|
+
required: false,
|
|
45
|
+
}),
|
|
46
|
+
tenant: Flags.string({
|
|
47
|
+
char: 't',
|
|
48
|
+
description: 'Name of the target tenant',
|
|
49
|
+
required: true,
|
|
50
|
+
}),
|
|
51
|
+
workspace: Flags.string({
|
|
52
|
+
char: 'w',
|
|
53
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
54
|
+
required: false,
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
async run() {
|
|
58
|
+
const { args, flags } = await this.parse(TenantDeployRequestCreate);
|
|
59
|
+
const { profile } = this.resolveProfile(flags);
|
|
60
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
61
|
+
if (!workspaceId) {
|
|
62
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
63
|
+
}
|
|
64
|
+
const reviewerIds = flags.reviewers
|
|
65
|
+
? flags.reviewers.split(',').map((id) => Number.parseInt(id.trim(), 10))
|
|
66
|
+
: [];
|
|
67
|
+
const body = {
|
|
68
|
+
release_name: flags.release,
|
|
69
|
+
reviewer_ids: reviewerIds,
|
|
70
|
+
status: flags.draft ? 'draft' : 'pending',
|
|
71
|
+
tenant_name: flags.tenant,
|
|
72
|
+
title: args.title,
|
|
73
|
+
};
|
|
74
|
+
if (flags.description)
|
|
75
|
+
body.description = flags.description;
|
|
76
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/approval_request`;
|
|
77
|
+
try {
|
|
78
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
79
|
+
body: JSON.stringify(body),
|
|
80
|
+
headers: {
|
|
81
|
+
accept: 'application/json',
|
|
82
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
},
|
|
85
|
+
method: 'POST',
|
|
86
|
+
}, flags.verbose, profile.access_token);
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const message = await this.parseApiError(response, 'Failed to create tenant deploy request');
|
|
89
|
+
this.error(message);
|
|
90
|
+
}
|
|
91
|
+
const item = (await response.json());
|
|
92
|
+
if (flags.output === 'json') {
|
|
93
|
+
this.log(JSON.stringify(item, null, 2));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.log(`Created deploy request #${item.id}: "${item.title}" [${item.status}]`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error instanceof Error) {
|
|
101
|
+
this.error(`Failed to create tenant deploy request: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.error(`Failed to create tenant deploy request: ${String(error)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantDeployRequestEdit extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
reviewers: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
title: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
};
|
|
21
|
+
run(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
export default class TenantDeployRequestEdit extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({
|
|
6
|
+
description: 'Tenant deploy request ID',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = "Edit a tenant deploy request's title, description or reviewers. The author or an existing reviewer may do this.";
|
|
11
|
+
static examples = [
|
|
12
|
+
`$ xano tenant_deploy_request edit 12 --title "Deploy v1.2.1 to prod"
|
|
13
|
+
Updated deploy request #12: "Deploy v1.2.1 to prod" [pending]
|
|
14
|
+
`,
|
|
15
|
+
`$ xano tenant_deploy_request edit 12 --reviewers 12,45,67 -o json`,
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
...BaseCommand.baseFlags,
|
|
19
|
+
description: Flags.string({
|
|
20
|
+
char: 'd',
|
|
21
|
+
description: 'New description',
|
|
22
|
+
required: false,
|
|
23
|
+
}),
|
|
24
|
+
output: Flags.string({
|
|
25
|
+
char: 'o',
|
|
26
|
+
default: 'summary',
|
|
27
|
+
description: 'Output format',
|
|
28
|
+
options: ['summary', 'json'],
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
31
|
+
reviewers: Flags.string({
|
|
32
|
+
description: 'Replacement comma-separated user IDs to request review from',
|
|
33
|
+
required: false,
|
|
34
|
+
}),
|
|
35
|
+
title: Flags.string({
|
|
36
|
+
description: 'New title',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
workspace: Flags.string({
|
|
40
|
+
char: 'w',
|
|
41
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
42
|
+
required: false,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const { args, flags } = await this.parse(TenantDeployRequestEdit);
|
|
47
|
+
const { profile } = this.resolveProfile(flags);
|
|
48
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
49
|
+
if (!workspaceId) {
|
|
50
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
51
|
+
}
|
|
52
|
+
const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/approval_request/${args.id}`;
|
|
53
|
+
try {
|
|
54
|
+
const getResponse = await this.verboseFetch(baseUrl, {
|
|
55
|
+
headers: {
|
|
56
|
+
accept: 'application/json',
|
|
57
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
58
|
+
},
|
|
59
|
+
method: 'GET',
|
|
60
|
+
}, flags.verbose, profile.access_token);
|
|
61
|
+
if (!getResponse.ok) {
|
|
62
|
+
const message = await this.parseApiError(getResponse, 'Failed to fetch tenant deploy request');
|
|
63
|
+
this.error(message);
|
|
64
|
+
}
|
|
65
|
+
const current = (await getResponse.json());
|
|
66
|
+
const body = {
|
|
67
|
+
description: flags.description !== undefined ? flags.description : (current.description ?? ''),
|
|
68
|
+
reviewer_ids: flags.reviewers
|
|
69
|
+
? flags.reviewers.split(',').map((id) => Number.parseInt(id.trim(), 10))
|
|
70
|
+
: (current.reviewers ?? []).map((r) => r.id),
|
|
71
|
+
title: flags.title !== undefined ? flags.title : current.title,
|
|
72
|
+
};
|
|
73
|
+
const putResponse = await this.verboseFetch(baseUrl, {
|
|
74
|
+
body: JSON.stringify(body),
|
|
75
|
+
headers: {
|
|
76
|
+
accept: 'application/json',
|
|
77
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
},
|
|
80
|
+
method: 'PUT',
|
|
81
|
+
}, flags.verbose, profile.access_token);
|
|
82
|
+
if (!putResponse.ok) {
|
|
83
|
+
const message = await this.parseApiError(putResponse, 'Failed to edit tenant deploy request');
|
|
84
|
+
this.error(message);
|
|
85
|
+
}
|
|
86
|
+
const item = (await putResponse.json());
|
|
87
|
+
if (flags.output === 'json') {
|
|
88
|
+
this.log(JSON.stringify(item, null, 2));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.log(`Updated deploy request #${item.id}: "${item.title}" [${item.status}]`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof Error) {
|
|
96
|
+
this.error(`Failed to edit tenant deploy request: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.error(`Failed to edit tenant deploy request: ${String(error)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantDeployRequestGet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
export default class TenantDeployRequestGet extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({
|
|
6
|
+
description: 'Tenant deploy request ID',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Get details of a specific tenant deploy request';
|
|
11
|
+
static examples = [
|
|
12
|
+
`$ xano tenant_deploy_request get 12
|
|
13
|
+
Deploy request #12: "Deploy v1.2 to prod" [pending]
|
|
14
|
+
`,
|
|
15
|
+
`$ xano tenant_deploy_request get 12 -o json`,
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
...BaseCommand.baseFlags,
|
|
19
|
+
output: Flags.string({
|
|
20
|
+
char: 'o',
|
|
21
|
+
default: 'summary',
|
|
22
|
+
description: 'Output format',
|
|
23
|
+
options: ['summary', 'json'],
|
|
24
|
+
required: false,
|
|
25
|
+
}),
|
|
26
|
+
workspace: Flags.string({
|
|
27
|
+
char: 'w',
|
|
28
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { args, flags } = await this.parse(TenantDeployRequestGet);
|
|
34
|
+
const { profile } = this.resolveProfile(flags);
|
|
35
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
36
|
+
if (!workspaceId) {
|
|
37
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
38
|
+
}
|
|
39
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/approval_request/${args.id}`;
|
|
40
|
+
try {
|
|
41
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
42
|
+
headers: {
|
|
43
|
+
accept: 'application/json',
|
|
44
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
45
|
+
},
|
|
46
|
+
method: 'GET',
|
|
47
|
+
}, flags.verbose, profile.access_token);
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const message = await this.parseApiError(response, 'Failed to get tenant deploy request');
|
|
50
|
+
this.error(message);
|
|
51
|
+
}
|
|
52
|
+
const item = (await response.json());
|
|
53
|
+
if (flags.output === 'json') {
|
|
54
|
+
this.log(JSON.stringify(item, null, 2));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.log(`Deploy request #${item.id}: "${item.title}" [${item.status}]`);
|
|
58
|
+
if (item.description)
|
|
59
|
+
this.log(` Description: ${item.description}`);
|
|
60
|
+
if (item.reviewers?.length)
|
|
61
|
+
this.log(` Reviewers: ${item.reviewers.map((r) => r.id).join(', ')}`);
|
|
62
|
+
if (item.can_review !== undefined)
|
|
63
|
+
this.log(` Can review: ${item.can_review}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof Error) {
|
|
68
|
+
this.error(`Failed to get tenant deploy request: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.error(`Failed to get tenant deploy request: ${String(error)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantDeployRequestList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
mine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'per-page': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
release: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
tenant: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'to-review': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
};
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
}
|