@xano/cli 1.2.0 → 1.3.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +381 -4
- package/dist/base-command.d.ts +54 -36
- package/dist/base-command.js +246 -169
- package/dist/commands/auth/index.d.ts +1 -1
- package/dist/commands/auth/index.js +7 -7
- package/dist/commands/branch/create/index.d.ts +1 -1
- package/dist/commands/branch/create/index.js +1 -1
- 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/create/index.d.ts +18 -0
- package/dist/commands/ephemeral/create/index.js +103 -0
- package/dist/commands/ephemeral/delete/index.d.ts +18 -0
- package/dist/commands/ephemeral/delete/index.js +99 -0
- package/dist/commands/ephemeral/edit/index.d.ts +18 -0
- package/dist/commands/ephemeral/edit/index.js +109 -0
- package/dist/commands/ephemeral/get/index.d.ts +21 -0
- package/dist/commands/ephemeral/get/index.js +125 -0
- package/dist/commands/ephemeral/impersonate/index.d.ts +20 -0
- package/dist/commands/ephemeral/impersonate/index.js +120 -0
- package/dist/commands/ephemeral/list/index.d.ts +19 -0
- package/dist/commands/ephemeral/list/index.js +123 -0
- package/dist/commands/ephemeral/pull/index.d.ts +19 -0
- package/dist/commands/ephemeral/pull/index.js +163 -0
- package/dist/commands/ephemeral/push/index.d.ts +35 -0
- package/dist/commands/ephemeral/push/index.js +228 -0
- package/dist/commands/ephemeral/static_host/build/delete/index.d.ts +20 -0
- package/dist/commands/ephemeral/static_host/build/delete/index.js +121 -0
- package/dist/commands/ephemeral/static_host/build/get/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/build/get/index.js +137 -0
- package/dist/commands/ephemeral/static_host/build/list/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/build/list/index.js +137 -0
- package/dist/commands/ephemeral/static_host/build/pull/index.d.ts +53 -0
- package/dist/commands/ephemeral/static_host/build/pull/index.js +308 -0
- package/dist/commands/ephemeral/static_host/build/push/index.d.ts +24 -0
- package/dist/commands/ephemeral/static_host/build/push/index.js +250 -0
- package/dist/commands/ephemeral/static_host/create/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/create/index.js +92 -0
- package/dist/commands/ephemeral/static_host/deploy/index.d.ts +19 -0
- package/dist/commands/ephemeral/static_host/deploy/index.js +112 -0
- package/dist/commands/ephemeral/static_host/edit/index.d.ts +24 -0
- package/dist/commands/ephemeral/static_host/edit/index.js +158 -0
- package/dist/commands/ephemeral/static_host/get/index.d.ts +19 -0
- package/dist/commands/ephemeral/static_host/get/index.js +101 -0
- package/dist/commands/ephemeral/static_host/list/index.d.ts +17 -0
- package/dist/commands/ephemeral/static_host/list/index.js +132 -0
- package/dist/commands/flatten/index.d.ts +34 -0
- package/dist/commands/flatten/index.js +140 -0
- package/dist/commands/function/create/index.d.ts +15 -0
- package/dist/commands/function/create/index.js +41 -1
- package/dist/commands/function/edit/index.d.ts +15 -0
- package/dist/commands/function/edit/index.js +66 -32
- package/dist/commands/function/get/index.js +2 -3
- package/dist/commands/function/run/index.d.ts +1 -0
- package/dist/commands/function/run/index.js +19 -6
- 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/profile/create/index.d.ts +1 -1
- package/dist/commands/profile/create/index.js +7 -7
- package/dist/commands/profile/me/index.js +1 -1
- package/dist/commands/profile/use/index.js +0 -4
- package/dist/commands/profile/wizard/index.d.ts +1 -1
- package/dist/commands/profile/wizard/index.js +12 -10
- package/dist/commands/profile/workspace/set/index.js +1 -1
- 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/edit/index.js +2 -2
- package/dist/commands/release/export/index.js +1 -2
- package/dist/commands/release/import/index.js +6 -2
- package/dist/commands/release/list/index.d.ts +7 -0
- package/dist/commands/release/list/index.js +54 -10
- package/dist/commands/release/pull/index.d.ts +0 -6
- package/dist/commands/release/pull/index.js +13 -99
- package/dist/commands/release/push/index.js +21 -3
- package/dist/commands/sandbox/env/get_all/index.js +1 -1
- package/dist/commands/sandbox/env/set_all/index.js +1 -1
- package/dist/commands/sandbox/get/index.js +1 -1
- package/dist/commands/sandbox/license/get/index.js +1 -1
- package/dist/commands/sandbox/license/set/index.js +1 -1
- package/dist/commands/sandbox/pull/index.d.ts +0 -1
- package/dist/commands/sandbox/pull/index.js +13 -81
- package/dist/commands/sandbox/push/index.d.ts +1 -1
- package/dist/commands/sandbox/push/index.js +11 -6
- package/dist/commands/skills/pull/index.d.ts +30 -0
- package/dist/commands/skills/pull/index.js +118 -0
- package/dist/commands/static_host/build/create/index.d.ts +1 -1
- package/dist/commands/static_host/build/create/index.js +2 -2
- package/dist/commands/static_host/build/pull/index.js +1 -1
- package/dist/commands/static_host/build/push/index.js +1 -1
- package/dist/commands/static_host/edit/index.js +1 -1
- package/dist/commands/tenant/backup/export/index.js +1 -2
- package/dist/commands/tenant/backup/import/index.js +1 -1
- package/dist/commands/tenant/cluster/create/index.js +1 -1
- package/dist/commands/tenant/cluster/license/get/index.js +1 -1
- package/dist/commands/tenant/cluster/license/set/index.js +1 -1
- package/dist/commands/tenant/create/index.d.ts +4 -1
- package/dist/commands/tenant/create/index.js +26 -5
- package/dist/commands/tenant/deploy_platform/index.js +1 -1
- 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 +46 -11
- package/dist/commands/tenant/env/get_all/index.js +1 -1
- package/dist/commands/tenant/env/set_all/index.js +1 -1
- package/dist/commands/tenant/impersonate/index.d.ts +1 -1
- package/dist/commands/tenant/impersonate/index.js +14 -14
- package/dist/commands/tenant/license/get/index.js +1 -1
- package/dist/commands/tenant/license/set/index.js +1 -1
- package/dist/commands/tenant/pull/index.d.ts +0 -6
- package/dist/commands/tenant/pull/index.js +14 -100
- 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/git/pull/index.d.ts +4 -4
- package/dist/commands/workspace/git/pull/index.js +158 -97
- package/dist/commands/workspace/pull/index.d.ts +9 -5
- package/dist/commands/workspace/pull/index.js +83 -106
- package/dist/commands/workspace/push/index.d.ts +19 -0
- package/dist/commands/workspace/push/index.js +124 -5
- package/dist/help.js +4 -2
- package/dist/policy-command.d.ts +90 -0
- package/dist/policy-command.js +94 -0
- package/dist/update-check.d.ts +1 -1
- package/dist/update-check.js +1 -1
- package/dist/utils/document-parser.d.ts +126 -0
- package/dist/utils/document-parser.js +332 -4
- 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 -6
- package/dist/utils/microservice-wait.d.ts +53 -0
- package/dist/utils/microservice-wait.js +84 -0
- package/dist/utils/multidoc-push.d.ts +143 -2
- package/dist/utils/multidoc-push.js +479 -40
- package/dist/utils/paging.d.ts +2 -1
- package/dist/utils/paging.js +0 -1
- 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/dist/utils/reference-checker.js +14 -3
- package/oclif.manifest.json +8538 -3445
- package/package.json +16 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
export default class SandboxLicenseSet extends BaseCommand {
|
|
6
6
|
static description = 'Set/update the license for a sandbox environment';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import snakeCase from 'lodash.snakecase';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
3
5
|
import BaseCommand from '../../../base-command.js';
|
|
4
|
-
import { buildApiGroupFolderResolver, parseDocument } from '../../../utils/document-parser.js';
|
|
6
|
+
import { buildApiGroupFolderResolver, buildChannelServerResolver, parseDocument, resolveDocumentPath, } from '../../../utils/document-parser.js';
|
|
5
7
|
import { fetchKnowledge, writeKnowledge } from '../../../utils/knowledge-sync.js';
|
|
6
|
-
import * as fs from 'node:fs';
|
|
7
|
-
import * as path from 'node:path';
|
|
8
8
|
export default class SandboxPull extends BaseCommand {
|
|
9
9
|
static description = 'Pull documents from your sandbox environment and split into individual files';
|
|
10
10
|
static examples = [
|
|
@@ -92,82 +92,18 @@ Pulled 42 documents from sandbox environment to ./my-sandbox
|
|
|
92
92
|
const outputDir = path.resolve(flags.directory);
|
|
93
93
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
94
94
|
const getApiGroupFolder = buildApiGroupFolderResolver(documents, snakeCase);
|
|
95
|
+
// Resolve a realtime v2 channel path -> owning realtime_server name, so
|
|
96
|
+
// messages can nest under their channel's server (see resolver docs).
|
|
97
|
+
const getChannelServer = buildChannelServerResolver(documents);
|
|
95
98
|
const filenameCounters = new Map();
|
|
96
99
|
let writtenCount = 0;
|
|
97
100
|
for (const doc of documents) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
else if (doc.type === 'workspace_trigger') {
|
|
105
|
-
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
106
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
107
|
-
}
|
|
108
|
-
else if (doc.type === 'error_trigger') {
|
|
109
|
-
// error_trigger → workspace/trigger/{name}.xs (singleton, colocated with workspace triggers)
|
|
110
|
-
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
111
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
112
|
-
}
|
|
113
|
-
else if (doc.type === 'agent') {
|
|
114
|
-
typeDir = path.join(outputDir, 'ai', 'agent');
|
|
115
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
116
|
-
}
|
|
117
|
-
else if (doc.type === 'mcp_server') {
|
|
118
|
-
typeDir = path.join(outputDir, 'ai', 'mcp_server');
|
|
119
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
120
|
-
}
|
|
121
|
-
else if (doc.type === 'tool') {
|
|
122
|
-
typeDir = path.join(outputDir, 'ai', 'tool');
|
|
123
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
124
|
-
}
|
|
125
|
-
else if (doc.type === 'agent_trigger') {
|
|
126
|
-
typeDir = path.join(outputDir, 'ai', 'agent', 'trigger');
|
|
127
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
128
|
-
}
|
|
129
|
-
else if (doc.type === 'mcp_server_trigger') {
|
|
130
|
-
typeDir = path.join(outputDir, 'ai', 'mcp_server', 'trigger');
|
|
131
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
132
|
-
}
|
|
133
|
-
else if (doc.type === 'table_trigger') {
|
|
134
|
-
typeDir = path.join(outputDir, 'table', 'trigger');
|
|
135
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
136
|
-
}
|
|
137
|
-
else if (doc.type === 'realtime_channel') {
|
|
138
|
-
typeDir = path.join(outputDir, 'realtime', 'channel');
|
|
139
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
140
|
-
}
|
|
141
|
-
else if (doc.type === 'realtime_trigger') {
|
|
142
|
-
typeDir = path.join(outputDir, 'realtime', 'trigger');
|
|
143
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
144
|
-
}
|
|
145
|
-
else if (doc.type === 'api_group') {
|
|
146
|
-
const groupFolder = getApiGroupFolder(doc.name);
|
|
147
|
-
typeDir = path.join(outputDir, 'api', groupFolder);
|
|
148
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
149
|
-
}
|
|
150
|
-
else if (doc.type === 'query' && doc.apiGroup) {
|
|
151
|
-
const groupFolder = getApiGroupFolder(doc.apiGroup);
|
|
152
|
-
const nameParts = doc.name.split('/');
|
|
153
|
-
const leafName = nameParts.pop();
|
|
154
|
-
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
155
|
-
typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
|
|
156
|
-
baseName = this.sanitizeFilename(leafName);
|
|
157
|
-
if (doc.verb) {
|
|
158
|
-
baseName = `${baseName}_${doc.verb}`;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
const nameParts = doc.name.split('/');
|
|
163
|
-
const leafName = nameParts.pop();
|
|
164
|
-
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
165
|
-
typeDir = path.join(outputDir, doc.type, ...folderParts);
|
|
166
|
-
baseName = this.sanitizeFilename(leafName);
|
|
167
|
-
if (doc.verb) {
|
|
168
|
-
baseName = `${baseName}_${doc.verb}`;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
101
|
+
const { baseName, typeDir } = resolveDocumentPath(doc, outputDir, {
|
|
102
|
+
getApiGroupFolder,
|
|
103
|
+
getChannelServer,
|
|
104
|
+
join: path.join,
|
|
105
|
+
snakeCase,
|
|
106
|
+
});
|
|
171
107
|
fs.mkdirSync(typeDir, { recursive: true });
|
|
172
108
|
const dirKey = path.relative(outputDir, typeDir);
|
|
173
109
|
if (!filenameCounters.has(dirKey)) {
|
|
@@ -176,8 +112,7 @@ Pulled 42 documents from sandbox environment to ./my-sandbox
|
|
|
176
112
|
const typeCounters = filenameCounters.get(dirKey);
|
|
177
113
|
const count = typeCounters.get(baseName) || 0;
|
|
178
114
|
typeCounters.set(baseName, count + 1);
|
|
179
|
-
|
|
180
|
-
filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
|
|
115
|
+
const filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
|
|
181
116
|
const filePath = path.join(typeDir, filename);
|
|
182
117
|
fs.writeFileSync(filePath, doc.content, 'utf8');
|
|
183
118
|
writtenCount++;
|
|
@@ -194,7 +129,4 @@ Pulled 42 documents from sandbox environment to ./my-sandbox
|
|
|
194
129
|
parts.push(`${knowledgeCount} knowledge file${knowledgeCount === 1 ? '' : 's'}`);
|
|
195
130
|
this.log(`Pulled ${parts.join(' + ')} from sandbox environment to ${flags.directory}`);
|
|
196
131
|
}
|
|
197
|
-
sanitizeFilename(name) {
|
|
198
|
-
return snakeCase(name.replaceAll('"', ''));
|
|
199
|
-
}
|
|
200
132
|
}
|
|
@@ -3,8 +3,8 @@ export default class SandboxPush extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
-
directory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
6
|
delete: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
directory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
8
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -4,6 +4,7 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import open from 'open';
|
|
5
5
|
import BaseCommand from '../../../base-command.js';
|
|
6
6
|
import { executePush } from '../../../utils/multidoc-push.js';
|
|
7
|
+
import { policiesSkippedNotice } from '../../../utils/policy/feedback.js';
|
|
7
8
|
export default class SandboxPush extends BaseCommand {
|
|
8
9
|
static description = '[IMPORTANT] ALWAYS run --dry-run first and show the user the output before pushing. Push local documents to your sandbox environment via multidoc import. By default, only changed files are pushed (partial mode). Use --sync to push all files. Shows a preview of changes before pushing unless --force is specified. Use --dry-run to preview only. Include/exclude glob filters are intentionally not supported on sandbox push — partial pushes can hide deletions during review and lead to data loss when promoted to the workspace. Pushing into a sandbox that currently holds a different workspace will prompt for confirmation; run `xano sandbox reset` first to start clean.';
|
|
9
10
|
static examples = [
|
|
@@ -33,17 +34,17 @@ Push and open sandbox review in the browser
|
|
|
33
34
|
];
|
|
34
35
|
static flags = {
|
|
35
36
|
...BaseCommand.baseFlags,
|
|
37
|
+
delete: Flags.boolean({
|
|
38
|
+
default: false,
|
|
39
|
+
description: 'Delete sandbox objects not included in the push (requires --sync)',
|
|
40
|
+
required: false,
|
|
41
|
+
}),
|
|
36
42
|
directory: Flags.string({
|
|
37
43
|
char: 'd',
|
|
38
44
|
default: '.',
|
|
39
45
|
description: 'Directory containing documents to push (defaults to current directory)',
|
|
40
46
|
required: false,
|
|
41
47
|
}),
|
|
42
|
-
delete: Flags.boolean({
|
|
43
|
-
default: false,
|
|
44
|
-
description: 'Delete sandbox objects not included in the push (requires --sync)',
|
|
45
|
-
required: false,
|
|
46
|
-
}),
|
|
47
48
|
'dry-run': Flags.boolean({
|
|
48
49
|
default: false,
|
|
49
50
|
description: 'Show preview of changes without pushing (exit after preview)',
|
|
@@ -126,7 +127,7 @@ Push and open sandbox review in the browser
|
|
|
126
127
|
truncate: flags.truncate,
|
|
127
128
|
verbose: flags.verbose,
|
|
128
129
|
};
|
|
129
|
-
await executePush({
|
|
130
|
+
const result = await executePush({
|
|
130
131
|
accessToken: profile.access_token,
|
|
131
132
|
branch: '',
|
|
132
133
|
command: this,
|
|
@@ -137,6 +138,10 @@ Push and open sandbox review in the browser
|
|
|
137
138
|
},
|
|
138
139
|
verboseFetch: this.verboseFetch.bind(this),
|
|
139
140
|
}, target, pushFlags);
|
|
141
|
+
// When the platform leaves policy files out, it says so once.
|
|
142
|
+
const skipped = policiesSkippedNotice(result.response ?? result.preview);
|
|
143
|
+
if (skipped)
|
|
144
|
+
this.warn(skipped);
|
|
140
145
|
if (flags.review) {
|
|
141
146
|
await this.openReview(profile.instance_origin, profile.access_token, flags.verbose);
|
|
142
147
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
2
|
+
/**
|
|
3
|
+
* The SKILL.md a coding agent reads: the CLI's own frontmatter (`name` and `description`), the
|
|
4
|
+
* instance's content below it. The CLI always writes the frontmatter itself and drops any block the
|
|
5
|
+
* served content opens with: frontmatter configures the agent (`allowed-tools`, `hooks`, `model`),
|
|
6
|
+
* and a workspace knowledge record may replace the platform skill, so its text must never set it.
|
|
7
|
+
* The description is written as a double-quoted YAML scalar, so a `:` or `#` in it stays a
|
|
8
|
+
* description instead of becoming syntax.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildSkillDocument(description: string, content: string): string;
|
|
11
|
+
export default class SkillsPull extends PolicyCommand {
|
|
12
|
+
static description: string;
|
|
13
|
+
static examples: string[];
|
|
14
|
+
static flags: {
|
|
15
|
+
directory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
};
|
|
23
|
+
/** Any failure exits 1 with its own message: this is not a `policy *` command to name. */
|
|
24
|
+
protected catch(error: Error & {
|
|
25
|
+
oclif?: {
|
|
26
|
+
exit?: number;
|
|
27
|
+
};
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
run(): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import PolicyCommand from '../../../policy-command.js';
|
|
5
|
+
/** The one skill this command installs. The instance generates it from its check catalogue. */
|
|
6
|
+
const SKILL = 'xano-policies';
|
|
7
|
+
/**
|
|
8
|
+
* The served content without a leading YAML frontmatter block, if it opens with one: the lines up
|
|
9
|
+
* to and including the closing `---`, and the blank lines after it.
|
|
10
|
+
*/
|
|
11
|
+
function stripFrontmatter(content) {
|
|
12
|
+
if (!/^---\r?\n/.test(content))
|
|
13
|
+
return content;
|
|
14
|
+
const lines = content.split(/\r?\n/);
|
|
15
|
+
const close = lines.indexOf('---', 1);
|
|
16
|
+
if (close === -1)
|
|
17
|
+
return content;
|
|
18
|
+
let start = close + 1;
|
|
19
|
+
while (start < lines.length && lines[start].trim() === '')
|
|
20
|
+
start++;
|
|
21
|
+
return lines.slice(start).join('\n');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The SKILL.md a coding agent reads: the CLI's own frontmatter (`name` and `description`), the
|
|
25
|
+
* instance's content below it. The CLI always writes the frontmatter itself and drops any block the
|
|
26
|
+
* served content opens with: frontmatter configures the agent (`allowed-tools`, `hooks`, `model`),
|
|
27
|
+
* and a workspace knowledge record may replace the platform skill, so its text must never set it.
|
|
28
|
+
* The description is written as a double-quoted YAML scalar, so a `:` or `#` in it stays a
|
|
29
|
+
* description instead of becoming syntax.
|
|
30
|
+
*/
|
|
31
|
+
export function buildSkillDocument(description, content) {
|
|
32
|
+
// Exactly one trailing newline, whatever the instance sent, so a re-pull is byte-identical.
|
|
33
|
+
const body = `${stripFrontmatter(content).replace(/\n+$/, '')}\n`;
|
|
34
|
+
return `---\nname: ${SKILL}\ndescription: ${JSON.stringify(description)}\n---\n\n${body}`;
|
|
35
|
+
}
|
|
36
|
+
/** True when the path is a symbolic link; false when it is anything else or does not exist. */
|
|
37
|
+
function isSymbolicLink(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
return fs.lstatSync(filePath).isSymbolicLink();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code === 'ENOENT')
|
|
43
|
+
return false;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether the served skill is the workspace's own knowledge record. A stored record has a positive
|
|
49
|
+
* id; the skill the platform generates has none of its own (the platform serves it under a
|
|
50
|
+
* negative sentinel id), so an item without a positive id is the platform's.
|
|
51
|
+
*/
|
|
52
|
+
function isWorkspaceRecord(skill) {
|
|
53
|
+
return typeof skill.id === 'number' && Number.isInteger(skill.id) && skill.id > 0;
|
|
54
|
+
}
|
|
55
|
+
export default class SkillsPull extends PolicyCommand {
|
|
56
|
+
static description = `Install the ${SKILL} agent skill this instance generates
|
|
57
|
+
|
|
58
|
+
Writes <directory>/.claude/skills/${SKILL}/SKILL.md, the project skill folder Claude Code reads, and replaces that file without a backup. A copy installed anywhere else, such as a global ~/.claude/skills/${SKILL} or another agent's skills folder, is not touched: remove it, so the agent does not load it instead.`;
|
|
59
|
+
static examples = [
|
|
60
|
+
`$ xano skills pull
|
|
61
|
+
Wrote .claude/skills/${SKILL}/SKILL.md (${SKILL} skill for workspace 40, branch live)
|
|
62
|
+
`,
|
|
63
|
+
'$ xano skills pull -d ./my-project',
|
|
64
|
+
'$ xano skills pull -b dev -o json',
|
|
65
|
+
];
|
|
66
|
+
static flags = {
|
|
67
|
+
...PolicyCommand.policyFlags,
|
|
68
|
+
directory: Flags.string({
|
|
69
|
+
char: 'd',
|
|
70
|
+
default: '.',
|
|
71
|
+
description: `Project directory whose .claude/skills/${SKILL}/SKILL.md is written (defaults to current directory)`,
|
|
72
|
+
required: false,
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
/** Any failure exits 1 with its own message: this is not a `policy *` command to name. */
|
|
76
|
+
async catch(error) {
|
|
77
|
+
return this.catchAsOperational(error);
|
|
78
|
+
}
|
|
79
|
+
async run() {
|
|
80
|
+
const { flags } = await this.parse(SkillsPull);
|
|
81
|
+
// The route lives beside /policy under the workspace and is gated by the same
|
|
82
|
+
// `workspace:policy` permission, so it answers failures the way the policy commands do.
|
|
83
|
+
const { branch, request, workspace } = this.policyTarget(flags, { label: 'Agent skills', path: '/agent-skills' });
|
|
84
|
+
// `surface=cli` asks for the terminal wording of the skill, not the Studio one.
|
|
85
|
+
const payload = (await request('', 'GET', undefined, { surface: 'cli' }));
|
|
86
|
+
const items = Array.isArray(payload?.knowledge) ? payload.knowledge : [];
|
|
87
|
+
const skill = items.find((item) => typeof item?.name === 'string' && item.name.trim().toLowerCase() === SKILL);
|
|
88
|
+
if (!skill) {
|
|
89
|
+
this.error(`The instance returned no ${SKILL} skill for workspace ${workspace}, branch ${branch || 'live'}.`);
|
|
90
|
+
}
|
|
91
|
+
const document = buildSkillDocument(skill.description ?? '', skill.content ?? '');
|
|
92
|
+
const filePath = path.join(path.resolve(flags.directory), '.claude', 'skills', SKILL, 'SKILL.md');
|
|
93
|
+
// Writing through a symbolic link would overwrite whatever it points at.
|
|
94
|
+
if (isSymbolicLink(filePath)) {
|
|
95
|
+
this.error(`${filePath} is a symbolic link; remove it and run the command again. Nothing was written.`);
|
|
96
|
+
}
|
|
97
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
98
|
+
// The file is regenerated on every pull and no backup is kept.
|
|
99
|
+
fs.writeFileSync(filePath, document, 'utf8');
|
|
100
|
+
const own = isWorkspaceRecord(skill);
|
|
101
|
+
if (flags.output === 'json') {
|
|
102
|
+
this.log(JSON.stringify({
|
|
103
|
+
branch,
|
|
104
|
+
bytes: Buffer.byteLength(document, 'utf8'),
|
|
105
|
+
name: SKILL,
|
|
106
|
+
path: filePath,
|
|
107
|
+
source: own ? 'workspace' : 'platform',
|
|
108
|
+
workspace,
|
|
109
|
+
}, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const relative = path.relative(process.cwd(), filePath);
|
|
113
|
+
const shown = relative && !relative.startsWith('..') ? relative : filePath;
|
|
114
|
+
this.log(`Wrote ${shown} (${SKILL} skill for workspace ${workspace}, branch ${branch || 'live'})`);
|
|
115
|
+
if (own)
|
|
116
|
+
this.log(`This is the workspace's own ${SKILL} knowledge record, which replaces the platform skill.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -6,7 +6,6 @@ import BaseCommand from '../../../../base-command.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function generateBuildName(date?: Date): string;
|
|
8
8
|
export default class StaticHostBuildCreate extends BaseCommand {
|
|
9
|
-
static hidden: boolean;
|
|
10
9
|
static args: {
|
|
11
10
|
static_host: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
11
|
};
|
|
@@ -23,5 +22,6 @@ export default class StaticHostBuildCreate extends BaseCommand {
|
|
|
23
22
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
23
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
25
24
|
};
|
|
25
|
+
static hidden: boolean;
|
|
26
26
|
run(): Promise<void>;
|
|
27
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
const pad2 = (n) => String(n).padStart(2, '0');
|
|
6
6
|
/**
|
|
@@ -18,7 +18,6 @@ export function generateBuildName(date = new Date()) {
|
|
|
18
18
|
return `${y}${mo}${d}-${h}${mi}${s}`;
|
|
19
19
|
}
|
|
20
20
|
export default class StaticHostBuildCreate extends BaseCommand {
|
|
21
|
-
static hidden = true;
|
|
22
21
|
static args = {
|
|
23
22
|
static_host: Args.string({
|
|
24
23
|
description: 'Static Host name',
|
|
@@ -88,6 +87,7 @@ Description: Production build
|
|
|
88
87
|
required: false,
|
|
89
88
|
}),
|
|
90
89
|
};
|
|
90
|
+
static hidden = true;
|
|
91
91
|
async run() {
|
|
92
92
|
this.warn('`static_host build create` is deprecated. Use `static_host build push -f <file>` instead.');
|
|
93
93
|
const { args, flags } = await this.parse(StaticHostBuildCreate);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
import { normalizeListResponse } from '../../../../utils/paging.js';
|
|
6
6
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Flags, ux } from '@oclif/core';
|
|
2
2
|
import archiver from 'archiver';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import BaseCommand from '../../../../base-command.js';
|
|
6
6
|
import { collectStaticHostFiles } from '../../../../utils/static-host-files.js';
|
|
7
7
|
import { generateBuildName } from '../create/index.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../base-command.js';
|
|
5
5
|
export default class StaticHostEdit extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand, { buildUserAgent } from '../../../../base-command.js';
|
|
5
5
|
export default class TenantBackupExport extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -82,7 +82,6 @@ Downloaded backup #10 to ./tenant-t1234-abcd-xyz1-backup-10.tar.gz
|
|
|
82
82
|
const fileStream = fs.createWriteStream(resolvedPath);
|
|
83
83
|
const reader = downloadResponse.body.getReader();
|
|
84
84
|
let totalBytes = 0;
|
|
85
|
-
// eslint-disable-next-line no-constant-condition
|
|
86
85
|
while (true) {
|
|
87
86
|
// eslint-disable-next-line no-await-in-loop
|
|
88
87
|
const { done, value } = await reader.read();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
export default class TenantBackupImport extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
export default class TenantClusterCreate extends BaseCommand {
|
|
6
6
|
static description = 'Create a new tenant cluster';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../../base-command.js';
|
|
5
5
|
export default class TenantClusterLicenseGet extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../../base-command.js';
|
|
5
5
|
export default class TenantClusterLicenseSet extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -6,6 +6,8 @@ export default class TenantCreate extends BaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
+
allow_deploy_bypass: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
allow_quick_deploy: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
11
|
cluster_id: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
12
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
13
|
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -13,8 +15,9 @@ export default class TenantCreate extends BaseCommand {
|
|
|
13
15
|
license: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
16
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
17
|
platform_id: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
-
|
|
18
|
+
required_reviewers: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
19
|
tasks: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
21
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
22
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
23
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as yaml from 'js-yaml';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import BaseCommand from '../../../base-command.js';
|
|
6
6
|
export default class TenantCreate extends BaseCommand {
|
|
7
7
|
static args = {
|
|
@@ -17,9 +17,20 @@ Created tenant: Production (production) - ID: 42
|
|
|
17
17
|
`,
|
|
18
18
|
`$ xano tenant create "Staging" --description "Staging env" --cluster_id 1 --platform_id 1 --type tier2 -o json`,
|
|
19
19
|
`$ xano tenant create "Staging" --type tier2 --cluster_id 1 --license ./license.yaml`,
|
|
20
|
+
`$ xano tenant create "Production" --required_reviewers 1 --allow_deploy_bypass`,
|
|
20
21
|
];
|
|
21
22
|
static flags = {
|
|
22
23
|
...BaseCommand.baseFlags,
|
|
24
|
+
allow_deploy_bypass: Flags.boolean({
|
|
25
|
+
default: false,
|
|
26
|
+
description: 'Allow authorized users to bypass the deploy approval gate on this tenant (via tenant_deploy_request bypass). Has no effect unless --required_reviewers is also set above 0.',
|
|
27
|
+
required: false,
|
|
28
|
+
}),
|
|
29
|
+
allow_quick_deploy: Flags.boolean({
|
|
30
|
+
default: false,
|
|
31
|
+
description: 'Allow this tenant to skip the deploy approval gate entirely for quick deploys, regardless of --required_reviewers.',
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
23
34
|
cluster_id: Flags.integer({
|
|
24
35
|
description: 'Cluster ID to deploy to (required for tier2/tier3)',
|
|
25
36
|
required: false,
|
|
@@ -54,10 +65,9 @@ Created tenant: Production (production) - ID: 42
|
|
|
54
65
|
description: 'Platform ID to use',
|
|
55
66
|
required: false,
|
|
56
67
|
}),
|
|
57
|
-
|
|
58
|
-
default:
|
|
59
|
-
description: '
|
|
60
|
-
options: ['tier1', 'tier2', 'tier3'],
|
|
68
|
+
required_reviewers: Flags.integer({
|
|
69
|
+
default: 0,
|
|
70
|
+
description: 'When greater than 0, that many distinct reviewers must approve a tenant_deploy_request before a release may be deployed to this tenant. 0 (default) means not gated.',
|
|
61
71
|
required: false,
|
|
62
72
|
}),
|
|
63
73
|
tasks: Flags.boolean({
|
|
@@ -65,6 +75,12 @@ Created tenant: Production (production) - ID: 42
|
|
|
65
75
|
default: true,
|
|
66
76
|
description: 'Enable background tasks',
|
|
67
77
|
}),
|
|
78
|
+
type: Flags.string({
|
|
79
|
+
default: 'tier1',
|
|
80
|
+
description: 'Tenant type',
|
|
81
|
+
options: ['tier1', 'tier2', 'tier3'],
|
|
82
|
+
required: false,
|
|
83
|
+
}),
|
|
68
84
|
workspace: Flags.string({
|
|
69
85
|
char: 'w',
|
|
70
86
|
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
@@ -79,6 +95,11 @@ Created tenant: Production (production) - ID: 42
|
|
|
79
95
|
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
80
96
|
}
|
|
81
97
|
const body = {
|
|
98
|
+
deploy_settings: {
|
|
99
|
+
allow_deploy_bypass: flags.allow_deploy_bypass,
|
|
100
|
+
allow_quick_deploy: flags.allow_quick_deploy,
|
|
101
|
+
required_reviewers: flags.required_reviewers,
|
|
102
|
+
},
|
|
82
103
|
display: args.display,
|
|
83
104
|
ingress: flags.ingress,
|
|
84
105
|
license: flags.type,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../base-command.js';
|
|
5
5
|
export default class TenantDeployPlatform extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -6,12 +6,52 @@ export default class TenantDeployRelease extends BaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
+
check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
10
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'override-reason': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
12
|
release: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
13
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
14
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
15
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
16
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
17
|
};
|
|
18
|
+
/** Set once the policy gate refuses the deploy, the one failure that exits 2. */
|
|
19
|
+
private refusedByPolicyGate;
|
|
20
|
+
/** Exit 2 is the policy gate's refusal; every other failure, flag errors included, exits 1. */
|
|
21
|
+
protected catch(error: Error & {
|
|
22
|
+
oclif?: {
|
|
23
|
+
exit?: number;
|
|
24
|
+
};
|
|
25
|
+
}): Promise<void>;
|
|
16
26
|
run(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* `--check`: what the policy gate would decide for this release on this tenant, without deploying.
|
|
29
|
+
* Exits 2 when the deploy would be refused; a preview the platform cannot answer exits 1.
|
|
30
|
+
*/
|
|
31
|
+
private checkPolicyGate;
|
|
32
|
+
/**
|
|
33
|
+
* Does an approved tenant_deploy_request already exist for this (tenant, release)
|
|
34
|
+
* pair? The list endpoint has no release filter, so this fetches approved
|
|
35
|
+
* requests for the tenant and filters client-side by the resolved release name.
|
|
36
|
+
*/
|
|
37
|
+
private hasApprovedDeployRequest;
|
|
38
|
+
/** A deploy the policy gate refused: its verdict, then the refusal and how to proceed, exiting 2. */
|
|
39
|
+
private refuseByPolicyGate;
|
|
40
|
+
/**
|
|
41
|
+
* Does this tenant have the deploy approval gate enabled?
|
|
42
|
+
*
|
|
43
|
+
* Returns false if the tenant has `allow_quick_deploy` set — that flag lets a
|
|
44
|
+
* tenant skip the approval gate entirely for quick deploys, regardless of
|
|
45
|
+
* `required_reviewers`.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately uses `tenant list` rather than `tenant get`: the single-tenant
|
|
48
|
+
* GET route's output whitelist (tenant-detail.yaml) omits `deploy_settings`
|
|
49
|
+
* (which holds `required_reviewers`, `allow_deploy_bypass`, and
|
|
50
|
+
* `allow_quick_deploy`) even though the list route's output includes it
|
|
51
|
+
* — confirmed live against a running instance. Filed as a backend bug; this
|
|
52
|
+
* is the workaround until it's fixed. See TENANT_DEPLOY_REQUEST_TEST.md.
|
|
53
|
+
*/
|
|
54
|
+
private tenantRequiresApproval;
|
|
55
|
+
/** A release policy the tenant's import could not land (its key is held by another policy) is never dropped silently. */
|
|
56
|
+
private warnPoliciesSkipped;
|
|
17
57
|
}
|