eslint-plugin-svg 0.0.4 → 0.0.5

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
@@ -53,6 +53,11 @@ export default defineConfig([
53
53
  | [no-empty-desc](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-desc) | Disallow empty desc element | ✅ | | |
54
54
  | [no-empty-text](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-text) | Disallow empty text element | ✅ | | |
55
55
  | [no-empty-container](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-container) | Disallow empty container element | ✅ | | |
56
+ | [no-empty-groups](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-groups) | Disallow empty g element | ✅ | | |
57
+ | [no-inline-styles](https://eslint-plugin-svg.ntnyq.com/rules/no-inline-styles) | Disallow inline style attribute | ✅ | | |
58
+ | [no-event-handlers](https://eslint-plugin-svg.ntnyq.com/rules/no-event-handlers) | Disallow inline event handlers | ✅ | | |
59
+ | [no-script-tags](https://eslint-plugin-svg.ntnyq.com/rules/no-script-tags) | Disallow script elements | ✅ | | |
60
+ | [require-viewbox](https://eslint-plugin-svg.ntnyq.com/rules/require-viewbox) | Require viewBox on svg elements | ✅ | | |
56
61
  | [no-deprecated](https://eslint-plugin-svg.ntnyq.com/rules/no-deprecated) | Disallow deprecated elements | ✅ | | |
57
62
  | [no-elements](https://eslint-plugin-svg.ntnyq.com/rules/no-elements) | Disallow elements by name | | | |
58
63
  | [no-doctype](https://eslint-plugin-svg.ntnyq.com/rules/no-doctype) | Disallow doctype | ✅ | 🔧 | |
package/dist/index.d.ts CHANGED
@@ -165,23 +165,28 @@ type SVGSettings = {
165
165
  };
166
166
  //#endregion
167
167
  //#region src/rules/no-deprecated.d.ts
168
- type Options$3 = [{
168
+ type Options$4 = [{
169
169
  allowElements?: string[];
170
170
  }];
171
171
  //#endregion
172
172
  //#region src/rules/no-elements.d.ts
173
- type Options$2 = [{
173
+ type Options$3 = [{
174
174
  elements?: string[];
175
175
  }];
176
176
  //#endregion
177
177
  //#region src/rules/no-empty-container.d.ts
178
- type Options$1 = [{
178
+ type Options$2 = [{
179
179
  elements?: string[];
180
180
  ignores?: string[];
181
181
  ignoreComments?: boolean;
182
182
  ignoreWhitespace?: boolean;
183
183
  }];
184
184
  //#endregion
185
+ //#region src/rules/no-event-handlers.d.ts
186
+ type Options$1 = [{
187
+ ignores?: string[];
188
+ }];
189
+ //#endregion
185
190
  //#region src/rules/no-invalid-role.d.ts
186
191
  type Options = [{
187
192
  roles?: string[];
@@ -189,14 +194,19 @@ type Options = [{
189
194
  //#endregion
190
195
  //#region src/rules/index.d.ts
191
196
  declare const rules: {
192
- 'no-deprecated': RuleModule<"deprecatedElement", Options$3, unknown>;
197
+ 'no-deprecated': RuleModule<"deprecatedElement", Options$4, unknown>;
193
198
  'no-doctype': RuleModule<"invalid", [], unknown>;
194
- 'no-elements': RuleModule<"invalid", Options$2, unknown>;
195
- 'no-empty-container': RuleModule<"invalid", Options$1, unknown>;
199
+ 'no-elements': RuleModule<"invalid", Options$3, unknown>;
200
+ 'no-empty-container': RuleModule<"invalid", Options$2, unknown>;
196
201
  'no-empty-desc': RuleModule<"invalid", [], unknown>;
202
+ 'no-empty-groups': RuleModule<"invalid", [], unknown>;
197
203
  'no-empty-text': RuleModule<"invalid", [], unknown>;
198
204
  'no-empty-title': RuleModule<"invalid", [], unknown>;
205
+ 'no-event-handlers': RuleModule<"invalid", Options$1, unknown>;
206
+ 'no-inline-styles': RuleModule<"invalid", [], unknown>;
199
207
  'no-invalid-role': RuleModule<"invalid", Options, unknown>;
208
+ 'no-script-tags': RuleModule<"invalid", [], unknown>;
209
+ 'require-viewbox': RuleModule<"missing", [], unknown>;
200
210
  };
201
211
  //#endregion
202
212
  //#region src/types/plugin.d.ts
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as parserSVG from "svg-eslint-parser";
2
2
  import "uncase";
3
- import { isNonEmptyString } from "@ntnyq/utils";
3
+ import { isNonEmptyString, toArray } from "@ntnyq/utils";
4
4
 
5
5
  //#region src/configs/index.ts
6
6
  /**
@@ -18,9 +18,14 @@ const recommended = [{
18
18
  "svg/no-doctype": "error",
19
19
  "svg/no-empty-container": "error",
20
20
  "svg/no-empty-desc": "error",
21
+ "svg/no-empty-groups": "error",
21
22
  "svg/no-empty-text": "error",
22
23
  "svg/no-empty-title": "error",
23
- "svg/no-invalid-role": "error"
24
+ "svg/no-event-handlers": "error",
25
+ "svg/no-inline-styles": "error",
26
+ "svg/no-invalid-role": "error",
27
+ "svg/no-script-tags": "error",
28
+ "svg/require-viewbox": "error"
24
29
  }
25
30
  }];
26
31
  const configs = { recommended };
@@ -28,7 +33,7 @@ const configs = { recommended };
28
33
  //#endregion
29
34
  //#region package.json
30
35
  var name = "eslint-plugin-svg";
31
- var version = "0.0.4";
36
+ var version = "0.0.5";
32
37
 
33
38
  //#endregion
34
39
  //#region src/meta.ts
@@ -264,21 +269,21 @@ function deepMerge(first = {}, second = {}) {
264
269
 
265
270
  //#endregion
266
271
  //#region src/utils/createRule.ts
267
- function createRule({ create, defaultOptions: defaultOptions$4, meta: meta$1 }) {
272
+ function createRule({ create, defaultOptions: defaultOptions$5, meta: meta$1 }) {
268
273
  return {
269
274
  create: ((context) => {
270
- const optionsCount = Math.max(context.options.length, defaultOptions$4.length);
275
+ const optionsCount = Math.max(context.options.length, defaultOptions$5.length);
271
276
  return create(context, Array.from({ length: optionsCount }, (_, i) => {
272
277
  /* v8 ignore start */
273
- if (isObjectNotArray(context.options[i]) && isObjectNotArray(defaultOptions$4[i])) return deepMerge(defaultOptions$4[i], context.options[i]);
274
- return context.options[i] ?? defaultOptions$4[i];
278
+ if (isObjectNotArray(context.options[i]) && isObjectNotArray(defaultOptions$5[i])) return deepMerge(defaultOptions$5[i], context.options[i]);
279
+ return context.options[i] ?? defaultOptions$5[i];
275
280
  /* v8 ignore stop */
276
281
  }));
277
282
  }),
278
- defaultOptions: defaultOptions$4,
283
+ defaultOptions: defaultOptions$5,
279
284
  meta: {
280
285
  ...meta$1,
281
- defaultOptions: defaultOptions$4
286
+ defaultOptions: defaultOptions$5
282
287
  }
283
288
  };
284
289
  }
@@ -307,17 +312,17 @@ const createESLintRule = RuleCreator((ruleName) => `https://eslint-plugin-svg.nt
307
312
  * @param defaultOptions - default options
308
313
  * @returns - resolved options
309
314
  */
310
- function resolveOptions(options, defaultOptions$4) {
315
+ function resolveOptions(options, defaultOptions$5) {
311
316
  /* v8 ignore next guard by eslint */
312
- return options?.[0] || defaultOptions$4;
317
+ return options?.[0] || defaultOptions$5;
313
318
  }
314
319
 
315
320
  //#endregion
316
321
  //#region src/rules/no-deprecated.ts
317
- const RULE_NAME$7 = "no-deprecated";
318
- const defaultOptions$3 = {};
322
+ const RULE_NAME$12 = "no-deprecated";
323
+ const defaultOptions$4 = {};
319
324
  var no_deprecated_default = createESLintRule({
320
- name: RULE_NAME$7,
325
+ name: RULE_NAME$12,
321
326
  meta: {
322
327
  type: "suggestion",
323
328
  docs: {
@@ -335,9 +340,9 @@ var no_deprecated_default = createESLintRule({
335
340
  }],
336
341
  messages: { deprecatedElement: `Element '{{name}}' is deprecated` }
337
342
  },
338
- defaultOptions: [defaultOptions$3],
343
+ defaultOptions: [defaultOptions$4],
339
344
  create(context) {
340
- const { allowElements = [] } = resolveOptions(context.options, defaultOptions$3);
345
+ const { allowElements = [] } = resolveOptions(context.options, defaultOptions$4);
341
346
  const deprecatedElements = new Set(DEPRECATED_ELEMENTS.filter((element) => !allowElements.includes(element)));
342
347
  return { Tag(node) {
343
348
  if (deprecatedElements.has(node.name)) context.report({
@@ -351,9 +356,9 @@ var no_deprecated_default = createESLintRule({
351
356
 
352
357
  //#endregion
353
358
  //#region src/rules/no-doctype.ts
354
- const RULE_NAME$6 = "no-doctype";
359
+ const RULE_NAME$11 = "no-doctype";
355
360
  var no_doctype_default = createESLintRule({
356
- name: RULE_NAME$6,
361
+ name: RULE_NAME$11,
357
362
  meta: {
358
363
  type: "suggestion",
359
364
  docs: {
@@ -378,10 +383,10 @@ var no_doctype_default = createESLintRule({
378
383
 
379
384
  //#endregion
380
385
  //#region src/rules/no-elements.ts
381
- const RULE_NAME$5 = "no-elements";
382
- const defaultOptions$2 = {};
386
+ const RULE_NAME$10 = "no-elements";
387
+ const defaultOptions$3 = {};
383
388
  var no_elements_default = createESLintRule({
384
- name: RULE_NAME$5,
389
+ name: RULE_NAME$10,
385
390
  meta: {
386
391
  type: "suggestion",
387
392
  docs: {
@@ -399,9 +404,9 @@ var no_elements_default = createESLintRule({
399
404
  }],
400
405
  messages: { invalid: `Element '{{name}}' is not allowed` }
401
406
  },
402
- defaultOptions: [defaultOptions$2],
407
+ defaultOptions: [defaultOptions$3],
403
408
  create(context) {
404
- const { elements = [] } = resolveOptions(context.options, defaultOptions$2);
409
+ const { elements = [] } = resolveOptions(context.options, defaultOptions$3);
405
410
  if (!elements.length) return {};
406
411
  return { Tag(node) {
407
412
  if (elements.includes(node.name)) context.report({
@@ -415,10 +420,10 @@ var no_elements_default = createESLintRule({
415
420
 
416
421
  //#endregion
417
422
  //#region src/rules/no-empty-container.ts
418
- const RULE_NAME$4 = "no-empty-container";
419
- const defaultOptions$1 = {};
423
+ const RULE_NAME$9 = "no-empty-container";
424
+ const defaultOptions$2 = {};
420
425
  var no_empty_container_default = createESLintRule({
421
- name: RULE_NAME$4,
426
+ name: RULE_NAME$9,
422
427
  meta: {
423
428
  type: "suggestion",
424
429
  docs: {
@@ -453,9 +458,9 @@ var no_empty_container_default = createESLintRule({
453
458
  }],
454
459
  messages: { invalid: `Container element '{{name}}' must not be empty` }
455
460
  },
456
- defaultOptions: [defaultOptions$1],
461
+ defaultOptions: [defaultOptions$2],
457
462
  create(context) {
458
- const { elements = [], ignores = [], ignoreComments = true, ignoreWhitespace = true } = resolveOptions(context.options, defaultOptions$1);
463
+ const { elements = [], ignores = [], ignoreComments = true, ignoreWhitespace = true } = resolveOptions(context.options, defaultOptions$2);
459
464
  const containerElements = new Set([...CONTAINER_ELEMENTS, ...elements].filter((v) => !ignores.includes(v)));
460
465
  return { Tag(node) {
461
466
  if (!containerElements.has(node.name)) return;
@@ -470,9 +475,9 @@ var no_empty_container_default = createESLintRule({
470
475
 
471
476
  //#endregion
472
477
  //#region src/rules/no-empty-desc.ts
473
- const RULE_NAME$3 = "no-empty-desc";
478
+ const RULE_NAME$8 = "no-empty-desc";
474
479
  var no_empty_desc_default = createESLintRule({
475
- name: RULE_NAME$3,
480
+ name: RULE_NAME$8,
476
481
  meta: {
477
482
  type: "suggestion",
478
483
  docs: {
@@ -486,13 +491,36 @@ var no_empty_desc_default = createESLintRule({
486
491
  create(context) {
487
492
  return { Tag(node) {
488
493
  if (node.name !== "desc") return;
489
- const textNode = node.children.find((n) => n.type === "Text");
490
- if (textNode) {
491
- if (!textNode.value.trim()) return context.report({
492
- node,
493
- messageId: "invalid"
494
- });
495
- } else return context.report({
494
+ if (!node.children || node.children.length === 0 || node.children.every((child) => child.type === "Text" && !child.value.trim())) return context.report({
495
+ node,
496
+ messageId: "invalid"
497
+ });
498
+ } };
499
+ }
500
+ });
501
+
502
+ //#endregion
503
+ //#region src/rules/no-empty-groups.ts
504
+ const RULE_NAME$7 = "no-empty-groups";
505
+ var no_empty_groups_default = createESLintRule({
506
+ name: RULE_NAME$7,
507
+ meta: {
508
+ type: "suggestion",
509
+ docs: {
510
+ description: "disallow empty group element",
511
+ recommended: true
512
+ },
513
+ schema: [],
514
+ messages: { invalid: `Group element 'g' must not be empty` }
515
+ },
516
+ defaultOptions: [],
517
+ create(context) {
518
+ return { Tag(node) {
519
+ if (node.name !== "g") return;
520
+ if ((node.children ?? []).filter((child) => child.type !== "Comment").filter((child) => {
521
+ if (child.type !== "Text") return true;
522
+ return isNonEmptyString((child.value ?? "").trim());
523
+ }).length === 0) context.report({
496
524
  node,
497
525
  messageId: "invalid"
498
526
  });
@@ -502,9 +530,9 @@ var no_empty_desc_default = createESLintRule({
502
530
 
503
531
  //#endregion
504
532
  //#region src/rules/no-empty-text.ts
505
- const RULE_NAME$2 = "no-empty-text";
533
+ const RULE_NAME$6 = "no-empty-text";
506
534
  var no_empty_text_default = createESLintRule({
507
- name: RULE_NAME$2,
535
+ name: RULE_NAME$6,
508
536
  meta: {
509
537
  type: "suggestion",
510
538
  docs: {
@@ -518,13 +546,7 @@ var no_empty_text_default = createESLintRule({
518
546
  create(context) {
519
547
  return { Tag(node) {
520
548
  if (node.name !== "text") return;
521
- const textNode = node.children.find((n) => n.type === "Text");
522
- if (textNode) {
523
- if (!textNode.value.trim()) return context.report({
524
- node,
525
- messageId: "invalid"
526
- });
527
- } else return context.report({
549
+ if (!node.children || node.children.length === 0 || node.children.every((child) => child.type === "Text" && !child.value.trim())) return context.report({
528
550
  node,
529
551
  messageId: "invalid"
530
552
  });
@@ -534,9 +556,9 @@ var no_empty_text_default = createESLintRule({
534
556
 
535
557
  //#endregion
536
558
  //#region src/rules/no-empty-title.ts
537
- const RULE_NAME$1 = "no-empty-title";
559
+ const RULE_NAME$5 = "no-empty-title";
538
560
  var no_empty_title_default = createESLintRule({
539
- name: RULE_NAME$1,
561
+ name: RULE_NAME$5,
540
562
  meta: {
541
563
  type: "suggestion",
542
564
  docs: {
@@ -550,13 +572,7 @@ var no_empty_title_default = createESLintRule({
550
572
  create(context) {
551
573
  return { Tag(node) {
552
574
  if (node.name !== "title") return;
553
- const textNode = node.children.find((n) => n.type === "Text");
554
- if (textNode) {
555
- if (!textNode.value.trim()) return context.report({
556
- node,
557
- messageId: "invalid"
558
- });
559
- } else return context.report({
575
+ if (!node.children || node.children.length === 0 || node.children.every((child) => child.type === "Text" && !child.value.trim())) return context.report({
560
576
  node,
561
577
  messageId: "invalid"
562
578
  });
@@ -564,12 +580,80 @@ var no_empty_title_default = createESLintRule({
564
580
  }
565
581
  });
566
582
 
583
+ //#endregion
584
+ //#region src/rules/no-event-handlers.ts
585
+ const RULE_NAME$4 = "no-event-handlers";
586
+ const defaultOptions$1 = {};
587
+ var no_event_handlers_default = createESLintRule({
588
+ name: RULE_NAME$4,
589
+ meta: {
590
+ type: "suggestion",
591
+ docs: {
592
+ description: "disallow inline event handler attributes (e.g. onclick)",
593
+ recommended: true
594
+ },
595
+ schema: [{
596
+ type: "object",
597
+ properties: { ignores: {
598
+ type: "array",
599
+ description: "Event handler attribute names to ignore",
600
+ items: { type: "string" },
601
+ uniqueItems: true
602
+ } },
603
+ additionalProperties: false
604
+ }],
605
+ messages: { invalid: `Inline event handler '{{name}}' is not allowed` }
606
+ },
607
+ defaultOptions: [defaultOptions$1],
608
+ create(context) {
609
+ const { ignores = [] } = resolveOptions(context.options, defaultOptions$1);
610
+ const ignorePatterns = ignores.map((pattern) => new RegExp(pattern));
611
+ const isIgnored = (name$1) => ignorePatterns.some((pattern) => pattern.test(name$1));
612
+ return { Attribute(node) {
613
+ const name$1 = node.key.value;
614
+ if (!name$1 || !name$1.toLowerCase().startsWith("on")) return;
615
+ if (isIgnored(name$1)) return;
616
+ context.report({
617
+ node: node.value ?? node.key,
618
+ messageId: "invalid",
619
+ data: { name: name$1 }
620
+ });
621
+ } };
622
+ }
623
+ });
624
+
625
+ //#endregion
626
+ //#region src/rules/no-inline-styles.ts
627
+ const RULE_NAME$3 = "no-inline-styles";
628
+ var no_inline_styles_default = createESLintRule({
629
+ name: RULE_NAME$3,
630
+ meta: {
631
+ type: "suggestion",
632
+ docs: {
633
+ description: "disallow inline style attribute usage",
634
+ recommended: true
635
+ },
636
+ schema: [],
637
+ messages: { invalid: "Inline style attributes are not allowed" }
638
+ },
639
+ defaultOptions: [],
640
+ create(context) {
641
+ return { Attribute(node) {
642
+ if (node.key.value !== "style") return;
643
+ context.report({
644
+ node: node.value ?? node.key,
645
+ messageId: "invalid"
646
+ });
647
+ } };
648
+ }
649
+ });
650
+
567
651
  //#endregion
568
652
  //#region src/rules/no-invalid-role.ts
569
- const RULE_NAME = "no-invalid-role";
653
+ const RULE_NAME$2 = "no-invalid-role";
570
654
  const defaultOptions = {};
571
655
  var no_invalid_role_default = createESLintRule({
572
- name: RULE_NAME,
656
+ name: RULE_NAME$2,
573
657
  meta: {
574
658
  type: "suggestion",
575
659
  docs: {
@@ -606,6 +690,66 @@ var no_invalid_role_default = createESLintRule({
606
690
  }
607
691
  });
608
692
 
693
+ //#endregion
694
+ //#region src/rules/no-script-tags.ts
695
+ const RULE_NAME$1 = "no-script-tags";
696
+ var no_script_tags_default = createESLintRule({
697
+ name: RULE_NAME$1,
698
+ meta: {
699
+ type: "suggestion",
700
+ docs: {
701
+ description: "disallow usage of script elements",
702
+ recommended: true
703
+ },
704
+ schema: [],
705
+ messages: { invalid: `Script elements are not allowed in SVG` }
706
+ },
707
+ defaultOptions: [],
708
+ create(context) {
709
+ return { Tag(node) {
710
+ if (node.name !== "script") return;
711
+ context.report({
712
+ node,
713
+ messageId: "invalid"
714
+ });
715
+ } };
716
+ }
717
+ });
718
+
719
+ //#endregion
720
+ //#region src/rules/require-viewbox.ts
721
+ const RULE_NAME = "require-viewbox";
722
+ var require_viewbox_default = createESLintRule({
723
+ name: RULE_NAME,
724
+ meta: {
725
+ type: "suggestion",
726
+ docs: {
727
+ description: "require svg elements to include a non-empty viewBox attribute",
728
+ recommended: true
729
+ },
730
+ schema: [],
731
+ messages: { missing: `SVG element must include a non-empty viewBox attribute` }
732
+ },
733
+ defaultOptions: [],
734
+ create(context) {
735
+ return { Tag(node) {
736
+ if (node.name !== "svg") return;
737
+ const viewBoxAttr = toArray(node.attributes).find((attr) => attr.key.value === "viewBox");
738
+ if (!viewBoxAttr) {
739
+ context.report({
740
+ node,
741
+ messageId: "missing"
742
+ });
743
+ return;
744
+ }
745
+ if (!isNonEmptyString((viewBoxAttr.value?.value ?? "").trim())) context.report({
746
+ node: viewBoxAttr.value ?? viewBoxAttr.key,
747
+ messageId: "missing"
748
+ });
749
+ } };
750
+ }
751
+ });
752
+
609
753
  //#endregion
610
754
  //#region src/rules/index.ts
611
755
  const rules = {
@@ -614,9 +758,14 @@ const rules = {
614
758
  "no-elements": no_elements_default,
615
759
  "no-empty-container": no_empty_container_default,
616
760
  "no-empty-desc": no_empty_desc_default,
761
+ "no-empty-groups": no_empty_groups_default,
617
762
  "no-empty-text": no_empty_text_default,
618
763
  "no-empty-title": no_empty_title_default,
619
- "no-invalid-role": no_invalid_role_default
764
+ "no-event-handlers": no_event_handlers_default,
765
+ "no-inline-styles": no_inline_styles_default,
766
+ "no-invalid-role": no_invalid_role_default,
767
+ "no-script-tags": no_script_tags_default,
768
+ "require-viewbox": require_viewbox_default
620
769
  };
621
770
 
622
771
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-svg",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.0.5",
5
5
  "description": "Rules for consistent, readable and valid SVG files.",
6
6
  "keywords": [
7
7
  "eslint",
@@ -34,27 +34,27 @@
34
34
  "eslint": "^9.5.0"
35
35
  },
36
36
  "dependencies": {
37
- "@ntnyq/utils": "^0.10.0",
37
+ "@ntnyq/utils": "^0.11.0",
38
38
  "@types/json-schema": "^7.0.15",
39
- "svg-eslint-parser": "^0.0.5",
39
+ "svg-eslint-parser": "^0.0.7",
40
40
  "uncase": "^0.2.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@ntnyq/eslint-config": "^5.9.0",
43
+ "@ntnyq/eslint-config": "^6.0.0-beta.2",
44
44
  "@ntnyq/prettier-config": "^3.0.1",
45
- "@types/node": "^25.0.3",
46
- "bumpp": "^10.3.2",
45
+ "@types/node": "^25.0.10",
46
+ "bumpp": "^10.4.0",
47
47
  "eslint": "^9.39.2",
48
- "eslint-plugin-eslint-plugin": "^7.2.0",
48
+ "eslint-plugin-eslint-plugin": "^7.3.0",
49
49
  "eslint-vitest-rule-tester": "^3.0.1",
50
50
  "husky": "^9.1.7",
51
51
  "nano-staged": "^0.9.0",
52
52
  "npm-run-all2": "^8.0.4",
53
- "prettier": "^3.7.4",
53
+ "prettier": "^3.8.1",
54
54
  "tinyglobby": "^0.2.15",
55
- "tsdown": "^0.19.0-beta.2",
55
+ "tsdown": "^0.20.0-beta.4",
56
56
  "typescript": "^5.9.3",
57
- "vitest": "^4.0.16"
57
+ "vitest": "^4.0.17"
58
58
  },
59
59
  "engines": {
60
60
  "node": "^20.19.0 || ^22.13.0 || >=24"