@xano/cli 0.0.37 → 0.0.40
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 +325 -102
- package/dist/commands/auth/index.d.ts +0 -2
- package/dist/commands/auth/index.js +2 -55
- package/dist/commands/profile/create/index.d.ts +0 -2
- package/dist/commands/profile/create/index.js +0 -15
- package/dist/commands/profile/edit/index.d.ts +0 -4
- package/dist/commands/profile/edit/index.js +7 -38
- package/dist/commands/profile/wizard/index.d.ts +0 -2
- package/dist/commands/profile/wizard/index.js +0 -106
- package/dist/commands/profile/{project → workspace}/index.d.ts +1 -1
- package/dist/commands/profile/{project → workspace}/index.js +10 -10
- package/dist/commands/release/delete/index.d.ts +2 -4
- package/dist/commands/release/delete/index.js +39 -12
- package/dist/commands/release/edit/index.d.ts +2 -4
- package/dist/commands/release/edit/index.js +31 -5
- package/dist/commands/release/export/index.d.ts +2 -4
- package/dist/commands/release/export/index.js +39 -11
- package/dist/commands/release/get/index.d.ts +2 -4
- package/dist/commands/release/get/index.js +31 -5
- package/dist/commands/release/pull/index.d.ts +31 -0
- package/dist/commands/release/pull/index.js +345 -0
- package/dist/commands/release/push/index.d.ts +26 -0
- package/dist/commands/release/push/index.js +230 -0
- package/dist/commands/tenant/backup/delete/index.d.ts +1 -1
- package/dist/commands/tenant/backup/delete/index.js +8 -9
- package/dist/commands/tenant/backup/export/index.d.ts +1 -1
- package/dist/commands/tenant/backup/export/index.js +9 -10
- package/dist/commands/tenant/backup/restore/index.d.ts +1 -1
- package/dist/commands/tenant/backup/restore/index.js +8 -9
- package/dist/commands/tenant/cluster/create/index.d.ts +18 -0
- package/dist/commands/tenant/cluster/create/index.js +149 -0
- package/dist/commands/{run/sessions/start → tenant/cluster/delete}/index.d.ts +9 -3
- package/dist/commands/tenant/cluster/delete/index.js +125 -0
- package/dist/commands/tenant/cluster/edit/index.d.ts +22 -0
- package/dist/commands/tenant/cluster/edit/index.js +128 -0
- package/dist/commands/{run/sessions → tenant/cluster}/get/index.d.ts +7 -3
- package/dist/commands/tenant/cluster/get/index.js +114 -0
- package/dist/commands/{run/info → tenant/cluster/license/get}/index.d.ts +10 -7
- package/dist/commands/tenant/cluster/license/get/index.js +118 -0
- package/dist/commands/tenant/cluster/license/set/index.d.ts +21 -0
- package/dist/commands/tenant/cluster/license/set/index.js +132 -0
- package/dist/commands/{run/env → tenant/cluster}/list/index.d.ts +3 -3
- package/dist/commands/tenant/cluster/list/index.js +109 -0
- package/dist/commands/tenant/create/index.d.ts +6 -3
- package/dist/commands/tenant/create/index.js +28 -20
- package/dist/commands/tenant/deploy_platform/index.d.ts +1 -1
- package/dist/commands/tenant/deploy_platform/index.js +8 -9
- package/dist/commands/tenant/deploy_release/index.d.ts +1 -1
- package/dist/commands/tenant/deploy_release/index.js +13 -13
- package/dist/commands/tenant/env/delete/index.d.ts +19 -0
- package/dist/commands/tenant/env/delete/index.js +139 -0
- package/dist/commands/{run/projects/create → tenant/env/get}/index.d.ts +7 -4
- package/dist/commands/tenant/env/get/index.js +113 -0
- package/dist/commands/{run/projects/update → tenant/env/get_all}/index.d.ts +7 -5
- package/dist/commands/tenant/env/get_all/index.js +123 -0
- package/dist/commands/{run/secrets/get → tenant/env/list}/index.d.ts +5 -3
- package/dist/commands/tenant/env/list/index.js +116 -0
- package/dist/commands/tenant/env/set/index.d.ts +18 -0
- package/dist/commands/tenant/env/set/index.js +122 -0
- package/dist/commands/tenant/env/set_all/index.d.ts +18 -0
- package/dist/commands/tenant/env/set_all/index.js +131 -0
- package/dist/commands/tenant/get/index.js +6 -5
- package/dist/commands/tenant/impersonate/index.d.ts +19 -0
- package/dist/commands/tenant/impersonate/index.js +146 -0
- package/dist/commands/tenant/license/get/index.d.ts +18 -0
- package/dist/commands/tenant/license/get/index.js +127 -0
- package/dist/commands/tenant/license/set/index.d.ts +19 -0
- package/dist/commands/tenant/license/set/index.js +141 -0
- package/dist/commands/tenant/list/index.js +6 -6
- package/dist/commands/tenant/pull/index.d.ts +31 -0
- package/dist/commands/tenant/pull/index.js +327 -0
- package/dist/commands/tenant/push/index.d.ts +24 -0
- package/dist/commands/tenant/push/index.js +245 -0
- package/oclif.manifest.json +2076 -1670
- package/package.json +1 -19
- package/dist/commands/run/env/delete/index.d.ts +0 -14
- package/dist/commands/run/env/delete/index.js +0 -65
- package/dist/commands/run/env/get/index.d.ts +0 -14
- package/dist/commands/run/env/get/index.js +0 -52
- package/dist/commands/run/env/list/index.js +0 -56
- package/dist/commands/run/env/set/index.d.ts +0 -14
- package/dist/commands/run/env/set/index.js +0 -51
- package/dist/commands/run/exec/index.d.ts +0 -31
- package/dist/commands/run/exec/index.js +0 -431
- package/dist/commands/run/info/index.js +0 -160
- package/dist/commands/run/projects/create/index.js +0 -75
- package/dist/commands/run/projects/delete/index.d.ts +0 -14
- package/dist/commands/run/projects/delete/index.js +0 -65
- package/dist/commands/run/projects/list/index.d.ts +0 -13
- package/dist/commands/run/projects/list/index.js +0 -66
- package/dist/commands/run/projects/update/index.js +0 -86
- package/dist/commands/run/secrets/delete/index.d.ts +0 -14
- package/dist/commands/run/secrets/delete/index.js +0 -65
- package/dist/commands/run/secrets/get/index.js +0 -52
- package/dist/commands/run/secrets/list/index.d.ts +0 -12
- package/dist/commands/run/secrets/list/index.js +0 -60
- package/dist/commands/run/secrets/set/index.d.ts +0 -16
- package/dist/commands/run/secrets/set/index.js +0 -74
- package/dist/commands/run/sessions/delete/index.d.ts +0 -14
- package/dist/commands/run/sessions/delete/index.js +0 -65
- package/dist/commands/run/sessions/get/index.js +0 -72
- package/dist/commands/run/sessions/list/index.d.ts +0 -13
- package/dist/commands/run/sessions/list/index.js +0 -64
- package/dist/commands/run/sessions/start/index.js +0 -56
- package/dist/commands/run/sessions/stop/index.d.ts +0 -14
- package/dist/commands/run/sessions/stop/index.js +0 -56
- package/dist/commands/run/sink/get/index.d.ts +0 -14
- package/dist/commands/run/sink/get/index.js +0 -63
- package/dist/lib/base-run-command.d.ts +0 -41
- package/dist/lib/base-run-command.js +0 -75
- package/dist/lib/run-http-client.d.ts +0 -64
- package/dist/lib/run-http-client.js +0 -171
- package/dist/lib/run-types.d.ts +0 -226
- package/dist/lib/run-types.js +0 -5
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../../base-command.js';
|
|
7
|
+
export default class TenantClusterDelete extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
cluster_id: Args.integer({
|
|
10
|
+
description: 'Cluster ID to delete',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Delete a tenant cluster. This action cannot be undone.';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant cluster delete 3
|
|
17
|
+
Are you sure you want to delete tenant cluster 3? This action cannot be undone. (y/N) y
|
|
18
|
+
Tenant cluster 3 deleted successfully
|
|
19
|
+
`,
|
|
20
|
+
`$ xano tenant cluster delete 3 --force
|
|
21
|
+
Tenant cluster 3 deleted successfully
|
|
22
|
+
`,
|
|
23
|
+
`$ xano tenant cluster delete 3 -f -o json`,
|
|
24
|
+
];
|
|
25
|
+
static flags = {
|
|
26
|
+
...BaseCommand.baseFlags,
|
|
27
|
+
force: Flags.boolean({
|
|
28
|
+
char: 'f',
|
|
29
|
+
default: false,
|
|
30
|
+
description: 'Skip confirmation prompt',
|
|
31
|
+
required: false,
|
|
32
|
+
}),
|
|
33
|
+
output: Flags.string({
|
|
34
|
+
char: 'o',
|
|
35
|
+
default: 'summary',
|
|
36
|
+
description: 'Output format',
|
|
37
|
+
options: ['summary', 'json'],
|
|
38
|
+
required: false,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
const { args, flags } = await this.parse(TenantClusterDelete);
|
|
43
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
44
|
+
const credentials = this.loadCredentials();
|
|
45
|
+
if (!(profileName in credentials.profiles)) {
|
|
46
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
47
|
+
`Create a profile using 'xano profile create'`);
|
|
48
|
+
}
|
|
49
|
+
const profile = credentials.profiles[profileName];
|
|
50
|
+
if (!profile.instance_origin) {
|
|
51
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
52
|
+
}
|
|
53
|
+
if (!profile.access_token) {
|
|
54
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
55
|
+
}
|
|
56
|
+
const clusterId = args.cluster_id;
|
|
57
|
+
if (!flags.force) {
|
|
58
|
+
const confirmed = await this.confirm(`Are you sure you want to delete tenant cluster ${clusterId}? This action cannot be undone.`);
|
|
59
|
+
if (!confirmed) {
|
|
60
|
+
this.log('Deletion cancelled.');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const apiUrl = `${profile.instance_origin}/api:meta/tenant/cluster/${clusterId}`;
|
|
65
|
+
try {
|
|
66
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
67
|
+
headers: {
|
|
68
|
+
'accept': 'application/json',
|
|
69
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
70
|
+
},
|
|
71
|
+
method: 'DELETE',
|
|
72
|
+
}, flags.verbose, profile.access_token);
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const errorText = await response.text();
|
|
75
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
76
|
+
}
|
|
77
|
+
if (flags.output === 'json') {
|
|
78
|
+
this.log(JSON.stringify({ cluster_id: clusterId, deleted: true }, null, 2));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.log(`Tenant cluster ${clusterId} deleted successfully`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
this.error(`Failed to delete tenant cluster: ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.error(`Failed to delete tenant cluster: ${String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async confirm(message) {
|
|
94
|
+
const readline = await import('node:readline');
|
|
95
|
+
const rl = readline.createInterface({
|
|
96
|
+
input: process.stdin,
|
|
97
|
+
output: process.stdout,
|
|
98
|
+
});
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
101
|
+
rl.close();
|
|
102
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
loadCredentials() {
|
|
107
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
108
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
109
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
110
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
111
|
+
`Create a profile using 'xano profile create'`);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
115
|
+
const parsed = yaml.load(fileContent);
|
|
116
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
117
|
+
this.error('Credentials file has invalid format.');
|
|
118
|
+
}
|
|
119
|
+
return parsed;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import BaseCommand from '../../../../base-command.js';
|
|
2
|
+
export default class TenantClusterEdit extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
cluster_id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
description: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, 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
|
+
private loadCredentials;
|
|
22
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../../base-command.js';
|
|
7
|
+
export default class TenantClusterEdit extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
cluster_id: Args.integer({
|
|
10
|
+
description: 'Cluster ID to edit',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Update an existing tenant cluster';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant cluster edit 1 --name "us-east-1-updated" --description "Updated cluster" --domain "us-east.xano.io" --type standard
|
|
17
|
+
Updated tenant cluster: us-east-1-updated (standard) - ID: 1
|
|
18
|
+
`,
|
|
19
|
+
`$ xano tenant cluster edit 1 --name "eu-west" --description "" --domain "" --type run -o json`,
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
...BaseCommand.baseFlags,
|
|
23
|
+
description: Flags.string({
|
|
24
|
+
char: 'd',
|
|
25
|
+
description: 'Cluster description',
|
|
26
|
+
required: true,
|
|
27
|
+
}),
|
|
28
|
+
domain: Flags.string({
|
|
29
|
+
description: 'Custom domain for the cluster',
|
|
30
|
+
required: true,
|
|
31
|
+
}),
|
|
32
|
+
name: Flags.string({
|
|
33
|
+
char: 'n',
|
|
34
|
+
description: 'Cluster name',
|
|
35
|
+
required: true,
|
|
36
|
+
}),
|
|
37
|
+
output: Flags.string({
|
|
38
|
+
char: 'o',
|
|
39
|
+
default: 'summary',
|
|
40
|
+
description: 'Output format',
|
|
41
|
+
options: ['summary', 'json'],
|
|
42
|
+
required: false,
|
|
43
|
+
}),
|
|
44
|
+
type: Flags.string({
|
|
45
|
+
description: 'Cluster type',
|
|
46
|
+
options: ['standard', 'run'],
|
|
47
|
+
required: true,
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
async run() {
|
|
51
|
+
const { args, flags } = await this.parse(TenantClusterEdit);
|
|
52
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
53
|
+
const credentials = this.loadCredentials();
|
|
54
|
+
if (!(profileName in credentials.profiles)) {
|
|
55
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
56
|
+
`Create a profile using 'xano profile create'`);
|
|
57
|
+
}
|
|
58
|
+
const profile = credentials.profiles[profileName];
|
|
59
|
+
if (!profile.instance_origin) {
|
|
60
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
61
|
+
}
|
|
62
|
+
if (!profile.access_token) {
|
|
63
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
64
|
+
}
|
|
65
|
+
const clusterId = args.cluster_id;
|
|
66
|
+
const body = {
|
|
67
|
+
description: flags.description,
|
|
68
|
+
domain: flags.domain,
|
|
69
|
+
name: flags.name,
|
|
70
|
+
type: flags.type,
|
|
71
|
+
};
|
|
72
|
+
const apiUrl = `${profile.instance_origin}/api:meta/tenant/cluster/${clusterId}`;
|
|
73
|
+
try {
|
|
74
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
75
|
+
body: JSON.stringify(body),
|
|
76
|
+
headers: {
|
|
77
|
+
'accept': 'application/json',
|
|
78
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
},
|
|
81
|
+
method: 'PUT',
|
|
82
|
+
}, flags.verbose, profile.access_token);
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const errorText = await response.text();
|
|
85
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
86
|
+
}
|
|
87
|
+
const cluster = await response.json();
|
|
88
|
+
if (flags.output === 'json') {
|
|
89
|
+
this.log(JSON.stringify(cluster, null, 2));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const type = cluster.type ? ` (${cluster.type})` : '';
|
|
93
|
+
this.log(`Updated tenant cluster: ${cluster.name}${type} - ID: ${cluster.id}`);
|
|
94
|
+
if (cluster.description)
|
|
95
|
+
this.log(` Description: ${cluster.description}`);
|
|
96
|
+
if (cluster.domain)
|
|
97
|
+
this.log(` Domain: ${cluster.domain}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof Error) {
|
|
102
|
+
this.error(`Failed to edit tenant cluster: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.error(`Failed to edit tenant cluster: ${String(error)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
loadCredentials() {
|
|
110
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
111
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
112
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
113
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
114
|
+
`Create a profile using 'xano profile create'`);
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
118
|
+
const parsed = yaml.load(fileContent);
|
|
119
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
120
|
+
this.error('Credentials file has invalid format.');
|
|
121
|
+
}
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default class
|
|
1
|
+
import BaseCommand from '../../../../base-command.js';
|
|
2
|
+
export default class TenantClusterGet extends BaseCommand {
|
|
3
3
|
static args: {
|
|
4
|
-
|
|
4
|
+
cluster_id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
5
8
|
};
|
|
6
9
|
static description: string;
|
|
7
10
|
static examples: string[];
|
|
@@ -11,4 +14,5 @@ export default class RunSessionsGet extends BaseRunCommand {
|
|
|
11
14
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
15
|
};
|
|
13
16
|
run(): Promise<void>;
|
|
17
|
+
private loadCredentials;
|
|
14
18
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../../base-command.js';
|
|
7
|
+
export default class TenantClusterGet extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
cluster_id: Args.integer({
|
|
10
|
+
description: 'Cluster ID to retrieve',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Get details of a specific tenant cluster';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant cluster get 1
|
|
17
|
+
Cluster: us-east-1
|
|
18
|
+
ID: 1
|
|
19
|
+
Type: standard
|
|
20
|
+
Domain: us-east-1.xano.io
|
|
21
|
+
`,
|
|
22
|
+
`$ xano tenant cluster get 1 -o json`,
|
|
23
|
+
];
|
|
24
|
+
static flags = {
|
|
25
|
+
...BaseCommand.baseFlags,
|
|
26
|
+
output: Flags.string({
|
|
27
|
+
char: 'o',
|
|
28
|
+
default: 'summary',
|
|
29
|
+
description: 'Output format',
|
|
30
|
+
options: ['summary', 'json'],
|
|
31
|
+
required: false,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { args, flags } = await this.parse(TenantClusterGet);
|
|
36
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
37
|
+
const credentials = this.loadCredentials();
|
|
38
|
+
if (!(profileName in credentials.profiles)) {
|
|
39
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
40
|
+
`Create a profile using 'xano profile create'`);
|
|
41
|
+
}
|
|
42
|
+
const profile = credentials.profiles[profileName];
|
|
43
|
+
if (!profile.instance_origin) {
|
|
44
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
45
|
+
}
|
|
46
|
+
if (!profile.access_token) {
|
|
47
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
48
|
+
}
|
|
49
|
+
const clusterId = args.cluster_id;
|
|
50
|
+
const apiUrl = `${profile.instance_origin}/api:meta/tenant/cluster/${clusterId}`;
|
|
51
|
+
try {
|
|
52
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
53
|
+
headers: {
|
|
54
|
+
accept: 'application/json',
|
|
55
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
56
|
+
},
|
|
57
|
+
method: 'GET',
|
|
58
|
+
}, flags.verbose, profile.access_token);
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const errorText = await response.text();
|
|
61
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
62
|
+
}
|
|
63
|
+
const cluster = (await response.json());
|
|
64
|
+
if (flags.output === 'json') {
|
|
65
|
+
this.log(JSON.stringify(cluster, null, 2));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.log(`Cluster: ${cluster.name}`);
|
|
69
|
+
this.log(` ID: ${cluster.id}`);
|
|
70
|
+
if (cluster.type)
|
|
71
|
+
this.log(` Type: ${cluster.type}`);
|
|
72
|
+
if (cluster.description)
|
|
73
|
+
this.log(` Description: ${cluster.description}`);
|
|
74
|
+
if (cluster.domain)
|
|
75
|
+
this.log(` Domain: ${cluster.domain}`);
|
|
76
|
+
if (cluster.warm)
|
|
77
|
+
this.log(` Warm: ${JSON.stringify(cluster.warm)}`);
|
|
78
|
+
if (cluster.ingress)
|
|
79
|
+
this.log(` Ingress: ${JSON.stringify(cluster.ingress)}`);
|
|
80
|
+
if (cluster.created_at) {
|
|
81
|
+
const d = new Date(cluster.created_at);
|
|
82
|
+
const createdDate = Number.isNaN(d.getTime()) ? cluster.created_at : d.toISOString().split('T')[0];
|
|
83
|
+
this.log(` Created: ${createdDate}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error instanceof Error) {
|
|
89
|
+
this.error(`Failed to get tenant cluster: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.error(`Failed to get tenant cluster: ${String(error)}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
loadCredentials() {
|
|
97
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
98
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
99
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
100
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
104
|
+
const parsed = yaml.load(fileContent);
|
|
105
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
106
|
+
this.error('Credentials file has invalid format.');
|
|
107
|
+
}
|
|
108
|
+
return parsed;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default class
|
|
3
|
-
static args: {
|
|
1
|
+
import BaseCommand from '../../../../../base-command.js';
|
|
2
|
+
export default class TenantClusterLicenseGet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
cluster_id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
4
9
|
static description: string;
|
|
5
10
|
static examples: string[];
|
|
6
11
|
static flags: {
|
|
7
12
|
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
13
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
|
|
14
|
+
view: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
15
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
16
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
17
|
};
|
|
13
18
|
run(): Promise<void>;
|
|
14
|
-
private
|
|
15
|
-
private outputSummary;
|
|
16
|
-
private readStdin;
|
|
19
|
+
private loadCredentials;
|
|
17
20
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../../../base-command.js';
|
|
7
|
+
export default class TenantClusterLicenseGet extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
cluster_id: Args.integer({
|
|
10
|
+
description: 'Tenant cluster ID',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Get the license (kubeconfig) for a tenant cluster';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant cluster license get 1
|
|
17
|
+
License saved to kubeconfig-1.yaml
|
|
18
|
+
`,
|
|
19
|
+
`$ xano tenant cluster license get 1 --file ./my-kubeconfig.yaml
|
|
20
|
+
License saved to my-kubeconfig.yaml
|
|
21
|
+
`,
|
|
22
|
+
`$ xano tenant cluster license get 1 --view`,
|
|
23
|
+
`$ xano tenant cluster license get 1 -o json`,
|
|
24
|
+
];
|
|
25
|
+
static flags = {
|
|
26
|
+
...BaseCommand.baseFlags,
|
|
27
|
+
file: Flags.string({
|
|
28
|
+
char: 'f',
|
|
29
|
+
description: 'Output file path (default: kubeconfig_<cluster_id>.yaml)',
|
|
30
|
+
required: false,
|
|
31
|
+
}),
|
|
32
|
+
output: Flags.string({
|
|
33
|
+
char: 'o',
|
|
34
|
+
default: 'summary',
|
|
35
|
+
description: 'Output format',
|
|
36
|
+
options: ['summary', 'json'],
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
view: Flags.boolean({
|
|
40
|
+
default: false,
|
|
41
|
+
description: 'Print license to stdout instead of saving to file',
|
|
42
|
+
required: false,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const { args, flags } = await this.parse(TenantClusterLicenseGet);
|
|
47
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
48
|
+
const credentials = this.loadCredentials();
|
|
49
|
+
if (!(profileName in credentials.profiles)) {
|
|
50
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
51
|
+
`Create a profile using 'xano profile create'`);
|
|
52
|
+
}
|
|
53
|
+
const profile = credentials.profiles[profileName];
|
|
54
|
+
if (!profile.instance_origin) {
|
|
55
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
56
|
+
}
|
|
57
|
+
if (!profile.access_token) {
|
|
58
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
59
|
+
}
|
|
60
|
+
const clusterId = args.cluster_id;
|
|
61
|
+
const apiUrl = `${profile.instance_origin}/api:meta/tenant/cluster/${clusterId}/license`;
|
|
62
|
+
try {
|
|
63
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
64
|
+
headers: {
|
|
65
|
+
accept: 'application/json',
|
|
66
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
67
|
+
},
|
|
68
|
+
method: 'GET',
|
|
69
|
+
}, flags.verbose, profile.access_token);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const errorText = await response.text();
|
|
72
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
73
|
+
}
|
|
74
|
+
const license = await response.json();
|
|
75
|
+
// The license is a raw YAML string (kubeconfig) — write it directly, not yaml.dump'd
|
|
76
|
+
const licenseContent = typeof license === 'string' ? license : JSON.stringify(license, null, 2);
|
|
77
|
+
if (flags.view || flags.output === 'json') {
|
|
78
|
+
if (flags.output === 'json') {
|
|
79
|
+
this.log(JSON.stringify(license, null, 2));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.log(licenseContent);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const filePath = path.resolve(flags.file || `kubeconfig_${clusterId}.yaml`);
|
|
87
|
+
fs.writeFileSync(filePath, licenseContent, 'utf8');
|
|
88
|
+
this.log(`License saved to ${filePath}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error instanceof Error) {
|
|
93
|
+
this.error(`Failed to get tenant cluster license: ${error.message}`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.error(`Failed to get tenant cluster license: ${String(error)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
loadCredentials() {
|
|
101
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
102
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
103
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
104
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
108
|
+
const parsed = yaml.load(fileContent);
|
|
109
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
110
|
+
this.error('Credentials file has invalid format.');
|
|
111
|
+
}
|
|
112
|
+
return parsed;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import BaseCommand from '../../../../../base-command.js';
|
|
2
|
+
export default class TenantClusterLicenseSet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
cluster_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
|
+
clean: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
value: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
};
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
private loadCredentials;
|
|
21
|
+
}
|