@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
@@ -16,7 +16,8 @@
16
16
  * so the item count IS the total.
17
17
  *
18
18
  * There is deliberately no tier for endpoints that page but return a bare array
19
- * with no metadata (tenants, releases, platforms, clusters, tenant backups). Those commands are left alone: the only has-more signal available
19
+ * with no metadata (tenants, releases, platforms, clusters, ephemerals, tenant
20
+ * backups). Those commands are left alone: the only has-more signal available
20
21
  * would be "the page came back full", which is a guaranteed false positive when
21
22
  * the result set is an exact multiple of the page size — sending the user to an
22
23
  * empty page. Better to offer nothing than to offer a lie.
@@ -1,4 +1,3 @@
1
- /* eslint-disable camelcase */
2
1
  import { Flags } from '@oclif/core';
3
2
  function makePageFlag(description) {
4
3
  return Flags.integer({ default: 1, description, required: false });
@@ -0,0 +1,7 @@
1
+ import type { PolicyCatalogueEntry } from './types.js';
2
+ /**
3
+ * `--check` narrows the catalogue to one check. An id that is not in it names the closest matches
4
+ * instead of reprinting the whole list: the usual mistake is a typo one character from a real id.
5
+ */
6
+ export declare function selectCatalogueCheck(checks: PolicyCatalogueEntry[], id: string): PolicyCatalogueEntry[];
7
+ export declare function policyCatalogueSummary(checks: PolicyCatalogueEntry[]): string[];
@@ -0,0 +1,72 @@
1
+ /**
2
+ * `--check` narrows the catalogue to one check. An id that is not in it names the closest matches
3
+ * instead of reprinting the whole list: the usual mistake is a typo one character from a real id.
4
+ */
5
+ export function selectCatalogueCheck(checks, id) {
6
+ const wanted = id.trim().toLowerCase();
7
+ const exact = checks.filter(check => check.id.toLowerCase() === wanted);
8
+ if (exact.length > 0)
9
+ return exact;
10
+ const near = checks.map(check => check.id).filter(known => {
11
+ const lower = known.toLowerCase();
12
+ return lower.includes(wanted) || wanted.includes(lower) || editDistanceWithin(lower, wanted, 2);
13
+ }).sort();
14
+ throw new Error(`"${id}" is not a policy check.${near.length > 0
15
+ ? ` Did you mean ${near.join(', ')}?`
16
+ : ' Run `xano policy catalogue` for the full list.'}`);
17
+ }
18
+ /** Cheap bounded Levenshtein: true when `a` and `b` are at most `max` edits apart. */
19
+ function editDistanceWithin(a, b, max) {
20
+ if (Math.abs(a.length - b.length) > max)
21
+ return false;
22
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
23
+ for (let i = 1; i <= a.length; i++) {
24
+ const current = [i];
25
+ for (let j = 1; j <= b.length; j++) {
26
+ current[j] = a[i - 1] === b[j - 1]
27
+ ? previous[j - 1]
28
+ : 1 + Math.min(previous[j - 1], previous[j], current[j - 1]);
29
+ }
30
+ if (Math.min(...current) > max)
31
+ return false;
32
+ previous = current;
33
+ }
34
+ return previous[b.length] <= max;
35
+ }
36
+ export function policyCatalogueSummary(checks) {
37
+ if (checks.length === 0)
38
+ return ['No policy checks found.'];
39
+ const rows = [
40
+ ['Check ID', 'Label / Description', 'Object kinds', 'Required params'],
41
+ ...checks.map(check => [
42
+ check.id,
43
+ [check.label.trim(), check.description, check.fix_hint && `Fix hint: ${check.fix_hint}`].filter(Boolean).join('\n'),
44
+ (check.object_kinds ?? []).join(', ') || '—',
45
+ requiredParams(check),
46
+ ]),
47
+ ];
48
+ const widths = rows[0].map((_, column) => Math.min([36, 46, 24, 32][column], Math.max(...rows.map(row => row[column].length))));
49
+ return rows.flatMap(row => {
50
+ const cells = row.map((cell, column) => cell.split('\n').flatMap(paragraph => {
51
+ const lines = [''];
52
+ for (const word of paragraph.split(/\s+/)) {
53
+ const last = lines.length - 1;
54
+ if (lines[last] && lines[last].length + word.length + 1 > widths[column])
55
+ lines.push(word);
56
+ else
57
+ lines[last] += `${lines[last] ? ' ' : ''}${word}`;
58
+ }
59
+ return lines;
60
+ }));
61
+ return Array.from({ length: Math.max(...cells.map(cell => cell.length)) }, (_, line) => cells.map((cell, column) => (cell[line] ?? '').padEnd(widths[column])).join(' ').trimEnd());
62
+ });
63
+ }
64
+ /** The params a rule must set: each required one, and any "one of" group. */
65
+ function requiredParams(check) {
66
+ const required = Object.entries(check.params ?? {}).filter(([, schema]) => schema.required)
67
+ .map(([name, schema]) => `${name}: ${schema.type ?? 'any'}`);
68
+ const oneOf = check.requires_one_of ?? [];
69
+ if (oneOf.length > 0)
70
+ required.push(`one of: ${oneOf.join(' | ')}`);
71
+ return required.join(', ') || 'none';
72
+ }
@@ -0,0 +1,12 @@
1
+ /** Guidance appended to a refusal whose `payload.code` the CLI has advice for, or `''`. */
2
+ export declare function policyCodeGuidance(payload: unknown): string;
3
+ /** A failed policy-route response: one message to print, and the refusal's payload. */
4
+ export interface PolicyError {
5
+ message: string;
6
+ payload?: unknown;
7
+ }
8
+ /**
9
+ * A failed policy-route response as one message: the platform's code and message, and where a parse
10
+ * error is. Traces are dropped, and an empty 404 names the branch the request selected.
11
+ */
12
+ export declare function describePolicyError(text: string, status: number, requestUrl: string): PolicyError;
@@ -0,0 +1,61 @@
1
+ /** Keep backend implementation details out of ordinary command errors. */
2
+ function withoutStack(text) {
3
+ return text.split(/\n\s*(?:Stack trace:|#\d+\s|at\s|file:\s*\/)/i, 1)[0];
4
+ }
5
+ /** Where a parse error is: the payload's 1-based `line` and `col`, printed as served, and the offending text. */
6
+ function positionLine(payload) {
7
+ const text = [payload.error_line, payload.error_snippet, payload.snippet]
8
+ .find(value => typeof value === 'string' && value.trim() !== '');
9
+ const where = typeof payload.line === 'number' && Number.isFinite(payload.line)
10
+ ? `line ${payload.line}${typeof payload.col === 'number' && Number.isFinite(payload.col) ? `, col ${payload.col}` : ''}`
11
+ : '';
12
+ if (!where && text === undefined)
13
+ return '';
14
+ return `\n ${where ? `at ${where}` : 'at'}${text === undefined ? '' : `: ${text}`}`;
15
+ }
16
+ /** An empty 404 from a branch-scoped request means the branch it named does not exist. */
17
+ function missingBranch(requestUrl) {
18
+ const url = new URL(requestUrl);
19
+ const workspace = url.pathname.match(/\/workspace\/([^/]+)/)?.[1];
20
+ const branch = url.searchParams.get('branch');
21
+ return workspace && branch ? `Branch "${branch}" was not found in workspace ${decodeURIComponent(workspace)}.` : '';
22
+ }
23
+ /**
24
+ * What to do about a refusal of the request itself, keyed on the platform's `payload.code`:
25
+ *
26
+ * policy_unknown_check a rule names a check id this instance does not have
27
+ * policy_stale the policy changed after the command read it
28
+ *
29
+ * Permission refusals are explained by `policyPermissionGuidance`.
30
+ */
31
+ const CODE_GUIDANCE = {
32
+ policy_stale: '\nThe policy changed after this command read it, so nothing was changed. Run the command again to act on its current version.',
33
+ policy_unknown_check: '\nRun `xano policy catalogue` to list every check id this instance has.',
34
+ };
35
+ /** Guidance appended to a refusal whose `payload.code` the CLI has advice for, or `''`. */
36
+ export function policyCodeGuidance(payload) {
37
+ const code = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.code : undefined;
38
+ return typeof code === 'string' && Object.hasOwn(CODE_GUIDANCE, code) ? CODE_GUIDANCE[code] : '';
39
+ }
40
+ /**
41
+ * A failed policy-route response as one message: the platform's code and message, and where a parse
42
+ * error is. Traces are dropped, and an empty 404 names the branch the request selected.
43
+ */
44
+ export function describePolicyError(text, status, requestUrl) {
45
+ let data;
46
+ try {
47
+ data = JSON.parse(text.trim() || '{}');
48
+ }
49
+ catch {
50
+ return { message: withoutStack(text) };
51
+ }
52
+ if (!data || typeof data !== 'object' || Array.isArray(data))
53
+ return { message: withoutStack(text) };
54
+ const { code, message: raw, payload } = data;
55
+ let message = typeof raw === 'string' ? withoutStack(raw) : '';
56
+ if (status === 404 && !message.trim())
57
+ message = missingBranch(requestUrl);
58
+ const headline = [typeof code === 'string' ? code : '', message].filter(Boolean).join(': ');
59
+ const position = payload && typeof payload === 'object' ? positionLine(payload) : '';
60
+ return { message: `${headline}${position}` || 'The server returned no message.', payload };
61
+ }
@@ -0,0 +1,68 @@
1
+ import type { PolicyEvaluation, PolicyFinding, PolicyRuleResult, PolicySnapshotPolicy, PolicyVerdict, PushPolicyCheck } from './types.js';
2
+ /** 2 whenever the platform says a finding blocks, whatever the status; every other outcome is 0. */
3
+ export declare function policyExitCode(check?: PolicyVerdict): number;
4
+ /**
5
+ * The one warning line for feedback without a headline (`disabled`, `forbidden`, `unavailable`,
6
+ * `error`, an unknown status, or a pass or fail that does not say whether it blocks): the server's
7
+ * own status and message, or that none came back. `null` for headlined feedback.
8
+ */
9
+ export declare function policyCheckWarning(check?: PolicyVerdict): null | string;
10
+ /**
11
+ * What happened to the policy documents a push carried. The preview says, per policy, whether it was
12
+ * created, updated or left unchanged. Without a preview only the number sent is known: the import
13
+ * response reports an unchanged policy exactly as it reports a saved one.
14
+ */
15
+ export declare function policyDocumentSummary(preview: null | {
16
+ operations: Array<{
17
+ action: string;
18
+ name: string;
19
+ type: string;
20
+ }>;
21
+ }, sentPolicies: number): string[];
22
+ /** What a verdict is about: its findings, which of them block, and the rule results. */
23
+ export interface PolicyEvidence {
24
+ /** The ids of the findings that block. */
25
+ blocking: Set<string>;
26
+ /**
27
+ * When the answer lists only the first findings: how many there are in all, and how many block.
28
+ * The stored run (`runId`, 0 when nothing was stored) has them all.
29
+ */
30
+ cut?: {
31
+ blocking: number;
32
+ findings: number;
33
+ runId: number;
34
+ };
35
+ /** In the platform's order: blocking first, then by severity. */
36
+ findings: PolicyFinding[];
37
+ results: PolicyRuleResult[];
38
+ /** The run's own `policies[]`, which names unnamed rules; a push answers none. */
39
+ snapshot: PolicySnapshotPolicy[];
40
+ }
41
+ /**
42
+ * A push answers no run, so its `policy_check` carries the findings and results itself: the first
43
+ * 100 of each list, with `counts` and `total` saying what they are out of.
44
+ */
45
+ export declare function pushEvidence(check?: PushPolicyCheck): PolicyEvidence;
46
+ /**
47
+ * An evaluation answers the run, and its verdict names the blocking findings by id. Its summary
48
+ * answer (`--summary`) lists the first findings, and its counts say what they are out of.
49
+ */
50
+ export declare function evaluationEvidence(evaluation: PolicyEvaluation): PolicyEvidence;
51
+ /**
52
+ * The feedback on stdout: a headline with the server's message, then any findings, errors and
53
+ * warnings. Feedback without a headline is reported by `policyCheckWarning` instead.
54
+ */
55
+ export declare function policySummary(check: PolicyVerdict | undefined, evidence: PolicyEvidence): string[];
56
+ /**
57
+ * The platform's one notice that a push or its dry run left policy files out
58
+ * (`policies_skipped.message`): a push to a remote tenant or to a tenant without a policy table, a
59
+ * `release push` by a caller without `workspace:policy` create and update (its release carries the
60
+ * live branch's policies instead), and any push while the Policies feature is off. `null` when it
61
+ * left none out.
62
+ */
63
+ export declare function policiesSkippedNotice(answer: unknown): null | string;
64
+ /**
65
+ * The line for an evaluation that ran but was not recorded (the credential may read, not write), or
66
+ * `null`. A branch with nothing to evaluate stores nothing either, and its headline says so.
67
+ */
68
+ export declare function notStoredLine(evaluation: PolicyEvaluation): null | string;
@@ -0,0 +1,165 @@
1
+ import { findingLine, hasNoObjects, policyResultSummary, snapshotRules } from './findings.js';
2
+ /**
3
+ * Feedback printed as a headline: a pass or fail that says whether its findings block, or a branch
4
+ * with no active policy to evaluate. Everything else is reported by `policyCheckWarning`.
5
+ */
6
+ function isHeadlined(check) {
7
+ if (check.status === 'not_applicable')
8
+ return true;
9
+ return ['fail', 'pass'].includes(check.status ?? '') && typeof check.blocking === 'boolean';
10
+ }
11
+ /** 2 whenever the platform says a finding blocks, whatever the status; every other outcome is 0. */
12
+ export function policyExitCode(check) {
13
+ return check?.blocking === true ? 2 : 0;
14
+ }
15
+ /**
16
+ * The one warning line for feedback without a headline (`disabled`, `forbidden`, `unavailable`,
17
+ * `error`, an unknown status, or a pass or fail that does not say whether it blocks): the server's
18
+ * own status and message, or that none came back. `null` for headlined feedback.
19
+ */
20
+ export function policyCheckWarning(check) {
21
+ if (!check)
22
+ return 'Policy check: no policy feedback returned.';
23
+ if (isHeadlined(check))
24
+ return null;
25
+ const status = typeof check.status === 'string' && check.status.trim() !== '' ? check.status.trim() : 'unknown';
26
+ const message = typeof check.message === 'string' && check.message.trim() !== ''
27
+ ? check.message.trim()
28
+ : (['fail', 'pass'].includes(status) ? 'the server did not say whether its findings block.' : 'no message returned.');
29
+ return `Policy check ${status}: ${message}`;
30
+ }
31
+ /**
32
+ * What happened to the policy documents a push carried. The preview says, per policy, whether it was
33
+ * created, updated or left unchanged. Without a preview only the number sent is known: the import
34
+ * response reports an unchanged policy exactly as it reports a saved one.
35
+ */
36
+ export function policyDocumentSummary(preview, sentPolicies) {
37
+ const operations = (preview?.operations ?? []).filter((op) => op.type === 'policy');
38
+ if (operations.length === 0) {
39
+ return sentPolicies > 0
40
+ ? [`Policy documents: ${sentPolicies} sent without a preview, so which of them changed is not known`]
41
+ : [];
42
+ }
43
+ const named = (action) => operations.filter((op) => op.action === action).map((op) => op.name).sort();
44
+ const created = named('create');
45
+ const updated = named('update');
46
+ const unchanged = named('unchanged');
47
+ const parts = [
48
+ created.length > 0 && `${created.length} created (${created.join(', ')})`,
49
+ updated.length > 0 && `${updated.length} updated (${updated.join(', ')})`,
50
+ unchanged.length > 0 && `${unchanged.length} unchanged`,
51
+ ].filter(Boolean);
52
+ return parts.length > 0 ? [`Policy documents: ${parts.join(', ')}`] : [];
53
+ }
54
+ /**
55
+ * A push answers no run, so its `policy_check` carries the findings and results itself: the first
56
+ * 100 of each list, with `counts` and `total` saying what they are out of.
57
+ */
58
+ export function pushEvidence(check) {
59
+ return {
60
+ blocking: new Set((check?.blocking_findings ?? []).map(finding => finding.id ?? '').filter(Boolean)),
61
+ ...(check?.truncated && check.counts
62
+ ? { cut: { blocking: check.counts.blocking, findings: check.total ?? check.counts.findings, runId: check.run_id ?? 0 } }
63
+ : {}),
64
+ findings: check?.findings ?? [],
65
+ results: check?.results ?? [],
66
+ snapshot: [],
67
+ };
68
+ }
69
+ /**
70
+ * An evaluation answers the run, and its verdict names the blocking findings by id. Its summary
71
+ * answer (`--summary`) lists the first findings, and its counts say what they are out of.
72
+ */
73
+ export function evaluationEvidence(evaluation) {
74
+ const { counts } = evaluation;
75
+ return {
76
+ blocking: new Set(evaluation.policy_check?.blocking_finding_ids ?? []),
77
+ ...(evaluation.truncated && counts
78
+ ? { cut: { blocking: counts.blocking, findings: evaluation.total ?? counts.findings, runId: evaluation.stored === false ? 0 : (evaluation.id ?? 0) } }
79
+ : {}),
80
+ findings: evaluation.findings ?? [],
81
+ results: evaluation.results ?? [],
82
+ snapshot: evaluation.policies ?? [],
83
+ };
84
+ }
85
+ /** A list's count, or `first N of M` when the answer lists only the first of them. */
86
+ function listed(count, all) {
87
+ return all !== undefined && all > count ? `first ${count} of ${all}` : String(count);
88
+ }
89
+ /** The headline's outcome. A pass whose rules inspected no object is never headlined as a plain pass. */
90
+ function outcome(check, results) {
91
+ if (check.status === 'fail')
92
+ return check.blocking ? 'fail (blocking findings)' : 'advisory findings (not blocking)';
93
+ if (check.status !== 'pass')
94
+ return check.status ?? '';
95
+ const unchecked = results.filter((result) => hasNoObjects(result)).length;
96
+ if (unchecked === 0)
97
+ return 'pass';
98
+ return unchecked === results.length
99
+ ? 'pass, but no objects were checked'
100
+ : `pass, but ${unchecked} rule${unchecked === 1 ? '' : 's'} checked no objects`;
101
+ }
102
+ /**
103
+ * The feedback on stdout: a headline with the server's message, then any findings, errors and
104
+ * warnings. Feedback without a headline is reported by `policyCheckWarning` instead.
105
+ */
106
+ export function policySummary(check, evidence) {
107
+ if (!check)
108
+ return [];
109
+ const lines = [];
110
+ if (isHeadlined(check)) {
111
+ lines.push(`Policy check: ${outcome(check, evidence.results)}`);
112
+ if (check.message)
113
+ lines.push(check.message);
114
+ }
115
+ const rules = snapshotRules(evidence.snapshot);
116
+ const { blocking, cut, findings } = evidence;
117
+ // A cut list is out of the counts; the ones the answer lists come first, blocking first.
118
+ const allBlocking = cut?.blocking;
119
+ const allAdvisory = cut ? cut.findings - cut.blocking : undefined;
120
+ if (cut)
121
+ lines.push(`Findings: ${cut.findings} (${cut.blocking} blocking, ${allAdvisory} advisory)`);
122
+ // The list splits only when some findings block and others do not.
123
+ const separated = blocking.size > 0 && findings.some(finding => !blocking.has(finding.id ?? ''));
124
+ if (separated) {
125
+ const blocked = findings.filter(finding => blocking.has(finding.id ?? ''));
126
+ const advisory = findings.filter(finding => !blocking.has(finding.id ?? ''));
127
+ lines.push(`Blocking findings (${listed(blocked.length, allBlocking)}) — active mandatory policies; a gate refuses a change that introduces one or changes its object:`, ...blocked.map(finding => findingLine(finding, rules)), `Advisory findings (${listed(advisory.length, allAdvisory)}) — reported, not blocking:`, ...advisory.map(finding => findingLine(finding, rules)));
128
+ }
129
+ else {
130
+ if (blocking.size > 0 && findings.length > 0)
131
+ lines.push(`Blocking findings (${listed(findings.length, allBlocking)}) — active mandatory policies; a gate refuses a change that introduces one or changes its object:`);
132
+ // Without a headline (an `error` status, say) nothing else says what these findings are.
133
+ else if (findings.length > 0 && !isHeadlined(check))
134
+ lines.push(`Advisory findings (${listed(findings.length, allAdvisory)}) — reported, not blocking:`);
135
+ lines.push(...findings.map(finding => findingLine(finding, rules)));
136
+ }
137
+ if (cut) {
138
+ lines.push(`Listed: the first ${findings.length} of ${cut.findings}; ${cut.runId > 0
139
+ ? `\`xano policy runs ${cut.runId}\` has them all.`
140
+ : 'this evaluation was not stored, so the rest cannot be listed.'}`);
141
+ }
142
+ lines.push(...policyResultSummary(evidence.results));
143
+ return lines;
144
+ }
145
+ /**
146
+ * The platform's one notice that a push or its dry run left policy files out
147
+ * (`policies_skipped.message`): a push to a remote tenant or to a tenant without a policy table, a
148
+ * `release push` by a caller without `workspace:policy` create and update (its release carries the
149
+ * live branch's policies instead), and any push while the Policies feature is off. `null` when it
150
+ * left none out.
151
+ */
152
+ export function policiesSkippedNotice(answer) {
153
+ const skipped = answer && typeof answer === 'object' ? answer.policies_skipped : undefined;
154
+ const message = skipped && typeof skipped === 'object' ? skipped.message : undefined;
155
+ return typeof message === 'string' && message.trim() !== '' ? message.trim() : null;
156
+ }
157
+ /**
158
+ * The line for an evaluation that ran but was not recorded (the credential may read, not write), or
159
+ * `null`. A branch with nothing to evaluate stores nothing either, and its headline says so.
160
+ */
161
+ export function notStoredLine(evaluation) {
162
+ if (evaluation.stored !== false || !['error', 'fail', 'pass'].includes(evaluation.policy_check?.status ?? ''))
163
+ return null;
164
+ return 'Not stored: this credential can run checks but not record runs, so `xano policy status` and `xano policy runs` will not show this one.';
165
+ }
@@ -0,0 +1,23 @@
1
+ import type { PolicyFinding, PolicyRuleResult, PolicySnapshotPolicy, PolicySnapshotRule } from './types.js';
2
+ /**
3
+ * The platform's naming rule, shared with Studio: the author's title, then the check's human
4
+ * label, then the rule id. Pass `label` from the run snapshot, or from the catalogue entry for
5
+ * `check` when the caller already holds the catalogue; never fetch one just to name a rule.
6
+ */
7
+ export declare function policyRuleName(rule: PolicySnapshotRule): string;
8
+ /** Collision-free regardless of what a key or rule id contains. */
9
+ export declare const ruleKey: (policyKey?: string, ruleId?: string) => string;
10
+ export declare function snapshotRules(policies: PolicySnapshotPolicy[]): Map<string, PolicySnapshotRule>;
11
+ /**
12
+ * One finding line. The rule id leads it, because the check's label is shared by every
13
+ * rule that uses that check — with two rules of one policy failing, the labels alone do
14
+ * not tell them apart, and the id is what `--run-detail` and the JSON cite.
15
+ */
16
+ export declare function findingLine(finding: PolicyFinding, rules: Map<string, PolicySnapshotRule>): string;
17
+ /** A completed rule with no objects checked, including older stored pass + checked 0 results. */
18
+ export declare function hasNoObjects(result: PolicyRuleResult): boolean;
19
+ /**
20
+ * Rule errors ("this rule could not run") and scope warnings ("part of your scope selected
21
+ * nothing") under their own headings, then the rules that inspected nothing.
22
+ */
23
+ export declare function policyResultSummary(results?: PolicyRuleResult[]): string[];
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The platform's naming rule, shared with Studio: the author's title, then the check's human
3
+ * label, then the rule id. Pass `label` from the run snapshot, or from the catalogue entry for
4
+ * `check` when the caller already holds the catalogue; never fetch one just to name a rule.
5
+ */
6
+ export function policyRuleName(rule) {
7
+ return rule.title?.trim() || rule.label?.trim() || rule.id?.trim() || '';
8
+ }
9
+ /** Collision-free regardless of what a key or rule id contains. */
10
+ export const ruleKey = (policyKey = '', ruleId = '') => JSON.stringify([policyKey, ruleId]);
11
+ export function snapshotRules(policies) {
12
+ return new Map(policies.flatMap(policy => (policy.rules ?? []).map(rule => [ruleKey(policy.key, rule.id), rule])));
13
+ }
14
+ /** A finding names its rule the way the platform does, falling back to what the run recorded. */
15
+ function findingName(finding, rules) {
16
+ const recorded = rules.get(ruleKey(finding.policy_key, finding.rule_id)) ?? {};
17
+ return policyRuleName({
18
+ ...recorded,
19
+ id: finding.rule_id ?? recorded.id,
20
+ title: finding.rule_title || recorded.title,
21
+ }) || 'finding';
22
+ }
23
+ /**
24
+ * One finding line. The rule id leads it, because the check's label is shared by every
25
+ * rule that uses that check — with two rules of one policy failing, the labels alone do
26
+ * not tell them apart, and the id is what `--run-detail` and the JSON cite.
27
+ */
28
+ export function findingLine(finding, rules) {
29
+ // A rule the author left unnamed is called by its check's label, and only then by its id.
30
+ const rule = findingName(finding, rules);
31
+ const id = finding.rule_id?.trim();
32
+ const policy = finding.policy_title || finding.policy_key || 'policy';
33
+ const severity = finding.severity?.trim() ? ` [${finding.severity.trim()}]` : '';
34
+ return ` ${[id, id === rule ? '' : rule].filter(Boolean).join(' ')}${severity} (${policy}) ${finding.object?.type ?? ''} ${finding.object?.name ?? ''}: ${finding.message ?? ''}`;
35
+ }
36
+ /** A completed rule with no objects checked, including older stored pass + checked 0 results. */
37
+ export function hasNoObjects(result) {
38
+ return ['no_objects', 'pass'].includes(result.status ?? '') && (result.checked ?? 0) === 0;
39
+ }
40
+ /**
41
+ * Rule errors ("this rule could not run") and scope warnings ("part of your scope selected
42
+ * nothing") under their own headings, then the rules that inspected nothing.
43
+ */
44
+ export function policyResultSummary(results = []) {
45
+ const where = (result) => `${result.policy_key ?? 'policy'} ${result.check_id ?? 'rule'}`;
46
+ const errors = results.filter(result => result.status === 'error')
47
+ .map(result => ` ${where(result)}: ${result.message ?? 'No diagnostic returned.'}`);
48
+ const warnings = results.flatMap(result => (result.warnings ?? []).map(warning => ` ${where(result)}: ${warning}`));
49
+ const lines = [];
50
+ if (errors.length > 0)
51
+ lines.push('Errors:', ...errors);
52
+ if (warnings.length > 0)
53
+ lines.push('Warnings:', ...warnings);
54
+ const checked = results.reduce((sum, result) => sum + (result.checked ?? 0), 0);
55
+ // When no rule checked anything, one sentence about the run says it all.
56
+ if (results.length > 0 && checked === 0) {
57
+ lines.push('No objects checked; this run does not demonstrate coverage.');
58
+ return lines;
59
+ }
60
+ const empty = results.filter(result => hasNoObjects(result));
61
+ if (empty.length > 0) {
62
+ lines.push('No objects checked (proves nothing about coverage):', ...empty.map(result => ` ${where(result)}: no objects checked`));
63
+ }
64
+ return lines;
65
+ }
@@ -0,0 +1,161 @@
1
+ import type { ProfileConfig } from '../../base-command.js';
2
+ import type { PolicyFinding } from './types.js';
3
+ /** The release the tenant deploy gate judges. */
4
+ export interface GateRelease {
5
+ /** Whether the release was cut with its policies; one cut without them has no check. */
6
+ carried?: boolean;
7
+ counts?: null | {
8
+ advisory?: number;
9
+ blocking?: number;
10
+ errors?: number;
11
+ findings?: number;
12
+ };
13
+ id?: number;
14
+ name?: string;
15
+ /** The release's stored policy run, 0 when it has none. */
16
+ run_id?: number;
17
+ status?: string;
18
+ }
19
+ /**
20
+ * The tenant deploy gate's answer: what `GET tenant/{name}/policy_gate` previews, and the payload of
21
+ * the 403 a refused deploy answers (with `code: policy_gate`). The gate judges the release alone, by
22
+ * its own policy check, so the same release gets the same verdict on every tenant; only `gated`
23
+ * differs. The findings come only to a credential that reads the workspace's policies.
24
+ */
25
+ export interface PolicyGateAnswer {
26
+ can_override?: boolean;
27
+ code?: string;
28
+ /** The first of the release's blocking findings. */
29
+ findings?: PolicyFinding[];
30
+ /** `tenant_deploy`, or `tenant_approve` when an approval completes the deploy. */
31
+ gate?: string;
32
+ /** False for an ephemeral or sandbox tenant: the verdict is reported but never refuses. */
33
+ gated?: boolean;
34
+ message?: string;
35
+ override_denied?: boolean;
36
+ release?: GateRelease | null;
37
+ run_id?: number;
38
+ /** `pass`, `blocked`, `overridden`, `not_applicable`, `not_carried`, `disabled` or `unavailable`. */
39
+ status?: string;
40
+ /** Every blocking finding the release has, listed or not. */
41
+ total?: number;
42
+ truncated?: boolean;
43
+ }
44
+ /**
45
+ * A gate that measures a change against the live branch (set-live, publish, a live-branch save): its
46
+ * blocking findings split by why they block.
47
+ */
48
+ export interface LiveGateAnswer extends Omit<PolicyGateAnswer, 'gated' | 'release'> {
49
+ changed?: number;
50
+ existing?: number;
51
+ introduced?: number;
52
+ }
53
+ /** The refusal code a policy gate's 403 carries in `payload.code`. */
54
+ export declare const GATE_REFUSAL = "policy_gate";
55
+ /** 2 when the gate refuses this deploy (blocked on a gated tenant); every other verdict is 0. */
56
+ export declare function gateExitCode(answer?: null | PolicyGateAnswer): number;
57
+ /**
58
+ * The gate's verdict on stdout: a headline with its sentence, the release it judged, how many of its
59
+ * findings block, and the first of them. Every blocking finding of the release counts, whatever the
60
+ * tenant runs today.
61
+ */
62
+ export declare function gateLines(answer: PolicyGateAnswer): string[];
63
+ /**
64
+ * The line a completed deploy prints about its gate. A blocked verdict on an ephemeral or sandbox
65
+ * tenant refused nothing (the deploy went ahead), so it says so rather than reading as a failure.
66
+ */
67
+ export declare function deployedGateLine(answer: PolicyGateAnswer): string;
68
+ /**
69
+ * The verdict of a gate that measures a change against the live branch (set-live, publish): its
70
+ * status, what blocks, and the first blocking findings. `subject` is what changes: a branch set live,
71
+ * the branch a release was deployed as (`release deploy --set_live`), or an object saved to live.
72
+ */
73
+ export declare function liveGateLines(answer: LiveGateAnswer, subject: 'branch' | 'release' | 'save'): string[];
74
+ /**
75
+ * A save the live-branch publish gate refused (`function create`, `function edit`): what goes on
76
+ * stdout (the verdict, or under `-o json` `{created|updated: false, message, policy_gate}`) and the
77
+ * error the command exits 2 with, saying nothing was saved and how to proceed (`rerun` is the same
78
+ * command with `--policy-override`).
79
+ */
80
+ export declare function publishRefusal(refused: GateRefused, options: {
81
+ json: boolean;
82
+ outcome: 'created' | 'updated';
83
+ rerun: string;
84
+ }): {
85
+ lines: string[];
86
+ message: string;
87
+ };
88
+ /** The refusal of a `--policy-override` given without a reason, before any request is made. */
89
+ export declare const BLANK_POLICY_OVERRIDE = "--policy-override needs a reason: say why this change may proceed past the policy gate.";
90
+ /** True when `--policy-override` was given but holds only whitespace. */
91
+ export declare function blankPolicyOverride(reason?: string): boolean;
92
+ /** A value quoted for the shell when it needs it. */
93
+ export declare function quoted(value: string): string;
94
+ /**
95
+ * What to do about a refusal: override it (the credential may, and `command` is how), or ask for
96
+ * the permission. A refusal whose override was denied already says so in its message.
97
+ */
98
+ export declare function gateOverrideHint(answer: PolicyGateAnswer, command: string): string;
99
+ /** A deploy the policy gate refused: the platform's sentence and the gate's answer. */
100
+ export interface GateRefused {
101
+ answer: PolicyGateAnswer;
102
+ message: string;
103
+ }
104
+ /**
105
+ * What a command the gate refused prints on stdout: the verdict, or under `-o json` the refusal as
106
+ * `{deployed: false, message, policy_gate}`. The command then exits 2 with the message.
107
+ */
108
+ export declare function gateRefusalOutput(refused: GateRefused, json: boolean): string[];
109
+ /** A policy gate's refusal read from a failed response: its message and answer, or `null` for any other failure. */
110
+ export declare function gateRefusal(response: Response): Promise<GateRefused | null>;
111
+ /**
112
+ * A change refused because it weakens a policy active and mandatory on the branch, from a caller
113
+ * without the `workspace:policy` update permission (`policy_weakening_permission_required`). A
114
+ * release deploy or archive import with set live adds the branch it landed, which stays.
115
+ */
116
+ export interface WeakeningRefused {
117
+ message: string;
118
+ /** `unavailable`: the policies could not be read to check it, so only the permission proceeds (`policies` is empty). */
119
+ payload: {
120
+ branch?: {
121
+ id?: number;
122
+ label?: string;
123
+ };
124
+ code: string;
125
+ gate?: string;
126
+ level?: string;
127
+ permission?: string;
128
+ policies?: string[];
129
+ unavailable?: boolean;
130
+ };
131
+ }
132
+ /** The weakening refusal read from a failed response, or `null` for any other failure. */
133
+ export declare function weakeningRefusal(response: Response): Promise<null | WeakeningRefused>;
134
+ /** What a gate preview needs from the command that asks for it. */
135
+ export interface GateHost {
136
+ verboseFetch(url: string, options: RequestInit, verbose: boolean, authToken?: string): Promise<Response>;
137
+ }
138
+ /** A preview the platform could not answer: the HTTP status, its message and the refusal's payload. */
139
+ export interface GateUnavailable {
140
+ message: string;
141
+ payload?: unknown;
142
+ status: number;
143
+ }
144
+ /**
145
+ * `GET workspace/{ws}/tenant/{tenant}/policy_gate?release_name=`: what the gate would decide for the
146
+ * release on the tenant, without deploying. A refusal (the Policies feature is off, the credential
147
+ * cannot read policies, no such tenant or release) is answered as `unavailable` rather than thrown.
148
+ */
149
+ export declare function fetchPolicyGate(host: GateHost, target: {
150
+ profile: ProfileConfig;
151
+ release: string;
152
+ tenant: string;
153
+ verbose: boolean;
154
+ workspace: string;
155
+ }): Promise<{
156
+ answer: PolicyGateAnswer;
157
+ } | {
158
+ unavailable: GateUnavailable;
159
+ }>;
160
+ /** Why a preview is not available, in a few words for a line that must not fail its command. */
161
+ export declare function unavailableReason(unavailable: GateUnavailable): string;