@xano/cli 1.0.6-beta.0 → 1.0.6-beta.10

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 (101) hide show
  1. package/README.md +124 -7
  2. package/dist/base-command.d.ts +2 -0
  3. package/dist/base-command.js +10 -4
  4. package/dist/commands/branch/list/index.js +5 -1
  5. package/dist/commands/ephemeral/impersonate/index.d.ts +1 -0
  6. package/dist/commands/ephemeral/impersonate/index.js +11 -3
  7. package/dist/commands/ephemeral/list/index.js +2 -1
  8. package/dist/commands/ephemeral/pull/index.d.ts +3 -7
  9. package/dist/commands/ephemeral/pull/index.js +25 -109
  10. package/dist/commands/ephemeral/push/index.d.ts +13 -1
  11. package/dist/commands/ephemeral/push/index.js +94 -16
  12. package/dist/commands/ephemeral/static_host/build/delete/index.d.ts +20 -0
  13. package/dist/commands/ephemeral/static_host/build/delete/index.js +121 -0
  14. package/dist/commands/ephemeral/static_host/build/get/index.d.ts +18 -0
  15. package/dist/commands/ephemeral/static_host/build/get/index.js +137 -0
  16. package/dist/commands/ephemeral/static_host/build/list/index.d.ts +18 -0
  17. package/dist/commands/ephemeral/static_host/build/list/index.js +137 -0
  18. package/dist/commands/ephemeral/static_host/build/pull/index.d.ts +53 -0
  19. package/dist/commands/ephemeral/static_host/build/pull/index.js +308 -0
  20. package/dist/commands/ephemeral/static_host/build/push/index.d.ts +24 -0
  21. package/dist/commands/ephemeral/static_host/build/push/index.js +250 -0
  22. package/dist/commands/ephemeral/static_host/create/index.d.ts +18 -0
  23. package/dist/commands/ephemeral/static_host/create/index.js +92 -0
  24. package/dist/commands/ephemeral/static_host/deploy/index.d.ts +19 -0
  25. package/dist/commands/ephemeral/static_host/deploy/index.js +112 -0
  26. package/dist/commands/ephemeral/static_host/edit/index.d.ts +24 -0
  27. package/dist/commands/ephemeral/static_host/edit/index.js +158 -0
  28. package/dist/commands/ephemeral/static_host/get/index.d.ts +19 -0
  29. package/dist/commands/ephemeral/static_host/get/index.js +101 -0
  30. package/dist/commands/ephemeral/static_host/list/index.d.ts +17 -0
  31. package/dist/commands/ephemeral/static_host/list/index.js +132 -0
  32. package/dist/commands/flatten/index.d.ts +34 -0
  33. package/dist/commands/flatten/index.js +140 -0
  34. package/dist/commands/function/list/index.d.ts +4 -4
  35. package/dist/commands/function/list/index.js +30 -38
  36. package/dist/commands/knowledge/list/index.js +2 -1
  37. package/dist/commands/platform/list/index.js +13 -14
  38. package/dist/commands/release/list/index.js +13 -14
  39. package/dist/commands/release/pull/index.d.ts +0 -6
  40. package/dist/commands/release/pull/index.js +10 -95
  41. package/dist/commands/sandbox/env/list/index.js +4 -0
  42. package/dist/commands/sandbox/pull/index.d.ts +0 -1
  43. package/dist/commands/sandbox/pull/index.js +10 -77
  44. package/dist/commands/sandbox/unit_test/list/index.d.ts +2 -0
  45. package/dist/commands/sandbox/unit_test/list/index.js +9 -14
  46. package/dist/commands/sandbox/unit_test/run_all/index.d.ts +1 -0
  47. package/dist/commands/sandbox/unit_test/run_all/index.js +67 -37
  48. package/dist/commands/sandbox/workflow_test/list/index.d.ts +2 -0
  49. package/dist/commands/sandbox/workflow_test/list/index.js +9 -14
  50. package/dist/commands/sandbox/workflow_test/run_all/index.d.ts +1 -0
  51. package/dist/commands/sandbox/workflow_test/run_all/index.js +65 -35
  52. package/dist/commands/static_host/build/list/index.d.ts +2 -3
  53. package/dist/commands/static_host/build/list/index.js +19 -40
  54. package/dist/commands/static_host/build/pull/index.js +12 -9
  55. package/dist/commands/static_host/list/index.d.ts +2 -3
  56. package/dist/commands/static_host/list/index.js +20 -40
  57. package/dist/commands/static_host/migrate/index.js +14 -10
  58. package/dist/commands/tenant/backup/list/index.js +13 -14
  59. package/dist/commands/tenant/cluster/list/index.js +9 -10
  60. package/dist/commands/tenant/env/list/index.js +4 -0
  61. package/dist/commands/tenant/list/index.js +19 -20
  62. package/dist/commands/tenant/pull/index.d.ts +0 -6
  63. package/dist/commands/tenant/pull/index.js +10 -95
  64. package/dist/commands/tenant/snapshot/list/index.js +5 -1
  65. package/dist/commands/tenant/unit_test/list/index.d.ts +4 -2
  66. package/dist/commands/tenant/unit_test/list/index.js +9 -14
  67. package/dist/commands/tenant/unit_test/run_all/index.d.ts +1 -0
  68. package/dist/commands/tenant/unit_test/run_all/index.js +67 -37
  69. package/dist/commands/tenant/workflow_test/list/index.d.ts +4 -2
  70. package/dist/commands/tenant/workflow_test/list/index.js +9 -14
  71. package/dist/commands/tenant/workflow_test/run_all/index.d.ts +1 -0
  72. package/dist/commands/tenant/workflow_test/run_all/index.js +65 -35
  73. package/dist/commands/unit_test/list/index.d.ts +3 -1
  74. package/dist/commands/unit_test/list/index.js +9 -14
  75. package/dist/commands/unit_test/run_all/index.d.ts +1 -0
  76. package/dist/commands/unit_test/run_all/index.js +68 -40
  77. package/dist/commands/workflow_test/list/index.d.ts +3 -1
  78. package/dist/commands/workflow_test/list/index.js +10 -13
  79. package/dist/commands/workflow_test/run_all/index.d.ts +1 -0
  80. package/dist/commands/workflow_test/run_all/index.js +67 -36
  81. package/dist/commands/workspace/git/pull/index.js +53 -3
  82. package/dist/commands/workspace/list/index.js +7 -13
  83. package/dist/commands/workspace/pull/index.d.ts +0 -6
  84. package/dist/commands/workspace/pull/index.js +10 -95
  85. package/dist/utils/concurrency.d.ts +29 -0
  86. package/dist/utils/concurrency.js +54 -0
  87. package/dist/utils/document-parser.d.ts +116 -0
  88. package/dist/utils/document-parser.js +274 -1
  89. package/dist/utils/flatten.d.ts +36 -0
  90. package/dist/utils/flatten.js +68 -0
  91. package/dist/utils/knowledge-sync.d.ts +1 -0
  92. package/dist/utils/knowledge-sync.js +11 -5
  93. package/dist/utils/microservice-wait.d.ts +53 -0
  94. package/dist/utils/microservice-wait.js +83 -0
  95. package/dist/utils/multidoc-push.d.ts +12 -0
  96. package/dist/utils/multidoc-push.js +84 -0
  97. package/dist/utils/paging.d.ts +145 -0
  98. package/dist/utils/paging.js +199 -0
  99. package/dist/utils/reference-checker.js +13 -0
  100. package/oclif.manifest.json +5038 -3635
  101. package/package.json +7 -1
@@ -1,31 +1,38 @@
1
- import { Flags } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
2
  import * as fs from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import BaseCommand from '../../../base-command.js';
5
+ import { isAwaited, waitForMicroservices, } from '../../../utils/microservice-wait.js';
5
6
  import { executePush } from '../../../utils/multidoc-push.js';
6
7
  export default class EphemeralPush extends BaseCommand {
8
+ static args = {
9
+ tenant_name: Args.string({
10
+ description: 'Ephemeral tenant name to push to',
11
+ required: true,
12
+ }),
13
+ };
7
14
  static description = '[IMPORTANT] ALWAYS run --dry-run first and show the user the output before pushing. Push local documents to an ephemeral tenant via multidoc import. By default, only changed files are pushed (partial mode). Use --sync to push all files. Shows a preview of changes before pushing unless --force is specified. Use --dry-run to preview only.';
8
15
  static examples = [
9
- `$ xano ephemeral push -t e4f2-9ab1-xyz1
16
+ `$ xano ephemeral push e4f2-9ab1-xyz1
10
17
  Push from current directory (default partial mode)
11
18
  `,
12
- `$ xano ephemeral push -t e4f2-9ab1-xyz1 -d ./my-workspace
19
+ `$ xano ephemeral push e4f2-9ab1-xyz1 -d ./my-workspace
13
20
  Push from a specific directory
14
21
  `,
15
- `$ xano ephemeral push -t e4f2-9ab1-xyz1 --sync
22
+ `$ xano ephemeral push e4f2-9ab1-xyz1 --sync
16
23
  Push all files to the ephemeral tenant
17
24
  `,
18
- `$ xano ephemeral push -t e4f2-9ab1-xyz1 --sync --delete
25
+ `$ xano ephemeral push e4f2-9ab1-xyz1 --sync --delete
19
26
  Push all files and delete remote objects not included
20
27
  `,
21
- `$ xano ephemeral push -t e4f2-9ab1-xyz1 --dry-run
28
+ `$ xano ephemeral push e4f2-9ab1-xyz1 --dry-run
22
29
  Preview changes without pushing
23
30
  `,
24
- `$ xano ephemeral push -t e4f2-9ab1-xyz1 --force
31
+ `$ xano ephemeral push e4f2-9ab1-xyz1 --force
25
32
  Skip preview and push immediately
26
33
  `,
27
- `$ xano ephemeral push -t e4f2-9ab1-xyz1 --records --env`,
28
- `$ xano ephemeral push -t e4f2-9ab1-xyz1 --truncate`,
34
+ `$ xano ephemeral push e4f2-9ab1-xyz1 --records --env`,
35
+ `$ xano ephemeral push e4f2-9ab1-xyz1 --truncate`,
29
36
  ];
30
37
  static flags = {
31
38
  ...BaseCommand.baseFlags,
@@ -71,11 +78,6 @@ Skip preview and push immediately
71
78
  description: 'Full push — send all files, not just changed ones. Required for --delete.',
72
79
  required: false,
73
80
  }),
74
- tenant: Flags.string({
75
- char: 't',
76
- description: 'Ephemeral tenant name to push to',
77
- required: true,
78
- }),
79
81
  transaction: Flags.boolean({
80
82
  allowNo: true,
81
83
  default: true,
@@ -87,6 +89,17 @@ Skip preview and push immediately
87
89
  description: '[CRITICAL] STOP and confirm with the user; this truncates live tables before importing.',
88
90
  required: false,
89
91
  }),
92
+ wait: Flags.boolean({
93
+ default: false,
94
+ description: 'After pushing, wait for auto-deployed microservices (tenant_deploy="auto") to become ready. Exits non-zero if any fails to deploy or the wait times out. Ignored with --dry-run.',
95
+ required: false,
96
+ }),
97
+ 'wait-timeout': Flags.integer({
98
+ default: 300,
99
+ description: 'Seconds to wait for microservices to become ready when --wait is set (default 300).',
100
+ min: 1,
101
+ required: false,
102
+ }),
90
103
  workspace: Flags.string({
91
104
  char: 'w',
92
105
  description: 'Workspace ID (uses profile workspace if not provided)',
@@ -94,7 +107,7 @@ Skip preview and push immediately
94
107
  }),
95
108
  };
96
109
  async run() {
97
- const { flags } = await this.parse(EphemeralPush);
110
+ const { args, flags } = await this.parse(EphemeralPush);
98
111
  const { profile } = this.resolveProfile(flags);
99
112
  const workspaceId = flags.workspace || profile.workspace;
100
113
  if (!workspaceId) {
@@ -107,7 +120,7 @@ Skip preview and push immediately
107
120
  if (!fs.statSync(inputDir).isDirectory()) {
108
121
  this.error(`Not a directory: ${inputDir}`);
109
122
  }
110
- const tenantName = flags.tenant;
123
+ const tenantName = args.tenant_name;
111
124
  const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
112
125
  const target = {
113
126
  buildDryRunUrl: (params) => `${baseUrl}/multidoc/dry-run?${params.toString()}`,
@@ -139,5 +152,70 @@ Skip preview and push immediately
139
152
  inputDir,
140
153
  verboseFetch: this.verboseFetch.bind(this),
141
154
  }, target, pushFlags);
155
+ // --wait: after a real push, poll the tenant's live microservice status until
156
+ // every auto-deployed microservice (tenant_deploy="auto") is ready or the
157
+ // wait times out. Skipped on --dry-run (nothing was deployed).
158
+ if (flags.wait && !flags['dry-run']) {
159
+ await this.waitForDeploy(baseUrl, profile.access_token, tenantName, flags['wait-timeout'], flags.verbose);
160
+ }
161
+ }
162
+ /**
163
+ * Poll GET .../tenant/{name}/microservice until auto microservices settle.
164
+ * Renders a one-line-per-microservice progress view that updates in place, then
165
+ * exits non-zero if any auto microservice failed or the wait timed out.
166
+ */
167
+ async waitForDeploy(baseUrl, accessToken, tenantName, timeoutSeconds, verbose) {
168
+ this.log('');
169
+ this.log(`Waiting for microservices to deploy (timeout ${timeoutSeconds}s)…`);
170
+ const icon = (e) => {
171
+ if (!isAwaited(e))
172
+ return '➖';
173
+ switch (e.status) {
174
+ case 'error': {
175
+ return '❌';
176
+ }
177
+ case 'ok': {
178
+ return '✅';
179
+ }
180
+ default: {
181
+ return '⏳';
182
+ }
183
+ }
184
+ };
185
+ const line = (e) => {
186
+ const detail = isAwaited(e) ? e.detail || e.status : `skipped (${e.tenant_deploy ?? 'manual'})`;
187
+ return ` ${icon(e)} ${e.name.padEnd(24)} ${detail}`;
188
+ };
189
+ const result = await waitForMicroservices({
190
+ accessToken,
191
+ onPoll: (entries) => {
192
+ // Re-render the block: move the cursor up over the previous render (if any)
193
+ // and rewrite. Falls back to plain appends when not a TTY.
194
+ if (process.stdout.isTTY && this.lastRenderLines > 0) {
195
+ process.stdout.write(`[${this.lastRenderLines}A`);
196
+ }
197
+ const lines = entries.map((e) => line(e));
198
+ for (const l of lines)
199
+ this.log(l);
200
+ this.lastRenderLines = process.stdout.isTTY ? lines.length : 0;
201
+ },
202
+ statusUrl: `${baseUrl}/microservice`,
203
+ timeoutMs: timeoutSeconds * 1000,
204
+ verbose,
205
+ verboseFetch: this.verboseFetch.bind(this),
206
+ });
207
+ const awaited = result.entries.filter((e) => isAwaited(e));
208
+ const ready = awaited.filter((e) => e.status === 'ok').length;
209
+ if (result.timedOut) {
210
+ this.error(`Timed out after ${timeoutSeconds}s waiting for microservices (${ready}/${awaited.length} ready). ` +
211
+ `Check status with the Xano dashboard or re-run with a larger --wait-timeout.`);
212
+ }
213
+ if (result.hadError) {
214
+ const failed = awaited.filter((e) => e.status === 'error').map((e) => `${e.name} (${e.detail || 'error'})`);
215
+ this.error(`Microservice deploy failed: ${failed.join(', ')}`);
216
+ }
217
+ this.log(`All microservices ready (${ready}/${awaited.length}).`);
142
218
  }
219
+ /** Number of lines the last progress render wrote (for in-place TTY updates). */
220
+ lastRenderLines = 0;
143
221
  }
@@ -0,0 +1,20 @@
1
+ import BaseCommand from '../../../../../base-command.js';
2
+ export default class EphemeralStaticHostBuildDelete 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
+ 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
+ 'static-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ };
18
+ run(): Promise<void>;
19
+ private confirm;
20
+ }
@@ -0,0 +1,121 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../../../base-command.js';
3
+ export default class EphemeralStaticHostBuildDelete extends BaseCommand {
4
+ static args = {
5
+ tenant_name: Args.string({
6
+ description: 'Ephemeral tenant name',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Delete a static host build for an ephemeral tenant permanently. This action cannot be undone.';
11
+ static examples = [
12
+ `$ xano ephemeral static_host build delete e4f2-9ab1-xyz1 --static-host 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 ephemeral static_host build delete e4f2-9ab1-xyz1 -H default --build_id 52 --force
17
+ Deleted build 52 from static host 'default'
18
+ `,
19
+ `$ xano ephemeral static_host build delete e4f2-9ab1-xyz1 -H myhost --build_id 123 -w 40 -f
20
+ Deleted build 123 from static host 'myhost'
21
+ `,
22
+ `$ xano ephemeral static_host build delete e4f2-9ab1-xyz1 -H 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
+ 'static-host': Flags.string({
44
+ char: 'H',
45
+ description: 'Static host name',
46
+ required: true,
47
+ }),
48
+ workspace: Flags.string({
49
+ char: 'w',
50
+ description: 'Workspace ID (optional if set in profile)',
51
+ required: false,
52
+ }),
53
+ };
54
+ async run() {
55
+ const { args, flags } = await this.parse(EphemeralStaticHostBuildDelete);
56
+ const { profile, profileName } = this.resolveProfile(flags);
57
+ const tenantName = args.tenant_name;
58
+ const staticHost = flags['static-host'];
59
+ // Determine workspace_id from flag or profile
60
+ let workspaceId;
61
+ if (flags.workspace) {
62
+ workspaceId = flags.workspace;
63
+ }
64
+ else if (profile.workspace) {
65
+ workspaceId = profile.workspace;
66
+ }
67
+ else {
68
+ this.error(`Workspace ID is required. Either:\n` +
69
+ ` 1. Provide it as a flag: xano ephemeral static_host build delete <tenant_name> --static-host <static_host> --build_id <id> -w <workspace_id>\n` +
70
+ ` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
71
+ }
72
+ if (!flags.force) {
73
+ const confirmed = await this.confirm(`Are you sure you want to delete build ${flags.build_id} from static host '${staticHost}'? This action cannot be undone.`);
74
+ if (!confirmed) {
75
+ this.log('Deletion cancelled.');
76
+ return;
77
+ }
78
+ }
79
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build/${flags.build_id}`;
80
+ try {
81
+ const response = await this.verboseFetch(apiUrl, {
82
+ headers: {
83
+ 'accept': 'application/json',
84
+ 'Authorization': `Bearer ${profile.access_token}`,
85
+ },
86
+ method: 'DELETE',
87
+ }, flags.verbose, profile.access_token);
88
+ if (!response.ok) {
89
+ const errorText = await response.text();
90
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
91
+ }
92
+ if (flags.output === 'json') {
93
+ this.log(JSON.stringify({ build_id: flags.build_id, deleted: true, static_host: staticHost }, null, 2));
94
+ }
95
+ else {
96
+ this.log(`Deleted build ${flags.build_id} from static host '${staticHost}'`);
97
+ }
98
+ }
99
+ catch (error) {
100
+ if (error instanceof Error) {
101
+ this.error(`Failed to delete build: ${error.message}`);
102
+ }
103
+ else {
104
+ this.error(`Failed to delete build: ${String(error)}`);
105
+ }
106
+ }
107
+ }
108
+ async confirm(message) {
109
+ const readline = await import('node:readline');
110
+ const rl = readline.createInterface({
111
+ input: process.stdin,
112
+ output: process.stdout,
113
+ });
114
+ return new Promise((resolve) => {
115
+ rl.question(`${message} (y/N) `, (answer) => {
116
+ rl.close();
117
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
118
+ });
119
+ });
120
+ }
121
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../../../base-command.js';
2
+ export default class EphemeralStaticHostBuildGet 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
+ build_id: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'static-host': 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
+ }
@@ -0,0 +1,137 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../../../base-command.js';
3
+ export default class EphemeralStaticHostBuildGet extends BaseCommand {
4
+ static args = {
5
+ tenant_name: Args.string({
6
+ description: 'Ephemeral tenant name',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Get details of a specific build for a static host of an ephemeral tenant';
11
+ static examples = [
12
+ `$ xano ephemeral static_host build get e4f2-9ab1-xyz1 --static-host default --build_id 52
13
+ Build Details:
14
+ ID: 52
15
+ Name: v1.0.0
16
+ Status: completed
17
+ `,
18
+ `$ xano ephemeral static_host build get e4f2-9ab1-xyz1 -H default --build_id 52 -w 40
19
+ Build Details:
20
+ ID: 52
21
+ Name: v1.0.0
22
+ Status: completed
23
+ `,
24
+ `$ xano ephemeral static_host build get e4f2-9ab1-xyz1 -H myhost --build_id 123 --profile production
25
+ Build Details:
26
+ ID: 123
27
+ Name: production-build
28
+ `,
29
+ `$ xano ephemeral static_host build get e4f2-9ab1-xyz1 -H default --build_id 52 -o json
30
+ {
31
+ "id": 52,
32
+ "name": "v1.0.0",
33
+ "status": "completed"
34
+ }
35
+ `,
36
+ ];
37
+ static flags = {
38
+ ...BaseCommand.baseFlags,
39
+ build_id: Flags.string({
40
+ description: 'Build ID',
41
+ required: true,
42
+ }),
43
+ output: Flags.string({
44
+ char: 'o',
45
+ default: 'summary',
46
+ description: 'Output format',
47
+ options: ['summary', 'json'],
48
+ required: false,
49
+ }),
50
+ 'static-host': Flags.string({
51
+ char: 'H',
52
+ description: 'Static host name',
53
+ required: true,
54
+ }),
55
+ workspace: Flags.string({
56
+ char: 'w',
57
+ description: 'Workspace ID (optional if set in profile)',
58
+ required: false,
59
+ }),
60
+ };
61
+ async run() {
62
+ const { args, flags } = await this.parse(EphemeralStaticHostBuildGet);
63
+ const { profile, profileName } = this.resolveProfile(flags);
64
+ const tenantName = args.tenant_name;
65
+ const staticHost = flags['static-host'];
66
+ // Determine workspace_id from flag or profile
67
+ let workspaceId;
68
+ if (flags.workspace) {
69
+ workspaceId = flags.workspace;
70
+ }
71
+ else if (profile.workspace) {
72
+ workspaceId = profile.workspace;
73
+ }
74
+ else {
75
+ this.error(`Workspace ID is required. Either:\n` +
76
+ ` 1. Provide it as a flag: xano ephemeral static_host build get <tenant_name> --static-host <static_host> --build_id <id> -w <workspace_id>\n` +
77
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
78
+ }
79
+ // Construct the API URL
80
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}/build/${flags.build_id}`;
81
+ // Fetch build from the API
82
+ try {
83
+ const response = await this.verboseFetch(apiUrl, {
84
+ headers: {
85
+ 'accept': 'application/json',
86
+ 'Authorization': `Bearer ${profile.access_token}`,
87
+ },
88
+ method: 'GET',
89
+ }, flags.verbose, profile.access_token);
90
+ if (!response.ok) {
91
+ const errorText = await response.text();
92
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
93
+ }
94
+ const build = await response.json();
95
+ // Validate response
96
+ if (!build || typeof build !== 'object') {
97
+ this.error('Unexpected API response format');
98
+ }
99
+ // Output results
100
+ if (flags.output === 'json') {
101
+ this.log(JSON.stringify(build, null, 2));
102
+ }
103
+ else {
104
+ // summary format
105
+ this.log('Build Details:');
106
+ this.log(`ID: ${build.id}`);
107
+ this.log(`Name: ${build.name}`);
108
+ if (build.description) {
109
+ this.log(`Description: ${build.description}`);
110
+ }
111
+ if (build.status) {
112
+ this.log(`Status: ${build.status}`);
113
+ }
114
+ if (typeof build.file_count === 'number') {
115
+ this.log(`Files: ${build.file_count}`);
116
+ }
117
+ if (typeof build.file_bytes === 'number') {
118
+ this.log(`Size: ${build.file_bytes} bytes`);
119
+ }
120
+ if (build.created_at) {
121
+ this.log(`Created: ${build.created_at}`);
122
+ }
123
+ if (build.updated_at) {
124
+ this.log(`Updated: ${build.updated_at}`);
125
+ }
126
+ }
127
+ }
128
+ catch (error) {
129
+ if (error instanceof Error) {
130
+ this.error(`Failed to fetch build: ${error.message}`);
131
+ }
132
+ else {
133
+ this.error(`Failed to fetch build: ${String(error)}`);
134
+ }
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../../../base-command.js';
2
+ export default class EphemeralStaticHostBuildList 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
+ 'static-host': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
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
+ }
@@ -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
+ }