@xano/cli 0.0.95-beta.3 → 0.0.95-beta.5

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 CHANGED
@@ -307,7 +307,8 @@ xano tenant get <tenant_name>
307
307
 
308
308
  # Create a tenant
309
309
  xano tenant create "My Tenant"
310
- xano tenant create "My Tenant" -d "Description" --cluster_id 1 --platform_id 5
310
+ xano tenant create "My Tenant" -d "Description" --type tier2 --cluster_id 1 --platform_id 5
311
+ xano tenant create "My Tenant" --type tier2 --cluster_id 1 --license ./license.yaml
311
312
 
312
313
  # Edit a tenant
313
314
  xano tenant edit <tenant_name> --display "New Name" -d "New description"
@@ -283,7 +283,7 @@ Opening browser for Xano login at https://custom.xano.com...`,
283
283
  choices: [
284
284
  { name: '(Skip workspace)', value: '' },
285
285
  ...workspaces.map((ws) => ({
286
- name: ws.name,
286
+ name: `${ws.name} (${ws.id})`,
287
287
  value: ws.id,
288
288
  })),
289
289
  ],
@@ -111,8 +111,27 @@ User Information:
111
111
  this.log(` Name: ${inst.name}`);
112
112
  if (inst.display)
113
113
  this.log(` Display: ${inst.display}`);
114
- if (profile.workspace)
115
- this.log(` Workspace: ${profile.workspace}`);
114
+ if (profile.workspace) {
115
+ let wsLabel = String(profile.workspace);
116
+ try {
117
+ const wsResponse = await this.verboseFetch(`${profile.instance_origin}/api:meta/workspace/${profile.workspace}`, {
118
+ headers: {
119
+ accept: 'application/json',
120
+ Authorization: `Bearer ${profile.access_token}`,
121
+ },
122
+ method: 'GET',
123
+ }, false, profile.access_token);
124
+ if (wsResponse.ok) {
125
+ const ws = (await wsResponse.json());
126
+ if (ws.name)
127
+ wsLabel = `${ws.name} (ID: ${profile.workspace})`;
128
+ }
129
+ }
130
+ catch {
131
+ // Fall back to just showing the ID
132
+ }
133
+ this.log(` Workspace: ${wsLabel}`);
134
+ }
116
135
  if (profile.branch)
117
136
  this.log(` Branch: ${profile.branch}`);
118
137
  }
@@ -126,7 +126,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
126
126
  choices: [
127
127
  { name: '(Skip workspace)', value: '' },
128
128
  ...workspaces.map((ws) => ({
129
- name: ws.name,
129
+ name: `${ws.name} (${ws.id})`,
130
130
  value: ws.id,
131
131
  })),
132
132
  ],
@@ -40,7 +40,7 @@ Workspace updated to 'Production API' (xyz789) on profile 'production'
40
40
  const { selectedWorkspace } = await inquirer.prompt([
41
41
  {
42
42
  choices: workspaces.map((ws) => ({
43
- name: String(ws.id) === String(profile.workspace) ? `${ws.name} (current)` : ws.name,
43
+ name: String(ws.id) === String(profile.workspace) ? `${ws.name} (${ws.id}) (current)` : `${ws.name} (${ws.id})`,
44
44
  value: ws.id,
45
45
  })),
46
46
  message: 'Select a workspace',
@@ -10,9 +10,10 @@ export default class TenantCreate extends BaseCommand {
10
10
  description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  ingress: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
- license: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ license: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
14
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
15
  platform_id: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
17
  tasks: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
18
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
19
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -16,7 +16,8 @@ export default class TenantCreate extends BaseCommand {
16
16
  `$ xano tenant create "Production"
17
17
  Created tenant: Production (production) - ID: 42
18
18
  `,
19
- `$ xano tenant create "Staging" --description "Staging env" --cluster_id 1 --platform_id 1 --license tier2 -o json`,
19
+ `$ xano tenant create "Staging" --description "Staging env" --cluster_id 1 --platform_id 1 --type tier2 -o json`,
20
+ `$ xano tenant create "Staging" --type tier2 --cluster_id 1 --license ./license.yaml`,
20
21
  ];
21
22
  static flags = {
22
23
  ...BaseCommand.baseFlags,
@@ -39,9 +40,8 @@ Created tenant: Production (production) - ID: 42
39
40
  description: 'Enable ingress',
40
41
  }),
41
42
  license: Flags.string({
42
- default: 'tier1',
43
- description: 'License tier',
44
- options: ['tier1', 'tier2', 'tier3'],
43
+ char: 'l',
44
+ description: 'Path to a license override file to apply during creation',
45
45
  required: false,
46
46
  }),
47
47
  output: Flags.string({
@@ -55,6 +55,12 @@ Created tenant: Production (production) - ID: 42
55
55
  description: 'Platform ID to use',
56
56
  required: false,
57
57
  }),
58
+ type: Flags.string({
59
+ default: 'tier1',
60
+ description: 'Tenant type',
61
+ options: ['tier1', 'tier2', 'tier3'],
62
+ required: false,
63
+ }),
58
64
  tasks: Flags.boolean({
59
65
  allowNo: true,
60
66
  default: true,
@@ -88,7 +94,7 @@ Created tenant: Production (production) - ID: 42
88
94
  const body = {
89
95
  display: args.display,
90
96
  ingress: flags.ingress,
91
- license: flags.license,
97
+ license: flags.type,
92
98
  tag: [],
93
99
  tasks: flags.tasks,
94
100
  };
@@ -102,7 +108,16 @@ Created tenant: Production (production) - ID: 42
102
108
  body.platform_id = flags.platform_id;
103
109
  if (flags.domain)
104
110
  body.domain = flags.domain;
105
- if (flags.license === 'tier2' || flags.license === 'tier3' || flags.cluster_id) {
111
+ if (flags.license) {
112
+ const licensePath = path.resolve(flags.license);
113
+ if (!fs.existsSync(licensePath)) {
114
+ this.error(`License file not found: ${licensePath}`);
115
+ }
116
+ const licenseContent = fs.readFileSync(licensePath, 'utf8');
117
+ body.license_overrides = yaml.load(licenseContent);
118
+ }
119
+ const effectiveType = flags.cluster_id ? 'tier3' : flags.type;
120
+ if (effectiveType === 'tier2' || effectiveType === 'tier3') {
106
121
  this.warn('This may take a few minutes. Please be patient.');
107
122
  }
108
123
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant`;
@@ -129,6 +144,8 @@ Created tenant: Production (production) - ID: 42
129
144
  if (tenant.state) {
130
145
  this.log(` State: ${tenant.state}`);
131
146
  }
147
+ if (flags.license)
148
+ this.log(` License: applied`);
132
149
  }
133
150
  }
134
151
  catch (error) {
@@ -9,6 +9,7 @@ export default class WorkspaceEdit extends BaseCommand {
9
9
  static description: string;
10
10
  static examples: string[];
11
11
  static flags: {
12
+ 'allow-push': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
13
  description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
14
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
15
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -35,6 +35,11 @@ Updated workspace: my-workspace (ID: 123)
35
35
  ];
36
36
  static flags = {
37
37
  ...BaseCommand.baseFlags,
38
+ 'allow-push': Flags.boolean({
39
+ allowNo: true,
40
+ description: 'Enable or disable direct CLI push to this workspace',
41
+ required: false,
42
+ }),
38
43
  description: Flags.string({
39
44
  char: 'd',
40
45
  description: 'New description for the workspace',
@@ -102,9 +107,12 @@ Updated workspace: my-workspace (ID: 123)
102
107
  if (flags['require-token'] !== undefined) {
103
108
  body.documentation = { require_token: flags['require-token'] };
104
109
  }
110
+ if (flags['allow-push'] !== undefined) {
111
+ body.preferences = { allow_push: flags['allow-push'] };
112
+ }
105
113
  // Check if at least one field is being updated
106
114
  if (Object.keys(body).length === 0) {
107
- this.error('No fields specified to update. Use --name, --description, --swagger, or --require-token flags.\n' +
115
+ this.error('No fields specified to update. Use --name, --description, --swagger, --require-token, or --allow-push flags.\n' +
108
116
  'Example: xano workspace edit 123 --name "new-name"');
109
117
  }
110
118
  // Construct the API URL
@@ -114,8 +122,8 @@ Updated workspace: my-workspace (ID: 123)
114
122
  const response = await this.verboseFetch(apiUrl, {
115
123
  body: JSON.stringify(body),
116
124
  headers: {
117
- 'accept': 'application/json',
118
- 'Authorization': `Bearer ${profile.access_token}`,
125
+ accept: 'application/json',
126
+ Authorization: `Bearer ${profile.access_token}`,
119
127
  'Content-Type': 'application/json',
120
128
  },
121
129
  method: 'PUT',
@@ -124,7 +132,7 @@ Updated workspace: my-workspace (ID: 123)
124
132
  const errorText = await response.text();
125
133
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
126
134
  }
127
- const workspace = await response.json();
135
+ const workspace = (await response.json());
128
136
  // Output results
129
137
  if (flags.output === 'json') {
130
138
  this.log(JSON.stringify(workspace, null, 2));
@@ -141,6 +149,9 @@ Updated workspace: my-workspace (ID: 123)
141
149
  if (workspace.documentation?.require_token !== undefined) {
142
150
  this.log(` Require Token: ${workspace.documentation.require_token}`);
143
151
  }
152
+ if (workspace.preferences?.allow_push !== undefined) {
153
+ this.log(` Allow Push: ${workspace.preferences.allow_push}`);
154
+ }
144
155
  }
145
156
  }
146
157
  catch (error) {
@@ -157,8 +168,7 @@ Updated workspace: my-workspace (ID: 123)
157
168
  const credentialsPath = path.join(configDir, 'credentials.yaml');
158
169
  // Check if credentials file exists
159
170
  if (!fs.existsSync(credentialsPath)) {
160
- this.error(`Credentials file not found at ${credentialsPath}\n` +
161
- `Create a profile using 'xano profile create'`);
171
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
162
172
  }
163
173
  // Read credentials file
164
174
  try {
@@ -73,8 +73,8 @@ Workspace: my-workspace (ID: 123)
73
73
  try {
74
74
  const response = await this.verboseFetch(apiUrl, {
75
75
  headers: {
76
- 'accept': 'application/json',
77
- 'Authorization': `Bearer ${profile.access_token}`,
76
+ accept: 'application/json',
77
+ Authorization: `Bearer ${profile.access_token}`,
78
78
  },
79
79
  method: 'GET',
80
80
  }, flags.verbose, profile.access_token);
@@ -82,7 +82,7 @@ Workspace: my-workspace (ID: 123)
82
82
  const errorText = await response.text();
83
83
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
84
84
  }
85
- const workspace = await response.json();
85
+ const workspace = (await response.json());
86
86
  // Output results
87
87
  if (flags.output === 'json') {
88
88
  this.log(JSON.stringify(workspace, null, 2));
@@ -94,13 +94,16 @@ Workspace: my-workspace (ID: 123)
94
94
  this.log(` Description: ${workspace.description}`);
95
95
  }
96
96
  if (workspace.created_at) {
97
- const createdDate = new Date(workspace.created_at * 1000).toISOString().split('T')[0];
97
+ const createdDate = new Date(workspace.created_at).toISOString().split('T')[0];
98
98
  this.log(` Created: ${createdDate}`);
99
99
  }
100
100
  if (workspace.updated_at) {
101
- const updatedDate = new Date(workspace.updated_at * 1000).toISOString().split('T')[0];
101
+ const updatedDate = new Date(workspace.updated_at).toISOString().split('T')[0];
102
102
  this.log(` Updated: ${updatedDate}`);
103
103
  }
104
+ if (workspace.preferences?.allow_push !== undefined) {
105
+ this.log(` Allow Push: ${workspace.preferences.allow_push}`);
106
+ }
104
107
  }
105
108
  }
106
109
  catch (error) {
@@ -117,8 +120,7 @@ Workspace: my-workspace (ID: 123)
117
120
  const credentialsPath = path.join(configDir, 'credentials.yaml');
118
121
  // Check if credentials file exists
119
122
  if (!fs.existsSync(credentialsPath)) {
120
- this.error(`Credentials file not found at ${credentialsPath}\n` +
121
- `Create a profile using 'xano profile create'`);
123
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
122
124
  }
123
125
  // Read credentials file
124
126
  try {
@@ -3,6 +3,7 @@ export default class WorkspaceList extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ latest: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
8
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -45,6 +45,10 @@ Available workspaces:
45
45
  ];
46
46
  static flags = {
47
47
  ...BaseCommand.baseFlags,
48
+ latest: Flags.boolean({
49
+ default: false,
50
+ description: 'Sort by newest first (descending ID)',
51
+ }),
48
52
  output: Flags.string({
49
53
  char: 'o',
50
54
  default: 'summary',
@@ -78,8 +82,8 @@ Available workspaces:
78
82
  try {
79
83
  const response = await this.verboseFetch(apiUrl, {
80
84
  headers: {
81
- 'accept': 'application/json',
82
- 'Authorization': `Bearer ${profile.access_token}`,
85
+ accept: 'application/json',
86
+ Authorization: `Bearer ${profile.access_token}`,
83
87
  },
84
88
  method: 'GET',
85
89
  }, flags.verbose, profile.access_token);
@@ -87,7 +91,7 @@ Available workspaces:
87
91
  const errorText = await response.text();
88
92
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
89
93
  }
90
- const data = await response.json();
94
+ const data = (await response.json());
91
95
  // Handle different response formats
92
96
  let workspaces;
93
97
  if (Array.isArray(data)) {
@@ -99,6 +103,9 @@ Available workspaces:
99
103
  else {
100
104
  this.error('Unexpected API response format');
101
105
  }
106
+ if (flags.latest) {
107
+ workspaces.sort((a, b) => b.id - a.id);
108
+ }
102
109
  // Output results
103
110
  if (flags.output === 'json') {
104
111
  this.log(JSON.stringify(workspaces, null, 2));
@@ -111,11 +118,12 @@ Available workspaces:
111
118
  else {
112
119
  this.log('Available workspaces:');
113
120
  for (const workspace of workspaces) {
121
+ const created = workspace.created_at ? ` (created: ${workspace.created_at.split(' ')[0]})` : '';
114
122
  if (workspace.id === undefined) {
115
- this.log(` - ${workspace.name}`);
123
+ this.log(` - ${workspace.name}${created}`);
116
124
  }
117
125
  else {
118
- this.log(` - ${workspace.name} (ID: ${workspace.id})`);
126
+ this.log(` - ${workspace.name} (ID: ${workspace.id})${created}`);
119
127
  }
120
128
  }
121
129
  }
@@ -135,8 +143,7 @@ Available workspaces:
135
143
  const credentialsPath = path.join(configDir, 'credentials.yaml');
136
144
  // Check if credentials file exists
137
145
  if (!fs.existsSync(credentialsPath)) {
138
- this.error(`Credentials file not found at ${credentialsPath}\n` +
139
- `Create a profile using 'xano profile:create'`);
146
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
140
147
  }
141
148
  // Read credentials file
142
149
  try {