@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,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
+ }
@@ -0,0 +1,38 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ import { listAllPolicies, sameKey } from '../../../utils/policy/request.js';
3
+ export default class PolicyPublish extends PolicyCommand {
4
+ static args = { ...PolicyCommand.sourceArgs };
5
+ static description = 'Create or update a workspace policy from native XanoScript';
6
+ static examples = [
7
+ '$ xano policy publish policies/AUTH-001.xs',
8
+ '$ xano policy publish --file policies/AUTH-001.xs',
9
+ '$ xano policy publish --file policies/AUTH-001.xs -m "Tightened the scope"',
10
+ ];
11
+ static flags = { ...PolicyCommand.policyFlags, ...PolicyCommand.sourceFlags, ...PolicyCommand.publishFlags };
12
+ async run() {
13
+ const { args, flags } = await this.parse(PolicyPublish);
14
+ const target = this.policyTarget(flags);
15
+ const { request } = target;
16
+ const source = this.readSource(flags, args.file);
17
+ // The key picks PUT or POST, so the list is read alongside the parse rather than after it.
18
+ const [parsed, policies] = await Promise.all([this.parseSource(request, { source }), listAllPolicies(request)]);
19
+ // A key that differs only in case names the same policy, which PUT updates, key included.
20
+ const existing = policies.find((policy) => sameKey(policy.key, parsed.policy.key));
21
+ const body = { data: { source: parsed.source } };
22
+ if (flags.message)
23
+ body.message = flags.message;
24
+ const saved = (await request(existing ? `/${existing.id}` : '', existing ? 'PUT' : 'POST', body));
25
+ if (typeof saved?.id !== 'number') {
26
+ this.error('The platform did not return the saved policy. Check `xano policy list` before retrying.');
27
+ }
28
+ this.logRuleWarnings(parsed);
29
+ if (flags.output === 'json') {
30
+ this.log(JSON.stringify(saved, null, 2));
31
+ return;
32
+ }
33
+ // A save identical to the stored definition writes nothing: no version, history entry or audit record.
34
+ this.log(saved.unchanged === true
35
+ ? `No changes to ${parsed.policy.key} (Version ${saved.version}) in ${this.where(target)}.`
36
+ : `Published ${parsed.policy.key} (Version ${saved.version}) to ${this.where(target)}.`);
37
+ }
38
+ }
@@ -0,0 +1,54 @@
1
+ import { Interfaces } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ export default class PolicyRuns extends PolicyCommand {
4
+ static args: {
5
+ run_id: Interfaces.Arg<string | undefined, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static examples: string[];
9
+ static flags: {
10
+ advisory: Interfaces.BooleanFlag<boolean>;
11
+ all: Interfaces.BooleanFlag<boolean>;
12
+ blocking: Interfaces.BooleanFlag<boolean>;
13
+ kind: Interfaces.OptionFlag<string[] | undefined, Interfaces.CustomOptions>;
14
+ limit: Interfaces.OptionFlag<number | undefined, Interfaces.CustomOptions>;
15
+ object: Interfaces.OptionFlag<string[] | undefined, Interfaces.CustomOptions>;
16
+ offset: Interfaces.OptionFlag<number | undefined, Interfaces.CustomOptions>;
17
+ policy: Interfaces.OptionFlag<string[] | undefined, Interfaces.CustomOptions>;
18
+ recheck: Interfaces.BooleanFlag<boolean>;
19
+ release: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
20
+ 'release-id': Interfaces.OptionFlag<number | undefined, Interfaces.CustomOptions>;
21
+ rule: Interfaces.OptionFlag<string[] | undefined, Interfaces.CustomOptions>;
22
+ 'run-detail': Interfaces.BooleanFlag<boolean>;
23
+ search: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
24
+ severity: Interfaces.OptionFlag<string[] | undefined, Interfaces.CustomOptions>;
25
+ tag: Interfaces.OptionFlag<string[] | undefined, Interfaces.CustomOptions>;
26
+ branch: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
27
+ output: Interfaces.OptionFlag<string, Interfaces.CustomOptions>;
28
+ workspace: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
29
+ config: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
30
+ profile: Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
31
+ verbose: Interfaces.BooleanFlag<boolean>;
32
+ };
33
+ run(): Promise<void>;
34
+ /**
35
+ * `--all`: every finding that matches, read a page of 500 at a time in the platform's order, as
36
+ * one page. A run of many pages says on stderr what it is reading.
37
+ */
38
+ private everyFinding;
39
+ /** The retained runs, newest first; the flags that read one run's findings are refused. */
40
+ private listRuns;
41
+ /**
42
+ * `--release <name>`: the release's stored check (`GET release/{id}/policy_run`) and a page of its
43
+ * findings, as a run is read. `--recheck` checks the release again from its archive first. A
44
+ * release with no check says so; under `-o json` it is `{run: null}`.
45
+ */
46
+ private readRelease;
47
+ /** The release's ID from its name, reading the workspace's releases a page at a time until it is found. */
48
+ private releaseId;
49
+ /**
50
+ * One run's summary and a page of its findings (`path`), or under `-o json` the summary with
51
+ * `findings` and `findings_page`, plus what `extra` adds to it.
52
+ */
53
+ private reportRun;
54
+ }
@@ -0,0 +1,273 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { quoted } from '../../../utils/policy/gate.js';
4
+ import { releasePolicyRunLines, releaseRunLines } from '../../../utils/policy/release.js';
5
+ import { listItems } from '../../../utils/policy/request.js';
6
+ import { policyRunSummary, policyRunTable } from '../../../utils/policy/runs.js';
7
+ /** A branch retains this many runs, and the run list serves at most this many. */
8
+ const RUNS_RETAINED = 20;
9
+ /** How many findings a run read prints unless `--limit` says otherwise, and the most a page holds. */
10
+ const FINDINGS_LIMIT = 100;
11
+ const FINDINGS_MAX = 500;
12
+ /** The findings route's list filters, each a repeatable flag of the same name. */
13
+ const LIST_FILTERS = ['policy', 'rule', 'severity', 'kind', 'object', 'tag'];
14
+ /** The flags that page or filter one run's findings, so a run list refuses them. */
15
+ const FINDING_FLAGS = ['offset', 'all', 'advisory', 'blocking', ...LIST_FILTERS, 'search'];
16
+ /** How many releases a lookup by name reads a request, and the most requests it makes. */
17
+ const RELEASES_PAGE = 100;
18
+ const RELEASE_PAGES = 50;
19
+ export default class PolicyRuns extends PolicyCommand {
20
+ static args = {
21
+ run_id: Args.string({
22
+ description: 'Run ID to read; omit to list the retained runs, newest first',
23
+ required: false,
24
+ }),
25
+ };
26
+ static description = "List the stored policy check runs, or read one of them a page of findings at a time. --release reads a release's check instead: the run stored when the release was cut, evaluated on what it ships against the policies it ships";
27
+ static examples = [
28
+ '$ xano policy runs',
29
+ '$ xano policy runs --limit 5',
30
+ '$ xano policy runs 1674 --run-detail',
31
+ '$ xano policy runs 1674 --offset 100 --limit 100',
32
+ '$ xano policy runs 1674 --all -o json',
33
+ '$ xano policy runs 1674 --blocking --policy AUTH-001 --severity critical --severity high',
34
+ '$ xano policy runs 1674 -o json',
35
+ '$ xano policy runs --release v1.2',
36
+ '$ xano policy runs --release v1.2 --blocking --all -o json',
37
+ '$ xano policy runs --release v1.2 --recheck',
38
+ '$ xano policy runs --release-id 12',
39
+ ];
40
+ static flags = {
41
+ ...PolicyCommand.policyFlags,
42
+ advisory: Flags.boolean({
43
+ default: false,
44
+ description: 'With a run ID, only the advisory findings',
45
+ exclusive: ['blocking'],
46
+ }),
47
+ all: Flags.boolean({
48
+ default: false,
49
+ description: 'With a run ID, read every page and print every finding that matches, instead of one page',
50
+ exclusive: ['offset', 'limit'],
51
+ }),
52
+ blocking: Flags.boolean({
53
+ default: false,
54
+ description: "With a run ID, only the blocking findings (an active mandatory policy's)",
55
+ exclusive: ['advisory'],
56
+ }),
57
+ kind: Flags.string({
58
+ description: 'With a run ID, only findings on this object kind (query, function, table, ...); repeatable',
59
+ multiple: true,
60
+ }),
61
+ limit: Flags.integer({
62
+ description: `How many runs to list, newest first (1-${RUNS_RETAINED}, default ${RUNS_RETAINED}: a branch retains at most ${RUNS_RETAINED}); with a run ID, how many findings to print (1-${FINDINGS_MAX}, default ${FINDINGS_LIMIT})`,
63
+ min: 1,
64
+ }),
65
+ object: Flags.string({
66
+ description: 'With a run ID, only findings on this object, written type:id as a finding names it (query:18); repeatable',
67
+ multiple: true,
68
+ }),
69
+ offset: Flags.integer({
70
+ description: "With a run ID, how many findings to skip, in the platform's order (blocking first)",
71
+ min: 0,
72
+ }),
73
+ policy: Flags.string({
74
+ description: 'With a run ID, only findings of this policy key; repeatable',
75
+ multiple: true,
76
+ }),
77
+ recheck: Flags.boolean({
78
+ default: false,
79
+ description: "With --release, check the release again from its archive and store the new run, then read it (a read-only credential is refused)",
80
+ }),
81
+ release: Flags.string({
82
+ description: "Read this release's policy check (by release name) instead of a branch's run; the finding flags page and filter it as they do a run's",
83
+ exclusive: ['branch', 'release-id'],
84
+ }),
85
+ 'release-id': Flags.integer({
86
+ description: 'Read a release policy check by release ID, without looking up its name',
87
+ exclusive: ['branch', 'release'],
88
+ min: 1,
89
+ }),
90
+ rule: Flags.string({
91
+ description: 'With a run ID, only findings of this rule ID (AUTH-001.R1); repeatable',
92
+ multiple: true,
93
+ }),
94
+ 'run-detail': Flags.boolean({
95
+ default: false,
96
+ description: 'With a run ID, also print the policy descriptions and rule settings that run recorded',
97
+ }),
98
+ search: Flags.string({
99
+ description: 'With a run ID, only findings whose policy key or title, rule title, object name or message contains this text',
100
+ }),
101
+ severity: Flags.string({
102
+ description: 'With a run ID, only findings of this severity (critical, high, medium, low); repeatable',
103
+ multiple: true,
104
+ }),
105
+ tag: Flags.string({
106
+ description: 'With a run ID, only findings whose policy or object carries this tag; repeatable',
107
+ multiple: true,
108
+ }),
109
+ };
110
+ async run() {
111
+ const { args, flags } = await this.parse(PolicyRuns);
112
+ if (flags.release !== undefined || flags['release-id'] !== undefined)
113
+ return this.readRelease(flags, args.run_id);
114
+ if (flags.recheck)
115
+ this.error('--recheck checks a release again: `xano policy runs --release <name> --recheck`.');
116
+ const { request } = this.policyTarget(flags);
117
+ const wanted = args.run_id?.trim() ?? '';
118
+ if (wanted === '')
119
+ return this.listRuns(request, flags);
120
+ // Runs older than the one `policy status` reads are reachable here by id.
121
+ if (!/^\d+$/.test(wanted))
122
+ this.error(`"${wanted}" is not a run ID. Run \`xano policy runs\` for the retained runs.`);
123
+ const limit = findingsLimit(this, flags);
124
+ // The run's summary and its findings a page at a time: neither reads the whole run, which can hold tens of thousands.
125
+ const head = (await request(`/run/${wanted}/summary`));
126
+ await this.reportRun({ flags, head, limit, path: `/run/${wanted}/findings`, reading: `run ${wanted}`, request });
127
+ }
128
+ /**
129
+ * `--all`: every finding that matches, read a page of 500 at a time in the platform's order, as
130
+ * one page. A run of many pages says on stderr what it is reading.
131
+ */
132
+ async everyFinding(request, path, reading, query) {
133
+ const items = [];
134
+ let total = Number.POSITIVE_INFINITY;
135
+ while (items.length < total) {
136
+ // eslint-disable-next-line no-await-in-loop -- one page at a time, each starting where the last ended
137
+ const page = (await request(path, 'GET', undefined, { ...query, limit: String(FINDINGS_MAX), offset: String(items.length) }));
138
+ if (items.length === 0 && page.total > FINDINGS_MAX)
139
+ this.logToStderr(`Reading ${page.total} findings of ${reading}, ${FINDINGS_MAX} a request (${Math.ceil(page.total / FINDINGS_MAX)} requests)...`);
140
+ total = page.total ?? 0;
141
+ if ((page.items ?? []).length === 0)
142
+ break;
143
+ items.push(...page.items);
144
+ }
145
+ return { items, limit: items.length, offset: 0, total: Number.isFinite(total) ? total : items.length };
146
+ }
147
+ /** The retained runs, newest first; the flags that read one run's findings are refused. */
148
+ async listRuns(request, flags) {
149
+ if (flags['run-detail'])
150
+ this.error('--run-detail reads one run: `xano policy runs <id> --run-detail`.');
151
+ const paging = FINDING_FLAGS.find(name => flags[name] !== undefined && flags[name] !== false);
152
+ if (paging)
153
+ this.error(`--${paging} pages one run's findings: \`xano policy runs <id> --${paging} ...\`.`);
154
+ const limit = flags.limit ?? RUNS_RETAINED;
155
+ if (limit > RUNS_RETAINED)
156
+ this.error(`--limit lists at most ${RUNS_RETAINED} runs: a branch retains only its newest ${RUNS_RETAINED}.`);
157
+ const result = await request('/run', 'GET', undefined, { limit: String(limit) });
158
+ if (flags.output === 'json') {
159
+ this.log(JSON.stringify(result, null, 2));
160
+ return;
161
+ }
162
+ const runs = listItems(result);
163
+ if (runs.length === 0) {
164
+ this.log('No policy runs retained on this branch.');
165
+ return;
166
+ }
167
+ for (const line of policyRunTable(runs))
168
+ this.log(line);
169
+ this.log('Only the most recent runs are retained per branch. Read one with `xano policy runs <id> --run-detail`.');
170
+ }
171
+ /**
172
+ * `--release <name>`: the release's stored check (`GET release/{id}/policy_run`) and a page of its
173
+ * findings, as a run is read. `--recheck` checks the release again from its archive first. A
174
+ * release with no check says so; under `-o json` it is `{run: null}`.
175
+ */
176
+ async readRelease(flags, runId) {
177
+ if (runId?.trim())
178
+ this.error('Read one run by its ID, or a release\'s check with --release, not both.');
179
+ const name = flags.release?.trim() ?? String(flags['release-id'] ?? '');
180
+ if (!name)
181
+ this.error('--release needs a release name.');
182
+ const limit = findingsLimit(this, flags);
183
+ const { request } = this.policyTarget(flags, { label: 'Release policy check', path: '' });
184
+ const releaseId = flags['release-id'] ?? await this.releaseId(request, name);
185
+ const route = `/release/${releaseId}/policy_run`;
186
+ const answer = (flags.recheck ? await request(route, 'POST', {}) : await request(route));
187
+ const check = flags.recheck ? { check: answer.check ?? null } : {};
188
+ // The recheck's outcome in a line: recorded with its verdict, or why nothing was recorded.
189
+ const rechecked = flags.recheck ? releasePolicyRunLines(answer.check, name).slice(0, 1).map(line => `Rechecked. ${line.trim()}`) : [];
190
+ const head = answer.run ?? null;
191
+ if (!head) {
192
+ if (flags.output === 'json') {
193
+ this.log(JSON.stringify({ ...check, run: null }, null, 2));
194
+ return;
195
+ }
196
+ for (const line of rechecked)
197
+ this.log(line);
198
+ this.log(`Release ${name} has no policy check.${flags.recheck ? '' : ' `--recheck` checks it now from its archive.'}`);
199
+ return;
200
+ }
201
+ await this.reportRun({
202
+ check,
203
+ flags,
204
+ head,
205
+ limit,
206
+ next: flags['release-id'] ? `xano policy runs --release-id ${releaseId}` : `xano policy runs --release ${quoted(name)}`,
207
+ path: `${route}/findings`,
208
+ preface: [...rechecked, ...releaseRunLines(name, releaseId, head)],
209
+ reading: `release ${name}`,
210
+ request,
211
+ });
212
+ }
213
+ /** The release's ID from its name, reading the workspace's releases a page at a time until it is found. */
214
+ async releaseId(request, name) {
215
+ for (let page = 1; page <= RELEASE_PAGES; page++) {
216
+ // eslint-disable-next-line no-await-in-loop -- pages in order, stopping at the release
217
+ const data = await request('/release', 'GET', undefined, { page: String(page), per_page: String(RELEASES_PAGE) });
218
+ const releases = Array.isArray(data) ? data : listItems(data);
219
+ const match = releases.find(release => release.name === name);
220
+ if (match)
221
+ return match.id;
222
+ if (releases.length < RELEASES_PAGE)
223
+ break;
224
+ }
225
+ return this.error(`Release '${name}' not found. \`xano release list\` lists the workspace's releases.`);
226
+ }
227
+ /**
228
+ * One run's summary and a page of its findings (`path`), or under `-o json` the summary with
229
+ * `findings` and `findings_page`, plus what `extra` adds to it.
230
+ */
231
+ async reportRun(opts) {
232
+ const { flags, head, limit, path, request } = opts;
233
+ const query = findingsQuery(flags, limit);
234
+ const page = flags.all
235
+ ? await this.everyFinding(request, path, opts.reading, query)
236
+ : (await request(path, 'GET', undefined, query));
237
+ const items = page.items ?? [];
238
+ const offset = page.offset ?? 0;
239
+ const total = page.total ?? items.length;
240
+ if (flags.output === 'json') {
241
+ const next = offset + items.length < total && items.length > 0 ? offset + items.length : null;
242
+ const findingsPage = { limit: page.limit ?? limit, next_offset: next, offset, total };
243
+ this.log(JSON.stringify({ ...head, ...opts.check, findings: items, findings_page: findingsPage }, null, 2));
244
+ return;
245
+ }
246
+ for (const line of opts.preface ?? [])
247
+ this.log(line);
248
+ const filtered = Object.keys(query).some(name => !['limit', 'offset'].includes(name));
249
+ for (const line of policyRunSummary({ ...head, findings: items }, { filtered, next: opts.next, offset, total }))
250
+ this.log(line);
251
+ if (flags['run-detail'])
252
+ this.logRunDetail(head);
253
+ }
254
+ }
255
+ /** How many findings a page prints: `--limit`, at most 500. */
256
+ function findingsLimit(command, flags) {
257
+ const limit = flags.limit ?? FINDINGS_LIMIT;
258
+ if (limit > FINDINGS_MAX)
259
+ command.error(`--limit prints at most ${FINDINGS_MAX} findings a page; page on with --offset.`);
260
+ return limit;
261
+ }
262
+ /** The findings route's query for one page: `blocking` true or false, and each list filter given. */
263
+ function findingsQuery(flags, limit) {
264
+ const query = { limit: String(limit), offset: String(flags.offset ?? 0) };
265
+ if (flags.blocking || flags.advisory)
266
+ query.blocking = String(flags.blocking);
267
+ for (const name of LIST_FILTERS)
268
+ if (flags[name]?.length)
269
+ query[name] = flags[name];
270
+ if (flags.search?.trim())
271
+ query.q = flags.search;
272
+ return query;
273
+ }
@@ -0,0 +1,21 @@
1
+ import PolicyCommand from '../../../policy-command.js';
2
+ export default class PolicyStatus extends PolicyCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'fail-on-findings': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ 'run-detail': import("@oclif/core/interfaces").BooleanFlag<boolean>;
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
+ /**
17
+ * The run every policy's `latest_run` answer was decided against, or none when the branch has no
18
+ * run: its summary, which counts each policy's findings without listing them.
19
+ */
20
+ private latestRun;
21
+ }
@@ -0,0 +1,54 @@
1
+ import { Flags } from '@oclif/core';
2
+ import PolicyCommand from '../../../policy-command.js';
3
+ import { policyResultSummary } from '../../../utils/policy/findings.js';
4
+ import { listAllPolicies } from '../../../utils/policy/request.js';
5
+ import { computeStatusRows, enforcementLabel, findingsLabel, statusExitCode, statusExitReason, statusLabel, } from '../../../utils/policy/status.js';
6
+ export default class PolicyStatus extends PolicyCommand {
7
+ static description = 'Show the latest policy check results without evaluating';
8
+ static examples = ['$ xano policy status -o json', '$ xano policy status --fail-on-findings -o json', '$ xano policy status --run-detail'];
9
+ static flags = {
10
+ ...PolicyCommand.policyFlags,
11
+ 'fail-on-findings': Flags.boolean({
12
+ default: false,
13
+ description: 'Exit 2 for current blocking findings, whatever else is true; otherwise exit 1 for stale, missing or errored evaluation evidence',
14
+ }),
15
+ 'run-detail': Flags.boolean({
16
+ default: false,
17
+ description: 'Also print what the latest run recorded: each policy description and the settings each rule ran with',
18
+ }),
19
+ };
20
+ async run() {
21
+ const { flags } = await this.parse(PolicyStatus);
22
+ const { request } = this.policyTarget(flags);
23
+ const policies = await listAllPolicies(request);
24
+ const run = await this.latestRun(request, policies);
25
+ const rows = computeStatusRows(policies, run);
26
+ const failOnFindings = flags['fail-on-findings'] ? { exit: statusExitCode(rows), reason: statusExitReason(rows) } : undefined;
27
+ if (flags.output === 'json') {
28
+ this.log(JSON.stringify({ policies, run: run ?? null, status: rows, ...(failOnFindings ? { fail_on_findings: failOnFindings } : {}) }, null, 2));
29
+ }
30
+ else if (rows.length === 0)
31
+ this.log('No policies found.');
32
+ else {
33
+ for (const row of rows)
34
+ this.log(`${row.key} ${statusLabel(row)} ${enforcementLabel(row.enforcement)} ${findingsLabel(row)} ${row.title ?? ''}`);
35
+ const counted = new Set(rows.filter((row) => row.counted).map((row) => row.key));
36
+ for (const line of policyResultSummary(run?.results?.filter((result) => counted.has(result.policy_key ?? ''))))
37
+ this.log(line);
38
+ if (flags['run-detail'] && run)
39
+ this.logRunDetail(run);
40
+ if (failOnFindings?.reason)
41
+ this.log(failOnFindings.reason);
42
+ }
43
+ if (failOnFindings?.exit)
44
+ process.exitCode = failOnFindings.exit;
45
+ }
46
+ /**
47
+ * The run every policy's `latest_run` answer was decided against, or none when the branch has no
48
+ * run: its summary, which counts each policy's findings without listing them.
49
+ */
50
+ async latestRun(request, policies) {
51
+ const runId = policies.map((policy) => policy.latest_run?.run_id).find(Boolean);
52
+ return runId ? (await request(`/run/${runId}/summary`)) : undefined;
53
+ }
54
+ }
@@ -6,10 +6,10 @@ export default class ProfileCreate extends Command {
6
6
  static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
- config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
9
  access_token: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
10
  account_origin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
11
  branch: 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
13
  default: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
14
  insecure: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
15
  instance_origin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import * as yaml from 'js-yaml';
3
3
  import * as fs from 'node:fs';
4
- import * as path from 'node:path';
4
+ import path from 'node:path';
5
5
  import { resolveCredentialsPath } from '../../../base-command.js';
6
6
  export default class ProfileCreate extends Command {
7
7
  static args = {
@@ -30,12 +30,6 @@ Profile 'selfhosted' created successfully at ~/.xano/credentials.yaml
30
30
  `,
31
31
  ];
32
32
  static flags = {
33
- config: Flags.string({
34
- char: 'c',
35
- description: 'Path to credentials file (default: ~/.xano/credentials.yaml)',
36
- env: 'XANO_CONFIG',
37
- required: false,
38
- }),
39
33
  access_token: Flags.string({
40
34
  char: 't',
41
35
  description: 'Access token for the Xano Metadata API',
@@ -51,6 +45,12 @@ Profile 'selfhosted' created successfully at ~/.xano/credentials.yaml
51
45
  description: 'Branch name',
52
46
  required: false,
53
47
  }),
48
+ config: Flags.string({
49
+ char: 'c',
50
+ description: 'Path to credentials file (default: ~/.xano/credentials.yaml)',
51
+ env: 'XANO_CONFIG',
52
+ required: false,
53
+ }),
54
54
  default: Flags.boolean({
55
55
  default: false,
56
56
  description: 'Set this profile as the default',
@@ -171,7 +171,7 @@ User Information:
171
171
  const credentialsPath = this.getCredentialsPath();
172
172
  // Check if credentials file exists
173
173
  if (!fs.existsSync(credentialsPath)) {
174
- this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
174
+ this.error(`Credentials file not found at ${credentialsPath}\nCreate a profile using 'xano profile:create'`);
175
175
  }
176
176
  // Read credentials file
177
177
  try {
@@ -68,7 +68,6 @@ Added profile.yaml to .gitignore
68
68
  `,
69
69
  ];
70
70
  static flags = {
71
- // eslint-disable-next-line camelcase
72
71
  account_origin: Flags.string({
73
72
  char: 'a',
74
73
  description: 'Override account origin for this project',
@@ -90,7 +89,6 @@ Added profile.yaml to .gitignore
90
89
  description: 'Add (or skip adding) profile.yaml to .gitignore without prompting',
91
90
  required: false,
92
91
  }),
93
- // eslint-disable-next-line camelcase
94
92
  instance_origin: Flags.string({
95
93
  char: 'i',
96
94
  description: 'Override instance origin for this project',
@@ -108,10 +106,8 @@ Added profile.yaml to .gitignore
108
106
  const config = { profile: args.name };
109
107
  if (flags.workspace)
110
108
  config.workspace = flags.workspace;
111
- // eslint-disable-next-line camelcase
112
109
  if (flags.instance_origin)
113
110
  config.instance_origin = flags.instance_origin;
114
- // eslint-disable-next-line camelcase
115
111
  if (flags.account_origin)
116
112
  config.account_origin = flags.account_origin;
117
113
  if (flags.branch)
@@ -9,10 +9,10 @@ export default class ProfileWizard extends Command {
9
9
  origin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  };
11
11
  run(): Promise<void>;
12
- private getHeaders;
13
12
  private fetchBranches;
14
13
  private fetchInstances;
15
14
  private fetchWorkspaces;
16
15
  private getDefaultProfileName;
16
+ private getHeaders;
17
17
  private saveProfile;
18
18
  }
@@ -1,9 +1,8 @@
1
- import { ExitPromptError } from '@inquirer/core';
2
1
  import { Command, Flags } from '@oclif/core';
3
2
  import inquirer from 'inquirer';
4
3
  import * as yaml from 'js-yaml';
5
4
  import * as fs from 'node:fs';
6
- import * as path from 'node:path';
5
+ import path from 'node:path';
7
6
  import { buildUserAgent, resolveCredentialsPath } from '../../../base-command.js';
8
7
  export default class ProfileWizard extends Command {
9
8
  static description = 'Create a new profile configuration using an interactive wizard';
@@ -188,20 +187,16 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
188
187
  this.log(`✓ Profile '${profileName}' created successfully!`);
189
188
  }
190
189
  catch (error) {
191
- if (error instanceof ExitPromptError) {
190
+ // Ctrl+C at an inquirer prompt throws ExitPromptError. Match on the name
191
+ // rather than `instanceof`: inquirer bundles its own copy of
192
+ // @inquirer/core, so the thrown class won't match an imported one.
193
+ if (error?.name === 'ExitPromptError') {
192
194
  this.log('Wizard cancelled.');
193
195
  process.exit(0);
194
196
  }
195
197
  throw error;
196
198
  }
197
199
  }
198
- getHeaders(accessToken) {
199
- return {
200
- 'User-Agent': buildUserAgent(this.config.version),
201
- accept: 'application/json',
202
- ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
203
- };
204
- }
205
200
  async fetchBranches(accessToken, origin, workspaceId) {
206
201
  const response = await fetch(`${origin}/api:meta/workspace/${workspaceId}/branch`, {
207
202
  headers: this.getHeaders(accessToken),
@@ -325,6 +320,13 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
325
320
  return 'default';
326
321
  }
327
322
  }
323
+ getHeaders(accessToken) {
324
+ return {
325
+ accept: 'application/json',
326
+ 'User-Agent': buildUserAgent(this.config.version),
327
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
328
+ };
329
+ }
328
330
  async saveProfile(profile, setAsDefault = false, configPath) {
329
331
  const credentialsPath = resolveCredentialsPath(configPath);
330
332
  const configDir = path.dirname(credentialsPath);
@@ -61,9 +61,9 @@ Workspace updated to 'Production API' (xyz789) on profile 'production'
61
61
  async fetchWorkspaces(accessToken, origin) {
62
62
  const response = await fetch(`${origin}/api:meta/workspace`, {
63
63
  headers: {
64
- 'User-Agent': buildUserAgent(this.config.version),
65
64
  accept: 'application/json',
66
65
  Authorization: `Bearer ${accessToken}`,
66
+ 'User-Agent': buildUserAgent(this.config.version),
67
67
  },
68
68
  method: 'GET',
69
69
  });