@xano/cli 1.3.0 → 1.3.1-beta.1
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 +273 -1
- package/dist/base-command.d.ts +12 -1
- package/dist/base-command.js +36 -22
- package/dist/commands/branch/set_live/index.d.ts +22 -0
- package/dist/commands/branch/set_live/index.js +60 -1
- package/dist/commands/ephemeral/push/index.js +6 -1
- package/dist/commands/function/create/index.d.ts +15 -0
- package/dist/commands/function/create/index.js +40 -0
- package/dist/commands/function/edit/index.d.ts +15 -0
- package/dist/commands/function/edit/index.js +40 -0
- package/dist/commands/function/run/index.d.ts +1 -0
- package/dist/commands/function/run/index.js +10 -2
- package/dist/commands/policy/catalogue/index.d.ts +15 -0
- package/dist/commands/policy/catalogue/index.js +29 -0
- package/dist/commands/policy/create/index.d.ts +18 -0
- package/dist/commands/policy/create/index.js +39 -0
- package/dist/commands/policy/delete/index.d.ts +19 -0
- package/dist/commands/policy/delete/index.js +61 -0
- package/dist/commands/policy/evaluate/index.d.ts +32 -0
- package/dist/commands/policy/evaluate/index.js +71 -0
- package/dist/commands/policy/list/index.d.ts +14 -0
- package/dist/commands/policy/list/index.js +21 -0
- package/dist/commands/policy/parse/index.d.ts +19 -0
- package/dist/commands/policy/parse/index.js +18 -0
- package/dist/commands/policy/publish/index.d.ts +20 -0
- package/dist/commands/policy/publish/index.js +38 -0
- package/dist/commands/policy/runs/index.d.ts +54 -0
- package/dist/commands/policy/runs/index.js +273 -0
- package/dist/commands/policy/status/index.d.ts +21 -0
- package/dist/commands/policy/status/index.js +54 -0
- package/dist/commands/release/create/index.js +7 -0
- package/dist/commands/release/deploy/index.d.ts +28 -0
- package/dist/commands/release/deploy/index.js +90 -6
- package/dist/commands/release/import/index.js +5 -1
- package/dist/commands/release/list/index.d.ts +7 -0
- package/dist/commands/release/list/index.js +54 -10
- package/dist/commands/release/push/index.js +19 -1
- package/dist/commands/sandbox/push/index.js +6 -1
- package/dist/commands/skills/pull/index.d.ts +30 -0
- package/dist/commands/skills/pull/index.js +118 -0
- 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 +40 -0
- package/dist/commands/tenant/deploy_release/index.js +186 -4
- package/dist/commands/tenant/edit/index.d.ts +2 -0
- package/dist/commands/tenant/edit/index.js +39 -4
- package/dist/commands/tenant_deploy_request/bypass/index.d.ts +31 -0
- package/dist/commands/tenant_deploy_request/bypass/index.js +109 -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 +25 -0
- package/dist/commands/tenant_deploy_request/get/index.js +116 -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 +38 -0
- package/dist/commands/tenant_deploy_request/set_status/index.js +159 -0
- package/dist/commands/workspace/pull/index.d.ts +10 -0
- package/dist/commands/workspace/pull/index.js +70 -7
- package/dist/commands/workspace/push/index.d.ts +19 -0
- package/dist/commands/workspace/push/index.js +124 -5
- package/dist/policy-command.d.ts +90 -0
- package/dist/policy-command.js +94 -0
- package/dist/utils/document-parser.d.ts +10 -0
- package/dist/utils/document-parser.js +21 -0
- package/dist/utils/multidoc-push.d.ts +55 -2
- package/dist/utils/multidoc-push.js +103 -27
- package/dist/utils/policy/catalogue.d.ts +7 -0
- package/dist/utils/policy/catalogue.js +72 -0
- package/dist/utils/policy/errors.d.ts +12 -0
- package/dist/utils/policy/errors.js +61 -0
- package/dist/utils/policy/feedback.d.ts +68 -0
- package/dist/utils/policy/feedback.js +165 -0
- package/dist/utils/policy/findings.d.ts +23 -0
- package/dist/utils/policy/findings.js +65 -0
- package/dist/utils/policy/gate.d.ts +161 -0
- package/dist/utils/policy/gate.js +201 -0
- package/dist/utils/policy/goal.d.ts +46 -0
- package/dist/utils/policy/goal.js +95 -0
- package/dist/utils/policy/permission.d.ts +23 -0
- package/dist/utils/policy/permission.js +84 -0
- package/dist/utils/policy/push-gate.d.ts +5 -0
- package/dist/utils/policy/push-gate.js +29 -0
- package/dist/utils/policy/release.d.ts +85 -0
- package/dist/utils/policy/release.js +115 -0
- package/dist/utils/policy/request.d.ts +55 -0
- package/dist/utils/policy/request.js +115 -0
- package/dist/utils/policy/runs.d.ts +30 -0
- package/dist/utils/policy/runs.js +101 -0
- package/dist/utils/policy/status.d.ts +47 -0
- package/dist/utils/policy/status.js +117 -0
- package/dist/utils/policy/types.d.ts +185 -0
- package/dist/utils/policy/types.js +1 -0
- package/oclif.manifest.json +5520 -2511
- package/package.json +7 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateOverrideHint, gateRefusal, liveGateLines, quoted, weakeningRefusal, } from '../../../utils/policy/gate.js';
|
|
4
|
+
import { policyWeakeningGuidance } from '../../../utils/policy/permission.js';
|
|
3
5
|
export default class BranchSetLive extends BaseCommand {
|
|
4
6
|
static args = {
|
|
5
7
|
branch_label: Args.string({
|
|
@@ -7,7 +9,7 @@ export default class BranchSetLive extends BaseCommand {
|
|
|
7
9
|
required: true,
|
|
8
10
|
}),
|
|
9
11
|
};
|
|
10
|
-
static description = '[IMPORTANT] ALWAYS confirm with the user before changing the live branch. Sets a branch as the live (active) branch for API requests.';
|
|
12
|
+
static description = '[IMPORTANT] ALWAYS confirm with the user before changing the live branch. Sets a branch as the live (active) branch for API requests. The set-live policy gate checks the branch against its own policies, which live will have once it is set live: when blocking findings refuse it, the command exits 2 unless --policy-override gives a reason. A branch that weakens a mandatory policy of the live branch needs workspace:policy update; without it the command exits 1.';
|
|
11
13
|
static examples = [
|
|
12
14
|
`$ xano branch set-live staging
|
|
13
15
|
Are you sure you want to set 'staging' as the live branch? (y/N) y
|
|
@@ -24,6 +26,7 @@ Branch 'v1' is now live
|
|
|
24
26
|
"live": true
|
|
25
27
|
}
|
|
26
28
|
`,
|
|
29
|
+
`$ xano branch set_live rollback --force --policy-override "Rollback approved; finding tracked in JIRA-12"`,
|
|
27
30
|
];
|
|
28
31
|
static flags = {
|
|
29
32
|
...BaseCommand.baseFlags,
|
|
@@ -40,14 +43,27 @@ Branch 'v1' is now live
|
|
|
40
43
|
options: ['summary', 'json'],
|
|
41
44
|
required: false,
|
|
42
45
|
}),
|
|
46
|
+
'policy-override': Flags.string({ description: 'Set the branch live past a blocking set-live policy gate with an audited reason (requires workspace:policy update)' }),
|
|
43
47
|
workspace: Flags.integer({
|
|
44
48
|
char: 'w',
|
|
45
49
|
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
46
50
|
required: false,
|
|
47
51
|
}),
|
|
48
52
|
};
|
|
53
|
+
/** Set once the platform refuses for a weakened mandatory policy: already explained, it exits 1 as it is. */
|
|
54
|
+
refusedByPermission = false;
|
|
55
|
+
/** Set once the set-live policy gate refuses, the one failure that exits 2. */
|
|
56
|
+
refusedByPolicyGate = false;
|
|
57
|
+
/** Exit 2 is the set-live gate's refusal; every other failure, flag errors included, exits 1. */
|
|
58
|
+
async catch(error) {
|
|
59
|
+
if (this.refusedByPolicyGate)
|
|
60
|
+
return super.catch(error);
|
|
61
|
+
return this.catchAsOperational(error);
|
|
62
|
+
}
|
|
49
63
|
async run() {
|
|
50
64
|
const { args, flags } = await this.parse(BranchSetLive);
|
|
65
|
+
if (blankPolicyOverride(flags['policy-override']))
|
|
66
|
+
this.error(BLANK_POLICY_OVERRIDE);
|
|
51
67
|
const { profile } = this.resolveProfile(flags);
|
|
52
68
|
// Get workspace ID from flag or profile
|
|
53
69
|
const workspaceId = flags.workspace || profile.workspace;
|
|
@@ -66,16 +82,26 @@ Branch 'v1' is now live
|
|
|
66
82
|
}
|
|
67
83
|
// Construct the API URL
|
|
68
84
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch/${encodeURIComponent(branchLabel)}/live`;
|
|
85
|
+
// A reason from someone with workspace:policy update sets the branch live past a blocking gate.
|
|
86
|
+
const overrideReason = flags['policy-override']?.trim();
|
|
69
87
|
// Set branch as live via the API
|
|
70
88
|
try {
|
|
71
89
|
const response = await this.verboseFetch(apiUrl, {
|
|
90
|
+
...(overrideReason ? { body: JSON.stringify({ override_reason: overrideReason }) } : {}),
|
|
72
91
|
headers: {
|
|
73
92
|
'accept': 'application/json',
|
|
74
93
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
94
|
+
...(overrideReason ? { 'Content-Type': 'application/json' } : {}),
|
|
75
95
|
},
|
|
76
96
|
method: 'POST',
|
|
77
97
|
}, flags.verbose, profile.access_token);
|
|
78
98
|
if (!response.ok) {
|
|
99
|
+
const refused = await gateRefusal(response);
|
|
100
|
+
if (refused)
|
|
101
|
+
this.refuseSetLive(refused, flags, branchLabel);
|
|
102
|
+
const weakening = await weakeningRefusal(response);
|
|
103
|
+
if (weakening)
|
|
104
|
+
this.refuseWeakening(weakening, flags, branchLabel);
|
|
79
105
|
const errorText = await response.text();
|
|
80
106
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
81
107
|
}
|
|
@@ -89,6 +115,8 @@ Branch 'v1' is now live
|
|
|
89
115
|
}
|
|
90
116
|
}
|
|
91
117
|
catch (error) {
|
|
118
|
+
if (this.refusedByPolicyGate || this.refusedByPermission)
|
|
119
|
+
throw error;
|
|
92
120
|
if (error instanceof Error) {
|
|
93
121
|
this.error(`Failed to set branch as live: ${error.message}`);
|
|
94
122
|
}
|
|
@@ -111,4 +139,35 @@ Branch 'v1' is now live
|
|
|
111
139
|
});
|
|
112
140
|
});
|
|
113
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* The set-live gate refused: the verdict on stdout (under `-o json`, `{set_live: false, message,
|
|
144
|
+
* policy_gate}`), then the platform's message and how to proceed, exiting 2.
|
|
145
|
+
*/
|
|
146
|
+
refuseSetLive(refused, flags, branchLabel) {
|
|
147
|
+
if (flags.output === 'json') {
|
|
148
|
+
this.log(JSON.stringify({ message: refused.message, policy_gate: refused.answer, set_live: false }, null, 2));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
for (const line of liveGateLines(refused.answer, 'branch'))
|
|
152
|
+
this.log(line);
|
|
153
|
+
}
|
|
154
|
+
const workspace = flags.workspace ? ` -w ${flags.workspace}` : '';
|
|
155
|
+
const hint = gateOverrideHint(refused.answer, `xano branch set_live ${quoted(branchLabel)}${workspace} --policy-override "<why>"`);
|
|
156
|
+
this.refusedByPolicyGate = true;
|
|
157
|
+
this.error(`${refused.message}${hint}`, { exit: 2 });
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The branch weakens a mandatory policy of the live branch (or lacks one), and the credential lacks
|
|
161
|
+
* `workspace:policy` update. That is a permission refusal, not a blocking finding: it exits 1 (under
|
|
162
|
+
* `-o json` as `{error}`) with the platform's sentence and how to go on. The live branch is unchanged.
|
|
163
|
+
*/
|
|
164
|
+
refuseWeakening(refused, flags, branchLabel) {
|
|
165
|
+
const workspace = flags.workspace ? ` -w ${flags.workspace}` : '';
|
|
166
|
+
this.refusedByPermission = true;
|
|
167
|
+
const again = `xano branch set_live ${quoted(branchLabel)}${workspace}`;
|
|
168
|
+
const way = refused.payload.unavailable === true
|
|
169
|
+
? `The check may read them next time: ${again}`
|
|
170
|
+
: `To set it live yourself, give ${quoted(branchLabel)} the live branch's version of those policies, then run: ${again}`;
|
|
171
|
+
this.error(`${refused.message}${policyWeakeningGuidance(refused.payload, [way], true)}`);
|
|
172
|
+
}
|
|
114
173
|
}
|
|
@@ -4,6 +4,7 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import BaseCommand from '../../../base-command.js';
|
|
5
5
|
import { isAwaited, waitForMicroservices, } from '../../../utils/microservice-wait.js';
|
|
6
6
|
import { executePush } from '../../../utils/multidoc-push.js';
|
|
7
|
+
import { policiesSkippedNotice } from '../../../utils/policy/feedback.js';
|
|
7
8
|
/** Status glyph for one microservice row in the `--wait` progress block. */
|
|
8
9
|
const icon = (e) => {
|
|
9
10
|
if (!isAwaited(e))
|
|
@@ -168,13 +169,17 @@ Skip preview and push immediately
|
|
|
168
169
|
truncate: flags.truncate,
|
|
169
170
|
verbose: flags.verbose,
|
|
170
171
|
};
|
|
171
|
-
await executePush({
|
|
172
|
+
const result = await executePush({
|
|
172
173
|
accessToken: profile.access_token,
|
|
173
174
|
branch: '',
|
|
174
175
|
command: this,
|
|
175
176
|
inputDir,
|
|
176
177
|
verboseFetch: this.verboseFetch.bind(this),
|
|
177
178
|
}, target, pushFlags);
|
|
179
|
+
// When the platform leaves policy files out, it says so once.
|
|
180
|
+
const skipped = policiesSkippedNotice(result.response ?? result.preview);
|
|
181
|
+
if (skipped)
|
|
182
|
+
this.warn(skipped);
|
|
178
183
|
// --wait: after a real push, poll the tenant's live microservice status until
|
|
179
184
|
// every auto-deployed microservice (tenant_deploy="auto") is ready or the
|
|
180
185
|
// wait times out. Skipped on --dry-run (nothing was deployed).
|
|
@@ -7,13 +7,28 @@ export default class FunctionCreate extends BaseCommand {
|
|
|
7
7
|
edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
16
|
};
|
|
17
|
+
/** Set once the publish policy gate refuses, the one failure that exits 2. */
|
|
18
|
+
private refusedByPolicyGate;
|
|
19
|
+
/** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
|
|
20
|
+
protected catch(error: Error & {
|
|
21
|
+
oclif?: {
|
|
22
|
+
exit?: number;
|
|
23
|
+
};
|
|
24
|
+
}): Promise<void>;
|
|
16
25
|
run(): Promise<void>;
|
|
17
26
|
private editFile;
|
|
18
27
|
private readStdin;
|
|
28
|
+
/**
|
|
29
|
+
* The live-branch publish gate refused the new function: the verdict on stdout (under `-o json`,
|
|
30
|
+
* `{created: false, message, policy_gate}`), then the platform's message and how to proceed,
|
|
31
|
+
* exiting 2.
|
|
32
|
+
*/
|
|
33
|
+
private refusePublish;
|
|
19
34
|
}
|
|
@@ -4,6 +4,7 @@ import * as fs from 'node:fs';
|
|
|
4
4
|
import * as os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import BaseCommand from '../../../base-command.js';
|
|
7
|
+
import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateRefusal, publishRefusal, quoted } from '../../../utils/policy/gate.js';
|
|
7
8
|
export default class FunctionCreate extends BaseCommand {
|
|
8
9
|
static args = {};
|
|
9
10
|
static description = 'Create a new function in a workspace';
|
|
@@ -36,6 +37,7 @@ Name: my_function
|
|
|
36
37
|
...
|
|
37
38
|
}
|
|
38
39
|
`,
|
|
40
|
+
`$ xano function:create -f function.xs --policy-override "Exception approved; finding tracked in JIRA-12"`,
|
|
39
41
|
];
|
|
40
42
|
static flags = {
|
|
41
43
|
...BaseCommand.baseFlags,
|
|
@@ -59,6 +61,7 @@ Name: my_function
|
|
|
59
61
|
options: ['summary', 'json'],
|
|
60
62
|
required: false,
|
|
61
63
|
}),
|
|
64
|
+
'policy-override': Flags.string({ description: 'Create the function past a blocking live-branch publish policy gate with an audited reason (requires workspace:policy update)' }),
|
|
62
65
|
stdin: Flags.boolean({
|
|
63
66
|
char: 's',
|
|
64
67
|
default: false,
|
|
@@ -72,8 +75,18 @@ Name: my_function
|
|
|
72
75
|
required: false,
|
|
73
76
|
}),
|
|
74
77
|
};
|
|
78
|
+
/** Set once the publish policy gate refuses, the one failure that exits 2. */
|
|
79
|
+
refusedByPolicyGate = false;
|
|
80
|
+
/** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
|
|
81
|
+
async catch(error) {
|
|
82
|
+
if (this.refusedByPolicyGate)
|
|
83
|
+
return super.catch(error);
|
|
84
|
+
return this.catchAsOperational(error);
|
|
85
|
+
}
|
|
75
86
|
async run() {
|
|
76
87
|
const { flags } = await this.parse(FunctionCreate);
|
|
88
|
+
if (blankPolicyOverride(flags['policy-override']))
|
|
89
|
+
this.error(BLANK_POLICY_OVERRIDE);
|
|
77
90
|
const { profile, profileName } = this.resolveProfile(flags);
|
|
78
91
|
// Determine workspace_id from flag or profile
|
|
79
92
|
let workspaceId;
|
|
@@ -133,6 +146,10 @@ Name: my_function
|
|
|
133
146
|
const queryParams = new URLSearchParams({
|
|
134
147
|
include_xanoscript: 'false',
|
|
135
148
|
});
|
|
149
|
+
// A reason from someone with workspace:policy update saves past a blocking publish gate.
|
|
150
|
+
const overrideReason = flags['policy-override']?.trim();
|
|
151
|
+
if (overrideReason)
|
|
152
|
+
queryParams.set('override_reason', overrideReason);
|
|
136
153
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
|
|
137
154
|
// Create function via API
|
|
138
155
|
try {
|
|
@@ -146,6 +163,9 @@ Name: my_function
|
|
|
146
163
|
method: 'POST',
|
|
147
164
|
}, flags.verbose, profile.access_token);
|
|
148
165
|
if (!response.ok) {
|
|
166
|
+
const refused = await gateRefusal(response);
|
|
167
|
+
if (refused)
|
|
168
|
+
this.refusePublish(refused, flags);
|
|
149
169
|
const errorText = await response.text();
|
|
150
170
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
151
171
|
}
|
|
@@ -166,6 +186,8 @@ Name: my_function
|
|
|
166
186
|
}
|
|
167
187
|
}
|
|
168
188
|
catch (error) {
|
|
189
|
+
if (this.refusedByPolicyGate)
|
|
190
|
+
throw error;
|
|
169
191
|
if (error instanceof Error) {
|
|
170
192
|
this.error(`Failed to create function: ${error.message}`);
|
|
171
193
|
}
|
|
@@ -239,4 +261,22 @@ Name: my_function
|
|
|
239
261
|
process.stdin.resume();
|
|
240
262
|
});
|
|
241
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* The live-branch publish gate refused the new function: the verdict on stdout (under `-o json`,
|
|
266
|
+
* `{created: false, message, policy_gate}`), then the platform's message and how to proceed,
|
|
267
|
+
* exiting 2.
|
|
268
|
+
*/
|
|
269
|
+
refusePublish(refused, flags) {
|
|
270
|
+
const workspace = flags.workspace ? ` -w ${quoted(flags.workspace)}` : '';
|
|
271
|
+
const source = flags.file ? ` -f ${quoted(flags.file)}` : ' --stdin';
|
|
272
|
+
const { lines, message } = publishRefusal(refused, {
|
|
273
|
+
json: flags.output === 'json',
|
|
274
|
+
outcome: 'created',
|
|
275
|
+
rerun: `xano function create${workspace}${source} --policy-override "<why>"`,
|
|
276
|
+
});
|
|
277
|
+
for (const line of lines)
|
|
278
|
+
this.log(line);
|
|
279
|
+
this.refusedByPolicyGate = true;
|
|
280
|
+
this.error(message, { exit: 2 });
|
|
281
|
+
}
|
|
242
282
|
}
|
|
@@ -9,6 +9,7 @@ export default class FunctionEdit extends BaseCommand {
|
|
|
9
9
|
edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
publish: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -16,10 +17,24 @@ export default class FunctionEdit extends BaseCommand {
|
|
|
16
17
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
18
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
19
|
};
|
|
20
|
+
/** Set once the publish policy gate refuses, the one failure that exits 2. */
|
|
21
|
+
private refusedByPolicyGate;
|
|
22
|
+
/** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
|
|
23
|
+
protected catch(error: Error & {
|
|
24
|
+
oclif?: {
|
|
25
|
+
exit?: number;
|
|
26
|
+
};
|
|
27
|
+
}): Promise<void>;
|
|
19
28
|
run(): Promise<void>;
|
|
20
29
|
private editFile;
|
|
21
30
|
private editFunctionContent;
|
|
22
31
|
private fetchFunctionCode;
|
|
23
32
|
private promptForFunctionId;
|
|
24
33
|
private readStdin;
|
|
34
|
+
/**
|
|
35
|
+
* The live-branch publish gate refused the update: the verdict on stdout (under `-o json`,
|
|
36
|
+
* `{updated: false, message, policy_gate}`), then the platform's message and how to proceed,
|
|
37
|
+
* exiting 2.
|
|
38
|
+
*/
|
|
39
|
+
private refusePublish;
|
|
25
40
|
}
|
|
@@ -5,6 +5,7 @@ import * as fs from 'node:fs';
|
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import BaseCommand, { buildUserAgent } from '../../../base-command.js';
|
|
8
|
+
import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateRefusal, publishRefusal, quoted } from '../../../utils/policy/gate.js';
|
|
8
9
|
export default class FunctionEdit extends BaseCommand {
|
|
9
10
|
static args = {
|
|
10
11
|
function_id: Args.string({
|
|
@@ -59,6 +60,7 @@ Name: my_function
|
|
|
59
60
|
...
|
|
60
61
|
}
|
|
61
62
|
`,
|
|
63
|
+
`$ xano function:edit 163 -f function.xs --policy-override "Exception approved; finding tracked in JIRA-12"`,
|
|
62
64
|
];
|
|
63
65
|
static flags = {
|
|
64
66
|
...BaseCommand.baseFlags,
|
|
@@ -81,6 +83,7 @@ Name: my_function
|
|
|
81
83
|
options: ['summary', 'json'],
|
|
82
84
|
required: false,
|
|
83
85
|
}),
|
|
86
|
+
'policy-override': Flags.string({ description: 'Publish past a blocking live-branch publish policy gate with an audited reason (requires workspace:policy update)' }),
|
|
84
87
|
publish: Flags.boolean({
|
|
85
88
|
default: true,
|
|
86
89
|
description: 'Publish the function after editing',
|
|
@@ -99,8 +102,18 @@ Name: my_function
|
|
|
99
102
|
required: false,
|
|
100
103
|
}),
|
|
101
104
|
};
|
|
105
|
+
/** Set once the publish policy gate refuses, the one failure that exits 2. */
|
|
106
|
+
refusedByPolicyGate = false;
|
|
107
|
+
/** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
|
|
108
|
+
async catch(error) {
|
|
109
|
+
if (this.refusedByPolicyGate)
|
|
110
|
+
return super.catch(error);
|
|
111
|
+
return this.catchAsOperational(error);
|
|
112
|
+
}
|
|
102
113
|
async run() {
|
|
103
114
|
const { args, flags } = await this.parse(FunctionEdit);
|
|
115
|
+
if (blankPolicyOverride(flags['policy-override']))
|
|
116
|
+
this.error(BLANK_POLICY_OVERRIDE);
|
|
104
117
|
const { profile, profileName } = this.resolveProfile(flags);
|
|
105
118
|
// Validate flag combinations
|
|
106
119
|
if (flags.edit && !flags.file) {
|
|
@@ -175,6 +188,10 @@ Name: my_function
|
|
|
175
188
|
include_xanoscript: 'false',
|
|
176
189
|
publish: flags.publish ? 'true' : 'false',
|
|
177
190
|
});
|
|
191
|
+
// A reason from someone with workspace:policy update publishes past a blocking publish gate.
|
|
192
|
+
const overrideReason = flags['policy-override']?.trim();
|
|
193
|
+
if (overrideReason)
|
|
194
|
+
queryParams.set('override_reason', overrideReason);
|
|
178
195
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/${functionId}?${queryParams.toString()}`;
|
|
179
196
|
// Update function via API
|
|
180
197
|
try {
|
|
@@ -188,6 +205,9 @@ Name: my_function
|
|
|
188
205
|
method: 'PUT',
|
|
189
206
|
}, flags.verbose, profile.access_token);
|
|
190
207
|
if (!response.ok) {
|
|
208
|
+
const refused = await gateRefusal(response);
|
|
209
|
+
if (refused)
|
|
210
|
+
this.refusePublish(refused, flags, functionId);
|
|
191
211
|
const errorText = await response.text();
|
|
192
212
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
193
213
|
}
|
|
@@ -208,6 +228,8 @@ Name: my_function
|
|
|
208
228
|
}
|
|
209
229
|
}
|
|
210
230
|
catch (error) {
|
|
231
|
+
if (this.refusedByPolicyGate)
|
|
232
|
+
throw error;
|
|
211
233
|
if (error instanceof Error) {
|
|
212
234
|
this.error(`Failed to update function: ${error.message}`);
|
|
213
235
|
}
|
|
@@ -428,4 +450,22 @@ Name: my_function
|
|
|
428
450
|
process.stdin.resume();
|
|
429
451
|
});
|
|
430
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* The live-branch publish gate refused the update: the verdict on stdout (under `-o json`,
|
|
455
|
+
* `{updated: false, message, policy_gate}`), then the platform's message and how to proceed,
|
|
456
|
+
* exiting 2.
|
|
457
|
+
*/
|
|
458
|
+
refusePublish(refused, flags, functionId) {
|
|
459
|
+
const workspace = flags.workspace ? ` -w ${quoted(flags.workspace)}` : '';
|
|
460
|
+
const source = flags.file ? ` -f ${quoted(flags.file)}` : flags.stdin ? ' --stdin' : '';
|
|
461
|
+
const { lines, message } = publishRefusal(refused, {
|
|
462
|
+
json: flags.output === 'json',
|
|
463
|
+
outcome: 'updated',
|
|
464
|
+
rerun: `xano function edit ${quoted(functionId)}${workspace}${source} --policy-override "<why>"`,
|
|
465
|
+
});
|
|
466
|
+
for (const line of lines)
|
|
467
|
+
this.log(line);
|
|
468
|
+
this.refusedByPolicyGate = true;
|
|
469
|
+
this.error(message, { exit: 2 });
|
|
470
|
+
}
|
|
431
471
|
}
|
|
@@ -15,6 +15,7 @@ export default class FunctionRun extends BaseCommand {
|
|
|
15
15
|
'no-input-check': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
16
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
17
|
stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
tenant: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
19
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
20
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
21
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -19,6 +19,8 @@ export default class FunctionRun extends BaseCommand {
|
|
|
19
19
|
`$ xano function:run calcScore --branch dev --logs`,
|
|
20
20
|
`$ xano function:run calcScore --datasource test
|
|
21
21
|
# Runs against the 'test' data source instead of 'live'`,
|
|
22
|
+
`$ xano function:run calcScore --tenant my-tenant
|
|
23
|
+
# Runs the function on the tenant instead of the workspace`,
|
|
22
24
|
];
|
|
23
25
|
static flags = {
|
|
24
26
|
...BaseCommand.baseFlags,
|
|
@@ -70,6 +72,11 @@ export default class FunctionRun extends BaseCommand {
|
|
|
70
72
|
exclusive: ['json'],
|
|
71
73
|
required: false,
|
|
72
74
|
}),
|
|
75
|
+
tenant: Flags.string({
|
|
76
|
+
char: 't',
|
|
77
|
+
description: 'Tenant name to run the function on (defaults to the workspace)',
|
|
78
|
+
required: false,
|
|
79
|
+
}),
|
|
73
80
|
workspace: Flags.string({
|
|
74
81
|
char: 'w',
|
|
75
82
|
description: 'Workspace ID (optional if set in profile)',
|
|
@@ -107,8 +114,9 @@ export default class FunctionRun extends BaseCommand {
|
|
|
107
114
|
input = await this.promptForMissing(input, missingRequired);
|
|
108
115
|
}
|
|
109
116
|
}
|
|
110
|
-
// 3) Execute via the meta run endpoint.
|
|
111
|
-
const
|
|
117
|
+
// 3) Execute via the meta run endpoint, scoped to the tenant when one is given.
|
|
118
|
+
const tenantPath = flags.tenant ? `/tenant/${encodeURIComponent(flags.tenant)}` : '';
|
|
119
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}${tenantPath}/function/run`;
|
|
112
120
|
const body = { input, name: functionName };
|
|
113
121
|
if (branch)
|
|
114
122
|
body.branch = branch;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
2
|
+
export default class PolicyCatalogue extends PolicyCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
check: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
3
|
+
import { policyCatalogueSummary, selectCatalogueCheck } from '../../../utils/policy/catalogue.js';
|
|
4
|
+
import { listItems } from '../../../utils/policy/request.js';
|
|
5
|
+
export default class PolicyCatalogue extends PolicyCommand {
|
|
6
|
+
static description = 'List built-in policy checks and their parameter schemas';
|
|
7
|
+
static examples = ['$ xano policy catalogue -o json', '$ xano policy catalogue --check object.auth_required'];
|
|
8
|
+
static flags = {
|
|
9
|
+
...PolicyCommand.policyFlags,
|
|
10
|
+
check: Flags.string({ description: 'Show only this check id (an unknown id names the closest matches)' }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(PolicyCatalogue);
|
|
14
|
+
const { request } = this.policyTarget(flags);
|
|
15
|
+
const result = await request('/check');
|
|
16
|
+
if (flags.check) {
|
|
17
|
+
// `--check` narrows both output modes, so `-o json` stays pipeable for one check too.
|
|
18
|
+
const selected = selectCatalogueCheck(listItems(result), flags.check);
|
|
19
|
+
this.log(flags.output === 'json' ? JSON.stringify(selected, null, 2) : policyCatalogueSummary(selected).join('\n'));
|
|
20
|
+
}
|
|
21
|
+
else if (flags.output === 'json') {
|
|
22
|
+
this.log(JSON.stringify(result, null, 2));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
for (const line of policyCatalogueSummary(listItems(result)))
|
|
26
|
+
this.log(line);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
2
|
+
export default class PolicyCreate extends PolicyCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
goal: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
key: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
param: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
3
|
+
import { policyFromGoal } from '../../../utils/policy/goal.js';
|
|
4
|
+
import { listAllPolicies } from '../../../utils/policy/request.js';
|
|
5
|
+
export default class PolicyCreate extends PolicyCommand {
|
|
6
|
+
static description = 'Create an independent policy from a platform goal';
|
|
7
|
+
static examples = [
|
|
8
|
+
'$ xano policy create --goal endpoints_need_login -b dev',
|
|
9
|
+
`$ xano policy create --goal no_pii_in_responses --key DATA-020 --param 'fields=["email","ssn"]'`,
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
...PolicyCommand.policyFlags,
|
|
13
|
+
...PolicyCommand.publishFlags,
|
|
14
|
+
goal: Flags.string({ description: 'Goal id from policy catalogue -o json', required: true }),
|
|
15
|
+
key: Flags.string({ description: 'New policy key (default: the goal key with a free numeric suffix if taken)' }),
|
|
16
|
+
param: Flags.string({ description: 'Parameter override: N.path=JSON, 1-based rule number; path=JSON for a single-rule goal', multiple: true }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags } = await this.parse(PolicyCreate);
|
|
20
|
+
const target = this.policyTarget(flags);
|
|
21
|
+
const { request } = target;
|
|
22
|
+
const catalogue = await request('/check');
|
|
23
|
+
const goal = catalogue.goals?.find(item => item.id === flags.goal);
|
|
24
|
+
if (!goal)
|
|
25
|
+
this.error(`Unknown goal "${flags.goal}". Run xano policy catalogue -o json to see this instance's goals.`);
|
|
26
|
+
const keys = (await listAllPolicies(request)).map(policy => policy.key);
|
|
27
|
+
// The key is checked against the pattern this instance's catalogue serves.
|
|
28
|
+
const document = policyFromGoal(goal, keys, { key: flags.key, keyRule: catalogue.document?.key, overrides: flags.param });
|
|
29
|
+
const parsed = await this.parseSource(request, { data: document });
|
|
30
|
+
const saved = await request('', 'POST', { data: { source: parsed.source }, ...(flags.message ? { message: flags.message } : {}) });
|
|
31
|
+
if (typeof saved?.id !== 'number')
|
|
32
|
+
this.error('The platform did not return the saved policy. Check xano policy list before retrying.');
|
|
33
|
+
this.logRuleWarnings(parsed);
|
|
34
|
+
if (flags.output === 'json')
|
|
35
|
+
this.log(JSON.stringify(saved, null, 2));
|
|
36
|
+
else
|
|
37
|
+
this.log(`Created ${parsed.policy.key} (Version ${saved.version}) in ${this.where(target)}.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
2
|
+
export default class PolicyDelete extends PolicyCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
policy: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
/** `workspace push` is additive, so deleting a policy file leaves the policy in place; this removes it. */
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
3
|
+
import { confirm } from '../../../utils/multidoc-push.js';
|
|
4
|
+
import { listAllPolicies, sameKey } from '../../../utils/policy/request.js';
|
|
5
|
+
export default class PolicyDelete extends PolicyCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
policy: Args.string({
|
|
8
|
+
description: 'Policy key (AUTH-001) or ID to delete',
|
|
9
|
+
required: true,
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
12
|
+
static description = 'Delete a policy from a branch; its Version History is kept. Deleting an active, mandatory policy also needs the workspace:policy update permission.';
|
|
13
|
+
static examples = [
|
|
14
|
+
`$ xano policy delete TMP-DX-001
|
|
15
|
+
Delete policy TMP-DX-001 (ID: 922, Version 2) from workspace 3? Its Version History is kept. (y/N) y
|
|
16
|
+
Deleted policy TMP-DX-001 (ID: 922) from workspace 3.
|
|
17
|
+
`,
|
|
18
|
+
'$ xano policy delete 922 --force',
|
|
19
|
+
'$ xano policy delete TMP-DX-001 -w 3 --force -o json',
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
...PolicyCommand.policyFlags,
|
|
23
|
+
force: Flags.boolean({
|
|
24
|
+
char: 'f',
|
|
25
|
+
default: false,
|
|
26
|
+
description: '[IMPORTANT] NEVER run without explicit user confirmation. Skips the confirmation prompt.',
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
/** `workspace push` is additive, so deleting a policy file leaves the policy in place; this removes it. */
|
|
30
|
+
async run() {
|
|
31
|
+
const { args, flags } = await this.parse(PolicyDelete);
|
|
32
|
+
const target = this.policyTarget(flags);
|
|
33
|
+
const wanted = args.policy.trim();
|
|
34
|
+
if (!wanted)
|
|
35
|
+
this.error('Provide the policy key or ID to delete.');
|
|
36
|
+
const policies = await listAllPolicies(target.request);
|
|
37
|
+
const matched = policies.find((policy) => sameKey(policy.key, wanted))
|
|
38
|
+
?? (/^\d+$/.test(wanted) ? policies.find((policy) => policy.id === Number(wanted)) : undefined);
|
|
39
|
+
if (!matched) {
|
|
40
|
+
const known = policies.map((policy) => policy.key).sort();
|
|
41
|
+
this.error(`No policy "${wanted}" on ${this.where(target)}.${known.length > 0 ? ` This branch has: ${known.join(', ')}.` : ' This branch has no policies.'}`);
|
|
42
|
+
}
|
|
43
|
+
if (!flags.force) {
|
|
44
|
+
// Nobody can answer the prompt: refuse rather than report a cancel as success.
|
|
45
|
+
if (!process.stdin.isTTY)
|
|
46
|
+
this.error('Non-interactive environment detected. Use --force to skip confirmation.');
|
|
47
|
+
const confirmed = await confirm(`Delete policy ${matched.key} (ID: ${matched.id}, Version ${matched.version}) from ${this.where(target)}? Its Version History is kept.`);
|
|
48
|
+
if (!confirmed) {
|
|
49
|
+
this.log('Deletion cancelled.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// The policy as listed is what the prompt named: a delete of a policy changed since is refused (`policy_stale`).
|
|
54
|
+
const staleCheck = matched.updated_at ? { last_updated_at: String(matched.updated_at) } : {};
|
|
55
|
+
await target.request(`/${matched.id}`, 'DELETE', undefined, staleCheck);
|
|
56
|
+
if (flags.output === 'json')
|
|
57
|
+
this.log(JSON.stringify({ deleted: true, id: matched.id, key: matched.key }, null, 2));
|
|
58
|
+
else
|
|
59
|
+
this.log(`Deleted policy ${matched.key} (ID: ${matched.id}) from ${this.where(target)}.`);
|
|
60
|
+
}
|
|
61
|
+
}
|