@cisco_open/linting-orchestrator 1.0.0-rc.4
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/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +43 -0
- package/build/cli/api-client.d.ts +170 -0
- package/build/cli/api-client.d.ts.map +1 -0
- package/build/cli/api-client.js +284 -0
- package/build/cli/api-client.js.map +1 -0
- package/build/cli/commands/agents.d.ts +7 -0
- package/build/cli/commands/agents.d.ts.map +1 -0
- package/build/cli/commands/agents.js +694 -0
- package/build/cli/commands/agents.js.map +1 -0
- package/build/cli/commands/completion.d.ts +9 -0
- package/build/cli/commands/completion.d.ts.map +1 -0
- package/build/cli/commands/completion.js +177 -0
- package/build/cli/commands/completion.js.map +1 -0
- package/build/cli/commands/config.d.ts +10 -0
- package/build/cli/commands/config.d.ts.map +1 -0
- package/build/cli/commands/config.js +284 -0
- package/build/cli/commands/config.js.map +1 -0
- package/build/cli/commands/health.d.ts +11 -0
- package/build/cli/commands/health.d.ts.map +1 -0
- package/build/cli/commands/health.js +38 -0
- package/build/cli/commands/health.js.map +1 -0
- package/build/cli/commands/help.d.ts +6 -0
- package/build/cli/commands/help.d.ts.map +1 -0
- package/build/cli/commands/help.js +20 -0
- package/build/cli/commands/help.js.map +1 -0
- package/build/cli/commands/history.d.ts +11 -0
- package/build/cli/commands/history.d.ts.map +1 -0
- package/build/cli/commands/history.js +50 -0
- package/build/cli/commands/history.js.map +1 -0
- package/build/cli/commands/jobs.d.ts +12 -0
- package/build/cli/commands/jobs.d.ts.map +1 -0
- package/build/cli/commands/jobs.js +84 -0
- package/build/cli/commands/jobs.js.map +1 -0
- package/build/cli/commands/lint.d.ts +15 -0
- package/build/cli/commands/lint.d.ts.map +1 -0
- package/build/cli/commands/lint.js +384 -0
- package/build/cli/commands/lint.js.map +1 -0
- package/build/cli/commands/ps.d.ts +8 -0
- package/build/cli/commands/ps.d.ts.map +1 -0
- package/build/cli/commands/ps.js +74 -0
- package/build/cli/commands/ps.js.map +1 -0
- package/build/cli/commands/reproduce.d.ts +9 -0
- package/build/cli/commands/reproduce.d.ts.map +1 -0
- package/build/cli/commands/reproduce.js +31 -0
- package/build/cli/commands/reproduce.js.map +1 -0
- package/build/cli/commands/reset.d.ts +5 -0
- package/build/cli/commands/reset.d.ts.map +1 -0
- package/build/cli/commands/reset.js +13 -0
- package/build/cli/commands/reset.js.map +1 -0
- package/build/cli/commands/results.d.ts +13 -0
- package/build/cli/commands/results.d.ts.map +1 -0
- package/build/cli/commands/results.js +129 -0
- package/build/cli/commands/results.js.map +1 -0
- package/build/cli/commands/rulesets/check.d.ts +12 -0
- package/build/cli/commands/rulesets/check.d.ts.map +1 -0
- package/build/cli/commands/rulesets/check.js +226 -0
- package/build/cli/commands/rulesets/check.js.map +1 -0
- package/build/cli/commands/rulesets/index.d.ts +5 -0
- package/build/cli/commands/rulesets/index.d.ts.map +1 -0
- package/build/cli/commands/rulesets/index.js +6 -0
- package/build/cli/commands/rulesets/index.js.map +1 -0
- package/build/cli/commands/rulesets/view.d.ts +16 -0
- package/build/cli/commands/rulesets/view.d.ts.map +1 -0
- package/build/cli/commands/rulesets/view.js +100 -0
- package/build/cli/commands/rulesets/view.js.map +1 -0
- package/build/cli/commands/start.d.ts +16 -0
- package/build/cli/commands/start.d.ts.map +1 -0
- package/build/cli/commands/start.js +167 -0
- package/build/cli/commands/start.js.map +1 -0
- package/build/cli/commands/status.d.ts +9 -0
- package/build/cli/commands/status.d.ts.map +1 -0
- package/build/cli/commands/status.js +46 -0
- package/build/cli/commands/status.js.map +1 -0
- package/build/cli/commands/stop.d.ts +11 -0
- package/build/cli/commands/stop.d.ts.map +1 -0
- package/build/cli/commands/stop.js +78 -0
- package/build/cli/commands/stop.js.map +1 -0
- package/build/cli/config-manager.d.ts +134 -0
- package/build/cli/config-manager.d.ts.map +1 -0
- package/build/cli/config-manager.js +288 -0
- package/build/cli/config-manager.js.map +1 -0
- package/build/cli/formatters.d.ts +62 -0
- package/build/cli/formatters.d.ts.map +1 -0
- package/build/cli/formatters.js +715 -0
- package/build/cli/formatters.js.map +1 -0
- package/build/cli/history-manager.d.ts +97 -0
- package/build/cli/history-manager.d.ts.map +1 -0
- package/build/cli/history-manager.js +201 -0
- package/build/cli/history-manager.js.map +1 -0
- package/build/cli/index.d.ts +16 -0
- package/build/cli/index.d.ts.map +1 -0
- package/build/cli/index.js +335 -0
- package/build/cli/index.js.map +1 -0
- package/build/cli/list-rulesets.d.ts +15 -0
- package/build/cli/list-rulesets.d.ts.map +1 -0
- package/build/cli/list-rulesets.js +193 -0
- package/build/cli/list-rulesets.js.map +1 -0
- package/build/cli/utils/connection-error.d.ts +9 -0
- package/build/cli/utils/connection-error.d.ts.map +1 -0
- package/build/cli/utils/connection-error.js +30 -0
- package/build/cli/utils/connection-error.js.map +1 -0
- package/build/cli/utils/embedded-server.d.ts +21 -0
- package/build/cli/utils/embedded-server.d.ts.map +1 -0
- package/build/cli/utils/embedded-server.js +61 -0
- package/build/cli/utils/embedded-server.js.map +1 -0
- package/build/cli/utils/mode-validator.d.ts +13 -0
- package/build/cli/utils/mode-validator.d.ts.map +1 -0
- package/build/cli/utils/mode-validator.js +31 -0
- package/build/cli/utils/mode-validator.js.map +1 -0
- package/build/cli/utils/port-checker.d.ts +20 -0
- package/build/cli/utils/port-checker.d.ts.map +1 -0
- package/build/cli/utils/port-checker.js +49 -0
- package/build/cli/utils/port-checker.js.map +1 -0
- package/build/config.d.ts +57 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +527 -0
- package/build/config.js.map +1 -0
- package/build/document-accessor.d.ts +79 -0
- package/build/document-accessor.d.ts.map +1 -0
- package/build/document-accessor.js +148 -0
- package/build/document-accessor.js.map +1 -0
- package/build/formatters/reproduce-markdown.d.ts +14 -0
- package/build/formatters/reproduce-markdown.d.ts.map +1 -0
- package/build/formatters/reproduce-markdown.js +182 -0
- package/build/formatters/reproduce-markdown.js.map +1 -0
- package/build/formatters/sarif-builder.d.ts +86 -0
- package/build/formatters/sarif-builder.d.ts.map +1 -0
- package/build/formatters/sarif-builder.js +276 -0
- package/build/formatters/sarif-builder.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +174 -0
- package/build/index.js.map +1 -0
- package/build/logger.d.ts +38 -0
- package/build/logger.d.ts.map +1 -0
- package/build/logger.js +105 -0
- package/build/logger.js.map +1 -0
- package/build/mock-server.d.ts +2 -0
- package/build/mock-server.d.ts.map +1 -0
- package/build/mock-server.js +290 -0
- package/build/mock-server.js.map +1 -0
- package/build/orchestrator.d.ts +149 -0
- package/build/orchestrator.d.ts.map +1 -0
- package/build/orchestrator.js +874 -0
- package/build/orchestrator.js.map +1 -0
- package/build/ruleset-loader.d.ts +79 -0
- package/build/ruleset-loader.d.ts.map +1 -0
- package/build/ruleset-loader.js +514 -0
- package/build/ruleset-loader.js.map +1 -0
- package/build/schemas.d.ts +2568 -0
- package/build/schemas.d.ts.map +1 -0
- package/build/schemas.js +674 -0
- package/build/schemas.js.map +1 -0
- package/build/server.d.ts +39 -0
- package/build/server.d.ts.map +1 -0
- package/build/server.js +834 -0
- package/build/server.js.map +1 -0
- package/build/storage/memory-storage.d.ts +190 -0
- package/build/storage/memory-storage.d.ts.map +1 -0
- package/build/storage/memory-storage.js +629 -0
- package/build/storage/memory-storage.js.map +1 -0
- package/build/storage/redis-storage.d.ts +134 -0
- package/build/storage/redis-storage.d.ts.map +1 -0
- package/build/storage/redis-storage.js +236 -0
- package/build/storage/redis-storage.js.map +1 -0
- package/build/storage/storage-adapter.d.ts +189 -0
- package/build/storage/storage-adapter.d.ts.map +1 -0
- package/build/storage/storage-adapter.js +36 -0
- package/build/storage/storage-adapter.js.map +1 -0
- package/build/types.d.ts +981 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +5 -0
- package/build/types.js.map +1 -0
- package/build/utils/version.d.ts +40 -0
- package/build/utils/version.d.ts.map +1 -0
- package/build/utils/version.js +94 -0
- package/build/utils/version.js.map +1 -0
- package/build/validation.d.ts +95 -0
- package/build/validation.d.ts.map +1 -0
- package/build/validation.js +150 -0
- package/build/validation.js.map +1 -0
- package/build/worker-pool.d.ts +137 -0
- package/build/worker-pool.d.ts.map +1 -0
- package/build/worker-pool.js +549 -0
- package/build/worker-pool.js.map +1 -0
- package/build/worker.d.ts +2 -0
- package/build/worker.d.ts.map +1 -0
- package/build/worker.js +427 -0
- package/build/worker.js.map +1 -0
- package/package.json +110 -0
- package/rulesets/CHANGELOG.md +25 -0
- package/rulesets/config/rulesets.yaml +96 -0
- package/rulesets/sources/README.md +47 -0
- package/rulesets/sources/example/oas-recommended/v1.0.0/ruleset.yaml +6 -0
- package/rulesets/sources/example/oas-recommended/v2.0.0/ruleset.yaml +14 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatters for orchestrator CLI output
|
|
3
|
+
* Handles color coding, tables, and summary displays
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import Table from 'cli-table3';
|
|
7
|
+
/**
|
|
8
|
+
* Ensure terminal colors are reset at end of output
|
|
9
|
+
* This prevents terminal corruption when piping to more/less and interrupting
|
|
10
|
+
*/
|
|
11
|
+
function ensureReset(output) {
|
|
12
|
+
// Always end with a reset sequence to ensure terminal is left in clean state
|
|
13
|
+
return output + '\x1b[0m';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get colored severity indicator
|
|
17
|
+
*/
|
|
18
|
+
export function formatSeverity(severity) {
|
|
19
|
+
const severityNum = typeof severity === 'number' ? severity :
|
|
20
|
+
severity === 'error' ? 0 : severity === 'warn' ? 1 : severity === 'info' ? 2 : 3;
|
|
21
|
+
switch (severityNum) {
|
|
22
|
+
case 0: // error
|
|
23
|
+
return chalk.red('error');
|
|
24
|
+
case 1: // warning
|
|
25
|
+
return chalk.yellow('warning');
|
|
26
|
+
case 2: // info
|
|
27
|
+
return chalk.blue('info');
|
|
28
|
+
case 3: // hint
|
|
29
|
+
return chalk.gray('hint');
|
|
30
|
+
default:
|
|
31
|
+
return String(severity);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get severity icon
|
|
36
|
+
*/
|
|
37
|
+
export function getSeverityIcon(severity) {
|
|
38
|
+
const severityNum = typeof severity === 'number' ? severity :
|
|
39
|
+
severity === 'error' ? 0 : severity === 'warn' ? 1 : severity === 'info' ? 2 : 3;
|
|
40
|
+
switch (severityNum) {
|
|
41
|
+
case 0: // error
|
|
42
|
+
return chalk.red('✖');
|
|
43
|
+
case 1: // warning
|
|
44
|
+
return chalk.yellow('⚠');
|
|
45
|
+
case 2: // info
|
|
46
|
+
return chalk.blue('ℹ');
|
|
47
|
+
case 3: // hint
|
|
48
|
+
return chalk.gray('💡');
|
|
49
|
+
default:
|
|
50
|
+
return '•';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Format ruleset display name with override info.
|
|
55
|
+
* Returns long form: "pubhub v1.1.0 (2 rules excluded, 1 severity override)"
|
|
56
|
+
* Or short form: "pubhub v1.1.0" when no overrides present.
|
|
57
|
+
*/
|
|
58
|
+
export function formatRulesetDisplay(rulesetName, rulesetVersion, ruleOverrides) {
|
|
59
|
+
let display = `${rulesetName} v${rulesetVersion}`;
|
|
60
|
+
if (ruleOverrides && Object.keys(ruleOverrides).length > 0) {
|
|
61
|
+
const excluded = Object.values(ruleOverrides).filter(v => v === 'off').length;
|
|
62
|
+
const severityChanges = Object.values(ruleOverrides).filter(v => v !== 'off').length;
|
|
63
|
+
const parts = [];
|
|
64
|
+
if (excluded > 0)
|
|
65
|
+
parts.push(`${excluded} rule${excluded !== 1 ? 's' : ''} excluded`);
|
|
66
|
+
if (severityChanges > 0)
|
|
67
|
+
parts.push(`${severityChanges} severity override${severityChanges !== 1 ? 's' : ''}`);
|
|
68
|
+
if (parts.length > 0) {
|
|
69
|
+
display += ` (${parts.join(', ')})`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return display;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Format ruleset name in short form: "pubhub*" if overrides present, "pubhub" otherwise.
|
|
76
|
+
*/
|
|
77
|
+
export function formatRulesetShort(rulesetName, ruleOverrides) {
|
|
78
|
+
if (ruleOverrides && Object.keys(ruleOverrides).length > 0) {
|
|
79
|
+
return `${rulesetName}*`;
|
|
80
|
+
}
|
|
81
|
+
return rulesetName;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Format lint result summary
|
|
85
|
+
*/
|
|
86
|
+
export function formatSummary(result, dimmed = false) {
|
|
87
|
+
const { summary } = result;
|
|
88
|
+
const lines = [];
|
|
89
|
+
const applyStyle = (text) => dimmed ? chalk.dim(text) : text;
|
|
90
|
+
const applyBold = (text) => dimmed ? chalk.dim(text) : chalk.bold(text);
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push(applyBold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
93
|
+
lines.push(applyBold('Summary'));
|
|
94
|
+
lines.push(applyBold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
95
|
+
if (summary.totalIssues === 0) {
|
|
96
|
+
lines.push(applyStyle(chalk.green('✓ No issues found!')));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
lines.push(applyStyle(`Total Issues: ${chalk.bold(summary.totalIssues)}`));
|
|
100
|
+
if (summary.errorCount > 0) {
|
|
101
|
+
lines.push(applyStyle(` ${chalk.red('✖')} Errors: ${chalk.red(summary.errorCount)}`));
|
|
102
|
+
}
|
|
103
|
+
if (summary.warningCount > 0) {
|
|
104
|
+
lines.push(applyStyle(` ${chalk.yellow('⚠')} Warnings: ${chalk.yellow(summary.warningCount)}`));
|
|
105
|
+
}
|
|
106
|
+
if (summary.infoCount > 0) {
|
|
107
|
+
lines.push(applyStyle(` ${chalk.blue('ℹ')} Info: ${chalk.blue(summary.infoCount)}`));
|
|
108
|
+
}
|
|
109
|
+
if (summary.hintCount > 0) {
|
|
110
|
+
lines.push(applyStyle(` ${chalk.gray('💡')} Hints: ${chalk.gray(summary.hintCount)}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
lines.push('');
|
|
114
|
+
lines.push(applyStyle(`Ruleset: ${formatRulesetDisplay(result.rulesetName, result.rulesetVersion, result.ruleOverrides)}`));
|
|
115
|
+
lines.push(applyStyle(`Document: ${result.documentId}`));
|
|
116
|
+
lines.push(applyStyle(`Job ID: ${result.jobId}`));
|
|
117
|
+
if (result.summary.cacheHit) {
|
|
118
|
+
lines.push(chalk.dim('(cached result)'));
|
|
119
|
+
}
|
|
120
|
+
lines.push('');
|
|
121
|
+
return ensureReset(lines.join('\n'));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Format lint issues as a table
|
|
125
|
+
*/
|
|
126
|
+
export function formatIssuesTable(issues, limit) {
|
|
127
|
+
if (issues.length === 0) {
|
|
128
|
+
return chalk.green('✓ No issues found!\n');
|
|
129
|
+
}
|
|
130
|
+
const displayIssues = limit ? issues.slice(0, limit) : issues;
|
|
131
|
+
const table = new Table({
|
|
132
|
+
head: [
|
|
133
|
+
chalk.bold('Severity'),
|
|
134
|
+
chalk.bold('Rule'),
|
|
135
|
+
chalk.bold('Location'),
|
|
136
|
+
chalk.bold('Message'),
|
|
137
|
+
],
|
|
138
|
+
colWidths: [12, 35, 20, 50],
|
|
139
|
+
wordWrap: true,
|
|
140
|
+
style: {
|
|
141
|
+
head: [],
|
|
142
|
+
border: [],
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
for (const issue of displayIssues) {
|
|
146
|
+
const location = issue.path
|
|
147
|
+
? `${issue.path.join('.')}${issue.range ? `:${issue.range.start.line}` : ''}`
|
|
148
|
+
: issue.range
|
|
149
|
+
? `line ${issue.range.start.line}`
|
|
150
|
+
: '-';
|
|
151
|
+
table.push([
|
|
152
|
+
getSeverityIcon(issue.severity),
|
|
153
|
+
chalk.cyan(issue.code || issue.ruleId),
|
|
154
|
+
chalk.gray(location),
|
|
155
|
+
issue.message,
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
let output = table.toString() + '\n';
|
|
159
|
+
if (limit && issues.length > limit) {
|
|
160
|
+
output += chalk.dim(`\n... and ${issues.length - limit} more issues\n`);
|
|
161
|
+
output += chalk.dim(`Use 'spectify results <jobId>' to see all issues\n`);
|
|
162
|
+
}
|
|
163
|
+
return ensureReset(output);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Format issues grouped by rule (summary view for drill-down)
|
|
167
|
+
*/
|
|
168
|
+
export function formatRuleSummaryTable(issues) {
|
|
169
|
+
if (issues.length === 0) {
|
|
170
|
+
return chalk.green('✓ No issues found!\n');
|
|
171
|
+
}
|
|
172
|
+
// Group issues by rule
|
|
173
|
+
const ruleGroups = new Map();
|
|
174
|
+
for (const issue of issues) {
|
|
175
|
+
const ruleCode = issue.code || issue.ruleId;
|
|
176
|
+
if (!ruleGroups.has(ruleCode)) {
|
|
177
|
+
ruleGroups.set(ruleCode, {
|
|
178
|
+
code: ruleCode,
|
|
179
|
+
errors: 0,
|
|
180
|
+
warnings: 0,
|
|
181
|
+
info: 0,
|
|
182
|
+
hints: 0,
|
|
183
|
+
total: 0,
|
|
184
|
+
message: issue.message, // Save first message as example
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const group = ruleGroups.get(ruleCode);
|
|
188
|
+
group.total++;
|
|
189
|
+
switch (issue.severity) {
|
|
190
|
+
case 0:
|
|
191
|
+
group.errors++;
|
|
192
|
+
break;
|
|
193
|
+
case 1:
|
|
194
|
+
group.warnings++;
|
|
195
|
+
break;
|
|
196
|
+
case 2:
|
|
197
|
+
group.info++;
|
|
198
|
+
break;
|
|
199
|
+
case 3:
|
|
200
|
+
group.hints++;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Sort by total count (descending), then by severity
|
|
205
|
+
const sortedRules = Array.from(ruleGroups.values()).sort((a, b) => {
|
|
206
|
+
if (a.errors !== b.errors)
|
|
207
|
+
return b.errors - a.errors;
|
|
208
|
+
if (a.warnings !== b.warnings)
|
|
209
|
+
return b.warnings - a.warnings;
|
|
210
|
+
if (a.total !== b.total)
|
|
211
|
+
return b.total - a.total;
|
|
212
|
+
return a.code.localeCompare(b.code);
|
|
213
|
+
});
|
|
214
|
+
const table = new Table({
|
|
215
|
+
head: [
|
|
216
|
+
chalk.bold('Rule'),
|
|
217
|
+
chalk.bold('Total'),
|
|
218
|
+
chalk.bold('Errors'),
|
|
219
|
+
chalk.bold('Warnings'),
|
|
220
|
+
chalk.bold('Info'),
|
|
221
|
+
chalk.bold('Hints'),
|
|
222
|
+
chalk.bold('Example Message'),
|
|
223
|
+
],
|
|
224
|
+
colWidths: [35, 8, 10, 12, 8, 8, 50],
|
|
225
|
+
wordWrap: true,
|
|
226
|
+
style: {
|
|
227
|
+
head: [],
|
|
228
|
+
border: [],
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
for (const rule of sortedRules) {
|
|
232
|
+
const errorText = rule.errors > 0 ? chalk.red(rule.errors) : chalk.dim('0');
|
|
233
|
+
const warningText = rule.warnings > 0 ? chalk.yellow(rule.warnings) : chalk.dim('0');
|
|
234
|
+
const infoText = rule.info > 0 ? chalk.blue(rule.info) : chalk.dim('0');
|
|
235
|
+
const hintText = rule.hints > 0 ? chalk.gray(rule.hints) : chalk.dim('0');
|
|
236
|
+
table.push([
|
|
237
|
+
chalk.cyan(rule.code),
|
|
238
|
+
rule.total.toString(),
|
|
239
|
+
errorText,
|
|
240
|
+
warningText,
|
|
241
|
+
infoText,
|
|
242
|
+
hintText,
|
|
243
|
+
chalk.gray(rule.message.substring(0, 80) + (rule.message.length > 80 ? '...' : '')),
|
|
244
|
+
]);
|
|
245
|
+
}
|
|
246
|
+
return ensureReset(table.toString() + '\n');
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Format issues for a specific rule (detail view without repetition)
|
|
250
|
+
*/
|
|
251
|
+
export function formatRuleDetailView(issues) {
|
|
252
|
+
if (issues.length === 0) {
|
|
253
|
+
return chalk.green('✓ No issues found!\n');
|
|
254
|
+
}
|
|
255
|
+
// Get rule information from first issue (all should be the same)
|
|
256
|
+
const firstIssue = issues[0];
|
|
257
|
+
const severity = getSeverityIcon(firstIssue.severity);
|
|
258
|
+
const severityText = formatSeverity(firstIssue.severity);
|
|
259
|
+
const ruleCode = firstIssue.code || firstIssue.ruleId;
|
|
260
|
+
// Show rule header in clear white text
|
|
261
|
+
const lines = [];
|
|
262
|
+
lines.push(chalk.bold('Rule Details:'));
|
|
263
|
+
lines.push(` ${severity} ${severityText.toUpperCase()} - ${chalk.cyan(ruleCode)}`);
|
|
264
|
+
// Wrap message text at 80 characters
|
|
265
|
+
const message = firstIssue.message;
|
|
266
|
+
const wrappedMessage = wrapText(message, 80, ' ');
|
|
267
|
+
lines.push(wrappedMessage);
|
|
268
|
+
lines.push('');
|
|
269
|
+
console.log(lines.join('\n'));
|
|
270
|
+
// Create table with Line # first, then Path (wider, no Suggestion column)
|
|
271
|
+
const table = new Table({
|
|
272
|
+
head: [
|
|
273
|
+
chalk.bold('Line #'),
|
|
274
|
+
chalk.bold('Path'),
|
|
275
|
+
],
|
|
276
|
+
colWidths: [10, 110],
|
|
277
|
+
wordWrap: true,
|
|
278
|
+
style: {
|
|
279
|
+
head: [],
|
|
280
|
+
border: [],
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
for (const issue of issues) {
|
|
284
|
+
const lineStr = issue.range
|
|
285
|
+
? issue.range.start.line.toString()
|
|
286
|
+
: '-';
|
|
287
|
+
// Format path - join with dots for better wrapping
|
|
288
|
+
const pathStr = issue.path && issue.path.length > 0
|
|
289
|
+
? issue.path.join('.')
|
|
290
|
+
: '-';
|
|
291
|
+
table.push([
|
|
292
|
+
chalk.yellow(lineStr),
|
|
293
|
+
pathStr,
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
return ensureReset(table.toString() + '\n');
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Wrap text at specified width with prefix
|
|
300
|
+
*/
|
|
301
|
+
function wrapText(text, width, prefix = '') {
|
|
302
|
+
const words = text.split(' ');
|
|
303
|
+
const lines = [];
|
|
304
|
+
let currentLine = prefix;
|
|
305
|
+
for (const word of words) {
|
|
306
|
+
const testLine = currentLine === prefix ? currentLine + word : currentLine + ' ' + word;
|
|
307
|
+
if (testLine.length > width && currentLine !== prefix) {
|
|
308
|
+
lines.push(currentLine);
|
|
309
|
+
currentLine = prefix + word;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
currentLine = testLine;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (currentLine !== prefix) {
|
|
316
|
+
lines.push(currentLine);
|
|
317
|
+
}
|
|
318
|
+
return lines.join('\n');
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Format history entries as a table
|
|
322
|
+
*/
|
|
323
|
+
export function formatHistoryTable(entries) {
|
|
324
|
+
if (entries.length === 0) {
|
|
325
|
+
return chalk.dim('No history entries found.\n');
|
|
326
|
+
}
|
|
327
|
+
const table = new Table({
|
|
328
|
+
head: [
|
|
329
|
+
chalk.bold('Date'),
|
|
330
|
+
chalk.bold('JobId / OpenAPI document'),
|
|
331
|
+
chalk.bold('Ruleset'),
|
|
332
|
+
chalk.bold('Issues'),
|
|
333
|
+
],
|
|
334
|
+
colWidths: [20, 45, 20, 20],
|
|
335
|
+
wordWrap: true,
|
|
336
|
+
});
|
|
337
|
+
for (const entry of entries) {
|
|
338
|
+
const date = new Date(entry.timestamp).toLocaleString();
|
|
339
|
+
const fileName = entry.filePath.split('/').pop() || entry.filePath;
|
|
340
|
+
const issuesSummary = entry.summary.totalIssues === 0
|
|
341
|
+
? chalk.green('✓ No issues')
|
|
342
|
+
: `${chalk.red(`${entry.summary.errorCount}E`)} ${chalk.yellow(`${entry.summary.warningCount}W`)}`;
|
|
343
|
+
// Format JobId / OpenAPI document on two lines
|
|
344
|
+
const jobIdAndFile = `${chalk.dim(entry.jobId)}\n${fileName}`;
|
|
345
|
+
table.push([
|
|
346
|
+
chalk.dim(date),
|
|
347
|
+
jobIdAndFile,
|
|
348
|
+
`${entry.rulesetName} v${entry.rulesetVersion}`,
|
|
349
|
+
issuesSummary,
|
|
350
|
+
]);
|
|
351
|
+
}
|
|
352
|
+
return ensureReset(table.toString() + '\n');
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Format rulesets as a table
|
|
356
|
+
*/
|
|
357
|
+
export function formatRulesetsTable(rulesets) {
|
|
358
|
+
if (rulesets.length === 0) {
|
|
359
|
+
return chalk.dim('No rulesets available.\n');
|
|
360
|
+
}
|
|
361
|
+
const table = new Table({
|
|
362
|
+
head: [
|
|
363
|
+
chalk.bold('Name'),
|
|
364
|
+
chalk.bold('Versions'),
|
|
365
|
+
chalk.bold('Rules'),
|
|
366
|
+
chalk.bold('Description'),
|
|
367
|
+
],
|
|
368
|
+
colWidths: [18, 22, 8, 58],
|
|
369
|
+
wordWrap: true,
|
|
370
|
+
});
|
|
371
|
+
for (const ruleset of rulesets) {
|
|
372
|
+
// Format description with display name on first line
|
|
373
|
+
const description = ruleset.displayName
|
|
374
|
+
? `${chalk.bold(ruleset.displayName)}\n${ruleset.description || ''}`
|
|
375
|
+
: ruleset.description || '-';
|
|
376
|
+
// List all versions, default first
|
|
377
|
+
const allVersions = ruleset.availableVersions && ruleset.availableVersions.length > 0
|
|
378
|
+
? ruleset.availableVersions
|
|
379
|
+
: [ruleset.version];
|
|
380
|
+
const defaultVer = ruleset.defaultVersion || ruleset.version;
|
|
381
|
+
const sorted = [
|
|
382
|
+
...allVersions.filter(v => v === defaultVer),
|
|
383
|
+
...allVersions.filter(v => v !== defaultVer),
|
|
384
|
+
];
|
|
385
|
+
const versionsStr = sorted
|
|
386
|
+
.map(v => v === defaultVer ? chalk.green(`${v} (default)`) : chalk.dim(v))
|
|
387
|
+
.join('\n');
|
|
388
|
+
table.push([
|
|
389
|
+
chalk.cyan(ruleset.name),
|
|
390
|
+
versionsStr,
|
|
391
|
+
String(ruleset.ruleCount),
|
|
392
|
+
description,
|
|
393
|
+
]);
|
|
394
|
+
}
|
|
395
|
+
return ensureReset(table.toString() + '\n');
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Format job status
|
|
399
|
+
*/
|
|
400
|
+
export function formatJobStatus(status) {
|
|
401
|
+
const lines = [];
|
|
402
|
+
lines.push('');
|
|
403
|
+
lines.push(`Job ID: ${status.jobId}`);
|
|
404
|
+
switch (status.status) {
|
|
405
|
+
case 'queued':
|
|
406
|
+
lines.push(`Status: ${chalk.yellow('⏳ Queued')}`);
|
|
407
|
+
break;
|
|
408
|
+
case 'running':
|
|
409
|
+
lines.push(`Status: ${chalk.blue('▶ Running')}`);
|
|
410
|
+
if (status.progress) {
|
|
411
|
+
const percent = Math.round((status.progress.completed / status.progress.total) * 100);
|
|
412
|
+
lines.push(`Progress: ${status.progress.completed}/${status.progress.total} (${percent}%)`);
|
|
413
|
+
if (status.progress.currentRule) {
|
|
414
|
+
lines.push(`Current: ${status.progress.currentRule}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
case 'completed':
|
|
419
|
+
lines.push(`Status: ${chalk.green('✓ Completed')}`);
|
|
420
|
+
break;
|
|
421
|
+
case 'failed':
|
|
422
|
+
lines.push(`Status: ${chalk.red('✖ Failed')}`);
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
if (status.startedAt) {
|
|
426
|
+
lines.push(`Started: ${new Date(status.startedAt).toLocaleString()}`);
|
|
427
|
+
}
|
|
428
|
+
if (status.completedAt) {
|
|
429
|
+
lines.push(`Completed: ${new Date(status.completedAt).toLocaleString()}`);
|
|
430
|
+
}
|
|
431
|
+
lines.push('');
|
|
432
|
+
return ensureReset(lines.join('\n'));
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Format health status
|
|
436
|
+
*/
|
|
437
|
+
export function formatHealth(health) {
|
|
438
|
+
const lines = [];
|
|
439
|
+
lines.push('');
|
|
440
|
+
lines.push(chalk.bold('Linting Orchestrator — Health Status'));
|
|
441
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
442
|
+
const statusIcon = health.status === 'healthy' || health.status === 'ok'
|
|
443
|
+
? chalk.green('✓')
|
|
444
|
+
: health.status === 'degraded'
|
|
445
|
+
? chalk.yellow('⚠')
|
|
446
|
+
: chalk.red('✖');
|
|
447
|
+
lines.push(`Status: ${statusIcon} ${health.status}`);
|
|
448
|
+
lines.push(`Version: ${health.version}`);
|
|
449
|
+
// Show server metadata
|
|
450
|
+
if (health.server) {
|
|
451
|
+
lines.push('');
|
|
452
|
+
lines.push(chalk.bold('Server Information'));
|
|
453
|
+
if (health.mode) {
|
|
454
|
+
lines.push(`Mode: ${health.mode}`);
|
|
455
|
+
}
|
|
456
|
+
lines.push(`Port: ${health.server.port}`);
|
|
457
|
+
lines.push(`Host: ${health.server.host}`);
|
|
458
|
+
if (health.server.startedAt) {
|
|
459
|
+
const started = new Date(health.server.startedAt);
|
|
460
|
+
lines.push(`Started: ${started.toLocaleString()}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Show document store configuration
|
|
464
|
+
if (health.documentStore) {
|
|
465
|
+
lines.push('');
|
|
466
|
+
lines.push(chalk.bold('Document Store'));
|
|
467
|
+
lines.push(`Type: ${health.documentStore.type}`);
|
|
468
|
+
if (health.documentStore.fullPath) {
|
|
469
|
+
lines.push(`Location: ${health.documentStore.fullPath}`);
|
|
470
|
+
}
|
|
471
|
+
else if (health.documentStore.baseDir) {
|
|
472
|
+
lines.push(`Location: ${health.documentStore.baseDir}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Show runtime information (Spectral versions, resolver)
|
|
476
|
+
if (health.runtime) {
|
|
477
|
+
const rt = health.runtime;
|
|
478
|
+
lines.push('');
|
|
479
|
+
lines.push(chalk.bold('Runtime'));
|
|
480
|
+
if (rt.nodeVersion)
|
|
481
|
+
lines.push(`Node: ${rt.nodeVersion}`);
|
|
482
|
+
if (rt.spectralCore)
|
|
483
|
+
lines.push(`Spectral: core ${rt.spectralCore}`);
|
|
484
|
+
if (rt.spectralRulesets)
|
|
485
|
+
lines.push(` rulesets ${rt.spectralRulesets}`);
|
|
486
|
+
if (rt.spectralCli)
|
|
487
|
+
lines.push(` cli ${rt.spectralCli}`);
|
|
488
|
+
lines.push(`Resolver: ${rt.resolver ? rt.resolver : chalk.dim('none (Spectral default)')}`);
|
|
489
|
+
}
|
|
490
|
+
// Show Report Service integration status (always show, even if not configured)
|
|
491
|
+
lines.push('');
|
|
492
|
+
lines.push(chalk.bold('Report Service Integration'));
|
|
493
|
+
if (health.reportService) {
|
|
494
|
+
const rs = health.reportService;
|
|
495
|
+
// Status indicator based on actual connection state
|
|
496
|
+
let statusIcon;
|
|
497
|
+
if (rs.status === 'connected') {
|
|
498
|
+
statusIcon = chalk.green('✓ Connected');
|
|
499
|
+
}
|
|
500
|
+
else if (rs.status === 'degraded') {
|
|
501
|
+
statusIcon = chalk.yellow('⚠ Degraded');
|
|
502
|
+
if (rs.pendingNotifications > 0) {
|
|
503
|
+
statusIcon += chalk.dim(` (service unreachable, ${rs.pendingNotifications} pending)`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else if (rs.status === 'unreachable') {
|
|
507
|
+
statusIcon = chalk.red('✖ Unreachable');
|
|
508
|
+
}
|
|
509
|
+
else if (rs.status === 'error') {
|
|
510
|
+
statusIcon = chalk.red('✖ Error');
|
|
511
|
+
}
|
|
512
|
+
else if (rs.enabled) {
|
|
513
|
+
statusIcon = chalk.green('✓ Enabled');
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
statusIcon = chalk.red('✖ Disabled');
|
|
517
|
+
}
|
|
518
|
+
lines.push(`Status: ${statusIcon}`);
|
|
519
|
+
if (rs.serviceUrl) {
|
|
520
|
+
lines.push(`URL: ${rs.serviceUrl}`);
|
|
521
|
+
}
|
|
522
|
+
// Show message for degraded/error states
|
|
523
|
+
if (rs.message) {
|
|
524
|
+
lines.push(` ${chalk.dim(rs.message)}`);
|
|
525
|
+
}
|
|
526
|
+
if (rs.pendingNotifications !== undefined && rs.pendingNotifications !== 'N/A') {
|
|
527
|
+
const pendingColor = rs.pendingNotifications > 0 ? chalk.yellow : chalk.dim;
|
|
528
|
+
lines.push(`Pending: ${pendingColor(rs.pendingNotifications.toString())}`);
|
|
529
|
+
}
|
|
530
|
+
if (rs.retryJobRunning !== undefined) {
|
|
531
|
+
const retryIcon = rs.retryJobRunning ? chalk.green('✓ Running') : chalk.dim('○ Stopped');
|
|
532
|
+
lines.push(`Retry Job: ${retryIcon}`);
|
|
533
|
+
}
|
|
534
|
+
if (rs.lastRetryRun) {
|
|
535
|
+
const lastRun = new Date(rs.lastRetryRun);
|
|
536
|
+
lines.push(`Last Run: ${lastRun.toLocaleString()}`);
|
|
537
|
+
}
|
|
538
|
+
// Show next retry time when there are pending notifications
|
|
539
|
+
if (rs.nextRetryAt && rs.pendingNotifications > 0) {
|
|
540
|
+
const nextRetry = new Date(rs.nextRetryAt);
|
|
541
|
+
const now = new Date();
|
|
542
|
+
const diffMs = nextRetry.getTime() - now.getTime();
|
|
543
|
+
if (diffMs > 0) {
|
|
544
|
+
const diffSecs = Math.ceil(diffMs / 1000);
|
|
545
|
+
const minutes = Math.floor(diffSecs / 60);
|
|
546
|
+
const seconds = diffSecs % 60;
|
|
547
|
+
const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
548
|
+
lines.push(`Next Retry: ${chalk.cyan(`in ${timeStr}`)}`);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
lines.push(`Next Retry: ${chalk.cyan('imminent')}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else if (rs.retryJobRunning && rs.retryJobInterval && rs.pendingNotifications > 0) {
|
|
555
|
+
// Fallback: show retry interval if no specific next time
|
|
556
|
+
const intervalMinutes = Math.round(rs.retryJobInterval / 60000);
|
|
557
|
+
lines.push(`Retry Interval: ${chalk.dim(`every ${intervalMinutes}m`)}`);
|
|
558
|
+
}
|
|
559
|
+
if (rs.error) {
|
|
560
|
+
lines.push(`Error: ${chalk.red(rs.error)}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
lines.push(`Status: ${chalk.dim('○ Not configured')}`);
|
|
565
|
+
lines.push(chalk.dim(`To enable: Set SPECTIFYD_REPORTS_ENABLED=true in .env`));
|
|
566
|
+
}
|
|
567
|
+
// Show capacity and worker stats
|
|
568
|
+
if (health.stats) {
|
|
569
|
+
const stats = health.stats;
|
|
570
|
+
if (stats.capacity) {
|
|
571
|
+
lines.push('');
|
|
572
|
+
lines.push(chalk.bold('Capacity'));
|
|
573
|
+
const pct = stats.capacity.utilizationPercent || 0;
|
|
574
|
+
const capacityColor = pct >= 90 ? chalk.red : pct >= 70 ? chalk.yellow : chalk.green;
|
|
575
|
+
lines.push(`Active: ${capacityColor(`${stats.capacity.activeJobs}/${stats.capacity.maxConcurrentJobs}`)} jobs (${capacityColor(`${pct}%`)})`);
|
|
576
|
+
}
|
|
577
|
+
if (stats.jobs) {
|
|
578
|
+
lines.push(`Queued: ${stats.jobs.queued} Running: ${stats.jobs.running} Completed: ${stats.jobs.completed} Failed: ${stats.jobs.failed}`);
|
|
579
|
+
}
|
|
580
|
+
if (stats.workers) {
|
|
581
|
+
lines.push('');
|
|
582
|
+
lines.push(chalk.bold('Workers'));
|
|
583
|
+
lines.push(`Total: ${stats.workers.total} Active: ${stats.workers.active} Idle: ${stats.workers.idle}`);
|
|
584
|
+
}
|
|
585
|
+
if (stats.cache) {
|
|
586
|
+
const hitRate = stats.cache.hitRate !== undefined
|
|
587
|
+
? `${(stats.cache.hitRate * 100).toFixed(0)}%`
|
|
588
|
+
: 'N/A';
|
|
589
|
+
lines.push('');
|
|
590
|
+
lines.push(chalk.bold('Cache'));
|
|
591
|
+
lines.push(`Hit Rate: ${hitRate} (${stats.cache.hits} hits / ${stats.cache.misses} misses)`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (health.uptime !== undefined) {
|
|
595
|
+
const uptimeSeconds = health.uptime;
|
|
596
|
+
const hours = Math.floor(uptimeSeconds / 3600);
|
|
597
|
+
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
|
598
|
+
const seconds = uptimeSeconds % 60;
|
|
599
|
+
let uptimeStr = '';
|
|
600
|
+
if (hours > 0)
|
|
601
|
+
uptimeStr += `${hours}h `;
|
|
602
|
+
if (minutes > 0)
|
|
603
|
+
uptimeStr += `${minutes}m `;
|
|
604
|
+
uptimeStr += `${seconds}s`;
|
|
605
|
+
lines.push('');
|
|
606
|
+
lines.push(`Uptime: ${uptimeStr}`);
|
|
607
|
+
}
|
|
608
|
+
lines.push('');
|
|
609
|
+
return ensureReset(lines.join('\n'));
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Format jobs list as a table
|
|
613
|
+
*/
|
|
614
|
+
export function formatJobsTable(jobs, detailed = false) {
|
|
615
|
+
if (jobs.length === 0) {
|
|
616
|
+
return chalk.dim('No jobs found.\n');
|
|
617
|
+
}
|
|
618
|
+
const table = new Table({
|
|
619
|
+
head: detailed
|
|
620
|
+
? [
|
|
621
|
+
chalk.bold('Date'),
|
|
622
|
+
chalk.bold('JobId'),
|
|
623
|
+
chalk.bold('Document'),
|
|
624
|
+
chalk.bold('Ruleset'),
|
|
625
|
+
chalk.bold('Status'),
|
|
626
|
+
chalk.bold('Issues'),
|
|
627
|
+
]
|
|
628
|
+
: [
|
|
629
|
+
chalk.bold('Date'),
|
|
630
|
+
chalk.bold('JobId'),
|
|
631
|
+
chalk.bold('DocumentId'),
|
|
632
|
+
chalk.bold('Ruleset'),
|
|
633
|
+
chalk.bold('Status'),
|
|
634
|
+
chalk.bold('Issues'),
|
|
635
|
+
],
|
|
636
|
+
colWidths: detailed ? [18, 15, 30, 18, 12, 15] : [18, 15, 15, 18, 12, 15],
|
|
637
|
+
wordWrap: true,
|
|
638
|
+
});
|
|
639
|
+
for (const job of jobs) {
|
|
640
|
+
const date = new Date(job.timestamp).toLocaleString('en-US', {
|
|
641
|
+
month: 'short',
|
|
642
|
+
day: 'numeric',
|
|
643
|
+
hour: '2-digit',
|
|
644
|
+
minute: '2-digit'
|
|
645
|
+
});
|
|
646
|
+
// Format status with color
|
|
647
|
+
let statusDisplay = job.status;
|
|
648
|
+
if (job.status === 'completed') {
|
|
649
|
+
statusDisplay = chalk.green('✓ done');
|
|
650
|
+
}
|
|
651
|
+
else if (job.status === 'completed_with_errors') {
|
|
652
|
+
statusDisplay = chalk.yellow('⚠ done');
|
|
653
|
+
}
|
|
654
|
+
else if (job.status === 'failed') {
|
|
655
|
+
statusDisplay = chalk.red('✗ failed');
|
|
656
|
+
}
|
|
657
|
+
else if (job.status === 'timeout') {
|
|
658
|
+
statusDisplay = chalk.red('⏱ timeout');
|
|
659
|
+
}
|
|
660
|
+
else if (job.status === 'running') {
|
|
661
|
+
statusDisplay = chalk.blue('⟳ running');
|
|
662
|
+
}
|
|
663
|
+
// Format issues summary
|
|
664
|
+
let issuesSummary = '';
|
|
665
|
+
if (job.summary && job.summary.totalIssues === 0) {
|
|
666
|
+
issuesSummary = chalk.green('✓ 0');
|
|
667
|
+
}
|
|
668
|
+
else if (job.summary) {
|
|
669
|
+
const parts = [];
|
|
670
|
+
if (job.summary.errorCount > 0)
|
|
671
|
+
parts.push(chalk.red(`${job.summary.errorCount}E`));
|
|
672
|
+
if (job.summary.warningCount > 0)
|
|
673
|
+
parts.push(chalk.yellow(`${job.summary.warningCount}W`));
|
|
674
|
+
if (job.summary.infoCount > 0)
|
|
675
|
+
parts.push(chalk.cyan(`${job.summary.infoCount}I`));
|
|
676
|
+
issuesSummary = parts.join(' ');
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
issuesSummary = chalk.dim('n/a');
|
|
680
|
+
}
|
|
681
|
+
// Short job ID (first 12 chars)
|
|
682
|
+
const shortJobId = chalk.dim(job.jobId.slice(0, 12));
|
|
683
|
+
// Ruleset version
|
|
684
|
+
const rulesetDisplay = `${job.rulesetName}\n${chalk.dim('v' + job.rulesetVersion)}`;
|
|
685
|
+
if (detailed && job.document) {
|
|
686
|
+
// Detailed mode - show document name
|
|
687
|
+
const docName = job.document.name || 'Unknown';
|
|
688
|
+
const docInfo = job.document.version
|
|
689
|
+
? `${docName}\n${chalk.dim('v' + job.document.version)}`
|
|
690
|
+
: docName;
|
|
691
|
+
table.push([
|
|
692
|
+
chalk.dim(date),
|
|
693
|
+
shortJobId,
|
|
694
|
+
docInfo,
|
|
695
|
+
rulesetDisplay,
|
|
696
|
+
statusDisplay,
|
|
697
|
+
issuesSummary,
|
|
698
|
+
]);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// Lightweight mode - show document ID
|
|
702
|
+
const shortDocId = chalk.dim(job.documentId.slice(0, 12));
|
|
703
|
+
table.push([
|
|
704
|
+
chalk.dim(date),
|
|
705
|
+
shortJobId,
|
|
706
|
+
shortDocId,
|
|
707
|
+
rulesetDisplay,
|
|
708
|
+
statusDisplay,
|
|
709
|
+
issuesSummary,
|
|
710
|
+
]);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return ensureReset(table.toString() + '\n');
|
|
714
|
+
}
|
|
715
|
+
//# sourceMappingURL=formatters.js.map
|