@xano/cli 1.0.2 → 1.0.3-beta.3
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 +37 -2
- package/dist/base-command.d.ts +21 -0
- package/dist/base-command.js +100 -1
- package/dist/commands/static_host/build/create/index.d.ts +8 -1
- package/dist/commands/static_host/build/create/index.js +48 -3
- package/dist/commands/static_host/build/delete/index.d.ts +19 -0
- package/dist/commands/static_host/build/delete/index.js +114 -0
- package/dist/commands/static_host/build/get/index.d.ts +1 -1
- package/dist/commands/static_host/build/get/index.js +16 -10
- package/dist/commands/static_host/build/pull/index.d.ts +52 -0
- package/dist/commands/static_host/build/pull/index.js +300 -0
- package/dist/commands/static_host/build/push/index.d.ts +22 -0
- package/dist/commands/static_host/build/push/index.js +198 -0
- package/dist/commands/static_host/create/index.d.ts +17 -0
- package/dist/commands/static_host/create/index.js +86 -0
- package/dist/commands/static_host/deploy/index.d.ts +18 -0
- package/dist/commands/static_host/deploy/index.js +105 -0
- package/dist/commands/static_host/edit/index.d.ts +23 -0
- package/dist/commands/static_host/edit/index.js +151 -0
- package/dist/commands/static_host/get/index.d.ts +18 -0
- package/dist/commands/static_host/get/index.js +94 -0
- package/dist/commands/static_host/migrate/index.d.ts +44 -0
- package/dist/commands/static_host/migrate/index.js +205 -0
- package/dist/utils/multidoc-push.js +1 -1
- package/dist/utils/reference-checker.js +2 -2
- package/oclif.manifest.json +3319 -2417
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -522,14 +522,49 @@ xano sandbox reset --force
|
|
|
522
522
|
# List static hosts
|
|
523
523
|
xano static_host list
|
|
524
524
|
|
|
525
|
-
# Create a
|
|
525
|
+
# Create / get / edit a static host
|
|
526
|
+
xano static_host create marketing --description "Marketing site"
|
|
527
|
+
xano static_host get marketing
|
|
528
|
+
xano static_host edit marketing --name marketing-v2 --description "Updated"
|
|
529
|
+
|
|
530
|
+
# Create a build (name optional — auto-generated from the timestamp if omitted).
|
|
531
|
+
# For package.json builds, the CLI waits for the build to finish (--no-wait to skip).
|
|
526
532
|
xano static_host build create default -f ./build.zip -n "v1.0.0"
|
|
533
|
+
xano static_host build create default -f ./build.zip # name: 20260531-143022
|
|
527
534
|
|
|
528
535
|
# List builds
|
|
529
536
|
xano static_host build list default
|
|
530
537
|
|
|
531
538
|
# Get build details
|
|
532
|
-
xano static_host build get default 52
|
|
539
|
+
xano static_host build get default --build_id 52
|
|
540
|
+
|
|
541
|
+
# Pull a build to disk. Defaults to the original uploaded source
|
|
542
|
+
# (including package.json). Use --source built for the compiled/served output.
|
|
543
|
+
xano static_host build pull default --build_id 52 # By build ID (original source)
|
|
544
|
+
xano static_host build pull default --build_id 52 --source built # Compiled output
|
|
545
|
+
xano static_host build pull default --latest # Latest build
|
|
546
|
+
xano static_host build pull default --env dev # Build currently deployed to dev
|
|
547
|
+
xano static_host build pull default --env prod -d ./prod-release
|
|
548
|
+
|
|
549
|
+
# Push a directory as a new build (name optional — auto-generated if omitted).
|
|
550
|
+
# For package.json builds, the CLI waits for the build to finish (--no-wait to skip).
|
|
551
|
+
xano static_host build push default -d ./dist -n "v1.0.0"
|
|
552
|
+
xano static_host build push default # current dir, auto-name
|
|
553
|
+
xano static_host build push default -n "release" --description "Production build"
|
|
554
|
+
|
|
555
|
+
# Delete a build (prompts for confirmation; --force to skip)
|
|
556
|
+
xano static_host build delete default --build_id 52
|
|
557
|
+
xano static_host build delete default --build_id 52 --force
|
|
558
|
+
|
|
559
|
+
# Deploy a build to an environment
|
|
560
|
+
xano static_host deploy default --build_id 52 --env dev
|
|
561
|
+
xano static_host deploy default --build_id 52 --env prod
|
|
562
|
+
|
|
563
|
+
# Migrate a host to instance-managed (v2) hosting
|
|
564
|
+
xano static_host migrate newsite # one host (both envs)
|
|
565
|
+
xano static_host migrate newsite --env dev # one env
|
|
566
|
+
xano static_host migrate --all # every v1 host in the workspace
|
|
567
|
+
xano static_host migrate --all --dry-run # preview without changing anything
|
|
533
568
|
```
|
|
534
569
|
|
|
535
570
|
## Global Options
|
package/dist/base-command.d.ts
CHANGED
|
@@ -103,4 +103,25 @@ export default abstract class BaseCommand extends Command {
|
|
|
103
103
|
* Use this for all Metadata API calls to support the --verbose flag.
|
|
104
104
|
*/
|
|
105
105
|
protected verboseFetch(url: string, options: RequestInit, verbose: boolean, authToken?: string): Promise<Response>;
|
|
106
|
+
/**
|
|
107
|
+
* Poll a static-host build until it reaches a terminal status (ok | error),
|
|
108
|
+
* showing a live, ticking spinner with the current stage and elapsed time —
|
|
109
|
+
* mirroring the UI's build progress for async (package.json) builds, which
|
|
110
|
+
* keep running after the upload returns.
|
|
111
|
+
*
|
|
112
|
+
* On a TTY it renders an animated spinner via ux.action; when quiet (JSON
|
|
113
|
+
* output) or non-interactive it falls back to plain one-line status updates.
|
|
114
|
+
*
|
|
115
|
+
* Returns the final status. Resolves to the last-seen status on timeout.
|
|
116
|
+
*/
|
|
117
|
+
protected waitForBuild(opts: {
|
|
118
|
+
buildId: number | string;
|
|
119
|
+
intervalMs?: number;
|
|
120
|
+
profile: ProfileConfig;
|
|
121
|
+
quiet?: boolean;
|
|
122
|
+
staticHost: string;
|
|
123
|
+
timeoutMs?: number;
|
|
124
|
+
verbose: boolean;
|
|
125
|
+
workspaceId: string;
|
|
126
|
+
}): Promise<string>;
|
|
106
127
|
}
|
package/dist/base-command.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command, Flags } from '@oclif/core';
|
|
1
|
+
import { Command, Flags, ux } from '@oclif/core';
|
|
2
2
|
import * as yaml from 'js-yaml';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import * as os from 'node:os';
|
|
@@ -314,4 +314,103 @@ export default class BaseCommand extends Command {
|
|
|
314
314
|
}
|
|
315
315
|
return response;
|
|
316
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Poll a static-host build until it reaches a terminal status (ok | error),
|
|
319
|
+
* showing a live, ticking spinner with the current stage and elapsed time —
|
|
320
|
+
* mirroring the UI's build progress for async (package.json) builds, which
|
|
321
|
+
* keep running after the upload returns.
|
|
322
|
+
*
|
|
323
|
+
* On a TTY it renders an animated spinner via ux.action; when quiet (JSON
|
|
324
|
+
* output) or non-interactive it falls back to plain one-line status updates.
|
|
325
|
+
*
|
|
326
|
+
* Returns the final status. Resolves to the last-seen status on timeout.
|
|
327
|
+
*/
|
|
328
|
+
async waitForBuild(opts) {
|
|
329
|
+
const { buildId, profile, quiet, staticHost, verbose, workspaceId } = opts;
|
|
330
|
+
const intervalMs = opts.intervalMs ?? 2000;
|
|
331
|
+
const timeoutMs = opts.timeoutMs ?? 600_000; // 10 min
|
|
332
|
+
const terminal = new Set(['error', 'ok']);
|
|
333
|
+
const url = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${staticHost}/build/${buildId}`;
|
|
334
|
+
// Spinner only on an interactive TTY and when not emitting JSON. Verbose mode
|
|
335
|
+
// also disables it (the spinner would interleave with request/response logs).
|
|
336
|
+
const animate = Boolean(process.stdout.isTTY) && !quiet && !verbose;
|
|
337
|
+
const start = Date.now();
|
|
338
|
+
const elapsed = () => Math.round((Date.now() - start) / 1000);
|
|
339
|
+
let stage = 'pending';
|
|
340
|
+
let ticker;
|
|
341
|
+
// Reflect the current stage: live spinner status on a TTY, else a plain line.
|
|
342
|
+
const render = () => {
|
|
343
|
+
if (animate) {
|
|
344
|
+
ux.action.status = `${stageLabel(stage)} (${elapsed()}s)`;
|
|
345
|
+
}
|
|
346
|
+
else if (!quiet && !terminal.has(stage)) {
|
|
347
|
+
this.log(`Build status: ${stage}`);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
// Stop the spinner/ticker and emit a final line.
|
|
351
|
+
const conclude = (message) => {
|
|
352
|
+
if (ticker)
|
|
353
|
+
clearInterval(ticker);
|
|
354
|
+
if (animate) {
|
|
355
|
+
ux.action.stop(message);
|
|
356
|
+
}
|
|
357
|
+
else if (!quiet) {
|
|
358
|
+
this.log(message);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
if (animate) {
|
|
362
|
+
ux.action.start('Building', stageLabel(stage));
|
|
363
|
+
// Re-render every 120ms so the elapsed counter ticks even between polls.
|
|
364
|
+
ticker = setInterval(render, 120);
|
|
365
|
+
}
|
|
366
|
+
else if (!quiet) {
|
|
367
|
+
this.log(`Build status: ${stage}`);
|
|
368
|
+
}
|
|
369
|
+
/* eslint-disable no-await-in-loop */
|
|
370
|
+
while (Date.now() - start < timeoutMs) {
|
|
371
|
+
const response = await this.verboseFetch(url, {
|
|
372
|
+
headers: { accept: 'application/json', Authorization: `Bearer ${profile.access_token}` },
|
|
373
|
+
method: 'GET',
|
|
374
|
+
}, verbose, profile.access_token);
|
|
375
|
+
if (response.ok) {
|
|
376
|
+
const build = (await response.json());
|
|
377
|
+
const status = build.status ?? 'pending';
|
|
378
|
+
if (status !== stage) {
|
|
379
|
+
stage = status;
|
|
380
|
+
render();
|
|
381
|
+
}
|
|
382
|
+
if (terminal.has(status)) {
|
|
383
|
+
const took = `${elapsed()}s`;
|
|
384
|
+
conclude(status === 'ok' ? `done in ${took}` : `failed after ${took}`);
|
|
385
|
+
return status;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
await new Promise((resolve) => {
|
|
389
|
+
setTimeout(resolve, intervalMs);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
/* eslint-enable no-await-in-loop */
|
|
393
|
+
conclude(`stopped waiting after ${Math.round(timeoutMs / 1000)}s (last status: ${stage || 'unknown'})`);
|
|
394
|
+
return stage;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/** Human-friendly label for a build status stage. */
|
|
398
|
+
function stageLabel(status) {
|
|
399
|
+
switch (status) {
|
|
400
|
+
case 'building': {
|
|
401
|
+
return 'Installing & building (npm)';
|
|
402
|
+
}
|
|
403
|
+
case 'ok': {
|
|
404
|
+
return 'Finishing';
|
|
405
|
+
}
|
|
406
|
+
case 'pending': {
|
|
407
|
+
return 'Queued';
|
|
408
|
+
}
|
|
409
|
+
case 'publishing': {
|
|
410
|
+
return 'Publishing files';
|
|
411
|
+
}
|
|
412
|
+
default: {
|
|
413
|
+
return status;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
317
416
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import BaseCommand from '../../../../base-command.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a default build name from a compact timestamp: `YYYYMMDD-HHmmss`
|
|
4
|
+
* (e.g. `20260531-143022`). Sortable, distinct down to the second, and uses
|
|
5
|
+
* local time so it lines up with when the user ran the command.
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateBuildName(date?: Date): string;
|
|
2
8
|
export default class StaticHostBuildCreate extends BaseCommand {
|
|
3
9
|
static args: {
|
|
4
10
|
static_host: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -8,7 +14,8 @@ export default class StaticHostBuildCreate extends BaseCommand {
|
|
|
8
14
|
static flags: {
|
|
9
15
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
16
|
file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
'no-wait': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
19
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
20
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
21
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -2,6 +2,21 @@ import { Args, Flags } from '@oclif/core';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import BaseCommand from '../../../../base-command.js';
|
|
5
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
6
|
+
/**
|
|
7
|
+
* Generate a default build name from a compact timestamp: `YYYYMMDD-HHmmss`
|
|
8
|
+
* (e.g. `20260531-143022`). Sortable, distinct down to the second, and uses
|
|
9
|
+
* local time so it lines up with when the user ran the command.
|
|
10
|
+
*/
|
|
11
|
+
export function generateBuildName(date = new Date()) {
|
|
12
|
+
const y = date.getFullYear();
|
|
13
|
+
const mo = pad2(date.getMonth() + 1);
|
|
14
|
+
const d = pad2(date.getDate());
|
|
15
|
+
const h = pad2(date.getHours());
|
|
16
|
+
const mi = pad2(date.getMinutes());
|
|
17
|
+
const s = pad2(date.getSeconds());
|
|
18
|
+
return `${y}${mo}${d}-${h}${mi}${s}`;
|
|
19
|
+
}
|
|
5
20
|
export default class StaticHostBuildCreate extends BaseCommand {
|
|
6
21
|
static args = {
|
|
7
22
|
static_host: Args.string({
|
|
@@ -16,6 +31,12 @@ Build created successfully!
|
|
|
16
31
|
ID: 123
|
|
17
32
|
Name: v1.0.0
|
|
18
33
|
Status: pending
|
|
34
|
+
`,
|
|
35
|
+
`$ xano static_host:build:create default -f ./build.zip
|
|
36
|
+
Build created successfully!
|
|
37
|
+
ID: 123
|
|
38
|
+
Name: 20260531-143022
|
|
39
|
+
Status: pending
|
|
19
40
|
`,
|
|
20
41
|
`$ xano static_host:build:create default -w 40 -f ./dist.zip -n "production" -d "Production build"
|
|
21
42
|
Build created successfully!
|
|
@@ -45,8 +66,13 @@ Description: Production build
|
|
|
45
66
|
}),
|
|
46
67
|
name: Flags.string({
|
|
47
68
|
char: 'n',
|
|
48
|
-
description: 'Build name',
|
|
49
|
-
required:
|
|
69
|
+
description: 'Build name (auto-generated from the current timestamp if omitted)',
|
|
70
|
+
required: false,
|
|
71
|
+
}),
|
|
72
|
+
'no-wait': Flags.boolean({
|
|
73
|
+
default: false,
|
|
74
|
+
description: 'Return immediately after upload instead of waiting for the build to finish',
|
|
75
|
+
required: false,
|
|
50
76
|
}),
|
|
51
77
|
output: Flags.string({
|
|
52
78
|
char: 'o',
|
|
@@ -102,7 +128,10 @@ Description: Production build
|
|
|
102
128
|
const fileBuffer = fs.readFileSync(filePath);
|
|
103
129
|
const blob = new Blob([fileBuffer], { type: 'application/zip' });
|
|
104
130
|
formData.append('file', blob, path.basename(filePath));
|
|
105
|
-
|
|
131
|
+
// Name is optional — fall back to a timestamped name so builds can be
|
|
132
|
+
// created without thinking up a label each time.
|
|
133
|
+
const buildName = flags.name ?? generateBuildName();
|
|
134
|
+
formData.append('name', buildName);
|
|
106
135
|
if (flags.description) {
|
|
107
136
|
formData.append('description', flags.description);
|
|
108
137
|
}
|
|
@@ -141,6 +170,22 @@ Description: Production build
|
|
|
141
170
|
this.log(`Description: ${flags.description}`);
|
|
142
171
|
}
|
|
143
172
|
}
|
|
173
|
+
// Async (package.json) builds keep running after upload. Unless --no-wait,
|
|
174
|
+
// poll until the build finishes so the CLI mirrors the UI's progress.
|
|
175
|
+
const inProgress = result.status !== undefined && !['error', 'ok'].includes(result.status);
|
|
176
|
+
if (inProgress && !flags['no-wait']) {
|
|
177
|
+
const finalStatus = await this.waitForBuild({
|
|
178
|
+
buildId: result.id,
|
|
179
|
+
profile,
|
|
180
|
+
quiet: flags.output === 'json',
|
|
181
|
+
staticHost: args.static_host,
|
|
182
|
+
verbose: flags.verbose,
|
|
183
|
+
workspaceId,
|
|
184
|
+
});
|
|
185
|
+
if (finalStatus === 'error') {
|
|
186
|
+
this.error(`Build ${result.id} failed (status: error). Check the build log with: xano static_host build get ${args.static_host} --build_id ${result.id}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
144
189
|
}
|
|
145
190
|
catch (error) {
|
|
146
191
|
if (error instanceof Error) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import BaseCommand from '../../../../base-command.js';
|
|
2
|
+
export default class StaticHostBuildDelete extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
static_host: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
build_id: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
private confirm;
|
|
19
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../../base-command.js';
|
|
3
|
+
export default class StaticHostBuildDelete extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
static_host: Args.string({
|
|
6
|
+
description: 'Static Host name',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Delete a static host build permanently. This action cannot be undone.';
|
|
11
|
+
static examples = [
|
|
12
|
+
`$ xano static_host build delete default --build_id 52
|
|
13
|
+
Are you sure you want to delete build 52 from static host 'default'? This action cannot be undone. (y/N) y
|
|
14
|
+
Deleted build 52 from static host 'default'
|
|
15
|
+
`,
|
|
16
|
+
`$ xano static_host build delete default --build_id 52 --force
|
|
17
|
+
Deleted build 52 from static host 'default'
|
|
18
|
+
`,
|
|
19
|
+
`$ xano static_host build delete myhost --build_id 123 -w 40 -f
|
|
20
|
+
Deleted build 123 from static host 'myhost'
|
|
21
|
+
`,
|
|
22
|
+
`$ xano static_host build delete default --build_id 52 -f -o json`,
|
|
23
|
+
];
|
|
24
|
+
static flags = {
|
|
25
|
+
...BaseCommand.baseFlags,
|
|
26
|
+
build_id: Flags.string({
|
|
27
|
+
description: 'Build ID to delete',
|
|
28
|
+
required: true,
|
|
29
|
+
}),
|
|
30
|
+
force: Flags.boolean({
|
|
31
|
+
char: 'f',
|
|
32
|
+
default: false,
|
|
33
|
+
description: '[CRITICAL] NEVER run without explicit user confirmation. Skips the confirmation prompt.',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
output: Flags.string({
|
|
37
|
+
char: 'o',
|
|
38
|
+
default: 'summary',
|
|
39
|
+
description: 'Output format',
|
|
40
|
+
options: ['summary', 'json'],
|
|
41
|
+
required: false,
|
|
42
|
+
}),
|
|
43
|
+
workspace: Flags.string({
|
|
44
|
+
char: 'w',
|
|
45
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
46
|
+
required: false,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
async run() {
|
|
50
|
+
const { args, flags } = await this.parse(StaticHostBuildDelete);
|
|
51
|
+
const { profile, profileName } = this.resolveProfile(flags);
|
|
52
|
+
// Determine workspace_id from flag or profile
|
|
53
|
+
let workspaceId;
|
|
54
|
+
if (flags.workspace) {
|
|
55
|
+
workspaceId = flags.workspace;
|
|
56
|
+
}
|
|
57
|
+
else if (profile.workspace) {
|
|
58
|
+
workspaceId = profile.workspace;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.error(`Workspace ID is required. Either:\n` +
|
|
62
|
+
` 1. Provide it as a flag: xano static_host build delete <static_host> --build_id <id> -w <workspace_id>\n` +
|
|
63
|
+
` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
|
|
64
|
+
}
|
|
65
|
+
if (!flags.force) {
|
|
66
|
+
const confirmed = await this.confirm(`Are you sure you want to delete build ${flags.build_id} from static host '${args.static_host}'? This action cannot be undone.`);
|
|
67
|
+
if (!confirmed) {
|
|
68
|
+
this.log('Deletion cancelled.');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}/build/${flags.build_id}`;
|
|
73
|
+
try {
|
|
74
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
75
|
+
headers: {
|
|
76
|
+
'accept': 'application/json',
|
|
77
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
78
|
+
},
|
|
79
|
+
method: 'DELETE',
|
|
80
|
+
}, flags.verbose, profile.access_token);
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const errorText = await response.text();
|
|
83
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
84
|
+
}
|
|
85
|
+
if (flags.output === 'json') {
|
|
86
|
+
this.log(JSON.stringify({ build_id: flags.build_id, deleted: true, static_host: args.static_host }, null, 2));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.log(`Deleted build ${flags.build_id} from static host '${args.static_host}'`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error instanceof Error) {
|
|
94
|
+
this.error(`Failed to delete build: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.error(`Failed to delete build: ${String(error)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async confirm(message) {
|
|
102
|
+
const readline = await import('node:readline');
|
|
103
|
+
const rl = readline.createInterface({
|
|
104
|
+
input: process.stdin,
|
|
105
|
+
output: process.stdout,
|
|
106
|
+
});
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
109
|
+
rl.close();
|
|
110
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import BaseCommand from '../../../../base-command.js';
|
|
2
2
|
export default class StaticHostBuildGet extends BaseCommand {
|
|
3
3
|
static args: {
|
|
4
|
-
build_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
4
|
static_host: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
5
|
};
|
|
7
6
|
static description: string;
|
|
8
7
|
static examples: string[];
|
|
9
8
|
static flags: {
|
|
9
|
+
build_id: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -2,10 +2,6 @@ import { Args, Flags } from '@oclif/core';
|
|
|
2
2
|
import BaseCommand from '../../../../base-command.js';
|
|
3
3
|
export default class StaticHostBuildGet extends BaseCommand {
|
|
4
4
|
static args = {
|
|
5
|
-
build_id: Args.string({
|
|
6
|
-
description: 'Build ID',
|
|
7
|
-
required: true,
|
|
8
|
-
}),
|
|
9
5
|
static_host: Args.string({
|
|
10
6
|
description: 'Static Host name',
|
|
11
7
|
required: true,
|
|
@@ -13,24 +9,24 @@ export default class StaticHostBuildGet extends BaseCommand {
|
|
|
13
9
|
};
|
|
14
10
|
static description = 'Get details of a specific build for a static host';
|
|
15
11
|
static examples = [
|
|
16
|
-
`$ xano static_host:build:get default 52
|
|
12
|
+
`$ xano static_host:build:get default --build_id 52
|
|
17
13
|
Build Details:
|
|
18
14
|
ID: 52
|
|
19
15
|
Name: v1.0.0
|
|
20
16
|
Status: completed
|
|
21
17
|
`,
|
|
22
|
-
`$ xano static_host:build:get default 52 -w 40
|
|
18
|
+
`$ xano static_host:build:get default --build_id 52 -w 40
|
|
23
19
|
Build Details:
|
|
24
20
|
ID: 52
|
|
25
21
|
Name: v1.0.0
|
|
26
22
|
Status: completed
|
|
27
23
|
`,
|
|
28
|
-
`$ xano static_host:build:get myhost 123 --profile production
|
|
24
|
+
`$ xano static_host:build:get myhost --build_id 123 --profile production
|
|
29
25
|
Build Details:
|
|
30
26
|
ID: 123
|
|
31
27
|
Name: production-build
|
|
32
28
|
`,
|
|
33
|
-
`$ xano static_host:build:get default 52 -o json
|
|
29
|
+
`$ xano static_host:build:get default --build_id 52 -o json
|
|
34
30
|
{
|
|
35
31
|
"id": 52,
|
|
36
32
|
"name": "v1.0.0",
|
|
@@ -40,6 +36,10 @@ Name: production-build
|
|
|
40
36
|
];
|
|
41
37
|
static flags = {
|
|
42
38
|
...BaseCommand.baseFlags,
|
|
39
|
+
build_id: Flags.string({
|
|
40
|
+
description: 'Build ID',
|
|
41
|
+
required: true,
|
|
42
|
+
}),
|
|
43
43
|
output: Flags.string({
|
|
44
44
|
char: 'o',
|
|
45
45
|
default: 'summary',
|
|
@@ -66,11 +66,11 @@ Name: production-build
|
|
|
66
66
|
}
|
|
67
67
|
else {
|
|
68
68
|
this.error(`Workspace ID is required. Either:\n` +
|
|
69
|
-
` 1. Provide it as a flag: xano static_host:build:get <static_host> <
|
|
69
|
+
` 1. Provide it as a flag: xano static_host:build:get <static_host> --build_id <id> -w <workspace_id>\n` +
|
|
70
70
|
` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
|
|
71
71
|
}
|
|
72
72
|
// Construct the API URL
|
|
73
|
-
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}/build/${
|
|
73
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}/build/${flags.build_id}`;
|
|
74
74
|
// Fetch build from the API
|
|
75
75
|
try {
|
|
76
76
|
const response = await this.verboseFetch(apiUrl, {
|
|
@@ -104,6 +104,12 @@ Name: production-build
|
|
|
104
104
|
if (build.status) {
|
|
105
105
|
this.log(`Status: ${build.status}`);
|
|
106
106
|
}
|
|
107
|
+
if (typeof build.file_count === 'number') {
|
|
108
|
+
this.log(`Files: ${build.file_count}`);
|
|
109
|
+
}
|
|
110
|
+
if (typeof build.file_bytes === 'number') {
|
|
111
|
+
this.log(`Size: ${build.file_bytes} bytes`);
|
|
112
|
+
}
|
|
107
113
|
if (build.created_at) {
|
|
108
114
|
this.log(`Created: ${build.created_at}`);
|
|
109
115
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 StaticHostBuildPull extends BaseCommand {
|
|
23
|
+
static args: {
|
|
24
|
+
static_host: 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
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
35
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
36
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
37
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
38
|
+
};
|
|
39
|
+
run(): Promise<void>;
|
|
40
|
+
private parseFileDocument;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the build ID currently deployed to a static host's dev/prod env.
|
|
43
|
+
*
|
|
44
|
+
* The deployed env stores the `canonical` of the build it was created from
|
|
45
|
+
* (static_host.{env}.canonical). Each build carries that same unique
|
|
46
|
+
* `canonical`, so we match the env's canonical against the build list to
|
|
47
|
+
* recover the build ID — a shortcut for "list builds, find the deployed one,
|
|
48
|
+
* then pull it".
|
|
49
|
+
*/
|
|
50
|
+
private resolveEnvBuild;
|
|
51
|
+
private resolveLatestBuild;
|
|
52
|
+
}
|