@xano/cli 1.3.5-beta.0 → 1.3.5-beta.1
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 +1 -1
- package/oclif.manifest.json +3502 -3888
- package/package.json +1 -1
- package/dist/commands/tenant/snapshot/create/index.d.ts +0 -17
- package/dist/commands/tenant/snapshot/create/index.js +0 -78
- package/dist/commands/tenant/snapshot/delete/index.d.ts +0 -19
- package/dist/commands/tenant/snapshot/delete/index.js +0 -102
- package/dist/commands/tenant/snapshot/list/index.d.ts +0 -16
- package/dist/commands/tenant/snapshot/list/index.js +0 -100
- package/dist/commands/tenant/snapshot/swap/index.d.ts +0 -19
- package/dist/commands/tenant/snapshot/swap/index.js +0 -103
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class TenantSnapshotCreate 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
|
-
label: 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
|
-
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
config: 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
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class TenantSnapshotCreate extends BaseCommand {
|
|
4
|
-
static args = {
|
|
5
|
-
tenant_name: Args.string({
|
|
6
|
-
description: 'Tenant name to snapshot',
|
|
7
|
-
required: true,
|
|
8
|
-
}),
|
|
9
|
-
};
|
|
10
|
-
static description = "Create a database snapshot for a tenant (an instant clone of the tenant's database)";
|
|
11
|
-
static examples = [
|
|
12
|
-
`$ xano tenant snapshot create t1234-abcd-xyz1 --label before-v2
|
|
13
|
-
Created snapshot t1234-abcd-xyz1_bk_20260603_203614 for tenant t1234-abcd-xyz1
|
|
14
|
-
`,
|
|
15
|
-
`$ xano tenant snapshot create t1234-abcd-xyz1 -l before-v2 -o json`,
|
|
16
|
-
];
|
|
17
|
-
static flags = {
|
|
18
|
-
...BaseCommand.baseFlags,
|
|
19
|
-
label: Flags.string({
|
|
20
|
-
char: 'l',
|
|
21
|
-
default: '',
|
|
22
|
-
description: 'Optional label appended to the snapshot description (alphanumeric)',
|
|
23
|
-
required: false,
|
|
24
|
-
}),
|
|
25
|
-
output: Flags.string({
|
|
26
|
-
char: 'o',
|
|
27
|
-
default: 'summary',
|
|
28
|
-
description: 'Output format',
|
|
29
|
-
options: ['summary', 'json'],
|
|
30
|
-
required: false,
|
|
31
|
-
}),
|
|
32
|
-
workspace: Flags.string({
|
|
33
|
-
char: 'w',
|
|
34
|
-
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
35
|
-
required: false,
|
|
36
|
-
}),
|
|
37
|
-
};
|
|
38
|
-
async run() {
|
|
39
|
-
const { args, flags } = await this.parse(TenantSnapshotCreate);
|
|
40
|
-
const { profile } = this.resolveProfile(flags);
|
|
41
|
-
const workspaceId = flags.workspace || profile.workspace;
|
|
42
|
-
if (!workspaceId) {
|
|
43
|
-
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
44
|
-
}
|
|
45
|
-
const tenantName = args.tenant_name;
|
|
46
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/snapshot`;
|
|
47
|
-
try {
|
|
48
|
-
const response = await this.verboseFetch(apiUrl, {
|
|
49
|
-
body: JSON.stringify({ label: flags.label }),
|
|
50
|
-
headers: {
|
|
51
|
-
'accept': 'application/json',
|
|
52
|
-
'Authorization': `Bearer ${profile.access_token}`,
|
|
53
|
-
'Content-Type': 'application/json',
|
|
54
|
-
},
|
|
55
|
-
method: 'POST',
|
|
56
|
-
}, flags.verbose, profile.access_token);
|
|
57
|
-
if (!response.ok) {
|
|
58
|
-
const errorText = await response.text();
|
|
59
|
-
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
60
|
-
}
|
|
61
|
-
const result = (await response.json());
|
|
62
|
-
if (flags.output === 'json') {
|
|
63
|
-
this.log(JSON.stringify(result, null, 2));
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
this.log(`Created snapshot ${result.backup} for tenant ${tenantName}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
if (error instanceof Error) {
|
|
71
|
-
this.error(`Failed to create snapshot: ${error.message}`);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
this.error(`Failed to create snapshot: ${String(error)}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class TenantSnapshotDelete 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
|
-
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
snapshot: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
-
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
-
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
-
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
-
};
|
|
17
|
-
run(): Promise<void>;
|
|
18
|
-
private confirm;
|
|
19
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import * as readline from 'node:readline';
|
|
3
|
-
import BaseCommand from '../../../../base-command.js';
|
|
4
|
-
export default class TenantSnapshotDelete extends BaseCommand {
|
|
5
|
-
static args = {
|
|
6
|
-
tenant_name: Args.string({
|
|
7
|
-
description: 'Tenant name that owns the snapshot',
|
|
8
|
-
required: true,
|
|
9
|
-
}),
|
|
10
|
-
};
|
|
11
|
-
static description = '[CRITICAL] NEVER delete a snapshot without explicit user confirmation; this permanently drops the snapshot database. The live and original databases cannot be deleted.';
|
|
12
|
-
static examples = [
|
|
13
|
-
`$ xano tenant snapshot delete t1234-abcd-xyz1 --snapshot t1234-abcd-xyz1_bk_20260603_203614
|
|
14
|
-
Are you sure you want to delete snapshot "t1234-abcd-xyz1_bk_20260603_203614"? This action cannot be undone. (y/N) y
|
|
15
|
-
Deleted snapshot t1234-abcd-xyz1_bk_20260603_203614
|
|
16
|
-
`,
|
|
17
|
-
`$ xano tenant snapshot delete t1234-abcd-xyz1 --snapshot t1234-abcd-xyz1_bk_20260603_203614 --force`,
|
|
18
|
-
];
|
|
19
|
-
static flags = {
|
|
20
|
-
...BaseCommand.baseFlags,
|
|
21
|
-
force: Flags.boolean({
|
|
22
|
-
char: 'f',
|
|
23
|
-
default: false,
|
|
24
|
-
description: '[CRITICAL] Skips the confirmation prompt.',
|
|
25
|
-
required: false,
|
|
26
|
-
}),
|
|
27
|
-
output: Flags.string({
|
|
28
|
-
char: 'o',
|
|
29
|
-
default: 'summary',
|
|
30
|
-
description: 'Output format',
|
|
31
|
-
options: ['summary', 'json'],
|
|
32
|
-
required: false,
|
|
33
|
-
}),
|
|
34
|
-
snapshot: Flags.string({
|
|
35
|
-
description: 'Snapshot database name to delete',
|
|
36
|
-
required: true,
|
|
37
|
-
}),
|
|
38
|
-
workspace: Flags.string({
|
|
39
|
-
char: 'w',
|
|
40
|
-
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
41
|
-
required: false,
|
|
42
|
-
}),
|
|
43
|
-
};
|
|
44
|
-
async run() {
|
|
45
|
-
const { args, flags } = await this.parse(TenantSnapshotDelete);
|
|
46
|
-
const { profile } = this.resolveProfile(flags);
|
|
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 tenantName = args.tenant_name;
|
|
52
|
-
const { snapshot } = flags;
|
|
53
|
-
if (!flags.force) {
|
|
54
|
-
const confirmed = await this.confirm(`Are you sure you want to delete snapshot "${snapshot}"? This action cannot be undone.`);
|
|
55
|
-
if (!confirmed) {
|
|
56
|
-
this.log('Deletion cancelled.');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
const queryParams = new URLSearchParams({ snapshot });
|
|
61
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/snapshot?${queryParams.toString()}`;
|
|
62
|
-
try {
|
|
63
|
-
const response = await this.verboseFetch(apiUrl, {
|
|
64
|
-
headers: {
|
|
65
|
-
accept: 'application/json',
|
|
66
|
-
Authorization: `Bearer ${profile.access_token}`,
|
|
67
|
-
},
|
|
68
|
-
method: 'DELETE',
|
|
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
|
-
if (flags.output === 'json') {
|
|
75
|
-
this.log(JSON.stringify({ deleted: true, snapshot, tenant_name: tenantName }, null, 2));
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
this.log(`Deleted snapshot ${snapshot}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
if (error instanceof Error) {
|
|
83
|
-
this.error(`Failed to delete snapshot: ${error.message}`);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
this.error(`Failed to delete snapshot: ${String(error)}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async confirm(message) {
|
|
91
|
-
const rl = readline.createInterface({
|
|
92
|
-
input: process.stdin,
|
|
93
|
-
output: process.stdout,
|
|
94
|
-
});
|
|
95
|
-
return new Promise((resolve) => {
|
|
96
|
-
rl.question(`${message} (y/N) `, (answer) => {
|
|
97
|
-
rl.close();
|
|
98
|
-
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class TenantSnapshotList 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
|
-
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
-
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
-
};
|
|
15
|
-
run(): Promise<void>;
|
|
16
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
import { buildPagingJson, formatPagingFooter } from '../../../../utils/paging.js';
|
|
4
|
-
export default class TenantSnapshotList extends BaseCommand {
|
|
5
|
-
static args = {
|
|
6
|
-
tenant_name: Args.string({
|
|
7
|
-
description: 'Tenant name to list snapshots for',
|
|
8
|
-
required: true,
|
|
9
|
-
}),
|
|
10
|
-
};
|
|
11
|
-
static description = 'List database snapshots for a tenant';
|
|
12
|
-
static examples = [
|
|
13
|
-
`$ xano tenant snapshot list t1234-abcd-xyz1
|
|
14
|
-
Snapshots for tenant t1234-abcd-xyz1:
|
|
15
|
-
- t1234-abcd-xyz1 (25 MB) [ORIGINAL]
|
|
16
|
-
- t1234-abcd-xyz1_bk_20260603_203614 (25 MB, 2026-06-03 20:36:14) [LIVE]
|
|
17
|
-
`,
|
|
18
|
-
`$ xano tenant snapshot list t1234-abcd-xyz1 -o json`,
|
|
19
|
-
];
|
|
20
|
-
static flags = {
|
|
21
|
-
...BaseCommand.baseFlags,
|
|
22
|
-
output: Flags.string({
|
|
23
|
-
char: 'o',
|
|
24
|
-
default: 'summary',
|
|
25
|
-
description: 'Output format',
|
|
26
|
-
options: ['summary', 'json'],
|
|
27
|
-
required: false,
|
|
28
|
-
}),
|
|
29
|
-
workspace: Flags.string({
|
|
30
|
-
char: 'w',
|
|
31
|
-
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
32
|
-
required: false,
|
|
33
|
-
}),
|
|
34
|
-
};
|
|
35
|
-
async run() {
|
|
36
|
-
const { args, flags } = await this.parse(TenantSnapshotList);
|
|
37
|
-
const { profile } = this.resolveProfile(flags);
|
|
38
|
-
const workspaceId = flags.workspace || profile.workspace;
|
|
39
|
-
if (!workspaceId) {
|
|
40
|
-
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
41
|
-
}
|
|
42
|
-
const tenantName = args.tenant_name;
|
|
43
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/snapshot`;
|
|
44
|
-
try {
|
|
45
|
-
const response = await this.verboseFetch(apiUrl, {
|
|
46
|
-
headers: {
|
|
47
|
-
'accept': 'application/json',
|
|
48
|
-
'Authorization': `Bearer ${profile.access_token}`,
|
|
49
|
-
},
|
|
50
|
-
method: 'GET',
|
|
51
|
-
}, flags.verbose, profile.access_token);
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
const errorText = await response.text();
|
|
54
|
-
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
55
|
-
}
|
|
56
|
-
const data = (await response.json());
|
|
57
|
-
let snapshots;
|
|
58
|
-
if (Array.isArray(data)) {
|
|
59
|
-
snapshots = data;
|
|
60
|
-
}
|
|
61
|
-
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
62
|
-
snapshots = data.items;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
this.error('Unexpected API response format');
|
|
66
|
-
}
|
|
67
|
-
if (flags.output === 'json') {
|
|
68
|
-
this.log(JSON.stringify(buildPagingJson({ items: snapshots }, { tier: 'none' }), null, 2));
|
|
69
|
-
}
|
|
70
|
-
else if (snapshots.length === 0) {
|
|
71
|
-
this.log(`No snapshots found for tenant ${tenantName}`);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
this.log(`Snapshots for tenant ${tenantName}:`);
|
|
75
|
-
for (const snapshot of snapshots) {
|
|
76
|
-
const size = snapshot.size_pretty || (snapshot.size_bytes ? `${snapshot.size_bytes} bytes` : 'unknown');
|
|
77
|
-
const meta = snapshot.created ? `${size}, ${snapshot.created}` : size;
|
|
78
|
-
const tags = [];
|
|
79
|
-
if (snapshot.is_original)
|
|
80
|
-
tags.push('ORIGINAL');
|
|
81
|
-
if (snapshot.is_live)
|
|
82
|
-
tags.push('LIVE');
|
|
83
|
-
const tagStr = tags.length > 0 ? ` [${tags.join(', ')}]` : '';
|
|
84
|
-
this.log(` - ${snapshot.name} (${meta})${tagStr}`);
|
|
85
|
-
}
|
|
86
|
-
const footer = formatPagingFooter({ items: snapshots }, { noun: 'snapshot', tier: 'none' });
|
|
87
|
-
if (footer)
|
|
88
|
-
this.log(footer);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
if (error instanceof Error) {
|
|
93
|
-
this.error(`Failed to list snapshots: ${error.message}`);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
this.error(`Failed to list snapshots: ${String(error)}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class TenantSnapshotSwap 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
|
-
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
snapshot: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
-
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
-
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
-
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
-
};
|
|
17
|
-
run(): Promise<void>;
|
|
18
|
-
private confirm;
|
|
19
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import * as readline from 'node:readline';
|
|
3
|
-
import BaseCommand from '../../../../base-command.js';
|
|
4
|
-
export default class TenantSnapshotSwap extends BaseCommand {
|
|
5
|
-
static args = {
|
|
6
|
-
tenant_name: Args.string({
|
|
7
|
-
description: 'Tenant name to swap',
|
|
8
|
-
required: true,
|
|
9
|
-
}),
|
|
10
|
-
};
|
|
11
|
-
static description = "Swap a tenant's live database to a snapshot. This repoints the tenant; the current live database is left untouched, so you can swap back. To roll back, swap to the original database name.";
|
|
12
|
-
static examples = [
|
|
13
|
-
`$ xano tenant snapshot swap t1234-abcd-xyz1 --snapshot t1234-abcd-xyz1_bk_20260603_203614
|
|
14
|
-
Swapped tenant t1234-abcd-xyz1 from t1234-abcd-xyz1 to t1234-abcd-xyz1_bk_20260603_203614
|
|
15
|
-
`,
|
|
16
|
-
`$ xano tenant snapshot swap t1234-abcd-xyz1 --snapshot t1234-abcd-xyz1 --force`,
|
|
17
|
-
];
|
|
18
|
-
static flags = {
|
|
19
|
-
...BaseCommand.baseFlags,
|
|
20
|
-
force: Flags.boolean({
|
|
21
|
-
char: 'f',
|
|
22
|
-
default: false,
|
|
23
|
-
description: 'Skips the confirmation prompt.',
|
|
24
|
-
required: false,
|
|
25
|
-
}),
|
|
26
|
-
output: Flags.string({
|
|
27
|
-
char: 'o',
|
|
28
|
-
default: 'summary',
|
|
29
|
-
description: 'Output format',
|
|
30
|
-
options: ['summary', 'json'],
|
|
31
|
-
required: false,
|
|
32
|
-
}),
|
|
33
|
-
snapshot: Flags.string({
|
|
34
|
-
description: 'Snapshot database name to swap to (use the original tenant name to roll back)',
|
|
35
|
-
required: true,
|
|
36
|
-
}),
|
|
37
|
-
workspace: Flags.string({
|
|
38
|
-
char: 'w',
|
|
39
|
-
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
40
|
-
required: false,
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
async run() {
|
|
44
|
-
const { args, flags } = await this.parse(TenantSnapshotSwap);
|
|
45
|
-
const { profile } = this.resolveProfile(flags);
|
|
46
|
-
const workspaceId = flags.workspace || profile.workspace;
|
|
47
|
-
if (!workspaceId) {
|
|
48
|
-
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
49
|
-
}
|
|
50
|
-
const tenantName = args.tenant_name;
|
|
51
|
-
const { snapshot } = flags;
|
|
52
|
-
if (!flags.force) {
|
|
53
|
-
const confirmed = await this.confirm(`Swap tenant ${tenantName} to "${snapshot}"? This repoints the tenant's live database.`);
|
|
54
|
-
if (!confirmed) {
|
|
55
|
-
this.log('Swap cancelled.');
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/snapshot/swap`;
|
|
60
|
-
try {
|
|
61
|
-
const response = await this.verboseFetch(apiUrl, {
|
|
62
|
-
body: JSON.stringify({ snapshot }),
|
|
63
|
-
headers: {
|
|
64
|
-
'accept': 'application/json',
|
|
65
|
-
'Authorization': `Bearer ${profile.access_token}`,
|
|
66
|
-
'Content-Type': 'application/json',
|
|
67
|
-
},
|
|
68
|
-
method: 'POST',
|
|
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 result = (await response.json());
|
|
75
|
-
if (flags.output === 'json') {
|
|
76
|
-
this.log(JSON.stringify(result, null, 2));
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
this.log(`Swapped tenant ${tenantName} from ${result.from ?? 'unknown'} to ${result.to ?? snapshot}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
if (error instanceof Error) {
|
|
84
|
-
this.error(`Failed to swap snapshot: ${error.message}`);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
this.error(`Failed to swap snapshot: ${String(error)}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async confirm(message) {
|
|
92
|
-
const rl = readline.createInterface({
|
|
93
|
-
input: process.stdin,
|
|
94
|
-
output: process.stdout,
|
|
95
|
-
});
|
|
96
|
-
return new Promise((resolve) => {
|
|
97
|
-
rl.question(`${message} (y/N) `, (answer) => {
|
|
98
|
-
rl.close();
|
|
99
|
-
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|