@dependably/npm-check 1.8.0 → 1.9.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/package.json +6 -5
- package/src/audit-config.js +29 -7
- package/src/audit.js +60 -0
- package/src/exceptions.js +5 -4
- package/src/npmrc-validator.js +1 -1
- package/src/report.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dependably/npm-check",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "A comprehensive tool for validating, migrating, and updating npm package-lock.json files across versions 1, 2, and 3.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"eslint": "10.5.0",
|
|
81
81
|
"globals": "17.6.0",
|
|
82
82
|
"jest": "29.7.0",
|
|
83
|
-
"sonarqube-scanner": "
|
|
83
|
+
"sonarqube-scanner": "5.0.0"
|
|
84
84
|
},
|
|
85
85
|
"allowScripts": {
|
|
86
86
|
"fsevents": true,
|
|
@@ -89,15 +89,16 @@
|
|
|
89
89
|
"overrides": {
|
|
90
90
|
"@babel/plugin-transform-modules-systemjs": "7.29.7",
|
|
91
91
|
"@ungap/structured-clone": "1.3.1",
|
|
92
|
-
"
|
|
92
|
+
"brace-expansion": "5.0.9",
|
|
93
|
+
"js-yaml": "4.3.1",
|
|
93
94
|
"picomatch": "4.0.4",
|
|
94
95
|
"glob": {
|
|
95
96
|
"minimatch": "3.1.5",
|
|
96
|
-
"brace-expansion": "1.1.
|
|
97
|
+
"brace-expansion": "1.1.18"
|
|
97
98
|
},
|
|
98
99
|
"test-exclude": {
|
|
99
100
|
"minimatch": "3.1.5",
|
|
100
|
-
"brace-expansion": "1.1.
|
|
101
|
+
"brace-expansion": "1.1.18"
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
}
|
package/src/audit-config.js
CHANGED
|
@@ -16,7 +16,8 @@ export const CONFIG_FILENAMES = ['.npm-checkrc.json', 'npm-check.config.json'];
|
|
|
16
16
|
|
|
17
17
|
// Shared, cross-tool config file (JSON, no extension) discovered by walking up
|
|
18
18
|
// from the working directory. `.dependably` is canonical; `.dependably-check` is
|
|
19
|
-
// a deprecated alias kept for the migration window (
|
|
19
|
+
// a deprecated alias kept for the migration window (config spec §7, at
|
|
20
|
+
// https://gitlab.northwardlabs.ca/moonlitlabs/dependably-spec).
|
|
20
21
|
export const SHARED_CONFIG_FILENAME = '.dependably';
|
|
21
22
|
export const DEPRECATED_SHARED_CONFIG_FILENAME = '.dependably-check';
|
|
22
23
|
// Checked in this order at each directory level (canonical wins).
|
|
@@ -34,8 +35,9 @@ export const SUPPORTED_CONFIG_VERSION = 1;
|
|
|
34
35
|
// own section but tolerated (ignored) in `common`.
|
|
35
36
|
export const APPLICABLE_SELECTORS = ['package', 'id'];
|
|
36
37
|
|
|
37
|
-
// Keys npm-check recognizes inside
|
|
38
|
-
|
|
38
|
+
// Keys npm-check recognizes inside its own section. An unrecognized key there warns;
|
|
39
|
+
// the same key in `common` is ignored, since it may belong to a sibling tool.
|
|
40
|
+
export const KNOWN_SECTION_KEYS = new Set([
|
|
39
41
|
'rules', 'exceptions', 'exclude', 'failOn',
|
|
40
42
|
'allowedRegistryHosts', 'allowedLocalFeeds', 'maxWarnings'
|
|
41
43
|
]);
|
|
@@ -58,6 +60,12 @@ export const DEFAULT_CONFIG = {
|
|
|
58
60
|
'install-scripts': ['warn', { allow: [] }],
|
|
59
61
|
'no-git-deps': 'warn',
|
|
60
62
|
'no-remote-deps': ['warn', { allowedHosts: ['registry.npmjs.org', 'npm.pkg.github.com'] }],
|
|
63
|
+
// Lockfile portability, distinct from the trust question the two rules above
|
|
64
|
+
// ask. Empty `hosts` == off: pinning is a per-project decision (a project
|
|
65
|
+
// that genuinely installs from a private registry must not be flagged), and
|
|
66
|
+
// unlike `allowedRegistryHosts` this list is NOT unioned from shared config —
|
|
67
|
+
// a pin that only ever widens would not be a pin.
|
|
68
|
+
'resolved-registry-pin': ['error', { hosts: [] }],
|
|
61
69
|
'pinned-versions': ['error', {
|
|
62
70
|
sections: ['dependencies', 'devDependencies', 'optionalDependencies'],
|
|
63
71
|
ignore: []
|
|
@@ -188,11 +196,26 @@ function unionList(a, b) {
|
|
|
188
196
|
// id replaces common's wholesale (no cross-section option deep-merge, §B.3).
|
|
189
197
|
function mergeRuleMaps(commonRules, toolRules) {
|
|
190
198
|
if (!commonRules && !toolRules) return undefined;
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
|
|
200
|
+
// A rule id in `common` that npm-check does not know belongs to a sibling tool, so it is
|
|
201
|
+
// dropped rather than merged (§8). Without this it reached mergeConfig's registry check and
|
|
202
|
+
// raised UNKNOWN_RULE, which made a shared config unusable the moment any sibling configured
|
|
203
|
+
// one of its own rules. Ids from the tool's own section are passed through untouched, so an
|
|
204
|
+
// unknown id there still errors — that one is a typo, not a sibling's.
|
|
205
|
+
const fromCommon = {};
|
|
206
|
+
if (commonRules && typeof commonRules === 'object') {
|
|
207
|
+
for (const [ruleId, value] of Object.entries(commonRules)) {
|
|
208
|
+
if (KNOWN_RULES.includes(ruleId)) fromCommon[ruleId] = value;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { ...fromCommon, ...(toolRules && typeof toolRules === 'object' ? toolRules : {}) };
|
|
193
213
|
}
|
|
194
214
|
|
|
195
|
-
// Warn about keys npm-check does not recognize inside
|
|
215
|
+
// Warn about keys npm-check does not recognize inside its own section (§8). `common`
|
|
216
|
+
// is deliberately not checked: it is shared with the sibling tools, so a key npm-check
|
|
217
|
+
// does not know there belongs to one of them, not to a typo. This matches how unknown
|
|
218
|
+
// rule ids are already treated — tolerated in `common`, an error in the own section.
|
|
196
219
|
function unknownKeyWarnings(section, label, warnings) {
|
|
197
220
|
if (!section || typeof section !== 'object') return;
|
|
198
221
|
for (const key of Object.keys(section)) {
|
|
@@ -218,7 +241,6 @@ function resolveToolSection(parsed, warnings) {
|
|
|
218
241
|
warnings.push({ code: 'DEPRECATED_ALIAS_SECTION', message: `both "${SECTION_KEY}" and "${DEPRECATED_SECTION_KEY}" sections present; using "${SECTION_KEY}"` });
|
|
219
242
|
}
|
|
220
243
|
|
|
221
|
-
unknownKeyWarnings(common, 'common', warnings);
|
|
222
244
|
unknownKeyWarnings(tool, SECTION_KEY, warnings);
|
|
223
245
|
|
|
224
246
|
const settings = {};
|
package/src/audit.js
CHANGED
|
@@ -666,6 +666,65 @@ const validPnpmFieldRule = {
|
|
|
666
666
|
}
|
|
667
667
|
};
|
|
668
668
|
|
|
669
|
+
/**
|
|
670
|
+
* Lockfile portability: every `resolved` URL must point at a host this project
|
|
671
|
+
* pins to.
|
|
672
|
+
*
|
|
673
|
+
* This is deliberately NOT the same question as `secure-resolved` /
|
|
674
|
+
* `no-remote-deps`, which both consult `allowedRegistryHosts` to ask "is this
|
|
675
|
+
* host a legitimate, trusted registry?". Trust and portability are orthogonal:
|
|
676
|
+
* an org's own private mirror is entirely trusted, yet a lockfile resolving
|
|
677
|
+
* from it cannot be installed by anyone outside that network (a public CI
|
|
678
|
+
* runner, an external contributor, a GitHub build). A shared
|
|
679
|
+
* `allowedRegistryHosts` also unions across config levels, so it can only ever
|
|
680
|
+
* grow more permissive — correct for a trust allowlist, but useless as a pin,
|
|
681
|
+
* which must be able to narrow.
|
|
682
|
+
*
|
|
683
|
+
* Opt-in: with no `hosts` configured the rule is a no-op, so projects that
|
|
684
|
+
* genuinely install from a private registry are unaffected.
|
|
685
|
+
*/
|
|
686
|
+
const resolvedRegistryPinRule = {
|
|
687
|
+
id: 'resolved-registry-pin',
|
|
688
|
+
description: 'Resolved URLs must point only at the registry hosts this project pins to',
|
|
689
|
+
defaultSeverity: 'error',
|
|
690
|
+
// pnpm lockfiles carry no `resolved` URLs (the registry is implied by config),
|
|
691
|
+
// so there is nothing to pin.
|
|
692
|
+
flavors: ['npm'],
|
|
693
|
+
check({ lockfile, options }) {
|
|
694
|
+
const { hosts = [] } = options;
|
|
695
|
+
const findings = [];
|
|
696
|
+
// Unconfigured == off. Pinning is a per-project decision, not a default.
|
|
697
|
+
if (!Array.isArray(hosts) || hosts.length === 0) return findings;
|
|
698
|
+
if (!lockfile.packages) return findings;
|
|
699
|
+
|
|
700
|
+
const pinned = hosts
|
|
701
|
+
.filter((h) => typeof h === 'string' && h.trim())
|
|
702
|
+
.map((h) => h.trim().toLowerCase());
|
|
703
|
+
if (pinned.length === 0) return findings;
|
|
704
|
+
|
|
705
|
+
forEachPackageEntry(lockfile, ({ key, entry, name, isRoot, isWorkspaceSource, isLink, isGitDep, isFileDep }) => {
|
|
706
|
+
// Git/file/link/workspace entries resolve outside the registry by
|
|
707
|
+
// definition — no-git-deps / secure-resolved own those.
|
|
708
|
+
if (isRoot || isWorkspaceSource || isLink || isGitDep || isFileDep) return;
|
|
709
|
+
const resolved = entry && entry.resolved;
|
|
710
|
+
if (!resolved || !/^https?:/i.test(resolved)) return;
|
|
711
|
+
let hostname;
|
|
712
|
+
try {
|
|
713
|
+
hostname = new URL(resolved).hostname.toLowerCase();
|
|
714
|
+
} catch {
|
|
715
|
+
// Unparseable URL — secure-resolved flags it; not this rule's job.
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (pinned.includes(hostname)) return;
|
|
719
|
+
findings.push({
|
|
720
|
+
packagePath: key,
|
|
721
|
+
message: `${name || key} resolves from "${hostname}", which is not a pinned registry host (${pinned.join(', ')}) — the lockfile will not install where that host is unreachable`
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
return findings;
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
669
728
|
export const rules = [
|
|
670
729
|
lockfileVersionRule,
|
|
671
730
|
validStructureRule,
|
|
@@ -675,6 +734,7 @@ export const rules = [
|
|
|
675
734
|
installScriptsRule,
|
|
676
735
|
noGitDepsRule,
|
|
677
736
|
noRemoteDepsRule,
|
|
737
|
+
resolvedRegistryPinRule,
|
|
678
738
|
pinnedVersionsRule,
|
|
679
739
|
lockfileSyncRule,
|
|
680
740
|
noOrphanPackagesRule,
|
package/src/exceptions.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/exceptions.js
|
|
2
2
|
//
|
|
3
|
-
// Reference implementation of the `.dependably` exception grammar
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// Reference implementation of the `.dependably` exception grammar, specified in
|
|
4
|
+
// §6 of the config spec at
|
|
5
|
+
// https://gitlab.northwardlabs.ca/moonlitlabs/dependably-spec. This is the module
|
|
6
|
+
// the C# and Python ports mirror; keep it behavior-compatible with the vendored
|
|
7
|
+
// conformance fixtures under conformance/dependably/.
|
|
7
8
|
//
|
|
8
9
|
// An exception suppresses SPECIFIC findings so a run does not fail on them,
|
|
9
10
|
// without excluding whole files (`exclude`) or disabling a rule globally
|
package/src/npmrc-validator.js
CHANGED
|
@@ -59,7 +59,7 @@ const KNOWN_KEYS = new Set([
|
|
|
59
59
|
'fetch-retry-maxtimeout', 'fetch-timeout', 'access', 'tag', 'lockfile-version',
|
|
60
60
|
'omit', 'include', 'ignore-scripts', 'foreground-scripts', 'node-options',
|
|
61
61
|
'progress', 'prefer-offline', 'prefer-online', 'offline', 'global', 'unsafe-perm',
|
|
62
|
-
'user-agent', 'maxsockets', 'before', 'workspaces', 'workspace'
|
|
62
|
+
'update-notifier', 'user-agent', 'maxsockets', 'before', 'workspaces', 'workspace'
|
|
63
63
|
]);
|
|
64
64
|
|
|
65
65
|
// Plaintext-credential keys: bare `_auth`/`_authtoken`/`_password`, or the
|
package/src/report.js
CHANGED
|
@@ -43,6 +43,7 @@ const RULE_SECTION = {
|
|
|
43
43
|
'install-scripts': 'install-scripts',
|
|
44
44
|
'no-git-deps': 'git',
|
|
45
45
|
'no-remote-deps': 'remote',
|
|
46
|
+
'resolved-registry-pin': 'registry-pin',
|
|
46
47
|
'pinned-versions': 'pinned',
|
|
47
48
|
'no-orphan-packages': 'orphans',
|
|
48
49
|
'unused-dependencies': 'unused',
|
|
@@ -72,6 +73,7 @@ const SECTIONS = [
|
|
|
72
73
|
{ id: 'install-scripts', title: 'Install scripts' },
|
|
73
74
|
{ id: 'git', title: 'Git dependencies' },
|
|
74
75
|
{ id: 'remote', title: 'Remote-URL deps' },
|
|
76
|
+
{ id: 'registry-pin', title: 'Registry pin' },
|
|
75
77
|
{ id: 'pinned', title: 'Pinned versions' },
|
|
76
78
|
{ id: 'orphans', title: 'Orphaned packages' },
|
|
77
79
|
{ id: 'unused', title: 'Unused dependencies' },
|