react19-compat-linter 1.0.0

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) Scott Mikula
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,55 @@
1
+ # react19-compat-linter
2
+
3
+ React 19 removes several deprecated APIs that were available in React 18 and earlier (`ReactDOM.render`, `findDOMNode`, etc.) Before upgrading to React 19, you need to remove these from your code—you might use a lint rule for this. But you also need to make sure that _all bundled dependencies_ are clean—and this problem is much harder.
4
+
5
+ This package solves this by providing a utility to scan your dependencies to identify compatibility issues. At a high level, it is composed of two parts: (1) a webpack plugin that emits a list of modules to lint, and (2) a utility that runs ESLint over those modules and reports on incompatible packages.
6
+
7
+ > **Why split the functionality like this?** While webpack is the most common bundler, you may be using something else. By decoupling the linting from the bundler, you can produce your own module list and still reuse the linting functionality. And linting is slow; you may want to separate it from your webpack process anyway.
8
+
9
+ ## Getting Started
10
+
11
+ ### 1. Install the package
12
+
13
+ ```bash
14
+ # NPM
15
+ npm install --save-dev react19-compat-linter
16
+
17
+ # Yarn
18
+ yarn add --dev react19-compat-linter
19
+ ```
20
+
21
+ ### 2. Configure the webpack plugin
22
+
23
+ Add the `DependencyModuleListPlugin` to your webpack configuration to extract a list of all modules used in your build:
24
+
25
+ ```javascript
26
+ const { DependencyModuleListPlugin } = require('react19-compat-linter');
27
+ const outputFile = './modules-list.json';
28
+
29
+ module.exports = {
30
+ // ... your webpack config
31
+ plugins: [
32
+ new DependencyModuleListPlugin(outputFile)
33
+ ]
34
+ };
35
+ ```
36
+
37
+ When you run webpack, this plugin will generate a JSON file containing all bundled JavaScript (or TypeScript) modules from packages in the `node_modules` directory.
38
+
39
+ ### 3. Run the linter
40
+
41
+ You can run the linter via the command line:
42
+
43
+ ```bash
44
+ npm react19-compat-linter ./modules-list.json
45
+ ```
46
+
47
+ Or integrate it into your build via the API:
48
+
49
+ ```javascript
50
+ const { runLinter } = require('react19-compat-linter');
51
+
52
+ runLinter('./modules-list.json').then(result => {
53
+ console.log(`Found violations in ${result.packages.length} packages.`);
54
+ });
55
+ ```
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { cli } = require('../lib/cli');
4
+
5
+ cli(process.argv.slice(2));
@@ -0,0 +1,6 @@
1
+ import webpack from 'webpack';
2
+ export declare class DependencyModuleListPlugin {
3
+ private filePath;
4
+ constructor(filePath: string);
5
+ apply(compiler: webpack.Compiler): void;
6
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DependencyModuleListPlugin = void 0;
13
+ const webpack_1 = require("webpack");
14
+ const promises_1 = require("fs/promises");
15
+ const path_1 = require("path");
16
+ class DependencyModuleListPlugin {
17
+ constructor(filePath) {
18
+ this.filePath = filePath;
19
+ }
20
+ apply(compiler) {
21
+ compiler.hooks.thisCompilation.tap('DependencyModuleListPlugin', compilation => {
22
+ compilation.hooks.processAssets.tapPromise({
23
+ name: 'DependencyModuleListPlugin',
24
+ stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT,
25
+ }, () => __awaiter(this, void 0, void 0, function* () {
26
+ const modules = [];
27
+ for (const module of compilation.modules) {
28
+ const resource = module.resource;
29
+ if (resource && shouldIncludeModule(resource)) {
30
+ modules.push(resource);
31
+ }
32
+ }
33
+ const jsonContent = JSON.stringify(modules, null, 2);
34
+ yield (0, promises_1.mkdir)((0, path_1.dirname)(this.filePath), { recursive: true });
35
+ yield (0, promises_1.writeFile)(this.filePath, jsonContent, 'utf-8');
36
+ }));
37
+ });
38
+ }
39
+ }
40
+ exports.DependencyModuleListPlugin = DependencyModuleListPlugin;
41
+ const jsExtensions = /\.(js|jsx|ts|tsx|mjs|cjs)$/i;
42
+ function shouldIncludeModule(resource) {
43
+ return jsExtensions.test(resource) && resource.includes('node_modules');
44
+ }
package/lib/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function cli(args: string[]): Promise<void>;
package/lib/cli.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.cli = cli;
13
+ const runLinter_1 = require("./runLinter");
14
+ function cli(args) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ if (args.length === 0) {
17
+ console.error('Error: Please provide the path to the modules list file');
18
+ console.error('Usage: react19-compat-linter <modules-list.json>');
19
+ process.exit(1);
20
+ }
21
+ const modulesListPath = args[0];
22
+ try {
23
+ const result = yield (0, runLinter_1.runLinter)(modulesListPath);
24
+ console.log(`Found violations in ${result.packages.length} packages:\n`);
25
+ result.packages.forEach(pkg => {
26
+ console.log(`${pkg.packageName} (${pkg.files.length} files):`);
27
+ pkg.files.forEach(file => {
28
+ console.log(` ${file.filePath}:`);
29
+ file.violations.forEach(violation => {
30
+ console.log(` ${violation.line}:${violation.column} - ${violation.message}`);
31
+ });
32
+ });
33
+ console.log('');
34
+ });
35
+ process.exit(0);
36
+ }
37
+ catch (error) {
38
+ console.error('Error running linter:', error);
39
+ process.exit(1);
40
+ }
41
+ });
42
+ }
@@ -0,0 +1,13 @@
1
+ declare const _default: {
2
+ plugins: {
3
+ 'react19-compat-linter': {
4
+ rules: {
5
+ 'no-restricted-imports': any;
6
+ };
7
+ };
8
+ };
9
+ rules: {
10
+ 'react19-compat-linter/no-restricted-imports': "error";
11
+ };
12
+ }[];
13
+ export = _default;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ const no_restricted_imports_1 = require("./no-restricted-imports");
3
+ module.exports = [
4
+ {
5
+ plugins: {
6
+ 'react19-compat-linter': {
7
+ rules: {
8
+ // Cast to `any` because the rule type from @typescript-eslint/utils doesn't quite match
9
+ 'no-restricted-imports': no_restricted_imports_1.noRestrictedImports,
10
+ },
11
+ },
12
+ },
13
+ rules: {
14
+ 'react19-compat-linter/no-restricted-imports': 'error',
15
+ },
16
+ },
17
+ ];
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { DependencyModuleListPlugin } from './DependencyModuleListPlugin';
2
+ export { runLinter } from './runLinter';
3
+ export type { LinterResult, LinterPackageResult, LinterFileResult, LinterViolation } from './types';
package/lib/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runLinter = exports.DependencyModuleListPlugin = void 0;
4
+ var DependencyModuleListPlugin_1 = require("./DependencyModuleListPlugin");
5
+ Object.defineProperty(exports, "DependencyModuleListPlugin", { enumerable: true, get: function () { return DependencyModuleListPlugin_1.DependencyModuleListPlugin; } });
6
+ var runLinter_1 = require("./runLinter");
7
+ Object.defineProperty(exports, "runLinter", { enumerable: true, get: function () { return runLinter_1.runLinter; } });
@@ -0,0 +1,9 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const noRestrictedImports: ESLintUtils.RuleModule<"restrictedImport" | "restrictedNamespaceAccess" | "restrictedDestructuring", [{
3
+ restrictedImports: {
4
+ module: string;
5
+ imports: string[];
6
+ }[];
7
+ }], unknown, ESLintUtils.RuleListener> & {
8
+ name: string;
9
+ };
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noRestrictedImports = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ exports.noRestrictedImports = utils_1.ESLintUtils.RuleCreator(f => f)({
6
+ name: 'no-restricted-imports',
7
+ meta: {
8
+ type: 'problem',
9
+ messages: {
10
+ restrictedImport: 'Importing {{importName}} from "{{moduleName}}" is not allowed.',
11
+ restrictedNamespaceAccess: 'Accessing {{importName}} from {{namespace}} is not allowed.',
12
+ restrictedDestructuring: 'Destructuring {{importName}} from {{namespace}} is not allowed.',
13
+ },
14
+ schema: [
15
+ {
16
+ type: 'object',
17
+ properties: {
18
+ restrictedImports: {
19
+ type: 'array',
20
+ items: {
21
+ type: 'object',
22
+ properties: {
23
+ module: {
24
+ type: 'string',
25
+ },
26
+ imports: {
27
+ type: 'array',
28
+ items: {
29
+ type: 'string',
30
+ },
31
+ },
32
+ },
33
+ required: ['module', 'imports'],
34
+ additionalProperties: false,
35
+ },
36
+ },
37
+ },
38
+ additionalProperties: false,
39
+ required: ['restrictedImports'],
40
+ },
41
+ ],
42
+ docs: {
43
+ description: 'Restricts specific imports from specified modules.',
44
+ },
45
+ },
46
+ defaultOptions: [
47
+ {
48
+ restrictedImports: [
49
+ {
50
+ module: 'react-dom',
51
+ imports: ['findDOMNode', 'render', 'unmountComponentAtNode'],
52
+ },
53
+ ],
54
+ },
55
+ ],
56
+ create(context, options) {
57
+ const restrictedImports = options[0].restrictedImports;
58
+ const namespaceIdentifiers = new Map();
59
+ return {
60
+ ImportDeclaration(node) {
61
+ const moduleName = node.source.value;
62
+ // Find if this module has any restricted imports
63
+ const restriction = restrictedImports.find(r => r.module === moduleName);
64
+ if (!restriction) {
65
+ return;
66
+ }
67
+ // Check each import specifier
68
+ node.specifiers.forEach(specifier => {
69
+ if (specifier.type === 'ImportSpecifier') {
70
+ const importedName = specifier.imported.type === 'Identifier'
71
+ ? specifier.imported.name
72
+ : specifier.imported.value;
73
+ if (restriction.imports.includes(importedName)) {
74
+ context.report({
75
+ node: specifier,
76
+ messageId: 'restrictedImport',
77
+ data: {
78
+ importName: importedName,
79
+ moduleName: moduleName,
80
+ },
81
+ });
82
+ }
83
+ }
84
+ else if (specifier.type === 'ImportDefaultSpecifier' ||
85
+ specifier.type === 'ImportNamespaceSpecifier') {
86
+ // Track namespace/default imports for this module
87
+ // e.g., "import React from 'react'" or "import * as React from 'react'"
88
+ namespaceIdentifiers.set(specifier.local.name, moduleName);
89
+ }
90
+ });
91
+ },
92
+ MemberExpression(node) {
93
+ // Check if accessing a restricted import through a namespace identifier
94
+ // e.g., ReactDOM.render
95
+ if (node.object.type === 'Identifier' && node.property.type === 'Identifier') {
96
+ const namespaceName = node.object.name;
97
+ const propertyName = node.property.name;
98
+ // Check if this identifier is a tracked namespace
99
+ const moduleName = namespaceIdentifiers.get(namespaceName);
100
+ if (!moduleName) {
101
+ return;
102
+ }
103
+ // Find if this module has restrictions
104
+ const restriction = restrictedImports.find(r => r.module === moduleName);
105
+ if (!restriction) {
106
+ return;
107
+ }
108
+ // Check if the accessed property is in the restricted imports list
109
+ if (restriction.imports.includes(propertyName)) {
110
+ context.report({
111
+ node,
112
+ messageId: 'restrictedNamespaceAccess',
113
+ data: {
114
+ importName: propertyName,
115
+ namespace: namespaceName,
116
+ },
117
+ });
118
+ }
119
+ }
120
+ },
121
+ VariableDeclarator(node) {
122
+ // Check for destructuring from a namespace identifier
123
+ // e.g., const { findDOMNode } = ReactDOM;
124
+ if (node.id.type === 'ObjectPattern' &&
125
+ node.init &&
126
+ node.init.type === 'Identifier') {
127
+ const namespaceName = node.init.name;
128
+ // Check if this identifier is a tracked namespace
129
+ const moduleName = namespaceIdentifiers.get(namespaceName);
130
+ if (!moduleName) {
131
+ return;
132
+ }
133
+ // Find if this module has restrictions
134
+ const restriction = restrictedImports.find(r => r.module === moduleName);
135
+ if (!restriction) {
136
+ return;
137
+ }
138
+ // Check each destructured property
139
+ node.id.properties.forEach(prop => {
140
+ if (prop.type === 'Property' && prop.key.type === 'Identifier') {
141
+ const propertyName = prop.key.name;
142
+ if (restriction.imports.includes(propertyName)) {
143
+ context.report({
144
+ node: prop,
145
+ messageId: 'restrictedDestructuring',
146
+ data: {
147
+ importName: propertyName,
148
+ namespace: namespaceName,
149
+ },
150
+ });
151
+ }
152
+ }
153
+ });
154
+ }
155
+ },
156
+ };
157
+ },
158
+ });
@@ -0,0 +1,2 @@
1
+ import type { LinterResult } from './types';
2
+ export declare function runLinter(modulesList: string): Promise<LinterResult>;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.runLinter = runLinter;
46
+ const eslint_1 = require("eslint");
47
+ const path = __importStar(require("path"));
48
+ const extractPackageName_1 = require("./utils/extractPackageName");
49
+ function runLinter(modulesList) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ const eslint = new eslint_1.ESLint({
52
+ overrideConfigFile: path.resolve(__dirname, './eslint.config.js'),
53
+ ignorePatterns: ['!**/node_modules/'],
54
+ concurrency: 'auto',
55
+ });
56
+ const allResults = yield eslint.lintFiles(modulesList);
57
+ // Filter for results containing the react19-compat-linter/no-restricted-imports rule violation
58
+ const results = allResults
59
+ .map(result => (Object.assign(Object.assign({}, result), { messages: result.messages.filter(msg => msg.ruleId === 'react19-compat-linter/no-restricted-imports') })))
60
+ .filter(result => result.messages.length > 0);
61
+ const files = results.map(result => ({
62
+ filePath: result.filePath,
63
+ violations: result.messages.map(msg => ({
64
+ line: msg.line,
65
+ column: msg.column,
66
+ message: msg.message,
67
+ })),
68
+ }));
69
+ // Group files by package
70
+ const packageMap = new Map();
71
+ for (const file of files) {
72
+ const packageName = (0, extractPackageName_1.extractPackageName)(file.filePath);
73
+ if (!packageMap.has(packageName)) {
74
+ packageMap.set(packageName, []);
75
+ }
76
+ packageMap.get(packageName).push(file);
77
+ }
78
+ // Convert to array of package results
79
+ const packages = Array.from(packageMap.entries()).map(([packageName, files]) => ({
80
+ packageName,
81
+ files,
82
+ }));
83
+ // Sort packages by name for consistent output
84
+ packages.sort((a, b) => a.packageName.localeCompare(b.packageName));
85
+ return {
86
+ packages,
87
+ };
88
+ });
89
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export interface LinterViolation {
2
+ line: number;
3
+ column: number;
4
+ message: string;
5
+ }
6
+ export interface LinterFileResult {
7
+ filePath: string;
8
+ violations: LinterViolation[];
9
+ }
10
+ export interface LinterPackageResult {
11
+ packageName: string;
12
+ files: LinterFileResult[];
13
+ }
14
+ export interface LinterResult {
15
+ packages: LinterPackageResult[];
16
+ }
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export declare function extractPackageName(filePath: string): string;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractPackageName = extractPackageName;
37
+ const path = __importStar(require("path"));
38
+ function extractPackageName(filePath) {
39
+ const parts = filePath.split(path.sep);
40
+ const nodeModulesIndex = parts.lastIndexOf('node_modules');
41
+ if (nodeModulesIndex === -1) {
42
+ throw new Error(`Path does not contain node_modules: ${filePath}`);
43
+ }
44
+ // Handle scoped packages (@scope/package-name)
45
+ const packageParts = parts.slice(nodeModulesIndex + 1);
46
+ if (packageParts[0].startsWith('@')) {
47
+ return packageParts.slice(0, 2).join('/');
48
+ }
49
+ // Handle regular packages
50
+ return packageParts[0];
51
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "react19-compat-linter",
3
+ "version": "1.0.0",
4
+ "description": "Utility to validate project depedencies are React 19 compatible",
5
+ "main": "./lib/index.js",
6
+ "typings": "./lib/index.d.ts",
7
+ "bin": {
8
+ "react19-compat-linter": "./bin/react19-compat-linter.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "jest",
13
+ "prepublishOnly": "yarn test && yarn build"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/smikula/react19-compat-linter.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/smikula/react19-compat-linter/issues"
21
+ },
22
+ "homepage": "https://github.com/smikula/react19-compat-linter#readme",
23
+ "keywords": [
24
+ "react",
25
+ "react19",
26
+ "compatibility",
27
+ "linter",
28
+ "eslint",
29
+ "webpack",
30
+ "migration",
31
+ "deprecated",
32
+ "react-dom"
33
+ ],
34
+ "files": [
35
+ "lib/",
36
+ "bin/",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "author": "Scott Mikula <mikula@gmail.com>",
44
+ "dependencies": {
45
+ "@typescript-eslint/utils": "^8.54.0",
46
+ "webpack": "^5.105.0",
47
+ "webpack-cli": "^6.0.1"
48
+ },
49
+ "devDependencies": {
50
+ "@types/eslint": "^9.6.1",
51
+ "@types/jest": "^30.0.0",
52
+ "@types/node": "^22.19.7",
53
+ "@typescript-eslint/parser": "^8.54.0",
54
+ "@typescript-eslint/rule-tester": "^8.54.0",
55
+ "eslint": "^9.39.2",
56
+ "husky": "^4.3.8",
57
+ "jest": "^30.2.0",
58
+ "prettier": "^3.8.1",
59
+ "pretty-quick": "^4.2.2",
60
+ "ts-jest": "^29.4.6",
61
+ "typescript": "^5.9.3"
62
+ },
63
+ "license": "MIT",
64
+ "husky": {
65
+ "hooks": {
66
+ "pre-commit": "pretty-quick --staged"
67
+ }
68
+ }
69
+ }