@sfdxy/mule-lint 1.4.0 → 1.5.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 +25 -7
- package/dist/src/formatters/CsvFormatter.d.ts +6 -0
- package/dist/src/formatters/CsvFormatter.d.ts.map +1 -0
- package/dist/src/formatters/CsvFormatter.js +46 -0
- package/dist/src/formatters/CsvFormatter.js.map +1 -0
- package/dist/src/formatters/HtmlFormatter.d.ts +6 -0
- package/dist/src/formatters/HtmlFormatter.d.ts.map +1 -0
- package/dist/src/formatters/HtmlFormatter.js +407 -0
- package/dist/src/formatters/HtmlFormatter.js.map +1 -0
- package/dist/src/formatters/index.d.ts +2 -0
- package/dist/src/formatters/index.d.ts.map +1 -1
- package/dist/src/formatters/index.js +8 -0
- package/dist/src/formatters/index.js.map +1 -1
- package/dist/src/types/Config.d.ts +1 -1
- package/dist/src/types/Config.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
**Mule-Lint** is a TypeScript-based linting tool designed to enforce best practices and standards for MuleSoft applications. It provides:
|
|
21
21
|
|
|
22
22
|
- ✅ **10+ Built-in Rules** covering error handling, naming conventions, security, and logging
|
|
23
|
-
- ✅ **Multiple Output Formats** - Table
|
|
23
|
+
- ✅ **Multiple Output Formats** - Table, JSON, SARIF, HTML, CSV <!-- id: 4 -->
|
|
24
24
|
- ✅ **CI/CD Ready** - Exit codes and machine-readable output
|
|
25
25
|
- ✅ **TypeScript** - Fully typed for VS Code extension integration
|
|
26
26
|
- ✅ **Extensible** - Add custom rules for your organization
|
|
@@ -65,7 +65,7 @@ flowchart LR
|
|
|
65
65
|
B --> C["Execute Rules"]
|
|
66
66
|
C --> D["Collect Issues"]
|
|
67
67
|
D --> E["Format Output"]
|
|
68
|
-
E --> F["Table / JSON / SARIF"]
|
|
68
|
+
E --> F["Table / JSON / SARIF / HTML / CSV"]
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
|
|
@@ -75,10 +75,10 @@ flowchart LR
|
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
# Global installation
|
|
78
|
-
npm install -g mule-lint
|
|
78
|
+
npm install -g @sfdxy/mule-lint
|
|
79
79
|
|
|
80
80
|
# Or as a dev dependency
|
|
81
|
-
npm install --save-dev mule-lint
|
|
81
|
+
npm install --save-dev @sfdxy/mule-lint
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
---
|
|
@@ -109,7 +109,7 @@ mule-lint ./src/main/mule --fail-on-warning
|
|
|
109
109
|
|
|
110
110
|
| Option | Description |
|
|
111
111
|
|--------|-------------|
|
|
112
|
-
| `-f, --format <type>` | Output format: `table`, `json`, `sarif` (default: `table`) |
|
|
112
|
+
| `-f, --format <type>` | Output format: `table`, `json`, `sarif`, `html`, `csv` (default: `table`) |
|
|
113
113
|
| `-o, --output <file>` | Write output to file instead of stdout |
|
|
114
114
|
| `-c, --config <file>` | Path to configuration file |
|
|
115
115
|
| `-q, --quiet` | Show only errors (suppress warnings and info) |
|
|
@@ -259,6 +259,24 @@ Machine-readable for scripting:
|
|
|
259
259
|
}
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
+
### HTML (Human Readable)
|
|
263
|
+
|
|
264
|
+
Generates a visual report with summary cards and correct issue highlighting:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
mule-lint src/main/mule -f html -o report.html
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### CSV (Spreadsheet)
|
|
271
|
+
|
|
272
|
+
Generates a comma-separated values file for Excel import:
|
|
273
|
+
|
|
274
|
+
```csv
|
|
275
|
+
Severity,Rule,File,Line,Column,Message
|
|
276
|
+
error,MULE-001,src/main/mule/app.xml,10,5,"Global Error Handler missing"
|
|
277
|
+
warning,MULE-002,src/main/mule/app.xml,15,4,"Flow name not kebab-case"
|
|
278
|
+
```
|
|
279
|
+
|
|
262
280
|
---
|
|
263
281
|
|
|
264
282
|
## Configuration
|
|
@@ -298,7 +316,7 @@ Create a `.mulelintrc.json` file in your project root:
|
|
|
298
316
|
Import directly into your TypeScript/JavaScript projects:
|
|
299
317
|
|
|
300
318
|
```typescript
|
|
301
|
-
import { LintEngine, ALL_RULES, formatSarif } from 'mule-lint';
|
|
319
|
+
import { LintEngine, ALL_RULES, formatSarif } from '@sfdxy/mule-lint';
|
|
302
320
|
|
|
303
321
|
// Create engine with all rules
|
|
304
322
|
const engine = new LintEngine({
|
|
@@ -325,7 +343,7 @@ const issues = engine.scanContent(xmlContent, 'file.xml');
|
|
|
325
343
|
See [Extending Guide](docs/extending.md) for detailed instructions on creating custom rules.
|
|
326
344
|
|
|
327
345
|
```typescript
|
|
328
|
-
import { BaseRule, ValidationContext, Issue } from 'mule-lint';
|
|
346
|
+
import { BaseRule, ValidationContext, Issue } from '@sfdxy/mule-lint';
|
|
329
347
|
|
|
330
348
|
export class MyCustomRule extends BaseRule {
|
|
331
349
|
id = 'CUSTOM-001';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CsvFormatter.d.ts","sourceRoot":"","sources":["../../../src/formatters/CsvFormatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAiCpD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatCsv = formatCsv;
|
|
4
|
+
/**
|
|
5
|
+
* Format lint report as CSV
|
|
6
|
+
*/
|
|
7
|
+
function formatCsv(report) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
// Header
|
|
10
|
+
lines.push('Severity,Rule,File,Line,Column,Message');
|
|
11
|
+
// Files
|
|
12
|
+
for (const file of report.files) {
|
|
13
|
+
if (!file.parsed) {
|
|
14
|
+
lines.push(escapeCsvRow([
|
|
15
|
+
'error',
|
|
16
|
+
'PARSE-ERROR',
|
|
17
|
+
file.relativePath,
|
|
18
|
+
'1',
|
|
19
|
+
'1',
|
|
20
|
+
file.parseError || 'Failed to parse file'
|
|
21
|
+
]));
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
for (const issue of file.issues) {
|
|
25
|
+
lines.push(escapeCsvRow([
|
|
26
|
+
issue.severity,
|
|
27
|
+
issue.ruleId,
|
|
28
|
+
file.relativePath,
|
|
29
|
+
issue.line.toString(),
|
|
30
|
+
(issue.column || 0).toString(),
|
|
31
|
+
issue.message
|
|
32
|
+
]));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
}
|
|
37
|
+
function escapeCsvRow(fields) {
|
|
38
|
+
return fields.map(field => {
|
|
39
|
+
const escaped = field.replace(/"/g, '""');
|
|
40
|
+
if (escaped.includes(',') || escaped.includes('"') || escaped.includes('\n')) {
|
|
41
|
+
return `"${escaped}"`;
|
|
42
|
+
}
|
|
43
|
+
return escaped;
|
|
44
|
+
}).join(',');
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=CsvFormatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CsvFormatter.js","sourceRoot":"","sources":["../../../src/formatters/CsvFormatter.ts"],"names":[],"mappings":";;AAKA,8BAiCC;AApCD;;GAEG;AACH,SAAgB,SAAS,CAAC,MAAkB;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,SAAS;IACT,KAAK,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;IAErD,QAAQ;IACR,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC;gBACpB,OAAO;gBACP,aAAa;gBACb,IAAI,CAAC,YAAY;gBACjB,GAAG;gBACH,GAAG;gBACH,IAAI,CAAC,UAAU,IAAI,sBAAsB;aAC5C,CAAC,CAAC,CAAC;YACJ,SAAS;QACb,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC;gBACpB,KAAK,CAAC,QAAQ;gBACd,KAAK,CAAC,MAAM;gBACZ,IAAI,CAAC,YAAY;gBACjB,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;gBACrB,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE;gBAC9B,KAAK,CAAC,OAAO;aAChB,CAAC,CAAC,CAAC;QACR,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,YAAY,CAAC,MAAgB;IAClC,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;QACtB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3E,OAAO,IAAI,OAAO,GAAG,CAAC;QAC1B,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HtmlFormatter.d.ts","sourceRoot":"","sources":["../../../src/formatters/HtmlFormatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CA4UrD"}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatHtml = formatHtml;
|
|
4
|
+
/**
|
|
5
|
+
* Format lint report as a modern, interactive HTML page
|
|
6
|
+
*/
|
|
7
|
+
function formatHtml(report) {
|
|
8
|
+
const title = 'Mule-Lint Report';
|
|
9
|
+
const date = new Date(report.timestamp).toLocaleString();
|
|
10
|
+
// Calculate stats
|
|
11
|
+
const totalErrors = report.summary.bySeverity.error;
|
|
12
|
+
const totalWarnings = report.summary.bySeverity.warning;
|
|
13
|
+
const totalInfos = report.summary.bySeverity.info;
|
|
14
|
+
const totalIssues = totalErrors + totalWarnings + totalInfos;
|
|
15
|
+
const score = Math.max(0, 100 - (totalErrors * 5) - (totalWarnings * 1));
|
|
16
|
+
return `<!DOCTYPE html>
|
|
17
|
+
<html lang="en">
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8">
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
21
|
+
<title>${title}</title>
|
|
22
|
+
<style>
|
|
23
|
+
:root {
|
|
24
|
+
--primary: #00A1DF; /* MuleSoft Blue */
|
|
25
|
+
--primary-dark: #0077A5;
|
|
26
|
+
--success: #4CAF50;
|
|
27
|
+
--error: #F44336;
|
|
28
|
+
--warning: #FF9800;
|
|
29
|
+
--info: #2196F3;
|
|
30
|
+
--surface: #ffffff;
|
|
31
|
+
--background: #f4f6f8;
|
|
32
|
+
--text-primary: #172b4d;
|
|
33
|
+
--text-secondary: #6b778c;
|
|
34
|
+
--border: #dfe1e6;
|
|
35
|
+
--shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
body {
|
|
39
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
background-color: var(--background);
|
|
43
|
+
color: var(--text-primary);
|
|
44
|
+
line-height: 1.6;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Layout */
|
|
48
|
+
.container {
|
|
49
|
+
max-width: 1200px;
|
|
50
|
+
margin: 0 auto;
|
|
51
|
+
padding: 20px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Header */
|
|
55
|
+
header {
|
|
56
|
+
background-color: var(--surface);
|
|
57
|
+
padding: 1rem 0;
|
|
58
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
59
|
+
margin-bottom: 2rem;
|
|
60
|
+
position: sticky;
|
|
61
|
+
top: 0;
|
|
62
|
+
z-index: 100;
|
|
63
|
+
}
|
|
64
|
+
.header-content {
|
|
65
|
+
display: flex;
|
|
66
|
+
justify-content: space-between;
|
|
67
|
+
align-items: center;
|
|
68
|
+
}
|
|
69
|
+
h1 { margin: 0; color: var(--primary); font-size: 1.5rem; display: flex; align-items: center; gap: 10px; }
|
|
70
|
+
.meta { color: var(--text-secondary); font-size: 0.9em; }
|
|
71
|
+
|
|
72
|
+
/* Dashboard Grid */
|
|
73
|
+
.dashboard {
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
76
|
+
gap: 20px;
|
|
77
|
+
margin-bottom: 30px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Cards */
|
|
81
|
+
.card {
|
|
82
|
+
background: var(--surface);
|
|
83
|
+
padding: 24px;
|
|
84
|
+
border-radius: 8px;
|
|
85
|
+
box-shadow: var(--shadow);
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
transition: transform 0.2s;
|
|
91
|
+
}
|
|
92
|
+
.card:hover { transform: translateY(-2px); }
|
|
93
|
+
|
|
94
|
+
.number { font-size: 3rem; font-weight: 700; line-height: 1; margin-bottom: 0.5rem; }
|
|
95
|
+
.label { color: var(--text-secondary); font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; }
|
|
96
|
+
|
|
97
|
+
.score-ring {
|
|
98
|
+
width: 100px;
|
|
99
|
+
height: 100px;
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: center;
|
|
104
|
+
font-size: 2.5rem;
|
|
105
|
+
font-weight: bold;
|
|
106
|
+
color: var(--primary);
|
|
107
|
+
border: 8px solid var(--background);
|
|
108
|
+
position: relative;
|
|
109
|
+
background: conic-gradient(var(--primary) calc(var(--score) * 1%), var(--border) 0);
|
|
110
|
+
}
|
|
111
|
+
.score-inner {
|
|
112
|
+
width: 80px;
|
|
113
|
+
height: 80px;
|
|
114
|
+
background: var(--surface);
|
|
115
|
+
border-radius: 50%;
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
position: absolute;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Filters */
|
|
123
|
+
.controls {
|
|
124
|
+
display: flex;
|
|
125
|
+
gap: 15px;
|
|
126
|
+
margin-bottom: 20px;
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
}
|
|
129
|
+
.search-box {
|
|
130
|
+
flex: 1;
|
|
131
|
+
padding: 10px 15px;
|
|
132
|
+
border: 1px solid var(--border);
|
|
133
|
+
border-radius: 6px;
|
|
134
|
+
font-size: 1rem;
|
|
135
|
+
min-width: 200px;
|
|
136
|
+
}
|
|
137
|
+
.filter-btn {
|
|
138
|
+
padding: 8px 16px;
|
|
139
|
+
border: 1px solid var(--border);
|
|
140
|
+
background: var(--surface);
|
|
141
|
+
border-radius: 6px;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
font-weight: 500;
|
|
144
|
+
color: var(--text-secondary);
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
gap: 6px;
|
|
148
|
+
}
|
|
149
|
+
.filter-btn.active {
|
|
150
|
+
background: var(--primary);
|
|
151
|
+
color: white;
|
|
152
|
+
border-color: var(--primary);
|
|
153
|
+
}
|
|
154
|
+
.filter-btn:hover:not(.active) { background: #f0f0f0; }
|
|
155
|
+
|
|
156
|
+
/* File List */
|
|
157
|
+
.file-section {
|
|
158
|
+
background: var(--surface);
|
|
159
|
+
border-radius: 8px;
|
|
160
|
+
box-shadow: var(--shadow);
|
|
161
|
+
margin-bottom: 20px;
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
}
|
|
164
|
+
.file-header {
|
|
165
|
+
padding: 15px 20px;
|
|
166
|
+
background: #fafafa;
|
|
167
|
+
border-bottom: 1px solid var(--border);
|
|
168
|
+
display: flex;
|
|
169
|
+
justify-content: space-between;
|
|
170
|
+
align-items: center;
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
user-select: none;
|
|
173
|
+
}
|
|
174
|
+
.file-header:hover { background: #f0f0f0; }
|
|
175
|
+
.file-path { font-weight: 600; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; color: var(--text-primary); }
|
|
176
|
+
.badge-group { display: flex; gap: 8px; }
|
|
177
|
+
.badge { padding: 4px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; color: white; min-width: 20px; text-align: center; }
|
|
178
|
+
.badge.error { background: var(--error); }
|
|
179
|
+
.badge.warning { background: var(--warning); }
|
|
180
|
+
.badge.info { background: var(--info); }
|
|
181
|
+
|
|
182
|
+
/* Issues Table */
|
|
183
|
+
.issues-table {
|
|
184
|
+
width: 100%;
|
|
185
|
+
border-collapse: collapse;
|
|
186
|
+
display: table; /* Default visible */
|
|
187
|
+
}
|
|
188
|
+
.issues-table.collapsed { display: none; }
|
|
189
|
+
|
|
190
|
+
th, td {
|
|
191
|
+
text-align: left;
|
|
192
|
+
padding: 12px 20px;
|
|
193
|
+
border-bottom: 1px solid var(--border);
|
|
194
|
+
}
|
|
195
|
+
th { background: #f9f9f9; color: var(--text-secondary); font-weight: 600; font-size: 0.85rem; text-transform: uppercase; }
|
|
196
|
+
tr:last-child td { border-bottom: none; }
|
|
197
|
+
|
|
198
|
+
.severity-cell { font-weight: 700; text-transform: uppercase; font-size: 0.8rem; }
|
|
199
|
+
.location { font-family: monospace; color: var(--text-secondary); }
|
|
200
|
+
.rule-pill {
|
|
201
|
+
display: inline-block;
|
|
202
|
+
background: #eef2f5;
|
|
203
|
+
padding: 2px 8px;
|
|
204
|
+
border-radius: 4px;
|
|
205
|
+
font-size: 0.8rem;
|
|
206
|
+
color: var(--text-secondary);
|
|
207
|
+
font-family: monospace;
|
|
208
|
+
border: 1px solid #dce1e6;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.color-error { color: var(--error); }
|
|
212
|
+
.color-warning { color: var(--warning); }
|
|
213
|
+
.color-info { color: var(--info); }
|
|
214
|
+
|
|
215
|
+
.empty-state {
|
|
216
|
+
text-align: center;
|
|
217
|
+
padding: 60px;
|
|
218
|
+
color: var(--text-secondary);
|
|
219
|
+
}
|
|
220
|
+
.empty-icon { font-size: 3rem; margin-bottom: 1rem; }
|
|
221
|
+
|
|
222
|
+
/* Responsive */
|
|
223
|
+
@media (max-width: 768px) {
|
|
224
|
+
.file-header { flex-direction: column; align-items: flex-start; gap: 10px; }
|
|
225
|
+
.badge-group { align-self: flex-start; }
|
|
226
|
+
}
|
|
227
|
+
</style>
|
|
228
|
+
</head>
|
|
229
|
+
<body>
|
|
230
|
+
<header>
|
|
231
|
+
<div class="container header-content">
|
|
232
|
+
<div>
|
|
233
|
+
<h1>
|
|
234
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
|
|
235
|
+
${title}
|
|
236
|
+
</h1>
|
|
237
|
+
<div class="meta">Generated on ${date}</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div>
|
|
240
|
+
<a href="#" onclick="window.print()" style="color: var(--primary); text-decoration: none; font-weight: 500;">Download / Print</a>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</header>
|
|
244
|
+
|
|
245
|
+
<div class="container">
|
|
246
|
+
<!-- Dashboard -->
|
|
247
|
+
<div class="dashboard">
|
|
248
|
+
<div class="card">
|
|
249
|
+
<div class="score-ring" style="--score: ${score}">
|
|
250
|
+
<div class="score-inner">${score}</div>
|
|
251
|
+
</div>
|
|
252
|
+
<span class="label" style="margin-top: 10px;">Health Score</span>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="card">
|
|
255
|
+
<span class="number" style="color: var(--error)">${totalErrors}</span>
|
|
256
|
+
<span class="label">Errors</span>
|
|
257
|
+
</div>
|
|
258
|
+
<div class="card">
|
|
259
|
+
<span class="number" style="color: var(--warning)">${totalWarnings}</span>
|
|
260
|
+
<span class="label">Warnings</span>
|
|
261
|
+
</div>
|
|
262
|
+
<div class="card">
|
|
263
|
+
<span class="number" style="color: default">${report.files.length}</span>
|
|
264
|
+
<span class="label">Files Scanned</span>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<!-- Controls -->
|
|
269
|
+
<div class="controls">
|
|
270
|
+
<input type="text" id="searchInput" class="search-box" placeholder="Search files, rules, or messages..." onkeyup="filterIssues()">
|
|
271
|
+
|
|
272
|
+
<button class="filter-btn active" onclick="toggleFilter('all', this)" id="btn-all">All</button>
|
|
273
|
+
<button class="filter-btn" onclick="toggleFilter('error', this)" id="btn-error">Errors <span class="badge error">${totalErrors}</span></button>
|
|
274
|
+
<button class="filter-btn" onclick="toggleFilter('warning', this)" id="btn-warning">Warnings <span class="badge warning">${totalWarnings}</span></button>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<!-- File List -->
|
|
278
|
+
<div id="report-content">
|
|
279
|
+
${renderFiles(report)}
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
${totalIssues === 0 && report.summary.parseErrors === 0 ? `
|
|
283
|
+
<div class="empty-state">
|
|
284
|
+
<div class="empty-icon">🎉</div>
|
|
285
|
+
<h2>No issues found!</h2>
|
|
286
|
+
<p>Your MuleSoft code looks clean and compliant.</p>
|
|
287
|
+
</div>
|
|
288
|
+
` : ''}
|
|
289
|
+
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<script>
|
|
293
|
+
let currentFilter = 'all';
|
|
294
|
+
|
|
295
|
+
function toggleFilter(filter, btn) {
|
|
296
|
+
currentFilter = filter;
|
|
297
|
+
|
|
298
|
+
// Update buttons
|
|
299
|
+
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
|
300
|
+
btn.classList.add('active');
|
|
301
|
+
|
|
302
|
+
filterIssues();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function filterIssues() {
|
|
306
|
+
const search = document.getElementById('searchInput').value.toLowerCase();
|
|
307
|
+
const fileSections = document.querySelectorAll('.file-section');
|
|
308
|
+
|
|
309
|
+
fileSections.forEach(section => {
|
|
310
|
+
const text = section.innerText.toLowerCase();
|
|
311
|
+
const matchesSearch = text.includes(search);
|
|
312
|
+
|
|
313
|
+
// For filtering by severity, we check if the section has relevant issues
|
|
314
|
+
// This is a simplified approach: we either show or hide the whole file if it matches
|
|
315
|
+
// Ideally we filter rows, but file-level hiding is often better for overview
|
|
316
|
+
|
|
317
|
+
let matchesFilter = true;
|
|
318
|
+
if (currentFilter !== 'all') {
|
|
319
|
+
matchesFilter = section.dataset.has.includes(currentFilter);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (matchesSearch && matchesFilter) {
|
|
323
|
+
section.style.display = 'block';
|
|
324
|
+
} else {
|
|
325
|
+
section.style.display = 'none';
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function toggleFile(header) {
|
|
331
|
+
const table = header.nextElementSibling;
|
|
332
|
+
table.classList.toggle('collapsed');
|
|
333
|
+
}
|
|
334
|
+
</script>
|
|
335
|
+
</body>
|
|
336
|
+
</html>`;
|
|
337
|
+
}
|
|
338
|
+
function renderFiles(report) {
|
|
339
|
+
return report.files
|
|
340
|
+
.filter(f => f.issues.length > 0 || !f.parsed)
|
|
341
|
+
.map(file => {
|
|
342
|
+
const errorCount = file.issues.filter(i => i.severity === 'error').length + (file.parsed ? 0 : 1);
|
|
343
|
+
const warningCount = file.issues.filter(i => i.severity === 'warning').length;
|
|
344
|
+
const infoCount = file.issues.filter(i => i.severity === 'info').length;
|
|
345
|
+
const hasTypes = [];
|
|
346
|
+
if (errorCount > 0)
|
|
347
|
+
hasTypes.push('error');
|
|
348
|
+
if (warningCount > 0)
|
|
349
|
+
hasTypes.push('warning');
|
|
350
|
+
if (infoCount > 0)
|
|
351
|
+
hasTypes.push('info');
|
|
352
|
+
return `<div class="file-section" data-has="${hasTypes.join(' ')}">
|
|
353
|
+
<div class="file-header" onclick="toggleFile(this)">
|
|
354
|
+
<span class="file-path">${file.relativePath}</span>
|
|
355
|
+
<div class="badge-group">
|
|
356
|
+
${!file.parsed ? '<span class="badge error">PARSE</span>' : ''}
|
|
357
|
+
${errorCount > 0 ? `<span class="badge error">${errorCount}</span>` : ''}
|
|
358
|
+
${warningCount > 0 ? `<span class="badge warning">${warningCount}</span>` : ''}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
<table class="issues-table">
|
|
362
|
+
<thead>
|
|
363
|
+
<tr>
|
|
364
|
+
<th width="80">Severity</th>
|
|
365
|
+
<th width="100">Location</th>
|
|
366
|
+
<th>Message</th>
|
|
367
|
+
<th width="120">Rule</th>
|
|
368
|
+
</tr>
|
|
369
|
+
</thead>
|
|
370
|
+
<tbody>
|
|
371
|
+
${!file.parsed ? renderParseError(file) : ''}
|
|
372
|
+
${file.issues.map(issue => renderIssue(issue)).join('')}
|
|
373
|
+
</tbody>
|
|
374
|
+
</table>
|
|
375
|
+
</div>`;
|
|
376
|
+
}).join('');
|
|
377
|
+
}
|
|
378
|
+
function renderParseError(file) {
|
|
379
|
+
return `<tr>
|
|
380
|
+
<td class="severity-cell color-error">ERROR</td>
|
|
381
|
+
<td class="location">1:1</td>
|
|
382
|
+
<td>
|
|
383
|
+
<div><strong>Failed to parse XML file</strong></div>
|
|
384
|
+
<div style="font-size: 0.9em; margin-top: 4px; color: var(--text-secondary);">
|
|
385
|
+
${file.parseError || 'Unknown error'}
|
|
386
|
+
</div>
|
|
387
|
+
</td>
|
|
388
|
+
<td><span class="rule-pill">PARSE-ERROR</span></td>
|
|
389
|
+
</tr>`;
|
|
390
|
+
}
|
|
391
|
+
function renderIssue(issue) {
|
|
392
|
+
return `<tr>
|
|
393
|
+
<td class="severity-cell color-${issue.severity}">${issue.severity}</td>
|
|
394
|
+
<td class="location">${issue.line}:${issue.column || 0}</td>
|
|
395
|
+
<td>${escapeHtml(issue.message)}</td>
|
|
396
|
+
<td><span class="rule-pill">${issue.ruleId}</span></td>
|
|
397
|
+
</tr>`;
|
|
398
|
+
}
|
|
399
|
+
function escapeHtml(unsafe) {
|
|
400
|
+
return unsafe
|
|
401
|
+
.replace(/&/g, "&")
|
|
402
|
+
.replace(/</g, "<")
|
|
403
|
+
.replace(/>/g, ">")
|
|
404
|
+
.replace(/"/g, """)
|
|
405
|
+
.replace(/'/g, "'");
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=HtmlFormatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HtmlFormatter.js","sourceRoot":"","sources":["../../../src/formatters/HtmlFormatter.ts"],"names":[],"mappings":";;AAMA,gCA4UC;AA/UD;;GAEG;AACH,SAAgB,UAAU,CAAC,MAAkB;IACzC,MAAM,KAAK,GAAG,kBAAkB,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,CAAC;IAEzD,kBAAkB;IAClB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC;IACpD,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;IACxD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;IAClD,MAAM,WAAW,GAAG,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC;IAEzE,OAAO;;;;;aAKE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAsNI,KAAK;;iDAEsB,IAAI;;;;;;;;;;;;0DAYK,KAAK;+CAChB,KAAK;;;;;mEAKe,WAAW;;;;qEAIT,aAAa;;;;8DAIpB,MAAM,CAAC,KAAK,CAAC,MAAM;;;;;;;;;;+HAU8C,WAAW;uIACH,aAAa;;;;;cAKtI,WAAW,CAAC,MAAM,CAAC;;;UAGvB,WAAW,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC;;;;;;SAMzD,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgDN,CAAC;AACT,CAAC;AAED,SAAS,WAAW,CAAC,MAAkB;IACnC,OAAO,MAAM,CAAC,KAAK;SACd,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;SAC7C,GAAG,CAAC,IAAI,CAAC,EAAE;QACR,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClG,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;QAC9E,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;QAExE,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,IAAI,UAAU,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,YAAY,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,SAAS,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEzC,OAAO,uCAAuC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;;8CAE9B,IAAI,CAAC,YAAY;;0BAErC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,wCAAwC,CAAC,CAAC,CAAC,EAAE;0BAC5D,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,6BAA6B,UAAU,SAAS,CAAC,CAAC,CAAC,EAAE;0BACtE,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,+BAA+B,YAAY,SAAS,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;0BAa5E,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;0BAC1C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;mBAG5D,CAAC;IACZ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAS;IAC/B,OAAO;;;;;;kBAMO,IAAI,CAAC,UAAU,IAAI,eAAe;;;;UAI1C,CAAC;AACX,CAAC;AAED,SAAS,WAAW,CAAC,KAAY;IAC7B,OAAO;yCAC8B,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;+BAC3C,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;cAChD,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC;sCACD,KAAK,CAAC,MAAM;UACxC,CAAC;AACX,CAAC;AAED,SAAS,UAAU,CAAC,MAAc;IAC9B,OAAO,MAAM;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './TableFormatter';
|
|
2
2
|
export * from './JsonFormatter';
|
|
3
3
|
export * from './SarifFormatter';
|
|
4
|
+
export * from './HtmlFormatter';
|
|
5
|
+
export * from './CsvFormatter';
|
|
4
6
|
import { LintReport } from '../types/Report';
|
|
5
7
|
import { FormatterType } from '../types/Config';
|
|
6
8
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/formatters/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/formatters/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQhD;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAiBtE"}
|
|
@@ -18,10 +18,14 @@ exports.format = format;
|
|
|
18
18
|
__exportStar(require("./TableFormatter"), exports);
|
|
19
19
|
__exportStar(require("./JsonFormatter"), exports);
|
|
20
20
|
__exportStar(require("./SarifFormatter"), exports);
|
|
21
|
+
__exportStar(require("./HtmlFormatter"), exports);
|
|
22
|
+
__exportStar(require("./CsvFormatter"), exports);
|
|
21
23
|
const rules_1 = require("../rules");
|
|
22
24
|
const TableFormatter_1 = require("./TableFormatter");
|
|
23
25
|
const JsonFormatter_1 = require("./JsonFormatter");
|
|
24
26
|
const SarifFormatter_1 = require("./SarifFormatter");
|
|
27
|
+
const HtmlFormatter_1 = require("./HtmlFormatter");
|
|
28
|
+
const CsvFormatter_1 = require("./CsvFormatter");
|
|
25
29
|
/**
|
|
26
30
|
* Format a lint report using the specified formatter
|
|
27
31
|
*/
|
|
@@ -33,6 +37,10 @@ function format(report, type) {
|
|
|
33
37
|
return (0, JsonFormatter_1.formatJson)(report);
|
|
34
38
|
case 'sarif':
|
|
35
39
|
return (0, SarifFormatter_1.formatSarif)(report, rules_1.ALL_RULES);
|
|
40
|
+
case 'html':
|
|
41
|
+
return (0, HtmlFormatter_1.formatHtml)(report);
|
|
42
|
+
case 'csv':
|
|
43
|
+
return (0, CsvFormatter_1.formatCsv)(report);
|
|
36
44
|
default: {
|
|
37
45
|
const _exhaustiveCheck = type;
|
|
38
46
|
throw new Error(`Unknown formatter type: ${String(_exhaustiveCheck)}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/formatters/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/formatters/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAkBA,wBAiBC;AAnCD,mDAAiC;AACjC,kDAAgC;AAChC,mDAAiC;AACjC,kDAAgC;AAChC,iDAA+B;AAI/B,oCAAqC;AACrC,qDAA+C;AAC/C,mDAA6C;AAC7C,qDAA+C;AAC/C,mDAA6C;AAC7C,iDAA2C;AAE3C;;GAEG;AACH,SAAgB,MAAM,CAAC,MAAkB,EAAE,IAAmB;IAC1D,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,OAAO;YACR,OAAO,IAAA,4BAAW,EAAC,MAAM,CAAC,CAAC;QAC/B,KAAK,MAAM;YACP,OAAO,IAAA,0BAAU,EAAC,MAAM,CAAC,CAAC;QAC9B,KAAK,OAAO;YACR,OAAO,IAAA,4BAAW,EAAC,MAAM,EAAE,iBAAS,CAAC,CAAC;QAC1C,KAAK,MAAM;YACP,OAAO,IAAA,0BAAU,EAAC,MAAM,CAAC,CAAC;QAC9B,KAAK,KAAK;YACN,OAAO,IAAA,wBAAS,EAAC,MAAM,CAAC,CAAC;QAC7B,OAAO,CAAC,CAAC,CAAC;YACN,MAAM,gBAAgB,GAAU,IAAI,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAC3E,CAAC;IACL,CAAC;AACL,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../../src/types/Config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../../src/types/Config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE5B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;IAE5C,yCAAyC;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB,yCAAyC;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,mCAAmC;IACnC,gBAAgB,EAAE,aAAa,CAAC;IAEhC,mCAAmC;IACnC,aAAa,EAAE,OAAO,CAAC;IAEvB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,UAU5B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uBAAuB;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB"}
|