@xano/cli 0.0.31 → 0.0.33
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/dist/base-command.d.ts +5 -0
- package/dist/base-command.js +32 -1
- package/dist/commands/branch/create/index.js +3 -3
- package/dist/commands/branch/delete/index.js +2 -2
- package/dist/commands/branch/edit/index.js +3 -3
- package/dist/commands/branch/get/index.js +2 -2
- package/dist/commands/branch/list/index.js +2 -2
- package/dist/commands/branch/set-live/index.js +2 -2
- package/dist/commands/function/create/index.js +2 -2
- package/dist/commands/function/edit/index.js +2 -2
- package/dist/commands/function/get/index.js +2 -2
- package/dist/commands/function/list/index.js +2 -2
- package/dist/commands/platform/get/index.d.ts +18 -0
- package/dist/commands/platform/get/index.js +126 -0
- package/dist/commands/platform/list/index.d.ts +12 -0
- package/dist/commands/platform/list/index.js +113 -0
- package/dist/commands/profile/me/index.js +2 -2
- package/dist/commands/release/create/index.d.ts +18 -0
- package/dist/commands/release/create/index.js +138 -0
- package/dist/commands/release/delete/index.d.ts +21 -0
- package/dist/commands/release/delete/index.js +134 -0
- package/dist/commands/release/edit/index.d.ts +21 -0
- package/dist/commands/release/edit/index.js +137 -0
- package/dist/commands/release/export/index.d.ts +20 -0
- package/dist/commands/release/export/index.js +142 -0
- package/dist/commands/release/get/index.d.ts +19 -0
- package/dist/commands/release/get/index.js +123 -0
- package/dist/commands/release/import/index.d.ts +15 -0
- package/dist/commands/release/import/index.js +114 -0
- package/dist/commands/release/list/index.d.ts +13 -0
- package/dist/commands/release/list/index.js +120 -0
- package/dist/commands/static_host/build/create/index.js +2 -2
- package/dist/commands/static_host/build/get/index.js +2 -2
- package/dist/commands/static_host/build/list/index.js +2 -2
- package/dist/commands/static_host/list/index.js +2 -2
- package/dist/commands/tenant/backup/create/index.d.ts +17 -0
- package/dist/commands/tenant/backup/create/index.js +113 -0
- package/dist/commands/tenant/backup/delete/index.d.ts +19 -0
- package/dist/commands/tenant/backup/delete/index.js +137 -0
- package/dist/commands/tenant/backup/export/index.d.ts +18 -0
- package/dist/commands/tenant/backup/export/index.js +147 -0
- package/dist/commands/tenant/backup/import/index.d.ts +18 -0
- package/dist/commands/tenant/backup/import/index.js +127 -0
- package/dist/commands/tenant/backup/list/index.d.ts +17 -0
- package/dist/commands/tenant/backup/list/index.js +137 -0
- package/dist/commands/tenant/backup/restore/index.d.ts +19 -0
- package/dist/commands/tenant/backup/restore/index.js +141 -0
- package/dist/commands/tenant/create/index.d.ts +21 -0
- package/dist/commands/tenant/create/index.js +155 -0
- package/dist/commands/tenant/delete/index.d.ts +18 -0
- package/dist/commands/tenant/delete/index.js +134 -0
- package/dist/commands/tenant/deploy-platform/index.d.ts +17 -0
- package/dist/commands/tenant/deploy-platform/index.js +116 -0
- package/dist/commands/tenant/deploy-release/index.d.ts +17 -0
- package/dist/commands/tenant/deploy-release/index.js +116 -0
- package/dist/commands/tenant/edit/index.d.ts +23 -0
- package/dist/commands/tenant/edit/index.js +167 -0
- package/dist/commands/tenant/get/index.d.ts +16 -0
- package/dist/commands/tenant/get/index.js +135 -0
- package/dist/commands/tenant/list/index.d.ts +13 -0
- package/dist/commands/tenant/list/index.js +123 -0
- package/dist/commands/workspace/create/index.d.ts +3 -1
- package/dist/commands/workspace/create/index.js +15 -14
- package/dist/commands/workspace/delete/index.js +2 -2
- package/dist/commands/workspace/edit/index.js +3 -3
- package/dist/commands/workspace/get/index.js +2 -2
- package/dist/commands/workspace/list/index.js +2 -2
- package/dist/commands/workspace/pull/index.d.ts +1 -0
- package/dist/commands/workspace/pull/index.js +45 -10
- package/dist/commands/workspace/push/index.d.ts +3 -0
- package/dist/commands/workspace/push/index.js +41 -8
- package/oclif.manifest.json +3174 -1220
- package/package.json +10 -1
|
@@ -0,0 +1,116 @@
|
|
|
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 TenantDeployRelease extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
tenant_name: Args.string({
|
|
10
|
+
description: 'Tenant name to deploy to',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Deploy a release to a tenant';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant deploy-release t1234-abcd-xyz1 --release-id 10
|
|
17
|
+
Deployed release 10 to tenant: My Tenant (my-tenant)
|
|
18
|
+
`,
|
|
19
|
+
`$ xano tenant deploy-release t1234-abcd-xyz1 --release-id 10 -o json`,
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
...BaseCommand.baseFlags,
|
|
23
|
+
output: Flags.string({
|
|
24
|
+
char: 'o',
|
|
25
|
+
default: 'summary',
|
|
26
|
+
description: 'Output format',
|
|
27
|
+
options: ['summary', 'json'],
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
'release-id': Flags.integer({
|
|
31
|
+
description: 'Release ID to deploy',
|
|
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(TenantDeployRelease);
|
|
42
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
43
|
+
const credentials = this.loadCredentials();
|
|
44
|
+
if (!(profileName in credentials.profiles)) {
|
|
45
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
46
|
+
`Create a profile using 'xano profile create'`);
|
|
47
|
+
}
|
|
48
|
+
const profile = credentials.profiles[profileName];
|
|
49
|
+
if (!profile.instance_origin) {
|
|
50
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
51
|
+
}
|
|
52
|
+
if (!profile.access_token) {
|
|
53
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
54
|
+
}
|
|
55
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
56
|
+
if (!workspaceId) {
|
|
57
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
58
|
+
}
|
|
59
|
+
const tenantName = args.tenant_name;
|
|
60
|
+
const releaseId = flags['release-id'];
|
|
61
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/deploy`;
|
|
62
|
+
try {
|
|
63
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
64
|
+
body: JSON.stringify({ release_id: releaseId }),
|
|
65
|
+
headers: {
|
|
66
|
+
'accept': 'application/json',
|
|
67
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
},
|
|
70
|
+
method: 'POST',
|
|
71
|
+
}, flags.verbose, profile.access_token);
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const errorText = await response.text();
|
|
74
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
75
|
+
}
|
|
76
|
+
const tenant = await response.json();
|
|
77
|
+
if (flags.output === 'json') {
|
|
78
|
+
this.log(JSON.stringify(tenant, null, 2));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.log(`Deployed release ${releaseId} to tenant: ${tenant.display || tenant.name} (${tenant.name})`);
|
|
82
|
+
if (tenant.state)
|
|
83
|
+
this.log(` State: ${tenant.state}`);
|
|
84
|
+
if (tenant.release?.name)
|
|
85
|
+
this.log(` Release: ${tenant.release.name}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof Error) {
|
|
90
|
+
this.error(`Failed to deploy to tenant: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this.error(`Failed to deploy to tenant: ${String(error)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
loadCredentials() {
|
|
98
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
99
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
100
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
101
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
102
|
+
`Create a profile using 'xano profile create'`);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
106
|
+
const parsed = yaml.load(fileContent);
|
|
107
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
108
|
+
this.error('Credentials file has invalid format.');
|
|
109
|
+
}
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantEdit extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
display: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
ingress: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
proxy: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
rbac: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
tasks: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
workspace: 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
|
+
run(): Promise<void>;
|
|
22
|
+
private loadCredentials;
|
|
23
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
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 TenantEdit extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
tenant_name: Args.string({
|
|
10
|
+
description: 'Tenant name to edit',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Edit an existing tenant';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant edit t1234-abcd-xyz1 --display "New Name" --description "Updated description"
|
|
17
|
+
Updated tenant: New Name (my-tenant) - ID: 42
|
|
18
|
+
`,
|
|
19
|
+
`$ xano tenant edit t1234-abcd-xyz1 --no-tasks --no-ingress -o json`,
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
...BaseCommand.baseFlags,
|
|
23
|
+
description: Flags.string({
|
|
24
|
+
char: 'd',
|
|
25
|
+
description: 'New description',
|
|
26
|
+
required: false,
|
|
27
|
+
}),
|
|
28
|
+
display: Flags.string({
|
|
29
|
+
description: 'New display name',
|
|
30
|
+
required: false,
|
|
31
|
+
}),
|
|
32
|
+
domain: Flags.string({
|
|
33
|
+
description: 'Custom domain',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
ingress: Flags.boolean({
|
|
37
|
+
allowNo: true,
|
|
38
|
+
description: 'Enable/disable ingress',
|
|
39
|
+
required: false,
|
|
40
|
+
}),
|
|
41
|
+
output: Flags.string({
|
|
42
|
+
char: 'o',
|
|
43
|
+
default: 'summary',
|
|
44
|
+
description: 'Output format',
|
|
45
|
+
options: ['summary', 'json'],
|
|
46
|
+
required: false,
|
|
47
|
+
}),
|
|
48
|
+
proxy: Flags.string({
|
|
49
|
+
description: 'Proxy URL',
|
|
50
|
+
required: false,
|
|
51
|
+
}),
|
|
52
|
+
rbac: Flags.boolean({
|
|
53
|
+
allowNo: true,
|
|
54
|
+
description: 'Enable/disable RBAC',
|
|
55
|
+
required: false,
|
|
56
|
+
}),
|
|
57
|
+
tasks: Flags.boolean({
|
|
58
|
+
allowNo: true,
|
|
59
|
+
description: 'Enable/disable background tasks',
|
|
60
|
+
required: false,
|
|
61
|
+
}),
|
|
62
|
+
workspace: Flags.string({
|
|
63
|
+
char: 'w',
|
|
64
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
65
|
+
required: false,
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
async run() {
|
|
69
|
+
const { args, flags } = await this.parse(TenantEdit);
|
|
70
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
71
|
+
const credentials = this.loadCredentials();
|
|
72
|
+
if (!(profileName in credentials.profiles)) {
|
|
73
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
74
|
+
`Create a profile using 'xano profile create'`);
|
|
75
|
+
}
|
|
76
|
+
const profile = credentials.profiles[profileName];
|
|
77
|
+
if (!profile.instance_origin) {
|
|
78
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
79
|
+
}
|
|
80
|
+
if (!profile.access_token) {
|
|
81
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
82
|
+
}
|
|
83
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
84
|
+
if (!workspaceId) {
|
|
85
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
86
|
+
}
|
|
87
|
+
const tenantName = args.tenant_name;
|
|
88
|
+
const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
|
|
89
|
+
const headers = {
|
|
90
|
+
'accept': 'application/json',
|
|
91
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
// Fetch current tenant state (PUT requires all fields)
|
|
96
|
+
const getResponse = await this.verboseFetch(baseUrl, {
|
|
97
|
+
headers: {
|
|
98
|
+
'accept': 'application/json',
|
|
99
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
100
|
+
},
|
|
101
|
+
method: 'GET',
|
|
102
|
+
}, flags.verbose, profile.access_token);
|
|
103
|
+
if (!getResponse.ok) {
|
|
104
|
+
const errorText = await getResponse.text();
|
|
105
|
+
this.error(`Failed to fetch tenant: ${getResponse.status} ${getResponse.statusText}\n${errorText}`);
|
|
106
|
+
}
|
|
107
|
+
const current = await getResponse.json();
|
|
108
|
+
// Merge in user-provided values
|
|
109
|
+
const body = {
|
|
110
|
+
description: flags.description !== undefined ? flags.description : (current.description ?? ''),
|
|
111
|
+
display: flags.display !== undefined ? flags.display : (current.display ?? current.name),
|
|
112
|
+
domain: flags.domain !== undefined ? flags.domain : (current.domain ?? ''),
|
|
113
|
+
ingress: flags.ingress !== undefined ? flags.ingress : (current.ingress ?? true),
|
|
114
|
+
proxy: flags.proxy !== undefined ? flags.proxy : (current.proxy ?? ''),
|
|
115
|
+
rbac: {
|
|
116
|
+
enabled: flags.rbac !== undefined ? flags.rbac : (current.rbac?.enabled ?? false),
|
|
117
|
+
},
|
|
118
|
+
tag: current.tag ?? [],
|
|
119
|
+
tasks: flags.tasks !== undefined ? flags.tasks : (current.tasks ?? true),
|
|
120
|
+
};
|
|
121
|
+
// Update tenant
|
|
122
|
+
const putResponse = await this.verboseFetch(baseUrl, {
|
|
123
|
+
body: JSON.stringify(body),
|
|
124
|
+
headers,
|
|
125
|
+
method: 'PUT',
|
|
126
|
+
}, flags.verbose, profile.access_token);
|
|
127
|
+
if (!putResponse.ok) {
|
|
128
|
+
const errorText = await putResponse.text();
|
|
129
|
+
this.error(`API request failed with status ${putResponse.status}: ${putResponse.statusText}\n${errorText}`);
|
|
130
|
+
}
|
|
131
|
+
const tenant = await putResponse.json();
|
|
132
|
+
if (flags.output === 'json') {
|
|
133
|
+
this.log(JSON.stringify(tenant, null, 2));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.log(`Updated tenant: ${tenant.display || tenant.name} (${tenant.name}) - ID: ${tenant.id}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error instanceof Error) {
|
|
141
|
+
this.error(`Failed to edit tenant: ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
this.error(`Failed to edit tenant: ${String(error)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
loadCredentials() {
|
|
149
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
150
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
151
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
152
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
153
|
+
`Create a profile using 'xano profile create'`);
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
157
|
+
const parsed = yaml.load(fileContent);
|
|
158
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
159
|
+
this.error('Credentials file has invalid format.');
|
|
160
|
+
}
|
|
161
|
+
return parsed;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantGet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
private loadCredentials;
|
|
16
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
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 TenantGet extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
tenant_name: Args.string({
|
|
10
|
+
description: 'Tenant name to retrieve',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Get details of a specific tenant';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant get t1234-abcd-xyz1
|
|
17
|
+
Tenant: My Tenant (my-tenant)
|
|
18
|
+
State: ok
|
|
19
|
+
License: tier1
|
|
20
|
+
Domain: my-tenant.xano.io
|
|
21
|
+
Cluster: default
|
|
22
|
+
Release: v1.0
|
|
23
|
+
`,
|
|
24
|
+
`$ xano tenant get t1234-abcd-xyz1 -w 5 -o json`,
|
|
25
|
+
];
|
|
26
|
+
static flags = {
|
|
27
|
+
...BaseCommand.baseFlags,
|
|
28
|
+
output: Flags.string({
|
|
29
|
+
char: 'o',
|
|
30
|
+
default: 'summary',
|
|
31
|
+
description: 'Output format',
|
|
32
|
+
options: ['summary', 'json'],
|
|
33
|
+
required: false,
|
|
34
|
+
}),
|
|
35
|
+
workspace: Flags.string({
|
|
36
|
+
char: 'w',
|
|
37
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
38
|
+
required: false,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
const { args, flags } = await this.parse(TenantGet);
|
|
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 workspaceId = flags.workspace || profile.workspace;
|
|
57
|
+
if (!workspaceId) {
|
|
58
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
59
|
+
}
|
|
60
|
+
const tenantName = args.tenant_name;
|
|
61
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
|
|
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 tenant = await response.json();
|
|
75
|
+
if (flags.output === 'json') {
|
|
76
|
+
this.log(JSON.stringify(tenant, null, 2));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.log(`Tenant: ${tenant.display || tenant.name} (${tenant.name})`);
|
|
80
|
+
if (tenant.state)
|
|
81
|
+
this.log(` State: ${tenant.state}`);
|
|
82
|
+
if (tenant.license)
|
|
83
|
+
this.log(` License: ${tenant.license}`);
|
|
84
|
+
if (tenant.xano_domain)
|
|
85
|
+
this.log(` Domain: ${tenant.xano_domain}`);
|
|
86
|
+
if (tenant.domain)
|
|
87
|
+
this.log(` Custom Domain: ${tenant.domain}`);
|
|
88
|
+
if (tenant.cluster?.name)
|
|
89
|
+
this.log(` Cluster: ${tenant.cluster.name}`);
|
|
90
|
+
if (tenant.release?.name)
|
|
91
|
+
this.log(` Release: ${tenant.release.name}`);
|
|
92
|
+
if (tenant.platform?.name)
|
|
93
|
+
this.log(` Platform: ${tenant.platform.name}`);
|
|
94
|
+
if (tenant.version !== undefined)
|
|
95
|
+
this.log(` Version: ${tenant.version}`);
|
|
96
|
+
if (tenant.tasks !== undefined)
|
|
97
|
+
this.log(` Tasks: ${tenant.tasks}`);
|
|
98
|
+
if (tenant.ingress !== undefined)
|
|
99
|
+
this.log(` Ingress: ${tenant.ingress}`);
|
|
100
|
+
if (tenant.deployed_at) {
|
|
101
|
+
const d = new Date(tenant.deployed_at);
|
|
102
|
+
const deployedDate = Number.isNaN(d.getTime()) ? tenant.deployed_at : d.toISOString().split('T')[0];
|
|
103
|
+
this.log(` Deployed: ${deployedDate}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error instanceof Error) {
|
|
109
|
+
this.error(`Failed to get tenant: ${error.message}`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.error(`Failed to get tenant: ${String(error)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
loadCredentials() {
|
|
117
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
118
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
119
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
120
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
121
|
+
`Create a profile using 'xano profile create'`);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
125
|
+
const parsed = yaml.load(fileContent);
|
|
126
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
127
|
+
this.error('Credentials file has invalid format.');
|
|
128
|
+
}
|
|
129
|
+
return parsed;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class TenantList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
private loadCredentials;
|
|
13
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { 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 TenantList extends BaseCommand {
|
|
8
|
+
static description = 'List all tenants in a workspace';
|
|
9
|
+
static examples = [
|
|
10
|
+
`$ xano tenant list
|
|
11
|
+
Tenants in workspace 5:
|
|
12
|
+
- My Tenant (my-tenant) [ok] - tier1
|
|
13
|
+
- Staging (staging) [ok] - tier1
|
|
14
|
+
`,
|
|
15
|
+
`$ xano tenant list -w 5 --output json`,
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
...BaseCommand.baseFlags,
|
|
19
|
+
output: Flags.string({
|
|
20
|
+
char: 'o',
|
|
21
|
+
default: 'summary',
|
|
22
|
+
description: 'Output format',
|
|
23
|
+
options: ['summary', 'json'],
|
|
24
|
+
required: false,
|
|
25
|
+
}),
|
|
26
|
+
workspace: Flags.string({
|
|
27
|
+
char: 'w',
|
|
28
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { flags } = await this.parse(TenantList);
|
|
34
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
35
|
+
const credentials = this.loadCredentials();
|
|
36
|
+
if (!(profileName in credentials.profiles)) {
|
|
37
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
38
|
+
`Create 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 workspaceId = flags.workspace || profile.workspace;
|
|
48
|
+
if (!workspaceId) {
|
|
49
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
50
|
+
}
|
|
51
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant`;
|
|
52
|
+
try {
|
|
53
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
54
|
+
headers: {
|
|
55
|
+
'accept': 'application/json',
|
|
56
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
57
|
+
},
|
|
58
|
+
method: 'GET',
|
|
59
|
+
}, flags.verbose, profile.access_token);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const errorText = await response.text();
|
|
62
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
63
|
+
}
|
|
64
|
+
const data = await response.json();
|
|
65
|
+
let tenants;
|
|
66
|
+
if (Array.isArray(data)) {
|
|
67
|
+
tenants = data;
|
|
68
|
+
}
|
|
69
|
+
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
70
|
+
tenants = data.items;
|
|
71
|
+
}
|
|
72
|
+
else if (data && typeof data === 'object' && 'tenants' in data && Array.isArray(data.tenants)) {
|
|
73
|
+
tenants = data.tenants;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.error('Unexpected API response format');
|
|
77
|
+
}
|
|
78
|
+
if (flags.output === 'json') {
|
|
79
|
+
this.log(JSON.stringify(tenants, null, 2));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (tenants.length === 0) {
|
|
83
|
+
this.log('No tenants found');
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.log(`Tenants in workspace ${workspaceId}:`);
|
|
87
|
+
for (const tenant of tenants) {
|
|
88
|
+
const state = tenant.state ? ` [${tenant.state}]` : '';
|
|
89
|
+
const license = tenant.license ? ` - ${tenant.license}` : '';
|
|
90
|
+
this.log(` - ${tenant.display || tenant.name} (${tenant.name})${state}${license}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
this.error(`Failed to list tenants: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.error(`Failed to list tenants: ${String(error)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
loadCredentials() {
|
|
105
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
106
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
107
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
108
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
109
|
+
`Create a profile using 'xano profile create'`);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
113
|
+
const parsed = yaml.load(fileContent);
|
|
114
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
115
|
+
this.error('Credentials file has invalid format.');
|
|
116
|
+
}
|
|
117
|
+
return parsed;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import BaseCommand from '../../../base-command.js';
|
|
2
2
|
export default class WorkspaceCreate extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
3
6
|
static description: string;
|
|
4
7
|
static examples: string[];
|
|
5
8
|
static flags: {
|
|
6
9
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
-
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
10
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
11
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
12
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|