@xano/cli 1.2.0 → 1.3.1-beta.0

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 (192) hide show
  1. package/README.md +381 -4
  2. package/dist/base-command.d.ts +54 -36
  3. package/dist/base-command.js +246 -169
  4. package/dist/commands/auth/index.d.ts +1 -1
  5. package/dist/commands/auth/index.js +7 -7
  6. package/dist/commands/branch/create/index.d.ts +1 -1
  7. package/dist/commands/branch/create/index.js +1 -1
  8. package/dist/commands/branch/set_live/index.d.ts +22 -0
  9. package/dist/commands/branch/set_live/index.js +60 -1
  10. package/dist/commands/ephemeral/create/index.d.ts +18 -0
  11. package/dist/commands/ephemeral/create/index.js +103 -0
  12. package/dist/commands/ephemeral/delete/index.d.ts +18 -0
  13. package/dist/commands/ephemeral/delete/index.js +99 -0
  14. package/dist/commands/ephemeral/edit/index.d.ts +18 -0
  15. package/dist/commands/ephemeral/edit/index.js +109 -0
  16. package/dist/commands/ephemeral/get/index.d.ts +21 -0
  17. package/dist/commands/ephemeral/get/index.js +125 -0
  18. package/dist/commands/ephemeral/impersonate/index.d.ts +20 -0
  19. package/dist/commands/ephemeral/impersonate/index.js +120 -0
  20. package/dist/commands/ephemeral/list/index.d.ts +19 -0
  21. package/dist/commands/ephemeral/list/index.js +123 -0
  22. package/dist/commands/ephemeral/pull/index.d.ts +19 -0
  23. package/dist/commands/ephemeral/pull/index.js +163 -0
  24. package/dist/commands/ephemeral/push/index.d.ts +35 -0
  25. package/dist/commands/ephemeral/push/index.js +228 -0
  26. package/dist/commands/ephemeral/static_host/build/delete/index.d.ts +20 -0
  27. package/dist/commands/ephemeral/static_host/build/delete/index.js +121 -0
  28. package/dist/commands/ephemeral/static_host/build/get/index.d.ts +18 -0
  29. package/dist/commands/ephemeral/static_host/build/get/index.js +137 -0
  30. package/dist/commands/ephemeral/static_host/build/list/index.d.ts +18 -0
  31. package/dist/commands/ephemeral/static_host/build/list/index.js +137 -0
  32. package/dist/commands/ephemeral/static_host/build/pull/index.d.ts +53 -0
  33. package/dist/commands/ephemeral/static_host/build/pull/index.js +308 -0
  34. package/dist/commands/ephemeral/static_host/build/push/index.d.ts +24 -0
  35. package/dist/commands/ephemeral/static_host/build/push/index.js +250 -0
  36. package/dist/commands/ephemeral/static_host/create/index.d.ts +18 -0
  37. package/dist/commands/ephemeral/static_host/create/index.js +92 -0
  38. package/dist/commands/ephemeral/static_host/deploy/index.d.ts +19 -0
  39. package/dist/commands/ephemeral/static_host/deploy/index.js +112 -0
  40. package/dist/commands/ephemeral/static_host/edit/index.d.ts +24 -0
  41. package/dist/commands/ephemeral/static_host/edit/index.js +158 -0
  42. package/dist/commands/ephemeral/static_host/get/index.d.ts +19 -0
  43. package/dist/commands/ephemeral/static_host/get/index.js +101 -0
  44. package/dist/commands/ephemeral/static_host/list/index.d.ts +17 -0
  45. package/dist/commands/ephemeral/static_host/list/index.js +132 -0
  46. package/dist/commands/flatten/index.d.ts +34 -0
  47. package/dist/commands/flatten/index.js +140 -0
  48. package/dist/commands/function/create/index.d.ts +15 -0
  49. package/dist/commands/function/create/index.js +41 -1
  50. package/dist/commands/function/edit/index.d.ts +15 -0
  51. package/dist/commands/function/edit/index.js +66 -32
  52. package/dist/commands/function/get/index.js +2 -3
  53. package/dist/commands/function/run/index.d.ts +1 -0
  54. package/dist/commands/function/run/index.js +19 -6
  55. package/dist/commands/policy/catalogue/index.d.ts +15 -0
  56. package/dist/commands/policy/catalogue/index.js +29 -0
  57. package/dist/commands/policy/create/index.d.ts +18 -0
  58. package/dist/commands/policy/create/index.js +39 -0
  59. package/dist/commands/policy/delete/index.d.ts +19 -0
  60. package/dist/commands/policy/delete/index.js +61 -0
  61. package/dist/commands/policy/evaluate/index.d.ts +32 -0
  62. package/dist/commands/policy/evaluate/index.js +71 -0
  63. package/dist/commands/policy/list/index.d.ts +14 -0
  64. package/dist/commands/policy/list/index.js +21 -0
  65. package/dist/commands/policy/parse/index.d.ts +19 -0
  66. package/dist/commands/policy/parse/index.js +18 -0
  67. package/dist/commands/policy/publish/index.d.ts +20 -0
  68. package/dist/commands/policy/publish/index.js +38 -0
  69. package/dist/commands/policy/runs/index.d.ts +54 -0
  70. package/dist/commands/policy/runs/index.js +273 -0
  71. package/dist/commands/policy/status/index.d.ts +21 -0
  72. package/dist/commands/policy/status/index.js +54 -0
  73. package/dist/commands/profile/create/index.d.ts +1 -1
  74. package/dist/commands/profile/create/index.js +7 -7
  75. package/dist/commands/profile/me/index.js +1 -1
  76. package/dist/commands/profile/use/index.js +0 -4
  77. package/dist/commands/profile/wizard/index.d.ts +1 -1
  78. package/dist/commands/profile/wizard/index.js +12 -10
  79. package/dist/commands/profile/workspace/set/index.js +1 -1
  80. package/dist/commands/release/create/index.js +7 -0
  81. package/dist/commands/release/deploy/index.d.ts +28 -0
  82. package/dist/commands/release/deploy/index.js +90 -6
  83. package/dist/commands/release/edit/index.js +2 -2
  84. package/dist/commands/release/export/index.js +1 -2
  85. package/dist/commands/release/import/index.js +6 -2
  86. package/dist/commands/release/list/index.d.ts +7 -0
  87. package/dist/commands/release/list/index.js +54 -10
  88. package/dist/commands/release/pull/index.d.ts +0 -6
  89. package/dist/commands/release/pull/index.js +13 -99
  90. package/dist/commands/release/push/index.js +21 -3
  91. package/dist/commands/sandbox/env/get_all/index.js +1 -1
  92. package/dist/commands/sandbox/env/set_all/index.js +1 -1
  93. package/dist/commands/sandbox/get/index.js +1 -1
  94. package/dist/commands/sandbox/license/get/index.js +1 -1
  95. package/dist/commands/sandbox/license/set/index.js +1 -1
  96. package/dist/commands/sandbox/pull/index.d.ts +0 -1
  97. package/dist/commands/sandbox/pull/index.js +13 -81
  98. package/dist/commands/sandbox/push/index.d.ts +1 -1
  99. package/dist/commands/sandbox/push/index.js +11 -6
  100. package/dist/commands/skills/pull/index.d.ts +30 -0
  101. package/dist/commands/skills/pull/index.js +118 -0
  102. package/dist/commands/static_host/build/create/index.d.ts +1 -1
  103. package/dist/commands/static_host/build/create/index.js +2 -2
  104. package/dist/commands/static_host/build/pull/index.js +1 -1
  105. package/dist/commands/static_host/build/push/index.js +1 -1
  106. package/dist/commands/static_host/edit/index.js +1 -1
  107. package/dist/commands/tenant/backup/export/index.js +1 -2
  108. package/dist/commands/tenant/backup/import/index.js +1 -1
  109. package/dist/commands/tenant/cluster/create/index.js +1 -1
  110. package/dist/commands/tenant/cluster/license/get/index.js +1 -1
  111. package/dist/commands/tenant/cluster/license/set/index.js +1 -1
  112. package/dist/commands/tenant/create/index.d.ts +4 -1
  113. package/dist/commands/tenant/create/index.js +26 -5
  114. package/dist/commands/tenant/deploy_platform/index.js +1 -1
  115. package/dist/commands/tenant/deploy_release/index.d.ts +40 -0
  116. package/dist/commands/tenant/deploy_release/index.js +186 -4
  117. package/dist/commands/tenant/edit/index.d.ts +2 -0
  118. package/dist/commands/tenant/edit/index.js +46 -11
  119. package/dist/commands/tenant/env/get_all/index.js +1 -1
  120. package/dist/commands/tenant/env/set_all/index.js +1 -1
  121. package/dist/commands/tenant/impersonate/index.d.ts +1 -1
  122. package/dist/commands/tenant/impersonate/index.js +14 -14
  123. package/dist/commands/tenant/license/get/index.js +1 -1
  124. package/dist/commands/tenant/license/set/index.js +1 -1
  125. package/dist/commands/tenant/pull/index.d.ts +0 -6
  126. package/dist/commands/tenant/pull/index.js +14 -100
  127. package/dist/commands/tenant_deploy_request/bypass/index.d.ts +31 -0
  128. package/dist/commands/tenant_deploy_request/bypass/index.js +109 -0
  129. package/dist/commands/tenant_deploy_request/create/index.d.ts +21 -0
  130. package/dist/commands/tenant_deploy_request/create/index.js +108 -0
  131. package/dist/commands/tenant_deploy_request/edit/index.d.ts +22 -0
  132. package/dist/commands/tenant_deploy_request/edit/index.js +103 -0
  133. package/dist/commands/tenant_deploy_request/get/index.d.ts +25 -0
  134. package/dist/commands/tenant_deploy_request/get/index.js +116 -0
  135. package/dist/commands/tenant_deploy_request/list/index.d.ts +20 -0
  136. package/dist/commands/tenant_deploy_request/list/index.js +122 -0
  137. package/dist/commands/tenant_deploy_request/revision/index.d.ts +21 -0
  138. package/dist/commands/tenant_deploy_request/revision/index.js +83 -0
  139. package/dist/commands/tenant_deploy_request/set_status/index.d.ts +38 -0
  140. package/dist/commands/tenant_deploy_request/set_status/index.js +159 -0
  141. package/dist/commands/workspace/git/pull/index.d.ts +4 -4
  142. package/dist/commands/workspace/git/pull/index.js +158 -97
  143. package/dist/commands/workspace/pull/index.d.ts +9 -5
  144. package/dist/commands/workspace/pull/index.js +83 -106
  145. package/dist/commands/workspace/push/index.d.ts +19 -0
  146. package/dist/commands/workspace/push/index.js +124 -5
  147. package/dist/help.js +4 -2
  148. package/dist/policy-command.d.ts +90 -0
  149. package/dist/policy-command.js +94 -0
  150. package/dist/update-check.d.ts +1 -1
  151. package/dist/update-check.js +1 -1
  152. package/dist/utils/document-parser.d.ts +126 -0
  153. package/dist/utils/document-parser.js +332 -4
  154. package/dist/utils/flatten.d.ts +36 -0
  155. package/dist/utils/flatten.js +68 -0
  156. package/dist/utils/knowledge-sync.d.ts +1 -0
  157. package/dist/utils/knowledge-sync.js +11 -6
  158. package/dist/utils/microservice-wait.d.ts +53 -0
  159. package/dist/utils/microservice-wait.js +84 -0
  160. package/dist/utils/multidoc-push.d.ts +143 -2
  161. package/dist/utils/multidoc-push.js +479 -40
  162. package/dist/utils/paging.d.ts +2 -1
  163. package/dist/utils/paging.js +0 -1
  164. package/dist/utils/policy/catalogue.d.ts +7 -0
  165. package/dist/utils/policy/catalogue.js +72 -0
  166. package/dist/utils/policy/errors.d.ts +12 -0
  167. package/dist/utils/policy/errors.js +61 -0
  168. package/dist/utils/policy/feedback.d.ts +68 -0
  169. package/dist/utils/policy/feedback.js +165 -0
  170. package/dist/utils/policy/findings.d.ts +23 -0
  171. package/dist/utils/policy/findings.js +65 -0
  172. package/dist/utils/policy/gate.d.ts +161 -0
  173. package/dist/utils/policy/gate.js +201 -0
  174. package/dist/utils/policy/goal.d.ts +46 -0
  175. package/dist/utils/policy/goal.js +95 -0
  176. package/dist/utils/policy/permission.d.ts +23 -0
  177. package/dist/utils/policy/permission.js +84 -0
  178. package/dist/utils/policy/push-gate.d.ts +5 -0
  179. package/dist/utils/policy/push-gate.js +29 -0
  180. package/dist/utils/policy/release.d.ts +85 -0
  181. package/dist/utils/policy/release.js +115 -0
  182. package/dist/utils/policy/request.d.ts +55 -0
  183. package/dist/utils/policy/request.js +115 -0
  184. package/dist/utils/policy/runs.d.ts +30 -0
  185. package/dist/utils/policy/runs.js +101 -0
  186. package/dist/utils/policy/status.d.ts +47 -0
  187. package/dist/utils/policy/status.js +117 -0
  188. package/dist/utils/policy/types.d.ts +185 -0
  189. package/dist/utils/policy/types.js +1 -0
  190. package/dist/utils/reference-checker.js +14 -3
  191. package/oclif.manifest.json +8538 -3445
  192. package/package.json +16 -1
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class EphemeralStaticHostGet 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
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'static-host': 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
+ /** Print a one-line summary for a dev/prod env if it has been deployed. */
18
+ private logEnv;
19
+ }
@@ -0,0 +1,101 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../../base-command.js';
3
+ export default class EphemeralStaticHostGet extends BaseCommand {
4
+ static args = {
5
+ tenant_name: Args.string({
6
+ description: 'Ephemeral tenant name',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Get a single static host\'s details (name, git config, dev/prod environments) for an ephemeral tenant';
11
+ static examples = [
12
+ `$ xano ephemeral static_host get e4f2-9ab1-xyz1 --static-host newsite
13
+ Static Host: newsite
14
+ ID: 5
15
+ Dev: https://newsite-dev-....dev.xano.io (v2)
16
+ `,
17
+ `$ xano ephemeral static_host get e4f2-9ab1-xyz1 -H newsite -w 40 -o json`,
18
+ ];
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ output: Flags.string({
22
+ char: 'o',
23
+ default: 'summary',
24
+ description: 'Output format',
25
+ options: ['summary', 'json'],
26
+ required: false,
27
+ }),
28
+ 'static-host': Flags.string({
29
+ char: 'H',
30
+ description: 'Static host name',
31
+ required: true,
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(EphemeralStaticHostGet);
41
+ const { profile, profileName } = this.resolveProfile(flags);
42
+ const tenantName = args.tenant_name;
43
+ let workspaceId;
44
+ if (flags.workspace) {
45
+ workspaceId = flags.workspace;
46
+ }
47
+ else if (profile.workspace) {
48
+ workspaceId = profile.workspace;
49
+ }
50
+ else {
51
+ this.error(`Workspace ID is required. Either:\n` +
52
+ ` 1. Provide it as a flag: xano ephemeral static_host get <tenant_name> --static-host <static_host> -w <workspace_id>\n` +
53
+ ` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
54
+ }
55
+ const staticHost = flags['static-host'];
56
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host/${staticHost}`;
57
+ try {
58
+ const response = await this.verboseFetch(apiUrl, {
59
+ headers: {
60
+ accept: 'application/json',
61
+ Authorization: `Bearer ${profile.access_token}`,
62
+ },
63
+ method: 'GET',
64
+ }, flags.verbose, profile.access_token);
65
+ if (!response.ok) {
66
+ const message = await this.parseApiError(response, `Failed to get static host '${staticHost}'`);
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(`Static Host: ${host.name}`);
75
+ this.log(`ID: ${host.id}`);
76
+ if (host.description)
77
+ this.log(`Description: ${host.description}`);
78
+ if (host.git?.repo)
79
+ this.log(`Git: ${host.git.repo}`);
80
+ this.logEnv('Dev', host.dev);
81
+ this.logEnv('Prod', host.prod);
82
+ }
83
+ }
84
+ catch (error) {
85
+ if (error instanceof Error) {
86
+ this.error(`Failed to get static host: ${error.message}`);
87
+ }
88
+ else {
89
+ this.error(`Failed to get static host: ${String(error)}`);
90
+ }
91
+ }
92
+ }
93
+ /** Print a one-line summary for a dev/prod env if it has been deployed. */
94
+ logEnv(label, env) {
95
+ if (!env?.default_url)
96
+ return;
97
+ const modeInfo = env.mode ? ` (${env.mode})` : '';
98
+ const custom = env.custom_url ? ` [custom: ${env.custom_url}]` : '';
99
+ this.log(`${label}: ${env.default_url}${modeInfo}${custom}`);
100
+ }
101
+ }
@@ -0,0 +1,17 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class EphemeralStaticHostList 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
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string, 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,132 @@
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 EphemeralStaticHostList 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 static hosts for an ephemeral tenant from the Xano Metadata API';
12
+ static examples = [
13
+ `$ xano ephemeral static_host list e4f2-9ab1-xyz1 -w 40
14
+ Available static hosts:
15
+ - my-static-host (ID: 1)
16
+ - another-host (ID: 2)
17
+ `,
18
+ `$ xano ephemeral static_host list e4f2-9ab1-xyz1 --profile production
19
+ Available static hosts:
20
+ - my-static-host (ID: 1)
21
+ - another-host (ID: 2)
22
+ `,
23
+ `$ xano ephemeral static_host list e4f2-9ab1-xyz1 -w 40 --output json
24
+ {
25
+ "count": 1,
26
+ "page": 1,
27
+ "total": 3,
28
+ "items": [
29
+ {
30
+ "id": 1,
31
+ "name": "my-static-host"
32
+ }
33
+ ]
34
+ }
35
+ `,
36
+ `$ xano ephemeral static_host list e4f2-9ab1-xyz1 -p staging -o json --page 2
37
+ [
38
+ {
39
+ "id": 3,
40
+ "name": "static-host-3"
41
+ }
42
+ ]
43
+ `,
44
+ ];
45
+ static flags = {
46
+ ...BaseCommand.baseFlags,
47
+ output: Flags.string({
48
+ char: 'o',
49
+ default: 'summary',
50
+ description: 'Output format',
51
+ options: ['summary', 'json'],
52
+ required: false,
53
+ }),
54
+ ...pagingFlags('page-only-envelope', { fixedPerPage: 100 }),
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(EphemeralStaticHostList);
63
+ const { profile, profileName } = this.resolveProfile(flags);
64
+ const tenantName = args.tenant_name;
65
+ // Determine workspace_id from flag or profile
66
+ let workspaceId;
67
+ if (flags.workspace) {
68
+ workspaceId = flags.workspace;
69
+ }
70
+ else if (profile.workspace) {
71
+ workspaceId = profile.workspace;
72
+ }
73
+ else {
74
+ this.error(`Workspace ID is required. Either:\n` +
75
+ ` 1. Provide it as a flag: xano ephemeral static_host list <tenant_name> -w <workspace_id>\n` +
76
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
77
+ }
78
+ // Build query parameters
79
+ const queryParams = new URLSearchParams(buildPagingParams(flags, 'page-only-envelope'));
80
+ // Construct the API URL
81
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/static_host?${queryParams.toString()}`;
82
+ // Fetch static hosts from the API
83
+ try {
84
+ const response = await this.verboseFetch(apiUrl, {
85
+ headers: {
86
+ 'accept': 'application/json',
87
+ 'Authorization': `Bearer ${profile.access_token}`,
88
+ },
89
+ method: 'GET',
90
+ }, flags.verbose, profile.access_token);
91
+ if (!response.ok) {
92
+ const errorText = await response.text();
93
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
94
+ }
95
+ const list = normalizeListResponse(await response.json(), ['static_hosts']);
96
+ const staticHosts = list.items;
97
+ // Output results
98
+ if (flags.output === 'json') {
99
+ this.log(JSON.stringify(buildPagingJson(list, { page: flags.page, tier: 'page-only-envelope' }), null, 2));
100
+ }
101
+ else {
102
+ // summary format
103
+ if (staticHosts.length === 0) {
104
+ this.log('No static hosts found');
105
+ }
106
+ else {
107
+ this.log('Available static hosts:');
108
+ for (const host of staticHosts) {
109
+ if (host.id === undefined) {
110
+ this.log(` - ${host.name}`);
111
+ }
112
+ else {
113
+ const domainInfo = host.domain ? ` - ${host.domain}` : '';
114
+ this.log(` - ${host.name} (ID: ${host.id})${domainInfo}`);
115
+ }
116
+ }
117
+ }
118
+ const footer = formatPagingFooter(list, { noun: 'static host', page: flags.page, tier: 'page-only-envelope' });
119
+ if (footer)
120
+ this.log(footer);
121
+ }
122
+ }
123
+ catch (error) {
124
+ if (error instanceof Error) {
125
+ this.error(`Failed to fetch static hosts: ${error.message}`);
126
+ }
127
+ else {
128
+ this.error(`Failed to fetch static hosts: ${String(error)}`);
129
+ }
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,34 @@
1
+ import { Command } from '@oclif/core';
2
+ /**
3
+ * Split a multi-document `.xs` bundle into the standard one-document-per-file
4
+ * layout that the push pipeline requires.
5
+ *
6
+ * A hand-authored bundle keeps every object (workspace, tables, queries,
7
+ * microservices, …) in a single file joined by `---`. That is convenient to
8
+ * edit but unsupported by `push`: the partial-diff filter and GUID writeback
9
+ * both parse only the first document in a file, so such a bundle silently
10
+ * pushes nothing (partial mode) or corrupts GUIDs (full mode). `flatten` turns
11
+ * it back into the exact tree `pull` would have produced, using the shared
12
+ * placement logic so the two never drift.
13
+ *
14
+ * Not a BaseCommand: this is a purely local, offline transform — no profile,
15
+ * token, or network — so it extends oclif's Command directly.
16
+ */
17
+ export default class Flatten extends Command {
18
+ static args: {
19
+ input: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
20
+ };
21
+ static description: string;
22
+ static examples: string[];
23
+ static flags: {
24
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
25
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ 'keep-source': import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
+ };
29
+ run(): Promise<void>;
30
+ /** Recursively collect `.xs` files under a dir that actually hold >1 document. */
31
+ private collectMultiDocFiles;
32
+ /** Flatten a single bundle file. Returns the number of documents written. */
33
+ private flattenOne;
34
+ }
@@ -0,0 +1,140 @@
1
+ import { Args, Command, Flags, ux } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import { join, relative, resolve } from 'node:path';
4
+ import { flattenBundleFile } from '../../utils/flatten.js';
5
+ /**
6
+ * Split a multi-document `.xs` bundle into the standard one-document-per-file
7
+ * layout that the push pipeline requires.
8
+ *
9
+ * A hand-authored bundle keeps every object (workspace, tables, queries,
10
+ * microservices, …) in a single file joined by `---`. That is convenient to
11
+ * edit but unsupported by `push`: the partial-diff filter and GUID writeback
12
+ * both parse only the first document in a file, so such a bundle silently
13
+ * pushes nothing (partial mode) or corrupts GUIDs (full mode). `flatten` turns
14
+ * it back into the exact tree `pull` would have produced, using the shared
15
+ * placement logic so the two never drift.
16
+ *
17
+ * Not a BaseCommand: this is a purely local, offline transform — no profile,
18
+ * token, or network — so it extends oclif's Command directly.
19
+ */
20
+ export default class Flatten extends Command {
21
+ static args = {
22
+ input: Args.string({
23
+ description: 'A multi-document .xs file, or a directory containing them, to split',
24
+ required: true,
25
+ }),
26
+ };
27
+ static description = 'Split a multi-document .xs bundle (documents joined by `---`) into the standard ' +
28
+ 'one-document-per-file layout that push requires. Deletes the original bundle by ' +
29
+ 'default (use --keep-source to keep it). Purely local — no network.';
30
+ static examples = [
31
+ `$ xano flatten secret/pdf-micro/multidoc.xs
32
+ Split the bundle in place into per-document files (removes multidoc.xs)`,
33
+ `$ xano flatten ./bundle.xs -o ./workspace
34
+ Write the split files under ./workspace instead of alongside the bundle`,
35
+ `$ xano flatten secret/pdf-micro/multidoc.xs --dry-run
36
+ Preview the file layout without writing anything`,
37
+ `$ xano flatten ./dir-of-bundles --keep-source
38
+ Flatten every multi-doc .xs under a directory, keeping the originals`,
39
+ ];
40
+ static flags = {
41
+ 'dry-run': Flags.boolean({
42
+ default: false,
43
+ description: 'Show the resulting file layout without writing anything',
44
+ }),
45
+ force: Flags.boolean({
46
+ char: 'f',
47
+ default: false,
48
+ description: 'Overwrite existing destination files instead of erroring',
49
+ }),
50
+ 'keep-source': Flags.boolean({
51
+ default: false,
52
+ description: 'Keep the original multi-document file(s) (default: delete after a successful split)',
53
+ }),
54
+ output: Flags.string({
55
+ char: 'o',
56
+ description: 'Output directory (default: the directory each bundle file lives in)',
57
+ }),
58
+ };
59
+ async run() {
60
+ const { args, flags } = await this.parse(Flatten);
61
+ const inputPath = resolve(args.input);
62
+ if (!fs.existsSync(inputPath)) {
63
+ this.error(`Not found: ${inputPath}`);
64
+ }
65
+ // Resolve the set of bundle files to flatten.
66
+ const bundleFiles = fs.statSync(inputPath).isDirectory()
67
+ ? this.collectMultiDocFiles(inputPath)
68
+ : [inputPath];
69
+ if (bundleFiles.length === 0) {
70
+ this.error(fs.statSync(inputPath).isDirectory()
71
+ ? `No multi-document .xs files found under ${inputPath}`
72
+ : `${inputPath} is not a .xs file`);
73
+ }
74
+ let totalWritten = 0;
75
+ for (const bundleFile of bundleFiles) {
76
+ totalWritten += this.flattenOne(bundleFile, flags);
77
+ }
78
+ if (flags['dry-run']) {
79
+ this.log('');
80
+ this.log(ux.colorize('dim', `Dry run — ${totalWritten} file(s) would be written. Nothing changed.`));
81
+ }
82
+ else {
83
+ this.log('');
84
+ this.log(`Flattened ${bundleFiles.length} bundle(s) into ${totalWritten} file(s).`);
85
+ }
86
+ }
87
+ /** Recursively collect `.xs` files under a dir that actually hold >1 document. */
88
+ collectMultiDocFiles(dir) {
89
+ const out = [];
90
+ const walk = (d) => {
91
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
92
+ const full = join(d, entry.name);
93
+ if (entry.isDirectory()) {
94
+ walk(full);
95
+ }
96
+ else if (entry.isFile() && entry.name.endsWith('.xs')) {
97
+ const content = fs.readFileSync(full, 'utf8');
98
+ if (/^---$/m.test(content))
99
+ out.push(full);
100
+ }
101
+ }
102
+ };
103
+ walk(dir);
104
+ return out.sort();
105
+ }
106
+ /** Flatten a single bundle file. Returns the number of documents written. */
107
+ flattenOne(bundleFile, flags) {
108
+ const rel = relative(process.cwd(), bundleFile) || bundleFile;
109
+ let result;
110
+ try {
111
+ // Buffer the plan so we can print a header before it only when there's work.
112
+ const planLines = [];
113
+ result = flattenBundleFile(bundleFile, {
114
+ dryRun: flags['dry-run'],
115
+ force: flags.force,
116
+ keepSource: flags['keep-source'],
117
+ log: (msg) => planLines.push(msg),
118
+ outputDir: flags.output,
119
+ });
120
+ if (result.skipped === 'empty') {
121
+ this.warn(`No parseable documents in ${rel}; skipping.`);
122
+ return 0;
123
+ }
124
+ if (result.skipped === 'single') {
125
+ this.warn(`${rel} holds a single document; nothing to split. Skipping.`);
126
+ return 0;
127
+ }
128
+ this.log('');
129
+ this.log(ux.colorize('bold', `${rel} → ${result.written.length} documents`));
130
+ for (const l of planLines) {
131
+ // Colorize the WRITE marker; dim the incidental notes.
132
+ this.log(l.startsWith(' WRITE') ? l.replace('WRITE', ux.colorize('green', 'WRITE')) : ux.colorize('dim', l));
133
+ }
134
+ }
135
+ catch (error) {
136
+ this.error(error.message);
137
+ }
138
+ return result.written.length;
139
+ }
140
+ }
@@ -7,13 +7,28 @@ export default class FunctionCreate extends BaseCommand {
7
7
  edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
9
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
14
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
15
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
16
  };
17
+ /** Set once the publish policy gate refuses, the one failure that exits 2. */
18
+ private refusedByPolicyGate;
19
+ /** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
20
+ protected catch(error: Error & {
21
+ oclif?: {
22
+ exit?: number;
23
+ };
24
+ }): Promise<void>;
16
25
  run(): Promise<void>;
17
26
  private editFile;
18
27
  private readStdin;
28
+ /**
29
+ * The live-branch publish gate refused the new function: the verdict on stdout (under `-o json`,
30
+ * `{created: false, message, policy_gate}`), then the platform's message and how to proceed,
31
+ * exiting 2.
32
+ */
33
+ private refusePublish;
19
34
  }
@@ -2,8 +2,9 @@ import { Flags } from '@oclif/core';
2
2
  import { execSync } from 'node:child_process';
3
3
  import * as fs from 'node:fs';
4
4
  import * as os from 'node:os';
5
- import * as path from 'node:path';
5
+ import path from 'node:path';
6
6
  import BaseCommand from '../../../base-command.js';
7
+ import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateRefusal, publishRefusal, quoted } from '../../../utils/policy/gate.js';
7
8
  export default class FunctionCreate extends BaseCommand {
8
9
  static args = {};
9
10
  static description = 'Create a new function in a workspace';
@@ -36,6 +37,7 @@ Name: my_function
36
37
  ...
37
38
  }
38
39
  `,
40
+ `$ xano function:create -f function.xs --policy-override "Exception approved; finding tracked in JIRA-12"`,
39
41
  ];
40
42
  static flags = {
41
43
  ...BaseCommand.baseFlags,
@@ -59,6 +61,7 @@ Name: my_function
59
61
  options: ['summary', 'json'],
60
62
  required: false,
61
63
  }),
64
+ 'policy-override': Flags.string({ description: 'Create the function past a blocking live-branch publish policy gate with an audited reason (requires workspace:policy update)' }),
62
65
  stdin: Flags.boolean({
63
66
  char: 's',
64
67
  default: false,
@@ -72,8 +75,18 @@ Name: my_function
72
75
  required: false,
73
76
  }),
74
77
  };
78
+ /** Set once the publish policy gate refuses, the one failure that exits 2. */
79
+ refusedByPolicyGate = false;
80
+ /** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
81
+ async catch(error) {
82
+ if (this.refusedByPolicyGate)
83
+ return super.catch(error);
84
+ return this.catchAsOperational(error);
85
+ }
75
86
  async run() {
76
87
  const { flags } = await this.parse(FunctionCreate);
88
+ if (blankPolicyOverride(flags['policy-override']))
89
+ this.error(BLANK_POLICY_OVERRIDE);
77
90
  const { profile, profileName } = this.resolveProfile(flags);
78
91
  // Determine workspace_id from flag or profile
79
92
  let workspaceId;
@@ -133,6 +146,10 @@ Name: my_function
133
146
  const queryParams = new URLSearchParams({
134
147
  include_xanoscript: 'false',
135
148
  });
149
+ // A reason from someone with workspace:policy update saves past a blocking publish gate.
150
+ const overrideReason = flags['policy-override']?.trim();
151
+ if (overrideReason)
152
+ queryParams.set('override_reason', overrideReason);
136
153
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
137
154
  // Create function via API
138
155
  try {
@@ -146,6 +163,9 @@ Name: my_function
146
163
  method: 'POST',
147
164
  }, flags.verbose, profile.access_token);
148
165
  if (!response.ok) {
166
+ const refused = await gateRefusal(response);
167
+ if (refused)
168
+ this.refusePublish(refused, flags);
149
169
  const errorText = await response.text();
150
170
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
151
171
  }
@@ -166,6 +186,8 @@ Name: my_function
166
186
  }
167
187
  }
168
188
  catch (error) {
189
+ if (this.refusedByPolicyGate)
190
+ throw error;
169
191
  if (error instanceof Error) {
170
192
  this.error(`Failed to create function: ${error.message}`);
171
193
  }
@@ -239,4 +261,22 @@ Name: my_function
239
261
  process.stdin.resume();
240
262
  });
241
263
  }
264
+ /**
265
+ * The live-branch publish gate refused the new function: the verdict on stdout (under `-o json`,
266
+ * `{created: false, message, policy_gate}`), then the platform's message and how to proceed,
267
+ * exiting 2.
268
+ */
269
+ refusePublish(refused, flags) {
270
+ const workspace = flags.workspace ? ` -w ${quoted(flags.workspace)}` : '';
271
+ const source = flags.file ? ` -f ${quoted(flags.file)}` : ' --stdin';
272
+ const { lines, message } = publishRefusal(refused, {
273
+ json: flags.output === 'json',
274
+ outcome: 'created',
275
+ rerun: `xano function create${workspace}${source} --policy-override "<why>"`,
276
+ });
277
+ for (const line of lines)
278
+ this.log(line);
279
+ this.refusedByPolicyGate = true;
280
+ this.error(message, { exit: 2 });
281
+ }
242
282
  }
@@ -9,6 +9,7 @@ export default class FunctionEdit extends BaseCommand {
9
9
  edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  publish: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
14
  stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
15
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -16,10 +17,24 @@ export default class FunctionEdit extends BaseCommand {
16
17
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
18
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
19
  };
20
+ /** Set once the publish policy gate refuses, the one failure that exits 2. */
21
+ private refusedByPolicyGate;
22
+ /** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
23
+ protected catch(error: Error & {
24
+ oclif?: {
25
+ exit?: number;
26
+ };
27
+ }): Promise<void>;
19
28
  run(): Promise<void>;
20
29
  private editFile;
21
30
  private editFunctionContent;
22
31
  private fetchFunctionCode;
23
32
  private promptForFunctionId;
24
33
  private readStdin;
34
+ /**
35
+ * The live-branch publish gate refused the update: the verdict on stdout (under `-o json`,
36
+ * `{updated: false, message, policy_gate}`), then the platform's message and how to proceed,
37
+ * exiting 2.
38
+ */
39
+ private refusePublish;
25
40
  }