@xano/cli 1.3.3 → 1.3.5-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 (118) hide show
  1. package/README.md +137 -3
  2. package/dist/base-command.d.ts +12 -1
  3. package/dist/base-command.js +36 -22
  4. package/dist/commands/branch/set_live/index.d.ts +14 -0
  5. package/dist/commands/branch/set_live/index.js +38 -0
  6. package/dist/commands/ephemeral/push/index.js +6 -1
  7. package/dist/commands/function/create/index.d.ts +15 -0
  8. package/dist/commands/function/create/index.js +39 -0
  9. package/dist/commands/function/edit/index.d.ts +15 -0
  10. package/dist/commands/function/edit/index.js +39 -0
  11. package/dist/commands/function/run/index.js +5 -11
  12. package/dist/commands/policy/catalogue/index.d.ts +16 -0
  13. package/dist/commands/policy/catalogue/index.js +60 -0
  14. package/dist/commands/policy/coverage/index.d.ts +17 -0
  15. package/dist/commands/policy/coverage/index.js +33 -0
  16. package/dist/commands/policy/create/index.d.ts +18 -0
  17. package/dist/commands/policy/create/index.js +44 -0
  18. package/dist/commands/policy/delete/index.d.ts +19 -0
  19. package/dist/commands/policy/delete/index.js +61 -0
  20. package/dist/commands/policy/evaluate/index.d.ts +32 -0
  21. package/dist/commands/policy/evaluate/index.js +71 -0
  22. package/dist/commands/policy/list/index.d.ts +14 -0
  23. package/dist/commands/policy/list/index.js +21 -0
  24. package/dist/commands/policy/parse/index.d.ts +19 -0
  25. package/dist/commands/policy/parse/index.js +18 -0
  26. package/dist/commands/policy/publish/index.d.ts +20 -0
  27. package/dist/commands/policy/publish/index.js +38 -0
  28. package/dist/commands/policy/runs/index.d.ts +54 -0
  29. package/dist/commands/policy/runs/index.js +273 -0
  30. package/dist/commands/policy/status/index.d.ts +21 -0
  31. package/dist/commands/policy/status/index.js +54 -0
  32. package/dist/commands/release/create/index.js +4 -0
  33. package/dist/commands/release/deploy/index.d.ts +19 -0
  34. package/dist/commands/release/deploy/index.js +68 -6
  35. package/dist/commands/release/import/index.js +5 -1
  36. package/dist/commands/release/list/index.d.ts +7 -0
  37. package/dist/commands/release/list/index.js +46 -9
  38. package/dist/commands/release/push/index.js +19 -1
  39. package/dist/commands/sandbox/delete/index.js +8 -0
  40. package/dist/commands/sandbox/pull/index.d.ts +7 -0
  41. package/dist/commands/sandbox/pull/index.js +49 -0
  42. package/dist/commands/sandbox/push/index.d.ts +4 -0
  43. package/dist/commands/sandbox/push/index.js +59 -2
  44. package/dist/commands/sandbox/reset/index.js +8 -0
  45. package/dist/commands/skills/pull/index.d.ts +30 -0
  46. package/dist/commands/skills/pull/index.js +118 -0
  47. package/dist/commands/tenant/create/index.d.ts +3 -0
  48. package/dist/commands/tenant/create/index.js +21 -0
  49. package/dist/commands/tenant/deploy_release/index.d.ts +40 -0
  50. package/dist/commands/tenant/deploy_release/index.js +182 -4
  51. package/dist/commands/tenant/edit/index.d.ts +2 -0
  52. package/dist/commands/tenant/edit/index.js +39 -4
  53. package/dist/commands/tenant_deploy_request/bypass/index.d.ts +31 -0
  54. package/dist/commands/tenant_deploy_request/bypass/index.js +109 -0
  55. package/dist/commands/tenant_deploy_request/create/index.d.ts +21 -0
  56. package/dist/commands/tenant_deploy_request/create/index.js +108 -0
  57. package/dist/commands/tenant_deploy_request/edit/index.d.ts +22 -0
  58. package/dist/commands/tenant_deploy_request/edit/index.js +103 -0
  59. package/dist/commands/tenant_deploy_request/get/index.d.ts +25 -0
  60. package/dist/commands/tenant_deploy_request/get/index.js +113 -0
  61. package/dist/commands/tenant_deploy_request/list/index.d.ts +20 -0
  62. package/dist/commands/tenant_deploy_request/list/index.js +122 -0
  63. package/dist/commands/tenant_deploy_request/revision/index.d.ts +21 -0
  64. package/dist/commands/tenant_deploy_request/revision/index.js +83 -0
  65. package/dist/commands/tenant_deploy_request/set_status/index.d.ts +38 -0
  66. package/dist/commands/tenant_deploy_request/set_status/index.js +159 -0
  67. package/dist/commands/workspace/pull/index.d.ts +17 -0
  68. package/dist/commands/workspace/pull/index.js +124 -7
  69. package/dist/commands/workspace/push/index.d.ts +23 -0
  70. package/dist/commands/workspace/push/index.js +188 -6
  71. package/dist/policy-command.d.ts +91 -0
  72. package/dist/policy-command.js +95 -0
  73. package/dist/utils/document-parser.d.ts +10 -0
  74. package/dist/utils/document-parser.js +21 -0
  75. package/dist/utils/export-compare.d.ts +52 -0
  76. package/dist/utils/export-compare.js +134 -0
  77. package/dist/utils/local-config.d.ts +23 -0
  78. package/dist/utils/local-config.js +62 -2
  79. package/dist/utils/multidoc-push.d.ts +124 -4
  80. package/dist/utils/multidoc-push.js +670 -49
  81. package/dist/utils/object-fetch.d.ts +86 -0
  82. package/dist/utils/object-fetch.js +265 -0
  83. package/dist/utils/policy/catalogue.d.ts +85 -0
  84. package/dist/utils/policy/catalogue.js +196 -0
  85. package/dist/utils/policy/coverage.d.ts +19 -0
  86. package/dist/utils/policy/coverage.js +93 -0
  87. package/dist/utils/policy/errors.d.ts +20 -0
  88. package/dist/utils/policy/errors.js +93 -0
  89. package/dist/utils/policy/feedback.d.ts +90 -0
  90. package/dist/utils/policy/feedback.js +210 -0
  91. package/dist/utils/policy/findings.d.ts +29 -0
  92. package/dist/utils/policy/findings.js +82 -0
  93. package/dist/utils/policy/gate.d.ts +138 -0
  94. package/dist/utils/policy/gate.js +193 -0
  95. package/dist/utils/policy/permission.d.ts +7 -0
  96. package/dist/utils/policy/permission.js +55 -0
  97. package/dist/utils/policy/push-gate.d.ts +5 -0
  98. package/dist/utils/policy/push-gate.js +43 -0
  99. package/dist/utils/policy/release.d.ts +85 -0
  100. package/dist/utils/policy/release.js +117 -0
  101. package/dist/utils/policy/request.d.ts +55 -0
  102. package/dist/utils/policy/request.js +119 -0
  103. package/dist/utils/policy/runs.d.ts +30 -0
  104. package/dist/utils/policy/runs.js +101 -0
  105. package/dist/utils/policy/status.d.ts +55 -0
  106. package/dist/utils/policy/status.js +127 -0
  107. package/dist/utils/policy/template.d.ts +66 -0
  108. package/dist/utils/policy/template.js +152 -0
  109. package/dist/utils/policy/types.d.ts +295 -0
  110. package/dist/utils/policy/types.js +1 -0
  111. package/dist/utils/reference-checker.d.ts +3 -0
  112. package/dist/utils/reference-checker.js +3 -2
  113. package/dist/utils/sandbox-marker.d.ts +9 -0
  114. package/dist/utils/sandbox-marker.js +119 -0
  115. package/dist/utils/sync-state.d.ts +71 -0
  116. package/dist/utils/sync-state.js +353 -0
  117. package/oclif.manifest.json +6472 -3181
  118. package/package.json +9 -1
@@ -5,6 +5,7 @@ import * as fs from 'node:fs';
5
5
  import * as os from 'node:os';
6
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({
@@ -81,6 +82,7 @@ Name: my_function
81
82
  options: ['summary', 'json'],
82
83
  required: false,
83
84
  }),
85
+ 'policy-override': Flags.string({ description: 'Publish past a blocking live-branch publish policy gate with an audited reason (requires workspace:policy update)', hidden: true }),
84
86
  publish: Flags.boolean({
85
87
  default: true,
86
88
  description: 'Publish the function after editing',
@@ -99,8 +101,18 @@ Name: my_function
99
101
  required: false,
100
102
  }),
101
103
  };
104
+ /** Set once the publish policy gate refuses, the one failure that exits 2. */
105
+ refusedByPolicyGate = false;
106
+ /** Exit 2 is the publish gate's refusal; every other failure, flag errors included, exits 1. */
107
+ async catch(error) {
108
+ if (this.refusedByPolicyGate)
109
+ return super.catch(error);
110
+ return this.catchAsOperational(error);
111
+ }
102
112
  async run() {
103
113
  const { args, flags } = await this.parse(FunctionEdit);
114
+ if (blankPolicyOverride(flags['policy-override']))
115
+ this.error(BLANK_POLICY_OVERRIDE);
104
116
  const { profile, profileName } = this.resolveProfile(flags);
105
117
  // Validate flag combinations
106
118
  if (flags.edit && !flags.file) {
@@ -175,6 +187,10 @@ Name: my_function
175
187
  include_xanoscript: 'false',
176
188
  publish: flags.publish ? 'true' : 'false',
177
189
  });
190
+ // A reason from someone with workspace:policy update publishes past a blocking publish gate.
191
+ const overrideReason = flags['policy-override']?.trim();
192
+ if (overrideReason)
193
+ queryParams.set('override_reason', overrideReason);
178
194
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/${functionId}?${queryParams.toString()}`;
179
195
  // Update function via API
180
196
  try {
@@ -188,6 +204,9 @@ Name: my_function
188
204
  method: 'PUT',
189
205
  }, flags.verbose, profile.access_token);
190
206
  if (!response.ok) {
207
+ const refused = await gateRefusal(response);
208
+ if (refused)
209
+ this.refusePublish(refused, flags, functionId);
191
210
  const errorText = await response.text();
192
211
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
193
212
  }
@@ -208,6 +227,8 @@ Name: my_function
208
227
  }
209
228
  }
210
229
  catch (error) {
230
+ if (this.refusedByPolicyGate)
231
+ throw error;
211
232
  if (error instanceof Error) {
212
233
  this.error(`Failed to update function: ${error.message}`);
213
234
  }
@@ -428,4 +449,22 @@ Name: my_function
428
449
  process.stdin.resume();
429
450
  });
430
451
  }
452
+ /**
453
+ * The live-branch publish gate refused the update: the verdict on stdout (under `-o json`,
454
+ * `{updated: false, message, policy_gate}`), then the platform's message and how to proceed,
455
+ * exiting 2.
456
+ */
457
+ refusePublish(refused, flags, functionId) {
458
+ const workspace = flags.workspace ? ` -w ${quoted(flags.workspace)}` : '';
459
+ const source = flags.file ? ` -f ${quoted(flags.file)}` : flags.stdin ? ' --stdin' : '';
460
+ const { lines, message } = publishRefusal(refused, {
461
+ json: flags.output === 'json',
462
+ outcome: 'updated',
463
+ rerun: `xano function edit ${quoted(functionId)}${workspace}${source} --policy-override "<why>"`,
464
+ });
465
+ for (const line of lines)
466
+ this.log(line);
467
+ this.refusedByPolicyGate = true;
468
+ this.error(message, { exit: 2 });
469
+ }
431
470
  }
@@ -103,7 +103,6 @@ export default class FunctionRun extends BaseCommand {
103
103
  branch,
104
104
  name: functionName,
105
105
  profile,
106
- tenant: flags.tenant,
107
106
  verbose: flags.verbose,
108
107
  workspaceId,
109
108
  });
@@ -115,8 +114,9 @@ export default class FunctionRun extends BaseCommand {
115
114
  input = await this.promptForMissing(input, missingRequired);
116
115
  }
117
116
  }
118
- // 3) Execute via the meta run endpoint.
119
- const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/run`;
117
+ // 3) Execute via the meta run endpoint, scoped to the tenant when one is given.
118
+ const tenantPath = flags.tenant ? `/tenant/${encodeURIComponent(flags.tenant)}` : '';
119
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}${tenantPath}/function/run`;
120
120
  const body = { input, name: functionName };
121
121
  if (branch)
122
122
  body.branch = branch;
@@ -132,9 +132,6 @@ export default class FunctionRun extends BaseCommand {
132
132
  const dataSource = flags.datasource?.trim();
133
133
  if (dataSource)
134
134
  headers['X-Data-Source'] = dataSource;
135
- // X-Tenant runs the function on that tenant instead of the workspace.
136
- if (flags.tenant)
137
- headers['X-Tenant'] = flags.tenant;
138
135
  const response = await this.verboseFetch(apiUrl, {
139
136
  body: JSON.stringify(body),
140
137
  headers,
@@ -177,16 +174,13 @@ export default class FunctionRun extends BaseCommand {
177
174
  * transient introspection problem never blocks an otherwise-valid run.
178
175
  */
179
176
  async fetchFunctionByName(opts) {
180
- const { branch, name, profile, tenant, verbose, workspaceId } = opts;
177
+ const { branch, name, profile, verbose, workspaceId } = opts;
181
178
  const params = new URLSearchParams({ per_page: '100', search: name });
182
179
  if (branch)
183
180
  params.set('branch', branch);
184
181
  const url = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${params.toString()}`;
185
- const headers = { accept: 'application/json', Authorization: `Bearer ${profile.access_token}` };
186
- if (tenant)
187
- headers['X-Tenant'] = tenant;
188
182
  try {
189
- const response = await this.verboseFetch(url, { headers, method: 'GET' }, verbose, profile.access_token);
183
+ const response = await this.verboseFetch(url, { headers: { accept: 'application/json', Authorization: `Bearer ${profile.access_token}` }, method: 'GET' }, verbose, profile.access_token);
190
184
  if (!response.ok)
191
185
  return undefined;
192
186
  const page = (await response.json());
@@ -0,0 +1,16 @@
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
+ recipe: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ }
@@ -0,0 +1,60 @@
1
+ import { Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { policyCatalogueSummary, policyNotCheckableSummary, policyRecipeSummary, policyTemplateSummary, selectCatalogueCheck, selectCatalogueRecipe, } from '../../../utils/policy/catalogue.js';
4
+ import { listItems } from '../../../utils/policy/request.js';
5
+ /** A served list, or none when the instance serves something else (an older one serves no recipes). */
6
+ const array = (value) => Array.isArray(value) ? value : [];
7
+ export default class PolicyCatalogue extends PolicyCommand {
8
+ static description = 'List built-in policy checks with their uses and parameter schemas, the templates a policy can start from, the recipes that combine them and what no check can see';
9
+ static examples = [
10
+ '$ xano policy catalogue -o json',
11
+ '$ xano policy catalogue --check object.auth_required',
12
+ '$ xano policy catalogue --recipe protect_card_data',
13
+ ];
14
+ static flags = {
15
+ ...PolicyCommand.policyFlags,
16
+ check: Flags.string({ description: 'Show only this check id (an unknown id names the closest matches)', exclusive: ['recipe'] }),
17
+ recipe: Flags.string({ description: 'Show only this recipe id and what it cannot see (an unknown id names the closest matches)', exclusive: ['check'] }),
18
+ };
19
+ async run() {
20
+ const { flags } = await this.parse(PolicyCatalogue);
21
+ const { request } = this.policyTarget(flags);
22
+ const result = await request('/check');
23
+ const sections = (result && typeof result === 'object' ? result : {});
24
+ const recipeContext = {
25
+ frameworks: array(sections.template_frameworks),
26
+ frameworksNote: sections.template_frameworks_note ?? '',
27
+ notCheckable: array(sections.not_checkable),
28
+ templates: array(sections.templates),
29
+ };
30
+ if (flags.check) {
31
+ // `--check` narrows both output modes, so `-o json` stays pipeable for one check too.
32
+ const selected = selectCatalogueCheck(listItems(result), flags.check);
33
+ this.log(flags.output === 'json' ? JSON.stringify(selected, null, 2) : policyCatalogueSummary(selected).join('\n'));
34
+ }
35
+ else if (flags.recipe) {
36
+ // `--recipe` narrows both output modes too; the summary adds the topics the recipe cannot see.
37
+ const selected = selectCatalogueRecipe(array(sections.recipes), flags.recipe);
38
+ if (flags.output === 'json') {
39
+ this.log(JSON.stringify(selected, null, 2));
40
+ return;
41
+ }
42
+ const cited = new Set(selected.flatMap(recipe => array(recipe.limits)));
43
+ const limits = recipeContext.notCheckable.filter(entry => cited.has(entry.id));
44
+ this.log([...policyRecipeSummary(selected, recipeContext), ...(limits.length > 0 ? ['', ...policyNotCheckableSummary(limits)] : [])].join('\n'));
45
+ }
46
+ else if (flags.output === 'json') {
47
+ this.log(JSON.stringify(result, null, 2));
48
+ }
49
+ else {
50
+ for (const line of policyCatalogueSummary(listItems(result)))
51
+ this.log(line);
52
+ const templates = policyTemplateSummary(recipeContext.templates, array(sections.template_categories), recipeContext.frameworks, recipeContext.frameworksNote);
53
+ const recipes = policyRecipeSummary(array(sections.recipes), recipeContext);
54
+ const notCheckable = policyNotCheckableSummary(recipeContext.notCheckable);
55
+ for (const section of [templates, recipes, notCheckable])
56
+ if (section.length > 0)
57
+ this.log(['', ...section].join('\n'));
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,17 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyCoverage extends PolicyCommand {
3
+ static args: {
4
+ object: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,33 @@
1
+ import { Args } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { coverageLines, parseObjectRef } from '../../../utils/policy/coverage.js';
4
+ export default class PolicyCoverage extends PolicyCommand {
5
+ static args = {
6
+ object: Args.string({
7
+ description: 'The object, written type:id as findings name it (query:42, function:12, table:7); for workspace, the workspace\'s own id (workspace:17)',
8
+ required: true,
9
+ }),
10
+ };
11
+ static description = "Show which active policies apply to one object, why, and the object's findings in the branch's latest run, "
12
+ + 'with each fix hint. Needs only the read permission for that object kind (workspace:api for an endpoint, '
13
+ + 'workspace:function for a function, …), not workspace:policy; the answer leaves out policy details that need it.';
14
+ static examples = [
15
+ '$ xano policy coverage query:42',
16
+ '$ xano policy coverage function:12 -b dev -o json',
17
+ ];
18
+ static flags = { ...PolicyCommand.policyFlags };
19
+ async run() {
20
+ const { args, flags } = await this.parse(PolicyCoverage);
21
+ const object = parseObjectRef(args.object);
22
+ if (!object)
23
+ this.error(`Name the object as type:id, as findings name it (for example query:42); got "${args.object}".`);
24
+ const { request } = this.policyTarget(flags);
25
+ const answer = (await request('/object/coverage', 'GET', undefined, { id: String(object.id), type: object.type }));
26
+ if (flags.output === 'json') {
27
+ this.log(JSON.stringify(answer, null, 2));
28
+ return;
29
+ }
30
+ for (const line of coverageLines(answer))
31
+ this.log(line);
32
+ }
33
+ }
@@ -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
+ key: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ param: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ template: import("@oclif/core/interfaces").OptionFlag<string, 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,44 @@
1
+ import { Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { listAllPolicies } from '../../../utils/policy/request.js';
4
+ import { policyFromTemplate, recipeRefusal } from '../../../utils/policy/template.js';
5
+ export default class PolicyCreate extends PolicyCommand {
6
+ static description = 'Create an independent policy from a platform template';
7
+ static examples = [
8
+ '$ xano policy create --template endpoints_need_login -b dev',
9
+ `$ xano policy create --template no_pii_in_responses --key DATA-020 --param 'fields=["email","ssn"]'`,
10
+ ];
11
+ static flags = {
12
+ ...PolicyCommand.policyFlags,
13
+ ...PolicyCommand.publishFlags,
14
+ key: Flags.string({ description: 'New policy key (default: the template key with a free numeric suffix if taken)' }),
15
+ param: Flags.string({ description: 'Parameter override: N.path=JSON, 1-based rule number; path=JSON for a single-rule template', multiple: true }),
16
+ template: Flags.string({ description: 'Template id from policy catalogue', required: 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 template = catalogue.templates?.find(item => item.id === flags.template);
24
+ // A recipe is data with no create action: name the templates it points at instead.
25
+ const recipe = template || !Array.isArray(catalogue.recipes) ? undefined : catalogue.recipes.find(item => item.id === flags.template);
26
+ if (recipe)
27
+ this.error(recipeRefusal(recipe));
28
+ if (!template)
29
+ this.error(`Unknown template "${flags.template}". Run xano policy catalogue to see this instance's templates.`);
30
+ const keys = (await listAllPolicies(request)).map(policy => policy.key);
31
+ // The key is checked against the pattern this instance's catalogue serves, and an unfilled need's
32
+ // example falls back to its check's own.
33
+ const document = policyFromTemplate(template, keys, { checks: catalogue.items, key: flags.key, keyRule: catalogue.document?.key, overrides: flags.param });
34
+ const parsed = await this.parseSource(request, { data: document });
35
+ const saved = await request('', 'POST', { data: { source: parsed.source }, ...(flags.message ? { message: flags.message } : {}) });
36
+ if (typeof saved?.id !== 'number')
37
+ this.error('The platform did not return the saved policy. Check xano policy list before retrying.');
38
+ this.logRuleWarnings(parsed);
39
+ if (flags.output === 'json')
40
+ this.log(JSON.stringify(saved, null, 2));
41
+ else
42
+ this.log(`Created ${parsed.policy.key} (Version ${saved.version}) in ${this.where(target)}.`);
43
+ }
44
+ }
@@ -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. It needs the workspace:policy delete permission only, an active, blocking policy included.';
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 === 'blocking' ? 'Blocking' : '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
+ }
@@ -0,0 +1,18 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyParse extends PolicyCommand {
3
+ static args = { ...PolicyCommand.sourceArgs };
4
+ static description = 'Validate and format policy XanoScript using the platform parser, with rule warnings against the branch';
5
+ static examples = [
6
+ '$ xano policy parse policies/AUTH-001.xs',
7
+ '$ xano policy parse --file policies/AUTH-001.xs',
8
+ ];
9
+ static flags = { ...PolicyCommand.policyFlags, ...PolicyCommand.sourceFlags };
10
+ async run() {
11
+ const { args, flags } = await this.parse(PolicyParse);
12
+ const { request } = this.policyTarget(flags);
13
+ const parsed = await this.parseSource(request, { source: this.readSource(flags, args.file) });
14
+ this.log(flags.output === 'json' ? JSON.stringify(parsed, null, 2) : parsed.source);
15
+ if (flags.output !== 'json')
16
+ this.logRuleWarnings(parsed);
17
+ }
18
+ }
@@ -0,0 +1,20 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyPublish 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
+ message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ };
19
+ run(): Promise<void>;
20
+ }