@xano/cli 0.0.95 → 1.0.0

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 (236) hide show
  1. package/README.md +133 -66
  2. package/dist/base-command.d.ts +41 -1
  3. package/dist/base-command.js +92 -3
  4. package/dist/commands/auth/index.d.ts +1 -0
  5. package/dist/commands/auth/index.js +16 -11
  6. package/dist/commands/branch/create/index.d.ts +4 -1
  7. package/dist/commands/branch/create/index.js +22 -21
  8. package/dist/commands/branch/delete/index.d.ts +1 -0
  9. package/dist/commands/branch/delete/index.js +1 -4
  10. package/dist/commands/branch/edit/index.d.ts +1 -0
  11. package/dist/commands/branch/edit/index.js +1 -4
  12. package/dist/commands/branch/get/index.d.ts +1 -0
  13. package/dist/commands/branch/get/index.js +1 -4
  14. package/dist/commands/branch/list/index.d.ts +2 -6
  15. package/dist/commands/branch/list/index.js +13 -17
  16. package/dist/commands/branch/set_live/index.d.ts +1 -0
  17. package/dist/commands/branch/set_live/index.js +1 -4
  18. package/dist/commands/function/create/index.d.ts +1 -0
  19. package/dist/commands/function/create/index.js +1 -2
  20. package/dist/commands/function/edit/index.d.ts +1 -0
  21. package/dist/commands/function/edit/index.js +1 -2
  22. package/dist/commands/function/get/index.d.ts +1 -0
  23. package/dist/commands/function/get/index.js +1 -4
  24. package/dist/commands/function/list/index.d.ts +1 -0
  25. package/dist/commands/function/list/index.js +1 -4
  26. package/dist/commands/platform/get/index.d.ts +1 -0
  27. package/dist/commands/platform/get/index.js +1 -4
  28. package/dist/commands/platform/list/index.d.ts +1 -0
  29. package/dist/commands/platform/list/index.js +1 -4
  30. package/dist/commands/profile/create/index.d.ts +1 -0
  31. package/dist/commands/profile/create/index.js +12 -6
  32. package/dist/commands/profile/delete/index.d.ts +1 -0
  33. package/dist/commands/profile/delete/index.js +8 -4
  34. package/dist/commands/profile/edit/index.d.ts +1 -0
  35. package/dist/commands/profile/edit/index.js +3 -6
  36. package/dist/commands/profile/get/index.d.ts +3 -0
  37. package/dist/commands/profile/get/index.js +12 -5
  38. package/dist/commands/profile/list/index.d.ts +1 -0
  39. package/dist/commands/profile/list/index.js +8 -4
  40. package/dist/commands/profile/me/index.d.ts +1 -0
  41. package/dist/commands/profile/me/index.js +22 -6
  42. package/dist/commands/profile/set/index.d.ts +3 -0
  43. package/dist/commands/profile/set/index.js +12 -6
  44. package/dist/commands/profile/token/index.d.ts +3 -0
  45. package/dist/commands/profile/token/index.js +12 -5
  46. package/dist/commands/profile/wizard/index.d.ts +1 -0
  47. package/dist/commands/profile/wizard/index.js +16 -12
  48. package/dist/commands/profile/workspace/index.d.ts +3 -0
  49. package/dist/commands/profile/workspace/index.js +12 -5
  50. package/dist/commands/profile/workspace/set/index.d.ts +1 -0
  51. package/dist/commands/profile/workspace/set/index.js +2 -4
  52. package/dist/commands/release/create/index.d.ts +4 -1
  53. package/dist/commands/release/create/index.js +12 -14
  54. package/dist/commands/release/delete/index.d.ts +1 -0
  55. package/dist/commands/release/delete/index.js +1 -4
  56. package/dist/commands/release/deploy/index.d.ts +20 -0
  57. package/dist/commands/release/deploy/index.js +137 -0
  58. package/dist/commands/release/edit/index.d.ts +1 -0
  59. package/dist/commands/release/edit/index.js +1 -4
  60. package/dist/commands/release/export/index.d.ts +1 -0
  61. package/dist/commands/release/export/index.js +1 -3
  62. package/dist/commands/release/get/index.d.ts +1 -0
  63. package/dist/commands/release/get/index.js +1 -4
  64. package/dist/commands/release/import/index.d.ts +1 -0
  65. package/dist/commands/release/import/index.js +1 -3
  66. package/dist/commands/release/list/index.d.ts +1 -0
  67. package/dist/commands/release/list/index.js +1 -4
  68. package/dist/commands/release/pull/index.d.ts +2 -3
  69. package/dist/commands/release/pull/index.js +19 -18
  70. package/dist/commands/release/push/index.d.ts +2 -3
  71. package/dist/commands/release/push/index.js +19 -22
  72. package/dist/commands/sandbox/delete/index.d.ts +13 -0
  73. package/dist/commands/sandbox/delete/index.js +71 -0
  74. package/dist/commands/sandbox/env/delete/index.d.ts +15 -0
  75. package/dist/commands/sandbox/env/delete/index.js +89 -0
  76. package/dist/commands/sandbox/env/get/index.d.ts +13 -0
  77. package/dist/commands/sandbox/env/get/index.js +65 -0
  78. package/dist/commands/sandbox/env/get_all/index.d.ts +14 -0
  79. package/dist/commands/sandbox/env/get_all/index.js +78 -0
  80. package/dist/commands/sandbox/env/list/index.d.ts +12 -0
  81. package/dist/commands/sandbox/env/list/index.js +67 -0
  82. package/dist/commands/sandbox/env/set/index.d.ts +14 -0
  83. package/dist/commands/sandbox/env/set/index.js +74 -0
  84. package/dist/commands/sandbox/env/set_all/index.d.ts +14 -0
  85. package/dist/commands/sandbox/env/set_all/index.js +86 -0
  86. package/dist/commands/sandbox/get/index.d.ts +12 -0
  87. package/dist/commands/sandbox/get/index.js +63 -0
  88. package/dist/commands/sandbox/impersonate/index.d.ts +5 -0
  89. package/dist/commands/sandbox/impersonate/index.js +5 -0
  90. package/dist/commands/sandbox/license/get/index.d.ts +14 -0
  91. package/dist/commands/sandbox/license/get/index.js +78 -0
  92. package/dist/commands/sandbox/license/set/index.d.ts +15 -0
  93. package/dist/commands/sandbox/license/set/index.js +95 -0
  94. package/dist/commands/sandbox/pull/index.d.ts +16 -0
  95. package/dist/commands/sandbox/pull/index.js +185 -0
  96. package/dist/commands/sandbox/push/index.d.ts +26 -0
  97. package/dist/commands/sandbox/push/index.js +196 -0
  98. package/dist/commands/sandbox/reset/index.d.ts +13 -0
  99. package/dist/commands/sandbox/reset/index.js +71 -0
  100. package/dist/commands/sandbox/review/index.d.ts +14 -0
  101. package/dist/commands/sandbox/review/index.js +94 -0
  102. package/dist/commands/sandbox/unit_test/list/index.d.ts +14 -0
  103. package/dist/commands/sandbox/unit_test/list/index.js +91 -0
  104. package/dist/commands/sandbox/unit_test/run/index.d.ts +15 -0
  105. package/dist/commands/sandbox/unit_test/run/index.js +79 -0
  106. package/dist/commands/sandbox/unit_test/run_all/index.d.ts +14 -0
  107. package/dist/commands/sandbox/unit_test/run_all/index.js +171 -0
  108. package/dist/commands/sandbox/workflow_test/list/index.d.ts +13 -0
  109. package/dist/commands/sandbox/workflow_test/list/index.js +84 -0
  110. package/dist/commands/sandbox/workflow_test/run/index.d.ts +18 -0
  111. package/dist/commands/sandbox/workflow_test/run/index.js +77 -0
  112. package/dist/commands/sandbox/workflow_test/run_all/index.d.ts +13 -0
  113. package/dist/commands/sandbox/workflow_test/run_all/index.js +157 -0
  114. package/dist/commands/static_host/build/create/index.d.ts +1 -0
  115. package/dist/commands/static_host/build/create/index.js +1 -3
  116. package/dist/commands/static_host/build/get/index.d.ts +1 -0
  117. package/dist/commands/static_host/build/get/index.js +1 -4
  118. package/dist/commands/static_host/build/list/index.d.ts +1 -0
  119. package/dist/commands/static_host/build/list/index.js +1 -4
  120. package/dist/commands/static_host/list/index.d.ts +1 -0
  121. package/dist/commands/static_host/list/index.js +1 -4
  122. package/dist/commands/tenant/backup/create/index.d.ts +1 -0
  123. package/dist/commands/tenant/backup/create/index.js +1 -4
  124. package/dist/commands/tenant/backup/delete/index.d.ts +1 -0
  125. package/dist/commands/tenant/backup/delete/index.js +1 -4
  126. package/dist/commands/tenant/backup/export/index.d.ts +1 -0
  127. package/dist/commands/tenant/backup/export/index.js +1 -3
  128. package/dist/commands/tenant/backup/import/index.d.ts +1 -0
  129. package/dist/commands/tenant/backup/import/index.js +1 -3
  130. package/dist/commands/tenant/backup/list/index.d.ts +1 -0
  131. package/dist/commands/tenant/backup/list/index.js +1 -4
  132. package/dist/commands/tenant/backup/restore/index.d.ts +1 -0
  133. package/dist/commands/tenant/backup/restore/index.js +1 -4
  134. package/dist/commands/tenant/cluster/create/index.d.ts +1 -0
  135. package/dist/commands/tenant/cluster/create/index.js +1 -3
  136. package/dist/commands/tenant/cluster/delete/index.d.ts +1 -0
  137. package/dist/commands/tenant/cluster/delete/index.js +1 -4
  138. package/dist/commands/tenant/cluster/edit/index.d.ts +1 -0
  139. package/dist/commands/tenant/cluster/edit/index.js +1 -4
  140. package/dist/commands/tenant/cluster/get/index.d.ts +1 -0
  141. package/dist/commands/tenant/cluster/get/index.js +1 -4
  142. package/dist/commands/tenant/cluster/license/get/index.d.ts +1 -0
  143. package/dist/commands/tenant/cluster/license/get/index.js +1 -3
  144. package/dist/commands/tenant/cluster/license/set/index.d.ts +1 -0
  145. package/dist/commands/tenant/cluster/license/set/index.js +1 -3
  146. package/dist/commands/tenant/cluster/list/index.d.ts +1 -0
  147. package/dist/commands/tenant/cluster/list/index.js +1 -4
  148. package/dist/commands/tenant/create/index.d.ts +1 -1
  149. package/dist/commands/tenant/create/index.js +1 -8
  150. package/dist/commands/tenant/delete/index.d.ts +1 -0
  151. package/dist/commands/tenant/delete/index.js +1 -4
  152. package/dist/commands/tenant/deploy_platform/index.d.ts +1 -0
  153. package/dist/commands/tenant/deploy_platform/index.js +1 -3
  154. package/dist/commands/tenant/deploy_release/index.d.ts +1 -0
  155. package/dist/commands/tenant/deploy_release/index.js +1 -4
  156. package/dist/commands/tenant/edit/index.d.ts +1 -0
  157. package/dist/commands/tenant/edit/index.js +1 -4
  158. package/dist/commands/tenant/env/delete/index.d.ts +1 -0
  159. package/dist/commands/tenant/env/delete/index.js +1 -4
  160. package/dist/commands/tenant/env/get/index.d.ts +1 -0
  161. package/dist/commands/tenant/env/get/index.js +1 -4
  162. package/dist/commands/tenant/env/get_all/index.d.ts +1 -0
  163. package/dist/commands/tenant/env/get_all/index.js +1 -3
  164. package/dist/commands/tenant/env/list/index.d.ts +1 -0
  165. package/dist/commands/tenant/env/list/index.js +1 -4
  166. package/dist/commands/tenant/env/set/index.d.ts +1 -0
  167. package/dist/commands/tenant/env/set/index.js +1 -4
  168. package/dist/commands/tenant/env/set_all/index.d.ts +1 -0
  169. package/dist/commands/tenant/env/set_all/index.js +1 -3
  170. package/dist/commands/tenant/get/index.d.ts +1 -0
  171. package/dist/commands/tenant/get/index.js +3 -6
  172. package/dist/commands/tenant/impersonate/index.d.ts +1 -0
  173. package/dist/commands/tenant/impersonate/index.js +1 -4
  174. package/dist/commands/tenant/license/get/index.d.ts +1 -0
  175. package/dist/commands/tenant/license/get/index.js +1 -3
  176. package/dist/commands/tenant/license/set/index.d.ts +1 -0
  177. package/dist/commands/tenant/license/set/index.js +1 -3
  178. package/dist/commands/tenant/list/index.d.ts +1 -0
  179. package/dist/commands/tenant/list/index.js +3 -6
  180. package/dist/commands/tenant/pull/index.d.ts +2 -3
  181. package/dist/commands/tenant/pull/index.js +20 -21
  182. package/dist/commands/tenant/push/index.d.ts +2 -22
  183. package/dist/commands/tenant/push/index.js +7 -259
  184. package/dist/commands/tenant/unit_test/list/index.d.ts +16 -0
  185. package/dist/commands/tenant/unit_test/list/index.js +115 -0
  186. package/dist/commands/tenant/unit_test/run/index.d.ts +17 -0
  187. package/dist/commands/tenant/unit_test/run/index.js +103 -0
  188. package/dist/commands/tenant/unit_test/run_all/index.d.ts +16 -0
  189. package/dist/commands/tenant/unit_test/run_all/index.js +190 -0
  190. package/dist/commands/tenant/workflow_test/list/index.d.ts +15 -0
  191. package/dist/commands/tenant/workflow_test/list/index.js +108 -0
  192. package/dist/commands/tenant/workflow_test/run/index.d.ts +20 -0
  193. package/dist/commands/tenant/workflow_test/run/index.js +101 -0
  194. package/dist/commands/tenant/workflow_test/run_all/index.d.ts +15 -0
  195. package/dist/commands/tenant/workflow_test/run_all/index.js +176 -0
  196. package/dist/commands/unit_test/list/index.d.ts +1 -0
  197. package/dist/commands/unit_test/list/index.js +1 -4
  198. package/dist/commands/unit_test/run/index.d.ts +1 -0
  199. package/dist/commands/unit_test/run/index.js +1 -4
  200. package/dist/commands/unit_test/run_all/index.d.ts +1 -0
  201. package/dist/commands/unit_test/run_all/index.js +1 -4
  202. package/dist/commands/update/index.d.ts +1 -0
  203. package/dist/commands/workflow_test/delete/index.d.ts +1 -0
  204. package/dist/commands/workflow_test/delete/index.js +1 -4
  205. package/dist/commands/workflow_test/get/index.d.ts +1 -0
  206. package/dist/commands/workflow_test/get/index.js +1 -4
  207. package/dist/commands/workflow_test/list/index.d.ts +1 -0
  208. package/dist/commands/workflow_test/list/index.js +1 -4
  209. package/dist/commands/workflow_test/run/index.d.ts +1 -0
  210. package/dist/commands/workflow_test/run/index.js +1 -4
  211. package/dist/commands/workflow_test/run_all/index.d.ts +1 -0
  212. package/dist/commands/workflow_test/run_all/index.js +1 -4
  213. package/dist/commands/workspace/create/index.d.ts +1 -0
  214. package/dist/commands/workspace/create/index.js +1 -4
  215. package/dist/commands/workspace/delete/index.d.ts +2 -6
  216. package/dist/commands/workspace/delete/index.js +17 -16
  217. package/dist/commands/workspace/edit/index.d.ts +3 -6
  218. package/dist/commands/workspace/edit/index.js +31 -25
  219. package/dist/commands/workspace/get/index.d.ts +2 -6
  220. package/dist/commands/workspace/get/index.js +23 -25
  221. package/dist/commands/workspace/git/pull/index.d.ts +2 -3
  222. package/dist/commands/workspace/git/pull/index.js +18 -17
  223. package/dist/commands/workspace/list/index.d.ts +2 -0
  224. package/dist/commands/workspace/list/index.js +15 -11
  225. package/dist/commands/workspace/pull/index.d.ts +2 -3
  226. package/dist/commands/workspace/pull/index.js +21 -24
  227. package/dist/commands/workspace/push/index.d.ts +7 -16
  228. package/dist/commands/workspace/push/index.js +85 -674
  229. package/dist/help.d.ts +2 -1
  230. package/dist/help.js +39 -1
  231. package/dist/utils/multidoc-push.d.ts +63 -0
  232. package/dist/utils/multidoc-push.js +690 -0
  233. package/dist/utils/reference-checker.d.ts +57 -0
  234. package/dist/utils/reference-checker.js +232 -0
  235. package/oclif.manifest.json +5631 -2297
  236. package/package.json +17 -2
@@ -1,35 +1,35 @@
1
- import { Args, Flags } from '@oclif/core';
1
+ import { Flags } from '@oclif/core';
2
2
  import * as yaml from 'js-yaml';
3
3
  import * as fs from 'node:fs';
4
- import * as os from 'node:os';
5
4
  import * as path from 'node:path';
6
5
  import snakeCase from 'lodash.snakecase';
7
6
  import BaseCommand from '../../../base-command.js';
8
7
  import { buildApiGroupFolderResolver, parseDocument } from '../../../utils/document-parser.js';
9
8
  export default class Pull extends BaseCommand {
10
- static args = {
11
- directory: Args.string({
12
- description: 'Output directory for pulled documents',
13
- required: true,
14
- }),
15
- };
16
9
  static description = 'Pull a tenant multidoc from the Xano Metadata API and split into individual files';
17
10
  static examples = [
18
- `$ xano tenant pull ./my-tenant -t my-tenant
11
+ `$ xano tenant pull -t my-tenant
12
+ Pulled 42 documents from tenant my-tenant to current directory
13
+ `,
14
+ `$ xano tenant pull -d ./my-tenant -t my-tenant
19
15
  Pulled 42 documents from tenant my-tenant to ./my-tenant
20
16
  `,
21
- `$ xano tenant pull ./output -t my-tenant -w 40
17
+ `$ xano tenant pull -d ./output -t my-tenant -w 40
22
18
  Pulled 15 documents from tenant my-tenant to ./output
23
19
  `,
24
- `$ xano tenant pull ./backup -t my-tenant --profile production --env --records
25
- Pulled 58 documents from tenant my-tenant to ./backup
26
- `,
27
- `$ xano tenant pull ./my-tenant -t my-tenant --draft
28
- Pulled 42 documents from tenant my-tenant to ./my-tenant
20
+ `$ xano tenant pull -t my-tenant --profile production --env --records
21
+ Pulled 58 documents from tenant my-tenant
29
22
  `,
23
+ `$ xano tenant pull -t my-tenant --draft`,
30
24
  ];
31
25
  static flags = {
32
26
  ...BaseCommand.baseFlags,
27
+ directory: Flags.string({
28
+ char: 'd',
29
+ default: '.',
30
+ description: 'Output directory for pulled documents (defaults to current directory)',
31
+ required: false,
32
+ }),
33
33
  draft: Flags.boolean({
34
34
  default: false,
35
35
  description: 'Include draft versions',
@@ -57,7 +57,7 @@ Pulled 42 documents from tenant my-tenant to ./my-tenant
57
57
  }),
58
58
  };
59
59
  async run() {
60
- const { args, flags } = await this.parse(Pull);
60
+ const { flags } = await this.parse(Pull);
61
61
  // Get profile name (default or from flag/env)
62
62
  const profileName = flags.profile || this.getDefaultProfile();
63
63
  // Load credentials
@@ -85,7 +85,7 @@ Pulled 42 documents from tenant my-tenant to ./my-tenant
85
85
  }
86
86
  else {
87
87
  this.error(`Workspace ID is required. Either:\n` +
88
- ` 1. Provide it as a flag: xano tenant pull <directory> -t <tenant_name> -w <workspace_id>\n` +
88
+ ` 1. Provide it as a flag: xano tenant pull -t <tenant_name> -w <workspace_id>\n` +
89
89
  ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
90
90
  }
91
91
  const tenantName = flags.tenant;
@@ -141,7 +141,7 @@ Pulled 42 documents from tenant my-tenant to ./my-tenant
141
141
  return;
142
142
  }
143
143
  // Resolve the output directory
144
- const outputDir = path.resolve(args.directory);
144
+ const outputDir = path.resolve(flags.directory);
145
145
  // Create the output directory if it doesn't exist
146
146
  fs.mkdirSync(outputDir, { recursive: true });
147
147
  // Resolve api_group names to unique folder names, disambiguating collisions
@@ -247,11 +247,10 @@ Pulled 42 documents from tenant my-tenant to ./my-tenant
247
247
  fs.writeFileSync(filePath, doc.content, 'utf8');
248
248
  writtenCount++;
249
249
  }
250
- this.log(`Pulled ${writtenCount} documents from tenant ${tenantName} to ${args.directory}`);
250
+ this.log(`Pulled ${writtenCount} documents from tenant ${tenantName} to ${flags.directory}`);
251
251
  }
252
252
  loadCredentials() {
253
- const configDir = path.join(os.homedir(), '.xano');
254
- const credentialsPath = path.join(configDir, 'credentials.yaml');
253
+ const credentialsPath = this.getCredentialsPath();
255
254
  // Check if credentials file exists
256
255
  if (!fs.existsSync(credentialsPath)) {
257
256
  this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
@@ -1,25 +1,5 @@
1
- import BaseCommand from '../../../base-command.js';
2
- export default class Push extends BaseCommand {
3
- static args: {
4
- directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
- };
1
+ import { Command } from '@oclif/core';
2
+ export default class Push extends Command {
6
3
  static description: string;
7
- static examples: string[];
8
- static flags: {
9
- env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
- records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
- tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
- transaction: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
- truncate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
- workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
- profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
- verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
- };
18
4
  run(): Promise<void>;
19
- /**
20
- * Recursively collect all .xs files from a directory, sorted by
21
- * type subdirectory name then filename for deterministic ordering.
22
- */
23
- private collectFiles;
24
- private loadCredentials;
25
5
  }
@@ -1,262 +1,10 @@
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
- import { findFilesWithGuid } from '../../../utils/document-parser.js';
8
- export default class Push extends BaseCommand {
9
- static args = {
10
- directory: Args.string({
11
- description: 'Directory containing documents to push (as produced by tenant pull or workspace pull)',
12
- required: true,
13
- }),
14
- };
15
- static description = 'Push local documents to a tenant via the Xano Metadata API multidoc endpoint';
16
- static examples = [
17
- `$ xano tenant push ./my-workspace -t my-tenant
18
- Pushed 42 documents to tenant my-tenant from ./my-workspace
19
- `,
20
- `$ xano tenant push ./output -t my-tenant -w 40
21
- Pushed 15 documents to tenant my-tenant from ./output
22
- `,
23
- `$ xano tenant push ./backup -t my-tenant --profile production
24
- Pushed 58 documents to tenant my-tenant from ./backup
25
- `,
26
- `$ xano tenant push ./my-workspace -t my-tenant --records
27
- Include table records in import
28
- `,
29
- `$ xano tenant push ./my-workspace -t my-tenant --env
30
- Include environment variables in import
31
- `,
32
- `$ xano tenant push ./my-workspace -t my-tenant --truncate
33
- Truncate all table records before importing
34
- `,
35
- ];
36
- static flags = {
37
- ...BaseCommand.baseFlags,
38
- env: Flags.boolean({
39
- default: false,
40
- description: 'Include environment variables in import',
41
- required: false,
42
- }),
43
- records: Flags.boolean({
44
- default: false,
45
- description: 'Include records in import',
46
- required: false,
47
- }),
48
- tenant: Flags.string({
49
- char: 't',
50
- description: 'Tenant name to push to',
51
- required: true,
52
- }),
53
- transaction: Flags.boolean({
54
- allowNo: true,
55
- default: true,
56
- description: 'Wrap import in a database transaction (use --no-transaction for debugging purposes)',
57
- required: false,
58
- }),
59
- truncate: Flags.boolean({
60
- default: false,
61
- description: 'Truncate all table records before importing',
62
- required: false,
63
- }),
64
- workspace: Flags.string({
65
- char: 'w',
66
- description: 'Workspace ID (optional if set in profile)',
67
- required: false,
68
- }),
69
- };
1
+ import { Command } from '@oclif/core';
2
+ export default class Push extends Command {
3
+ static description = 'Direct tenant push is not supported. Deploy through a release or use the sandbox (xano sandbox push).';
70
4
  async run() {
71
- const { args, flags } = await this.parse(Push);
72
- // Get profile name (default or from flag/env)
73
- const profileName = flags.profile || this.getDefaultProfile();
74
- // Load credentials
75
- const credentials = this.loadCredentials();
76
- // Get the profile configuration
77
- if (!(profileName in credentials.profiles)) {
78
- this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
79
- `Create a profile using 'xano profile:create'`);
80
- }
81
- const profile = credentials.profiles[profileName];
82
- // Validate required fields
83
- if (!profile.instance_origin) {
84
- this.error(`Profile '${profileName}' is missing instance_origin`);
85
- }
86
- if (!profile.access_token) {
87
- this.error(`Profile '${profileName}' is missing access_token`);
88
- }
89
- // Determine workspace_id from flag or profile
90
- let workspaceId;
91
- if (flags.workspace) {
92
- workspaceId = flags.workspace;
93
- }
94
- else if (profile.workspace) {
95
- workspaceId = profile.workspace;
96
- }
97
- else {
98
- this.error(`Workspace ID is required. Either:\n` +
99
- ` 1. Provide it as a flag: xano tenant push <directory> -t <tenant_name> -w <workspace_id>\n` +
100
- ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
101
- }
102
- const tenantName = flags.tenant;
103
- // Fetch tenant details and verify it's ephemeral
104
- const tenantApiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
105
- try {
106
- const tenantResponse = await this.verboseFetch(tenantApiUrl, {
107
- headers: {
108
- accept: 'application/json',
109
- Authorization: `Bearer ${profile.access_token}`,
110
- },
111
- method: 'GET',
112
- }, flags.verbose, profile.access_token);
113
- if (!tenantResponse.ok) {
114
- const errorText = await tenantResponse.text();
115
- this.error(`Failed to fetch tenant '${tenantName}' (${tenantResponse.status}): ${errorText}`);
116
- }
117
- const tenantData = (await tenantResponse.json());
118
- if (!tenantData.ephemeral) {
119
- this.error(`Tenant '${tenantName}' is not ephemeral. Push is only allowed for ephemeral tenants.\n` +
120
- `Create an ephemeral tenant with: xano tenant create "name" --ephemeral`);
121
- }
122
- }
123
- catch (error) {
124
- if (error instanceof Error && error.message.includes('is not ephemeral')) {
125
- throw error;
126
- }
127
- if (error instanceof Error && error.message.includes('Failed to fetch tenant')) {
128
- throw error;
129
- }
130
- if (error instanceof Error) {
131
- this.error(`Failed to verify tenant: ${error.message}`);
132
- }
133
- else {
134
- this.error(`Failed to verify tenant: ${String(error)}`);
135
- }
136
- }
137
- // Resolve the input directory
138
- const inputDir = path.resolve(args.directory);
139
- if (!fs.existsSync(inputDir)) {
140
- this.error(`Directory not found: ${inputDir}`);
141
- }
142
- if (!fs.statSync(inputDir).isDirectory()) {
143
- this.error(`Not a directory: ${inputDir}`);
144
- }
145
- // Collect all .xs files from the directory tree
146
- const files = this.collectFiles(inputDir);
147
- if (files.length === 0) {
148
- this.error(`No .xs files found in ${args.directory}`);
149
- }
150
- // Read each file and track file path alongside content
151
- const documentEntries = [];
152
- for (const filePath of files) {
153
- const content = fs.readFileSync(filePath, 'utf8').trim();
154
- if (content) {
155
- documentEntries.push({ content, filePath });
156
- }
157
- }
158
- if (documentEntries.length === 0) {
159
- this.error(`All .xs files in ${args.directory} are empty`);
160
- }
161
- const multidoc = documentEntries.map((d) => d.content).join('\n---\n');
162
- // Construct the API URL
163
- const queryParams = new URLSearchParams({
164
- env: flags.env.toString(),
165
- records: flags.records.toString(),
166
- transaction: flags.transaction.toString(),
167
- truncate: flags.truncate.toString(),
168
- });
169
- const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/multidoc?${queryParams.toString()}`;
170
- // POST the multidoc to the API
171
- const requestHeaders = {
172
- accept: 'application/json',
173
- Authorization: `Bearer ${profile.access_token}`,
174
- 'Content-Type': 'text/x-xanoscript',
175
- };
176
- const startTime = Date.now();
177
- try {
178
- const response = await this.verboseFetch(apiUrl, {
179
- body: multidoc,
180
- headers: requestHeaders,
181
- method: 'POST',
182
- }, flags.verbose, profile.access_token);
183
- if (!response.ok) {
184
- const errorText = await response.text();
185
- let errorMessage = `Push failed (${response.status})`;
186
- try {
187
- const errorJson = JSON.parse(errorText);
188
- errorMessage += `: ${errorJson.message}`;
189
- if (errorJson.payload?.param) {
190
- errorMessage += `\n Parameter: ${errorJson.payload.param}`;
191
- }
192
- }
193
- catch {
194
- errorMessage += `\n${errorText}`;
195
- }
196
- // Surface local files involved in duplicate GUID errors
197
- const guidMatch = errorMessage.match(/Duplicate \w+ guid: (\S+)/);
198
- if (guidMatch) {
199
- const dupeFiles = findFilesWithGuid(documentEntries, guidMatch[1]);
200
- if (dupeFiles.length > 0) {
201
- const relPaths = dupeFiles.map((f) => path.relative(inputDir, f));
202
- errorMessage += `\n Local files with this GUID:\n${relPaths.map((f) => ` ${f}`).join('\n')}`;
203
- }
204
- }
205
- this.error(errorMessage);
206
- }
207
- // Parse the response (suppress raw output; only show in verbose mode)
208
- const responseText = await response.text();
209
- if (responseText && responseText !== 'null' && flags.verbose) {
210
- this.log(responseText);
211
- }
212
- }
213
- catch (error) {
214
- if (error instanceof Error) {
215
- this.error(`Failed to push multidoc: ${error.message}`);
216
- }
217
- else {
218
- this.error(`Failed to push multidoc: ${String(error)}`);
219
- }
220
- }
221
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
222
- this.log(`Pushed ${documentEntries.length} documents to tenant ${tenantName} from ${args.directory} in ${elapsed}s`);
223
- }
224
- /**
225
- * Recursively collect all .xs files from a directory, sorted by
226
- * type subdirectory name then filename for deterministic ordering.
227
- */
228
- collectFiles(dir) {
229
- const files = [];
230
- const entries = fs.readdirSync(dir, { withFileTypes: true });
231
- for (const entry of entries) {
232
- const fullPath = path.join(dir, entry.name);
233
- if (entry.isDirectory()) {
234
- files.push(...this.collectFiles(fullPath));
235
- }
236
- else if (entry.isFile() && entry.name.endsWith('.xs')) {
237
- files.push(fullPath);
238
- }
239
- }
240
- return files.sort();
241
- }
242
- loadCredentials() {
243
- const configDir = path.join(os.homedir(), '.xano');
244
- const credentialsPath = path.join(configDir, 'credentials.yaml');
245
- // Check if credentials file exists
246
- if (!fs.existsSync(credentialsPath)) {
247
- this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
248
- }
249
- // Read credentials file
250
- try {
251
- const fileContent = fs.readFileSync(credentialsPath, 'utf8');
252
- const parsed = yaml.load(fileContent);
253
- if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
254
- this.error('Credentials file has invalid format.');
255
- }
256
- return parsed;
257
- }
258
- catch (error) {
259
- this.error(`Failed to parse credentials file: ${error}`);
260
- }
5
+ this.error('Direct tenant push is not supported.\n' +
6
+ 'To deploy changes, use one of the following:\n' +
7
+ ' - Create a release through the standard deployment workflow\n' +
8
+ ' - Use the sandbox: xano sandbox push <directory>');
261
9
  }
262
10
  }
@@ -0,0 +1,16 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class TenantUnitTestList extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'obj-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ }
@@ -0,0 +1,115 @@
1
+ import { Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../../base-command.js';
3
+ export default class TenantUnitTestList extends BaseCommand {
4
+ static description = 'List all unit tests for a tenant';
5
+ static examples = [
6
+ `$ xano tenant unit-test list -t my-tenant
7
+ Unit tests for tenant my-tenant:
8
+ - my-test (ID: abc-123) [function: math]
9
+ `,
10
+ `$ xano tenant unit-test list -t my-tenant -w 5 -o json`,
11
+ ];
12
+ static flags = {
13
+ ...BaseCommand.baseFlags,
14
+ branch: Flags.string({
15
+ char: 'b',
16
+ description: 'Filter by branch name',
17
+ required: false,
18
+ }),
19
+ 'obj-type': Flags.string({
20
+ description: 'Filter by object type',
21
+ options: ['function', 'query', 'middleware'],
22
+ required: false,
23
+ }),
24
+ output: Flags.string({
25
+ char: 'o',
26
+ default: 'summary',
27
+ description: 'Output format',
28
+ options: ['summary', 'json'],
29
+ required: false,
30
+ }),
31
+ tenant: Flags.string({
32
+ char: 't',
33
+ description: 'Tenant name',
34
+ required: true,
35
+ }),
36
+ workspace: Flags.string({
37
+ char: 'w',
38
+ description: 'Workspace ID (uses profile workspace if not provided)',
39
+ required: false,
40
+ }),
41
+ };
42
+ async run() {
43
+ const { flags } = await this.parse(TenantUnitTestList);
44
+ const profileName = flags.profile || this.getDefaultProfile();
45
+ const credentials = this.loadCredentialsFile();
46
+ if (!credentials || !(profileName in credentials.profiles)) {
47
+ this.error(`Profile '${profileName}' not found.\nCreate 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 tenantName = encodeURIComponent(flags.tenant);
61
+ const params = new URLSearchParams();
62
+ params.set('per_page', '10000');
63
+ if (flags.branch)
64
+ params.set('branch', flags.branch);
65
+ if (flags['obj-type'])
66
+ params.set('obj_type', flags['obj-type']);
67
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/unit_test?${params}`;
68
+ try {
69
+ const response = await this.verboseFetch(apiUrl, {
70
+ headers: {
71
+ accept: 'application/json',
72
+ Authorization: `Bearer ${profile.access_token}`,
73
+ },
74
+ method: 'GET',
75
+ }, flags.verbose, profile.access_token);
76
+ if (!response.ok) {
77
+ const errorText = await response.text();
78
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
79
+ }
80
+ const data = (await response.json());
81
+ let tests;
82
+ if (Array.isArray(data)) {
83
+ tests = data;
84
+ }
85
+ else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
86
+ tests = data.items;
87
+ }
88
+ else {
89
+ this.error('Unexpected API response format');
90
+ }
91
+ if (flags.output === 'json') {
92
+ this.log(JSON.stringify(tests, null, 2));
93
+ }
94
+ else {
95
+ if (tests.length === 0) {
96
+ this.log('No unit tests found');
97
+ }
98
+ else {
99
+ this.log(`Unit tests for tenant ${flags.tenant}:`);
100
+ for (const test of tests) {
101
+ this.log(` - ${test.name} (ID: ${test.id}) [${test.obj_type}: ${test.obj_name}]`);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ catch (error) {
107
+ if (error instanceof Error) {
108
+ this.error(`Failed to list unit tests: ${error.message}`);
109
+ }
110
+ else {
111
+ this.error(`Failed to list unit tests: ${String(error)}`);
112
+ }
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,17 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class TenantUnitTestRun extends BaseCommand {
3
+ static args: {
4
+ unit_test_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,103 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../../base-command.js';
3
+ export default class TenantUnitTestRun extends BaseCommand {
4
+ static args = {
5
+ unit_test_id: Args.string({
6
+ description: 'ID of the unit test to run',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Run a unit test for a tenant';
11
+ static examples = [
12
+ `$ xano tenant unit-test run abc-123 -t my-tenant
13
+ Running unit test abc-123...
14
+ Result: PASS
15
+ `,
16
+ `$ xano tenant unit-test run abc-123 -t my-tenant -o json`,
17
+ ];
18
+ static flags = {
19
+ ...BaseCommand.baseFlags,
20
+ output: Flags.string({
21
+ char: 'o',
22
+ default: 'summary',
23
+ description: 'Output format',
24
+ options: ['summary', 'json'],
25
+ required: false,
26
+ }),
27
+ tenant: Flags.string({
28
+ char: 't',
29
+ description: 'Tenant name',
30
+ required: true,
31
+ }),
32
+ workspace: Flags.string({
33
+ char: 'w',
34
+ description: 'Workspace ID (uses profile workspace if not provided)',
35
+ required: false,
36
+ }),
37
+ };
38
+ async run() {
39
+ const { args, flags } = await this.parse(TenantUnitTestRun);
40
+ const profileName = flags.profile || this.getDefaultProfile();
41
+ const credentials = this.loadCredentialsFile();
42
+ if (!credentials || !(profileName in credentials.profiles)) {
43
+ this.error(`Profile '${profileName}' not found.\nCreate a profile using 'xano profile create'`);
44
+ }
45
+ const profile = credentials.profiles[profileName];
46
+ if (!profile.instance_origin) {
47
+ this.error(`Profile '${profileName}' is missing instance_origin`);
48
+ }
49
+ if (!profile.access_token) {
50
+ this.error(`Profile '${profileName}' is missing access_token`);
51
+ }
52
+ const workspaceId = flags.workspace || profile.workspace;
53
+ if (!workspaceId) {
54
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
55
+ }
56
+ const tenantName = encodeURIComponent(flags.tenant);
57
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/unit_test/${encodeURIComponent(args.unit_test_id)}/run`;
58
+ try {
59
+ if (flags.output === 'summary') {
60
+ this.log(`Running unit test ${args.unit_test_id}...`);
61
+ }
62
+ const response = await this.verboseFetch(apiUrl, {
63
+ headers: {
64
+ accept: 'application/json',
65
+ Authorization: `Bearer ${profile.access_token}`,
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ method: 'POST',
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 result = (await response.json());
75
+ if (flags.output === 'json') {
76
+ this.log(JSON.stringify(result, null, 2));
77
+ }
78
+ else {
79
+ if (result.status === 'ok') {
80
+ this.log('Result: PASS');
81
+ }
82
+ else {
83
+ this.log('Result: FAIL');
84
+ const failedExpects = result.results?.filter((r) => r.status === 'fail') ?? [];
85
+ for (const expect of failedExpects) {
86
+ if (expect.message) {
87
+ this.log(` Error: ${expect.message}`);
88
+ }
89
+ }
90
+ this.exit(1);
91
+ }
92
+ }
93
+ }
94
+ catch (error) {
95
+ if (error instanceof Error) {
96
+ this.error(`Failed to run unit test: ${error.message}`);
97
+ }
98
+ else {
99
+ this.error(`Failed to run unit test: ${String(error)}`);
100
+ }
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,16 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class TenantUnitTestRunAll extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'obj-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ }