@vertaaux/cli 0.2.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.
- package/README.md +345 -0
- package/dist/auth/ci-token.d.ts +49 -0
- package/dist/auth/ci-token.d.ts.map +1 -0
- package/dist/auth/ci-token.js +83 -0
- package/dist/auth/device-flow.d.ts +66 -0
- package/dist/auth/device-flow.d.ts.map +1 -0
- package/dist/auth/device-flow.js +156 -0
- package/dist/auth/token-store.d.ts +53 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +78 -0
- package/dist/baseline/diff.d.ts +57 -0
- package/dist/baseline/diff.d.ts.map +1 -0
- package/dist/baseline/diff.js +152 -0
- package/dist/baseline/hash.d.ts +54 -0
- package/dist/baseline/hash.d.ts.map +1 -0
- package/dist/baseline/hash.js +66 -0
- package/dist/baseline/manager.d.ts +89 -0
- package/dist/baseline/manager.d.ts.map +1 -0
- package/dist/baseline/manager.js +157 -0
- package/dist/cache/index.d.ts +8 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +7 -0
- package/dist/cache/route-cache.d.ts +119 -0
- package/dist/cache/route-cache.d.ts.map +1 -0
- package/dist/cache/route-cache.js +213 -0
- package/dist/ci/changed-routes.d.ts +95 -0
- package/dist/ci/changed-routes.d.ts.map +1 -0
- package/dist/ci/changed-routes.js +304 -0
- package/dist/ci/github-api.d.ts +68 -0
- package/dist/ci/github-api.d.ts.map +1 -0
- package/dist/ci/github-api.js +138 -0
- package/dist/ci/gitlab-api.d.ts +75 -0
- package/dist/ci/gitlab-api.d.ts.map +1 -0
- package/dist/ci/gitlab-api.js +180 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/commands/audit.d.ts +58 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +862 -0
- package/dist/commands/baseline.d.ts +22 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +210 -0
- package/dist/commands/comment.d.ts +14 -0
- package/dist/commands/comment.d.ts.map +1 -0
- package/dist/commands/comment.js +363 -0
- package/dist/commands/diff.d.ts +24 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +196 -0
- package/dist/commands/doctor.d.ts +58 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +338 -0
- package/dist/commands/download.d.ts +12 -0
- package/dist/commands/download.d.ts.map +1 -0
- package/dist/commands/download.js +183 -0
- package/dist/commands/explain.d.ts +62 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +302 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +212 -0
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +222 -0
- package/dist/commands/policy.d.ts +13 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +347 -0
- package/dist/commands/upload.d.ts +12 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +158 -0
- package/dist/config/defaults.d.ts +21 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +49 -0
- package/dist/config/loader.d.ts +66 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +167 -0
- package/dist/config/schema.d.ts +55 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +6 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1090 -0
- package/dist/interactive/fix-wizard.d.ts +44 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -0
- package/dist/interactive/fix-wizard.js +286 -0
- package/dist/interactive/init-wizard.d.ts +32 -0
- package/dist/interactive/init-wizard.d.ts.map +1 -0
- package/dist/interactive/init-wizard.js +193 -0
- package/dist/interactive/prompts.d.ts +62 -0
- package/dist/interactive/prompts.d.ts.map +1 -0
- package/dist/interactive/prompts.js +78 -0
- package/dist/monorepo/detector.d.ts +70 -0
- package/dist/monorepo/detector.d.ts.map +1 -0
- package/dist/monorepo/detector.js +278 -0
- package/dist/monorepo/index.d.ts +9 -0
- package/dist/monorepo/index.d.ts.map +1 -0
- package/dist/monorepo/index.js +8 -0
- package/dist/monorepo/workspace.d.ts +142 -0
- package/dist/monorepo/workspace.d.ts.map +1 -0
- package/dist/monorepo/workspace.js +171 -0
- package/dist/output/envelope.d.ts +21 -0
- package/dist/output/envelope.d.ts.map +1 -0
- package/dist/output/envelope.js +27 -0
- package/dist/output/factory.d.ts +73 -0
- package/dist/output/factory.d.ts.map +1 -0
- package/dist/output/factory.js +60 -0
- package/dist/output/formats.d.ts +11 -0
- package/dist/output/formats.d.ts.map +1 -0
- package/dist/output/formats.js +41 -0
- package/dist/output/html.d.ts +45 -0
- package/dist/output/html.d.ts.map +1 -0
- package/dist/output/html.js +607 -0
- package/dist/output/human.d.ts +41 -0
- package/dist/output/human.d.ts.map +1 -0
- package/dist/output/human.js +274 -0
- package/dist/output/json.d.ts +42 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +37 -0
- package/dist/output/junit.d.ts +56 -0
- package/dist/output/junit.d.ts.map +1 -0
- package/dist/output/junit.js +135 -0
- package/dist/output/markdown.d.ts +77 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +411 -0
- package/dist/output/sarif.d.ts +160 -0
- package/dist/output/sarif.d.ts.map +1 -0
- package/dist/output/sarif.js +207 -0
- package/dist/policy/evaluator.d.ts +111 -0
- package/dist/policy/evaluator.d.ts.map +1 -0
- package/dist/policy/evaluator.js +362 -0
- package/dist/policy/index.d.ts +15 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +11 -0
- package/dist/policy/loader.d.ts +97 -0
- package/dist/policy/loader.d.ts.map +1 -0
- package/dist/policy/loader.js +281 -0
- package/dist/policy/schema.d.ts +297 -0
- package/dist/policy/schema.d.ts.map +1 -0
- package/dist/policy/schema.js +230 -0
- package/dist/quality-gate/evaluator.d.ts +58 -0
- package/dist/quality-gate/evaluator.d.ts.map +1 -0
- package/dist/quality-gate/evaluator.js +274 -0
- package/dist/quality-gate/index.d.ts +10 -0
- package/dist/quality-gate/index.d.ts.map +1 -0
- package/dist/quality-gate/index.js +7 -0
- package/dist/quality-gate/types.d.ts +103 -0
- package/dist/quality-gate/types.d.ts.map +1 -0
- package/dist/quality-gate/types.js +23 -0
- package/dist/templates/azure-devops.d.ts +25 -0
- package/dist/templates/azure-devops.d.ts.map +1 -0
- package/dist/templates/azure-devops.js +109 -0
- package/dist/templates/circleci.d.ts +28 -0
- package/dist/templates/circleci.d.ts.map +1 -0
- package/dist/templates/circleci.js +86 -0
- package/dist/templates/github-actions.d.ts +81 -0
- package/dist/templates/github-actions.d.ts.map +1 -0
- package/dist/templates/github-actions.js +393 -0
- package/dist/templates/gitlab-ci.d.ts +26 -0
- package/dist/templates/gitlab-ci.d.ts.map +1 -0
- package/dist/templates/gitlab-ci.js +70 -0
- package/dist/templates/index.d.ts +72 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +112 -0
- package/dist/templates/jenkins.d.ts +26 -0
- package/dist/templates/jenkins.d.ts.map +1 -0
- package/dist/templates/jenkins.js +110 -0
- package/dist/ui/banner.d.ts +31 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +84 -0
- package/dist/ui/diagnostics.d.ts +39 -0
- package/dist/ui/diagnostics.d.ts.map +1 -0
- package/dist/ui/diagnostics.js +153 -0
- package/dist/ui/spinner.d.ts +61 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +101 -0
- package/dist/ui/table.d.ts +63 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +236 -0
- package/dist/utils/client.d.ts +82 -0
- package/dist/utils/client.d.ts.map +1 -0
- package/dist/utils/client.js +128 -0
- package/dist/utils/detect-env.d.ts +59 -0
- package/dist/utils/detect-env.d.ts.map +1 -0
- package/dist/utils/detect-env.js +115 -0
- package/dist/utils/exit-codes.d.ts +47 -0
- package/dist/utils/exit-codes.d.ts.map +1 -0
- package/dist/utils/exit-codes.js +61 -0
- package/dist/utils/logger.d.ts +87 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +185 -0
- package/dist/utils/sanitize.d.ts +36 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +64 -0
- package/dist/utils/validators.d.ts +41 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +123 -0
- package/package.json +63 -0
- package/schemas/vertaaux.config.schema.json +103 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy management commands for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Provides commands to initialize, validate, and display policy files.
|
|
5
|
+
*
|
|
6
|
+
* Implements CICD-17: Policy-as-code support.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import yaml from "yaml";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import { loadPolicy, loadPolicyFile, resolveBranchPolicy, POLICY_TEMPLATES, policyJsonSchema, PolicyValidationError, PolicyLoadError, } from "../policy/index.js";
|
|
13
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
14
|
+
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
15
|
+
import { resolveCommandFormat } from "../output/formats.js";
|
|
16
|
+
/**
|
|
17
|
+
* Default policy file path.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_POLICY_FILE = "vertaa.policy.yml";
|
|
20
|
+
/**
|
|
21
|
+
* Generate a policy file with comments.
|
|
22
|
+
*/
|
|
23
|
+
function generatePolicyYaml(template) {
|
|
24
|
+
const policy = POLICY_TEMPLATES[template];
|
|
25
|
+
const assertions = policy.assertions;
|
|
26
|
+
// Build YAML with helpful comments
|
|
27
|
+
const lines = [
|
|
28
|
+
"# VertaaUX Policy Configuration",
|
|
29
|
+
"# https://vertaaux.ai/docs/policy",
|
|
30
|
+
"",
|
|
31
|
+
"# JSON Schema for IDE validation",
|
|
32
|
+
"$schema: https://vertaaux.ai/schemas/policy.json",
|
|
33
|
+
"",
|
|
34
|
+
`# Policy format version`,
|
|
35
|
+
`version: ${policy.version}`,
|
|
36
|
+
"",
|
|
37
|
+
];
|
|
38
|
+
if (policy.name) {
|
|
39
|
+
lines.push(`# Human-readable policy name`);
|
|
40
|
+
lines.push(`name: "${policy.name}"`);
|
|
41
|
+
lines.push("");
|
|
42
|
+
}
|
|
43
|
+
if (policy.description) {
|
|
44
|
+
lines.push(`# Policy description`);
|
|
45
|
+
lines.push(`description: "${policy.description}"`);
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
// Assertions section
|
|
49
|
+
lines.push("# Quality gate assertions");
|
|
50
|
+
lines.push("# All assertions must pass for the build to succeed");
|
|
51
|
+
lines.push("assertions:");
|
|
52
|
+
if (assertions.fail_on) {
|
|
53
|
+
lines.push(` # Fail on issues at or above this severity: error|warning|info`);
|
|
54
|
+
lines.push(` fail_on: ${assertions.fail_on}`);
|
|
55
|
+
}
|
|
56
|
+
if (assertions.overall_score !== undefined) {
|
|
57
|
+
lines.push(` # Minimum overall score (0-100)`);
|
|
58
|
+
lines.push(` overall_score: ${assertions.overall_score}`);
|
|
59
|
+
}
|
|
60
|
+
if (assertions.max_new_errors !== undefined) {
|
|
61
|
+
lines.push(` # Maximum new error-severity issues allowed`);
|
|
62
|
+
lines.push(` max_new_errors: ${assertions.max_new_errors}`);
|
|
63
|
+
}
|
|
64
|
+
if (assertions.max_new_warnings !== undefined) {
|
|
65
|
+
lines.push(` # Maximum new warning-severity issues allowed`);
|
|
66
|
+
lines.push(` max_new_warnings: ${assertions.max_new_warnings}`);
|
|
67
|
+
}
|
|
68
|
+
lines.push("");
|
|
69
|
+
// Branches section (if present)
|
|
70
|
+
const branches = policy.branches;
|
|
71
|
+
if (branches) {
|
|
72
|
+
lines.push("# Branch-specific policy overrides");
|
|
73
|
+
lines.push("# Use stricter rules for protected branches");
|
|
74
|
+
lines.push("branches:");
|
|
75
|
+
for (const [branchKey, branchConfig] of Object.entries(branches)) {
|
|
76
|
+
lines.push(` ${branchKey}:`);
|
|
77
|
+
if (branchConfig.pattern) {
|
|
78
|
+
lines.push(` pattern: "${branchConfig.pattern}"`);
|
|
79
|
+
}
|
|
80
|
+
if (branchConfig.assertions) {
|
|
81
|
+
lines.push(" assertions:");
|
|
82
|
+
for (const [key, value] of Object.entries(branchConfig.assertions)) {
|
|
83
|
+
lines.push(` ${key}: ${value}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
lines.push("");
|
|
88
|
+
}
|
|
89
|
+
// Bypass labels
|
|
90
|
+
if (policy.bypass_labels) {
|
|
91
|
+
lines.push("# PR labels that bypass quality gate entirely");
|
|
92
|
+
lines.push("# Use sparingly for genuine emergencies");
|
|
93
|
+
lines.push("bypass_labels:");
|
|
94
|
+
for (const label of policy.bypass_labels) {
|
|
95
|
+
lines.push(` - "${label}"`);
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
}
|
|
99
|
+
// Rule overrides placeholder
|
|
100
|
+
lines.push("# Rule-specific overrides (uncomment to customize)");
|
|
101
|
+
lines.push("# rules:");
|
|
102
|
+
lines.push("# color-contrast:");
|
|
103
|
+
lines.push("# severity: warning # Downgrade from error");
|
|
104
|
+
lines.push('# reason: "Using brand colors, manually verified"');
|
|
105
|
+
lines.push("# image-alt:");
|
|
106
|
+
lines.push("# severity: ignore # Handled by CMS");
|
|
107
|
+
lines.push("# paths:");
|
|
108
|
+
lines.push('# - "src/components/cms/**"');
|
|
109
|
+
lines.push("");
|
|
110
|
+
// Exclude paths placeholder
|
|
111
|
+
lines.push("# Paths to exclude from auditing (uncomment to customize)");
|
|
112
|
+
lines.push("# exclude_paths:");
|
|
113
|
+
lines.push('# - "src/legacy/**"');
|
|
114
|
+
lines.push('# - "vendor/**"');
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Initialize a new policy file.
|
|
119
|
+
*/
|
|
120
|
+
async function initPolicy(options) {
|
|
121
|
+
const template = options.template || "basic";
|
|
122
|
+
const outputPath = options.output || DEFAULT_POLICY_FILE;
|
|
123
|
+
const absolutePath = path.resolve(process.cwd(), outputPath);
|
|
124
|
+
// Check if file exists
|
|
125
|
+
if (fs.existsSync(absolutePath) && !options.force) {
|
|
126
|
+
console.error(chalk.red(`Policy file already exists: ${absolutePath}`));
|
|
127
|
+
console.error(chalk.dim("Use --force to overwrite."));
|
|
128
|
+
process.exit(ExitCode.ERROR);
|
|
129
|
+
}
|
|
130
|
+
// Validate template
|
|
131
|
+
if (!POLICY_TEMPLATES[template]) {
|
|
132
|
+
console.error(chalk.red(`Unknown template: ${template}`));
|
|
133
|
+
console.error(chalk.dim(`Available templates: ${Object.keys(POLICY_TEMPLATES).join(", ")}`));
|
|
134
|
+
process.exit(ExitCode.ERROR);
|
|
135
|
+
}
|
|
136
|
+
// Generate policy
|
|
137
|
+
const policyYaml = generatePolicyYaml(template);
|
|
138
|
+
// Write file
|
|
139
|
+
fs.writeFileSync(absolutePath, policyYaml, "utf-8");
|
|
140
|
+
console.error(chalk.green(`Created policy file: ${absolutePath}`));
|
|
141
|
+
console.error(chalk.dim(`Template: ${template}`));
|
|
142
|
+
console.error("");
|
|
143
|
+
console.error("Next steps:");
|
|
144
|
+
console.error(` 1. Review and customize ${outputPath}`);
|
|
145
|
+
console.error(" 2. Commit the file to your repository");
|
|
146
|
+
console.error(" 3. Run 'vertaa policy validate' to verify");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Validate a policy file.
|
|
150
|
+
*/
|
|
151
|
+
async function validatePolicyCommand(filePath, options) {
|
|
152
|
+
const quiet = options.quiet ?? false;
|
|
153
|
+
try {
|
|
154
|
+
// Load policy from path or search
|
|
155
|
+
let policy;
|
|
156
|
+
let policyPath;
|
|
157
|
+
if (filePath) {
|
|
158
|
+
policy = await loadPolicyFile(filePath);
|
|
159
|
+
policyPath = filePath;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const result = await loadPolicy();
|
|
163
|
+
policy = result.policy;
|
|
164
|
+
policyPath = result.path;
|
|
165
|
+
}
|
|
166
|
+
if (!policyPath) {
|
|
167
|
+
console.error(chalk.yellow("No policy file found. Using defaults."));
|
|
168
|
+
console.error(chalk.dim("Create a policy file with: vertaa policy init"));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Validation passed if we got here
|
|
172
|
+
if (!quiet) {
|
|
173
|
+
console.error(chalk.green(`Policy file valid: ${policyPath}`));
|
|
174
|
+
console.error("");
|
|
175
|
+
console.error("Summary:");
|
|
176
|
+
console.error(` Version: ${policy.version}`);
|
|
177
|
+
console.error(` Name: ${policy.name || "(unnamed)"}`);
|
|
178
|
+
if (policy.assertions) {
|
|
179
|
+
console.error(" Assertions:");
|
|
180
|
+
if (policy.assertions.fail_on) {
|
|
181
|
+
console.error(` - Fail on: ${policy.assertions.fail_on}`);
|
|
182
|
+
}
|
|
183
|
+
if (policy.assertions.overall_score !== undefined) {
|
|
184
|
+
console.error(` - Overall score >= ${policy.assertions.overall_score}`);
|
|
185
|
+
}
|
|
186
|
+
if (policy.assertions.max_new_errors !== undefined) {
|
|
187
|
+
console.error(` - Max new errors: ${policy.assertions.max_new_errors}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (policy.branches) {
|
|
191
|
+
console.error(` Branches: ${Object.keys(policy.branches).join(", ")}`);
|
|
192
|
+
}
|
|
193
|
+
if (policy.rules) {
|
|
194
|
+
console.error(` Rule overrides: ${Object.keys(policy.rules).length}`);
|
|
195
|
+
}
|
|
196
|
+
if (policy.bypass_labels?.length) {
|
|
197
|
+
console.error(` Bypass labels: ${policy.bypass_labels.join(", ")}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
if (error instanceof PolicyValidationError) {
|
|
203
|
+
console.error(chalk.red("Policy validation failed:"));
|
|
204
|
+
for (const err of error.errors) {
|
|
205
|
+
console.error(chalk.red(` - ${err}`));
|
|
206
|
+
}
|
|
207
|
+
process.exit(ExitCode.ERROR);
|
|
208
|
+
}
|
|
209
|
+
if (error instanceof PolicyLoadError) {
|
|
210
|
+
console.error(chalk.red(`Failed to load policy: ${error.message}`));
|
|
211
|
+
process.exit(ExitCode.ERROR);
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Show effective policy for a branch.
|
|
218
|
+
*/
|
|
219
|
+
async function showPolicy(options) {
|
|
220
|
+
const branch = options.branch || "";
|
|
221
|
+
const format = options.format || "yaml";
|
|
222
|
+
try {
|
|
223
|
+
// Load policy
|
|
224
|
+
let policy;
|
|
225
|
+
let policyPath;
|
|
226
|
+
if (options.policy) {
|
|
227
|
+
policy = await loadPolicyFile(options.policy);
|
|
228
|
+
policyPath = options.policy;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const result = await loadPolicy();
|
|
232
|
+
policy = result.policy;
|
|
233
|
+
policyPath = result.path;
|
|
234
|
+
}
|
|
235
|
+
// Resolve branch-specific overrides
|
|
236
|
+
let effectivePolicy = policy;
|
|
237
|
+
let branchInfo = "";
|
|
238
|
+
if (branch) {
|
|
239
|
+
effectivePolicy = resolveBranchPolicy(policy, branch);
|
|
240
|
+
branchInfo = ` (branch: ${branch})`;
|
|
241
|
+
}
|
|
242
|
+
// Output
|
|
243
|
+
if (policyPath) {
|
|
244
|
+
console.error(chalk.dim(`Policy: ${policyPath}${branchInfo}`));
|
|
245
|
+
console.error("");
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
console.error(chalk.dim(`Using default policy${branchInfo}`));
|
|
249
|
+
console.error("");
|
|
250
|
+
}
|
|
251
|
+
if (format === "json") {
|
|
252
|
+
writeJsonOutput(effectivePolicy, "policy-show");
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// YAML output
|
|
256
|
+
const yamlOutput = yaml.stringify(effectivePolicy, {
|
|
257
|
+
indent: 2,
|
|
258
|
+
lineWidth: 100,
|
|
259
|
+
});
|
|
260
|
+
writeOutput(yamlOutput);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
if (error instanceof PolicyValidationError) {
|
|
265
|
+
console.error(chalk.red("Policy validation failed:"));
|
|
266
|
+
for (const err of error.errors) {
|
|
267
|
+
console.error(chalk.red(` - ${err}`));
|
|
268
|
+
}
|
|
269
|
+
process.exit(ExitCode.ERROR);
|
|
270
|
+
}
|
|
271
|
+
if (error instanceof PolicyLoadError) {
|
|
272
|
+
console.error(chalk.red(`Failed to load policy: ${error.message}`));
|
|
273
|
+
process.exit(ExitCode.ERROR);
|
|
274
|
+
}
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Output JSON Schema for policy file.
|
|
280
|
+
*/
|
|
281
|
+
function outputSchema() {
|
|
282
|
+
writeJsonOutput(policyJsonSchema, "policy-schema");
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Register the policy command with the Commander program.
|
|
286
|
+
*/
|
|
287
|
+
export function registerPolicyCommand(program) {
|
|
288
|
+
const policyCmd = program
|
|
289
|
+
.command("policy")
|
|
290
|
+
.description("Manage policy-as-code configuration");
|
|
291
|
+
// policy init
|
|
292
|
+
policyCmd
|
|
293
|
+
.command("init")
|
|
294
|
+
.description("Create a new policy file")
|
|
295
|
+
.option("-t, --template <template>", "Policy template: basic|strict|lenient", "basic")
|
|
296
|
+
.option("-o, --output <path>", "Output file path", DEFAULT_POLICY_FILE)
|
|
297
|
+
.option("-f, --force", "Overwrite existing file")
|
|
298
|
+
.action(async (options) => {
|
|
299
|
+
try {
|
|
300
|
+
await initPolicy(options);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
304
|
+
process.exit(ExitCode.ERROR);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
// policy validate
|
|
308
|
+
policyCmd
|
|
309
|
+
.command("validate [file]")
|
|
310
|
+
.description("Validate a policy file")
|
|
311
|
+
.option("-q, --quiet", "Suppress output on success")
|
|
312
|
+
.action(async (filePath, options) => {
|
|
313
|
+
try {
|
|
314
|
+
await validatePolicyCommand(filePath, options);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
318
|
+
process.exit(ExitCode.ERROR);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
// policy show
|
|
322
|
+
policyCmd
|
|
323
|
+
.command("show")
|
|
324
|
+
.description("Display effective policy for a branch")
|
|
325
|
+
.option("-b, --branch <name>", "Branch to show policy for")
|
|
326
|
+
.option("-f, --format <format>", "Output format: yaml|json", "yaml")
|
|
327
|
+
.option("-p, --policy <path>", "Path to policy file")
|
|
328
|
+
.action(async (options, cmd) => {
|
|
329
|
+
try {
|
|
330
|
+
// Validate format using per-command registry
|
|
331
|
+
const machineMode = cmd.optsWithGlobals?.().machine || false;
|
|
332
|
+
const validatedFormat = resolveCommandFormat("policy-show", options.format, machineMode);
|
|
333
|
+
await showPolicy({ ...options, format: validatedFormat });
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
337
|
+
process.exit(ExitCode.ERROR);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
// policy schema
|
|
341
|
+
policyCmd
|
|
342
|
+
.command("schema")
|
|
343
|
+
.description("Output JSON Schema for policy file validation")
|
|
344
|
+
.action(() => {
|
|
345
|
+
outputSchema();
|
|
346
|
+
});
|
|
347
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Syncs local audit results to VertaaUX cloud.
|
|
5
|
+
* Enables sharing results across team members and CI runs.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
/**
|
|
9
|
+
* Register the upload command with the Commander program.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerUploadCommand(program: Command): void;
|
|
12
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyLpC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiB5D"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Syncs local audit results to VertaaUX cloud.
|
|
5
|
+
* Enables sharing results across team members and CI runs.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
import { loadToken } from "../auth/token-store.js";
|
|
12
|
+
import { getCIToken } from "../auth/ci-token.js";
|
|
13
|
+
import { resolveApiBase } from "../utils/client.js";
|
|
14
|
+
import { resolveConfig } from "../config/loader.js";
|
|
15
|
+
import { loadBaseline, DEFAULT_BASELINE_PATH } from "../baseline/manager.js";
|
|
16
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
17
|
+
/**
|
|
18
|
+
* Artifacts directory.
|
|
19
|
+
*/
|
|
20
|
+
const ARTIFACTS_DIR = ".vertaaux/artifacts";
|
|
21
|
+
/**
|
|
22
|
+
* Get authentication token from stored credentials or environment.
|
|
23
|
+
*/
|
|
24
|
+
async function getAuthToken() {
|
|
25
|
+
// Check stored token first
|
|
26
|
+
const storedToken = await loadToken();
|
|
27
|
+
if (storedToken?.accessToken) {
|
|
28
|
+
return storedToken.accessToken;
|
|
29
|
+
}
|
|
30
|
+
// Check environment
|
|
31
|
+
return getCIToken();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Handle the upload command.
|
|
35
|
+
*/
|
|
36
|
+
async function handleUpload(jobId, options) {
|
|
37
|
+
// Get auth token
|
|
38
|
+
const token = await getAuthToken();
|
|
39
|
+
if (!token) {
|
|
40
|
+
console.error(chalk.red("Error: Not authenticated."));
|
|
41
|
+
console.error("Run `vertaa login` to authenticate or set VERTAAUX_TOKEN environment variable.");
|
|
42
|
+
process.exit(ExitCode.ERROR);
|
|
43
|
+
}
|
|
44
|
+
// Load config for API base (supports --config global option)
|
|
45
|
+
const config = await resolveConfig(options.configPath);
|
|
46
|
+
const apiBase = resolveApiBase(options.base);
|
|
47
|
+
// Determine what to upload
|
|
48
|
+
const spinner = ora("Preparing upload...").start();
|
|
49
|
+
try {
|
|
50
|
+
// If no job ID, find the most recent local result
|
|
51
|
+
let targetJobId = jobId;
|
|
52
|
+
let localResultPath;
|
|
53
|
+
if (!targetJobId) {
|
|
54
|
+
// Look for recent results in artifacts directory
|
|
55
|
+
const artifactsPath = path.resolve(process.cwd(), ARTIFACTS_DIR);
|
|
56
|
+
if (fs.existsSync(artifactsPath)) {
|
|
57
|
+
const dirs = fs.readdirSync(artifactsPath).filter((d) => {
|
|
58
|
+
const stat = fs.statSync(path.join(artifactsPath, d));
|
|
59
|
+
return stat.isDirectory();
|
|
60
|
+
});
|
|
61
|
+
if (dirs.length > 0) {
|
|
62
|
+
// Get most recent by mtime
|
|
63
|
+
const sorted = dirs
|
|
64
|
+
.map((d) => ({
|
|
65
|
+
name: d,
|
|
66
|
+
mtime: fs.statSync(path.join(artifactsPath, d)).mtime,
|
|
67
|
+
}))
|
|
68
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
69
|
+
targetJobId = sorted[0].name;
|
|
70
|
+
localResultPath = path.join(artifactsPath, targetJobId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!targetJobId) {
|
|
74
|
+
spinner.fail("No job ID provided and no local results found.");
|
|
75
|
+
console.error("Run `vertaa audit --save-trace` to save local results first.");
|
|
76
|
+
process.exit(ExitCode.ERROR);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
spinner.text = `Uploading audit ${targetJobId}...`;
|
|
80
|
+
// Prepare upload payload
|
|
81
|
+
const payload = {
|
|
82
|
+
job_id: targetJobId,
|
|
83
|
+
project: options.project || path.basename(process.cwd()),
|
|
84
|
+
};
|
|
85
|
+
// Add local results if available
|
|
86
|
+
if (localResultPath && fs.existsSync(localResultPath)) {
|
|
87
|
+
const files = fs.readdirSync(localResultPath);
|
|
88
|
+
const artifacts = {};
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
const filePath = path.join(localResultPath, file);
|
|
91
|
+
const stat = fs.statSync(filePath);
|
|
92
|
+
// Skip large files (> 10MB)
|
|
93
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Read file content (base64 for binary)
|
|
97
|
+
const content = fs.readFileSync(filePath);
|
|
98
|
+
const ext = path.extname(file).toLowerCase();
|
|
99
|
+
const isBinary = [".png", ".jpg", ".jpeg", ".gif", ".zip"].includes(ext);
|
|
100
|
+
artifacts[file] = isBinary
|
|
101
|
+
? content.toString("base64")
|
|
102
|
+
: content.toString("utf-8");
|
|
103
|
+
}
|
|
104
|
+
payload.artifacts = artifacts;
|
|
105
|
+
}
|
|
106
|
+
// Upload baseline if requested
|
|
107
|
+
if (options.baseline) {
|
|
108
|
+
const baseline = await loadBaseline(DEFAULT_BASELINE_PATH);
|
|
109
|
+
if (baseline) {
|
|
110
|
+
payload.baseline = baseline;
|
|
111
|
+
spinner.text = `Uploading audit ${targetJobId} with baseline...`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Make API request
|
|
115
|
+
const response = await fetch(`${apiBase}/sync/upload`, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
"X-API-Key": token,
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify(payload),
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const error = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
125
|
+
throw new Error(error.error?.message || `HTTP ${response.status}`);
|
|
126
|
+
}
|
|
127
|
+
const result = (await response.json());
|
|
128
|
+
if (!result.success) {
|
|
129
|
+
throw new Error(result.error?.message || "Upload failed");
|
|
130
|
+
}
|
|
131
|
+
spinner.succeed("Upload complete!");
|
|
132
|
+
console.error("");
|
|
133
|
+
console.error(` Job ID: ${result.job_id}`);
|
|
134
|
+
console.error(` URL: ${chalk.cyan(result.url)}`);
|
|
135
|
+
console.error("");
|
|
136
|
+
console.error("Share this URL with your team to view the results.");
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
spinner.fail("Upload failed");
|
|
140
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
141
|
+
process.exit(ExitCode.ERROR);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Register the upload command with the Commander program.
|
|
146
|
+
*/
|
|
147
|
+
export function registerUploadCommand(program) {
|
|
148
|
+
program
|
|
149
|
+
.command("upload [job-id]")
|
|
150
|
+
.description("Upload audit results to VertaaUX cloud")
|
|
151
|
+
.option("--baseline", "Also upload baseline file")
|
|
152
|
+
.option("--project <name>", "Cloud project name (default: current directory name)")
|
|
153
|
+
.option("-b, --base <url>", "API base URL")
|
|
154
|
+
.action(async (jobId, options, command) => {
|
|
155
|
+
const globalOpts = command.optsWithGlobals();
|
|
156
|
+
await handleUpload(jobId, { ...options, configPath: globalOpts.config });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration values for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* These defaults are merged with config file values, with the config file
|
|
5
|
+
* taking precedence. CLI flags override both.
|
|
6
|
+
*
|
|
7
|
+
* Precedence: CLI flags > env vars > config file > defaults
|
|
8
|
+
*/
|
|
9
|
+
import type { VertaauxConfig } from "./schema.js";
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration values.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_CONFIG: Required<Pick<VertaauxConfig, "mode" | "output" | "baseline" | "timeout" | "interval">> & {
|
|
14
|
+
ci: NonNullable<VertaauxConfig["ci"]>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Deep merge two config objects.
|
|
18
|
+
* Target values take precedence over defaults.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mergeWithDefaults(config: VertaauxConfig): VertaauxConfig;
|
|
21
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CACnC,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC,CAC9E,GAAG;IACF,EAAE,EAAE,WAAW,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;CAgBvC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAiBxE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration values for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* These defaults are merged with config file values, with the config file
|
|
5
|
+
* taking precedence. CLI flags override both.
|
|
6
|
+
*
|
|
7
|
+
* Precedence: CLI flags > env vars > config file > defaults
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Default configuration values.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_CONFIG = {
|
|
13
|
+
mode: "basic",
|
|
14
|
+
output: {
|
|
15
|
+
format: "auto",
|
|
16
|
+
groupBy: "severity",
|
|
17
|
+
},
|
|
18
|
+
baseline: {
|
|
19
|
+
path: ".vertaaux/baseline.json",
|
|
20
|
+
autoUpdate: false,
|
|
21
|
+
},
|
|
22
|
+
ci: {
|
|
23
|
+
template: "none",
|
|
24
|
+
},
|
|
25
|
+
timeout: 60000,
|
|
26
|
+
interval: 5000,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Deep merge two config objects.
|
|
30
|
+
* Target values take precedence over defaults.
|
|
31
|
+
*/
|
|
32
|
+
export function mergeWithDefaults(config) {
|
|
33
|
+
return {
|
|
34
|
+
...DEFAULT_CONFIG,
|
|
35
|
+
...config,
|
|
36
|
+
output: {
|
|
37
|
+
...DEFAULT_CONFIG.output,
|
|
38
|
+
...config.output,
|
|
39
|
+
},
|
|
40
|
+
baseline: {
|
|
41
|
+
...DEFAULT_CONFIG.baseline,
|
|
42
|
+
...config.baseline,
|
|
43
|
+
},
|
|
44
|
+
ci: {
|
|
45
|
+
...DEFAULT_CONFIG.ci,
|
|
46
|
+
...config.ci,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader using cosmiconfig with Ajv validation.
|
|
3
|
+
*
|
|
4
|
+
* Searches for configuration in multiple formats:
|
|
5
|
+
* - .vertaaux.yml / .vertaaux.yaml
|
|
6
|
+
* - .vertaaux.json
|
|
7
|
+
* - vertaaux.config.js / vertaaux.config.mjs
|
|
8
|
+
* - package.json (vertaaux key)
|
|
9
|
+
*
|
|
10
|
+
* Validates against JSON Schema and merges with defaults.
|
|
11
|
+
*/
|
|
12
|
+
import { type ErrorObject } from "ajv";
|
|
13
|
+
import type { VertaauxConfig } from "./schema.js";
|
|
14
|
+
/**
|
|
15
|
+
* Error thrown when config validation fails.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ConfigValidationError extends Error {
|
|
18
|
+
readonly errors: ErrorObject[] | null | undefined;
|
|
19
|
+
constructor(errors: ErrorObject[] | null | undefined, filePath?: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when config file cannot be loaded.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ConfigLoadError extends Error {
|
|
25
|
+
readonly cause?: unknown | undefined;
|
|
26
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Load configuration from file and environment.
|
|
30
|
+
*
|
|
31
|
+
* Searches for config file starting from current working directory
|
|
32
|
+
* and walking up the directory tree.
|
|
33
|
+
*
|
|
34
|
+
* @param searchFrom - Directory to start search from (defaults to cwd)
|
|
35
|
+
* @returns Merged configuration with defaults
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const config = await loadConfig();
|
|
40
|
+
* console.log(config.mode); // 'basic' (default or from config)
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function loadConfig(searchFrom?: string): Promise<VertaauxConfig>;
|
|
44
|
+
/**
|
|
45
|
+
* Load configuration from a specific file path.
|
|
46
|
+
*
|
|
47
|
+
* @param filePath - Path to config file
|
|
48
|
+
* @returns Merged configuration with defaults
|
|
49
|
+
*/
|
|
50
|
+
export declare function loadConfigFile(filePath: string): Promise<VertaauxConfig>;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve configuration from explicit path or auto-detection.
|
|
53
|
+
*
|
|
54
|
+
* If an explicit path is provided, loads that file directly.
|
|
55
|
+
* Otherwise, falls back to cosmiconfig auto-detection.
|
|
56
|
+
*
|
|
57
|
+
* @param explicitPath - Optional explicit config file path (from --config flag)
|
|
58
|
+
* @returns Merged configuration with defaults
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveConfig(explicitPath?: string): Promise<VertaauxConfig>;
|
|
61
|
+
/**
|
|
62
|
+
* Clear the config cache.
|
|
63
|
+
* Useful for testing or when config files change.
|
|
64
|
+
*/
|
|
65
|
+
export declare function clearConfigCache(): void;
|
|
66
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAY,EAAE,KAAK,WAAW,EAAyB,MAAM,KAAK,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAelD;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS;gBAAxC,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,EACxD,QAAQ,CAAC,EAAE,MAAM;CAWpB;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,KAAK;aAGtB,KAAK,CAAC,EAAE,OAAO;gBAD/B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAqBD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAsC7E;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CA6BzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAWlF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|