@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.
Files changed (72) hide show
  1. package/dist/base-command.d.ts +5 -0
  2. package/dist/base-command.js +32 -1
  3. package/dist/commands/branch/create/index.js +3 -3
  4. package/dist/commands/branch/delete/index.js +2 -2
  5. package/dist/commands/branch/edit/index.js +3 -3
  6. package/dist/commands/branch/get/index.js +2 -2
  7. package/dist/commands/branch/list/index.js +2 -2
  8. package/dist/commands/branch/set-live/index.js +2 -2
  9. package/dist/commands/function/create/index.js +2 -2
  10. package/dist/commands/function/edit/index.js +2 -2
  11. package/dist/commands/function/get/index.js +2 -2
  12. package/dist/commands/function/list/index.js +2 -2
  13. package/dist/commands/platform/get/index.d.ts +18 -0
  14. package/dist/commands/platform/get/index.js +126 -0
  15. package/dist/commands/platform/list/index.d.ts +12 -0
  16. package/dist/commands/platform/list/index.js +113 -0
  17. package/dist/commands/profile/me/index.js +2 -2
  18. package/dist/commands/release/create/index.d.ts +18 -0
  19. package/dist/commands/release/create/index.js +138 -0
  20. package/dist/commands/release/delete/index.d.ts +21 -0
  21. package/dist/commands/release/delete/index.js +134 -0
  22. package/dist/commands/release/edit/index.d.ts +21 -0
  23. package/dist/commands/release/edit/index.js +137 -0
  24. package/dist/commands/release/export/index.d.ts +20 -0
  25. package/dist/commands/release/export/index.js +142 -0
  26. package/dist/commands/release/get/index.d.ts +19 -0
  27. package/dist/commands/release/get/index.js +123 -0
  28. package/dist/commands/release/import/index.d.ts +15 -0
  29. package/dist/commands/release/import/index.js +114 -0
  30. package/dist/commands/release/list/index.d.ts +13 -0
  31. package/dist/commands/release/list/index.js +120 -0
  32. package/dist/commands/static_host/build/create/index.js +2 -2
  33. package/dist/commands/static_host/build/get/index.js +2 -2
  34. package/dist/commands/static_host/build/list/index.js +2 -2
  35. package/dist/commands/static_host/list/index.js +2 -2
  36. package/dist/commands/tenant/backup/create/index.d.ts +20 -0
  37. package/dist/commands/tenant/backup/create/index.js +113 -0
  38. package/dist/commands/tenant/backup/delete/index.d.ts +22 -0
  39. package/dist/commands/tenant/backup/delete/index.js +137 -0
  40. package/dist/commands/tenant/backup/export/index.d.ts +21 -0
  41. package/dist/commands/tenant/backup/export/index.js +147 -0
  42. package/dist/commands/tenant/backup/import/index.d.ts +21 -0
  43. package/dist/commands/tenant/backup/import/index.js +127 -0
  44. package/dist/commands/tenant/backup/list/index.d.ts +20 -0
  45. package/dist/commands/tenant/backup/list/index.js +137 -0
  46. package/dist/commands/tenant/backup/restore/index.d.ts +22 -0
  47. package/dist/commands/tenant/backup/restore/index.js +141 -0
  48. package/dist/commands/tenant/create/index.d.ts +21 -0
  49. package/dist/commands/tenant/create/index.js +155 -0
  50. package/dist/commands/tenant/delete/index.d.ts +21 -0
  51. package/dist/commands/tenant/delete/index.js +134 -0
  52. package/dist/commands/tenant/deploy-platform/index.d.ts +20 -0
  53. package/dist/commands/tenant/deploy-platform/index.js +116 -0
  54. package/dist/commands/tenant/deploy-release/index.d.ts +20 -0
  55. package/dist/commands/tenant/deploy-release/index.js +116 -0
  56. package/dist/commands/tenant/edit/index.d.ts +26 -0
  57. package/dist/commands/tenant/edit/index.js +167 -0
  58. package/dist/commands/tenant/get/index.d.ts +19 -0
  59. package/dist/commands/tenant/get/index.js +135 -0
  60. package/dist/commands/tenant/list/index.d.ts +13 -0
  61. package/dist/commands/tenant/list/index.js +123 -0
  62. package/dist/commands/workspace/create/index.js +3 -3
  63. package/dist/commands/workspace/delete/index.js +2 -2
  64. package/dist/commands/workspace/edit/index.js +3 -3
  65. package/dist/commands/workspace/get/index.js +2 -2
  66. package/dist/commands/workspace/list/index.js +2 -2
  67. package/dist/commands/workspace/pull/index.d.ts +1 -0
  68. package/dist/commands/workspace/pull/index.js +45 -10
  69. package/dist/commands/workspace/push/index.d.ts +3 -0
  70. package/dist/commands/workspace/push/index.js +41 -8
  71. package/oclif.manifest.json +3213 -1256
  72. 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 fetch(apiUrl, {
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 fetch(apiUrl, {
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 fetch(apiUrl, {
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 fetch(apiUrl, {
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
+ }