@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
|
@@ -0,0 +1,123 @@
|
|
|
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 ReleaseGet extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
release_id: Args.integer({
|
|
10
|
+
description: 'Release ID to retrieve',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Get details of a specific release';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano release get 10
|
|
17
|
+
Release: v1.0 - ID: 10
|
|
18
|
+
Branch: main
|
|
19
|
+
Description: Initial release
|
|
20
|
+
Hotfix: false
|
|
21
|
+
`,
|
|
22
|
+
`$ xano release get 10 -w 5 -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
|
+
workspace: Flags.string({
|
|
34
|
+
char: 'w',
|
|
35
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
36
|
+
required: false,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
async run() {
|
|
40
|
+
const { args, flags } = await this.parse(ReleaseGet);
|
|
41
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
42
|
+
const credentials = this.loadCredentials();
|
|
43
|
+
if (!(profileName in credentials.profiles)) {
|
|
44
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
45
|
+
`Create a profile using 'xano profile create'`);
|
|
46
|
+
}
|
|
47
|
+
const profile = credentials.profiles[profileName];
|
|
48
|
+
if (!profile.instance_origin) {
|
|
49
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
50
|
+
}
|
|
51
|
+
if (!profile.access_token) {
|
|
52
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
53
|
+
}
|
|
54
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
55
|
+
if (!workspaceId) {
|
|
56
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
57
|
+
}
|
|
58
|
+
const releaseId = args.release_id;
|
|
59
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release/${releaseId}`;
|
|
60
|
+
try {
|
|
61
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
62
|
+
headers: {
|
|
63
|
+
'accept': 'application/json',
|
|
64
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
65
|
+
},
|
|
66
|
+
method: 'GET',
|
|
67
|
+
}, flags.verbose, profile.access_token);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const errorText = await response.text();
|
|
70
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
71
|
+
}
|
|
72
|
+
const release = await response.json();
|
|
73
|
+
if (flags.output === 'json') {
|
|
74
|
+
this.log(JSON.stringify(release, null, 2));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
this.log(`Release: ${release.name} - ID: ${release.id}`);
|
|
78
|
+
if (release.branch)
|
|
79
|
+
this.log(` Branch: ${release.branch}`);
|
|
80
|
+
if (release.description)
|
|
81
|
+
this.log(` Description: ${release.description}`);
|
|
82
|
+
if (release.hotfix !== undefined)
|
|
83
|
+
this.log(` Hotfix: ${release.hotfix}`);
|
|
84
|
+
if (release.resource_size !== undefined)
|
|
85
|
+
this.log(` Resource Size: ${release.resource_size}`);
|
|
86
|
+
if (release.tables && release.tables.length > 0) {
|
|
87
|
+
this.log(` Tables: ${release.tables.map(t => t.name || t.id).join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
if (release.created_at) {
|
|
90
|
+
const createdDate = new Date(release.created_at).toISOString().split('T')[0];
|
|
91
|
+
this.log(` Created: ${createdDate}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
this.error(`Failed to get release: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.error(`Failed to get release: ${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
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class ReleaseImport extends BaseCommand {
|
|
3
|
+
static args: {};
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private loadCredentials;
|
|
15
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as yaml from 'js-yaml';
|
|
6
|
+
import BaseCommand from '../../../base-command.js';
|
|
7
|
+
export default class ReleaseImport extends BaseCommand {
|
|
8
|
+
static args = {};
|
|
9
|
+
static description = 'Import a release file into a workspace';
|
|
10
|
+
static examples = [
|
|
11
|
+
`$ xano release import --file ./my-release.tar.gz
|
|
12
|
+
Imported release as #15
|
|
13
|
+
`,
|
|
14
|
+
`$ xano release import --file ./my-release.tar.gz -o json`,
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...BaseCommand.baseFlags,
|
|
18
|
+
file: Flags.string({
|
|
19
|
+
char: 'f',
|
|
20
|
+
description: 'Path to the release file (.tar.gz)',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
output: Flags.string({
|
|
24
|
+
char: 'o',
|
|
25
|
+
default: 'summary',
|
|
26
|
+
description: 'Output format',
|
|
27
|
+
options: ['summary', 'json'],
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
workspace: Flags.string({
|
|
31
|
+
char: 'w',
|
|
32
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
33
|
+
required: false,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
async run() {
|
|
37
|
+
const { flags } = await this.parse(ReleaseImport);
|
|
38
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
39
|
+
const credentials = this.loadCredentials();
|
|
40
|
+
if (!(profileName in credentials.profiles)) {
|
|
41
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
42
|
+
`Create 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 workspaceId = flags.workspace || profile.workspace;
|
|
52
|
+
if (!workspaceId) {
|
|
53
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
54
|
+
}
|
|
55
|
+
const filePath = path.resolve(flags.file);
|
|
56
|
+
if (!fs.existsSync(filePath)) {
|
|
57
|
+
this.error(`File not found: ${filePath}`);
|
|
58
|
+
}
|
|
59
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release/import`;
|
|
60
|
+
try {
|
|
61
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
62
|
+
const blob = new Blob([fileBuffer], { type: 'application/gzip' });
|
|
63
|
+
const formData = new FormData();
|
|
64
|
+
formData.append('file', blob, path.basename(filePath));
|
|
65
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
66
|
+
body: formData,
|
|
67
|
+
headers: {
|
|
68
|
+
'accept': 'application/json',
|
|
69
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
70
|
+
},
|
|
71
|
+
method: 'POST',
|
|
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
|
+
const result = await response.json();
|
|
78
|
+
if (flags.output === 'json') {
|
|
79
|
+
this.log(JSON.stringify(result, null, 2));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const sizeMb = (fileBuffer.length / 1024 / 1024).toFixed(2);
|
|
83
|
+
this.log(`Imported release as #${result.id} (${sizeMb} MB)`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
this.error(`Failed to import release: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.error(`Failed to import release: ${String(error)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
loadCredentials() {
|
|
96
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
97
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
98
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
99
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
100
|
+
`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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class ReleaseList 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,120 @@
|
|
|
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 ReleaseList extends BaseCommand {
|
|
8
|
+
static description = 'List all releases in a workspace';
|
|
9
|
+
static examples = [
|
|
10
|
+
`$ xano release list
|
|
11
|
+
Releases in workspace 5:
|
|
12
|
+
- v1.0 (ID: 10) - main
|
|
13
|
+
- v1.1-hotfix (ID: 11) - main [hotfix]
|
|
14
|
+
`,
|
|
15
|
+
`$ xano release 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(ReleaseList);
|
|
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}/release`;
|
|
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 releases;
|
|
66
|
+
if (Array.isArray(data)) {
|
|
67
|
+
releases = data;
|
|
68
|
+
}
|
|
69
|
+
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
70
|
+
releases = data.items;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.error('Unexpected API response format');
|
|
74
|
+
}
|
|
75
|
+
if (flags.output === 'json') {
|
|
76
|
+
this.log(JSON.stringify(releases, null, 2));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (releases.length === 0) {
|
|
80
|
+
this.log('No releases found');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.log(`Releases in workspace ${workspaceId}:`);
|
|
84
|
+
for (const release of releases) {
|
|
85
|
+
const branch = release.branch ? ` - ${release.branch}` : '';
|
|
86
|
+
const hotfix = release.hotfix ? ' [hotfix]' : '';
|
|
87
|
+
this.log(` - ${release.name} (ID: ${release.id})${branch}${hotfix}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error instanceof Error) {
|
|
94
|
+
this.error(`Failed to list releases: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.error(`Failed to list releases: ${String(error)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
loadCredentials() {
|
|
102
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
103
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
104
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
105
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
106
|
+
`Create a profile using 'xano profile create'`);
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
110
|
+
const parsed = yaml.load(fileContent);
|
|
111
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
112
|
+
this.error('Credentials file has invalid format.');
|
|
113
|
+
}
|
|
114
|
+
return parsed;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -127,14 +127,14 @@ Description: Production build
|
|
|
127
127
|
}
|
|
128
128
|
// Create build via API
|
|
129
129
|
try {
|
|
130
|
-
const response = await
|
|
130
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
131
131
|
body: formData,
|
|
132
132
|
headers: {
|
|
133
133
|
'accept': 'application/json',
|
|
134
134
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
135
135
|
},
|
|
136
136
|
method: 'POST',
|
|
137
|
-
});
|
|
137
|
+
}, flags.verbose, profile.access_token);
|
|
138
138
|
if (!response.ok) {
|
|
139
139
|
const errorText = await response.text();
|
|
140
140
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -93,13 +93,13 @@ Name: production-build
|
|
|
93
93
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}/build/${args.build_id}`;
|
|
94
94
|
// Fetch build from the API
|
|
95
95
|
try {
|
|
96
|
-
const response = await
|
|
96
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
97
97
|
headers: {
|
|
98
98
|
'accept': 'application/json',
|
|
99
99
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
100
100
|
},
|
|
101
101
|
method: 'GET',
|
|
102
|
-
});
|
|
102
|
+
}, flags.verbose, profile.access_token);
|
|
103
103
|
if (!response.ok) {
|
|
104
104
|
const errorText = await response.text();
|
|
105
105
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -110,13 +110,13 @@ Available builds:
|
|
|
110
110
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}/build?${queryParams.toString()}`;
|
|
111
111
|
// Fetch builds from the API
|
|
112
112
|
try {
|
|
113
|
-
const response = await
|
|
113
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
114
114
|
headers: {
|
|
115
115
|
'accept': 'application/json',
|
|
116
116
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
117
117
|
},
|
|
118
118
|
method: 'GET',
|
|
119
|
-
});
|
|
119
|
+
}, flags.verbose, profile.access_token);
|
|
120
120
|
if (!response.ok) {
|
|
121
121
|
const errorText = await response.text();
|
|
122
122
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -105,13 +105,13 @@ Available static hosts:
|
|
|
105
105
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host?${queryParams.toString()}`;
|
|
106
106
|
// Fetch static hosts from the API
|
|
107
107
|
try {
|
|
108
|
-
const response = await
|
|
108
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
109
109
|
headers: {
|
|
110
110
|
'accept': 'application/json',
|
|
111
111
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
112
112
|
},
|
|
113
113
|
method: 'GET',
|
|
114
|
-
});
|
|
114
|
+
}, flags.verbose, profile.access_token);
|
|
115
115
|
if (!response.ok) {
|
|
116
116
|
const errorText = await response.text();
|
|
117
117
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import BaseCommand from '../../../../base-command.js';
|
|
2
|
+
export default class TenantBackupCreate extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
tenant_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
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
private loadCredentials;
|
|
20
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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 TenantBackupCreate extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
tenant_id: Args.integer({
|
|
10
|
+
description: 'Tenant ID to back up',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Create a backup for a tenant';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant backup create 42 --description "Pre-deploy backup"
|
|
17
|
+
Created backup #15 for tenant 42
|
|
18
|
+
`,
|
|
19
|
+
`$ xano tenant backup create 42 -d "Daily backup" -o json`,
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
...BaseCommand.baseFlags,
|
|
23
|
+
description: Flags.string({
|
|
24
|
+
char: 'd',
|
|
25
|
+
default: '',
|
|
26
|
+
description: 'Backup description',
|
|
27
|
+
required: false,
|
|
28
|
+
}),
|
|
29
|
+
output: Flags.string({
|
|
30
|
+
char: 'o',
|
|
31
|
+
default: 'summary',
|
|
32
|
+
description: 'Output format',
|
|
33
|
+
options: ['summary', 'json'],
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
workspace: Flags.string({
|
|
37
|
+
char: 'w',
|
|
38
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
39
|
+
required: false,
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { args, flags } = await this.parse(TenantBackupCreate);
|
|
44
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
45
|
+
const credentials = this.loadCredentials();
|
|
46
|
+
if (!(profileName in credentials.profiles)) {
|
|
47
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
48
|
+
`Create a profile using 'xano profile create'`);
|
|
49
|
+
}
|
|
50
|
+
const profile = credentials.profiles[profileName];
|
|
51
|
+
if (!profile.instance_origin) {
|
|
52
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
53
|
+
}
|
|
54
|
+
if (!profile.access_token) {
|
|
55
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
56
|
+
}
|
|
57
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
58
|
+
if (!workspaceId) {
|
|
59
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
60
|
+
}
|
|
61
|
+
const tenantId = args.tenant_id;
|
|
62
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantId}/backup`;
|
|
63
|
+
try {
|
|
64
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
65
|
+
body: JSON.stringify({ description: flags.description }),
|
|
66
|
+
headers: {
|
|
67
|
+
'accept': 'application/json',
|
|
68
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
},
|
|
71
|
+
method: 'POST',
|
|
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
|
+
const result = await response.json();
|
|
78
|
+
if (flags.output === 'json') {
|
|
79
|
+
this.log(JSON.stringify(result, null, 2));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.log(`Created backup #${result.id} for tenant ${tenantId}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof Error) {
|
|
87
|
+
this.error(`Failed to create backup: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.error(`Failed to create backup: ${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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import BaseCommand from '../../../../base-command.js';
|
|
2
|
+
export default class TenantBackupDelete extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
tenant_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
|
+
'backup-id': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
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 confirm;
|
|
21
|
+
private loadCredentials;
|
|
22
|
+
}
|