@xano/cli 0.0.31 → 0.0.32
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 +20 -0
- package/dist/commands/tenant/backup/create/index.js +113 -0
- package/dist/commands/tenant/backup/delete/index.d.ts +22 -0
- package/dist/commands/tenant/backup/delete/index.js +137 -0
- package/dist/commands/tenant/backup/export/index.d.ts +21 -0
- package/dist/commands/tenant/backup/export/index.js +147 -0
- package/dist/commands/tenant/backup/import/index.d.ts +21 -0
- package/dist/commands/tenant/backup/import/index.js +127 -0
- package/dist/commands/tenant/backup/list/index.d.ts +20 -0
- package/dist/commands/tenant/backup/list/index.js +137 -0
- package/dist/commands/tenant/backup/restore/index.d.ts +22 -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 +21 -0
- package/dist/commands/tenant/delete/index.js +134 -0
- package/dist/commands/tenant/deploy-platform/index.d.ts +20 -0
- package/dist/commands/tenant/deploy-platform/index.js +116 -0
- package/dist/commands/tenant/deploy-release/index.d.ts +20 -0
- package/dist/commands/tenant/deploy-release/index.js +116 -0
- package/dist/commands/tenant/edit/index.d.ts +26 -0
- package/dist/commands/tenant/edit/index.js +167 -0
- package/dist/commands/tenant/get/index.d.ts +19 -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.js +3 -3
- 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 +3213 -1256
- package/package.json +10 -1
package/dist/base-command.d.ts
CHANGED
|
@@ -10,4 +10,9 @@ export default abstract class BaseCommand extends Command {
|
|
|
10
10
|
};
|
|
11
11
|
protected getDefaultProfile(): string;
|
|
12
12
|
protected getProfile(): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Make an HTTP request with optional verbose logging.
|
|
15
|
+
* Use this for all Metadata API calls to support the --verbose flag.
|
|
16
|
+
*/
|
|
17
|
+
protected verboseFetch(url: string, options: RequestInit, verbose: boolean, authToken?: string): Promise<Response>;
|
|
13
18
|
}
|
package/dist/base-command.js
CHANGED
|
@@ -7,7 +7,7 @@ export default class BaseCommand extends Command {
|
|
|
7
7
|
static baseFlags = {
|
|
8
8
|
profile: Flags.string({
|
|
9
9
|
char: 'p',
|
|
10
|
-
description: 'Profile to use
|
|
10
|
+
description: 'Profile to use (uses default profile if not specified)',
|
|
11
11
|
env: 'XANO_PROFILE',
|
|
12
12
|
required: false,
|
|
13
13
|
}),
|
|
@@ -44,4 +44,35 @@ export default class BaseCommand extends Command {
|
|
|
44
44
|
getProfile() {
|
|
45
45
|
return this.flags?.profile;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Make an HTTP request with optional verbose logging.
|
|
49
|
+
* Use this for all Metadata API calls to support the --verbose flag.
|
|
50
|
+
*/
|
|
51
|
+
async verboseFetch(url, options, verbose, authToken) {
|
|
52
|
+
const method = options.method || 'GET';
|
|
53
|
+
const contentType = options.headers?.['Content-Type'] || 'application/json';
|
|
54
|
+
if (verbose) {
|
|
55
|
+
this.log('');
|
|
56
|
+
this.log('─'.repeat(60));
|
|
57
|
+
this.log(`→ ${method} ${url}`);
|
|
58
|
+
this.log(` Content-Type: ${contentType}`);
|
|
59
|
+
if (authToken) {
|
|
60
|
+
this.log(` Authorization: Bearer ${authToken.slice(0, 8)}...${authToken.slice(-4)}`);
|
|
61
|
+
}
|
|
62
|
+
if (options.body) {
|
|
63
|
+
const bodyStr = typeof options.body === 'string' ? options.body : String(options.body);
|
|
64
|
+
const bodyPreview = bodyStr.length > 500 ? bodyStr.slice(0, 500) + '...' : bodyStr;
|
|
65
|
+
this.log(` Body: ${bodyPreview}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
const response = await fetch(url, options);
|
|
70
|
+
const elapsed = Date.now() - startTime;
|
|
71
|
+
if (verbose) {
|
|
72
|
+
this.log(`← ${response.status} ${response.statusText} (${elapsed}ms)`);
|
|
73
|
+
this.log('─'.repeat(60));
|
|
74
|
+
this.log('');
|
|
75
|
+
}
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
47
78
|
}
|
|
@@ -101,15 +101,15 @@ Created branch: feature-auth
|
|
|
101
101
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch`;
|
|
102
102
|
// Create branch via the API
|
|
103
103
|
try {
|
|
104
|
-
const response = await
|
|
104
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
105
105
|
body: JSON.stringify(body),
|
|
106
106
|
headers: {
|
|
107
107
|
'accept': 'application/json',
|
|
108
108
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
109
|
-
'
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
110
|
},
|
|
111
111
|
method: 'POST',
|
|
112
|
-
});
|
|
112
|
+
}, flags.verbose, profile.access_token);
|
|
113
113
|
if (!response.ok) {
|
|
114
114
|
const errorText = await response.text();
|
|
115
115
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -90,13 +90,13 @@ Deleted branch: dev
|
|
|
90
90
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch/${encodeURIComponent(branchLabel)}`;
|
|
91
91
|
// Delete branch via the API
|
|
92
92
|
try {
|
|
93
|
-
const response = await
|
|
93
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
94
94
|
headers: {
|
|
95
95
|
'accept': 'application/json',
|
|
96
96
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
97
97
|
},
|
|
98
98
|
method: 'DELETE',
|
|
99
|
-
});
|
|
99
|
+
}, flags.verbose, profile.access_token);
|
|
100
100
|
if (!response.ok) {
|
|
101
101
|
const errorText = await response.text();
|
|
102
102
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -104,15 +104,15 @@ Updated branch: feature-authentication
|
|
|
104
104
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch/${encodeURIComponent(branchLabel)}`;
|
|
105
105
|
// Update branch via the API
|
|
106
106
|
try {
|
|
107
|
-
const response = await
|
|
107
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
108
108
|
body: JSON.stringify(body),
|
|
109
109
|
headers: {
|
|
110
110
|
'accept': 'application/json',
|
|
111
111
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
112
|
-
'
|
|
112
|
+
'Content-Type': 'application/json',
|
|
113
113
|
},
|
|
114
114
|
method: 'PUT',
|
|
115
|
-
});
|
|
115
|
+
}, flags.verbose, profile.access_token);
|
|
116
116
|
if (!response.ok) {
|
|
117
117
|
const errorText = await response.text();
|
|
118
118
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -75,13 +75,13 @@ Branch: dev
|
|
|
75
75
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch/${encodeURIComponent(branchLabel)}`;
|
|
76
76
|
// Fetch branch from the API
|
|
77
77
|
try {
|
|
78
|
-
const response = await
|
|
78
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
79
79
|
headers: {
|
|
80
80
|
'accept': 'application/json',
|
|
81
81
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
82
82
|
},
|
|
83
83
|
method: 'GET',
|
|
84
|
-
});
|
|
84
|
+
}, flags.verbose, profile.access_token);
|
|
85
85
|
if (!response.ok) {
|
|
86
86
|
const errorText = await response.text();
|
|
87
87
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -74,13 +74,13 @@ Available branches:
|
|
|
74
74
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch`;
|
|
75
75
|
// Fetch branches from the API
|
|
76
76
|
try {
|
|
77
|
-
const response = await
|
|
77
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
78
78
|
headers: {
|
|
79
79
|
'accept': 'application/json',
|
|
80
80
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
81
81
|
},
|
|
82
82
|
method: 'GET',
|
|
83
|
-
});
|
|
83
|
+
}, flags.verbose, profile.access_token);
|
|
84
84
|
if (!response.ok) {
|
|
85
85
|
const errorText = await response.text();
|
|
86
86
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -88,13 +88,13 @@ Branch 'v1' is now live
|
|
|
88
88
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch/${encodeURIComponent(branchLabel)}/live`;
|
|
89
89
|
// Set branch as live via the API
|
|
90
90
|
try {
|
|
91
|
-
const response = await
|
|
91
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
92
92
|
headers: {
|
|
93
93
|
'accept': 'application/json',
|
|
94
94
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
95
95
|
},
|
|
96
96
|
method: 'POST',
|
|
97
|
-
});
|
|
97
|
+
}, flags.verbose, profile.access_token);
|
|
98
98
|
if (!response.ok) {
|
|
99
99
|
const errorText = await response.text();
|
|
100
100
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -153,7 +153,7 @@ Name: my_function
|
|
|
153
153
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
|
|
154
154
|
// Create function via API
|
|
155
155
|
try {
|
|
156
|
-
const response = await
|
|
156
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
157
157
|
body: xanoscript,
|
|
158
158
|
headers: {
|
|
159
159
|
'accept': 'application/json',
|
|
@@ -161,7 +161,7 @@ Name: my_function
|
|
|
161
161
|
'Content-Type': 'text/x-xanoscript',
|
|
162
162
|
},
|
|
163
163
|
method: 'POST',
|
|
164
|
-
});
|
|
164
|
+
}, flags.verbose, profile.access_token);
|
|
165
165
|
if (!response.ok) {
|
|
166
166
|
const errorText = await response.text();
|
|
167
167
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -196,7 +196,7 @@ Name: my_function
|
|
|
196
196
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/${functionId}?${queryParams.toString()}`;
|
|
197
197
|
// Update function via API
|
|
198
198
|
try {
|
|
199
|
-
const response = await
|
|
199
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
200
200
|
body: xanoscript,
|
|
201
201
|
headers: {
|
|
202
202
|
'accept': 'application/json',
|
|
@@ -204,7 +204,7 @@ Name: my_function
|
|
|
204
204
|
'Content-Type': 'text/x-xanoscript',
|
|
205
205
|
},
|
|
206
206
|
method: 'PUT',
|
|
207
|
-
});
|
|
207
|
+
}, flags.verbose, profile.access_token);
|
|
208
208
|
if (!response.ok) {
|
|
209
209
|
const errorText = await response.text();
|
|
210
210
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -122,13 +122,13 @@ function yo {
|
|
|
122
122
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/${functionId}?${queryParams.toString()}`;
|
|
123
123
|
// Fetch function from the API
|
|
124
124
|
try {
|
|
125
|
-
const response = await
|
|
125
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
126
126
|
headers: {
|
|
127
127
|
'accept': 'application/json',
|
|
128
128
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
129
129
|
},
|
|
130
130
|
method: 'GET',
|
|
131
|
-
});
|
|
131
|
+
}, flags.verbose, profile.access_token);
|
|
132
132
|
if (!response.ok) {
|
|
133
133
|
const errorText = await response.text();
|
|
134
134
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -127,13 +127,13 @@ Available functions:
|
|
|
127
127
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
|
|
128
128
|
// Fetch functions from the API
|
|
129
129
|
try {
|
|
130
|
-
const response = await
|
|
130
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
131
131
|
headers: {
|
|
132
132
|
'accept': 'application/json',
|
|
133
133
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
134
134
|
},
|
|
135
135
|
method: 'GET',
|
|
136
|
-
});
|
|
136
|
+
}, flags.verbose, profile.access_token);
|
|
137
137
|
if (!response.ok) {
|
|
138
138
|
const errorText = await response.text();
|
|
139
139
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class PlatformGet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
platform_id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
private loadCredentials;
|
|
18
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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 PlatformGet extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
platform_id: Args.integer({
|
|
10
|
+
description: 'Platform ID to retrieve',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Get details of a specific platform';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano platform get 23629
|
|
17
|
+
Platform ID: 23629
|
|
18
|
+
Created: 2025-11-28
|
|
19
|
+
Helm: 0.1.356
|
|
20
|
+
Images:
|
|
21
|
+
backend 0.0.2985
|
|
22
|
+
frontend 0.1.3427
|
|
23
|
+
database 0.1.6
|
|
24
|
+
node 0.1.192
|
|
25
|
+
deno 0.0.212
|
|
26
|
+
redis 0.1.34
|
|
27
|
+
realtime 0.1.149
|
|
28
|
+
standalone 0.0.2456
|
|
29
|
+
playwright 0.0.992
|
|
30
|
+
static 0.0.10
|
|
31
|
+
static-build 0.0.4
|
|
32
|
+
backend-encoded 0.0.1396
|
|
33
|
+
`,
|
|
34
|
+
`$ xano platform get 23629 -o json`,
|
|
35
|
+
];
|
|
36
|
+
static flags = {
|
|
37
|
+
...BaseCommand.baseFlags,
|
|
38
|
+
output: Flags.string({
|
|
39
|
+
char: 'o',
|
|
40
|
+
default: 'summary',
|
|
41
|
+
description: 'Output format',
|
|
42
|
+
options: ['summary', 'json'],
|
|
43
|
+
required: false,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
async run() {
|
|
47
|
+
const { args, flags } = await this.parse(PlatformGet);
|
|
48
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
49
|
+
const credentials = this.loadCredentials();
|
|
50
|
+
if (!(profileName in credentials.profiles)) {
|
|
51
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
52
|
+
`Create a profile using 'xano profile create'`);
|
|
53
|
+
}
|
|
54
|
+
const profile = credentials.profiles[profileName];
|
|
55
|
+
if (!profile.instance_origin) {
|
|
56
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
57
|
+
}
|
|
58
|
+
if (!profile.access_token) {
|
|
59
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
60
|
+
}
|
|
61
|
+
const platformId = args.platform_id;
|
|
62
|
+
const apiUrl = `${profile.instance_origin}/api:meta/platform/${platformId}`;
|
|
63
|
+
try {
|
|
64
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
65
|
+
headers: {
|
|
66
|
+
'accept': 'application/json',
|
|
67
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
68
|
+
},
|
|
69
|
+
method: 'GET',
|
|
70
|
+
}, flags.verbose, profile.access_token);
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const errorText = await response.text();
|
|
73
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
74
|
+
}
|
|
75
|
+
const platform = await response.json();
|
|
76
|
+
if (flags.output === 'json') {
|
|
77
|
+
this.log(JSON.stringify(platform, null, 2));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const label = platform.name ? `Platform: ${platform.name} (ID: ${platform.id})` : `Platform ID: ${platform.id}`;
|
|
81
|
+
this.log(label);
|
|
82
|
+
if (platform.created_at) {
|
|
83
|
+
const createdDate = new Date(platform.created_at).toISOString().split('T')[0];
|
|
84
|
+
this.log(` Created: ${createdDate}`);
|
|
85
|
+
}
|
|
86
|
+
if (platform.helm?.tag) {
|
|
87
|
+
this.log(` Helm: ${platform.helm.tag}`);
|
|
88
|
+
}
|
|
89
|
+
if (platform.images && Object.keys(platform.images).length > 0) {
|
|
90
|
+
this.log(' Images:');
|
|
91
|
+
const maxLen = Math.max(...Object.keys(platform.images).map(k => k.length));
|
|
92
|
+
for (const [name, image] of Object.entries(platform.images)) {
|
|
93
|
+
this.log(` ${name.padEnd(maxLen)} ${image.tag ?? 'unknown'}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error instanceof Error) {
|
|
100
|
+
this.error(`Failed to get platform: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.error(`Failed to get platform: ${String(error)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
loadCredentials() {
|
|
108
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
109
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
110
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
111
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
112
|
+
`Create a profile using 'xano profile create'`);
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
116
|
+
const parsed = yaml.load(fileContent);
|
|
117
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
118
|
+
this.error('Credentials file has invalid format.');
|
|
119
|
+
}
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class PlatformList 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
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
private loadCredentials;
|
|
12
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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 PlatformList extends BaseCommand {
|
|
8
|
+
static description = 'List all platforms';
|
|
9
|
+
static examples = [
|
|
10
|
+
`$ xano platform list
|
|
11
|
+
Platforms:
|
|
12
|
+
ID: 23629 | Helm: 0.1.356 | Created: 2025-11-28
|
|
13
|
+
`,
|
|
14
|
+
`$ xano platform list --output json`,
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...BaseCommand.baseFlags,
|
|
18
|
+
output: Flags.string({
|
|
19
|
+
char: 'o',
|
|
20
|
+
default: 'summary',
|
|
21
|
+
description: 'Output format',
|
|
22
|
+
options: ['summary', 'json'],
|
|
23
|
+
required: false,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(PlatformList);
|
|
28
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
29
|
+
const credentials = this.loadCredentials();
|
|
30
|
+
if (!(profileName in credentials.profiles)) {
|
|
31
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
32
|
+
`Create a profile using 'xano profile create'`);
|
|
33
|
+
}
|
|
34
|
+
const profile = credentials.profiles[profileName];
|
|
35
|
+
if (!profile.instance_origin) {
|
|
36
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
37
|
+
}
|
|
38
|
+
if (!profile.access_token) {
|
|
39
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
40
|
+
}
|
|
41
|
+
const apiUrl = `${profile.instance_origin}/api:meta/platform`;
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
44
|
+
headers: {
|
|
45
|
+
'accept': 'application/json',
|
|
46
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
47
|
+
},
|
|
48
|
+
method: 'GET',
|
|
49
|
+
}, flags.verbose, profile.access_token);
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const errorText = await response.text();
|
|
52
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
53
|
+
}
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
let platforms;
|
|
56
|
+
if (Array.isArray(data)) {
|
|
57
|
+
platforms = data;
|
|
58
|
+
}
|
|
59
|
+
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
60
|
+
platforms = data.items;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.error('Unexpected API response format');
|
|
64
|
+
}
|
|
65
|
+
if (flags.output === 'json') {
|
|
66
|
+
this.log(JSON.stringify(platforms, null, 2));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (platforms.length === 0) {
|
|
70
|
+
this.log('No platforms found');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.log('Platforms:');
|
|
74
|
+
for (const platform of platforms) {
|
|
75
|
+
const label = platform.name || `ID: ${platform.id}`;
|
|
76
|
+
const helmTag = platform.helm?.tag ? ` | Helm: ${platform.helm.tag}` : '';
|
|
77
|
+
const created = platform.created_at
|
|
78
|
+
? ` | Created: ${new Date(platform.created_at).toISOString().split('T')[0]}`
|
|
79
|
+
: '';
|
|
80
|
+
this.log(` ${label}${helmTag}${created}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof Error) {
|
|
87
|
+
this.error(`Failed to list platforms: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.error(`Failed to list platforms: ${String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
loadCredentials() {
|
|
95
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
96
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
97
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
98
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
99
|
+
`Create a profile using 'xano profile create'`);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
103
|
+
const parsed = yaml.load(fileContent);
|
|
104
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
105
|
+
this.error('Credentials file has invalid format.');
|
|
106
|
+
}
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -60,13 +60,13 @@ User Information:
|
|
|
60
60
|
const apiUrl = `${profile.instance_origin}/api:meta/auth/me`;
|
|
61
61
|
// Fetch user info from the API
|
|
62
62
|
try {
|
|
63
|
-
const response = await
|
|
63
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
64
64
|
headers: {
|
|
65
65
|
'accept': 'application/json',
|
|
66
66
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
67
67
|
},
|
|
68
68
|
method: 'GET',
|
|
69
|
-
});
|
|
69
|
+
}, flags.verbose, profile.access_token);
|
|
70
70
|
if (!response.ok) {
|
|
71
71
|
const errorText = await response.text();
|
|
72
72
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class ReleaseCreate extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
hotfix: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'table-ids': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
private loadCredentials;
|
|
18
|
+
}
|