ng-checkup 0.0.0-stage → 0.2.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 +21 -0
- package/README.md +273 -2
- package/dist/checks/change-detection.d.ts +22 -0
- package/dist/checks/change-detection.js +99 -0
- package/dist/checks/change-detection.js.map +1 -0
- package/dist/checks/circular-deps.d.ts +13 -0
- package/dist/checks/circular-deps.js +43 -0
- package/dist/checks/circular-deps.js.map +1 -0
- package/dist/checks/components.d.ts +19 -0
- package/dist/checks/components.js +97 -0
- package/dist/checks/components.js.map +1 -0
- package/dist/checks/context.d.ts +51 -0
- package/dist/checks/context.js +53 -0
- package/dist/checks/context.js.map +1 -0
- package/dist/checks/deprecated-apis.d.ts +14 -0
- package/dist/checks/deprecated-apis.js +141 -0
- package/dist/checks/deprecated-apis.js.map +1 -0
- package/dist/checks/state.d.ts +13 -0
- package/dist/checks/state.js +72 -0
- package/dist/checks/state.js.map +1 -0
- package/dist/checks/testing.d.ts +13 -0
- package/dist/checks/testing.js +98 -0
- package/dist/checks/testing.js.map +1 -0
- package/dist/checks/type-safety.d.ts +14 -0
- package/dist/checks/type-safety.js +117 -0
- package/dist/checks/type-safety.js.map +1 -0
- package/dist/checks/version.d.ts +12 -0
- package/dist/checks/version.js +107 -0
- package/dist/checks/version.js.map +1 -0
- package/dist/cli.d.ts +65 -0
- package/dist/cli.js +285 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +93 -0
- package/dist/config.js.map +1 -0
- package/dist/deprecations.d.ts +30 -0
- package/dist/deprecations.js +279 -0
- package/dist/deprecations.js.map +1 -0
- package/dist/files.d.ts +33 -0
- package/dist/files.js +98 -0
- package/dist/files.js.map +1 -0
- package/dist/graph.d.ts +44 -0
- package/dist/graph.js +151 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +78 -0
- package/dist/init.js +220 -0
- package/dist/init.js.map +1 -0
- package/dist/report/html.d.ts +14 -0
- package/dist/report/html.js +210 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/markdown.d.ts +8 -0
- package/dist/report/markdown.js +55 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/summary.d.ts +45 -0
- package/dist/report/summary.js +65 -0
- package/dist/report/summary.js.map +1 -0
- package/dist/report/terminal.d.ts +17 -0
- package/dist/report/terminal.js +70 -0
- package/dist/report/terminal.js.map +1 -0
- package/dist/scan.d.ts +119 -0
- package/dist/scan.js +270 -0
- package/dist/scan.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace.d.ts +76 -0
- package/dist/workspace.js +156 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +67 -4
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/index.cjs +60 -0
- package/schematics/ng-add/schema.json +29 -0
package/dist/init.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { CONFIG_FILE } from './config.js';
|
|
2
|
+
/** The script `init` adds. Reports come from the config file, so the script stays this short. */
|
|
3
|
+
const CHECKUP_SCRIPT = 'ng-checkup';
|
|
4
|
+
/** Report files the generated config writes locally and the CI step writes as artifacts. */
|
|
5
|
+
const LOCAL_REPORT = 'checkup.html';
|
|
6
|
+
const CI_REPORTS = ['checkup.md', 'checkup.html'];
|
|
7
|
+
/**
|
|
8
|
+
* Plans what `ng-checkup init` and `ng add ng-checkup` set up in a repository:
|
|
9
|
+
*
|
|
10
|
+
* 1. a `checkup` script in `package.json` (and `ng-checkup` in `devDependencies`),
|
|
11
|
+
* 2. `ng-checkup.config.json` with the local report and an empty `ignore` list,
|
|
12
|
+
* 3. the generated reports in `.gitignore`, when there is one,
|
|
13
|
+
* 4. a CI step: a GitHub Actions workflow, or a GitLab CI file to `include`.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: on a repository that already has all of it, the plan is empty.
|
|
16
|
+
* It never overwrites a file the user wrote: an existing workflow, config or
|
|
17
|
+
* different `checkup` script is kept and reported in `notes` instead
|
|
18
|
+
* (`force` replaces the script only). It does not edit `.gitlab-ci.yml`
|
|
19
|
+
* either, since merging into someone's pipeline YAML is where a tool does
|
|
20
|
+
* damage; it writes a separate file and says which `include` to add.
|
|
21
|
+
*
|
|
22
|
+
* @throws When there is no `package.json` or it is not valid JSON. Both mean
|
|
23
|
+
* this is not the root of a Node project, and writing anything would be wrong.
|
|
24
|
+
*/
|
|
25
|
+
export function planInit(files, options) {
|
|
26
|
+
const changes = [];
|
|
27
|
+
const notes = [];
|
|
28
|
+
const packageManager = detectPackageManager(files);
|
|
29
|
+
const packageText = files.read('package.json');
|
|
30
|
+
if (packageText === undefined)
|
|
31
|
+
throw new Error('No package.json here. Run ng-checkup init in the root of your Angular project.');
|
|
32
|
+
let packageJson;
|
|
33
|
+
try {
|
|
34
|
+
packageJson = JSON.parse(packageText);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`package.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
+
}
|
|
39
|
+
const dependencies = asRecord(packageJson['dependencies']);
|
|
40
|
+
const devDependencies = asRecord(packageJson['devDependencies']);
|
|
41
|
+
if (!files.exists('angular.json') && !files.exists('nx.json') && !('@angular/core' in dependencies) && !('@angular/core' in devDependencies)) {
|
|
42
|
+
notes.push('This does not look like an Angular workspace (no angular.json, nx.json or @angular/core). The checkup will still run, but most areas will be "info".');
|
|
43
|
+
}
|
|
44
|
+
// 1. package.json: the script and the dev dependency.
|
|
45
|
+
let packageChanged = false;
|
|
46
|
+
const summaryParts = [];
|
|
47
|
+
const scripts = { ...asRecord(packageJson['scripts']) };
|
|
48
|
+
const existingScript = scripts['checkup'];
|
|
49
|
+
if (existingScript === undefined || (existingScript !== CHECKUP_SCRIPT && options.force)) {
|
|
50
|
+
scripts['checkup'] = CHECKUP_SCRIPT;
|
|
51
|
+
packageJson['scripts'] = scripts;
|
|
52
|
+
packageChanged = true;
|
|
53
|
+
summaryParts.push(`"checkup" script`);
|
|
54
|
+
}
|
|
55
|
+
else if (existingScript !== CHECKUP_SCRIPT) {
|
|
56
|
+
notes.push(`Kept your existing "checkup" script (${String(existingScript)}). Run with --force to replace it with "${CHECKUP_SCRIPT}".`);
|
|
57
|
+
}
|
|
58
|
+
const addDependency = options.addDependency ?? true;
|
|
59
|
+
if (addDependency && !('ng-checkup' in dependencies) && !('ng-checkup' in devDependencies)) {
|
|
60
|
+
packageJson['devDependencies'] = { ...devDependencies, 'ng-checkup': `^${options.version}` };
|
|
61
|
+
packageChanged = true;
|
|
62
|
+
summaryParts.push(`ng-checkup ^${options.version} in devDependencies`);
|
|
63
|
+
}
|
|
64
|
+
if (packageChanged) {
|
|
65
|
+
changes.push({ path: 'package.json', action: 'update', content: formatJsonLike(packageText, packageJson), summary: `Adds ${summaryParts.join(' and ')}` });
|
|
66
|
+
}
|
|
67
|
+
// 2. The config file.
|
|
68
|
+
if (!files.exists(CONFIG_FILE)) {
|
|
69
|
+
changes.push({
|
|
70
|
+
path: CONFIG_FILE,
|
|
71
|
+
action: 'create',
|
|
72
|
+
content: `${JSON.stringify({ reports: [LOCAL_REPORT], ignore: [] }, null, 2)}\n`,
|
|
73
|
+
summary: `Writes ${LOCAL_REPORT} on every run; add paths to "ignore" to leave them out`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// 3. .gitignore, only when the repository has one.
|
|
77
|
+
const gitignore = files.read('.gitignore');
|
|
78
|
+
if (gitignore !== undefined) {
|
|
79
|
+
const lines = new Set(gitignore.split(/\r?\n/).map((line) => line.trim()));
|
|
80
|
+
const missing = [...new Set([LOCAL_REPORT, ...CI_REPORTS])].map((report) => `/${report}`).filter((entry) => !lines.has(entry) && !lines.has(entry.slice(1)));
|
|
81
|
+
if (missing.length > 0) {
|
|
82
|
+
const separator = gitignore.length === 0 || gitignore.endsWith('\n') ? '' : '\n';
|
|
83
|
+
changes.push({ path: '.gitignore', action: 'update', content: `${gitignore}${separator}\n# ng-checkup reports\n${missing.join('\n')}\n`, summary: 'Keeps the generated reports out of git' });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// 4. CI.
|
|
87
|
+
const ci = resolveCi(files, options.ci ?? 'auto');
|
|
88
|
+
if (ci === 'github') {
|
|
89
|
+
const path = '.github/workflows/ng-checkup.yml';
|
|
90
|
+
if (files.exists(path))
|
|
91
|
+
notes.push(`Kept the existing ${path}.`);
|
|
92
|
+
else
|
|
93
|
+
changes.push({ path, action: 'create', content: githubWorkflow(packageManager), summary: 'Runs the checkup on every pull request and uploads the reports' });
|
|
94
|
+
}
|
|
95
|
+
else if (ci === 'gitlab') {
|
|
96
|
+
const path = 'ng-checkup.gitlab-ci.yml';
|
|
97
|
+
if (files.exists(path))
|
|
98
|
+
notes.push(`Kept the existing ${path}.`);
|
|
99
|
+
else
|
|
100
|
+
changes.push({ path, action: 'create', content: gitlabJob(packageManager), summary: 'A GitLab CI job that runs the checkup and keeps the reports as artifacts' });
|
|
101
|
+
if (!(files.read('.gitlab-ci.yml') ?? '').includes(path)) {
|
|
102
|
+
notes.push(`Add this to your .gitlab-ci.yml to run it:\n include:\n - local: ${path}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if ((options.ci ?? 'auto') === 'auto') {
|
|
106
|
+
notes.push('No .github/ or .gitlab-ci.yml found, so no CI step was added. Run with --ci github or --ci gitlab to add one.');
|
|
107
|
+
}
|
|
108
|
+
const nextSteps = [];
|
|
109
|
+
if (packageChanged && addDependency)
|
|
110
|
+
nextSteps.push(installCommand(packageManager, false));
|
|
111
|
+
nextSteps.push(runScriptCommand(packageManager));
|
|
112
|
+
return { changes, notes, nextSteps, ci, packageManager };
|
|
113
|
+
}
|
|
114
|
+
/** The CI to set up for `auto`: whatever the repository already uses. */
|
|
115
|
+
function resolveCi(files, choice) {
|
|
116
|
+
if (choice !== 'auto')
|
|
117
|
+
return choice;
|
|
118
|
+
if (files.exists('.github'))
|
|
119
|
+
return 'github';
|
|
120
|
+
if (files.exists('.gitlab-ci.yml'))
|
|
121
|
+
return 'gitlab';
|
|
122
|
+
return 'none';
|
|
123
|
+
}
|
|
124
|
+
/** The package manager, from its lockfile. npm when there is none or only npm's. */
|
|
125
|
+
export function detectPackageManager(files) {
|
|
126
|
+
if (files.exists('pnpm-lock.yaml'))
|
|
127
|
+
return 'pnpm';
|
|
128
|
+
if (files.exists('yarn.lock'))
|
|
129
|
+
return 'yarn';
|
|
130
|
+
return 'npm';
|
|
131
|
+
}
|
|
132
|
+
/** Install command; `frozen` is the CI form that refuses to change the lockfile. */
|
|
133
|
+
function installCommand(manager, frozen) {
|
|
134
|
+
if (manager === 'pnpm')
|
|
135
|
+
return frozen ? 'pnpm install --frozen-lockfile' : 'pnpm install';
|
|
136
|
+
if (manager === 'yarn')
|
|
137
|
+
return frozen ? 'yarn install --frozen-lockfile' : 'yarn install';
|
|
138
|
+
return frozen ? 'npm ci' : 'npm install';
|
|
139
|
+
}
|
|
140
|
+
/** How to run the `checkup` script. */
|
|
141
|
+
function runScriptCommand(manager) {
|
|
142
|
+
return manager === 'npm' ? 'npm run checkup' : `${manager} checkup`;
|
|
143
|
+
}
|
|
144
|
+
/** How CI runs the installed binary. */
|
|
145
|
+
function binCommand(manager) {
|
|
146
|
+
if (manager === 'pnpm')
|
|
147
|
+
return 'pnpm exec ng-checkup';
|
|
148
|
+
if (manager === 'yarn')
|
|
149
|
+
return 'yarn ng-checkup';
|
|
150
|
+
return 'npx ng-checkup';
|
|
151
|
+
}
|
|
152
|
+
/** The CI invocation: fail on a poor area, keep both report formats. */
|
|
153
|
+
function ciRun(manager) {
|
|
154
|
+
return `${binCommand(manager)} --fail-on poor ${CI_REPORTS.map((report) => `--report ${report}`).join(' ')}`;
|
|
155
|
+
}
|
|
156
|
+
/** A GitHub Actions workflow for the repository's package manager. */
|
|
157
|
+
export function githubWorkflow(manager) {
|
|
158
|
+
const pnpmSetup = manager === 'pnpm' ? ' - uses: pnpm/action-setup@v4\n' : '';
|
|
159
|
+
return `# Added by ng-checkup init. Grades the Angular codebase on every pull request
|
|
160
|
+
# and fails the job when an area turns poor. Reports: see the run's artifacts.
|
|
161
|
+
name: ng-checkup
|
|
162
|
+
|
|
163
|
+
on:
|
|
164
|
+
pull_request:
|
|
165
|
+
push:
|
|
166
|
+
branches: [main]
|
|
167
|
+
|
|
168
|
+
jobs:
|
|
169
|
+
checkup:
|
|
170
|
+
runs-on: ubuntu-latest
|
|
171
|
+
steps:
|
|
172
|
+
- uses: actions/checkout@v4
|
|
173
|
+
${pnpmSetup} - uses: actions/setup-node@v4
|
|
174
|
+
with:
|
|
175
|
+
node-version: 22
|
|
176
|
+
cache: ${manager}
|
|
177
|
+
- run: ${installCommand(manager, true)}
|
|
178
|
+
- run: ${ciRun(manager)}
|
|
179
|
+
- uses: actions/upload-artifact@v4
|
|
180
|
+
if: always()
|
|
181
|
+
with:
|
|
182
|
+
name: ng-checkup
|
|
183
|
+
path: |
|
|
184
|
+
${CI_REPORTS.map((report) => ` ${report}`).join('\n')}
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
/** A GitLab CI job file for the repository's package manager, meant to be `include`d. */
|
|
188
|
+
export function gitlabJob(manager) {
|
|
189
|
+
const corepack = manager === 'pnpm' ? ' - corepack enable\n' : '';
|
|
190
|
+
return `# Added by ng-checkup init. Include it from .gitlab-ci.yml:
|
|
191
|
+
# include:
|
|
192
|
+
# - local: ng-checkup.gitlab-ci.yml
|
|
193
|
+
ng-checkup:
|
|
194
|
+
image: node:22
|
|
195
|
+
script:
|
|
196
|
+
${corepack} - ${installCommand(manager, true)}
|
|
197
|
+
- ${ciRun(manager)}
|
|
198
|
+
artifacts:
|
|
199
|
+
when: always
|
|
200
|
+
expose_as: ng-checkup report
|
|
201
|
+
paths:
|
|
202
|
+
${CI_REPORTS.map((report) => ` - ${report}`).join('\n')}
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Serialises `value` with the indentation and final newline of `original`,
|
|
207
|
+
* so `init` changes only the lines it means to and a `git diff` of
|
|
208
|
+
* `package.json` shows exactly the added keys.
|
|
209
|
+
*/
|
|
210
|
+
export function formatJsonLike(original, value) {
|
|
211
|
+
const indent = /^[{[]\r?\n([ \t]+)/.exec(original)?.[1] ?? ' ';
|
|
212
|
+
const newline = original.includes('\r\n') ? '\r\n' : '\n';
|
|
213
|
+
const body = JSON.stringify(value, null, indent).replace(/\n/g, newline);
|
|
214
|
+
return original.endsWith('\n') ? `${body}${newline}` : body;
|
|
215
|
+
}
|
|
216
|
+
/** Narrows an unknown JSON value to an object, treating anything else as empty. */
|
|
217
|
+
function asRecord(value) {
|
|
218
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=init.js.map
|
package/dist/init.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Report } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Renders the report as one self-contained HTML file.
|
|
4
|
+
*
|
|
5
|
+
* Self-contained is a hard requirement, not a style choice: the report is
|
|
6
|
+
* meant to be mailed to a client, attached to a ticket or opened on a machine
|
|
7
|
+
* without network, and it describes a private codebase. So there is no
|
|
8
|
+
* external font, script, stylesheet or image, and nothing on the page phones
|
|
9
|
+
* home. It follows the system light/dark preference and prints cleanly, since
|
|
10
|
+
* "save as PDF" is how many readers will pass it on.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderHtml(report: Report): string;
|
|
13
|
+
/** HTML-escapes text for element content and double-quoted attributes. */
|
|
14
|
+
export declare function escape(text: string): string;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { formatCount, plural } from '../checks/context.js';
|
|
2
|
+
import { formatDuration, formatLocation, gradeLabel, kindLabel, overallScore, remainingCount, SCORE_EXPLANATION, severityLabel, topPriorities } from './summary.js';
|
|
3
|
+
/**
|
|
4
|
+
* Renders the report as one self-contained HTML file.
|
|
5
|
+
*
|
|
6
|
+
* Self-contained is a hard requirement, not a style choice: the report is
|
|
7
|
+
* meant to be mailed to a client, attached to a ticket or opened on a machine
|
|
8
|
+
* without network, and it describes a private codebase. So there is no
|
|
9
|
+
* external font, script, stylesheet or image, and nothing on the page phones
|
|
10
|
+
* home. It follows the system light/dark preference and prints cleanly, since
|
|
11
|
+
* "save as PDF" is how many readers will pass it on.
|
|
12
|
+
*/
|
|
13
|
+
export function renderHtml(report) {
|
|
14
|
+
const { project } = report;
|
|
15
|
+
const score = overallScore(report);
|
|
16
|
+
const priorities = topPriorities(report);
|
|
17
|
+
const meta = [kindLabel(project.kind), project.angularVersion ? `Angular ${project.angularVersion}` : undefined, plural(project.sourceFiles, 'TypeScript file'), plural(project.templateFiles, 'template'), plural(project.linesOfCode, 'line') + ' of code']
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.join(' · ');
|
|
20
|
+
return `<!doctype html>
|
|
21
|
+
<html lang="en">
|
|
22
|
+
<head>
|
|
23
|
+
<meta charset="utf-8">
|
|
24
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
25
|
+
<meta name="generator" content="${escape(`${report.tool.name} ${report.tool.version}`)}">
|
|
26
|
+
<title>Angular checkup: ${escape(project.name)}</title>
|
|
27
|
+
<style>${STYLES}</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<main>
|
|
31
|
+
<header class="masthead">
|
|
32
|
+
<div>
|
|
33
|
+
<p class="eyebrow">Angular checkup</p>
|
|
34
|
+
<h1>${escape(project.name)}</h1>
|
|
35
|
+
<p class="meta">${escape(meta)}</p>
|
|
36
|
+
<p class="meta">Generated ${escape(report.generatedAt.slice(0, 10))}</p>
|
|
37
|
+
</div>
|
|
38
|
+
${score === undefined ? '' : scoreRing(score)}
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
<section aria-labelledby="overview">
|
|
42
|
+
<h2 id="overview" class="section-title">Overview</h2>
|
|
43
|
+
<div class="cards">
|
|
44
|
+
${report.checks.map(overviewCard).join('\n ')}
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
${priorities.length === 0
|
|
49
|
+
? ''
|
|
50
|
+
: `<section aria-labelledby="priorities">
|
|
51
|
+
<h2 id="priorities" class="section-title">Top priorities</h2>
|
|
52
|
+
<ol class="priorities">
|
|
53
|
+
${priorities
|
|
54
|
+
.map(({ check, finding }) => `<li><span class="chip sev-${finding.severity}">${severityLabel(finding.severity)}</span><span><strong>${inline(finding.message)}</strong> <a href="#${check.id}" class="area">${escape(check.title)}</a></span></li>`)
|
|
55
|
+
.join('\n ')}
|
|
56
|
+
</ol>
|
|
57
|
+
</section>`}
|
|
58
|
+
|
|
59
|
+
${report.checks.map(checkSection).join('\n ')}
|
|
60
|
+
|
|
61
|
+
<footer>
|
|
62
|
+
<p>${score === undefined ? '' : `Score: ${escape(SCORE_EXPLANATION)} `}File counts are static and need no build; deprecations come from the installed packages' own <code>@deprecated</code> tags.</p>
|
|
63
|
+
<p>Generated by ${escape(report.tool.name)} ${escape(report.tool.version)} in ${escape(formatDuration(report.durationMs))}.</p>
|
|
64
|
+
</footer>
|
|
65
|
+
</main>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
/** The score as a ring: the arc length is the score, the colour its band. */
|
|
71
|
+
function scoreRing(score) {
|
|
72
|
+
const radius = 42;
|
|
73
|
+
const circumference = 2 * Math.PI * radius;
|
|
74
|
+
const band = score >= 80 ? 'good' : score >= 50 ? 'fair' : 'poor';
|
|
75
|
+
return `<figure class="score" aria-label="Score ${score} out of 100">
|
|
76
|
+
<svg viewBox="0 0 100 100" width="112" height="112" aria-hidden="true">
|
|
77
|
+
<circle cx="50" cy="50" r="${radius}" class="ring-track"/>
|
|
78
|
+
<circle cx="50" cy="50" r="${radius}" class="ring-value grade-${band}" stroke-dasharray="${((score / 100) * circumference).toFixed(1)} ${circumference.toFixed(1)}" transform="rotate(-90 50 50)"/>
|
|
79
|
+
</svg>
|
|
80
|
+
<figcaption><span class="score-value">${score}</span><span class="score-unit">/100</span></figcaption>
|
|
81
|
+
</figure>`;
|
|
82
|
+
}
|
|
83
|
+
/** One card of the overview grid, linking to its section. */
|
|
84
|
+
function overviewCard(check) {
|
|
85
|
+
return `<a class="card" href="#${check.id}"><span class="pill grade-${check.grade}">${gradeLabel(check.grade)}</span><strong>${escape(check.title)}</strong><span class="card-summary">${inline(check.summary)}</span></a>`;
|
|
86
|
+
}
|
|
87
|
+
/** The full section of one check: summary, metric grid, findings. */
|
|
88
|
+
function checkSection(check) {
|
|
89
|
+
return `<section class="check" id="${check.id}" aria-labelledby="${check.id}-title">
|
|
90
|
+
<div class="check-head">
|
|
91
|
+
<h2 id="${check.id}-title">${escape(check.title)}</h2>
|
|
92
|
+
<span class="pill grade-${check.grade}">${gradeLabel(check.grade)}</span>
|
|
93
|
+
</div>
|
|
94
|
+
<p class="check-summary">${inline(check.summary)}</p>
|
|
95
|
+
${check.metrics.length === 0
|
|
96
|
+
? ''
|
|
97
|
+
: `<dl class="metrics">
|
|
98
|
+
${check.metrics.map((metric) => `<div class="metric"><dt>${escape(metric.label)}</dt><dd>${escape(String(metric.value))}</dd>${metric.hint ? `<dd class="hint">${escape(metric.hint)}</dd>` : ''}</div>`).join('\n ')}
|
|
99
|
+
</dl>`}
|
|
100
|
+
${check.findings.length === 0 ? '' : `<ul class="findings">\n ${check.findings.map(findingItem).join('\n ')}\n </ul>`}
|
|
101
|
+
</section>`;
|
|
102
|
+
}
|
|
103
|
+
/** One finding with its detail and example files. */
|
|
104
|
+
function findingItem(finding) {
|
|
105
|
+
const more = remainingCount(finding);
|
|
106
|
+
const files = finding.files ?? [];
|
|
107
|
+
return `<li class="finding">
|
|
108
|
+
<div class="finding-head"><span class="chip sev-${finding.severity}">${severityLabel(finding.severity)}</span><strong>${inline(finding.message)}</strong></div>
|
|
109
|
+
${finding.detail ? `<p>${inline(finding.detail)}</p>` : ''}
|
|
110
|
+
${files.length === 0 ? '' : `<ul class="files">${files.map((file) => `<li>${escape(formatLocation(file))}</li>`).join('')}${more > 0 ? `<li class="more">and ${formatCount(more)} more</li>` : ''}</ul>`}
|
|
111
|
+
</li>`;
|
|
112
|
+
}
|
|
113
|
+
/** Escapes text and turns backtick spans into `<code>`, the only markup finding text uses. */
|
|
114
|
+
function inline(text) {
|
|
115
|
+
return escape(text).replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
116
|
+
}
|
|
117
|
+
/** HTML-escapes text for element content and double-quoted attributes. */
|
|
118
|
+
export function escape(text) {
|
|
119
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The stylesheet. Colours are tokens on `:root`, redefined for dark mode, so
|
|
123
|
+
* the grade and severity colours stay legible on both backgrounds.
|
|
124
|
+
*/
|
|
125
|
+
const STYLES = `
|
|
126
|
+
:root {
|
|
127
|
+
--bg: #f7f7f5; --surface: #ffffff; --text: #1b1d21; --muted: #5d636e; --border: #e3e3df; --code-bg: #f0f0ec;
|
|
128
|
+
--good: #1f7a4d; --good-bg: #e3f3ea; --fair: #8a5a00; --fair-bg: #fbf0d9; --poor: #b42318; --poor-bg: #fde8e6; --info: #5d636e; --info-bg: #ececea;
|
|
129
|
+
--accent: #3b5bdb; --ring-track: #e7e7e3;
|
|
130
|
+
color-scheme: light;
|
|
131
|
+
}
|
|
132
|
+
@media (prefers-color-scheme: dark) {
|
|
133
|
+
:root {
|
|
134
|
+
--bg: #121316; --surface: #1b1d21; --text: #ececec; --muted: #9aa0aa; --border: #2c2f35; --code-bg: #25282e;
|
|
135
|
+
--good: #5cc98f; --good-bg: #173326; --fair: #e8b54d; --fair-bg: #3a2d10; --poor: #f17a6e; --poor-bg: #3d1a17; --info: #9aa0aa; --info-bg: #2a2c31;
|
|
136
|
+
--accent: #8ea6ff; --ring-track: #2c2f35;
|
|
137
|
+
color-scheme: dark;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
* { box-sizing: border-box; }
|
|
141
|
+
body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; -webkit-font-smoothing: antialiased; }
|
|
142
|
+
main { max-width: 960px; margin: 0 auto; padding: 40px 16px 56px; }
|
|
143
|
+
code, .files { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.88em; }
|
|
144
|
+
code { background: var(--code-bg); padding: 1px 5px; border-radius: 4px; overflow-wrap: anywhere; }
|
|
145
|
+
a { color: inherit; }
|
|
146
|
+
.masthead { display: flex; justify-content: space-between; align-items: center; gap: 24px; padding-bottom: 28px; border-bottom: 1px solid var(--border); }
|
|
147
|
+
.eyebrow { margin: 0 0 4px; color: var(--accent); font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }
|
|
148
|
+
h1 { margin: 0 0 8px; font-size: 32px; line-height: 1.15; letter-spacing: -0.01em; overflow-wrap: anywhere; }
|
|
149
|
+
.meta { margin: 0; color: var(--muted); font-size: 14px; }
|
|
150
|
+
.score { margin: 0; position: relative; width: 112px; height: 112px; flex: none; }
|
|
151
|
+
.score svg { display: block; }
|
|
152
|
+
.ring-track { fill: none; stroke: var(--ring-track); stroke-width: 9; }
|
|
153
|
+
.ring-value { fill: none; stroke-width: 9; stroke-linecap: round; }
|
|
154
|
+
.ring-value.grade-good { stroke: var(--good); } .ring-value.grade-fair { stroke: var(--fair); } .ring-value.grade-poor { stroke: var(--poor); }
|
|
155
|
+
.score figcaption { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
156
|
+
.score-value { font-size: 30px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
157
|
+
.score-unit { color: var(--muted); font-size: 13px; margin-left: 1px; padding-top: 10px; }
|
|
158
|
+
.section-title { font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin: 36px 0 12px; }
|
|
159
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
|
|
160
|
+
.card { display: flex; flex-direction: column; gap: 6px; padding: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; text-decoration: none; transition: border-color 0.15s; }
|
|
161
|
+
.card:hover { border-color: var(--accent); }
|
|
162
|
+
.card strong { font-size: 15px; }
|
|
163
|
+
.card-summary { color: var(--muted); font-size: 13.5px; }
|
|
164
|
+
.pill, .chip { display: inline-block; width: fit-content; border-radius: 999px; font-size: 12px; font-weight: 600; padding: 2px 10px; }
|
|
165
|
+
.grade-good.pill { color: var(--good); background: var(--good-bg); } .grade-fair.pill { color: var(--fair); background: var(--fair-bg); }
|
|
166
|
+
.grade-poor.pill { color: var(--poor); background: var(--poor-bg); } .grade-info.pill { color: var(--info); background: var(--info-bg); }
|
|
167
|
+
.chip { flex: none; padding: 1px 8px; font-size: 11.5px; }
|
|
168
|
+
.sev-high { color: var(--poor); background: var(--poor-bg); } .sev-medium { color: var(--fair); background: var(--fair-bg); }
|
|
169
|
+
.sev-low, .sev-info { color: var(--info); background: var(--info-bg); }
|
|
170
|
+
.priorities { margin: 0; padding: 0; list-style: none; display: grid; gap: 8px; counter-reset: priority; }
|
|
171
|
+
.priorities li { display: flex; gap: 10px; align-items: baseline; padding: 12px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; }
|
|
172
|
+
.priorities .area { color: var(--muted); font-size: 13px; margin-left: 6px; white-space: nowrap; }
|
|
173
|
+
.check { margin-top: 40px; padding-top: 28px; border-top: 1px solid var(--border); }
|
|
174
|
+
.check-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
175
|
+
.check-head h2 { margin: 0; font-size: 22px; letter-spacing: -0.005em; }
|
|
176
|
+
.check-summary { margin: 8px 0 16px; color: var(--muted); }
|
|
177
|
+
.metrics { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin: 0 0 18px; }
|
|
178
|
+
.metric { padding: 12px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; }
|
|
179
|
+
.metric dt { color: var(--muted); font-size: 12.5px; }
|
|
180
|
+
.metric dd { margin: 2px 0 0; font-size: 20px; font-weight: 650; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
181
|
+
.metric dd.hint { font-size: 12.5px; font-weight: 400; color: var(--muted); }
|
|
182
|
+
.findings { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
|
|
183
|
+
.finding { min-width: 0; padding: 14px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; break-inside: avoid; }
|
|
184
|
+
.finding-head { display: flex; gap: 10px; align-items: baseline; }
|
|
185
|
+
.finding p { margin: 6px 0 0; color: var(--muted); font-size: 14px; }
|
|
186
|
+
.files { margin: 8px 0 0; padding: 8px 12px; list-style: none; background: var(--code-bg); border-radius: 6px; color: var(--muted); overflow-x: auto; }
|
|
187
|
+
.files li { white-space: nowrap; line-height: 1.7; }
|
|
188
|
+
.files .more { font-family: inherit; font-style: italic; }
|
|
189
|
+
footer { margin-top: 48px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--muted); font-size: 13px; }
|
|
190
|
+
footer p { margin: 4px 0; }
|
|
191
|
+
@media (max-width: 600px) {
|
|
192
|
+
main { padding-top: 24px; }
|
|
193
|
+
.masthead { flex-direction: column-reverse; align-items: flex-start; }
|
|
194
|
+
h1 { font-size: 26px; }
|
|
195
|
+
.priorities li, .finding-head { flex-direction: column; gap: 6px; }
|
|
196
|
+
.priorities .area { margin-left: 0; }
|
|
197
|
+
}
|
|
198
|
+
@media print {
|
|
199
|
+
/* Printing from a dark-mode browser must still give black on white. */
|
|
200
|
+
:root {
|
|
201
|
+
--bg: #fff; --surface: #fff; --text: #1b1d21; --muted: #5d636e; --border: #e3e3df; --code-bg: #f0f0ec;
|
|
202
|
+
--good: #1f7a4d; --good-bg: #e3f3ea; --fair: #8a5a00; --fair-bg: #fbf0d9; --poor: #b42318; --poor-bg: #fde8e6; --info: #5d636e; --info-bg: #ececea;
|
|
203
|
+
--accent: #3b5bdb; --ring-track: #e7e7e3; color-scheme: light;
|
|
204
|
+
}
|
|
205
|
+
main { padding: 0; max-width: none; }
|
|
206
|
+
.card:hover { border-color: var(--border); }
|
|
207
|
+
.check { break-before: auto; }
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/report/html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Report } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Renders the report as GitHub-flavoured Markdown, for a pull request, a
|
|
4
|
+
* wiki page or an issue. Finding text already uses Markdown backticks, so it
|
|
5
|
+
* goes in as written; only table cells are escaped, where a `|` would split
|
|
6
|
+
* the row.
|
|
7
|
+
*/
|
|
8
|
+
export declare function renderMarkdown(report: Report): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { formatCount, plural } from '../checks/context.js';
|
|
2
|
+
import { formatDuration, formatLocation, gradeLabel, kindLabel, overallScore, remainingCount, SCORE_EXPLANATION, severityLabel, topPriorities } from './summary.js';
|
|
3
|
+
/**
|
|
4
|
+
* Renders the report as GitHub-flavoured Markdown, for a pull request, a
|
|
5
|
+
* wiki page or an issue. Finding text already uses Markdown backticks, so it
|
|
6
|
+
* goes in as written; only table cells are escaped, where a `|` would split
|
|
7
|
+
* the row.
|
|
8
|
+
*/
|
|
9
|
+
export function renderMarkdown(report) {
|
|
10
|
+
const { project } = report;
|
|
11
|
+
const lines = [];
|
|
12
|
+
const score = overallScore(report);
|
|
13
|
+
lines.push(`# Angular checkup: ${project.name}`, '');
|
|
14
|
+
lines.push(`${kindLabel(project.kind)}${project.angularVersion ? ` · Angular ${project.angularVersion}` : ''} · ${plural(project.sourceFiles, 'TypeScript file')} · ${plural(project.templateFiles, 'template')} · ${plural(project.linesOfCode, 'line')} of application code`, '');
|
|
15
|
+
if (score !== undefined)
|
|
16
|
+
lines.push(`**Score: ${score}/100.** ${SCORE_EXPLANATION}`, '');
|
|
17
|
+
lines.push('| Area | Grade | Summary |', '| --- | --- | --- |');
|
|
18
|
+
for (const check of report.checks)
|
|
19
|
+
lines.push(`| ${cell(check.title)} | ${gradeLabel(check.grade)} | ${cell(check.summary)} |`);
|
|
20
|
+
lines.push('');
|
|
21
|
+
const priorities = topPriorities(report);
|
|
22
|
+
if (priorities.length > 0) {
|
|
23
|
+
lines.push('## Top priorities', '');
|
|
24
|
+
priorities.forEach(({ check, finding }, index) => lines.push(`${index + 1}. **${severityLabel(finding.severity)}:** ${finding.message} _(${check.title})_`));
|
|
25
|
+
lines.push('');
|
|
26
|
+
}
|
|
27
|
+
for (const check of report.checks) {
|
|
28
|
+
lines.push(`## ${check.title}: ${gradeLabel(check.grade)}`, '', check.summary, '');
|
|
29
|
+
if (check.metrics.length > 0) {
|
|
30
|
+
lines.push('| Metric | Value | |', '| --- | --- | --- |');
|
|
31
|
+
for (const metric of check.metrics)
|
|
32
|
+
lines.push(`| ${cell(metric.label)} | ${cell(String(metric.value))} | ${cell(metric.hint ?? '')} |`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
}
|
|
35
|
+
for (const finding of check.findings) {
|
|
36
|
+
lines.push(`- **${severityLabel(finding.severity)}:** ${finding.message}`);
|
|
37
|
+
if (finding.detail)
|
|
38
|
+
lines.push(` ${finding.detail}`);
|
|
39
|
+
for (const file of finding.files ?? [])
|
|
40
|
+
lines.push(` - \`${formatLocation(file)}\``);
|
|
41
|
+
const more = remainingCount(finding);
|
|
42
|
+
if (more > 0)
|
|
43
|
+
lines.push(` - and ${formatCount(more)} more`);
|
|
44
|
+
}
|
|
45
|
+
if (check.findings.length > 0)
|
|
46
|
+
lines.push('');
|
|
47
|
+
}
|
|
48
|
+
lines.push('---', '', `Generated ${report.generatedAt.slice(0, 10)} by ${report.tool.name} ${report.tool.version} in ${formatDuration(report.durationMs)}.`, '');
|
|
49
|
+
return lines.join('\n');
|
|
50
|
+
}
|
|
51
|
+
/** Escapes a table cell: a `|` would end it and a newline would end the row. */
|
|
52
|
+
function cell(text) {
|
|
53
|
+
return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/report/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CheckResult, Finding, Grade, Report, Severity } from '../types.js';
|
|
2
|
+
/** A finding together with the check it belongs to, for the cross-check priority list. */
|
|
3
|
+
export interface Priority {
|
|
4
|
+
check: CheckResult;
|
|
5
|
+
finding: Finding;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The overall score, 0 to 100: the average of the graded areas at 100 for
|
|
9
|
+
* good, 60 for fair and 20 for poor. Areas graded `info` are left out, so a
|
|
10
|
+
* repository is not marked down for what the tool could not measure.
|
|
11
|
+
* Undefined when nothing was graded at all.
|
|
12
|
+
*
|
|
13
|
+
* A deliberately simple formula. Every report prints it next to the score,
|
|
14
|
+
* because a number whose derivation is hidden invites arguing with it instead
|
|
15
|
+
* of with the findings.
|
|
16
|
+
*/
|
|
17
|
+
export declare function overallScore(report: Report): number | undefined;
|
|
18
|
+
/** The sentence every format prints under the score. */
|
|
19
|
+
export declare const SCORE_EXPLANATION = "Average of the graded areas: good counts 100, fair 60, poor 20. Areas marked info are not counted.";
|
|
20
|
+
/**
|
|
21
|
+
* The findings to act on first, across all checks: every `high` finding,
|
|
22
|
+
* then `medium` ones if fewer than `limit` are high, in report order within a
|
|
23
|
+
* severity. Low and info findings never make the list.
|
|
24
|
+
*/
|
|
25
|
+
export declare function topPriorities(report: Report, limit?: number): Priority[];
|
|
26
|
+
/** Display label of a grade. */
|
|
27
|
+
export declare function gradeLabel(grade: Grade): string;
|
|
28
|
+
/** Display label of a severity. */
|
|
29
|
+
export declare function severityLabel(severity: Severity): string;
|
|
30
|
+
/** `path:line`, or just `path` when the location has no line. */
|
|
31
|
+
export declare function formatLocation(ref: {
|
|
32
|
+
path: string;
|
|
33
|
+
line?: number;
|
|
34
|
+
}): string;
|
|
35
|
+
/** "1.5 s" or "820 ms". */
|
|
36
|
+
export declare function formatDuration(milliseconds: number): string;
|
|
37
|
+
/** The workspace kind as a reader would name it. */
|
|
38
|
+
export declare function kindLabel(kind: Report['project']['kind']): string;
|
|
39
|
+
/**
|
|
40
|
+
* How many more files a finding covers than it lists. 0 when it lists them
|
|
41
|
+
* all, and 0 when it lists none: then `count` is a number of occurrences in
|
|
42
|
+
* the message, not of files, and "and 12 more" under an empty list would
|
|
43
|
+
* read as a rendering bug.
|
|
44
|
+
*/
|
|
45
|
+
export declare function remainingCount(finding: Finding): number;
|