@projectwallace/css-analyzer 9.5.0 → 9.6.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.
@@ -0,0 +1,107 @@
1
+ import { t as KeywordSet } from "./keyword-set-qSyAMR9o.js";
2
+ import { is_custom, is_vendor_prefixed } from "@projectwallace/css-parser";
3
+ //#region src/properties/property-utils.ts
4
+ const shorthand_properties = new Set([
5
+ "all",
6
+ "animation",
7
+ "background",
8
+ "border",
9
+ "border-block-end",
10
+ "border-block-start",
11
+ "border-bottom",
12
+ "border-color",
13
+ "border-image",
14
+ "border-inline-end",
15
+ "border-inline-start",
16
+ "border-left",
17
+ "border-radius",
18
+ "border-right",
19
+ "border-style",
20
+ "border-top",
21
+ "border-width",
22
+ "column-rule",
23
+ "columns",
24
+ "contain-intrinsic-size",
25
+ "flex",
26
+ "flex-flow",
27
+ "font",
28
+ "gap",
29
+ "grid",
30
+ "grid-area",
31
+ "grid-column",
32
+ "grid-row",
33
+ "grid-template",
34
+ "inset",
35
+ "list-style",
36
+ "margin",
37
+ "mask",
38
+ "offset",
39
+ "outline",
40
+ "overflow",
41
+ "padding",
42
+ "place-content",
43
+ "place-items",
44
+ "place-self",
45
+ "scroll-margin",
46
+ "scroll-padding",
47
+ "scroll-timeline",
48
+ "text-decoration",
49
+ "text-emphasis",
50
+ "transition",
51
+ "vertical-align"
52
+ ]);
53
+ const SPACING_RESET_PROPERTIES = new Set([
54
+ "margin",
55
+ "margin-block",
56
+ "margin-inline",
57
+ "margin-top",
58
+ "margin-block-start",
59
+ "margin-block-end",
60
+ "margin-inline-end",
61
+ "margin-inline-end",
62
+ "margin-right",
63
+ "margin-bottom",
64
+ "margin-left",
65
+ "padding",
66
+ "padding-block",
67
+ "padding-inline",
68
+ "padding-top",
69
+ "padding-right",
70
+ "padding-bottom",
71
+ "padding-left",
72
+ "padding-block-start",
73
+ "padding-block-end",
74
+ "padding-inline-start",
75
+ "padding-inline-end"
76
+ ]);
77
+ const border_radius_properties = new KeywordSet([
78
+ "border-radius",
79
+ "border-top-left-radius",
80
+ "border-top-right-radius",
81
+ "border-bottom-right-radius",
82
+ "border-bottom-left-radius",
83
+ "border-start-start-radius",
84
+ "border-start-end-radius",
85
+ "border-end-end-radius",
86
+ "border-end-start-radius"
87
+ ]);
88
+ /**
89
+ * @see https://github.com/csstree/csstree/blob/master/lib/utils/names.js#L69
90
+ */
91
+ function isHack(property) {
92
+ if (is_custom(property) || is_vendor_prefixed(property)) return false;
93
+ let code = property.charCodeAt(0);
94
+ return code === 47 || code === 42 || code === 95 || code === 43 || code === 38 || code === 36 || code === 35;
95
+ }
96
+ /**
97
+ * Get the normalized basename for a property with a vendor prefix
98
+ * @returns The property name without vendor prefix
99
+ */
100
+ function basename(property) {
101
+ if (is_custom(property)) return property;
102
+ if (is_vendor_prefixed(property)) return property.slice(property.indexOf("-", 2) + 1).toLowerCase();
103
+ if (isHack(property)) return property.slice(1).toLowerCase();
104
+ return property.toLowerCase();
105
+ }
106
+ //#endregion
107
+ export { shorthand_properties as a, isHack as i, basename as n, border_radius_properties as r, SPACING_RESET_PROPERTIES as t };
@@ -0,0 +1,17 @@
1
+ import { t as KeywordSet } from "./keyword-set-BXSoLQ6m.js";
2
+
3
+ //#region src/properties/property-utils.d.ts
4
+ declare const shorthand_properties: Set<string>;
5
+ declare const SPACING_RESET_PROPERTIES: Set<string>;
6
+ declare const border_radius_properties: KeywordSet;
7
+ /**
8
+ * @see https://github.com/csstree/csstree/blob/master/lib/utils/names.js#L69
9
+ */
10
+ declare function isHack(property: string): boolean;
11
+ /**
12
+ * Get the normalized basename for a property with a vendor prefix
13
+ * @returns The property name without vendor prefix
14
+ */
15
+ declare function basename(property: string): string;
16
+ //#endregion
17
+ export { shorthand_properties as a, isHack as i, basename as n, border_radius_properties as r, SPACING_RESET_PROPERTIES as t };
@@ -1,2 +1,2 @@
1
- import { a as calculate, i as isPrefixed, n as getComplexity, o as calculateForAST, r as isAccessibility, s as compare, t as getCombinators } from "../utils-BUeYqEL1.js";
1
+ import { a as calculate, i as isPrefixed, n as getComplexity, o as calculateForAST, r as isAccessibility, s as compare, t as getCombinators } from "../utils-yIUD7vGy.js";
2
2
  export { compare as compareSpecificity, getCombinators, getComplexity, calculate as getSpecificity, calculateForAST as getSpecificityForAST, isAccessibility, isPrefixed };
@@ -1,3 +1,2 @@
1
- import "../string-utils-olNNcOlY.js";
2
- import { a as getComplexity, i as getCombinators, n as calculateForAST, o as isAccessibility, r as compare, s as isPrefixed, t as calculate } from "../specificity-svLpcKkT.js";
1
+ import { a as getComplexity, i as getCombinators, n as calculateForAST, o as isAccessibility, r as compare, s as isPrefixed, t as calculate } from "../specificity-DKo9lvB5.js";
3
2
  export { compare as compareSpecificity, getCombinators, getComplexity, calculate as getSpecificity, calculateForAST as getSpecificityForAST, isAccessibility, isPrefixed };
@@ -1,5 +1,6 @@
1
- import { n as unquote, r as KeywordSet } from "./string-utils-olNNcOlY.js";
2
- import { ATTRIBUTE_SELECTOR, CLASS_SELECTOR, COMBINATOR, ID_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, SKIP, TYPE_SELECTOR, str_equals, str_starts_with, walk } from "@projectwallace/css-parser";
1
+ import { t as KeywordSet } from "./keyword-set-qSyAMR9o.js";
2
+ import { n as unquote } from "./string-utils-C97yyuqE.js";
3
+ import { ATTRIBUTE_SELECTOR, CLASS_SELECTOR, ID_SELECTOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SKIP, TYPE_SELECTOR, is_attribute_selector, is_combinator, is_nth_of_selector, is_nth_selector, is_pseudo_class_selector, is_pseudo_element_selector, is_selector, is_selector_list, is_type_selector, str_equals, str_starts_with, walk } from "@projectwallace/css-parser";
3
4
  import { parse_selector } from "@projectwallace/css-parser/parse-selector";
4
5
  //#region src/selectors/utils.ts
5
6
  const PSEUDO_FUNCTIONS = new KeywordSet([
@@ -15,12 +16,12 @@ const PSEUDO_FUNCTIONS = new KeywordSet([
15
16
  ]);
16
17
  function isPrefixed(selector, on_selector) {
17
18
  walk(selector, function(node) {
18
- if (node.type === PSEUDO_ELEMENT_SELECTOR || node.type === PSEUDO_CLASS_SELECTOR || node.type === TYPE_SELECTOR) {
19
+ if (is_pseudo_element_selector(node) || is_pseudo_class_selector(node) || is_type_selector(node)) {
19
20
  if (node.is_vendor_prefixed) {
20
21
  let prefix = "";
21
- if (node.type === PSEUDO_CLASS_SELECTOR) prefix = ":";
22
- else if (node.type === PSEUDO_ELEMENT_SELECTOR) prefix = "::";
23
- on_selector(prefix + (node.name || node.text));
22
+ if (is_pseudo_class_selector(node)) prefix = ":";
23
+ else if (is_pseudo_element_selector(node)) prefix = "::";
24
+ on_selector(prefix + node.name);
24
25
  }
25
26
  }
26
27
  });
@@ -35,7 +36,7 @@ function isAccessibility(selector, on_selector) {
35
36
  return "[" + clone.name?.toLowerCase() + "]";
36
37
  }
37
38
  walk(selector, function(node) {
38
- if (node.type === ATTRIBUTE_SELECTOR) {
39
+ if (is_attribute_selector(node)) {
39
40
  const name = node.name || "";
40
41
  if (str_equals("role", name) || str_starts_with(name, "aria-")) on_selector(normalize(node));
41
42
  }
@@ -48,31 +49,31 @@ function isAccessibility(selector, on_selector) {
48
49
  */
49
50
  function getComplexity(selector) {
50
51
  let complexity = 0;
51
- function findSelectors(node, complexities) {
52
- walk(node, function(n) {
53
- if (n.type === SELECTOR) complexities.push(getComplexity(n));
52
+ function findSelectors(tree, complexities) {
53
+ walk(tree, function(node) {
54
+ if (is_selector(node)) complexities.push(getComplexity(node));
54
55
  });
55
56
  }
56
57
  walk(selector, function(node) {
57
- const type = node.type;
58
- if (type === SELECTOR) return;
59
- if (type === NTH_SELECTOR) {
60
- if (node.text && node.text.trim()) complexity++;
58
+ node.type;
59
+ if (is_selector(node)) return;
60
+ if (is_nth_selector(node)) {
61
+ if (node.text.trim()) complexity++;
61
62
  return;
62
63
  }
63
64
  complexity++;
64
- if (type === PSEUDO_ELEMENT_SELECTOR || type === TYPE_SELECTOR || type === PSEUDO_CLASS_SELECTOR) {
65
+ if (is_pseudo_class_selector(node) || is_type_selector(node) || is_pseudo_element_selector(node)) {
65
66
  if (node.is_vendor_prefixed) complexity++;
66
67
  }
67
- if (type === ATTRIBUTE_SELECTOR) {
68
+ if (is_attribute_selector(node)) {
68
69
  if (node.value) complexity++;
69
70
  return SKIP;
70
71
  }
71
- if (type === PSEUDO_CLASS_SELECTOR) {
72
- const name = node.name || "";
72
+ if (is_pseudo_class_selector(node)) {
73
+ const name = node.name;
73
74
  if (PSEUDO_FUNCTIONS.has(name.toLowerCase())) {
74
75
  const childComplexities = [];
75
- if (node.has_children) for (const child of node) if (child.type === SELECTOR) childComplexities.push(getComplexity(child));
76
+ if (node.has_children) for (const child of node) if (is_selector(child)) childComplexities.push(getComplexity(child));
76
77
  else findSelectors(child, childComplexities);
77
78
  if (childComplexities.length > 0) {
78
79
  for (const c of childComplexities) complexity += c;
@@ -88,8 +89,8 @@ function getComplexity(selector) {
88
89
  */
89
90
  function getCombinators(selector, onMatch) {
90
91
  walk(selector, function(node) {
91
- if (node.type === COMBINATOR) onMatch({
92
- name: node.name?.trim() === "" ? " " : node.name,
92
+ if (is_combinator(node)) onMatch({
93
+ name: node.name.trim() === "" ? " " : node.name,
93
94
  loc: {
94
95
  offset: node.start,
95
96
  line: node.line,
@@ -129,7 +130,7 @@ const calculateForAST = (selectorAST) => {
129
130
  b += 1;
130
131
  break;
131
132
  case PSEUDO_CLASS_SELECTOR:
132
- switch (current.name?.toLowerCase()) {
133
+ switch (current.name.toLowerCase()) {
133
134
  case "where": break;
134
135
  case "-webkit-any":
135
136
  case "any":
@@ -142,7 +143,7 @@ const calculateForAST = (selectorAST) => {
142
143
  case "has":
143
144
  if (current.has_children) {
144
145
  const childSelectorList = current.first_child;
145
- if (childSelectorList?.type === SELECTOR_LIST) {
146
+ if (is_selector_list(childSelectorList)) {
146
147
  const max1 = max(calculate(childSelectorList));
147
148
  a += max1[0];
148
149
  b += max1[1];
@@ -154,7 +155,7 @@ const calculateForAST = (selectorAST) => {
154
155
  case "nth-last-child":
155
156
  b += 1;
156
157
  const nthOf = current.first_child;
157
- if (nthOf?.type === NTH_OF_SELECTOR && nthOf.selector) {
158
+ if (is_nth_of_selector(nthOf) && nthOf.selector) {
158
159
  const max2 = max(calculate(nthOf.selector));
159
160
  a += max2[0];
160
161
  b += max2[1];
@@ -164,11 +165,11 @@ const calculateForAST = (selectorAST) => {
164
165
  case "host-context":
165
166
  case "host":
166
167
  b += 1;
167
- const childSelector = current.first_child?.first_child;
168
- if (childSelector?.type === SELECTOR) {
168
+ const childSelector = current.first_child.first_child;
169
+ if (childSelector && is_selector(childSelector)) {
169
170
  let childPart = childSelector.first_child;
170
171
  while (childPart) {
171
- if (childPart.type === COMBINATOR) break;
172
+ if (is_combinator(childPart)) break;
172
173
  const partSpecificity = calculateForAST({
173
174
  type_name: "Selector",
174
175
  first_child: childPart,
@@ -193,14 +194,14 @@ const calculateForAST = (selectorAST) => {
193
194
  }
194
195
  break;
195
196
  case PSEUDO_ELEMENT_SELECTOR:
196
- switch (current.name?.toLowerCase()) {
197
+ switch (current.name.toLowerCase()) {
197
198
  case "slotted":
198
199
  c += 1;
199
- const childSelector = current.first_child?.first_child;
200
- if (childSelector?.type === SELECTOR) {
200
+ const childSelector = current.first_child.first_child;
201
+ if (is_selector(childSelector)) {
201
202
  let childPart = childSelector.first_child;
202
203
  while (childPart) {
203
- if (childPart.type === COMBINATOR) break;
204
+ if (is_combinator(childPart)) break;
204
205
  const partSpecificity = calculateForAST({
205
206
  type_name: "Selector",
206
207
  first_child: childPart,
@@ -248,20 +249,16 @@ const convertToAST = (source) => {
248
249
  throw new TypeError(`Could not convert passed in source '${source}' to SelectorList: ${message}`);
249
250
  }
250
251
  if (source instanceof Object) {
251
- if (source.type === SELECTOR_LIST) return source;
252
+ if (is_selector_list(source)) return source;
252
253
  throw new TypeError(`Passed in source is an Object but no AST / AST of the type SelectorList`);
253
254
  }
254
255
  throw new TypeError(`Passed in source is not a String nor an Object. I don't know what to do with it.`);
255
256
  };
256
257
  const calculate = (selector) => {
257
258
  if (!selector) return [];
258
- const ast = convertToAST(selector);
259
+ const selector_list = convertToAST(selector);
259
260
  const specificities = [];
260
- let selectorNode = ast.first_child;
261
- while (selectorNode) {
262
- specificities.push(calculateForAST(selectorNode));
263
- selectorNode = selectorNode.next_sibling;
264
- }
261
+ for (const selector_node of selector_list) specificities.push(calculateForAST(selector_node));
265
262
  return specificities;
266
263
  };
267
264
  //#endregion
@@ -1,19 +1,4 @@
1
1
  import "@projectwallace/css-parser";
2
- //#region src/keyword-set.ts
3
- /**
4
- * @description A Set-like construct to search CSS keywords in a case-insensitive way
5
- */
6
- var KeywordSet = class {
7
- set;
8
- constructor(items) {
9
- /** @type {Set<string>} */
10
- this.set = new Set(items);
11
- }
12
- has(item) {
13
- return this.set.has(item.toLowerCase());
14
- }
15
- };
16
- //#endregion
17
2
  //#region src/string-utils.ts
18
3
  function unquote(str) {
19
4
  return str.replaceAll(/(?:^['"])|(?:['"]$)/g, "");
@@ -46,4 +31,4 @@ function endsWith(base, maybe) {
46
31
  return true;
47
32
  }
48
33
  //#endregion
49
- export { unquote as n, KeywordSet as r, endsWith as t };
34
+ export { unquote as n, endsWith as t };
@@ -1,4 +1,4 @@
1
- import { CSSNode } from "@projectwallace/css-parser";
1
+ import { CSSNode, Selector } from "@projectwallace/css-parser";
2
2
 
3
3
  //#region src/collection.d.ts
4
4
  type Location = {
@@ -25,7 +25,7 @@ type Specificity = [number, number, number];
25
25
  * @returns 0 if s1 equals s2, a negative number if s1 is lower than s2, or a positive number if s1 higher than s2
26
26
  */
27
27
  declare function compare(s1: Specificity, s2: Specificity): number;
28
- declare const calculateForAST: (selectorAST: CSSNode) => Specificity;
28
+ declare const calculateForAST: (selectorAST: Selector) => Specificity;
29
29
  declare const calculate: (selector: string | CSSNode) => Specificity[];
30
30
  //#endregion
31
31
  //#region src/selectors/utils.d.ts
@@ -1,7 +1,29 @@
1
- import { a as namedColors, i as colorKeywords, n as keywords, o as systemColors, r as colorFunctions, t as isValueReset } from "../values-Dw53soqy.js";
2
- import { CSSNode } from "@projectwallace/css-parser";
1
+ import { a as namedColors, i as colorKeywords, n as keywords, o as systemColors, r as colorFunctions, t as isValueReset } from "../values-DgD2lJqZ.js";
2
+ import { Value } from "@projectwallace/css-parser";
3
3
 
4
4
  //#region src/values/browserhacks.d.ts
5
- declare function isIe9Hack(node: CSSNode): boolean;
5
+ declare function isIe9Hack(node: Value): boolean;
6
6
  //#endregion
7
- export { colorFunctions, colorKeywords, isIe9Hack, isValueReset, keywords, namedColors, systemColors };
7
+ //#region src/values/destructure-font-shorthand.d.ts
8
+ /**
9
+ * Parse the CSS `font` shorthand value and extract its structural components.
10
+ *
11
+ * Grammar:
12
+ * font: [<font-style> || <font-variant> || <font-weight> || <font-stretch>]?
13
+ * <font-size>[/<line-height>]? <font-family>
14
+ *
15
+ * Does NOT handle system fonts (caption, icon, menu, …) — the caller should
16
+ * check SYSTEM_FONTS before calling this function.
17
+ *
18
+ * Returns null when the value is a single var() and can't be decomposed.
19
+ *
20
+ * @param value - The VALUE CSSNode for a `font` declaration
21
+ * @param cb - Called for every global CSS keyword found in the value (e.g. inherit)
22
+ */
23
+ declare function destructure(value: Value, cb: (keyword: string) => void): {
24
+ font_size?: string;
25
+ line_height?: string;
26
+ font_family?: string | null;
27
+ } | null;
28
+ //#endregion
29
+ export { colorFunctions, colorKeywords, destructure as destructureFontShorthand, isIe9Hack, isValueReset, keywords, namedColors, systemColors };
@@ -1,3 +1,2 @@
1
- import "../string-utils-olNNcOlY.js";
2
- import { a as colorKeywords, i as colorFunctions, n as isValueReset, o as namedColors, r as keywords, s as systemColors, t as isIe9Hack } from "../browserhacks-eP_e1D5u.js";
3
- export { colorFunctions, colorKeywords, isIe9Hack, isValueReset, keywords, namedColors, systemColors };
1
+ import { a as keywords, c as namedColors, i as isValueReset, l as systemColors, o as colorFunctions, r as destructure, s as colorKeywords, t as isIe9Hack } from "../browserhacks-BtVwVqdU.js";
2
+ export { colorFunctions, colorKeywords, destructure as destructureFontShorthand, isIe9Hack, isValueReset, keywords, namedColors, systemColors };
@@ -0,0 +1,17 @@
1
+ import { t as KeywordSet } from "./keyword-set-BXSoLQ6m.js";
2
+ import { Value } from "@projectwallace/css-parser";
3
+
4
+ //#region src/values/colors.d.ts
5
+ declare const namedColors: KeywordSet;
6
+ declare const systemColors: KeywordSet;
7
+ declare const colorFunctions: KeywordSet;
8
+ declare const colorKeywords: KeywordSet;
9
+ //#endregion
10
+ //#region src/values/values.d.ts
11
+ declare const keywords: KeywordSet;
12
+ /**
13
+ * Test whether a value is a reset (0, 0px, -0.0e0 etc.)
14
+ */
15
+ declare function isValueReset(value: Value): boolean;
16
+ //#endregion
17
+ export { namedColors as a, colorKeywords as i, keywords as n, systemColors as o, colorFunctions as r, isValueReset as t };
package/package.json CHANGED
@@ -1,19 +1,36 @@
1
1
  {
2
2
  "name": "@projectwallace/css-analyzer",
3
+ "version": "9.6.2",
3
4
  "description": "The best CSS analyzer out there. Check design tokens, complexity, specificity, performance and more.",
4
- "version": "9.5.0",
5
+ "keywords": [
6
+ "analytics",
7
+ "code",
8
+ "colors",
9
+ "css",
10
+ "designsystem",
11
+ "fonts",
12
+ "metrics",
13
+ "performance",
14
+ "projectwallace",
15
+ "quality",
16
+ "statistics",
17
+ "stats",
18
+ "styleguide",
19
+ "stylesheet",
20
+ "wallace"
21
+ ],
22
+ "homepage": "https://github.com/projectwallace/css-analyzer",
23
+ "bugs": "https://github.com/projectwallace/css-analyzer/issues",
24
+ "license": "MIT",
5
25
  "author": "Bart Veneman",
6
26
  "repository": {
7
27
  "type": "git",
8
28
  "url": "git+https://github.com/projectwallace/css-analyzer.git"
9
29
  },
10
- "homepage": "https://github.com/projectwallace/css-analyzer",
11
- "issues": "https://github.com/projectwallace/css-analyzer/issues",
12
- "license": "MIT",
13
- "type": "module",
14
30
  "files": [
15
31
  "dist"
16
32
  ],
33
+ "type": "module",
17
34
  "main": "./dist/index.js",
18
35
  "types": "./dist/index.d.ts",
19
36
  "exports": {
@@ -32,48 +49,35 @@
32
49
  "./values": {
33
50
  "types": "./dist/values/index.d.ts",
34
51
  "default": "./dist/values/index.js"
52
+ },
53
+ "./properties": {
54
+ "types": "./dist/properties/index.d.ts",
55
+ "default": "./dist/properties/index.js"
35
56
  }
36
57
  },
37
- "engines": {
38
- "node": ">=18.0.0"
39
- },
40
58
  "scripts": {
41
- "lint": "oxlint --config .oxlintrc.json",
59
+ "lint": "oxlint --config .oxlintrc.json; oxfmt --check",
42
60
  "lint-package": "publint",
43
61
  "test": "vitest --coverage",
44
62
  "check": "tsc --noEmit",
45
63
  "build": "tsdown",
46
64
  "knip": "knip"
47
65
  },
48
- "keywords": [
49
- "projectwallace",
50
- "wallace",
51
- "css",
52
- "stylesheet",
53
- "stats",
54
- "statistics",
55
- "analytics",
56
- "performance",
57
- "styleguide",
58
- "metrics",
59
- "designsystem",
60
- "fonts",
61
- "colors",
62
- "quality",
63
- "code"
64
- ],
65
66
  "dependencies": {
66
- "@projectwallace/css-parser": "^0.13.8"
67
+ "@projectwallace/css-parser": "^0.14.8"
67
68
  },
68
69
  "devDependencies": {
69
70
  "@codecov/rollup-plugin": "^1.9.1",
70
- "@vitest/coverage-v8": "^4.0.18",
71
- "knip": "^6.0.4",
72
- "oxlint": "^1.43.0",
73
- "prettier": "^3.6.2",
74
- "publint": "^0.3.17",
75
- "tsdown": "^0.21.0",
76
- "typescript": "^5.9.3",
77
- "vitest": "^4.0.18"
71
+ "@vitest/coverage-v8": "^4.1.2",
72
+ "knip": "^6.3.0",
73
+ "oxfmt": "^0.43.0",
74
+ "oxlint": "^1.58.0",
75
+ "publint": "^0.3.18",
76
+ "tsdown": "^0.21.7",
77
+ "typescript": "^6.0.2",
78
+ "vitest": "^4.1.2"
79
+ },
80
+ "engines": {
81
+ "node": ">=18.0.0"
78
82
  }
79
83
  }