@xano/cli 0.0.37 → 0.0.40

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 (114) hide show
  1. package/README.md +325 -102
  2. package/dist/commands/auth/index.d.ts +0 -2
  3. package/dist/commands/auth/index.js +2 -55
  4. package/dist/commands/profile/create/index.d.ts +0 -2
  5. package/dist/commands/profile/create/index.js +0 -15
  6. package/dist/commands/profile/edit/index.d.ts +0 -4
  7. package/dist/commands/profile/edit/index.js +7 -38
  8. package/dist/commands/profile/wizard/index.d.ts +0 -2
  9. package/dist/commands/profile/wizard/index.js +0 -106
  10. package/dist/commands/profile/{project → workspace}/index.d.ts +1 -1
  11. package/dist/commands/profile/{project → workspace}/index.js +10 -10
  12. package/dist/commands/release/delete/index.d.ts +2 -4
  13. package/dist/commands/release/delete/index.js +39 -12
  14. package/dist/commands/release/edit/index.d.ts +2 -4
  15. package/dist/commands/release/edit/index.js +31 -5
  16. package/dist/commands/release/export/index.d.ts +2 -4
  17. package/dist/commands/release/export/index.js +39 -11
  18. package/dist/commands/release/get/index.d.ts +2 -4
  19. package/dist/commands/release/get/index.js +31 -5
  20. package/dist/commands/release/pull/index.d.ts +31 -0
  21. package/dist/commands/release/pull/index.js +345 -0
  22. package/dist/commands/release/push/index.d.ts +26 -0
  23. package/dist/commands/release/push/index.js +230 -0
  24. package/dist/commands/tenant/backup/delete/index.d.ts +1 -1
  25. package/dist/commands/tenant/backup/delete/index.js +8 -9
  26. package/dist/commands/tenant/backup/export/index.d.ts +1 -1
  27. package/dist/commands/tenant/backup/export/index.js +9 -10
  28. package/dist/commands/tenant/backup/restore/index.d.ts +1 -1
  29. package/dist/commands/tenant/backup/restore/index.js +8 -9
  30. package/dist/commands/tenant/cluster/create/index.d.ts +18 -0
  31. package/dist/commands/tenant/cluster/create/index.js +149 -0
  32. package/dist/commands/{run/sessions/start → tenant/cluster/delete}/index.d.ts +9 -3
  33. package/dist/commands/tenant/cluster/delete/index.js +125 -0
  34. package/dist/commands/tenant/cluster/edit/index.d.ts +22 -0
  35. package/dist/commands/tenant/cluster/edit/index.js +128 -0
  36. package/dist/commands/{run/sessions → tenant/cluster}/get/index.d.ts +7 -3
  37. package/dist/commands/tenant/cluster/get/index.js +114 -0
  38. package/dist/commands/{run/info → tenant/cluster/license/get}/index.d.ts +10 -7
  39. package/dist/commands/tenant/cluster/license/get/index.js +118 -0
  40. package/dist/commands/tenant/cluster/license/set/index.d.ts +21 -0
  41. package/dist/commands/tenant/cluster/license/set/index.js +132 -0
  42. package/dist/commands/{run/env → tenant/cluster}/list/index.d.ts +3 -3
  43. package/dist/commands/tenant/cluster/list/index.js +109 -0
  44. package/dist/commands/tenant/create/index.d.ts +6 -3
  45. package/dist/commands/tenant/create/index.js +28 -20
  46. package/dist/commands/tenant/deploy_platform/index.d.ts +1 -1
  47. package/dist/commands/tenant/deploy_platform/index.js +8 -9
  48. package/dist/commands/tenant/deploy_release/index.d.ts +1 -1
  49. package/dist/commands/tenant/deploy_release/index.js +13 -13
  50. package/dist/commands/tenant/env/delete/index.d.ts +19 -0
  51. package/dist/commands/tenant/env/delete/index.js +139 -0
  52. package/dist/commands/{run/projects/create → tenant/env/get}/index.d.ts +7 -4
  53. package/dist/commands/tenant/env/get/index.js +113 -0
  54. package/dist/commands/{run/projects/update → tenant/env/get_all}/index.d.ts +7 -5
  55. package/dist/commands/tenant/env/get_all/index.js +123 -0
  56. package/dist/commands/{run/secrets/get → tenant/env/list}/index.d.ts +5 -3
  57. package/dist/commands/tenant/env/list/index.js +116 -0
  58. package/dist/commands/tenant/env/set/index.d.ts +18 -0
  59. package/dist/commands/tenant/env/set/index.js +122 -0
  60. package/dist/commands/tenant/env/set_all/index.d.ts +18 -0
  61. package/dist/commands/tenant/env/set_all/index.js +131 -0
  62. package/dist/commands/tenant/get/index.js +6 -5
  63. package/dist/commands/tenant/impersonate/index.d.ts +19 -0
  64. package/dist/commands/tenant/impersonate/index.js +146 -0
  65. package/dist/commands/tenant/license/get/index.d.ts +18 -0
  66. package/dist/commands/tenant/license/get/index.js +127 -0
  67. package/dist/commands/tenant/license/set/index.d.ts +19 -0
  68. package/dist/commands/tenant/license/set/index.js +141 -0
  69. package/dist/commands/tenant/list/index.js +6 -6
  70. package/dist/commands/tenant/pull/index.d.ts +31 -0
  71. package/dist/commands/tenant/pull/index.js +327 -0
  72. package/dist/commands/tenant/push/index.d.ts +24 -0
  73. package/dist/commands/tenant/push/index.js +245 -0
  74. package/oclif.manifest.json +2076 -1670
  75. package/package.json +1 -19
  76. package/dist/commands/run/env/delete/index.d.ts +0 -14
  77. package/dist/commands/run/env/delete/index.js +0 -65
  78. package/dist/commands/run/env/get/index.d.ts +0 -14
  79. package/dist/commands/run/env/get/index.js +0 -52
  80. package/dist/commands/run/env/list/index.js +0 -56
  81. package/dist/commands/run/env/set/index.d.ts +0 -14
  82. package/dist/commands/run/env/set/index.js +0 -51
  83. package/dist/commands/run/exec/index.d.ts +0 -31
  84. package/dist/commands/run/exec/index.js +0 -431
  85. package/dist/commands/run/info/index.js +0 -160
  86. package/dist/commands/run/projects/create/index.js +0 -75
  87. package/dist/commands/run/projects/delete/index.d.ts +0 -14
  88. package/dist/commands/run/projects/delete/index.js +0 -65
  89. package/dist/commands/run/projects/list/index.d.ts +0 -13
  90. package/dist/commands/run/projects/list/index.js +0 -66
  91. package/dist/commands/run/projects/update/index.js +0 -86
  92. package/dist/commands/run/secrets/delete/index.d.ts +0 -14
  93. package/dist/commands/run/secrets/delete/index.js +0 -65
  94. package/dist/commands/run/secrets/get/index.js +0 -52
  95. package/dist/commands/run/secrets/list/index.d.ts +0 -12
  96. package/dist/commands/run/secrets/list/index.js +0 -60
  97. package/dist/commands/run/secrets/set/index.d.ts +0 -16
  98. package/dist/commands/run/secrets/set/index.js +0 -74
  99. package/dist/commands/run/sessions/delete/index.d.ts +0 -14
  100. package/dist/commands/run/sessions/delete/index.js +0 -65
  101. package/dist/commands/run/sessions/get/index.js +0 -72
  102. package/dist/commands/run/sessions/list/index.d.ts +0 -13
  103. package/dist/commands/run/sessions/list/index.js +0 -64
  104. package/dist/commands/run/sessions/start/index.js +0 -56
  105. package/dist/commands/run/sessions/stop/index.d.ts +0 -14
  106. package/dist/commands/run/sessions/stop/index.js +0 -56
  107. package/dist/commands/run/sink/get/index.d.ts +0 -14
  108. package/dist/commands/run/sink/get/index.js +0 -63
  109. package/dist/lib/base-run-command.d.ts +0 -41
  110. package/dist/lib/base-run-command.js +0 -75
  111. package/dist/lib/run-http-client.d.ts +0 -64
  112. package/dist/lib/run-http-client.js +0 -171
  113. package/dist/lib/run-types.d.ts +0 -226
  114. package/dist/lib/run-types.js +0 -5
@@ -20,9 +20,6 @@ Profile 'staging' created successfully at ~/.xano/credentials.yaml
20
20
  `,
21
21
  `$ xano profile:create dev -i https://dev-instance.xano.com -t token789 -w my-workspace -b feature-branch
22
22
  Profile 'dev' created successfully at ~/.xano/credentials.yaml
23
- `,
24
- `$ xano profile:create dev -i https://dev-instance.xano.com -t token789 -w my-workspace -b feature-branch -j my-project
25
- Profile 'dev' created successfully at ~/.xano/credentials.yaml
26
23
  `,
27
24
  `$ xano profile:create production --account_origin https://account.xano.com --instance_origin https://instance.xano.com --access_token token123 --default
28
25
  Profile 'production' created successfully at ~/.xano/credentials.yaml
@@ -55,16 +52,6 @@ Default profile set to 'production'
55
52
  description: 'Instance origin URL',
56
53
  required: true,
57
54
  }),
58
- project: Flags.string({
59
- char: 'j',
60
- description: 'Project name',
61
- required: false,
62
- }),
63
- run_base_url: Flags.string({
64
- char: 'r',
65
- description: 'Xano Run API base URL (default: https://app.xano.com/)',
66
- required: false,
67
- }),
68
55
  workspace: Flags.string({
69
56
  char: 'w',
70
57
  description: 'Workspace name',
@@ -106,8 +93,6 @@ Default profile set to 'production'
106
93
  instance_origin: flags.instance_origin,
107
94
  ...(flags.workspace && { workspace: flags.workspace }),
108
95
  ...(flags.branch && { branch: flags.branch }),
109
- ...(flags.project && { project: flags.project }),
110
- ...(flags.run_base_url && { run_base_url: flags.run_base_url }),
111
96
  };
112
97
  // Set default if flag is provided
113
98
  if (flags.default) {
@@ -10,12 +10,8 @@ export default class ProfileEdit extends BaseCommand {
10
10
  account_origin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  instance_origin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
13
  'remove-branch': import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
- 'remove-project': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
- 'remove-run-base-url': import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
14
  'remove-workspace': import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
- run_base_url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
15
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
16
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
17
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -27,12 +27,6 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
27
27
  `,
28
28
  `$ xano profile:edit --remove-branch
29
29
  Profile 'default' updated successfully at ~/.xano/credentials.yaml
30
- `,
31
- `$ xano profile:edit -j my-project
32
- Profile 'default' updated successfully at ~/.xano/credentials.yaml
33
- `,
34
- `$ xano profile:edit --remove-project
35
- Profile 'default' updated successfully at ~/.xano/credentials.yaml
36
30
  `,
37
31
  ];
38
32
  static flags = {
@@ -57,36 +51,16 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
57
51
  description: 'Update instance origin URL',
58
52
  required: false,
59
53
  }),
60
- project: Flags.string({
61
- char: 'j',
62
- description: 'Update project ID',
63
- required: false,
64
- }),
65
54
  'remove-branch': Flags.boolean({
66
55
  default: false,
67
56
  description: 'Remove branch from profile',
68
57
  required: false,
69
58
  }),
70
- 'remove-project': Flags.boolean({
71
- default: false,
72
- description: 'Remove project from profile',
73
- required: false,
74
- }),
75
- 'remove-run-base-url': Flags.boolean({
76
- default: false,
77
- description: 'Remove run_base_url from profile (use default)',
78
- required: false,
79
- }),
80
59
  'remove-workspace': Flags.boolean({
81
60
  default: false,
82
61
  description: 'Remove workspace from profile',
83
62
  required: false,
84
63
  }),
85
- run_base_url: Flags.string({
86
- char: 'r',
87
- description: 'Update Xano Run API base URL',
88
- required: false,
89
- }),
90
64
  workspace: Flags.string({
91
65
  char: 'w',
92
66
  description: 'Update workspace name',
@@ -123,10 +97,13 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
123
97
  // Get the existing profile
124
98
  const existingProfile = credentials.profiles[profileName];
125
99
  // Check if any flags were provided
126
- const hasFlags = flags.account_origin || flags.instance_origin || flags.access_token ||
127
- flags.workspace || flags.branch || flags.project || flags.run_base_url ||
128
- flags['remove-workspace'] || flags['remove-branch'] || flags['remove-project'] ||
129
- flags['remove-run-base-url'];
100
+ const hasFlags = flags.account_origin ||
101
+ flags.instance_origin ||
102
+ flags.access_token ||
103
+ flags.workspace ||
104
+ flags.branch ||
105
+ flags['remove-workspace'] ||
106
+ flags['remove-branch'];
130
107
  if (!hasFlags) {
131
108
  this.error('No fields specified to update. Use at least one flag to edit the profile.');
132
109
  }
@@ -138,8 +115,6 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
138
115
  ...(flags.access_token !== undefined && { access_token: flags.access_token }),
139
116
  ...(flags.workspace !== undefined && { workspace: flags.workspace }),
140
117
  ...(flags.branch !== undefined && { branch: flags.branch }),
141
- ...(flags.project !== undefined && { project: flags.project }),
142
- ...(flags.run_base_url !== undefined && { run_base_url: flags.run_base_url }),
143
118
  };
144
119
  // Handle removal flags
145
120
  if (flags['remove-workspace']) {
@@ -148,12 +123,6 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
148
123
  if (flags['remove-branch']) {
149
124
  delete updatedProfile.branch;
150
125
  }
151
- if (flags['remove-project']) {
152
- delete updatedProfile.project;
153
- }
154
- if (flags['remove-run-base-url']) {
155
- delete updatedProfile.run_base_url;
156
- }
157
126
  credentials.profiles[profileName] = updatedProfile;
158
127
  // Write the updated credentials back to the file
159
128
  try {
@@ -9,8 +9,6 @@ export default class ProfileWizard extends Command {
9
9
  run(): Promise<void>;
10
10
  private fetchBranches;
11
11
  private fetchInstances;
12
- private fetchProjects;
13
- private fetchRunProjects;
14
12
  private fetchWorkspaces;
15
13
  private getDefaultProfileName;
16
14
  private saveProfile;
@@ -4,7 +4,6 @@ import * as yaml from 'js-yaml';
4
4
  import * as fs from 'node:fs';
5
5
  import * as os from 'node:os';
6
6
  import * as path from 'node:path';
7
- import { DEFAULT_RUN_BASE_URL } from '../../../lib/run-http-client.js';
8
7
  export default class ProfileWizard extends Command {
9
8
  static description = 'Create a new profile configuration using an interactive wizard';
10
9
  static examples = [
@@ -97,7 +96,6 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
97
96
  // Step 5: Workspace selection
98
97
  let workspace;
99
98
  let branch;
100
- let project;
101
99
  // Fetch workspaces from the selected instance
102
100
  this.log('');
103
101
  this.log('Fetching available workspaces...');
@@ -157,56 +155,8 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
157
155
  ]);
158
156
  branch = selectedBranch || undefined;
159
157
  }
160
- // Step 6: Project selection
161
- this.log('');
162
- this.log('Fetching available projects...');
163
- let projects = [];
164
- try {
165
- projects = await this.fetchProjects(accessToken, selectedInstance.origin, workspace, branch);
166
- }
167
- catch (error) {
168
- this.warn(`Failed to fetch projects: ${error instanceof Error ? error.message : String(error)}`);
169
- }
170
- // If projects were fetched, let user select one
171
- if (projects.length > 0) {
172
- this.log('');
173
- const { selectedProject } = await inquirer.prompt([
174
- {
175
- choices: [
176
- { name: '(Skip project)', value: '' },
177
- ...projects.map((proj) => ({
178
- name: proj.name,
179
- value: proj.id,
180
- })),
181
- ],
182
- message: 'Select a project',
183
- name: 'selectedProject',
184
- type: 'list',
185
- },
186
- ]);
187
- project = selectedProject || undefined;
188
- }
189
158
  }
190
159
  }
191
- // Step 7: Fetch run projects and auto-select the first one if no project was selected
192
- this.log('');
193
- this.log('Fetching available run projects...');
194
- try {
195
- const runProjects = await this.fetchRunProjects(accessToken);
196
- if (runProjects.length > 0) {
197
- // Use run project if no metadata project was selected
198
- if (!project) {
199
- project = runProjects[0].id;
200
- }
201
- this.log(`✓ Found ${runProjects.length} run project(s). Using "${runProjects[0].name}" as default.`);
202
- }
203
- else {
204
- this.log('No run projects found. You can create one later with "xano run projects create".');
205
- }
206
- }
207
- catch {
208
- // Silently ignore - project will remain undefined
209
- }
210
160
  // Save profile
211
161
  await this.saveProfile({
212
162
  access_token: accessToken,
@@ -214,7 +164,6 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
214
164
  branch,
215
165
  instance_origin: selectedInstance.origin,
216
166
  name: profileName,
217
- project,
218
167
  workspace,
219
168
  }, true);
220
169
  this.log('');
@@ -302,60 +251,6 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
302
251
  }
303
252
  return [];
304
253
  }
305
- async fetchProjects(accessToken, origin, workspaceId, branchId) {
306
- const branchParam = branchId ? `?branch=${branchId}` : '';
307
- const response = await fetch(`${origin}/api:meta/workspace/${workspaceId}/project${branchParam}`, {
308
- headers: {
309
- accept: 'application/json',
310
- Authorization: `Bearer ${accessToken}`,
311
- },
312
- method: 'GET',
313
- });
314
- if (!response.ok) {
315
- if (response.status === 401) {
316
- throw new Error('Unauthorized. Please check your access token.');
317
- }
318
- throw new Error(`API request failed with status ${response.status}`);
319
- }
320
- const data = (await response.json());
321
- // Transform API response to Project format
322
- // Assuming the API returns an array or object with projects
323
- if (Array.isArray(data)) {
324
- return data.map((proj) => ({
325
- id: proj.id || proj.name,
326
- name: proj.name,
327
- }));
328
- }
329
- // If it's an object, try to extract projects
330
- if (data && typeof data === 'object') {
331
- const projects = data.projects || data.data || [];
332
- if (Array.isArray(projects)) {
333
- return projects.map((proj) => ({
334
- id: proj.id || proj.name,
335
- name: proj.name,
336
- }));
337
- }
338
- }
339
- return [];
340
- }
341
- async fetchRunProjects(accessToken, runBaseUrl = DEFAULT_RUN_BASE_URL) {
342
- const baseUrl = runBaseUrl.endsWith('/') ? runBaseUrl.slice(0, -1) : runBaseUrl;
343
- const response = await fetch(`${baseUrl}/api:run/project`, {
344
- headers: {
345
- Authorization: `Bearer ${accessToken}`,
346
- 'Content-Type': 'application/json',
347
- },
348
- method: 'GET',
349
- });
350
- if (!response.ok) {
351
- if (response.status === 401) {
352
- throw new Error('Unauthorized. Please check your access token.');
353
- }
354
- throw new Error(`API request failed with status ${response.status}`);
355
- }
356
- const data = (await response.json());
357
- return Array.isArray(data) ? data : [];
358
- }
359
254
  async fetchWorkspaces(accessToken, origin) {
360
255
  const response = await fetch(`${origin}/api:meta/workspace`, {
361
256
  headers: {
@@ -437,7 +332,6 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
437
332
  instance_origin: profile.instance_origin,
438
333
  ...(profile.workspace && { workspace: profile.workspace }),
439
334
  ...(profile.branch && { branch: profile.branch }),
440
- ...(profile.project && { project: profile.project }),
441
335
  };
442
336
  // Set as default if requested
443
337
  if (setAsDefault) {
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class ProfileProject extends Command {
2
+ export default class ProfileWorkspace extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  run(): Promise<void>;
@@ -3,14 +3,14 @@ import * as yaml from 'js-yaml';
3
3
  import * as fs from 'node:fs';
4
4
  import * as os from 'node:os';
5
5
  import * as path from 'node:path';
6
- export default class ProfileProject extends Command {
7
- static description = 'Print the project for the default profile';
6
+ export default class ProfileWorkspace extends Command {
7
+ static description = 'Print the workspace ID for the default profile';
8
8
  static examples = [
9
- `$ xano profile:project
10
- my-project-id
9
+ `$ xano profile:workspace
10
+ abc123-workspace-id
11
11
  `,
12
- `$ xano profile:project | pbcopy
13
- # Copies the project to clipboard on macOS
12
+ `$ xano profile:workspace | pbcopy
13
+ # Copies the workspace ID to clipboard on macOS
14
14
  `,
15
15
  ];
16
16
  async run() {
@@ -43,12 +43,12 @@ my-project-id
43
43
  this.error(`Default profile '${defaultProfileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}`);
44
44
  }
45
45
  const profile = credentials.profiles[defaultProfileName];
46
- // Get and display the project
47
- if (profile.project) {
48
- this.log(profile.project);
46
+ // Get and display the workspace ID
47
+ if (profile.workspace) {
48
+ this.log(profile.workspace);
49
49
  }
50
50
  else {
51
- this.error(`Profile '${defaultProfileName}' does not have a project set. Set one using 'profile:edit -j <project>'.`);
51
+ this.error(`Profile '${defaultProfileName}' does not have a workspace set. Set one using 'profile:edit ${defaultProfileName} -w <workspace_id>'.`);
52
52
  }
53
53
  }
54
54
  }
@@ -1,10 +1,7 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
2
  export default class ReleaseDelete extends BaseCommand {
3
3
  static args: {
4
- release_id: import("@oclif/core/interfaces").Arg<number, {
5
- max?: number;
6
- min?: number;
7
- }>;
4
+ release_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
5
  };
9
6
  static description: string;
10
7
  static examples: string[];
@@ -18,4 +15,5 @@ export default class ReleaseDelete extends BaseCommand {
18
15
  run(): Promise<void>;
19
16
  private confirm;
20
17
  private loadCredentials;
18
+ private resolveReleaseName;
21
19
  }
@@ -6,21 +6,21 @@ import * as path from 'node:path';
6
6
  import BaseCommand from '../../../base-command.js';
7
7
  export default class ReleaseDelete extends BaseCommand {
8
8
  static args = {
9
- release_id: Args.integer({
10
- description: 'Release ID to delete',
9
+ release_name: Args.string({
10
+ description: 'Release name to delete',
11
11
  required: true,
12
12
  }),
13
13
  };
14
14
  static description = 'Delete a release permanently. This action cannot be undone.';
15
15
  static examples = [
16
- `$ xano release delete 10
17
- Are you sure you want to delete release #10? This action cannot be undone. (y/N) y
18
- Deleted release #10
16
+ `$ xano release delete v1.0
17
+ Are you sure you want to delete release 'v1.0'? This action cannot be undone. (y/N) y
18
+ Deleted release 'v1.0'
19
19
  `,
20
- `$ xano release delete 10 --force
21
- Deleted release #10
20
+ `$ xano release delete v1.0 --force
21
+ Deleted release 'v1.0'
22
22
  `,
23
- `$ xano release delete 10 -f -o json`,
23
+ `$ xano release delete v1.0 -f -o json`,
24
24
  ];
25
25
  static flags = {
26
26
  ...BaseCommand.baseFlags,
@@ -62,14 +62,15 @@ Deleted release #10
62
62
  if (!workspaceId) {
63
63
  this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
64
64
  }
65
- const releaseId = args.release_id;
65
+ const releaseName = args.release_name;
66
66
  if (!flags.force) {
67
- const confirmed = await this.confirm(`Are you sure you want to delete release #${releaseId}? This action cannot be undone.`);
67
+ const confirmed = await this.confirm(`Are you sure you want to delete release '${releaseName}'? This action cannot be undone.`);
68
68
  if (!confirmed) {
69
69
  this.log('Deletion cancelled.');
70
70
  return;
71
71
  }
72
72
  }
73
+ const releaseId = await this.resolveReleaseName(profile, workspaceId, releaseName, flags.verbose);
73
74
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release/${releaseId}`;
74
75
  try {
75
76
  const response = await this.verboseFetch(apiUrl, {
@@ -84,10 +85,10 @@ Deleted release #10
84
85
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
85
86
  }
86
87
  if (flags.output === 'json') {
87
- this.log(JSON.stringify({ deleted: true, release_id: releaseId }, null, 2));
88
+ this.log(JSON.stringify({ deleted: true, release_name: releaseName }, null, 2));
88
89
  }
89
90
  else {
90
- this.log(`Deleted release #${releaseId}`);
91
+ this.log(`Deleted release '${releaseName}'`);
91
92
  }
92
93
  }
93
94
  catch (error) {
@@ -131,4 +132,30 @@ Deleted release #10
131
132
  this.error(`Failed to parse credentials file: ${error}`);
132
133
  }
133
134
  }
135
+ async resolveReleaseName(profile, workspaceId, releaseName, verbose) {
136
+ const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release`;
137
+ const response = await this.verboseFetch(listUrl, {
138
+ headers: {
139
+ 'accept': 'application/json',
140
+ 'Authorization': `Bearer ${profile.access_token}`,
141
+ },
142
+ method: 'GET',
143
+ }, verbose, profile.access_token);
144
+ if (!response.ok) {
145
+ const errorText = await response.text();
146
+ this.error(`Failed to list releases: ${response.status} ${response.statusText}\n${errorText}`);
147
+ }
148
+ const data = await response.json();
149
+ const releases = Array.isArray(data)
150
+ ? data
151
+ : (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items))
152
+ ? data.items
153
+ : [];
154
+ const match = releases.find(r => r.name === releaseName);
155
+ if (!match) {
156
+ const available = releases.map(r => r.name).join(', ');
157
+ this.error(`Release '${releaseName}' not found.${available ? ` Available releases: ${available}` : ''}`);
158
+ }
159
+ return match.id;
160
+ }
134
161
  }
@@ -1,10 +1,7 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
2
  export default class ReleaseEdit extends BaseCommand {
3
3
  static args: {
4
- release_id: import("@oclif/core/interfaces").Arg<number, {
5
- max?: number;
6
- min?: number;
7
- }>;
4
+ release_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
5
  };
9
6
  static description: string;
10
7
  static examples: string[];
@@ -18,4 +15,5 @@ export default class ReleaseEdit extends BaseCommand {
18
15
  };
19
16
  run(): Promise<void>;
20
17
  private loadCredentials;
18
+ private resolveReleaseName;
21
19
  }
@@ -6,17 +6,17 @@ import * as path from 'node:path';
6
6
  import BaseCommand from '../../../base-command.js';
7
7
  export default class ReleaseEdit extends BaseCommand {
8
8
  static args = {
9
- release_id: Args.integer({
10
- description: 'Release ID to edit',
9
+ release_name: Args.string({
10
+ description: 'Release name to edit',
11
11
  required: true,
12
12
  }),
13
13
  };
14
14
  static description = 'Edit an existing release';
15
15
  static examples = [
16
- `$ xano release edit 10 --name "v1.0-final" --description "Updated description"
16
+ `$ xano release edit v1.0 --name "v1.0-final" --description "Updated description"
17
17
  Updated release: v1.0-final - ID: 10
18
18
  `,
19
- `$ xano release edit 10 --description "New description" -o json`,
19
+ `$ xano release edit v1.0 --description "New description" -o json`,
20
20
  ];
21
21
  static flags = {
22
22
  ...BaseCommand.baseFlags,
@@ -62,7 +62,7 @@ Updated release: v1.0-final - ID: 10
62
62
  if (!workspaceId) {
63
63
  this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
64
64
  }
65
- const releaseId = args.release_id;
65
+ const releaseId = await this.resolveReleaseName(profile, workspaceId, args.release_name, flags.verbose);
66
66
  const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release/${releaseId}`;
67
67
  const headers = {
68
68
  'accept': 'application/json',
@@ -134,4 +134,30 @@ Updated release: v1.0-final - ID: 10
134
134
  this.error(`Failed to parse credentials file: ${error}`);
135
135
  }
136
136
  }
137
+ async resolveReleaseName(profile, workspaceId, releaseName, verbose) {
138
+ const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release`;
139
+ const response = await this.verboseFetch(listUrl, {
140
+ headers: {
141
+ 'accept': 'application/json',
142
+ 'Authorization': `Bearer ${profile.access_token}`,
143
+ },
144
+ method: 'GET',
145
+ }, verbose, profile.access_token);
146
+ if (!response.ok) {
147
+ const errorText = await response.text();
148
+ this.error(`Failed to list releases: ${response.status} ${response.statusText}\n${errorText}`);
149
+ }
150
+ const data = await response.json();
151
+ const releases = Array.isArray(data)
152
+ ? data
153
+ : (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items))
154
+ ? data.items
155
+ : [];
156
+ const match = releases.find(r => r.name === releaseName);
157
+ if (!match) {
158
+ const available = releases.map(r => r.name).join(', ');
159
+ this.error(`Release '${releaseName}' not found.${available ? ` Available releases: ${available}` : ''}`);
160
+ }
161
+ return match.id;
162
+ }
137
163
  }
@@ -1,10 +1,7 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
2
  export default class ReleaseExport extends BaseCommand {
3
3
  static args: {
4
- release_id: import("@oclif/core/interfaces").Arg<number, {
5
- max?: number;
6
- min?: number;
7
- }>;
4
+ release_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
5
  };
9
6
  static description: string;
10
7
  static examples: string[];
@@ -17,4 +14,5 @@ export default class ReleaseExport extends BaseCommand {
17
14
  };
18
15
  run(): Promise<void>;
19
16
  private loadCredentials;
17
+ private resolveReleaseName;
20
18
  }
@@ -6,18 +6,18 @@ import * as yaml from 'js-yaml';
6
6
  import BaseCommand from '../../../base-command.js';
7
7
  export default class ReleaseExport extends BaseCommand {
8
8
  static args = {
9
- release_id: Args.integer({
10
- description: 'Release ID to export',
9
+ release_name: Args.string({
10
+ description: 'Release name to export',
11
11
  required: true,
12
12
  }),
13
13
  };
14
14
  static description = 'Export (download) a release to a local file';
15
15
  static examples = [
16
- `$ xano release export 10
17
- Downloaded release #10 to ./release-10.tar.gz
16
+ `$ xano release export v1.0
17
+ Downloaded release 'v1.0' to ./release-v1.0.tar.gz
18
18
  `,
19
- `$ xano release export 10 --output ./backups/my-release.tar.gz`,
20
- `$ xano release export 10 -o json`,
19
+ `$ xano release export v1.0 --output ./backups/my-release.tar.gz`,
20
+ `$ xano release export v1.0 -o json`,
21
21
  ];
22
22
  static flags = {
23
23
  ...BaseCommand.baseFlags,
@@ -29,7 +29,7 @@ Downloaded release #10 to ./release-10.tar.gz
29
29
  required: false,
30
30
  }),
31
31
  output: Flags.string({
32
- description: 'Output file path (defaults to ./release-{id}.tar.gz)',
32
+ description: 'Output file path (defaults to ./release-{name}.tar.gz)',
33
33
  required: false,
34
34
  }),
35
35
  workspace: Flags.string({
@@ -57,7 +57,8 @@ Downloaded release #10 to ./release-10.tar.gz
57
57
  if (!workspaceId) {
58
58
  this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
59
59
  }
60
- const releaseId = args.release_id;
60
+ const releaseName = args.release_name;
61
+ const releaseId = await this.resolveReleaseName(profile, workspaceId, releaseName, flags.verbose);
61
62
  // Step 1: Get signed download URL
62
63
  const exportUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release/${releaseId}/export`;
63
64
  try {
@@ -77,7 +78,8 @@ Downloaded release #10 to ./release-10.tar.gz
77
78
  this.error('API did not return a download URL');
78
79
  }
79
80
  // Step 2: Download the file
80
- const outputPath = flags.output || `release-${releaseId}.tar.gz`;
81
+ const safeFilename = releaseName.replaceAll(/[^\w.-]/g, '_');
82
+ const outputPath = flags.output || `release-${safeFilename}.tar.gz`;
81
83
  const resolvedPath = path.resolve(outputPath);
82
84
  const downloadResponse = await fetch(exportLink.src);
83
85
  if (!downloadResponse.ok) {
@@ -104,11 +106,11 @@ Downloaded release #10 to ./release-10.tar.gz
104
106
  fileStream.on('error', reject);
105
107
  });
106
108
  if (flags.format === 'json') {
107
- this.log(JSON.stringify({ bytes: totalBytes, file: resolvedPath, release_id: releaseId }, null, 2));
109
+ this.log(JSON.stringify({ bytes: totalBytes, file: resolvedPath, release_name: releaseName }, null, 2));
108
110
  }
109
111
  else {
110
112
  const sizeMb = (totalBytes / 1024 / 1024).toFixed(2);
111
- this.log(`Downloaded release #${releaseId} to ${resolvedPath} (${sizeMb} MB)`);
113
+ this.log(`Downloaded release '${releaseName}' to ${resolvedPath} (${sizeMb} MB)`);
112
114
  }
113
115
  }
114
116
  catch (error) {
@@ -139,4 +141,30 @@ Downloaded release #10 to ./release-10.tar.gz
139
141
  this.error(`Failed to parse credentials file: ${error}`);
140
142
  }
141
143
  }
144
+ async resolveReleaseName(profile, workspaceId, releaseName, verbose) {
145
+ const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release`;
146
+ const response = await this.verboseFetch(listUrl, {
147
+ headers: {
148
+ 'accept': 'application/json',
149
+ 'Authorization': `Bearer ${profile.access_token}`,
150
+ },
151
+ method: 'GET',
152
+ }, verbose, profile.access_token);
153
+ if (!response.ok) {
154
+ const errorText = await response.text();
155
+ this.error(`Failed to list releases: ${response.status} ${response.statusText}\n${errorText}`);
156
+ }
157
+ const data = await response.json();
158
+ const releases = Array.isArray(data)
159
+ ? data
160
+ : (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items))
161
+ ? data.items
162
+ : [];
163
+ const match = releases.find(r => r.name === releaseName);
164
+ if (!match) {
165
+ const available = releases.map(r => r.name).join(', ');
166
+ this.error(`Release '${releaseName}' not found.${available ? ` Available releases: ${available}` : ''}`);
167
+ }
168
+ return match.id;
169
+ }
142
170
  }