@xano/cli 0.0.36 → 0.0.39

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 (115) 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 +8 -9
  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/dist/commands/workspace/push/index.js +21 -6
  75. package/oclif.manifest.json +2323 -1918
  76. package/package.json +1 -19
  77. package/dist/commands/run/env/delete/index.d.ts +0 -14
  78. package/dist/commands/run/env/delete/index.js +0 -65
  79. package/dist/commands/run/env/get/index.d.ts +0 -14
  80. package/dist/commands/run/env/get/index.js +0 -52
  81. package/dist/commands/run/env/list/index.js +0 -56
  82. package/dist/commands/run/env/set/index.d.ts +0 -14
  83. package/dist/commands/run/env/set/index.js +0 -51
  84. package/dist/commands/run/exec/index.d.ts +0 -31
  85. package/dist/commands/run/exec/index.js +0 -431
  86. package/dist/commands/run/info/index.js +0 -160
  87. package/dist/commands/run/projects/create/index.js +0 -75
  88. package/dist/commands/run/projects/delete/index.d.ts +0 -14
  89. package/dist/commands/run/projects/delete/index.js +0 -65
  90. package/dist/commands/run/projects/list/index.d.ts +0 -13
  91. package/dist/commands/run/projects/list/index.js +0 -66
  92. package/dist/commands/run/projects/update/index.js +0 -86
  93. package/dist/commands/run/secrets/delete/index.d.ts +0 -14
  94. package/dist/commands/run/secrets/delete/index.js +0 -65
  95. package/dist/commands/run/secrets/get/index.js +0 -52
  96. package/dist/commands/run/secrets/list/index.d.ts +0 -12
  97. package/dist/commands/run/secrets/list/index.js +0 -60
  98. package/dist/commands/run/secrets/set/index.d.ts +0 -16
  99. package/dist/commands/run/secrets/set/index.js +0 -74
  100. package/dist/commands/run/sessions/delete/index.d.ts +0 -14
  101. package/dist/commands/run/sessions/delete/index.js +0 -65
  102. package/dist/commands/run/sessions/get/index.js +0 -72
  103. package/dist/commands/run/sessions/list/index.d.ts +0 -13
  104. package/dist/commands/run/sessions/list/index.js +0 -64
  105. package/dist/commands/run/sessions/start/index.js +0 -56
  106. package/dist/commands/run/sessions/stop/index.d.ts +0 -14
  107. package/dist/commands/run/sessions/stop/index.js +0 -56
  108. package/dist/commands/run/sink/get/index.d.ts +0 -14
  109. package/dist/commands/run/sink/get/index.js +0 -63
  110. package/dist/lib/base-run-command.d.ts +0 -41
  111. package/dist/lib/base-run-command.js +0 -75
  112. package/dist/lib/run-http-client.d.ts +0 -64
  113. package/dist/lib/run-http-client.js +0 -171
  114. package/dist/lib/run-types.d.ts +0 -226
  115. package/dist/lib/run-types.js +0 -5
@@ -0,0 +1,245 @@
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 Push extends BaseCommand {
8
+ static args = {
9
+ directory: Args.string({
10
+ description: 'Directory containing documents to push (as produced by tenant pull or workspace pull)',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Push local documents to a tenant via the Xano Metadata API multidoc endpoint';
15
+ static examples = [
16
+ `$ xano tenant push ./my-workspace -t my-tenant
17
+ Pushed 42 documents to tenant my-tenant from ./my-workspace
18
+ `,
19
+ `$ xano tenant push ./output -t my-tenant -w 40
20
+ Pushed 15 documents to tenant my-tenant from ./output
21
+ `,
22
+ `$ xano tenant push ./backup -t my-tenant --profile production
23
+ Pushed 58 documents to tenant my-tenant from ./backup
24
+ `,
25
+ `$ xano tenant push ./my-workspace -t my-tenant --no-records
26
+ Push schema only, skip importing table records
27
+ `,
28
+ `$ xano tenant push ./my-workspace -t my-tenant --no-env
29
+ Push without overwriting environment variables
30
+ `,
31
+ `$ xano tenant push ./my-workspace -t my-tenant --truncate
32
+ Truncate all table records before importing
33
+ `,
34
+ ];
35
+ static flags = {
36
+ ...BaseCommand.baseFlags,
37
+ env: Flags.boolean({
38
+ allowNo: true,
39
+ default: true,
40
+ description: 'Include environment variables in import (default: true, use --no-env to exclude)',
41
+ required: false,
42
+ }),
43
+ records: Flags.boolean({
44
+ allowNo: true,
45
+ default: true,
46
+ description: 'Include records in import (default: true, use --no-records to exclude)',
47
+ required: false,
48
+ }),
49
+ tenant: Flags.string({
50
+ char: 't',
51
+ description: 'Tenant name to push to',
52
+ required: true,
53
+ }),
54
+ truncate: Flags.boolean({
55
+ default: false,
56
+ description: 'Truncate all table records before importing',
57
+ required: false,
58
+ }),
59
+ workspace: Flags.string({
60
+ char: 'w',
61
+ description: 'Workspace ID (optional if set in profile)',
62
+ required: false,
63
+ }),
64
+ };
65
+ async run() {
66
+ const { args, flags } = await this.parse(Push);
67
+ // Get profile name (default or from flag/env)
68
+ const profileName = flags.profile || this.getDefaultProfile();
69
+ // Load credentials
70
+ const credentials = this.loadCredentials();
71
+ // Get the profile configuration
72
+ if (!(profileName in credentials.profiles)) {
73
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
74
+ `Create a profile using 'xano profile:create'`);
75
+ }
76
+ const profile = credentials.profiles[profileName];
77
+ // Validate required fields
78
+ if (!profile.instance_origin) {
79
+ this.error(`Profile '${profileName}' is missing instance_origin`);
80
+ }
81
+ if (!profile.access_token) {
82
+ this.error(`Profile '${profileName}' is missing access_token`);
83
+ }
84
+ // Determine workspace_id from flag or profile
85
+ let workspaceId;
86
+ if (flags.workspace) {
87
+ workspaceId = flags.workspace;
88
+ }
89
+ else if (profile.workspace) {
90
+ workspaceId = profile.workspace;
91
+ }
92
+ else {
93
+ this.error(`Workspace ID is required. Either:\n` +
94
+ ` 1. Provide it as a flag: xano tenant push <directory> -t <tenant_name> -w <workspace_id>\n` +
95
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
96
+ }
97
+ const tenantName = flags.tenant;
98
+ // Fetch tenant details and verify it's ephemeral
99
+ const tenantApiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
100
+ try {
101
+ const tenantResponse = await this.verboseFetch(tenantApiUrl, {
102
+ headers: {
103
+ accept: 'application/json',
104
+ Authorization: `Bearer ${profile.access_token}`,
105
+ },
106
+ method: 'GET',
107
+ }, flags.verbose, profile.access_token);
108
+ if (!tenantResponse.ok) {
109
+ const errorText = await tenantResponse.text();
110
+ this.error(`Failed to fetch tenant '${tenantName}' (${tenantResponse.status}): ${errorText}`);
111
+ }
112
+ const tenantData = (await tenantResponse.json());
113
+ if (!tenantData.ephemeral) {
114
+ this.error(`Tenant '${tenantName}' is not ephemeral. Push is only allowed for ephemeral tenants.\n` +
115
+ `Create an ephemeral tenant with: xano tenant create "name" --ephemeral`);
116
+ }
117
+ }
118
+ catch (error) {
119
+ if (error instanceof Error && error.message.includes('is not ephemeral')) {
120
+ throw error;
121
+ }
122
+ if (error instanceof Error && error.message.includes('Failed to fetch tenant')) {
123
+ throw error;
124
+ }
125
+ if (error instanceof Error) {
126
+ this.error(`Failed to verify tenant: ${error.message}`);
127
+ }
128
+ else {
129
+ this.error(`Failed to verify tenant: ${String(error)}`);
130
+ }
131
+ }
132
+ // Resolve the input directory
133
+ const inputDir = path.resolve(args.directory);
134
+ if (!fs.existsSync(inputDir)) {
135
+ this.error(`Directory not found: ${inputDir}`);
136
+ }
137
+ if (!fs.statSync(inputDir).isDirectory()) {
138
+ this.error(`Not a directory: ${inputDir}`);
139
+ }
140
+ // Collect all .xs files from the directory tree
141
+ const files = this.collectFiles(inputDir);
142
+ if (files.length === 0) {
143
+ this.error(`No .xs files found in ${args.directory}`);
144
+ }
145
+ // Read each file and join with --- separator
146
+ const documents = [];
147
+ for (const filePath of files) {
148
+ const content = fs.readFileSync(filePath, 'utf8').trim();
149
+ if (content) {
150
+ documents.push(content);
151
+ }
152
+ }
153
+ if (documents.length === 0) {
154
+ this.error(`All .xs files in ${args.directory} are empty`);
155
+ }
156
+ const multidoc = documents.join('\n---\n');
157
+ // Construct the API URL
158
+ const queryParams = new URLSearchParams({
159
+ env: flags.env.toString(),
160
+ records: flags.records.toString(),
161
+ truncate: flags.truncate.toString(),
162
+ });
163
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/multidoc?${queryParams.toString()}`;
164
+ // POST the multidoc to the API
165
+ const requestHeaders = {
166
+ accept: 'application/json',
167
+ Authorization: `Bearer ${profile.access_token}`,
168
+ 'Content-Type': 'text/x-xanoscript',
169
+ };
170
+ try {
171
+ const response = await this.verboseFetch(apiUrl, {
172
+ body: multidoc,
173
+ headers: requestHeaders,
174
+ method: 'POST',
175
+ }, flags.verbose, profile.access_token);
176
+ if (!response.ok) {
177
+ const errorText = await response.text();
178
+ let errorMessage = `Push failed (${response.status})`;
179
+ try {
180
+ const errorJson = JSON.parse(errorText);
181
+ errorMessage += `: ${errorJson.message}`;
182
+ if (errorJson.payload?.param) {
183
+ errorMessage += `\n Parameter: ${errorJson.payload.param}`;
184
+ }
185
+ }
186
+ catch {
187
+ errorMessage += `\n${errorText}`;
188
+ }
189
+ this.error(errorMessage);
190
+ }
191
+ // Log the response if any
192
+ const responseText = await response.text();
193
+ if (responseText && responseText !== 'null') {
194
+ this.log(responseText);
195
+ }
196
+ }
197
+ catch (error) {
198
+ if (error instanceof Error) {
199
+ this.error(`Failed to push multidoc: ${error.message}`);
200
+ }
201
+ else {
202
+ this.error(`Failed to push multidoc: ${String(error)}`);
203
+ }
204
+ }
205
+ this.log(`Pushed ${documents.length} documents to tenant ${tenantName} from ${args.directory}`);
206
+ }
207
+ /**
208
+ * Recursively collect all .xs files from a directory, sorted by
209
+ * type subdirectory name then filename for deterministic ordering.
210
+ */
211
+ collectFiles(dir) {
212
+ const files = [];
213
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
214
+ for (const entry of entries) {
215
+ const fullPath = path.join(dir, entry.name);
216
+ if (entry.isDirectory()) {
217
+ files.push(...this.collectFiles(fullPath));
218
+ }
219
+ else if (entry.isFile() && entry.name.endsWith('.xs')) {
220
+ files.push(fullPath);
221
+ }
222
+ }
223
+ return files.sort();
224
+ }
225
+ loadCredentials() {
226
+ const configDir = path.join(os.homedir(), '.xano');
227
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
228
+ // Check if credentials file exists
229
+ if (!fs.existsSync(credentialsPath)) {
230
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
231
+ }
232
+ // Read credentials file
233
+ try {
234
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
235
+ const parsed = yaml.load(fileContent);
236
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
237
+ this.error('Credentials file has invalid format.');
238
+ }
239
+ return parsed;
240
+ }
241
+ catch (error) {
242
+ this.error(`Failed to parse credentials file: ${error}`);
243
+ }
244
+ }
245
+ }
@@ -131,12 +131,17 @@ Push schema only, skip records and environment variables
131
131
  // Determine branch from flag or profile
132
132
  const branch = flags.branch || profile.branch || '';
133
133
  // Construct the API URL
134
- const queryParams = new URLSearchParams({ branch, env: flags.env.toString(), records: flags.records.toString(), truncate: flags.truncate.toString() });
134
+ const queryParams = new URLSearchParams({
135
+ branch,
136
+ env: flags.env.toString(),
137
+ records: flags.records.toString(),
138
+ truncate: flags.truncate.toString(),
139
+ });
135
140
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
136
141
  // POST the multidoc to the API
137
142
  const requestHeaders = {
138
- 'accept': 'application/json',
139
- 'Authorization': `Bearer ${profile.access_token}`,
143
+ accept: 'application/json',
144
+ Authorization: `Bearer ${profile.access_token}`,
140
145
  'Content-Type': 'text/x-xanoscript',
141
146
  };
142
147
  try {
@@ -147,7 +152,18 @@ Push schema only, skip records and environment variables
147
152
  }, flags.verbose, profile.access_token);
148
153
  if (!response.ok) {
149
154
  const errorText = await response.text();
150
- this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
155
+ let errorMessage = `Push failed (${response.status})`;
156
+ try {
157
+ const errorJson = JSON.parse(errorText);
158
+ errorMessage += `: ${errorJson.message}`;
159
+ if (errorJson.payload?.param) {
160
+ errorMessage += `\n Parameter: ${errorJson.payload.param}`;
161
+ }
162
+ }
163
+ catch {
164
+ errorMessage += `\n${errorText}`;
165
+ }
166
+ this.error(errorMessage);
151
167
  }
152
168
  // Log the response if any
153
169
  const responseText = await response.text();
@@ -188,8 +204,7 @@ Push schema only, skip records and environment variables
188
204
  const credentialsPath = path.join(configDir, 'credentials.yaml');
189
205
  // Check if credentials file exists
190
206
  if (!fs.existsSync(credentialsPath)) {
191
- this.error(`Credentials file not found at ${credentialsPath}\n` +
192
- `Create a profile using 'xano profile:create'`);
207
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
193
208
  }
194
209
  // Read credentials file
195
210
  try {