@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
@@ -3,8 +3,9 @@ import inquirer from 'inquirer';
3
3
  import { execSync } from 'node:child_process';
4
4
  import * as fs from 'node:fs';
5
5
  import * as os from 'node:os';
6
- import * as path from 'node:path';
6
+ import path from 'node:path';
7
7
  import BaseCommand, { buildUserAgent } from '../../../base-command.js';
8
+ import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateRefusal, publishRefusal, quoted } from '../../../utils/policy/gate.js';
8
9
  export default class FunctionEdit extends BaseCommand {
9
10
  static args = {
10
11
  function_id: Args.string({
@@ -59,6 +60,7 @@ Name: my_function
59
60
  ...
60
61
  }
61
62
  `,
63
+ `$ xano function:edit 163 -f function.xs --policy-override "Exception approved; finding tracked in JIRA-12"`,
62
64
  ];
63
65
  static flags = {
64
66
  ...BaseCommand.baseFlags,
@@ -81,6 +83,7 @@ Name: my_function
81
83
  options: ['summary', 'json'],
82
84
  required: false,
83
85
  }),
86
+ 'policy-override': Flags.string({ description: 'Publish past a blocking live-branch publish policy gate with an audited reason (requires workspace:policy update)' }),
84
87
  publish: Flags.boolean({
85
88
  default: true,
86
89
  description: 'Publish the function after editing',
@@ -99,8 +102,18 @@ Name: my_function
99
102
  required: false,
100
103
  }),
101
104
  };
105
+ /** Set once the publish policy gate refuses, the one failure that exits 2. */
106
+ refusedByPolicyGate = false;
107
+ /** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
108
+ async catch(error) {
109
+ if (this.refusedByPolicyGate)
110
+ return super.catch(error);
111
+ return this.catchAsOperational(error);
112
+ }
102
113
  async run() {
103
114
  const { args, flags } = await this.parse(FunctionEdit);
115
+ if (blankPolicyOverride(flags['policy-override']))
116
+ this.error(BLANK_POLICY_OVERRIDE);
104
117
  const { profile, profileName } = this.resolveProfile(flags);
105
118
  // Validate flag combinations
106
119
  if (flags.edit && !flags.file) {
@@ -120,8 +133,7 @@ Name: my_function
120
133
  ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
121
134
  }
122
135
  // If function_id is not provided, prompt user to select from list
123
- let functionId;
124
- functionId = args.function_id ? args.function_id : await this.promptForFunctionId(profile, workspaceId);
136
+ const functionId = args.function_id || (await this.promptForFunctionId(profile, workspaceId));
125
137
  // Read XanoScript content
126
138
  let xanoscript;
127
139
  if (flags.file) {
@@ -176,6 +188,10 @@ Name: my_function
176
188
  include_xanoscript: 'false',
177
189
  publish: flags.publish ? 'true' : 'false',
178
190
  });
191
+ // A reason from someone with workspace:policy update publishes past a blocking publish gate.
192
+ const overrideReason = flags['policy-override']?.trim();
193
+ if (overrideReason)
194
+ queryParams.set('override_reason', overrideReason);
179
195
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/${functionId}?${queryParams.toString()}`;
180
196
  // Update function via API
181
197
  try {
@@ -189,6 +205,9 @@ Name: my_function
189
205
  method: 'PUT',
190
206
  }, flags.verbose, profile.access_token);
191
207
  if (!response.ok) {
208
+ const refused = await gateRefusal(response);
209
+ if (refused)
210
+ this.refusePublish(refused, flags, functionId);
192
211
  const errorText = await response.text();
193
212
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
194
213
  }
@@ -209,6 +228,8 @@ Name: my_function
209
228
  }
210
229
  }
211
230
  catch (error) {
231
+ if (this.refusedByPolicyGate)
232
+ throw error;
212
233
  if (error instanceof Error) {
213
234
  this.error(`Failed to update function: ${error.message}`);
214
235
  }
@@ -221,14 +242,14 @@ Name: my_function
221
242
  // Get the EDITOR environment variable
222
243
  const editor = process.env.EDITOR || process.env.VISUAL;
223
244
  if (!editor) {
224
- this.error('No editor configured. Please set the EDITOR or VISUAL environment variable.\n' + 'Example: export EDITOR=vim');
245
+ this.error('No editor configured. Please set the EDITOR or VISUAL environment variable.\nExample: export EDITOR=vim');
225
246
  }
226
247
  // Validate editor executable exists
227
248
  try {
228
249
  execSync(`which ${editor.split(' ')[0]}`, { stdio: 'ignore' });
229
250
  }
230
251
  catch {
231
- this.error(`Editor '${editor}' not found. Please set EDITOR to a valid editor.\n` + 'Example: export EDITOR=vim');
252
+ this.error(`Editor '${editor}' not found. Please set EDITOR to a valid editor.\nExample: export EDITOR=vim`);
232
253
  }
233
254
  // Read the original file
234
255
  let originalContent;
@@ -321,35 +342,30 @@ Name: my_function
321
342
  include_xanoscript: 'true',
322
343
  });
323
344
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/${functionId}?${queryParams.toString()}`;
324
- try {
325
- const response = await fetch(apiUrl, {
326
- headers: {
327
- 'User-Agent': buildUserAgent(this.config.version),
328
- accept: 'application/json',
329
- Authorization: `Bearer ${profile.access_token}`,
330
- },
331
- method: 'GET',
332
- });
333
- if (!response.ok) {
334
- const errorText = await response.text();
335
- throw new Error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
345
+ const response = await fetch(apiUrl, {
346
+ headers: {
347
+ accept: 'application/json',
348
+ Authorization: `Bearer ${profile.access_token}`,
349
+ 'User-Agent': buildUserAgent(this.config.version),
350
+ },
351
+ method: 'GET',
352
+ });
353
+ if (!response.ok) {
354
+ const errorText = await response.text();
355
+ throw new Error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
356
+ }
357
+ const result = (await response.json());
358
+ // Handle xanoscript as an object with status and value
359
+ if (result.xanoscript) {
360
+ if (result.xanoscript.status === 'ok' && result.xanoscript.value !== undefined) {
361
+ return result.xanoscript.value;
336
362
  }
337
- const result = (await response.json());
338
- // Handle xanoscript as an object with status and value
339
- if (result.xanoscript) {
340
- if (result.xanoscript.status === 'ok' && result.xanoscript.value !== undefined) {
341
- return result.xanoscript.value;
342
- }
343
- if (typeof result.xanoscript === 'string') {
344
- return result.xanoscript;
345
- }
346
- throw new Error(`Invalid xanoscript format: ${JSON.stringify(result.xanoscript)}`);
363
+ if (typeof result.xanoscript === 'string') {
364
+ return result.xanoscript;
347
365
  }
348
- return '';
349
- }
350
- catch (error) {
351
- throw error;
366
+ throw new Error(`Invalid xanoscript format: ${JSON.stringify(result.xanoscript)}`);
352
367
  }
368
+ return '';
353
369
  }
354
370
  async promptForFunctionId(profile, workspaceId) {
355
371
  try {
@@ -365,9 +381,9 @@ Name: my_function
365
381
  const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
366
382
  const response = await fetch(listUrl, {
367
383
  headers: {
368
- 'User-Agent': buildUserAgent(this.config.version),
369
384
  accept: 'application/json',
370
385
  Authorization: `Bearer ${profile.access_token}`,
386
+ 'User-Agent': buildUserAgent(this.config.version),
371
387
  },
372
388
  method: 'GET',
373
389
  });
@@ -434,4 +450,22 @@ Name: my_function
434
450
  process.stdin.resume();
435
451
  });
436
452
  }
453
+ /**
454
+ * The live-branch publish gate refused the update: the verdict on stdout (under `-o json`,
455
+ * `{updated: false, message, policy_gate}`), then the platform's message and how to proceed,
456
+ * exiting 2.
457
+ */
458
+ refusePublish(refused, flags, functionId) {
459
+ const workspace = flags.workspace ? ` -w ${quoted(flags.workspace)}` : '';
460
+ const source = flags.file ? ` -f ${quoted(flags.file)}` : flags.stdin ? ' --stdin' : '';
461
+ const { lines, message } = publishRefusal(refused, {
462
+ json: flags.output === 'json',
463
+ outcome: 'updated',
464
+ rerun: `xano function edit ${quoted(functionId)}${workspace}${source} --policy-override "<why>"`,
465
+ });
466
+ for (const line of lines)
467
+ this.log(line);
468
+ this.refusedByPolicyGate = true;
469
+ this.error(message, { exit: 2 });
470
+ }
437
471
  }
@@ -89,8 +89,7 @@ function yo {
89
89
  ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
90
90
  }
91
91
  // If function_id is not provided, prompt user to select from list
92
- let functionId;
93
- functionId = args.function_id ? args.function_id : await this.promptForFunctionId(profile, workspaceId);
92
+ const functionId = args.function_id || (await this.promptForFunctionId(profile, workspaceId));
94
93
  // Build query parameters
95
94
  // Automatically set include_xanoscript to true if output format is xs
96
95
  const includeXanoscript = flags.output === 'xs' ? true : flags.include_xanoscript;
@@ -178,9 +177,9 @@ function yo {
178
177
  const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
179
178
  const response = await fetch(listUrl, {
180
179
  headers: {
181
- 'User-Agent': buildUserAgent(this.config.version),
182
180
  accept: 'application/json',
183
181
  Authorization: `Bearer ${profile.access_token}`,
182
+ 'User-Agent': buildUserAgent(this.config.version),
184
183
  },
185
184
  method: 'GET',
186
185
  });
@@ -8,6 +8,7 @@ export default class FunctionRun extends BaseCommand {
8
8
  static flags: {
9
9
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  data: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ datasource: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  json: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  logs: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
14
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -17,6 +17,8 @@ export default class FunctionRun extends BaseCommand {
17
17
  `$ xano function:run calcScore --json @payload.json --data env=staging`,
18
18
  `$ echo '{"email":"jo@x.com"}' | xano function:run calcScore --stdin -o json | jq .result`,
19
19
  `$ xano function:run calcScore --branch dev --logs`,
20
+ `$ xano function:run calcScore --datasource test
21
+ # Runs against the 'test' data source instead of 'live'`,
20
22
  ];
21
23
  static flags = {
22
24
  ...BaseCommand.baseFlags,
@@ -30,6 +32,10 @@ export default class FunctionRun extends BaseCommand {
30
32
  multiple: true,
31
33
  required: false,
32
34
  }),
35
+ datasource: Flags.string({
36
+ description: 'Data source to run against, e.g. test (defaults to live)',
37
+ required: false,
38
+ }),
33
39
  json: Flags.string({
34
40
  description: "Input as a JSON object: inline, @file.json, or '-' for stdin",
35
41
  exclusive: ['stdin'],
@@ -106,13 +112,21 @@ export default class FunctionRun extends BaseCommand {
106
112
  const body = { input, name: functionName };
107
113
  if (branch)
108
114
  body.branch = branch;
115
+ const headers = {
116
+ 'accept': 'application/json',
117
+ 'Authorization': `Bearer ${profile.access_token}`,
118
+ 'Content-Type': 'application/json',
119
+ };
120
+ // The Metadata API resolves the workspace's tables against the data source
121
+ // named by X-Data-Source (defaulting to `live`), so the run reads and writes
122
+ // the same tables a request carrying that header would. An unknown label is
123
+ // rejected server-side with "Invalid data source."
124
+ const dataSource = flags.datasource?.trim();
125
+ if (dataSource)
126
+ headers['X-Data-Source'] = dataSource;
109
127
  const response = await this.verboseFetch(apiUrl, {
110
128
  body: JSON.stringify(body),
111
- headers: {
112
- 'accept': 'application/json',
113
- 'Authorization': `Bearer ${profile.access_token}`,
114
- 'Content-Type': 'application/json',
115
- },
129
+ headers,
116
130
  method: 'POST',
117
131
  }, flags.verbose, profile.access_token);
118
132
  if (!response.ok) {
@@ -153,7 +167,6 @@ export default class FunctionRun extends BaseCommand {
153
167
  */
154
168
  async fetchFunctionByName(opts) {
155
169
  const { branch, name, profile, verbose, workspaceId } = opts;
156
- // eslint-disable-next-line camelcase -- external Metadata API query param
157
170
  const params = new URLSearchParams({ per_page: '100', search: name });
158
171
  if (branch)
159
172
  params.set('branch', branch);
@@ -0,0 +1,15 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyCatalogue extends PolicyCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ check: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,29 @@
1
+ import { Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { policyCatalogueSummary, selectCatalogueCheck } from '../../../utils/policy/catalogue.js';
4
+ import { listItems } from '../../../utils/policy/request.js';
5
+ export default class PolicyCatalogue extends PolicyCommand {
6
+ static description = 'List built-in policy checks and their parameter schemas';
7
+ static examples = ['$ xano policy catalogue -o json', '$ xano policy catalogue --check object.auth_required'];
8
+ static flags = {
9
+ ...PolicyCommand.policyFlags,
10
+ check: Flags.string({ description: 'Show only this check id (an unknown id names the closest matches)' }),
11
+ };
12
+ async run() {
13
+ const { flags } = await this.parse(PolicyCatalogue);
14
+ const { request } = this.policyTarget(flags);
15
+ const result = await request('/check');
16
+ if (flags.check) {
17
+ // `--check` narrows both output modes, so `-o json` stays pipeable for one check too.
18
+ const selected = selectCatalogueCheck(listItems(result), flags.check);
19
+ this.log(flags.output === 'json' ? JSON.stringify(selected, null, 2) : policyCatalogueSummary(selected).join('\n'));
20
+ }
21
+ else if (flags.output === 'json') {
22
+ this.log(JSON.stringify(result, null, 2));
23
+ }
24
+ else {
25
+ for (const line of policyCatalogueSummary(listItems(result)))
26
+ this.log(line);
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,18 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyCreate extends PolicyCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ goal: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ key: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ param: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, 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
+ }
@@ -0,0 +1,39 @@
1
+ import { Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { policyFromGoal } from '../../../utils/policy/goal.js';
4
+ import { listAllPolicies } from '../../../utils/policy/request.js';
5
+ export default class PolicyCreate extends PolicyCommand {
6
+ static description = 'Create an independent policy from a platform goal';
7
+ static examples = [
8
+ '$ xano policy create --goal endpoints_need_login -b dev',
9
+ `$ xano policy create --goal no_pii_in_responses --key DATA-020 --param 'fields=["email","ssn"]'`,
10
+ ];
11
+ static flags = {
12
+ ...PolicyCommand.policyFlags,
13
+ ...PolicyCommand.publishFlags,
14
+ goal: Flags.string({ description: 'Goal id from policy catalogue -o json', required: true }),
15
+ key: Flags.string({ description: 'New policy key (default: the goal key with a free numeric suffix if taken)' }),
16
+ param: Flags.string({ description: 'Parameter override: N.path=JSON, 1-based rule number; path=JSON for a single-rule goal', multiple: true }),
17
+ };
18
+ async run() {
19
+ const { flags } = await this.parse(PolicyCreate);
20
+ const target = this.policyTarget(flags);
21
+ const { request } = target;
22
+ const catalogue = await request('/check');
23
+ const goal = catalogue.goals?.find(item => item.id === flags.goal);
24
+ if (!goal)
25
+ this.error(`Unknown goal "${flags.goal}". Run xano policy catalogue -o json to see this instance's goals.`);
26
+ const keys = (await listAllPolicies(request)).map(policy => policy.key);
27
+ // The key is checked against the pattern this instance's catalogue serves.
28
+ const document = policyFromGoal(goal, keys, { key: flags.key, keyRule: catalogue.document?.key, overrides: flags.param });
29
+ const parsed = await this.parseSource(request, { data: document });
30
+ const saved = await request('', 'POST', { data: { source: parsed.source }, ...(flags.message ? { message: flags.message } : {}) });
31
+ if (typeof saved?.id !== 'number')
32
+ this.error('The platform did not return the saved policy. Check xano policy list before retrying.');
33
+ this.logRuleWarnings(parsed);
34
+ if (flags.output === 'json')
35
+ this.log(JSON.stringify(saved, null, 2));
36
+ else
37
+ this.log(`Created ${parsed.policy.key} (Version ${saved.version}) in ${this.where(target)}.`);
38
+ }
39
+ }
@@ -0,0 +1,19 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyDelete extends PolicyCommand {
3
+ static args: {
4
+ policy: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, 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
+ /** `workspace push` is additive, so deleting a policy file leaves the policy in place; this removes it. */
18
+ run(): Promise<void>;
19
+ }
@@ -0,0 +1,61 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { confirm } from '../../../utils/multidoc-push.js';
4
+ import { listAllPolicies, sameKey } from '../../../utils/policy/request.js';
5
+ export default class PolicyDelete extends PolicyCommand {
6
+ static args = {
7
+ policy: Args.string({
8
+ description: 'Policy key (AUTH-001) or ID to delete',
9
+ required: true,
10
+ }),
11
+ };
12
+ static description = 'Delete a policy from a branch; its Version History is kept. Deleting an active, mandatory policy also needs the workspace:policy update permission.';
13
+ static examples = [
14
+ `$ xano policy delete TMP-DX-001
15
+ Delete policy TMP-DX-001 (ID: 922, Version 2) from workspace 3? Its Version History is kept. (y/N) y
16
+ Deleted policy TMP-DX-001 (ID: 922) from workspace 3.
17
+ `,
18
+ '$ xano policy delete 922 --force',
19
+ '$ xano policy delete TMP-DX-001 -w 3 --force -o json',
20
+ ];
21
+ static flags = {
22
+ ...PolicyCommand.policyFlags,
23
+ force: Flags.boolean({
24
+ char: 'f',
25
+ default: false,
26
+ description: '[IMPORTANT] NEVER run without explicit user confirmation. Skips the confirmation prompt.',
27
+ }),
28
+ };
29
+ /** `workspace push` is additive, so deleting a policy file leaves the policy in place; this removes it. */
30
+ async run() {
31
+ const { args, flags } = await this.parse(PolicyDelete);
32
+ const target = this.policyTarget(flags);
33
+ const wanted = args.policy.trim();
34
+ if (!wanted)
35
+ this.error('Provide the policy key or ID to delete.');
36
+ const policies = await listAllPolicies(target.request);
37
+ const matched = policies.find((policy) => sameKey(policy.key, wanted))
38
+ ?? (/^\d+$/.test(wanted) ? policies.find((policy) => policy.id === Number(wanted)) : undefined);
39
+ if (!matched) {
40
+ const known = policies.map((policy) => policy.key).sort();
41
+ this.error(`No policy "${wanted}" on ${this.where(target)}.${known.length > 0 ? ` This branch has: ${known.join(', ')}.` : ' This branch has no policies.'}`);
42
+ }
43
+ if (!flags.force) {
44
+ // Nobody can answer the prompt: refuse rather than report a cancel as success.
45
+ if (!process.stdin.isTTY)
46
+ this.error('Non-interactive environment detected. Use --force to skip confirmation.');
47
+ const confirmed = await confirm(`Delete policy ${matched.key} (ID: ${matched.id}, Version ${matched.version}) from ${this.where(target)}? Its Version History is kept.`);
48
+ if (!confirmed) {
49
+ this.log('Deletion cancelled.');
50
+ return;
51
+ }
52
+ }
53
+ // The policy as listed is what the prompt named: a delete of a policy changed since is refused (`policy_stale`).
54
+ const staleCheck = matched.updated_at ? { last_updated_at: String(matched.updated_at) } : {};
55
+ await target.request(`/${matched.id}`, 'DELETE', undefined, staleCheck);
56
+ if (flags.output === 'json')
57
+ this.log(JSON.stringify({ deleted: true, id: matched.id, key: matched.key }, null, 2));
58
+ else
59
+ this.log(`Deleted policy ${matched.key} (ID: ${matched.id}) from ${this.where(target)}.`);
60
+ }
61
+ }
@@ -0,0 +1,32 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ /** One rule's clear objects in a trial's answer: the first ones and how many there are. */
3
+ type TrialClear = {
4
+ items?: Array<{
5
+ name?: string;
6
+ }>;
7
+ policy_key?: string;
8
+ rule_id?: string;
9
+ total?: number;
10
+ };
11
+ /**
12
+ * What a trial says it is, then each rule's clear objects: the objects it examined and found
13
+ * nothing on. A trial is never stored, so its answer is all there is to show.
14
+ */
15
+ export declare function trialLines(key: string, clear: TrialClear[] | undefined): string[];
16
+ export default class PolicyEvaluate extends PolicyCommand {
17
+ static description: string;
18
+ static examples: string[];
19
+ static flags: {
20
+ policy: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'run-detail': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ summary: import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
25
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
26
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
+ };
30
+ run(): Promise<void>;
31
+ }
32
+ export {};
@@ -0,0 +1,71 @@
1
+ import { Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { evaluationEvidence, notStoredLine, policyCheckWarning, policyExitCode, policySummary, } from '../../../utils/policy/feedback.js';
4
+ /** How many clear objects a trial names per rule; the rest are counted. */
5
+ const TRIAL_CLEAR_NAMES = 5;
6
+ /**
7
+ * What a trial says it is, then each rule's clear objects: the objects it examined and found
8
+ * nothing on. A trial is never stored, so its answer is all there is to show.
9
+ */
10
+ export function trialLines(key, clear) {
11
+ const lines = [`Trial of ${key}: this policy alone, evaluated without storing a run. It is not a report and gates nothing.`];
12
+ if (!clear?.length)
13
+ return lines;
14
+ lines.push('Clear objects (examined, nothing found):');
15
+ for (const rule of clear) {
16
+ const names = (rule.items ?? []).map((item) => item.name ?? '').filter(Boolean).slice(0, TRIAL_CLEAR_NAMES);
17
+ const total = rule.total ?? 0;
18
+ const more = total > names.length ? `${names.length > 0 ? ', ' : ''}${total - names.length} more` : '';
19
+ lines.push(` ${rule.rule_id ?? ''} ${total}${names.length > 0 || more ? ` ${names.join(', ')}${more}` : ''}`);
20
+ }
21
+ return lines;
22
+ }
23
+ export default class PolicyEvaluate extends PolicyCommand {
24
+ static description = 'Evaluate active branch policies and report their findings; blocking findings exit 2. With --policy, try one policy alone, whatever its state, without storing a run';
25
+ static examples = ['$ xano policy evaluate -o json', '$ xano policy evaluate --run-detail', '$ xano policy evaluate --summary -o json', '$ xano policy evaluate --policy AUTH-001'];
26
+ static flags = {
27
+ ...PolicyCommand.policyFlags,
28
+ policy: Flags.string({
29
+ description: 'Try this one policy (its key) alone, whatever its state: nothing is stored, and the answer lists each rule\'s clear objects',
30
+ }),
31
+ 'run-detail': Flags.boolean({
32
+ default: false,
33
+ description: 'Also print what this run recorded: each policy description and the settings each rule ran with',
34
+ }),
35
+ summary: Flags.boolean({
36
+ default: false,
37
+ description: 'Answer the run summary and its first 50 findings instead of the whole run; `xano policy runs <id>` pages through the rest',
38
+ }),
39
+ };
40
+ async run() {
41
+ const { flags } = await this.parse(PolicyEvaluate);
42
+ if (flags.policy !== undefined && !flags.policy.trim())
43
+ this.error('--policy must name a policy key.');
44
+ const { request } = this.policyTarget(flags);
45
+ // The evaluation answers with its run, so the run's own snapshot names any unnamed rule. The
46
+ // summary answer carries the same snapshot and results, and only the first findings.
47
+ const body = { ...(flags.summary ? { answer: 'summary' } : {}), ...(flags.policy ? { policy: flags.policy } : {}) };
48
+ const result = (await request('/evaluate', 'POST', Object.keys(body).length > 0 ? body : undefined));
49
+ if (flags.output === 'json')
50
+ this.log(JSON.stringify(result, null, 2));
51
+ else {
52
+ for (const line of policySummary(result.policy_check, evaluationEvidence(result)))
53
+ this.log(line);
54
+ // A trial is never stored by design; the "cannot record runs" note is about credentials.
55
+ const notStored = flags.policy ? null : notStoredLine(result);
56
+ if (notStored)
57
+ this.log(notStored);
58
+ if (flags.policy)
59
+ for (const line of trialLines(flags.policy, result.clear))
60
+ this.log(line);
61
+ if (flags['run-detail'])
62
+ this.logRunDetail(result);
63
+ }
64
+ const warning = policyCheckWarning(result.policy_check);
65
+ if (warning)
66
+ this.warn(warning);
67
+ const code = policyExitCode(result.policy_check);
68
+ if (code)
69
+ process.exitCode = code;
70
+ }
71
+ }
@@ -0,0 +1,14 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyList extends PolicyCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,21 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ import { listItems } from '../../../utils/policy/request.js';
3
+ export default class PolicyList extends PolicyCommand {
4
+ static description = 'List workspace policies and their descriptions';
5
+ static examples = ['$ xano policy list -o json'];
6
+ static flags = { ...PolicyCommand.policyFlags };
7
+ async run() {
8
+ const { flags } = await this.parse(PolicyList);
9
+ const { request } = this.policyTarget(flags);
10
+ const result = await request();
11
+ if (flags.output === 'json') {
12
+ this.log(JSON.stringify(result, null, 2));
13
+ return;
14
+ }
15
+ const policies = listItems(result);
16
+ if (policies.length === 0)
17
+ this.log('No policies found.');
18
+ for (const policy of policies)
19
+ this.log(`${policy.key} ${policy.active ? `Active, ${policy.enforcement === 'mandatory' ? 'Mandatory' : 'Advisory'}` : 'Inactive'} ${policy.title ?? ''} (ID: ${policy.id}, Version ${policy.version})`);
20
+ }
21
+ }
@@ -0,0 +1,19 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyParse extends PolicyCommand {
3
+ static args: {
4
+ file: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ };
18
+ run(): Promise<void>;
19
+ }