@xano/cli 1.3.1-beta.1 → 1.3.2

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 (98) hide show
  1. package/README.md +1 -273
  2. package/dist/base-command.d.ts +1 -12
  3. package/dist/base-command.js +22 -36
  4. package/dist/commands/branch/set_live/index.d.ts +0 -22
  5. package/dist/commands/branch/set_live/index.js +1 -60
  6. package/dist/commands/ephemeral/push/index.js +1 -6
  7. package/dist/commands/function/create/index.d.ts +0 -15
  8. package/dist/commands/function/create/index.js +0 -40
  9. package/dist/commands/function/edit/index.d.ts +0 -15
  10. package/dist/commands/function/edit/index.js +0 -40
  11. package/dist/commands/function/run/index.d.ts +0 -1
  12. package/dist/commands/function/run/index.js +2 -10
  13. package/dist/commands/release/create/index.js +0 -7
  14. package/dist/commands/release/deploy/index.d.ts +0 -28
  15. package/dist/commands/release/deploy/index.js +6 -90
  16. package/dist/commands/release/import/index.js +1 -5
  17. package/dist/commands/release/list/index.d.ts +0 -7
  18. package/dist/commands/release/list/index.js +10 -54
  19. package/dist/commands/release/push/index.js +1 -19
  20. package/dist/commands/sandbox/push/index.js +1 -6
  21. package/dist/commands/tenant/create/index.d.ts +0 -3
  22. package/dist/commands/tenant/create/index.js +0 -21
  23. package/dist/commands/tenant/deploy_release/index.d.ts +0 -40
  24. package/dist/commands/tenant/deploy_release/index.js +4 -186
  25. package/dist/commands/tenant/edit/index.d.ts +0 -2
  26. package/dist/commands/tenant/edit/index.js +4 -39
  27. package/dist/commands/workspace/pull/index.d.ts +0 -10
  28. package/dist/commands/workspace/pull/index.js +7 -70
  29. package/dist/commands/workspace/push/index.d.ts +0 -19
  30. package/dist/commands/workspace/push/index.js +5 -124
  31. package/dist/utils/document-parser.d.ts +0 -10
  32. package/dist/utils/document-parser.js +0 -21
  33. package/dist/utils/multidoc-push.d.ts +2 -55
  34. package/dist/utils/multidoc-push.js +27 -103
  35. package/oclif.manifest.json +3570 -6579
  36. package/package.json +1 -7
  37. package/dist/commands/policy/catalogue/index.d.ts +0 -15
  38. package/dist/commands/policy/catalogue/index.js +0 -29
  39. package/dist/commands/policy/create/index.d.ts +0 -18
  40. package/dist/commands/policy/create/index.js +0 -39
  41. package/dist/commands/policy/delete/index.d.ts +0 -19
  42. package/dist/commands/policy/delete/index.js +0 -61
  43. package/dist/commands/policy/evaluate/index.d.ts +0 -32
  44. package/dist/commands/policy/evaluate/index.js +0 -71
  45. package/dist/commands/policy/list/index.d.ts +0 -14
  46. package/dist/commands/policy/list/index.js +0 -21
  47. package/dist/commands/policy/parse/index.d.ts +0 -19
  48. package/dist/commands/policy/parse/index.js +0 -18
  49. package/dist/commands/policy/publish/index.d.ts +0 -20
  50. package/dist/commands/policy/publish/index.js +0 -38
  51. package/dist/commands/policy/runs/index.d.ts +0 -54
  52. package/dist/commands/policy/runs/index.js +0 -273
  53. package/dist/commands/policy/status/index.d.ts +0 -21
  54. package/dist/commands/policy/status/index.js +0 -54
  55. package/dist/commands/skills/pull/index.d.ts +0 -30
  56. package/dist/commands/skills/pull/index.js +0 -118
  57. package/dist/commands/tenant_deploy_request/bypass/index.d.ts +0 -31
  58. package/dist/commands/tenant_deploy_request/bypass/index.js +0 -109
  59. package/dist/commands/tenant_deploy_request/create/index.d.ts +0 -21
  60. package/dist/commands/tenant_deploy_request/create/index.js +0 -108
  61. package/dist/commands/tenant_deploy_request/edit/index.d.ts +0 -22
  62. package/dist/commands/tenant_deploy_request/edit/index.js +0 -103
  63. package/dist/commands/tenant_deploy_request/get/index.d.ts +0 -25
  64. package/dist/commands/tenant_deploy_request/get/index.js +0 -116
  65. package/dist/commands/tenant_deploy_request/list/index.d.ts +0 -20
  66. package/dist/commands/tenant_deploy_request/list/index.js +0 -122
  67. package/dist/commands/tenant_deploy_request/revision/index.d.ts +0 -21
  68. package/dist/commands/tenant_deploy_request/revision/index.js +0 -83
  69. package/dist/commands/tenant_deploy_request/set_status/index.d.ts +0 -38
  70. package/dist/commands/tenant_deploy_request/set_status/index.js +0 -159
  71. package/dist/policy-command.d.ts +0 -90
  72. package/dist/policy-command.js +0 -94
  73. package/dist/utils/policy/catalogue.d.ts +0 -7
  74. package/dist/utils/policy/catalogue.js +0 -72
  75. package/dist/utils/policy/errors.d.ts +0 -12
  76. package/dist/utils/policy/errors.js +0 -61
  77. package/dist/utils/policy/feedback.d.ts +0 -68
  78. package/dist/utils/policy/feedback.js +0 -165
  79. package/dist/utils/policy/findings.d.ts +0 -23
  80. package/dist/utils/policy/findings.js +0 -65
  81. package/dist/utils/policy/gate.d.ts +0 -161
  82. package/dist/utils/policy/gate.js +0 -201
  83. package/dist/utils/policy/goal.d.ts +0 -46
  84. package/dist/utils/policy/goal.js +0 -95
  85. package/dist/utils/policy/permission.d.ts +0 -23
  86. package/dist/utils/policy/permission.js +0 -84
  87. package/dist/utils/policy/push-gate.d.ts +0 -5
  88. package/dist/utils/policy/push-gate.js +0 -29
  89. package/dist/utils/policy/release.d.ts +0 -85
  90. package/dist/utils/policy/release.js +0 -115
  91. package/dist/utils/policy/request.d.ts +0 -55
  92. package/dist/utils/policy/request.js +0 -115
  93. package/dist/utils/policy/runs.d.ts +0 -30
  94. package/dist/utils/policy/runs.js +0 -101
  95. package/dist/utils/policy/status.d.ts +0 -47
  96. package/dist/utils/policy/status.js +0 -117
  97. package/dist/utils/policy/types.d.ts +0 -185
  98. package/dist/utils/policy/types.js +0 -1
@@ -1,38 +0,0 @@
1
- import BaseCommand from '../../../base-command.js';
2
- export default class TenantDeployRequestSetStatus extends BaseCommand {
3
- static args: {
4
- id: import("@oclif/core/interfaces").Arg<number, {
5
- max?: number;
6
- min?: number;
7
- }>;
8
- };
9
- static description: string;
10
- static examples: string[];
11
- static flags: {
12
- output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
- 'override-policy': import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
- reason: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
- status: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
- workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
- config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
- profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
- verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
- };
21
- /** Set once the policy gate refuses the deploy an approval triggered, the one failure that exits 2. */
22
- private refusedByPolicyGate;
23
- /** Exit 2 is the policy gate's refusal; every other failure, flag errors included, exits 1. */
24
- protected catch(error: Error & {
25
- oclif?: {
26
- exit?: number;
27
- };
28
- }): Promise<void>;
29
- run(): Promise<void>;
30
- /** `--override-policy` overrides the deploy an approve can trigger, and its reason is `--reason`. */
31
- private checkOverride;
32
- /**
33
- * The deploy this approval triggered was refused by the policy gate: its verdict, then the refusal,
34
- * exiting 2. The vote that completes the approvals is recorded before the deploy, so the request
35
- * stays approved, and a later deploy of the pair that overrides the gate completes it.
36
- */
37
- private refuseByPolicyGate;
38
- }
@@ -1,159 +0,0 @@
1
- import { Args, Flags } from '@oclif/core';
2
- import BaseCommand from '../../../base-command.js';
3
- import { gateOverrideHint, gateRefusal, gateRefusalOutput, quoted } from '../../../utils/policy/gate.js';
4
- export default class TenantDeployRequestSetStatus extends BaseCommand {
5
- static args = {
6
- id: Args.integer({
7
- description: 'Tenant deploy request ID',
8
- required: true,
9
- }),
10
- };
11
- static description = "[CRITICAL] STOP and confirm with the user before running --status approve; if this is the vote that completes the tenant's required reviewer count, it deploys the release to the tenant immediately as part of approving. Change a tenant deploy request's status (draft, submit, approve, request_changes, close, or reopen). That deploy is gated on the release's policy check: a refusal exits 2 and leaves the request approved; --override-policy with --reason proceeds past it.";
12
- static examples = [
13
- `$ xano tenant_deploy_request set_status 12 --status submit
14
- Deploy request #12 is now: pending
15
- `,
16
- `$ xano tenant_deploy_request set_status 12 --status approve
17
- Vote recorded on deploy request #12: 1/2 required reviewers.
18
- `,
19
- `$ xano tenant_deploy_request set_status 12 --status approve
20
- Deploy request #12 is now: approved (2/2 required reviewers)
21
- Deployed to tenant as part of this approval.
22
- `,
23
- `$ xano tenant_deploy_request set_status 12 --status approve --override-policy --reason "Finding accepted; fix tracked in JIRA-12"`,
24
- `$ xano tenant_deploy_request set_status 12 --status request_changes --reason "Needs a migration note"`,
25
- `$ xano tenant_deploy_request set_status 12 --status close --reason "Superseded by #14"`,
26
- `$ xano tenant_deploy_request set_status 12 --status reopen`,
27
- ];
28
- static flags = {
29
- ...BaseCommand.baseFlags,
30
- output: Flags.string({
31
- char: 'o',
32
- default: 'summary',
33
- description: 'Output format',
34
- options: ['summary', 'json'],
35
- required: false,
36
- }),
37
- 'override-policy': Flags.boolean({
38
- default: false,
39
- description: 'With --status approve: if this vote deploys, proceed past a blocking policy gate, recording --reason as the override reason (needs the workspace:policy update permission; audited)',
40
- }),
41
- reason: Flags.string({
42
- description: 'Reason for this status change. Required for request_changes and close, and with --override-policy.',
43
- required: false,
44
- }),
45
- status: Flags.string({
46
- description: 'The status transition to apply',
47
- options: ['draft', 'submit', 'approve', 'request_changes', 'close', 'reopen'],
48
- required: true,
49
- }),
50
- workspace: Flags.string({
51
- char: 'w',
52
- description: 'Workspace ID (uses profile workspace if not provided)',
53
- required: false,
54
- }),
55
- };
56
- /** Set once the policy gate refuses the deploy an approval triggered, the one failure that exits 2. */
57
- refusedByPolicyGate = false;
58
- /** Exit 2 is the policy gate's refusal; every other failure, flag errors included, exits 1. */
59
- async catch(error) {
60
- if (this.refusedByPolicyGate)
61
- return super.catch(error);
62
- return this.catchAsOperational(error);
63
- }
64
- async run() {
65
- const { args, flags } = await this.parse(TenantDeployRequestSetStatus);
66
- const { profile } = this.resolveProfile(flags);
67
- const workspaceId = flags.workspace || profile.workspace;
68
- if (!workspaceId) {
69
- this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
70
- }
71
- if ((flags.status === 'request_changes' || flags.status === 'close') && !flags.reason) {
72
- this.error(`--reason is required when --status is "${flags.status}"`);
73
- }
74
- if (flags['override-policy'])
75
- this.checkOverride(flags);
76
- const body = {
77
- action: flags.status,
78
- reason: flags.reason ?? '',
79
- ...(flags['override-policy'] ? { override_policy: true } : {}),
80
- };
81
- // A refusal by the policy gate exits 2 with its verdict; the catch that rewords other failures keeps it.
82
- let refused = null;
83
- const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/approval_request/${args.id}/status`;
84
- try {
85
- const response = await this.verboseFetch(apiUrl, {
86
- body: JSON.stringify(body),
87
- headers: {
88
- accept: 'application/json',
89
- Authorization: `Bearer ${profile.access_token}`,
90
- 'Content-Type': 'application/json',
91
- },
92
- method: 'POST',
93
- }, flags.verbose, profile.access_token);
94
- if (!response.ok) {
95
- refused = await gateRefusal(response);
96
- if (refused)
97
- this.refuseByPolicyGate(refused, args.id, flags.output === 'json');
98
- const message = await this.parseApiError(response, 'Failed to change tenant deploy request status');
99
- this.error(message);
100
- }
101
- const item = (await response.json());
102
- if (flags.output === 'json') {
103
- this.log(JSON.stringify(item, null, 2));
104
- }
105
- else if (flags.status === 'approve' && item.fully_approved === false) {
106
- // required_reviewers > 1: this vote didn't complete the quorum, so status
107
- // stays "pending" and nothing was deployed yet.
108
- this.log(`Vote recorded on deploy request #${item.id}: ${item.approvals_count}/${item.required_reviewers} required reviewers.`);
109
- }
110
- else {
111
- const quorum = item.approvals_count !== undefined && item.required_reviewers !== undefined
112
- ? ` (${item.approvals_count}/${item.required_reviewers} required reviewers)`
113
- : '';
114
- this.log(`Deploy request #${item.id} is now: ${item.status}${quorum}`);
115
- if (flags.status === 'approve') {
116
- if (item.resolution?.deploy_status === 'deployed') {
117
- this.log('Deployed to tenant as part of this approval.');
118
- }
119
- else if (item.resolution?.deploy_status === 'failed') {
120
- this.log(`Approved, but the deploy failed: ${item.resolution.deploy_error ?? 'unknown error'}`);
121
- }
122
- }
123
- }
124
- }
125
- catch (error) {
126
- if (refused)
127
- throw error;
128
- if (error instanceof Error) {
129
- this.error(`Failed to change tenant deploy request status: ${error.message}`);
130
- }
131
- else {
132
- this.error(`Failed to change tenant deploy request status: ${String(error)}`);
133
- }
134
- }
135
- }
136
- /** `--override-policy` overrides the deploy an approve can trigger, and its reason is `--reason`. */
137
- checkOverride(flags) {
138
- if (flags.status !== 'approve')
139
- this.error('--override-policy applies only to --status approve, the vote that can deploy.');
140
- if (!flags.reason?.trim())
141
- this.error('--override-policy needs --reason: say why this deploy may proceed past the policy gate.');
142
- }
143
- /**
144
- * The deploy this approval triggered was refused by the policy gate: its verdict, then the refusal,
145
- * exiting 2. The vote that completes the approvals is recorded before the deploy, so the request
146
- * stays approved, and a later deploy of the pair that overrides the gate completes it.
147
- */
148
- refuseByPolicyGate(refused, id, json) {
149
- for (const line of gateRefusalOutput(refused, json))
150
- this.log(line);
151
- const approved = refused.answer.gate === 'tenant_approve'
152
- ? '\nYour approval was recorded and the request is approved, but its release was not deployed.'
153
- : '';
154
- const release = refused.answer.release?.name?.trim();
155
- const override = `xano tenant deploy_release <tenant> --release ${release ? quoted(release) : '<release>'} --override-reason "<why>" (\`xano tenant_deploy_request get ${id}\` names the tenant)`;
156
- this.refusedByPolicyGate = true;
157
- this.error(`${refused.message}${approved}${gateOverrideHint(refused.answer, override)}`, { exit: 2 });
158
- }
159
- }
@@ -1,90 +0,0 @@
1
- import type { Policy, PolicyRun } from './utils/policy/types.js';
2
- import BaseCommand from './base-command.js';
3
- import { type PolicyRequest } from './utils/policy/request.js';
4
- /** One rule's warnings from the parse route: its 0-based index, id and sentences. */
5
- export interface RuleWarnings {
6
- rule: number;
7
- rule_id: string;
8
- warnings: string[];
9
- }
10
- /** What the parse route answers. */
11
- export interface ParsedPolicy {
12
- policy: Policy;
13
- rule_warnings: RuleWarnings[];
14
- source: string;
15
- }
16
- /** The workspace and branch a policy command acts on, and requests bound to them. */
17
- export interface PolicyTarget {
18
- branch: string;
19
- request: PolicyRequest;
20
- workspace: string;
21
- }
22
- interface TargetFlags {
23
- branch?: string;
24
- config?: string;
25
- profile?: string;
26
- verbose: boolean;
27
- workspace?: string;
28
- }
29
- /** Shared flags, request helper and output for the `policy *` commands and `skills pull`. */
30
- export default abstract class PolicyCommand extends BaseCommand {
31
- static policyFlags: {
32
- branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
33
- output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
34
- workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
- config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
- profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
37
- verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
38
- };
39
- /** `publish -m`: the message on the Version History entry the save creates. */
40
- static publishFlags: {
41
- message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
42
- };
43
- /** One positional, as CLAUDE.md requires: the file `--file` would otherwise name. */
44
- static sourceArgs: {
45
- file: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
46
- };
47
- static sourceFlags: {
48
- file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
49
- stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
50
- };
51
- /** Any failure exits 1; an error that is not the CLI's own names the command it broke. */
52
- protected catch(error: Error & {
53
- oclif?: {
54
- exit?: number;
55
- };
56
- }): Promise<void>;
57
- /**
58
- * Print each rule's warnings (a name, tag or value that makes the rule check nothing) to stderr,
59
- * so piped source stays clean; nothing when there are none.
60
- */
61
- protected logRuleWarnings(parsed: ParsedPolicy): void;
62
- /** Print a run's recorded detail, or say it evaluated no policy. */
63
- protected logRunDetail(run: PolicyRun): void;
64
- /**
65
- * Ask the parse route for the policy, its canonical source and each rule's warnings against the
66
- * target branch, from XanoScript (`{source}`) or a policy document (`{data}`); the policy and source
67
- * must come back.
68
- */
69
- protected parseSource(request: PolicyRequest, input: {
70
- data: unknown;
71
- } | {
72
- source: string;
73
- }): Promise<ParsedPolicy>;
74
- /**
75
- * The workspace and branch from the flags or the profile, and a request helper bound to them for
76
- * a route gated by `workspace:policy`: `/policy` unless another is named.
77
- */
78
- protected policyTarget(flags: TargetFlags, route?: Pick<import("./utils/policy/request.js").PolicyRequestRoute, "path" | "label">): PolicyTarget;
79
- /** Policy source from the positional, `--file` or `--stdin`; a file named twice must be named the same. */
80
- protected readSource(flags: {
81
- file?: string;
82
- stdin?: boolean;
83
- }, positional?: string): string;
84
- /** `workspace 3 (feature)`, or just `workspace 3` for live. */
85
- protected where({ branch, workspace }: {
86
- branch: string;
87
- workspace: string;
88
- }): string;
89
- }
90
- export {};
@@ -1,94 +0,0 @@
1
- import { Args, Flags } from '@oclif/core';
2
- import * as fs from 'node:fs';
3
- import BaseCommand from './base-command.js';
4
- import { POLICY_ROUTE, policyRequest, policyScope } from './utils/policy/request.js';
5
- import { policyRunDetail } from './utils/policy/runs.js';
6
- /** Shared flags, request helper and output for the `policy *` commands and `skills pull`. */
7
- export default class PolicyCommand extends BaseCommand {
8
- static policyFlags = {
9
- ...BaseCommand.baseFlags,
10
- branch: Flags.string({ char: 'b', description: "Branch label (defaults to profile branch or live; -b '' selects live)" }),
11
- output: Flags.string({ char: 'o', default: 'summary', description: 'Output format', options: ['summary', 'json'] }),
12
- workspace: Flags.string({ char: 'w', description: 'Workspace ID (defaults to profile workspace)' }),
13
- };
14
- /** `publish -m`: the message on the Version History entry the save creates. */
15
- static publishFlags = {
16
- message: Flags.string({ char: 'm', description: 'Message stored on the Version History entry this save creates' }),
17
- };
18
- /** One positional, as CLAUDE.md requires: the file `--file` would otherwise name. */
19
- static sourceArgs = {
20
- file: Args.string({ description: 'Policy XanoScript file (same as --file)', ignoreStdin: true, required: false }),
21
- };
22
- static sourceFlags = {
23
- file: Flags.string({ char: 'f', description: 'Policy XanoScript file', exclusive: ['stdin'] }),
24
- stdin: Flags.boolean({ default: false, description: 'Read policy XanoScript from stdin', exclusive: ['file'] }),
25
- };
26
- /** Any failure exits 1; an error that is not the CLI's own names the command it broke. */
27
- async catch(error) {
28
- if ('oclif' in error)
29
- return this.catchAsOperational(error);
30
- const action = this.id?.split(':').at(-1) ?? 'command';
31
- return this.catchAsOperational(new Error(`Policy ${action} failed: ${error.message}`));
32
- }
33
- /**
34
- * Print each rule's warnings (a name, tag or value that makes the rule check nothing) to stderr,
35
- * so piped source stays clean; nothing when there are none.
36
- */
37
- logRuleWarnings(parsed) {
38
- const warned = parsed.rule_warnings ?? [];
39
- if (warned.length === 0)
40
- return;
41
- this.logToStderr('Rule warnings (the rule may check nothing or everything; fix them before relying on it):');
42
- for (const rule of warned) {
43
- for (const warning of rule.warnings)
44
- this.logToStderr(` ${rule.rule_id || `rule[${rule.rule}]`}: ${warning}`);
45
- }
46
- }
47
- /** Print a run's recorded detail, or say it evaluated no policy. */
48
- logRunDetail(run) {
49
- const detail = policyRunDetail(run);
50
- for (const line of detail)
51
- this.log(line);
52
- if (detail.length === 0)
53
- this.log(run.id ? `Run ${run.id} evaluated no policies.` : 'No policies were evaluated.');
54
- }
55
- /**
56
- * Ask the parse route for the policy, its canonical source and each rule's warnings against the
57
- * target branch, from XanoScript (`{source}`) or a policy document (`{data}`); the policy and source
58
- * must come back.
59
- */
60
- async parseSource(request, input) {
61
- const answer = (await request('/parse', 'POST', input));
62
- if (!answer?.policy?.key || typeof answer.source !== 'string')
63
- this.error('The platform did not return a parsed policy and canonical source.');
64
- return { ...answer, rule_warnings: Array.isArray(answer.rule_warnings) ? answer.rule_warnings : [] };
65
- }
66
- /**
67
- * The workspace and branch from the flags or the profile, and a request helper bound to them for
68
- * a route gated by `workspace:policy`: `/policy` unless another is named.
69
- */
70
- policyTarget(flags, route = POLICY_ROUTE) {
71
- const { profile } = this.resolveProfile(flags);
72
- const { branch, workspace } = policyScope(flags, profile);
73
- if (!workspace)
74
- this.error('Workspace ID required. Use --workspace or set one in your profile.');
75
- const request = policyRequest({ error: (message) => this.error(message), logToStderr: (message) => this.logToStderr(message), verboseFetch: (...args) => this.verboseFetch(...args) }, { ...route, branch, profile, verbose: flags.verbose, workspace });
76
- return { branch, request, workspace };
77
- }
78
- /** Policy source from the positional, `--file` or `--stdin`; a file named twice must be named the same. */
79
- readSource(flags, positional) {
80
- const named = positional?.trim();
81
- if (named && flags.stdin)
82
- this.error('Provide policy source once: a file (positional or --file) or --stdin.');
83
- if (named && flags.file && flags.file !== named)
84
- this.error('Provide the file once: as a positional or as --file.');
85
- const file = flags.file ?? named;
86
- if (!file && !flags.stdin)
87
- this.error('Provide the policy file (as an argument or --file) or --stdin.');
88
- return fs.readFileSync(flags.stdin ? 0 : file, 'utf8');
89
- }
90
- /** `workspace 3 (feature)`, or just `workspace 3` for live. */
91
- where({ branch, workspace }) {
92
- return `workspace ${workspace}${branch ? ` (${branch})` : ''}`;
93
- }
94
- }
@@ -1,7 +0,0 @@
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[];
@@ -1,72 +0,0 @@
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
- }
@@ -1,12 +0,0 @@
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;
@@ -1,61 +0,0 @@
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
- }
@@ -1,68 +0,0 @@
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;