es-guard 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) 2025 Max Kayander
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,198 @@
1
+ # ES-Guard
2
+
3
+ [![codecov](https://codecov.io/gh/mkayander/es-guard/branch/main/graph/badge.svg)](https://codecov.io/gh/mkayander/es-guard)
4
+
5
+ A TypeScript-based tool to check JavaScript compatibility with target environments using ESLint.
6
+
7
+ ## Features
8
+
9
+ - 🔍 **ES Version Compatibility**: Check if your JavaScript code is compatible with specific ES versions (ES2015, ES2016, ES2017, etc.)
10
+ - 🌐 **Browser Compatibility**: Verify browser support using eslint-plugin-compat
11
+ - 🎯 **Auto Browser Detection**: Automatically determine browser targets from ES version (optional)
12
+ - 📁 **Directory Scanning**: Automatically scan directories for JavaScript files
13
+ - 🎯 **GitHub Actions Ready**: Works seamlessly with GitHub Actions
14
+ - 📦 **NPM Package**: Install globally or use as a dependency
15
+
16
+ ## Installation
17
+
18
+ ### Global Installation
19
+
20
+ ```bash
21
+ npm install -g es-guard
22
+ ```
23
+
24
+ ### Local Installation
25
+
26
+ ```bash
27
+ npm install --save-dev es-guard
28
+ ```
29
+
30
+ ### From Source
31
+
32
+ ```bash
33
+ git clone https://github.com/mkayander/es-guard.git
34
+ cd es-guard
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Command Line
42
+
43
+ ```bash
44
+ # Basic usage with defaults (auto-determined browsers)
45
+ es-guard
46
+
47
+ # Check specific directory
48
+ es-guard build
49
+
50
+ # Specify target ES version (year format)
51
+ es-guard -t 2020 build
52
+
53
+ # Specify target ES version (numeric format)
54
+ es-guard -t 11 build
55
+
56
+ # Use latest ES version
57
+ es-guard -t latest build
58
+
59
+ # Specify custom browser targets
60
+ es-guard --browsers "> 0.5%, last 2 versions, Firefox ESR, not dead" dist
61
+
62
+ # Show help
63
+ es-guard --help
64
+
65
+ # Show version
66
+ es-guard --version
67
+ ```
68
+
69
+ ### Programmatic Usage
70
+
71
+ ```typescript
72
+ import { checkCompatibility } from "es-guard";
73
+
74
+ // With auto-determined browsers
75
+ const violations = await checkCompatibility({
76
+ dir: "dist",
77
+ target: "2015",
78
+ });
79
+
80
+ // With custom browsers
81
+ const violations = await checkCompatibility({
82
+ dir: "dist",
83
+ target: "2015",
84
+ browsers: "> 1%, last 2 versions, not dead, ie 11",
85
+ });
86
+ ```
87
+
88
+ ### GitHub Actions
89
+
90
+ ```yaml
91
+ name: Check Compatibility
92
+ on: [push, pull_request]
93
+
94
+ jobs:
95
+ compatibility:
96
+ runs-on: ubuntu-latest
97
+ steps:
98
+ - uses: actions/checkout@v3
99
+ - uses: actions/setup-node@v3
100
+ with:
101
+ node-version: "18"
102
+ - run: npm install
103
+ - run: npm run build
104
+ - run: npx es-guard -t 2015 dist
105
+ ```
106
+
107
+ ## Configuration
108
+
109
+ ### Parameters
110
+
111
+ | Parameter | Description | Default | Required |
112
+ | ---------- | ------------------------------------------ | --------------------------- | -------- |
113
+ | `path` | Directory to scan for JavaScript files | `dist` | No |
114
+ | `target` | Target ES version | `2015` | Yes |
115
+ | `browsers` | Browser targets for compatibility checking | Auto-determined from target | No |
116
+
117
+ ### ES Target Versions
118
+
119
+ The `target` parameter accepts multiple formats:
120
+
121
+ - **Year format**: `2015`, `2016`, `2017`, etc.
122
+ - **Numeric format**: `6` (ES2015), `7` (ES2016), `11` (ES2020), etc.
123
+ - **Latest**: `latest` for the most recent ES version
124
+
125
+ ### Browser Targets
126
+
127
+ The `browsers` parameter uses the same format as Browserslist. If not specified, browsers will be automatically determined based on the ES target:
128
+
129
+ - **ES2015/ES6**: `> 1%, last 2 versions, not dead, ie 11`
130
+ - **ES2016-2017/ES7-8**: `> 1%, last 2 versions, not dead, not ie 11`
131
+ - **ES2018-2019/ES9-10**: `> 1%, last 2 versions, not dead, not ie 11, not op_mini all`
132
+ - **ES2020+/ES11+**: `> 1%, last 2 versions, not dead, not ie 11, not op_mini all, not android < 67`
133
+
134
+ Custom browser targets examples:
135
+
136
+ - `> 1%, last 2 versions, not dead, ie 11` - Modern browsers + IE11
137
+ - `> 0.5%, last 2 versions, Firefox ESR, not dead` - Broader support
138
+ - `defaults` - Default Browserslist targets
139
+ - `last 1 version` - Latest version of each browser
140
+
141
+ ## Development
142
+
143
+ ### Setup
144
+
145
+ ```bash
146
+ npm install
147
+ ```
148
+
149
+ ### Build
150
+
151
+ ```bash
152
+ npm run build
153
+ ```
154
+
155
+ ### Development Mode
156
+
157
+ ```bash
158
+ npm run dev
159
+ ```
160
+
161
+ ### Testing
162
+
163
+ ```bash
164
+ npm test
165
+ npm run test:run
166
+ ```
167
+
168
+ ### Linting
169
+
170
+ ```bash
171
+ npm run lint
172
+ ```
173
+
174
+ ## Publishing to NPM
175
+
176
+ 1. Update the version in `package.json`
177
+ 2. Update the repository URL in `package.json`
178
+ 3. Build the project: `npm run build`
179
+ 4. Publish: `npm publish`
180
+
181
+ ## License
182
+
183
+ MIT License - see [LICENSE](LICENSE) file for details.
184
+
185
+ ## Contributing
186
+
187
+ 1. Fork the repository
188
+ 2. Create a feature branch
189
+ 3. Make your changes
190
+ 4. Add tests if applicable
191
+ 5. Run the linter: `npm run lint`
192
+ 6. Submit a pull request
193
+
194
+ ## Support
195
+
196
+ - 📖 [Documentation](https://github.com/mkayander/es-guard#readme)
197
+ - 🐛 [Issues](https://github.com/mkayander/es-guard/issues)
198
+ - 💬 [Discussions](https://github.com/mkayander/es-guard/discussions)
package/dist/cli.js ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import * as fs from "fs";
4
+ import packageJson from "../package.json" with { type: "json" };
5
+ import { checkCompatibility } from "./lib/checkCompatiblity.js";
6
+ import { getBrowserTargetsFromString } from "./lib/getBrowserTargets.js";
7
+ const version = packageJson.version;
8
+ // Create the main program
9
+ const program = new Command();
10
+ // Configure the program
11
+ program
12
+ .name("es-guard")
13
+ .description("JavaScript Compatibility Checker - Check if your JavaScript code is compatible with target environments")
14
+ .version(version)
15
+ .argument("[directory]", "Directory to scan for JavaScript files", "dist")
16
+ .option("-t, --target <version>", "Target ES version (2015, 2016, 2017, etc. or 6, 7, 8, etc. or 'latest')", "2015")
17
+ .option("-b, --browsers <targets>", "Browser targets for compatibility checking (optional: auto-determined from target)")
18
+ .addHelpText("after", `
19
+
20
+ Examples:
21
+ es-guard # Check 'dist' directory with ES2015 (auto-determined browsers)
22
+ es-guard build # Check 'build' directory with ES2015 (auto-determined browsers)
23
+ es-guard -t 2020 build # Check 'build' directory with ES2020 (auto-determined browsers)
24
+ es-guard -t 6 build # Check 'build' directory with ES6 (auto-determined browsers)
25
+ es-guard -t latest build # Check 'build' directory with latest ES (auto-determined browsers)
26
+ es-guard --target 2017 --browsers "> 0.5%, last 2 versions" dist
27
+
28
+ Browser targets use Browserslist format:
29
+ - If not specified, browsers will be auto-determined from the ES target version
30
+ - "> 1%, last 2 versions, not dead, ie 11" (for ES2015/ES6)
31
+ - "> 1%, last 2 versions, not dead, not ie 11" (for ES2016-2017/ES7-8)
32
+ - "> 1%, last 2 versions, not dead, not ie 11, not op_mini all" (for ES2018-2019/ES9-10)
33
+ - "> 1%, last 2 versions, not dead, not ie 11, not op_mini all, not android < 67" (for ES2020+/ES11+)
34
+
35
+ Exit codes:
36
+ 0 - No compatibility issues found
37
+ 1 - Compatibility issues found or error occurred
38
+ `);
39
+ // Add validation for the target option
40
+ program.hook("preAction", (thisCommand) => {
41
+ const options = thisCommand.opts();
42
+ const target = options.target;
43
+ // Validate ES target format - accept year format (YYYY), numeric format (N), or "latest"
44
+ if (!/^\d{4}$/.test(target) && !/^\d+$/.test(target) && target !== "latest") {
45
+ console.error(`Error: Invalid ES target: "${target}". Expected format: YYYY (e.g., 2015, 2020), numeric (e.g., 6, 11), or "latest"`);
46
+ process.exit(1);
47
+ }
48
+ });
49
+ // Main action
50
+ program.action(async (directory, options) => {
51
+ try {
52
+ // Validate directory exists
53
+ if (!fs.existsSync(directory)) {
54
+ console.error(`Error: Directory "${directory}" does not exist`);
55
+ process.exit(1);
56
+ }
57
+ const stat = fs.statSync(directory);
58
+ if (!stat.isDirectory()) {
59
+ console.error(`Error: "${directory}" is not a directory`);
60
+ process.exit(1);
61
+ }
62
+ // Determine browser targets
63
+ const browserTargets = options.browsers || getBrowserTargetsFromString(options.target);
64
+ console.log(`🔍 ES-Guard v${version}`);
65
+ console.log(`📁 Scanning directory: ${directory}`);
66
+ console.log(`🎯 Target ES version: ${options.target}`);
67
+ console.log(`🌐 Browser targets: ${browserTargets}${options.browsers ? "" : " (auto-determined)"}`);
68
+ console.log("");
69
+ const violations = await checkCompatibility({
70
+ dir: directory,
71
+ target: options.target,
72
+ browsers: browserTargets,
73
+ });
74
+ if (violations.length > 0) {
75
+ console.error(`❌ Found ${violations.length} file(s) with compatibility issues:`);
76
+ for (const violation of violations) {
77
+ console.error(`\n📄 ${violation.file}:`);
78
+ for (const message of violation.messages) {
79
+ console.error(` ${message.line}:${message.column} - ${message.message} (${message.ruleId})`);
80
+ }
81
+ }
82
+ process.exit(1);
83
+ }
84
+ else {
85
+ console.log("✅ No compatibility issues found!");
86
+ }
87
+ }
88
+ catch (error) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ console.error(`❌ Error: ${message}`);
91
+ process.exit(1);
92
+ }
93
+ });
94
+ // Parse command line arguments
95
+ program.parse();
@@ -0,0 +1,34 @@
1
+ import { ESLint } from "eslint";
2
+ import { createESLintConfig } from "./createESLintConfig.js";
3
+ import { walkDir } from "./walkDir.js";
4
+ export const checkCompatibility = async (config) => {
5
+ const jsFiles = walkDir(config.dir);
6
+ if (jsFiles.length === 0) {
7
+ console.log(`No JavaScript files found in directory: ${config.dir}`);
8
+ return [];
9
+ }
10
+ const eslint = new ESLint(createESLintConfig(config.target, config.browsers));
11
+ const violations = [];
12
+ for (const file of jsFiles) {
13
+ try {
14
+ const results = await eslint.lintFiles([file]);
15
+ if (Array.isArray(results)) {
16
+ for (const result of results) {
17
+ if (result.messages.length > 0) {
18
+ violations.push({
19
+ file: result.filePath,
20
+ messages: result.messages,
21
+ });
22
+ }
23
+ }
24
+ }
25
+ else {
26
+ console.warn(`Warning: ESLint did not return an array for file ${file}.`, results);
27
+ }
28
+ }
29
+ catch (error) {
30
+ console.warn(`Warning: Could not lint file ${file}:`, error);
31
+ }
32
+ }
33
+ return violations;
34
+ };
@@ -0,0 +1,36 @@
1
+ import compat from "eslint-plugin-compat";
2
+ import { getBrowserTargetsFromString, parseEcmaVersion } from "./getBrowserTargets.js";
3
+ import { isValidEcmaVersion } from "./isValidEcmaVersion.js";
4
+ const getEcmaVersion = (target) => {
5
+ const ecmaVersion = parseEcmaVersion(target);
6
+ if (!isValidEcmaVersion(ecmaVersion)) {
7
+ throw new Error(`Invalid ECMAScript version: ${ecmaVersion}. Target year ${target} is not supported.`);
8
+ }
9
+ return ecmaVersion;
10
+ };
11
+ export const createESLintConfig = (target, browsers) => {
12
+ // Convert target year to ECMAScript version number using the validation function
13
+ const ecmaVersion = getEcmaVersion(target);
14
+ // Use provided browsers or auto-determine from target
15
+ const browserTargets = browsers || getBrowserTargetsFromString(target);
16
+ return {
17
+ overrideConfigFile: true,
18
+ overrideConfig: [
19
+ {
20
+ plugins: {
21
+ compat,
22
+ },
23
+ rules: {
24
+ "compat/compat": "error",
25
+ },
26
+ languageOptions: {
27
+ ecmaVersion: ecmaVersion,
28
+ sourceType: "module",
29
+ },
30
+ settings: {
31
+ browsers: browserTargets,
32
+ },
33
+ },
34
+ ],
35
+ };
36
+ };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Converts a string target to Linter.EcmaVersion type.
3
+ * Handles year format (2015), numeric format (6), and "latest".
4
+ * Returns null for unknown values to indicate they should use default.
5
+ */
6
+ export const parseEcmaVersion = (target) => {
7
+ // Handle "latest"
8
+ if (target === "latest") {
9
+ return "latest";
10
+ }
11
+ const num = parseInt(target);
12
+ // Handle year format (2015, 2016, etc.)
13
+ if (num >= 2015 && num <= 2026) {
14
+ return num;
15
+ }
16
+ // Handle numeric format (3, 5, 6, 7, etc.)
17
+ if (num >= 3 && num <= 17) {
18
+ return num;
19
+ }
20
+ // For unknown values, return null to indicate default should be used
21
+ return null;
22
+ };
23
+ /**
24
+ * Maps ES target versions to appropriate browser targets for compatibility checking.
25
+ * This provides sensible defaults based on when ES features were widely supported.
26
+ */
27
+ export const getBrowserTargets = (target) => {
28
+ switch (target) {
29
+ // Legacy versions
30
+ case 3:
31
+ return "> 0.1%, ie 6";
32
+ case 5:
33
+ return "> 0.5%, ie 8";
34
+ // ES2015 (ES6) - Conservative targets including IE11
35
+ case 6:
36
+ case 2015:
37
+ return "> 1%, last 2 versions, not dead, ie 11";
38
+ // ES2016-2017 (ES7-8) - Drop IE11 but keep other older browsers
39
+ case 7:
40
+ case 8:
41
+ case 2016:
42
+ case 2017:
43
+ return "> 1%, last 2 versions, not dead, not ie 11";
44
+ // ES2018-2019 (ES9-10) - More modern browsers
45
+ case 9:
46
+ case 10:
47
+ case 2018:
48
+ case 2019:
49
+ return "> 1%, last 2 versions, not dead, not ie 11, not op_mini all";
50
+ // ES2020+ (ES11+) - Latest browsers with good modern JS support
51
+ case 11:
52
+ case 12:
53
+ case 13:
54
+ case 14:
55
+ case 15:
56
+ case 16:
57
+ case 17:
58
+ case 2020:
59
+ case 2021:
60
+ case 2022:
61
+ case 2023:
62
+ case 2024:
63
+ case 2025:
64
+ case 2026:
65
+ case "latest":
66
+ return "> 1%, last 2 versions, not dead, not ie 11, not op_mini all, not android < 67";
67
+ // Default fallback for unknown versions
68
+ default:
69
+ return "> 1%, last 2 versions, not dead";
70
+ }
71
+ };
72
+ /**
73
+ * Convenience function that combines parsing and browser target determination.
74
+ * Accepts string input and returns appropriate browser targets.
75
+ */
76
+ export const getBrowserTargetsFromString = (target) => {
77
+ const ecmaVersion = parseEcmaVersion(target);
78
+ return getBrowserTargets(ecmaVersion);
79
+ };
@@ -0,0 +1,13 @@
1
+ const validVersions = new Set([
2
+ 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025,
3
+ 2026,
4
+ ]);
5
+ export const isValidEcmaVersion = (ecmaVersion) => {
6
+ if (!ecmaVersion) {
7
+ return false;
8
+ }
9
+ if (typeof ecmaVersion === "string") {
10
+ return ecmaVersion === "latest";
11
+ }
12
+ return validVersions.has(ecmaVersion);
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import * as fs from "fs";
2
+ export const validateConfig = (config) => {
3
+ if (!fs.existsSync(config.dir)) {
4
+ throw new Error(`Output directory "${config.dir}" does not exist. Please build the project first.`);
5
+ }
6
+ if (!config.target) {
7
+ throw new Error("Target ES version is required");
8
+ }
9
+ };
@@ -0,0 +1,22 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export const walkDir = (dir) => {
4
+ const files = [];
5
+ try {
6
+ const entries = fs.readdirSync(dir);
7
+ for (const entry of entries) {
8
+ const fullPath = path.join(dir, entry);
9
+ const stat = fs.statSync(fullPath);
10
+ if (stat.isDirectory()) {
11
+ files.push(...walkDir(fullPath));
12
+ }
13
+ else if (fullPath.endsWith(".js")) {
14
+ files.push(fullPath);
15
+ }
16
+ }
17
+ }
18
+ catch (error) {
19
+ console.warn(`Warning: Could not read directory ${dir}:`, error);
20
+ }
21
+ return files;
22
+ };
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "es-guard",
3
+ "version": "1.0.0",
4
+ "description": "A tool to check JavaScript compatibility with target environments",
5
+ "type": "module",
6
+ "main": "dist/cli.js",
7
+ "bin": {
8
+ "es-guard": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch",
16
+ "start": "node dist/cli.js",
17
+ "test": "npm run build && vitest",
18
+ "test:run": "npm run build && vitest run",
19
+ "coverage": "vitest run --coverage",
20
+ "coverage:check": "vitest run --coverage --reporter=verbose",
21
+ "lint": "eslint src/**/*.ts",
22
+ "lint:check": "eslint src/**/*.ts --max-warnings 0",
23
+ "format": "prettier --write src/**/*.ts",
24
+ "format:check": "prettier --check src/**/*.ts",
25
+ "clean": "rimraf dist",
26
+ "prepublishOnly": "npm run clean && npm run build && npm run coverage:check",
27
+ "semantic-release": "semantic-release"
28
+ },
29
+ "keywords": [
30
+ "javascript",
31
+ "compatibility",
32
+ "eslint",
33
+ "browser-support",
34
+ "es2015",
35
+ "es2016",
36
+ "es2017",
37
+ "es2018",
38
+ "es2019",
39
+ "es2020"
40
+ ],
41
+ "author": "",
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "@actions/core": "^1.11.1",
45
+ "commander": "^14.0.0",
46
+ "eslint": "^9.30.1",
47
+ "eslint-plugin-compat": "^6.0.2"
48
+ },
49
+ "devDependencies": {
50
+ "@eslint/js": "^9.30.1",
51
+ "@semantic-release/changelog": "^6.0.3",
52
+ "@semantic-release/git": "^10.0.1",
53
+ "@types/eslint": "^9.6.1",
54
+ "@types/node": "^20.19.4",
55
+ "@typescript-eslint/eslint-plugin": "^8.35.1",
56
+ "@typescript-eslint/parser": "^8.35.1",
57
+ "@vitest/coverage-v8": "^3.2.4",
58
+ "prettier": "^3.6.2",
59
+ "rimraf": "^6.0.1",
60
+ "semantic-release": "^24.2.6",
61
+ "typescript": "5.8.3",
62
+ "vitest": "^3.2.4"
63
+ },
64
+ "engines": {
65
+ "node": ">=14.0.0"
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+https://github.com/mkayander/es-guard.git"
70
+ },
71
+ "bugs": {
72
+ "url": "https://github.com/mkayander/es-guard/issues"
73
+ },
74
+ "homepage": "https://github.com/mkayander/es-guard#readme",
75
+ "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"
76
+ }