@xano/cli 0.0.95-beta.1 → 0.0.95-beta.11
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.
- package/README.md +22 -9
- package/dist/base-command.d.ts +30 -0
- package/dist/base-command.js +61 -0
- package/dist/commands/auth/index.js +1 -1
- package/dist/commands/profile/create/index.js +2 -2
- package/dist/commands/profile/edit/index.js +2 -2
- package/dist/commands/profile/me/index.js +21 -2
- package/dist/commands/profile/wizard/index.js +3 -3
- package/dist/commands/profile/workspace/set/index.js +1 -1
- package/dist/commands/release/deploy/index.d.ts +17 -0
- package/dist/commands/release/deploy/index.js +107 -0
- package/dist/commands/{ephemeral → sandbox}/env/delete/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/env/delete/index.js +18 -36
- package/dist/commands/{ephemeral → sandbox}/env/get/index.d.ts +1 -4
- package/dist/commands/sandbox/env/get/index.js +63 -0
- package/dist/commands/{ephemeral → sandbox}/env/get_all/index.d.ts +1 -4
- package/dist/commands/sandbox/env/get_all/index.js +76 -0
- package/dist/commands/{ephemeral → sandbox}/env/list/index.d.ts +1 -4
- package/dist/commands/sandbox/env/list/index.js +65 -0
- package/dist/commands/{ephemeral → sandbox}/env/set/index.d.ts +1 -4
- package/dist/commands/sandbox/env/set/index.js +72 -0
- package/dist/commands/{ephemeral → sandbox}/env/set_all/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/env/set_all/index.js +17 -35
- package/dist/commands/{ephemeral → sandbox}/get/index.d.ts +1 -4
- package/dist/commands/sandbox/get/index.js +61 -0
- package/dist/commands/sandbox/impersonate/index.d.ts +5 -0
- package/dist/commands/sandbox/impersonate/index.js +5 -0
- package/dist/commands/{ephemeral → sandbox}/license/get/index.d.ts +1 -4
- package/dist/commands/sandbox/license/get/index.js +76 -0
- package/dist/commands/{ephemeral → sandbox}/license/set/index.d.ts +1 -4
- package/dist/commands/{ephemeral → sandbox}/license/set/index.js +18 -36
- package/dist/commands/{ephemeral → sandbox}/pull/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/pull/index.js +11 -28
- package/dist/commands/{ephemeral → sandbox}/push/index.d.ts +2 -2
- package/dist/commands/{ephemeral → sandbox}/push/index.js +37 -31
- package/dist/commands/{ephemeral/delete → sandbox/reset}/index.d.ts +1 -5
- package/dist/commands/sandbox/reset/index.js +69 -0
- package/dist/commands/{ephemeral/impersonate → sandbox/review}/index.d.ts +1 -4
- package/dist/commands/{ephemeral/impersonate → sandbox/review}/index.js +15 -33
- package/dist/commands/{ephemeral/unit_test/run_all → sandbox/unit_test/list}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/unit_test/list/index.js +10 -26
- package/dist/commands/{ephemeral → sandbox}/unit_test/run/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/unit_test/run/index.js +9 -25
- package/dist/commands/{ephemeral/unit_test/list → sandbox/unit_test/run_all}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/unit_test/run_all/index.js +7 -23
- package/dist/commands/{ephemeral/workflow_test/get → sandbox/workflow_test/delete}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/delete/index.js +9 -25
- package/dist/commands/{ephemeral/workflow_test/run → sandbox/workflow_test/get}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/get/index.js +8 -27
- package/dist/commands/{ephemeral/workflow_test/run_all → sandbox/workflow_test/list}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/list/index.js +11 -27
- package/dist/commands/{ephemeral/workflow_test/delete → sandbox/workflow_test/run}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/run/index.js +9 -25
- package/dist/commands/{ephemeral/workflow_test/list → sandbox/workflow_test/run_all}/index.d.ts +1 -2
- package/dist/commands/{ephemeral → sandbox}/workflow_test/run_all/index.js +7 -23
- package/dist/commands/tenant/create/index.d.ts +2 -1
- package/dist/commands/tenant/create/index.js +23 -6
- package/dist/commands/tenant/get/index.js +2 -2
- package/dist/commands/tenant/list/index.js +2 -2
- package/dist/commands/tenant/push/index.js +0 -34
- package/dist/commands/workspace/edit/index.d.ts +1 -0
- package/dist/commands/workspace/edit/index.js +16 -6
- package/dist/commands/workspace/get/index.js +9 -7
- package/dist/commands/workspace/list/index.d.ts +1 -0
- package/dist/commands/workspace/list/index.js +14 -7
- package/dist/commands/workspace/push/index.d.ts +1 -0
- package/dist/commands/workspace/push/index.js +60 -6
- package/dist/utils/reference-checker.d.ts +45 -0
- package/dist/utils/reference-checker.js +137 -0
- package/oclif.manifest.json +2525 -2894
- package/package.json +8 -8
- package/dist/commands/ephemeral/access/index.d.ts +0 -15
- package/dist/commands/ephemeral/access/index.js +0 -78
- package/dist/commands/ephemeral/create/index.d.ts +0 -17
- package/dist/commands/ephemeral/create/index.js +0 -102
- package/dist/commands/ephemeral/delete/index.js +0 -99
- package/dist/commands/ephemeral/env/get/index.js +0 -81
- package/dist/commands/ephemeral/env/get_all/index.js +0 -94
- package/dist/commands/ephemeral/env/list/index.js +0 -83
- package/dist/commands/ephemeral/env/set/index.js +0 -90
- package/dist/commands/ephemeral/get/index.js +0 -102
- package/dist/commands/ephemeral/license/get/index.js +0 -94
- package/dist/commands/ephemeral/list/index.d.ts +0 -15
- package/dist/commands/ephemeral/list/index.js +0 -109
- package/dist/commands/ephemeral/shared/index.d.ts +0 -15
- package/dist/commands/ephemeral/shared/index.js +0 -108
|
@@ -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
|
|
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
|
|
14
|
+
static description = 'Pull documents from your sandbox environment and split into individual files';
|
|
15
15
|
static examples = [
|
|
16
|
-
`$ xano
|
|
17
|
-
Pulled 42 documents from
|
|
16
|
+
`$ xano sandbox pull ./my-sandbox
|
|
17
|
+
Pulled 42 documents from sandbox environment to ./my-sandbox
|
|
18
18
|
`,
|
|
19
|
-
`$ xano
|
|
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(
|
|
46
|
-
const
|
|
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/
|
|
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,8 +55,8 @@ 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
|
|
76
|
-
this.error(
|
|
58
|
+
const message = await this.parseApiError(response, 'API request failed');
|
|
59
|
+
this.error(message);
|
|
77
60
|
}
|
|
78
61
|
responseText = await response.text();
|
|
79
62
|
}
|
|
@@ -189,7 +172,7 @@ Pulled 42 documents from ephemeral tenant my-tenant to ./my-tenant
|
|
|
189
172
|
fs.writeFileSync(filePath, doc.content, 'utf8');
|
|
190
173
|
writtenCount++;
|
|
191
174
|
}
|
|
192
|
-
this.log(`Pulled ${writtenCount} documents from
|
|
175
|
+
this.log(`Pulled ${writtenCount} documents from sandbox environment to ${args.directory}`);
|
|
193
176
|
}
|
|
194
177
|
sanitizeFilename(name) {
|
|
195
178
|
return snakeCase(name.replaceAll('"', ''));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import BaseCommand from '../../../base-command.js';
|
|
2
|
-
export default class
|
|
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,5 @@ export default class EphemeralPush extends BaseCommand {
|
|
|
16
15
|
};
|
|
17
16
|
run(): Promise<void>;
|
|
18
17
|
private collectFiles;
|
|
18
|
+
private renderBadReferences;
|
|
19
19
|
}
|
|
@@ -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
|
-
|
|
4
|
+
import BaseCommand from '../../../base-command.js';
|
|
5
|
+
import { findFilesWithGuid } from '../../../utils/document-parser.js';
|
|
6
|
+
import { checkReferences } 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
|
|
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
|
|
14
|
+
static description = 'Push local documents to your sandbox environment via multidoc import';
|
|
14
15
|
static examples = [
|
|
15
|
-
`$ xano
|
|
16
|
-
Pushed 42 documents to
|
|
16
|
+
`$ xano sandbox push ./my-workspace
|
|
17
|
+
Pushed 42 documents to sandbox environment from ./my-workspace
|
|
17
18
|
`,
|
|
18
|
-
`$ xano
|
|
19
|
-
`$ xano
|
|
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(
|
|
52
|
-
const
|
|
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,11 @@ 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
|
+
}
|
|
86
75
|
const multidoc = documentEntries.map((d) => d.content).join('\n---\n');
|
|
87
76
|
const queryParams = new URLSearchParams({
|
|
88
77
|
env: flags.env.toString(),
|
|
@@ -90,7 +79,7 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
|
|
|
90
79
|
transaction: flags.transaction.toString(),
|
|
91
80
|
truncate: flags.truncate.toString(),
|
|
92
81
|
});
|
|
93
|
-
const apiUrl = `${profile.instance_origin}/api:meta/
|
|
82
|
+
const apiUrl = `${profile.instance_origin}/api:meta/sandbox/multidoc?${queryParams.toString()}`;
|
|
94
83
|
const startTime = Date.now();
|
|
95
84
|
try {
|
|
96
85
|
const response = await this.verboseFetch(apiUrl, {
|
|
@@ -115,6 +104,10 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
|
|
|
115
104
|
catch {
|
|
116
105
|
errorMessage += `\n${errorText}`;
|
|
117
106
|
}
|
|
107
|
+
// Provide guidance when sandbox access is denied (free plan restriction)
|
|
108
|
+
if (response.status === 500 && errorMessage.includes('Access Denied')) {
|
|
109
|
+
this.error('Sandbox is not available on the Free plan. Upgrade your plan to use sandbox features.');
|
|
110
|
+
}
|
|
118
111
|
const guidMatch = errorMessage.match(/Duplicate \w+ guid: (\S+)/);
|
|
119
112
|
if (guidMatch) {
|
|
120
113
|
const dupeFiles = findFilesWithGuid(documentEntries, guidMatch[1]);
|
|
@@ -139,7 +132,7 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
|
|
|
139
132
|
}
|
|
140
133
|
}
|
|
141
134
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
142
|
-
this.log(`Pushed ${documentEntries.length} documents to
|
|
135
|
+
this.log(`Pushed ${documentEntries.length} documents to sandbox environment from ${args.directory} in ${elapsed}s`);
|
|
143
136
|
}
|
|
144
137
|
collectFiles(dir) {
|
|
145
138
|
const files = [];
|
|
@@ -155,4 +148,17 @@ Pushed 42 documents to ephemeral tenant my-tenant from ./my-workspace
|
|
|
155
148
|
}
|
|
156
149
|
return files.sort();
|
|
157
150
|
}
|
|
151
|
+
renderBadReferences(badRefs) {
|
|
152
|
+
this.log('');
|
|
153
|
+
this.log(ux.colorize('yellow', ux.colorize('bold', '=== Unresolved References ===')));
|
|
154
|
+
this.log('');
|
|
155
|
+
this.log(ux.colorize('yellow', "The following references point to objects that don't exist in this push or on the server."));
|
|
156
|
+
this.log(ux.colorize('yellow', 'These will become placeholder statements after import.'));
|
|
157
|
+
this.log('');
|
|
158
|
+
for (const ref of badRefs) {
|
|
159
|
+
this.log(` ${ux.colorize('yellow', 'WARNING'.padEnd(16))} ${ref.sourceType.padEnd(18)} ${ref.source}`);
|
|
160
|
+
this.log(` ${' '.repeat(16)} ${' '.repeat(18)} ${ux.colorize('dim', `${ref.statementType} → ${ref.targetType} "${ref.target}" does not exist`)}`);
|
|
161
|
+
}
|
|
162
|
+
this.log('');
|
|
163
|
+
}
|
|
158
164
|
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import BaseCommand from '../../../base-command.js';
|
|
2
|
-
export default class
|
|
3
|
-
static args: {
|
|
4
|
-
tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
-
};
|
|
2
|
+
export default class SandboxReset extends BaseCommand {
|
|
6
3
|
static description: string;
|
|
7
4
|
static examples: string[];
|
|
8
5
|
static flags: {
|
|
9
6
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
7
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
8
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
9
|
};
|
|
@@ -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 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) {
|
|
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
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import BaseCommand from '../../../base-command.js';
|
|
2
|
-
export default class
|
|
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 {
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
2
|
import open from 'open';
|
|
3
3
|
import BaseCommand from '../../../base-command.js';
|
|
4
|
-
export default class
|
|
5
|
-
static
|
|
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
|
|
7
|
+
`$ xano sandbox review
|
|
14
8
|
Opening browser...
|
|
15
|
-
|
|
9
|
+
Review session started!
|
|
16
10
|
`,
|
|
17
|
-
`$ xano
|
|
18
|
-
`$ xano
|
|
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 {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
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
|
|
62
|
-
this.error(
|
|
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,17 @@ Impersonation successful!
|
|
|
80
62
|
else {
|
|
81
63
|
this.log('Opening browser...');
|
|
82
64
|
await open(impersonateUrl);
|
|
83
|
-
this.log('
|
|
65
|
+
this.log('Review session started!');
|
|
84
66
|
}
|
|
85
67
|
}
|
|
86
68
|
process.exit(0);
|
|
87
69
|
}
|
|
88
70
|
catch (error) {
|
|
89
71
|
if (error instanceof Error) {
|
|
90
|
-
this.error(`Failed to
|
|
72
|
+
this.error(`Failed to open sandbox review: ${error.message}`);
|
|
91
73
|
}
|
|
92
74
|
else {
|
|
93
|
-
this.error(`Failed to
|
|
75
|
+
this.error(`Failed to open sandbox review: ${String(error)}`);
|
|
94
76
|
}
|
|
95
77
|
}
|
|
96
78
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class
|
|
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
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class
|
|
4
|
-
static description = 'List all unit tests for
|
|
3
|
+
export default class SandboxUnitTestList extends BaseCommand {
|
|
4
|
+
static description = 'List all unit tests for a sandbox environment';
|
|
5
5
|
static examples = [
|
|
6
|
-
`$ xano
|
|
6
|
+
`$ xano sandbox unit-test list
|
|
7
7
|
Unit tests:
|
|
8
8
|
- my-test (ID: abc-123) [function: math]
|
|
9
9
|
`,
|
|
10
|
-
`$ xano
|
|
10
|
+
`$ xano sandbox unit-test list -o json`,
|
|
11
11
|
];
|
|
12
12
|
static flags = {
|
|
13
13
|
...BaseCommand.baseFlags,
|
|
@@ -28,33 +28,17 @@ Unit tests:
|
|
|
28
28
|
options: ['summary', 'json'],
|
|
29
29
|
required: false,
|
|
30
30
|
}),
|
|
31
|
-
tenant: Flags.string({
|
|
32
|
-
char: 't',
|
|
33
|
-
description: 'Ephemeral tenant name',
|
|
34
|
-
required: true,
|
|
35
|
-
}),
|
|
36
31
|
};
|
|
37
32
|
async run() {
|
|
38
|
-
const { flags } = await this.parse(
|
|
39
|
-
const
|
|
40
|
-
const credentials = this.loadCredentialsFile();
|
|
41
|
-
if (!credentials || !(profileName in credentials.profiles)) {
|
|
42
|
-
this.error(`Profile '${profileName}' not found.\nCreate a profile using 'xano profile create'`);
|
|
43
|
-
}
|
|
44
|
-
const profile = credentials.profiles[profileName];
|
|
45
|
-
if (!profile.instance_origin) {
|
|
46
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
47
|
-
}
|
|
48
|
-
if (!profile.access_token) {
|
|
49
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
50
|
-
}
|
|
33
|
+
const { flags } = await this.parse(SandboxUnitTestList);
|
|
34
|
+
const { profile } = this.resolveProfile(flags);
|
|
51
35
|
const params = new URLSearchParams();
|
|
52
36
|
params.set('per_page', '10000');
|
|
53
37
|
if (flags.branch)
|
|
54
38
|
params.set('branch', flags.branch);
|
|
55
39
|
if (flags['obj-type'])
|
|
56
40
|
params.set('obj_type', flags['obj-type']);
|
|
57
|
-
const apiUrl = `${profile.instance_origin}/api:meta/
|
|
41
|
+
const apiUrl = `${profile.instance_origin}/api:meta/sandbox/unit_test?${params}`;
|
|
58
42
|
try {
|
|
59
43
|
const response = await this.verboseFetch(apiUrl, {
|
|
60
44
|
headers: {
|
|
@@ -64,8 +48,8 @@ Unit tests:
|
|
|
64
48
|
method: 'GET',
|
|
65
49
|
}, flags.verbose, profile.access_token);
|
|
66
50
|
if (!response.ok) {
|
|
67
|
-
const
|
|
68
|
-
this.error(
|
|
51
|
+
const message = await this.parseApiError(response, 'API request failed');
|
|
52
|
+
this.error(message);
|
|
69
53
|
}
|
|
70
54
|
const data = (await response.json());
|
|
71
55
|
let tests;
|
|
@@ -86,7 +70,7 @@ Unit tests:
|
|
|
86
70
|
this.log('No unit tests found');
|
|
87
71
|
}
|
|
88
72
|
else {
|
|
89
|
-
this.log(`Unit tests for
|
|
73
|
+
this.log(`Unit tests for sandbox environment:`);
|
|
90
74
|
for (const test of tests) {
|
|
91
75
|
this.log(` - ${test.name} (ID: ${test.id}) [${test.obj_type}: ${test.obj_name}]`);
|
|
92
76
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class
|
|
2
|
+
export default class SandboxUnitTestRun extends BaseCommand {
|
|
3
3
|
static args: {
|
|
4
4
|
unit_test_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
5
|
};
|
|
@@ -7,7 +7,6 @@ export default class EphemeralUnitTestRun extends BaseCommand {
|
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
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
10
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
11
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
12
|
};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class
|
|
3
|
+
export default class SandboxUnitTestRun extends BaseCommand {
|
|
4
4
|
static args = {
|
|
5
5
|
unit_test_id: Args.string({
|
|
6
6
|
description: 'ID of the unit test to run',
|
|
7
7
|
required: true,
|
|
8
8
|
}),
|
|
9
9
|
};
|
|
10
|
-
static description = 'Run a unit test for
|
|
10
|
+
static description = 'Run a unit test for a sandbox environment';
|
|
11
11
|
static examples = [
|
|
12
|
-
`$ xano
|
|
12
|
+
`$ xano sandbox unit-test run abc-123
|
|
13
13
|
Running unit test abc-123...
|
|
14
14
|
Result: PASS
|
|
15
15
|
`,
|
|
16
|
-
`$ xano
|
|
16
|
+
`$ xano sandbox unit-test run abc-123 -o json`,
|
|
17
17
|
];
|
|
18
18
|
static flags = {
|
|
19
19
|
...BaseCommand.baseFlags,
|
|
@@ -24,27 +24,11 @@ Result: PASS
|
|
|
24
24
|
options: ['summary', 'json'],
|
|
25
25
|
required: false,
|
|
26
26
|
}),
|
|
27
|
-
tenant: Flags.string({
|
|
28
|
-
char: 't',
|
|
29
|
-
description: 'Ephemeral tenant name',
|
|
30
|
-
required: true,
|
|
31
|
-
}),
|
|
32
27
|
};
|
|
33
28
|
async run() {
|
|
34
|
-
const { args, flags } = await this.parse(
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
if (!credentials || !(profileName in credentials.profiles)) {
|
|
38
|
-
this.error(`Profile '${profileName}' not found.\nCreate a profile using 'xano profile create'`);
|
|
39
|
-
}
|
|
40
|
-
const profile = credentials.profiles[profileName];
|
|
41
|
-
if (!profile.instance_origin) {
|
|
42
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
43
|
-
}
|
|
44
|
-
if (!profile.access_token) {
|
|
45
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
46
|
-
}
|
|
47
|
-
const apiUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${encodeURIComponent(flags.tenant)}/unit_test/${encodeURIComponent(args.unit_test_id)}/run`;
|
|
29
|
+
const { args, flags } = await this.parse(SandboxUnitTestRun);
|
|
30
|
+
const { profile } = this.resolveProfile(flags);
|
|
31
|
+
const apiUrl = `${profile.instance_origin}/api:meta/sandbox/unit_test/${encodeURIComponent(args.unit_test_id)}/run`;
|
|
48
32
|
try {
|
|
49
33
|
if (flags.output === 'summary') {
|
|
50
34
|
this.log(`Running unit test ${args.unit_test_id}...`);
|
|
@@ -58,8 +42,8 @@ Result: PASS
|
|
|
58
42
|
method: 'POST',
|
|
59
43
|
}, flags.verbose, profile.access_token);
|
|
60
44
|
if (!response.ok) {
|
|
61
|
-
const
|
|
62
|
-
this.error(
|
|
45
|
+
const message = await this.parseApiError(response, 'API request failed');
|
|
46
|
+
this.error(message);
|
|
63
47
|
}
|
|
64
48
|
const result = (await response.json());
|
|
65
49
|
if (flags.output === 'json') {
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import BaseCommand from '../../../../base-command.js';
|
|
2
|
-
export default class
|
|
2
|
+
export default class SandboxUnitTestRunAll 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
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
-
export default class
|
|
4
|
-
static description = 'Run all unit tests for
|
|
3
|
+
export default class SandboxUnitTestRunAll extends BaseCommand {
|
|
4
|
+
static description = 'Run all unit tests for a sandbox environment';
|
|
5
5
|
static examples = [
|
|
6
|
-
`$ xano
|
|
6
|
+
`$ xano sandbox unit-test run-all
|
|
7
7
|
Running 5 unit tests...
|
|
8
8
|
|
|
9
9
|
PASS my-test [function: math]
|
|
@@ -12,7 +12,7 @@ FAIL data-validation [function: validate]
|
|
|
12
12
|
|
|
13
13
|
Results: 4 passed, 1 failed
|
|
14
14
|
`,
|
|
15
|
-
`$ xano
|
|
15
|
+
`$ xano sandbox unit-test run-all -o json`,
|
|
16
16
|
];
|
|
17
17
|
static flags = {
|
|
18
18
|
...BaseCommand.baseFlags,
|
|
@@ -33,27 +33,11 @@ Results: 4 passed, 1 failed
|
|
|
33
33
|
options: ['summary', 'json'],
|
|
34
34
|
required: false,
|
|
35
35
|
}),
|
|
36
|
-
tenant: Flags.string({
|
|
37
|
-
char: 't',
|
|
38
|
-
description: 'Ephemeral tenant name',
|
|
39
|
-
required: true,
|
|
40
|
-
}),
|
|
41
36
|
};
|
|
42
37
|
async run() {
|
|
43
|
-
const { flags } = await this.parse(
|
|
44
|
-
const
|
|
45
|
-
const
|
|
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 baseUrl = `${profile.instance_origin}/api:meta/ephemeral/tenant/${encodeURIComponent(flags.tenant)}/unit_test`;
|
|
38
|
+
const { flags } = await this.parse(SandboxUnitTestRunAll);
|
|
39
|
+
const { profile } = this.resolveProfile(flags);
|
|
40
|
+
const baseUrl = `${profile.instance_origin}/api:meta/sandbox/unit_test`;
|
|
57
41
|
try {
|
|
58
42
|
// Step 1: List all unit tests
|
|
59
43
|
const listParams = new URLSearchParams();
|