@xano/cli 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -4
- package/dist/base-command.d.ts +43 -36
- package/dist/base-command.js +223 -160
- package/dist/commands/auth/index.d.ts +1 -1
- package/dist/commands/auth/index.js +7 -7
- package/dist/commands/branch/create/index.d.ts +1 -1
- package/dist/commands/branch/create/index.js +1 -1
- package/dist/commands/ephemeral/create/index.d.ts +18 -0
- package/dist/commands/ephemeral/create/index.js +103 -0
- package/dist/commands/ephemeral/delete/index.d.ts +18 -0
- package/dist/commands/ephemeral/delete/index.js +99 -0
- package/dist/commands/ephemeral/edit/index.d.ts +18 -0
- package/dist/commands/ephemeral/edit/index.js +109 -0
- package/dist/commands/ephemeral/get/index.d.ts +21 -0
- package/dist/commands/ephemeral/get/index.js +125 -0
- package/dist/commands/ephemeral/impersonate/index.d.ts +20 -0
- package/dist/commands/ephemeral/impersonate/index.js +120 -0
- package/dist/commands/ephemeral/list/index.d.ts +19 -0
- package/dist/commands/ephemeral/list/index.js +123 -0
- package/dist/commands/ephemeral/pull/index.d.ts +19 -0
- package/dist/commands/ephemeral/pull/index.js +163 -0
- package/dist/commands/ephemeral/push/index.d.ts +35 -0
- package/dist/commands/ephemeral/push/index.js +223 -0
- package/dist/commands/ephemeral/static_host/build/delete/index.d.ts +20 -0
- package/dist/commands/ephemeral/static_host/build/delete/index.js +121 -0
- package/dist/commands/ephemeral/static_host/build/get/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/build/get/index.js +137 -0
- package/dist/commands/ephemeral/static_host/build/list/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/build/list/index.js +137 -0
- package/dist/commands/ephemeral/static_host/build/pull/index.d.ts +53 -0
- package/dist/commands/ephemeral/static_host/build/pull/index.js +308 -0
- package/dist/commands/ephemeral/static_host/build/push/index.d.ts +24 -0
- package/dist/commands/ephemeral/static_host/build/push/index.js +250 -0
- package/dist/commands/ephemeral/static_host/create/index.d.ts +18 -0
- package/dist/commands/ephemeral/static_host/create/index.js +92 -0
- package/dist/commands/ephemeral/static_host/deploy/index.d.ts +19 -0
- package/dist/commands/ephemeral/static_host/deploy/index.js +112 -0
- package/dist/commands/ephemeral/static_host/edit/index.d.ts +24 -0
- package/dist/commands/ephemeral/static_host/edit/index.js +158 -0
- package/dist/commands/ephemeral/static_host/get/index.d.ts +19 -0
- package/dist/commands/ephemeral/static_host/get/index.js +101 -0
- package/dist/commands/ephemeral/static_host/list/index.d.ts +17 -0
- package/dist/commands/ephemeral/static_host/list/index.js +132 -0
- package/dist/commands/flatten/index.d.ts +34 -0
- package/dist/commands/flatten/index.js +140 -0
- package/dist/commands/function/create/index.js +1 -1
- package/dist/commands/function/edit/index.js +26 -32
- package/dist/commands/function/get/index.js +2 -3
- package/dist/commands/function/run/index.d.ts +1 -0
- package/dist/commands/function/run/index.js +19 -6
- package/dist/commands/profile/create/index.d.ts +1 -1
- package/dist/commands/profile/create/index.js +7 -7
- package/dist/commands/profile/me/index.js +1 -1
- package/dist/commands/profile/use/index.js +0 -4
- package/dist/commands/profile/wizard/index.d.ts +1 -1
- package/dist/commands/profile/wizard/index.js +12 -10
- package/dist/commands/profile/workspace/set/index.js +1 -1
- package/dist/commands/release/edit/index.js +2 -2
- package/dist/commands/release/export/index.js +1 -2
- package/dist/commands/release/import/index.js +1 -1
- package/dist/commands/release/pull/index.d.ts +0 -6
- package/dist/commands/release/pull/index.js +13 -99
- package/dist/commands/release/push/index.js +2 -2
- package/dist/commands/sandbox/env/get_all/index.js +1 -1
- package/dist/commands/sandbox/env/set_all/index.js +1 -1
- package/dist/commands/sandbox/get/index.js +1 -1
- package/dist/commands/sandbox/license/get/index.js +1 -1
- package/dist/commands/sandbox/license/set/index.js +1 -1
- package/dist/commands/sandbox/pull/index.d.ts +0 -1
- package/dist/commands/sandbox/pull/index.js +13 -81
- package/dist/commands/sandbox/push/index.d.ts +1 -1
- package/dist/commands/sandbox/push/index.js +5 -5
- package/dist/commands/sandbox/unit_test/run/index.js +17 -11
- package/dist/commands/sandbox/unit_test/run_all/index.js +9 -1
- package/dist/commands/sandbox/workflow_test/run/index.js +12 -9
- package/dist/commands/sandbox/workflow_test/run_all/index.js +9 -1
- package/dist/commands/static_host/build/create/index.d.ts +1 -1
- package/dist/commands/static_host/build/create/index.js +2 -2
- package/dist/commands/static_host/build/pull/index.js +1 -1
- package/dist/commands/static_host/build/push/index.js +1 -1
- package/dist/commands/static_host/edit/index.js +1 -1
- package/dist/commands/tenant/backup/export/index.js +1 -2
- package/dist/commands/tenant/backup/import/index.js +1 -1
- package/dist/commands/tenant/cluster/create/index.js +1 -1
- package/dist/commands/tenant/cluster/license/get/index.js +1 -1
- package/dist/commands/tenant/cluster/license/set/index.js +1 -1
- package/dist/commands/tenant/create/index.d.ts +1 -1
- package/dist/commands/tenant/create/index.js +6 -6
- package/dist/commands/tenant/deploy_platform/index.js +1 -1
- package/dist/commands/tenant/edit/index.js +7 -7
- package/dist/commands/tenant/env/get_all/index.js +1 -1
- package/dist/commands/tenant/env/set_all/index.js +1 -1
- package/dist/commands/tenant/impersonate/index.d.ts +1 -1
- package/dist/commands/tenant/impersonate/index.js +14 -14
- package/dist/commands/tenant/license/get/index.js +1 -1
- package/dist/commands/tenant/license/set/index.js +1 -1
- package/dist/commands/tenant/pull/index.d.ts +0 -6
- package/dist/commands/tenant/pull/index.js +14 -100
- package/dist/commands/tenant/unit_test/run/index.js +19 -11
- package/dist/commands/tenant/unit_test/run_all/index.js +13 -1
- package/dist/commands/tenant/workflow_test/run/index.js +14 -9
- package/dist/commands/tenant/workflow_test/run_all/index.js +13 -1
- package/dist/commands/unit_test/run/index.js +19 -11
- package/dist/commands/unit_test/run_all/index.js +13 -1
- package/dist/commands/workflow_test/run/index.js +15 -10
- package/dist/commands/workflow_test/run_all/index.js +15 -3
- package/dist/commands/workspace/git/pull/index.d.ts +4 -4
- package/dist/commands/workspace/git/pull/index.js +158 -97
- package/dist/commands/workspace/pull/index.d.ts +1 -7
- package/dist/commands/workspace/pull/index.js +17 -103
- package/dist/help.js +4 -2
- package/dist/update-check.d.ts +1 -1
- package/dist/update-check.js +1 -1
- package/dist/utils/document-parser.d.ts +116 -0
- package/dist/utils/document-parser.js +311 -4
- package/dist/utils/flatten.d.ts +36 -0
- package/dist/utils/flatten.js +68 -0
- package/dist/utils/knowledge-sync.d.ts +1 -0
- package/dist/utils/knowledge-sync.js +11 -6
- package/dist/utils/microservice-wait.d.ts +53 -0
- package/dist/utils/microservice-wait.js +84 -0
- package/dist/utils/multidoc-push.d.ts +88 -0
- package/dist/utils/multidoc-push.js +378 -15
- package/dist/utils/paging.d.ts +2 -1
- package/dist/utils/paging.js +0 -1
- package/dist/utils/reference-checker.js +14 -3
- package/oclif.manifest.json +5898 -3804
- package/package.json +10 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
export default class TenantClusterCreate extends BaseCommand {
|
|
6
6
|
static description = 'Create a new tenant cluster';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../../base-command.js';
|
|
5
5
|
export default class TenantClusterLicenseGet extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../../base-command.js';
|
|
5
5
|
export default class TenantClusterLicenseSet extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -13,8 +13,8 @@ export default class TenantCreate extends BaseCommand {
|
|
|
13
13
|
license: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
14
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
15
|
platform_id: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
-
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
16
|
tasks: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
18
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
19
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
20
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as yaml from 'js-yaml';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import BaseCommand from '../../../base-command.js';
|
|
6
6
|
export default class TenantCreate extends BaseCommand {
|
|
7
7
|
static args = {
|
|
@@ -54,17 +54,17 @@ Created tenant: Production (production) - ID: 42
|
|
|
54
54
|
description: 'Platform ID to use',
|
|
55
55
|
required: false,
|
|
56
56
|
}),
|
|
57
|
+
tasks: Flags.boolean({
|
|
58
|
+
allowNo: true,
|
|
59
|
+
default: true,
|
|
60
|
+
description: 'Enable background tasks',
|
|
61
|
+
}),
|
|
57
62
|
type: Flags.string({
|
|
58
63
|
default: 'tier1',
|
|
59
64
|
description: 'Tenant type',
|
|
60
65
|
options: ['tier1', 'tier2', 'tier3'],
|
|
61
66
|
required: false,
|
|
62
67
|
}),
|
|
63
|
-
tasks: Flags.boolean({
|
|
64
|
-
allowNo: true,
|
|
65
|
-
default: true,
|
|
66
|
-
description: 'Enable background tasks',
|
|
67
|
-
}),
|
|
68
68
|
workspace: Flags.string({
|
|
69
69
|
char: 'w',
|
|
70
70
|
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../base-command.js';
|
|
5
5
|
export default class TenantDeployPlatform extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -91,16 +91,16 @@ Updated tenant: New Name (my-tenant) - ID: 42
|
|
|
91
91
|
const current = await getResponse.json();
|
|
92
92
|
// Merge in user-provided values
|
|
93
93
|
const body = {
|
|
94
|
-
description: flags.description
|
|
95
|
-
display: flags.display
|
|
96
|
-
domain: flags.domain
|
|
97
|
-
ingress: flags.ingress
|
|
98
|
-
proxy: flags.proxy
|
|
94
|
+
description: flags.description === undefined ? (current.description ?? '') : flags.description,
|
|
95
|
+
display: flags.display === undefined ? (current.display ?? current.name) : flags.display,
|
|
96
|
+
domain: flags.domain === undefined ? (current.domain ?? '') : flags.domain,
|
|
97
|
+
ingress: flags.ingress === undefined ? (current.ingress ?? true) : flags.ingress,
|
|
98
|
+
proxy: flags.proxy === undefined ? (current.proxy ?? '') : flags.proxy,
|
|
99
99
|
rbac: {
|
|
100
|
-
enabled: flags.rbac
|
|
100
|
+
enabled: flags.rbac === undefined ? (current.rbac?.enabled ?? false) : flags.rbac,
|
|
101
101
|
},
|
|
102
102
|
tag: current.tag ?? [],
|
|
103
|
-
tasks: flags.tasks
|
|
103
|
+
tasks: flags.tasks === undefined ? (current.tasks ?? true) : flags.tasks,
|
|
104
104
|
};
|
|
105
105
|
// Update tenant
|
|
106
106
|
const putResponse = await this.verboseFetch(baseUrl, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as yaml from 'js-yaml';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import BaseCommand from '../../../../base-command.js';
|
|
6
6
|
export default class TenantEnvGetAll extends BaseCommand {
|
|
7
7
|
static args = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as yaml from 'js-yaml';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import BaseCommand from '../../../../base-command.js';
|
|
6
6
|
export default class TenantEnvSetAll extends BaseCommand {
|
|
7
7
|
static args = {
|
|
@@ -76,13 +76,26 @@ Impersonation successful!
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
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
|
+
}
|
|
79
92
|
async getImpersonateResponse(profile, workspaceId, tenantName) {
|
|
80
93
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${encodeURIComponent(tenantName)}/impersonate`;
|
|
81
94
|
const { verbose } = await this.parse(TenantImpersonate).then((r) => r.flags);
|
|
82
95
|
const response = await this.verboseFetch(apiUrl, {
|
|
83
96
|
headers: {
|
|
84
|
-
Authorization: `Bearer ${profile.access_token}`,
|
|
85
97
|
accept: 'application/json',
|
|
98
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
86
99
|
},
|
|
87
100
|
method: 'GET',
|
|
88
101
|
}, verbose, profile.access_token);
|
|
@@ -96,17 +109,4 @@ Impersonation successful!
|
|
|
96
109
|
}
|
|
97
110
|
return result;
|
|
98
111
|
}
|
|
99
|
-
getFrontendUrl(instanceOrigin) {
|
|
100
|
-
try {
|
|
101
|
-
const url = new URL(instanceOrigin);
|
|
102
|
-
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
|
103
|
-
url.port = '4200';
|
|
104
|
-
return url.origin;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
// fall through
|
|
109
|
-
}
|
|
110
|
-
return instanceOrigin;
|
|
111
|
-
}
|
|
112
112
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
export default class TenantLicenseGet extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
5
|
export default class TenantLicenseSet extends BaseCommand {
|
|
6
6
|
static args = {
|
|
@@ -14,10 +14,4 @@ export default class Pull extends BaseCommand {
|
|
|
14
14
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
15
|
};
|
|
16
16
|
run(): Promise<void>;
|
|
17
|
-
/**
|
|
18
|
-
* Sanitize a document name for use as a filename.
|
|
19
|
-
* Strips quotes, replaces spaces with underscores, and removes
|
|
20
|
-
* characters that are unsafe in filenames.
|
|
21
|
-
*/
|
|
22
|
-
private sanitizeFilename;
|
|
23
17
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
2
|
import snakeCase from 'lodash.snakecase';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
5
|
import BaseCommand from '../../../base-command.js';
|
|
6
|
-
import { buildApiGroupFolderResolver, parseDocument } from '../../../utils/document-parser.js';
|
|
6
|
+
import { buildApiGroupFolderResolver, buildChannelServerResolver, parseDocument, resolveDocumentPath, } from '../../../utils/document-parser.js';
|
|
7
7
|
export default class Pull extends BaseCommand {
|
|
8
8
|
static description = 'Pull a tenant multidoc from the Xano Metadata API and split into individual files';
|
|
9
9
|
static examples = [
|
|
@@ -57,7 +57,7 @@ Pulled 58 documents from tenant my-tenant
|
|
|
57
57
|
};
|
|
58
58
|
async run() {
|
|
59
59
|
const { flags } = await this.parse(Pull);
|
|
60
|
-
const {
|
|
60
|
+
const { profile, profileName } = this.resolveProfile(flags);
|
|
61
61
|
// Determine workspace_id from flag or profile
|
|
62
62
|
let workspaceId;
|
|
63
63
|
if (flags.workspace) {
|
|
@@ -129,96 +129,19 @@ Pulled 58 documents from tenant my-tenant
|
|
|
129
129
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
130
130
|
// Resolve api_group names to unique folder names, disambiguating collisions
|
|
131
131
|
const getApiGroupFolder = buildApiGroupFolderResolver(documents, snakeCase);
|
|
132
|
+
// Resolve a realtime v2 channel path -> owning realtime_server name, so
|
|
133
|
+
// messages can nest under their channel's server (see resolver docs).
|
|
134
|
+
const getChannelServer = buildChannelServerResolver(documents);
|
|
132
135
|
// Track filenames per type to handle duplicates
|
|
133
136
|
const filenameCounters = new Map();
|
|
134
137
|
let writtenCount = 0;
|
|
135
138
|
for (const doc of documents) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
else if (doc.type === 'workspace_trigger') {
|
|
144
|
-
// workspace_trigger → workspace/trigger/{name}.xs
|
|
145
|
-
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
146
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
147
|
-
}
|
|
148
|
-
else if (doc.type === 'error_trigger') {
|
|
149
|
-
// error_trigger → workspace/trigger/{name}.xs (singleton, colocated with workspace triggers)
|
|
150
|
-
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
151
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
152
|
-
}
|
|
153
|
-
else if (doc.type === 'agent') {
|
|
154
|
-
// agent → ai/agent/{name}.xs
|
|
155
|
-
typeDir = path.join(outputDir, 'ai', 'agent');
|
|
156
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
157
|
-
}
|
|
158
|
-
else if (doc.type === 'mcp_server') {
|
|
159
|
-
// mcp_server → ai/mcp_server/{name}.xs
|
|
160
|
-
typeDir = path.join(outputDir, 'ai', 'mcp_server');
|
|
161
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
162
|
-
}
|
|
163
|
-
else if (doc.type === 'tool') {
|
|
164
|
-
// tool → ai/tool/{name}.xs
|
|
165
|
-
typeDir = path.join(outputDir, 'ai', 'tool');
|
|
166
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
167
|
-
}
|
|
168
|
-
else if (doc.type === 'agent_trigger') {
|
|
169
|
-
// agent_trigger → ai/agent/trigger/{name}.xs
|
|
170
|
-
typeDir = path.join(outputDir, 'ai', 'agent', 'trigger');
|
|
171
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
172
|
-
}
|
|
173
|
-
else if (doc.type === 'mcp_server_trigger') {
|
|
174
|
-
// mcp_server_trigger → ai/mcp_server/trigger/{name}.xs
|
|
175
|
-
typeDir = path.join(outputDir, 'ai', 'mcp_server', 'trigger');
|
|
176
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
177
|
-
}
|
|
178
|
-
else if (doc.type === 'table_trigger') {
|
|
179
|
-
// table_trigger → table/trigger/{name}.xs
|
|
180
|
-
typeDir = path.join(outputDir, 'table', 'trigger');
|
|
181
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
182
|
-
}
|
|
183
|
-
else if (doc.type === 'realtime_channel') {
|
|
184
|
-
// realtime_channel → realtime/channel/{name}.xs
|
|
185
|
-
typeDir = path.join(outputDir, 'realtime', 'channel');
|
|
186
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
187
|
-
}
|
|
188
|
-
else if (doc.type === 'realtime_trigger') {
|
|
189
|
-
// realtime_trigger → realtime/trigger/{name}.xs
|
|
190
|
-
typeDir = path.join(outputDir, 'realtime', 'trigger');
|
|
191
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
192
|
-
}
|
|
193
|
-
else if (doc.type === 'api_group') {
|
|
194
|
-
// api_group "test" → api/{resolved_folder}/{name}.xs
|
|
195
|
-
const groupFolder = getApiGroupFolder(doc.name);
|
|
196
|
-
typeDir = path.join(outputDir, 'api', groupFolder);
|
|
197
|
-
baseName = this.sanitizeFilename(doc.name);
|
|
198
|
-
}
|
|
199
|
-
else if (doc.type === 'query' && doc.apiGroup) {
|
|
200
|
-
// query in group "test" → api/{resolved_folder}/{query_name}.xs
|
|
201
|
-
const groupFolder = getApiGroupFolder(doc.apiGroup);
|
|
202
|
-
const nameParts = doc.name.split('/');
|
|
203
|
-
const leafName = nameParts.pop();
|
|
204
|
-
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
205
|
-
typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
|
|
206
|
-
baseName = this.sanitizeFilename(leafName);
|
|
207
|
-
if (doc.verb) {
|
|
208
|
-
baseName = `${baseName}_${doc.verb}`;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
// Default: split folder path from name
|
|
213
|
-
const nameParts = doc.name.split('/');
|
|
214
|
-
const leafName = nameParts.pop();
|
|
215
|
-
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
216
|
-
typeDir = path.join(outputDir, doc.type, ...folderParts);
|
|
217
|
-
baseName = this.sanitizeFilename(leafName);
|
|
218
|
-
if (doc.verb) {
|
|
219
|
-
baseName = `${baseName}_${doc.verb}`;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
139
|
+
const { baseName, typeDir } = resolveDocumentPath(doc, outputDir, {
|
|
140
|
+
getApiGroupFolder,
|
|
141
|
+
getChannelServer,
|
|
142
|
+
join: path.join,
|
|
143
|
+
snakeCase,
|
|
144
|
+
});
|
|
222
145
|
fs.mkdirSync(typeDir, { recursive: true });
|
|
223
146
|
// Track duplicates per directory
|
|
224
147
|
const dirKey = path.relative(outputDir, typeDir);
|
|
@@ -229,20 +152,11 @@ Pulled 58 documents from tenant my-tenant
|
|
|
229
152
|
const count = typeCounters.get(baseName) || 0;
|
|
230
153
|
typeCounters.set(baseName, count + 1);
|
|
231
154
|
// Append numeric suffix for duplicates
|
|
232
|
-
|
|
233
|
-
filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
|
|
155
|
+
const filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
|
|
234
156
|
const filePath = path.join(typeDir, filename);
|
|
235
157
|
fs.writeFileSync(filePath, doc.content, 'utf8');
|
|
236
158
|
writtenCount++;
|
|
237
159
|
}
|
|
238
160
|
this.log(`Pulled ${writtenCount} documents from tenant ${tenantName} to ${flags.directory}`);
|
|
239
161
|
}
|
|
240
|
-
/**
|
|
241
|
-
* Sanitize a document name for use as a filename.
|
|
242
|
-
* Strips quotes, replaces spaces with underscores, and removes
|
|
243
|
-
* characters that are unsafe in filenames.
|
|
244
|
-
*/
|
|
245
|
-
sanitizeFilename(name) {
|
|
246
|
-
return snakeCase(name.replaceAll('"', ''));
|
|
247
|
-
}
|
|
248
162
|
}
|
|
@@ -68,26 +68,34 @@ Result: PASS
|
|
|
68
68
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
69
69
|
}
|
|
70
70
|
const result = (await response.json());
|
|
71
|
+
// Anything other than 'ok' is a failure -- the API has been observed
|
|
72
|
+
// returning several distinct non-ok statuses, so never enumerate them.
|
|
73
|
+
const passed = result.status === 'ok';
|
|
71
74
|
if (flags.output === 'json') {
|
|
72
75
|
this.log(JSON.stringify(result, null, 2));
|
|
73
76
|
}
|
|
77
|
+
else if (passed) {
|
|
78
|
+
this.log('Result: PASS');
|
|
79
|
+
}
|
|
74
80
|
else {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const failedExpects = result.results?.filter((r) => r.status === 'fail') ?? [];
|
|
81
|
-
for (const expect of failedExpects) {
|
|
82
|
-
if (expect.message) {
|
|
83
|
-
this.log(` Error: ${expect.message}`);
|
|
84
|
-
}
|
|
81
|
+
this.log('Result: FAIL');
|
|
82
|
+
const failedExpects = result.results?.filter((r) => r.status === 'fail') ?? [];
|
|
83
|
+
for (const expect of failedExpects) {
|
|
84
|
+
if (expect.message) {
|
|
85
|
+
this.log(` Error: ${expect.message}`);
|
|
85
86
|
}
|
|
86
|
-
this.exit(1);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
// Set the code rather than throwing via this.exit(): the exit must apply
|
|
90
|
+
// in both output modes, and process.exitCode cannot be swallowed by the
|
|
91
|
+
// surrounding catch. Mirrors what run_all already does.
|
|
92
|
+
if (!passed) {
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
}
|
|
89
95
|
}
|
|
90
96
|
catch (error) {
|
|
97
|
+
if (error instanceof Error && 'oclif' in error)
|
|
98
|
+
throw error;
|
|
91
99
|
if (error instanceof Error) {
|
|
92
100
|
this.error(`Failed to run unit test: ${error.message}`);
|
|
93
101
|
}
|
|
@@ -95,7 +95,15 @@ Results: 4 passed, 1 failed
|
|
|
95
95
|
},
|
|
96
96
|
});
|
|
97
97
|
if (tests.length === 0) {
|
|
98
|
-
|
|
98
|
+
// An empty suite is a normal state (fresh branches have no tests) and
|
|
99
|
+
// is a success, not a failure. Under -o json it must still be parseable,
|
|
100
|
+
// with the same keys the populated path emits below.
|
|
101
|
+
if (flags.output === 'json') {
|
|
102
|
+
this.log(JSON.stringify({ failed: 0, passed: 0, results: [] }, null, 2));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.log('No unit tests found');
|
|
106
|
+
}
|
|
99
107
|
return;
|
|
100
108
|
}
|
|
101
109
|
if (flags.output === 'summary') {
|
|
@@ -162,6 +170,8 @@ Results: 4 passed, 1 failed
|
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
172
|
catch (error) {
|
|
173
|
+
if (error instanceof Error && 'oclif' in error)
|
|
174
|
+
throw error;
|
|
165
175
|
const message = error instanceof Error ? error.message : String(error);
|
|
166
176
|
results.push({
|
|
167
177
|
message,
|
|
@@ -202,6 +212,8 @@ Results: 4 passed, 1 failed
|
|
|
202
212
|
}
|
|
203
213
|
}
|
|
204
214
|
catch (error) {
|
|
215
|
+
if (error instanceof Error && 'oclif' in error)
|
|
216
|
+
throw error;
|
|
205
217
|
if (error instanceof Error) {
|
|
206
218
|
this.error(`Failed to run unit tests: ${error.message}`);
|
|
207
219
|
}
|
|
@@ -61,24 +61,29 @@ Result: PASS (0.25s)
|
|
|
61
61
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
62
62
|
}
|
|
63
63
|
const result = (await response.json());
|
|
64
|
+
// Anything other than 'ok' is a failure -- the API has been observed
|
|
65
|
+
// returning several distinct non-ok statuses, so never enumerate them.
|
|
66
|
+
const passed = result.status === 'ok';
|
|
64
67
|
if (flags.output === 'json') {
|
|
65
68
|
this.log(JSON.stringify(result, null, 2));
|
|
66
69
|
}
|
|
67
70
|
else {
|
|
68
71
|
const timing = result.timing ? ` (${result.timing}s)` : '';
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
else {
|
|
73
|
-
this.log(`Result: FAIL${timing}`);
|
|
74
|
-
if (result.message) {
|
|
75
|
-
this.log(` Error: ${result.message}`);
|
|
76
|
-
}
|
|
77
|
-
this.exit(1);
|
|
72
|
+
this.log(`Result: ${passed ? 'PASS' : 'FAIL'}${timing}`);
|
|
73
|
+
if (!passed && result.message) {
|
|
74
|
+
this.log(` Error: ${result.message}`);
|
|
78
75
|
}
|
|
79
76
|
}
|
|
77
|
+
// Set the code rather than throwing via this.exit(): the exit must apply
|
|
78
|
+
// in both output modes, and process.exitCode cannot be swallowed by the
|
|
79
|
+
// surrounding catch. Mirrors what run_all already does.
|
|
80
|
+
if (!passed) {
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
80
83
|
}
|
|
81
84
|
catch (error) {
|
|
85
|
+
if (error instanceof Error && 'oclif' in error)
|
|
86
|
+
throw error;
|
|
82
87
|
if (error instanceof Error) {
|
|
83
88
|
this.error(`Failed to run workflow test: ${error.message}`);
|
|
84
89
|
}
|
|
@@ -85,7 +85,15 @@ Results: 2 passed, 1 failed
|
|
|
85
85
|
},
|
|
86
86
|
});
|
|
87
87
|
if (tests.length === 0) {
|
|
88
|
-
|
|
88
|
+
// An empty suite is a normal state (fresh branches have no tests) and
|
|
89
|
+
// is a success, not a failure. Under -o json it must still be parseable,
|
|
90
|
+
// with the same keys the populated path emits below.
|
|
91
|
+
if (flags.output === 'json') {
|
|
92
|
+
this.log(JSON.stringify({ failed: 0, passed: 0, results: [] }, null, 2));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.log('No workflow tests found');
|
|
96
|
+
}
|
|
89
97
|
return;
|
|
90
98
|
}
|
|
91
99
|
if (flags.output === 'summary') {
|
|
@@ -146,6 +154,8 @@ Results: 2 passed, 1 failed
|
|
|
146
154
|
}
|
|
147
155
|
}
|
|
148
156
|
catch (error) {
|
|
157
|
+
if (error instanceof Error && 'oclif' in error)
|
|
158
|
+
throw error;
|
|
149
159
|
const message = error instanceof Error ? error.message : String(error);
|
|
150
160
|
results.push({
|
|
151
161
|
message,
|
|
@@ -184,6 +194,8 @@ Results: 2 passed, 1 failed
|
|
|
184
194
|
}
|
|
185
195
|
}
|
|
186
196
|
catch (error) {
|
|
197
|
+
if (error instanceof Error && 'oclif' in error)
|
|
198
|
+
throw error;
|
|
187
199
|
if (error instanceof Error) {
|
|
188
200
|
this.error(`Failed to run workflow tests: ${error.message}`);
|
|
189
201
|
}
|
|
@@ -63,26 +63,34 @@ Result: PASS
|
|
|
63
63
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
64
64
|
}
|
|
65
65
|
const result = await response.json();
|
|
66
|
+
// Anything other than 'ok' is a failure -- the API has been observed
|
|
67
|
+
// returning several distinct non-ok statuses, so never enumerate them.
|
|
68
|
+
const passed = result.status === 'ok';
|
|
66
69
|
if (flags.output === 'json') {
|
|
67
70
|
this.log(JSON.stringify(result, null, 2));
|
|
68
71
|
}
|
|
72
|
+
else if (passed) {
|
|
73
|
+
this.log('Result: PASS');
|
|
74
|
+
}
|
|
69
75
|
else {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const failedExpects = result.results?.filter(r => r.status === 'fail') ?? [];
|
|
76
|
-
for (const expect of failedExpects) {
|
|
77
|
-
if (expect.message) {
|
|
78
|
-
this.log(` Error: ${expect.message}`);
|
|
79
|
-
}
|
|
76
|
+
this.log('Result: FAIL');
|
|
77
|
+
const failedExpects = result.results?.filter(r => r.status === 'fail') ?? [];
|
|
78
|
+
for (const expect of failedExpects) {
|
|
79
|
+
if (expect.message) {
|
|
80
|
+
this.log(` Error: ${expect.message}`);
|
|
80
81
|
}
|
|
81
|
-
this.exit(1);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
// Set the code rather than throwing via this.exit(): the exit must apply
|
|
85
|
+
// in both output modes, and process.exitCode cannot be swallowed by the
|
|
86
|
+
// surrounding catch. Mirrors what run_all already does.
|
|
87
|
+
if (!passed) {
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
catch (error) {
|
|
92
|
+
if (error instanceof Error && 'oclif' in error)
|
|
93
|
+
throw error;
|
|
86
94
|
if (error instanceof Error) {
|
|
87
95
|
this.error(`Failed to run unit test: ${error.message}`);
|
|
88
96
|
}
|
|
@@ -91,7 +91,15 @@ Results: 2 passed, 1 failed
|
|
|
91
91
|
},
|
|
92
92
|
});
|
|
93
93
|
if (tests.length === 0) {
|
|
94
|
-
|
|
94
|
+
// An empty suite is a normal state (fresh branches have no tests) and
|
|
95
|
+
// is a success, not a failure. Under -o json it must still be parseable,
|
|
96
|
+
// with the same keys the populated path emits below.
|
|
97
|
+
if (flags.output === 'json') {
|
|
98
|
+
this.log(JSON.stringify({ failed: 0, passed: 0, results: [] }, null, 2));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
this.log('No unit tests found');
|
|
102
|
+
}
|
|
95
103
|
return;
|
|
96
104
|
}
|
|
97
105
|
if (flags.output === 'summary') {
|
|
@@ -161,6 +169,8 @@ Results: 2 passed, 1 failed
|
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
catch (error) {
|
|
172
|
+
if (error instanceof Error && 'oclif' in error)
|
|
173
|
+
throw error;
|
|
164
174
|
const message = error instanceof Error ? error.message : String(error);
|
|
165
175
|
results.push({
|
|
166
176
|
message,
|
|
@@ -202,6 +212,8 @@ Results: 2 passed, 1 failed
|
|
|
202
212
|
}
|
|
203
213
|
}
|
|
204
214
|
catch (error) {
|
|
215
|
+
if (error instanceof Error && 'oclif' in error)
|
|
216
|
+
throw error;
|
|
205
217
|
if (error instanceof Error) {
|
|
206
218
|
this.error(`Failed to run unit tests: ${error.message}`);
|
|
207
219
|
}
|
|
@@ -55,24 +55,29 @@ Result: PASS (1.234s)
|
|
|
55
55
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
56
56
|
}
|
|
57
57
|
const result = await response.json();
|
|
58
|
+
// Anything other than 'ok' is a failure -- the API has been observed
|
|
59
|
+
// returning several distinct non-ok statuses, so never enumerate them.
|
|
60
|
+
const passed = result.status === 'ok';
|
|
58
61
|
if (flags.output === 'json') {
|
|
59
62
|
this.log(JSON.stringify(result, null, 2));
|
|
60
63
|
}
|
|
61
64
|
else {
|
|
62
|
-
const timing = `(${result.timing.toFixed(3)}s)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
else {
|
|
67
|
-
this.log(`Result: FAIL ${timing}`);
|
|
68
|
-
if (result.message) {
|
|
69
|
-
this.log(` Error: ${result.message}`);
|
|
70
|
-
}
|
|
71
|
-
this.exit(1);
|
|
65
|
+
const timing = typeof result.timing === 'number' ? ` (${result.timing.toFixed(3)}s)` : '';
|
|
66
|
+
this.log(`Result: ${passed ? 'PASS' : 'FAIL'}${timing}`);
|
|
67
|
+
if (!passed && result.message) {
|
|
68
|
+
this.log(` Error: ${result.message}`);
|
|
72
69
|
}
|
|
73
70
|
}
|
|
71
|
+
// Set the code rather than throwing via this.exit(): the exit must apply
|
|
72
|
+
// in both output modes, and process.exitCode cannot be swallowed by the
|
|
73
|
+
// surrounding catch. Mirrors what run_all already does.
|
|
74
|
+
if (!passed) {
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
catch (error) {
|
|
79
|
+
if (error instanceof Error && 'oclif' in error)
|
|
80
|
+
throw error;
|
|
76
81
|
if (error instanceof Error) {
|
|
77
82
|
this.error(`Failed to run workflow test: ${error.message}`);
|
|
78
83
|
}
|