@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
@@ -0,0 +1,300 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import BaseCommand from '../../../../base-command.js';
5
+ /**
6
+ * Find the deployed `canonical` for a static host's env from a list of hosts.
7
+ * Returns null when the host isn't present or nothing is deployed to that env.
8
+ */
9
+ export function extractEnvCanonical(hosts, staticHost, env) {
10
+ const host = hosts.find((h) => h.name === staticHost);
11
+ if (!host) {
12
+ return null;
13
+ }
14
+ const envObj = host[env];
15
+ return envObj?.canonical ?? null;
16
+ }
17
+ /** Find the build whose unique `canonical` matches, or null if none do. */
18
+ export function findBuildByCanonical(builds, canonical) {
19
+ return builds.find((b) => b.canonical === canonical) ?? null;
20
+ }
21
+ export default class StaticHostBuildPull extends BaseCommand {
22
+ static args = {
23
+ static_host: Args.string({
24
+ description: 'Static Host name',
25
+ required: true,
26
+ }),
27
+ };
28
+ static description = 'Pull a static host build to disk. Defaults to the original uploaded source (including package.json); use --source built for the compiled/served output.';
29
+ static examples = [
30
+ `$ xano static_host build pull default --build_id 52
31
+ Pulled 15 files to current directory
32
+ `,
33
+ `$ xano static_host build pull default --build_id 52 -d ./output
34
+ Pulled 15 files to ./output
35
+ `,
36
+ `$ xano static_host build pull myhost --build_id 123 -w 40
37
+ Pulled 8 files to current directory
38
+ `,
39
+ `$ xano static_host build pull default --latest
40
+ Pulled 22 files to current directory
41
+ `,
42
+ `$ xano static_host build pull default --env dev
43
+ Pulled 18 files to current directory
44
+ `,
45
+ `$ xano static_host build pull default --env prod -d ./prod-release
46
+ Pulled 18 files to ./prod-release
47
+ `,
48
+ `$ xano static_host build pull default --build_id 52 --source built
49
+ Pulled the compiled/served output instead of the original source
50
+ `,
51
+ ];
52
+ static flags = {
53
+ ...BaseCommand.baseFlags,
54
+ build_id: Flags.string({
55
+ description: 'Build ID to pull',
56
+ exclusive: ['latest', 'env'],
57
+ required: false,
58
+ }),
59
+ directory: Flags.string({
60
+ char: 'd',
61
+ default: '.',
62
+ description: 'Output directory for pulled files (defaults to current directory)',
63
+ required: false,
64
+ }),
65
+ env: Flags.string({
66
+ description: 'Pull the build currently deployed to this environment',
67
+ exclusive: ['build_id', 'latest'],
68
+ options: ['dev', 'prod'],
69
+ required: false,
70
+ }),
71
+ latest: Flags.boolean({
72
+ default: false,
73
+ description: 'Pull the latest build',
74
+ exclusive: ['build_id', 'env'],
75
+ required: false,
76
+ }),
77
+ source: Flags.string({
78
+ default: 'original',
79
+ description: 'Which files to pull: "original" (the uploaded source, including package.json) or "built" (the compiled/served output)',
80
+ options: ['original', 'built'],
81
+ required: false,
82
+ }),
83
+ workspace: Flags.string({
84
+ char: 'w',
85
+ description: 'Workspace ID (optional if set in profile)',
86
+ required: false,
87
+ }),
88
+ };
89
+ async run() {
90
+ const { args, flags } = await this.parse(StaticHostBuildPull);
91
+ if (!flags.build_id && !flags.latest && !flags.env) {
92
+ this.error('One of --build_id <id>, --latest, or --env <dev|prod> is required');
93
+ }
94
+ const { profile, profileName } = this.resolveProfile(flags);
95
+ let workspaceId;
96
+ if (flags.workspace) {
97
+ workspaceId = flags.workspace;
98
+ }
99
+ else if (profile.workspace) {
100
+ workspaceId = profile.workspace;
101
+ }
102
+ else {
103
+ this.error(`Workspace ID is required. Either:\n` +
104
+ ` 1. Provide it as a flag: xano static_host build pull <static_host> --build_id <id> -w <workspace_id>\n` +
105
+ ` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
106
+ }
107
+ let buildId;
108
+ if (flags.build_id) {
109
+ buildId = flags.build_id;
110
+ }
111
+ else if (flags.env) {
112
+ buildId = await this.resolveEnvBuild({
113
+ env: flags.env,
114
+ profile,
115
+ staticHost: args.static_host,
116
+ verbose: flags.verbose,
117
+ workspaceId,
118
+ });
119
+ }
120
+ else {
121
+ buildId = await this.resolveLatestBuild(profile, workspaceId, args.static_host, flags.verbose);
122
+ }
123
+ // Default is "original" (the uploaded source); only append the param when
124
+ // pulling the built output, so default requests stay clean.
125
+ const query = flags.source === 'built' ? '?source=built' : '';
126
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}/build/${buildId}/multidoc${query}`;
127
+ let responseText;
128
+ try {
129
+ const response = await this.verboseFetch(apiUrl, {
130
+ headers: {
131
+ accept: 'application/json',
132
+ Authorization: `Bearer ${profile.access_token}`,
133
+ },
134
+ method: 'GET',
135
+ }, flags.verbose, profile.access_token);
136
+ if (!response.ok) {
137
+ const errorText = await response.text();
138
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
139
+ }
140
+ responseText = await response.text();
141
+ }
142
+ catch (error) {
143
+ if (error instanceof Error) {
144
+ this.error(`Failed to fetch build multidoc: ${error.message}`);
145
+ }
146
+ else {
147
+ this.error(`Failed to fetch build multidoc: ${String(error)}`);
148
+ }
149
+ }
150
+ if (!responseText.trim()) {
151
+ this.log('No files found in build');
152
+ return;
153
+ }
154
+ const rawDocuments = responseText.split('\n---\n');
155
+ const files = [];
156
+ for (const raw of rawDocuments) {
157
+ const trimmed = raw.trim();
158
+ if (!trimmed)
159
+ continue;
160
+ const parsed = this.parseFileDocument(trimmed);
161
+ if (parsed) {
162
+ files.push(parsed);
163
+ }
164
+ }
165
+ if (files.length === 0) {
166
+ this.log('No files found in response');
167
+ return;
168
+ }
169
+ const outputDir = path.resolve(flags.directory);
170
+ fs.mkdirSync(outputDir, { recursive: true });
171
+ let writtenCount = 0;
172
+ for (const file of files) {
173
+ const filePath = path.resolve(outputDir, file.path);
174
+ if (!filePath.startsWith(outputDir + path.sep) && filePath !== outputDir) {
175
+ this.warn(`Skipping file with path outside output directory: ${file.path}`);
176
+ continue;
177
+ }
178
+ const fileDir = path.dirname(filePath);
179
+ fs.mkdirSync(fileDir, { recursive: true });
180
+ if (file.encoding === 'base64') {
181
+ const buffer = Buffer.from(file.content, 'base64');
182
+ fs.writeFileSync(filePath, buffer);
183
+ }
184
+ else {
185
+ fs.writeFileSync(filePath, file.content, 'utf8');
186
+ }
187
+ writtenCount++;
188
+ }
189
+ this.log(`Pulled ${writtenCount} files to ${flags.directory}`);
190
+ }
191
+ parseFileDocument(raw) {
192
+ const lines = raw.split('\n');
193
+ const firstLine = lines[0];
194
+ if (!firstLine.startsWith('path: ')) {
195
+ return null;
196
+ }
197
+ const filePath = firstLine.slice('path: '.length).trim();
198
+ if (lines.length > 1 && lines[1] === 'encoding: base64') {
199
+ const content = lines.slice(2).join('\n');
200
+ return { content, encoding: 'base64', path: filePath };
201
+ }
202
+ const content = lines.slice(1).join('\n');
203
+ return { content, path: filePath };
204
+ }
205
+ /**
206
+ * Resolve the build ID currently deployed to a static host's dev/prod env.
207
+ *
208
+ * The deployed env stores the `canonical` of the build it was created from
209
+ * (static_host.{env}.canonical). Each build carries that same unique
210
+ * `canonical`, so we match the env's canonical against the build list to
211
+ * recover the build ID — a shortcut for "list builds, find the deployed one,
212
+ * then pull it".
213
+ */
214
+ async resolveEnvBuild(opts) {
215
+ const { env, profile, staticHost, verbose, workspaceId } = opts;
216
+ // 1. Look up the static host to read the deployed canonical for this env.
217
+ const hostUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host`;
218
+ const hostResponse = await this.verboseFetch(hostUrl, {
219
+ headers: {
220
+ accept: 'application/json',
221
+ Authorization: `Bearer ${profile.access_token}`,
222
+ },
223
+ method: 'GET',
224
+ }, verbose, profile.access_token);
225
+ if (!hostResponse.ok) {
226
+ const errorText = await hostResponse.text();
227
+ this.error(`Failed to list static hosts: ${hostResponse.status} ${hostResponse.statusText}\n${errorText}`);
228
+ }
229
+ const hostData = (await hostResponse.json());
230
+ const hosts = Array.isArray(hostData)
231
+ ? hostData
232
+ : hostData && typeof hostData === 'object' && 'items' in hostData && Array.isArray(hostData.items)
233
+ ? hostData.items
234
+ : [];
235
+ const canonical = extractEnvCanonical(hosts, staticHost, env);
236
+ if (!canonical) {
237
+ if (!hosts.some((h) => h.name === staticHost)) {
238
+ this.error(`Static host '${staticHost}' not found`);
239
+ }
240
+ this.error(`No build is deployed to the '${env}' environment of static host '${staticHost}'`);
241
+ }
242
+ // 2. Page through builds to find the one matching the deployed canonical.
243
+ const perPage = 100;
244
+ for (let page = 1;; page++) {
245
+ const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${staticHost}/build?page=${page}&per_page=${perPage}`;
246
+ // eslint-disable-next-line no-await-in-loop
247
+ const response = await this.verboseFetch(listUrl, {
248
+ headers: {
249
+ accept: 'application/json',
250
+ Authorization: `Bearer ${profile.access_token}`,
251
+ },
252
+ method: 'GET',
253
+ }, verbose, profile.access_token);
254
+ if (!response.ok) {
255
+ // eslint-disable-next-line no-await-in-loop
256
+ const errorText = await response.text();
257
+ this.error(`Failed to list builds: ${response.status} ${response.statusText}\n${errorText}`);
258
+ }
259
+ // eslint-disable-next-line no-await-in-loop
260
+ const data = (await response.json());
261
+ const builds = Array.isArray(data)
262
+ ? data
263
+ : data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)
264
+ ? data.items
265
+ : [];
266
+ const match = findBuildByCanonical(builds, canonical);
267
+ if (match) {
268
+ return String(match.id);
269
+ }
270
+ if (builds.length < perPage) {
271
+ // Reached the last page without a match.
272
+ this.error(`Could not find the build deployed to '${env}' (canonical ${canonical}) in the build list for '${staticHost}'`);
273
+ }
274
+ }
275
+ }
276
+ async resolveLatestBuild(profile, workspaceId, staticHost, verbose) {
277
+ const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${staticHost}/build?page=1&per_page=1`;
278
+ const response = await this.verboseFetch(listUrl, {
279
+ headers: {
280
+ accept: 'application/json',
281
+ Authorization: `Bearer ${profile.access_token}`,
282
+ },
283
+ method: 'GET',
284
+ }, verbose, profile.access_token);
285
+ if (!response.ok) {
286
+ const errorText = await response.text();
287
+ this.error(`Failed to list builds: ${response.status} ${response.statusText}\n${errorText}`);
288
+ }
289
+ const data = await response.json();
290
+ const builds = Array.isArray(data)
291
+ ? data
292
+ : data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)
293
+ ? data.items
294
+ : [];
295
+ if (builds.length === 0) {
296
+ this.error(`No builds found for static host '${staticHost}'`);
297
+ }
298
+ return String(builds[0].id);
299
+ }
300
+ }
@@ -0,0 +1,22 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class StaticHostBuildPush 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
+ description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ directory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'no-wait': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ };
19
+ run(): Promise<void>;
20
+ private countFiles;
21
+ private createZipBuffer;
22
+ }
@@ -0,0 +1,198 @@
1
+ import { Args, Flags, ux } from '@oclif/core';
2
+ import archiver from 'archiver';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import BaseCommand from '../../../../base-command.js';
6
+ import { generateBuildName } from '../create/index.js';
7
+ export default class StaticHostBuildPush extends BaseCommand {
8
+ static args = {
9
+ static_host: Args.string({
10
+ description: 'Static Host name',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Push a directory as a new static host build';
15
+ static examples = [
16
+ `$ xano static_host build push default -n "v1.0.0" -d ./dist
17
+ Pushed 15 files as build "v1.0.0"
18
+ ID: 123
19
+ `,
20
+ `$ xano static_host build push default -n "v1.0.0"
21
+ Pushed 8 files as build "v1.0.0" (from current directory)
22
+ `,
23
+ `$ xano static_host build push default
24
+ Pushed 8 files as build "20260531-143022"
25
+ `,
26
+ `$ xano static_host build push myhost -n "production" --description "Production build" -w 40
27
+ Pushed 22 files as build "production"
28
+ ID: 124
29
+ `,
30
+ `$ xano static_host build push default -n "release-1.2" -d ./build -o json`,
31
+ ];
32
+ static flags = {
33
+ ...BaseCommand.baseFlags,
34
+ description: Flags.string({
35
+ description: 'Build description',
36
+ required: false,
37
+ }),
38
+ directory: Flags.string({
39
+ char: 'd',
40
+ default: '.',
41
+ description: 'Directory to push (defaults to current directory)',
42
+ required: false,
43
+ }),
44
+ name: Flags.string({
45
+ char: 'n',
46
+ description: 'Build name (auto-generated from the current timestamp if omitted)',
47
+ required: false,
48
+ }),
49
+ 'no-wait': Flags.boolean({
50
+ default: false,
51
+ description: 'Return immediately after upload instead of waiting for the build to finish',
52
+ required: false,
53
+ }),
54
+ output: Flags.string({
55
+ char: 'o',
56
+ default: 'summary',
57
+ description: 'Output format',
58
+ options: ['summary', 'json'],
59
+ required: false,
60
+ }),
61
+ workspace: Flags.string({
62
+ char: 'w',
63
+ description: 'Workspace ID (optional if set in profile)',
64
+ required: false,
65
+ }),
66
+ };
67
+ async run() {
68
+ const { args, flags } = await this.parse(StaticHostBuildPush);
69
+ const { profile, profileName } = this.resolveProfile(flags);
70
+ let workspaceId;
71
+ if (flags.workspace) {
72
+ workspaceId = flags.workspace;
73
+ }
74
+ else if (profile.workspace) {
75
+ workspaceId = profile.workspace;
76
+ }
77
+ else {
78
+ this.error(`Workspace ID is required. Either:\n` +
79
+ ` 1. Provide it as a flag: xano static_host build push <static_host> -n <name> -w <workspace_id>\n` +
80
+ ` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
81
+ }
82
+ const sourceDir = path.resolve(flags.directory);
83
+ if (!fs.existsSync(sourceDir)) {
84
+ this.error(`Directory not found: ${sourceDir}`);
85
+ }
86
+ const stats = fs.statSync(sourceDir);
87
+ if (!stats.isDirectory()) {
88
+ this.error(`Path is not a directory: ${sourceDir}`);
89
+ }
90
+ const animate = Boolean(process.stdout.isTTY) && flags.output !== 'json' && !flags.verbose;
91
+ const fileCount = this.countFiles(sourceDir);
92
+ if (animate) {
93
+ ux.action.start('Packaging', `${fileCount} files`);
94
+ }
95
+ const zipBuffer = await this.createZipBuffer(sourceDir);
96
+ const sizeMB = (zipBuffer.length / (1024 * 1024)).toFixed(1);
97
+ if (animate) {
98
+ ux.action.stop(`${fileCount} files (${sizeMB} MB)`);
99
+ ux.action.start('Uploading');
100
+ }
101
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${args.static_host}/build`;
102
+ // Name is optional — fall back to a timestamped name so builds can be
103
+ // pushed without thinking up a label each time.
104
+ const buildName = flags.name ?? generateBuildName();
105
+ const formData = new globalThis.FormData();
106
+ const blob = new Blob([new Uint8Array(zipBuffer)], { type: 'application/zip' });
107
+ formData.append('file', blob, 'build.zip');
108
+ formData.append('name', buildName);
109
+ if (flags.description) {
110
+ formData.append('description', flags.description);
111
+ }
112
+ try {
113
+ const response = await this.verboseFetch(apiUrl, {
114
+ body: formData,
115
+ headers: {
116
+ 'accept': 'application/json',
117
+ 'Authorization': `Bearer ${profile.access_token}`,
118
+ },
119
+ method: 'POST',
120
+ }, flags.verbose, profile.access_token);
121
+ if (animate) {
122
+ ux.action.stop('done');
123
+ }
124
+ if (!response.ok) {
125
+ const errorText = await response.text();
126
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
127
+ }
128
+ const result = await response.json();
129
+ if (!result || typeof result !== 'object') {
130
+ this.error('Unexpected API response format');
131
+ }
132
+ if (flags.output === 'json') {
133
+ this.log(JSON.stringify(result, null, 2));
134
+ }
135
+ else {
136
+ this.log(`Pushed ${fileCount} files as build "${buildName}" (${sizeMB} MB)`);
137
+ this.log(`ID: ${result.id}`);
138
+ if (result.status) {
139
+ this.log(`Status: ${result.status}`);
140
+ }
141
+ }
142
+ // Async (package.json) builds keep running after upload. Unless --no-wait,
143
+ // poll until the build finishes so the CLI mirrors the UI's progress.
144
+ const inProgress = result.status !== undefined && !['error', 'ok'].includes(result.status);
145
+ if (inProgress && !flags['no-wait']) {
146
+ const finalStatus = await this.waitForBuild({
147
+ buildId: result.id,
148
+ profile,
149
+ quiet: flags.output === 'json',
150
+ staticHost: args.static_host,
151
+ verbose: flags.verbose,
152
+ workspaceId,
153
+ });
154
+ if (finalStatus === 'error') {
155
+ 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}`);
156
+ }
157
+ }
158
+ }
159
+ catch (error) {
160
+ if (error instanceof Error) {
161
+ this.error(`Failed to push build: ${error.message}`);
162
+ }
163
+ else {
164
+ this.error(`Failed to push build: ${String(error)}`);
165
+ }
166
+ }
167
+ }
168
+ countFiles(dir) {
169
+ let count = 0;
170
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
171
+ for (const entry of entries) {
172
+ if (entry.isDirectory()) {
173
+ count += this.countFiles(path.join(dir, entry.name));
174
+ }
175
+ else if (entry.isFile()) {
176
+ count++;
177
+ }
178
+ }
179
+ return count;
180
+ }
181
+ async createZipBuffer(sourceDir) {
182
+ return new Promise((resolve, reject) => {
183
+ const chunks = [];
184
+ const archive = archiver('zip', { zlib: { level: 9 } });
185
+ archive.on('data', (chunk) => {
186
+ chunks.push(chunk);
187
+ });
188
+ archive.on('end', () => {
189
+ resolve(Buffer.concat(chunks));
190
+ });
191
+ archive.on('error', (err) => {
192
+ reject(err);
193
+ });
194
+ archive.directory(sourceDir, false);
195
+ archive.finalize();
196
+ });
197
+ }
198
+ }
@@ -0,0 +1,17 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class StaticHostCreate extends BaseCommand {
3
+ static args: {
4
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,86 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class StaticHostCreate extends BaseCommand {
4
+ static args = {
5
+ name: Args.string({
6
+ description: 'Name for the new static host',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Create a new static host in the workspace';
11
+ static examples = [
12
+ `$ xano static_host create marketing
13
+ Created static host 'marketing' (ID: 7)
14
+ `,
15
+ `$ xano static_host create marketing --description "Marketing site" -w 40
16
+ Created static host 'marketing' (ID: 7)
17
+ `,
18
+ `$ xano static_host create marketing -o json`,
19
+ ];
20
+ static flags = {
21
+ ...BaseCommand.baseFlags,
22
+ description: Flags.string({
23
+ description: 'Description for the static host',
24
+ required: false,
25
+ }),
26
+ output: Flags.string({
27
+ char: 'o',
28
+ default: 'summary',
29
+ description: 'Output format',
30
+ options: ['summary', 'json'],
31
+ required: false,
32
+ }),
33
+ workspace: Flags.string({
34
+ char: 'w',
35
+ description: 'Workspace ID (optional if set in profile)',
36
+ required: false,
37
+ }),
38
+ };
39
+ async run() {
40
+ const { args, flags } = await this.parse(StaticHostCreate);
41
+ const { profile, profileName } = this.resolveProfile(flags);
42
+ let workspaceId;
43
+ if (flags.workspace) {
44
+ workspaceId = flags.workspace;
45
+ }
46
+ else if (profile.workspace) {
47
+ workspaceId = profile.workspace;
48
+ }
49
+ else {
50
+ this.error(`Workspace ID is required. Either:\n` +
51
+ ` 1. Provide it as a flag: xano static_host create <name> -w <workspace_id>\n` +
52
+ ` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
53
+ }
54
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host`;
55
+ try {
56
+ const response = await this.verboseFetch(apiUrl, {
57
+ body: JSON.stringify({ description: flags.description ?? '', name: args.name }),
58
+ headers: {
59
+ accept: 'application/json',
60
+ Authorization: `Bearer ${profile.access_token}`,
61
+ 'Content-Type': 'application/json',
62
+ },
63
+ method: 'POST',
64
+ }, flags.verbose, profile.access_token);
65
+ if (!response.ok) {
66
+ const message = await this.parseApiError(response, `Failed to create static host '${args.name}'`);
67
+ this.error(message);
68
+ }
69
+ const host = (await response.json());
70
+ if (flags.output === 'json') {
71
+ this.log(JSON.stringify(host, null, 2));
72
+ }
73
+ else {
74
+ this.log(`Created static host '${host.name}' (ID: ${host.id})`);
75
+ }
76
+ }
77
+ catch (error) {
78
+ if (error instanceof Error) {
79
+ this.error(`Failed to create static host: ${error.message}`);
80
+ }
81
+ else {
82
+ this.error(`Failed to create static host: ${String(error)}`);
83
+ }
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class StaticHostDeploy 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
+ env: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
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
+ }