@xano/cli 1.2.0 → 1.3.1-beta.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 +381 -4
- package/dist/base-command.d.ts +54 -36
- package/dist/base-command.js +246 -169
- 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/branch/set_live/index.d.ts +22 -0
- package/dist/commands/branch/set_live/index.js +60 -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 +228 -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.d.ts +15 -0
- package/dist/commands/function/create/index.js +41 -1
- package/dist/commands/function/edit/index.d.ts +15 -0
- package/dist/commands/function/edit/index.js +66 -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/policy/catalogue/index.d.ts +15 -0
- package/dist/commands/policy/catalogue/index.js +29 -0
- package/dist/commands/policy/create/index.d.ts +18 -0
- package/dist/commands/policy/create/index.js +39 -0
- package/dist/commands/policy/delete/index.d.ts +19 -0
- package/dist/commands/policy/delete/index.js +61 -0
- package/dist/commands/policy/evaluate/index.d.ts +32 -0
- package/dist/commands/policy/evaluate/index.js +71 -0
- package/dist/commands/policy/list/index.d.ts +14 -0
- package/dist/commands/policy/list/index.js +21 -0
- package/dist/commands/policy/parse/index.d.ts +19 -0
- package/dist/commands/policy/parse/index.js +18 -0
- package/dist/commands/policy/publish/index.d.ts +20 -0
- package/dist/commands/policy/publish/index.js +38 -0
- package/dist/commands/policy/runs/index.d.ts +54 -0
- package/dist/commands/policy/runs/index.js +273 -0
- package/dist/commands/policy/status/index.d.ts +21 -0
- package/dist/commands/policy/status/index.js +54 -0
- 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/create/index.js +7 -0
- package/dist/commands/release/deploy/index.d.ts +28 -0
- package/dist/commands/release/deploy/index.js +90 -6
- 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 +6 -2
- package/dist/commands/release/list/index.d.ts +7 -0
- package/dist/commands/release/list/index.js +54 -10
- 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 +21 -3
- 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 +11 -6
- package/dist/commands/skills/pull/index.d.ts +30 -0
- package/dist/commands/skills/pull/index.js +118 -0
- 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 +4 -1
- package/dist/commands/tenant/create/index.js +26 -5
- package/dist/commands/tenant/deploy_platform/index.js +1 -1
- package/dist/commands/tenant/deploy_release/index.d.ts +40 -0
- package/dist/commands/tenant/deploy_release/index.js +186 -4
- package/dist/commands/tenant/edit/index.d.ts +2 -0
- package/dist/commands/tenant/edit/index.js +46 -11
- 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_deploy_request/bypass/index.d.ts +31 -0
- package/dist/commands/tenant_deploy_request/bypass/index.js +109 -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 +25 -0
- package/dist/commands/tenant_deploy_request/get/index.js +116 -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 +38 -0
- package/dist/commands/tenant_deploy_request/set_status/index.js +159 -0
- 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 +9 -5
- package/dist/commands/workspace/pull/index.js +83 -106
- package/dist/commands/workspace/push/index.d.ts +19 -0
- package/dist/commands/workspace/push/index.js +124 -5
- package/dist/help.js +4 -2
- package/dist/policy-command.d.ts +90 -0
- package/dist/policy-command.js +94 -0
- package/dist/update-check.d.ts +1 -1
- package/dist/update-check.js +1 -1
- package/dist/utils/document-parser.d.ts +126 -0
- package/dist/utils/document-parser.js +332 -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 +143 -2
- package/dist/utils/multidoc-push.js +479 -40
- package/dist/utils/paging.d.ts +2 -1
- package/dist/utils/paging.js +0 -1
- package/dist/utils/policy/catalogue.d.ts +7 -0
- package/dist/utils/policy/catalogue.js +72 -0
- package/dist/utils/policy/errors.d.ts +12 -0
- package/dist/utils/policy/errors.js +61 -0
- package/dist/utils/policy/feedback.d.ts +68 -0
- package/dist/utils/policy/feedback.js +165 -0
- package/dist/utils/policy/findings.d.ts +23 -0
- package/dist/utils/policy/findings.js +65 -0
- package/dist/utils/policy/gate.d.ts +161 -0
- package/dist/utils/policy/gate.js +201 -0
- package/dist/utils/policy/goal.d.ts +46 -0
- package/dist/utils/policy/goal.js +95 -0
- package/dist/utils/policy/permission.d.ts +23 -0
- package/dist/utils/policy/permission.js +84 -0
- package/dist/utils/policy/push-gate.d.ts +5 -0
- package/dist/utils/policy/push-gate.js +29 -0
- package/dist/utils/policy/release.d.ts +85 -0
- package/dist/utils/policy/release.js +115 -0
- package/dist/utils/policy/request.d.ts +55 -0
- package/dist/utils/policy/request.js +115 -0
- package/dist/utils/policy/runs.d.ts +30 -0
- package/dist/utils/policy/runs.js +101 -0
- package/dist/utils/policy/status.d.ts +47 -0
- package/dist/utils/policy/status.js +117 -0
- package/dist/utils/policy/types.d.ts +185 -0
- package/dist/utils/policy/types.js +1 -0
- package/dist/utils/reference-checker.js +14 -3
- package/oclif.manifest.json +8538 -3445
- package/package.json +16 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../../../base-command.js';
|
|
3
|
+
import { buildPagingJson, buildPagingParams, formatPagingFooter, normalizeListResponse, pagingFlags } from '../../../../../utils/paging.js';
|
|
4
|
+
export default class EphemeralStaticHostBuildList extends BaseCommand {
|
|
5
|
+
static args = {
|
|
6
|
+
tenant_name: Args.string({
|
|
7
|
+
description: 'Ephemeral tenant name',
|
|
8
|
+
required: true,
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
static description = 'List all builds for a static host of an ephemeral tenant';
|
|
12
|
+
static examples = [
|
|
13
|
+
`$ xano ephemeral static_host build list e4f2-9ab1-xyz1 --static-host default -w 40
|
|
14
|
+
Available builds:
|
|
15
|
+
- v1.0.0 (ID: 1) - Status: completed
|
|
16
|
+
- v1.0.1 (ID: 2) - Status: pending
|
|
17
|
+
`,
|
|
18
|
+
`$ xano ephemeral static_host build list e4f2-9ab1-xyz1 -H myhost --profile production
|
|
19
|
+
Available builds:
|
|
20
|
+
- production (ID: 1) - Status: completed
|
|
21
|
+
- staging (ID: 2) - Status: completed
|
|
22
|
+
`,
|
|
23
|
+
`$ xano ephemeral static_host build list e4f2-9ab1-xyz1 -H default -w 40 --output json
|
|
24
|
+
{
|
|
25
|
+
"count": 1,
|
|
26
|
+
"page": 1,
|
|
27
|
+
"total": 12,
|
|
28
|
+
"items": [
|
|
29
|
+
{
|
|
30
|
+
"id": 52
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
`,
|
|
35
|
+
`$ xano ephemeral static_host build list e4f2-9ab1-xyz1 -H default -p staging -o json --page 2
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
"id": 3,
|
|
39
|
+
"name": "v1.0.2"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
`,
|
|
43
|
+
];
|
|
44
|
+
static flags = {
|
|
45
|
+
...BaseCommand.baseFlags,
|
|
46
|
+
output: Flags.string({
|
|
47
|
+
char: 'o',
|
|
48
|
+
default: 'summary',
|
|
49
|
+
description: 'Output format',
|
|
50
|
+
options: ['summary', 'json'],
|
|
51
|
+
required: false,
|
|
52
|
+
}),
|
|
53
|
+
...pagingFlags('page-only-envelope', { fixedPerPage: 100 }),
|
|
54
|
+
'static-host': Flags.string({
|
|
55
|
+
char: 'H',
|
|
56
|
+
description: 'Static host name',
|
|
57
|
+
required: true,
|
|
58
|
+
}),
|
|
59
|
+
workspace: Flags.string({
|
|
60
|
+
char: 'w',
|
|
61
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
62
|
+
required: false,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
async run() {
|
|
66
|
+
const { args, flags } = await this.parse(EphemeralStaticHostBuildList);
|
|
67
|
+
const { profile, profileName } = this.resolveProfile(flags);
|
|
68
|
+
const tenantName = args.tenant_name;
|
|
69
|
+
const staticHost = flags['static-host'];
|
|
70
|
+
// Determine workspace_id from flag or profile
|
|
71
|
+
let workspaceId;
|
|
72
|
+
if (flags.workspace) {
|
|
73
|
+
workspaceId = flags.workspace;
|
|
74
|
+
}
|
|
75
|
+
else if (profile.workspace) {
|
|
76
|
+
workspaceId = profile.workspace;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.error(`Workspace ID is required. Either:\n` +
|
|
80
|
+
` 1. Provide it as a flag: xano ephemeral static_host build list <tenant_name> --static-host <static_host> -w <workspace_id>\n` +
|
|
81
|
+
` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
|
|
82
|
+
}
|
|
83
|
+
// Build query parameters
|
|
84
|
+
const queryParams = new URLSearchParams(buildPagingParams(flags, 'page-only-envelope'));
|
|
85
|
+
// Construct the API URL
|
|
86
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build?${queryParams.toString()}`;
|
|
87
|
+
// Fetch builds from the API
|
|
88
|
+
try {
|
|
89
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
90
|
+
headers: {
|
|
91
|
+
'accept': 'application/json',
|
|
92
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
93
|
+
},
|
|
94
|
+
method: 'GET',
|
|
95
|
+
}, flags.verbose, profile.access_token);
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const errorText = await response.text();
|
|
98
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
99
|
+
}
|
|
100
|
+
const list = normalizeListResponse(await response.json(), ['builds']);
|
|
101
|
+
const builds = list.items;
|
|
102
|
+
// Output results
|
|
103
|
+
if (flags.output === 'json') {
|
|
104
|
+
this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, tier: 'page-only-envelope' }), null, 2));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// summary format
|
|
108
|
+
if (builds.length === 0) {
|
|
109
|
+
this.log('No builds found');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.log('Available builds:');
|
|
113
|
+
for (const build of builds) {
|
|
114
|
+
if (build.id === undefined) {
|
|
115
|
+
this.log(` - ${build.name}`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const statusInfo = build.status ? ` - Status: ${build.status}` : '';
|
|
119
|
+
this.log(` - ${build.name} (ID: ${build.id})${statusInfo}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const footer = formatPagingFooter(list, { noun: 'build', page: flags.page, tier: 'page-only-envelope' });
|
|
124
|
+
if (footer)
|
|
125
|
+
this.log(footer);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error instanceof Error) {
|
|
130
|
+
this.error(`Failed to fetch builds: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this.error(`Failed to fetch builds: ${String(error)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import BaseCommand from '../../../../../base-command.js';
|
|
2
|
+
export interface StaticHostEnv {
|
|
3
|
+
canonical?: null | string;
|
|
4
|
+
}
|
|
5
|
+
export interface StaticHostSummary {
|
|
6
|
+
[k: string]: unknown;
|
|
7
|
+
dev?: StaticHostEnv;
|
|
8
|
+
name: string;
|
|
9
|
+
prod?: StaticHostEnv;
|
|
10
|
+
}
|
|
11
|
+
export interface BuildSummary {
|
|
12
|
+
canonical?: string;
|
|
13
|
+
id: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Find the deployed `canonical` for a static host's env from a list of hosts.
|
|
17
|
+
* Returns null when the host isn't present or nothing is deployed to that env.
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractEnvCanonical(hosts: StaticHostSummary[], staticHost: string, env: string): null | string;
|
|
20
|
+
/** Find the build whose unique `canonical` matches, or null if none do. */
|
|
21
|
+
export declare function findBuildByCanonical(builds: BuildSummary[], canonical: string): BuildSummary | null;
|
|
22
|
+
export default class EphemeralStaticHostBuildPull extends BaseCommand {
|
|
23
|
+
static args: {
|
|
24
|
+
tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
25
|
+
};
|
|
26
|
+
static description: string;
|
|
27
|
+
static examples: string[];
|
|
28
|
+
static flags: {
|
|
29
|
+
build_id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
|
+
directory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
31
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
|
+
latest: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
33
|
+
source: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
34
|
+
'static-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
35
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
36
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
37
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
38
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
39
|
+
};
|
|
40
|
+
run(): Promise<void>;
|
|
41
|
+
private parseFileDocument;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the build ID currently deployed to a static host's dev/prod env.
|
|
44
|
+
*
|
|
45
|
+
* The deployed env stores the `canonical` of the build it was created from
|
|
46
|
+
* (static_host.{env}.canonical). Each build carries that same unique
|
|
47
|
+
* `canonical`, so we match the env's canonical against the build list to
|
|
48
|
+
* recover the build ID — a shortcut for "list builds, find the deployed one,
|
|
49
|
+
* then pull it".
|
|
50
|
+
*/
|
|
51
|
+
private resolveEnvBuild;
|
|
52
|
+
private resolveLatestBuild;
|
|
53
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import BaseCommand from '../../../../../base-command.js';
|
|
5
|
+
/**
|
|
6
|
+
* Find the deployed `canonical` for a static host's env from a list of hosts.
|
|
7
|
+
* Returns null when the host isn't present or nothing is deployed to that env.
|
|
8
|
+
*/
|
|
9
|
+
export function extractEnvCanonical(hosts, staticHost, env) {
|
|
10
|
+
const host = hosts.find((h) => h.name === staticHost);
|
|
11
|
+
if (!host) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const envObj = host[env];
|
|
15
|
+
return envObj?.canonical ?? null;
|
|
16
|
+
}
|
|
17
|
+
/** Find the build whose unique `canonical` matches, or null if none do. */
|
|
18
|
+
export function findBuildByCanonical(builds, canonical) {
|
|
19
|
+
return builds.find((b) => b.canonical === canonical) ?? null;
|
|
20
|
+
}
|
|
21
|
+
export default class EphemeralStaticHostBuildPull extends BaseCommand {
|
|
22
|
+
static args = {
|
|
23
|
+
tenant_name: Args.string({
|
|
24
|
+
description: 'Ephemeral tenant name',
|
|
25
|
+
required: true,
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
static description = 'Pull a static host build for an ephemeral tenant to disk. Defaults to the original uploaded source (including package.json); use --source built for the compiled/served output.';
|
|
29
|
+
static examples = [
|
|
30
|
+
`$ xano ephemeral static_host build pull e4f2-9ab1-xyz1 --static-host default --build_id 52
|
|
31
|
+
Pulled 15 files to current directory
|
|
32
|
+
`,
|
|
33
|
+
`$ xano ephemeral static_host build pull e4f2-9ab1-xyz1 -H default --build_id 52 -d ./output
|
|
34
|
+
Pulled 15 files to ./output
|
|
35
|
+
`,
|
|
36
|
+
`$ xano ephemeral static_host build pull e4f2-9ab1-xyz1 -H myhost --build_id 123 -w 40
|
|
37
|
+
Pulled 8 files to current directory
|
|
38
|
+
`,
|
|
39
|
+
`$ xano ephemeral static_host build pull e4f2-9ab1-xyz1 -H default --latest
|
|
40
|
+
Pulled 22 files to current directory
|
|
41
|
+
`,
|
|
42
|
+
`$ xano ephemeral static_host build pull e4f2-9ab1-xyz1 -H default --env dev
|
|
43
|
+
Pulled 18 files to current directory
|
|
44
|
+
`,
|
|
45
|
+
`$ xano ephemeral static_host build pull e4f2-9ab1-xyz1 -H default --env prod -d ./prod-release
|
|
46
|
+
Pulled 18 files to ./prod-release
|
|
47
|
+
`,
|
|
48
|
+
`$ xano ephemeral static_host build pull e4f2-9ab1-xyz1 -H default --build_id 52 --source built
|
|
49
|
+
Pulled the compiled/served output instead of the original source
|
|
50
|
+
`,
|
|
51
|
+
];
|
|
52
|
+
static flags = {
|
|
53
|
+
...BaseCommand.baseFlags,
|
|
54
|
+
build_id: Flags.string({
|
|
55
|
+
description: 'Build ID to pull',
|
|
56
|
+
exclusive: ['latest', 'env'],
|
|
57
|
+
required: false,
|
|
58
|
+
}),
|
|
59
|
+
directory: Flags.string({
|
|
60
|
+
char: 'd',
|
|
61
|
+
default: '.',
|
|
62
|
+
description: 'Output directory for pulled files (defaults to current directory)',
|
|
63
|
+
required: false,
|
|
64
|
+
}),
|
|
65
|
+
env: Flags.string({
|
|
66
|
+
description: 'Pull the build currently deployed to this environment',
|
|
67
|
+
exclusive: ['build_id', 'latest'],
|
|
68
|
+
options: ['dev', 'prod'],
|
|
69
|
+
required: false,
|
|
70
|
+
}),
|
|
71
|
+
latest: Flags.boolean({
|
|
72
|
+
default: false,
|
|
73
|
+
description: 'Pull the latest build',
|
|
74
|
+
exclusive: ['build_id', 'env'],
|
|
75
|
+
required: false,
|
|
76
|
+
}),
|
|
77
|
+
source: Flags.string({
|
|
78
|
+
default: 'original',
|
|
79
|
+
description: 'Which files to pull: "original" (the uploaded source, including package.json) or "built" (the compiled/served output)',
|
|
80
|
+
options: ['original', 'built'],
|
|
81
|
+
required: false,
|
|
82
|
+
}),
|
|
83
|
+
'static-host': Flags.string({
|
|
84
|
+
char: 'H',
|
|
85
|
+
description: 'Static host name',
|
|
86
|
+
required: true,
|
|
87
|
+
}),
|
|
88
|
+
workspace: Flags.string({
|
|
89
|
+
char: 'w',
|
|
90
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
91
|
+
required: false,
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
async run() {
|
|
95
|
+
const { args, flags } = await this.parse(EphemeralStaticHostBuildPull);
|
|
96
|
+
if (!flags.build_id && !flags.latest && !flags.env) {
|
|
97
|
+
this.error('One of --build_id <id>, --latest, or --env <dev|prod> is required');
|
|
98
|
+
}
|
|
99
|
+
const { profile, profileName } = this.resolveProfile(flags);
|
|
100
|
+
const tenantName = args.tenant_name;
|
|
101
|
+
const staticHost = flags['static-host'];
|
|
102
|
+
let workspaceId;
|
|
103
|
+
if (flags.workspace) {
|
|
104
|
+
workspaceId = flags.workspace;
|
|
105
|
+
}
|
|
106
|
+
else if (profile.workspace) {
|
|
107
|
+
workspaceId = profile.workspace;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.error(`Workspace ID is required. Either:\n` +
|
|
111
|
+
` 1. Provide it as a flag: xano ephemeral static_host build pull <tenant_name> --static-host <static_host> --build_id <id> -w <workspace_id>\n` +
|
|
112
|
+
` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
|
|
113
|
+
}
|
|
114
|
+
let buildId;
|
|
115
|
+
if (flags.build_id) {
|
|
116
|
+
buildId = flags.build_id;
|
|
117
|
+
}
|
|
118
|
+
else if (flags.env) {
|
|
119
|
+
buildId = await this.resolveEnvBuild({
|
|
120
|
+
env: flags.env,
|
|
121
|
+
profile,
|
|
122
|
+
staticHost,
|
|
123
|
+
tenantName,
|
|
124
|
+
verbose: flags.verbose,
|
|
125
|
+
workspaceId,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
buildId = await this.resolveLatestBuild(profile, workspaceId, tenantName, staticHost, flags.verbose);
|
|
130
|
+
}
|
|
131
|
+
// Default is "original" (the uploaded source); only append the param when
|
|
132
|
+
// pulling the built output, so default requests stay clean.
|
|
133
|
+
const query = flags.source === 'built' ? '?source=built' : '';
|
|
134
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build/${buildId}/multidoc${query}`;
|
|
135
|
+
let responseText;
|
|
136
|
+
try {
|
|
137
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
138
|
+
headers: {
|
|
139
|
+
accept: 'application/json',
|
|
140
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
141
|
+
},
|
|
142
|
+
method: 'GET',
|
|
143
|
+
}, flags.verbose, profile.access_token);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const errorText = await response.text();
|
|
146
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
147
|
+
}
|
|
148
|
+
responseText = await response.text();
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error instanceof Error) {
|
|
152
|
+
this.error(`Failed to fetch build multidoc: ${error.message}`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
this.error(`Failed to fetch build multidoc: ${String(error)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!responseText.trim()) {
|
|
159
|
+
this.log('No files found in build');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const rawDocuments = responseText.split('\n---\n');
|
|
163
|
+
const files = [];
|
|
164
|
+
for (const raw of rawDocuments) {
|
|
165
|
+
const trimmed = raw.trim();
|
|
166
|
+
if (!trimmed)
|
|
167
|
+
continue;
|
|
168
|
+
const parsed = this.parseFileDocument(trimmed);
|
|
169
|
+
if (parsed) {
|
|
170
|
+
files.push(parsed);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (files.length === 0) {
|
|
174
|
+
this.log('No files found in response');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const outputDir = path.resolve(flags.directory);
|
|
178
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
179
|
+
let writtenCount = 0;
|
|
180
|
+
for (const file of files) {
|
|
181
|
+
const filePath = path.resolve(outputDir, file.path);
|
|
182
|
+
if (!filePath.startsWith(outputDir + path.sep) && filePath !== outputDir) {
|
|
183
|
+
this.warn(`Skipping file with path outside output directory: ${file.path}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const fileDir = path.dirname(filePath);
|
|
187
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
188
|
+
if (file.encoding === 'base64') {
|
|
189
|
+
const buffer = Buffer.from(file.content, 'base64');
|
|
190
|
+
fs.writeFileSync(filePath, buffer);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
fs.writeFileSync(filePath, file.content, 'utf8');
|
|
194
|
+
}
|
|
195
|
+
writtenCount++;
|
|
196
|
+
}
|
|
197
|
+
this.log(`Pulled ${writtenCount} files to ${flags.directory}`);
|
|
198
|
+
}
|
|
199
|
+
parseFileDocument(raw) {
|
|
200
|
+
const lines = raw.split('\n');
|
|
201
|
+
const firstLine = lines[0];
|
|
202
|
+
if (!firstLine.startsWith('path: ')) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const filePath = firstLine.slice('path: '.length).trim();
|
|
206
|
+
if (lines.length > 1 && lines[1] === 'encoding: base64') {
|
|
207
|
+
const content = lines.slice(2).join('\n');
|
|
208
|
+
return { content, encoding: 'base64', path: filePath };
|
|
209
|
+
}
|
|
210
|
+
const content = lines.slice(1).join('\n');
|
|
211
|
+
return { content, path: filePath };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Resolve the build ID currently deployed to a static host's dev/prod env.
|
|
215
|
+
*
|
|
216
|
+
* The deployed env stores the `canonical` of the build it was created from
|
|
217
|
+
* (static_host.{env}.canonical). Each build carries that same unique
|
|
218
|
+
* `canonical`, so we match the env's canonical against the build list to
|
|
219
|
+
* recover the build ID — a shortcut for "list builds, find the deployed one,
|
|
220
|
+
* then pull it".
|
|
221
|
+
*/
|
|
222
|
+
async resolveEnvBuild(opts) {
|
|
223
|
+
const { env, profile, staticHost, tenantName, verbose, workspaceId } = opts;
|
|
224
|
+
// 1. Look up the static host to read the deployed canonical for this env.
|
|
225
|
+
const hostUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host`;
|
|
226
|
+
const hostResponse = await this.verboseFetch(hostUrl, {
|
|
227
|
+
headers: {
|
|
228
|
+
accept: 'application/json',
|
|
229
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
230
|
+
},
|
|
231
|
+
method: 'GET',
|
|
232
|
+
}, verbose, profile.access_token);
|
|
233
|
+
if (!hostResponse.ok) {
|
|
234
|
+
const errorText = await hostResponse.text();
|
|
235
|
+
this.error(`Failed to list static hosts: ${hostResponse.status} ${hostResponse.statusText}\n${errorText}`);
|
|
236
|
+
}
|
|
237
|
+
const hostData = (await hostResponse.json());
|
|
238
|
+
const hosts = Array.isArray(hostData)
|
|
239
|
+
? hostData
|
|
240
|
+
: hostData && typeof hostData === 'object' && 'items' in hostData && Array.isArray(hostData.items)
|
|
241
|
+
? hostData.items
|
|
242
|
+
: [];
|
|
243
|
+
const canonical = extractEnvCanonical(hosts, staticHost, env);
|
|
244
|
+
if (!canonical) {
|
|
245
|
+
if (!hosts.some((h) => h.name === staticHost)) {
|
|
246
|
+
this.error(`Static host '${staticHost}' not found`);
|
|
247
|
+
}
|
|
248
|
+
this.error(`No build is deployed to the '${env}' environment of static host '${staticHost}'`);
|
|
249
|
+
}
|
|
250
|
+
// 2. Page through builds to find the one matching the deployed canonical.
|
|
251
|
+
const perPage = 100;
|
|
252
|
+
for (let page = 1;; page++) {
|
|
253
|
+
const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build?page=${page}&per_page=${perPage}`;
|
|
254
|
+
// eslint-disable-next-line no-await-in-loop
|
|
255
|
+
const response = await this.verboseFetch(listUrl, {
|
|
256
|
+
headers: {
|
|
257
|
+
accept: 'application/json',
|
|
258
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
259
|
+
},
|
|
260
|
+
method: 'GET',
|
|
261
|
+
}, verbose, profile.access_token);
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
// eslint-disable-next-line no-await-in-loop
|
|
264
|
+
const errorText = await response.text();
|
|
265
|
+
this.error(`Failed to list builds: ${response.status} ${response.statusText}\n${errorText}`);
|
|
266
|
+
}
|
|
267
|
+
// eslint-disable-next-line no-await-in-loop
|
|
268
|
+
const data = (await response.json());
|
|
269
|
+
const builds = Array.isArray(data)
|
|
270
|
+
? data
|
|
271
|
+
: data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)
|
|
272
|
+
? data.items
|
|
273
|
+
: [];
|
|
274
|
+
const match = findBuildByCanonical(builds, canonical);
|
|
275
|
+
if (match) {
|
|
276
|
+
return String(match.id);
|
|
277
|
+
}
|
|
278
|
+
if (builds.length < perPage) {
|
|
279
|
+
// Reached the last page without a match.
|
|
280
|
+
this.error(`Could not find the build deployed to '${env}' (canonical ${canonical}) in the build list for '${staticHost}'`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async resolveLatestBuild(profile, workspaceId, tenantName, staticHost, verbose) {
|
|
285
|
+
const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build?page=1&per_page=1`;
|
|
286
|
+
const response = await this.verboseFetch(listUrl, {
|
|
287
|
+
headers: {
|
|
288
|
+
accept: 'application/json',
|
|
289
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
290
|
+
},
|
|
291
|
+
method: 'GET',
|
|
292
|
+
}, verbose, profile.access_token);
|
|
293
|
+
if (!response.ok) {
|
|
294
|
+
const errorText = await response.text();
|
|
295
|
+
this.error(`Failed to list builds: ${response.status} ${response.statusText}\n${errorText}`);
|
|
296
|
+
}
|
|
297
|
+
const data = await response.json();
|
|
298
|
+
const builds = Array.isArray(data)
|
|
299
|
+
? data
|
|
300
|
+
: data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)
|
|
301
|
+
? data.items
|
|
302
|
+
: [];
|
|
303
|
+
if (builds.length === 0) {
|
|
304
|
+
this.error(`No builds found for static host '${staticHost}'`);
|
|
305
|
+
}
|
|
306
|
+
return String(builds[0].id);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import BaseCommand from '../../../../../base-command.js';
|
|
2
|
+
export default class EphemeralStaticHostBuildPush extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'no-gitignore': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'no-wait': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
'static-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
};
|
|
22
|
+
run(): Promise<void>;
|
|
23
|
+
private createZipBuffer;
|
|
24
|
+
}
|