@dependably/npm-check 1.7.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 +201 -0
- package/README.md +71 -0
- package/bin/cli.js +1577 -0
- package/package.json +103 -0
- package/src/audit-config.js +331 -0
- package/src/audit.js +805 -0
- package/src/backup.js +235 -0
- package/src/checker.js +771 -0
- package/src/checksum-fixer.js +419 -0
- package/src/deprecation.js +325 -0
- package/src/fixer.js +207 -0
- package/src/format-library.js +132 -0
- package/src/index.js +138 -0
- package/src/integrity.js +519 -0
- package/src/migrator.js +263 -0
- package/src/npmrc-validator.js +205 -0
- package/src/overrides.js +71 -0
- package/src/package-json-validator.js +356 -0
- package/src/parallel-processor.js +374 -0
- package/src/parser.js +133 -0
- package/src/performance.js +283 -0
- package/src/pinner.js +248 -0
- package/src/pnpm-format.js +229 -0
- package/src/pnpm-workspace-validator.js +121 -0
- package/src/progress-reporter.js +245 -0
- package/src/pruner.js +177 -0
- package/src/remediate.js +389 -0
- package/src/report.js +663 -0
- package/src/schema.js +76 -0
- package/src/streaming-parser.js +251 -0
- package/src/updater.js +251 -0
- package/src/usage-scanner.js +215 -0
- package/src/validator.js +282 -0
- package/src/vuln.js +618 -0
- package/src/workers/dedupe-worker.js +20 -0
- package/src/workers/hash-upgrade-worker.js +20 -0
- package/src/workers/migration-worker.js +21 -0
- package/src/workers/validation-worker.js +20 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// src/package-json-validator.js
|
|
2
|
+
// Standalone validation for a package.json manifest, mirroring validator.js's
|
|
3
|
+
// contract: validatePackageJson(packageJson, options) => { valid, errors, warnings, info }.
|
|
4
|
+
// Errors/warnings are { message, code } (errors are PackageJsonValidationError instances).
|
|
5
|
+
|
|
6
|
+
import { walkOverrides } from './overrides.js';
|
|
7
|
+
|
|
8
|
+
export class PackageJsonValidationError extends Error {
|
|
9
|
+
constructor(message, code) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'PackageJsonValidationError';
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// npm package-name rules (subset of validate-npm-package-name; no new deps):
|
|
17
|
+
// optional @scope/, lowercase, url-safe, can't start with . or _.
|
|
18
|
+
const NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
19
|
+
// Legacy variant for dependency keys: npm's registry still hosts mixed-case names
|
|
20
|
+
// published before the lowercase-only rule (e.g. JSONStream). Case-insensitive so
|
|
21
|
+
// those names are not flagged PJ_INVALID_DEP_NAME. The own-package `name` field
|
|
22
|
+
// still uses the strict NAME_RE (new uploads must be lowercase).
|
|
23
|
+
const LEGACY_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
|
|
24
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
|
|
25
|
+
const DEP_SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Accept what npm accepts as a dependency "range": semver ranges, exact versions,
|
|
29
|
+
* `*`, dist-tags (latest), the npm:/file:/git:/github:/workspace:/http(s): protocols,
|
|
30
|
+
* and `owner/repo` GitHub shorthand.
|
|
31
|
+
*/
|
|
32
|
+
function isValidRange(range) {
|
|
33
|
+
if (typeof range !== 'string') return false;
|
|
34
|
+
const r = range.trim();
|
|
35
|
+
if (r === '' || r === '*' || r === 'latest' || r === 'x') return true;
|
|
36
|
+
if (/^(npm|file|git|git\+ssh|git\+https|git\+http|github|http|https|workspace|catalog|jsr):/i.test(r)) return true;
|
|
37
|
+
if (/^[\w.-]+\/[\w.#/-]+$/.test(r)) return true; // owner/repo[#ref] shorthand
|
|
38
|
+
if (/^[a-z][a-z0-9._-]*$/i.test(r)) return true; // dist-tag (latest, next, beta, canary, ...)
|
|
39
|
+
// caret/tilde/comparator/exact/x-ranges/||/hyphen/v-prefixed ranges
|
|
40
|
+
return /^[v\d~^<>=*xX][\w.\-+~^<>=|\s*]*$/.test(r);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Each section helper takes the manifest plus the shared { errors, warnings }
|
|
44
|
+
// sink and pushes its findings, keeping validatePackageJson a flat checklist.
|
|
45
|
+
|
|
46
|
+
// --- name ---
|
|
47
|
+
function validateName(packageJson, isPrivate, errors, warnings) {
|
|
48
|
+
if (packageJson.name === undefined) {
|
|
49
|
+
// private / workspace-root packages may legitimately omit name → warn, not error
|
|
50
|
+
if (isPrivate) {
|
|
51
|
+
warnings.push({ code: 'PJ_MISSING_NAME', message: 'no "name" field (allowed for private packages)' });
|
|
52
|
+
} else {
|
|
53
|
+
errors.push(new PackageJsonValidationError('package.json is missing "name"', 'PJ_MISSING_NAME'));
|
|
54
|
+
}
|
|
55
|
+
} else if (typeof packageJson.name !== 'string' || !NAME_RE.test(packageJson.name)) {
|
|
56
|
+
errors.push(new PackageJsonValidationError(`invalid package name "${packageJson.name}"`, 'PJ_INVALID_NAME'));
|
|
57
|
+
} else if (packageJson.name.length > 214) {
|
|
58
|
+
errors.push(new PackageJsonValidationError('package name exceeds 214 characters', 'PJ_NAME_TOO_LONG'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- version ---
|
|
63
|
+
function validateVersion(packageJson, isPrivate, errors, warnings) {
|
|
64
|
+
if (packageJson.version === undefined) {
|
|
65
|
+
if (isPrivate) {
|
|
66
|
+
warnings.push({ code: 'PJ_MISSING_VERSION', message: 'no "version" field (allowed for private packages)' });
|
|
67
|
+
} else {
|
|
68
|
+
errors.push(new PackageJsonValidationError('package.json is missing "version"', 'PJ_MISSING_VERSION'));
|
|
69
|
+
}
|
|
70
|
+
} else if (typeof packageJson.version !== 'string' || !SEMVER_RE.test(packageJson.version)) {
|
|
71
|
+
errors.push(new PackageJsonValidationError(`invalid version "${packageJson.version}" (expected semver)`, 'PJ_INVALID_VERSION'));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --- dependency names + ranges across all four sections ---
|
|
76
|
+
function validateDependencySections(packageJson, errors) {
|
|
77
|
+
for (const section of DEP_SECTIONS) {
|
|
78
|
+
const deps = packageJson[section];
|
|
79
|
+
if (deps === undefined) continue;
|
|
80
|
+
if (typeof deps !== 'object' || deps === null || Array.isArray(deps)) {
|
|
81
|
+
errors.push(new PackageJsonValidationError(`"${section}" must be an object`, 'PJ_INVALID_DEP_SECTION'));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
85
|
+
if (!LEGACY_NAME_RE.test(name)) {
|
|
86
|
+
errors.push(new PackageJsonValidationError(`invalid dependency name "${name}" in ${section}`, 'PJ_INVALID_DEP_NAME'));
|
|
87
|
+
}
|
|
88
|
+
if (!isValidRange(range)) {
|
|
89
|
+
errors.push(new PackageJsonValidationError(`invalid version range "${range}" for "${name}" in ${section}`, 'PJ_INVALID_DEP_RANGE'));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- scripts shape (object of string commands) ---
|
|
96
|
+
function validateScripts(packageJson, errors) {
|
|
97
|
+
if (packageJson.scripts === undefined) return;
|
|
98
|
+
if (typeof packageJson.scripts !== 'object' || packageJson.scripts === null || Array.isArray(packageJson.scripts)) {
|
|
99
|
+
errors.push(new PackageJsonValidationError('"scripts" must be an object', 'PJ_INVALID_SCRIPTS'));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
for (const [k, v] of Object.entries(packageJson.scripts)) {
|
|
103
|
+
if (typeof v !== 'string') {
|
|
104
|
+
errors.push(new PackageJsonValidationError(`script "${k}" must be a string`, 'PJ_INVALID_SCRIPT_VALUE'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- license (warn-level) ---
|
|
110
|
+
function validateLicense(packageJson, isPrivate, warnings) {
|
|
111
|
+
if (packageJson.license === undefined && packageJson.licenses === undefined) {
|
|
112
|
+
if (!isPrivate) {
|
|
113
|
+
warnings.push({ code: 'PJ_MISSING_LICENSE', message: 'no "license" field (use a valid SPDX identifier)' });
|
|
114
|
+
}
|
|
115
|
+
} else if (packageJson.license !== undefined && typeof packageJson.license !== 'string') {
|
|
116
|
+
warnings.push({ code: 'PJ_INVALID_LICENSE', message: '"license" should be an SPDX string (object/array form is deprecated)' });
|
|
117
|
+
} else if (typeof packageJson.license === 'string' && /^see\s+license/i.test(packageJson.license)) {
|
|
118
|
+
warnings.push({ code: 'PJ_NONSTANDARD_LICENSE', message: `non-SPDX license "${packageJson.license}"` });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- bin / main / exports sanity (types only; FS existence is out of scope) ---
|
|
123
|
+
function validateEntryPoints(packageJson, errors) {
|
|
124
|
+
if (packageJson.main !== undefined && typeof packageJson.main !== 'string') {
|
|
125
|
+
errors.push(new PackageJsonValidationError('"main" must be a string', 'PJ_INVALID_MAIN'));
|
|
126
|
+
}
|
|
127
|
+
if (packageJson.bin !== undefined && typeof packageJson.bin !== 'string' &&
|
|
128
|
+
(typeof packageJson.bin !== 'object' || packageJson.bin === null || Array.isArray(packageJson.bin))) {
|
|
129
|
+
errors.push(new PackageJsonValidationError('"bin" must be a string or an object', 'PJ_INVALID_BIN'));
|
|
130
|
+
}
|
|
131
|
+
if (packageJson.exports !== undefined &&
|
|
132
|
+
typeof packageJson.exports !== 'object' && typeof packageJson.exports !== 'string') {
|
|
133
|
+
errors.push(new PackageJsonValidationError('"exports" must be a string or an object', 'PJ_INVALID_EXPORTS'));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Pull the glob list out of a workspaces field: a bare array, or { packages: [] }.
|
|
138
|
+
function workspaceGlobs(ws) {
|
|
139
|
+
if (Array.isArray(ws)) return ws;
|
|
140
|
+
if (ws && typeof ws === 'object' && Array.isArray(ws.packages)) return ws.packages;
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- workspaces shape (array of strings, or { packages: [...] }) ---
|
|
145
|
+
function validateWorkspaces(packageJson, errors) {
|
|
146
|
+
if (packageJson.workspaces === undefined) return;
|
|
147
|
+
const arr = workspaceGlobs(packageJson.workspaces);
|
|
148
|
+
if (!arr) {
|
|
149
|
+
errors.push(new PackageJsonValidationError('"workspaces" must be an array or { packages: [] }', 'PJ_INVALID_WORKSPACES'));
|
|
150
|
+
} else if (!arr.every((p) => typeof p === 'string')) {
|
|
151
|
+
errors.push(new PackageJsonValidationError('"workspaces" entries must be strings (glob patterns)', 'PJ_INVALID_WORKSPACE_ENTRY'));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Helpers for the `pnpm` field's sub-key types.
|
|
156
|
+
const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
157
|
+
const isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === 'string');
|
|
158
|
+
|
|
159
|
+
// pnpm-specific manifest fields and the type each must have. `peerDependencyRules`
|
|
160
|
+
// is an object; `onlyBuiltDependencies`/`neverBuiltDependencies` are string arrays;
|
|
161
|
+
// the rest are objects. Surfaced for both flavors (the field is harmless on npm,
|
|
162
|
+
// and validating it where present is always useful).
|
|
163
|
+
const PNPM_FIELD_TYPES = {
|
|
164
|
+
overrides: isPlainObject,
|
|
165
|
+
packageExtensions: isPlainObject,
|
|
166
|
+
peerDependencyRules: isPlainObject,
|
|
167
|
+
patchedDependencies: isPlainObject,
|
|
168
|
+
allowedDeprecatedVersions: isPlainObject,
|
|
169
|
+
onlyBuiltDependencies: isStringArray,
|
|
170
|
+
neverBuiltDependencies: isStringArray,
|
|
171
|
+
ignoredBuiltDependencies: isStringArray,
|
|
172
|
+
updateConfig: isPlainObject,
|
|
173
|
+
auditConfig: isPlainObject,
|
|
174
|
+
supportedArchitectures: isPlainObject
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// --- overrides ranges (npm `overrides` + pnpm.overrides) ---
|
|
178
|
+
// walkOverrides descends the nested npm form and the flat pnpm form and skips
|
|
179
|
+
// `$`-references; each remaining leaf must be a valid range specifier.
|
|
180
|
+
function validateOverrideRanges(overrides, label, errors) {
|
|
181
|
+
for (const { path, range } of walkOverrides(overrides)) {
|
|
182
|
+
if (!isValidRange(range)) {
|
|
183
|
+
errors.push(new PackageJsonValidationError(
|
|
184
|
+
`invalid override range "${range}" at ${label}/${path}`, 'PJ_INVALID_OVERRIDE_RANGE'));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function validateOverrides(packageJson, errors) {
|
|
190
|
+
if (packageJson.overrides !== undefined) {
|
|
191
|
+
if (!isPlainObject(packageJson.overrides)) {
|
|
192
|
+
errors.push(new PackageJsonValidationError('"overrides" must be an object', 'PJ_INVALID_OVERRIDES'));
|
|
193
|
+
} else {
|
|
194
|
+
validateOverrideRanges(packageJson.overrides, 'overrides', errors);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// pnpm.overrides is type-checked in validatePnpmField; validate its ranges here
|
|
198
|
+
// (only when it's actually an object, to avoid double-reporting a type error).
|
|
199
|
+
const pnpmOverrides = packageJson.pnpm && packageJson.pnpm.overrides;
|
|
200
|
+
if (isPlainObject(pnpmOverrides)) {
|
|
201
|
+
validateOverrideRanges(pnpmOverrides, 'pnpm.overrides', errors);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// --- range-carrying pnpm sub-fields (validation only; these ranges are loose
|
|
206
|
+
// BY DESIGN — packageExtensions patches upstream metadata, allowedVersions /
|
|
207
|
+
// allowedDeprecatedVersions are allowances — so we validate syntax but never
|
|
208
|
+
// flag/pin them as "unpinned" the way overrides/deps are) ---
|
|
209
|
+
|
|
210
|
+
// Validate a flat { name: range } map's range values.
|
|
211
|
+
function validateRangeMap(map, label, errors) {
|
|
212
|
+
if (!isPlainObject(map)) return; // shape handled by the caller / type check; absent → nothing
|
|
213
|
+
for (const [name, range] of Object.entries(map)) {
|
|
214
|
+
if (!isValidRange(range)) {
|
|
215
|
+
const shown = typeof range === 'string' ? range : JSON.stringify(range);
|
|
216
|
+
errors.push(new PackageJsonValidationError(
|
|
217
|
+
`invalid version range "${shown}" for "${name}" in ${label}`, 'PJ_INVALID_RANGE'));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// pnpm.packageExtensions: { "selector@range": { dependencies|peerDependencies:
|
|
223
|
+
// {name: range}, peerDependenciesMeta: {...} } } — the inner dep maps carry real
|
|
224
|
+
// ranges that pnpm injects into the graph, so validate them.
|
|
225
|
+
function validatePackageExtensions(packageExtensions, errors) {
|
|
226
|
+
if (!isPlainObject(packageExtensions)) return;
|
|
227
|
+
for (const [selector, ext] of Object.entries(packageExtensions)) {
|
|
228
|
+
if (!isPlainObject(ext)) {
|
|
229
|
+
errors.push(new PackageJsonValidationError(
|
|
230
|
+
`"pnpm.packageExtensions.${selector}" must be an object`, 'PJ_INVALID_PKG_EXTENSION'));
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
// pnpm packageExtensions extend all four dependency-map fields.
|
|
234
|
+
for (const depKey of ['dependencies', 'optionalDependencies', 'peerDependencies']) {
|
|
235
|
+
if (ext[depKey] === undefined) continue;
|
|
236
|
+
if (!isPlainObject(ext[depKey])) {
|
|
237
|
+
errors.push(new PackageJsonValidationError(
|
|
238
|
+
`"pnpm.packageExtensions.${selector}.${depKey}" must be an object`, 'PJ_INVALID_PKG_EXTENSION'));
|
|
239
|
+
} else {
|
|
240
|
+
validateRangeMap(ext[depKey], `pnpm.packageExtensions.${selector}.${depKey}`, errors);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function validatePnpmRanges(packageJson, errors) {
|
|
247
|
+
const pnpm = packageJson.pnpm;
|
|
248
|
+
if (!isPlainObject(pnpm)) return;
|
|
249
|
+
validatePackageExtensions(pnpm.packageExtensions, errors);
|
|
250
|
+
if (isPlainObject(pnpm.peerDependencyRules)) {
|
|
251
|
+
// `peerDependencyRules` itself is type-checked in validatePnpmField, but its
|
|
252
|
+
// nested `allowedVersions` sub-map is not — flag a wrong-typed one here.
|
|
253
|
+
const allowedVersions = pnpm.peerDependencyRules.allowedVersions;
|
|
254
|
+
if (allowedVersions !== undefined && !isPlainObject(allowedVersions)) {
|
|
255
|
+
errors.push(new PackageJsonValidationError(
|
|
256
|
+
'"pnpm.peerDependencyRules.allowedVersions" must be an object', 'PJ_INVALID_PNPM_FIELD'));
|
|
257
|
+
} else {
|
|
258
|
+
validateRangeMap(allowedVersions, 'pnpm.peerDependencyRules.allowedVersions', errors);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
validateRangeMap(pnpm.allowedDeprecatedVersions, 'pnpm.allowedDeprecatedVersions', errors);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// --- bundleDependencies / bundledDependencies (array of package names that ship
|
|
265
|
+
// INSIDE the tarball). npm accepts either spelling, and a boolean (bundle all /
|
|
266
|
+
// none). Validate array-of-valid-names and warn when a name isn't declared in
|
|
267
|
+
// dependencies/optionalDependencies (npm requires it). ---
|
|
268
|
+
function collectDeclaredDeps(packageJson) {
|
|
269
|
+
const declared = new Set();
|
|
270
|
+
for (const section of ['dependencies', 'optionalDependencies']) {
|
|
271
|
+
if (isPlainObject(packageJson[section])) {
|
|
272
|
+
for (const name of Object.keys(packageJson[section])) declared.add(name);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return declared;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function validateBundledName(name, field, declared, errors, warnings) {
|
|
279
|
+
if (typeof name !== 'string' || !LEGACY_NAME_RE.test(name)) {
|
|
280
|
+
errors.push(new PackageJsonValidationError(
|
|
281
|
+
`invalid bundled dependency name ${JSON.stringify(name)} in ${field}`, 'PJ_INVALID_BUNDLE_DEP_NAME'));
|
|
282
|
+
} else if (!declared.has(name)) {
|
|
283
|
+
warnings.push({ code: 'PJ_BUNDLE_DEP_NOT_IN_DEPS',
|
|
284
|
+
message: `bundled dependency "${name}" is not listed in dependencies/optionalDependencies` });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function validateBundleDependencies(packageJson, errors, warnings) {
|
|
289
|
+
const declared = collectDeclaredDeps(packageJson);
|
|
290
|
+
for (const field of ['bundleDependencies', 'bundledDependencies']) {
|
|
291
|
+
const value = packageJson[field];
|
|
292
|
+
if (value === undefined || typeof value === 'boolean') continue; // boolean = bundle all/none
|
|
293
|
+
if (!Array.isArray(value)) {
|
|
294
|
+
errors.push(new PackageJsonValidationError(
|
|
295
|
+
`"${field}" must be an array of package names (or a boolean)`, 'PJ_INVALID_BUNDLE_DEPS'));
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
for (const name of value) validateBundledName(name, field, declared, errors, warnings);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// --- pnpm field (overrides / packageExtensions / build-script allowlists / …) ---
|
|
303
|
+
function validatePnpmField(packageJson, errors, warnings) {
|
|
304
|
+
const pnpm = packageJson.pnpm;
|
|
305
|
+
if (pnpm === undefined) return;
|
|
306
|
+
if (!isPlainObject(pnpm)) {
|
|
307
|
+
errors.push(new PackageJsonValidationError('"pnpm" must be an object', 'PJ_INVALID_PNPM'));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
for (const [key, value] of Object.entries(pnpm)) {
|
|
311
|
+
const validator = PNPM_FIELD_TYPES[key];
|
|
312
|
+
if (!validator) {
|
|
313
|
+
warnings.push({ code: 'PJ_UNKNOWN_PNPM_KEY', message: `unrecognized pnpm config key "pnpm.${key}"` });
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (!validator(value)) {
|
|
317
|
+
const expected = validator === isStringArray ? 'an array of strings' : 'an object';
|
|
318
|
+
errors.push(new PackageJsonValidationError(`"pnpm.${key}" must be ${expected}`, 'PJ_INVALID_PNPM_FIELD'));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function validatePackageJson(packageJson, options = {}) {
|
|
324
|
+
const errors = [];
|
|
325
|
+
const warnings = [];
|
|
326
|
+
const info = {};
|
|
327
|
+
|
|
328
|
+
if (!packageJson || typeof packageJson !== 'object' || Array.isArray(packageJson)) {
|
|
329
|
+
errors.push(new PackageJsonValidationError('package.json is not an object', 'PJ_NOT_OBJECT'));
|
|
330
|
+
return { valid: false, errors, warnings, info };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const isPrivate = packageJson.private === true;
|
|
334
|
+
info.private = isPrivate;
|
|
335
|
+
|
|
336
|
+
validateName(packageJson, isPrivate, errors, warnings);
|
|
337
|
+
validateVersion(packageJson, isPrivate, errors, warnings);
|
|
338
|
+
|
|
339
|
+
// --- private flag type ---
|
|
340
|
+
if (packageJson.private !== undefined && typeof packageJson.private !== 'boolean') {
|
|
341
|
+
errors.push(new PackageJsonValidationError('"private" must be a boolean', 'PJ_INVALID_PRIVATE'));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
validateDependencySections(packageJson, errors);
|
|
345
|
+
validateScripts(packageJson, errors);
|
|
346
|
+
validateLicense(packageJson, isPrivate, warnings);
|
|
347
|
+
validateEntryPoints(packageJson, errors);
|
|
348
|
+
validateWorkspaces(packageJson, errors);
|
|
349
|
+
validatePnpmField(packageJson, errors, warnings);
|
|
350
|
+
validateOverrides(packageJson, errors);
|
|
351
|
+
validatePnpmRanges(packageJson, errors);
|
|
352
|
+
validateBundleDependencies(packageJson, errors, warnings);
|
|
353
|
+
|
|
354
|
+
const valid = errors.length === 0 && !(options.strictMode && warnings.length > 0);
|
|
355
|
+
return { valid, errors, warnings, info };
|
|
356
|
+
}
|