@icebreakers/eslint-config 1.5.3 → 1.5.4

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
@@ -65,10 +65,19 @@ export default icebreaker({
65
65
  - `tailwindcss` – pass `true` to use the built-in Tailwind flat config or provide `{ entryPoint, tailwindConfig }` for Tailwind v4/v3 projects.
66
66
  - `mdx` – activates MDX linting via `eslint-plugin-mdx`.
67
67
  - `a11y` – wires in JSX (React) and Vue accessibility plugins.
68
- - `typescript` – extends the TypeScript preset and applies stricter unused diagnostics plus NestJS conveniences when `nestjs` is true.
68
+ - `typescript` – extends the TypeScript preset and applies stricter unused diagnostics. Pair with `nestjs` for Nest specific adjustments.
69
+ - `nestjs` – enables NestJS-centric TypeScript tweaks (empty decorated constructors, declaration merging, DI parameter properties, etc.).
69
70
  - `formatters` – keeps the built-in formatting rules enabled by default.
70
71
  - `test` – relaxes certain Vitest/Jest style rules (`test/prefer-lowercase-title`).
71
72
 
73
+ ### NestJS Projects
74
+
75
+ Enable `nestjs: true` together with the TypeScript preset to apply rules tailored for Nest idioms:
76
+
77
+ - Keeps decorated lifecycle hooks and class constructors legal even when empty.
78
+ - Allows DI parameter properties and ambient module augmentation (e.g. Express request typing).
79
+ - Relaxes `no-explicit-any`/`ban-types` patterns commonly used with provider tokens while keeping other strict defaults intact.
80
+
72
81
  ## Adding Extra Config Items
73
82
 
74
83
  Because `icebreaker()` returns a composer you can append overrides:
package/README.zh.md CHANGED
@@ -63,10 +63,19 @@ export default icebreaker({
63
63
  - `tailwindcss`:传入 `true` 使用内置 Tailwind flat 配置,或通过对象指定 Tailwind v4 的入口文件 / v3 的配置文件路径。
64
64
  - `mdx`:激活 `eslint-plugin-mdx` 处理 `.mdx` 文件。
65
65
  - `a11y`:按需引入 JSX 与 Vue 的无障碍规则。
66
- - `typescript`:开启 TypeScript 预设,并在 `nestjs` `true` 时放宽 NestJS 场景常见限制。
66
+ - `typescript`:开启 TypeScript 预设,加强未使用诊断,可与 `nestjs` 搭配使用以获得 Nest 专属优化。
67
+ - `nestjs`:针对 NestJS 场景做 TypeScript 调整(允许带装饰器的空构造函数、依赖注入参数属性、声明合并等)。
67
68
  - `formatters`:默认启用格式化辅助规则。
68
69
  - `test`:放宽 Vitest / Jest 常见规则,例如关闭 `test/prefer-lowercase-title`。
69
70
 
71
+ ### NestJS 项目
72
+
73
+ 建议在 Nest 项目中同时开启 `typescript` 与 `nestjs`,以便应用以下定制:
74
+
75
+ - 允许常见的空装饰器类(如 `@Controller()`、`@Module()`)以及生命周期钩子占位实现。
76
+ - 放宽依赖注入常用的构造函数参数属性、 ambient 模块扩展等约束。
77
+ - 针对 `Function`、`any` 等在注入令牌或元数据中常见的类型用法做精确豁免,同时保留其他严格检查。
78
+
70
79
  ## 追加自定义配置
71
80
 
72
81
  `icebreaker()` 返回的 composer 支持继续拼接 Flat Config:
package/dist/index.cjs CHANGED
@@ -41,6 +41,160 @@ module.exports = __toCommonJS(index_exports);
41
41
  var antfu_exports = {};
42
42
  __reExport(antfu_exports, require("@antfu/eslint-config"));
43
43
 
44
+ // ../../node_modules/.pnpm/eslint-plugin-vue@10.5.0_@stylistic+eslint-plugin@5.4.0_eslint@9.38.0_jiti@2.6.1___@typ_2ccd39cea8e0322a4f686961a30a63c8/node_modules/eslint-plugin-vue/lib/utils/inline-non-void-elements.json
45
+ var inline_non_void_elements_default = [
46
+ "a",
47
+ "abbr",
48
+ "audio",
49
+ "b",
50
+ "bdi",
51
+ "bdo",
52
+ "canvas",
53
+ "cite",
54
+ "code",
55
+ "data",
56
+ "del",
57
+ "dfn",
58
+ "em",
59
+ "i",
60
+ "iframe",
61
+ "ins",
62
+ "kbd",
63
+ "label",
64
+ "map",
65
+ "mark",
66
+ "noscript",
67
+ "object",
68
+ "output",
69
+ "picture",
70
+ "q",
71
+ "ruby",
72
+ "s",
73
+ "samp",
74
+ "small",
75
+ "span",
76
+ "strong",
77
+ "sub",
78
+ "sup",
79
+ "svg",
80
+ "time",
81
+ "u",
82
+ "var",
83
+ "video"
84
+ ];
85
+
86
+ // src/defaults.ts
87
+ var nestjsTypeScriptRules = {
88
+ "ts/explicit-function-return-type": "off",
89
+ "ts/explicit-module-boundary-types": "off",
90
+ "ts/no-explicit-any": "off",
91
+ "ts/no-parameter-properties": "off",
92
+ "ts/no-empty-function": [
93
+ "error",
94
+ {
95
+ allow: [
96
+ "decoratedFunctions",
97
+ "overrideMethods",
98
+ "private-constructors",
99
+ "protected-constructors"
100
+ ]
101
+ }
102
+ ],
103
+ "ts/no-empty-interface": "off",
104
+ "ts/no-namespace": [
105
+ "error",
106
+ {
107
+ allowDeclarations: true,
108
+ allowDefinitionFiles: true
109
+ }
110
+ ],
111
+ "ts/ban-types": [
112
+ "error",
113
+ {
114
+ extendDefaults: true,
115
+ types: {
116
+ Function: false
117
+ }
118
+ }
119
+ ]
120
+ };
121
+ function getDefaultVueOptions(opts) {
122
+ const overrides = {
123
+ "vue/attribute-hyphenation": "off",
124
+ "vue/v-on-event-hyphenation": "off",
125
+ "vue/custom-event-name-casing": "off",
126
+ "vue/no-mutating-props": "warn",
127
+ // https://eslint.vuejs.org/rules/no-useless-v-bind.html
128
+ "vue/no-useless-v-bind": [
129
+ "error",
130
+ {
131
+ // 不允许注释
132
+ ignoreIncludesComment: false,
133
+ // 允许在里面使用转义
134
+ // 比如 v-bind:foo="'bar\nbaz'"
135
+ ignoreStringEscape: true
136
+ }
137
+ ],
138
+ // https://eslint.vuejs.org/rules/no-unused-refs.html
139
+ "vue/no-unused-refs": "warn"
140
+ };
141
+ if (opts?.ionic) {
142
+ overrides["vue/no-deprecated-slot-attribute"] = "off";
143
+ }
144
+ if (opts?.weapp) {
145
+ overrides["vue/singleline-html-element-content-newline"] = [
146
+ "warn",
147
+ {
148
+ ignoreWhenNoAttributes: true,
149
+ ignoreWhenEmpty: true,
150
+ ignores: [
151
+ // 小程序标签
152
+ "text",
153
+ ...inline_non_void_elements_default
154
+ ],
155
+ externalIgnores: []
156
+ }
157
+ ];
158
+ }
159
+ const vueOptions = {
160
+ overrides
161
+ };
162
+ return vueOptions;
163
+ }
164
+ function getDefaultTypescriptOptions(opts) {
165
+ const overrides = {
166
+ "ts/no-unused-vars": [
167
+ "error",
168
+ {
169
+ args: "all",
170
+ argsIgnorePattern: "^_",
171
+ caughtErrors: "all",
172
+ caughtErrorsIgnorePattern: "^_",
173
+ destructuredArrayIgnorePattern: "^_",
174
+ varsIgnorePattern: "^_",
175
+ ignoreRestSiblings: true
176
+ }
177
+ ],
178
+ "ts/prefer-ts-expect-error": "off",
179
+ "ts/ban-ts-comment": "off",
180
+ "ts/no-use-before-define": "warn",
181
+ "ts/no-unused-expressions": [
182
+ "error",
183
+ {
184
+ allowShortCircuit: true,
185
+ allowTernary: true
186
+ }
187
+ ]
188
+ };
189
+ if (opts?.nestjs) {
190
+ Object.assign(overrides, nestjsTypeScriptRules);
191
+ }
192
+ const typescriptOptions = {
193
+ overrides
194
+ };
195
+ return typescriptOptions;
196
+ }
197
+
44
198
  // src/features.ts
45
199
  function resolveTailwindPresets(option) {
46
200
  if (!option) {
@@ -133,6 +287,15 @@ function resolveAccessibilityPresets(isEnabled, vueOption, reactOption) {
133
287
  }
134
288
  return presets;
135
289
  }
290
+ function resolveNestPresets(isEnabled) {
291
+ if (!isEnabled) {
292
+ return [];
293
+ }
294
+ return [{
295
+ name: "icebreaker/nestjs/rules",
296
+ rules: nestjsTypeScriptRules
297
+ }];
298
+ }
136
299
 
137
300
  // ../../node_modules/.pnpm/defu@6.1.4/node_modules/defu/dist/defu.mjs
138
301
  function isPlainObject(value) {
@@ -202,132 +365,6 @@ var defuArrayFn = createDefu((object, key, currentValue) => {
202
365
  }
203
366
  });
204
367
 
205
- // ../../node_modules/.pnpm/eslint-plugin-vue@10.5.0_@stylistic+eslint-plugin@5.4.0_eslint@9.37.0_jiti@2.6.1___@typ_a6f18fca60e8529df7d5b4523871b3c4/node_modules/eslint-plugin-vue/lib/utils/inline-non-void-elements.json
206
- var inline_non_void_elements_default = [
207
- "a",
208
- "abbr",
209
- "audio",
210
- "b",
211
- "bdi",
212
- "bdo",
213
- "canvas",
214
- "cite",
215
- "code",
216
- "data",
217
- "del",
218
- "dfn",
219
- "em",
220
- "i",
221
- "iframe",
222
- "ins",
223
- "kbd",
224
- "label",
225
- "map",
226
- "mark",
227
- "noscript",
228
- "object",
229
- "output",
230
- "picture",
231
- "q",
232
- "ruby",
233
- "s",
234
- "samp",
235
- "small",
236
- "span",
237
- "strong",
238
- "sub",
239
- "sup",
240
- "svg",
241
- "time",
242
- "u",
243
- "var",
244
- "video"
245
- ];
246
-
247
- // src/defaults.ts
248
- function getDefaultVueOptions(opts) {
249
- const overrides = {
250
- "vue/attribute-hyphenation": "off",
251
- "vue/v-on-event-hyphenation": "off",
252
- "vue/custom-event-name-casing": "off",
253
- "vue/no-mutating-props": "warn",
254
- // https://eslint.vuejs.org/rules/no-useless-v-bind.html
255
- "vue/no-useless-v-bind": [
256
- "error",
257
- {
258
- // 不允许注释
259
- ignoreIncludesComment: false,
260
- // 允许在里面使用转义
261
- // 比如 v-bind:foo="'bar\nbaz'"
262
- ignoreStringEscape: true
263
- }
264
- ],
265
- // https://eslint.vuejs.org/rules/no-unused-refs.html
266
- "vue/no-unused-refs": "warn"
267
- };
268
- if (opts?.ionic) {
269
- overrides["vue/no-deprecated-slot-attribute"] = "off";
270
- }
271
- if (opts?.weapp) {
272
- overrides["vue/singleline-html-element-content-newline"] = [
273
- "warn",
274
- {
275
- ignoreWhenNoAttributes: true,
276
- ignoreWhenEmpty: true,
277
- ignores: [
278
- // 小程序标签
279
- "text",
280
- ...inline_non_void_elements_default
281
- ],
282
- externalIgnores: []
283
- }
284
- ];
285
- }
286
- const vueOptions = {
287
- overrides
288
- };
289
- return vueOptions;
290
- }
291
- function getDefaultTypescriptOptions(opts) {
292
- const overrides = {
293
- "ts/no-unused-vars": [
294
- "error",
295
- {
296
- args: "all",
297
- argsIgnorePattern: "^_",
298
- caughtErrors: "all",
299
- caughtErrorsIgnorePattern: "^_",
300
- destructuredArrayIgnorePattern: "^_",
301
- varsIgnorePattern: "^_",
302
- ignoreRestSiblings: true
303
- }
304
- ],
305
- "ts/prefer-ts-expect-error": "off",
306
- "ts/ban-ts-comment": "off",
307
- "ts/no-use-before-define": "warn",
308
- "ts/no-unused-expressions": [
309
- "error",
310
- {
311
- allowShortCircuit: true,
312
- allowTernary: true
313
- }
314
- ]
315
- };
316
- if (opts?.nestjs) {
317
- Object.assign(overrides, {
318
- "ts/interface-name-prefix": "off",
319
- "ts/explicit-function-return-type": "off",
320
- "ts/explicit-module-boundary-types": "off",
321
- "ts/no-explicit-any": "off",
322
- "ts/consistent-type-imports": "off"
323
- });
324
- }
325
- const typescriptOptions = {
326
- overrides
327
- };
328
- return typescriptOptions;
329
- }
330
-
331
368
  // src/utils.ts
332
369
  function isObject(o) {
333
370
  return Object.prototype.toString.call(o) === "[object Object]";
@@ -414,6 +451,7 @@ function getPresets(options, mode) {
414
451
  presets.push(
415
452
  ...resolveTailwindPresets(resolved.tailwindcss),
416
453
  ...resolveMdxPresets(resolved.mdx),
454
+ ...resolveNestPresets(resolved.nestjs),
417
455
  ...resolveAccessibilityPresets(resolved.a11y, resolved.vue, resolved.react)
418
456
  );
419
457
  return [resolved, ...presets];
package/dist/index.js CHANGED
@@ -17,6 +17,160 @@ var antfu_exports = {};
17
17
  __reExport(antfu_exports, eslint_config_star);
18
18
  import * as eslint_config_star from "@antfu/eslint-config";
19
19
 
20
+ // ../../node_modules/.pnpm/eslint-plugin-vue@10.5.0_@stylistic+eslint-plugin@5.4.0_eslint@9.38.0_jiti@2.6.1___@typ_2ccd39cea8e0322a4f686961a30a63c8/node_modules/eslint-plugin-vue/lib/utils/inline-non-void-elements.json
21
+ var inline_non_void_elements_default = [
22
+ "a",
23
+ "abbr",
24
+ "audio",
25
+ "b",
26
+ "bdi",
27
+ "bdo",
28
+ "canvas",
29
+ "cite",
30
+ "code",
31
+ "data",
32
+ "del",
33
+ "dfn",
34
+ "em",
35
+ "i",
36
+ "iframe",
37
+ "ins",
38
+ "kbd",
39
+ "label",
40
+ "map",
41
+ "mark",
42
+ "noscript",
43
+ "object",
44
+ "output",
45
+ "picture",
46
+ "q",
47
+ "ruby",
48
+ "s",
49
+ "samp",
50
+ "small",
51
+ "span",
52
+ "strong",
53
+ "sub",
54
+ "sup",
55
+ "svg",
56
+ "time",
57
+ "u",
58
+ "var",
59
+ "video"
60
+ ];
61
+
62
+ // src/defaults.ts
63
+ var nestjsTypeScriptRules = {
64
+ "ts/explicit-function-return-type": "off",
65
+ "ts/explicit-module-boundary-types": "off",
66
+ "ts/no-explicit-any": "off",
67
+ "ts/no-parameter-properties": "off",
68
+ "ts/no-empty-function": [
69
+ "error",
70
+ {
71
+ allow: [
72
+ "decoratedFunctions",
73
+ "overrideMethods",
74
+ "private-constructors",
75
+ "protected-constructors"
76
+ ]
77
+ }
78
+ ],
79
+ "ts/no-empty-interface": "off",
80
+ "ts/no-namespace": [
81
+ "error",
82
+ {
83
+ allowDeclarations: true,
84
+ allowDefinitionFiles: true
85
+ }
86
+ ],
87
+ "ts/ban-types": [
88
+ "error",
89
+ {
90
+ extendDefaults: true,
91
+ types: {
92
+ Function: false
93
+ }
94
+ }
95
+ ]
96
+ };
97
+ function getDefaultVueOptions(opts) {
98
+ const overrides = {
99
+ "vue/attribute-hyphenation": "off",
100
+ "vue/v-on-event-hyphenation": "off",
101
+ "vue/custom-event-name-casing": "off",
102
+ "vue/no-mutating-props": "warn",
103
+ // https://eslint.vuejs.org/rules/no-useless-v-bind.html
104
+ "vue/no-useless-v-bind": [
105
+ "error",
106
+ {
107
+ // 不允许注释
108
+ ignoreIncludesComment: false,
109
+ // 允许在里面使用转义
110
+ // 比如 v-bind:foo="'bar\nbaz'"
111
+ ignoreStringEscape: true
112
+ }
113
+ ],
114
+ // https://eslint.vuejs.org/rules/no-unused-refs.html
115
+ "vue/no-unused-refs": "warn"
116
+ };
117
+ if (opts?.ionic) {
118
+ overrides["vue/no-deprecated-slot-attribute"] = "off";
119
+ }
120
+ if (opts?.weapp) {
121
+ overrides["vue/singleline-html-element-content-newline"] = [
122
+ "warn",
123
+ {
124
+ ignoreWhenNoAttributes: true,
125
+ ignoreWhenEmpty: true,
126
+ ignores: [
127
+ // 小程序标签
128
+ "text",
129
+ ...inline_non_void_elements_default
130
+ ],
131
+ externalIgnores: []
132
+ }
133
+ ];
134
+ }
135
+ const vueOptions = {
136
+ overrides
137
+ };
138
+ return vueOptions;
139
+ }
140
+ function getDefaultTypescriptOptions(opts) {
141
+ const overrides = {
142
+ "ts/no-unused-vars": [
143
+ "error",
144
+ {
145
+ args: "all",
146
+ argsIgnorePattern: "^_",
147
+ caughtErrors: "all",
148
+ caughtErrorsIgnorePattern: "^_",
149
+ destructuredArrayIgnorePattern: "^_",
150
+ varsIgnorePattern: "^_",
151
+ ignoreRestSiblings: true
152
+ }
153
+ ],
154
+ "ts/prefer-ts-expect-error": "off",
155
+ "ts/ban-ts-comment": "off",
156
+ "ts/no-use-before-define": "warn",
157
+ "ts/no-unused-expressions": [
158
+ "error",
159
+ {
160
+ allowShortCircuit: true,
161
+ allowTernary: true
162
+ }
163
+ ]
164
+ };
165
+ if (opts?.nestjs) {
166
+ Object.assign(overrides, nestjsTypeScriptRules);
167
+ }
168
+ const typescriptOptions = {
169
+ overrides
170
+ };
171
+ return typescriptOptions;
172
+ }
173
+
20
174
  // src/features.ts
21
175
  function resolveTailwindPresets(option) {
22
176
  if (!option) {
@@ -109,6 +263,15 @@ function resolveAccessibilityPresets(isEnabled, vueOption, reactOption) {
109
263
  }
110
264
  return presets;
111
265
  }
266
+ function resolveNestPresets(isEnabled) {
267
+ if (!isEnabled) {
268
+ return [];
269
+ }
270
+ return [{
271
+ name: "icebreaker/nestjs/rules",
272
+ rules: nestjsTypeScriptRules
273
+ }];
274
+ }
112
275
 
113
276
  // ../../node_modules/.pnpm/defu@6.1.4/node_modules/defu/dist/defu.mjs
114
277
  function isPlainObject(value) {
@@ -178,132 +341,6 @@ var defuArrayFn = createDefu((object, key, currentValue) => {
178
341
  }
179
342
  });
180
343
 
181
- // ../../node_modules/.pnpm/eslint-plugin-vue@10.5.0_@stylistic+eslint-plugin@5.4.0_eslint@9.37.0_jiti@2.6.1___@typ_a6f18fca60e8529df7d5b4523871b3c4/node_modules/eslint-plugin-vue/lib/utils/inline-non-void-elements.json
182
- var inline_non_void_elements_default = [
183
- "a",
184
- "abbr",
185
- "audio",
186
- "b",
187
- "bdi",
188
- "bdo",
189
- "canvas",
190
- "cite",
191
- "code",
192
- "data",
193
- "del",
194
- "dfn",
195
- "em",
196
- "i",
197
- "iframe",
198
- "ins",
199
- "kbd",
200
- "label",
201
- "map",
202
- "mark",
203
- "noscript",
204
- "object",
205
- "output",
206
- "picture",
207
- "q",
208
- "ruby",
209
- "s",
210
- "samp",
211
- "small",
212
- "span",
213
- "strong",
214
- "sub",
215
- "sup",
216
- "svg",
217
- "time",
218
- "u",
219
- "var",
220
- "video"
221
- ];
222
-
223
- // src/defaults.ts
224
- function getDefaultVueOptions(opts) {
225
- const overrides = {
226
- "vue/attribute-hyphenation": "off",
227
- "vue/v-on-event-hyphenation": "off",
228
- "vue/custom-event-name-casing": "off",
229
- "vue/no-mutating-props": "warn",
230
- // https://eslint.vuejs.org/rules/no-useless-v-bind.html
231
- "vue/no-useless-v-bind": [
232
- "error",
233
- {
234
- // 不允许注释
235
- ignoreIncludesComment: false,
236
- // 允许在里面使用转义
237
- // 比如 v-bind:foo="'bar\nbaz'"
238
- ignoreStringEscape: true
239
- }
240
- ],
241
- // https://eslint.vuejs.org/rules/no-unused-refs.html
242
- "vue/no-unused-refs": "warn"
243
- };
244
- if (opts?.ionic) {
245
- overrides["vue/no-deprecated-slot-attribute"] = "off";
246
- }
247
- if (opts?.weapp) {
248
- overrides["vue/singleline-html-element-content-newline"] = [
249
- "warn",
250
- {
251
- ignoreWhenNoAttributes: true,
252
- ignoreWhenEmpty: true,
253
- ignores: [
254
- // 小程序标签
255
- "text",
256
- ...inline_non_void_elements_default
257
- ],
258
- externalIgnores: []
259
- }
260
- ];
261
- }
262
- const vueOptions = {
263
- overrides
264
- };
265
- return vueOptions;
266
- }
267
- function getDefaultTypescriptOptions(opts) {
268
- const overrides = {
269
- "ts/no-unused-vars": [
270
- "error",
271
- {
272
- args: "all",
273
- argsIgnorePattern: "^_",
274
- caughtErrors: "all",
275
- caughtErrorsIgnorePattern: "^_",
276
- destructuredArrayIgnorePattern: "^_",
277
- varsIgnorePattern: "^_",
278
- ignoreRestSiblings: true
279
- }
280
- ],
281
- "ts/prefer-ts-expect-error": "off",
282
- "ts/ban-ts-comment": "off",
283
- "ts/no-use-before-define": "warn",
284
- "ts/no-unused-expressions": [
285
- "error",
286
- {
287
- allowShortCircuit: true,
288
- allowTernary: true
289
- }
290
- ]
291
- };
292
- if (opts?.nestjs) {
293
- Object.assign(overrides, {
294
- "ts/interface-name-prefix": "off",
295
- "ts/explicit-function-return-type": "off",
296
- "ts/explicit-module-boundary-types": "off",
297
- "ts/no-explicit-any": "off",
298
- "ts/consistent-type-imports": "off"
299
- });
300
- }
301
- const typescriptOptions = {
302
- overrides
303
- };
304
- return typescriptOptions;
305
- }
306
-
307
344
  // src/utils.ts
308
345
  function isObject(o) {
309
346
  return Object.prototype.toString.call(o) === "[object Object]";
@@ -390,6 +427,7 @@ function getPresets(options, mode) {
390
427
  presets.push(
391
428
  ...resolveTailwindPresets(resolved.tailwindcss),
392
429
  ...resolveMdxPresets(resolved.mdx),
430
+ ...resolveNestPresets(resolved.nestjs),
393
431
  ...resolveAccessibilityPresets(resolved.a11y, resolved.vue, resolved.react)
394
432
  );
395
433
  return [resolved, ...presets];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@icebreakers/eslint-config",
3
3
  "type": "module",
4
- "version": "1.5.3",
4
+ "version": "1.5.4",
5
5
  "description": "ESLint preset from Icebreaker's dev-configs",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",