@xano/cli 1.0.2-beta.9 → 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.
Files changed (29) hide show
  1. package/README.md +39 -2
  2. package/dist/base-command.d.ts +21 -0
  3. package/dist/base-command.js +100 -1
  4. package/dist/commands/sandbox/review/index.d.ts +1 -0
  5. package/dist/commands/sandbox/review/index.js +11 -0
  6. package/dist/commands/static_host/build/create/index.d.ts +8 -1
  7. package/dist/commands/static_host/build/create/index.js +48 -3
  8. package/dist/commands/static_host/build/delete/index.d.ts +19 -0
  9. package/dist/commands/static_host/build/delete/index.js +114 -0
  10. package/dist/commands/static_host/build/get/index.d.ts +1 -1
  11. package/dist/commands/static_host/build/get/index.js +16 -10
  12. package/dist/commands/static_host/build/pull/index.d.ts +52 -0
  13. package/dist/commands/static_host/build/pull/index.js +300 -0
  14. package/dist/commands/static_host/build/push/index.d.ts +22 -0
  15. package/dist/commands/static_host/build/push/index.js +198 -0
  16. package/dist/commands/static_host/create/index.d.ts +17 -0
  17. package/dist/commands/static_host/create/index.js +86 -0
  18. package/dist/commands/static_host/deploy/index.d.ts +18 -0
  19. package/dist/commands/static_host/deploy/index.js +105 -0
  20. package/dist/commands/static_host/edit/index.d.ts +23 -0
  21. package/dist/commands/static_host/edit/index.js +151 -0
  22. package/dist/commands/static_host/get/index.d.ts +18 -0
  23. package/dist/commands/static_host/get/index.js +94 -0
  24. package/dist/commands/static_host/migrate/index.d.ts +44 -0
  25. package/dist/commands/static_host/migrate/index.js +205 -0
  26. package/dist/utils/multidoc-push.js +1 -1
  27. package/dist/utils/reference-checker.js +2 -2
  28. package/oclif.manifest.json +3642 -2722
  29. package/package.json +3 -1
package/README.md CHANGED
@@ -505,6 +505,8 @@ xano sandbox push --review # Push and open sandbox
505
505
 
506
506
  # Review (open in browser)
507
507
  xano sandbox review
508
+ xano sandbox review --url-only # Print the URL without opening the browser
509
+ xano sandbox review --insecure # Skip TLS verification (self-signed certs)
508
510
 
509
511
  # Impersonate (open in browser)
510
512
  xano sandbox impersonate
@@ -520,14 +522,49 @@ xano sandbox reset --force
520
522
  # List static hosts
521
523
  xano static_host list
522
524
 
523
- # Create a build
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).
524
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
525
534
 
526
535
  # List builds
527
536
  xano static_host build list default
528
537
 
529
538
  # Get build details
530
- 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
531
568
  ```
532
569
 
533
570
  ## Global Options
@@ -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
  }
@@ -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
  }
@@ -3,6 +3,7 @@ export default class SandboxReview extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ insecure: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
8
  'url-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
9
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -10,9 +10,16 @@ Review session started!
10
10
  `,
11
11
  `$ xano sandbox review -u`,
12
12
  `$ xano sandbox review -o json`,
13
+ `$ xano sandbox review --insecure`,
13
14
  ];
14
15
  static flags = {
15
16
  ...BaseCommand.baseFlags,
17
+ insecure: Flags.boolean({
18
+ char: 'k',
19
+ default: false,
20
+ description: 'Skip TLS certificate verification (for self-signed certificates)',
21
+ required: false,
22
+ }),
16
23
  output: Flags.string({
17
24
  char: 'o',
18
25
  default: 'summary',
@@ -29,6 +36,10 @@ Review session started!
29
36
  };
30
37
  async run() {
31
38
  const { flags } = await this.parse(SandboxReview);
39
+ if (flags.insecure) {
40
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
41
+ this.warn('TLS certificate verification is disabled (insecure mode)');
42
+ }
32
43
  const { profile } = this.resolveProfile(flags);
33
44
  const apiUrl = `${profile.instance_origin}/api:meta/sandbox/impersonate`;
34
45
  try {
@@ -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: true,
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
- formData.append('name', flags.name);
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> <build_id> -w <workspace_id>\n` +
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/${args.build_id}`;
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
+ }