@theglitchking/gimme-the-lint 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/.claude-plugin/commands/lint-baseline.md +33 -0
- package/.claude-plugin/commands/lint-status.md +26 -0
- package/.claude-plugin/commands/lint.md +40 -0
- package/.claude-plugin/plugin.json +48 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/agents/linting-agent.md +24 -0
- package/bin/gimme-the-lint.js +220 -0
- package/bin/postinstall.js +29 -0
- package/githooks/install.sh +43 -0
- package/githooks/pre-commit +49 -0
- package/githooks/pre-push +39 -0
- package/install.sh +58 -0
- package/lib/config-manager.js +98 -0
- package/lib/directory-discovery.js +120 -0
- package/lib/drift-detector.js +92 -0
- package/lib/git-hooks-manager.js +115 -0
- package/lib/index.js +19 -0
- package/lib/installer.js +110 -0
- package/lib/manifest-manager.js +58 -0
- package/lib/venv-manager.js +94 -0
- package/package.json +82 -0
- package/scripts/dashboard.sh +168 -0
- package/scripts/eslint-baseline.sh +227 -0
- package/scripts/ruff-baseline.sh +226 -0
- package/scripts/run-checks.sh +291 -0
- package/scripts/setup-venv.sh +81 -0
- package/scripts/validate-version.sh +46 -0
- package/templates/.gitleaks.template.toml +71 -0
- package/templates/.pre-commit-config.template.yaml +51 -0
- package/templates/commitlint.config.template.js +24 -0
- package/templates/eslint.config.template.js +124 -0
- package/templates/pyproject.template.toml +61 -0
- package/templates/requirements.linting.txt +11 -0
- package/uninstall.sh +54 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { Command } = require('commander');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
|
|
9
|
+
const pkg = require('../package.json');
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
const SCRIPTS_DIR = path.join(__dirname, '..', 'scripts');
|
|
13
|
+
|
|
14
|
+
function runScript(name, args = '') {
|
|
15
|
+
const script = path.join(SCRIPTS_DIR, name);
|
|
16
|
+
try {
|
|
17
|
+
execSync(`bash "${script}" ${args}`, { stdio: 'inherit', cwd: process.cwd() });
|
|
18
|
+
} catch (e) {
|
|
19
|
+
process.exit(e.status || 1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name('gimme-the-lint')
|
|
25
|
+
.description(pkg.description)
|
|
26
|
+
.version(pkg.version);
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command('install')
|
|
30
|
+
.description('Install gimme-the-lint into the current project')
|
|
31
|
+
.option('--scope <scope>', 'Installation scope: project or user', 'project')
|
|
32
|
+
.option('--frontend', 'Frontend only')
|
|
33
|
+
.option('--backend', 'Backend only')
|
|
34
|
+
.option('--force', 'Overwrite existing configs')
|
|
35
|
+
.action(async (opts) => {
|
|
36
|
+
const installer = require('../lib/installer');
|
|
37
|
+
const chalk = require('chalk');
|
|
38
|
+
|
|
39
|
+
console.log(chalk.blue('\ngimme-the-lint: Installing progressive linting system...\n'));
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const result = await installer.init(process.cwd(), {
|
|
43
|
+
frontend: opts.frontend !== undefined ? true : undefined,
|
|
44
|
+
backend: opts.backend !== undefined ? true : undefined,
|
|
45
|
+
force: opts.force,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (const step of result.steps) {
|
|
49
|
+
console.log(chalk.green(' ✓ ') + step);
|
|
50
|
+
}
|
|
51
|
+
for (const err of result.errors) {
|
|
52
|
+
console.log(chalk.yellow(' ⚠ ') + err);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(chalk.green('\n✓ Installation complete!\n'));
|
|
56
|
+
console.log('Next steps:');
|
|
57
|
+
console.log(' gimme-the-lint baseline Create LTTF baselines');
|
|
58
|
+
console.log(' gimme-the-lint hooks Install git hooks');
|
|
59
|
+
console.log(' gimme-the-lint dashboard View linting status');
|
|
60
|
+
console.log('');
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(chalk.red(`\n✗ Installation failed: ${e.message}\n`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command('uninstall')
|
|
69
|
+
.description('Remove gimme-the-lint from the current project')
|
|
70
|
+
.action(async () => {
|
|
71
|
+
const chalk = require('chalk');
|
|
72
|
+
const gitHooksManager = require('../lib/git-hooks-manager');
|
|
73
|
+
|
|
74
|
+
console.log(chalk.blue('\ngimme-the-lint: Uninstalling...\n'));
|
|
75
|
+
|
|
76
|
+
const removed = await gitHooksManager.uninstallHooks(process.cwd());
|
|
77
|
+
if (removed.length > 0) {
|
|
78
|
+
console.log(chalk.green(` ✓ Removed git hooks: ${removed.join(', ')}`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const configPath = path.join(process.cwd(), 'gimme-the-lint.config.js');
|
|
82
|
+
if (fs.existsSync(configPath)) {
|
|
83
|
+
fs.unlinkSync(configPath);
|
|
84
|
+
console.log(chalk.green(' ✓ Removed gimme-the-lint.config.js'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(chalk.green('\n✓ Uninstall complete.\n'));
|
|
88
|
+
console.log('Note: Baseline files, linter configs, and .venv were NOT removed.');
|
|
89
|
+
console.log('Remove manually if desired.');
|
|
90
|
+
console.log('');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
program
|
|
94
|
+
.command('init')
|
|
95
|
+
.description('Initialize linting configuration (alias for install)')
|
|
96
|
+
.option('--frontend', 'Frontend only')
|
|
97
|
+
.option('--backend', 'Backend only')
|
|
98
|
+
.option('--force', 'Overwrite existing configs')
|
|
99
|
+
.action(async (opts) => {
|
|
100
|
+
// Delegate to install
|
|
101
|
+
await program.commands.find((c) => c.name() === 'install').parseAsync(['node', 'cmd', ...(opts.frontend ? ['--frontend'] : []), ...(opts.backend ? ['--backend'] : []), ...(opts.force ? ['--force'] : [])]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
program
|
|
105
|
+
.command('check')
|
|
106
|
+
.description('Run progressive linting checks')
|
|
107
|
+
.option('--fix', 'Auto-fix violations')
|
|
108
|
+
.option('--verbose', 'Show detailed output')
|
|
109
|
+
.option('--frontend-only', 'Frontend only')
|
|
110
|
+
.option('--backend-only', 'Backend only')
|
|
111
|
+
.option('--all', 'Lint entire codebase')
|
|
112
|
+
.action((opts) => {
|
|
113
|
+
const args = [];
|
|
114
|
+
if (opts.fix) args.push('--fix');
|
|
115
|
+
if (opts.verbose) args.push('--verbose');
|
|
116
|
+
if (opts.frontendOnly) args.push('--frontend-only');
|
|
117
|
+
if (opts.backendOnly) args.push('--backend-only');
|
|
118
|
+
if (opts.all) args.push('--all');
|
|
119
|
+
runScript('run-checks.sh', args.join(' '));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
program
|
|
123
|
+
.command('baseline [target]')
|
|
124
|
+
.description('Create LTTF baselines (frontend, backend, or both)')
|
|
125
|
+
.action((target) => {
|
|
126
|
+
if (target === 'frontend') {
|
|
127
|
+
runScript('eslint-baseline.sh');
|
|
128
|
+
} else if (target === 'backend') {
|
|
129
|
+
runScript('ruff-baseline.sh');
|
|
130
|
+
} else {
|
|
131
|
+
runScript('eslint-baseline.sh');
|
|
132
|
+
runScript('ruff-baseline.sh');
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
program
|
|
137
|
+
.command('dashboard')
|
|
138
|
+
.description('Show progressive linting status dashboard')
|
|
139
|
+
.action(() => {
|
|
140
|
+
runScript('dashboard.sh');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
program
|
|
144
|
+
.command('hooks')
|
|
145
|
+
.description('Install git hooks for pre-commit linting')
|
|
146
|
+
.action(async () => {
|
|
147
|
+
const chalk = require('chalk');
|
|
148
|
+
const gitHooksManager = require('../lib/git-hooks-manager');
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const installed = await gitHooksManager.installHooks(process.cwd());
|
|
152
|
+
console.log(chalk.green(`\n✓ Installed git hooks: ${installed.join(', ')}\n`));
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.error(chalk.red(`\n✗ ${e.message}\n`));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
program
|
|
160
|
+
.command('venv [action]')
|
|
161
|
+
.description('Manage Python virtual environment (setup, status)')
|
|
162
|
+
.action((action) => {
|
|
163
|
+
if (action === 'status') {
|
|
164
|
+
const venvManager = require('../lib/venv-manager');
|
|
165
|
+
const chalk = require('chalk');
|
|
166
|
+
const status = venvManager.getStatus(process.cwd());
|
|
167
|
+
|
|
168
|
+
console.log(chalk.blue('\nPython Virtual Environment Status:\n'));
|
|
169
|
+
console.log(` Exists: ${status.exists ? chalk.green('yes') : chalk.red('no')}`);
|
|
170
|
+
console.log(` Path: ${status.path}`);
|
|
171
|
+
if (status.pythonVersion) console.log(` Python: ${status.pythonVersion}`);
|
|
172
|
+
if (status.ruffVersion) console.log(` Ruff: ${status.ruffVersion}`);
|
|
173
|
+
console.log('');
|
|
174
|
+
} else {
|
|
175
|
+
runScript('setup-venv.sh');
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
program
|
|
180
|
+
.command('status')
|
|
181
|
+
.description('Show overall gimme-the-lint status')
|
|
182
|
+
.action(async () => {
|
|
183
|
+
const chalk = require('chalk');
|
|
184
|
+
const venvManager = require('../lib/venv-manager');
|
|
185
|
+
const gitHooksManager = require('../lib/git-hooks-manager');
|
|
186
|
+
const configManager = require('../lib/config-manager');
|
|
187
|
+
|
|
188
|
+
const projectRoot = process.cwd();
|
|
189
|
+
const projectType = await configManager.detectProjectType(projectRoot);
|
|
190
|
+
const venvStatus = venvManager.getStatus(projectRoot);
|
|
191
|
+
const hookStatus = await gitHooksManager.getStatus(projectRoot);
|
|
192
|
+
const configExists = fs.existsSync(path.join(projectRoot, 'gimme-the-lint.config.js'));
|
|
193
|
+
|
|
194
|
+
console.log(chalk.blue('\ngimme-the-lint Status\n'));
|
|
195
|
+
console.log(` Project type: ${projectType}`);
|
|
196
|
+
console.log(` Config: ${configExists ? chalk.green('found') : chalk.yellow('not found')}`);
|
|
197
|
+
console.log(` Python venv: ${venvStatus.exists ? chalk.green('active') : chalk.yellow('missing')}`);
|
|
198
|
+
if (venvStatus.ruffVersion) console.log(` Ruff: ${venvStatus.ruffVersion}`);
|
|
199
|
+
console.log(` Git repo: ${hookStatus.gitRepo ? chalk.green('yes') : chalk.red('no')}`);
|
|
200
|
+
if (hookStatus.gitRepo) {
|
|
201
|
+
for (const [hook, status] of Object.entries(hookStatus.hooks)) {
|
|
202
|
+
const color = status === 'installed' ? chalk.green : status === 'other' ? chalk.yellow : chalk.red;
|
|
203
|
+
console.log(` ${hook}: ${color(status)}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
console.log('');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
program
|
|
210
|
+
.command('help-text')
|
|
211
|
+
.description('Show help')
|
|
212
|
+
.action(() => {
|
|
213
|
+
program.help();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
program.parse(process.argv);
|
|
217
|
+
|
|
218
|
+
if (!process.argv.slice(2).length) {
|
|
219
|
+
program.help();
|
|
220
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Post-install script for gimme-the-lint
|
|
5
|
+
// Runs after npm install to show next steps
|
|
6
|
+
// Does NOT auto-setup venv (user should opt-in via `gimme-the-lint install`)
|
|
7
|
+
|
|
8
|
+
const isGlobal = process.env.npm_config_global === 'true';
|
|
9
|
+
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log(' gimme-the-lint installed successfully!');
|
|
12
|
+
console.log('');
|
|
13
|
+
|
|
14
|
+
if (isGlobal) {
|
|
15
|
+
console.log(' Global install detected. Usage:');
|
|
16
|
+
console.log(' cd your-project');
|
|
17
|
+
console.log(' gimme-the-lint install Initialize configs & venv');
|
|
18
|
+
console.log(' gimme-the-lint baseline Create linting baselines');
|
|
19
|
+
console.log(' gimme-the-lint hooks Install git hooks');
|
|
20
|
+
} else {
|
|
21
|
+
console.log(' Next steps:');
|
|
22
|
+
console.log(' npx gimme-the-lint install Initialize configs & venv');
|
|
23
|
+
console.log(' npx gimme-the-lint baseline Create linting baselines');
|
|
24
|
+
console.log(' npx gimme-the-lint hooks Install git hooks');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log(' Documentation: https://github.com/TheGlitchKing/gimme-the-lint');
|
|
29
|
+
console.log('');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gimme-the-lint: Git Hooks Installer
|
|
3
|
+
# Usage: bash githooks/install.sh
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
8
|
+
if [ -z "$PROJECT_ROOT" ]; then
|
|
9
|
+
echo "Error: Not a git repository."
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
HOOKS_DIR="${PROJECT_ROOT}/.git/hooks"
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
|
|
16
|
+
echo "gimme-the-lint: Installing git hooks..."
|
|
17
|
+
|
|
18
|
+
for hook in pre-commit pre-push; do
|
|
19
|
+
src="${SCRIPT_DIR}/${hook}"
|
|
20
|
+
dest="${HOOKS_DIR}/${hook}"
|
|
21
|
+
|
|
22
|
+
if [ ! -f "$src" ]; then
|
|
23
|
+
continue
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [ -f "$dest" ]; then
|
|
27
|
+
if ! grep -q "gimme-the-lint" "$dest"; then
|
|
28
|
+
backup="${dest}.backup.$(date +%s)"
|
|
29
|
+
cp "$dest" "$backup"
|
|
30
|
+
echo " Backed up existing ${hook} -> $(basename "$backup")"
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
cp "$src" "$dest"
|
|
35
|
+
chmod +x "$dest"
|
|
36
|
+
echo " Installed: ${hook}"
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
echo ""
|
|
40
|
+
echo "Git hooks installed!"
|
|
41
|
+
echo " pre-commit: Lints changed files on commit"
|
|
42
|
+
echo " pre-push: Full lint on push"
|
|
43
|
+
echo ""
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gimme-the-lint: Pre-Commit Hook
|
|
3
|
+
# Runs progressive linting on changed files before every commit
|
|
4
|
+
# Bypass: git commit --no-verify
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
|
|
7
|
+
|
|
8
|
+
# Look for gimme-the-lint scripts in node_modules or local
|
|
9
|
+
SCRIPT=""
|
|
10
|
+
if [ -f "${PROJECT_ROOT}/node_modules/@theglitchking/gimme-the-lint/scripts/run-checks.sh" ]; then
|
|
11
|
+
SCRIPT="${PROJECT_ROOT}/node_modules/@theglitchking/gimme-the-lint/scripts/run-checks.sh"
|
|
12
|
+
elif [ -f "${PROJECT_ROOT}/node_modules/gimme-the-lint/scripts/run-checks.sh" ]; then
|
|
13
|
+
SCRIPT="${PROJECT_ROOT}/node_modules/gimme-the-lint/scripts/run-checks.sh"
|
|
14
|
+
elif command -v gimme-the-lint &>/dev/null; then
|
|
15
|
+
# Global install
|
|
16
|
+
GIMME_PROJECT_ROOT="$PROJECT_ROOT" gimme-the-lint check
|
|
17
|
+
exit $?
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if [ -z "$SCRIPT" ]; then
|
|
21
|
+
echo "gimme-the-lint: run-checks.sh not found, skipping"
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "Running pre-commit checks (gimme-the-lint)..."
|
|
26
|
+
echo ""
|
|
27
|
+
|
|
28
|
+
export GIMME_PROJECT_ROOT="$PROJECT_ROOT"
|
|
29
|
+
|
|
30
|
+
if bash "$SCRIPT"; then
|
|
31
|
+
echo ""
|
|
32
|
+
echo "Pre-commit checks passed!"
|
|
33
|
+
exit 0
|
|
34
|
+
else
|
|
35
|
+
echo ""
|
|
36
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
37
|
+
echo "Pre-commit checks failed - violations detected"
|
|
38
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
39
|
+
echo ""
|
|
40
|
+
echo " Step 1: gimme-the-lint check --fix"
|
|
41
|
+
echo " Step 2: git add <fixed-files>"
|
|
42
|
+
echo " Step 3: git commit -m \"your message\""
|
|
43
|
+
echo ""
|
|
44
|
+
echo " For LLMs: AUTOMATICALLY run --fix without asking"
|
|
45
|
+
echo ""
|
|
46
|
+
echo " Bypass (emergency): git commit --no-verify"
|
|
47
|
+
echo ""
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gimme-the-lint: Pre-Push Hook
|
|
3
|
+
# Runs full codebase lint before push (more thorough than pre-commit)
|
|
4
|
+
# Bypass: git push --no-verify
|
|
5
|
+
|
|
6
|
+
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
|
|
7
|
+
|
|
8
|
+
SCRIPT=""
|
|
9
|
+
if [ -f "${PROJECT_ROOT}/node_modules/@theglitchking/gimme-the-lint/scripts/run-checks.sh" ]; then
|
|
10
|
+
SCRIPT="${PROJECT_ROOT}/node_modules/@theglitchking/gimme-the-lint/scripts/run-checks.sh"
|
|
11
|
+
elif [ -f "${PROJECT_ROOT}/node_modules/gimme-the-lint/scripts/run-checks.sh" ]; then
|
|
12
|
+
SCRIPT="${PROJECT_ROOT}/node_modules/gimme-the-lint/scripts/run-checks.sh"
|
|
13
|
+
elif command -v gimme-the-lint &>/dev/null; then
|
|
14
|
+
GIMME_PROJECT_ROOT="$PROJECT_ROOT" gimme-the-lint check --all
|
|
15
|
+
exit $?
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ -z "$SCRIPT" ]; then
|
|
19
|
+
echo "gimme-the-lint: run-checks.sh not found, skipping"
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo "Running pre-push checks (gimme-the-lint --all)..."
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
export GIMME_PROJECT_ROOT="$PROJECT_ROOT"
|
|
27
|
+
|
|
28
|
+
if bash "$SCRIPT" --all; then
|
|
29
|
+
echo ""
|
|
30
|
+
echo "Pre-push checks passed!"
|
|
31
|
+
exit 0
|
|
32
|
+
else
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Pre-push checks failed. Fix violations before pushing."
|
|
35
|
+
echo " gimme-the-lint check --fix --all"
|
|
36
|
+
echo ""
|
|
37
|
+
echo " Bypass: git push --no-verify"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
package/install.sh
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gimme-the-lint: Global Installation Script
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/TheGlitchKing/gimme-the-lint/main/install.sh | bash
|
|
4
|
+
# Or: ./install.sh [--scope user|project]
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
BLUE='\033[0;34m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
NC='\033[0m'
|
|
13
|
+
|
|
14
|
+
SCOPE="project"
|
|
15
|
+
for arg in "$@"; do
|
|
16
|
+
case $arg in
|
|
17
|
+
--scope) shift; SCOPE="$1"; shift ;;
|
|
18
|
+
--scope=*) SCOPE="${arg#*=}" ;;
|
|
19
|
+
esac
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
echo -e "${BLUE}gimme-the-lint: Installing...${NC}"
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
# Check Node.js
|
|
26
|
+
if ! command -v node &>/dev/null; then
|
|
27
|
+
echo -e "${RED}Node.js not found. Please install Node.js 18+.${NC}"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
NODE_VERSION=$(node -v | grep -oP '\d+' | head -1)
|
|
32
|
+
if [ "$NODE_VERSION" -lt 18 ]; then
|
|
33
|
+
echo -e "${RED}Node.js 18+ required. Found: $(node -v)${NC}"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
echo -e "${GREEN}✓${NC} Node.js $(node -v)"
|
|
37
|
+
|
|
38
|
+
# Install via npm
|
|
39
|
+
if [ "$SCOPE" = "user" ]; then
|
|
40
|
+
echo -e "${BLUE}Installing globally...${NC}"
|
|
41
|
+
npm install -g @theglitchking/gimme-the-lint
|
|
42
|
+
else
|
|
43
|
+
echo -e "${BLUE}Installing as dev dependency...${NC}"
|
|
44
|
+
npm install --save-dev @theglitchking/gimme-the-lint
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
echo -e "${GREEN}✓ gimme-the-lint installed!${NC}"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Next steps:"
|
|
51
|
+
if [ "$SCOPE" = "user" ]; then
|
|
52
|
+
echo " gimme-the-lint install Initialize in current project"
|
|
53
|
+
else
|
|
54
|
+
echo " npx gimme-the-lint install Initialize configs & venv"
|
|
55
|
+
fi
|
|
56
|
+
echo " npx gimme-the-lint baseline Create linting baselines"
|
|
57
|
+
echo " npx gimme-the-lint hooks Install git hooks"
|
|
58
|
+
echo ""
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
7
|
+
|
|
8
|
+
async function copyTemplate(templateName, destPath, substitutions = {}) {
|
|
9
|
+
const templatePath = path.join(TEMPLATES_DIR, templateName);
|
|
10
|
+
|
|
11
|
+
if (!await fs.pathExists(templatePath)) {
|
|
12
|
+
throw new Error(`Template not found: ${templateName}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let content = await fs.readFile(templatePath, 'utf8');
|
|
16
|
+
|
|
17
|
+
for (const [key, value] of Object.entries(substitutions)) {
|
|
18
|
+
content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
22
|
+
await fs.writeFile(destPath, content, 'utf8');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function detectProjectType(projectRoot) {
|
|
26
|
+
const hasFrontend = await fs.pathExists(path.join(projectRoot, 'frontend'));
|
|
27
|
+
const hasBackend = await fs.pathExists(path.join(projectRoot, 'backend'));
|
|
28
|
+
const hasSrc = await fs.pathExists(path.join(projectRoot, 'src'));
|
|
29
|
+
const hasApp = await fs.pathExists(path.join(projectRoot, 'app'));
|
|
30
|
+
const hasPackageJson = await fs.pathExists(path.join(projectRoot, 'package.json'));
|
|
31
|
+
const hasPyproject = await fs.pathExists(path.join(projectRoot, 'pyproject.toml'));
|
|
32
|
+
const hasRequirements = await fs.pathExists(path.join(projectRoot, 'requirements.txt'));
|
|
33
|
+
|
|
34
|
+
if (hasFrontend && hasBackend) return 'monorepo';
|
|
35
|
+
if (hasFrontend || (hasSrc && hasPackageJson)) return 'frontend';
|
|
36
|
+
if (hasBackend || hasApp || hasPyproject || hasRequirements) return 'backend';
|
|
37
|
+
if (hasPackageJson) return 'frontend';
|
|
38
|
+
return 'unknown';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getConfig(projectRoot) {
|
|
42
|
+
const configPath = path.join(projectRoot, 'gimme-the-lint.config.js');
|
|
43
|
+
const defaults = {
|
|
44
|
+
frontendDir: 'frontend',
|
|
45
|
+
backendDir: 'backend',
|
|
46
|
+
srcDir: 'src',
|
|
47
|
+
appDir: 'app',
|
|
48
|
+
lttfDir: '.lttf',
|
|
49
|
+
ruffBaselineDir: '.lttf-ruff',
|
|
50
|
+
excludePatterns: [],
|
|
51
|
+
testExcludedFrontend: ['__tests__', 'testing', 'e2e', '*.test.*', '*.spec.*'],
|
|
52
|
+
testExcludedBackend: ['tests', '*test*', '__pycache__'],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (fs.existsSync(configPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const userConfig = require(configPath);
|
|
58
|
+
return { ...defaults, ...userConfig };
|
|
59
|
+
} catch {
|
|
60
|
+
return defaults;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return defaults;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function initConfig(projectRoot, options = {}) {
|
|
68
|
+
const configPath = path.join(projectRoot, 'gimme-the-lint.config.js');
|
|
69
|
+
|
|
70
|
+
if (await fs.pathExists(configPath) && !options.force) {
|
|
71
|
+
return { created: false, path: configPath };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const projectType = await detectProjectType(projectRoot);
|
|
75
|
+
const config = {
|
|
76
|
+
projectType,
|
|
77
|
+
frontendDir: options.frontendDir || 'frontend',
|
|
78
|
+
backendDir: options.backendDir || 'backend',
|
|
79
|
+
srcDir: options.srcDir || 'src',
|
|
80
|
+
appDir: options.appDir || 'app',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const content = `// gimme-the-lint configuration
|
|
84
|
+
// Generated by gimme-the-lint init
|
|
85
|
+
module.exports = ${JSON.stringify(config, null, 2)};
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
await fs.writeFile(configPath, content, 'utf8');
|
|
89
|
+
return { created: true, path: configPath, projectType };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
copyTemplate,
|
|
94
|
+
detectProjectType,
|
|
95
|
+
getConfig,
|
|
96
|
+
initConfig,
|
|
97
|
+
TEMPLATES_DIR,
|
|
98
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const TEST_PATTERNS = [/test/i, /^__pycache__$/, /^e2e$/, /^\./, /^node_modules$/];
|
|
8
|
+
|
|
9
|
+
function isTestDir(name) {
|
|
10
|
+
return TEST_PATTERNS.some((p) => p.test(name));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function discoverDirs(basePath, options = {}) {
|
|
14
|
+
const { excludePatterns = [], maxDepth = 1 } = options;
|
|
15
|
+
const allPatterns = [...TEST_PATTERNS, ...excludePatterns.map((p) => new RegExp(p, 'i'))];
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(basePath)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const entries = fs.readdirSync(basePath, { withFileTypes: true });
|
|
22
|
+
return entries
|
|
23
|
+
.filter((e) => e.isDirectory())
|
|
24
|
+
.filter((e) => !allPatterns.some((p) => p.test(e.name)))
|
|
25
|
+
.map((e) => e.name)
|
|
26
|
+
.sort();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function discoverFrontendDirs(projectRoot, options = {}) {
|
|
30
|
+
const srcPath = path.join(projectRoot, options.frontendDir || 'frontend', options.srcDir || 'src');
|
|
31
|
+
return discoverDirs(srcPath, {
|
|
32
|
+
excludePatterns: options.excludePatterns || [],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function discoverBackendDirs(projectRoot, options = {}) {
|
|
37
|
+
const appPath = path.join(projectRoot, options.backendDir || 'backend', options.appDir || 'app');
|
|
38
|
+
return discoverDirs(appPath, {
|
|
39
|
+
excludePatterns: options.excludePatterns || [],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getChangedDirs(projectRoot, options = {}) {
|
|
44
|
+
const frontendPrefix = (options.frontendDir || 'frontend') + '/' + (options.srcDir || 'src') + '/';
|
|
45
|
+
const backendPrefix = (options.backendDir || 'backend') + '/' + (options.appDir || 'app') + '/';
|
|
46
|
+
|
|
47
|
+
let diff;
|
|
48
|
+
try {
|
|
49
|
+
diff = execSync('git diff --cached --name-only --diff-filter=ACMR', {
|
|
50
|
+
cwd: projectRoot,
|
|
51
|
+
encoding: 'utf8',
|
|
52
|
+
}).trim();
|
|
53
|
+
} catch {
|
|
54
|
+
return { frontend: [], backend: [], all: [] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!diff) {
|
|
58
|
+
return { frontend: [], backend: [], all: [] };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const files = diff.split('\n');
|
|
62
|
+
const frontendDirs = new Set();
|
|
63
|
+
const backendDirs = new Set();
|
|
64
|
+
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
if (file.startsWith(frontendPrefix)) {
|
|
67
|
+
const rest = file.slice(frontendPrefix.length);
|
|
68
|
+
const dir = rest.split('/')[0];
|
|
69
|
+
if (dir && !isTestDir(dir)) frontendDirs.add(dir);
|
|
70
|
+
} else if (file.startsWith(backendPrefix)) {
|
|
71
|
+
const rest = file.slice(backendPrefix.length);
|
|
72
|
+
const dir = rest.split('/')[0];
|
|
73
|
+
if (dir && !isTestDir(dir)) backendDirs.add(dir);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
frontend: Array.from(frontendDirs).sort(),
|
|
79
|
+
backend: Array.from(backendDirs).sort(),
|
|
80
|
+
all: files,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getChangedFiles(projectRoot, options = {}) {
|
|
85
|
+
let diff;
|
|
86
|
+
try {
|
|
87
|
+
diff = execSync('git diff --cached --name-only --diff-filter=ACMR', {
|
|
88
|
+
cwd: projectRoot,
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
}).trim();
|
|
91
|
+
} catch {
|
|
92
|
+
return { frontend: [], backend: [] };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!diff) {
|
|
96
|
+
return { frontend: [], backend: [] };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const files = diff.split('\n');
|
|
100
|
+
const frontendPrefix = (options.frontendDir || 'frontend') + '/';
|
|
101
|
+
const backendPrefix = (options.backendDir || 'backend') + '/';
|
|
102
|
+
|
|
103
|
+
const frontend = files.filter(
|
|
104
|
+
(f) => f.startsWith(frontendPrefix) && /\.(js|jsx|ts|tsx)$/.test(f)
|
|
105
|
+
);
|
|
106
|
+
const backend = files.filter(
|
|
107
|
+
(f) => f.startsWith(backendPrefix) && /\.py$/.test(f)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return { frontend, backend };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
discoverDirs,
|
|
115
|
+
discoverFrontendDirs,
|
|
116
|
+
discoverBackendDirs,
|
|
117
|
+
getChangedDirs,
|
|
118
|
+
getChangedFiles,
|
|
119
|
+
isTestDir,
|
|
120
|
+
};
|