@xano/cli 0.0.94 → 0.0.95-beta.10

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 (86) hide show
  1. package/README.md +28 -1
  2. package/dist/base-command.d.ts +25 -0
  3. package/dist/base-command.js +37 -0
  4. package/dist/commands/auth/index.js +1 -1
  5. package/dist/commands/profile/create/index.js +2 -2
  6. package/dist/commands/profile/edit/index.js +2 -2
  7. package/dist/commands/profile/me/index.js +21 -2
  8. package/dist/commands/profile/wizard/index.js +3 -3
  9. package/dist/commands/profile/workspace/set/index.js +1 -1
  10. package/dist/commands/release/deploy/index.d.ts +17 -0
  11. package/dist/commands/release/deploy/index.js +107 -0
  12. package/dist/commands/sandbox/env/delete/index.d.ts +14 -0
  13. package/dist/commands/sandbox/env/delete/index.js +87 -0
  14. package/dist/commands/sandbox/env/get/index.d.ts +12 -0
  15. package/dist/commands/sandbox/env/get/index.js +63 -0
  16. package/dist/commands/sandbox/env/get_all/index.d.ts +13 -0
  17. package/dist/commands/sandbox/env/get_all/index.js +76 -0
  18. package/dist/commands/sandbox/env/list/index.d.ts +11 -0
  19. package/dist/commands/sandbox/env/list/index.js +65 -0
  20. package/dist/commands/sandbox/env/set/index.d.ts +13 -0
  21. package/dist/commands/sandbox/env/set/index.js +72 -0
  22. package/dist/commands/sandbox/env/set_all/index.d.ts +13 -0
  23. package/dist/commands/sandbox/env/set_all/index.js +84 -0
  24. package/dist/commands/sandbox/get/index.d.ts +11 -0
  25. package/dist/commands/sandbox/get/index.js +61 -0
  26. package/dist/commands/sandbox/impersonate/index.d.ts +5 -0
  27. package/dist/commands/sandbox/impersonate/index.js +5 -0
  28. package/dist/commands/sandbox/license/get/index.d.ts +13 -0
  29. package/dist/commands/sandbox/license/get/index.js +76 -0
  30. package/dist/commands/sandbox/license/set/index.d.ts +14 -0
  31. package/dist/commands/sandbox/license/set/index.js +93 -0
  32. package/dist/commands/sandbox/pull/index.d.ts +17 -0
  33. package/dist/commands/sandbox/pull/index.js +180 -0
  34. package/dist/commands/sandbox/push/index.d.ts +18 -0
  35. package/dist/commands/sandbox/push/index.js +141 -0
  36. package/dist/commands/sandbox/reset/index.d.ts +12 -0
  37. package/dist/commands/sandbox/reset/index.js +69 -0
  38. package/dist/commands/sandbox/review/index.d.ts +13 -0
  39. package/dist/commands/sandbox/review/index.js +92 -0
  40. package/dist/commands/sandbox/unit_test/list/index.d.ts +13 -0
  41. package/dist/commands/sandbox/unit_test/list/index.js +89 -0
  42. package/dist/commands/sandbox/unit_test/run/index.d.ts +14 -0
  43. package/dist/commands/sandbox/unit_test/run/index.js +77 -0
  44. package/dist/commands/sandbox/unit_test/run_all/index.d.ts +13 -0
  45. package/dist/commands/sandbox/unit_test/run_all/index.js +167 -0
  46. package/dist/commands/sandbox/workflow_test/delete/index.d.ts +17 -0
  47. package/dist/commands/sandbox/workflow_test/delete/index.js +59 -0
  48. package/dist/commands/sandbox/workflow_test/get/index.d.ts +17 -0
  49. package/dist/commands/sandbox/workflow_test/get/index.js +58 -0
  50. package/dist/commands/sandbox/workflow_test/list/index.d.ts +12 -0
  51. package/dist/commands/sandbox/workflow_test/list/index.js +82 -0
  52. package/dist/commands/sandbox/workflow_test/run/index.d.ts +17 -0
  53. package/dist/commands/sandbox/workflow_test/run/index.js +75 -0
  54. package/dist/commands/sandbox/workflow_test/run_all/index.d.ts +12 -0
  55. package/dist/commands/sandbox/workflow_test/run_all/index.js +153 -0
  56. package/dist/commands/tenant/create/index.d.ts +2 -2
  57. package/dist/commands/tenant/create/index.js +23 -11
  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.d.ts +15 -0
  62. package/dist/commands/tenant/unit_test/list/index.js +140 -0
  63. package/dist/commands/tenant/unit_test/run/index.d.ts +16 -0
  64. package/dist/commands/tenant/unit_test/run/index.js +128 -0
  65. package/dist/commands/tenant/unit_test/run_all/index.d.ts +15 -0
  66. package/dist/commands/tenant/unit_test/run_all/index.js +215 -0
  67. package/dist/commands/tenant/workflow_test/delete/index.d.ts +19 -0
  68. package/dist/commands/tenant/workflow_test/delete/index.js +110 -0
  69. package/dist/commands/tenant/workflow_test/get/index.d.ts +19 -0
  70. package/dist/commands/tenant/workflow_test/get/index.js +112 -0
  71. package/dist/commands/tenant/workflow_test/list/index.d.ts +14 -0
  72. package/dist/commands/tenant/workflow_test/list/index.js +133 -0
  73. package/dist/commands/tenant/workflow_test/run/index.d.ts +19 -0
  74. package/dist/commands/tenant/workflow_test/run/index.js +126 -0
  75. package/dist/commands/tenant/workflow_test/run_all/index.d.ts +14 -0
  76. package/dist/commands/tenant/workflow_test/run_all/index.js +201 -0
  77. package/dist/commands/workspace/edit/index.d.ts +1 -0
  78. package/dist/commands/workspace/edit/index.js +16 -6
  79. package/dist/commands/workspace/get/index.js +9 -7
  80. package/dist/commands/workspace/list/index.d.ts +1 -0
  81. package/dist/commands/workspace/list/index.js +14 -7
  82. package/dist/commands/workspace/push/index.js +30 -2
  83. package/dist/help.d.ts +2 -1
  84. package/dist/help.js +39 -1
  85. package/oclif.manifest.json +4701 -2272
  86. package/package.json +17 -2
@@ -0,0 +1,180 @@
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 SandboxPull 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 documents from your sandbox environment and split into individual files';
15
+ static examples = [
16
+ `$ xano sandbox pull ./my-sandbox
17
+ Pulled 42 documents from sandbox environment to ./my-sandbox
18
+ `,
19
+ `$ xano sandbox pull ./backup --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
+ };
39
+ async run() {
40
+ const { args, flags } = await this.parse(SandboxPull);
41
+ const { profile } = this.resolveProfile(flags);
42
+ const queryParams = new URLSearchParams({
43
+ env: flags.env.toString(),
44
+ include_draft: flags.draft.toString(),
45
+ records: flags.records.toString(),
46
+ });
47
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/multidoc?${queryParams.toString()}`;
48
+ let responseText;
49
+ try {
50
+ const response = await this.verboseFetch(apiUrl, {
51
+ headers: {
52
+ accept: 'application/json',
53
+ Authorization: `Bearer ${profile.access_token}`,
54
+ },
55
+ method: 'GET',
56
+ }, flags.verbose, profile.access_token);
57
+ if (!response.ok) {
58
+ const errorText = await response.text();
59
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
60
+ }
61
+ responseText = await response.text();
62
+ }
63
+ catch (error) {
64
+ if (error instanceof Error) {
65
+ this.error(`Failed to fetch multidoc: ${error.message}`);
66
+ }
67
+ else {
68
+ this.error(`Failed to fetch multidoc: ${String(error)}`);
69
+ }
70
+ }
71
+ const rawDocuments = responseText.split('\n---\n');
72
+ const documents = [];
73
+ for (const raw of rawDocuments) {
74
+ const trimmed = raw.trim();
75
+ if (!trimmed) {
76
+ continue;
77
+ }
78
+ const parsed = parseDocument(trimmed);
79
+ if (parsed) {
80
+ documents.push(parsed);
81
+ }
82
+ }
83
+ if (documents.length === 0) {
84
+ this.log('No documents found in response');
85
+ return;
86
+ }
87
+ const outputDir = path.resolve(args.directory);
88
+ fs.mkdirSync(outputDir, { recursive: true });
89
+ const getApiGroupFolder = buildApiGroupFolderResolver(documents, snakeCase);
90
+ const filenameCounters = new Map();
91
+ let writtenCount = 0;
92
+ for (const doc of documents) {
93
+ let typeDir;
94
+ let baseName;
95
+ if (doc.type === 'workspace') {
96
+ typeDir = path.join(outputDir, 'workspace');
97
+ baseName = this.sanitizeFilename(doc.name);
98
+ }
99
+ else if (doc.type === 'workspace_trigger') {
100
+ typeDir = path.join(outputDir, 'workspace', 'trigger');
101
+ baseName = this.sanitizeFilename(doc.name);
102
+ }
103
+ else if (doc.type === 'agent') {
104
+ typeDir = path.join(outputDir, 'ai', 'agent');
105
+ baseName = this.sanitizeFilename(doc.name);
106
+ }
107
+ else if (doc.type === 'mcp_server') {
108
+ typeDir = path.join(outputDir, 'ai', 'mcp_server');
109
+ baseName = this.sanitizeFilename(doc.name);
110
+ }
111
+ else if (doc.type === 'tool') {
112
+ typeDir = path.join(outputDir, 'ai', 'tool');
113
+ baseName = this.sanitizeFilename(doc.name);
114
+ }
115
+ else if (doc.type === 'agent_trigger') {
116
+ typeDir = path.join(outputDir, 'ai', 'agent', 'trigger');
117
+ baseName = this.sanitizeFilename(doc.name);
118
+ }
119
+ else if (doc.type === 'mcp_server_trigger') {
120
+ typeDir = path.join(outputDir, 'ai', 'mcp_server', 'trigger');
121
+ baseName = this.sanitizeFilename(doc.name);
122
+ }
123
+ else if (doc.type === 'table_trigger') {
124
+ typeDir = path.join(outputDir, 'table', 'trigger');
125
+ baseName = this.sanitizeFilename(doc.name);
126
+ }
127
+ else if (doc.type === 'realtime_channel') {
128
+ typeDir = path.join(outputDir, 'realtime', 'channel');
129
+ baseName = this.sanitizeFilename(doc.name);
130
+ }
131
+ else if (doc.type === 'realtime_trigger') {
132
+ typeDir = path.join(outputDir, 'realtime', 'trigger');
133
+ baseName = this.sanitizeFilename(doc.name);
134
+ }
135
+ else if (doc.type === 'api_group') {
136
+ const groupFolder = getApiGroupFolder(doc.name);
137
+ typeDir = path.join(outputDir, 'api', groupFolder);
138
+ baseName = this.sanitizeFilename(doc.name);
139
+ }
140
+ else if (doc.type === 'query' && doc.apiGroup) {
141
+ const groupFolder = getApiGroupFolder(doc.apiGroup);
142
+ const nameParts = doc.name.split('/');
143
+ const leafName = nameParts.pop();
144
+ const folderParts = nameParts.map((part) => snakeCase(part));
145
+ typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
146
+ baseName = this.sanitizeFilename(leafName);
147
+ if (doc.verb) {
148
+ baseName = `${baseName}_${doc.verb}`;
149
+ }
150
+ }
151
+ else {
152
+ const nameParts = doc.name.split('/');
153
+ const leafName = nameParts.pop();
154
+ const folderParts = nameParts.map((part) => snakeCase(part));
155
+ typeDir = path.join(outputDir, doc.type, ...folderParts);
156
+ baseName = this.sanitizeFilename(leafName);
157
+ if (doc.verb) {
158
+ baseName = `${baseName}_${doc.verb}`;
159
+ }
160
+ }
161
+ fs.mkdirSync(typeDir, { recursive: true });
162
+ const dirKey = path.relative(outputDir, typeDir);
163
+ if (!filenameCounters.has(dirKey)) {
164
+ filenameCounters.set(dirKey, new Map());
165
+ }
166
+ const typeCounters = filenameCounters.get(dirKey);
167
+ const count = typeCounters.get(baseName) || 0;
168
+ typeCounters.set(baseName, count + 1);
169
+ let filename;
170
+ filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
171
+ const filePath = path.join(typeDir, filename);
172
+ fs.writeFileSync(filePath, doc.content, 'utf8');
173
+ writtenCount++;
174
+ }
175
+ this.log(`Pulled ${writtenCount} documents from sandbox environment to ${args.directory}`);
176
+ }
177
+ sanitizeFilename(name) {
178
+ return snakeCase(name.replaceAll('"', ''));
179
+ }
180
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class SandboxPush 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
+ transaction: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ truncate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
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 collectFiles;
18
+ }
@@ -0,0 +1,141 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ import { findFilesWithGuid } from '../../../utils/document-parser.js';
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ export default class SandboxPush extends BaseCommand {
7
+ static args = {
8
+ directory: Args.string({
9
+ description: 'Directory containing documents to push (as produced by sandbox pull or workspace pull)',
10
+ required: true,
11
+ }),
12
+ };
13
+ static description = 'Push local documents to your sandbox environment via multidoc import';
14
+ static examples = [
15
+ `$ xano sandbox push ./my-workspace
16
+ Pushed 42 documents to sandbox environment from ./my-workspace
17
+ `,
18
+ `$ xano sandbox push ./backup --records --env`,
19
+ `$ xano sandbox push ./my-workspace --truncate`,
20
+ ];
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ env: Flags.boolean({
24
+ default: false,
25
+ description: 'Include environment variables in import',
26
+ required: false,
27
+ }),
28
+ records: Flags.boolean({
29
+ default: false,
30
+ description: 'Include records in import',
31
+ required: false,
32
+ }),
33
+ transaction: Flags.boolean({
34
+ allowNo: true,
35
+ default: true,
36
+ description: 'Wrap import in a database transaction (use --no-transaction for debugging purposes)',
37
+ required: false,
38
+ }),
39
+ truncate: Flags.boolean({
40
+ default: false,
41
+ description: 'Truncate all table records before importing',
42
+ required: false,
43
+ }),
44
+ };
45
+ async run() {
46
+ const { args, flags } = await this.parse(SandboxPush);
47
+ const { profile } = this.resolveProfile(flags);
48
+ const inputDir = path.resolve(args.directory);
49
+ if (!fs.existsSync(inputDir)) {
50
+ this.error(`Directory not found: ${inputDir}`);
51
+ }
52
+ if (!fs.statSync(inputDir).isDirectory()) {
53
+ this.error(`Not a directory: ${inputDir}`);
54
+ }
55
+ const files = this.collectFiles(inputDir);
56
+ if (files.length === 0) {
57
+ this.error(`No .xs files found in ${args.directory}`);
58
+ }
59
+ const documentEntries = [];
60
+ for (const filePath of files) {
61
+ const content = fs.readFileSync(filePath, 'utf8').trim();
62
+ if (content) {
63
+ documentEntries.push({ content, filePath });
64
+ }
65
+ }
66
+ if (documentEntries.length === 0) {
67
+ this.error(`All .xs files in ${args.directory} are empty`);
68
+ }
69
+ const multidoc = documentEntries.map((d) => d.content).join('\n---\n');
70
+ const queryParams = new URLSearchParams({
71
+ env: flags.env.toString(),
72
+ records: flags.records.toString(),
73
+ transaction: flags.transaction.toString(),
74
+ truncate: flags.truncate.toString(),
75
+ });
76
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/multidoc?${queryParams.toString()}`;
77
+ const startTime = Date.now();
78
+ try {
79
+ const response = await this.verboseFetch(apiUrl, {
80
+ body: multidoc,
81
+ headers: {
82
+ accept: 'application/json',
83
+ Authorization: `Bearer ${profile.access_token}`,
84
+ 'Content-Type': 'text/x-xanoscript',
85
+ },
86
+ method: 'POST',
87
+ }, flags.verbose, profile.access_token);
88
+ if (!response.ok) {
89
+ const errorText = await response.text();
90
+ let errorMessage = `Push failed (${response.status})`;
91
+ try {
92
+ const errorJson = JSON.parse(errorText);
93
+ errorMessage += `: ${errorJson.message}`;
94
+ if (errorJson.payload?.param) {
95
+ errorMessage += `\n Parameter: ${errorJson.payload.param}`;
96
+ }
97
+ }
98
+ catch {
99
+ errorMessage += `\n${errorText}`;
100
+ }
101
+ const guidMatch = errorMessage.match(/Duplicate \w+ guid: (\S+)/);
102
+ if (guidMatch) {
103
+ const dupeFiles = findFilesWithGuid(documentEntries, guidMatch[1]);
104
+ if (dupeFiles.length > 0) {
105
+ const relPaths = dupeFiles.map((f) => path.relative(inputDir, f));
106
+ errorMessage += `\n Local files with this GUID:\n${relPaths.map((f) => ` ${f}`).join('\n')}`;
107
+ }
108
+ }
109
+ this.error(errorMessage);
110
+ }
111
+ const responseText = await response.text();
112
+ if (responseText && responseText !== 'null' && flags.verbose) {
113
+ this.log(responseText);
114
+ }
115
+ }
116
+ catch (error) {
117
+ if (error instanceof Error) {
118
+ this.error(`Failed to push multidoc: ${error.message}`);
119
+ }
120
+ else {
121
+ this.error(`Failed to push multidoc: ${String(error)}`);
122
+ }
123
+ }
124
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
125
+ this.log(`Pushed ${documentEntries.length} documents to sandbox environment from ${args.directory} in ${elapsed}s`);
126
+ }
127
+ collectFiles(dir) {
128
+ const files = [];
129
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
130
+ for (const entry of entries) {
131
+ const fullPath = path.join(dir, entry.name);
132
+ if (entry.isDirectory()) {
133
+ files.push(...this.collectFiles(fullPath));
134
+ }
135
+ else if (entry.isFile() && entry.name.endsWith('.xs')) {
136
+ files.push(fullPath);
137
+ }
138
+ }
139
+ return files.sort();
140
+ }
141
+ }
@@ -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,69 @@
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 errorText = await response.text();
43
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
44
+ }
45
+ this.log('Sandbox environment has been reset.');
46
+ }
47
+ catch (error) {
48
+ if (error instanceof Error) {
49
+ this.error(`Failed to reset sandbox environment: ${error.message}`);
50
+ }
51
+ else {
52
+ this.error(`Failed to reset sandbox environment: ${String(error)}`);
53
+ }
54
+ }
55
+ }
56
+ async confirm(message) {
57
+ const readline = await import('node:readline');
58
+ const rl = readline.createInterface({
59
+ input: process.stdin,
60
+ output: process.stdout,
61
+ });
62
+ return new Promise((resolve) => {
63
+ rl.question(`${message} (y/N) `, (answer) => {
64
+ rl.close();
65
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
66
+ });
67
+ });
68
+ }
69
+ }
@@ -0,0 +1,13 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class SandboxReview extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'url-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ private getFrontendUrl;
13
+ }
@@ -0,0 +1,92 @@
1
+ import { Flags } from '@oclif/core';
2
+ import open from 'open';
3
+ import BaseCommand from '../../../base-command.js';
4
+ export default class SandboxReview extends BaseCommand {
5
+ static description = 'Open your sandbox environment in the browser to review and promote changes';
6
+ static examples = [
7
+ `$ xano sandbox review
8
+ Opening browser...
9
+ Review session started!
10
+ `,
11
+ `$ xano sandbox review -u`,
12
+ `$ xano sandbox review -o json`,
13
+ ];
14
+ static flags = {
15
+ ...BaseCommand.baseFlags,
16
+ output: Flags.string({
17
+ char: 'o',
18
+ default: 'summary',
19
+ description: 'Output format',
20
+ options: ['summary', 'json'],
21
+ required: false,
22
+ }),
23
+ 'url-only': Flags.boolean({
24
+ char: 'u',
25
+ default: false,
26
+ description: 'Print the URL without opening the browser',
27
+ required: false,
28
+ }),
29
+ };
30
+ async run() {
31
+ const { flags } = await this.parse(SandboxReview);
32
+ const { profile } = this.resolveProfile(flags);
33
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/impersonate`;
34
+ try {
35
+ const response = await this.verboseFetch(apiUrl, {
36
+ headers: {
37
+ accept: 'application/json',
38
+ Authorization: `Bearer ${profile.access_token}`,
39
+ },
40
+ method: 'GET',
41
+ }, flags.verbose, profile.access_token);
42
+ if (!response.ok) {
43
+ const errorText = await response.text();
44
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
45
+ }
46
+ const result = (await response.json());
47
+ if (!result._ti) {
48
+ this.error('No one-time token returned from impersonate API');
49
+ }
50
+ if (flags.output === 'json') {
51
+ this.log(JSON.stringify(result, null, 2));
52
+ }
53
+ else {
54
+ const frontendUrl = this.getFrontendUrl(profile.instance_origin);
55
+ const params = new URLSearchParams({
56
+ _ti: result._ti,
57
+ });
58
+ const impersonateUrl = `${frontendUrl}/impersonate?${params.toString()}`;
59
+ if (flags['url-only']) {
60
+ this.log(impersonateUrl);
61
+ }
62
+ else {
63
+ this.log('Opening browser...');
64
+ await open(impersonateUrl);
65
+ this.log('Review session started!');
66
+ }
67
+ }
68
+ process.exit(0);
69
+ }
70
+ catch (error) {
71
+ if (error instanceof Error) {
72
+ this.error(`Failed to open sandbox review: ${error.message}`);
73
+ }
74
+ else {
75
+ this.error(`Failed to open sandbox review: ${String(error)}`);
76
+ }
77
+ }
78
+ }
79
+ getFrontendUrl(instanceOrigin) {
80
+ try {
81
+ const url = new URL(instanceOrigin);
82
+ if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
83
+ url.port = '4200';
84
+ return url.origin;
85
+ }
86
+ }
87
+ catch {
88
+ // fall through
89
+ }
90
+ return instanceOrigin;
91
+ }
92
+ }
@@ -0,0 +1,13 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class SandboxUnitTestList 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
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,89 @@
1
+ import { Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../../base-command.js';
3
+ export default class SandboxUnitTestList extends BaseCommand {
4
+ static description = 'List all unit tests for a sandbox environment';
5
+ static examples = [
6
+ `$ xano sandbox unit-test list
7
+ Unit tests:
8
+ - my-test (ID: abc-123) [function: math]
9
+ `,
10
+ `$ xano sandbox unit-test list -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
+ };
32
+ async run() {
33
+ const { flags } = await this.parse(SandboxUnitTestList);
34
+ const { profile } = this.resolveProfile(flags);
35
+ const params = new URLSearchParams();
36
+ params.set('per_page', '10000');
37
+ if (flags.branch)
38
+ params.set('branch', flags.branch);
39
+ if (flags['obj-type'])
40
+ params.set('obj_type', flags['obj-type']);
41
+ const apiUrl = `${profile.instance_origin}/api:meta/sandbox/unit_test?${params}`;
42
+ try {
43
+ const response = await this.verboseFetch(apiUrl, {
44
+ headers: {
45
+ accept: 'application/json',
46
+ Authorization: `Bearer ${profile.access_token}`,
47
+ },
48
+ method: 'GET',
49
+ }, flags.verbose, profile.access_token);
50
+ if (!response.ok) {
51
+ const errorText = await response.text();
52
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
53
+ }
54
+ const data = (await response.json());
55
+ let tests;
56
+ if (Array.isArray(data)) {
57
+ tests = data;
58
+ }
59
+ else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
60
+ tests = data.items;
61
+ }
62
+ else {
63
+ this.error('Unexpected API response format');
64
+ }
65
+ if (flags.output === 'json') {
66
+ this.log(JSON.stringify(tests, null, 2));
67
+ }
68
+ else {
69
+ if (tests.length === 0) {
70
+ this.log('No unit tests found');
71
+ }
72
+ else {
73
+ this.log(`Unit tests for sandbox environment:`);
74
+ for (const test of tests) {
75
+ this.log(` - ${test.name} (ID: ${test.id}) [${test.obj_type}: ${test.obj_name}]`);
76
+ }
77
+ }
78
+ }
79
+ }
80
+ catch (error) {
81
+ if (error instanceof Error) {
82
+ this.error(`Failed to list unit tests: ${error.message}`);
83
+ }
84
+ else {
85
+ this.error(`Failed to list unit tests: ${String(error)}`);
86
+ }
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,14 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class SandboxUnitTestRun 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
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ }