@xano/cli 0.0.30 → 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 (51) hide show
  1. package/dist/base-command.js +1 -1
  2. package/dist/commands/platform/get/index.d.ts +18 -0
  3. package/dist/commands/platform/get/index.js +126 -0
  4. package/dist/commands/platform/list/index.d.ts +12 -0
  5. package/dist/commands/platform/list/index.js +113 -0
  6. package/dist/commands/release/create/index.d.ts +18 -0
  7. package/dist/commands/release/create/index.js +138 -0
  8. package/dist/commands/release/delete/index.d.ts +21 -0
  9. package/dist/commands/release/delete/index.js +134 -0
  10. package/dist/commands/release/edit/index.d.ts +21 -0
  11. package/dist/commands/release/edit/index.js +137 -0
  12. package/dist/commands/release/export/index.d.ts +20 -0
  13. package/dist/commands/release/export/index.js +142 -0
  14. package/dist/commands/release/get/index.d.ts +19 -0
  15. package/dist/commands/release/get/index.js +123 -0
  16. package/dist/commands/release/import/index.d.ts +15 -0
  17. package/dist/commands/release/import/index.js +114 -0
  18. package/dist/commands/release/list/index.d.ts +13 -0
  19. package/dist/commands/release/list/index.js +120 -0
  20. package/dist/commands/tenant/backup/create/index.d.ts +20 -0
  21. package/dist/commands/tenant/backup/create/index.js +113 -0
  22. package/dist/commands/tenant/backup/delete/index.d.ts +22 -0
  23. package/dist/commands/tenant/backup/delete/index.js +137 -0
  24. package/dist/commands/tenant/backup/export/index.d.ts +21 -0
  25. package/dist/commands/tenant/backup/export/index.js +147 -0
  26. package/dist/commands/tenant/backup/import/index.d.ts +21 -0
  27. package/dist/commands/tenant/backup/import/index.js +127 -0
  28. package/dist/commands/tenant/backup/list/index.d.ts +20 -0
  29. package/dist/commands/tenant/backup/list/index.js +137 -0
  30. package/dist/commands/tenant/backup/restore/index.d.ts +22 -0
  31. package/dist/commands/tenant/backup/restore/index.js +141 -0
  32. package/dist/commands/tenant/create/index.d.ts +21 -0
  33. package/dist/commands/tenant/create/index.js +155 -0
  34. package/dist/commands/tenant/delete/index.d.ts +21 -0
  35. package/dist/commands/tenant/delete/index.js +134 -0
  36. package/dist/commands/tenant/deploy-platform/index.d.ts +20 -0
  37. package/dist/commands/tenant/deploy-platform/index.js +116 -0
  38. package/dist/commands/tenant/deploy-release/index.d.ts +20 -0
  39. package/dist/commands/tenant/deploy-release/index.js +116 -0
  40. package/dist/commands/tenant/edit/index.d.ts +26 -0
  41. package/dist/commands/tenant/edit/index.js +167 -0
  42. package/dist/commands/tenant/get/index.d.ts +19 -0
  43. package/dist/commands/tenant/get/index.js +135 -0
  44. package/dist/commands/tenant/list/index.d.ts +13 -0
  45. package/dist/commands/tenant/list/index.js +123 -0
  46. package/dist/commands/workspace/pull/index.d.ts +1 -0
  47. package/dist/commands/workspace/pull/index.js +38 -4
  48. package/dist/commands/workspace/push/index.d.ts +3 -0
  49. package/dist/commands/workspace/push/index.js +33 -1
  50. package/oclif.manifest.json +3006 -1049
  51. package/package.json +10 -1
@@ -0,0 +1,135 @@
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 TenantGet extends BaseCommand {
8
+ static args = {
9
+ tenant_id: Args.integer({
10
+ description: 'Tenant ID to retrieve',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Get details of a specific tenant';
15
+ static examples = [
16
+ `$ xano tenant get 42
17
+ Tenant: My Tenant (my-tenant) - ID: 42
18
+ State: ok
19
+ License: tier1
20
+ Domain: my-tenant.xano.io
21
+ Cluster: default
22
+ Release: v1.0
23
+ `,
24
+ `$ xano tenant get 42 -w 5 -o json`,
25
+ ];
26
+ static flags = {
27
+ ...BaseCommand.baseFlags,
28
+ output: Flags.string({
29
+ char: 'o',
30
+ default: 'summary',
31
+ description: 'Output format',
32
+ options: ['summary', 'json'],
33
+ required: false,
34
+ }),
35
+ workspace: Flags.string({
36
+ char: 'w',
37
+ description: 'Workspace ID (uses profile workspace if not provided)',
38
+ required: false,
39
+ }),
40
+ };
41
+ async run() {
42
+ const { args, flags } = await this.parse(TenantGet);
43
+ const profileName = flags.profile || this.getDefaultProfile();
44
+ const credentials = this.loadCredentials();
45
+ if (!(profileName in credentials.profiles)) {
46
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
47
+ `Create a profile using 'xano profile create'`);
48
+ }
49
+ const profile = credentials.profiles[profileName];
50
+ if (!profile.instance_origin) {
51
+ this.error(`Profile '${profileName}' is missing instance_origin`);
52
+ }
53
+ if (!profile.access_token) {
54
+ this.error(`Profile '${profileName}' is missing access_token`);
55
+ }
56
+ const workspaceId = flags.workspace || profile.workspace;
57
+ if (!workspaceId) {
58
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
59
+ }
60
+ const tenantId = args.tenant_id;
61
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantId}`;
62
+ try {
63
+ const response = await this.verboseFetch(apiUrl, {
64
+ headers: {
65
+ 'accept': 'application/json',
66
+ 'Authorization': `Bearer ${profile.access_token}`,
67
+ },
68
+ method: 'GET',
69
+ }, flags.verbose, profile.access_token);
70
+ if (!response.ok) {
71
+ const errorText = await response.text();
72
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
73
+ }
74
+ const tenant = await response.json();
75
+ if (flags.output === 'json') {
76
+ this.log(JSON.stringify(tenant, null, 2));
77
+ }
78
+ else {
79
+ this.log(`Tenant: ${tenant.display || tenant.name} (${tenant.name}) - ID: ${tenant.id}`);
80
+ if (tenant.state)
81
+ this.log(` State: ${tenant.state}`);
82
+ if (tenant.license)
83
+ this.log(` License: ${tenant.license}`);
84
+ if (tenant.xano_domain)
85
+ this.log(` Domain: ${tenant.xano_domain}`);
86
+ if (tenant.domain)
87
+ this.log(` Custom Domain: ${tenant.domain}`);
88
+ if (tenant.cluster?.name)
89
+ this.log(` Cluster: ${tenant.cluster.name}`);
90
+ if (tenant.release?.name)
91
+ this.log(` Release: ${tenant.release.name}`);
92
+ if (tenant.platform?.name)
93
+ this.log(` Platform: ${tenant.platform.name}`);
94
+ if (tenant.version !== undefined)
95
+ this.log(` Version: ${tenant.version}`);
96
+ if (tenant.tasks !== undefined)
97
+ this.log(` Tasks: ${tenant.tasks}`);
98
+ if (tenant.ingress !== undefined)
99
+ this.log(` Ingress: ${tenant.ingress}`);
100
+ if (tenant.deployed_at) {
101
+ const d = new Date(tenant.deployed_at);
102
+ const deployedDate = Number.isNaN(d.getTime()) ? tenant.deployed_at : d.toISOString().split('T')[0];
103
+ this.log(` Deployed: ${deployedDate}`);
104
+ }
105
+ }
106
+ }
107
+ catch (error) {
108
+ if (error instanceof Error) {
109
+ this.error(`Failed to get tenant: ${error.message}`);
110
+ }
111
+ else {
112
+ this.error(`Failed to get tenant: ${String(error)}`);
113
+ }
114
+ }
115
+ }
116
+ loadCredentials() {
117
+ const configDir = path.join(os.homedir(), '.xano');
118
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
119
+ if (!fs.existsSync(credentialsPath)) {
120
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
121
+ `Create a profile using 'xano profile create'`);
122
+ }
123
+ try {
124
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
125
+ const parsed = yaml.load(fileContent);
126
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
127
+ this.error('Credentials file has invalid format.');
128
+ }
129
+ return parsed;
130
+ }
131
+ catch (error) {
132
+ this.error(`Failed to parse credentials file: ${error}`);
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,13 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class TenantList 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,123 @@
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 TenantList extends BaseCommand {
8
+ static description = 'List all tenants in a workspace';
9
+ static examples = [
10
+ `$ xano tenant list
11
+ Tenants in workspace 5:
12
+ - My Tenant (my-tenant) [ok] - tier1
13
+ - Staging (staging) [ok] - tier1
14
+ `,
15
+ `$ xano tenant 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(TenantList);
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}/tenant`;
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 tenants;
66
+ if (Array.isArray(data)) {
67
+ tenants = data;
68
+ }
69
+ else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
70
+ tenants = data.items;
71
+ }
72
+ else if (data && typeof data === 'object' && 'tenants' in data && Array.isArray(data.tenants)) {
73
+ tenants = data.tenants;
74
+ }
75
+ else {
76
+ this.error('Unexpected API response format');
77
+ }
78
+ if (flags.output === 'json') {
79
+ this.log(JSON.stringify(tenants, null, 2));
80
+ }
81
+ else {
82
+ if (tenants.length === 0) {
83
+ this.log('No tenants found');
84
+ }
85
+ else {
86
+ this.log(`Tenants in workspace ${workspaceId}:`);
87
+ for (const tenant of tenants) {
88
+ const state = tenant.state ? ` [${tenant.state}]` : '';
89
+ const license = tenant.license ? ` - ${tenant.license}` : '';
90
+ this.log(` - ${tenant.display || tenant.name} (${tenant.name}) (ID: ${tenant.id})${state}${license}`);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ catch (error) {
96
+ if (error instanceof Error) {
97
+ this.error(`Failed to list tenants: ${error.message}`);
98
+ }
99
+ else {
100
+ this.error(`Failed to list tenants: ${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
+ }
@@ -8,6 +8,7 @@ export default class Pull extends BaseCommand {
8
8
  static flags: {
9
9
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
13
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
14
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -22,6 +22,9 @@ Pulled 15 documents to ./output
22
22
  `,
23
23
  `$ xano workspace pull ./backup --profile production --env --records
24
24
  Pulled 58 documents to ./backup
25
+ `,
26
+ `$ xano workspace pull ./my-workspace --draft
27
+ Pulled 42 documents to ./my-workspace
25
28
  `,
26
29
  `$ xano workspace pull ./my-workspace -b dev
27
30
  Pulled 42 documents to ./my-workspace
@@ -39,6 +42,11 @@ Pulled 42 documents to ./my-workspace
39
42
  description: 'Include environment variables',
40
43
  required: false,
41
44
  }),
45
+ draft: Flags.boolean({
46
+ default: false,
47
+ description: 'Include draft versions',
48
+ required: false,
49
+ }),
42
50
  records: Flags.boolean({
43
51
  default: false,
44
52
  description: 'Include records',
@@ -88,6 +96,7 @@ Pulled 42 documents to ./my-workspace
88
96
  const queryParams = new URLSearchParams({
89
97
  branch,
90
98
  env: flags.env.toString(),
99
+ include_draft: flags.draft.toString(),
91
100
  records: flags.records.toString(),
92
101
  });
93
102
  // Construct the API URL
@@ -155,14 +164,29 @@ Pulled 42 documents to ./my-workspace
155
164
  typeDir = path.join(outputDir, 'workspace', 'trigger');
156
165
  baseName = this.sanitizeFilename(doc.name);
157
166
  }
167
+ else if (doc.type === 'agent') {
168
+ // agent → ai/agent/{name}.xs
169
+ typeDir = path.join(outputDir, 'ai', 'agent');
170
+ baseName = this.sanitizeFilename(doc.name);
171
+ }
172
+ else if (doc.type === 'mcp_server') {
173
+ // mcp_server → ai/mcp_server/{name}.xs
174
+ typeDir = path.join(outputDir, 'ai', 'mcp_server');
175
+ baseName = this.sanitizeFilename(doc.name);
176
+ }
177
+ else if (doc.type === 'tool') {
178
+ // tool → ai/tool/{name}.xs
179
+ typeDir = path.join(outputDir, 'ai', 'tool');
180
+ baseName = this.sanitizeFilename(doc.name);
181
+ }
158
182
  else if (doc.type === 'agent_trigger') {
159
- // agent_trigger → agent/trigger/{name}.xs
160
- typeDir = path.join(outputDir, 'agent', 'trigger');
183
+ // agent_trigger → ai/agent/trigger/{name}.xs
184
+ typeDir = path.join(outputDir, 'ai', 'agent', 'trigger');
161
185
  baseName = this.sanitizeFilename(doc.name);
162
186
  }
163
187
  else if (doc.type === 'mcp_server_trigger') {
164
- // mcp_server_trigger → mcp_server/trigger/{name}.xs
165
- typeDir = path.join(outputDir, 'mcp_server', 'trigger');
188
+ // mcp_server_trigger → ai/mcp_server/trigger/{name}.xs
189
+ typeDir = path.join(outputDir, 'ai', 'mcp_server', 'trigger');
166
190
  baseName = this.sanitizeFilename(doc.name);
167
191
  }
168
192
  else if (doc.type === 'table_trigger') {
@@ -170,6 +194,16 @@ Pulled 42 documents to ./my-workspace
170
194
  typeDir = path.join(outputDir, 'table', 'trigger');
171
195
  baseName = this.sanitizeFilename(doc.name);
172
196
  }
197
+ else if (doc.type === 'realtime_channel') {
198
+ // realtime_channel → realtime/channel/{name}.xs
199
+ typeDir = path.join(outputDir, 'realtime', 'channel');
200
+ baseName = this.sanitizeFilename(doc.name);
201
+ }
202
+ else if (doc.type === 'realtime_trigger') {
203
+ // realtime_trigger → realtime/trigger/{name}.xs
204
+ typeDir = path.join(outputDir, 'realtime', 'trigger');
205
+ baseName = this.sanitizeFilename(doc.name);
206
+ }
173
207
  else if (doc.type === 'api_group') {
174
208
  // api_group "test" → api/test/api_group.xs
175
209
  const groupFolder = snakeCase(doc.name);
@@ -7,6 +7,9 @@ export default class Push extends BaseCommand {
7
7
  static examples: string[];
8
8
  static flags: {
9
9
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ truncate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
13
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
14
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
15
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -24,6 +24,21 @@ Pushed 58 documents from ./backup
24
24
  `,
25
25
  `$ xano workspace push ./my-workspace -b dev
26
26
  Pushed 42 documents from ./my-workspace
27
+ `,
28
+ `$ xano workspace push ./my-workspace --no-records
29
+ Push schema only, skip importing table records
30
+ `,
31
+ `$ xano workspace push ./my-workspace --no-env
32
+ Push without overwriting environment variables
33
+ `,
34
+ `$ xano workspace push ./my-workspace --truncate
35
+ Truncate all table records before importing
36
+ `,
37
+ `$ xano workspace push ./my-workspace --truncate --no-records
38
+ Truncate all table records without importing new ones
39
+ `,
40
+ `$ xano workspace push ./my-workspace --no-records --no-env
41
+ Push schema only, skip records and environment variables
27
42
  `,
28
43
  ];
29
44
  static flags = {
@@ -33,6 +48,23 @@ Pushed 42 documents from ./my-workspace
33
48
  description: 'Branch name (optional if set in profile, defaults to live)',
34
49
  required: false,
35
50
  }),
51
+ env: Flags.boolean({
52
+ allowNo: true,
53
+ default: true,
54
+ description: 'Include environment variables in import (default: true, use --no-env to exclude)',
55
+ required: false,
56
+ }),
57
+ records: Flags.boolean({
58
+ allowNo: true,
59
+ default: true,
60
+ description: 'Include records in import (default: true, use --no-records to exclude)',
61
+ required: false,
62
+ }),
63
+ truncate: Flags.boolean({
64
+ default: false,
65
+ description: 'Truncate all table records before importing',
66
+ required: false,
67
+ }),
36
68
  workspace: Flags.string({
37
69
  char: 'w',
38
70
  description: 'Workspace ID (optional if set in profile)',
@@ -99,7 +131,7 @@ Pushed 42 documents from ./my-workspace
99
131
  // Determine branch from flag or profile
100
132
  const branch = flags.branch || profile.branch || '';
101
133
  // Construct the API URL
102
- const queryParams = new URLSearchParams({ branch });
134
+ const queryParams = new URLSearchParams({ branch, env: flags.env.toString(), records: flags.records.toString(), truncate: flags.truncate.toString() });
103
135
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
104
136
  // POST the multidoc to the API
105
137
  const requestHeaders = {