@eduardbar/drift 0.2.1 → 0.2.3
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/.github/workflows/publish.yml +14 -4
- package/bin/drift.js +2 -0
- package/dist/reporter.js +49 -39
- package/package.json +2 -2
- package/src/reporter.ts +52 -41
|
@@ -3,9 +3,6 @@ name: Publish to npm
|
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
5
5
|
types: [published]
|
|
6
|
-
push:
|
|
7
|
-
tags:
|
|
8
|
-
- 'v*'
|
|
9
6
|
workflow_dispatch:
|
|
10
7
|
inputs:
|
|
11
8
|
tag:
|
|
@@ -23,7 +20,7 @@ jobs:
|
|
|
23
20
|
- name: Checkout
|
|
24
21
|
uses: actions/checkout@v4
|
|
25
22
|
with:
|
|
26
|
-
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.tag) ||
|
|
23
|
+
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.tag) || github.ref }}
|
|
27
24
|
|
|
28
25
|
- name: Setup Node.js
|
|
29
26
|
uses: actions/setup-node@v4
|
|
@@ -46,13 +43,26 @@ jobs:
|
|
|
46
43
|
fi
|
|
47
44
|
echo "Version check passed: $PKG_VERSION"
|
|
48
45
|
|
|
46
|
+
- name: Check if version already published
|
|
47
|
+
run: |
|
|
48
|
+
PKG_VERSION=$(node -p "require('./package.json').version")
|
|
49
|
+
if npm view @eduardbar/drift@$PKG_VERSION version 2>/dev/null; then
|
|
50
|
+
echo "Version $PKG_VERSION already published on npm. Skipping."
|
|
51
|
+
echo "skip_publish=true" >> $GITHUB_ENV
|
|
52
|
+
fi
|
|
53
|
+
env:
|
|
54
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
55
|
+
|
|
49
56
|
- name: Install dependencies
|
|
57
|
+
if: env.skip_publish != 'true'
|
|
50
58
|
run: npm ci
|
|
51
59
|
|
|
52
60
|
- name: Build
|
|
61
|
+
if: env.skip_publish != 'true'
|
|
53
62
|
run: npm run build
|
|
54
63
|
|
|
55
64
|
- name: Publish to npm
|
|
65
|
+
if: env.skip_publish != 'true'
|
|
56
66
|
run: npm publish --access public
|
|
57
67
|
env:
|
|
58
68
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/bin/drift.js
ADDED
package/dist/reporter.js
CHANGED
|
@@ -23,52 +23,62 @@ export function buildReport(targetPath, files) {
|
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
+
function formatHeader(report, grade) {
|
|
27
|
+
return [
|
|
28
|
+
`# drift report`,
|
|
29
|
+
``,
|
|
30
|
+
`> Generated: ${new Date(report.scannedAt).toLocaleString()}`,
|
|
31
|
+
`> Path: \`${report.targetPath}\``,
|
|
32
|
+
``,
|
|
33
|
+
`## Overall drift score: ${report.totalScore}/100 ${grade.badge}`,
|
|
34
|
+
``,
|
|
35
|
+
`| | Count |`,
|
|
36
|
+
`|---|---|`,
|
|
37
|
+
`| Errors | ${report.summary.errors} |`,
|
|
38
|
+
`| Warnings | ${report.summary.warnings} |`,
|
|
39
|
+
`| Info | ${report.summary.infos} |`,
|
|
40
|
+
`| Files with issues | ${report.files.length} |`,
|
|
41
|
+
`| Total issues | ${report.totalIssues} |`,
|
|
42
|
+
``,
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
function formatByRule(byRule) {
|
|
46
|
+
if (Object.keys(byRule).length === 0)
|
|
47
|
+
return [];
|
|
48
|
+
const sorted = Object.entries(byRule).sort((a, b) => b[1] - a[1]);
|
|
49
|
+
return [
|
|
50
|
+
`## Issues by rule`,
|
|
51
|
+
``,
|
|
52
|
+
...sorted.map(([rule, count]) => `- \`${rule}\`: ${count}`),
|
|
53
|
+
``,
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
function formatFileSection(file) {
|
|
57
|
+
const lines = [
|
|
58
|
+
`### \`${file.path}\` — score ${file.score}/100`,
|
|
59
|
+
``,
|
|
60
|
+
];
|
|
61
|
+
for (const issue of file.issues) {
|
|
62
|
+
lines.push(`**${severityIcon(issue.severity)} [${issue.rule}]** Line ${issue.line}: ${issue.message}`);
|
|
63
|
+
lines.push(`\`\`\`typescript`);
|
|
64
|
+
lines.push(issue.snippet);
|
|
65
|
+
lines.push(`\`\`\``);
|
|
66
|
+
lines.push(``);
|
|
67
|
+
}
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
26
70
|
export function formatMarkdown(report) {
|
|
27
71
|
const grade = scoreToGradeText(report.totalScore);
|
|
28
72
|
const lines = [];
|
|
29
|
-
lines.push(
|
|
30
|
-
lines.push(
|
|
31
|
-
lines.push(`> Generated: ${new Date(report.scannedAt).toLocaleString()}`);
|
|
32
|
-
lines.push(`> Path: \`${report.targetPath}\``);
|
|
33
|
-
lines.push(``);
|
|
34
|
-
lines.push(`## Overall drift score: ${report.totalScore}/100 ${grade.badge}`);
|
|
35
|
-
lines.push(``);
|
|
36
|
-
lines.push(`| | Count |`);
|
|
37
|
-
lines.push(`|---|---|`);
|
|
38
|
-
lines.push(`| Errors | ${report.summary.errors} |`);
|
|
39
|
-
lines.push(`| Warnings | ${report.summary.warnings} |`);
|
|
40
|
-
lines.push(`| Info | ${report.summary.infos} |`);
|
|
41
|
-
lines.push(`| Files with issues | ${report.files.length} |`);
|
|
42
|
-
lines.push(`| Total issues | ${report.totalIssues} |`);
|
|
43
|
-
lines.push(``);
|
|
44
|
-
if (Object.keys(report.summary.byRule).length > 0) {
|
|
45
|
-
lines.push(`## Issues by rule`);
|
|
46
|
-
lines.push(``);
|
|
47
|
-
const sorted = Object.entries(report.summary.byRule).sort((a, b) => b[1] - a[1]);
|
|
48
|
-
for (const [rule, count] of sorted) {
|
|
49
|
-
lines.push(`- \`${rule}\`: ${count}`);
|
|
50
|
-
}
|
|
51
|
-
lines.push(``);
|
|
52
|
-
}
|
|
73
|
+
lines.push(...formatHeader(report, grade));
|
|
74
|
+
lines.push(...formatByRule(report.summary.byRule));
|
|
53
75
|
if (report.files.length === 0) {
|
|
54
|
-
lines.push(`## No drift detected`);
|
|
55
|
-
lines.push(``);
|
|
56
|
-
lines.push(`No issues found. Clean codebase.`);
|
|
76
|
+
lines.push(`## No drift detected`, ``, `No issues found. Clean codebase.`);
|
|
57
77
|
}
|
|
58
78
|
else {
|
|
59
|
-
lines.push(`## Files (sorted by drift score)
|
|
60
|
-
lines.push(``);
|
|
79
|
+
lines.push(`## Files (sorted by drift score)`, ``);
|
|
61
80
|
for (const file of report.files) {
|
|
62
|
-
lines.push(
|
|
63
|
-
lines.push(``);
|
|
64
|
-
for (const issue of file.issues) {
|
|
65
|
-
const icon = severityIcon(issue.severity);
|
|
66
|
-
lines.push(`**${icon} [${issue.rule}]** Line ${issue.line}: ${issue.message}`);
|
|
67
|
-
lines.push(`\`\`\`typescript`);
|
|
68
|
-
lines.push(issue.snippet);
|
|
69
|
-
lines.push(`\`\`\``);
|
|
70
|
-
lines.push(``);
|
|
71
|
-
}
|
|
81
|
+
lines.push(...formatFileSection(file));
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
return lines.join('\n');
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eduardbar/drift",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Detect silent technical debt left by AI-generated code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"drift": "
|
|
8
|
+
"drift": "bin/drift.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
package/src/reporter.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FileReport, DriftReport } from './types.js'
|
|
1
|
+
import type { FileReport, DriftReport, DriftIssue } from './types.js'
|
|
2
2
|
import { scoreToGradeText, severityIcon } from './utils.js'
|
|
3
3
|
|
|
4
4
|
export function buildReport(targetPath: string, files: FileReport[]): DriftReport {
|
|
@@ -30,54 +30,65 @@ export function buildReport(targetPath: string, files: FileReport[]): DriftRepor
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
function formatHeader(report: DriftReport, grade: { badge: string }): string[] {
|
|
34
|
+
return [
|
|
35
|
+
`# drift report`,
|
|
36
|
+
``,
|
|
37
|
+
`> Generated: ${new Date(report.scannedAt).toLocaleString()}`,
|
|
38
|
+
`> Path: \`${report.targetPath}\``,
|
|
39
|
+
``,
|
|
40
|
+
`## Overall drift score: ${report.totalScore}/100 ${grade.badge}`,
|
|
41
|
+
``,
|
|
42
|
+
`| | Count |`,
|
|
43
|
+
`|---|---|`,
|
|
44
|
+
`| Errors | ${report.summary.errors} |`,
|
|
45
|
+
`| Warnings | ${report.summary.warnings} |`,
|
|
46
|
+
`| Info | ${report.summary.infos} |`,
|
|
47
|
+
`| Files with issues | ${report.files.length} |`,
|
|
48
|
+
`| Total issues | ${report.totalIssues} |`,
|
|
49
|
+
``,
|
|
50
|
+
]
|
|
51
|
+
}
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
lines.push(`| Warnings | ${report.summary.warnings} |`)
|
|
48
|
-
lines.push(`| Info | ${report.summary.infos} |`)
|
|
49
|
-
lines.push(`| Files with issues | ${report.files.length} |`)
|
|
50
|
-
lines.push(`| Total issues | ${report.totalIssues} |`)
|
|
51
|
-
lines.push(``)
|
|
53
|
+
function formatByRule(byRule: Record<string, number>): string[] {
|
|
54
|
+
if (Object.keys(byRule).length === 0) return []
|
|
55
|
+
const sorted = Object.entries(byRule).sort((a, b) => b[1] - a[1])
|
|
56
|
+
return [
|
|
57
|
+
`## Issues by rule`,
|
|
58
|
+
``,
|
|
59
|
+
...sorted.map(([rule, count]) => `- \`${rule}\`: ${count}`),
|
|
60
|
+
``,
|
|
61
|
+
]
|
|
62
|
+
}
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
64
|
+
function formatFileSection(file: { path: string; score: number; issues: DriftIssue[] }): string[] {
|
|
65
|
+
const lines: string[] = [
|
|
66
|
+
`### \`${file.path}\` — score ${file.score}/100`,
|
|
67
|
+
``,
|
|
68
|
+
]
|
|
69
|
+
for (const issue of file.issues) {
|
|
70
|
+
lines.push(`**${severityIcon(issue.severity)} [${issue.rule}]** Line ${issue.line}: ${issue.message}`)
|
|
71
|
+
lines.push(`\`\`\`typescript`)
|
|
72
|
+
lines.push(issue.snippet)
|
|
73
|
+
lines.push(`\`\`\``)
|
|
60
74
|
lines.push(``)
|
|
61
75
|
}
|
|
76
|
+
return lines
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function formatMarkdown(report: DriftReport): string {
|
|
80
|
+
const grade = scoreToGradeText(report.totalScore)
|
|
81
|
+
const lines: string[] = []
|
|
82
|
+
|
|
83
|
+
lines.push(...formatHeader(report, grade))
|
|
84
|
+
lines.push(...formatByRule(report.summary.byRule))
|
|
62
85
|
|
|
63
86
|
if (report.files.length === 0) {
|
|
64
|
-
lines.push(`## No drift detected`)
|
|
65
|
-
lines.push(``)
|
|
66
|
-
lines.push(`No issues found. Clean codebase.`)
|
|
87
|
+
lines.push(`## No drift detected`, ``, `No issues found. Clean codebase.`)
|
|
67
88
|
} else {
|
|
68
|
-
lines.push(`## Files (sorted by drift score)
|
|
69
|
-
lines.push(``)
|
|
89
|
+
lines.push(`## Files (sorted by drift score)`, ``)
|
|
70
90
|
for (const file of report.files) {
|
|
71
|
-
lines.push(
|
|
72
|
-
lines.push(``)
|
|
73
|
-
for (const issue of file.issues) {
|
|
74
|
-
const icon = severityIcon(issue.severity)
|
|
75
|
-
lines.push(`**${icon} [${issue.rule}]** Line ${issue.line}: ${issue.message}`)
|
|
76
|
-
lines.push(`\`\`\`typescript`)
|
|
77
|
-
lines.push(issue.snippet)
|
|
78
|
-
lines.push(`\`\`\``)
|
|
79
|
-
lines.push(``)
|
|
80
|
-
}
|
|
91
|
+
lines.push(...formatFileSection(file))
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
|