eslint-plugin-package-json 1.8.1 โ†’ 1.10.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
@@ -15,7 +15,7 @@
15
15
  <p align="center">
16
16
  <!-- prettier-ignore-start -->
17
17
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
18
- <a href="#contributors" target="_blank"><img alt="๐Ÿ‘ช All Contributors: 44" src="https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-44-21bb42.svg" /></a>
18
+ <a href="#contributors" target="_blank"><img alt="๐Ÿ‘ช All Contributors: 45" src="https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-45-21bb42.svg" /></a>
19
19
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
20
20
  <!-- prettier-ignore-end -->
21
21
  <a href="https://codecov.io/gh/michaelfaith/eslint-plugin-package-json" target="_blank"><img alt="๐Ÿงช Coverage" src="https://img.shields.io/codecov/c/github/michaelfaith/eslint-plugin-package-json?label=%F0%9F%A7%AA%20coverage" /></a>
@@ -49,7 +49,9 @@ See [Getting Started](https://eslint-plugin-package-json.dev/getting-started) fo
49
49
  | [prefer-rolling-workspace-spec](https://eslint-plugin-package-json.dev/rules/prefer-rolling-workspace-spec) | Require that dependencies declared with workspace protocol use a rolling workspace spec instead of a specific semver range. | | | ๐Ÿ’ก |
50
50
  | [repository-shorthand](https://eslint-plugin-package-json.dev/rules/repository-shorthand) | Enforce either object or shorthand declaration for repository. | โœ… | ๐Ÿ”ง | |
51
51
  | [require-attribution](https://eslint-plugin-package-json.dev/rules/require-attribution) | Ensures that proper attribution is included, requiring that either `author` or `contributors` is defined, and that if `contributors` is present, it should include at least one contributor. | โœ… | | ๐Ÿ’ก |
52
+ | [require-package-json-export](https://eslint-plugin-package-json.dev/rules/require-package-json-export) | Ensures that if a package specifies `exports` (or `publishConfig.exports`), an explicit `package.json` export is included. | | | ๐Ÿ’ก |
52
53
  | [restrict-dependency-ranges](https://eslint-plugin-package-json.dev/rules/restrict-dependency-ranges) | Restricts the range of dependencies to allow or disallow specific types of ranges. | | | ๐Ÿ’ก |
54
+ | [restrict-dist-tags](https://eslint-plugin-package-json.dev/rules/restrict-dist-tags) | Restricts dependency dist-tags to an allowed list and selected dependency types. | | | |
53
55
  | [restrict-private-properties](https://eslint-plugin-package-json.dev/rules/restrict-private-properties) | Disallows unnecessary properties in private packages. | | ๐Ÿ”ง | ๐Ÿ’ก |
54
56
  | [restrict-top-level-properties](https://eslint-plugin-package-json.dev/rules/restrict-top-level-properties) | Disallows specified top-level properties in package.json. | | | ๐Ÿ’ก |
55
57
  | [scripts-name-casing](https://eslint-plugin-package-json.dev/rules/scripts-name-casing) | Enforce that names for `scripts` are in kebab case (optionally separated by colons). | ๐ŸŽจ | | ๐Ÿ’ก |
@@ -209,7 +211,7 @@ Thanks! ๐Ÿป
209
211
  </tr>
210
212
  <tr>
211
213
  <td align="center" valign="top" width="14.28%"><a href="http://heggria.site"><img src="https://avatars.githubusercontent.com/u/34475327?v=4?s=100" width="100px;" alt="Heggria"/><br /><sub><b>Heggria</b></sub></a><br /><a href="#ideas-heggria" title="Ideas, Planning, & Feedback">๐Ÿค”</a></td>
212
- <td align="center" valign="top" width="14.28%"><a href="https://hirok.io"><img src="https://avatars.githubusercontent.com/u/1075694?v=4?s=100" width="100px;" alt="Hiroki Osame"/><br /><sub><b>Hiroki Osame</b></sub></a><br /><a href="#ideas-privatenumber" title="Ideas, Planning, & Feedback">๐Ÿค”</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=privatenumber" title="Code">๐Ÿ’ป</a></td>
214
+ <td align="center" valign="top" width="14.28%"><a href="https://hirok.io"><img src="https://avatars.githubusercontent.com/u/1075694?v=4?s=100" width="100px;" alt="Hiroki Osame"/><br /><sub><b>Hiroki Osame</b></sub></a><br /><a href="#ideas-privatenumber" title="Ideas, Planning, & Feedback">๐Ÿค”</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=privatenumber" title="Code">๐Ÿ’ป</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Aprivatenumber" title="Bug reports">๐Ÿ›</a></td>
213
215
  <td align="center" valign="top" width="14.28%"><a href="https://vanyauhalin.me/"><img src="https://avatars.githubusercontent.com/u/56481109?v=4?s=100" width="100px;" alt="Ivan Uhalin"/><br /><sub><b>Ivan Uhalin</b></sub></a><br /><a href="#ideas-vanyauhalin" title="Ideas, Planning, & Feedback">๐Ÿค”</a></td>
214
216
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Zamiell"><img src="https://avatars.githubusercontent.com/u/5511220?v=4?s=100" width="100px;" alt="James"/><br /><sub><b>James</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=Zamiell" title="Code">๐Ÿ’ป</a> <a href="#ideas-Zamiell" title="Ideas, Planning, & Feedback">๐Ÿค”</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3AZamiell" title="Bug reports">๐Ÿ›</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=Zamiell" title="Documentation">๐Ÿ“–</a></td>
215
217
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/zetlen"><img src="https://avatars.githubusercontent.com/u/1643758?v=4?s=100" width="100px;" alt="James Zetlen"/><br /><sub><b>James Zetlen</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=zetlen" title="Code">๐Ÿ’ป</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Azetlen" title="Bug reports">๐Ÿ›</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=zetlen" title="Documentation">๐Ÿ“–</a> <a href="#infra-zetlen" title="Infrastructure (Hosting, Build-Tools, etc)">๐Ÿš‡</a> <a href="#maintenance-zetlen" title="Maintenance">๐Ÿšง</a> <a href="#tool-zetlen" title="Tools">๐Ÿ”ง</a></td>
@@ -237,13 +239,14 @@ Thanks! ๐Ÿป
237
239
  <tr>
238
240
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/sirugh"><img src="https://avatars.githubusercontent.com/u/1278869?v=4?s=100" width="100px;" alt="Stephen"/><br /><sub><b>Stephen</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=sirugh" title="Code">๐Ÿ’ป</a></td>
239
241
  <td align="center" valign="top" width="14.28%"><a href="https://hyoban.cc"><img src="https://avatars.githubusercontent.com/u/38493346?v=4?s=100" width="100px;" alt="Stephen Zhou"/><br /><sub><b>Stephen Zhou</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Ahyoban" title="Bug reports">๐Ÿ›</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=hyoban" title="Code">๐Ÿ’ป</a> <a href="#ideas-hyoban" title="Ideas, Planning, & Feedback">๐Ÿค”</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=hyoban" title="Documentation">๐Ÿ“–</a></td>
242
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/thernstig"><img src="https://avatars.githubusercontent.com/u/30827238?v=4?s=100" width="100px;" alt="Tobias Hernstig"/><br /><sub><b>Tobias Hernstig</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Athernstig" title="Bug reports">๐Ÿ›</a></td>
240
243
  <td align="center" valign="top" width="14.28%"><a href="https://www.wesleytodd.com/"><img src="https://avatars.githubusercontent.com/u/1027776?v=4?s=100" width="100px;" alt="Wes Todd"/><br /><sub><b>Wes Todd</b></sub></a><br /><a href="#ideas-wesleytodd" title="Ideas, Planning, & Feedback">๐Ÿค”</a></td>
241
244
  <td align="center" valign="top" width="14.28%"><a href="https://ota-meshi.github.io/"><img src="https://avatars.githubusercontent.com/u/16508807?v=4?s=100" width="100px;" alt="Yosuke Ota"/><br /><sub><b>Yosuke Ota</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Aota-meshi" title="Bug reports">๐Ÿ›</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=ota-meshi" title="Code">๐Ÿ’ป</a></td>
242
245
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/b3rnhard"><img src="https://avatars.githubusercontent.com/u/10774404?v=4?s=100" width="100px;" alt="b3rnhard"/><br /><sub><b>b3rnhard</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Ab3rnhard" title="Bug reports">๐Ÿ›</a></td>
243
246
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/chouchouji"><img src="https://avatars.githubusercontent.com/u/70570907?v=4?s=100" width="100px;" alt="chouchouji"/><br /><sub><b>chouchouji</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=chouchouji" title="Code">๐Ÿ’ป</a></td>
244
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/michaelfaith"><img src="https://avatars.githubusercontent.com/u/8071845?v=4?s=100" width="100px;" alt="michael faith"/><br /><sub><b>michael faith</b></sub></a><br /><a href="#infra-michaelfaith" title="Infrastructure (Hosting, Build-Tools, etc)">๐Ÿš‡</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=michaelfaith" title="Code">๐Ÿ’ป</a> <a href="#maintenance-michaelfaith" title="Maintenance">๐Ÿšง</a> <a href="#ideas-michaelfaith" title="Ideas, Planning, & Feedback">๐Ÿค”</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Amichaelfaith" title="Bug reports">๐Ÿ›</a> <a href="#tool-michaelfaith" title="Tools">๐Ÿ”ง</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=michaelfaith" title="Documentation">๐Ÿ“–</a> <a href="#projectManagement-michaelfaith" title="Project Management">๐Ÿ“†</a></td>
245
247
  </tr>
246
248
  <tr>
249
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/michaelfaith"><img src="https://avatars.githubusercontent.com/u/8071845?v=4?s=100" width="100px;" alt="michael faith"/><br /><sub><b>michael faith</b></sub></a><br /><a href="#infra-michaelfaith" title="Infrastructure (Hosting, Build-Tools, etc)">๐Ÿš‡</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=michaelfaith" title="Code">๐Ÿ’ป</a> <a href="#maintenance-michaelfaith" title="Maintenance">๐Ÿšง</a> <a href="#ideas-michaelfaith" title="Ideas, Planning, & Feedback">๐Ÿค”</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Amichaelfaith" title="Bug reports">๐Ÿ›</a> <a href="#tool-michaelfaith" title="Tools">๐Ÿ”ง</a> <a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=michaelfaith" title="Documentation">๐Ÿ“–</a> <a href="#projectManagement-michaelfaith" title="Project Management">๐Ÿ“†</a></td>
247
250
  <td align="center" valign="top" width="14.28%"><a href="https://roottool.vercel.app"><img src="https://avatars.githubusercontent.com/u/11808736?v=4?s=100" width="100px;" alt="roottool"/><br /><sub><b>roottool</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/commits?author=roottool" title="Code">๐Ÿ’ป</a></td>
248
251
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/sunnytsang1998"><img src="https://avatars.githubusercontent.com/u/207208443?v=4?s=100" width="100px;" alt="sunnytsang1998"/><br /><sub><b>sunnytsang1998</b></sub></a><br /><a href="https://github.com/michaelfaith/eslint-plugin-package-json/issues?q=author%3Asunnytsang1998" title="Bug reports">๐Ÿ›</a></td>
249
252
  </tr>
@@ -1,4 +1,4 @@
1
- import { t as plugin$1 } from "../plugin-DlF-r5D9.mjs";
1
+ import { t as plugin$1 } from "../plugin-BfdzXm-J.mjs";
2
2
  import { toCompatRule } from "eslint-json-compat-utils";
3
3
  //#region src/experimental/plugin.ts
4
4
  const language = "json/json";
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as plugin } from "./plugin-DlF-r5D9.mjs";
1
+ import { t as plugin } from "./plugin-BfdzXm-J.mjs";
2
2
  //#region src/index.ts
3
3
  const rules = plugin.rules;
4
4
  const configs = plugin.configs;
@@ -41,8 +41,7 @@ function createRule(rule) {
41
41
  const ruleGroup = rule.meta.docs?.ruleGroup;
42
42
  return {
43
43
  create(context) {
44
- if (!isPackageJson(context.filename)) return {};
45
- return rule.create(context);
44
+ return isPackageJson(context.filename) ? rule.create(context) : {};
46
45
  },
47
46
  meta: {
48
47
  ...rule.meta,
@@ -55,7 +54,7 @@ function createRule(rule) {
55
54
  }
56
55
  //#endregion
57
56
  //#region src/rules/bin-name-casing.ts
58
- const rule$19 = createRule({
57
+ const rule$21 = createRule({
59
58
  create(context) {
60
59
  return { "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=bin]"(node) {
61
60
  if (node.value.type === "JSONObjectExpression") for (const property of node.value.properties) {
@@ -93,17 +92,16 @@ const rule$19 = createRule({
93
92
  });
94
93
  //#endregion
95
94
  //#region src/rules/exports-subpaths-style.ts
96
- function isImplicitFormat(node) {
97
- if (node.type === "JSONLiteral") return true;
98
- return node.properties.every((property) => !isJSONStringLiteral(property.key) || !property.key.value.startsWith("."));
95
+ function isImplicitFormat$1(node) {
96
+ return node.type === "JSONLiteral" ? true : node.properties.every((property) => !isJSONStringLiteral(property.key) || !property.key.value.startsWith("."));
99
97
  }
100
- const rule$18 = createRule({
98
+ const rule$20 = createRule({
101
99
  create(context) {
102
100
  const [{ prefer = "explicit" } = {}] = context.options;
103
101
  function validateForExplicit(node) {
104
102
  const { value } = node;
105
103
  if (isJSONNullLiteral(value)) return;
106
- if (value.type !== "JSONLiteral" && value.type !== "JSONObjectExpression" || !isImplicitFormat(value)) return;
104
+ if (value.type !== "JSONLiteral" && value.type !== "JSONObjectExpression" || !isImplicitFormat$1(value)) return;
107
105
  context.report({
108
106
  fix(fixer) {
109
107
  const valueText = context.sourceCode.getText(value);
@@ -200,25 +198,21 @@ const getTopLevelProperty = (node) => {
200
198
  return n.type === "JSONProperty" ? n.key : void 0;
201
199
  };
202
200
  const defaultIgnoreProperties = ["files"];
203
- const rule$17 = createRule({
201
+ const rule$19 = createRule({
204
202
  create(context) {
205
203
  const ignoreProperties = new Set(context.options[0]?.ignoreProperties ?? defaultIgnoreProperties);
206
204
  return {
207
205
  JSONArrayExpression(node) {
208
206
  const topLevelProperty = getTopLevelProperty(node);
209
- if (!topLevelProperty) return;
210
- if (!node.elements.length) {
211
- const topLevelPropertyName = topLevelProperty.value;
212
- if (!ignoreProperties.has(topLevelPropertyName)) report(context, getNode(node));
213
- }
207
+ if (!topLevelProperty || node.elements.length) return;
208
+ const topLevelPropertyName = topLevelProperty.value;
209
+ if (!ignoreProperties.has(topLevelPropertyName)) report(context, getNode(node));
214
210
  },
215
211
  JSONObjectExpression(node) {
216
212
  const topLevelProperty = getTopLevelProperty(node);
217
- if (!topLevelProperty) return;
218
- if (!node.properties.length) {
219
- const topLevelPropertyName = topLevelProperty.value;
220
- if (!ignoreProperties.has(topLevelPropertyName)) report(context, getNode(node));
221
- }
213
+ if (!topLevelProperty || node.properties.length) return;
214
+ const topLevelPropertyName = topLevelProperty.value;
215
+ if (!ignoreProperties.has(topLevelPropertyName)) report(context, getNode(node));
222
216
  }
223
217
  };
224
218
  },
@@ -256,7 +250,7 @@ const getBundledDependencyNames = (value) => {
256
250
  if (value?.type === "JSONArrayExpression") for (const element of value.elements.filter(isNotNullish).filter(isJSONStringLiteral)) names.add(element.value);
257
251
  return names;
258
252
  };
259
- const rule$16 = createRule({
253
+ const rule$18 = createRule({
260
254
  create(context) {
261
255
  const ignorePrivate = context.options[0]?.ignorePrivate ?? true;
262
256
  let isPrivate = false;
@@ -327,7 +321,7 @@ const getCachedLocalFileRegex = (filename) => {
327
321
  return regex;
328
322
  }
329
323
  };
330
- const rule$15 = createRule({
324
+ const rule$17 = createRule({
331
325
  create(context) {
332
326
  const entryCache = {
333
327
  bin: [],
@@ -404,7 +398,7 @@ const rule$15 = createRule({
404
398
  });
405
399
  //#endregion
406
400
  //#region src/rules/no-redundant-publishConfig.ts
407
- const rule$14 = createRule({
401
+ const rule$16 = createRule({
408
402
  create(context) {
409
403
  let packageName;
410
404
  let publishConfigAccessProperty;
@@ -450,7 +444,7 @@ const rule$14 = createRule({
450
444
  });
451
445
  //#endregion
452
446
  //#region src/rules/order-properties.ts
453
- const rule$13 = createRule({
447
+ const rule$15 = createRule({
454
448
  create(context) {
455
449
  return { "Program:exit"() {
456
450
  const { ast, text } = context.sourceCode;
@@ -510,7 +504,7 @@ const rule$13 = createRule({
510
504
  //#region src/rules/prefer-rolling-workspace-spec.ts
511
505
  const rollingWorkspaceSpecRegex = /^workspace:[*^~]?$/;
512
506
  const convertibleSpecRegex = /^(workspace:[~^])/;
513
- const rule$12 = createRule({
507
+ const rule$14 = createRule({
514
508
  create(context) {
515
509
  const { ignoreDependencies = [], ignorePatterns = [] } = context.options[0] ?? {};
516
510
  const ignoreRegexes = ignorePatterns.map((pattern) => new RegExp(pattern));
@@ -608,14 +602,13 @@ const createUrl = (shorthand) => {
608
602
  }
609
603
  return `${providerUrls.github}${shorthand}`;
610
604
  };
611
- const rule$11 = createRule({
605
+ const rule$13 = createRule({
612
606
  create(context) {
613
607
  const [{ form = "object" } = {}] = context.options;
614
608
  function validateRepositoryForObject(node) {
615
609
  if (isJSONStringLiteral(node.value)) context.report({
616
610
  fix(fixer) {
617
- if (!isJSONStringLiteral(node.value) || node.value.value.split("/").filter(Boolean).length !== 2) return null;
618
- return fixer.replaceText(node.value, JSON.stringify({
611
+ return !isJSONStringLiteral(node.value) || node.value.value.split("/").filter(Boolean).length !== 2 ? null : fixer.replaceText(node.value, JSON.stringify({
619
612
  type: "git",
620
613
  url: createUrl(node.value.value)
621
614
  }, null, 2));
@@ -687,7 +680,7 @@ const rule$11 = createRule({
687
680
  });
688
681
  //#endregion
689
682
  //#region src/rules/require-attribution.ts
690
- const rule$10 = createRule({
683
+ const rule$12 = createRule({
691
684
  create(context) {
692
685
  const preferContributorsOnly = context.options[0]?.preferContributorsOnly ?? false;
693
686
  const ignorePrivate = context.options[0]?.ignorePrivate ?? true;
@@ -774,6 +767,86 @@ const rule$10 = createRule({
774
767
  name: "require-attribution"
775
768
  });
776
769
  //#endregion
770
+ //#region src/utils/increaseIndent.ts
771
+ const increaseIndent = (original, additional) => original.split("\n").join(`\n${additional}`);
772
+ //#endregion
773
+ //#region src/rules/require-package-json-export.ts
774
+ function isImplicitFormat(node) {
775
+ return node.properties.every((property) => !isJSONStringLiteral(property.key) || !property.key.value.startsWith("."));
776
+ }
777
+ const rule$11 = createRule({
778
+ create(context) {
779
+ let publishConfigExportsValueNode;
780
+ let exportsValueNode;
781
+ return {
782
+ "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=exports]"(node) {
783
+ if (node.value.type === "JSONObjectExpression" || isJSONStringLiteral(node.value)) exportsValueNode = node.value;
784
+ },
785
+ "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=publishConfig] > JSONObjectExpression > JSONProperty[key.value=exports]"(node) {
786
+ if (node.value.type === "JSONObjectExpression" || isJSONStringLiteral(node.value)) publishConfigExportsValueNode = node.value;
787
+ },
788
+ "Program:exit"() {
789
+ const exportsNode = publishConfigExportsValueNode ?? exportsValueNode;
790
+ if (!exportsNode) return;
791
+ const isPublishConfig = !!publishConfigExportsValueNode;
792
+ const { text } = context.sourceCode;
793
+ const { indent, type } = detectIndent(text);
794
+ const indentUnit = type === "tab" ? " " : indent || " ";
795
+ const extraIndent = isPublishConfig ? `${indentUnit}${indentUnit}` : indentUnit;
796
+ if (isJSONStringLiteral(exportsNode) || isImplicitFormat(exportsNode)) {
797
+ context.report({
798
+ messageId: "missing",
799
+ node: exportsNode,
800
+ suggest: [{
801
+ messageId: "addExport",
802
+ fix(fixer) {
803
+ const valueText = context.sourceCode.getText(exportsNode);
804
+ const fixedValue = increaseIndent(JSON.stringify({
805
+ ".": JSON.parse(valueText),
806
+ "./package.json": "./package.json"
807
+ }, null, indentUnit), extraIndent);
808
+ return fixer.replaceText(exportsNode, fixedValue);
809
+ }
810
+ }]
811
+ });
812
+ return;
813
+ }
814
+ if (exportsNode.properties.some((property) => isJSONStringLiteral(property.key) && property.key.value === "./package.json")) return;
815
+ context.report({
816
+ messageId: "missing",
817
+ node: exportsNode,
818
+ suggest: [{
819
+ messageId: "addExport",
820
+ fix(fixer) {
821
+ const valueText = context.sourceCode.getText(exportsNode);
822
+ const existingExports = JSON.parse(valueText);
823
+ const fixedValue = increaseIndent(JSON.stringify({
824
+ ...existingExports,
825
+ "./package.json": "./package.json"
826
+ }, null, indentUnit), extraIndent);
827
+ return fixer.replaceText(exportsNode, fixedValue);
828
+ }
829
+ }]
830
+ });
831
+ }
832
+ };
833
+ },
834
+ meta: {
835
+ docs: {
836
+ category: "Best Practices",
837
+ description: "Ensures that if a package specifies `exports` (or `publishConfig.exports`), an explicit `package.json` export is included."
838
+ },
839
+ hasSuggestions: true,
840
+ messages: {
841
+ missing: "An export for the package.json is required, and missing.",
842
+ addExport: "Add package.json export."
843
+ },
844
+ schema: [],
845
+ type: "suggestion"
846
+ },
847
+ name: "require-package-json-export"
848
+ });
849
+ //#endregion
777
850
  //#region src/utils/createSimpleRequirePropertyRule.ts
778
851
  /**
779
852
  * Given a top-level property name, create a rule that requires that property to be present.
@@ -790,21 +863,20 @@ const createSimpleRequirePropertyRule = (propertyName, { category, fixValue, ign
790
863
  const ignorePrivate = context.options[0]?.ignorePrivate ?? (typeof enforceForPrivate === "boolean" ? !enforceForPrivate : ignorePrivateDefault);
791
864
  return { "Program > JSONExpressionStatement > JSONObjectExpression"(node) {
792
865
  if (ignorePrivate && node.properties.some((property) => isJSONStringLiteral(property.key) && property.key.value === "private" && property.value.type === "JSONLiteral" && property.value.value === true)) return;
793
- if (node.properties.every((property) => !(isJSONStringLiteral(property.key) && property.key.value === propertyName))) {
794
- const resolvedValue = typeof fixValue === "function" ? fixValue() : fixValue;
795
- context.report({
796
- data: { property: propertyName },
797
- fix: resolvedValue === void 0 ? void 0 : function* (fixer) {
798
- yield fixer.insertTextAfterRange([0, 1], `\n "${propertyName}": ${JSON.stringify(resolvedValue, null, 2).split("\n").join("\n ")}`);
799
- yield node.properties.length > 0 ? fixer.insertTextAfterRange([0, 1], ",") : fixer.insertTextAfterRange([0, 1], "\n");
800
- },
801
- loc: {
802
- column: 0,
803
- line: 1
804
- },
805
- messageId: "missing"
806
- });
807
- }
866
+ if (node.properties.some((property) => isJSONStringLiteral(property.key) && property.key.value === propertyName)) return;
867
+ const resolvedValue = typeof fixValue === "function" ? fixValue() : fixValue;
868
+ context.report({
869
+ data: { property: propertyName },
870
+ fix: resolvedValue === void 0 ? void 0 : function* (fixer) {
871
+ yield fixer.insertTextAfterRange([0, 1], `\n "${propertyName}": ${JSON.stringify(resolvedValue, null, 2).split("\n").join("\n ")}`);
872
+ yield node.properties.length > 0 ? fixer.insertTextAfterRange([0, 1], ",") : fixer.insertTextAfterRange([0, 1], "\n");
873
+ },
874
+ loc: {
875
+ column: 0,
876
+ line: 1
877
+ },
878
+ messageId: "missing"
879
+ });
808
880
  } };
809
881
  },
810
882
  meta: {
@@ -929,7 +1001,7 @@ const rules$2 = Object.fromEntries([
929
1001
  }));
930
1002
  //#endregion
931
1003
  //#region src/rules/restrict-dependency-ranges.ts
932
- const DEPENDENCY_TYPES = [
1004
+ const DEPENDENCY_TYPES$1 = [
933
1005
  "dependencies",
934
1006
  "devDependencies",
935
1007
  "optionalDependencies",
@@ -975,7 +1047,7 @@ const schemaOptions = {
975
1047
  properties: {
976
1048
  forDependencyTypes: {
977
1049
  description: "Apply a range type restriction for an entire group of dependencies by which type of dependencies they belong to.",
978
- items: { enum: DEPENDENCY_TYPES },
1050
+ items: { enum: DEPENDENCY_TYPES$1 },
979
1051
  type: "array"
980
1052
  },
981
1053
  forPackages: {
@@ -1001,7 +1073,7 @@ const schemaOptions = {
1001
1073
  const normalizeRangeType = (rangeTypeOrSymbol) => RANGE_TYPES.find((rangeType) => "symbol" in rangeType && rangeType.symbol === rangeTypeOrSymbol || rangeType.alias === rangeTypeOrSymbol);
1002
1074
  /** @returns `undefined` if workspace versions are not supported for the specified {@link rangeType} */
1003
1075
  const getWorkspaceVersionForRange = (rangeType) => {
1004
- if ("workspaceSymbol" in rangeType) return `workspace:${rangeType.workspaceSymbol}`;
1076
+ return "workspaceSymbol" in rangeType ? `workspace:${rangeType.workspaceSymbol}` : void 0;
1005
1077
  };
1006
1078
  /** For displaying a range type in a user-facing way (ie. an error message). */
1007
1079
  const displayRangeType = (rangeType) => "symbol" in rangeType ? rangeType.symbol : rangeType.alias;
@@ -1029,7 +1101,7 @@ const isVersionSupported = (version) => {
1029
1101
  return !!semver.validRange(rawVersion);
1030
1102
  };
1031
1103
  const POSSIBLE_WORKSPACE_SPEC_REGEX = /(?:workspace:)?[^~]?/;
1032
- const rule$9 = createRule({
1104
+ const rule$10 = createRule({
1033
1105
  create(context) {
1034
1106
  if (!context.options[0]) return {};
1035
1107
  const optionsArray = (Array.isArray(context.options[0]) ? context.options[0].toReversed() : [context.options[0]]).map((option) => ({
@@ -1039,7 +1111,7 @@ const rule$9 = createRule({
1039
1111
  }));
1040
1112
  return { "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.type=JSONLiteral][value.type=JSONObjectExpression]"(node) {
1041
1113
  const dependencyType = node.key.value;
1042
- if (!DEPENDENCY_TYPES.includes(dependencyType)) return;
1114
+ if (!DEPENDENCY_TYPES$1.includes(dependencyType)) return;
1043
1115
  for (const property of node.value.properties) {
1044
1116
  if (!isJSONStringLiteral(property.key) || !isJSONStringLiteral(property.value)) continue;
1045
1117
  const name = property.key.value;
@@ -1106,6 +1178,73 @@ const rule$9 = createRule({
1106
1178
  name: "restrict-dependency-ranges"
1107
1179
  });
1108
1180
  //#endregion
1181
+ //#region src/rules/restrict-dist-tags.ts
1182
+ const DEPENDENCY_TYPES = [
1183
+ "dependencies",
1184
+ "devDependencies",
1185
+ "optionalDependencies",
1186
+ "peerDependencies"
1187
+ ];
1188
+ const DEFAULT_ALLOWED_TAGS = [
1189
+ "latest",
1190
+ "dev",
1191
+ "next",
1192
+ "alpha",
1193
+ "beta",
1194
+ "canary",
1195
+ "rc"
1196
+ ];
1197
+ const distTagPattern = /^[A-Z0-9][\w.-]*$/i;
1198
+ function isDistTag(value) {
1199
+ return !semver.validRange(value) && distTagPattern.test(value);
1200
+ }
1201
+ const rule$9 = createRule({
1202
+ create(context) {
1203
+ const { allowed = DEFAULT_ALLOWED_TAGS, allowedFor } = context.options[0] ?? {};
1204
+ return { "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.type=JSONLiteral][value.type=JSONObjectExpression]"(node) {
1205
+ const dependencyType = node.key.value;
1206
+ if (!DEPENDENCY_TYPES.includes(dependencyType)) return;
1207
+ for (const property of node.value.properties) {
1208
+ if (!isJSONStringLiteral(property.key) || !isJSONStringLiteral(property.value)) continue;
1209
+ const spec = property.value.value;
1210
+ if (!isDistTag(spec)) continue;
1211
+ if ((allowedFor === void 0 || allowedFor.includes(dependencyType)) && allowed.includes(spec)) continue;
1212
+ context.report({
1213
+ data: {
1214
+ dependencyType,
1215
+ tag: spec
1216
+ },
1217
+ messageId: "disallowedDistTag",
1218
+ node: property.value
1219
+ });
1220
+ }
1221
+ } };
1222
+ },
1223
+ meta: {
1224
+ defaultOptions: [{ allowed: DEFAULT_ALLOWED_TAGS }],
1225
+ docs: { description: "Restricts dependency dist-tags to an allowed list and selected dependency types." },
1226
+ messages: { disallowedDistTag: "The \"{{ tag }}\" dist-tag is not allowed for {{ dependencyType }}." },
1227
+ schema: [{
1228
+ type: "object",
1229
+ properties: {
1230
+ allowed: {
1231
+ description: "Dist-tags allowed in dependency specifications.",
1232
+ type: "array",
1233
+ items: { type: "string" }
1234
+ },
1235
+ allowedFor: {
1236
+ description: "Dependency sections where the allowed dist-tags may be used.",
1237
+ type: "array",
1238
+ items: { enum: DEPENDENCY_TYPES }
1239
+ }
1240
+ },
1241
+ additionalProperties: false
1242
+ }],
1243
+ type: "problem"
1244
+ },
1245
+ name: "restrict-dist-tags"
1246
+ });
1247
+ //#endregion
1109
1248
  //#region src/rules/restrict-private-properties.ts
1110
1249
  const defaultBlockedProperties = ["files", "publishConfig"];
1111
1250
  const rule$8 = createRule({
@@ -1325,8 +1464,7 @@ const rule$5 = createRule({
1325
1464
  const bKey = b.key.value;
1326
1465
  const ai = rank(aKey);
1327
1466
  const bi = rank(bKey);
1328
- if (ai !== bi) return ai - bi;
1329
- return naturalCompare(aKey, bKey);
1467
+ return ai === bi ? naturalCompare(aKey, bKey) : ai - bi;
1330
1468
  });
1331
1469
  if (currentOrder.some((property, i) => desiredOrder[i] !== property)) context.report({
1332
1470
  data: { key },
@@ -1490,10 +1628,9 @@ const rule$3 = createRule({
1490
1628
  if (!dependencyPropertyNames.has(node.key.value)) return;
1491
1629
  const nodeValueType = node.value.type;
1492
1630
  if (nodeValueType === "JSONArrayExpression") check(node.value.elements, (element) => element);
1493
- if (nodeValueType === "JSONObjectExpression") {
1494
- check(node.value.properties.map((property) => property.key), (property) => property.parent);
1495
- if (trackForCrossGroupUniqueness.includes(node.key.value)) dependenciesCache[node.key.value] = node.value.properties;
1496
- }
1631
+ if (nodeValueType !== "JSONObjectExpression") return;
1632
+ check(node.value.properties.map((property) => property.key), (property) => property.parent);
1633
+ if (trackForCrossGroupUniqueness.includes(node.key.value)) dependenciesCache[node.key.value] = node.value.properties;
1497
1634
  },
1498
1635
  "Program:exit"() {
1499
1636
  const dependencyNames = new Set(dependenciesCache.dependencies.map((node) => node.key).filter(isJSONStringLiteral).map((dependencyNameNode) => dependencyNameNode.value));
@@ -1743,8 +1880,7 @@ function* lookup(cwd = process.cwd()) {
1743
1880
  }
1744
1881
  }
1745
1882
  function parsePackageJson(filepath) {
1746
- if (!filepath || !fs.existsSync(filepath)) return null;
1747
- return handlePackageManager(filepath);
1883
+ return !filepath || !fs.existsSync(filepath) ? null : handlePackageManager(filepath);
1748
1884
  }
1749
1885
  /**
1750
1886
  * Detects the package manager used in the project.
@@ -1788,10 +1924,10 @@ function getNameAndVersion(pkg) {
1788
1924
  version: normalizeVersion(version)
1789
1925
  };
1790
1926
  }
1791
- if (typeof pkg.devEngines?.packageManager?.name === "string") return {
1927
+ return typeof pkg.devEngines?.packageManager?.name === "string" ? {
1792
1928
  name: pkg.devEngines.packageManager.name,
1793
1929
  version: normalizeVersion(pkg.devEngines.packageManager.version)
1794
- };
1930
+ } : void 0;
1795
1931
  }
1796
1932
  function handlePackageManager(filepath) {
1797
1933
  try {
@@ -1940,18 +2076,20 @@ const rule = createRule({
1940
2076
  //#region src/plugin.ts
1941
2077
  const require = createRequire(import.meta.url);
1942
2078
  const rules = {
1943
- "bin-name-casing": rule$19,
1944
- "exports-subpaths-style": rule$18,
1945
- "no-empty-fields": rule$17,
1946
- "no-local-dependencies": rule$16,
1947
- "no-redundant-files": rule$15,
1948
- "no-redundant-publishConfig": rule$14,
1949
- "order-properties": rule$13,
1950
- "prefer-rolling-workspace-spec": rule$12,
1951
- "require-attribution": rule$10,
2079
+ "bin-name-casing": rule$21,
2080
+ "exports-subpaths-style": rule$20,
2081
+ "no-empty-fields": rule$19,
2082
+ "no-local-dependencies": rule$18,
2083
+ "no-redundant-files": rule$17,
2084
+ "no-redundant-publishConfig": rule$16,
2085
+ "order-properties": rule$15,
2086
+ "prefer-rolling-workspace-spec": rule$14,
2087
+ "require-attribution": rule$12,
2088
+ "require-package-json-export": rule$11,
1952
2089
  ...rules$2,
1953
- "repository-shorthand": rule$11,
1954
- "restrict-dependency-ranges": rule$9,
2090
+ "repository-shorthand": rule$13,
2091
+ "restrict-dependency-ranges": rule$10,
2092
+ "restrict-dist-tags": rule$9,
1955
2093
  "restrict-private-properties": rule$8,
1956
2094
  "restrict-top-level-properties": rule$7,
1957
2095
  "scripts-name-casing": rule$6,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-package-json",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "description": "Rules for consistent, readable, and valid package.json files. ๐Ÿ—‚๏ธ",
5
5
  "homepage": "https://github.com/michaelfaith/eslint-plugin-package-json#readme",
6
6
  "bugs": {
@@ -36,7 +36,8 @@
36
36
  "./experimental": {
37
37
  "types": "./dist/experimental/index.d.mts",
38
38
  "default": "./dist/experimental/index.mjs"
39
- }
39
+ },
40
+ "./package.json": "./package.json"
40
41
  },
41
42
  "main": "dist/index.mjs",
42
43
  "files": [
@@ -59,38 +60,38 @@
59
60
  "devDependencies": {
60
61
  "@astrojs/check": "0.9.10",
61
62
  "@astrojs/markdown-satteri": "0.4.1",
62
- "@astrojs/starlight": "0.42.0",
63
+ "@astrojs/starlight": "0.42.2",
63
64
  "@catppuccin/starlight": "2.1.0",
64
- "@eslint-community/eslint-plugin-eslint-comments": "4.8.0",
65
+ "@eslint-community/eslint-plugin-eslint-comments": "4.8.1",
65
66
  "@eslint/js": "10.0.1",
66
67
  "@eslint/json": "2.1.0",
67
68
  "@eslint/markdown": "8.0.3",
68
69
  "@ianvs/prettier-plugin-sort-imports": "4.7.1",
69
- "@types/node": "24.13.4",
70
+ "@types/node": "24.13.6",
70
71
  "@types/semver": "7.8.0",
71
72
  "@typescript/native": "npm:typescript@7.0.2",
72
- "@vitest/coverage-v8": "5.0.0",
73
+ "@vitest/coverage-v8": "5.0.1",
73
74
  "@vitest/eslint-plugin": "1.6.27",
74
- "astro": "7.3.2",
75
- "astro-og-canvas": "0.13.1",
75
+ "astro": "7.3.3",
76
+ "astro-og-canvas": "0.13.2",
76
77
  "canvaskit-wasm": "0.42.0",
77
78
  "console-fail-test": "0.6.1",
78
- "eslint": "10.10.0",
79
+ "eslint": "10.11.0",
79
80
  "eslint-doc-generator": "3.7.1",
80
81
  "eslint-plugin-eslint-plugin": "7.6.2",
81
- "eslint-plugin-jsdoc": "64.3.8",
82
+ "eslint-plugin-jsdoc": "64.5.4",
82
83
  "eslint-plugin-jsonc": "3.4.2",
83
84
  "eslint-plugin-n": "18.3.0",
84
85
  "eslint-plugin-node-dependencies": "2.2.0",
85
- "eslint-plugin-perfectionist": "5.11.0",
86
+ "eslint-plugin-perfectionist": "5.11.1",
86
87
  "eslint-plugin-regexp": "3.3.0",
87
- "eslint-plugin-unicorn": "74.0.0",
88
+ "eslint-plugin-unicorn": "75.0.0",
88
89
  "eslint-plugin-yml": "3.8.1",
89
90
  "jiti": "2.7.0",
90
91
  "json-schema-to-ts": "3.1.1",
91
- "knip": "6.35.1",
92
+ "knip": "6.37.0",
92
93
  "pkg-pr-new": "0.0.88",
93
- "prettier": "3.9.6",
94
+ "prettier": "3.9.8",
94
95
  "prettier-plugin-curly": "0.4.1",
95
96
  "prettier-plugin-packagejson": "3.0.2",
96
97
  "prettier-plugin-sh": "0.19.0",
@@ -101,7 +102,7 @@
101
102
  "tsdown": "0.23.0",
102
103
  "typescript": "npm:@typescript/typescript6@6.0.2",
103
104
  "typescript-eslint": "8.70.0",
104
- "vitest": "5.0.0"
105
+ "vitest": "5.0.1"
105
106
  },
106
107
  "peerDependencies": {
107
108
  "@eslint/json": ">=1.0.0",