@shidesheng0218/dsh-plugin-lint 0.0.0-stage → 0.4.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/LICENSE +202 -0
- package/NOTICE +13 -0
- package/README.md +734 -2
- package/bin/dsh-plugin-analyze.mjs +378 -0
- package/bin/dsh-plugin-lint.mjs +381 -0
- package/bin/dsh-plugin-matrix.mjs +78 -0
- package/bin/dsh-plugin-upgrade-plan.mjs +180 -0
- package/package.json +62 -4
- package/schema/dsh-manifest.schema.json +134 -0
- package/scripts/lib/probe.mjs +34 -0
- package/scripts/probe-boot.mjs +123 -0
- package/src/composition.js +1 -0
- package/src/core/baseline.js +98 -0
- package/src/core/changed.js +41 -0
- package/src/core/load.js +145 -0
- package/src/core/mask.js +207 -0
- package/src/core/pack.js +95 -0
- package/src/core/packlist.js +186 -0
- package/src/core/peer.js +39 -0
- package/src/core/profile-scan.js +121 -0
- package/src/core/report.js +174 -0
- package/src/core/rules.js +581 -0
- package/src/core/semver.js +192 -0
- package/src/core/tarball.js +90 -0
- package/src/data/event-types.json +393 -0
- package/src/experimental/conflicts.js +315 -0
- package/src/experimental/index.js +4 -0
- package/src/experimental/performance.js +515 -0
- package/src/experimental/security.js +462 -0
- package/src/index.js +74 -0
- package/src/load.js +1 -0
- package/src/mask.js +1 -0
- package/src/matrix/cache.js +19 -0
- package/src/matrix/cell.js +79 -0
- package/src/matrix/cells.js +58 -0
- package/src/matrix/conflicts.js +210 -0
- package/src/matrix/export.js +155 -0
- package/src/matrix/index.js +71 -0
- package/src/matrix/manifest.js +17 -0
- package/src/matrix/migrate.js +41 -0
- package/src/matrix/run.js +241 -0
- package/src/matrix/sanitize.js +42 -0
- package/src/matrix/schema.js +178 -0
- package/src/matrix/site.js +61 -0
- package/src/matrix/sync.js +72 -0
- package/src/matrix/verify.js +152 -0
- package/src/performance.js +1 -0
- package/src/probe/artifact-resolver.js +161 -0
- package/src/probe/classify.js +8 -0
- package/src/probe/dsh-adapter.js +212 -0
- package/src/probe/environment/docker.js +270 -0
- package/src/probe/environment/host-unsafe.js +39 -0
- package/src/probe/environment/materialize.js +22 -0
- package/src/probe/environment.js +50 -0
- package/src/probe/installed-artifact.js +17 -0
- package/src/probe/probe.js +278 -0
- package/src/report.js +1 -0
- package/src/rules.js +1 -0
- package/src/security.js +1 -0
- package/src/shared/errors.js +7 -0
- package/src/shared/hash.js +31 -0
- package/src/shared/status.js +12 -0
- package/src/shared/validate.js +14 -0
- package/src/shared/version.js +4 -0
- package/src/upgrade/compatibility.js +14 -0
- package/src/upgrade/planner.js +119 -0
- package/src/upgrade/profile.js +82 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, join, relative } from 'node:path';
|
|
3
|
+
import { fingerprint } from './baseline.js';
|
|
4
|
+
|
|
5
|
+
const COLOR = {
|
|
6
|
+
error: '\u001b[31m',
|
|
7
|
+
warn: '\u001b[33m',
|
|
8
|
+
info: '\u001b[36m',
|
|
9
|
+
dim: '\u001b[2m',
|
|
10
|
+
bold: '\u001b[1m',
|
|
11
|
+
reset: '\u001b[0m',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const LABEL = { error: 'error', warn: 'warn ', info: 'info ' };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* An SVG badge for a README. The badge carries the verdict, not just the brand:
|
|
18
|
+
* "0 error · 1 warn · compatible with 2 DSH versions". A reader should learn
|
|
19
|
+
* something from the badge alone.
|
|
20
|
+
*/
|
|
21
|
+
export function toBadge(report) {
|
|
22
|
+
const s = report.summary;
|
|
23
|
+
const declared = (report.dsh && report.dsh.declared) || [];
|
|
24
|
+
const versionText = declared.length > 0 ? ' · ' + declared.length + ' DSH' : '';
|
|
25
|
+
const label = s.error + ' error · ' + s.warn + ' warn' + versionText;
|
|
26
|
+
const color = s.error > 0 ? '#cf222e' : s.warn > 0 ? '#bf8700' : '#1a7f37';
|
|
27
|
+
const left = 'dsh-plugin-lint';
|
|
28
|
+
const width = 90 + label.length * 6.2;
|
|
29
|
+
const leftWidth = 96;
|
|
30
|
+
const rightWidth = width - leftWidth;
|
|
31
|
+
return [
|
|
32
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="' + Math.round(width) + '" height="20" role="img" aria-label="' + left + ': ' + label + '">',
|
|
33
|
+
' <title>' + left + ': ' + label + '</title>',
|
|
34
|
+
' <linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient>',
|
|
35
|
+
' <clipPath id="r"><rect width="' + Math.round(width) + '" height="20" rx="3" fill="#fff"/></clipPath>',
|
|
36
|
+
' <g clip-path="url(#r)">',
|
|
37
|
+
' <rect width="' + leftWidth + '" height="20" fill="#555"/>',
|
|
38
|
+
' <rect x="' + leftWidth + '" width="' + Math.round(rightWidth) + '" height="20" fill="' + color + '"/>',
|
|
39
|
+
' <rect width="' + Math.round(width) + '" height="20" fill="url(#s)"/>',
|
|
40
|
+
' </g>',
|
|
41
|
+
' <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">',
|
|
42
|
+
' <text aria-hidden="true" x="' + (leftWidth * 5) + '" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="' + (leftWidth * 10 - 100) + '">' + left + '</text>',
|
|
43
|
+
' <text x="' + (leftWidth * 5) + '" y="140" transform="scale(.1)" fill="#fff" textLength="' + (leftWidth * 10 - 100) + '">' + left + '</text>',
|
|
44
|
+
' <text aria-hidden="true" x="' + ((leftWidth + rightWidth / 2) * 10) + '" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="' + (rightWidth * 10 - 100) + '">' + label + '</text>',
|
|
45
|
+
' <text x="' + ((leftWidth + rightWidth / 2) * 10) + '" y="140" transform="scale(.1)" fill="#fff" textLength="' + (rightWidth * 10 - 100) + '">' + label + '</text>',
|
|
46
|
+
' </g>',
|
|
47
|
+
'</svg>',
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatText(report, options = {}) {
|
|
52
|
+
const paint = (level, text) => (options.color ? COLOR[level] + text + COLOR.reset : text);
|
|
53
|
+
const dim = (text) => (options.color ? COLOR.dim + text + COLOR.reset : text);
|
|
54
|
+
const lines = [];
|
|
55
|
+
const t = report.target;
|
|
56
|
+
lines.push((options.color ? COLOR.bold : '') + t.name + '@' + t.version
|
|
57
|
+
+ (options.color ? COLOR.reset : '') + dim(' ' + report.checks + ' checks'));
|
|
58
|
+
lines.push('');
|
|
59
|
+
|
|
60
|
+
if (report.findings.length === 0) {
|
|
61
|
+
lines.push(' ' + paint('info', 'clean') + ' no findings');
|
|
62
|
+
}
|
|
63
|
+
for (const f of report.findings) {
|
|
64
|
+
const where = f.file ? f.file + (f.line ? ':' + f.line : '') : '';
|
|
65
|
+
lines.push(' ' + paint(f.level, f.rule + ' ' + LABEL[f.level]) + ' ' + f.title);
|
|
66
|
+
if (where) lines.push(' ' + dim(where));
|
|
67
|
+
if (f.message) lines.push(' ' + f.message);
|
|
68
|
+
if (f.evidence) lines.push(' ' + dim('evidence: ' + f.evidence));
|
|
69
|
+
if (f.hint) lines.push(' ' + dim('why: ' + f.hint));
|
|
70
|
+
lines.push(' ' + dim('doc: ' + f.doc));
|
|
71
|
+
lines.push('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const s = report.summary;
|
|
75
|
+
lines.push(' ' + s.error + ' error · ' + s.warn + ' warn · ' + s.info + ' info');
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function uriFor(report, file) {
|
|
80
|
+
const absolute = join(report.target.path, file || 'package.json');
|
|
81
|
+
let cwd = process.cwd();
|
|
82
|
+
let target = absolute;
|
|
83
|
+
try { cwd = realpathSync(cwd); } catch { /* keep the logical path */ }
|
|
84
|
+
try { target = realpathSync(absolute); } catch { /* the finding may point at a virtual path */ }
|
|
85
|
+
const rel = relative(cwd, target).replace(/\\/g, '/');
|
|
86
|
+
return rel && !rel.startsWith('..') && !isAbsolute(rel) ? rel : target.replace(/\\/g, '/');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ruleHelpUri(id) {
|
|
90
|
+
return 'https://github.com/shidesheng0218/dsh-plugin-lint/blob/main/docs/rules/' + id + '.md';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function toSarif(reports) {
|
|
94
|
+
const list = Array.isArray(reports) ? reports : [reports];
|
|
95
|
+
const ruleIndex = new Map();
|
|
96
|
+
const rules = [];
|
|
97
|
+
const results = [];
|
|
98
|
+
for (const report of list) {
|
|
99
|
+
for (const f of report.findings) {
|
|
100
|
+
if (!ruleIndex.has(f.rule)) {
|
|
101
|
+
ruleIndex.set(f.rule, rules.length);
|
|
102
|
+
rules.push({
|
|
103
|
+
id: f.rule,
|
|
104
|
+
name: f.rule,
|
|
105
|
+
shortDescription: { text: f.title },
|
|
106
|
+
helpUri: ruleHelpUri(f.rule),
|
|
107
|
+
properties: { doc: f.doc, level: f.level },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
results.push({
|
|
111
|
+
ruleId: f.rule,
|
|
112
|
+
ruleIndex: ruleIndex.get(f.rule),
|
|
113
|
+
level: f.level === 'error' ? 'error' : f.level === 'warn' ? 'warning' : 'note',
|
|
114
|
+
message: { text: f.message + (f.hint ? ' — ' + f.hint : '') },
|
|
115
|
+
partialFingerprints: { 'dshPluginLint/v1': fingerprint(f) },
|
|
116
|
+
locations: [{
|
|
117
|
+
physicalLocation: {
|
|
118
|
+
artifactLocation: { uri: uriFor(report, f.file), uriBaseId: '%SRCROOT%' },
|
|
119
|
+
region: { startLine: f.line || 1 },
|
|
120
|
+
},
|
|
121
|
+
}],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
version: '2.1.0',
|
|
127
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
128
|
+
runs: [{
|
|
129
|
+
tool: { driver: { name: 'dsh-plugin-lint', informationUri: 'https://github.com/shidesheng0218/dsh-plugin-lint', rules } },
|
|
130
|
+
results,
|
|
131
|
+
}],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function escapeData(value) {
|
|
136
|
+
return String(value).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function escapeProperty(value) {
|
|
140
|
+
return escapeData(value).replace(/:/g, '%3A').replace(/,/g, '%2C');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function toGithub(reports) {
|
|
144
|
+
const lines = [];
|
|
145
|
+
for (const report of reports) {
|
|
146
|
+
for (const f of report.findings) {
|
|
147
|
+
const level = f.level === 'error' ? 'error' : f.level === 'warn' ? 'warning' : 'notice';
|
|
148
|
+
const properties = ['file=' + escapeProperty(uriFor(report, f.file))];
|
|
149
|
+
if (f.line) properties.push('line=' + f.line);
|
|
150
|
+
properties.push('title=' + escapeProperty(f.rule + ' ' + f.title));
|
|
151
|
+
lines.push('::' + level + ' ' + properties.join(',') + '::' + escapeData(f.message + (f.hint ? ' — ' + f.hint : '')));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function xml(value) {
|
|
158
|
+
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function toJunit(reports) {
|
|
162
|
+
const suites = reports.map((report) => {
|
|
163
|
+
const failures = report.findings.filter((finding) => finding.level !== 'info').length;
|
|
164
|
+
const cases = report.findings.map((finding) => {
|
|
165
|
+
const name = finding.rule + ' ' + (finding.file || 'package.json') + (finding.line ? ':' + finding.line : '');
|
|
166
|
+
const body = finding.level === 'info'
|
|
167
|
+
? ''
|
|
168
|
+
: '<failure type="' + xml(finding.rule) + '" message="' + xml(finding.message) + '">' + xml(finding.evidence || '') + '</failure>';
|
|
169
|
+
return '<testcase classname="' + xml(report.target.name) + '" name="' + xml(name) + '">' + body + '</testcase>';
|
|
170
|
+
}).join('');
|
|
171
|
+
return '<testsuite name="' + xml(report.target.name) + '" tests="' + report.findings.length + '" failures="' + failures + '">' + cases + '</testsuite>';
|
|
172
|
+
}).join('');
|
|
173
|
+
return '<?xml version="1.0" encoding="UTF-8"?>\n<testsuites>' + suites + '</testsuites>';
|
|
174
|
+
}
|