@xano/cli 1.0.4 → 1.0.5

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 (65) hide show
  1. package/README.md +107 -4
  2. package/dist/base-command.d.ts +27 -0
  3. package/dist/base-command.js +124 -1
  4. package/dist/commands/auth/index.d.ts +45 -1
  5. package/dist/commands/auth/index.js +197 -29
  6. package/dist/commands/function/run/index.d.ts +40 -0
  7. package/dist/commands/function/run/index.js +244 -0
  8. package/dist/commands/knowledge/{pull → get}/index.d.ts +6 -2
  9. package/dist/commands/knowledge/get/index.js +126 -0
  10. package/dist/commands/knowledge/{push → list}/index.d.ts +6 -8
  11. package/dist/commands/knowledge/list/index.js +135 -0
  12. package/dist/commands/sandbox/pull/index.js +12 -2
  13. package/dist/commands/sandbox/push/index.d.ts +1 -1
  14. package/dist/commands/sandbox/push/index.js +19 -14
  15. package/dist/commands/sandbox/reset/index.js +12 -0
  16. package/dist/commands/static_host/build/create/index.d.ts +9 -1
  17. package/dist/commands/static_host/build/create/index.js +54 -4
  18. package/dist/commands/static_host/build/delete/index.d.ts +19 -0
  19. package/dist/commands/static_host/build/delete/index.js +114 -0
  20. package/dist/commands/static_host/build/get/index.d.ts +1 -1
  21. package/dist/commands/static_host/build/get/index.js +16 -10
  22. package/dist/commands/static_host/build/pull/index.d.ts +52 -0
  23. package/dist/commands/static_host/build/pull/index.js +300 -0
  24. package/dist/commands/static_host/build/push/index.d.ts +23 -0
  25. package/dist/commands/static_host/build/push/index.js +228 -0
  26. package/dist/commands/static_host/create/index.d.ts +17 -0
  27. package/dist/commands/static_host/create/index.js +86 -0
  28. package/dist/commands/static_host/deploy/index.d.ts +18 -0
  29. package/dist/commands/static_host/deploy/index.js +105 -0
  30. package/dist/commands/static_host/edit/index.d.ts +23 -0
  31. package/dist/commands/static_host/edit/index.js +151 -0
  32. package/dist/commands/static_host/get/index.d.ts +18 -0
  33. package/dist/commands/static_host/get/index.js +94 -0
  34. package/dist/commands/static_host/migrate/index.d.ts +44 -0
  35. package/dist/commands/static_host/migrate/index.js +205 -0
  36. package/dist/commands/tenant/snapshot/create/index.d.ts +17 -0
  37. package/dist/commands/tenant/snapshot/create/index.js +78 -0
  38. package/dist/commands/tenant/snapshot/delete/index.d.ts +19 -0
  39. package/dist/commands/tenant/snapshot/delete/index.js +102 -0
  40. package/dist/commands/tenant/snapshot/list/index.d.ts +16 -0
  41. package/dist/commands/tenant/snapshot/list/index.js +96 -0
  42. package/dist/commands/tenant/snapshot/swap/index.d.ts +19 -0
  43. package/dist/commands/tenant/snapshot/swap/index.js +103 -0
  44. package/dist/commands/tenant/unit_test/run/index.d.ts +1 -0
  45. package/dist/commands/tenant/unit_test/run/index.js +7 -0
  46. package/dist/commands/tenant/unit_test/run_all/index.js +7 -3
  47. package/dist/commands/unit_test/run/index.d.ts +1 -0
  48. package/dist/commands/unit_test/run/index.js +8 -0
  49. package/dist/commands/unit_test/run_all/index.js +7 -3
  50. package/dist/commands/workspace/pull/index.js +13 -2
  51. package/dist/commands/workspace/push/index.d.ts +1 -1
  52. package/dist/commands/workspace/push/index.js +15 -5
  53. package/dist/utils/function-input.d.ts +77 -0
  54. package/dist/utils/function-input.js +148 -0
  55. package/dist/utils/knowledge-sync.d.ts +84 -79
  56. package/dist/utils/knowledge-sync.js +376 -323
  57. package/dist/utils/multidoc-push.d.ts +30 -18
  58. package/dist/utils/multidoc-push.js +284 -148
  59. package/dist/utils/reference-checker.js +2 -2
  60. package/dist/utils/static-host-files.d.ts +21 -0
  61. package/dist/utils/static-host-files.js +41 -0
  62. package/oclif.manifest.json +4574 -3045
  63. package/package.json +7 -1
  64. package/dist/commands/knowledge/pull/index.js +0 -86
  65. package/dist/commands/knowledge/push/index.js +0 -126
@@ -0,0 +1,126 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class KnowledgeGet extends BaseCommand {
4
+ static args = {
5
+ name: Args.string({
6
+ description: 'Knowledge item name',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Get a knowledge item by name, or one of its reference files';
11
+ static examples = [
12
+ '$ xano knowledge get "deploy-runbook" -w 40',
13
+ '$ xano knowledge get "deploy-runbook" -w 40 --output json',
14
+ '$ xano knowledge get "deploy-runbook" -w 40 --file checklist.md',
15
+ ];
16
+ static flags = {
17
+ ...BaseCommand.baseFlags,
18
+ branch: Flags.string({
19
+ char: 'b',
20
+ description: 'Branch ID',
21
+ required: false,
22
+ }),
23
+ file: Flags.string({
24
+ char: 'f',
25
+ description: 'Path of a reference file to fetch instead of the main content',
26
+ required: false,
27
+ }),
28
+ output: Flags.string({
29
+ char: 'o',
30
+ default: 'text',
31
+ description: 'Output format',
32
+ options: ['text', 'json'],
33
+ required: false,
34
+ }),
35
+ workspace: Flags.string({
36
+ char: 'w',
37
+ description: 'Workspace ID (optional if set in profile)',
38
+ required: false,
39
+ }),
40
+ };
41
+ async run() {
42
+ const { args, flags } = await this.parse(KnowledgeGet);
43
+ const { profile, profileName } = this.resolveProfile(flags);
44
+ let workspaceId;
45
+ if (flags.workspace) {
46
+ workspaceId = flags.workspace;
47
+ }
48
+ else if (profile.workspace) {
49
+ workspaceId = profile.workspace;
50
+ }
51
+ else {
52
+ this.error(`Workspace ID is required. Either:\n` +
53
+ ` 1. Provide it as a flag: xano knowledge get <name> -w <workspace_id>\n` +
54
+ ` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
55
+ }
56
+ const queryParams = new URLSearchParams();
57
+ queryParams.set('enabled_only', 'false');
58
+ queryParams.set('include_content', 'true');
59
+ if (flags.branch) {
60
+ queryParams.set('branch_id', flags.branch);
61
+ }
62
+ const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/knowledge?${queryParams.toString()}`;
63
+ try {
64
+ const listResponse = await this.verboseFetch(listUrl, {
65
+ headers: {
66
+ 'accept': 'application/json',
67
+ 'Authorization': `Bearer ${profile.access_token}`,
68
+ },
69
+ method: 'GET',
70
+ }, flags.verbose, profile.access_token);
71
+ if (!listResponse.ok) {
72
+ const errorText = await listResponse.text();
73
+ this.error(`API request failed with status ${listResponse.status}: ${listResponse.statusText}\n${errorText}`);
74
+ }
75
+ const data = await listResponse.json();
76
+ const items = Array.isArray(data) ? data : (data.items ?? []);
77
+ const targetName = args.name.toLowerCase();
78
+ const item = items.find((i) => i.name?.toLowerCase() === targetName);
79
+ if (!item) {
80
+ const available = items.map((i) => i.name).filter(Boolean).join(', ');
81
+ this.error(`Knowledge item not found: "${args.name}"\n` +
82
+ (available ? `Available items: ${available}` : 'No knowledge items exist in this workspace.'));
83
+ }
84
+ if (flags.file) {
85
+ const fileParams = new URLSearchParams({ path: flags.file });
86
+ const fileUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/knowledge/${item.id}/file?${fileParams.toString()}`;
87
+ const fileResponse = await this.verboseFetch(fileUrl, {
88
+ headers: {
89
+ 'accept': 'application/json',
90
+ 'Authorization': `Bearer ${profile.access_token}`,
91
+ },
92
+ method: 'GET',
93
+ }, flags.verbose, profile.access_token);
94
+ if (!fileResponse.ok) {
95
+ const errorText = await fileResponse.text();
96
+ this.error(`Failed to fetch file "${flags.file}" from knowledge item "${item.name}": ${fileResponse.status} ${fileResponse.statusText}\n${errorText}`);
97
+ }
98
+ const fileJson = await fileResponse.json();
99
+ const fileContent = fileJson.file?.content ?? '';
100
+ if (flags.output === 'json') {
101
+ this.log(JSON.stringify({ content: fileContent, name: item.name, path: flags.file }, null, 2));
102
+ }
103
+ else {
104
+ this.log(fileContent);
105
+ }
106
+ return;
107
+ }
108
+ if (flags.output === 'json') {
109
+ this.log(JSON.stringify(item, null, 2));
110
+ }
111
+ else {
112
+ this.log(item.content ?? '');
113
+ }
114
+ }
115
+ catch (error) {
116
+ if (error instanceof Error && 'oclif' in error)
117
+ throw error;
118
+ if (error instanceof Error) {
119
+ this.error(`Failed to fetch knowledge: ${error.message}`);
120
+ }
121
+ else {
122
+ this.error(`Failed to fetch knowledge: ${String(error)}`);
123
+ }
124
+ }
125
+ }
126
+ }
@@ -1,20 +1,18 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
- export default class KnowledgePush extends BaseCommand {
2
+ export default class KnowledgeList extends BaseCommand {
3
+ static args: {};
3
4
  static description: string;
4
5
  static examples: string[];
5
6
  static flags: {
6
7
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
- delete: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
- directory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
- 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
- exclude: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
- force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
- include: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- sync: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ 'enabled-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
11
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
12
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
13
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
14
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
15
  };
19
16
  run(): Promise<void>;
17
+ private formatMarkdown;
20
18
  }
@@ -0,0 +1,135 @@
1
+ import { Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class KnowledgeList extends BaseCommand {
4
+ static args = {};
5
+ static description = 'List workspace knowledge and skills as plain-text markdown';
6
+ static examples = [
7
+ `$ xano knowledge list -w 40
8
+ # Always-on Knowledge
9
+
10
+ ## deploy-runbook
11
+
12
+ # Deploy Runbook
13
+ ...
14
+
15
+ # On-demand Knowledge
16
+
17
+ - **code-style**: Our team coding conventions
18
+ `,
19
+ '$ xano knowledge list -w 40 --output json',
20
+ '$ xano knowledge list -w 40 --type skill',
21
+ '$ xano knowledge list --no-enabled-only',
22
+ ];
23
+ static flags = {
24
+ ...BaseCommand.baseFlags,
25
+ branch: Flags.string({
26
+ char: 'b',
27
+ description: 'Branch ID',
28
+ required: false,
29
+ }),
30
+ 'enabled-only': Flags.boolean({
31
+ allowNo: true,
32
+ default: true,
33
+ description: 'Only show enabled knowledge (use --no-enabled-only to include disabled)',
34
+ required: false,
35
+ }),
36
+ output: Flags.string({
37
+ char: 'o',
38
+ default: 'markdown',
39
+ description: 'Output format',
40
+ options: ['markdown', 'json'],
41
+ required: false,
42
+ }),
43
+ type: Flags.string({
44
+ char: 't',
45
+ description: 'Filter by knowledge type',
46
+ options: ['skill', 'doc', 'agents.md'],
47
+ required: false,
48
+ }),
49
+ workspace: Flags.string({
50
+ char: 'w',
51
+ description: 'Workspace ID (optional if set in profile)',
52
+ required: false,
53
+ }),
54
+ };
55
+ async run() {
56
+ const { flags } = await this.parse(KnowledgeList);
57
+ const { profile, profileName } = this.resolveProfile(flags);
58
+ let workspaceId;
59
+ if (flags.workspace) {
60
+ workspaceId = flags.workspace;
61
+ }
62
+ else if (profile.workspace) {
63
+ workspaceId = profile.workspace;
64
+ }
65
+ else {
66
+ this.error(`Workspace ID is required. Either:\n` +
67
+ ` 1. Provide it as a flag: xano knowledge list -w <workspace_id>\n` +
68
+ ` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
69
+ }
70
+ const queryParams = new URLSearchParams();
71
+ queryParams.set('enabled_only', flags['enabled-only'].toString());
72
+ queryParams.set('include_content', 'true');
73
+ if (flags.branch) {
74
+ queryParams.set('branch_id', flags.branch);
75
+ }
76
+ if (flags.type) {
77
+ queryParams.set('type', flags.type);
78
+ }
79
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/knowledge?${queryParams.toString()}`;
80
+ try {
81
+ const response = await this.verboseFetch(apiUrl, {
82
+ headers: {
83
+ 'accept': 'application/json',
84
+ 'Authorization': `Bearer ${profile.access_token}`,
85
+ },
86
+ method: 'GET',
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
+ const data = await response.json();
93
+ const items = Array.isArray(data) ? data : (data.items ?? []);
94
+ if (flags.output === 'json') {
95
+ this.log(JSON.stringify(items, null, 2));
96
+ return;
97
+ }
98
+ this.log(this.formatMarkdown(items));
99
+ }
100
+ catch (error) {
101
+ if (error instanceof Error && 'oclif' in error)
102
+ throw error;
103
+ if (error instanceof Error) {
104
+ this.error(`Failed to fetch knowledge: ${error.message}`);
105
+ }
106
+ else {
107
+ this.error(`Failed to fetch knowledge: ${String(error)}`);
108
+ }
109
+ }
110
+ }
111
+ formatMarkdown(items) {
112
+ if (items.length === 0) {
113
+ return 'No knowledge items found.';
114
+ }
115
+ const alwaysOn = items.filter((item) => item.mode === 'always');
116
+ const onDemand = items.filter((item) => item.mode !== 'always');
117
+ const lines = [];
118
+ if (alwaysOn.length > 0) {
119
+ lines.push('# Always-on Knowledge');
120
+ for (const item of alwaysOn) {
121
+ lines.push('', `## ${item.name ?? '(unnamed)'}`, '', item.content ?? '', '', '---');
122
+ }
123
+ }
124
+ if (onDemand.length > 0) {
125
+ if (lines.length > 0)
126
+ lines.push('');
127
+ lines.push('# On-demand Knowledge', '');
128
+ for (const item of onDemand) {
129
+ const desc = item.description || '(no description)';
130
+ lines.push(`- **${item.name ?? '(unnamed)'}**: ${desc}`);
131
+ }
132
+ }
133
+ return lines.join('\n');
134
+ }
135
+ }
@@ -2,6 +2,7 @@ import { Flags } from '@oclif/core';
2
2
  import snakeCase from 'lodash.snakecase';
3
3
  import BaseCommand from '../../../base-command.js';
4
4
  import { buildApiGroupFolderResolver, parseDocument } from '../../../utils/document-parser.js';
5
+ import { fetchKnowledge, writeKnowledge } from '../../../utils/knowledge-sync.js';
5
6
  import * as fs from 'node:fs';
6
7
  import * as path from 'node:path';
7
8
  export default class SandboxPull extends BaseCommand {
@@ -87,7 +88,6 @@ Pulled 42 documents from sandbox environment to ./my-sandbox
87
88
  }
88
89
  if (documents.length === 0) {
89
90
  this.log('No documents found in response');
90
- return;
91
91
  }
92
92
  const outputDir = path.resolve(flags.directory);
93
93
  fs.mkdirSync(outputDir, { recursive: true });
@@ -182,7 +182,17 @@ Pulled 42 documents from sandbox environment to ./my-sandbox
182
182
  fs.writeFileSync(filePath, doc.content, 'utf8');
183
183
  writtenCount++;
184
184
  }
185
- this.log(`Pulled ${writtenCount} documents from sandbox environment to ${flags.directory}`);
185
+ // ── Pull knowledge ────────────────────────────────────────────────────
186
+ const knowledgeUrl = `${profile.instance_origin}/api:meta/sandbox/knowledge/sync`;
187
+ const knowledgeObjects = await fetchKnowledge(knowledgeUrl, '', profile.access_token, this.verboseFetch.bind(this), flags.verbose);
188
+ let knowledgeCount = 0;
189
+ if (knowledgeObjects.length > 0) {
190
+ knowledgeCount = writeKnowledge(knowledgeObjects, outputDir);
191
+ }
192
+ const parts = [`${writtenCount} documents`];
193
+ if (knowledgeCount > 0)
194
+ parts.push(`${knowledgeCount} knowledge file${knowledgeCount === 1 ? '' : 's'}`);
195
+ this.log(`Pulled ${parts.join(' + ')} from sandbox environment to ${flags.directory}`);
186
196
  }
187
197
  sanitizeFilename(name) {
188
198
  return snakeCase(name.replaceAll('"', ''));
@@ -19,6 +19,6 @@ export default class SandboxPush extends BaseCommand {
19
19
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
20
  };
21
21
  run(): Promise<void>;
22
- private openReview;
23
22
  private getFrontendUrl;
23
+ private openReview;
24
24
  }
@@ -5,7 +5,7 @@ import open from 'open';
5
5
  import BaseCommand from '../../../base-command.js';
6
6
  import { executePush } from '../../../utils/multidoc-push.js';
7
7
  export default class SandboxPush extends BaseCommand {
8
- static description = '[IMPORTANT] ALWAYS run --dry-run first and show the user the output before pushing. Push local documents to your sandbox environment 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. Include/exclude glob filters are intentionally not supported on sandbox push — partial pushes can hide deletions during review and lead to data loss when promoted to the workspace. Large pushes against a sandbox loaded with a different workspace will prompt for confirmation; run `xano sandbox reset` first to start clean.';
8
+ static description = '[IMPORTANT] ALWAYS run --dry-run first and show the user the output before pushing. Push local documents to your sandbox environment 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. Include/exclude glob filters are intentionally not supported on sandbox push — partial pushes can hide deletions during review and lead to data loss when promoted to the workspace. Pushing into a sandbox that currently holds a different workspace will prompt for confirmation; run `xano sandbox reset` first to start clean.';
9
9
  static examples = [
10
10
  `$ xano sandbox push
11
11
  Push from current directory (default partial mode)
@@ -109,6 +109,7 @@ Push and open sandbox review in the browser
109
109
  cliVersion: this.config.version,
110
110
  instanceOrigin: profile.instance_origin,
111
111
  label: 'sandbox environment',
112
+ sourceWorkspaceId: profile.workspace,
112
113
  supportsBranches: false,
113
114
  supportsPartial: true,
114
115
  warnOnWorkspaceMismatch: true,
@@ -130,12 +131,29 @@ Push and open sandbox review in the browser
130
131
  branch: '',
131
132
  command: this,
132
133
  inputDir,
134
+ knowledge: {
135
+ listUrl: () => `${baseUrl}/knowledge/sync`,
136
+ rootDir: inputDir,
137
+ },
133
138
  verboseFetch: this.verboseFetch.bind(this),
134
139
  }, target, pushFlags);
135
140
  if (flags.review) {
136
141
  await this.openReview(profile.instance_origin, profile.access_token, flags.verbose);
137
142
  }
138
143
  }
144
+ getFrontendUrl(instanceOrigin) {
145
+ try {
146
+ const url = new URL(instanceOrigin);
147
+ if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
148
+ url.port = '4200';
149
+ return url.origin;
150
+ }
151
+ }
152
+ catch {
153
+ // fall through
154
+ }
155
+ return instanceOrigin;
156
+ }
139
157
  async openReview(instanceOrigin, accessToken, verbose) {
140
158
  const response = await this.verboseFetch(`${instanceOrigin}/api:meta/sandbox/impersonate`, {
141
159
  headers: {
@@ -158,17 +176,4 @@ Push and open sandbox review in the browser
158
176
  this.log('Opening sandbox review...');
159
177
  await open(reviewUrl);
160
178
  }
161
- getFrontendUrl(instanceOrigin) {
162
- try {
163
- const url = new URL(instanceOrigin);
164
- if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
165
- url.port = '4200';
166
- return url.origin;
167
- }
168
- }
169
- catch {
170
- // fall through
171
- }
172
- return instanceOrigin;
173
- }
174
179
  }
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
+ import { pushKnowledge } from '../../../utils/knowledge-sync.js';
3
4
  export default class SandboxReset extends BaseCommand {
4
5
  static description = '[CRITICAL] NEVER run without explicit user confirmation; this clears all workspace data and drafts. Resets your sandbox environment.';
5
6
  static examples = [
@@ -42,6 +43,17 @@ Sandbox environment has been reset.
42
43
  const message = await this.parseApiError(response, 'API request failed');
43
44
  this.error(message);
44
45
  }
46
+ try {
47
+ const knowledgeUrl = `${profile.instance_origin}/api:meta/sandbox/knowledge/sync`;
48
+ await pushKnowledge(knowledgeUrl, profile.access_token, this.verboseFetch.bind(this), flags.verbose, {
49
+ delete: true,
50
+ force: true,
51
+ items: [],
52
+ });
53
+ }
54
+ catch (error) {
55
+ this.warn(`Sandbox environment was reset, but clearing knowledge files failed: ${error instanceof Error ? error.message : String(error)}`);
56
+ }
45
57
  this.log('Sandbox environment has been reset.');
46
58
  }
47
59
  catch (error) {
@@ -1,5 +1,12 @@
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 {
9
+ static hidden: boolean;
3
10
  static args: {
4
11
  static_host: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
12
  };
@@ -8,7 +15,8 @@ export default class StaticHostBuildCreate extends BaseCommand {
8
15
  static flags: {
9
16
  description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
17
  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>;
18
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ 'no-wait': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
20
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
21
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
22
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -2,20 +2,42 @@ 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 {
21
+ static hidden = true;
6
22
  static args = {
7
23
  static_host: Args.string({
8
24
  description: 'Static Host name',
9
25
  required: true,
10
26
  }),
11
27
  };
12
- static description = 'Create a new build for a static host';
28
+ static description = '[Deprecated: use "static_host build push -f <file>" instead] Create a new build from a zip file';
13
29
  static examples = [
14
30
  `$ xano static_host:build:create default -f ./build.zip -n "v1.0.0"
15
31
  Build created successfully!
16
32
  ID: 123
17
33
  Name: v1.0.0
18
34
  Status: pending
35
+ `,
36
+ `$ xano static_host:build:create default -f ./build.zip
37
+ Build created successfully!
38
+ ID: 123
39
+ Name: 20260531-143022
40
+ Status: pending
19
41
  `,
20
42
  `$ xano static_host:build:create default -w 40 -f ./dist.zip -n "production" -d "Production build"
21
43
  Build created successfully!
@@ -45,8 +67,13 @@ Description: Production build
45
67
  }),
46
68
  name: Flags.string({
47
69
  char: 'n',
48
- description: 'Build name',
49
- required: true,
70
+ description: 'Build name (auto-generated from the current timestamp if omitted)',
71
+ required: false,
72
+ }),
73
+ 'no-wait': Flags.boolean({
74
+ default: false,
75
+ description: 'Return immediately after upload instead of waiting for the build to finish',
76
+ required: false,
50
77
  }),
51
78
  output: Flags.string({
52
79
  char: 'o',
@@ -62,6 +89,7 @@ Description: Production build
62
89
  }),
63
90
  };
64
91
  async run() {
92
+ this.warn('`static_host build create` is deprecated. Use `static_host build push -f <file>` instead.');
65
93
  const { args, flags } = await this.parse(StaticHostBuildCreate);
66
94
  const { profile, profileName } = this.resolveProfile(flags);
67
95
  // Determine workspace_id from flag or profile
@@ -102,7 +130,10 @@ Description: Production build
102
130
  const fileBuffer = fs.readFileSync(filePath);
103
131
  const blob = new Blob([fileBuffer], { type: 'application/zip' });
104
132
  formData.append('file', blob, path.basename(filePath));
105
- formData.append('name', flags.name);
133
+ // Name is optional — fall back to a timestamped name so builds can be
134
+ // created without thinking up a label each time.
135
+ const buildName = flags.name ?? generateBuildName();
136
+ formData.append('name', buildName);
106
137
  if (flags.description) {
107
138
  formData.append('description', flags.description);
108
139
  }
@@ -141,6 +172,25 @@ Description: Production build
141
172
  this.log(`Description: ${flags.description}`);
142
173
  }
143
174
  }
175
+ // Async (package.json) builds keep running after upload. Unless --no-wait,
176
+ // poll until the build finishes so the CLI mirrors the UI's progress.
177
+ const inProgress = result.status !== undefined && !['error', 'ok'].includes(result.status);
178
+ if (inProgress && !flags['no-wait']) {
179
+ const finalStatus = await this.waitForBuild({
180
+ buildId: result.id,
181
+ profile,
182
+ quiet: flags.output === 'json',
183
+ staticHost: args.static_host,
184
+ verbose: flags.verbose,
185
+ workspaceId,
186
+ });
187
+ if (finalStatus === 'error') {
188
+ 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}`);
189
+ }
190
+ }
191
+ if (flags.output !== 'json') {
192
+ await this.logStaticHostUrls({ profile, staticHost: args.static_host, verbose: flags.verbose, workspaceId });
193
+ }
144
194
  }
145
195
  catch (error) {
146
196
  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
+ }