@xano/cli 0.0.95-beta.2 → 0.0.95-beta.20

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 (100) hide show
  1. package/README.md +22 -12
  2. package/dist/base-command.d.ts +30 -0
  3. package/dist/base-command.js +61 -0
  4. package/dist/commands/auth/index.js +1 -1
  5. package/dist/commands/branch/create/index.d.ts +3 -1
  6. package/dist/commands/branch/create/index.js +21 -17
  7. package/dist/commands/profile/create/index.js +2 -2
  8. package/dist/commands/profile/edit/index.js +2 -2
  9. package/dist/commands/profile/me/index.js +21 -2
  10. package/dist/commands/profile/wizard/index.js +3 -3
  11. package/dist/commands/profile/workspace/set/index.js +1 -1
  12. package/dist/commands/{ephemeral → sandbox}/delete/index.d.ts +1 -5
  13. package/dist/commands/sandbox/delete/index.js +71 -0
  14. package/dist/commands/{ephemeral → sandbox}/env/delete/index.d.ts +1 -4
  15. package/dist/commands/{ephemeral → sandbox}/env/delete/index.js +20 -36
  16. package/dist/commands/{ephemeral → sandbox}/env/get/index.d.ts +1 -4
  17. package/dist/commands/sandbox/env/get/index.js +65 -0
  18. package/dist/commands/{ephemeral → sandbox}/env/get_all/index.d.ts +1 -4
  19. package/dist/commands/sandbox/env/get_all/index.js +78 -0
  20. package/dist/commands/{ephemeral → sandbox}/env/list/index.d.ts +1 -4
  21. package/dist/commands/sandbox/env/list/index.js +67 -0
  22. package/dist/commands/{ephemeral → sandbox}/env/set/index.d.ts +1 -4
  23. package/dist/commands/sandbox/env/set/index.js +74 -0
  24. package/dist/commands/{ephemeral → sandbox}/env/set_all/index.d.ts +1 -4
  25. package/dist/commands/{ephemeral → sandbox}/env/set_all/index.js +19 -35
  26. package/dist/commands/{ephemeral → sandbox}/get/index.d.ts +1 -4
  27. package/dist/commands/sandbox/get/index.js +63 -0
  28. package/dist/commands/sandbox/impersonate/index.d.ts +5 -0
  29. package/dist/commands/sandbox/impersonate/index.js +5 -0
  30. package/dist/commands/{ephemeral → sandbox}/license/get/index.d.ts +1 -4
  31. package/dist/commands/sandbox/license/get/index.js +78 -0
  32. package/dist/commands/{ephemeral → sandbox}/license/set/index.d.ts +1 -4
  33. package/dist/commands/{ephemeral → sandbox}/license/set/index.js +20 -36
  34. package/dist/commands/{ephemeral → sandbox}/pull/index.d.ts +1 -2
  35. package/dist/commands/{ephemeral → sandbox}/pull/index.js +13 -28
  36. package/dist/commands/{ephemeral → sandbox}/push/index.d.ts +3 -2
  37. package/dist/commands/{ephemeral → sandbox}/push/index.js +56 -31
  38. package/dist/commands/sandbox/reset/index.d.ts +12 -0
  39. package/dist/commands/sandbox/reset/index.js +71 -0
  40. package/dist/commands/{ephemeral/impersonate → sandbox/review}/index.d.ts +1 -4
  41. package/dist/commands/{ephemeral/impersonate → sandbox/review}/index.js +17 -33
  42. package/dist/commands/{ephemeral/unit_test/run_all → sandbox/unit_test/list}/index.d.ts +1 -2
  43. package/dist/commands/{ephemeral → sandbox}/unit_test/list/index.js +12 -26
  44. package/dist/commands/{ephemeral → sandbox}/unit_test/run/index.d.ts +1 -2
  45. package/dist/commands/{ephemeral → sandbox}/unit_test/run/index.js +11 -25
  46. package/dist/commands/{ephemeral/unit_test/list → sandbox/unit_test/run_all}/index.d.ts +1 -2
  47. package/dist/commands/{ephemeral → sandbox}/unit_test/run_all/index.js +11 -23
  48. package/dist/commands/{ephemeral/workflow_test/run_all → sandbox/workflow_test/list}/index.d.ts +1 -2
  49. package/dist/commands/{ephemeral → sandbox}/workflow_test/list/index.js +13 -27
  50. package/dist/commands/{ephemeral/workflow_test/get → sandbox/workflow_test/run}/index.d.ts +1 -2
  51. package/dist/commands/{ephemeral → sandbox}/workflow_test/run/index.js +11 -25
  52. package/dist/commands/{ephemeral/workflow_test/list → sandbox/workflow_test/run_all}/index.d.ts +1 -2
  53. package/dist/commands/{ephemeral → sandbox}/workflow_test/run_all/index.js +11 -23
  54. package/dist/commands/tenant/create/index.d.ts +2 -1
  55. package/dist/commands/tenant/create/index.js +23 -6
  56. package/dist/commands/tenant/deploy_release/index.d.ts +1 -0
  57. package/dist/commands/tenant/deploy_release/index.js +9 -1
  58. package/dist/commands/tenant/get/index.js +2 -2
  59. package/dist/commands/tenant/list/index.js +2 -2
  60. package/dist/commands/tenant/push/index.js +0 -34
  61. package/dist/commands/tenant/unit_test/list/index.js +2 -27
  62. package/dist/commands/tenant/unit_test/run/index.js +2 -27
  63. package/dist/commands/tenant/unit_test/run_all/index.js +2 -27
  64. package/dist/commands/tenant/workflow_test/list/index.js +2 -27
  65. package/dist/commands/tenant/workflow_test/run/index.js +2 -27
  66. package/dist/commands/tenant/workflow_test/run_all/index.js +2 -27
  67. package/dist/commands/workspace/edit/index.d.ts +1 -0
  68. package/dist/commands/workspace/edit/index.js +16 -6
  69. package/dist/commands/workspace/get/index.js +9 -7
  70. package/dist/commands/workspace/list/index.d.ts +1 -0
  71. package/dist/commands/workspace/list/index.js +14 -7
  72. package/dist/commands/workspace/push/index.d.ts +2 -0
  73. package/dist/commands/workspace/push/index.js +81 -6
  74. package/dist/utils/reference-checker.d.ts +57 -0
  75. package/dist/utils/reference-checker.js +232 -0
  76. package/oclif.manifest.json +1745 -2451
  77. package/package.json +8 -8
  78. package/dist/commands/ephemeral/access/index.d.ts +0 -15
  79. package/dist/commands/ephemeral/access/index.js +0 -78
  80. package/dist/commands/ephemeral/create/index.d.ts +0 -17
  81. package/dist/commands/ephemeral/create/index.js +0 -102
  82. package/dist/commands/ephemeral/delete/index.js +0 -99
  83. package/dist/commands/ephemeral/env/get/index.js +0 -81
  84. package/dist/commands/ephemeral/env/get_all/index.js +0 -94
  85. package/dist/commands/ephemeral/env/list/index.js +0 -83
  86. package/dist/commands/ephemeral/env/set/index.js +0 -90
  87. package/dist/commands/ephemeral/get/index.js +0 -102
  88. package/dist/commands/ephemeral/license/get/index.js +0 -94
  89. package/dist/commands/ephemeral/list/index.d.ts +0 -15
  90. package/dist/commands/ephemeral/list/index.js +0 -109
  91. package/dist/commands/ephemeral/shared/index.d.ts +0 -15
  92. package/dist/commands/ephemeral/shared/index.js +0 -108
  93. package/dist/commands/ephemeral/workflow_test/delete/index.d.ts +0 -18
  94. package/dist/commands/ephemeral/workflow_test/delete/index.js +0 -75
  95. package/dist/commands/ephemeral/workflow_test/get/index.js +0 -77
  96. package/dist/commands/ephemeral/workflow_test/run/index.d.ts +0 -18
  97. package/dist/commands/tenant/workflow_test/delete/index.d.ts +0 -19
  98. package/dist/commands/tenant/workflow_test/delete/index.js +0 -110
  99. package/dist/commands/tenant/workflow_test/get/index.d.ts +0 -19
  100. package/dist/commands/tenant/workflow_test/get/index.js +0 -112
@@ -1,22 +1,16 @@
1
- import { Args, Flags } from '@oclif/core';
1
+ import { Flags } from '@oclif/core';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
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';
5
+ export default class SandboxLicenseSet extends BaseCommand {
6
+ static description = 'Set/update the license for a sandbox environment';
13
7
  static examples = [
14
- `$ xano ephemeral license set my-tenant
15
- Reads from license_my-tenant.yaml
8
+ `$ xano sandbox license set
9
+ Reads from license_<tenant>.yaml
16
10
  `,
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`,
11
+ `$ xano sandbox license set --file ./license.yaml`,
12
+ `$ xano sandbox license set --value 'key: value'`,
13
+ `$ xano sandbox license set -o json`,
20
14
  ];
21
15
  static flags = {
22
16
  ...BaseCommand.baseFlags,
@@ -28,7 +22,7 @@ Reads from license_my-tenant.yaml
28
22
  }),
29
23
  file: Flags.string({
30
24
  char: 'f',
31
- description: 'Path to license file (default: license_<tenant_name>.yaml)',
25
+ description: 'Path to license file (default: license_<sandbox_name>.yaml)',
32
26
  exclusive: ['value'],
33
27
  required: false,
34
28
  }),
@@ -46,33 +40,21 @@ Reads from license_my-tenant.yaml
46
40
  }),
47
41
  };
48
42
  async run() {
49
- const { args, flags } = await this.parse(EphemeralLicenseSet);
50
- const tenantName = args.tenant_name;
43
+ const { flags } = await this.parse(SandboxLicenseSet);
44
+ const { profile } = this.resolveProfile(flags);
51
45
  let licenseValue;
52
46
  let sourceFilePath;
53
47
  if (flags.value) {
54
48
  licenseValue = flags.value;
55
49
  }
56
50
  else {
57
- sourceFilePath = path.resolve(flags.file || `license_${tenantName}.yaml`);
51
+ sourceFilePath = path.resolve(flags.file || `license.yaml`);
58
52
  if (!fs.existsSync(sourceFilePath)) {
59
53
  this.error(`File not found: ${sourceFilePath}`);
60
54
  }
61
55
  licenseValue = fs.readFileSync(sourceFilePath, 'utf8');
62
56
  }
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`;
57
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/license`;
76
58
  try {
77
59
  const response = await this.verboseFetch(apiUrl, {
78
60
  body: JSON.stringify({ value: licenseValue }),
@@ -84,15 +66,15 @@ Reads from license_my-tenant.yaml
84
66
  method: 'POST',
85
67
  }, flags.verbose, profile.access_token);
86
68
  if (!response.ok) {
87
- const errorText = await response.text();
88
- this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
69
+ const message = await this.parseApiError(response, 'API request failed');
70
+ this.error(message);
89
71
  }
90
72
  const result = await response.json();
91
73
  if (flags.output === 'json') {
92
74
  this.log(JSON.stringify(result, null, 2));
93
75
  }
94
76
  else {
95
- this.log(`Ephemeral tenant license updated successfully for ${tenantName}`);
77
+ this.log(`Sandbox environment license updated successfully`);
96
78
  }
97
79
  if (flags.clean && sourceFilePath && fs.existsSync(sourceFilePath)) {
98
80
  fs.unlinkSync(sourceFilePath);
@@ -100,11 +82,13 @@ Reads from license_my-tenant.yaml
100
82
  }
101
83
  }
102
84
  catch (error) {
85
+ if (error instanceof Error && 'oclif' in error)
86
+ throw error;
103
87
  if (error instanceof Error) {
104
- this.error(`Failed to set ephemeral tenant license: ${error.message}`);
88
+ this.error(`Failed to set sandbox environment license: ${error.message}`);
105
89
  }
106
90
  else {
107
- this.error(`Failed to set ephemeral tenant license: ${String(error)}`);
91
+ this.error(`Failed to set sandbox environment license: ${String(error)}`);
108
92
  }
109
93
  }
110
94
  }
@@ -1,5 +1,5 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
- export default class EphemeralPull extends BaseCommand {
2
+ export default class SandboxPull extends BaseCommand {
3
3
  static args: {
4
4
  directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
@@ -9,7 +9,6 @@ export default class EphemeralPull extends BaseCommand {
9
9
  draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
- tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
12
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
13
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
14
  };
@@ -4,19 +4,19 @@ import BaseCommand from '../../../base-command.js';
4
4
  import { buildApiGroupFolderResolver, parseDocument } from '../../../utils/document-parser.js';
5
5
  import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
- export default class EphemeralPull extends BaseCommand {
7
+ export default class SandboxPull extends BaseCommand {
8
8
  static args = {
9
9
  directory: Args.string({
10
10
  description: 'Output directory for pulled documents',
11
11
  required: true,
12
12
  }),
13
13
  };
14
- static description = 'Pull an ephemeral tenant multidoc and split into individual files';
14
+ static description = 'Pull documents from your sandbox environment and split into individual files';
15
15
  static examples = [
16
- `$ xano ephemeral pull ./my-tenant -t my-tenant
17
- Pulled 42 documents from ephemeral tenant my-tenant to ./my-tenant
16
+ `$ xano sandbox pull ./my-sandbox
17
+ Pulled 42 documents from sandbox environment to ./my-sandbox
18
18
  `,
19
- `$ xano ephemeral pull ./backup -t my-tenant --env --records`,
19
+ `$ xano sandbox pull ./backup --env --records`,
20
20
  ];
21
21
  static flags = {
22
22
  ...BaseCommand.baseFlags,
@@ -35,33 +35,16 @@ Pulled 42 documents from ephemeral tenant my-tenant to ./my-tenant
35
35
  description: 'Include records',
36
36
  required: false,
37
37
  }),
38
- tenant: Flags.string({
39
- char: 't',
40
- description: 'Ephemeral tenant name to pull from',
41
- required: true,
42
- }),
43
38
  };
44
39
  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;
40
+ const { args, flags } = await this.parse(SandboxPull);
41
+ const { profile } = this.resolveProfile(flags);
59
42
  const queryParams = new URLSearchParams({
60
43
  env: flags.env.toString(),
61
44
  include_draft: flags.draft.toString(),
62
45
  records: flags.records.toString(),
63
46
  });
64
- const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${tenantName}/multidoc?${queryParams.toString()}`;
47
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/multidoc?${queryParams.toString()}`;
65
48
  let responseText;
66
49
  try {
67
50
  const response = await this.verboseFetch(apiUrl, {
@@ -72,12 +55,14 @@ Pulled 42 documents from ephemeral tenant my-tenant to ./my-tenant
72
55
  method: 'GET',
73
56
  }, flags.verbose, profile.access_token);
74
57
  if (!response.ok) {
75
- const errorText = await response.text();
76
- this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
58
+ const message = await this.parseApiError(response, 'API request failed');
59
+ this.error(message);
77
60
  }
78
61
  responseText = await response.text();
79
62
  }
80
63
  catch (error) {
64
+ if (error instanceof Error && 'oclif' in error)
65
+ throw error;
81
66
  if (error instanceof Error) {
82
67
  this.error(`Failed to fetch multidoc: ${error.message}`);
83
68
  }
@@ -189,7 +174,7 @@ Pulled 42 documents from ephemeral tenant my-tenant to ./my-tenant
189
174
  fs.writeFileSync(filePath, doc.content, 'utf8');
190
175
  writtenCount++;
191
176
  }
192
- this.log(`Pulled ${writtenCount} documents from ephemeral tenant ${tenantName} to ${args.directory}`);
177
+ this.log(`Pulled ${writtenCount} documents from sandbox environment to ${args.directory}`);
193
178
  }
194
179
  sanitizeFilename(name) {
195
180
  return snakeCase(name.replaceAll('"', ''));
@@ -1,5 +1,5 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
- export default class EphemeralPush extends BaseCommand {
2
+ export default class SandboxPush extends BaseCommand {
3
3
  static args: {
4
4
  directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
@@ -8,7 +8,6 @@ export default class EphemeralPush extends BaseCommand {
8
8
  static flags: {
9
9
  env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
- tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
11
  transaction: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
12
  truncate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
13
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -16,4 +15,6 @@ export default class EphemeralPush extends BaseCommand {
16
15
  };
17
16
  run(): Promise<void>;
18
17
  private collectFiles;
18
+ private renderBadIndexes;
19
+ private renderBadReferences;
19
20
  }
@@ -1,22 +1,23 @@
1
- import { Args, Flags } from '@oclif/core';
2
- import BaseCommand from '../../../base-command.js';
3
- import { findFilesWithGuid } from '../../../utils/document-parser.js';
1
+ import { Args, Flags, ux } from '@oclif/core';
4
2
  import * as fs from 'node:fs';
5
3
  import * as path from 'node:path';
6
- export default class EphemeralPush extends BaseCommand {
4
+ import BaseCommand from '../../../base-command.js';
5
+ import { findFilesWithGuid } from '../../../utils/document-parser.js';
6
+ import { checkReferences, checkTableIndexes } from '../../../utils/reference-checker.js';
7
+ export default class SandboxPush extends BaseCommand {
7
8
  static args = {
8
9
  directory: Args.string({
9
- description: 'Directory containing documents to push (as produced by ephemeral pull or workspace pull)',
10
+ description: 'Directory containing documents to push (as produced by sandbox pull or workspace pull)',
10
11
  required: true,
11
12
  }),
12
13
  };
13
- static description = 'Push local documents to an ephemeral tenant via multidoc import';
14
+ static description = 'Push local documents to your sandbox environment via multidoc import';
14
15
  static examples = [
15
- `$ xano ephemeral push ./my-workspace -t my-tenant
16
- Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
16
+ `$ xano sandbox push ./my-workspace
17
+ Pushed 42 documents to sandbox environment from ./my-workspace
17
18
  `,
18
- `$ xano ephemeral push ./backup -t my-tenant --records --env`,
19
- `$ xano ephemeral push ./my-workspace -t my-tenant --truncate`,
19
+ `$ xano sandbox push ./backup --records --env`,
20
+ `$ xano sandbox push ./my-workspace --truncate`,
20
21
  ];
21
22
  static flags = {
22
23
  ...BaseCommand.baseFlags,
@@ -30,11 +31,6 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
30
31
  description: 'Include records in import',
31
32
  required: false,
32
33
  }),
33
- tenant: Flags.string({
34
- char: 't',
35
- description: 'Ephemeral tenant name to push to',
36
- required: true,
37
- }),
38
34
  transaction: Flags.boolean({
39
35
  allowNo: true,
40
36
  default: true,
@@ -48,20 +44,8 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
48
44
  }),
49
45
  };
50
46
  async run() {
51
- const { args, flags } = await this.parse(EphemeralPush);
52
- const profileName = flags.profile || this.getDefaultProfile();
53
- const credentials = this.loadCredentialsFile();
54
- if (!credentials || !(profileName in credentials.profiles)) {
55
- this.error(`Profile '${profileName}' not found.\n` + `Create a profile using 'xano profile create'`);
56
- }
57
- const profile = credentials.profiles[profileName];
58
- if (!profile.instance_origin) {
59
- this.error(`Profile '${profileName}' is missing instance_origin`);
60
- }
61
- if (!profile.access_token) {
62
- this.error(`Profile '${profileName}' is missing access_token`);
63
- }
64
- const tenantName = flags.tenant;
47
+ const { args, flags } = await this.parse(SandboxPush);
48
+ const { profile } = this.resolveProfile(flags);
65
49
  const inputDir = path.resolve(args.directory);
66
50
  if (!fs.existsSync(inputDir)) {
67
51
  this.error(`Directory not found: ${inputDir}`);
@@ -83,6 +67,16 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
83
67
  if (documentEntries.length === 0) {
84
68
  this.error(`All .xs files in ${args.directory} are empty`);
85
69
  }
70
+ // Check for bad cross-references within the local file set
71
+ const badRefs = checkReferences(documentEntries);
72
+ if (badRefs.length > 0) {
73
+ this.renderBadReferences(badRefs);
74
+ }
75
+ // Check for indexes referencing non-existent schema fields
76
+ const badIndexes = checkTableIndexes(documentEntries);
77
+ if (badIndexes.length > 0) {
78
+ this.renderBadIndexes(badIndexes);
79
+ }
86
80
  const multidoc = documentEntries.map((d) => d.content).join('\n---\n');
87
81
  const queryParams = new URLSearchParams({
88
82
  env: flags.env.toString(),
@@ -90,7 +84,7 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
90
84
  transaction: flags.transaction.toString(),
91
85
  truncate: flags.truncate.toString(),
92
86
  });
93
- const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${tenantName}/multidoc?${queryParams.toString()}`;
87
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/multidoc?${queryParams.toString()}`;
94
88
  const startTime = Date.now();
95
89
  try {
96
90
  const response = await this.verboseFetch(apiUrl, {
@@ -115,6 +109,10 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
115
109
  catch {
116
110
  errorMessage += `\n${errorText}`;
117
111
  }
112
+ // Provide guidance when sandbox access is denied (free plan restriction)
113
+ if (response.status === 500 && errorMessage.includes('Access Denied')) {
114
+ this.error('Sandbox is not available on the Free plan. Upgrade your plan to use sandbox features.');
115
+ }
118
116
  const guidMatch = errorMessage.match(/Duplicate \w+ guid: (\S+)/);
119
117
  if (guidMatch) {
120
118
  const dupeFiles = findFilesWithGuid(documentEntries, guidMatch[1]);
@@ -131,6 +129,8 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
131
129
  }
132
130
  }
133
131
  catch (error) {
132
+ if (error instanceof Error && 'oclif' in error)
133
+ throw error;
134
134
  if (error instanceof Error) {
135
135
  this.error(`Failed to push multidoc: ${error.message}`);
136
136
  }
@@ -139,7 +139,7 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
139
139
  }
140
140
  }
141
141
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
142
- this.log(`Pushed ${documentEntries.length} documents to ephemeral tenant ${tenantName} from ${args.directory} in ${elapsed}s`);
142
+ this.log(`Pushed ${documentEntries.length} documents to sandbox environment from ${args.directory} in ${elapsed}s`);
143
143
  }
144
144
  collectFiles(dir) {
145
145
  const files = [];
@@ -155,4 +155,29 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
155
155
  }
156
156
  return files.sort();
157
157
  }
158
+ renderBadIndexes(badIndexes) {
159
+ this.log('');
160
+ this.log(ux.colorize('red', ux.colorize('bold', '=== CRITICAL: Invalid Indexes ===')));
161
+ this.log('');
162
+ this.log(ux.colorize('red', 'The following tables have indexed referencing fields that do not exist in the schema, which may cause related issues.'));
163
+ this.log('');
164
+ for (const idx of badIndexes) {
165
+ this.log(` ${ux.colorize('red', 'CRITICAL'.padEnd(16))} ${'table'.padEnd(18)} ${idx.table}`);
166
+ this.log(` ${' '.repeat(16)} ${' '.repeat(18)} ${ux.colorize('dim', `${idx.indexType} index → field "${idx.field}" does not exist in schema`)}`);
167
+ }
168
+ this.log('');
169
+ }
170
+ renderBadReferences(badRefs) {
171
+ this.log('');
172
+ this.log(ux.colorize('yellow', ux.colorize('bold', '=== Unresolved References ===')));
173
+ this.log('');
174
+ this.log(ux.colorize('yellow', "The following references point to objects that don't exist in this push or on the server."));
175
+ this.log(ux.colorize('yellow', 'These will become placeholder statements after import.'));
176
+ this.log('');
177
+ for (const ref of badRefs) {
178
+ this.log(` ${ux.colorize('yellow', 'WARNING'.padEnd(16))} ${ref.sourceType.padEnd(18)} ${ref.source}`);
179
+ this.log(` ${' '.repeat(16)} ${' '.repeat(18)} ${ux.colorize('dim', `${ref.statementType} → ${ref.targetType} "${ref.target}" does not exist`)}`);
180
+ }
181
+ this.log('');
182
+ }
158
183
  }
@@ -0,0 +1,12 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class SandboxReset extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ private confirm;
12
+ }
@@ -0,0 +1,71 @@
1
+ import { Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class SandboxReset extends BaseCommand {
4
+ static description = 'Reset your sandbox environment (clears all workspace data and drafts)';
5
+ static examples = [
6
+ `$ xano sandbox reset
7
+ Are you sure you want to reset your sandbox environment? All workspace data and drafts will be cleared. (y/N) y
8
+ Sandbox environment has been reset.
9
+ `,
10
+ `$ xano sandbox reset --force`,
11
+ ];
12
+ static flags = {
13
+ ...BaseCommand.baseFlags,
14
+ force: Flags.boolean({
15
+ char: 'f',
16
+ default: false,
17
+ description: 'Skip confirmation prompt',
18
+ required: false,
19
+ }),
20
+ };
21
+ async run() {
22
+ const { flags } = await this.parse(SandboxReset);
23
+ const { profile } = this.resolveProfile(flags);
24
+ if (!flags.force) {
25
+ const confirmed = await this.confirm(`Are you sure you want to reset your sandbox environment? All workspace data and drafts will be cleared.`);
26
+ if (!confirmed) {
27
+ this.log('Reset cancelled.');
28
+ return;
29
+ }
30
+ }
31
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/reset`;
32
+ try {
33
+ const response = await this.verboseFetch(apiUrl, {
34
+ headers: {
35
+ accept: 'application/json',
36
+ Authorization: `Bearer ${profile.access_token}`,
37
+ 'Content-Type': 'application/json',
38
+ },
39
+ method: 'POST',
40
+ }, flags.verbose, profile.access_token);
41
+ if (!response.ok) {
42
+ const message = await this.parseApiError(response, 'API request failed');
43
+ this.error(message);
44
+ }
45
+ this.log('Sandbox environment has been reset.');
46
+ }
47
+ catch (error) {
48
+ if (error instanceof Error && 'oclif' in error)
49
+ throw error;
50
+ if (error instanceof Error) {
51
+ this.error(`Failed to reset sandbox environment: ${error.message}`);
52
+ }
53
+ else {
54
+ this.error(`Failed to reset sandbox environment: ${String(error)}`);
55
+ }
56
+ }
57
+ }
58
+ async confirm(message) {
59
+ const readline = await import('node:readline');
60
+ const rl = readline.createInterface({
61
+ input: process.stdin,
62
+ output: process.stdout,
63
+ });
64
+ return new Promise((resolve) => {
65
+ rl.question(`${message} (y/N) `, (answer) => {
66
+ rl.close();
67
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
68
+ });
69
+ });
70
+ }
71
+ }
@@ -1,8 +1,5 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
- export default class EphemeralImpersonate extends BaseCommand {
3
- static args: {
4
- tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
- };
2
+ export default class SandboxReview extends BaseCommand {
6
3
  static description: string;
7
4
  static examples: string[];
8
5
  static flags: {
@@ -1,21 +1,15 @@
1
- import { Args, Flags } from '@oclif/core';
1
+ import { Flags } from '@oclif/core';
2
2
  import open from 'open';
3
3
  import BaseCommand from '../../../base-command.js';
4
- export default class EphemeralImpersonate extends BaseCommand {
5
- static args = {
6
- tenant_name: Args.string({
7
- description: 'Ephemeral tenant name to impersonate',
8
- required: true,
9
- }),
10
- };
11
- static description = 'Impersonate an ephemeral tenant and open it in the browser';
4
+ export default class SandboxReview extends BaseCommand {
5
+ static description = 'Open your sandbox environment in the browser to review and promote changes';
12
6
  static examples = [
13
- `$ xano ephemeral impersonate my-tenant
7
+ `$ xano sandbox review
14
8
  Opening browser...
15
- Impersonation successful!
9
+ Review session started!
16
10
  `,
17
- `$ xano ephemeral impersonate my-tenant -u`,
18
- `$ xano ephemeral impersonate my-tenant -o json`,
11
+ `$ xano sandbox review -u`,
12
+ `$ xano sandbox review -o json`,
19
13
  ];
20
14
  static flags = {
21
15
  ...BaseCommand.baseFlags,
@@ -34,21 +28,9 @@ Impersonation successful!
34
28
  }),
35
29
  };
36
30
  async run() {
37
- const { args, flags } = await this.parse(EphemeralImpersonate);
38
- const profileName = flags.profile || this.getDefaultProfile();
39
- const credentials = this.loadCredentialsFile();
40
- if (!credentials || !(profileName in credentials.profiles)) {
41
- this.error(`Profile '${profileName}' not found.\n` + `Create a profile using 'xano auth'`);
42
- }
43
- const profile = credentials.profiles[profileName];
44
- if (!profile.instance_origin) {
45
- this.error(`Profile '${profileName}' is missing instance_origin`);
46
- }
47
- if (!profile.access_token) {
48
- this.error(`Profile '${profileName}' is missing access_token`);
49
- }
50
- const tenantName = args.tenant_name;
51
- const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${encodeURIComponent(tenantName)}/impersonate`;
31
+ const { flags } = await this.parse(SandboxReview);
32
+ const { profile } = this.resolveProfile(flags);
33
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/impersonate`;
52
34
  try {
53
35
  const response = await this.verboseFetch(apiUrl, {
54
36
  headers: {
@@ -58,8 +40,8 @@ Impersonation successful!
58
40
  method: 'GET',
59
41
  }, flags.verbose, profile.access_token);
60
42
  if (!response.ok) {
61
- const errorText = await response.text();
62
- this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
43
+ const message = await this.parseApiError(response, 'API request failed');
44
+ this.error(message);
63
45
  }
64
46
  const result = (await response.json());
65
47
  if (!result._ti) {
@@ -80,17 +62,19 @@ Impersonation successful!
80
62
  else {
81
63
  this.log('Opening browser...');
82
64
  await open(impersonateUrl);
83
- this.log('Impersonation successful!');
65
+ this.log('Review session started!');
84
66
  }
85
67
  }
86
68
  process.exit(0);
87
69
  }
88
70
  catch (error) {
71
+ if (error instanceof Error && 'oclif' in error)
72
+ throw error;
89
73
  if (error instanceof Error) {
90
- this.error(`Failed to impersonate ephemeral tenant: ${error.message}`);
74
+ this.error(`Failed to open sandbox review: ${error.message}`);
91
75
  }
92
76
  else {
93
- this.error(`Failed to impersonate ephemeral tenant: ${String(error)}`);
77
+ this.error(`Failed to open sandbox review: ${String(error)}`);
94
78
  }
95
79
  }
96
80
  }
@@ -1,12 +1,11 @@
1
1
  import BaseCommand from '../../../../base-command.js';
2
- export default class EphemeralUnitTestRunAll extends BaseCommand {
2
+ export default class SandboxUnitTestList extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
6
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
7
  'obj-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
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
9
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
10
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
11
  };