eslint-plugin-next-compat 1.0.0-alpha.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/README.md +105 -0
- package/dist/index.js +227 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +195 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# eslint-plugin-next-compat
|
|
2
|
+
|
|
3
|
+
ESLint plugin that automatically detects **client components only** in Next.js App Router projects and checks browser compatibility.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
In Next.js App Router, server and client components coexist. Browser compatibility checks are only needed for **client components**, but existing tools can't distinguish between them.
|
|
8
|
+
|
|
9
|
+
This plugin detects client components and **tracks all their dependencies**, ensuring complete coverage of client-side code.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
> **Requirements**
|
|
14
|
+
> ESLint 9+ (Flat Config only)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install eslint-plugin-next-compat --save-dev
|
|
18
|
+
# or
|
|
19
|
+
pnpm add -D eslint-plugin-next-compat
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
> **Note**
|
|
25
|
+
> Client files are detected when ESLint starts. If you add or remove client components, restart your ESLint server (or IDE) to detect the changes.
|
|
26
|
+
|
|
27
|
+
This plugin uses [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) internally, so all of its configuration options are supported.
|
|
28
|
+
|
|
29
|
+
### Basic
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
// eslint.config.js
|
|
33
|
+
import nextCompat from 'eslint-plugin-next-compat';
|
|
34
|
+
|
|
35
|
+
export default [
|
|
36
|
+
...nextCompat.configs.recommended,
|
|
37
|
+
];
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### With Options
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
// eslint.config.js
|
|
44
|
+
import nextCompat from 'eslint-plugin-next-compat';
|
|
45
|
+
|
|
46
|
+
export default [
|
|
47
|
+
...nextCompat.configs.recommended({
|
|
48
|
+
include: ['src/hooks/**', 'src/utils/client/**'],
|
|
49
|
+
exclude: ['**/*.test.ts', '**/legacy/**'],
|
|
50
|
+
}),
|
|
51
|
+
];
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Option | Type | Description |
|
|
55
|
+
|--------|------|-------------|
|
|
56
|
+
| `include` | `string[]` | Additional glob patterns to check |
|
|
57
|
+
| `exclude` | `string[]` | Glob patterns to exclude from checking |
|
|
58
|
+
|
|
59
|
+
### Strict Mode
|
|
60
|
+
|
|
61
|
+
Use `error` instead of `warn`:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
export default [
|
|
65
|
+
...nextCompat.configs.strict,
|
|
66
|
+
];
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Target Browsers
|
|
70
|
+
|
|
71
|
+
Configure via `.browserslistrc` or `browserslist` field in `package.json`:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
# .browserslistrc
|
|
75
|
+
last 2 versions
|
|
76
|
+
not dead
|
|
77
|
+
not ie 11
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Polyfills
|
|
81
|
+
|
|
82
|
+
Exclude APIs that are already polyfilled:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
// eslint.config.js
|
|
86
|
+
export default [
|
|
87
|
+
...nextCompat.configs.recommended,
|
|
88
|
+
{
|
|
89
|
+
settings: {
|
|
90
|
+
polyfills: ['fetch', 'Promise', 'IntersectionObserver'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## How It Works
|
|
97
|
+
|
|
98
|
+
1. Scans all `.tsx` files in `src/app` directory
|
|
99
|
+
2. Identifies files with `'use client'` directive
|
|
100
|
+
3. Analyzes dependency tree of those files
|
|
101
|
+
4. Applies [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) rules to identified client files
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name2 in all)
|
|
12
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// package.json
|
|
33
|
+
var require_package = __commonJS({
|
|
34
|
+
"package.json"(exports2, module2) {
|
|
35
|
+
module2.exports = {
|
|
36
|
+
name: "eslint-plugin-next-compat",
|
|
37
|
+
version: "1.0.0-alpha.1",
|
|
38
|
+
description: "ESLint plugin for browser compatibility checking in Next.js App Router client components",
|
|
39
|
+
main: "dist/index.js",
|
|
40
|
+
module: "dist/index.mjs",
|
|
41
|
+
exports: {
|
|
42
|
+
".": {
|
|
43
|
+
import: "./dist/index.mjs",
|
|
44
|
+
require: "./dist/index.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
files: [
|
|
48
|
+
"dist"
|
|
49
|
+
],
|
|
50
|
+
scripts: {
|
|
51
|
+
build: "tsup",
|
|
52
|
+
dev: "tsup --watch",
|
|
53
|
+
test: "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
prepublishOnly: "pnpm run build",
|
|
56
|
+
deploy: "pnpm run test && pnpm run build && npm publish --access=public"
|
|
57
|
+
},
|
|
58
|
+
keywords: [
|
|
59
|
+
"eslint",
|
|
60
|
+
"eslintplugin",
|
|
61
|
+
"eslint-plugin",
|
|
62
|
+
"next",
|
|
63
|
+
"nextjs",
|
|
64
|
+
"compat",
|
|
65
|
+
"browser-compatibility"
|
|
66
|
+
],
|
|
67
|
+
author: "dkpark10",
|
|
68
|
+
license: "MIT",
|
|
69
|
+
repository: {
|
|
70
|
+
type: "git",
|
|
71
|
+
url: "https://github.com/dkpark10/eslint-plugin-next-compat"
|
|
72
|
+
},
|
|
73
|
+
homepage: "https://github.com/dkpark10/eslint-plugin-next-compat#readme",
|
|
74
|
+
bugs: {
|
|
75
|
+
url: "https://github.com/dkpark10/eslint-plugin-next-compat/issues"
|
|
76
|
+
},
|
|
77
|
+
peerDependencies: {
|
|
78
|
+
eslint: "^9.0.0"
|
|
79
|
+
},
|
|
80
|
+
dependencies: {
|
|
81
|
+
"dependency-tree": "^11.4.0",
|
|
82
|
+
"eslint-plugin-compat": "^6.0.0",
|
|
83
|
+
glob: "^13.0.6",
|
|
84
|
+
minimatch: "^10.2.4"
|
|
85
|
+
},
|
|
86
|
+
devDependencies: {
|
|
87
|
+
eslint: "^9.0.0",
|
|
88
|
+
tsup: "^8.0.0",
|
|
89
|
+
vitest: "^1.6.0"
|
|
90
|
+
},
|
|
91
|
+
engines: {
|
|
92
|
+
node: ">=18.18.0"
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// src/index.js
|
|
99
|
+
var index_exports = {};
|
|
100
|
+
__export(index_exports, {
|
|
101
|
+
default: () => index_default,
|
|
102
|
+
plugin: () => plugin
|
|
103
|
+
});
|
|
104
|
+
module.exports = __toCommonJS(index_exports);
|
|
105
|
+
var import_glob2 = require("glob");
|
|
106
|
+
var import_minimatch = require("minimatch");
|
|
107
|
+
|
|
108
|
+
// src/get-client-files.js
|
|
109
|
+
var import_glob = require("glob");
|
|
110
|
+
var import_fs = __toESM(require("fs"));
|
|
111
|
+
var import_path = __toESM(require("path"));
|
|
112
|
+
var import_dependency_tree = __toESM(require("dependency-tree"));
|
|
113
|
+
var USE_CLIENT_REGEX = /^(?:\s|\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)*['"]use client['"]/;
|
|
114
|
+
function getClientFiles(options = {}) {
|
|
115
|
+
const {
|
|
116
|
+
appDir = "src/app",
|
|
117
|
+
cwd = process.cwd(),
|
|
118
|
+
tsConfigPath = "tsconfig.json"
|
|
119
|
+
} = options;
|
|
120
|
+
try {
|
|
121
|
+
let getDependencies = function(fileList) {
|
|
122
|
+
return fileList.reduce(
|
|
123
|
+
(acc, filePath) => {
|
|
124
|
+
try {
|
|
125
|
+
const deps = import_dependency_tree.default.toList({
|
|
126
|
+
filename: filePath,
|
|
127
|
+
directory: cwd,
|
|
128
|
+
filter: (depPath) => !depPath.includes("node_modules") && !depPath.endsWith(".css") && !depPath.endsWith(".scss"),
|
|
129
|
+
tsConfig: import_fs.default.existsSync(tsConfigFullPath) ? tsConfigFullPath : void 0
|
|
130
|
+
});
|
|
131
|
+
return [...acc, ...deps];
|
|
132
|
+
} catch {
|
|
133
|
+
return acc;
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
/** @type {string[]} */
|
|
137
|
+
[]
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
const srcPath = import_path.default.resolve(cwd, appDir);
|
|
141
|
+
const tsConfigFullPath = import_path.default.resolve(cwd, tsConfigPath);
|
|
142
|
+
const tsxFilePaths = (0, import_glob.globSync)(`${srcPath}/**/*.tsx`, {
|
|
143
|
+
ignore: ["**/node_modules/**"]
|
|
144
|
+
});
|
|
145
|
+
if (tsxFilePaths.length === 0) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
const allDependencies = getDependencies(tsxFilePaths);
|
|
149
|
+
const clientFiles = allDependencies.filter((filePath) => {
|
|
150
|
+
try {
|
|
151
|
+
const content = import_fs.default.readFileSync(filePath, "utf-8");
|
|
152
|
+
return USE_CLIENT_REGEX.test(content);
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
const clientDependencies = getDependencies(clientFiles);
|
|
158
|
+
const uniqueFiles = [...new Set(clientDependencies)];
|
|
159
|
+
return uniqueFiles.map((filePath) => {
|
|
160
|
+
return import_path.default.relative(cwd, filePath);
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error("get-client-files error:", err);
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/index.js
|
|
169
|
+
var import_eslint_plugin_compat = __toESM(require("eslint-plugin-compat"));
|
|
170
|
+
var { name, version } = require_package();
|
|
171
|
+
var PLUGIN_NAME = name.replace("eslint-plugin-", "");
|
|
172
|
+
var plugin = {
|
|
173
|
+
meta: {
|
|
174
|
+
name,
|
|
175
|
+
version
|
|
176
|
+
},
|
|
177
|
+
rules: {
|
|
178
|
+
compat: import_eslint_plugin_compat.default.rules.compat
|
|
179
|
+
},
|
|
180
|
+
configs: (
|
|
181
|
+
/** @type {NextCompatPlugin['configs']} */
|
|
182
|
+
{}
|
|
183
|
+
)
|
|
184
|
+
};
|
|
185
|
+
function getTargetFiles(include, exclude) {
|
|
186
|
+
const additionalFiles = (include == null ? void 0 : include.flatMap(
|
|
187
|
+
(pattern) => (0, import_glob2.globSync)(pattern, { ignore: ["**/node_modules/**"] })
|
|
188
|
+
)) ?? [];
|
|
189
|
+
const allFiles = [.../* @__PURE__ */ new Set([...getClientFiles(), ...additionalFiles])];
|
|
190
|
+
const filteredFiles = (exclude == null ? void 0 : exclude.length) ? allFiles.filter((file) => !exclude.some((pattern) => (0, import_minimatch.minimatch)(file, pattern))) : allFiles;
|
|
191
|
+
return filteredFiles.length > 0 ? filteredFiles : ["__no_client_files__"];
|
|
192
|
+
}
|
|
193
|
+
function createConfig(severity, options) {
|
|
194
|
+
const targetFiles = getTargetFiles(options == null ? void 0 : options.include, options == null ? void 0 : options.exclude);
|
|
195
|
+
return [
|
|
196
|
+
{
|
|
197
|
+
name: `${PLUGIN_NAME}/${severity === "warn" ? "recommended" : "strict"}`,
|
|
198
|
+
files: targetFiles,
|
|
199
|
+
plugins: {
|
|
200
|
+
[PLUGIN_NAME]: plugin
|
|
201
|
+
},
|
|
202
|
+
rules: {
|
|
203
|
+
[`${PLUGIN_NAME}/compat`]: severity
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
function createConfigFunction(severity) {
|
|
209
|
+
const fn = (options) => createConfig(severity, options);
|
|
210
|
+
fn[Symbol.iterator] = function* () {
|
|
211
|
+
yield* createConfig(severity);
|
|
212
|
+
};
|
|
213
|
+
return (
|
|
214
|
+
/** @type {ConfigFunction} */
|
|
215
|
+
fn
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
plugin.configs = {
|
|
219
|
+
recommended: createConfigFunction("warn"),
|
|
220
|
+
strict: createConfigFunction("error")
|
|
221
|
+
};
|
|
222
|
+
var index_default = plugin;
|
|
223
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
224
|
+
0 && (module.exports = {
|
|
225
|
+
plugin
|
|
226
|
+
});
|
|
227
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../package.json","../src/index.js","../src/get-client-files.js"],"sourcesContent":["{\n \"name\": \"eslint-plugin-next-compat\",\n \"version\": \"1.0.0-alpha.1\",\n \"description\": \"ESLint plugin for browser compatibility checking in Next.js App Router client components\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"pnpm run build\",\n \"deploy\": \"pnpm run test && pnpm run build && npm publish --access=public\"\n },\n \"keywords\": [\n \"eslint\",\n \"eslintplugin\",\n \"eslint-plugin\",\n \"next\",\n \"nextjs\",\n \"compat\",\n \"browser-compatibility\"\n ],\n \"author\": \"dkpark10\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/dkpark10/eslint-plugin-next-compat\"\n },\n \"homepage\": \"https://github.com/dkpark10/eslint-plugin-next-compat#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/dkpark10/eslint-plugin-next-compat/issues\"\n },\n \"peerDependencies\": {\n \"eslint\": \"^9.0.0\"\n },\n \"dependencies\": {\n \"dependency-tree\": \"^11.4.0\",\n \"eslint-plugin-compat\": \"^6.0.0\",\n \"glob\": \"^13.0.6\",\n \"minimatch\": \"^10.2.4\"\n },\n \"devDependencies\": {\n \"eslint\": \"^9.0.0\",\n \"tsup\": \"^8.0.0\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.18.0\"\n }\n}\n","import { globSync } from 'glob';\nimport { minimatch } from 'minimatch';\nimport { getClientFiles } from './get-client-files.js';\nimport compatPlugin from 'eslint-plugin-compat';\nconst { name, version } = require('../package.json');\n\nconst PLUGIN_NAME = name.replace('eslint-plugin-', '');\n\n/**\n * @typedef {Object} ConfigOptions\n * @property {string[]} [include] - Additional glob patterns to include as client files\n * @property {string[]} [exclude] - Glob patterns to exclude from client files\n */\n\n/**\n * @typedef {((options?: ConfigOptions) => import('eslint').Linter.Config[]) & { [Symbol.iterator](): Generator<import('eslint').Linter.Config> }} ConfigFunction\n */\n\n/**\n * @typedef {Object} NextCompatPlugin\n * @property {{ name: string; version: string }} meta\n * @property {Record<string, import('eslint').Rule.RuleModule>} rules\n * @property {{ recommended: ConfigFunction; strict: ConfigFunction }} configs\n */\n\n/** @type {NextCompatPlugin} */\nconst plugin = {\n meta: {\n name,\n version,\n },\n\n rules: {\n compat: compatPlugin.rules.compat,\n },\n\n configs: /** @type {NextCompatPlugin['configs']} */ ({}),\n};\n\n/**\n * @param {string[]} [include]\n * @param {string[]} [exclude]\n * @returns {string[]}\n */\nfunction getTargetFiles(include, exclude) {\n const additionalFiles = include?.flatMap((pattern) =>\n globSync(pattern, { ignore: ['**/node_modules/**'] })\n ) ?? [];\n\n const allFiles = [...new Set([...getClientFiles(), ...additionalFiles])];\n\n const filteredFiles = exclude?.length\n ? allFiles.filter((file) => !exclude.some((pattern) => minimatch(file, pattern)))\n : allFiles;\n\n return filteredFiles.length > 0 ? filteredFiles : ['__no_client_files__'];\n}\n\n/**\n * @param {'warn' | 'error'} severity\n * @param {ConfigOptions} [options]\n * @returns {import('eslint').Linter.Config[]}\n */\nfunction createConfig(severity, options) {\n const targetFiles = getTargetFiles(options?.include, options?.exclude);\n\n return [\n {\n name: `${PLUGIN_NAME}/${severity === 'warn' ? 'recommended' : 'strict'}`,\n files: targetFiles,\n plugins: {\n [PLUGIN_NAME]: plugin,\n },\n rules: {\n [`${PLUGIN_NAME}/compat`]: severity,\n },\n },\n ];\n}\n\n/**\n * Create a config function that is also iterable (for spread without calling)\n * @param {'warn' | 'error'} severity\n * @returns {ConfigFunction}\n */\nfunction createConfigFunction(severity) {\n /** @param {ConfigOptions} [options] */\n const fn = (options) => createConfig(severity, options);\n\n // Make it iterable so `...configs.recommended` works without ()\n fn[Symbol.iterator] = function* () {\n yield* createConfig(severity);\n };\n\n return /** @type {ConfigFunction} */ (fn);\n}\n\nplugin.configs = {\n recommended: createConfigFunction('warn'),\n strict: createConfigFunction('error'),\n};\n\nexport { plugin };\nexport default plugin;\n","import { globSync } from 'glob';\nimport fs from 'fs';\nimport path from 'path';\nimport dependencyTree from 'dependency-tree';\n\nconst USE_CLIENT_REGEX = /^(?:\\s|\\/\\/[^\\n]*\\n|\\/\\*[\\s\\S]*?\\*\\/)*['\"]use client['\"]/;\n\n/**\n * @typedef {Object} GetClientFilesOptions\n * @property {string} [appDir] - App directory path (default: 'src/app')\n * @property {string} [cwd] - Project root directory (default: process.cwd())\n * @property {string} [tsConfigPath] - tsconfig.json path (default: 'tsconfig.json')\n */\n\n/**\n * Get all client component files including their dependencies\n * @param {GetClientFilesOptions} [options]\n * @returns {string[]}\n */\nexport function getClientFiles(options = {}) {\n const {\n appDir = 'src/app',\n cwd = process.cwd(),\n tsConfigPath = 'tsconfig.json',\n } = options;\n\n try {\n const srcPath = path.resolve(cwd, appDir);\n const tsConfigFullPath = path.resolve(cwd, tsConfigPath);\n\n // Get all tsx files in app directory\n const tsxFilePaths = globSync(`${srcPath}/**/*.tsx`, {\n ignore: ['**/node_modules/**'],\n });\n\n if (tsxFilePaths.length === 0) {\n return [];\n }\n\n /**\n * Get dependencies for a list of files\n * @param {string[]} fileList\n * @returns {string[]}\n */\n function getDependencies(fileList) {\n return fileList.reduce((acc, filePath) => {\n try {\n const deps = dependencyTree.toList({\n filename: filePath,\n directory: cwd,\n filter: (/** @type {string} */ depPath) =>\n !depPath.includes('node_modules') &&\n !depPath.endsWith('.css') &&\n !depPath.endsWith('.scss'),\n tsConfig: fs.existsSync(tsConfigFullPath) ? tsConfigFullPath : undefined,\n });\n return [...acc, ...deps];\n } catch {\n return acc;\n }\n }, /** @type {string[]} */ ([]));\n }\n\n // Get all dependencies from tsx files\n const allDependencies = getDependencies(tsxFilePaths);\n\n // Filter files that have \"use client\" directive\n const clientFiles = allDependencies.filter((filePath) => {\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n return USE_CLIENT_REGEX.test(content);\n } catch {\n return false;\n }\n });\n\n // Get all dependencies of client files (these are also client components)\n const clientDependencies = getDependencies(clientFiles);\n\n // Return unique file paths as relative glob patterns\n const uniqueFiles = [...new Set(clientDependencies)];\n\n // Convert to relative paths for ESLint files pattern\n return uniqueFiles.map((filePath) => {\n return path.relative(cwd, filePath);\n });\n } catch (err) {\n console.error('get-client-files error:', err);\n return [];\n }\n}\n\nexport default getClientFiles;\n"],"mappings":"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","names":["exports","module","import_glob","dependencyTree","fs","path","compatPlugin"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
3
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// package.json
|
|
7
|
+
var require_package = __commonJS({
|
|
8
|
+
"package.json"(exports, module) {
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: "eslint-plugin-next-compat",
|
|
11
|
+
version: "1.0.0-alpha.1",
|
|
12
|
+
description: "ESLint plugin for browser compatibility checking in Next.js App Router client components",
|
|
13
|
+
main: "dist/index.js",
|
|
14
|
+
module: "dist/index.mjs",
|
|
15
|
+
exports: {
|
|
16
|
+
".": {
|
|
17
|
+
import: "./dist/index.mjs",
|
|
18
|
+
require: "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
files: [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
scripts: {
|
|
25
|
+
build: "tsup",
|
|
26
|
+
dev: "tsup --watch",
|
|
27
|
+
test: "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
prepublishOnly: "pnpm run build",
|
|
30
|
+
deploy: "pnpm run test && pnpm run build && npm publish --access=public"
|
|
31
|
+
},
|
|
32
|
+
keywords: [
|
|
33
|
+
"eslint",
|
|
34
|
+
"eslintplugin",
|
|
35
|
+
"eslint-plugin",
|
|
36
|
+
"next",
|
|
37
|
+
"nextjs",
|
|
38
|
+
"compat",
|
|
39
|
+
"browser-compatibility"
|
|
40
|
+
],
|
|
41
|
+
author: "dkpark10",
|
|
42
|
+
license: "MIT",
|
|
43
|
+
repository: {
|
|
44
|
+
type: "git",
|
|
45
|
+
url: "https://github.com/dkpark10/eslint-plugin-next-compat"
|
|
46
|
+
},
|
|
47
|
+
homepage: "https://github.com/dkpark10/eslint-plugin-next-compat#readme",
|
|
48
|
+
bugs: {
|
|
49
|
+
url: "https://github.com/dkpark10/eslint-plugin-next-compat/issues"
|
|
50
|
+
},
|
|
51
|
+
peerDependencies: {
|
|
52
|
+
eslint: "^9.0.0"
|
|
53
|
+
},
|
|
54
|
+
dependencies: {
|
|
55
|
+
"dependency-tree": "^11.4.0",
|
|
56
|
+
"eslint-plugin-compat": "^6.0.0",
|
|
57
|
+
glob: "^13.0.6",
|
|
58
|
+
minimatch: "^10.2.4"
|
|
59
|
+
},
|
|
60
|
+
devDependencies: {
|
|
61
|
+
eslint: "^9.0.0",
|
|
62
|
+
tsup: "^8.0.0",
|
|
63
|
+
vitest: "^1.6.0"
|
|
64
|
+
},
|
|
65
|
+
engines: {
|
|
66
|
+
node: ">=18.18.0"
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/index.js
|
|
73
|
+
import { globSync as globSync2 } from "glob";
|
|
74
|
+
import { minimatch } from "minimatch";
|
|
75
|
+
|
|
76
|
+
// src/get-client-files.js
|
|
77
|
+
import { globSync } from "glob";
|
|
78
|
+
import fs from "fs";
|
|
79
|
+
import path from "path";
|
|
80
|
+
import dependencyTree from "dependency-tree";
|
|
81
|
+
var USE_CLIENT_REGEX = /^(?:\s|\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)*['"]use client['"]/;
|
|
82
|
+
function getClientFiles(options = {}) {
|
|
83
|
+
const {
|
|
84
|
+
appDir = "src/app",
|
|
85
|
+
cwd = process.cwd(),
|
|
86
|
+
tsConfigPath = "tsconfig.json"
|
|
87
|
+
} = options;
|
|
88
|
+
try {
|
|
89
|
+
let getDependencies = function(fileList) {
|
|
90
|
+
return fileList.reduce(
|
|
91
|
+
(acc, filePath) => {
|
|
92
|
+
try {
|
|
93
|
+
const deps = dependencyTree.toList({
|
|
94
|
+
filename: filePath,
|
|
95
|
+
directory: cwd,
|
|
96
|
+
filter: (depPath) => !depPath.includes("node_modules") && !depPath.endsWith(".css") && !depPath.endsWith(".scss"),
|
|
97
|
+
tsConfig: fs.existsSync(tsConfigFullPath) ? tsConfigFullPath : void 0
|
|
98
|
+
});
|
|
99
|
+
return [...acc, ...deps];
|
|
100
|
+
} catch {
|
|
101
|
+
return acc;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
/** @type {string[]} */
|
|
105
|
+
[]
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
const srcPath = path.resolve(cwd, appDir);
|
|
109
|
+
const tsConfigFullPath = path.resolve(cwd, tsConfigPath);
|
|
110
|
+
const tsxFilePaths = globSync(`${srcPath}/**/*.tsx`, {
|
|
111
|
+
ignore: ["**/node_modules/**"]
|
|
112
|
+
});
|
|
113
|
+
if (tsxFilePaths.length === 0) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
const allDependencies = getDependencies(tsxFilePaths);
|
|
117
|
+
const clientFiles = allDependencies.filter((filePath) => {
|
|
118
|
+
try {
|
|
119
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
120
|
+
return USE_CLIENT_REGEX.test(content);
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const clientDependencies = getDependencies(clientFiles);
|
|
126
|
+
const uniqueFiles = [...new Set(clientDependencies)];
|
|
127
|
+
return uniqueFiles.map((filePath) => {
|
|
128
|
+
return path.relative(cwd, filePath);
|
|
129
|
+
});
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error("get-client-files error:", err);
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/index.js
|
|
137
|
+
import compatPlugin from "eslint-plugin-compat";
|
|
138
|
+
var { name, version } = require_package();
|
|
139
|
+
var PLUGIN_NAME = name.replace("eslint-plugin-", "");
|
|
140
|
+
var plugin = {
|
|
141
|
+
meta: {
|
|
142
|
+
name,
|
|
143
|
+
version
|
|
144
|
+
},
|
|
145
|
+
rules: {
|
|
146
|
+
compat: compatPlugin.rules.compat
|
|
147
|
+
},
|
|
148
|
+
configs: (
|
|
149
|
+
/** @type {NextCompatPlugin['configs']} */
|
|
150
|
+
{}
|
|
151
|
+
)
|
|
152
|
+
};
|
|
153
|
+
function getTargetFiles(include, exclude) {
|
|
154
|
+
const additionalFiles = (include == null ? void 0 : include.flatMap(
|
|
155
|
+
(pattern) => globSync2(pattern, { ignore: ["**/node_modules/**"] })
|
|
156
|
+
)) ?? [];
|
|
157
|
+
const allFiles = [.../* @__PURE__ */ new Set([...getClientFiles(), ...additionalFiles])];
|
|
158
|
+
const filteredFiles = (exclude == null ? void 0 : exclude.length) ? allFiles.filter((file) => !exclude.some((pattern) => minimatch(file, pattern))) : allFiles;
|
|
159
|
+
return filteredFiles.length > 0 ? filteredFiles : ["__no_client_files__"];
|
|
160
|
+
}
|
|
161
|
+
function createConfig(severity, options) {
|
|
162
|
+
const targetFiles = getTargetFiles(options == null ? void 0 : options.include, options == null ? void 0 : options.exclude);
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
name: `${PLUGIN_NAME}/${severity === "warn" ? "recommended" : "strict"}`,
|
|
166
|
+
files: targetFiles,
|
|
167
|
+
plugins: {
|
|
168
|
+
[PLUGIN_NAME]: plugin
|
|
169
|
+
},
|
|
170
|
+
rules: {
|
|
171
|
+
[`${PLUGIN_NAME}/compat`]: severity
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
function createConfigFunction(severity) {
|
|
177
|
+
const fn = (options) => createConfig(severity, options);
|
|
178
|
+
fn[Symbol.iterator] = function* () {
|
|
179
|
+
yield* createConfig(severity);
|
|
180
|
+
};
|
|
181
|
+
return (
|
|
182
|
+
/** @type {ConfigFunction} */
|
|
183
|
+
fn
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
plugin.configs = {
|
|
187
|
+
recommended: createConfigFunction("warn"),
|
|
188
|
+
strict: createConfigFunction("error")
|
|
189
|
+
};
|
|
190
|
+
var index_default = plugin;
|
|
191
|
+
export {
|
|
192
|
+
index_default as default,
|
|
193
|
+
plugin
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../package.json","../src/index.js","../src/get-client-files.js"],"sourcesContent":["{\n \"name\": \"eslint-plugin-next-compat\",\n \"version\": \"1.0.0-alpha.1\",\n \"description\": \"ESLint plugin for browser compatibility checking in Next.js App Router client components\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"pnpm run build\",\n \"deploy\": \"pnpm run test && pnpm run build && npm publish --access=public\"\n },\n \"keywords\": [\n \"eslint\",\n \"eslintplugin\",\n \"eslint-plugin\",\n \"next\",\n \"nextjs\",\n \"compat\",\n \"browser-compatibility\"\n ],\n \"author\": \"dkpark10\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/dkpark10/eslint-plugin-next-compat\"\n },\n \"homepage\": \"https://github.com/dkpark10/eslint-plugin-next-compat#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/dkpark10/eslint-plugin-next-compat/issues\"\n },\n \"peerDependencies\": {\n \"eslint\": \"^9.0.0\"\n },\n \"dependencies\": {\n \"dependency-tree\": \"^11.4.0\",\n \"eslint-plugin-compat\": \"^6.0.0\",\n \"glob\": \"^13.0.6\",\n \"minimatch\": \"^10.2.4\"\n },\n \"devDependencies\": {\n \"eslint\": \"^9.0.0\",\n \"tsup\": \"^8.0.0\",\n \"vitest\": \"^1.6.0\"\n },\n \"engines\": {\n \"node\": \">=18.18.0\"\n }\n}\n","import { globSync } from 'glob';\nimport { minimatch } from 'minimatch';\nimport { getClientFiles } from './get-client-files.js';\nimport compatPlugin from 'eslint-plugin-compat';\nconst { name, version } = require('../package.json');\n\nconst PLUGIN_NAME = name.replace('eslint-plugin-', '');\n\n/**\n * @typedef {Object} ConfigOptions\n * @property {string[]} [include] - Additional glob patterns to include as client files\n * @property {string[]} [exclude] - Glob patterns to exclude from client files\n */\n\n/**\n * @typedef {((options?: ConfigOptions) => import('eslint').Linter.Config[]) & { [Symbol.iterator](): Generator<import('eslint').Linter.Config> }} ConfigFunction\n */\n\n/**\n * @typedef {Object} NextCompatPlugin\n * @property {{ name: string; version: string }} meta\n * @property {Record<string, import('eslint').Rule.RuleModule>} rules\n * @property {{ recommended: ConfigFunction; strict: ConfigFunction }} configs\n */\n\n/** @type {NextCompatPlugin} */\nconst plugin = {\n meta: {\n name,\n version,\n },\n\n rules: {\n compat: compatPlugin.rules.compat,\n },\n\n configs: /** @type {NextCompatPlugin['configs']} */ ({}),\n};\n\n/**\n * @param {string[]} [include]\n * @param {string[]} [exclude]\n * @returns {string[]}\n */\nfunction getTargetFiles(include, exclude) {\n const additionalFiles = include?.flatMap((pattern) =>\n globSync(pattern, { ignore: ['**/node_modules/**'] })\n ) ?? [];\n\n const allFiles = [...new Set([...getClientFiles(), ...additionalFiles])];\n\n const filteredFiles = exclude?.length\n ? allFiles.filter((file) => !exclude.some((pattern) => minimatch(file, pattern)))\n : allFiles;\n\n return filteredFiles.length > 0 ? filteredFiles : ['__no_client_files__'];\n}\n\n/**\n * @param {'warn' | 'error'} severity\n * @param {ConfigOptions} [options]\n * @returns {import('eslint').Linter.Config[]}\n */\nfunction createConfig(severity, options) {\n const targetFiles = getTargetFiles(options?.include, options?.exclude);\n\n return [\n {\n name: `${PLUGIN_NAME}/${severity === 'warn' ? 'recommended' : 'strict'}`,\n files: targetFiles,\n plugins: {\n [PLUGIN_NAME]: plugin,\n },\n rules: {\n [`${PLUGIN_NAME}/compat`]: severity,\n },\n },\n ];\n}\n\n/**\n * Create a config function that is also iterable (for spread without calling)\n * @param {'warn' | 'error'} severity\n * @returns {ConfigFunction}\n */\nfunction createConfigFunction(severity) {\n /** @param {ConfigOptions} [options] */\n const fn = (options) => createConfig(severity, options);\n\n // Make it iterable so `...configs.recommended` works without ()\n fn[Symbol.iterator] = function* () {\n yield* createConfig(severity);\n };\n\n return /** @type {ConfigFunction} */ (fn);\n}\n\nplugin.configs = {\n recommended: createConfigFunction('warn'),\n strict: createConfigFunction('error'),\n};\n\nexport { plugin };\nexport default plugin;\n","import { globSync } from 'glob';\nimport fs from 'fs';\nimport path from 'path';\nimport dependencyTree from 'dependency-tree';\n\nconst USE_CLIENT_REGEX = /^(?:\\s|\\/\\/[^\\n]*\\n|\\/\\*[\\s\\S]*?\\*\\/)*['\"]use client['\"]/;\n\n/**\n * @typedef {Object} GetClientFilesOptions\n * @property {string} [appDir] - App directory path (default: 'src/app')\n * @property {string} [cwd] - Project root directory (default: process.cwd())\n * @property {string} [tsConfigPath] - tsconfig.json path (default: 'tsconfig.json')\n */\n\n/**\n * Get all client component files including their dependencies\n * @param {GetClientFilesOptions} [options]\n * @returns {string[]}\n */\nexport function getClientFiles(options = {}) {\n const {\n appDir = 'src/app',\n cwd = process.cwd(),\n tsConfigPath = 'tsconfig.json',\n } = options;\n\n try {\n const srcPath = path.resolve(cwd, appDir);\n const tsConfigFullPath = path.resolve(cwd, tsConfigPath);\n\n // Get all tsx files in app directory\n const tsxFilePaths = globSync(`${srcPath}/**/*.tsx`, {\n ignore: ['**/node_modules/**'],\n });\n\n if (tsxFilePaths.length === 0) {\n return [];\n }\n\n /**\n * Get dependencies for a list of files\n * @param {string[]} fileList\n * @returns {string[]}\n */\n function getDependencies(fileList) {\n return fileList.reduce((acc, filePath) => {\n try {\n const deps = dependencyTree.toList({\n filename: filePath,\n directory: cwd,\n filter: (/** @type {string} */ depPath) =>\n !depPath.includes('node_modules') &&\n !depPath.endsWith('.css') &&\n !depPath.endsWith('.scss'),\n tsConfig: fs.existsSync(tsConfigFullPath) ? tsConfigFullPath : undefined,\n });\n return [...acc, ...deps];\n } catch {\n return acc;\n }\n }, /** @type {string[]} */ ([]));\n }\n\n // Get all dependencies from tsx files\n const allDependencies = getDependencies(tsxFilePaths);\n\n // Filter files that have \"use client\" directive\n const clientFiles = allDependencies.filter((filePath) => {\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n return USE_CLIENT_REGEX.test(content);\n } catch {\n return false;\n }\n });\n\n // Get all dependencies of client files (these are also client components)\n const clientDependencies = getDependencies(clientFiles);\n\n // Return unique file paths as relative glob patterns\n const uniqueFiles = [...new Set(clientDependencies)];\n\n // Convert to relative paths for ESLint files pattern\n return uniqueFiles.map((filePath) => {\n return path.relative(cwd, filePath);\n });\n } catch (err) {\n console.error('get-client-files error:', err);\n return [];\n }\n}\n\nexport default getClientFiles;\n"],"mappings":"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","names":["globSync","globSync"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-next-compat",
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"description": "ESLint plugin for browser compatibility checking in Next.js App Router client components",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.mjs",
|
|
10
|
+
"require": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"prepublishOnly": "pnpm run build",
|
|
22
|
+
"deploy": "pnpm run test && pnpm run build && npm publish --access=public"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"eslint",
|
|
26
|
+
"eslintplugin",
|
|
27
|
+
"eslint-plugin",
|
|
28
|
+
"next",
|
|
29
|
+
"nextjs",
|
|
30
|
+
"compat",
|
|
31
|
+
"browser-compatibility"
|
|
32
|
+
],
|
|
33
|
+
"author": "dkpark10",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/dkpark10/eslint-plugin-next-compat"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/dkpark10/eslint-plugin-next-compat#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/dkpark10/eslint-plugin-next-compat/issues"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"eslint": "^9.0.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"dependency-tree": "^11.4.0",
|
|
48
|
+
"eslint-plugin-compat": "^6.0.0",
|
|
49
|
+
"glob": "^13.0.6",
|
|
50
|
+
"minimatch": "^10.2.4"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"eslint": "^9.0.0",
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"vitest": "^1.6.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18.18.0"
|
|
59
|
+
}
|
|
60
|
+
}
|