eslint-plugin-better-stylelint 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ice breaker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # eslint-plugin-better-stylelint
2
+
3
+ Bridge Stylelint diagnostics into ESLint for style files.
4
+
5
+ This package provides:
6
+
7
+ - ESLint processors for `*.css` and `*.scss`
8
+ - an ESLint rule for `.vue` files that forwards Stylelint diagnostics from
9
+ `<style>` blocks
10
+ - a bundled `synckit` worker so `stylelint.lint()` can be invoked through a
11
+ synchronous ESLint bridge
12
+
13
+ ## Why
14
+
15
+ Use this when you want style issues to show up in the same ESLint diagnostic
16
+ stream as the rest of your project.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add -D eslint stylelint eslint-plugin-better-stylelint
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```js
27
+ import betterStylelint from 'eslint-plugin-better-stylelint'
28
+
29
+ export default [
30
+ {
31
+ files: ['**/*.css'],
32
+ plugins: {
33
+ stylelint: betterStylelint,
34
+ },
35
+ processor: 'stylelint/css',
36
+ },
37
+ {
38
+ files: ['**/*.scss'],
39
+ plugins: {
40
+ stylelint: betterStylelint,
41
+ },
42
+ processor: 'stylelint/scss',
43
+ },
44
+ {
45
+ files: ['**/*.vue'],
46
+ plugins: {
47
+ stylelint: betterStylelint,
48
+ },
49
+ rules: {
50
+ 'stylelint/stylelint': 'error',
51
+ },
52
+ },
53
+ ]
54
+ ```
55
+
56
+ The plugin relies on the consuming project's Stylelint configuration and
57
+ prefers the consuming project's `stylelint` installation. If the project does
58
+ not provide one, it falls back to the bundled `stylelint` dependency.
59
+
60
+ ## Implementation Notes
61
+
62
+ - The synchronous bridge is implemented with `synckit`, not by spawning the
63
+ Stylelint CLI.
64
+ - The published package must include both `dist/index.js` and `dist/worker.js`.
65
+ - `src/core.ts` resolves the worker file by swapping `core.(ts|js)` to
66
+ `worker.(ts|js)`, so removing the worker build entry will break runtime
67
+ resolution.
68
+
69
+ ## FAQ
70
+
71
+ ### Does it automatically read `stylelint.config.js`?
72
+
73
+ Yes. The bridge calls `stylelint.lint({ code, codeFilename, cwd })` and lets
74
+ Stylelint handle config discovery. In practice that means common config entry
75
+ points such as `stylelint.config.js`, `stylelint.config.cjs`,
76
+ `stylelint.config.mjs`, `stylelint.config.ts`, and `package.json#stylelint`
77
+ are discovered by Stylelint itself.
78
+
79
+ ### Does it support `extends`, `plugins`, and `customSyntax`?
80
+
81
+ Yes, as long as your Stylelint config can resolve them from the project. The
82
+ bridge does not bypass or replace Stylelint's normal config loading, so
83
+ `extends`, plugin rules, and `customSyntax` continue to work the same way they
84
+ would when you run Stylelint directly.
85
+
86
+ ### Which `stylelint` installation does it use?
87
+
88
+ It prefers the consuming project's own `stylelint` installation. If the
89
+ project does not provide one, the bridge falls back to the bundled `stylelint`
90
+ dependency shipped with `eslint-plugin-better-stylelint`.
91
+
92
+ For the most predictable behavior, install `stylelint` in the project root so
93
+ your ESLint bridge, Stylelint CLI, and editor integrations all use the same
94
+ version.
95
+
96
+ ### Will this affect IDE plugins?
97
+
98
+ Usually the ESLint extension works as expected, because the bridge runs inside
99
+ ESLint. The main thing to watch is consistency:
100
+
101
+ - If the project has its own `stylelint`, the ESLint bridge and Stylelint IDE
102
+ extension should stay aligned.
103
+ - If the project does not have its own `stylelint`, the ESLint bridge can still
104
+ work via the bundled fallback, but a standalone Stylelint IDE extension may
105
+ not behave exactly the same way.
106
+
107
+ ### How does it handle Vue SFCs?
108
+
109
+ Each `<style>` block in a `.vue` file is linted separately. The bridge:
110
+
111
+ - extracts the block content instead of linting the whole SFC as one string
112
+ - generates a virtual filename based on the block `lang`, such as `css` or
113
+ `scss`
114
+ - maps Stylelint diagnostics back to the original `.vue` line and column
115
+ - includes block context such as `scoped`, `module`, and `lang` in the message
116
+ when that context helps identify the source block
@@ -0,0 +1,90 @@
1
+ //#region src/types.d.ts
2
+ interface BetterStylelintMessage {
3
+ ruleId: string;
4
+ message: string;
5
+ line: number;
6
+ column: number;
7
+ endLine?: number;
8
+ endColumn?: number;
9
+ severity: 1 | 2;
10
+ fatal?: boolean;
11
+ }
12
+ interface BetterStylelintProcessor {
13
+ meta?: {
14
+ name?: string;
15
+ version?: string;
16
+ };
17
+ preprocess: (text: string, filename: string) => string[];
18
+ postprocess: (messages: unknown[][], filename: string) => BetterStylelintMessage[];
19
+ supportsAutofix?: boolean;
20
+ }
21
+ interface BetterStylelintRuleOptions {
22
+ cwd?: string;
23
+ }
24
+ //#endregion
25
+ //#region src/processor.d.ts
26
+ declare const cssProcessor: BetterStylelintProcessor;
27
+ declare const scssProcessor: BetterStylelintProcessor;
28
+ //#endregion
29
+ //#region src/rule.d.ts
30
+ interface RuleContext {
31
+ filename: string;
32
+ options: BetterStylelintRuleOptions[];
33
+ report: (descriptor: {
34
+ loc: {
35
+ start: {
36
+ line: number;
37
+ column: number;
38
+ };
39
+ end?: {
40
+ line: number;
41
+ column: number;
42
+ };
43
+ };
44
+ message: string;
45
+ }) => void;
46
+ sourceCode: {
47
+ text: string;
48
+ };
49
+ }
50
+ declare const lintRule: {
51
+ meta: {
52
+ type: string;
53
+ docs: {
54
+ description: string;
55
+ };
56
+ schema: {
57
+ type: string;
58
+ additionalProperties: boolean;
59
+ properties: {
60
+ cwd: {
61
+ type: string;
62
+ };
63
+ };
64
+ }[];
65
+ };
66
+ create(context: RuleContext): {
67
+ Program(): void;
68
+ };
69
+ };
70
+ //#endregion
71
+ //#region src/core.d.ts
72
+ declare function runStylelintSync(code: string, filename: string, cwd?: string): BetterStylelintMessage[];
73
+ //#endregion
74
+ //#region src/index.d.ts
75
+ interface BetterStylelintPlugin {
76
+ meta: {
77
+ name: string;
78
+ version: string;
79
+ };
80
+ processors: {
81
+ css: typeof cssProcessor;
82
+ scss: typeof scssProcessor;
83
+ };
84
+ rules: {
85
+ stylelint: typeof lintRule;
86
+ };
87
+ }
88
+ declare const plugin: BetterStylelintPlugin;
89
+ //#endregion
90
+ export { type BetterStylelintMessage, type BetterStylelintProcessor, type BetterStylelintRuleOptions, cssProcessor, plugin as default, lintRule, runStylelintSync, scssProcessor };
package/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { createSyncFn } from "synckit";
5
+ //#region src/core.ts
6
+ const MAX_CACHE_ENTRIES = 100;
7
+ const CORE_TS_PATTERN = /core\.ts$/u;
8
+ const CORE_JS_PATTERN = /core\.js$/u;
9
+ const resultCache = /* @__PURE__ */ new Map();
10
+ let runStylelintWorker;
11
+ function normalizeStylelintResults(result) {
12
+ return [...result.warnings ?? [], ...result.parseErrors ?? []].map((warning) => ({
13
+ ruleId: warning.rule ?? "stylelint",
14
+ message: warning.text,
15
+ line: warning.line ?? 1,
16
+ column: warning.column ?? 1,
17
+ ...warning.endLine !== void 0 ? { endLine: warning.endLine } : {},
18
+ ...warning.endColumn !== void 0 ? { endColumn: warning.endColumn } : {},
19
+ severity: warning.severity === "warning" ? 1 : 2,
20
+ ...warning.rule === void 0 ? { fatal: true } : {}
21
+ }));
22
+ }
23
+ function createBridgeError(message) {
24
+ return [{
25
+ ruleId: "stylelint/bridge",
26
+ message,
27
+ line: 1,
28
+ column: 1,
29
+ severity: 2,
30
+ fatal: true
31
+ }];
32
+ }
33
+ function cloneMessages(messages) {
34
+ return messages.map((message) => ({ ...message }));
35
+ }
36
+ function createCacheKey(code, filename, cwd) {
37
+ return `${cwd}\0${filename}\0${createHash("sha1").update(code).digest("hex")}`;
38
+ }
39
+ function getCachedMessages(cacheKey) {
40
+ const cached = resultCache.get(cacheKey);
41
+ if (!cached) return;
42
+ resultCache.delete(cacheKey);
43
+ resultCache.set(cacheKey, cached);
44
+ return cloneMessages(cached);
45
+ }
46
+ function setCachedMessages(cacheKey, messages) {
47
+ if (resultCache.has(cacheKey)) resultCache.delete(cacheKey);
48
+ resultCache.set(cacheKey, messages);
49
+ while (resultCache.size > MAX_CACHE_ENTRIES) {
50
+ const oldestKey = resultCache.keys().next().value;
51
+ if (oldestKey === void 0) break;
52
+ resultCache.delete(oldestKey);
53
+ }
54
+ return cloneMessages(messages);
55
+ }
56
+ function resolveWorkerPath() {
57
+ const currentFilePath = fileURLToPath(import.meta.url);
58
+ if (currentFilePath.endsWith(".ts")) return currentFilePath.replace(CORE_TS_PATTERN, "worker.ts");
59
+ return currentFilePath.replace(CORE_JS_PATTERN, "worker.js");
60
+ }
61
+ function getRunStylelintWorker() {
62
+ runStylelintWorker ??= createSyncFn(resolveWorkerPath());
63
+ return runStylelintWorker;
64
+ }
65
+ function runStylelintSync(code, filename, cwd = path.dirname(filename)) {
66
+ const cacheKey = createCacheKey(code, filename, cwd);
67
+ const cached = getCachedMessages(cacheKey);
68
+ if (cached) return cached;
69
+ const response = getRunStylelintWorker()(code, filename, cwd);
70
+ if (!response.ok) return setCachedMessages(cacheKey, createBridgeError(response.error));
71
+ return setCachedMessages(cacheKey, normalizeStylelintResults(response.result));
72
+ }
73
+ //#endregion
74
+ //#region src/processor.ts
75
+ const sourceCache = /* @__PURE__ */ new Map();
76
+ function createProcessor() {
77
+ return {
78
+ meta: {
79
+ name: "eslint-plugin-better-stylelint/processor",
80
+ version: "0.0.1"
81
+ },
82
+ preprocess(text, filename) {
83
+ sourceCache.set(filename, text);
84
+ return ["/* eslint-plugin-better-stylelint */"];
85
+ },
86
+ postprocess(_messages, filename) {
87
+ const source = sourceCache.get(filename) ?? "";
88
+ sourceCache.delete(filename);
89
+ return runStylelintSync(source, filename);
90
+ },
91
+ supportsAutofix: false
92
+ };
93
+ }
94
+ const cssProcessor = createProcessor();
95
+ const scssProcessor = createProcessor();
96
+ //#endregion
97
+ //#region src/rule.ts
98
+ const VUE_STYLE_BLOCK_PATTERN = /<style\b[^>]*>[\s\S]*?<\/style>/giu;
99
+ const VUE_STYLE_OPENING_TAG_PATTERN = /^<style\b([^>]*)>/iu;
100
+ const HTML_ATTRIBUTE_PATTERN = /([:@\w-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gu;
101
+ function getColumn(value) {
102
+ return Math.max(0, (value ?? 1) - 1);
103
+ }
104
+ function getLineColumnAtOffset(source, offset) {
105
+ let line = 1;
106
+ let column = 1;
107
+ for (let index = 0; index < offset; index += 1) {
108
+ if (source[index] === "\n") {
109
+ line += 1;
110
+ column = 1;
111
+ continue;
112
+ }
113
+ column += 1;
114
+ }
115
+ return {
116
+ line,
117
+ column
118
+ };
119
+ }
120
+ function parseStyleAttributes(code) {
121
+ const rawAttributes = code.match(VUE_STYLE_OPENING_TAG_PATTERN)?.[1];
122
+ if (!rawAttributes) return {};
123
+ const attributes = {};
124
+ for (const match of rawAttributes.matchAll(HTML_ATTRIBUTE_PATTERN)) {
125
+ const name = match[1];
126
+ if (!name) continue;
127
+ const value = match[2] ?? match[3] ?? match[4];
128
+ attributes[name] = value === void 0 ? true : value;
129
+ }
130
+ return attributes;
131
+ }
132
+ function createVueStyleBlockLabel(index, attributes, blockCount) {
133
+ const segments = blockCount > 1 ? [`style#${index + 1}`] : ["style"];
134
+ if (attributes["scoped"]) segments.push("scoped");
135
+ if (attributes["module"]) {
136
+ const moduleValue = attributes["module"];
137
+ segments.push(moduleValue === true ? "module" : `module=${moduleValue}`);
138
+ }
139
+ if (typeof attributes["lang"] === "string") segments.push(`lang=${attributes["lang"]}`);
140
+ return segments.length > 1 ? segments.join(" ") : void 0;
141
+ }
142
+ function normalizeStyleLang(attributes) {
143
+ const lang = attributes["lang"];
144
+ if (typeof lang !== "string" || !lang.trim()) return "css";
145
+ return lang.trim().toLowerCase();
146
+ }
147
+ function createVueStyleVirtualFilename(filename, index, attributes) {
148
+ return `${filename}__style_${index}.${normalizeStyleLang(attributes)}`;
149
+ }
150
+ function extractVueStyleBlocks(source, filename = "Component.vue") {
151
+ const matches = [...source.matchAll(VUE_STYLE_BLOCK_PATTERN)];
152
+ const blocks = [];
153
+ for (const [matchIndex, match] of matches.entries()) {
154
+ const code = match[0];
155
+ const matchOffset = match.index;
156
+ if (code === void 0 || matchOffset === void 0) continue;
157
+ const attributes = parseStyleAttributes(code);
158
+ const label = createVueStyleBlockLabel(matchIndex, attributes, matches.length);
159
+ const contentStartOffset = matchOffset + (code.match(VUE_STYLE_OPENING_TAG_PATTERN)?.[0] ?? "<style>").length;
160
+ const contentEndOffset = matchOffset + code.length - 8;
161
+ const content = source.slice(contentStartOffset, contentEndOffset);
162
+ const location = getLineColumnAtOffset(source, contentStartOffset);
163
+ blocks.push({
164
+ attributes,
165
+ code,
166
+ content,
167
+ index: matchIndex,
168
+ ...label !== void 0 ? { label } : {},
169
+ startLine: location.line,
170
+ startColumn: location.column,
171
+ virtualFilename: createVueStyleVirtualFilename(filename, matchIndex, attributes)
172
+ });
173
+ }
174
+ return blocks;
175
+ }
176
+ function mapDiagnosticToVueFile(diagnostic, block) {
177
+ const mappedStartLine = block.startLine + diagnostic.line - 1;
178
+ const mappedEndLine = diagnostic.endLine !== void 0 ? block.startLine + diagnostic.endLine - 1 : void 0;
179
+ return {
180
+ ...diagnostic,
181
+ line: mappedStartLine,
182
+ column: diagnostic.line === 1 ? block.startColumn + diagnostic.column - 1 : diagnostic.column,
183
+ ...mappedEndLine !== void 0 ? { endLine: mappedEndLine } : {},
184
+ ...diagnostic.endColumn !== void 0 ? { endColumn: diagnostic.endLine === void 0 || diagnostic.endLine === 1 ? block.startColumn + diagnostic.endColumn - 1 : diagnostic.endColumn } : {}
185
+ };
186
+ }
187
+ const lintRule = {
188
+ meta: {
189
+ type: "problem",
190
+ docs: { description: "Run Stylelint and surface its diagnostics through ESLint" },
191
+ schema: [{
192
+ type: "object",
193
+ additionalProperties: false,
194
+ properties: { cwd: { type: "string" } }
195
+ }]
196
+ },
197
+ create(context) {
198
+ return { Program() {
199
+ if (!context.filename.endsWith(".vue")) return;
200
+ const cwd = context.options[0]?.cwd;
201
+ const styleBlocks = extractVueStyleBlocks(context.sourceCode.text, context.filename);
202
+ for (const styleBlock of styleBlocks) {
203
+ const diagnostics = runStylelintSync(styleBlock.content, styleBlock.virtualFilename, cwd);
204
+ for (const diagnostic of diagnostics) {
205
+ const mappedDiagnostic = mapDiagnosticToVueFile(diagnostic, styleBlock);
206
+ context.report({
207
+ loc: {
208
+ start: {
209
+ line: mappedDiagnostic.line,
210
+ column: getColumn(mappedDiagnostic.column)
211
+ },
212
+ ...mappedDiagnostic.endLine !== void 0 || mappedDiagnostic.endColumn !== void 0 ? { end: {
213
+ line: mappedDiagnostic.endLine ?? mappedDiagnostic.line,
214
+ column: getColumn(mappedDiagnostic.endColumn ?? mappedDiagnostic.column)
215
+ } } : {}
216
+ },
217
+ message: styleBlock.label ? `[${styleBlock.label}] ${mappedDiagnostic.ruleId ? `${mappedDiagnostic.message} (${mappedDiagnostic.ruleId})` : mappedDiagnostic.message}` : mappedDiagnostic.ruleId ? `${mappedDiagnostic.message} (${mappedDiagnostic.ruleId})` : mappedDiagnostic.message
218
+ });
219
+ }
220
+ }
221
+ } };
222
+ }
223
+ };
224
+ //#endregion
225
+ //#region src/index.ts
226
+ const plugin = {
227
+ meta: {
228
+ name: "stylelint",
229
+ version: "0.0.1"
230
+ },
231
+ processors: {
232
+ css: cssProcessor,
233
+ scss: scssProcessor
234
+ },
235
+ rules: { stylelint: lintRule }
236
+ };
237
+ //#endregion
238
+ export { cssProcessor, plugin as default, lintRule, runStylelintSync, scssProcessor };
@@ -0,0 +1,5 @@
1
+ //#region src/worker.d.ts
2
+ declare function createProjectRequire(cwd: string): NodeJS.Require;
3
+ declare function resolveStylelintEntry(cwd: string): string;
4
+ //#endregion
5
+ export { createProjectRequire as __createProjectRequire, resolveStylelintEntry as __resolveStylelintEntry };
package/dist/worker.js ADDED
@@ -0,0 +1,42 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { runAsWorker } from "synckit";
5
+ //#region src/worker.ts
6
+ function createProjectRequire(cwd) {
7
+ try {
8
+ return createRequire(path.join(cwd, "package.json"));
9
+ } catch {
10
+ return createRequire(import.meta.url);
11
+ }
12
+ }
13
+ function resolveStylelintEntry(cwd) {
14
+ const projectRequire = createProjectRequire(cwd);
15
+ try {
16
+ return projectRequire.resolve("stylelint");
17
+ } catch {
18
+ return createRequire(import.meta.url).resolve("stylelint");
19
+ }
20
+ }
21
+ async function runStylelint(code, filename, cwd) {
22
+ try {
23
+ const stylelintModule = await import(pathToFileURL(resolveStylelintEntry(cwd)).href);
24
+ return {
25
+ ok: true,
26
+ result: (await (stylelintModule.default ?? stylelintModule).lint({
27
+ allowEmptyInput: true,
28
+ code,
29
+ codeFilename: filename,
30
+ cwd
31
+ })).results[0] ?? { warnings: [] }
32
+ };
33
+ } catch (error) {
34
+ return {
35
+ ok: false,
36
+ error: error instanceof Error ? error.message : String(error)
37
+ };
38
+ }
39
+ }
40
+ runAsWorker(runStylelint);
41
+ //#endregion
42
+ export { createProjectRequire as __createProjectRequire, resolveStylelintEntry as __resolveStylelintEntry };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "eslint-plugin-better-stylelint",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "Bridge Stylelint diagnostics into ESLint for style files and Vue SFCs",
6
+ "author": "ice breaker <1324318532@qq.com>",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/sonofmagic/dev-configs.git",
11
+ "directory": "packages/eslint-plugin-better-stylelint"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/sonofmagic/dev-configs/issues"
15
+ },
16
+ "keywords": [
17
+ "dev-configs",
18
+ "eslint",
19
+ "stylelint",
20
+ "eslint-plugin"
21
+ ],
22
+ "sideEffects": false,
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "main": "./dist/index.js",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "files": [
34
+ "README.md",
35
+ "dist"
36
+ ],
37
+ "peerDependencies": {
38
+ "eslint": "^9.0.0",
39
+ "stylelint": "^17.4.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "stylelint": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "dependencies": {
47
+ "stylelint": "^17.4.0",
48
+ "synckit": "^0.11.12"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public",
52
+ "registry": "https://registry.npmjs.org"
53
+ },
54
+ "tsd": {
55
+ "directory": "test-d",
56
+ "compilerOptions": {
57
+ "skipLibCheck": true
58
+ }
59
+ },
60
+ "scripts": {
61
+ "dev": "tsdown --watch --sourcemap",
62
+ "build": "tsdown",
63
+ "lint": "eslint src test test-d",
64
+ "test": "vitest run",
65
+ "test:types": "tsd",
66
+ "test:dev": "vitest",
67
+ "release": "pnpm publish",
68
+ "sync": "cnpm sync eslint-plugin-better-stylelint",
69
+ "typecheck": "tsc --noEmit"
70
+ }
71
+ }