pr-checkmate 3.0.1 → 3.0.2
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.
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { Check } from '../../types';
|
|
2
|
+
/** Why we could not read a published version. */
|
|
3
|
+
export type QueryFailure = 'absent' | 'unauthenticated' | 'unreadable';
|
|
4
|
+
/**
|
|
5
|
+
* Tell "never published" apart from "we were not let in".
|
|
6
|
+
*
|
|
7
|
+
* Lumped into one sentence they read the same and they are not: one is a fact
|
|
8
|
+
* about the package, the other is about this run's credentials. A skip whose
|
|
9
|
+
* reason is a guess between two causes is the defect of ticket 057, one layer up.
|
|
10
|
+
*
|
|
11
|
+
* A 401 is also evidence in itself, and it points one way: a name nobody ever
|
|
12
|
+
* published answers 404 on the public registry, while 401 comes from a host that
|
|
13
|
+
* has the package and will not show it to a stranger. So the message leads with
|
|
14
|
+
* the token, which is what that situation actually needs.
|
|
15
|
+
*
|
|
16
|
+
* It deliberately does NOT suggest `"private": true` as the way out. That field
|
|
17
|
+
* means "never publish this", and a package behind a private registry is
|
|
18
|
+
* published — advising it would break the publish step of the very repository
|
|
19
|
+
* asking. `packageSize.enabled: false` silences the row and touches nothing
|
|
20
|
+
* else, so that is the opt-out worth naming.
|
|
21
|
+
*/
|
|
22
|
+
export declare function classifyQueryFailure(stderr: string): QueryFailure;
|
|
2
23
|
/**
|
|
3
24
|
* Reports how much the published package grew since the last release.
|
|
4
25
|
*
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.packageSizeCheck = void 0;
|
|
7
|
+
exports.classifyQueryFailure = classifyQueryFailure;
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const utils_1 = require("../../../utils");
|
|
@@ -31,19 +32,57 @@ const NAMED_FILES = 5;
|
|
|
31
32
|
*
|
|
32
33
|
* @param cwd Repository to run in.
|
|
33
34
|
* @param spec `undefined` for the working tree, or `name@tag` for a release.
|
|
35
|
+
* @param registry Where to look for a published version; the manifest's own
|
|
36
|
+
* `publishConfig.registry` when it names one.
|
|
34
37
|
*/
|
|
35
|
-
async function packManifest(cwd, spec) {
|
|
36
|
-
const args = [
|
|
38
|
+
async function packManifest(cwd, spec, registry) {
|
|
39
|
+
const args = [
|
|
40
|
+
'pack',
|
|
41
|
+
...(spec ? [spec] : []),
|
|
42
|
+
'--dry-run',
|
|
43
|
+
'--json',
|
|
44
|
+
'--ignore-scripts',
|
|
45
|
+
...(registry ? ['--registry', registry] : []),
|
|
46
|
+
];
|
|
37
47
|
const result = await (0, execa_1.execa)('npm', args, { cwd, reject: false });
|
|
38
48
|
if (result.exitCode !== 0)
|
|
39
|
-
return
|
|
49
|
+
return { failure: classifyQueryFailure(result.stderr ?? '') };
|
|
40
50
|
try {
|
|
41
51
|
const parsed = JSON.parse(result.stdout);
|
|
42
|
-
|
|
52
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
53
|
+
return { manifest: parsed[0] };
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
56
|
catch {
|
|
45
|
-
|
|
57
|
+
// Fall through: npm answered with something that is not a manifest.
|
|
58
|
+
}
|
|
59
|
+
return { failure: 'unreadable' };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Tell "never published" apart from "we were not let in".
|
|
63
|
+
*
|
|
64
|
+
* Lumped into one sentence they read the same and they are not: one is a fact
|
|
65
|
+
* about the package, the other is about this run's credentials. A skip whose
|
|
66
|
+
* reason is a guess between two causes is the defect of ticket 057, one layer up.
|
|
67
|
+
*
|
|
68
|
+
* A 401 is also evidence in itself, and it points one way: a name nobody ever
|
|
69
|
+
* published answers 404 on the public registry, while 401 comes from a host that
|
|
70
|
+
* has the package and will not show it to a stranger. So the message leads with
|
|
71
|
+
* the token, which is what that situation actually needs.
|
|
72
|
+
*
|
|
73
|
+
* It deliberately does NOT suggest `"private": true` as the way out. That field
|
|
74
|
+
* means "never publish this", and a package behind a private registry is
|
|
75
|
+
* published — advising it would break the publish step of the very repository
|
|
76
|
+
* asking. `packageSize.enabled: false` silences the row and touches nothing
|
|
77
|
+
* else, so that is the opt-out worth naming.
|
|
78
|
+
*/
|
|
79
|
+
function classifyQueryFailure(stderr) {
|
|
80
|
+
if (/\bE?40[13]\b|Unauthorized|Forbidden|authentication token/i.test(stderr)) {
|
|
81
|
+
return 'unauthenticated';
|
|
46
82
|
}
|
|
83
|
+
if (/\bE?404\b|Not found/i.test(stderr))
|
|
84
|
+
return 'absent';
|
|
85
|
+
return 'unreadable';
|
|
47
86
|
}
|
|
48
87
|
/** `1.15 MB`, at the precision a human compares two of these at. */
|
|
49
88
|
function mb(bytes) {
|
|
@@ -66,11 +105,21 @@ function growthByFile(before, after) {
|
|
|
66
105
|
.slice(0, NAMED_FILES)
|
|
67
106
|
.map(f => `${f.path} +${mb(f.delta)}${f.isNew ? ' (new)' : ''}`);
|
|
68
107
|
}
|
|
69
|
-
/**
|
|
70
|
-
function
|
|
108
|
+
/** What this repository publishes, and where — or `null` if it publishes nothing. */
|
|
109
|
+
function published(cwd) {
|
|
71
110
|
try {
|
|
72
111
|
const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(cwd, 'package.json'), 'utf-8'));
|
|
73
|
-
|
|
112
|
+
if (pkg.private || !pkg.name)
|
|
113
|
+
return null;
|
|
114
|
+
// `publishConfig.registry` is the manifest's own statement of where this
|
|
115
|
+
// package lives, and npm uses it for `publish` but not for a fetch. Asking
|
|
116
|
+
// the default registry instead reports "no published version" for every
|
|
117
|
+
// package on GitHub Packages or a company mirror — which is not a fact about
|
|
118
|
+
// the package, it is us having asked the wrong host. Measured on a real
|
|
119
|
+
// client: the default registry answered 404 for a package published at
|
|
120
|
+
// version 1.2.15.
|
|
121
|
+
const registry = pkg.publishConfig?.registry;
|
|
122
|
+
return registry ? { name: pkg.name, registry } : { name: pkg.name };
|
|
74
123
|
}
|
|
75
124
|
catch {
|
|
76
125
|
return null;
|
|
@@ -98,33 +147,57 @@ exports.packageSizeCheck = {
|
|
|
98
147
|
return false;
|
|
99
148
|
// A private package is never published, so there is nothing to compare with
|
|
100
149
|
// and no consumer to pay for the growth.
|
|
101
|
-
return
|
|
150
|
+
return published(ctx.cwd) !== null;
|
|
102
151
|
},
|
|
103
152
|
async run(ctx) {
|
|
104
|
-
const
|
|
105
|
-
if (!
|
|
153
|
+
const target = published(ctx.cwd);
|
|
154
|
+
if (!target)
|
|
106
155
|
return (0, types_1.skip)('no publishable package.json');
|
|
107
156
|
const tag = ctx.config.packageSize?.compareWith ?? 'latest';
|
|
108
157
|
const maxGrowth = ctx.config.packageSize?.maxGrowthPercent ?? DEFAULT_MAX_GROWTH;
|
|
109
|
-
const
|
|
110
|
-
|
|
158
|
+
const spec = `${target.name}@${tag}`;
|
|
159
|
+
const local = await packManifest(ctx.cwd);
|
|
160
|
+
if (!('manifest' in local))
|
|
111
161
|
return (0, types_1.skip)('npm could not describe this package');
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
162
|
+
const current = local.manifest;
|
|
163
|
+
// Never a pass: a green row over a comparison that did not happen is the
|
|
164
|
+
// mistake of ticket 072. And never one reason standing in for three — the
|
|
165
|
+
// three are different situations for the reader, and only one of them is a
|
|
166
|
+
// fact about the package rather than about the run.
|
|
167
|
+
const remote = await packManifest(ctx.cwd, spec, target.registry);
|
|
168
|
+
if (!('manifest' in remote)) {
|
|
169
|
+
// The fact, and nothing past it. The remedy for a private registry is three
|
|
170
|
+
// lines in a workflow — `setup-node` with `registry-url`, a scope, and
|
|
171
|
+
// `NODE_AUTH_TOKEN`, because npm reads the token through a reference in
|
|
172
|
+
// `.npmrc` — and instructions that do not fit cannot be followed. They
|
|
173
|
+
// belong on the check's page, which the report already links to.
|
|
174
|
+
//
|
|
175
|
+
// The package name is left out on purpose: the reader owns it. The host is
|
|
176
|
+
// not, because it is the one part they cannot infer — and the difference
|
|
177
|
+
// between "we asked the wrong registry" and "we were refused" is exactly
|
|
178
|
+
// what this row got wrong before.
|
|
179
|
+
// Named only when we know it. Without `publishConfig.registry` npm used
|
|
180
|
+
// whatever the client's own config points at, and we do not know what that
|
|
181
|
+
// was — "at the registry" would be filler dressed up as information.
|
|
182
|
+
const host = target.registry ? new URL(target.registry).host : null;
|
|
183
|
+
const at = host ? ` at ${host}` : '';
|
|
184
|
+
const reason = {
|
|
185
|
+
absent: `nothing published under this name${at} yet`,
|
|
186
|
+
unauthenticated: `no credentials for ${host ?? 'the registry'}`,
|
|
187
|
+
unreadable: `could not read the published version${at}`,
|
|
188
|
+
}[remote.failure];
|
|
189
|
+
return (0, types_1.skip)(reason);
|
|
118
190
|
}
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
191
|
+
const previous = remote.manifest;
|
|
192
|
+
const delta = current.unpackedSize - previous.unpackedSize;
|
|
193
|
+
const growth = previous.unpackedSize > 0 ? (delta / previous.unpackedSize) * 100 : 0;
|
|
194
|
+
const summary = `${mb(previous.unpackedSize)} → ${mb(current.unpackedSize)} ` +
|
|
195
|
+
`(${growth >= 0 ? '+' : ''}${growth.toFixed(1)}%) vs ${previous.version}`;
|
|
123
196
|
if (growth <= maxGrowth) {
|
|
124
197
|
utils_1.logger.info(`[Package Size]: ✅ ${summary}`);
|
|
125
198
|
return (0, types_1.pass)(summary);
|
|
126
199
|
}
|
|
127
|
-
(0, findings_1.logSample)('Package Size', `${summary}:`, growthByFile(
|
|
200
|
+
(0, findings_1.logSample)('Package Size', `${summary}:`, growthByFile(previous.files, current.files));
|
|
128
201
|
return (0, types_1.warn)(summary);
|
|
129
202
|
},
|
|
130
203
|
};
|
package/package.json
CHANGED