cursor-lint 0.4.0 ā 0.5.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/README.md +2 -0
- package/package.json +1 -1
- package/src/cli.js +49 -2
- package/src/generate.js +156 -0
package/README.md
CHANGED
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -5,8 +5,9 @@ const { lintProject } = require('./index');
|
|
|
5
5
|
const { verifyProject } = require('./verify');
|
|
6
6
|
const { initProject } = require('./init');
|
|
7
7
|
const { fixProject } = require('./fix');
|
|
8
|
+
const { generateRules } = require('./generate');
|
|
8
9
|
|
|
9
|
-
const VERSION = '0.
|
|
10
|
+
const VERSION = '0.5.0';
|
|
10
11
|
|
|
11
12
|
const RED = '\x1b[31m';
|
|
12
13
|
const YELLOW = '\x1b[33m';
|
|
@@ -30,6 +31,7 @@ ${YELLOW}Options:${RESET}
|
|
|
30
31
|
--verify Check if code follows rules with verify: blocks
|
|
31
32
|
--init Generate starter .mdc rules (auto-detects your stack)
|
|
32
33
|
--fix Auto-fix common issues (missing frontmatter, alwaysApply)
|
|
34
|
+
--generate Auto-detect stack & download matching .mdc rules from GitHub
|
|
33
35
|
|
|
34
36
|
${YELLOW}What it checks (default):${RESET}
|
|
35
37
|
⢠.cursorrules files (warns about agent mode compatibility)
|
|
@@ -61,6 +63,7 @@ ${YELLOW}Examples:${RESET}
|
|
|
61
63
|
npx cursor-lint # Lint rule files
|
|
62
64
|
npx cursor-lint --verify # Check code against rules
|
|
63
65
|
npx cursor-lint --init # Generate starter rules for your project
|
|
66
|
+
npx cursor-lint --generate # Download community rules for your stack
|
|
64
67
|
|
|
65
68
|
${YELLOW}More info:${RESET}
|
|
66
69
|
https://github.com/cursorrulespacks/cursor-lint
|
|
@@ -84,8 +87,50 @@ async function main() {
|
|
|
84
87
|
const isVerify = args.includes('--verify');
|
|
85
88
|
const isInit = args.includes('--init');
|
|
86
89
|
const isFix = args.includes('--fix');
|
|
90
|
+
const isGenerate = args.includes('--generate');
|
|
87
91
|
|
|
88
|
-
if (
|
|
92
|
+
if (isGenerate) {
|
|
93
|
+
console.log(`\nš cursor-lint v${VERSION} --generate\n`);
|
|
94
|
+
console.log(`Detecting stack in ${cwd}...\n`);
|
|
95
|
+
|
|
96
|
+
const results = await generateRules(cwd);
|
|
97
|
+
|
|
98
|
+
if (results.detected.length > 0) {
|
|
99
|
+
console.log(`${CYAN}Detected:${RESET} ${results.detected.join(', ')}\n`);
|
|
100
|
+
} else {
|
|
101
|
+
console.log(`${YELLOW}No recognized stack detected.${RESET}`);
|
|
102
|
+
console.log(`${DIM}Supports: package.json, tsconfig.json, requirements.txt, Cargo.toml, go.mod, Dockerfile${RESET}\n`);
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (results.created.length > 0) {
|
|
107
|
+
console.log(`${GREEN}Downloaded:${RESET}`);
|
|
108
|
+
for (const r of results.created) {
|
|
109
|
+
console.log(` ${GREEN}ā${RESET} .cursor/rules/${r.file} ${DIM}(${r.stack})${RESET}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (results.skipped.length > 0) {
|
|
114
|
+
console.log(`\n${YELLOW}Skipped (already exist):${RESET}`);
|
|
115
|
+
for (const r of results.skipped) {
|
|
116
|
+
console.log(` ${YELLOW}ā ${RESET} .cursor/rules/${r.file}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (results.failed.length > 0) {
|
|
121
|
+
console.log(`\n${RED}Failed:${RESET}`);
|
|
122
|
+
for (const r of results.failed) {
|
|
123
|
+
console.log(` ${RED}ā${RESET} ${r.file} ā ${r.error}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (results.created.length > 0) {
|
|
128
|
+
console.log(`\n${DIM}Run cursor-lint to check these rules${RESET}\n`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
process.exit(results.failed.length > 0 ? 1 : 0);
|
|
132
|
+
|
|
133
|
+
} else if (isFix) {
|
|
89
134
|
console.log(`\nš§ cursor-lint v${VERSION} --fix\n`);
|
|
90
135
|
console.log(`Scanning ${cwd} for fixable issues...\n`);
|
|
91
136
|
|
|
@@ -246,6 +291,8 @@ async function main() {
|
|
|
246
291
|
if (totalErrors > 0) {
|
|
247
292
|
console.log(`${DIM}Need help fixing these? Get a full setup review:${RESET}`);
|
|
248
293
|
console.log(`${CYAN}https://cursorrulespacks.gumroad.com/l/cursor-setup-audit${RESET}\n`);
|
|
294
|
+
} else if (totalPassed > 0) {
|
|
295
|
+
console.log(`${DIM}If cursor-lint saved you time: ${CYAN}https://github.com/cursorrulespacks/cursor-lint${RESET} ${DIM}(ā helps others find it)${RESET}\n`);
|
|
249
296
|
}
|
|
250
297
|
|
|
251
298
|
process.exit(totalErrors > 0 ? 1 : 0);
|
package/src/generate.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
|
|
5
|
+
const BASE_URL = 'https://raw.githubusercontent.com/cursorrulespacks/cursorrules-collection/main/rules-mdc/';
|
|
6
|
+
|
|
7
|
+
const PKG_DEP_MAP = {
|
|
8
|
+
'react': 'frameworks/react.mdc',
|
|
9
|
+
'next': 'frameworks/nextjs.mdc',
|
|
10
|
+
'vue': 'frameworks/vue.mdc',
|
|
11
|
+
'svelte': 'frameworks/svelte.mdc',
|
|
12
|
+
'express': 'frameworks/express.mdc',
|
|
13
|
+
'@nestjs/core': 'frameworks/nestjs.mdc',
|
|
14
|
+
'prisma': 'tools/prisma.mdc',
|
|
15
|
+
'drizzle-orm': 'tools/drizzle.mdc',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const REQ_DEP_MAP = {
|
|
19
|
+
'django': 'frameworks/django.mdc',
|
|
20
|
+
'fastapi': 'frameworks/fastapi.mdc',
|
|
21
|
+
'flask': 'frameworks/flask.mdc',
|
|
22
|
+
'pydantic': 'tools/pydantic.mdc',
|
|
23
|
+
'sqlalchemy': 'tools/sqlalchemy.mdc',
|
|
24
|
+
'pytest': 'tools/pytest.mdc',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function fetchFile(url) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const get = (u) => {
|
|
30
|
+
https.get(u, (res) => {
|
|
31
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
32
|
+
get(res.headers.location);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (res.statusCode !== 200) {
|
|
36
|
+
reject(new Error(`HTTP ${res.statusCode} for ${u}`));
|
|
37
|
+
res.resume();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let data = '';
|
|
41
|
+
res.on('data', (c) => data += c);
|
|
42
|
+
res.on('end', () => resolve(data));
|
|
43
|
+
res.on('error', reject);
|
|
44
|
+
}).on('error', reject);
|
|
45
|
+
};
|
|
46
|
+
get(url);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function detectStack(cwd) {
|
|
51
|
+
const detected = [];
|
|
52
|
+
const rules = new Map(); // rulePath -> stackName
|
|
53
|
+
|
|
54
|
+
// package.json
|
|
55
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
56
|
+
if (fs.existsSync(pkgPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
59
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
60
|
+
for (const [dep, rule] of Object.entries(PKG_DEP_MAP)) {
|
|
61
|
+
if (allDeps[dep]) {
|
|
62
|
+
detected.push(dep);
|
|
63
|
+
rules.set(rule, dep);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// tsconfig.json
|
|
70
|
+
if (fs.existsSync(path.join(cwd, 'tsconfig.json'))) {
|
|
71
|
+
detected.push('TypeScript');
|
|
72
|
+
rules.set('languages/typescript.mdc', 'TypeScript');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Python
|
|
76
|
+
const reqPath = path.join(cwd, 'requirements.txt');
|
|
77
|
+
const hasPy = fs.existsSync(reqPath) || fs.readdirSync(cwd).some(f => f.endsWith('.py'));
|
|
78
|
+
if (hasPy) {
|
|
79
|
+
detected.push('Python');
|
|
80
|
+
rules.set('languages/python.mdc', 'Python');
|
|
81
|
+
}
|
|
82
|
+
if (fs.existsSync(reqPath)) {
|
|
83
|
+
try {
|
|
84
|
+
const req = fs.readFileSync(reqPath, 'utf8').toLowerCase();
|
|
85
|
+
for (const [dep, rule] of Object.entries(REQ_DEP_MAP)) {
|
|
86
|
+
if (req.includes(dep)) {
|
|
87
|
+
detected.push(dep);
|
|
88
|
+
rules.set(rule, dep);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Cargo.toml
|
|
95
|
+
if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
|
|
96
|
+
detected.push('Rust');
|
|
97
|
+
rules.set('languages/rust.mdc', 'Rust');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// go.mod
|
|
101
|
+
if (fs.existsSync(path.join(cwd, 'go.mod'))) {
|
|
102
|
+
detected.push('Go');
|
|
103
|
+
rules.set('languages/go.mdc', 'Go');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Dockerfile
|
|
107
|
+
if (fs.existsSync(path.join(cwd, 'Dockerfile'))) {
|
|
108
|
+
detected.push('Docker');
|
|
109
|
+
rules.set('tools/docker.mdc', 'Docker');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// CI/CD
|
|
113
|
+
if (fs.existsSync(path.join(cwd, '.github', 'workflows'))) {
|
|
114
|
+
detected.push('CI/CD');
|
|
115
|
+
rules.set('tools/ci-cd.mdc', 'CI/CD');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { detected, rules };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function generateRules(cwd) {
|
|
122
|
+
const { detected, rules } = detectStack(cwd);
|
|
123
|
+
const rulesDir = path.join(cwd, '.cursor', 'rules');
|
|
124
|
+
const created = [];
|
|
125
|
+
const skipped = [];
|
|
126
|
+
const failed = [];
|
|
127
|
+
|
|
128
|
+
if (rules.size === 0) {
|
|
129
|
+
return { detected, created, skipped, failed };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
133
|
+
|
|
134
|
+
for (const [rulePath, stackName] of rules) {
|
|
135
|
+
const filename = path.basename(rulePath);
|
|
136
|
+
const destPath = path.join(rulesDir, filename);
|
|
137
|
+
|
|
138
|
+
if (fs.existsSync(destPath)) {
|
|
139
|
+
skipped.push({ file: filename, stack: stackName });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const url = BASE_URL + rulePath;
|
|
145
|
+
const content = await fetchFile(url);
|
|
146
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
147
|
+
created.push({ file: filename, stack: stackName });
|
|
148
|
+
} catch (err) {
|
|
149
|
+
failed.push({ file: filename, stack: stackName, error: err.message });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { detected, created, skipped, failed };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = { generateRules };
|