@xano/cli 0.0.95-beta.1 → 0.0.95-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/dist/base-command.d.ts +25 -0
- package/dist/base-command.js +37 -0
- package/dist/commands/auth/index.js +1 -1
- package/dist/commands/profile/create/index.js +2 -2
- package/dist/commands/profile/edit/index.js +2 -2
- package/dist/commands/profile/me/index.js +21 -2
- package/dist/commands/profile/wizard/index.js +3 -3
- package/dist/commands/profile/workspace/set/index.js +1 -1
- package/dist/commands/release/deploy/index.d.ts +17 -0
- package/dist/commands/release/deploy/index.js +107 -0
- package/dist/commands/{ephemeral → sandbox}/env/delete/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/env/delete/index.js +16 -34
- package/dist/commands/{ephemeral → sandbox}/env/get/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/env/get/index.js +11 -29
- package/dist/commands/{ephemeral → sandbox}/env/get_all/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/env/get_all/index.js +15 -33
- package/dist/commands/{ephemeral → sandbox}/env/list/index.d.ts +1 -4
- package/dist/commands/sandbox/env/list/index.js +65 -0
- package/dist/commands/{ephemeral → sandbox}/env/set/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/env/set/index.js +12 -30
- package/dist/commands/{ephemeral → sandbox}/env/set_all/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/env/set_all/index.js +15 -33
- package/dist/commands/{ephemeral → sandbox}/get/index.d.ts +1 -4
- package/dist/commands/sandbox/get/index.js +61 -0
- package/dist/commands/sandbox/impersonate/index.d.ts +5 -0
- package/dist/commands/sandbox/impersonate/index.js +5 -0
- package/dist/commands/{ephemeral → sandbox}/license/get/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/license/get/index.js +15 -33
- package/dist/commands/{ephemeral → sandbox}/license/set/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/license/set/index.js +16 -34
- package/dist/commands/{ephemeral → sandbox}/pull/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/pull/index.js +9 -26
- package/dist/commands/{ephemeral → sandbox}/push/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/push/index.js +11 -28
- package/dist/commands/{ephemeral/delete → sandbox/reset}/index.d.ts +1 -5
- package/dist/commands/sandbox/reset/index.js +69 -0
- package/dist/commands/{ephemeral/impersonate → sandbox/review}/index.d.ts +1 -4
- package/dist/commands/{ephemeral/impersonate → sandbox/review}/index.js +13 -31
- package/dist/commands/{ephemeral/unit_test/run_all → sandbox/unit_test/list}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/unit_test/list/index.js +8 -24
- package/dist/commands/{ephemeral → sandbox}/unit_test/run/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/unit_test/run/index.js +7 -23
- package/dist/commands/{ephemeral/unit_test/list → sandbox/unit_test/run_all}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/unit_test/run_all/index.js +7 -23
- package/dist/commands/{ephemeral/workflow_test/get → sandbox/workflow_test/delete}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/delete/index.js +7 -23
- package/dist/commands/{ephemeral/workflow_test/run → sandbox/workflow_test/get}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/get/index.js +6 -25
- package/dist/commands/{ephemeral/workflow_test/run_all → sandbox/workflow_test/list}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/list/index.js +9 -25
- package/dist/commands/{ephemeral/workflow_test/delete → sandbox/workflow_test/run}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/run/index.js +7 -23
- package/dist/commands/{ephemeral/workflow_test/list → sandbox/workflow_test/run_all}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/run_all/index.js +7 -23
- package/dist/commands/tenant/create/index.d.ts +2 -1
- package/dist/commands/tenant/create/index.js +23 -6
- package/dist/commands/tenant/get/index.js +2 -2
- package/dist/commands/tenant/list/index.js +2 -2
- package/dist/commands/tenant/push/index.js +0 -34
- package/dist/commands/workspace/edit/index.d.ts +1 -0
- package/dist/commands/workspace/edit/index.js +16 -6
- package/dist/commands/workspace/get/index.js +9 -7
- package/dist/commands/workspace/list/index.d.ts +1 -0
- package/dist/commands/workspace/list/index.js +14 -7
- package/dist/commands/workspace/push/index.js +30 -2
- package/oclif.manifest.json +1985 -2354
- package/package.json +8 -8
- package/dist/commands/ephemeral/access/index.d.ts +0 -15
- package/dist/commands/ephemeral/access/index.js +0 -78
- package/dist/commands/ephemeral/create/index.d.ts +0 -17
- package/dist/commands/ephemeral/create/index.js +0 -102
- package/dist/commands/ephemeral/delete/index.js +0 -99
- package/dist/commands/ephemeral/env/list/index.js +0 -83
- package/dist/commands/ephemeral/get/index.js +0 -102
- package/dist/commands/ephemeral/list/index.d.ts +0 -15
- package/dist/commands/ephemeral/list/index.js +0 -109
- package/dist/commands/ephemeral/shared/index.d.ts +0 -15
- package/dist/commands/ephemeral/shared/index.js +0 -108
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class
|
|
4
|
-
static description = 'List workflow tests for
|
|
3
|
+
export default class SandboxWorkflowTestList extends BaseCommand {
|
|
4
|
+
static description = 'List workflow tests for a sandbox environment';
|
|
5
5
|
static examples = [
|
|
6
|
-
`$ xano
|
|
7
|
-
Workflow tests
|
|
6
|
+
`$ xano sandbox workflow-test list
|
|
7
|
+
Workflow tests:
|
|
8
8
|
- my-test (ID: 1)
|
|
9
9
|
`,
|
|
10
|
-
`$ xano
|
|
10
|
+
`$ xano sandbox workflow-test list -o json`,
|
|
11
11
|
];
|
|
12
12
|
static flags = {
|
|
13
13
|
...BaseCommand.baseFlags,
|
|
@@ -23,31 +23,15 @@ Workflow tests for tenant e1a2-b3c4-x5y6:
|
|
|
23
23
|
options: ['summary', 'json'],
|
|
24
24
|
required: false,
|
|
25
25
|
}),
|
|
26
|
-
tenant: Flags.string({
|
|
27
|
-
char: 't',
|
|
28
|
-
description: 'Ephemeral tenant name',
|
|
29
|
-
required: true,
|
|
30
|
-
}),
|
|
31
26
|
};
|
|
32
27
|
async run() {
|
|
33
|
-
const { flags } = await this.parse(
|
|
34
|
-
const
|
|
35
|
-
const credentials = this.loadCredentialsFile();
|
|
36
|
-
if (!credentials || !(profileName in credentials.profiles)) {
|
|
37
|
-
this.error(`Profile '${profileName}' not found.\nCreate a profile using 'xano profile create'`);
|
|
38
|
-
}
|
|
39
|
-
const profile = credentials.profiles[profileName];
|
|
40
|
-
if (!profile.instance_origin) {
|
|
41
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
42
|
-
}
|
|
43
|
-
if (!profile.access_token) {
|
|
44
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
45
|
-
}
|
|
28
|
+
const { flags } = await this.parse(SandboxWorkflowTestList);
|
|
29
|
+
const { profile } = this.resolveProfile(flags);
|
|
46
30
|
const params = new URLSearchParams();
|
|
47
31
|
params.set('per_page', '10000');
|
|
48
32
|
if (flags.branch)
|
|
49
33
|
params.set('branch', flags.branch);
|
|
50
|
-
const apiUrl = `${profile.instance_origin}/api:meta/
|
|
34
|
+
const apiUrl = `${profile.instance_origin}/api:meta/sandbox/workflow_test?${params}`;
|
|
51
35
|
try {
|
|
52
36
|
const response = await this.verboseFetch(apiUrl, {
|
|
53
37
|
headers: {
|
|
@@ -79,7 +63,7 @@ Workflow tests for tenant e1a2-b3c4-x5y6:
|
|
|
79
63
|
this.log('No workflow tests found');
|
|
80
64
|
}
|
|
81
65
|
else {
|
|
82
|
-
this.log(`Workflow tests for
|
|
66
|
+
this.log(`Workflow tests for sandbox environment:`);
|
|
83
67
|
for (const test of tests) {
|
|
84
68
|
this.log(` - ${test.name} (ID: ${test.id})`);
|
|
85
69
|
}
|
package/dist/commands/{ephemeral/workflow_test/delete → sandbox/workflow_test/run}/index.d.ts
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class
|
|
2
|
+
export default class SandboxWorkflowTestRun extends BaseCommand {
|
|
3
3
|
static args: {
|
|
4
4
|
workflow_test_id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
5
|
max?: number;
|
|
@@ -10,7 +10,6 @@ export default class EphemeralWorkflowTestDelete extends BaseCommand {
|
|
|
10
10
|
static examples: string[];
|
|
11
11
|
static flags: {
|
|
12
12
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
-
tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
13
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
14
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
15
|
};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class
|
|
3
|
+
export default class SandboxWorkflowTestRun extends BaseCommand {
|
|
4
4
|
static args = {
|
|
5
5
|
workflow_test_id: Args.integer({
|
|
6
6
|
description: 'ID of the workflow test to run',
|
|
7
7
|
required: true,
|
|
8
8
|
}),
|
|
9
9
|
};
|
|
10
|
-
static description = 'Run a workflow test for
|
|
10
|
+
static description = 'Run a workflow test for a sandbox environment';
|
|
11
11
|
static examples = [
|
|
12
|
-
`$ xano
|
|
12
|
+
`$ xano sandbox workflow-test run 42
|
|
13
13
|
Running workflow test 42...
|
|
14
14
|
Result: PASS (0.25s)
|
|
15
15
|
`,
|
|
16
|
-
`$ xano
|
|
16
|
+
`$ xano sandbox workflow-test run 42 -o json`,
|
|
17
17
|
];
|
|
18
18
|
static flags = {
|
|
19
19
|
...BaseCommand.baseFlags,
|
|
@@ -24,27 +24,11 @@ Result: PASS (0.25s)
|
|
|
24
24
|
options: ['summary', 'json'],
|
|
25
25
|
required: false,
|
|
26
26
|
}),
|
|
27
|
-
tenant: Flags.string({
|
|
28
|
-
char: 't',
|
|
29
|
-
description: 'Ephemeral tenant name',
|
|
30
|
-
required: true,
|
|
31
|
-
}),
|
|
32
27
|
};
|
|
33
28
|
async run() {
|
|
34
|
-
const { args, flags } = await this.parse(
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
if (!credentials || !(profileName in credentials.profiles)) {
|
|
38
|
-
this.error(`Profile '${profileName}' not found.\nCreate a profile using 'xano profile create'`);
|
|
39
|
-
}
|
|
40
|
-
const profile = credentials.profiles[profileName];
|
|
41
|
-
if (!profile.instance_origin) {
|
|
42
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
43
|
-
}
|
|
44
|
-
if (!profile.access_token) {
|
|
45
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
46
|
-
}
|
|
47
|
-
const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${encodeURIComponent(flags.tenant)}/workflow_test/${args.workflow_test_id}/run`;
|
|
29
|
+
const { args, flags } = await this.parse(SandboxWorkflowTestRun);
|
|
30
|
+
const { profile } = this.resolveProfile(flags);
|
|
31
|
+
const apiUrl = `${profile.instance_origin}/api:meta/sandbox/workflow_test/${args.workflow_test_id}/run`;
|
|
48
32
|
try {
|
|
49
33
|
if (flags.output === 'summary') {
|
|
50
34
|
this.log(`Running workflow test ${args.workflow_test_id}...`);
|
package/dist/commands/{ephemeral/workflow_test/list → sandbox/workflow_test/run_all}/index.d.ts
RENAMED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class
|
|
2
|
+
export default class SandboxWorkflowTestRunAll extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
-
tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
8
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
9
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
10
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class
|
|
4
|
-
static description = 'Run all workflow tests for
|
|
3
|
+
export default class SandboxWorkflowTestRunAll extends BaseCommand {
|
|
4
|
+
static description = 'Run all workflow tests for a sandbox environment';
|
|
5
5
|
static examples = [
|
|
6
|
-
`$ xano
|
|
6
|
+
`$ xano sandbox workflow-test run-all
|
|
7
7
|
Running 3 workflow tests...
|
|
8
8
|
|
|
9
9
|
PASS my-test (0.25s)
|
|
@@ -12,7 +12,7 @@ FAIL data-check (0.10s)
|
|
|
12
12
|
|
|
13
13
|
Results: 2 passed, 1 failed
|
|
14
14
|
`,
|
|
15
|
-
`$ xano
|
|
15
|
+
`$ xano sandbox workflow-test run-all -o json`,
|
|
16
16
|
];
|
|
17
17
|
static flags = {
|
|
18
18
|
...BaseCommand.baseFlags,
|
|
@@ -28,27 +28,11 @@ Results: 2 passed, 1 failed
|
|
|
28
28
|
options: ['summary', 'json'],
|
|
29
29
|
required: false,
|
|
30
30
|
}),
|
|
31
|
-
tenant: Flags.string({
|
|
32
|
-
char: 't',
|
|
33
|
-
description: 'Ephemeral tenant name',
|
|
34
|
-
required: true,
|
|
35
|
-
}),
|
|
36
31
|
};
|
|
37
32
|
async run() {
|
|
38
|
-
const { flags } = await this.parse(
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
if (!credentials || !(profileName in credentials.profiles)) {
|
|
42
|
-
this.error(`Profile '${profileName}' not found.\nCreate a profile using 'xano profile create'`);
|
|
43
|
-
}
|
|
44
|
-
const profile = credentials.profiles[profileName];
|
|
45
|
-
if (!profile.instance_origin) {
|
|
46
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
47
|
-
}
|
|
48
|
-
if (!profile.access_token) {
|
|
49
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
50
|
-
}
|
|
51
|
-
const baseUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${encodeURIComponent(flags.tenant)}/workflow_test`;
|
|
33
|
+
const { flags } = await this.parse(SandboxWorkflowTestRunAll);
|
|
34
|
+
const { profile } = this.resolveProfile(flags);
|
|
35
|
+
const baseUrl = `${profile.instance_origin}/api:meta/sandbox/workflow_test`;
|
|
52
36
|
try {
|
|
53
37
|
// Step 1: List all workflow tests
|
|
54
38
|
const listParams = new URLSearchParams();
|
|
@@ -10,9 +10,10 @@ export default class TenantCreate extends BaseCommand {
|
|
|
10
10
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
ingress: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
-
license: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
license: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
14
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
15
|
platform_id: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
17
|
tasks: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
18
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
19
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -16,7 +16,8 @@ export default class TenantCreate extends BaseCommand {
|
|
|
16
16
|
`$ xano tenant create "Production"
|
|
17
17
|
Created tenant: Production (production) - ID: 42
|
|
18
18
|
`,
|
|
19
|
-
`$ xano tenant create "Staging" --description "Staging env" --cluster_id 1 --platform_id 1 --
|
|
19
|
+
`$ xano tenant create "Staging" --description "Staging env" --cluster_id 1 --platform_id 1 --type tier2 -o json`,
|
|
20
|
+
`$ xano tenant create "Staging" --type tier2 --cluster_id 1 --license ./license.yaml`,
|
|
20
21
|
];
|
|
21
22
|
static flags = {
|
|
22
23
|
...BaseCommand.baseFlags,
|
|
@@ -39,9 +40,8 @@ Created tenant: Production (production) - ID: 42
|
|
|
39
40
|
description: 'Enable ingress',
|
|
40
41
|
}),
|
|
41
42
|
license: Flags.string({
|
|
42
|
-
|
|
43
|
-
description: '
|
|
44
|
-
options: ['tier1', 'tier2', 'tier3'],
|
|
43
|
+
char: 'l',
|
|
44
|
+
description: 'Path to a license override file to apply during creation',
|
|
45
45
|
required: false,
|
|
46
46
|
}),
|
|
47
47
|
output: Flags.string({
|
|
@@ -55,6 +55,12 @@ Created tenant: Production (production) - ID: 42
|
|
|
55
55
|
description: 'Platform ID to use',
|
|
56
56
|
required: false,
|
|
57
57
|
}),
|
|
58
|
+
type: Flags.string({
|
|
59
|
+
default: 'tier1',
|
|
60
|
+
description: 'Tenant type',
|
|
61
|
+
options: ['tier1', 'tier2', 'tier3'],
|
|
62
|
+
required: false,
|
|
63
|
+
}),
|
|
58
64
|
tasks: Flags.boolean({
|
|
59
65
|
allowNo: true,
|
|
60
66
|
default: true,
|
|
@@ -88,7 +94,7 @@ Created tenant: Production (production) - ID: 42
|
|
|
88
94
|
const body = {
|
|
89
95
|
display: args.display,
|
|
90
96
|
ingress: flags.ingress,
|
|
91
|
-
license: flags.
|
|
97
|
+
license: flags.type,
|
|
92
98
|
tag: [],
|
|
93
99
|
tasks: flags.tasks,
|
|
94
100
|
};
|
|
@@ -102,7 +108,16 @@ Created tenant: Production (production) - ID: 42
|
|
|
102
108
|
body.platform_id = flags.platform_id;
|
|
103
109
|
if (flags.domain)
|
|
104
110
|
body.domain = flags.domain;
|
|
105
|
-
if (flags.license
|
|
111
|
+
if (flags.license) {
|
|
112
|
+
const licensePath = path.resolve(flags.license);
|
|
113
|
+
if (!fs.existsSync(licensePath)) {
|
|
114
|
+
this.error(`License file not found: ${licensePath}`);
|
|
115
|
+
}
|
|
116
|
+
const licenseContent = fs.readFileSync(licensePath, 'utf8');
|
|
117
|
+
body.license_overrides = yaml.load(licenseContent);
|
|
118
|
+
}
|
|
119
|
+
const effectiveType = flags.cluster_id ? 'tier3' : flags.type;
|
|
120
|
+
if (effectiveType === 'tier2' || effectiveType === 'tier3') {
|
|
106
121
|
this.warn('This may take a few minutes. Please be patient.');
|
|
107
122
|
}
|
|
108
123
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant`;
|
|
@@ -129,6 +144,8 @@ Created tenant: Production (production) - ID: 42
|
|
|
129
144
|
if (tenant.state) {
|
|
130
145
|
this.log(` State: ${tenant.state}`);
|
|
131
146
|
}
|
|
147
|
+
if (flags.license)
|
|
148
|
+
this.log(` License: applied`);
|
|
132
149
|
}
|
|
133
150
|
}
|
|
134
151
|
catch (error) {
|
|
@@ -99,8 +99,8 @@ Tenant: My Tenant (my-tenant)
|
|
|
99
99
|
this.log(` Tasks: ${tenant.tasks}`);
|
|
100
100
|
if (tenant.ingress !== undefined)
|
|
101
101
|
this.log(` Ingress: ${tenant.ingress}`);
|
|
102
|
-
if (tenant.
|
|
103
|
-
this.log(`
|
|
102
|
+
if (tenant.type)
|
|
103
|
+
this.log(` Type: ${tenant.type}`);
|
|
104
104
|
if (tenant.deployed_at) {
|
|
105
105
|
const d = new Date(tenant.deployed_at);
|
|
106
106
|
const deployedDate = Number.isNaN(d.getTime()) ? tenant.deployed_at : d.toISOString().split('T')[0];
|
|
@@ -92,8 +92,8 @@ Tenants in workspace 5:
|
|
|
92
92
|
for (const tenant of tenants) {
|
|
93
93
|
const state = tenant.state ? ` [${tenant.state}]` : '';
|
|
94
94
|
const license = tenant.license ? ` - ${tenant.license}` : '';
|
|
95
|
-
const
|
|
96
|
-
this.log(` - ${tenant.display || tenant.name} (${tenant.name})${state}${license}${
|
|
95
|
+
const typeLabel = tenant.type && tenant.type !== 'standard' ? ` [${tenant.type}]` : '';
|
|
96
|
+
this.log(` - ${tenant.display || tenant.name} (${tenant.name})${state}${license}${typeLabel}`);
|
|
97
97
|
if (tenant.cluster?.name)
|
|
98
98
|
this.log(` Cluster: ${tenant.cluster.name}`);
|
|
99
99
|
const releaseName = typeof tenant.release === 'string' ? tenant.release : tenant.release?.name;
|
|
@@ -100,40 +100,6 @@ Truncate all table records before importing
|
|
|
100
100
|
` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
|
|
101
101
|
}
|
|
102
102
|
const tenantName = flags.tenant;
|
|
103
|
-
// Fetch tenant details and verify it's ephemeral
|
|
104
|
-
const tenantApiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
|
|
105
|
-
try {
|
|
106
|
-
const tenantResponse = await this.verboseFetch(tenantApiUrl, {
|
|
107
|
-
headers: {
|
|
108
|
-
accept: 'application/json',
|
|
109
|
-
Authorization: `Bearer ${profile.access_token}`,
|
|
110
|
-
},
|
|
111
|
-
method: 'GET',
|
|
112
|
-
}, flags.verbose, profile.access_token);
|
|
113
|
-
if (!tenantResponse.ok) {
|
|
114
|
-
const errorText = await tenantResponse.text();
|
|
115
|
-
this.error(`Failed to fetch tenant '${tenantName}' (${tenantResponse.status}): ${errorText}`);
|
|
116
|
-
}
|
|
117
|
-
const tenantData = (await tenantResponse.json());
|
|
118
|
-
if (!tenantData.ephemeral) {
|
|
119
|
-
this.error(`Tenant '${tenantName}' is not ephemeral. Push is only allowed for ephemeral tenants.\n` +
|
|
120
|
-
`Create an ephemeral tenant with: xano ephemeral create "name"`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
if (error instanceof Error && error.message.includes('is not ephemeral')) {
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
if (error instanceof Error && error.message.includes('Failed to fetch tenant')) {
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
if (error instanceof Error) {
|
|
131
|
-
this.error(`Failed to verify tenant: ${error.message}`);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
this.error(`Failed to verify tenant: ${String(error)}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
103
|
// Resolve the input directory
|
|
138
104
|
const inputDir = path.resolve(args.directory);
|
|
139
105
|
if (!fs.existsSync(inputDir)) {
|
|
@@ -9,6 +9,7 @@ export default class WorkspaceEdit extends BaseCommand {
|
|
|
9
9
|
static description: string;
|
|
10
10
|
static examples: string[];
|
|
11
11
|
static flags: {
|
|
12
|
+
'allow-push': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -35,6 +35,11 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
35
35
|
];
|
|
36
36
|
static flags = {
|
|
37
37
|
...BaseCommand.baseFlags,
|
|
38
|
+
'allow-push': Flags.boolean({
|
|
39
|
+
allowNo: true,
|
|
40
|
+
description: 'Enable or disable direct CLI push to this workspace (not applicable on Free plan)',
|
|
41
|
+
required: false,
|
|
42
|
+
}),
|
|
38
43
|
description: Flags.string({
|
|
39
44
|
char: 'd',
|
|
40
45
|
description: 'New description for the workspace',
|
|
@@ -102,9 +107,12 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
102
107
|
if (flags['require-token'] !== undefined) {
|
|
103
108
|
body.documentation = { require_token: flags['require-token'] };
|
|
104
109
|
}
|
|
110
|
+
if (flags['allow-push'] !== undefined) {
|
|
111
|
+
body.preferences = { allow_push: flags['allow-push'] };
|
|
112
|
+
}
|
|
105
113
|
// Check if at least one field is being updated
|
|
106
114
|
if (Object.keys(body).length === 0) {
|
|
107
|
-
this.error('No fields specified to update. Use --name, --description, --swagger,
|
|
115
|
+
this.error('No fields specified to update. Use --name, --description, --swagger, --require-token, or --allow-push flags.\n' +
|
|
108
116
|
'Example: xano workspace edit 123 --name "new-name"');
|
|
109
117
|
}
|
|
110
118
|
// Construct the API URL
|
|
@@ -114,8 +122,8 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
114
122
|
const response = await this.verboseFetch(apiUrl, {
|
|
115
123
|
body: JSON.stringify(body),
|
|
116
124
|
headers: {
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
accept: 'application/json',
|
|
126
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
119
127
|
'Content-Type': 'application/json',
|
|
120
128
|
},
|
|
121
129
|
method: 'PUT',
|
|
@@ -124,7 +132,7 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
124
132
|
const errorText = await response.text();
|
|
125
133
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
126
134
|
}
|
|
127
|
-
const workspace = await response.json();
|
|
135
|
+
const workspace = (await response.json());
|
|
128
136
|
// Output results
|
|
129
137
|
if (flags.output === 'json') {
|
|
130
138
|
this.log(JSON.stringify(workspace, null, 2));
|
|
@@ -141,6 +149,9 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
141
149
|
if (workspace.documentation?.require_token !== undefined) {
|
|
142
150
|
this.log(` Require Token: ${workspace.documentation.require_token}`);
|
|
143
151
|
}
|
|
152
|
+
if (workspace.preferences?.allow_push !== undefined) {
|
|
153
|
+
this.log(` Allow Push: ${workspace.preferences.allow_push}`);
|
|
154
|
+
}
|
|
144
155
|
}
|
|
145
156
|
}
|
|
146
157
|
catch (error) {
|
|
@@ -157,8 +168,7 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
157
168
|
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
158
169
|
// Check if credentials file exists
|
|
159
170
|
if (!fs.existsSync(credentialsPath)) {
|
|
160
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
161
|
-
`Create a profile using 'xano profile create'`);
|
|
171
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
162
172
|
}
|
|
163
173
|
// Read credentials file
|
|
164
174
|
try {
|
|
@@ -73,8 +73,8 @@ Workspace: my-workspace (ID: 123)
|
|
|
73
73
|
try {
|
|
74
74
|
const response = await this.verboseFetch(apiUrl, {
|
|
75
75
|
headers: {
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
accept: 'application/json',
|
|
77
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
78
78
|
},
|
|
79
79
|
method: 'GET',
|
|
80
80
|
}, flags.verbose, profile.access_token);
|
|
@@ -82,7 +82,7 @@ Workspace: my-workspace (ID: 123)
|
|
|
82
82
|
const errorText = await response.text();
|
|
83
83
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
84
84
|
}
|
|
85
|
-
const workspace = await response.json();
|
|
85
|
+
const workspace = (await response.json());
|
|
86
86
|
// Output results
|
|
87
87
|
if (flags.output === 'json') {
|
|
88
88
|
this.log(JSON.stringify(workspace, null, 2));
|
|
@@ -94,13 +94,16 @@ Workspace: my-workspace (ID: 123)
|
|
|
94
94
|
this.log(` Description: ${workspace.description}`);
|
|
95
95
|
}
|
|
96
96
|
if (workspace.created_at) {
|
|
97
|
-
const createdDate = new Date(workspace.created_at
|
|
97
|
+
const createdDate = new Date(workspace.created_at).toISOString().split('T')[0];
|
|
98
98
|
this.log(` Created: ${createdDate}`);
|
|
99
99
|
}
|
|
100
100
|
if (workspace.updated_at) {
|
|
101
|
-
const updatedDate = new Date(workspace.updated_at
|
|
101
|
+
const updatedDate = new Date(workspace.updated_at).toISOString().split('T')[0];
|
|
102
102
|
this.log(` Updated: ${updatedDate}`);
|
|
103
103
|
}
|
|
104
|
+
if (workspace.preferences?.allow_push !== undefined) {
|
|
105
|
+
this.log(` Allow Push: ${workspace.preferences.allow_push}`);
|
|
106
|
+
}
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
catch (error) {
|
|
@@ -117,8 +120,7 @@ Workspace: my-workspace (ID: 123)
|
|
|
117
120
|
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
118
121
|
// Check if credentials file exists
|
|
119
122
|
if (!fs.existsSync(credentialsPath)) {
|
|
120
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
121
|
-
`Create a profile using 'xano profile create'`);
|
|
123
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
122
124
|
}
|
|
123
125
|
// Read credentials file
|
|
124
126
|
try {
|
|
@@ -3,6 +3,7 @@ export default class WorkspaceList extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
+
latest: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
7
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
8
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -45,6 +45,10 @@ Available workspaces:
|
|
|
45
45
|
];
|
|
46
46
|
static flags = {
|
|
47
47
|
...BaseCommand.baseFlags,
|
|
48
|
+
latest: Flags.boolean({
|
|
49
|
+
default: false,
|
|
50
|
+
description: 'Sort by newest first (descending ID)',
|
|
51
|
+
}),
|
|
48
52
|
output: Flags.string({
|
|
49
53
|
char: 'o',
|
|
50
54
|
default: 'summary',
|
|
@@ -78,8 +82,8 @@ Available workspaces:
|
|
|
78
82
|
try {
|
|
79
83
|
const response = await this.verboseFetch(apiUrl, {
|
|
80
84
|
headers: {
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
accept: 'application/json',
|
|
86
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
83
87
|
},
|
|
84
88
|
method: 'GET',
|
|
85
89
|
}, flags.verbose, profile.access_token);
|
|
@@ -87,7 +91,7 @@ Available workspaces:
|
|
|
87
91
|
const errorText = await response.text();
|
|
88
92
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
89
93
|
}
|
|
90
|
-
const data = await response.json();
|
|
94
|
+
const data = (await response.json());
|
|
91
95
|
// Handle different response formats
|
|
92
96
|
let workspaces;
|
|
93
97
|
if (Array.isArray(data)) {
|
|
@@ -99,6 +103,9 @@ Available workspaces:
|
|
|
99
103
|
else {
|
|
100
104
|
this.error('Unexpected API response format');
|
|
101
105
|
}
|
|
106
|
+
if (flags.latest) {
|
|
107
|
+
workspaces.sort((a, b) => b.id - a.id);
|
|
108
|
+
}
|
|
102
109
|
// Output results
|
|
103
110
|
if (flags.output === 'json') {
|
|
104
111
|
this.log(JSON.stringify(workspaces, null, 2));
|
|
@@ -111,11 +118,12 @@ Available workspaces:
|
|
|
111
118
|
else {
|
|
112
119
|
this.log('Available workspaces:');
|
|
113
120
|
for (const workspace of workspaces) {
|
|
121
|
+
const created = workspace.created_at ? ` (created: ${workspace.created_at.split(' ')[0]})` : '';
|
|
114
122
|
if (workspace.id === undefined) {
|
|
115
|
-
this.log(` - ${workspace.name}`);
|
|
123
|
+
this.log(` - ${workspace.name}${created}`);
|
|
116
124
|
}
|
|
117
125
|
else {
|
|
118
|
-
this.log(` - ${workspace.name} (ID: ${workspace.id})`);
|
|
126
|
+
this.log(` - ${workspace.name} (ID: ${workspace.id})${created}`);
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
}
|
|
@@ -135,8 +143,7 @@ Available workspaces:
|
|
|
135
143
|
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
136
144
|
// Check if credentials file exists
|
|
137
145
|
if (!fs.existsSync(credentialsPath)) {
|
|
138
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
139
|
-
`Create a profile using 'xano profile:create'`);
|
|
146
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
|
|
140
147
|
}
|
|
141
148
|
// Read credentials file
|
|
142
149
|
try {
|
|
@@ -282,6 +282,25 @@ Push functions but exclude test files
|
|
|
282
282
|
}
|
|
283
283
|
else {
|
|
284
284
|
const errorText = await dryRunResponse.text();
|
|
285
|
+
// Check if push is disabled on this workspace
|
|
286
|
+
try {
|
|
287
|
+
const errorJson = JSON.parse(errorText);
|
|
288
|
+
if (errorJson.message?.includes('Push is disabled')) {
|
|
289
|
+
this.log('');
|
|
290
|
+
this.log(ux.colorize('red', ux.colorize('bold', 'Direct push to this workspace is disabled.')));
|
|
291
|
+
this.log(ux.colorize('dim', 'To apply changes to the workspace, use the sandbox review flow:'));
|
|
292
|
+
this.log(` ${ux.colorize('cyan', 'xano sandbox push')} ${ux.colorize('dim', '— push changes to your sandbox')}`);
|
|
293
|
+
this.log(` ${ux.colorize('cyan', 'xano sandbox review')} ${ux.colorize('dim', '— edit any logic, inspect the snapshot diff, and promote changes to the workspace')}`);
|
|
294
|
+
this.log('');
|
|
295
|
+
this.log(ux.colorize('dim', 'To enable direct push, go to Workspace Settings → CLI → Allow Direct Workspace Push.'));
|
|
296
|
+
this.log(ux.colorize('dim', 'Note: This setting does not apply to Free plan instances.'));
|
|
297
|
+
this.log('');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Not JSON, fall through
|
|
303
|
+
}
|
|
285
304
|
this.warn(`Push preview failed (${dryRunResponse.status}). Skipping preview.`);
|
|
286
305
|
if (flags.verbose) {
|
|
287
306
|
this.log(ux.colorize('dim', errorText));
|
|
@@ -465,6 +484,15 @@ Push functions but exclude test files
|
|
|
465
484
|
if (errorJson.payload?.param) {
|
|
466
485
|
errorMessage += `\n Parameter: ${errorJson.payload.param}`;
|
|
467
486
|
}
|
|
487
|
+
// Provide clear guidance when push is disabled
|
|
488
|
+
if (errorJson.message?.includes('Push is disabled')) {
|
|
489
|
+
this.error(`Push is disabled for this workspace.\n\n` +
|
|
490
|
+
`To enable, go to Workspace Settings and turn on "Allow Push".\n` +
|
|
491
|
+
`Note: This setting does not apply to Free plan instances.\n\n` +
|
|
492
|
+
`Alternatively, use sandbox commands:\n` +
|
|
493
|
+
` xano sandbox push ${args.directory}\n` +
|
|
494
|
+
` xano sandbox impersonate`);
|
|
495
|
+
}
|
|
468
496
|
}
|
|
469
497
|
catch {
|
|
470
498
|
errorMessage += `\n${errorText}`;
|
|
@@ -635,7 +663,7 @@ Push functions but exclude test files
|
|
|
635
663
|
const color = op.action === 'update' || op.action === 'update_field' ? 'yellow' : 'green';
|
|
636
664
|
const actionLabel = op.action.toUpperCase();
|
|
637
665
|
this.log(` ${ux.colorize(color, actionLabel.padEnd(16))} ${op.type.padEnd(18)} ${op.name}`);
|
|
638
|
-
if (op.details) {
|
|
666
|
+
if (verbose && op.details) {
|
|
639
667
|
this.log(` ${' '.repeat(16)} ${' '.repeat(18)} ${ux.colorize('dim', op.details)}`);
|
|
640
668
|
}
|
|
641
669
|
if (verbose && op.reason) {
|
|
@@ -656,7 +684,7 @@ Push functions but exclude test files
|
|
|
656
684
|
const color = op.action === 'truncate' || op.action === 'alter_field' ? 'yellow' : 'red';
|
|
657
685
|
const actionLabel = op.action.toUpperCase();
|
|
658
686
|
this.log(` ${ux.colorize(color, actionLabel.padEnd(16))} ${op.type.padEnd(18)} ${op.name}`);
|
|
659
|
-
if (op.details) {
|
|
687
|
+
if (verbose && op.details) {
|
|
660
688
|
this.log(` ${' '.repeat(16)} ${' '.repeat(18)} ${ux.colorize('dim', op.details)}`);
|
|
661
689
|
}
|
|
662
690
|
if (verbose && op.reason) {
|