@xano/cli 0.0.94 → 0.0.95-beta.2

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 (76) hide show
  1. package/README.md +19 -0
  2. package/dist/commands/ephemeral/access/index.d.ts +15 -0
  3. package/dist/commands/ephemeral/access/index.js +78 -0
  4. package/dist/commands/ephemeral/create/index.d.ts +17 -0
  5. package/dist/commands/ephemeral/create/index.js +102 -0
  6. package/dist/commands/ephemeral/delete/index.d.ts +16 -0
  7. package/dist/commands/ephemeral/delete/index.js +99 -0
  8. package/dist/commands/ephemeral/env/delete/index.d.ts +17 -0
  9. package/dist/commands/ephemeral/env/delete/index.js +105 -0
  10. package/dist/commands/ephemeral/env/get/index.d.ts +15 -0
  11. package/dist/commands/ephemeral/env/get/index.js +81 -0
  12. package/dist/commands/ephemeral/env/get_all/index.d.ts +16 -0
  13. package/dist/commands/ephemeral/env/get_all/index.js +94 -0
  14. package/dist/commands/ephemeral/env/list/index.d.ts +14 -0
  15. package/dist/commands/ephemeral/env/list/index.js +83 -0
  16. package/dist/commands/ephemeral/env/set/index.d.ts +16 -0
  17. package/dist/commands/ephemeral/env/set/index.js +90 -0
  18. package/dist/commands/ephemeral/env/set_all/index.d.ts +16 -0
  19. package/dist/commands/ephemeral/env/set_all/index.js +102 -0
  20. package/dist/commands/ephemeral/get/index.d.ts +14 -0
  21. package/dist/commands/ephemeral/get/index.js +102 -0
  22. package/dist/commands/ephemeral/impersonate/index.d.ts +16 -0
  23. package/dist/commands/ephemeral/impersonate/index.js +110 -0
  24. package/dist/commands/ephemeral/license/get/index.d.ts +16 -0
  25. package/dist/commands/ephemeral/license/get/index.js +94 -0
  26. package/dist/commands/ephemeral/license/set/index.d.ts +17 -0
  27. package/dist/commands/ephemeral/license/set/index.js +111 -0
  28. package/dist/commands/ephemeral/list/index.d.ts +15 -0
  29. package/dist/commands/ephemeral/list/index.js +109 -0
  30. package/dist/commands/ephemeral/pull/index.d.ts +18 -0
  31. package/dist/commands/ephemeral/pull/index.js +197 -0
  32. package/dist/commands/ephemeral/push/index.d.ts +19 -0
  33. package/dist/commands/ephemeral/push/index.js +158 -0
  34. package/dist/commands/ephemeral/shared/index.d.ts +15 -0
  35. package/dist/commands/ephemeral/shared/index.js +108 -0
  36. package/dist/commands/ephemeral/unit_test/list/index.d.ts +14 -0
  37. package/dist/commands/ephemeral/unit_test/list/index.js +105 -0
  38. package/dist/commands/ephemeral/unit_test/run/index.d.ts +15 -0
  39. package/dist/commands/ephemeral/unit_test/run/index.js +93 -0
  40. package/dist/commands/ephemeral/unit_test/run_all/index.d.ts +14 -0
  41. package/dist/commands/ephemeral/unit_test/run_all/index.js +183 -0
  42. package/dist/commands/ephemeral/workflow_test/delete/index.d.ts +18 -0
  43. package/dist/commands/ephemeral/workflow_test/delete/index.js +75 -0
  44. package/dist/commands/ephemeral/workflow_test/get/index.d.ts +18 -0
  45. package/dist/commands/ephemeral/workflow_test/get/index.js +77 -0
  46. package/dist/commands/ephemeral/workflow_test/list/index.d.ts +13 -0
  47. package/dist/commands/ephemeral/workflow_test/list/index.js +98 -0
  48. package/dist/commands/ephemeral/workflow_test/run/index.d.ts +18 -0
  49. package/dist/commands/ephemeral/workflow_test/run/index.js +91 -0
  50. package/dist/commands/ephemeral/workflow_test/run_all/index.d.ts +13 -0
  51. package/dist/commands/ephemeral/workflow_test/run_all/index.js +169 -0
  52. package/dist/commands/release/deploy/index.d.ts +17 -0
  53. package/dist/commands/release/deploy/index.js +107 -0
  54. package/dist/commands/tenant/create/index.d.ts +0 -1
  55. package/dist/commands/tenant/create/index.js +0 -5
  56. package/dist/commands/tenant/push/index.js +1 -1
  57. package/dist/commands/tenant/unit_test/list/index.d.ts +15 -0
  58. package/dist/commands/tenant/unit_test/list/index.js +140 -0
  59. package/dist/commands/tenant/unit_test/run/index.d.ts +16 -0
  60. package/dist/commands/tenant/unit_test/run/index.js +128 -0
  61. package/dist/commands/tenant/unit_test/run_all/index.d.ts +15 -0
  62. package/dist/commands/tenant/unit_test/run_all/index.js +215 -0
  63. package/dist/commands/tenant/workflow_test/delete/index.d.ts +19 -0
  64. package/dist/commands/tenant/workflow_test/delete/index.js +110 -0
  65. package/dist/commands/tenant/workflow_test/get/index.d.ts +19 -0
  66. package/dist/commands/tenant/workflow_test/get/index.js +112 -0
  67. package/dist/commands/tenant/workflow_test/list/index.d.ts +14 -0
  68. package/dist/commands/tenant/workflow_test/list/index.js +133 -0
  69. package/dist/commands/tenant/workflow_test/run/index.d.ts +19 -0
  70. package/dist/commands/tenant/workflow_test/run/index.js +126 -0
  71. package/dist/commands/tenant/workflow_test/run_all/index.d.ts +14 -0
  72. package/dist/commands/tenant/workflow_test/run_all/index.js +201 -0
  73. package/dist/help.d.ts +2 -1
  74. package/dist/help.js +39 -1
  75. package/oclif.manifest.json +5193 -2303
  76. package/package.json +16 -1
@@ -0,0 +1,16 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class EphemeralLicenseGet extends BaseCommand {
3
+ static args: {
4
+ tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ view: import("@oclif/core/interfaces").BooleanFlag<boolean>;
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,94 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import BaseCommand from '../../../../base-command.js';
5
+ export default class EphemeralLicenseGet extends BaseCommand {
6
+ static args = {
7
+ tenant_name: Args.string({
8
+ description: 'Ephemeral tenant name',
9
+ required: true,
10
+ }),
11
+ };
12
+ static description = 'Get the license for an ephemeral tenant';
13
+ static examples = [
14
+ `$ xano ephemeral license get my-tenant
15
+ License saved to license_my-tenant.yaml
16
+ `,
17
+ `$ xano ephemeral license get my-tenant --file ./my-license.yaml`,
18
+ `$ xano ephemeral license get my-tenant --view`,
19
+ `$ xano ephemeral license get my-tenant -o json`,
20
+ ];
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ file: Flags.string({
24
+ char: 'f',
25
+ description: 'Output file path (default: license_<tenant_name>.yaml)',
26
+ required: false,
27
+ }),
28
+ output: Flags.string({
29
+ char: 'o',
30
+ default: 'summary',
31
+ description: 'Output format',
32
+ options: ['summary', 'json'],
33
+ required: false,
34
+ }),
35
+ view: Flags.boolean({
36
+ default: false,
37
+ description: 'Print license to stdout instead of saving to file',
38
+ required: false,
39
+ }),
40
+ };
41
+ async run() {
42
+ const { args, flags } = await this.parse(EphemeralLicenseGet);
43
+ const profileName = flags.profile || this.getDefaultProfile();
44
+ const credentials = this.loadCredentialsFile();
45
+ if (!credentials || !(profileName in credentials.profiles)) {
46
+ this.error(`Profile '${profileName}' not found.\n` + `Create a profile using 'xano profile create'`);
47
+ }
48
+ const profile = credentials.profiles[profileName];
49
+ if (!profile.instance_origin) {
50
+ this.error(`Profile '${profileName}' is missing instance_origin`);
51
+ }
52
+ if (!profile.access_token) {
53
+ this.error(`Profile '${profileName}' is missing access_token`);
54
+ }
55
+ const tenantName = args.tenant_name;
56
+ const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${tenantName}/license`;
57
+ try {
58
+ const response = await this.verboseFetch(apiUrl, {
59
+ headers: {
60
+ accept: 'application/json',
61
+ Authorization: `Bearer ${profile.access_token}`,
62
+ },
63
+ method: 'GET',
64
+ }, flags.verbose, profile.access_token);
65
+ if (!response.ok) {
66
+ const errorText = await response.text();
67
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
68
+ }
69
+ const license = await response.json();
70
+ const licenseContent = typeof license === 'string' ? license : JSON.stringify(license, null, 2);
71
+ if (flags.view || flags.output === 'json') {
72
+ if (flags.output === 'json') {
73
+ this.log(JSON.stringify(license, null, 2));
74
+ }
75
+ else {
76
+ this.log(licenseContent);
77
+ }
78
+ }
79
+ else {
80
+ const filePath = path.resolve(flags.file || `license_${tenantName}.yaml`);
81
+ fs.writeFileSync(filePath, licenseContent, 'utf8');
82
+ this.log(`License saved to ${filePath}`);
83
+ }
84
+ }
85
+ catch (error) {
86
+ if (error instanceof Error) {
87
+ this.error(`Failed to get ephemeral tenant license: ${error.message}`);
88
+ }
89
+ else {
90
+ this.error(`Failed to get ephemeral tenant license: ${String(error)}`);
91
+ }
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,17 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class EphemeralLicenseSet extends BaseCommand {
3
+ static args: {
4
+ tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ clean: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ value: 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,111 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import BaseCommand from '../../../../base-command.js';
5
+ export default class EphemeralLicenseSet extends BaseCommand {
6
+ static args = {
7
+ tenant_name: Args.string({
8
+ description: 'Ephemeral tenant name',
9
+ required: true,
10
+ }),
11
+ };
12
+ static description = 'Set/update the license for an ephemeral tenant';
13
+ static examples = [
14
+ `$ xano ephemeral license set my-tenant
15
+ Reads from license_my-tenant.yaml
16
+ `,
17
+ `$ xano ephemeral license set my-tenant --file ./license.yaml`,
18
+ `$ xano ephemeral license set my-tenant --value 'key: value'`,
19
+ `$ xano ephemeral license set my-tenant -o json`,
20
+ ];
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ clean: Flags.boolean({
24
+ default: false,
25
+ description: 'Remove the source file after successful upload',
26
+ exclusive: ['value'],
27
+ required: false,
28
+ }),
29
+ file: Flags.string({
30
+ char: 'f',
31
+ description: 'Path to license file (default: license_<tenant_name>.yaml)',
32
+ exclusive: ['value'],
33
+ required: false,
34
+ }),
35
+ output: Flags.string({
36
+ char: 'o',
37
+ default: 'summary',
38
+ description: 'Output format',
39
+ options: ['summary', 'json'],
40
+ required: false,
41
+ }),
42
+ value: Flags.string({
43
+ description: 'Inline license value',
44
+ exclusive: ['file', 'clean'],
45
+ required: false,
46
+ }),
47
+ };
48
+ async run() {
49
+ const { args, flags } = await this.parse(EphemeralLicenseSet);
50
+ const tenantName = args.tenant_name;
51
+ let licenseValue;
52
+ let sourceFilePath;
53
+ if (flags.value) {
54
+ licenseValue = flags.value;
55
+ }
56
+ else {
57
+ sourceFilePath = path.resolve(flags.file || `license_${tenantName}.yaml`);
58
+ if (!fs.existsSync(sourceFilePath)) {
59
+ this.error(`File not found: ${sourceFilePath}`);
60
+ }
61
+ licenseValue = fs.readFileSync(sourceFilePath, 'utf8');
62
+ }
63
+ const profileName = flags.profile || this.getDefaultProfile();
64
+ const credentials = this.loadCredentialsFile();
65
+ if (!credentials || !(profileName in credentials.profiles)) {
66
+ this.error(`Profile '${profileName}' not found.\n` + `Create a profile using 'xano profile create'`);
67
+ }
68
+ const profile = credentials.profiles[profileName];
69
+ if (!profile.instance_origin) {
70
+ this.error(`Profile '${profileName}' is missing instance_origin`);
71
+ }
72
+ if (!profile.access_token) {
73
+ this.error(`Profile '${profileName}' is missing access_token`);
74
+ }
75
+ const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${tenantName}/license`;
76
+ try {
77
+ const response = await this.verboseFetch(apiUrl, {
78
+ body: JSON.stringify({ value: licenseValue }),
79
+ headers: {
80
+ accept: 'application/json',
81
+ Authorization: `Bearer ${profile.access_token}`,
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ method: 'POST',
85
+ }, flags.verbose, profile.access_token);
86
+ if (!response.ok) {
87
+ const errorText = await response.text();
88
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
89
+ }
90
+ const result = await response.json();
91
+ if (flags.output === 'json') {
92
+ this.log(JSON.stringify(result, null, 2));
93
+ }
94
+ else {
95
+ this.log(`Ephemeral tenant license updated successfully for ${tenantName}`);
96
+ }
97
+ if (flags.clean && sourceFilePath && fs.existsSync(sourceFilePath)) {
98
+ fs.unlinkSync(sourceFilePath);
99
+ this.log(`Removed ${sourceFilePath}`);
100
+ }
101
+ }
102
+ catch (error) {
103
+ if (error instanceof Error) {
104
+ this.error(`Failed to set ephemeral tenant license: ${error.message}`);
105
+ }
106
+ else {
107
+ this.error(`Failed to set ephemeral tenant license: ${String(error)}`);
108
+ }
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,15 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class EphemeralList extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ order: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
9
+ per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ sort: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,109 @@
1
+ import { Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class EphemeralList extends BaseCommand {
4
+ static description = 'List ephemeral tenants';
5
+ static examples = [
6
+ `$ xano ephemeral list
7
+ Ephemeral tenants:
8
+ - My Tenant (my-tenant) [ok]
9
+ - CI Tenant (ci-tenant) [ok]
10
+ `,
11
+ `$ xano ephemeral list -o json`,
12
+ ];
13
+ static flags = {
14
+ ...BaseCommand.baseFlags,
15
+ order: Flags.string({
16
+ default: 'asc',
17
+ description: 'Sort order',
18
+ options: ['asc', 'desc'],
19
+ required: false,
20
+ }),
21
+ output: Flags.string({
22
+ char: 'o',
23
+ default: 'summary',
24
+ description: 'Output format',
25
+ options: ['summary', 'json'],
26
+ required: false,
27
+ }),
28
+ page: Flags.integer({
29
+ default: 1,
30
+ description: 'Page number',
31
+ required: false,
32
+ }),
33
+ per_page: Flags.integer({
34
+ default: 50,
35
+ description: 'Items per page',
36
+ required: false,
37
+ }),
38
+ sort: Flags.string({
39
+ default: 'name',
40
+ description: 'Sort field',
41
+ options: ['name', 'created_at', 'state'],
42
+ required: false,
43
+ }),
44
+ };
45
+ async run() {
46
+ const { flags } = await this.parse(EphemeralList);
47
+ const profileName = flags.profile || this.getDefaultProfile();
48
+ const credentials = this.loadCredentialsFile();
49
+ if (!credentials || !(profileName in credentials.profiles)) {
50
+ this.error(`Profile '${profileName}' not found.\n` + `Create a profile using 'xano profile create'`);
51
+ }
52
+ const profile = credentials.profiles[profileName];
53
+ if (!profile.instance_origin) {
54
+ this.error(`Profile '${profileName}' is missing instance_origin`);
55
+ }
56
+ if (!profile.access_token) {
57
+ this.error(`Profile '${profileName}' is missing access_token`);
58
+ }
59
+ const params = new URLSearchParams({
60
+ order: flags.order,
61
+ page: String(flags.page),
62
+ per_page: String(flags.per_page),
63
+ sort: flags.sort,
64
+ });
65
+ const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant?${params}`;
66
+ try {
67
+ const response = await this.verboseFetch(apiUrl, {
68
+ headers: {
69
+ accept: 'application/json',
70
+ Authorization: `Bearer ${profile.access_token}`,
71
+ },
72
+ method: 'GET',
73
+ }, flags.verbose, profile.access_token);
74
+ if (!response.ok) {
75
+ const errorText = await response.text();
76
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
77
+ }
78
+ const data = (await response.json());
79
+ const tenants = data.items ?? [];
80
+ if (flags.output === 'json') {
81
+ this.log(JSON.stringify(data, null, 2));
82
+ }
83
+ else {
84
+ if (tenants.length === 0) {
85
+ this.log('No ephemeral tenants found');
86
+ }
87
+ else {
88
+ this.log('Ephemeral tenants:');
89
+ for (const tenant of tenants) {
90
+ const state = tenant.state ? ` [${tenant.state}]` : '';
91
+ const access = tenant.ephemeral_access ? ` [${tenant.ephemeral_access}]` : '';
92
+ this.log(` - ${tenant.display || tenant.name} (${tenant.name})${state}${access}`);
93
+ }
94
+ if (data.nextPage) {
95
+ this.log(`\nPage ${data.curPage} — more results available (use --page ${data.nextPage})`);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ catch (error) {
101
+ if (error instanceof Error) {
102
+ this.error(`Failed to list ephemeral tenants: ${error.message}`);
103
+ }
104
+ else {
105
+ this.error(`Failed to list ephemeral tenants: ${String(error)}`);
106
+ }
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class EphemeralPull extends BaseCommand {
3
+ static args: {
4
+ directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ tenant: import("@oclif/core/interfaces").OptionFlag<string, 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
+ private sanitizeFilename;
18
+ }
@@ -0,0 +1,197 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import snakeCase from 'lodash.snakecase';
3
+ import BaseCommand from '../../../base-command.js';
4
+ import { buildApiGroupFolderResolver, parseDocument } from '../../../utils/document-parser.js';
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ export default class EphemeralPull extends BaseCommand {
8
+ static args = {
9
+ directory: Args.string({
10
+ description: 'Output directory for pulled documents',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Pull an ephemeral tenant multidoc and split into individual files';
15
+ static examples = [
16
+ `$ xano ephemeral pull ./my-tenant -t my-tenant
17
+ Pulled 42 documents from ephemeral tenant my-tenant to ./my-tenant
18
+ `,
19
+ `$ xano ephemeral pull ./backup -t my-tenant --env --records`,
20
+ ];
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ draft: Flags.boolean({
24
+ default: false,
25
+ description: 'Include draft versions',
26
+ required: false,
27
+ }),
28
+ env: Flags.boolean({
29
+ default: false,
30
+ description: 'Include environment variables',
31
+ required: false,
32
+ }),
33
+ records: Flags.boolean({
34
+ default: false,
35
+ description: 'Include records',
36
+ required: false,
37
+ }),
38
+ tenant: Flags.string({
39
+ char: 't',
40
+ description: 'Ephemeral tenant name to pull from',
41
+ required: true,
42
+ }),
43
+ };
44
+ async run() {
45
+ const { args, flags } = await this.parse(EphemeralPull);
46
+ const profileName = flags.profile || this.getDefaultProfile();
47
+ const credentials = this.loadCredentialsFile();
48
+ if (!credentials || !(profileName in credentials.profiles)) {
49
+ this.error(`Profile '${profileName}' not found.\n` + `Create a profile using 'xano profile create'`);
50
+ }
51
+ const profile = credentials.profiles[profileName];
52
+ if (!profile.instance_origin) {
53
+ this.error(`Profile '${profileName}' is missing instance_origin`);
54
+ }
55
+ if (!profile.access_token) {
56
+ this.error(`Profile '${profileName}' is missing access_token`);
57
+ }
58
+ const tenantName = flags.tenant;
59
+ const queryParams = new URLSearchParams({
60
+ env: flags.env.toString(),
61
+ include_draft: flags.draft.toString(),
62
+ records: flags.records.toString(),
63
+ });
64
+ const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${tenantName}/multidoc?${queryParams.toString()}`;
65
+ let responseText;
66
+ try {
67
+ const response = await this.verboseFetch(apiUrl, {
68
+ headers: {
69
+ accept: 'application/json',
70
+ Authorization: `Bearer ${profile.access_token}`,
71
+ },
72
+ method: 'GET',
73
+ }, flags.verbose, profile.access_token);
74
+ if (!response.ok) {
75
+ const errorText = await response.text();
76
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
77
+ }
78
+ responseText = await response.text();
79
+ }
80
+ catch (error) {
81
+ if (error instanceof Error) {
82
+ this.error(`Failed to fetch multidoc: ${error.message}`);
83
+ }
84
+ else {
85
+ this.error(`Failed to fetch multidoc: ${String(error)}`);
86
+ }
87
+ }
88
+ const rawDocuments = responseText.split('\n---\n');
89
+ const documents = [];
90
+ for (const raw of rawDocuments) {
91
+ const trimmed = raw.trim();
92
+ if (!trimmed) {
93
+ continue;
94
+ }
95
+ const parsed = parseDocument(trimmed);
96
+ if (parsed) {
97
+ documents.push(parsed);
98
+ }
99
+ }
100
+ if (documents.length === 0) {
101
+ this.log('No documents found in response');
102
+ return;
103
+ }
104
+ const outputDir = path.resolve(args.directory);
105
+ fs.mkdirSync(outputDir, { recursive: true });
106
+ const getApiGroupFolder = buildApiGroupFolderResolver(documents, snakeCase);
107
+ const filenameCounters = new Map();
108
+ let writtenCount = 0;
109
+ for (const doc of documents) {
110
+ let typeDir;
111
+ let baseName;
112
+ if (doc.type === 'workspace') {
113
+ typeDir = path.join(outputDir, 'workspace');
114
+ baseName = this.sanitizeFilename(doc.name);
115
+ }
116
+ else if (doc.type === 'workspace_trigger') {
117
+ typeDir = path.join(outputDir, 'workspace', 'trigger');
118
+ baseName = this.sanitizeFilename(doc.name);
119
+ }
120
+ else if (doc.type === 'agent') {
121
+ typeDir = path.join(outputDir, 'ai', 'agent');
122
+ baseName = this.sanitizeFilename(doc.name);
123
+ }
124
+ else if (doc.type === 'mcp_server') {
125
+ typeDir = path.join(outputDir, 'ai', 'mcp_server');
126
+ baseName = this.sanitizeFilename(doc.name);
127
+ }
128
+ else if (doc.type === 'tool') {
129
+ typeDir = path.join(outputDir, 'ai', 'tool');
130
+ baseName = this.sanitizeFilename(doc.name);
131
+ }
132
+ else if (doc.type === 'agent_trigger') {
133
+ typeDir = path.join(outputDir, 'ai', 'agent', 'trigger');
134
+ baseName = this.sanitizeFilename(doc.name);
135
+ }
136
+ else if (doc.type === 'mcp_server_trigger') {
137
+ typeDir = path.join(outputDir, 'ai', 'mcp_server', 'trigger');
138
+ baseName = this.sanitizeFilename(doc.name);
139
+ }
140
+ else if (doc.type === 'table_trigger') {
141
+ typeDir = path.join(outputDir, 'table', 'trigger');
142
+ baseName = this.sanitizeFilename(doc.name);
143
+ }
144
+ else if (doc.type === 'realtime_channel') {
145
+ typeDir = path.join(outputDir, 'realtime', 'channel');
146
+ baseName = this.sanitizeFilename(doc.name);
147
+ }
148
+ else if (doc.type === 'realtime_trigger') {
149
+ typeDir = path.join(outputDir, 'realtime', 'trigger');
150
+ baseName = this.sanitizeFilename(doc.name);
151
+ }
152
+ else if (doc.type === 'api_group') {
153
+ const groupFolder = getApiGroupFolder(doc.name);
154
+ typeDir = path.join(outputDir, 'api', groupFolder);
155
+ baseName = this.sanitizeFilename(doc.name);
156
+ }
157
+ else if (doc.type === 'query' && doc.apiGroup) {
158
+ const groupFolder = getApiGroupFolder(doc.apiGroup);
159
+ const nameParts = doc.name.split('/');
160
+ const leafName = nameParts.pop();
161
+ const folderParts = nameParts.map((part) => snakeCase(part));
162
+ typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
163
+ baseName = this.sanitizeFilename(leafName);
164
+ if (doc.verb) {
165
+ baseName = `${baseName}_${doc.verb}`;
166
+ }
167
+ }
168
+ else {
169
+ const nameParts = doc.name.split('/');
170
+ const leafName = nameParts.pop();
171
+ const folderParts = nameParts.map((part) => snakeCase(part));
172
+ typeDir = path.join(outputDir, doc.type, ...folderParts);
173
+ baseName = this.sanitizeFilename(leafName);
174
+ if (doc.verb) {
175
+ baseName = `${baseName}_${doc.verb}`;
176
+ }
177
+ }
178
+ fs.mkdirSync(typeDir, { recursive: true });
179
+ const dirKey = path.relative(outputDir, typeDir);
180
+ if (!filenameCounters.has(dirKey)) {
181
+ filenameCounters.set(dirKey, new Map());
182
+ }
183
+ const typeCounters = filenameCounters.get(dirKey);
184
+ const count = typeCounters.get(baseName) || 0;
185
+ typeCounters.set(baseName, count + 1);
186
+ let filename;
187
+ filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
188
+ const filePath = path.join(typeDir, filename);
189
+ fs.writeFileSync(filePath, doc.content, 'utf8');
190
+ writtenCount++;
191
+ }
192
+ this.log(`Pulled ${writtenCount} documents from ephemeral tenant ${tenantName} to ${args.directory}`);
193
+ }
194
+ sanitizeFilename(name) {
195
+ return snakeCase(name.replaceAll('"', ''));
196
+ }
197
+ }
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class EphemeralPush extends BaseCommand {
3
+ static args: {
4
+ directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ 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
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ };
17
+ run(): Promise<void>;
18
+ private collectFiles;
19
+ }