@xano/cli 0.0.65 → 0.0.67

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.
@@ -27,6 +27,12 @@ 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 --insecure
32
+ Profile 'default' updated successfully at ~/.xano/credentials.yaml
33
+ `,
34
+ `$ xano profile:edit --remove-insecure
35
+ Profile 'default' updated successfully at ~/.xano/credentials.yaml
30
36
  `,
31
37
  ];
32
38
  static flags = {
@@ -46,6 +52,11 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
46
52
  description: 'Update branch name',
47
53
  required: false,
48
54
  }),
55
+ insecure: Flags.boolean({
56
+ default: false,
57
+ description: 'Enable insecure mode (skip TLS certificate verification)',
58
+ required: false,
59
+ }),
49
60
  instance_origin: Flags.string({
50
61
  char: 'i',
51
62
  description: 'Update instance origin URL',
@@ -56,6 +67,11 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
56
67
  description: 'Remove branch from profile',
57
68
  required: false,
58
69
  }),
70
+ 'remove-insecure': Flags.boolean({
71
+ default: false,
72
+ description: 'Remove insecure mode from profile',
73
+ required: false,
74
+ }),
59
75
  'remove-workspace': Flags.boolean({
60
76
  default: false,
61
77
  description: 'Remove workspace from profile',
@@ -102,8 +118,10 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
102
118
  flags.access_token ||
103
119
  flags.workspace ||
104
120
  flags.branch ||
121
+ flags.insecure ||
105
122
  flags['remove-workspace'] ||
106
- flags['remove-branch'];
123
+ flags['remove-branch'] ||
124
+ flags['remove-insecure'];
107
125
  if (!hasFlags) {
108
126
  this.error('No fields specified to update. Use at least one flag to edit the profile.');
109
127
  }
@@ -115,6 +133,7 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
115
133
  ...(flags.access_token !== undefined && { access_token: flags.access_token }),
116
134
  ...(flags.workspace !== undefined && { workspace: flags.workspace }),
117
135
  ...(flags.branch !== undefined && { branch: flags.branch }),
136
+ ...(flags.insecure && { insecure: true }),
118
137
  };
119
138
  // Handle removal flags
120
139
  if (flags['remove-workspace']) {
@@ -123,6 +142,9 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
123
142
  if (flags['remove-branch']) {
124
143
  delete updatedProfile.branch;
125
144
  }
145
+ if (flags['remove-insecure']) {
146
+ delete updatedProfile.insecure;
147
+ }
126
148
  credentials.profiles[profileName] = updatedProfile;
127
149
  // Write the updated credentials back to the file
128
150
  try {
@@ -98,6 +98,9 @@ Profile: default
98
98
  if (profile.project) {
99
99
  this.log(` Project: ${profile.project}`);
100
100
  }
101
+ if (profile.insecure) {
102
+ this.log(` Insecure: true`);
103
+ }
101
104
  this.log(''); // Empty line between profiles
102
105
  }
103
106
  }
@@ -3,10 +3,12 @@ export default class ProfileWizard extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ insecure: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
8
  origin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
9
  };
9
10
  run(): Promise<void>;
11
+ private getHeaders;
10
12
  private fetchBranches;
11
13
  private fetchInstances;
12
14
  private fetchWorkspaces;
@@ -5,6 +5,7 @@ import * as yaml from 'js-yaml';
5
5
  import * as fs from 'node:fs';
6
6
  import * as os from 'node:os';
7
7
  import * as path from 'node:path';
8
+ import { buildUserAgent } from '../../../base-command.js';
8
9
  export default class ProfileWizard extends Command {
9
10
  static description = 'Create a new profile configuration using an interactive wizard';
10
11
  static examples = [
@@ -19,6 +20,12 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
19
20
  `,
20
21
  ];
21
22
  static flags = {
23
+ insecure: Flags.boolean({
24
+ char: 'k',
25
+ default: false,
26
+ description: 'Skip TLS certificate verification (for self-signed certificates)',
27
+ required: false,
28
+ }),
22
29
  name: Flags.string({
23
30
  char: 'n',
24
31
  description: 'Profile name (skip prompt if provided)',
@@ -33,6 +40,10 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
33
40
  };
34
41
  async run() {
35
42
  const { flags } = await this.parse(ProfileWizard);
43
+ if (flags.insecure) {
44
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
45
+ this.warn('TLS certificate verification is disabled (insecure mode)');
46
+ }
36
47
  this.log('Welcome to the Xano Profile Wizard!');
37
48
  this.log('');
38
49
  try {
@@ -163,6 +174,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
163
174
  access_token: accessToken,
164
175
  account_origin: flags.origin,
165
176
  branch,
177
+ ...(flags.insecure && { insecure: true }),
166
178
  instance_origin: selectedInstance.origin,
167
179
  name: profileName,
168
180
  workspace,
@@ -178,12 +190,16 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
178
190
  throw error;
179
191
  }
180
192
  }
193
+ getHeaders(accessToken) {
194
+ return {
195
+ 'User-Agent': buildUserAgent(this.config.version),
196
+ accept: 'application/json',
197
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
198
+ };
199
+ }
181
200
  async fetchBranches(accessToken, origin, workspaceId) {
182
201
  const response = await fetch(`${origin}/api:meta/workspace/${workspaceId}/branch`, {
183
- headers: {
184
- accept: 'application/json',
185
- Authorization: `Bearer ${accessToken}`,
186
- },
202
+ headers: this.getHeaders(accessToken),
187
203
  method: 'GET',
188
204
  });
189
205
  if (!response.ok) {
@@ -215,10 +231,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
215
231
  }
216
232
  async fetchInstances(accessToken, origin) {
217
233
  const response = await fetch(`${origin}/api:meta/instance`, {
218
- headers: {
219
- accept: 'application/json',
220
- Authorization: `Bearer ${accessToken}`,
221
- },
234
+ headers: this.getHeaders(accessToken),
222
235
  method: 'GET',
223
236
  });
224
237
  if (!response.ok) {
@@ -254,10 +267,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
254
267
  }
255
268
  async fetchWorkspaces(accessToken, origin) {
256
269
  const response = await fetch(`${origin}/api:meta/workspace`, {
257
- headers: {
258
- accept: 'application/json',
259
- Authorization: `Bearer ${accessToken}`,
260
- },
270
+ headers: this.getHeaders(accessToken),
261
271
  method: 'GET',
262
272
  });
263
273
  if (!response.ok) {
@@ -333,6 +343,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
333
343
  instance_origin: profile.instance_origin,
334
344
  ...(profile.workspace && { workspace: profile.workspace }),
335
345
  ...(profile.branch && { branch: profile.branch }),
346
+ ...(profile.insecure && { insecure: true }),
336
347
  };
337
348
  // Set as default if requested
338
349
  if (setAsDefault) {
@@ -0,0 +1,11 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class ProfileWorkspaceSet extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ private fetchWorkspaces;
11
+ }
@@ -0,0 +1,87 @@
1
+ import inquirer from 'inquirer';
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, { buildUserAgent } from '../../../../base-command.js';
7
+ export default class ProfileWorkspaceSet extends BaseCommand {
8
+ static description = 'Interactively select a workspace for a profile';
9
+ static examples = [
10
+ `$ xano profile workspace set
11
+ Fetching workspaces...
12
+ ? Select a workspace: My Workspace
13
+ Workspace updated to 'My Workspace' (abc123) on profile 'default'
14
+ `,
15
+ `$ xano profile workspace set -p production
16
+ Fetching workspaces...
17
+ ? Select a workspace: Production API
18
+ Workspace updated to 'Production API' (xyz789) on profile 'production'
19
+ `,
20
+ ];
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ };
24
+ async run() {
25
+ const { flags } = await this.parse(ProfileWorkspaceSet);
26
+ const profileName = flags.profile || this.getDefaultProfile();
27
+ const credentials = this.loadCredentialsFile();
28
+ if (!credentials) {
29
+ this.error("Credentials file not found. Create a profile first using 'xano auth'.");
30
+ }
31
+ if (!(profileName in credentials.profiles)) {
32
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}`);
33
+ }
34
+ const profile = credentials.profiles[profileName];
35
+ this.log('Fetching workspaces...');
36
+ const workspaces = await this.fetchWorkspaces(profile.access_token, profile.instance_origin);
37
+ if (workspaces.length === 0) {
38
+ this.error('No workspaces found on this instance.');
39
+ }
40
+ const { selectedWorkspace } = await inquirer.prompt([
41
+ {
42
+ choices: workspaces.map((ws) => ({
43
+ name: String(ws.id) === String(profile.workspace) ? `${ws.name} (current)` : ws.name,
44
+ value: ws.id,
45
+ })),
46
+ message: 'Select a workspace',
47
+ name: 'selectedWorkspace',
48
+ type: 'select',
49
+ },
50
+ ]);
51
+ profile.workspace = selectedWorkspace;
52
+ credentials.profiles[profileName] = profile;
53
+ const credentialsPath = path.join(os.homedir(), '.xano', 'credentials.yaml');
54
+ const yamlContent = yaml.dump(credentials, {
55
+ indent: 2,
56
+ lineWidth: -1,
57
+ noRefs: true,
58
+ });
59
+ fs.writeFileSync(credentialsPath, yamlContent, 'utf8');
60
+ const selected = workspaces.find((ws) => ws.id === selectedWorkspace);
61
+ this.log(`Workspace updated to '${selected?.name}' (${selectedWorkspace}) on profile '${profileName}'`);
62
+ }
63
+ async fetchWorkspaces(accessToken, origin) {
64
+ const response = await fetch(`${origin}/api:meta/workspace`, {
65
+ headers: {
66
+ 'User-Agent': buildUserAgent(this.config.version),
67
+ accept: 'application/json',
68
+ Authorization: `Bearer ${accessToken}`,
69
+ },
70
+ method: 'GET',
71
+ });
72
+ if (!response.ok) {
73
+ if (response.status === 401) {
74
+ this.error('Unauthorized. Your access token may be expired. Re-authenticate with "xano auth".');
75
+ }
76
+ this.error(`Failed to fetch workspaces (status ${response.status})`);
77
+ }
78
+ const data = (await response.json());
79
+ if (Array.isArray(data)) {
80
+ return data.map((ws) => ({
81
+ id: ws.id || ws.name,
82
+ name: ws.name,
83
+ }));
84
+ }
85
+ return [];
86
+ }
87
+ }
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
3
3
  import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import * as yaml from 'js-yaml';
6
- import BaseCommand from '../../../base-command.js';
6
+ import BaseCommand, { buildUserAgent } from '../../../base-command.js';
7
7
  export default class ReleaseExport extends BaseCommand {
8
8
  static args = {
9
9
  release_name: Args.string({
@@ -64,8 +64,8 @@ Downloaded release 'v1.0' to ./release-v1.0.tar.gz
64
64
  try {
65
65
  const response = await this.verboseFetch(exportUrl, {
66
66
  headers: {
67
- 'accept': 'application/json',
68
- 'Authorization': `Bearer ${profile.access_token}`,
67
+ accept: 'application/json',
68
+ Authorization: `Bearer ${profile.access_token}`,
69
69
  },
70
70
  method: 'GET',
71
71
  }, flags.verbose, profile.access_token);
@@ -73,7 +73,7 @@ Downloaded release 'v1.0' to ./release-v1.0.tar.gz
73
73
  const errorText = await response.text();
74
74
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
75
75
  }
76
- const exportLink = await response.json();
76
+ const exportLink = (await response.json());
77
77
  if (!exportLink.src) {
78
78
  this.error('API did not return a download URL');
79
79
  }
@@ -81,7 +81,9 @@ Downloaded release 'v1.0' to ./release-v1.0.tar.gz
81
81
  const safeFilename = releaseName.replaceAll(/[^\w.-]/g, '_');
82
82
  const outputPath = flags.output || `release-${safeFilename}.tar.gz`;
83
83
  const resolvedPath = path.resolve(outputPath);
84
- const downloadResponse = await fetch(exportLink.src);
84
+ const downloadResponse = await fetch(exportLink.src, {
85
+ headers: { 'User-Agent': buildUserAgent(this.config.version) },
86
+ });
85
87
  if (!downloadResponse.ok) {
86
88
  this.error(`Failed to download release: ${downloadResponse.status} ${downloadResponse.statusText}`);
87
89
  }
@@ -126,8 +128,7 @@ Downloaded release 'v1.0' to ./release-v1.0.tar.gz
126
128
  const configDir = path.join(os.homedir(), '.xano');
127
129
  const credentialsPath = path.join(configDir, 'credentials.yaml');
128
130
  if (!fs.existsSync(credentialsPath)) {
129
- this.error(`Credentials file not found at ${credentialsPath}\n` +
130
- `Create a profile using 'xano profile create'`);
131
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
131
132
  }
132
133
  try {
133
134
  const fileContent = fs.readFileSync(credentialsPath, 'utf8');
@@ -145,8 +146,8 @@ Downloaded release 'v1.0' to ./release-v1.0.tar.gz
145
146
  const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/release`;
146
147
  const response = await this.verboseFetch(listUrl, {
147
148
  headers: {
148
- 'accept': 'application/json',
149
- 'Authorization': `Bearer ${profile.access_token}`,
149
+ accept: 'application/json',
150
+ Authorization: `Bearer ${profile.access_token}`,
150
151
  },
151
152
  method: 'GET',
152
153
  }, verbose, profile.access_token);
@@ -154,15 +155,15 @@ Downloaded release 'v1.0' to ./release-v1.0.tar.gz
154
155
  const errorText = await response.text();
155
156
  this.error(`Failed to list releases: ${response.status} ${response.statusText}\n${errorText}`);
156
157
  }
157
- const data = await response.json();
158
+ const data = (await response.json());
158
159
  const releases = Array.isArray(data)
159
160
  ? data
160
- : (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items))
161
+ : data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)
161
162
  ? data.items
162
163
  : [];
163
- const match = releases.find(r => r.name === releaseName);
164
+ const match = releases.find((r) => r.name === releaseName);
164
165
  if (!match) {
165
- const available = releases.map(r => r.name).join(', ');
166
+ const available = releases.map((r) => r.name).join(', ');
166
167
  this.error(`Release '${releaseName}' not found.${available ? ` Available releases: ${available}` : ''}`);
167
168
  }
168
169
  return match.id;
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
3
3
  import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import * as yaml from 'js-yaml';
6
- import BaseCommand from '../../../../base-command.js';
6
+ import BaseCommand, { buildUserAgent } from '../../../../base-command.js';
7
7
  export default class TenantBackupExport extends BaseCommand {
8
8
  static args = {
9
9
  tenant_name: Args.string({
@@ -84,7 +84,9 @@ Downloaded backup #10 to ./tenant-t1234-abcd-xyz1-backup-10.tar.gz
84
84
  // Step 2: Download the file
85
85
  const outputPath = flags.output || `tenant-${tenantName}-backup-${backupId}.tar.gz`;
86
86
  const resolvedPath = path.resolve(outputPath);
87
- const downloadResponse = await fetch(exportLink.src);
87
+ const downloadResponse = await fetch(exportLink.src, {
88
+ headers: { 'User-Agent': buildUserAgent(this.config.version) },
89
+ });
88
90
  if (!downloadResponse.ok) {
89
91
  this.error(`Failed to download backup: ${downloadResponse.status} ${downloadResponse.statusText}`);
90
92
  }
@@ -4,7 +4,7 @@ import * as fs from 'node:fs';
4
4
  import * as os from 'node:os';
5
5
  import * as path from 'node:path';
6
6
  import snakeCase from 'lodash.snakecase';
7
- import BaseCommand from '../../../../base-command.js';
7
+ import BaseCommand, { buildUserAgent } from '../../../../base-command.js';
8
8
  import { buildApiGroupFolderResolver, parseDocument } from '../../../../utils/document-parser.js';
9
9
  export default class GitPull extends BaseCommand {
10
10
  static args = {
@@ -175,7 +175,7 @@ export default class GitPull extends BaseCommand {
175
175
  const apiUrl = `https://api.github.com/repos/${owner}/${repo}/tarball/${tarballRef}`;
176
176
  const headers = {
177
177
  Accept: 'application/vnd.github+json',
178
- 'User-Agent': 'xano-cli',
178
+ 'User-Agent': buildUserAgent(this.config.version),
179
179
  };
180
180
  if (token) {
181
181
  headers.Authorization = `Bearer ${token}`;