@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 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 (human), JSON (scripts), SARIF (AI agents/VS Code)
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,6 @@
1
+ import { LintReport } from '../types/Report';
2
+ /**
3
+ * Format lint report as CSV
4
+ */
5
+ export declare function formatCsv(report: LintReport): string;
6
+ //# sourceMappingURL=CsvFormatter.d.ts.map
@@ -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,6 @@
1
+ import { LintReport } from '../types/Report';
2
+ /**
3
+ * Format lint report as a modern, interactive HTML page
4
+ */
5
+ export declare function formatHtml(report: LintReport): string;
6
+ //# sourceMappingURL=HtmlFormatter.d.ts.map
@@ -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, "&amp;")
402
+ .replace(/</g, "&lt;")
403
+ .replace(/>/g, "&gt;")
404
+ .replace(/"/g, "&quot;")
405
+ .replace(/'/g, "&#039;");
406
+ }
407
+ //# sourceMappingURL=HtmlFormatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HtmlFormatter.js","sourceRoot":"","sources":["../../../src/formatters/HtmlFormatter.ts"],"names":[],"mappings":"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"}
@@ -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;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAMhD;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAatE"}
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":";;;;;;;;;;;;;;;;AAcA,wBAaC;AA3BD,mDAAiC;AACjC,kDAAgC;AAChC,mDAAiC;AAIjC,oCAAqC;AACrC,qDAA+C;AAC/C,mDAA6C;AAC7C,qDAA+C;AAE/C;;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,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
+ {"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"}
@@ -2,7 +2,7 @@ import { RuleConfig } from './Rule';
2
2
  /**
3
3
  * Formatter type for output
4
4
  */
5
- export type FormatterType = 'table' | 'json' | 'sarif';
5
+ export type FormatterType = 'table' | 'json' | 'sarif' | 'html' | 'csv';
6
6
  /**
7
7
  * Main configuration for mule-lint
8
8
  */
@@ -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;AAEvD;;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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfdxy/mule-lint",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Static analysis tool for MuleSoft applications - supports humans, AI agents, and CI/CD pipelines",
5
5
  "author": "Avinava",
6
6
  "license": "MIT",