@dmitryrechkin/eslint-standard 1.1.1 → 1.1.3

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/eslint.config.mjs CHANGED
@@ -6,8 +6,8 @@ import stylisticPlugin from '@stylistic/eslint-plugin';
6
6
  import jsdocPlugin from 'eslint-plugin-jsdoc';
7
7
  import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
8
8
  import perfectionistPlugin from 'eslint-plugin-perfectionist';
9
- import jsdocIndentPlugin from './eslint-plugin-jsdoc-indent.mjs';
10
- import interfaceBracePlugin from './eslint-plugin-interface-brace.mjs';
9
+ import jsdocIndentPlugin from './src/plugins/jsdoc-indent.mjs';
10
+ import interfaceBracePlugin from './src/plugins/interface-brace.mjs';
11
11
 
12
12
  export default function ({
13
13
  tsconfigPath = './tsconfig.json',
package/package.json CHANGED
@@ -1,19 +1,24 @@
1
1
  {
2
2
  "name": "@dmitryrechkin/eslint-standard",
3
3
  "description": "This package provides a shared ESLint configuration which includes TypeScript support and a set of specific linting rules designed to ensure high-quality and consistent code style across projects.",
4
- "version": "1.1.1",
4
+ "version": "1.1.3",
5
5
  "main": "eslint.config.mjs",
6
+ "bin": {
7
+ "eslint-standard": "./src/cli/index.mjs"
8
+ },
6
9
  "files": [
7
10
  "eslint.config.mjs",
8
- "eslint-plugin-jsdoc-indent.mjs",
11
+ "src/",
9
12
  "README.md",
10
13
  "LICENSE"
11
14
  ],
12
15
  "scripts": {
13
- "postinstall": "echo 'Remember to install peer dependencies:\n npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-unused-imports @stylistic/eslint-plugin eslint-plugin-jsdoc --save-dev\n bun add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-unused-imports @stylistic/eslint-plugin eslint-plugin-jsdoc --dev'",
16
+ "postinstall": "node src/cli/postinstall.mjs",
14
17
  "package:publish": "npm publish --access public",
15
- "test": "npm run test:formatting",
16
- "test:formatting": "node tests/test-runner.js"
18
+ "test": "npm run test:formatting && npm run test:cli",
19
+ "test:formatting": "node tests/test-runner.js",
20
+ "test:cli": "node tests/test-cli.js",
21
+ "test:install": "node tests/test-install-simulation.js"
17
22
  },
18
23
  "keywords": [],
19
24
  "author": "",
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join, resolve } from 'path';
6
+ import { spawn } from 'child_process';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // Check for --install flag
12
+ const shouldInstall = process.argv.includes('--install');
13
+
14
+ // Get peer dependencies
15
+ const packageJsonPath = join(__dirname, '../../package.json');
16
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
17
+ const peerDeps = packageJson.peerDependencies || {};
18
+
19
+ // Check if running from node_modules and find project root
20
+ const isInNodeModules = __dirname.includes('node_modules');
21
+ let projectRoot;
22
+
23
+ if (isInNodeModules) {
24
+ // Handle different package manager structures
25
+ // pnpm: .pnpm/@org+pkg@version_deps/node_modules/@org/pkg
26
+ // npm/yarn: node_modules/@org/pkg
27
+ const parts = __dirname.split('node_modules');
28
+ projectRoot = parts[0].replace(/[\\/]$/, ''); // Remove trailing slash
29
+ } else {
30
+ projectRoot = process.cwd();
31
+ }
32
+
33
+ // Read project's package.json
34
+ let projectPackageJson;
35
+ try {
36
+ projectPackageJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
37
+ } catch (error) {
38
+ console.error('❌ Could not read project package.json');
39
+ process.exit(1);
40
+ }
41
+
42
+ const allDeps = {
43
+ ...projectPackageJson.dependencies || {},
44
+ ...projectPackageJson.devDependencies || {}
45
+ };
46
+
47
+ console.log('🔍 Checking ESLint Standard peer dependencies...\n');
48
+
49
+ let missingDeps = [];
50
+ let outdatedDeps = [];
51
+
52
+ for (const [dep, requiredVersion] of Object.entries(peerDeps)) {
53
+ if (!allDeps[dep]) {
54
+ missingDeps.push(`${dep}@${requiredVersion}`);
55
+ console.log(`❌ Missing: ${dep} (required: ${requiredVersion})`);
56
+ } else {
57
+ console.log(`✅ Found: ${dep}@${allDeps[dep]}`);
58
+ // Simple version check - could be improved
59
+ if (!allDeps[dep].includes('^') && !allDeps[dep].includes('~') && allDeps[dep] !== requiredVersion) {
60
+ outdatedDeps.push(`${dep} (installed: ${allDeps[dep]}, required: ${requiredVersion})`);
61
+ }
62
+ }
63
+ }
64
+
65
+ console.log('\n📊 Summary:');
66
+ if (missingDeps.length === 0 && outdatedDeps.length === 0) {
67
+ console.log('✅ All peer dependencies are satisfied!');
68
+ } else {
69
+ if (missingDeps.length > 0) {
70
+ console.log(`\n❌ Missing ${missingDeps.length} dependencies:`);
71
+ missingDeps.forEach(dep => console.log(` - ${dep}`));
72
+
73
+ if (shouldInstall) {
74
+ console.log('\n🔧 Auto-installing missing dependencies...\n');
75
+
76
+ // Import and run the install-deps script
77
+ try {
78
+ const installDepsModule = await import('./install-deps.mjs');
79
+ // The install-deps script will handle the installation
80
+ } catch (error) {
81
+ console.error('❌ Failed to auto-install dependencies:', error.message);
82
+ process.exit(1);
83
+ }
84
+ } else {
85
+ console.log('\n💡 To install missing dependencies:');
86
+ console.log(' npx @dmitryrechkin/eslint-standard install-deps');
87
+ console.log(' or run this command with --install flag');
88
+ }
89
+ }
90
+ if (outdatedDeps.length > 0) {
91
+ console.log(`\n⚠️ ${outdatedDeps.length} dependencies may be outdated:`);
92
+ outdatedDeps.forEach(dep => console.log(` - ${dep}`));
93
+ }
94
+
95
+ if (!shouldInstall && missingDeps.length > 0) {
96
+ process.exit(1);
97
+ }
98
+ }
99
+
100
+ export default function checkDeps() {
101
+ // Export for programmatic use
102
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ const command = process.argv[2];
4
+
5
+ switch (command) {
6
+ case 'install-deps':
7
+ await import('./install-deps.mjs');
8
+ break;
9
+ case 'check-deps':
10
+ await import('./check-deps.mjs');
11
+ break;
12
+ case 'help':
13
+ case '--help':
14
+ case '-h':
15
+ case undefined:
16
+ console.log(`
17
+ @dmitryrechkin/eslint-standard CLI
18
+
19
+ Usage:
20
+ npx @dmitryrechkin/eslint-standard <command>
21
+
22
+ Commands:
23
+ install-deps Install all peer dependencies
24
+ check-deps Check if all peer dependencies are installed
25
+ check-deps --install Auto-install missing dependencies if any
26
+ help Show this help message
27
+
28
+ Examples:
29
+ npx @dmitryrechkin/eslint-standard install-deps
30
+ npx @dmitryrechkin/eslint-standard check-deps
31
+ npx @dmitryrechkin/eslint-standard check-deps --install
32
+ `);
33
+ break;
34
+ default:
35
+ console.error(`Unknown command: ${command}`);
36
+ console.log('Run "npx @dmitryrechkin/eslint-standard help" for usage information');
37
+ process.exit(1);
38
+ }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'child_process';
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // Detect package manager
12
+ function detectPackageManager() {
13
+ if (existsSync('pnpm-lock.yaml')) return 'pnpm';
14
+ if (existsSync('yarn.lock')) return 'yarn';
15
+ if (existsSync('package-lock.json')) return 'npm';
16
+ if (existsSync('bun.lockb')) return 'bun';
17
+ return 'npm'; // default
18
+ }
19
+
20
+ // Get peer dependencies from package.json
21
+ const packageJsonPath = join(__dirname, '../../package.json');
22
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
23
+ const peerDeps = packageJson.peerDependencies || {};
24
+
25
+ // Build install command
26
+ const packageManager = detectPackageManager();
27
+ const deps = Object.entries(peerDeps).map(([name, version]) => `${name}@${version}`).join(' ');
28
+
29
+ const installCommands = {
30
+ npm: `npm install --save-dev ${deps}`,
31
+ pnpm: `pnpm add -D ${deps}`,
32
+ yarn: `yarn add -D ${deps}`,
33
+ bun: `bun add -d ${deps}`
34
+ };
35
+
36
+ const command = installCommands[packageManager];
37
+
38
+ console.log(`🔧 Installing ESLint Standard peer dependencies...`);
39
+ console.log(`📦 Detected package manager: ${packageManager}`);
40
+ console.log(`📋 Running: ${command}\n`);
41
+
42
+ try {
43
+ execSync(command, { stdio: 'inherit' });
44
+ console.log('\n✅ All peer dependencies installed successfully!');
45
+ } catch (error) {
46
+ console.error('\n❌ Failed to install dependencies. Please run manually:');
47
+ console.error(command);
48
+ process.exit(1);
49
+ }
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, existsSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import { execSync } from 'child_process';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // Don't run during local development
12
+ if (!__dirname.includes('node_modules')) {
13
+ process.exit(0);
14
+ }
15
+
16
+ // Check if we're in CI environment
17
+ const isCI = process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS;
18
+
19
+ // Check if scripts are disabled
20
+ const npmConfigIgnoreScripts = process.env.npm_config_ignore_scripts === 'true';
21
+
22
+ if (isCI || npmConfigIgnoreScripts) {
23
+ // Silent exit in CI or when scripts are disabled
24
+ process.exit(0);
25
+ }
26
+
27
+ // Simple check for missing dependencies
28
+ try {
29
+ const packageJsonPath = join(__dirname, '../../package.json');
30
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
31
+ const peerDeps = Object.keys(packageJson.peerDependencies || {});
32
+
33
+ // Quick check if any peer deps are missing
34
+ let missingCount = 0;
35
+ for (const dep of peerDeps) {
36
+ try {
37
+ // Try to resolve the dependency
38
+ require.resolve(dep, { paths: [process.cwd()] });
39
+ } catch {
40
+ missingCount++;
41
+ }
42
+ }
43
+
44
+ if (missingCount > 0) {
45
+ console.log(`
46
+ ⚠️ @dmitryrechkin/eslint-standard: ${missingCount} peer dependencies are missing!
47
+
48
+ Run this command to check and install them:
49
+ 👉 ${'\x1b[36m'}npx @dmitryrechkin/eslint-standard check-deps --install${'\x1b[0m'}
50
+ `);
51
+ }
52
+ } catch (error) {
53
+ // Silent fail - don't break installation
54
+ }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ // Don't run during local development
10
+ if (!__dirname.includes('node_modules')) {
11
+ process.exit(0);
12
+ }
13
+
14
+ // Check environment variables for auto-install preference
15
+ const autoInstall = process.env.ESLINT_STANDARD_AUTO_INSTALL === 'true';
16
+ const skipInstall = process.env.ESLINT_STANDARD_SKIP_INSTALL === 'true';
17
+
18
+ // Check if we're in CI environment
19
+ const isCI = process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS;
20
+
21
+ if (skipInstall || isCI) {
22
+ process.exit(0);
23
+ }
24
+
25
+ console.log(`
26
+ ╔════════════════════════════════════════════════════════════════╗
27
+ ║ ║
28
+ ║ 🎉 Thanks for installing @dmitryrechkin/eslint-standard! ║
29
+ ║ ║
30
+ ╚════════════════════════════════════════════════════════════════╝
31
+
32
+ 📦 This package requires peer dependencies to work properly.
33
+
34
+ 🚀 Quick install (auto-detects your package manager):
35
+ ${'\x1b[36m'}npx @dmitryrechkin/eslint-standard check-deps --install${'\x1b[0m'}
36
+
37
+ 📋 Or install all dependencies directly:
38
+ ${'\x1b[90m'}npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-unused-imports @stylistic/eslint-plugin eslint-plugin-jsdoc eslint-plugin-simple-import-sort eslint-plugin-perfectionist${'\x1b[0m'}
39
+
40
+ 🔍 To check if all dependencies are installed:
41
+ ${'\x1b[36m'}npx @dmitryrechkin/eslint-standard check-deps${'\x1b[0m'}
42
+
43
+ 📚 Documentation: https://github.com/dmitryrechkin/eslint-standard
44
+ `);
45
+
46
+ // Don't run check-deps in postinstall to avoid errors
47
+ // Users can run it manually with: npx @dmitryrechkin/eslint-standard check-deps
48
+
49
+ if (autoInstall) {
50
+ console.log('\n🤖 Auto-install enabled via ESLINT_STANDARD_AUTO_INSTALL=true');
51
+ console.log('📦 Checking and installing peer dependencies...\n');
52
+
53
+ try {
54
+ // Run check-deps with --install flag
55
+ const { execSync } = await import('child_process');
56
+ execSync('node ' + join(__dirname, 'index.mjs') + ' check-deps --install', {
57
+ stdio: 'inherit',
58
+ cwd: process.cwd()
59
+ });
60
+ } catch (error) {
61
+ console.error('❌ Auto-install failed. Please run manually:');
62
+ console.error(' npx @dmitryrechkin/eslint-standard check-deps --install');
63
+ }
64
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Custom ESLint plugin to enforce Allman brace style for TypeScript interfaces
3
+ */
4
+
5
+ const interfaceBraceRule = {
6
+ meta: {
7
+ type: 'layout',
8
+ docs: {
9
+ description: 'Enforce Allman brace style for TypeScript interfaces',
10
+ category: 'Stylistic Issues',
11
+ recommended: false
12
+ },
13
+ fixable: 'whitespace',
14
+ schema: []
15
+ },
16
+
17
+ create(context) {
18
+ const sourceCode = context.sourceCode || context.getSourceCode();
19
+
20
+ return {
21
+ TSInterfaceDeclaration(node) {
22
+ // Get the interface name token
23
+ const interfaceId = node.id;
24
+ const typeParams = node.typeParameters;
25
+ const extendsClause = node.extends;
26
+
27
+ // Find the opening brace
28
+ let tokenBeforeBrace;
29
+ if (node.heritage && node.heritage.length > 0) {
30
+ // Interface extends something
31
+ tokenBeforeBrace = node.heritage[node.heritage.length - 1];
32
+ } else if (extendsClause && extendsClause.length > 0) {
33
+ // Interface extends something (newer AST)
34
+ tokenBeforeBrace = extendsClause[extendsClause.length - 1];
35
+ } else if (typeParams) {
36
+ // Interface has type parameters
37
+ tokenBeforeBrace = typeParams;
38
+ } else {
39
+ // Simple interface
40
+ tokenBeforeBrace = interfaceId;
41
+ }
42
+
43
+ // Get the opening brace token
44
+ const openingBrace = sourceCode.getTokenAfter(tokenBeforeBrace, token => token.type === 'Punctuator' && token.value === '{');
45
+
46
+ if (!openingBrace) return;
47
+
48
+ // Check if there's a newline before the opening brace
49
+ const tokenBefore = sourceCode.getTokenBefore(openingBrace);
50
+ const textBetween = sourceCode.text.slice(tokenBefore.range[1], openingBrace.range[0]);
51
+
52
+ // Check if brace is on the same line
53
+ if (!textBetween.includes('\n')) {
54
+ context.report({
55
+ node: openingBrace,
56
+ message: 'Opening brace should be on a new line (Allman style)',
57
+ fix(fixer) {
58
+ // Get the base indentation of the interface declaration
59
+ const interfaceLine = sourceCode.getLines()[node.loc.start.line - 1];
60
+ const baseIndentMatch = interfaceLine.match(/^(\s*)/);
61
+ const baseIndent = baseIndentMatch ? baseIndentMatch[1] : '';
62
+
63
+ // Replace the space before the brace with a newline and proper indentation
64
+ return fixer.replaceTextRange(
65
+ [tokenBefore.range[1], openingBrace.range[0]],
66
+ '\n' + baseIndent
67
+ );
68
+ }
69
+ });
70
+ }
71
+ },
72
+
73
+ // Also handle TSTypeAliasDeclaration with object type
74
+ TSTypeAliasDeclaration(node) {
75
+ if (node.typeAnnotation && node.typeAnnotation.type === 'TSTypeLiteral') {
76
+ const typeParams = node.typeParameters;
77
+ const tokenBeforeBrace = typeParams ? typeParams : node.id;
78
+
79
+ // Find the equals sign
80
+ const equalsToken = sourceCode.getTokenAfter(tokenBeforeBrace, token => token.type === 'Punctuator' && token.value === '=');
81
+
82
+ if (!equalsToken) return;
83
+
84
+ // Get the opening brace token
85
+ const openingBrace = sourceCode.getTokenAfter(equalsToken, token => token.type === 'Punctuator' && token.value === '{');
86
+
87
+ if (!openingBrace) return;
88
+
89
+ // Check if there's a newline before the opening brace
90
+ const textBetween = sourceCode.text.slice(equalsToken.range[1], openingBrace.range[0]);
91
+
92
+ // Check if brace is on the same line
93
+ if (!textBetween.includes('\n')) {
94
+ context.report({
95
+ node: openingBrace,
96
+ message: 'Opening brace should be on a new line (Allman style)',
97
+ fix(fixer) {
98
+ // Get the base indentation
99
+ const typeLine = sourceCode.getLines()[node.loc.start.line - 1];
100
+ const baseIndentMatch = typeLine.match(/^(\s*)/);
101
+ const baseIndent = baseIndentMatch ? baseIndentMatch[1] : '';
102
+
103
+ // Replace the space before the brace with a newline and proper indentation
104
+ return fixer.replaceTextRange(
105
+ [equalsToken.range[1], openingBrace.range[0]],
106
+ ' =\n' + baseIndent
107
+ );
108
+ }
109
+ });
110
+ }
111
+ }
112
+ }
113
+ };
114
+ }
115
+ };
116
+
117
+ export default {
118
+ rules: {
119
+ 'interface-brace-style': interfaceBraceRule
120
+ }
121
+ };