codebakers 1.0.45 ā 2.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/README.md +275 -60
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3999 -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/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 +268 -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
|
@@ -0,0 +1,574 @@
|
|
|
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 { execa } from 'execa';
|
|
6
|
+
import { Config } from '../utils/config.js';
|
|
7
|
+
import { GitHubService } from '../services/github.js';
|
|
8
|
+
import { VercelService } from '../services/vercel.js';
|
|
9
|
+
import { SupabaseService } from '../services/supabase.js';
|
|
10
|
+
import { generateClaudeMd } from '../utils/claude-md.js';
|
|
11
|
+
|
|
12
|
+
interface InitOptions {
|
|
13
|
+
name?: string;
|
|
14
|
+
template?: string;
|
|
15
|
+
git?: boolean;
|
|
16
|
+
vercel?: boolean;
|
|
17
|
+
supabase?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function initCommand(options: InitOptions = {}): Promise<void> {
|
|
21
|
+
const config = new Config();
|
|
22
|
+
|
|
23
|
+
// Check if configured
|
|
24
|
+
if (!config.isConfigured()) {
|
|
25
|
+
p.log.error('Please run `codebakers setup` first.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
p.intro(chalk.bgCyan.black(' Create New Project '));
|
|
30
|
+
|
|
31
|
+
// Project name
|
|
32
|
+
let projectName = options.name;
|
|
33
|
+
if (!projectName) {
|
|
34
|
+
const name = await p.text({
|
|
35
|
+
message: 'Project name:',
|
|
36
|
+
placeholder: 'my-app',
|
|
37
|
+
validate: (value) => {
|
|
38
|
+
if (!value) return 'Project name is required';
|
|
39
|
+
if (!/^[a-z0-9-]+$/.test(value)) return 'Use lowercase letters, numbers, and hyphens only';
|
|
40
|
+
return undefined;
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
if (p.isCancel(name)) return;
|
|
44
|
+
projectName = name as string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Project type
|
|
48
|
+
const projectType = await p.select({
|
|
49
|
+
message: 'What are you building?',
|
|
50
|
+
options: [
|
|
51
|
+
{ value: 'web', label: 'š Web app', hint: 'Next.js ā Vercel' },
|
|
52
|
+
{ value: 'mobile', label: 'š± Mobile app', hint: 'Expo ā App stores' },
|
|
53
|
+
{ value: 'desktop', label: 'š„ļø Desktop app', hint: 'Tauri ā Windows/Mac/Linux' },
|
|
54
|
+
{ value: 'fullstack', label: 'š¦ Full stack', hint: 'Web + Mobile + Desktop' },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
if (p.isCancel(projectType)) return;
|
|
58
|
+
|
|
59
|
+
// Framework selection (for web)
|
|
60
|
+
let framework = 'nextjs';
|
|
61
|
+
if (projectType === 'web' || projectType === 'fullstack') {
|
|
62
|
+
const selected = await p.select({
|
|
63
|
+
message: 'Framework:',
|
|
64
|
+
options: [
|
|
65
|
+
{ value: 'nextjs', label: 'Next.js 14+', hint: config.getPreference('defaultFramework') === 'nextjs' ? '(your usual choice)' : '' },
|
|
66
|
+
{ value: 'remix', label: 'Remix' },
|
|
67
|
+
{ value: 'vite', label: 'Vite + React' },
|
|
68
|
+
{ value: 'astro', label: 'Astro' },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
if (p.isCancel(selected)) return;
|
|
72
|
+
framework = selected as string;
|
|
73
|
+
config.learnPreference('defaultFramework', framework);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// UI Library
|
|
77
|
+
const uiLibrary = await p.select({
|
|
78
|
+
message: 'UI Library:',
|
|
79
|
+
options: [
|
|
80
|
+
{ value: 'shadcn', label: 'shadcn/ui', hint: config.getPreference('defaultUI') === 'shadcn' ? '(your usual choice)' : 'Recommended' },
|
|
81
|
+
{ value: 'chakra', label: 'Chakra UI' },
|
|
82
|
+
{ value: 'mantine', label: 'Mantine' },
|
|
83
|
+
{ value: 'mui', label: 'Material UI' },
|
|
84
|
+
{ value: 'headless', label: 'Headless UI' },
|
|
85
|
+
{ value: 'tailwind', label: 'Tailwind only' },
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
if (p.isCancel(uiLibrary)) return;
|
|
89
|
+
config.learnPreference('defaultUI', uiLibrary);
|
|
90
|
+
|
|
91
|
+
// Packages
|
|
92
|
+
const packages = await p.multiselect({
|
|
93
|
+
message: 'Additional packages:',
|
|
94
|
+
options: [
|
|
95
|
+
{ value: 'typescript', label: 'TypeScript', hint: 'Always recommended' },
|
|
96
|
+
{ value: 'tailwind', label: 'Tailwind CSS' },
|
|
97
|
+
{ value: 'zod', label: 'Zod', hint: 'Validation' },
|
|
98
|
+
{ value: 'tanstack-query', label: 'TanStack Query', hint: 'Data fetching' },
|
|
99
|
+
{ value: 'zustand', label: 'Zustand', hint: 'State management' },
|
|
100
|
+
{ value: 'react-hook-form', label: 'React Hook Form' },
|
|
101
|
+
{ value: 'lucide', label: 'Lucide Icons' },
|
|
102
|
+
{ value: 'trpc', label: 'tRPC' },
|
|
103
|
+
],
|
|
104
|
+
initialValues: ['typescript', 'tailwind', 'zod', 'zustand', 'lucide'],
|
|
105
|
+
});
|
|
106
|
+
if (p.isCancel(packages)) return;
|
|
107
|
+
|
|
108
|
+
// Services to provision
|
|
109
|
+
const services = await p.multiselect({
|
|
110
|
+
message: 'Services to provision:',
|
|
111
|
+
options: [
|
|
112
|
+
{ value: 'github', label: 'GitHub', hint: 'Repository' },
|
|
113
|
+
{ value: 'vercel', label: 'Vercel', hint: 'Hosting' },
|
|
114
|
+
{ value: 'supabase', label: 'Supabase', hint: 'Database + Auth' },
|
|
115
|
+
{ value: 'stripe', label: 'Stripe', hint: 'Payments' },
|
|
116
|
+
{ value: 'resend', label: 'Resend', hint: 'Email' },
|
|
117
|
+
{ value: 'vapi', label: 'VAPI', hint: 'Voice AI' },
|
|
118
|
+
],
|
|
119
|
+
initialValues: ['github', 'vercel', 'supabase'],
|
|
120
|
+
});
|
|
121
|
+
if (p.isCancel(services)) return;
|
|
122
|
+
|
|
123
|
+
// Domain
|
|
124
|
+
let domain: string | undefined;
|
|
125
|
+
const domainChoice = await p.select({
|
|
126
|
+
message: 'Domain:',
|
|
127
|
+
options: [
|
|
128
|
+
{ value: 'subdomain', label: 'Subdomain', hint: `${projectName}.yourdomain.com` },
|
|
129
|
+
{ value: 'vercel', label: 'Vercel subdomain', hint: `${projectName}.vercel.app` },
|
|
130
|
+
{ value: 'purchase', label: 'Purchase new domain' },
|
|
131
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
if (p.isCancel(domainChoice)) return;
|
|
135
|
+
|
|
136
|
+
if (domainChoice === 'subdomain') {
|
|
137
|
+
const sub = await p.text({
|
|
138
|
+
message: 'Subdomain:',
|
|
139
|
+
placeholder: projectName,
|
|
140
|
+
initialValue: projectName,
|
|
141
|
+
});
|
|
142
|
+
if (!p.isCancel(sub)) {
|
|
143
|
+
domain = `${sub}.yourdomain.com`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Confirm
|
|
148
|
+
const projectConfig = {
|
|
149
|
+
name: projectName,
|
|
150
|
+
type: projectType,
|
|
151
|
+
framework,
|
|
152
|
+
ui: uiLibrary,
|
|
153
|
+
packages,
|
|
154
|
+
services,
|
|
155
|
+
domain,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
p.note(
|
|
159
|
+
`Name: ${projectName}
|
|
160
|
+
Type: ${projectType}
|
|
161
|
+
Framework: ${framework}
|
|
162
|
+
UI: ${uiLibrary}
|
|
163
|
+
Packages: ${(packages as string[]).join(', ')}
|
|
164
|
+
Services: ${(services as string[]).join(', ')}
|
|
165
|
+
Domain: ${domain || 'Vercel default'}`,
|
|
166
|
+
'Configuration'
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const confirmed = await p.confirm({
|
|
170
|
+
message: 'Create project?',
|
|
171
|
+
initialValue: true,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (!confirmed || p.isCancel(confirmed)) {
|
|
175
|
+
p.cancel('Project creation cancelled.');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create project
|
|
180
|
+
const spinner = p.spinner();
|
|
181
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Step 1: Create local project
|
|
185
|
+
spinner.start('Creating local project...');
|
|
186
|
+
await createLocalProject(projectPath, projectConfig);
|
|
187
|
+
spinner.stop('Local project created');
|
|
188
|
+
|
|
189
|
+
// Step 2: Install dependencies
|
|
190
|
+
spinner.start('Installing dependencies...');
|
|
191
|
+
await execa('pnpm', ['install'], { cwd: projectPath });
|
|
192
|
+
spinner.stop('Dependencies installed');
|
|
193
|
+
|
|
194
|
+
// Step 3: GitHub
|
|
195
|
+
if ((services as string[]).includes('github')) {
|
|
196
|
+
spinner.start('Creating GitHub repository...');
|
|
197
|
+
const github = new GitHubService(config);
|
|
198
|
+
const repo = await github.createRepo(projectName, { private: true });
|
|
199
|
+
spinner.stop(`GitHub repo created: ${repo.html_url}`);
|
|
200
|
+
|
|
201
|
+
// Init git and push
|
|
202
|
+
await execa('git', ['init'], { cwd: projectPath });
|
|
203
|
+
await execa('git', ['add', '.'], { cwd: projectPath });
|
|
204
|
+
await execa('git', ['commit', '-m', 'Initial commit by CodeBakers'], { cwd: projectPath });
|
|
205
|
+
await execa('git', ['remote', 'add', 'origin', repo.clone_url], { cwd: projectPath });
|
|
206
|
+
await execa('git', ['push', '-u', 'origin', 'main'], { cwd: projectPath });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Step 4: Supabase
|
|
210
|
+
if ((services as string[]).includes('supabase')) {
|
|
211
|
+
spinner.start('Creating Supabase project...');
|
|
212
|
+
const supabase = new SupabaseService(config);
|
|
213
|
+
const project = await supabase.createProject(projectName);
|
|
214
|
+
spinner.stop(`Supabase project created: ${project.name}`);
|
|
215
|
+
|
|
216
|
+
// Save Supabase config
|
|
217
|
+
await fs.writeJson(
|
|
218
|
+
path.join(projectPath, '.codebakers', 'supabase.json'),
|
|
219
|
+
{ projectId: project.id, projectUrl: project.api_url },
|
|
220
|
+
{ spaces: 2 }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Step 5: Vercel
|
|
225
|
+
if ((services as string[]).includes('vercel')) {
|
|
226
|
+
spinner.start('Creating Vercel project...');
|
|
227
|
+
const vercel = new VercelService(config);
|
|
228
|
+
const project = await vercel.createProject(projectName);
|
|
229
|
+
spinner.stop(`Vercel project created`);
|
|
230
|
+
|
|
231
|
+
// Configure domain if specified
|
|
232
|
+
if (domain) {
|
|
233
|
+
spinner.start(`Configuring domain: ${domain}...`);
|
|
234
|
+
await vercel.addDomain(projectName, domain);
|
|
235
|
+
spinner.stop('Domain configured');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Deploy
|
|
239
|
+
spinner.start('Deploying to Vercel...');
|
|
240
|
+
const deployment = await vercel.deploy(projectPath);
|
|
241
|
+
spinner.stop(`Deployed: ${deployment.url}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Step 6: Generate CLAUDE.md
|
|
245
|
+
spinner.start('Generating CLAUDE.md...');
|
|
246
|
+
const claudeMd = generateClaudeMd(projectConfig);
|
|
247
|
+
await fs.writeFile(path.join(projectPath, 'CLAUDE.md'), claudeMd);
|
|
248
|
+
spinner.stop('CLAUDE.md generated');
|
|
249
|
+
|
|
250
|
+
// Step 7: Setup git hooks
|
|
251
|
+
spinner.start('Setting up CodeBakers enforcement...');
|
|
252
|
+
await setupGitHooks(projectPath);
|
|
253
|
+
spinner.stop('CodeBakers enforcement configured');
|
|
254
|
+
|
|
255
|
+
// Save project to config
|
|
256
|
+
config.addProject({
|
|
257
|
+
name: projectName,
|
|
258
|
+
path: projectPath,
|
|
259
|
+
createdAt: new Date().toISOString(),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Success!
|
|
263
|
+
p.outro(chalk.green(`
|
|
264
|
+
ā Project created!
|
|
265
|
+
|
|
266
|
+
${chalk.bold('Your project is ready:')}
|
|
267
|
+
${chalk.cyan(`cd ${projectName}`)}
|
|
268
|
+
${chalk.cyan('codebakers code')}
|
|
269
|
+
|
|
270
|
+
${chalk.dim('Shortcuts:')}
|
|
271
|
+
${chalk.cyan('codebakers')} ā Interactive menu
|
|
272
|
+
${chalk.cyan('codebakers code')} ā AI coding agent
|
|
273
|
+
${chalk.cyan('codebakers check')} ā Pattern enforcement
|
|
274
|
+
${chalk.cyan('codebakers deploy')} ā Deploy to production
|
|
275
|
+
`));
|
|
276
|
+
|
|
277
|
+
} catch (error) {
|
|
278
|
+
spinner.stop('Error occurred');
|
|
279
|
+
p.log.error(`Failed to create project: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
280
|
+
|
|
281
|
+
// Offer to clean up
|
|
282
|
+
const cleanup = await p.confirm({
|
|
283
|
+
message: 'Clean up partially created project?',
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (cleanup && !p.isCancel(cleanup)) {
|
|
287
|
+
await fs.remove(projectPath);
|
|
288
|
+
p.log.info('Cleaned up.');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function createLocalProject(
|
|
294
|
+
projectPath: string,
|
|
295
|
+
config: Record<string, unknown>
|
|
296
|
+
): Promise<void> {
|
|
297
|
+
const framework = config.framework as string;
|
|
298
|
+
const ui = config.ui as string;
|
|
299
|
+
const packages = config.packages as string[];
|
|
300
|
+
|
|
301
|
+
// Create directory structure
|
|
302
|
+
await fs.ensureDir(projectPath);
|
|
303
|
+
await fs.ensureDir(path.join(projectPath, '.codebakers'));
|
|
304
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'app'));
|
|
305
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'components', 'ui'));
|
|
306
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'components', 'features'));
|
|
307
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'lib'));
|
|
308
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'hooks'));
|
|
309
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'types'));
|
|
310
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'stores'));
|
|
311
|
+
await fs.ensureDir(path.join(projectPath, 'src', 'services'));
|
|
312
|
+
|
|
313
|
+
// Create package.json
|
|
314
|
+
const packageJson = {
|
|
315
|
+
name: path.basename(projectPath),
|
|
316
|
+
version: '0.1.0',
|
|
317
|
+
private: true,
|
|
318
|
+
scripts: {
|
|
319
|
+
dev: 'next dev',
|
|
320
|
+
build: 'next build',
|
|
321
|
+
start: 'next start',
|
|
322
|
+
lint: 'next lint',
|
|
323
|
+
typecheck: 'tsc --noEmit',
|
|
324
|
+
'codebakers:check': 'codebakers check',
|
|
325
|
+
},
|
|
326
|
+
dependencies: {
|
|
327
|
+
next: '^14.2.0',
|
|
328
|
+
react: '^18.3.0',
|
|
329
|
+
'react-dom': '^18.3.0',
|
|
330
|
+
},
|
|
331
|
+
devDependencies: {
|
|
332
|
+
typescript: '^5.5.0',
|
|
333
|
+
'@types/node': '^22.0.0',
|
|
334
|
+
'@types/react': '^18.3.0',
|
|
335
|
+
'@types/react-dom': '^18.3.0',
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Add packages based on selection
|
|
340
|
+
if (packages.includes('tailwind')) {
|
|
341
|
+
packageJson.devDependencies['tailwindcss'] = '^3.4.0';
|
|
342
|
+
packageJson.devDependencies['postcss'] = '^8.4.0';
|
|
343
|
+
packageJson.devDependencies['autoprefixer'] = '^10.4.0';
|
|
344
|
+
}
|
|
345
|
+
if (packages.includes('zod')) {
|
|
346
|
+
packageJson.dependencies['zod'] = '^3.23.0';
|
|
347
|
+
}
|
|
348
|
+
if (packages.includes('zustand')) {
|
|
349
|
+
packageJson.dependencies['zustand'] = '^4.5.0';
|
|
350
|
+
}
|
|
351
|
+
if (packages.includes('tanstack-query')) {
|
|
352
|
+
packageJson.dependencies['@tanstack/react-query'] = '^5.50.0';
|
|
353
|
+
}
|
|
354
|
+
if (packages.includes('react-hook-form')) {
|
|
355
|
+
packageJson.dependencies['react-hook-form'] = '^7.52.0';
|
|
356
|
+
packageJson.dependencies['@hookform/resolvers'] = '^3.9.0';
|
|
357
|
+
}
|
|
358
|
+
if (packages.includes('lucide')) {
|
|
359
|
+
packageJson.dependencies['lucide-react'] = '^0.400.0';
|
|
360
|
+
}
|
|
361
|
+
if (ui === 'shadcn') {
|
|
362
|
+
packageJson.dependencies['class-variance-authority'] = '^0.7.0';
|
|
363
|
+
packageJson.dependencies['clsx'] = '^2.1.0';
|
|
364
|
+
packageJson.dependencies['tailwind-merge'] = '^2.4.0';
|
|
365
|
+
packageJson.dependencies['sonner'] = '^1.5.0';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Add Supabase
|
|
369
|
+
packageJson.dependencies['@supabase/supabase-js'] = '^2.45.0';
|
|
370
|
+
packageJson.dependencies['@supabase/ssr'] = '^0.4.0';
|
|
371
|
+
|
|
372
|
+
await fs.writeJson(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
|
|
373
|
+
|
|
374
|
+
// Create other config files
|
|
375
|
+
await createConfigFiles(projectPath, config);
|
|
376
|
+
|
|
377
|
+
// Create CodeBakers project config
|
|
378
|
+
await fs.writeJson(
|
|
379
|
+
path.join(projectPath, '.codebakers', 'config.json'),
|
|
380
|
+
{
|
|
381
|
+
version: '1.0.0',
|
|
382
|
+
framework,
|
|
383
|
+
ui,
|
|
384
|
+
packages,
|
|
385
|
+
patterns: ['00-core', '01-database', '02-auth', '03-api', '04-frontend'],
|
|
386
|
+
createdAt: new Date().toISOString(),
|
|
387
|
+
},
|
|
388
|
+
{ spaces: 2 }
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function createConfigFiles(
|
|
393
|
+
projectPath: string,
|
|
394
|
+
config: Record<string, unknown>
|
|
395
|
+
): Promise<void> {
|
|
396
|
+
// TypeScript config
|
|
397
|
+
const tsConfig = {
|
|
398
|
+
compilerOptions: {
|
|
399
|
+
target: 'ES2022',
|
|
400
|
+
lib: ['dom', 'dom.iterable', 'ES2022'],
|
|
401
|
+
allowJs: true,
|
|
402
|
+
skipLibCheck: true,
|
|
403
|
+
strict: true,
|
|
404
|
+
noEmit: true,
|
|
405
|
+
esModuleInterop: true,
|
|
406
|
+
module: 'esnext',
|
|
407
|
+
moduleResolution: 'bundler',
|
|
408
|
+
resolveJsonModule: true,
|
|
409
|
+
isolatedModules: true,
|
|
410
|
+
jsx: 'preserve',
|
|
411
|
+
incremental: true,
|
|
412
|
+
plugins: [{ name: 'next' }],
|
|
413
|
+
paths: { '@/*': ['./src/*'] },
|
|
414
|
+
},
|
|
415
|
+
include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
|
|
416
|
+
exclude: ['node_modules'],
|
|
417
|
+
};
|
|
418
|
+
await fs.writeJson(path.join(projectPath, 'tsconfig.json'), tsConfig, { spaces: 2 });
|
|
419
|
+
|
|
420
|
+
// Next.js config
|
|
421
|
+
const nextConfig = `/** @type {import('next').NextConfig} */
|
|
422
|
+
const nextConfig = {
|
|
423
|
+
experimental: {
|
|
424
|
+
typedRoutes: true,
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
export default nextConfig;
|
|
429
|
+
`;
|
|
430
|
+
await fs.writeFile(path.join(projectPath, 'next.config.mjs'), nextConfig);
|
|
431
|
+
|
|
432
|
+
// Tailwind config
|
|
433
|
+
const tailwindConfig = `import type { Config } from 'tailwindcss';
|
|
434
|
+
|
|
435
|
+
const config: Config = {
|
|
436
|
+
darkMode: ['class'],
|
|
437
|
+
content: [
|
|
438
|
+
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
439
|
+
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
440
|
+
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
441
|
+
],
|
|
442
|
+
theme: {
|
|
443
|
+
extend: {
|
|
444
|
+
colors: {
|
|
445
|
+
border: 'hsl(var(--border))',
|
|
446
|
+
input: 'hsl(var(--input))',
|
|
447
|
+
ring: 'hsl(var(--ring))',
|
|
448
|
+
background: 'hsl(var(--background))',
|
|
449
|
+
foreground: 'hsl(var(--foreground))',
|
|
450
|
+
primary: {
|
|
451
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
452
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
453
|
+
},
|
|
454
|
+
secondary: {
|
|
455
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
456
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
457
|
+
},
|
|
458
|
+
destructive: {
|
|
459
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
460
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
461
|
+
},
|
|
462
|
+
muted: {
|
|
463
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
464
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
465
|
+
},
|
|
466
|
+
accent: {
|
|
467
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
468
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
borderRadius: {
|
|
472
|
+
lg: 'var(--radius)',
|
|
473
|
+
md: 'calc(var(--radius) - 2px)',
|
|
474
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
plugins: [require('tailwindcss-animate')],
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
export default config;
|
|
482
|
+
`;
|
|
483
|
+
await fs.writeFile(path.join(projectPath, 'tailwind.config.ts'), tailwindConfig);
|
|
484
|
+
|
|
485
|
+
// .env.example
|
|
486
|
+
const envExample = `# Supabase
|
|
487
|
+
NEXT_PUBLIC_SUPABASE_URL=
|
|
488
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
|
489
|
+
SUPABASE_SERVICE_ROLE_KEY=
|
|
490
|
+
|
|
491
|
+
# Stripe (optional)
|
|
492
|
+
STRIPE_SECRET_KEY=
|
|
493
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
|
494
|
+
STRIPE_WEBHOOK_SECRET=
|
|
495
|
+
|
|
496
|
+
# App
|
|
497
|
+
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
498
|
+
`;
|
|
499
|
+
await fs.writeFile(path.join(projectPath, '.env.example'), envExample);
|
|
500
|
+
await fs.writeFile(path.join(projectPath, '.env.local'), envExample);
|
|
501
|
+
|
|
502
|
+
// .gitignore
|
|
503
|
+
const gitignore = `# Dependencies
|
|
504
|
+
node_modules/
|
|
505
|
+
.pnp
|
|
506
|
+
.pnp.js
|
|
507
|
+
|
|
508
|
+
# Build
|
|
509
|
+
.next/
|
|
510
|
+
out/
|
|
511
|
+
build/
|
|
512
|
+
dist/
|
|
513
|
+
|
|
514
|
+
# Environment
|
|
515
|
+
.env
|
|
516
|
+
.env.local
|
|
517
|
+
.env.*.local
|
|
518
|
+
|
|
519
|
+
# IDE
|
|
520
|
+
.vscode/
|
|
521
|
+
.idea/
|
|
522
|
+
*.swp
|
|
523
|
+
*.swo
|
|
524
|
+
|
|
525
|
+
# OS
|
|
526
|
+
.DS_Store
|
|
527
|
+
Thumbs.db
|
|
528
|
+
|
|
529
|
+
# Logs
|
|
530
|
+
npm-debug.log*
|
|
531
|
+
yarn-debug.log*
|
|
532
|
+
yarn-error.log*
|
|
533
|
+
|
|
534
|
+
# Testing
|
|
535
|
+
coverage/
|
|
536
|
+
.nyc_output/
|
|
537
|
+
|
|
538
|
+
# Misc
|
|
539
|
+
*.pem
|
|
540
|
+
`;
|
|
541
|
+
await fs.writeFile(path.join(projectPath, '.gitignore'), gitignore);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function setupGitHooks(projectPath: string): Promise<void> {
|
|
545
|
+
// Create pre-commit hook
|
|
546
|
+
const hooksDir = path.join(projectPath, '.git', 'hooks');
|
|
547
|
+
|
|
548
|
+
// Check if git is initialized
|
|
549
|
+
if (!await fs.pathExists(path.join(projectPath, '.git'))) {
|
|
550
|
+
await execa('git', ['init'], { cwd: projectPath });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
await fs.ensureDir(hooksDir);
|
|
554
|
+
|
|
555
|
+
const preCommitHook = `#!/bin/sh
|
|
556
|
+
# CodeBakers pre-commit hook
|
|
557
|
+
|
|
558
|
+
echo "š Running CodeBakers check..."
|
|
559
|
+
|
|
560
|
+
# Run pattern check
|
|
561
|
+
codebakers check
|
|
562
|
+
|
|
563
|
+
# If check fails, abort commit
|
|
564
|
+
if [ $? -ne 0 ]; then
|
|
565
|
+
echo "ā CodeBakers check failed. Fix issues or use --no-verify to bypass."
|
|
566
|
+
exit 1
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
echo "ā CodeBakers check passed"
|
|
570
|
+
`;
|
|
571
|
+
|
|
572
|
+
await fs.writeFile(path.join(hooksDir, 'pre-commit'), preCommitHook);
|
|
573
|
+
await fs.chmod(path.join(hooksDir, 'pre-commit'), '755');
|
|
574
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Config } from '../utils/config.js';
|
|
4
|
+
|
|
5
|
+
export async function learnCommand(): Promise<void> {
|
|
6
|
+
const config = new Config();
|
|
7
|
+
const learning = config.getLearning();
|
|
8
|
+
|
|
9
|
+
p.intro(chalk.bgCyan.black(' Learning Settings '));
|
|
10
|
+
|
|
11
|
+
console.log(chalk.bold('\nš§ What CodeBakers has learned about you:\n'));
|
|
12
|
+
|
|
13
|
+
const shortcuts = Object.entries(learning.shortcuts);
|
|
14
|
+
if (shortcuts.length > 0) {
|
|
15
|
+
console.log(chalk.bold('Shortcuts:'));
|
|
16
|
+
for (const [short, full] of shortcuts) {
|
|
17
|
+
console.log(` ${chalk.cyan(short)} ā ${full}`);
|
|
18
|
+
}
|
|
19
|
+
console.log('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const prefs = Object.entries(learning.preferences);
|
|
23
|
+
if (prefs.length > 0) {
|
|
24
|
+
console.log(chalk.bold('Preferences:'));
|
|
25
|
+
for (const [key, value] of prefs) {
|
|
26
|
+
console.log(` ${key}: ${chalk.cyan(String(value))}`);
|
|
27
|
+
}
|
|
28
|
+
console.log('');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (shortcuts.length === 0 && prefs.length === 0) {
|
|
32
|
+
console.log(chalk.dim(' Nothing learned yet. Use CodeBakers more!\n'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
p.outro('');
|
|
36
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs-extra';
|
|
4
|
+
import glob from 'fast-glob';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
|
|
7
|
+
export async function securityCommand(): Promise<void> {
|
|
8
|
+
p.intro(chalk.bgCyan.black(' Security Audit '));
|
|
9
|
+
|
|
10
|
+
const spinner = p.spinner();
|
|
11
|
+
spinner.start('Scanning for security issues...');
|
|
12
|
+
|
|
13
|
+
const issues = await runSecurityScan();
|
|
14
|
+
|
|
15
|
+
spinner.stop('Scan complete');
|
|
16
|
+
|
|
17
|
+
if (issues.length === 0) {
|
|
18
|
+
console.log(chalk.green('\nā No security issues found!\n'));
|
|
19
|
+
displaySecurityScore(100);
|
|
20
|
+
} else {
|
|
21
|
+
console.log(chalk.yellow(`\nā ļø ${issues.length} security issues found:\n`));
|
|
22
|
+
|
|
23
|
+
for (const issue of issues) {
|
|
24
|
+
const icon = issue.severity === 'critical' ? chalk.red('š“') :
|
|
25
|
+
issue.severity === 'high' ? chalk.red('š ') :
|
|
26
|
+
chalk.yellow('š”');
|
|
27
|
+
|
|
28
|
+
console.log(`${icon} ${chalk.bold(issue.title)}`);
|
|
29
|
+
console.log(chalk.dim(` ${issue.file}:${issue.line}`));
|
|
30
|
+
console.log(` ${issue.description}\n`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const score = Math.max(0, 100 -
|
|
34
|
+
(issues.filter(i => i.severity === 'critical').length * 30) -
|
|
35
|
+
(issues.filter(i => i.severity === 'high').length * 15));
|
|
36
|
+
displaySecurityScore(score);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
p.outro('');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface SecurityIssue {
|
|
43
|
+
title: string;
|
|
44
|
+
description: string;
|
|
45
|
+
file: string;
|
|
46
|
+
line: number;
|
|
47
|
+
severity: 'critical' | 'high' | 'medium';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runSecurityScan(): Promise<SecurityIssue[]> {
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
const issues: SecurityIssue[] = [];
|
|
53
|
+
|
|
54
|
+
const files = await glob(['src/**/*.{ts,tsx,js,jsx}'], {
|
|
55
|
+
cwd,
|
|
56
|
+
ignore: ['**/node_modules/**'],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const patterns = [
|
|
60
|
+
{
|
|
61
|
+
pattern: /(sk_live_|sk_test_)[a-zA-Z0-9]+/g,
|
|
62
|
+
title: 'Hardcoded Stripe key',
|
|
63
|
+
severity: 'critical' as const,
|
|
64
|
+
description: 'Stripe API key should be in environment variables',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
pattern: /\beval\s*\(/g,
|
|
68
|
+
title: 'Use of eval()',
|
|
69
|
+
severity: 'critical' as const,
|
|
70
|
+
description: 'eval() is a security vulnerability',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
pattern: /dangerouslySetInnerHTML/g,
|
|
74
|
+
title: 'dangerouslySetInnerHTML',
|
|
75
|
+
severity: 'high' as const,
|
|
76
|
+
description: 'Ensure content is sanitized with DOMPurify',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
const content = await fs.readFile(path.join(cwd, file), 'utf-8');
|
|
82
|
+
|
|
83
|
+
for (const { pattern, title, severity, description } of patterns) {
|
|
84
|
+
let match;
|
|
85
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
86
|
+
while ((match = regex.exec(content)) !== null) {
|
|
87
|
+
const line = content.substring(0, match.index).split('\n').length;
|
|
88
|
+
issues.push({ title, description, file, line, severity });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return issues;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function displaySecurityScore(score: number): void {
|
|
97
|
+
const color = score >= 80 ? chalk.green : score >= 50 ? chalk.yellow : chalk.red;
|
|
98
|
+
const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'F';
|
|
99
|
+
|
|
100
|
+
console.log(chalk.bold('Security Score:'));
|
|
101
|
+
console.log(color(`\n ${score}/100 Grade: ${grade}\n`));
|
|
102
|
+
}
|