@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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantDeployRequestRevision 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
|
+
note: 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
|
+
release: 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,83 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
export default class TenantDeployRequestRevision extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({
|
|
6
|
+
description: 'Tenant deploy request ID',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Link a newer release candidate to an existing tenant deploy request after changes were requested. The previous candidate is kept in the revision history and the request returns to pending. Author only.';
|
|
11
|
+
static examples = [
|
|
12
|
+
`$ xano tenant_deploy_request revision 12 --release v1.2.1 --note "Added the missing migration"
|
|
13
|
+
Deploy request #12 is now: pending
|
|
14
|
+
`,
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...BaseCommand.baseFlags,
|
|
18
|
+
note: Flags.string({
|
|
19
|
+
description: 'Note describing what changed in this revision',
|
|
20
|
+
required: false,
|
|
21
|
+
}),
|
|
22
|
+
output: Flags.string({
|
|
23
|
+
char: 'o',
|
|
24
|
+
default: 'summary',
|
|
25
|
+
description: 'Output format',
|
|
26
|
+
options: ['summary', 'json'],
|
|
27
|
+
required: false,
|
|
28
|
+
}),
|
|
29
|
+
release: Flags.string({
|
|
30
|
+
char: 'r',
|
|
31
|
+
description: 'Name of the new release to review',
|
|
32
|
+
required: true,
|
|
33
|
+
}),
|
|
34
|
+
workspace: Flags.string({
|
|
35
|
+
char: 'w',
|
|
36
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
async run() {
|
|
41
|
+
const { args, flags } = await this.parse(TenantDeployRequestRevision);
|
|
42
|
+
const { profile } = this.resolveProfile(flags);
|
|
43
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
44
|
+
if (!workspaceId) {
|
|
45
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
46
|
+
}
|
|
47
|
+
const body = {
|
|
48
|
+
note: flags.note ?? '',
|
|
49
|
+
release_name: flags.release,
|
|
50
|
+
};
|
|
51
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/approval_request/${args.id}/revision`;
|
|
52
|
+
try {
|
|
53
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
54
|
+
body: JSON.stringify(body),
|
|
55
|
+
headers: {
|
|
56
|
+
accept: 'application/json',
|
|
57
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
},
|
|
60
|
+
method: 'POST',
|
|
61
|
+
}, flags.verbose, profile.access_token);
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const message = await this.parseApiError(response, 'Failed to add revision to tenant deploy request');
|
|
64
|
+
this.error(message);
|
|
65
|
+
}
|
|
66
|
+
const item = (await response.json());
|
|
67
|
+
if (flags.output === 'json') {
|
|
68
|
+
this.log(JSON.stringify(item, null, 2));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.log(`Deploy request #${item.id} is now: ${item.status}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
this.error(`Failed to add revision to tenant deploy request: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.error(`Failed to add revision to tenant deploy request: ${String(error)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantDeployRequestSetStatus 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
|
+
'override-policy': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
reason: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
status: import("@oclif/core/interfaces").OptionFlag<string, 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
|
+
/** Set once the policy gate refuses the deploy an approval triggered, the one failure that exits 2. */
|
|
22
|
+
private refusedByPolicyGate;
|
|
23
|
+
/** Exit 2 is the policy gate's refusal; every other failure, flag errors included, exits 1. */
|
|
24
|
+
protected catch(error: Error & {
|
|
25
|
+
oclif?: {
|
|
26
|
+
exit?: number;
|
|
27
|
+
};
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
run(): Promise<void>;
|
|
30
|
+
/** `--override-policy` overrides the deploy an approve can trigger, and its reason is `--reason`. */
|
|
31
|
+
private checkOverride;
|
|
32
|
+
/**
|
|
33
|
+
* The deploy this approval triggered was refused by the policy gate: its verdict, then the refusal,
|
|
34
|
+
* exiting 2. The vote that completes the approvals is recorded before the deploy, so the request
|
|
35
|
+
* stays approved, and a later deploy of the pair that overrides the gate completes it.
|
|
36
|
+
*/
|
|
37
|
+
private refuseByPolicyGate;
|
|
38
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
import { gateOverrideHint, gateRefusal, gateRefusalOutput, quoted } from '../../../utils/policy/gate.js';
|
|
4
|
+
export default class TenantDeployRequestSetStatus extends BaseCommand {
|
|
5
|
+
static args = {
|
|
6
|
+
id: Args.integer({
|
|
7
|
+
description: 'Tenant deploy request ID',
|
|
8
|
+
required: true,
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
static description = "[CRITICAL] STOP and confirm with the user before running --status approve; if this is the vote that completes the tenant's required reviewer count, it deploys the release to the tenant immediately as part of approving. Change a tenant deploy request's status (draft, submit, approve, request_changes, close, or reopen). That deploy is gated on the release's policy check: a refusal exits 2 and leaves the request approved; --override-policy with --reason proceeds past it.";
|
|
12
|
+
static examples = [
|
|
13
|
+
`$ xano tenant_deploy_request set_status 12 --status submit
|
|
14
|
+
Deploy request #12 is now: pending
|
|
15
|
+
`,
|
|
16
|
+
`$ xano tenant_deploy_request set_status 12 --status approve
|
|
17
|
+
Vote recorded on deploy request #12: 1/2 required reviewers.
|
|
18
|
+
`,
|
|
19
|
+
`$ xano tenant_deploy_request set_status 12 --status approve
|
|
20
|
+
Deploy request #12 is now: approved (2/2 required reviewers)
|
|
21
|
+
Deployed to tenant as part of this approval.
|
|
22
|
+
`,
|
|
23
|
+
`$ xano tenant_deploy_request set_status 12 --status approve --override-policy --reason "Finding accepted; fix tracked in JIRA-12"`,
|
|
24
|
+
`$ xano tenant_deploy_request set_status 12 --status request_changes --reason "Needs a migration note"`,
|
|
25
|
+
`$ xano tenant_deploy_request set_status 12 --status close --reason "Superseded by #14"`,
|
|
26
|
+
`$ xano tenant_deploy_request set_status 12 --status reopen`,
|
|
27
|
+
];
|
|
28
|
+
static flags = {
|
|
29
|
+
...BaseCommand.baseFlags,
|
|
30
|
+
output: Flags.string({
|
|
31
|
+
char: 'o',
|
|
32
|
+
default: 'summary',
|
|
33
|
+
description: 'Output format',
|
|
34
|
+
options: ['summary', 'json'],
|
|
35
|
+
required: false,
|
|
36
|
+
}),
|
|
37
|
+
'override-policy': Flags.boolean({
|
|
38
|
+
default: false,
|
|
39
|
+
description: 'With --status approve: if this vote deploys, proceed past a blocking policy gate, recording --reason as the override reason (needs the workspace:policy update permission; audited)',
|
|
40
|
+
}),
|
|
41
|
+
reason: Flags.string({
|
|
42
|
+
description: 'Reason for this status change. Required for request_changes and close, and with --override-policy.',
|
|
43
|
+
required: false,
|
|
44
|
+
}),
|
|
45
|
+
status: Flags.string({
|
|
46
|
+
description: 'The status transition to apply',
|
|
47
|
+
options: ['draft', 'submit', 'approve', 'request_changes', 'close', 'reopen'],
|
|
48
|
+
required: true,
|
|
49
|
+
}),
|
|
50
|
+
workspace: Flags.string({
|
|
51
|
+
char: 'w',
|
|
52
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
53
|
+
required: false,
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
/** Set once the policy gate refuses the deploy an approval triggered, the one failure that exits 2. */
|
|
57
|
+
refusedByPolicyGate = false;
|
|
58
|
+
/** Exit 2 is the policy gate's refusal; every other failure, flag errors included, exits 1. */
|
|
59
|
+
async catch(error) {
|
|
60
|
+
if (this.refusedByPolicyGate)
|
|
61
|
+
return super.catch(error);
|
|
62
|
+
return this.catchAsOperational(error);
|
|
63
|
+
}
|
|
64
|
+
async run() {
|
|
65
|
+
const { args, flags } = await this.parse(TenantDeployRequestSetStatus);
|
|
66
|
+
const { profile } = this.resolveProfile(flags);
|
|
67
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
68
|
+
if (!workspaceId) {
|
|
69
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
70
|
+
}
|
|
71
|
+
if ((flags.status === 'request_changes' || flags.status === 'close') && !flags.reason) {
|
|
72
|
+
this.error(`--reason is required when --status is "${flags.status}"`);
|
|
73
|
+
}
|
|
74
|
+
if (flags['override-policy'])
|
|
75
|
+
this.checkOverride(flags);
|
|
76
|
+
const body = {
|
|
77
|
+
action: flags.status,
|
|
78
|
+
reason: flags.reason ?? '',
|
|
79
|
+
...(flags['override-policy'] ? { override_policy: true } : {}),
|
|
80
|
+
};
|
|
81
|
+
// A refusal by the policy gate exits 2 with its verdict; the catch that rewords other failures keeps it.
|
|
82
|
+
let refused = null;
|
|
83
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/approval_request/${args.id}/status`;
|
|
84
|
+
try {
|
|
85
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
86
|
+
body: JSON.stringify(body),
|
|
87
|
+
headers: {
|
|
88
|
+
accept: 'application/json',
|
|
89
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
method: 'POST',
|
|
93
|
+
}, flags.verbose, profile.access_token);
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
refused = await gateRefusal(response);
|
|
96
|
+
if (refused)
|
|
97
|
+
this.refuseByPolicyGate(refused, args.id, flags.output === 'json');
|
|
98
|
+
const message = await this.parseApiError(response, 'Failed to change tenant deploy request status');
|
|
99
|
+
this.error(message);
|
|
100
|
+
}
|
|
101
|
+
const item = (await response.json());
|
|
102
|
+
if (flags.output === 'json') {
|
|
103
|
+
this.log(JSON.stringify(item, null, 2));
|
|
104
|
+
}
|
|
105
|
+
else if (flags.status === 'approve' && item.fully_approved === false) {
|
|
106
|
+
// required_reviewers > 1: this vote didn't complete the quorum, so status
|
|
107
|
+
// stays "pending" and nothing was deployed yet.
|
|
108
|
+
this.log(`Vote recorded on deploy request #${item.id}: ${item.approvals_count}/${item.required_reviewers} required reviewers.`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const quorum = item.approvals_count !== undefined && item.required_reviewers !== undefined
|
|
112
|
+
? ` (${item.approvals_count}/${item.required_reviewers} required reviewers)`
|
|
113
|
+
: '';
|
|
114
|
+
this.log(`Deploy request #${item.id} is now: ${item.status}${quorum}`);
|
|
115
|
+
if (flags.status === 'approve') {
|
|
116
|
+
if (item.resolution?.deploy_status === 'deployed') {
|
|
117
|
+
this.log('Deployed to tenant as part of this approval.');
|
|
118
|
+
}
|
|
119
|
+
else if (item.resolution?.deploy_status === 'failed') {
|
|
120
|
+
this.log(`Approved, but the deploy failed: ${item.resolution.deploy_error ?? 'unknown error'}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (refused)
|
|
127
|
+
throw error;
|
|
128
|
+
if (error instanceof Error) {
|
|
129
|
+
this.error(`Failed to change tenant deploy request status: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.error(`Failed to change tenant deploy request status: ${String(error)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** `--override-policy` overrides the deploy an approve can trigger, and its reason is `--reason`. */
|
|
137
|
+
checkOverride(flags) {
|
|
138
|
+
if (flags.status !== 'approve')
|
|
139
|
+
this.error('--override-policy applies only to --status approve, the vote that can deploy.');
|
|
140
|
+
if (!flags.reason?.trim())
|
|
141
|
+
this.error('--override-policy needs --reason: say why this deploy may proceed past the policy gate.');
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The deploy this approval triggered was refused by the policy gate: its verdict, then the refusal,
|
|
145
|
+
* exiting 2. The vote that completes the approvals is recorded before the deploy, so the request
|
|
146
|
+
* stays approved, and a later deploy of the pair that overrides the gate completes it.
|
|
147
|
+
*/
|
|
148
|
+
refuseByPolicyGate(refused, id, json) {
|
|
149
|
+
for (const line of gateRefusalOutput(refused, json))
|
|
150
|
+
this.log(line);
|
|
151
|
+
const approved = refused.answer.gate === 'tenant_approve'
|
|
152
|
+
? '\nYour approval was recorded and the request is approved, but its release was not deployed.'
|
|
153
|
+
: '';
|
|
154
|
+
const release = refused.answer.release?.name?.trim();
|
|
155
|
+
const override = `xano tenant deploy_release <tenant> --release ${release ? quoted(release) : '<release>'} --override-reason "<why>" (\`xano tenant_deploy_request get ${id}\` names the tenant)`;
|
|
156
|
+
this.refusedByPolicyGate = true;
|
|
157
|
+
this.error(`${refused.message}${approved}${gateOverrideHint(refused.answer, override)}`, { exit: 2 });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -13,14 +13,14 @@ export default class GitPull extends BaseCommand {
|
|
|
13
13
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
14
|
};
|
|
15
15
|
run(): Promise<void>;
|
|
16
|
-
/**
|
|
17
|
-
* Recursively collect all .xs files from a directory, sorted for deterministic ordering.
|
|
18
|
-
*/
|
|
19
|
-
private collectFiles;
|
|
20
16
|
/**
|
|
21
17
|
* Clone a git repository using shallow clone.
|
|
22
18
|
*/
|
|
23
19
|
private cloneRepo;
|
|
20
|
+
/**
|
|
21
|
+
* Recursively collect all .xs files from a directory, sorted for deterministic ordering.
|
|
22
|
+
*/
|
|
23
|
+
private collectFiles;
|
|
24
24
|
/**
|
|
25
25
|
* Fetch a GitHub repository via the tarball API for fast download without requiring git.
|
|
26
26
|
*/
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
+
import snakeCase from 'lodash.snakecase';
|
|
2
3
|
import { execSync } from 'node:child_process';
|
|
3
4
|
import * as fs from 'node:fs';
|
|
4
5
|
import * as os from 'node:os';
|
|
5
|
-
import
|
|
6
|
-
import snakeCase from 'lodash.snakecase';
|
|
6
|
+
import path from 'node:path';
|
|
7
7
|
import BaseCommand, { buildUserAgent } from '../../../../base-command.js';
|
|
8
|
-
import { buildApiGroupFolderResolver, parseDocument } from '../../../../utils/document-parser.js';
|
|
8
|
+
import { buildApiGroupFolderResolver, buildChannelServerResolver, channelPathSegments, parseDocument, } from '../../../../utils/document-parser.js';
|
|
9
9
|
export default class GitPull extends BaseCommand {
|
|
10
10
|
static description = 'Pull XanoScript files from a git repository into a local directory';
|
|
11
11
|
static examples = [
|
|
@@ -60,13 +60,7 @@ export default class GitPull extends BaseCommand {
|
|
|
60
60
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'xano-git-pull-'));
|
|
61
61
|
try {
|
|
62
62
|
// Fetch repository contents
|
|
63
|
-
|
|
64
|
-
if (repoInfo.host === 'github') {
|
|
65
|
-
repoRoot = await this.fetchGitHubTarball(repoInfo.owner, repoInfo.repo, ref, token, tempDir, flags.verbose);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
repoRoot = this.cloneRepo(repoInfo.url, ref, token, tempDir, flags.verbose);
|
|
69
|
-
}
|
|
63
|
+
const repoRoot = repoInfo.host === 'github' ? (await this.fetchGitHubTarball(repoInfo.owner, repoInfo.repo, ref, token, tempDir, flags.verbose)) : this.cloneRepo(repoInfo.url, ref, token, tempDir, flags.verbose);
|
|
70
64
|
// Determine source directory (optionally scoped to --path or URL path)
|
|
71
65
|
const sourceDir = subPath ? path.join(repoRoot, subPath) : repoRoot;
|
|
72
66
|
if (!fs.existsSync(sourceDir)) {
|
|
@@ -97,10 +91,13 @@ export default class GitPull extends BaseCommand {
|
|
|
97
91
|
// Write documents to output directory using the same file tree logic as workspace pull
|
|
98
92
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
99
93
|
const getApiGroupFolder = buildApiGroupFolderResolver(documents, snakeCase);
|
|
94
|
+
// Resolve a realtime v2 channel path -> owning realtime_server name, so
|
|
95
|
+
// messages can nest under their channel's server (see resolver docs).
|
|
96
|
+
const getChannelServer = buildChannelServerResolver(documents);
|
|
100
97
|
const filenameCounters = new Map();
|
|
101
98
|
let writtenCount = 0;
|
|
102
99
|
for (const doc of documents) {
|
|
103
|
-
const { baseName, typeDir } = this.resolveOutputPath(outputDir, doc, getApiGroupFolder);
|
|
100
|
+
const { baseName, typeDir } = this.resolveOutputPath(outputDir, doc, getApiGroupFolder, getChannelServer);
|
|
104
101
|
fs.mkdirSync(typeDir, { recursive: true });
|
|
105
102
|
// Track duplicates per directory
|
|
106
103
|
const dirKey = path.relative(outputDir, typeDir);
|
|
@@ -123,23 +120,6 @@ export default class GitPull extends BaseCommand {
|
|
|
123
120
|
fs.rmSync(tempDir, { force: true, recursive: true });
|
|
124
121
|
}
|
|
125
122
|
}
|
|
126
|
-
/**
|
|
127
|
-
* Recursively collect all .xs files from a directory, sorted for deterministic ordering.
|
|
128
|
-
*/
|
|
129
|
-
collectFiles(dir) {
|
|
130
|
-
const files = [];
|
|
131
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
132
|
-
for (const entry of entries) {
|
|
133
|
-
const fullPath = path.join(dir, entry.name);
|
|
134
|
-
if (entry.isDirectory()) {
|
|
135
|
-
files.push(...this.collectFiles(fullPath));
|
|
136
|
-
}
|
|
137
|
-
else if (entry.isFile() && entry.name.endsWith('.xs')) {
|
|
138
|
-
files.push(fullPath);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return files.sort();
|
|
142
|
-
}
|
|
143
123
|
/**
|
|
144
124
|
* Clone a git repository using shallow clone.
|
|
145
125
|
*/
|
|
@@ -168,6 +148,23 @@ export default class GitPull extends BaseCommand {
|
|
|
168
148
|
}
|
|
169
149
|
return cloneTarget;
|
|
170
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Recursively collect all .xs files from a directory, sorted for deterministic ordering.
|
|
153
|
+
*/
|
|
154
|
+
collectFiles(dir) {
|
|
155
|
+
const files = [];
|
|
156
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const fullPath = path.join(dir, entry.name);
|
|
159
|
+
if (entry.isDirectory()) {
|
|
160
|
+
files.push(...this.collectFiles(fullPath));
|
|
161
|
+
}
|
|
162
|
+
else if (entry.isFile() && entry.name.endsWith('.xs')) {
|
|
163
|
+
files.push(fullPath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return files.sort();
|
|
167
|
+
}
|
|
171
168
|
/**
|
|
172
169
|
* Fetch a GitHub repository via the tarball API for fast download without requiring git.
|
|
173
170
|
*/
|
|
@@ -325,78 +322,142 @@ export default class GitPull extends BaseCommand {
|
|
|
325
322
|
* Resolve the output directory and base filename for a parsed document.
|
|
326
323
|
* Uses the same type-to-directory mapping as workspace pull.
|
|
327
324
|
*/
|
|
328
|
-
resolveOutputPath(outputDir, doc, getApiGroupFolder) {
|
|
325
|
+
resolveOutputPath(outputDir, doc, getApiGroupFolder, getChannelServer) {
|
|
329
326
|
let typeDir;
|
|
330
327
|
let baseName;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
337
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
338
|
-
}
|
|
339
|
-
else if (doc.type === 'error_trigger') {
|
|
340
|
-
// error_trigger → workspace/trigger/{name}.xs (singleton, colocated with workspace triggers)
|
|
341
|
-
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
342
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
343
|
-
}
|
|
344
|
-
else if (doc.type === 'agent') {
|
|
345
|
-
typeDir = path.join(outputDir, 'ai', 'agent');
|
|
346
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
347
|
-
}
|
|
348
|
-
else if (doc.type === 'mcp_server') {
|
|
349
|
-
typeDir = path.join(outputDir, 'ai', 'mcp_server');
|
|
350
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
351
|
-
}
|
|
352
|
-
else if (doc.type === 'tool') {
|
|
353
|
-
typeDir = path.join(outputDir, 'ai', 'tool');
|
|
354
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
355
|
-
}
|
|
356
|
-
else if (doc.type === 'agent_trigger') {
|
|
357
|
-
typeDir = path.join(outputDir, 'ai', 'agent', 'trigger');
|
|
358
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
359
|
-
}
|
|
360
|
-
else if (doc.type === 'mcp_server_trigger') {
|
|
361
|
-
typeDir = path.join(outputDir, 'ai', 'mcp_server', 'trigger');
|
|
362
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
363
|
-
}
|
|
364
|
-
else if (doc.type === 'table_trigger') {
|
|
365
|
-
typeDir = path.join(outputDir, 'table', 'trigger');
|
|
366
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
367
|
-
}
|
|
368
|
-
else if (doc.type === 'realtime_channel') {
|
|
369
|
-
typeDir = path.join(outputDir, 'realtime', 'channel');
|
|
370
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
371
|
-
}
|
|
372
|
-
else if (doc.type === 'realtime_trigger') {
|
|
373
|
-
typeDir = path.join(outputDir, 'realtime', 'trigger');
|
|
374
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
375
|
-
}
|
|
376
|
-
else if (doc.type === 'api_group') {
|
|
377
|
-
const groupFolder = getApiGroupFolder(doc.name);
|
|
378
|
-
typeDir = path.join(outputDir, 'api', groupFolder);
|
|
379
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
380
|
-
}
|
|
381
|
-
else if (doc.type === 'query' && doc.apiGroup) {
|
|
382
|
-
const groupFolder = getApiGroupFolder(doc.apiGroup);
|
|
383
|
-
const nameParts = doc.name.split('/');
|
|
384
|
-
const leafName = nameParts.pop();
|
|
385
|
-
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
386
|
-
typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
|
|
387
|
-
baseName = this.sanitizeFilename(leafName);
|
|
388
|
-
if (doc.verb) {
|
|
389
|
-
baseName = `${baseName}_${doc.verb}`;
|
|
328
|
+
switch (doc.type) {
|
|
329
|
+
case 'agent': {
|
|
330
|
+
typeDir = path.join(outputDir, 'ai', 'agent');
|
|
331
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
332
|
+
break;
|
|
390
333
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
334
|
+
case 'agent_trigger': {
|
|
335
|
+
typeDir = path.join(outputDir, 'ai', 'agent', 'trigger');
|
|
336
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case 'channel': {
|
|
340
|
+
// Realtime v2. The channel owns a directory named after itself (the full
|
|
341
|
+
// channel name, snake_cased into a single flat segment — like every other
|
|
342
|
+
// object), and its messages live in a message/ subfolder inside it. It
|
|
343
|
+
// nests under its owning realtime_server (from `server = "..."`):
|
|
344
|
+
// channel "rooms/{room_id}" (server "chat")
|
|
345
|
+
// → realtime/server/chat/channel/rooms_room_id/rooms_room_id.xs
|
|
346
|
+
//
|
|
347
|
+
// The channel name is snake_cased as a whole (rooms/{room_id} →
|
|
348
|
+
// rooms_room_id), so there is no path nesting and no bracket escaping —
|
|
349
|
+
// the on-disk name is a display form; the .xs content keeps the real
|
|
350
|
+
// name. A channel with no resolvable server (e.g. a pre-server export)
|
|
351
|
+
// falls back to the legacy flat channel/<path>/_channel.xs layout.
|
|
352
|
+
if (doc.server) {
|
|
353
|
+
typeDir = path.join(outputDir, 'realtime', 'server', this.sanitizeFilename(doc.server), 'channel', snakeCase(doc.name));
|
|
354
|
+
baseName = snakeCase(doc.name);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
typeDir = path.join(outputDir, 'channel', ...channelPathSegments(doc.name, snakeCase));
|
|
358
|
+
baseName = '_channel';
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case 'error_trigger': {
|
|
363
|
+
// error_trigger → workspace/trigger/{name}.xs (singleton, colocated with workspace triggers)
|
|
364
|
+
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
365
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case 'mcp_server': {
|
|
369
|
+
typeDir = path.join(outputDir, 'ai', 'mcp_server');
|
|
370
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
case 'mcp_server_trigger': {
|
|
374
|
+
typeDir = path.join(outputDir, 'ai', 'mcp_server', 'trigger');
|
|
375
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case 'realtime_channel': {
|
|
379
|
+
typeDir = path.join(outputDir, 'realtime', 'channel');
|
|
380
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
case 'realtime_server': {
|
|
384
|
+
// Realtime v2 — see workspace/pull for the full rationale. Its own
|
|
385
|
+
// document is named after itself, mirroring api_group.
|
|
386
|
+
// realtime_server "chat" → realtime/server/chat/chat.xs
|
|
387
|
+
typeDir = path.join(outputDir, 'realtime', 'server', this.sanitizeFilename(doc.name));
|
|
388
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
case 'realtime_trigger': {
|
|
392
|
+
typeDir = path.join(outputDir, 'realtime', 'trigger');
|
|
393
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case 'table_trigger': {
|
|
397
|
+
typeDir = path.join(outputDir, 'table', 'trigger');
|
|
398
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
case 'tool': {
|
|
402
|
+
typeDir = path.join(outputDir, 'ai', 'tool');
|
|
403
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
case 'workspace': {
|
|
407
|
+
typeDir = path.join(outputDir, 'workspace');
|
|
408
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
case 'workspace_trigger': {
|
|
412
|
+
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
413
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
default: {
|
|
417
|
+
if (doc.type === 'message' && doc.channel) {
|
|
418
|
+
// Realtime v2 message → nests in a message/ subfolder under its channel,
|
|
419
|
+
// under that channel's server. The message names only its channel; the
|
|
420
|
+
// server is resolved by looking that channel up in the same multidoc
|
|
421
|
+
// (two-pass resolve).
|
|
422
|
+
// message "post" on channel "rooms/{room_id}" (server "chat")
|
|
423
|
+
// → realtime/server/chat/channel/rooms_room_id/message/post.xs
|
|
424
|
+
//
|
|
425
|
+
// Nesting matters beyond tidiness: message names are unique only WITHIN
|
|
426
|
+
// a channel, so a flat message/ directory would collide when two
|
|
427
|
+
// channels both define e.g. "say". A channel whose server can't be
|
|
428
|
+
// resolved falls back to the legacy flat channel/ layout.
|
|
429
|
+
const messageServer = getChannelServer(doc.channel);
|
|
430
|
+
typeDir = messageServer
|
|
431
|
+
? path.join(outputDir, 'realtime', 'server', this.sanitizeFilename(messageServer), 'channel', snakeCase(doc.channel), 'message')
|
|
432
|
+
: path.join(outputDir, 'channel', ...channelPathSegments(doc.channel, snakeCase));
|
|
433
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
434
|
+
}
|
|
435
|
+
else if (doc.type === 'api_group') {
|
|
436
|
+
const groupFolder = getApiGroupFolder(doc.name);
|
|
437
|
+
typeDir = path.join(outputDir, 'api', groupFolder);
|
|
438
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
439
|
+
}
|
|
440
|
+
else if (doc.type === 'query' && doc.apiGroup) {
|
|
441
|
+
const groupFolder = getApiGroupFolder(doc.apiGroup);
|
|
442
|
+
const nameParts = doc.name.split('/');
|
|
443
|
+
const leafName = nameParts.pop();
|
|
444
|
+
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
445
|
+
typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
|
|
446
|
+
baseName = this.sanitizeFilename(leafName);
|
|
447
|
+
if (doc.verb) {
|
|
448
|
+
baseName = `${baseName}_${doc.verb}`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const nameParts = doc.name.split('/');
|
|
453
|
+
const leafName = nameParts.pop();
|
|
454
|
+
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
455
|
+
typeDir = path.join(outputDir, doc.type, ...folderParts);
|
|
456
|
+
baseName = this.sanitizeFilename(leafName);
|
|
457
|
+
if (doc.verb) {
|
|
458
|
+
baseName = `${baseName}_${doc.verb}`;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
400
461
|
}
|
|
401
462
|
}
|
|
402
463
|
return { baseName, typeDir };
|
|
@@ -5,8 +5,8 @@ export default class Pull extends BaseCommand {
|
|
|
5
5
|
static flags: {
|
|
6
6
|
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
directory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
-
env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
8
|
draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -15,9 +15,13 @@ export default class Pull extends BaseCommand {
|
|
|
15
15
|
};
|
|
16
16
|
run(): Promise<void>;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Policy keys are unique on the server without regard to case, but an older export or a local file
|
|
19
|
+
* can still differ only in case, and file names are not case-sensitive on every checkout. The
|
|
20
|
+
* policies whose file name differs only in case from another exported policy, or from a local file
|
|
21
|
+
* it would overwrite, are left out with a warning; everything else is written. Local policy files
|
|
22
|
+
* absent from the export are kept. They are reported as stale only when this credential reads the
|
|
23
|
+
* branch's policies, because an export leaves out the policies its credential cannot read, and
|
|
24
|
+
* every policy while the Policies feature is off.
|
|
21
25
|
*/
|
|
22
|
-
private
|
|
26
|
+
private policyClashes;
|
|
23
27
|
}
|