@xano/cli 1.0.4-beta.2 → 1.0.4
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 +4 -51
- package/dist/base-command.d.ts +0 -27
- package/dist/base-command.js +1 -124
- package/dist/commands/auth/index.d.ts +0 -7
- package/dist/commands/auth/index.js +9 -85
- package/dist/commands/{tenant/snapshot/list → knowledge/pull}/index.d.ts +4 -6
- package/dist/commands/knowledge/pull/index.js +86 -0
- package/dist/commands/knowledge/push/index.d.ts +20 -0
- package/dist/commands/knowledge/push/index.js +126 -0
- package/dist/commands/static_host/build/create/index.d.ts +1 -9
- package/dist/commands/static_host/build/create/index.js +4 -54
- package/dist/commands/static_host/build/get/index.d.ts +1 -1
- package/dist/commands/static_host/build/get/index.js +10 -16
- package/dist/utils/knowledge-sync.d.ts +108 -0
- package/dist/utils/knowledge-sync.js +380 -0
- package/dist/utils/multidoc-push.js +17 -21
- package/dist/utils/reference-checker.js +2 -2
- package/oclif.manifest.json +2565 -3683
- package/package.json +1 -3
- package/dist/commands/static_host/build/delete/index.d.ts +0 -19
- package/dist/commands/static_host/build/delete/index.js +0 -114
- package/dist/commands/static_host/build/pull/index.d.ts +0 -52
- package/dist/commands/static_host/build/pull/index.js +0 -300
- package/dist/commands/static_host/build/push/index.d.ts +0 -23
- package/dist/commands/static_host/build/push/index.js +0 -225
- package/dist/commands/static_host/create/index.d.ts +0 -17
- package/dist/commands/static_host/create/index.js +0 -86
- package/dist/commands/static_host/deploy/index.d.ts +0 -18
- package/dist/commands/static_host/deploy/index.js +0 -105
- package/dist/commands/static_host/edit/index.d.ts +0 -23
- package/dist/commands/static_host/edit/index.js +0 -151
- package/dist/commands/static_host/get/index.d.ts +0 -18
- package/dist/commands/static_host/get/index.js +0 -94
- package/dist/commands/static_host/migrate/index.d.ts +0 -44
- package/dist/commands/static_host/migrate/index.js +0 -205
- 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.js +0 -96
- package/dist/commands/tenant/snapshot/swap/index.d.ts +0 -19
- package/dist/commands/tenant/snapshot/swap/index.js +0 -103
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import BaseCommand from '../../../base-command.js';
|
|
3
|
-
export default class StaticHostGet extends BaseCommand {
|
|
4
|
-
static args = {
|
|
5
|
-
static_host: Args.string({
|
|
6
|
-
description: 'Static Host name',
|
|
7
|
-
required: true,
|
|
8
|
-
}),
|
|
9
|
-
};
|
|
10
|
-
static description = 'Get a single static host\'s details (name, git config, dev/prod environments)';
|
|
11
|
-
static examples = [
|
|
12
|
-
`$ xano static_host get newsite
|
|
13
|
-
Static Host: newsite
|
|
14
|
-
ID: 5
|
|
15
|
-
Dev: https://newsite-dev-....dev.xano.io (v2)
|
|
16
|
-
`,
|
|
17
|
-
`$ xano static_host get newsite -w 40 -o json`,
|
|
18
|
-
];
|
|
19
|
-
static flags = {
|
|
20
|
-
...BaseCommand.baseFlags,
|
|
21
|
-
output: Flags.string({
|
|
22
|
-
char: 'o',
|
|
23
|
-
default: 'summary',
|
|
24
|
-
description: 'Output format',
|
|
25
|
-
options: ['summary', 'json'],
|
|
26
|
-
required: false,
|
|
27
|
-
}),
|
|
28
|
-
workspace: Flags.string({
|
|
29
|
-
char: 'w',
|
|
30
|
-
description: 'Workspace ID (optional if set in profile)',
|
|
31
|
-
required: false,
|
|
32
|
-
}),
|
|
33
|
-
};
|
|
34
|
-
async run() {
|
|
35
|
-
const { args, flags } = await this.parse(StaticHostGet);
|
|
36
|
-
const { profile, profileName } = this.resolveProfile(flags);
|
|
37
|
-
let workspaceId;
|
|
38
|
-
if (flags.workspace) {
|
|
39
|
-
workspaceId = flags.workspace;
|
|
40
|
-
}
|
|
41
|
-
else if (profile.workspace) {
|
|
42
|
-
workspaceId = profile.workspace;
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
this.error(`Workspace ID is required. Either:\n` +
|
|
46
|
-
` 1. Provide it as a flag: xano static_host get <static_host> -w <workspace_id>\n` +
|
|
47
|
-
` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
|
|
48
|
-
}
|
|
49
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}`;
|
|
50
|
-
try {
|
|
51
|
-
const response = await this.verboseFetch(apiUrl, {
|
|
52
|
-
headers: {
|
|
53
|
-
accept: 'application/json',
|
|
54
|
-
Authorization: `Bearer ${profile.access_token}`,
|
|
55
|
-
},
|
|
56
|
-
method: 'GET',
|
|
57
|
-
}, flags.verbose, profile.access_token);
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
const message = await this.parseApiError(response, `Failed to get static host '${args.static_host}'`);
|
|
60
|
-
this.error(message);
|
|
61
|
-
}
|
|
62
|
-
const host = (await response.json());
|
|
63
|
-
if (flags.output === 'json') {
|
|
64
|
-
this.log(JSON.stringify(host, null, 2));
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
this.log(`Static Host: ${host.name}`);
|
|
68
|
-
this.log(`ID: ${host.id}`);
|
|
69
|
-
if (host.description)
|
|
70
|
-
this.log(`Description: ${host.description}`);
|
|
71
|
-
if (host.git?.repo)
|
|
72
|
-
this.log(`Git: ${host.git.repo}`);
|
|
73
|
-
this.logEnv('Dev', host.dev);
|
|
74
|
-
this.logEnv('Prod', host.prod);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
if (error instanceof Error) {
|
|
79
|
-
this.error(`Failed to get static host: ${error.message}`);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
this.error(`Failed to get static host: ${String(error)}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/** Print a one-line summary for a dev/prod env if it has been deployed. */
|
|
87
|
-
logEnv(label, env) {
|
|
88
|
-
if (!env?.default_url)
|
|
89
|
-
return;
|
|
90
|
-
const modeInfo = env.mode ? ` (${env.mode})` : '';
|
|
91
|
-
const custom = env.custom_url ? ` [custom: ${env.custom_url}]` : '';
|
|
92
|
-
this.log(`${label}: ${env.default_url}${modeInfo}${custom}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import BaseCommand from '../../../base-command.js';
|
|
2
|
-
export interface StaticHostEnv {
|
|
3
|
-
canonical?: null | string;
|
|
4
|
-
mode?: null | string;
|
|
5
|
-
}
|
|
6
|
-
export interface StaticHost {
|
|
7
|
-
dev?: StaticHostEnv;
|
|
8
|
-
id: number;
|
|
9
|
-
name: string;
|
|
10
|
-
prod?: StaticHostEnv;
|
|
11
|
-
}
|
|
12
|
-
export default class StaticHostMigrate extends BaseCommand {
|
|
13
|
-
static args: {
|
|
14
|
-
static_host: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
15
|
-
};
|
|
16
|
-
static description: string;
|
|
17
|
-
static examples: string[];
|
|
18
|
-
static flags: {
|
|
19
|
-
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
-
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
-
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
|
-
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
|
-
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
|
-
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
|
-
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
-
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
27
|
-
};
|
|
28
|
-
run(): Promise<void>;
|
|
29
|
-
/** List all static hosts in the workspace (paginates until exhausted). */
|
|
30
|
-
private listHosts;
|
|
31
|
-
/** Discover v1 hosts and migrate each, with a per-host report. */
|
|
32
|
-
private migrateAll;
|
|
33
|
-
/** POST the meta migrate endpoint for one host. Throws on a non-OK response. */
|
|
34
|
-
private migrateHost;
|
|
35
|
-
/** Migrate a single named host and report the result. */
|
|
36
|
-
private migrateOne;
|
|
37
|
-
}
|
|
38
|
-
/** An env still needs migration when it's been deployed (has a canonical) but isn't yet v2. */
|
|
39
|
-
export declare function envNeedsMigration(e?: StaticHostEnv): boolean;
|
|
40
|
-
/**
|
|
41
|
-
* A host still needs migration when an env of interest is v1. When --env is given,
|
|
42
|
-
* only that env is considered; otherwise either env being v1 qualifies the host.
|
|
43
|
-
*/
|
|
44
|
-
export declare function isV1(host: StaticHost, env?: string): boolean;
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import BaseCommand from '../../../base-command.js';
|
|
3
|
-
export default class StaticHostMigrate extends BaseCommand {
|
|
4
|
-
static args = {
|
|
5
|
-
static_host: Args.string({
|
|
6
|
-
description: 'Static Host name to migrate (omit when using --all)',
|
|
7
|
-
required: false,
|
|
8
|
-
}),
|
|
9
|
-
};
|
|
10
|
-
static description = 'Migrate a static host to instance-managed (v2) hosting. Reparents the Ingress, verifies it, clears master, and marks the host v2.';
|
|
11
|
-
static examples = [
|
|
12
|
-
`$ xano static_host migrate newsite
|
|
13
|
-
Migrated 'newsite' to v2
|
|
14
|
-
`,
|
|
15
|
-
`$ xano static_host migrate newsite --env dev
|
|
16
|
-
Migrated 'newsite' (dev) to v2
|
|
17
|
-
`,
|
|
18
|
-
`$ xano static_host migrate --all
|
|
19
|
-
Migrating 3 v1 host(s)...
|
|
20
|
-
✓ newsite
|
|
21
|
-
✓ marketing
|
|
22
|
-
✗ legacy — <error>
|
|
23
|
-
`,
|
|
24
|
-
`$ xano static_host migrate --all --dry-run
|
|
25
|
-
Would migrate 3 v1 host(s): newsite, marketing, legacy
|
|
26
|
-
`,
|
|
27
|
-
];
|
|
28
|
-
static flags = {
|
|
29
|
-
...BaseCommand.baseFlags,
|
|
30
|
-
all: Flags.boolean({
|
|
31
|
-
default: false,
|
|
32
|
-
description: 'Migrate every host still on v1 in the workspace',
|
|
33
|
-
required: false,
|
|
34
|
-
}),
|
|
35
|
-
'dry-run': Flags.boolean({
|
|
36
|
-
default: false,
|
|
37
|
-
description: 'List the hosts that would be migrated without changing anything',
|
|
38
|
-
required: false,
|
|
39
|
-
}),
|
|
40
|
-
env: Flags.string({
|
|
41
|
-
description: 'Which environment to migrate (migrates both if omitted)',
|
|
42
|
-
options: ['dev', 'prod'],
|
|
43
|
-
required: false,
|
|
44
|
-
}),
|
|
45
|
-
output: Flags.string({
|
|
46
|
-
char: 'o',
|
|
47
|
-
default: 'summary',
|
|
48
|
-
description: 'Output format',
|
|
49
|
-
options: ['summary', 'json'],
|
|
50
|
-
required: false,
|
|
51
|
-
}),
|
|
52
|
-
workspace: Flags.string({
|
|
53
|
-
char: 'w',
|
|
54
|
-
description: 'Workspace ID (optional if set in profile)',
|
|
55
|
-
required: false,
|
|
56
|
-
}),
|
|
57
|
-
};
|
|
58
|
-
async run() {
|
|
59
|
-
const { args, flags } = await this.parse(StaticHostMigrate);
|
|
60
|
-
if (!flags.all && !args.static_host) {
|
|
61
|
-
this.error('Provide a static host name, or use --all to migrate every v1 host.');
|
|
62
|
-
}
|
|
63
|
-
if (flags.all && args.static_host) {
|
|
64
|
-
this.error('Use either a static host name or --all, not both.');
|
|
65
|
-
}
|
|
66
|
-
const { profile, profileName } = this.resolveProfile(flags);
|
|
67
|
-
let workspaceId;
|
|
68
|
-
if (flags.workspace) {
|
|
69
|
-
workspaceId = flags.workspace;
|
|
70
|
-
}
|
|
71
|
-
else if (profile.workspace) {
|
|
72
|
-
workspaceId = profile.workspace;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
this.error(`Workspace ID is required. Either:\n` +
|
|
76
|
-
` 1. Provide it as a flag: xano static_host migrate <static_host> -w <workspace_id>\n` +
|
|
77
|
-
` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
|
|
78
|
-
}
|
|
79
|
-
if (flags.all) {
|
|
80
|
-
await this.migrateAll(profile, workspaceId, flags);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
await this.migrateOne(profile, workspaceId, args.static_host, flags);
|
|
84
|
-
}
|
|
85
|
-
/** List all static hosts in the workspace (paginates until exhausted). */
|
|
86
|
-
async listHosts(profile, workspaceId, verbose) {
|
|
87
|
-
const hosts = [];
|
|
88
|
-
const perPage = 100;
|
|
89
|
-
for (let page = 1;; page++) {
|
|
90
|
-
const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host?page=${page}&per_page=${perPage}`;
|
|
91
|
-
// eslint-disable-next-line no-await-in-loop
|
|
92
|
-
const response = await this.verboseFetch(listUrl, {
|
|
93
|
-
headers: {
|
|
94
|
-
accept: 'application/json',
|
|
95
|
-
Authorization: `Bearer ${profile.access_token}`,
|
|
96
|
-
},
|
|
97
|
-
method: 'GET',
|
|
98
|
-
}, verbose, profile.access_token);
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
const errorText = await response.text(); // eslint-disable-line no-await-in-loop
|
|
101
|
-
this.error(`Failed to list static hosts: ${response.status} ${response.statusText}\n${errorText}`);
|
|
102
|
-
}
|
|
103
|
-
const data = (await response.json()); // eslint-disable-line no-await-in-loop
|
|
104
|
-
const pageHosts = Array.isArray(data)
|
|
105
|
-
? data
|
|
106
|
-
: data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)
|
|
107
|
-
? data.items
|
|
108
|
-
: [];
|
|
109
|
-
hosts.push(...pageHosts);
|
|
110
|
-
if (pageHosts.length < perPage)
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
return hosts;
|
|
114
|
-
}
|
|
115
|
-
/** Discover v1 hosts and migrate each, with a per-host report. */
|
|
116
|
-
async migrateAll(profile, workspaceId, flags) {
|
|
117
|
-
const hosts = await this.listHosts(profile, workspaceId, flags.verbose);
|
|
118
|
-
const v1Hosts = hosts.filter((h) => isV1(h, flags.env));
|
|
119
|
-
if (v1Hosts.length === 0) {
|
|
120
|
-
this.log('No v1 hosts to migrate — all hosts are already instance-managed (v2).');
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (flags['dry-run']) {
|
|
124
|
-
const names = v1Hosts.map((h) => h.name);
|
|
125
|
-
if (flags.output === 'json') {
|
|
126
|
-
this.log(JSON.stringify({ dryRun: true, wouldMigrate: names }, null, 2));
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
this.log(`Would migrate ${names.length} v1 host(s): ${names.join(', ')}`);
|
|
130
|
-
}
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (flags.output !== 'json') {
|
|
134
|
-
this.log(`Migrating ${v1Hosts.length} v1 host(s)...`);
|
|
135
|
-
}
|
|
136
|
-
const results = [];
|
|
137
|
-
for (const host of v1Hosts) {
|
|
138
|
-
try {
|
|
139
|
-
// eslint-disable-next-line no-await-in-loop
|
|
140
|
-
await this.migrateHost(profile, workspaceId, host.name, flags.env, flags.verbose);
|
|
141
|
-
results.push({ migrated: true, static_host: host.name });
|
|
142
|
-
if (flags.output !== 'json')
|
|
143
|
-
this.log(` ✓ ${host.name}`);
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
-
results.push({ error: message, migrated: false, static_host: host.name });
|
|
148
|
-
if (flags.output !== 'json')
|
|
149
|
-
this.log(` ✗ ${host.name} — ${message}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (flags.output === 'json') {
|
|
153
|
-
this.log(JSON.stringify({ results }, null, 2));
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
const ok = results.filter((r) => r.migrated).length;
|
|
157
|
-
const failed = results.length - ok;
|
|
158
|
-
this.log(`Done: ${ok} migrated${failed > 0 ? `, ${failed} failed` : ''}.`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/** POST the meta migrate endpoint for one host. Throws on a non-OK response. */
|
|
162
|
-
async migrateHost(profile, workspaceId, staticHost, env, verbose) {
|
|
163
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${staticHost}/migrate`;
|
|
164
|
-
const response = await this.verboseFetch(apiUrl, {
|
|
165
|
-
body: JSON.stringify(env ? { env } : {}),
|
|
166
|
-
headers: {
|
|
167
|
-
accept: 'application/json',
|
|
168
|
-
Authorization: `Bearer ${profile.access_token}`,
|
|
169
|
-
'Content-Type': 'application/json',
|
|
170
|
-
},
|
|
171
|
-
method: 'POST',
|
|
172
|
-
}, verbose, profile.access_token);
|
|
173
|
-
if (!response.ok) {
|
|
174
|
-
const message = await this.parseApiError(response, `Migrate failed for '${staticHost}'`);
|
|
175
|
-
throw new Error(message);
|
|
176
|
-
}
|
|
177
|
-
return (await response.json());
|
|
178
|
-
}
|
|
179
|
-
/** Migrate a single named host and report the result. */
|
|
180
|
-
async migrateOne(profile, workspaceId, staticHost, flags) {
|
|
181
|
-
const result = await this.migrateHost(profile, workspaceId, staticHost, flags.env, flags.verbose);
|
|
182
|
-
const envSuffix = flags.env ? ` (${flags.env})` : '';
|
|
183
|
-
if (flags.output === 'json') {
|
|
184
|
-
this.log(JSON.stringify({ env: flags.env ?? null, migrated: true, static_host: staticHost, ...result }, null, 2));
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
this.log(`Migrated '${staticHost}'${envSuffix} to v2`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/** An env still needs migration when it's been deployed (has a canonical) but isn't yet v2. */
|
|
192
|
-
export function envNeedsMigration(e) {
|
|
193
|
-
return Boolean(e?.canonical) && e?.mode !== 'v2';
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* A host still needs migration when an env of interest is v1. When --env is given,
|
|
197
|
-
* only that env is considered; otherwise either env being v1 qualifies the host.
|
|
198
|
-
*/
|
|
199
|
-
export function isV1(host, env) {
|
|
200
|
-
if (env === 'dev')
|
|
201
|
-
return envNeedsMigration(host.dev);
|
|
202
|
-
if (env === 'prod')
|
|
203
|
-
return envNeedsMigration(host.prod);
|
|
204
|
-
return envNeedsMigration(host.dev) || envNeedsMigration(host.prod);
|
|
205
|
-
}
|
|
@@ -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,96 +0,0 @@
|
|
|
1
|
-
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class TenantSnapshotList extends BaseCommand {
|
|
4
|
-
static args = {
|
|
5
|
-
tenant_name: Args.string({
|
|
6
|
-
description: 'Tenant name to list snapshots for',
|
|
7
|
-
required: true,
|
|
8
|
-
}),
|
|
9
|
-
};
|
|
10
|
-
static description = 'List database snapshots for a tenant';
|
|
11
|
-
static examples = [
|
|
12
|
-
`$ xano tenant snapshot list t1234-abcd-xyz1
|
|
13
|
-
Snapshots for tenant t1234-abcd-xyz1:
|
|
14
|
-
- t1234-abcd-xyz1 (25 MB) [ORIGINAL]
|
|
15
|
-
- t1234-abcd-xyz1_bk_20260603_203614 (25 MB, 2026-06-03 20:36:14) [LIVE]
|
|
16
|
-
`,
|
|
17
|
-
`$ xano tenant snapshot list t1234-abcd-xyz1 -o json`,
|
|
18
|
-
];
|
|
19
|
-
static flags = {
|
|
20
|
-
...BaseCommand.baseFlags,
|
|
21
|
-
output: Flags.string({
|
|
22
|
-
char: 'o',
|
|
23
|
-
default: 'summary',
|
|
24
|
-
description: 'Output format',
|
|
25
|
-
options: ['summary', 'json'],
|
|
26
|
-
required: false,
|
|
27
|
-
}),
|
|
28
|
-
workspace: Flags.string({
|
|
29
|
-
char: 'w',
|
|
30
|
-
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
31
|
-
required: false,
|
|
32
|
-
}),
|
|
33
|
-
};
|
|
34
|
-
async run() {
|
|
35
|
-
const { args, flags } = await this.parse(TenantSnapshotList);
|
|
36
|
-
const { profile } = this.resolveProfile(flags);
|
|
37
|
-
const workspaceId = flags.workspace || profile.workspace;
|
|
38
|
-
if (!workspaceId) {
|
|
39
|
-
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
40
|
-
}
|
|
41
|
-
const tenantName = args.tenant_name;
|
|
42
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/snapshot`;
|
|
43
|
-
try {
|
|
44
|
-
const response = await this.verboseFetch(apiUrl, {
|
|
45
|
-
headers: {
|
|
46
|
-
'accept': 'application/json',
|
|
47
|
-
'Authorization': `Bearer ${profile.access_token}`,
|
|
48
|
-
},
|
|
49
|
-
method: 'GET',
|
|
50
|
-
}, flags.verbose, profile.access_token);
|
|
51
|
-
if (!response.ok) {
|
|
52
|
-
const errorText = await response.text();
|
|
53
|
-
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
54
|
-
}
|
|
55
|
-
const data = (await response.json());
|
|
56
|
-
let snapshots;
|
|
57
|
-
if (Array.isArray(data)) {
|
|
58
|
-
snapshots = data;
|
|
59
|
-
}
|
|
60
|
-
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
61
|
-
snapshots = data.items;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
this.error('Unexpected API response format');
|
|
65
|
-
}
|
|
66
|
-
if (flags.output === 'json') {
|
|
67
|
-
this.log(JSON.stringify(snapshots, null, 2));
|
|
68
|
-
}
|
|
69
|
-
else if (snapshots.length === 0) {
|
|
70
|
-
this.log(`No snapshots found for tenant ${tenantName}`);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
this.log(`Snapshots for tenant ${tenantName}:`);
|
|
74
|
-
for (const snapshot of snapshots) {
|
|
75
|
-
const size = snapshot.size_pretty || (snapshot.size_bytes ? `${snapshot.size_bytes} bytes` : 'unknown');
|
|
76
|
-
const meta = snapshot.created ? `${size}, ${snapshot.created}` : size;
|
|
77
|
-
const tags = [];
|
|
78
|
-
if (snapshot.is_original)
|
|
79
|
-
tags.push('ORIGINAL');
|
|
80
|
-
if (snapshot.is_live)
|
|
81
|
-
tags.push('LIVE');
|
|
82
|
-
const tagStr = tags.length > 0 ? ` [${tags.join(', ')}]` : '';
|
|
83
|
-
this.log(` - ${snapshot.name} (${meta})${tagStr}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
if (error instanceof Error) {
|
|
89
|
-
this.error(`Failed to list snapshots: ${error.message}`);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
this.error(`Failed to list snapshots: ${String(error)}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|