npm-pkg-lint 4.4.2 → 4.5.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/README.md CHANGED
@@ -111,6 +111,28 @@ Verifies the presence of files specified in:
111
111
  - `bin`
112
112
  - `man`
113
113
 
114
+ ## `exports` paths
115
+
116
+ Requires all values in `exports` to start with `./`.
117
+
118
+ **Why?** The Node.js specification requires export paths to be relative paths starting with `./`.
119
+ Values not starting with `./` will be treated as package names by some runtimes and bundlers, which is almost certainly not the intent.
120
+
121
+ ## `import` before `require` in `exports`
122
+
123
+ Requires `import` and `module`, if either is present alongside `require`, to come before `require` in `exports`.
124
+
125
+ **Why?** Some runtimes and bundlers evaluate conditions in order and stop at the first match.
126
+ If `require` is listed before `import` (or `module`), ESM-capable consumers that support both may unexpectedly pick up the CJS build.
127
+ `module` is treated as an alias for `import` as it serves the same purpose for bundlers such as webpack.
128
+
129
+ ## `default` in `exports`
130
+
131
+ Requires `default`, if present, to be the last condition in `exports`.
132
+
133
+ **Why?** The `default` condition is a catch-all fallback.
134
+ If it is listed before more specific conditions (e.g. `require` or `import`) those conditions will never be reached by runtimes that support them.
135
+
114
136
  ## TypeScript `types` in `exports`
115
137
 
116
138
  Requires `types` to be the first condition in `exports`.
@@ -135,6 +157,23 @@ Requires only one of the two fields `types` and `typings` to be used, not both.
135
157
 
136
158
  **Why?** `typings` is an alias for `types` and if both are set it is unclear which is to be used (and could potentially be set to different values).
137
159
 
160
+ ## Protocol dependencies
161
+
162
+ Disallows dependencies that resolve outside the registry across all dependency fields (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`).
163
+
164
+ **Why?** Protocol specifiers such as `file:`, `link:`, `github:` or `git+https:` reference local paths or remote git repositories instead of versioned registry packages.
165
+ Published packages should only depend on registry packages so that consumers can reliably install the same code.
166
+
167
+ Disallowed protocols:
168
+
169
+ - `file:` - local filesystem path
170
+ - `link:` - symlink
171
+ - `github:` / `gitlab:` / `bitbucket:` - platform shorthand
172
+ - `git:` / `git+https:` / `git+http:` / `git+ssh:` / `git+file:` - arbitrary git URL
173
+ - `http:` / `https:` - direct URL tarball
174
+ - `user/repo` - Github shorthand (without `github:` prefix)
175
+ - `user@host:path` - git URL (e.g. `git@github.com:user/repo.git`)
176
+
138
177
  ## Disallowed dependencies
139
178
 
140
179
  Disallows certain packages from being included as `dependencies` (use `devDependencies` or `peerDependencies` instead).
package/dist/index.js CHANGED
@@ -11374,8 +11374,8 @@ function isDisallowedDependency(pkg, dependency) {
11374
11374
  return match(disallowedDependencies, dependency);
11375
11375
  }
11376
11376
 
11377
- // src/rules/exports-types-order.ts
11378
- var ruleId4 = "exports-types-order";
11377
+ // src/rules/exports-default-order.ts
11378
+ var ruleId4 = "exports-default-order";
11379
11379
  var severity2 = Severity.ERROR;
11380
11380
  function* validateOrder(pkgAst, value, path8) {
11381
11381
  if (!value || typeof value === "string") {
@@ -11385,13 +11385,13 @@ function* validateOrder(pkgAst, value, path8) {
11385
11385
  if (keys.length === 0) {
11386
11386
  return;
11387
11387
  }
11388
- if (keys.includes("types") && keys[0] !== "types") {
11389
- const { line, column } = jsonLocation(pkgAst, "member", "exports", ...path8, "types");
11388
+ if (keys.includes("default") && keys.at(-1) !== "default") {
11389
+ const { line, column } = jsonLocation(pkgAst, "member", "exports", ...path8, "default");
11390
11390
  const property = path8.map((it2) => `["${it2}"]`).join("");
11391
11391
  yield {
11392
11392
  ruleId: ruleId4,
11393
11393
  severity: severity2,
11394
- message: `"types" must be the first condition in "exports${property}"`,
11394
+ message: `"default" must be the last condition in "exports${property}"`,
11395
11395
  line,
11396
11396
  column
11397
11397
  };
@@ -11400,12 +11400,187 @@ function* validateOrder(pkgAst, value, path8) {
11400
11400
  yield* validateOrder(pkgAst, value[key], [...path8, key]);
11401
11401
  }
11402
11402
  }
11403
- function* exportsTypesOrder(pkg, pkgAst) {
11403
+ function* exportsDefaultOrder(pkg, pkgAst) {
11404
11404
  if (pkg.exports) {
11405
11405
  yield* validateOrder(pkgAst, pkg.exports, []);
11406
11406
  }
11407
11407
  }
11408
11408
 
11409
+ // src/rules/exports-import-require-order.ts
11410
+ var ruleId5 = "exports-import-require-order";
11411
+ var severity3 = Severity.ERROR;
11412
+ function* validateOrder2(pkgAst, value, path8) {
11413
+ if (!value || typeof value === "string") {
11414
+ return;
11415
+ }
11416
+ const keys = Object.keys(value);
11417
+ if (keys.includes("require")) {
11418
+ for (const esm of ["import", "module"]) {
11419
+ if (keys.includes(esm) && keys.indexOf(esm) > keys.indexOf("require")) {
11420
+ const { line, column } = jsonLocation(pkgAst, "member", "exports", ...path8, esm);
11421
+ const property = path8.map((it2) => `["${it2}"]`).join("");
11422
+ yield {
11423
+ ruleId: ruleId5,
11424
+ severity: severity3,
11425
+ message: `"${esm}" must come before "require" in "exports${property}"`,
11426
+ line,
11427
+ column
11428
+ };
11429
+ }
11430
+ }
11431
+ }
11432
+ for (const key of keys) {
11433
+ yield* validateOrder2(pkgAst, value[key], [...path8, key]);
11434
+ }
11435
+ }
11436
+ function* exportsImportRequireOrder(pkg, pkgAst) {
11437
+ if (pkg.exports) {
11438
+ yield* validateOrder2(pkgAst, pkg.exports, []);
11439
+ }
11440
+ }
11441
+
11442
+ // src/rules/exports-path.ts
11443
+ var ruleId6 = "exports-path";
11444
+ var severity4 = Severity.ERROR;
11445
+ function* validatePath(pkgAst, value, path8) {
11446
+ if (value === null) {
11447
+ return;
11448
+ }
11449
+ if (typeof value === "string") {
11450
+ if (!value.startsWith("./")) {
11451
+ const { line, column } = jsonLocation(pkgAst, "value", "exports", ...path8);
11452
+ const property = path8.map((it2) => `["${it2}"]`).join("");
11453
+ yield {
11454
+ ruleId: ruleId6,
11455
+ severity: severity4,
11456
+ message: `"exports${property}" value "${value}" must start with "./"`,
11457
+ line,
11458
+ column
11459
+ };
11460
+ }
11461
+ return;
11462
+ }
11463
+ for (const [key, val] of Object.entries(value)) {
11464
+ yield* validatePath(pkgAst, val, [...path8, key]);
11465
+ }
11466
+ }
11467
+ function* exportsPath(pkg, pkgAst) {
11468
+ if (pkg.exports) {
11469
+ yield* validatePath(pkgAst, pkg.exports, []);
11470
+ }
11471
+ }
11472
+
11473
+ // src/rules/exports-types-order.ts
11474
+ var ruleId7 = "exports-types-order";
11475
+ var severity5 = Severity.ERROR;
11476
+ function* validateOrder3(pkgAst, value, path8) {
11477
+ if (!value || typeof value === "string") {
11478
+ return;
11479
+ }
11480
+ const keys = Object.keys(value);
11481
+ if (keys.length === 0) {
11482
+ return;
11483
+ }
11484
+ if (keys.includes("types") && keys[0] !== "types") {
11485
+ const { line, column } = jsonLocation(pkgAst, "member", "exports", ...path8, "types");
11486
+ const property = path8.map((it2) => `["${it2}"]`).join("");
11487
+ yield {
11488
+ ruleId: ruleId7,
11489
+ severity: severity5,
11490
+ message: `"types" must be the first condition in "exports${property}"`,
11491
+ line,
11492
+ column
11493
+ };
11494
+ }
11495
+ for (const key of keys) {
11496
+ yield* validateOrder3(pkgAst, value[key], [...path8, key]);
11497
+ }
11498
+ }
11499
+ function* exportsTypesOrder(pkg, pkgAst) {
11500
+ if (pkg.exports) {
11501
+ yield* validateOrder3(pkgAst, pkg.exports, []);
11502
+ }
11503
+ }
11504
+
11505
+ // src/rules/no-protocol-dependencies.ts
11506
+ var ruleId8 = "no-protocol-dependencies";
11507
+ var severity6 = Severity.ERROR;
11508
+ var disallowed = [
11509
+ "bitbucket:",
11510
+ "file:",
11511
+ "git+file:",
11512
+ "git+http:",
11513
+ "git+https:",
11514
+ "git+ssh:",
11515
+ "git:",
11516
+ "github:",
11517
+ "gitlab:",
11518
+ "http:",
11519
+ "https:",
11520
+ "link:"
11521
+ ];
11522
+ var depFields = [
11523
+ "dependencies",
11524
+ "devDependencies",
11525
+ "peerDependencies",
11526
+ "optionalDependencies"
11527
+ ];
11528
+ function getProtocol(version2) {
11529
+ for (const protocol of disallowed) {
11530
+ if (version2.startsWith(protocol)) {
11531
+ return protocol;
11532
+ }
11533
+ }
11534
+ return null;
11535
+ }
11536
+ var githubShorthandRe = /^[A-Za-z][\w-]*\/[\w.-]+(?:#.+)?$/;
11537
+ var gitUrlRe = /^[^@]+@[^:]+:.+/;
11538
+ function isGithubShorthand(version2) {
11539
+ return !version2.includes(":") && githubShorthandRe.test(version2);
11540
+ }
11541
+ function isGitUrl(version2) {
11542
+ return gitUrlRe.test(version2);
11543
+ }
11544
+ function* noProtocolDependencies(pkg, pkgAst) {
11545
+ for (const field of depFields) {
11546
+ const deps = pkg[field];
11547
+ if (!deps) {
11548
+ continue;
11549
+ }
11550
+ for (const [name, version2] of Object.entries(deps)) {
11551
+ const protocol = getProtocol(version2);
11552
+ if (protocol) {
11553
+ const { line, column } = jsonLocation(pkgAst, "member", field, name);
11554
+ yield {
11555
+ ruleId: ruleId8,
11556
+ severity: severity6,
11557
+ message: `"${name}" uses the "${protocol}" protocol specifier which is not allowed in published packages`,
11558
+ line,
11559
+ column
11560
+ };
11561
+ } else if (isGithubShorthand(version2)) {
11562
+ const { line, column } = jsonLocation(pkgAst, "member", field, name);
11563
+ yield {
11564
+ ruleId: ruleId8,
11565
+ severity: severity6,
11566
+ message: `"${name}" uses the GitHub shorthand "${version2}" which is not allowed in published packages`,
11567
+ line,
11568
+ column
11569
+ };
11570
+ } else if (isGitUrl(version2)) {
11571
+ const { line, column } = jsonLocation(pkgAst, "member", field, name);
11572
+ yield {
11573
+ ruleId: ruleId8,
11574
+ severity: severity6,
11575
+ message: `"${name}" uses a git URL "${version2}" which is not allowed in published packages`,
11576
+ line,
11577
+ column
11578
+ };
11579
+ }
11580
+ }
11581
+ }
11582
+ }
11583
+
11409
11584
  // src/rules/obsolete-dependency.ts
11410
11585
  var obsolete = [
11411
11586
  { package: "make-dir", message: `use native "fs.mkdir(..., { recursive: true })" instead` },
@@ -11446,14 +11621,14 @@ var nodeVersions = [
11446
11621
  ];
11447
11622
 
11448
11623
  // src/rules/outdated-engines.ts
11449
- var ruleId5 = "outdated-engines";
11450
- var severity3 = Severity.ERROR;
11624
+ var ruleId9 = "outdated-engines";
11625
+ var severity7 = Severity.ERROR;
11451
11626
  function* outdatedEngines(pkg, pkgAst, ignoreNodeVersion) {
11452
11627
  if (!pkg.engines?.node) {
11453
11628
  const { line: line2, column: column2 } = pkg.engines ? jsonLocation(pkgAst, "member", "engines") : { line: 1, column: 1 };
11454
11629
  yield {
11455
- ruleId: ruleId5,
11456
- severity: severity3,
11630
+ ruleId: ruleId9,
11631
+ severity: severity7,
11457
11632
  message: "Missing engines.node field",
11458
11633
  line: line2,
11459
11634
  column: column2
@@ -11464,8 +11639,8 @@ function* outdatedEngines(pkg, pkgAst, ignoreNodeVersion) {
11464
11639
  const range = pkg.engines.node;
11465
11640
  if (!import_semver2.default.validRange(range)) {
11466
11641
  yield {
11467
- ruleId: ruleId5,
11468
- severity: severity3,
11642
+ ruleId: ruleId9,
11643
+ severity: severity7,
11469
11644
  message: `engines.node "${range}" is not a valid semver range`,
11470
11645
  line,
11471
11646
  column
@@ -11491,8 +11666,8 @@ function* outdatedEngines(pkg, pkgAst, ignoreNodeVersion) {
11491
11666
  const nodeRelease = major > 0 ? major : `0.${String(minor)}`;
11492
11667
  const message = `engines.node is satisfied by Node ${String(nodeRelease)} (EOL since ${descriptor.eol})`;
11493
11668
  yield {
11494
- ruleId: ruleId5,
11495
- severity: severity3,
11669
+ ruleId: ruleId9,
11670
+ severity: severity7,
11496
11671
  message,
11497
11672
  line,
11498
11673
  column
@@ -11504,8 +11679,8 @@ function* outdatedEngines(pkg, pkgAst, ignoreNodeVersion) {
11504
11679
  const version2 = `v${String(ignoreNodeVersion)}.x`;
11505
11680
  const message = `--ignore-node-version=${option} used but engines.node="${range}" does not match ${version2} or the version is not EOL yet`;
11506
11681
  yield {
11507
- ruleId: ruleId5,
11508
- severity: severity3,
11682
+ ruleId: ruleId9,
11683
+ severity: severity7,
11509
11684
  message,
11510
11685
  line,
11511
11686
  column
@@ -11514,16 +11689,16 @@ function* outdatedEngines(pkg, pkgAst, ignoreNodeVersion) {
11514
11689
  }
11515
11690
 
11516
11691
  // src/rules/prefer-types.ts
11517
- var ruleId6 = "prefer-types";
11518
- var severity4 = Severity.ERROR;
11692
+ var ruleId10 = "prefer-types";
11693
+ var severity8 = Severity.ERROR;
11519
11694
  function* preferTypes(pkg, pkgAst) {
11520
11695
  if (!pkg.typings || pkg.types) {
11521
11696
  return;
11522
11697
  }
11523
11698
  const { line, column } = jsonLocation(pkgAst, "member", "typings");
11524
11699
  yield {
11525
- ruleId: ruleId6,
11526
- severity: severity4,
11700
+ ruleId: ruleId10,
11701
+ severity: severity8,
11527
11702
  message: `Prefer "types" over "typings"`,
11528
11703
  line,
11529
11704
  column
@@ -11531,8 +11706,8 @@ function* preferTypes(pkg, pkgAst) {
11531
11706
  }
11532
11707
 
11533
11708
  // src/rules/shadowed-types.ts
11534
- var ruleId7 = "shadowed-types";
11535
- var severity5 = Severity.ERROR;
11709
+ var ruleId11 = "shadowed-types";
11710
+ var severity9 = Severity.ERROR;
11536
11711
  function walkConditions(conditions) {
11537
11712
  if (!conditions || typeof conditions === "string") {
11538
11713
  return [];
@@ -11566,8 +11741,8 @@ function* validateTypeSubpath(pkgAst, subpaths, field, value) {
11566
11741
  }
11567
11742
  const { line, column } = jsonLocation(pkgAst, "member", field);
11568
11743
  yield {
11569
- ruleId: ruleId7,
11570
- severity: severity5,
11744
+ ruleId: ruleId11,
11745
+ severity: severity9,
11571
11746
  message: `"${field}" cannot be resolved when respecting "exports" field`,
11572
11747
  line,
11573
11748
  column
@@ -11589,8 +11764,8 @@ function* shadowedTypes(pkg, pkgAst) {
11589
11764
 
11590
11765
  // src/rules/types-node-matching-engine.ts
11591
11766
  var import_semver3 = __toESM(require_semver2(), 1);
11592
- var ruleId8 = "types-node-matching-engine";
11593
- var severity6 = Severity.ERROR;
11767
+ var ruleId12 = "types-node-matching-engine";
11768
+ var severity10 = Severity.ERROR;
11594
11769
  function* typesNodeMatchingEngine(pkg, pkgAst) {
11595
11770
  if (!pkg.engines?.node) {
11596
11771
  return;
@@ -11614,8 +11789,8 @@ function* typesNodeMatchingEngine(pkg, pkgAst) {
11614
11789
  const actualVersion = `v${String(typesVersion.major)}`;
11615
11790
  const expectedVersion = `v${String(nodeVersion.major)}`;
11616
11791
  yield {
11617
- ruleId: ruleId8,
11618
- severity: severity6,
11792
+ ruleId: ruleId12,
11793
+ severity: severity10,
11619
11794
  message: `@types/node ${actualVersion} does not equal engines.node ${expectedVersion}`,
11620
11795
  line,
11621
11796
  column
@@ -11626,7 +11801,7 @@ function* typesNodeMatchingEngine(pkg, pkgAst) {
11626
11801
 
11627
11802
  // src/rules/verify-engine-constraint.ts
11628
11803
  var import_semver4 = __toESM(require_semver2(), 1);
11629
- var ruleId9 = "invalid-engine-constraint";
11804
+ var ruleId13 = "invalid-engine-constraint";
11630
11805
  async function* getDeepDependencies(pkg, visited, dependency) {
11631
11806
  if (dependency) {
11632
11807
  if (visited.has(dependency)) {
@@ -11664,7 +11839,7 @@ async function verifyDependency(dependency, minDeclared, declaredConstraint) {
11664
11839
  }
11665
11840
  if (!import_semver4.default.satisfies(minDeclared, constraint)) {
11666
11841
  return {
11667
- ruleId: ruleId9,
11842
+ ruleId: ruleId13,
11668
11843
  severity: 2,
11669
11844
  message: `the transitive dependency "${dependency}" (node ${constraint}) does not satisfy the declared node engine "${declaredConstraint}"`,
11670
11845
  line: 1,
@@ -11693,7 +11868,7 @@ async function verifyEngineConstraint(pkg) {
11693
11868
  } catch (err) {
11694
11869
  if (isNpmInfoError(err) && err.code === "E404") {
11695
11870
  messages.push({
11696
- ruleId: ruleId9,
11871
+ ruleId: ruleId13,
11697
11872
  severity: 1,
11698
11873
  message: `the transitive dependency "${dependency}" is not published to the NPM registry`,
11699
11874
  line: 1,
@@ -11857,6 +12032,7 @@ function validUrl(key, value) {
11857
12032
  // src/package-json.ts
11858
12033
  var fields = {
11859
12034
  description: [present, typeString, nonempty],
12035
+ files: [present, typeArray],
11860
12036
  keywords: [present, typeArray, nonempty],
11861
12037
  homepage: [present, typeString, validUrl],
11862
12038
  bugs: [present, validUrl],
@@ -11952,9 +12128,13 @@ async function verifyPackageJson(pkg, pkgAst, filePath, options = { allowedDepen
11952
12128
  ...conflictingTypesTypings(pkg, pkgAst),
11953
12129
  ...await deprecatedDependency(pkg, pkgAst, options),
11954
12130
  ...await verifyEngineConstraint(pkg),
12131
+ ...exportsDefaultOrder(pkg, pkgAst),
12132
+ ...exportsImportRequireOrder(pkg, pkgAst),
12133
+ ...exportsPath(pkg, pkgAst),
11955
12134
  ...exportsTypesOrder(pkg, pkgAst),
11956
12135
  ...verifyFields(pkg, pkgAst, options),
11957
12136
  ...verifyDependencies(pkg, pkgAst, options),
12137
+ ...noProtocolDependencies(pkg, pkgAst),
11958
12138
  ...outdatedEngines(pkg, pkgAst, ignoreNodeVersion),
11959
12139
  ...preferTypes(pkg, pkgAst),
11960
12140
  ...shadowedTypes(pkg, pkgAst),