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
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { applicationFiles, formatCount, plural } from './context.js';
|
|
2
|
+
/**
|
|
3
|
+
* Explicit `any` per 1 000 lines of application code above which the count is
|
|
4
|
+
* a `medium` finding rather than a `low` one. Two per thousand lines is roughly
|
|
5
|
+
* one per large component: past that, `any` is a habit rather than an exception.
|
|
6
|
+
*/
|
|
7
|
+
const ANY_DENSITY_THRESHOLD = 2;
|
|
8
|
+
/**
|
|
9
|
+
* How much of the code the compiler actually checks: the strictness flags of
|
|
10
|
+
* the application tsconfig, and the escape hatches the code uses to get
|
|
11
|
+
* around them.
|
|
12
|
+
*
|
|
13
|
+
* `strictNullChecks` and `strictTemplates` carry the grade. They are the two
|
|
14
|
+
* flags whose absence lets the most common runtime errors through (an
|
|
15
|
+
* `undefined` dereference, a mistyped template binding), and both are the
|
|
16
|
+
* ones large codebases most often have switched off because turning them on
|
|
17
|
+
* is a campaign rather than an edit.
|
|
18
|
+
*/
|
|
19
|
+
export function typeSafetyCheck(context) {
|
|
20
|
+
const options = context.appCompilerOptions;
|
|
21
|
+
const angular = context.angularCompilerOptions;
|
|
22
|
+
const hasConfig = context.workspace.appTsconfig !== undefined;
|
|
23
|
+
// What an absent flag means changed: `strict` is on by default from
|
|
24
|
+
// TypeScript 6, and `strictTemplates` from Angular 22 (17 to 21 default to
|
|
25
|
+
// false). A brand-new Angular 22 project sets neither and is fully strict,
|
|
26
|
+
// so reading only what the tsconfig says would grade it poor.
|
|
27
|
+
const typescriptMajor = context.workspace.typescriptMajor;
|
|
28
|
+
const angularMajor = context.workspace.angular.major;
|
|
29
|
+
const strictByDefault = typescriptMajor !== undefined && typescriptMajor >= 6;
|
|
30
|
+
const strictTemplatesByDefault = angularMajor !== undefined && angularMajor >= 22;
|
|
31
|
+
const strictNullChecks = options.strictNullChecks ?? options.strict ?? strictByDefault;
|
|
32
|
+
const noImplicitAny = options.noImplicitAny ?? options.strict ?? strictByDefault;
|
|
33
|
+
const explicitStrictTemplates = angular['strictTemplates'];
|
|
34
|
+
const strictTemplates = typeof explicitStrictTemplates === 'boolean' ? explicitStrictTemplates : strictTemplatesByDefault;
|
|
35
|
+
/** "default in TypeScript 6" when the value was not written down, so the reader knows where it came from. */
|
|
36
|
+
const source = (written, origin) => (written ? undefined : `default in ${origin}`);
|
|
37
|
+
const strictWritten = options.strict !== undefined;
|
|
38
|
+
const application = applicationFiles(context);
|
|
39
|
+
const linesOfCode = application.reduce((sum, file) => sum + file.linesOfCode, 0);
|
|
40
|
+
const anyCount = application.reduce((sum, file) => sum + file.anyLines.length, 0);
|
|
41
|
+
const anyDensity = linesOfCode === 0 ? 0 : (anyCount / linesOfCode) * 1000;
|
|
42
|
+
const nonNull = application.reduce((sum, file) => sum + file.nonNullLines.length, 0);
|
|
43
|
+
const suppressions = context.sources.reduce((sum, file) => sum + file.typeSuppressions, 0);
|
|
44
|
+
const templateAnyCasts = context.templates.reduce((sum, template) => sum + template.stats.anyCasts, 0);
|
|
45
|
+
const untypedForms = application.filter((file) => file.imports.some((fact) => fact.specifier === '@angular/forms' && fact.bindings.some((binding) => binding.imported.startsWith('Untyped'))));
|
|
46
|
+
const findings = [];
|
|
47
|
+
if (hasConfig && !strictNullChecks) {
|
|
48
|
+
findings.push({
|
|
49
|
+
severity: 'high',
|
|
50
|
+
message: '`strictNullChecks` is off',
|
|
51
|
+
detail: 'Every value that may be `null` or `undefined` is typed as if it never is, so the most common runtime error in an Angular app is invisible to the compiler. In a large codebase switching it on is staged: a separate tsconfig that checks one folder at a time, with a count that may only go down.',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (hasConfig && !strictTemplates) {
|
|
55
|
+
findings.push({
|
|
56
|
+
severity: 'high',
|
|
57
|
+
message: '`strictTemplates` is off',
|
|
58
|
+
detail: 'Template bindings are only loosely type-checked: a wrong input type, a misspelled output payload or a possibly-undefined value passed to a component compiles and fails at runtime. Turning it on typically surfaces a batch of real bugs alongside the type noise.',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (hasConfig && !noImplicitAny) {
|
|
62
|
+
findings.push({ severity: 'medium', message: '`noImplicitAny` is off', detail: 'An unannotated parameter silently becomes `any`, and no lint rule that counts the word `any` can see it.' });
|
|
63
|
+
}
|
|
64
|
+
if (anyCount > 0) {
|
|
65
|
+
const examples = application.filter((file) => file.anyLines.length > 0).sort((left, right) => right.anyLines.length - left.anyLines.length);
|
|
66
|
+
findings.push({
|
|
67
|
+
severity: anyDensity > ANY_DENSITY_THRESHOLD ? 'medium' : 'low',
|
|
68
|
+
message: `${formatCount(anyCount)} explicit \`any\` in application code (${anyDensity.toFixed(1)} per 1 000 lines)`,
|
|
69
|
+
detail: 'Files with the most are listed first. Each `any` switches type checking off for everything that flows through it.',
|
|
70
|
+
files: examples.slice(0, context.maxExamples).map((file) => ({ path: file.path, line: file.anyLines[0] })),
|
|
71
|
+
count: examples.length,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (nonNull > 0) {
|
|
75
|
+
findings.push({ severity: 'low', message: `${plural(nonNull, 'non-null assertion')} (\`x!\`) in application code`, detail: 'Each tells the compiler a value cannot be `null` without proving it; under `strictNullChecks` they are where the remaining null errors hide.' });
|
|
76
|
+
}
|
|
77
|
+
if (suppressions > 0) {
|
|
78
|
+
findings.push({ severity: 'low', message: `${plural(suppressions, '`@ts-ignore` / `@ts-expect-error` comment')}`, detail: 'Each switches off checking for the next line, including errors nobody has seen yet.' });
|
|
79
|
+
}
|
|
80
|
+
if (templateAnyCasts > 0) {
|
|
81
|
+
findings.push({ severity: 'low', message: `${plural(templateAnyCasts, '`$any()` cast')} in templates`, detail: 'The template equivalent of `as any`; usually a sign the component exposes a type the template cannot narrow.' });
|
|
82
|
+
}
|
|
83
|
+
if (untypedForms.length > 0) {
|
|
84
|
+
findings.push({
|
|
85
|
+
severity: 'low',
|
|
86
|
+
message: `${plural(untypedForms.length, 'file')} ${untypedForms.length === 1 ? 'imports' : 'import'} \`UntypedForm*\` classes`,
|
|
87
|
+
detail: 'Typed reactive forms exist since Angular 14. The `Untyped*` aliases were the escape hatch `ng update` inserted so the upgrade would compile; each is a form whose values are still `any`.',
|
|
88
|
+
files: untypedForms.slice(0, context.maxExamples).map((file) => ({ path: file.path })),
|
|
89
|
+
count: untypedForms.length,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const grade = !hasConfig
|
|
93
|
+
? 'info'
|
|
94
|
+
: !strictNullChecks && !strictTemplates
|
|
95
|
+
? 'poor'
|
|
96
|
+
: !strictNullChecks || !strictTemplates || !noImplicitAny || anyDensity > ANY_DENSITY_THRESHOLD
|
|
97
|
+
? 'fair'
|
|
98
|
+
: 'good';
|
|
99
|
+
const on = (flag) => (flag ? 'on' : 'off');
|
|
100
|
+
return {
|
|
101
|
+
id: 'type-safety',
|
|
102
|
+
title: 'Type safety',
|
|
103
|
+
grade,
|
|
104
|
+
summary: hasConfig
|
|
105
|
+
? `strictNullChecks ${on(strictNullChecks)}, strictTemplates ${on(strictTemplates)}, ${formatCount(anyCount)} explicit any.`
|
|
106
|
+
: 'No application tsconfig found.',
|
|
107
|
+
metrics: [
|
|
108
|
+
{ label: 'strictNullChecks', value: on(strictNullChecks), hint: source(options.strictNullChecks !== undefined || strictWritten, `TypeScript ${typescriptMajor ?? '?'}`) },
|
|
109
|
+
{ label: 'strictTemplates', value: on(strictTemplates), hint: source(typeof explicitStrictTemplates === 'boolean', `Angular ${angularMajor ?? '?'}`) },
|
|
110
|
+
{ label: 'noImplicitAny', value: on(noImplicitAny), hint: source(options.noImplicitAny !== undefined || strictWritten, `TypeScript ${typescriptMajor ?? '?'}`) },
|
|
111
|
+
{ label: 'Explicit any', value: formatCount(anyCount), hint: `${anyDensity.toFixed(1)} per 1 000 lines` },
|
|
112
|
+
{ label: 'Non-null assertions', value: formatCount(nonNull) },
|
|
113
|
+
],
|
|
114
|
+
findings,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=type-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-safety.js","sourceRoot":"","sources":["../../src/checks/type-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
import { type AnalysisContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Where the codebase stands against the newest Angular release, and whether
|
|
5
|
+
* its toolchain is current.
|
|
6
|
+
*
|
|
7
|
+
* Graded on the support window: Angular releases a major roughly every six
|
|
8
|
+
* months and supports each for 18 (6 active, 12 LTS), so the newest three
|
|
9
|
+
* majors are supported and anything older gets no security fixes. `ng update`
|
|
10
|
+
* moves one major at a time, so the gap is also the number of upgrade steps.
|
|
11
|
+
*/
|
|
12
|
+
export declare function versionCheck(context: AnalysisContext): CheckResult;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { majorOf, readInstalledVersion } from '../workspace.js';
|
|
2
|
+
import { plural } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Packages whose presence is a finding of its own, with what replaced them.
|
|
5
|
+
* `runtime` entries ship to users (a dead UI library), the others are tooling.
|
|
6
|
+
* The difference decides the grade: dead runtime code is a liability, dead
|
|
7
|
+
* tooling is friction.
|
|
8
|
+
*/
|
|
9
|
+
const END_OF_LIFE_PACKAGES = [
|
|
10
|
+
{ name: '@angular/http', runtime: true, note: 'Removed in Angular 8. Replaced by `HttpClient` from `@angular/common/http`.' },
|
|
11
|
+
{ name: '@angular/flex-layout', runtime: true, note: 'No longer maintained since Angular 15. Replaced by CSS flexbox/grid or a layout utility library.' },
|
|
12
|
+
{ name: 'protractor', runtime: false, note: 'End of life since 2023. Replaced by Playwright or Cypress.' },
|
|
13
|
+
{ name: 'tslint', runtime: false, note: 'Deprecated since 2019. Replaced by ESLint with `angular-eslint`.' },
|
|
14
|
+
{ name: 'codelyzer', runtime: false, note: 'A set of TSLint rules, so it died with TSLint. Replaced by `angular-eslint`.' },
|
|
15
|
+
{ name: 'node-sass', runtime: false, note: 'Deprecated. Replaced by `sass` (Dart Sass), which the Angular CLI already uses.' },
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* `@angular/*` packages that do not follow the framework's version numbers, so
|
|
19
|
+
* a different major is not a half-finished upgrade. AngularFire has its own
|
|
20
|
+
* release line, and flex-layout is reported as end of life instead.
|
|
21
|
+
*/
|
|
22
|
+
const OWN_VERSIONING = new Set(['@angular/fire', '@angular/flex-layout']);
|
|
23
|
+
/** Builders that compile with webpack, which the esbuild `application` builder replaced as the default in v17. */
|
|
24
|
+
const WEBPACK_BUILDERS = new Set(['@angular-devkit/build-angular:browser', '@nx/angular:webpack-browser', '@angular-builders/custom-webpack:browser']);
|
|
25
|
+
/**
|
|
26
|
+
* Where the codebase stands against the newest Angular release, and whether
|
|
27
|
+
* its toolchain is current.
|
|
28
|
+
*
|
|
29
|
+
* Graded on the support window: Angular releases a major roughly every six
|
|
30
|
+
* months and supports each for 18 (6 active, 12 LTS), so the newest three
|
|
31
|
+
* majors are supported and anything older gets no security fixes. `ng update`
|
|
32
|
+
* moves one major at a time, so the gap is also the number of upgrade steps.
|
|
33
|
+
*/
|
|
34
|
+
export function versionCheck(context) {
|
|
35
|
+
const { workspace, latestAngular } = context;
|
|
36
|
+
const current = workspace.angular.major;
|
|
37
|
+
const findings = [];
|
|
38
|
+
const latestLabel = latestAngular.source === 'registry' ? 'npm registry' : 'offline, bundled estimate';
|
|
39
|
+
if (current === undefined) {
|
|
40
|
+
return {
|
|
41
|
+
id: 'version',
|
|
42
|
+
title: 'Angular version & tooling',
|
|
43
|
+
grade: 'info',
|
|
44
|
+
summary: 'No `@angular/core` dependency found in package.json.',
|
|
45
|
+
metrics: [{ label: 'Latest Angular', value: latestAngular.major, hint: latestLabel }],
|
|
46
|
+
findings: [],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const gap = Math.max(0, latestAngular.major - current);
|
|
50
|
+
const versionLabel = workspace.angular.installed ?? workspace.angular.declared ?? String(current);
|
|
51
|
+
if (gap >= 3) {
|
|
52
|
+
findings.push({
|
|
53
|
+
severity: 'high',
|
|
54
|
+
message: `Angular ${current} is ${gap} majors behind ${latestAngular.major} and out of support`,
|
|
55
|
+
detail: `Angular supports each major for 18 months, so only the newest three get fixes; ${current} receives no security patches. \`ng update\` moves one major at a time, which makes this ${plural(gap, 'sequential upgrade step')}, each with its own migrations to review.`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else if (gap === 2) {
|
|
59
|
+
findings.push({
|
|
60
|
+
severity: 'medium',
|
|
61
|
+
message: `Angular ${current} is two majors behind and in its last months of LTS`,
|
|
62
|
+
detail: `Support for ${current} ends when Angular ${latestAngular.major + 1} ships, roughly six months after ${latestAngular.major}. Two \`ng update\` steps bring it current.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (gap === 1) {
|
|
66
|
+
findings.push({ severity: 'low', message: `One major behind (${current} → ${latestAngular.major})`, detail: 'Supported, but the next release will make it two.' });
|
|
67
|
+
}
|
|
68
|
+
const mismatched = Object.keys(workspace.dependencies)
|
|
69
|
+
.filter((name) => name.startsWith('@angular/') && !OWN_VERSIONING.has(name))
|
|
70
|
+
.map((name) => ({ name, major: majorOf(readInstalledVersion(workspace.root, name) ?? workspace.dependencies[name]) }))
|
|
71
|
+
.filter((entry) => entry.major !== undefined && entry.major !== current);
|
|
72
|
+
if (mismatched.length > 0) {
|
|
73
|
+
findings.push({
|
|
74
|
+
severity: 'medium',
|
|
75
|
+
message: `${plural(mismatched.length, 'Angular package')} on a different major than @angular/core ${current}`,
|
|
76
|
+
detail: `${mismatched.map((entry) => `${entry.name} ${entry.major}`).join(', ')}. Angular packages are released in lockstep and only tested against the same major; a mixed set is usually a half-finished upgrade.`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const webpackTargets = workspace.targets.filter((target) => WEBPACK_BUILDERS.has(target.builder));
|
|
80
|
+
if (webpackTargets.length > 0) {
|
|
81
|
+
findings.push({
|
|
82
|
+
severity: 'medium',
|
|
83
|
+
message: `${plural(webpackTargets.length, 'project')} still built with the webpack builder`,
|
|
84
|
+
detail: `The esbuild-based \`application\` builder has been the default since Angular 17 and builds several times faster. \`ng update @angular/cli --name use-application-builder\` migrates the configuration. Projects: ${webpackTargets.map((target) => target.project).join(', ')}.`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const endOfLife = END_OF_LIFE_PACKAGES.filter((entry) => entry.name in workspace.dependencies);
|
|
88
|
+
for (const entry of endOfLife) {
|
|
89
|
+
findings.push({ severity: entry.runtime ? 'high' : 'medium', message: `\`${entry.name}\` is end of life`, detail: entry.note });
|
|
90
|
+
}
|
|
91
|
+
const grade = gap >= 3 || endOfLife.some((entry) => entry.runtime) ? 'poor' : gap === 2 || mismatched.length > 0 || webpackTargets.length > 0 || endOfLife.length > 0 ? 'fair' : 'good';
|
|
92
|
+
const usesApplicationBuilder = workspace.targets.some((target) => /:(application|browser-esbuild)$/.test(target.builder));
|
|
93
|
+
return {
|
|
94
|
+
id: 'version',
|
|
95
|
+
title: 'Angular version & tooling',
|
|
96
|
+
grade,
|
|
97
|
+
summary: gap === 0 ? `On the latest major, Angular ${versionLabel}.` : `Angular ${versionLabel}, ${plural(gap, 'major')} behind ${latestAngular.major}.`,
|
|
98
|
+
metrics: [
|
|
99
|
+
{ label: 'Installed', value: versionLabel, hint: workspace.angular.installed ? 'node_modules' : 'package.json range' },
|
|
100
|
+
{ label: 'Latest', value: latestAngular.major, hint: latestLabel },
|
|
101
|
+
{ label: 'Majors behind', value: gap },
|
|
102
|
+
{ label: 'Build system', value: webpackTargets.length > 0 ? 'webpack' : usesApplicationBuilder ? 'esbuild' : 'unknown' },
|
|
103
|
+
],
|
|
104
|
+
findings,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/checks/version.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type CheckupConfig } from './config.js';
|
|
3
|
+
import { type CiChoice, type InitPlan } from './init.js';
|
|
4
|
+
import type { Report } from './types.js';
|
|
5
|
+
/** Parsed and validated arguments of a checkup run, before the config file is applied. */
|
|
6
|
+
export interface CliArguments {
|
|
7
|
+
root: string;
|
|
8
|
+
/** Reports named on the command line, resolved against the working directory. Empty when none. */
|
|
9
|
+
reports: string[];
|
|
10
|
+
json: boolean;
|
|
11
|
+
verbose: boolean;
|
|
12
|
+
failOn?: 'fair' | 'poor';
|
|
13
|
+
/** Undefined when the flag was not given, so the config file can supply it. */
|
|
14
|
+
maxExamples?: number;
|
|
15
|
+
offline: boolean;
|
|
16
|
+
color: boolean;
|
|
17
|
+
config?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Parsed arguments of `ng-checkup init`. */
|
|
20
|
+
export interface InitArguments {
|
|
21
|
+
root: string;
|
|
22
|
+
ci: CiChoice;
|
|
23
|
+
dryRun: boolean;
|
|
24
|
+
force: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** What a run actually uses: the command line laid over the config file. */
|
|
27
|
+
export interface RunSettings {
|
|
28
|
+
reports: string[];
|
|
29
|
+
failOn?: 'fair' | 'poor';
|
|
30
|
+
maxExamples: number;
|
|
31
|
+
offline: boolean;
|
|
32
|
+
ignore?: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parses `argv` (without the node and script entries) of a checkup run.
|
|
36
|
+
* Throws an `Error` whose message is meant for the user on anything invalid,
|
|
37
|
+
* which `main` prints with the hint to `--help`. Returns `'help'` or
|
|
38
|
+
* `'version'` for those two flags so `main` can print and exit before doing
|
|
39
|
+
* any work.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseCliArguments(argv: string[], environment?: NodeJS.ProcessEnv, isTty?: boolean): CliArguments | 'help' | 'version';
|
|
42
|
+
/** Parses the arguments after `init`. Same error contract as `parseCliArguments`. */
|
|
43
|
+
export declare function parseInitArguments(argv: string[]): InitArguments | 'help';
|
|
44
|
+
/**
|
|
45
|
+
* Lays the command line over the config file: every flag that was given
|
|
46
|
+
* wins, the config fills the rest, and the built-in defaults come last.
|
|
47
|
+
* Config reports resolve against the config file's directory, command-line
|
|
48
|
+
* ones were already resolved against the working directory, which is what a
|
|
49
|
+
* user typing a path expects.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveSettings(args: CliArguments, config: CheckupConfig, configDirectory: string): RunSettings;
|
|
52
|
+
/** Renders a report in the format its file extension names. */
|
|
53
|
+
export declare function renderForFile(report: Report, path: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* True when some area is graded `failOn` or worse. `info` never fails a
|
|
56
|
+
* run: it means the area could not be measured, not that it is bad.
|
|
57
|
+
*/
|
|
58
|
+
export declare function shouldFail(report: Report, failOn: 'fair' | 'poor' | undefined): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Runs `ng-checkup init`: plans against the real file system, then writes
|
|
61
|
+
* the changes unless `dryRun`. Returns the plan so the caller can print it.
|
|
62
|
+
*/
|
|
63
|
+
export declare function runInit(args: InitArguments): InitPlan;
|
|
64
|
+
/** The text `ng-checkup init` prints: what changed, what was left alone, what to run next. */
|
|
65
|
+
export declare function describeInitPlan(plan: InitPlan, dryRun: boolean): string;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, extname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { parseArgs } from 'node:util';
|
|
6
|
+
import { loadConfig } from './config.js';
|
|
7
|
+
import { runCheckup, TOOL } from './index.js';
|
|
8
|
+
import { planInit } from './init.js';
|
|
9
|
+
import { renderHtml } from './report/html.js';
|
|
10
|
+
import { renderMarkdown } from './report/markdown.js';
|
|
11
|
+
import { renderTerminal } from './report/terminal.js';
|
|
12
|
+
const HELP = `Usage: ng-checkup [path] [options]
|
|
13
|
+
ng-checkup init [path] [init options]
|
|
14
|
+
|
|
15
|
+
A one-minute modernization report for an Angular codebase. Reads the code,
|
|
16
|
+
never changes it, and sends nothing anywhere except one npm registry lookup
|
|
17
|
+
of the latest Angular version (skip it with --offline).
|
|
18
|
+
|
|
19
|
+
Arguments:
|
|
20
|
+
path Repository root (default: current directory)
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
-r, --report <file> Write the full report; format from the extension:
|
|
24
|
+
.html, .md or .json. Repeatable.
|
|
25
|
+
--json Print the report as JSON to stdout instead of the summary
|
|
26
|
+
-v, --verbose Print every finding with its files
|
|
27
|
+
--fail-on <grade> Exit with code 1 if any area is graded <grade> or worse:
|
|
28
|
+
poor or fair (for CI)
|
|
29
|
+
--max-examples <n> Example files listed per finding (default 5)
|
|
30
|
+
--offline Do not look up the latest Angular version online
|
|
31
|
+
--config <file> Config file (default: ng-checkup.config.json in the root)
|
|
32
|
+
--no-color Plain output (also off when NO_COLOR is set or not a TTY)
|
|
33
|
+
-h, --help Show this help
|
|
34
|
+
--version Show the version
|
|
35
|
+
|
|
36
|
+
Init options (ng-checkup init):
|
|
37
|
+
--ci <provider> auto (default), github, gitlab or none
|
|
38
|
+
--dry-run Show what would change without writing anything
|
|
39
|
+
--force Replace an existing "checkup" script
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
npx ng-checkup
|
|
43
|
+
npx ng-checkup ../my-app --report checkup.html
|
|
44
|
+
npx ng-checkup --fail-on poor --report checkup.md
|
|
45
|
+
npx ng-checkup init`;
|
|
46
|
+
/** Grades in order of how bad they are, for `--fail-on`. */
|
|
47
|
+
const SEVERITY_ORDER = ['good', 'fair', 'poor'];
|
|
48
|
+
/**
|
|
49
|
+
* Parses `argv` (without the node and script entries) of a checkup run.
|
|
50
|
+
* Throws an `Error` whose message is meant for the user on anything invalid,
|
|
51
|
+
* which `main` prints with the hint to `--help`. Returns `'help'` or
|
|
52
|
+
* `'version'` for those two flags so `main` can print and exit before doing
|
|
53
|
+
* any work.
|
|
54
|
+
*/
|
|
55
|
+
export function parseCliArguments(argv, environment = process.env, isTty = Boolean(process.stdout.isTTY)) {
|
|
56
|
+
const { values, positionals } = parseArgs({
|
|
57
|
+
args: argv,
|
|
58
|
+
allowPositionals: true,
|
|
59
|
+
options: {
|
|
60
|
+
report: { type: 'string', short: 'r', multiple: true },
|
|
61
|
+
json: { type: 'boolean' },
|
|
62
|
+
verbose: { type: 'boolean', short: 'v' },
|
|
63
|
+
'fail-on': { type: 'string' },
|
|
64
|
+
'max-examples': { type: 'string' },
|
|
65
|
+
offline: { type: 'boolean' },
|
|
66
|
+
config: { type: 'string' },
|
|
67
|
+
'no-color': { type: 'boolean' },
|
|
68
|
+
help: { type: 'boolean', short: 'h' },
|
|
69
|
+
version: { type: 'boolean' },
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
if (values.help)
|
|
73
|
+
return 'help';
|
|
74
|
+
if (values.version)
|
|
75
|
+
return 'version';
|
|
76
|
+
const root = parseRoot(positionals);
|
|
77
|
+
const reports = (values.report ?? []).map((report) => resolve(report));
|
|
78
|
+
for (const report of reports)
|
|
79
|
+
assertReportFormat(report);
|
|
80
|
+
const failOn = values['fail-on'];
|
|
81
|
+
if (failOn !== undefined && failOn !== 'fair' && failOn !== 'poor')
|
|
82
|
+
throw new Error(`--fail-on takes poor or fair, got ${failOn}`);
|
|
83
|
+
let maxExamples;
|
|
84
|
+
if (values['max-examples'] !== undefined) {
|
|
85
|
+
maxExamples = Number(values['max-examples']);
|
|
86
|
+
if (!Number.isInteger(maxExamples) || maxExamples < 0)
|
|
87
|
+
throw new Error(`--max-examples takes a whole number, got ${values['max-examples']}`);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
root,
|
|
91
|
+
reports,
|
|
92
|
+
json: values.json ?? false,
|
|
93
|
+
verbose: values.verbose ?? false,
|
|
94
|
+
failOn,
|
|
95
|
+
maxExamples,
|
|
96
|
+
offline: values.offline ?? false,
|
|
97
|
+
color: !values['no-color'] && environment['NO_COLOR'] === undefined && isTty,
|
|
98
|
+
config: values.config,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Parses the arguments after `init`. Same error contract as `parseCliArguments`. */
|
|
102
|
+
export function parseInitArguments(argv) {
|
|
103
|
+
const { values, positionals } = parseArgs({
|
|
104
|
+
args: argv,
|
|
105
|
+
allowPositionals: true,
|
|
106
|
+
options: {
|
|
107
|
+
ci: { type: 'string' },
|
|
108
|
+
'dry-run': { type: 'boolean' },
|
|
109
|
+
force: { type: 'boolean' },
|
|
110
|
+
help: { type: 'boolean', short: 'h' },
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
if (values.help)
|
|
114
|
+
return 'help';
|
|
115
|
+
const ci = values.ci ?? 'auto';
|
|
116
|
+
if (!['auto', 'github', 'gitlab', 'none'].includes(ci))
|
|
117
|
+
throw new Error(`--ci takes auto, github, gitlab or none, got ${ci}`);
|
|
118
|
+
return { root: parseRoot(positionals), ci: ci, dryRun: values['dry-run'] ?? false, force: values.force ?? false };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Lays the command line over the config file: every flag that was given
|
|
122
|
+
* wins, the config fills the rest, and the built-in defaults come last.
|
|
123
|
+
* Config reports resolve against the config file's directory, command-line
|
|
124
|
+
* ones were already resolved against the working directory, which is what a
|
|
125
|
+
* user typing a path expects.
|
|
126
|
+
*/
|
|
127
|
+
export function resolveSettings(args, config, configDirectory) {
|
|
128
|
+
return {
|
|
129
|
+
reports: args.reports.length > 0 ? args.reports : (config.reports ?? []).map((report) => resolve(configDirectory, report)),
|
|
130
|
+
failOn: args.failOn ?? config.failOn,
|
|
131
|
+
maxExamples: args.maxExamples ?? config.maxExamples ?? 5,
|
|
132
|
+
offline: args.offline || (config.offline ?? false),
|
|
133
|
+
ignore: config.ignore,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** The single optional path argument, checked to be a directory. */
|
|
137
|
+
function parseRoot(positionals) {
|
|
138
|
+
if (positionals.length > 1)
|
|
139
|
+
throw new Error(`Expected one path, got ${positionals.length}: ${positionals.join(' ')}`);
|
|
140
|
+
const root = resolve(positionals[0] ?? '.');
|
|
141
|
+
if (!existsSync(root) || !statSync(root).isDirectory())
|
|
142
|
+
throw new Error(`Not a directory: ${root}`);
|
|
143
|
+
return root;
|
|
144
|
+
}
|
|
145
|
+
/** Rejects a report path whose extension names no format. */
|
|
146
|
+
function assertReportFormat(path) {
|
|
147
|
+
if (!['.html', '.htm', '.md', '.json'].includes(extname(path).toLowerCase()))
|
|
148
|
+
throw new Error(`Unknown report format for ${path}: use .html, .md or .json`);
|
|
149
|
+
}
|
|
150
|
+
/** Renders a report in the format its file extension names. */
|
|
151
|
+
export function renderForFile(report, path) {
|
|
152
|
+
const extension = extname(path).toLowerCase();
|
|
153
|
+
if (extension === '.json')
|
|
154
|
+
return `${JSON.stringify(report, null, 2)}\n`;
|
|
155
|
+
if (extension === '.md')
|
|
156
|
+
return renderMarkdown(report);
|
|
157
|
+
return renderHtml(report);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* True when some area is graded `failOn` or worse. `info` never fails a
|
|
161
|
+
* run: it means the area could not be measured, not that it is bad.
|
|
162
|
+
*/
|
|
163
|
+
export function shouldFail(report, failOn) {
|
|
164
|
+
if (!failOn)
|
|
165
|
+
return false;
|
|
166
|
+
const threshold = SEVERITY_ORDER.indexOf(failOn);
|
|
167
|
+
return report.checks.some((check) => check.grade !== 'info' && SEVERITY_ORDER.indexOf(check.grade) >= threshold);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Runs `ng-checkup init`: plans against the real file system, then writes
|
|
171
|
+
* the changes unless `dryRun`. Returns the plan so the caller can print it.
|
|
172
|
+
*/
|
|
173
|
+
export function runInit(args) {
|
|
174
|
+
const plan = planInit({
|
|
175
|
+
read: (path) => {
|
|
176
|
+
const absolute = join(args.root, path);
|
|
177
|
+
return existsSync(absolute) && statSync(absolute).isFile() ? readFileSync(absolute, 'utf8') : undefined;
|
|
178
|
+
},
|
|
179
|
+
exists: (path) => existsSync(join(args.root, path)),
|
|
180
|
+
}, { ci: args.ci, force: args.force, version: TOOL.version });
|
|
181
|
+
if (!args.dryRun) {
|
|
182
|
+
for (const change of plan.changes) {
|
|
183
|
+
const target = join(args.root, change.path);
|
|
184
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
185
|
+
writeFileSync(target, change.content);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return plan;
|
|
189
|
+
}
|
|
190
|
+
/** The text `ng-checkup init` prints: what changed, what was left alone, what to run next. */
|
|
191
|
+
export function describeInitPlan(plan, dryRun) {
|
|
192
|
+
const lines = [];
|
|
193
|
+
if (plan.changes.length === 0) {
|
|
194
|
+
lines.push('Nothing to set up: ng-checkup is already configured here.');
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
lines.push(dryRun ? 'ng-checkup init would make these changes (dry run, nothing written):' : 'ng-checkup is set up:');
|
|
198
|
+
for (const change of plan.changes)
|
|
199
|
+
lines.push(` ${change.action === 'create' ? 'create' : 'update'} ${change.path.padEnd(36)} ${change.summary}`);
|
|
200
|
+
}
|
|
201
|
+
for (const note of plan.notes)
|
|
202
|
+
lines.push('', note);
|
|
203
|
+
if (!dryRun) {
|
|
204
|
+
lines.push('', 'Next:');
|
|
205
|
+
plan.nextSteps.forEach((step, index) => lines.push(` ${index + 1}. ${step}`));
|
|
206
|
+
}
|
|
207
|
+
return lines.join('\n');
|
|
208
|
+
}
|
|
209
|
+
/** Entry point. Exit codes: 0 done, 1 `--fail-on` tripped, 2 usage or runtime error. */
|
|
210
|
+
async function main(argv) {
|
|
211
|
+
if (argv[0] === 'init')
|
|
212
|
+
return mainInit(argv.slice(1));
|
|
213
|
+
let parsed;
|
|
214
|
+
let settings;
|
|
215
|
+
try {
|
|
216
|
+
parsed = parseCliArguments(argv);
|
|
217
|
+
if (parsed === 'help' || parsed === 'version') {
|
|
218
|
+
process.stdout.write(`${parsed === 'help' ? HELP : TOOL.version}\n`);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
const { config, directory } = loadConfig(parsed.root, parsed.config);
|
|
222
|
+
settings = resolveSettings(parsed, config, directory);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
return usageError(error);
|
|
226
|
+
}
|
|
227
|
+
const report = await runCheckup(parsed.root, { offline: settings.offline, maxExamples: settings.maxExamples, ignore: settings.ignore });
|
|
228
|
+
for (const target of settings.reports) {
|
|
229
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
230
|
+
writeFileSync(target, renderForFile(report, target));
|
|
231
|
+
}
|
|
232
|
+
if (parsed.json) {
|
|
233
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
process.stdout.write(`${renderTerminal(report, { color: parsed.color, verbose: parsed.verbose })}\n`);
|
|
237
|
+
for (const target of settings.reports)
|
|
238
|
+
process.stdout.write(`Report written to ${target}\n`);
|
|
239
|
+
}
|
|
240
|
+
return shouldFail(report, settings.failOn) ? 1 : 0;
|
|
241
|
+
}
|
|
242
|
+
/** `ng-checkup init`. */
|
|
243
|
+
function mainInit(argv) {
|
|
244
|
+
try {
|
|
245
|
+
const parsed = parseInitArguments(argv);
|
|
246
|
+
if (parsed === 'help') {
|
|
247
|
+
process.stdout.write(`${HELP}\n`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
const plan = runInit(parsed);
|
|
251
|
+
process.stdout.write(`${describeInitPlan(plan, parsed.dryRun)}\n`);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
return usageError(error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/** Prints a user-facing error with the hint to `--help` and returns exit code 2. */
|
|
259
|
+
function usageError(error) {
|
|
260
|
+
process.stderr.write(`ng-checkup: ${error instanceof Error ? error.message : String(error)}\nRun ng-checkup --help for usage.\n`);
|
|
261
|
+
return 2;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* True when this file is the process entry point. Compared through
|
|
265
|
+
* `realpath` because npm starts the CLI through a symlink in `.bin`, so
|
|
266
|
+
* `argv[1]` names the link while `import.meta.url` names the target. A test
|
|
267
|
+
* importing the parsing helpers is not the entry point and runs nothing.
|
|
268
|
+
*/
|
|
269
|
+
function isEntryPoint() {
|
|
270
|
+
try {
|
|
271
|
+
return process.argv[1] !== undefined && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (isEntryPoint()) {
|
|
278
|
+
main(process.argv.slice(2)).then((code) => {
|
|
279
|
+
process.exitCode = code;
|
|
280
|
+
}, (error) => {
|
|
281
|
+
process.stderr.write(`ng-checkup failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
282
|
+
process.exitCode = 2;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|