codebakers 1.0.45 → 2.0.1
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 +275 -60
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4260 -0
- package/install.bat +9 -0
- package/package.json +71 -115
- package/src/channels/discord.ts +5 -0
- package/src/channels/slack.ts +5 -0
- package/src/channels/sms.ts +4 -0
- package/src/channels/telegram.ts +5 -0
- package/src/channels/whatsapp.ts +7 -0
- package/src/commands/check.ts +365 -0
- package/src/commands/code.ts +684 -0
- package/src/commands/connect.ts +12 -0
- package/src/commands/deploy.ts +414 -0
- package/src/commands/design.ts +298 -0
- package/src/commands/fix.ts +20 -0
- package/src/commands/gateway.ts +604 -0
- package/src/commands/generate.ts +178 -0
- package/src/commands/init.ts +574 -0
- package/src/commands/learn.ts +36 -0
- package/src/commands/security.ts +102 -0
- package/src/commands/setup.ts +448 -0
- package/src/commands/status.ts +56 -0
- package/src/index.ts +278 -0
- package/src/patterns/loader.ts +337 -0
- package/src/services/github.ts +61 -0
- package/src/services/supabase.ts +147 -0
- package/src/services/vercel.ts +61 -0
- package/src/utils/claude-md.ts +287 -0
- package/src/utils/config.ts +282 -0
- package/src/utils/updates.ts +27 -0
- package/tsconfig.json +17 -10
- package/.vscodeignore +0 -18
- package/LICENSE +0 -21
- package/codebakers-1.0.0.vsix +0 -0
- package/codebakers-1.0.10.vsix +0 -0
- package/codebakers-1.0.11.vsix +0 -0
- package/codebakers-1.0.12.vsix +0 -0
- package/codebakers-1.0.13.vsix +0 -0
- package/codebakers-1.0.14.vsix +0 -0
- package/codebakers-1.0.15.vsix +0 -0
- package/codebakers-1.0.16.vsix +0 -0
- package/codebakers-1.0.17.vsix +0 -0
- package/codebakers-1.0.18.vsix +0 -0
- package/codebakers-1.0.19.vsix +0 -0
- package/codebakers-1.0.20.vsix +0 -0
- package/codebakers-1.0.21.vsix +0 -0
- package/codebakers-1.0.22.vsix +0 -0
- package/codebakers-1.0.23.vsix +0 -0
- package/codebakers-1.0.24.vsix +0 -0
- package/codebakers-1.0.25.vsix +0 -0
- package/codebakers-1.0.26.vsix +0 -0
- package/codebakers-1.0.27.vsix +0 -0
- package/codebakers-1.0.28.vsix +0 -0
- package/codebakers-1.0.29.vsix +0 -0
- package/codebakers-1.0.30.vsix +0 -0
- package/codebakers-1.0.31.vsix +0 -0
- package/codebakers-1.0.32.vsix +0 -0
- package/codebakers-1.0.35.vsix +0 -0
- package/codebakers-1.0.36.vsix +0 -0
- package/codebakers-1.0.37.vsix +0 -0
- package/codebakers-1.0.38.vsix +0 -0
- package/codebakers-1.0.39.vsix +0 -0
- package/codebakers-1.0.40.vsix +0 -0
- package/codebakers-1.0.41.vsix +0 -0
- package/codebakers-1.0.42.vsix +0 -0
- package/codebakers-1.0.43.vsix +0 -0
- package/codebakers-1.0.44.vsix +0 -0
- package/codebakers-1.0.45.vsix +0 -0
- package/dist/extension.js +0 -1394
- package/esbuild.js +0 -63
- package/media/icon.png +0 -0
- package/media/icon.svg +0 -7
- package/nul +0 -1
- package/preview.html +0 -547
- package/src/ChatPanelProvider.ts +0 -1815
- package/src/ChatViewProvider.ts +0 -749
- package/src/CodeBakersClient.ts +0 -1146
- package/src/CodeValidator.ts +0 -645
- package/src/FileOperations.ts +0 -410
- package/src/ProjectContext.ts +0 -526
- package/src/extension.ts +0 -332
package/install.bat
ADDED
package/package.json
CHANGED
|
@@ -1,127 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebakers",
|
|
3
|
-
"
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "AI dev team that follows the rules. Build apps from anywhere with pattern enforcement.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"codebakers": "./dist/index.js",
|
|
9
|
+
"cb": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
13
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"lint": "eslint src/",
|
|
17
|
+
"test": "vitest",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
9
19
|
},
|
|
10
|
-
"categories": [
|
|
11
|
-
"Programming Languages",
|
|
12
|
-
"Snippets",
|
|
13
|
-
"Machine Learning",
|
|
14
|
-
"Other"
|
|
15
|
-
],
|
|
16
20
|
"keywords": [
|
|
21
|
+
"cli",
|
|
17
22
|
"ai",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
+
"codebakers",
|
|
24
|
+
"code-generation",
|
|
25
|
+
"pattern-enforcement",
|
|
26
|
+
"developer-tools",
|
|
27
|
+
"automation",
|
|
28
|
+
"nextjs",
|
|
29
|
+
"supabase",
|
|
30
|
+
"vercel"
|
|
23
31
|
],
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"contributes": {
|
|
30
|
-
"commands": [
|
|
31
|
-
{
|
|
32
|
-
"command": "codebakers.openChat",
|
|
33
|
-
"title": "Open CodeBakers",
|
|
34
|
-
"category": "CodeBakers",
|
|
35
|
-
"icon": {
|
|
36
|
-
"light": "media/icon.svg",
|
|
37
|
-
"dark": "media/icon.svg"
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"command": "codebakers.login",
|
|
42
|
-
"title": "Login to CodeBakers",
|
|
43
|
-
"category": "CodeBakers"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"command": "codebakers.logout",
|
|
47
|
-
"title": "Logout from CodeBakers",
|
|
48
|
-
"category": "CodeBakers"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"command": "codebakers.showPatterns",
|
|
52
|
-
"title": "Show Available Patterns",
|
|
53
|
-
"category": "CodeBakers"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"command": "codebakers.runAudit",
|
|
57
|
-
"title": "Run Code Audit",
|
|
58
|
-
"category": "CodeBakers"
|
|
59
|
-
}
|
|
60
|
-
],
|
|
61
|
-
"menus": {
|
|
62
|
-
"editor/title": [
|
|
63
|
-
{
|
|
64
|
-
"command": "codebakers.openChat",
|
|
65
|
-
"group": "navigation"
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
"commandPalette": [
|
|
69
|
-
{
|
|
70
|
-
"command": "codebakers.openChat"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"command": "codebakers.login"
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"command": "codebakers.logout"
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
"keybindings": [
|
|
81
|
-
{
|
|
82
|
-
"command": "codebakers.openChat",
|
|
83
|
-
"key": "ctrl+alt+c",
|
|
84
|
-
"mac": "cmd+alt+c"
|
|
85
|
-
}
|
|
86
|
-
],
|
|
87
|
-
"configuration": {
|
|
88
|
-
"title": "CodeBakers",
|
|
89
|
-
"properties": {
|
|
90
|
-
"codebakers.apiEndpoint": {
|
|
91
|
-
"type": "string",
|
|
92
|
-
"default": "https://www.codebakers.ai",
|
|
93
|
-
"description": "CodeBakers API endpoint"
|
|
94
|
-
},
|
|
95
|
-
"codebakers.autoShowFooter": {
|
|
96
|
-
"type": "boolean",
|
|
97
|
-
"default": true,
|
|
98
|
-
"description": "Show CodeBakers footer after code generation"
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
"scripts": {
|
|
104
|
-
"vscode:prepublish": "npm run esbuild-base -- --production",
|
|
105
|
-
"compile": "npm run esbuild-base",
|
|
106
|
-
"esbuild-base": "node esbuild.js",
|
|
107
|
-
"esbuild-watch": "node esbuild.js --watch",
|
|
108
|
-
"watch": "npm run esbuild-watch",
|
|
109
|
-
"package": "vsce package",
|
|
110
|
-
"publish": "vsce publish"
|
|
32
|
+
"author": "BotMakers Inc",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/botmakers/codebakers-cli"
|
|
111
37
|
},
|
|
112
|
-
"
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
"@vscode/vsce": "^2.22.0",
|
|
116
|
-
"esbuild": "^0.27.2",
|
|
117
|
-
"typescript": "^5.3.0"
|
|
38
|
+
"homepage": "https://codebakers.dev",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
118
41
|
},
|
|
119
42
|
"dependencies": {
|
|
120
|
-
"@anthropic-ai/sdk": "^0.32.
|
|
121
|
-
"
|
|
43
|
+
"@anthropic-ai/sdk": "^0.32.1",
|
|
44
|
+
"@clack/prompts": "^0.7.0",
|
|
45
|
+
"@octokit/rest": "^21.0.0",
|
|
46
|
+
"@supabase/supabase-js": "^2.45.0",
|
|
47
|
+
"axios": "^1.7.0",
|
|
48
|
+
"boxen": "^8.0.0",
|
|
49
|
+
"chalk": "^5.3.0",
|
|
50
|
+
"cli-table3": "^0.6.5",
|
|
51
|
+
"commander": "^12.1.0",
|
|
52
|
+
"conf": "^13.0.0",
|
|
53
|
+
"dotenv": "^16.4.5",
|
|
54
|
+
"execa": "^9.3.0",
|
|
55
|
+
"fast-glob": "^3.3.2",
|
|
56
|
+
"figures": "^6.1.0",
|
|
57
|
+
"fs-extra": "^11.2.0",
|
|
58
|
+
"gradient-string": "^2.0.2",
|
|
59
|
+
"inquirer": "^10.0.0",
|
|
60
|
+
"node-fetch": "^3.3.2",
|
|
61
|
+
"open": "^10.1.0",
|
|
62
|
+
"ora": "^8.0.1",
|
|
63
|
+
"picocolors": "^1.0.1",
|
|
64
|
+
"simple-git": "^3.25.0",
|
|
65
|
+
"strip-ansi": "^7.1.0",
|
|
66
|
+
"terminal-link": "^3.0.0",
|
|
67
|
+
"update-notifier": "^7.1.0",
|
|
68
|
+
"which": "^4.0.0",
|
|
69
|
+
"zod": "^3.23.8"
|
|
122
70
|
},
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@types/fs-extra": "^11.0.4",
|
|
73
|
+
"@types/gradient-string": "^1.1.6",
|
|
74
|
+
"@types/inquirer": "^9.0.7",
|
|
75
|
+
"@types/node": "^22.0.0",
|
|
76
|
+
"@types/update-notifier": "^6.0.8",
|
|
77
|
+
"@types/which": "^3.0.4",
|
|
78
|
+
"eslint": "^9.8.0",
|
|
79
|
+
"tsup": "^8.2.0",
|
|
80
|
+
"typescript": "^5.5.0",
|
|
81
|
+
"vitest": "^2.0.0"
|
|
126
82
|
}
|
|
127
83
|
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs-extra';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import glob from 'fast-glob';
|
|
6
|
+
import { Config } from '../utils/config.js';
|
|
7
|
+
|
|
8
|
+
interface CheckOptions {
|
|
9
|
+
fix?: boolean;
|
|
10
|
+
watch?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Violation {
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
rule: string;
|
|
17
|
+
message: string;
|
|
18
|
+
severity: 'error' | 'warning';
|
|
19
|
+
autoFixable: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface CheckResult {
|
|
23
|
+
violations: Violation[];
|
|
24
|
+
filesChecked: number;
|
|
25
|
+
passed: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Pattern rules extracted from your CLAUDE.md
|
|
29
|
+
const RULES = [
|
|
30
|
+
{
|
|
31
|
+
id: 'no-any',
|
|
32
|
+
name: 'No any type',
|
|
33
|
+
description: 'Avoid using the "any" type',
|
|
34
|
+
severity: 'error' as const,
|
|
35
|
+
pattern: /:\s*any\b(?!\s*\=\>)/g,
|
|
36
|
+
message: 'Avoid using "any" type. Use proper TypeScript types.',
|
|
37
|
+
autoFixable: false,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'no-ts-ignore',
|
|
41
|
+
name: 'No @ts-ignore',
|
|
42
|
+
description: 'Avoid using @ts-ignore',
|
|
43
|
+
severity: 'error' as const,
|
|
44
|
+
pattern: /@ts-ignore/g,
|
|
45
|
+
message: '@ts-ignore bypasses type checking. Fix the underlying issue.',
|
|
46
|
+
autoFixable: false,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'no-console-log',
|
|
50
|
+
name: 'No console.log',
|
|
51
|
+
description: 'Remove debug console.log statements',
|
|
52
|
+
severity: 'warning' as const,
|
|
53
|
+
pattern: /console\.log\(/g,
|
|
54
|
+
message: 'Remove console.log before committing. Use proper logging.',
|
|
55
|
+
autoFixable: true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'no-todo',
|
|
59
|
+
name: 'No TODO comments',
|
|
60
|
+
description: 'Complete all TODOs before committing',
|
|
61
|
+
severity: 'warning' as const,
|
|
62
|
+
pattern: /\/\/\s*TODO:/gi,
|
|
63
|
+
message: 'Complete or remove TODO comments before committing.',
|
|
64
|
+
autoFixable: false,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'no-fixme',
|
|
68
|
+
name: 'No FIXME comments',
|
|
69
|
+
description: 'Fix all FIXME issues before committing',
|
|
70
|
+
severity: 'error' as const,
|
|
71
|
+
pattern: /\/\/\s*FIXME:/gi,
|
|
72
|
+
message: 'Address FIXME issues before committing.',
|
|
73
|
+
autoFixable: false,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'button-has-handler',
|
|
77
|
+
name: 'Button has onClick',
|
|
78
|
+
description: 'Buttons should have onClick handlers',
|
|
79
|
+
severity: 'error' as const,
|
|
80
|
+
pattern: /<Button[^>]*(?!onClick)[^>]*>/g,
|
|
81
|
+
message: 'Button must have an onClick handler.',
|
|
82
|
+
autoFixable: false,
|
|
83
|
+
validator: (content: string, match: RegExpExecArray) => {
|
|
84
|
+
// Check if onClick exists in the button
|
|
85
|
+
const buttonTag = match[0];
|
|
86
|
+
return !buttonTag.includes('onClick') && !buttonTag.includes('type="submit"');
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'async-has-try-catch',
|
|
91
|
+
name: 'Async has try/catch',
|
|
92
|
+
description: 'Async functions should have error handling',
|
|
93
|
+
severity: 'error' as const,
|
|
94
|
+
pattern: /async\s+(?:function\s+\w+|\([^)]*\)\s*=>|\w+\s*=\s*async\s*\([^)]*\)\s*=>)\s*\{[^}]*\}/g,
|
|
95
|
+
message: 'Async function should have try/catch error handling.',
|
|
96
|
+
autoFixable: false,
|
|
97
|
+
validator: (content: string, match: RegExpExecArray) => {
|
|
98
|
+
const funcBody = match[0];
|
|
99
|
+
return !funcBody.includes('try') && !funcBody.includes('catch');
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'no-hardcoded-secrets',
|
|
104
|
+
name: 'No hardcoded secrets',
|
|
105
|
+
description: 'Secrets should be in environment variables',
|
|
106
|
+
severity: 'error' as const,
|
|
107
|
+
pattern: /(sk_live_|sk_test_|pk_live_|pk_test_|api_key\s*=\s*['"][^'"]+['"])/gi,
|
|
108
|
+
message: 'Possible hardcoded secret. Use environment variables.',
|
|
109
|
+
autoFixable: false,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'no-eval',
|
|
113
|
+
name: 'No eval()',
|
|
114
|
+
description: 'Never use eval() - security risk',
|
|
115
|
+
severity: 'error' as const,
|
|
116
|
+
pattern: /\beval\s*\(/g,
|
|
117
|
+
message: 'Never use eval(). It is a security vulnerability.',
|
|
118
|
+
autoFixable: false,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'no-innerhtml',
|
|
122
|
+
name: 'No innerHTML',
|
|
123
|
+
description: 'Avoid direct innerHTML assignment',
|
|
124
|
+
severity: 'error' as const,
|
|
125
|
+
pattern: /\.innerHTML\s*=/g,
|
|
126
|
+
message: 'Avoid innerHTML. Use proper React rendering or DOMPurify.',
|
|
127
|
+
autoFixable: false,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'loading-state',
|
|
131
|
+
name: 'Loading state in components',
|
|
132
|
+
description: 'Components with fetch should have loading states',
|
|
133
|
+
severity: 'warning' as const,
|
|
134
|
+
pattern: /useEffect\s*\([^)]*fetch\([^)]*\)/g,
|
|
135
|
+
message: 'Components fetching data should have loading states.',
|
|
136
|
+
autoFixable: false,
|
|
137
|
+
validator: (content: string) => {
|
|
138
|
+
return content.includes('fetch(') &&
|
|
139
|
+
!content.includes('isLoading') &&
|
|
140
|
+
!content.includes('loading') &&
|
|
141
|
+
!content.includes('isPending');
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'form-validation',
|
|
146
|
+
name: 'Form has validation',
|
|
147
|
+
description: 'Forms should use Zod validation',
|
|
148
|
+
severity: 'warning' as const,
|
|
149
|
+
pattern: /<form[^>]*onSubmit/gi,
|
|
150
|
+
message: 'Forms should use Zod schema validation.',
|
|
151
|
+
autoFixable: false,
|
|
152
|
+
validator: (content: string) => {
|
|
153
|
+
return content.includes('<form') &&
|
|
154
|
+
!content.includes('zodResolver') &&
|
|
155
|
+
!content.includes('z.object');
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'use-client-directive',
|
|
160
|
+
name: 'use client directive',
|
|
161
|
+
description: 'Client components should have "use client" directive',
|
|
162
|
+
severity: 'error' as const,
|
|
163
|
+
pattern: /(useState|useEffect|useReducer|useContext|useRef|useCallback|useMemo)\s*\(/g,
|
|
164
|
+
message: 'Components using hooks must have "use client" directive.',
|
|
165
|
+
autoFixable: true,
|
|
166
|
+
validator: (content: string) => {
|
|
167
|
+
const hasHooks = /(useState|useEffect|useReducer|useContext|useRef|useCallback|useMemo)\s*\(/.test(content);
|
|
168
|
+
const hasDirective = content.trim().startsWith("'use client'") || content.trim().startsWith('"use client"');
|
|
169
|
+
return hasHooks && !hasDirective;
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'key-prop-in-map',
|
|
174
|
+
name: 'Key prop in map',
|
|
175
|
+
description: 'Elements in .map() should have key prop',
|
|
176
|
+
severity: 'error' as const,
|
|
177
|
+
pattern: /\.map\s*\([^)]*\)\s*=>\s*(?:\(?\s*<[A-Z][^>]*(?!key=)[^>]*>)/g,
|
|
178
|
+
message: 'Elements in .map() must have a unique key prop.',
|
|
179
|
+
autoFixable: false,
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
export async function checkCommand(options: CheckOptions = {}): Promise<void> {
|
|
184
|
+
const config = new Config();
|
|
185
|
+
|
|
186
|
+
if (!config.isInProject()) {
|
|
187
|
+
p.log.error('Not in a CodeBakers project.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
p.intro(chalk.bgCyan.black(' CodeBakers Pattern Check '));
|
|
192
|
+
|
|
193
|
+
const spinner = p.spinner();
|
|
194
|
+
spinner.start('Analyzing code...');
|
|
195
|
+
|
|
196
|
+
const result = await runPatternCheck(options.fix || false);
|
|
197
|
+
|
|
198
|
+
spinner.stop('Analysis complete');
|
|
199
|
+
|
|
200
|
+
// Display results
|
|
201
|
+
displayResults(result);
|
|
202
|
+
|
|
203
|
+
if (result.violations.length > 0 && options.fix) {
|
|
204
|
+
const fixable = result.violations.filter(v => v.autoFixable);
|
|
205
|
+
if (fixable.length > 0) {
|
|
206
|
+
spinner.start(`Auto-fixing ${fixable.length} violations...`);
|
|
207
|
+
await autoFix(fixable);
|
|
208
|
+
spinner.stop('Auto-fix complete');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Exit with error code if there are errors
|
|
213
|
+
const errors = result.violations.filter(v => v.severity === 'error');
|
|
214
|
+
if (errors.length > 0) {
|
|
215
|
+
p.outro(chalk.red(`❌ ${errors.length} errors found. Fix before committing.`));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
} else if (result.violations.length > 0) {
|
|
218
|
+
p.outro(chalk.yellow(`⚠️ ${result.violations.length} warnings. Consider fixing.`));
|
|
219
|
+
} else {
|
|
220
|
+
p.outro(chalk.green('✓ All patterns satisfied!'));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function runPatternCheck(autoFix: boolean): Promise<CheckResult> {
|
|
225
|
+
const cwd = process.cwd();
|
|
226
|
+
const violations: Violation[] = [];
|
|
227
|
+
|
|
228
|
+
// Find all TypeScript/JavaScript files
|
|
229
|
+
const files = await glob(['src/**/*.{ts,tsx,js,jsx}'], {
|
|
230
|
+
cwd,
|
|
231
|
+
ignore: ['**/node_modules/**', '**/.next/**', '**/dist/**'],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
for (const file of files) {
|
|
235
|
+
const filePath = path.join(cwd, file);
|
|
236
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
237
|
+
const lines = content.split('\n');
|
|
238
|
+
|
|
239
|
+
for (const rule of RULES) {
|
|
240
|
+
// If rule has custom validator, use it
|
|
241
|
+
if (rule.validator) {
|
|
242
|
+
// Create a fresh regex for each file
|
|
243
|
+
const regex = new RegExp(rule.pattern.source, rule.pattern.flags);
|
|
244
|
+
let match;
|
|
245
|
+
while ((match = regex.exec(content)) !== null) {
|
|
246
|
+
if (rule.validator(content, match)) {
|
|
247
|
+
const line = content.substring(0, match.index).split('\n').length;
|
|
248
|
+
violations.push({
|
|
249
|
+
file,
|
|
250
|
+
line,
|
|
251
|
+
rule: rule.id,
|
|
252
|
+
message: rule.message,
|
|
253
|
+
severity: rule.severity,
|
|
254
|
+
autoFixable: rule.autoFixable,
|
|
255
|
+
});
|
|
256
|
+
break; // One violation per rule per file
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
// Standard pattern matching
|
|
261
|
+
const regex = new RegExp(rule.pattern.source, rule.pattern.flags);
|
|
262
|
+
let match;
|
|
263
|
+
while ((match = regex.exec(content)) !== null) {
|
|
264
|
+
const line = content.substring(0, match.index).split('\n').length;
|
|
265
|
+
violations.push({
|
|
266
|
+
file,
|
|
267
|
+
line,
|
|
268
|
+
rule: rule.id,
|
|
269
|
+
message: rule.message,
|
|
270
|
+
severity: rule.severity,
|
|
271
|
+
autoFixable: rule.autoFixable,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
violations,
|
|
280
|
+
filesChecked: files.length,
|
|
281
|
+
passed: violations.length === 0,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function displayResults(result: CheckResult): void {
|
|
286
|
+
console.log(chalk.dim(`\nChecked ${result.filesChecked} files\n`));
|
|
287
|
+
|
|
288
|
+
if (result.violations.length === 0) {
|
|
289
|
+
console.log(chalk.green(' ✓ No violations found\n'));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Group by file
|
|
294
|
+
const byFile = new Map<string, Violation[]>();
|
|
295
|
+
for (const v of result.violations) {
|
|
296
|
+
if (!byFile.has(v.file)) {
|
|
297
|
+
byFile.set(v.file, []);
|
|
298
|
+
}
|
|
299
|
+
byFile.get(v.file)!.push(v);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (const [file, fileViolations] of byFile) {
|
|
303
|
+
console.log(chalk.bold(file));
|
|
304
|
+
|
|
305
|
+
for (const v of fileViolations) {
|
|
306
|
+
const icon = v.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
|
|
307
|
+
const fixable = v.autoFixable ? chalk.dim(' (auto-fixable)') : '';
|
|
308
|
+
console.log(` ${icon} Line ${v.line}: ${v.message}${fixable}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log('');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Summary
|
|
315
|
+
const errors = result.violations.filter(v => v.severity === 'error').length;
|
|
316
|
+
const warnings = result.violations.filter(v => v.severity === 'warning').length;
|
|
317
|
+
|
|
318
|
+
console.log(chalk.bold('Summary:'));
|
|
319
|
+
if (errors > 0) {
|
|
320
|
+
console.log(chalk.red(` ${errors} error(s)`));
|
|
321
|
+
}
|
|
322
|
+
if (warnings > 0) {
|
|
323
|
+
console.log(chalk.yellow(` ${warnings} warning(s)`));
|
|
324
|
+
}
|
|
325
|
+
console.log('');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function autoFix(violations: Violation[]): Promise<void> {
|
|
329
|
+
const cwd = process.cwd();
|
|
330
|
+
|
|
331
|
+
// Group by file
|
|
332
|
+
const byFile = new Map<string, Violation[]>();
|
|
333
|
+
for (const v of violations) {
|
|
334
|
+
if (!byFile.has(v.file)) {
|
|
335
|
+
byFile.set(v.file, []);
|
|
336
|
+
}
|
|
337
|
+
byFile.get(v.file)!.push(v);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (const [file, fileViolations] of byFile) {
|
|
341
|
+
const filePath = path.join(cwd, file);
|
|
342
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
343
|
+
|
|
344
|
+
for (const v of fileViolations) {
|
|
345
|
+
switch (v.rule) {
|
|
346
|
+
case 'no-console-log':
|
|
347
|
+
// Remove console.log statements
|
|
348
|
+
content = content.replace(/console\.log\([^)]*\);?\n?/g, '');
|
|
349
|
+
break;
|
|
350
|
+
|
|
351
|
+
case 'use-client-directive':
|
|
352
|
+
// Add "use client" directive at the top
|
|
353
|
+
if (!content.startsWith("'use client'") && !content.startsWith('"use client"')) {
|
|
354
|
+
content = "'use client';\n\n" + content;
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
await fs.writeFile(filePath, content);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Export for use in other commands
|
|
365
|
+
export { Violation, CheckResult };
|