@xano/cli 1.0.6-beta.2 → 1.0.6-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -6
- package/dist/base-command.d.ts +5 -0
- package/dist/base-command.js +61 -4
- package/dist/commands/branch/list/index.js +5 -1
- package/dist/commands/ephemeral/create/index.js +3 -3
- package/dist/commands/ephemeral/list/index.js +2 -1
- package/dist/commands/ephemeral/pull/index.d.ts +0 -6
- package/dist/commands/ephemeral/pull/index.js +10 -95
- package/dist/commands/ephemeral/push/index.d.ts +10 -0
- package/dist/commands/ephemeral/push/index.js +77 -0
- package/dist/commands/ephemeral/static_host/build/list/index.d.ts +2 -3
- package/dist/commands/ephemeral/static_host/build/list/index.js +19 -40
- package/dist/commands/ephemeral/static_host/list/index.d.ts +2 -3
- package/dist/commands/ephemeral/static_host/list/index.js +20 -40
- package/dist/commands/flatten/index.d.ts +34 -0
- package/dist/commands/flatten/index.js +140 -0
- package/dist/commands/function/list/index.d.ts +4 -4
- package/dist/commands/function/list/index.js +30 -38
- package/dist/commands/function/run/index.d.ts +1 -0
- package/dist/commands/function/run/index.js +19 -5
- package/dist/commands/knowledge/list/index.js +2 -1
- package/dist/commands/platform/list/index.js +13 -14
- package/dist/commands/release/list/index.js +13 -14
- package/dist/commands/release/pull/index.d.ts +0 -6
- package/dist/commands/release/pull/index.js +10 -95
- package/dist/commands/sandbox/env/list/index.js +4 -0
- package/dist/commands/sandbox/pull/index.d.ts +0 -1
- package/dist/commands/sandbox/pull/index.js +10 -77
- package/dist/commands/sandbox/unit_test/list/index.d.ts +2 -0
- package/dist/commands/sandbox/unit_test/list/index.js +9 -14
- package/dist/commands/sandbox/unit_test/run/index.js +17 -11
- package/dist/commands/sandbox/unit_test/run_all/index.d.ts +1 -0
- package/dist/commands/sandbox/unit_test/run_all/index.js +76 -38
- package/dist/commands/sandbox/workflow_test/list/index.d.ts +2 -0
- package/dist/commands/sandbox/workflow_test/list/index.js +9 -14
- package/dist/commands/sandbox/workflow_test/run/index.js +12 -9
- package/dist/commands/sandbox/workflow_test/run_all/index.d.ts +1 -0
- package/dist/commands/sandbox/workflow_test/run_all/index.js +74 -36
- package/dist/commands/static_host/build/list/index.d.ts +2 -3
- package/dist/commands/static_host/build/list/index.js +19 -40
- package/dist/commands/static_host/build/pull/index.js +12 -9
- package/dist/commands/static_host/list/index.d.ts +2 -3
- package/dist/commands/static_host/list/index.js +20 -40
- package/dist/commands/static_host/migrate/index.js +14 -10
- package/dist/commands/tenant/backup/list/index.js +13 -14
- package/dist/commands/tenant/cluster/list/index.js +9 -10
- package/dist/commands/tenant/create/index.d.ts +3 -0
- package/dist/commands/tenant/create/index.js +21 -0
- package/dist/commands/tenant/deploy_release/index.d.ts +21 -0
- package/dist/commands/tenant/deploy_release/index.js +94 -2
- package/dist/commands/tenant/edit/index.d.ts +2 -0
- package/dist/commands/tenant/edit/index.js +39 -4
- package/dist/commands/tenant/env/list/index.js +4 -0
- package/dist/commands/tenant/list/index.js +19 -20
- package/dist/commands/tenant/pull/index.d.ts +0 -6
- package/dist/commands/tenant/pull/index.js +10 -106
- package/dist/commands/tenant/snapshot/list/index.js +5 -1
- package/dist/commands/tenant/unit_test/list/index.d.ts +4 -2
- package/dist/commands/tenant/unit_test/list/index.js +9 -14
- package/dist/commands/tenant/unit_test/run/index.js +19 -11
- package/dist/commands/tenant/unit_test/run_all/index.d.ts +1 -0
- package/dist/commands/tenant/unit_test/run_all/index.js +80 -38
- package/dist/commands/tenant/workflow_test/list/index.d.ts +4 -2
- package/dist/commands/tenant/workflow_test/list/index.js +9 -14
- package/dist/commands/tenant/workflow_test/run/index.js +14 -9
- package/dist/commands/tenant/workflow_test/run_all/index.d.ts +1 -0
- package/dist/commands/tenant/workflow_test/run_all/index.js +78 -36
- package/dist/commands/tenant_deploy_request/bypass/index.d.ts +20 -0
- package/dist/commands/tenant_deploy_request/bypass/index.js +79 -0
- package/dist/commands/tenant_deploy_request/create/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/create/index.js +108 -0
- package/dist/commands/tenant_deploy_request/edit/index.d.ts +22 -0
- package/dist/commands/tenant_deploy_request/edit/index.js +103 -0
- package/dist/commands/tenant_deploy_request/get/index.d.ts +19 -0
- package/dist/commands/tenant_deploy_request/get/index.js +75 -0
- package/dist/commands/tenant_deploy_request/list/index.d.ts +20 -0
- package/dist/commands/tenant_deploy_request/list/index.js +122 -0
- package/dist/commands/tenant_deploy_request/revision/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/revision/index.js +83 -0
- package/dist/commands/tenant_deploy_request/set_status/index.d.ts +21 -0
- package/dist/commands/tenant_deploy_request/set_status/index.js +112 -0
- package/dist/commands/unit_test/list/index.d.ts +3 -1
- package/dist/commands/unit_test/list/index.js +9 -14
- package/dist/commands/unit_test/run/index.js +19 -11
- package/dist/commands/unit_test/run_all/index.d.ts +1 -0
- package/dist/commands/unit_test/run_all/index.js +81 -41
- package/dist/commands/workflow_test/list/index.d.ts +3 -1
- package/dist/commands/workflow_test/list/index.js +10 -13
- package/dist/commands/workflow_test/run/index.js +15 -10
- package/dist/commands/workflow_test/run_all/index.d.ts +1 -0
- package/dist/commands/workflow_test/run_all/index.js +82 -39
- package/dist/commands/workspace/git/pull/index.js +48 -7
- package/dist/commands/workspace/list/index.js +7 -13
- package/dist/commands/workspace/pull/index.d.ts +0 -6
- package/dist/commands/workspace/pull/index.js +10 -115
- package/dist/utils/concurrency.d.ts +29 -0
- package/dist/utils/concurrency.js +54 -0
- package/dist/utils/document-parser.d.ts +90 -0
- package/dist/utils/document-parser.js +273 -1
- 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 -5
- package/dist/utils/microservice-wait.d.ts +53 -0
- package/dist/utils/microservice-wait.js +83 -0
- package/dist/utils/multidoc-push.d.ts +88 -0
- package/dist/utils/multidoc-push.js +377 -8
- package/dist/utils/paging.d.ts +145 -0
- package/dist/utils/paging.js +199 -0
- package/dist/utils/reference-checker.js +13 -0
- package/oclif.manifest.json +4999 -3973
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { Args, Flags } from '@oclif/core';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../base-command.js';
|
|
5
|
+
import { isAwaited, waitForMicroservices, } from '../../../utils/microservice-wait.js';
|
|
5
6
|
import { executePush } from '../../../utils/multidoc-push.js';
|
|
6
7
|
export default class EphemeralPush extends BaseCommand {
|
|
7
8
|
static args = {
|
|
@@ -88,6 +89,17 @@ Skip preview and push immediately
|
|
|
88
89
|
description: '[CRITICAL] STOP and confirm with the user; this truncates live tables before importing.',
|
|
89
90
|
required: false,
|
|
90
91
|
}),
|
|
92
|
+
wait: Flags.boolean({
|
|
93
|
+
default: false,
|
|
94
|
+
description: 'After pushing, wait for auto-deployed microservices (tenant_deploy="auto") to become ready. Exits non-zero if any fails to deploy or the wait times out. Ignored with --dry-run.',
|
|
95
|
+
required: false,
|
|
96
|
+
}),
|
|
97
|
+
'wait-timeout': Flags.integer({
|
|
98
|
+
default: 300,
|
|
99
|
+
description: 'Seconds to wait for microservices to become ready when --wait is set (default 300).',
|
|
100
|
+
min: 1,
|
|
101
|
+
required: false,
|
|
102
|
+
}),
|
|
91
103
|
workspace: Flags.string({
|
|
92
104
|
char: 'w',
|
|
93
105
|
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
@@ -140,5 +152,70 @@ Skip preview and push immediately
|
|
|
140
152
|
inputDir,
|
|
141
153
|
verboseFetch: this.verboseFetch.bind(this),
|
|
142
154
|
}, target, pushFlags);
|
|
155
|
+
// --wait: after a real push, poll the tenant's live microservice status until
|
|
156
|
+
// every auto-deployed microservice (tenant_deploy="auto") is ready or the
|
|
157
|
+
// wait times out. Skipped on --dry-run (nothing was deployed).
|
|
158
|
+
if (flags.wait && !flags['dry-run']) {
|
|
159
|
+
await this.waitForDeploy(baseUrl, profile.access_token, tenantName, flags['wait-timeout'], flags.verbose);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Poll GET .../tenant/{name}/microservice until auto microservices settle.
|
|
164
|
+
* Renders a one-line-per-microservice progress view that updates in place, then
|
|
165
|
+
* exits non-zero if any auto microservice failed or the wait timed out.
|
|
166
|
+
*/
|
|
167
|
+
async waitForDeploy(baseUrl, accessToken, tenantName, timeoutSeconds, verbose) {
|
|
168
|
+
this.log('');
|
|
169
|
+
this.log(`Waiting for microservices to deploy (timeout ${timeoutSeconds}s)…`);
|
|
170
|
+
const icon = (e) => {
|
|
171
|
+
if (!isAwaited(e))
|
|
172
|
+
return '➖';
|
|
173
|
+
switch (e.status) {
|
|
174
|
+
case 'error': {
|
|
175
|
+
return '❌';
|
|
176
|
+
}
|
|
177
|
+
case 'ok': {
|
|
178
|
+
return '✅';
|
|
179
|
+
}
|
|
180
|
+
default: {
|
|
181
|
+
return '⏳';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const line = (e) => {
|
|
186
|
+
const detail = isAwaited(e) ? e.detail || e.status : `skipped (${e.tenant_deploy ?? 'manual'})`;
|
|
187
|
+
return ` ${icon(e)} ${e.name.padEnd(24)} ${detail}`;
|
|
188
|
+
};
|
|
189
|
+
const result = await waitForMicroservices({
|
|
190
|
+
accessToken,
|
|
191
|
+
onPoll: (entries) => {
|
|
192
|
+
// Re-render the block: move the cursor up over the previous render (if any)
|
|
193
|
+
// and rewrite. Falls back to plain appends when not a TTY.
|
|
194
|
+
if (process.stdout.isTTY && this.lastRenderLines > 0) {
|
|
195
|
+
process.stdout.write(`[${this.lastRenderLines}A[0J`);
|
|
196
|
+
}
|
|
197
|
+
const lines = entries.map((e) => line(e));
|
|
198
|
+
for (const l of lines)
|
|
199
|
+
this.log(l);
|
|
200
|
+
this.lastRenderLines = process.stdout.isTTY ? lines.length : 0;
|
|
201
|
+
},
|
|
202
|
+
statusUrl: `${baseUrl}/microservice`,
|
|
203
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
204
|
+
verbose,
|
|
205
|
+
verboseFetch: this.verboseFetch.bind(this),
|
|
206
|
+
});
|
|
207
|
+
const awaited = result.entries.filter((e) => isAwaited(e));
|
|
208
|
+
const ready = awaited.filter((e) => e.status === 'ok').length;
|
|
209
|
+
if (result.timedOut) {
|
|
210
|
+
this.error(`Timed out after ${timeoutSeconds}s waiting for microservices (${ready}/${awaited.length} ready). ` +
|
|
211
|
+
`Check status with the Xano dashboard or re-run with a larger --wait-timeout.`);
|
|
212
|
+
}
|
|
213
|
+
if (result.hadError) {
|
|
214
|
+
const failed = awaited.filter((e) => e.status === 'error').map((e) => `${e.name} (${e.detail || 'error'})`);
|
|
215
|
+
this.error(`Microservice deploy failed: ${failed.join(', ')}`);
|
|
216
|
+
}
|
|
217
|
+
this.log(`All microservices ready (${ready}/${awaited.length}).`);
|
|
143
218
|
}
|
|
219
|
+
/** Number of lines the last progress render wrote (for in-place TTY updates). */
|
|
220
|
+
lastRenderLines = 0;
|
|
144
221
|
}
|
|
@@ -6,11 +6,10 @@ export default class EphemeralStaticHostBuildList extends BaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
-
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
-
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
9
|
'static-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
10
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
13
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
14
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
15
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../../base-command.js';
|
|
3
|
+
import { buildPagingJson, buildPagingParams, formatPagingFooter, normalizeListResponse, pagingFlags } from '../../../../../utils/paging.js';
|
|
3
4
|
export default class EphemeralStaticHostBuildList extends BaseCommand {
|
|
4
5
|
static args = {
|
|
5
6
|
tenant_name: Args.string({
|
|
@@ -20,13 +21,16 @@ Available builds:
|
|
|
20
21
|
- staging (ID: 2) - Status: completed
|
|
21
22
|
`,
|
|
22
23
|
`$ xano ephemeral static_host build list e4f2-9ab1-xyz1 -H default -w 40 --output json
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
{
|
|
25
|
+
"count": 1,
|
|
26
|
+
"page": 1,
|
|
27
|
+
"total": 12,
|
|
28
|
+
"items": [
|
|
29
|
+
{
|
|
30
|
+
"id": 52
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
30
34
|
`,
|
|
31
35
|
`$ xano ephemeral static_host build list e4f2-9ab1-xyz1 -H default -p staging -o json --page 2
|
|
32
36
|
[
|
|
@@ -46,16 +50,7 @@ Available builds:
|
|
|
46
50
|
options: ['summary', 'json'],
|
|
47
51
|
required: false,
|
|
48
52
|
}),
|
|
49
|
-
page:
|
|
50
|
-
default: 1,
|
|
51
|
-
description: 'Page number for pagination',
|
|
52
|
-
required: false,
|
|
53
|
-
}),
|
|
54
|
-
per_page: Flags.integer({
|
|
55
|
-
default: 50,
|
|
56
|
-
description: 'Number of results per page',
|
|
57
|
-
required: false,
|
|
58
|
-
}),
|
|
53
|
+
...pagingFlags('page-only-envelope', { fixedPerPage: 100 }),
|
|
59
54
|
'static-host': Flags.string({
|
|
60
55
|
char: 'H',
|
|
61
56
|
description: 'Static host name',
|
|
@@ -86,13 +81,7 @@ Available builds:
|
|
|
86
81
|
` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
|
|
87
82
|
}
|
|
88
83
|
// Build query parameters
|
|
89
|
-
const queryParams = new URLSearchParams(
|
|
90
|
-
page: flags.page.toString(),
|
|
91
|
-
});
|
|
92
|
-
// Only add per_page if it's not the default value
|
|
93
|
-
if (flags.per_page !== 50) {
|
|
94
|
-
queryParams.append('per_page', flags.per_page.toString());
|
|
95
|
-
}
|
|
84
|
+
const queryParams = new URLSearchParams(buildPagingParams(flags, 'page-only-envelope'));
|
|
96
85
|
// Construct the API URL
|
|
97
86
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build?${queryParams.toString()}`;
|
|
98
87
|
// Fetch builds from the API
|
|
@@ -108,24 +97,11 @@ Available builds:
|
|
|
108
97
|
const errorText = await response.text();
|
|
109
98
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
110
99
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
let builds;
|
|
114
|
-
if (Array.isArray(data)) {
|
|
115
|
-
builds = data;
|
|
116
|
-
}
|
|
117
|
-
else if (data && typeof data === 'object' && 'builds' in data && Array.isArray(data.builds)) {
|
|
118
|
-
builds = data.builds;
|
|
119
|
-
}
|
|
120
|
-
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
121
|
-
builds = data.items;
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
this.error('Unexpected API response format');
|
|
125
|
-
}
|
|
100
|
+
const list = normalizeListResponse(await response.json(), ['builds']);
|
|
101
|
+
const builds = list.items;
|
|
126
102
|
// Output results
|
|
127
103
|
if (flags.output === 'json') {
|
|
128
|
-
this.log(JSON.stringify(
|
|
104
|
+
this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, tier: 'page-only-envelope' }), null, 2));
|
|
129
105
|
}
|
|
130
106
|
else {
|
|
131
107
|
// summary format
|
|
@@ -144,6 +120,9 @@ Available builds:
|
|
|
144
120
|
}
|
|
145
121
|
}
|
|
146
122
|
}
|
|
123
|
+
const footer = formatPagingFooter(list, { noun: 'build', page: flags.page, tier: 'page-only-envelope' });
|
|
124
|
+
if (footer)
|
|
125
|
+
this.log(footer);
|
|
147
126
|
}
|
|
148
127
|
}
|
|
149
128
|
catch (error) {
|
|
@@ -6,10 +6,9 @@ export default class EphemeralStaticHostList extends BaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
-
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
-
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
9
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
12
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
13
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
14
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
|
+
import { buildPagingJson, buildPagingParams, formatPagingFooter, normalizeListResponse, pagingFlags } from '../../../../utils/paging.js';
|
|
3
4
|
export default class EphemeralStaticHostList extends BaseCommand {
|
|
4
5
|
static args = {
|
|
5
6
|
tenant_name: Args.string({
|
|
@@ -20,13 +21,17 @@ Available static hosts:
|
|
|
20
21
|
- another-host (ID: 2)
|
|
21
22
|
`,
|
|
22
23
|
`$ xano ephemeral static_host list e4f2-9ab1-xyz1 -w 40 --output json
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
{
|
|
25
|
+
"count": 1,
|
|
26
|
+
"page": 1,
|
|
27
|
+
"total": 3,
|
|
28
|
+
"items": [
|
|
29
|
+
{
|
|
30
|
+
"id": 1,
|
|
31
|
+
"name": "my-static-host"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
30
35
|
`,
|
|
31
36
|
`$ xano ephemeral static_host list e4f2-9ab1-xyz1 -p staging -o json --page 2
|
|
32
37
|
[
|
|
@@ -46,16 +51,7 @@ Available static hosts:
|
|
|
46
51
|
options: ['summary', 'json'],
|
|
47
52
|
required: false,
|
|
48
53
|
}),
|
|
49
|
-
page:
|
|
50
|
-
default: 1,
|
|
51
|
-
description: 'Page number for pagination',
|
|
52
|
-
required: false,
|
|
53
|
-
}),
|
|
54
|
-
per_page: Flags.integer({
|
|
55
|
-
default: 50,
|
|
56
|
-
description: 'Number of results per page',
|
|
57
|
-
required: false,
|
|
58
|
-
}),
|
|
54
|
+
...pagingFlags('page-only-envelope', { fixedPerPage: 100 }),
|
|
59
55
|
workspace: Flags.string({
|
|
60
56
|
char: 'w',
|
|
61
57
|
description: 'Workspace ID (optional if set in profile)',
|
|
@@ -80,13 +76,7 @@ Available static hosts:
|
|
|
80
76
|
` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
|
|
81
77
|
}
|
|
82
78
|
// Build query parameters
|
|
83
|
-
const queryParams = new URLSearchParams(
|
|
84
|
-
page: flags.page.toString(),
|
|
85
|
-
});
|
|
86
|
-
// Only add per_page if it's not the default value
|
|
87
|
-
if (flags.per_page !== 50) {
|
|
88
|
-
queryParams.append('per_page', flags.per_page.toString());
|
|
89
|
-
}
|
|
79
|
+
const queryParams = new URLSearchParams(buildPagingParams(flags, 'page-only-envelope'));
|
|
90
80
|
// Construct the API URL
|
|
91
81
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host?${queryParams.toString()}`;
|
|
92
82
|
// Fetch static hosts from the API
|
|
@@ -102,24 +92,11 @@ Available static hosts:
|
|
|
102
92
|
const errorText = await response.text();
|
|
103
93
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
104
94
|
}
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
let staticHosts;
|
|
108
|
-
if (Array.isArray(data)) {
|
|
109
|
-
staticHosts = data;
|
|
110
|
-
}
|
|
111
|
-
else if (data && typeof data === 'object' && 'static_hosts' in data && Array.isArray(data.static_hosts)) {
|
|
112
|
-
staticHosts = data.static_hosts;
|
|
113
|
-
}
|
|
114
|
-
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
115
|
-
staticHosts = data.items;
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
this.error('Unexpected API response format');
|
|
119
|
-
}
|
|
95
|
+
const list = normalizeListResponse(await response.json(), ['static_hosts']);
|
|
96
|
+
const staticHosts = list.items;
|
|
120
97
|
// Output results
|
|
121
98
|
if (flags.output === 'json') {
|
|
122
|
-
this.log(JSON.stringify(
|
|
99
|
+
this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, tier: 'page-only-envelope' }), null, 2));
|
|
123
100
|
}
|
|
124
101
|
else {
|
|
125
102
|
// summary format
|
|
@@ -138,6 +115,9 @@ Available static hosts:
|
|
|
138
115
|
}
|
|
139
116
|
}
|
|
140
117
|
}
|
|
118
|
+
const footer = formatPagingFooter(list, { noun: 'static host', page: flags.page, tier: 'page-only-envelope' });
|
|
119
|
+
if (footer)
|
|
120
|
+
this.log(footer);
|
|
141
121
|
}
|
|
142
122
|
}
|
|
143
123
|
catch (error) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* Split a multi-document `.xs` bundle into the standard one-document-per-file
|
|
4
|
+
* layout that the push pipeline requires.
|
|
5
|
+
*
|
|
6
|
+
* A hand-authored bundle keeps every object (workspace, tables, queries,
|
|
7
|
+
* microservices, …) in a single file joined by `---`. That is convenient to
|
|
8
|
+
* edit but unsupported by `push`: the partial-diff filter and GUID writeback
|
|
9
|
+
* both parse only the first document in a file, so such a bundle silently
|
|
10
|
+
* pushes nothing (partial mode) or corrupts GUIDs (full mode). `flatten` turns
|
|
11
|
+
* it back into the exact tree `pull` would have produced, using the shared
|
|
12
|
+
* placement logic so the two never drift.
|
|
13
|
+
*
|
|
14
|
+
* Not a BaseCommand: this is a purely local, offline transform — no profile,
|
|
15
|
+
* token, or network — so it extends oclif's Command directly.
|
|
16
|
+
*/
|
|
17
|
+
export default class Flatten extends Command {
|
|
18
|
+
static args: {
|
|
19
|
+
input: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
20
|
+
};
|
|
21
|
+
static description: string;
|
|
22
|
+
static examples: string[];
|
|
23
|
+
static flags: {
|
|
24
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
25
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
26
|
+
'keep-source': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
27
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
|
+
};
|
|
29
|
+
run(): Promise<void>;
|
|
30
|
+
/** Recursively collect `.xs` files under a dir that actually hold >1 document. */
|
|
31
|
+
private collectMultiDocFiles;
|
|
32
|
+
/** Flatten a single bundle file. Returns the number of documents written. */
|
|
33
|
+
private flattenOne;
|
|
34
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Args, Command, Flags, ux } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { join, relative, resolve } from 'node:path';
|
|
4
|
+
import { flattenBundleFile } from '../../utils/flatten.js';
|
|
5
|
+
/**
|
|
6
|
+
* Split a multi-document `.xs` bundle into the standard one-document-per-file
|
|
7
|
+
* layout that the push pipeline requires.
|
|
8
|
+
*
|
|
9
|
+
* A hand-authored bundle keeps every object (workspace, tables, queries,
|
|
10
|
+
* microservices, …) in a single file joined by `---`. That is convenient to
|
|
11
|
+
* edit but unsupported by `push`: the partial-diff filter and GUID writeback
|
|
12
|
+
* both parse only the first document in a file, so such a bundle silently
|
|
13
|
+
* pushes nothing (partial mode) or corrupts GUIDs (full mode). `flatten` turns
|
|
14
|
+
* it back into the exact tree `pull` would have produced, using the shared
|
|
15
|
+
* placement logic so the two never drift.
|
|
16
|
+
*
|
|
17
|
+
* Not a BaseCommand: this is a purely local, offline transform — no profile,
|
|
18
|
+
* token, or network — so it extends oclif's Command directly.
|
|
19
|
+
*/
|
|
20
|
+
export default class Flatten extends Command {
|
|
21
|
+
static args = {
|
|
22
|
+
input: Args.string({
|
|
23
|
+
description: 'A multi-document .xs file, or a directory containing them, to split',
|
|
24
|
+
required: true,
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
static description = 'Split a multi-document .xs bundle (documents joined by `---`) into the standard ' +
|
|
28
|
+
'one-document-per-file layout that push requires. Deletes the original bundle by ' +
|
|
29
|
+
'default (use --keep-source to keep it). Purely local — no network.';
|
|
30
|
+
static examples = [
|
|
31
|
+
`$ xano flatten secret/pdf-micro/multidoc.xs
|
|
32
|
+
Split the bundle in place into per-document files (removes multidoc.xs)`,
|
|
33
|
+
`$ xano flatten ./bundle.xs -o ./workspace
|
|
34
|
+
Write the split files under ./workspace instead of alongside the bundle`,
|
|
35
|
+
`$ xano flatten secret/pdf-micro/multidoc.xs --dry-run
|
|
36
|
+
Preview the file layout without writing anything`,
|
|
37
|
+
`$ xano flatten ./dir-of-bundles --keep-source
|
|
38
|
+
Flatten every multi-doc .xs under a directory, keeping the originals`,
|
|
39
|
+
];
|
|
40
|
+
static flags = {
|
|
41
|
+
'dry-run': Flags.boolean({
|
|
42
|
+
default: false,
|
|
43
|
+
description: 'Show the resulting file layout without writing anything',
|
|
44
|
+
}),
|
|
45
|
+
force: Flags.boolean({
|
|
46
|
+
char: 'f',
|
|
47
|
+
default: false,
|
|
48
|
+
description: 'Overwrite existing destination files instead of erroring',
|
|
49
|
+
}),
|
|
50
|
+
'keep-source': Flags.boolean({
|
|
51
|
+
default: false,
|
|
52
|
+
description: 'Keep the original multi-document file(s) (default: delete after a successful split)',
|
|
53
|
+
}),
|
|
54
|
+
output: Flags.string({
|
|
55
|
+
char: 'o',
|
|
56
|
+
description: 'Output directory (default: the directory each bundle file lives in)',
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
async run() {
|
|
60
|
+
const { args, flags } = await this.parse(Flatten);
|
|
61
|
+
const inputPath = resolve(args.input);
|
|
62
|
+
if (!fs.existsSync(inputPath)) {
|
|
63
|
+
this.error(`Not found: ${inputPath}`);
|
|
64
|
+
}
|
|
65
|
+
// Resolve the set of bundle files to flatten.
|
|
66
|
+
const bundleFiles = fs.statSync(inputPath).isDirectory()
|
|
67
|
+
? this.collectMultiDocFiles(inputPath)
|
|
68
|
+
: [inputPath];
|
|
69
|
+
if (bundleFiles.length === 0) {
|
|
70
|
+
this.error(fs.statSync(inputPath).isDirectory()
|
|
71
|
+
? `No multi-document .xs files found under ${inputPath}`
|
|
72
|
+
: `${inputPath} is not a .xs file`);
|
|
73
|
+
}
|
|
74
|
+
let totalWritten = 0;
|
|
75
|
+
for (const bundleFile of bundleFiles) {
|
|
76
|
+
totalWritten += this.flattenOne(bundleFile, flags);
|
|
77
|
+
}
|
|
78
|
+
if (flags['dry-run']) {
|
|
79
|
+
this.log('');
|
|
80
|
+
this.log(ux.colorize('dim', `Dry run — ${totalWritten} file(s) would be written. Nothing changed.`));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.log('');
|
|
84
|
+
this.log(`Flattened ${bundleFiles.length} bundle(s) into ${totalWritten} file(s).`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Recursively collect `.xs` files under a dir that actually hold >1 document. */
|
|
88
|
+
collectMultiDocFiles(dir) {
|
|
89
|
+
const out = [];
|
|
90
|
+
const walk = (d) => {
|
|
91
|
+
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
|
92
|
+
const full = join(d, entry.name);
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
walk(full);
|
|
95
|
+
}
|
|
96
|
+
else if (entry.isFile() && entry.name.endsWith('.xs')) {
|
|
97
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
98
|
+
if (/^---$/m.test(content))
|
|
99
|
+
out.push(full);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
walk(dir);
|
|
104
|
+
return out.sort();
|
|
105
|
+
}
|
|
106
|
+
/** Flatten a single bundle file. Returns the number of documents written. */
|
|
107
|
+
flattenOne(bundleFile, flags) {
|
|
108
|
+
const rel = relative(process.cwd(), bundleFile) || bundleFile;
|
|
109
|
+
let result;
|
|
110
|
+
try {
|
|
111
|
+
// Buffer the plan so we can print a header before it only when there's work.
|
|
112
|
+
const planLines = [];
|
|
113
|
+
result = flattenBundleFile(bundleFile, {
|
|
114
|
+
dryRun: flags['dry-run'],
|
|
115
|
+
force: flags.force,
|
|
116
|
+
keepSource: flags['keep-source'],
|
|
117
|
+
log: (msg) => planLines.push(msg),
|
|
118
|
+
outputDir: flags.output,
|
|
119
|
+
});
|
|
120
|
+
if (result.skipped === 'empty') {
|
|
121
|
+
this.warn(`No parseable documents in ${rel}; skipping.`);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
if (result.skipped === 'single') {
|
|
125
|
+
this.warn(`${rel} holds a single document; nothing to split. Skipping.`);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
this.log('');
|
|
129
|
+
this.log(ux.colorize('bold', `${rel} → ${result.written.length} documents`));
|
|
130
|
+
for (const l of planLines) {
|
|
131
|
+
// Colorize the WRITE marker; dim the incidental notes.
|
|
132
|
+
this.log(l.startsWith(' WRITE') ? l.replace('WRITE', ux.colorize('green', 'WRITE')) : ux.colorize('dim', l));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
this.error(error.message);
|
|
137
|
+
}
|
|
138
|
+
return result.written.length;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -4,14 +4,14 @@ export default class FunctionList extends BaseCommand {
|
|
|
4
4
|
static description: string;
|
|
5
5
|
static examples: string[];
|
|
6
6
|
static flags: {
|
|
7
|
+
sort: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
11
|
include_draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
12
|
include_xanoscript: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
13
|
order: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
14
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
-
sort: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
-
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
15
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
16
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
17
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
import { buildPagingJson, formatPagingFooter, normalizeListResponse, pagingFlags } from '../../../utils/paging.js';
|
|
3
4
|
export default class FunctionList extends BaseCommand {
|
|
4
5
|
static args = {};
|
|
5
6
|
static description = 'List all functions in a workspace from the Xano Metadata API';
|
|
@@ -16,20 +17,30 @@ Available functions:
|
|
|
16
17
|
- another-function (ID: 2)
|
|
17
18
|
`,
|
|
18
19
|
`$ xano function:list -w 40 --output json
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
{
|
|
21
|
+
"count": 1,
|
|
22
|
+
"page": 1,
|
|
23
|
+
"per_page": 50,
|
|
24
|
+
"next_page": 2,
|
|
25
|
+
"items": [
|
|
26
|
+
{
|
|
27
|
+
"id": 1,
|
|
28
|
+
"name": "function-1"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
25
32
|
`,
|
|
26
33
|
`$ xano function:list -p staging -o json --include_draft
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
{
|
|
35
|
+
"count": 1,
|
|
36
|
+
"page": 1,
|
|
37
|
+
"items": [
|
|
38
|
+
{
|
|
39
|
+
"id": 1,
|
|
40
|
+
"name": "function-1"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
33
44
|
`,
|
|
34
45
|
];
|
|
35
46
|
static flags = {
|
|
@@ -57,16 +68,7 @@ Available functions:
|
|
|
57
68
|
options: ['summary', 'json'],
|
|
58
69
|
required: false,
|
|
59
70
|
}),
|
|
60
|
-
|
|
61
|
-
default: 1,
|
|
62
|
-
description: 'Page number for pagination',
|
|
63
|
-
required: false,
|
|
64
|
-
}),
|
|
65
|
-
per_page: Flags.integer({
|
|
66
|
-
default: 50,
|
|
67
|
-
description: 'Number of results per page',
|
|
68
|
-
required: false,
|
|
69
|
-
}),
|
|
71
|
+
...pagingFlags('envelope', { maxPerPage: 10_000 }),
|
|
70
72
|
sort: Flags.string({
|
|
71
73
|
default: 'created_at',
|
|
72
74
|
description: 'Sort field',
|
|
@@ -118,24 +120,11 @@ Available functions:
|
|
|
118
120
|
const errorText = await response.text();
|
|
119
121
|
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
120
122
|
}
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
let functions;
|
|
124
|
-
if (Array.isArray(data)) {
|
|
125
|
-
functions = data;
|
|
126
|
-
}
|
|
127
|
-
else if (data && typeof data === 'object' && 'functions' in data && Array.isArray(data.functions)) {
|
|
128
|
-
functions = data.functions;
|
|
129
|
-
}
|
|
130
|
-
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
131
|
-
functions = data.items;
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
this.error('Unexpected API response format');
|
|
135
|
-
}
|
|
123
|
+
const list = normalizeListResponse(await response.json(), ['functions']);
|
|
124
|
+
const functions = list.items;
|
|
136
125
|
// Output results
|
|
137
126
|
if (flags.output === 'json') {
|
|
138
|
-
this.log(JSON.stringify(
|
|
127
|
+
this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, perPage: flags.per_page, tier: 'envelope' }), null, 2));
|
|
139
128
|
}
|
|
140
129
|
else {
|
|
141
130
|
// summary format
|
|
@@ -153,6 +142,9 @@ Available functions:
|
|
|
153
142
|
}
|
|
154
143
|
}
|
|
155
144
|
}
|
|
145
|
+
const footer = formatPagingFooter(list, { noun: 'function', page: flags.page, tier: 'envelope' });
|
|
146
|
+
if (footer)
|
|
147
|
+
this.log(footer);
|
|
156
148
|
}
|
|
157
149
|
}
|
|
158
150
|
catch (error) {
|
|
@@ -8,6 +8,7 @@ export default class FunctionRun extends BaseCommand {
|
|
|
8
8
|
static flags: {
|
|
9
9
|
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
data: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
datasource: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
json: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
logs: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|