kyawthiha-nextjs-agent-cli 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/README.md +188 -0
- package/dist/agent/agent.js +340 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/prompts/agent-prompt.js +527 -0
- package/dist/agent/summarizer.js +97 -0
- package/dist/agent/tools/ast-tools.js +601 -0
- package/dist/agent/tools/code-tools.js +1059 -0
- package/dist/agent/tools/file-tools.js +199 -0
- package/dist/agent/tools/index.js +25 -0
- package/dist/agent/tools/search-tools.js +404 -0
- package/dist/agent/tools/shell-tools.js +334 -0
- package/dist/agent/types.js +4 -0
- package/dist/cli/commands/config.js +61 -0
- package/dist/cli/commands/start.js +236 -0
- package/dist/cli/index.js +12 -0
- package/dist/utils/cred-store.js +70 -0
- package/dist/utils/logger.js +9 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code and shell execution tools for the AI Agent
|
|
3
|
+
* Supports pnpm, project scaffolding, and fuzzy search
|
|
4
|
+
*/
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
/**
|
|
11
|
+
* Tool: Create Next.js project with TypeScript + Tailwind + App Router
|
|
12
|
+
*/
|
|
13
|
+
export const createProjectTool = {
|
|
14
|
+
name: 'create_nextjs_project',
|
|
15
|
+
description: 'Create a new Next.js 14+ project with TypeScript, Tailwind CSS, ESLint, and App Router using pnpm. This is the recommended setup for SSR websites.',
|
|
16
|
+
parameters: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
projectPath: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'The directory path where the project will be created'
|
|
22
|
+
},
|
|
23
|
+
projectName: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'The name of the project (for package.json)'
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: ['projectPath', 'projectName']
|
|
29
|
+
},
|
|
30
|
+
execute: async (input) => {
|
|
31
|
+
try {
|
|
32
|
+
const { projectPath, projectName } = input;
|
|
33
|
+
// Ensure parent directory exists
|
|
34
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
35
|
+
// Check if project already exists
|
|
36
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
37
|
+
const exists = await fs.access(packageJsonPath).then(() => true).catch(() => false);
|
|
38
|
+
if (exists) {
|
|
39
|
+
return `Project already exists at ${projectPath}. Use run_command to install dependencies if needed.`;
|
|
40
|
+
}
|
|
41
|
+
// Create Next.js project with all recommended options
|
|
42
|
+
// --typescript: TypeScript support
|
|
43
|
+
// --tailwind: Tailwind CSS pre-configured
|
|
44
|
+
// --eslint: ESLint for linting
|
|
45
|
+
// --app: App Router (not Pages Router)
|
|
46
|
+
// --src-dir: Use src/ directory
|
|
47
|
+
// --import-alias: @/* path alias
|
|
48
|
+
// --use-pnpm: Use pnpm package manager
|
|
49
|
+
const createCommand = `pnpm create next-app@latest . --yes --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-pnpm`;
|
|
50
|
+
await execAsync(createCommand, {
|
|
51
|
+
cwd: projectPath,
|
|
52
|
+
timeout: 180000,
|
|
53
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'
|
|
54
|
+
});
|
|
55
|
+
// Install clsx and tailwind-merge for the cn() helper
|
|
56
|
+
await execAsync('pnpm add clsx tailwind-merge', {
|
|
57
|
+
cwd: projectPath,
|
|
58
|
+
timeout: 60000,
|
|
59
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'
|
|
60
|
+
});
|
|
61
|
+
return `Successfully created Next.js 14+ project at ${projectPath} with TypeScript, Tailwind CSS, ESLint, and App Router. The cn() utility is ready in src/lib/utils.ts.`;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return `Error creating project: ${error.message}\n${error.stderr || ''}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Tool: Install packages with pnpm
|
|
70
|
+
*/
|
|
71
|
+
export const installPackagesTool = {
|
|
72
|
+
name: 'install_packages',
|
|
73
|
+
description: 'Install npm packages using pnpm. Can install multiple packages at once, with optional dev flag.',
|
|
74
|
+
parameters: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
projectPath: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'The project directory containing package.json'
|
|
80
|
+
},
|
|
81
|
+
packages: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Space-separated package names (e.g., "react-router-dom lucide-react")'
|
|
84
|
+
},
|
|
85
|
+
dev: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'Set to "true" to install as dev dependencies',
|
|
88
|
+
enum: ['true', 'false']
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
required: ['projectPath', 'packages']
|
|
92
|
+
},
|
|
93
|
+
execute: async (input) => {
|
|
94
|
+
try {
|
|
95
|
+
const { projectPath, packages, dev } = input;
|
|
96
|
+
const devFlag = dev === 'true' ? '-D' : '';
|
|
97
|
+
const command = `pnpm add ${devFlag} ${packages}`.trim();
|
|
98
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
99
|
+
cwd: projectPath,
|
|
100
|
+
timeout: 180000,
|
|
101
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'
|
|
102
|
+
});
|
|
103
|
+
return `Installed packages: ${packages}\n${stdout}\n${stderr}`;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return `Error installing packages: ${error.message}\n${error.stderr || ''}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Tool: Check TypeScript errors
|
|
112
|
+
*/
|
|
113
|
+
export const checkTypescriptTool = {
|
|
114
|
+
name: 'check_typescript',
|
|
115
|
+
description: 'Run TypeScript compiler to check for type errors in the project.',
|
|
116
|
+
parameters: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
projectPath: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
description: 'The project directory containing tsconfig.json'
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
required: ['projectPath']
|
|
125
|
+
},
|
|
126
|
+
execute: async (input) => {
|
|
127
|
+
try {
|
|
128
|
+
const { stdout, stderr } = await execAsync('npx tsc --noEmit', {
|
|
129
|
+
cwd: input.projectPath,
|
|
130
|
+
timeout: 60000,
|
|
131
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'
|
|
132
|
+
});
|
|
133
|
+
if (stdout || stderr) {
|
|
134
|
+
return `TypeScript check output:\n${stdout}\n${stderr}`;
|
|
135
|
+
}
|
|
136
|
+
return 'TypeScript check passed - no errors found';
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return `TypeScript errors found:\n${error.stdout || ''}\n${error.stderr || error.message}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Tool: Check Build Errors
|
|
145
|
+
*/
|
|
146
|
+
export const checkBuildErrorTool = {
|
|
147
|
+
name: 'check_build_errors',
|
|
148
|
+
description: 'Run pnpm run build to check for compilation and syntax errors. This is useful for verifying that changes compile correctly.',
|
|
149
|
+
parameters: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
projectPath: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'The project directory containing package.json'
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
required: ['projectPath']
|
|
158
|
+
},
|
|
159
|
+
execute: async (input) => {
|
|
160
|
+
try {
|
|
161
|
+
const { stdout, stderr } = await execAsync('pnpm run build', {
|
|
162
|
+
cwd: input.projectPath,
|
|
163
|
+
timeout: 300000, // 5 minutes
|
|
164
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'
|
|
165
|
+
});
|
|
166
|
+
return `Build successful:\n${stdout}\n${stderr}`;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
return `Build failed with errors:\n${error.stdout || ''}\n${error.stderr || error.message}`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Tool: Setup shadcn/ui for Next.js project
|
|
175
|
+
*/
|
|
176
|
+
export const setupShadcnTool = {
|
|
177
|
+
name: 'setup_shadcn_ui',
|
|
178
|
+
description: 'Initialize shadcn/ui in a Next.js project. Run this AFTER Tailwind is setup. Uses default configuration (-d).',
|
|
179
|
+
parameters: {
|
|
180
|
+
type: 'object',
|
|
181
|
+
properties: {
|
|
182
|
+
projectPath: {
|
|
183
|
+
type: 'string',
|
|
184
|
+
description: 'The project directory'
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
required: ['projectPath']
|
|
188
|
+
},
|
|
189
|
+
execute: async (input) => {
|
|
190
|
+
try {
|
|
191
|
+
const { projectPath } = input;
|
|
192
|
+
// initialize shadcn with defaults
|
|
193
|
+
// -d: defaults
|
|
194
|
+
const command = 'pnpm dlx shadcn@latest init -d';
|
|
195
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
196
|
+
cwd: projectPath,
|
|
197
|
+
timeout: 180000,
|
|
198
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'
|
|
199
|
+
});
|
|
200
|
+
return `shadcn/ui initialized successfully.\n${stdout}\n${stderr}`;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return `Error initializing shadcn/ui: ${error.message}\n${error.stderr || ''}`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Tool: Install all shadcn/ui components
|
|
209
|
+
*/
|
|
210
|
+
export const installShadcnComponentsTool = {
|
|
211
|
+
name: 'install_shadcn_components',
|
|
212
|
+
description: 'Install ALL shadcn/ui components into the project. Uses overwrite flag.',
|
|
213
|
+
parameters: {
|
|
214
|
+
type: 'object',
|
|
215
|
+
properties: {
|
|
216
|
+
projectPath: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
description: 'The project directory'
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
required: ['projectPath']
|
|
222
|
+
},
|
|
223
|
+
execute: async (input) => {
|
|
224
|
+
try {
|
|
225
|
+
const { projectPath } = input;
|
|
226
|
+
// Add all components
|
|
227
|
+
// -a: all
|
|
228
|
+
// -y: yes (skip confirmation)
|
|
229
|
+
// --overwrite: overwrite existing files
|
|
230
|
+
const command = 'pnpm dlx shadcn@latest add -a -y --overwrite';
|
|
231
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
232
|
+
cwd: projectPath,
|
|
233
|
+
timeout: 300000, // 5 minutes as installing all components takes time
|
|
234
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash'
|
|
235
|
+
});
|
|
236
|
+
return `All shadcn/ui components installed successfully.\n${stdout}\n${stderr}`;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
return `Error installing shadcn/ui components: ${error.message}\n${error.stderr || ''}`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Tool: Setup Prisma 7 with PostgreSQL (Driver Adapter)
|
|
245
|
+
*/
|
|
246
|
+
export const setupPrismaTool = {
|
|
247
|
+
name: 'setup_prisma',
|
|
248
|
+
description: 'Initialize Prisma 7 ORM with PostgreSQL in a Next.js project. Uses the new prisma-client provider with driver adapters. Creates prisma.config.ts, schema.prisma with output field, and lib/prisma.ts singleton.',
|
|
249
|
+
parameters: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
projectPath: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'The project directory'
|
|
255
|
+
},
|
|
256
|
+
databaseUrl: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
description: 'The database URL (e.g. postgresql://user:password@localhost:5432/mydb)'
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
required: ['projectPath', 'databaseUrl']
|
|
262
|
+
},
|
|
263
|
+
execute: async (input) => {
|
|
264
|
+
try {
|
|
265
|
+
const { projectPath, databaseUrl } = input;
|
|
266
|
+
const shell = process.platform === 'win32' ? 'powershell.exe' : '/bin/bash';
|
|
267
|
+
// Step 1: Install Prisma 7 dependencies
|
|
268
|
+
await execAsync('pnpm add -D prisma tsx @types/pg', { cwd: projectPath, shell });
|
|
269
|
+
await execAsync('pnpm add @prisma/client @prisma/adapter-pg dotenv pg', { cwd: projectPath, shell });
|
|
270
|
+
// Step 2: Create prisma directory
|
|
271
|
+
const prismaDir = path.join(projectPath, 'prisma');
|
|
272
|
+
await fs.mkdir(prismaDir, { recursive: true });
|
|
273
|
+
// Step 3: Create .env file with DATABASE_URL
|
|
274
|
+
await fs.writeFile(path.join(projectPath, '.env'), `DATABASE_URL="${databaseUrl}"\n`);
|
|
275
|
+
// Step 4: Create prisma/schema.prisma (Prisma 7 syntax)
|
|
276
|
+
const schemaContent = `// Prisma 7 Schema
|
|
277
|
+
// Documentation: https://www.prisma.io/docs/guides/nextjs
|
|
278
|
+
|
|
279
|
+
generator client {
|
|
280
|
+
provider = "prisma-client"
|
|
281
|
+
output = "../src/generated/prisma"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
datasource db {
|
|
285
|
+
provider = "postgresql"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Example models - customize as needed
|
|
289
|
+
model User {
|
|
290
|
+
id Int @id @default(autoincrement())
|
|
291
|
+
email String @unique
|
|
292
|
+
name String?
|
|
293
|
+
posts Post[]
|
|
294
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
295
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
296
|
+
|
|
297
|
+
@@map("users")
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
model Post {
|
|
301
|
+
id Int @id @default(autoincrement())
|
|
302
|
+
title String
|
|
303
|
+
content String?
|
|
304
|
+
published Boolean @default(false)
|
|
305
|
+
authorId Int @map("author_id")
|
|
306
|
+
author User @relation(fields: [authorId], references: [id])
|
|
307
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
308
|
+
|
|
309
|
+
@@map("posts")
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
await fs.writeFile(path.join(prismaDir, 'schema.prisma'), schemaContent);
|
|
313
|
+
// Step 5: Create prisma.config.ts at project root (Prisma 7 requirement)
|
|
314
|
+
const prismaConfigContent = `import 'dotenv/config'
|
|
315
|
+
import { defineConfig, env } from 'prisma/config';
|
|
316
|
+
|
|
317
|
+
export default defineConfig({
|
|
318
|
+
schema: 'prisma/schema.prisma',
|
|
319
|
+
migrations: {
|
|
320
|
+
path: 'prisma/migrations',
|
|
321
|
+
seed: 'tsx prisma/seed.ts',
|
|
322
|
+
},
|
|
323
|
+
datasource: {
|
|
324
|
+
url: env('DATABASE_URL'),
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
`;
|
|
328
|
+
await fs.writeFile(path.join(projectPath, 'prisma.config.ts'), prismaConfigContent);
|
|
329
|
+
// Step 6: Create src/lib/prisma.ts singleton with driver adapter (Prisma 7)
|
|
330
|
+
const libDir = path.join(projectPath, 'src', 'lib');
|
|
331
|
+
await fs.mkdir(libDir, { recursive: true });
|
|
332
|
+
const prismaClientContent = `// Prisma 7 Client with Driver Adapter
|
|
333
|
+
// Import from generated path (NOT @prisma/client)
|
|
334
|
+
import { PrismaClient } from '../generated/prisma/client'
|
|
335
|
+
import { PrismaPg } from '@prisma/adapter-pg'
|
|
336
|
+
|
|
337
|
+
const globalForPrisma = global as unknown as {
|
|
338
|
+
prisma: PrismaClient
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const adapter = new PrismaPg({
|
|
342
|
+
connectionString: process.env.DATABASE_URL,
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
const prisma = globalForPrisma.prisma || new PrismaClient({
|
|
346
|
+
adapter,
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
350
|
+
|
|
351
|
+
export default prisma
|
|
352
|
+
`;
|
|
353
|
+
await fs.writeFile(path.join(libDir, 'prisma.ts'), prismaClientContent);
|
|
354
|
+
// Step 7: Create prisma/seed.ts template (Prisma 7)
|
|
355
|
+
const seedContent = `// Prisma 7 Seed Script
|
|
356
|
+
// Run with: npx prisma db seed
|
|
357
|
+
import { PrismaClient, Prisma } from "../src/generated/prisma/client";
|
|
358
|
+
import { PrismaPg } from '@prisma/adapter-pg'
|
|
359
|
+
import 'dotenv/config'
|
|
360
|
+
|
|
361
|
+
const adapter = new PrismaPg({
|
|
362
|
+
connectionString: process.env.DATABASE_URL,
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
const prisma = new PrismaClient({
|
|
366
|
+
adapter,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const userData: Prisma.UserCreateInput[] = [
|
|
370
|
+
{
|
|
371
|
+
name: "Admin User",
|
|
372
|
+
email: "admin@example.com",
|
|
373
|
+
posts: {
|
|
374
|
+
create: [
|
|
375
|
+
{ title: "Welcome Post", content: "Hello World!", published: true },
|
|
376
|
+
{ title: "Draft Post", content: "Work in progress...", published: false },
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
|
|
382
|
+
export async function main() {
|
|
383
|
+
console.log('Seeding database...');
|
|
384
|
+
|
|
385
|
+
// Clean existing data
|
|
386
|
+
await prisma.post.deleteMany();
|
|
387
|
+
await prisma.user.deleteMany();
|
|
388
|
+
|
|
389
|
+
// Create seed data
|
|
390
|
+
for (const u of userData) {
|
|
391
|
+
const user = await prisma.user.create({ data: u });
|
|
392
|
+
console.log(\`Created user: \${user.email}\`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
console.log('Seeding completed!');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
main()
|
|
399
|
+
.catch((e) => {
|
|
400
|
+
console.error(e);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
})
|
|
403
|
+
.finally(async () => {
|
|
404
|
+
await prisma.$disconnect();
|
|
405
|
+
});
|
|
406
|
+
`;
|
|
407
|
+
await fs.writeFile(path.join(prismaDir, 'seed.ts'), seedContent);
|
|
408
|
+
// Step 8: Create src/generated/prisma directory for output
|
|
409
|
+
const generatedDir = path.join(projectPath, 'src', 'generated', 'prisma');
|
|
410
|
+
await fs.mkdir(generatedDir, { recursive: true });
|
|
411
|
+
// Step 9: Auto-create Database (optional, may fail if DB exists)
|
|
412
|
+
const createDbScript = `
|
|
413
|
+
import { Client } from 'pg';
|
|
414
|
+
import 'dotenv/config';
|
|
415
|
+
|
|
416
|
+
async function main() {
|
|
417
|
+
try {
|
|
418
|
+
const url = process.env.DATABASE_URL;
|
|
419
|
+
if (!url) {
|
|
420
|
+
console.error('DATABASE_URL not found');
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const dbUrl = new URL(url);
|
|
425
|
+
const dbName = dbUrl.pathname.substring(1);
|
|
426
|
+
|
|
427
|
+
dbUrl.pathname = 'postgres';
|
|
428
|
+
const postgresUrl = dbUrl.toString();
|
|
429
|
+
|
|
430
|
+
const client = new Client({ connectionString: postgresUrl });
|
|
431
|
+
await client.connect();
|
|
432
|
+
|
|
433
|
+
const checkRes = await client.query("SELECT 1 FROM pg_database WHERE datname=$1", [dbName]);
|
|
434
|
+
if (checkRes.rowCount === 0) {
|
|
435
|
+
console.log("Creating database " + dbName);
|
|
436
|
+
await client.query('CREATE DATABASE "' + dbName + '"');
|
|
437
|
+
console.log("Database created successfully");
|
|
438
|
+
} else {
|
|
439
|
+
console.log("Database " + dbName + " already exists");
|
|
440
|
+
}
|
|
441
|
+
await client.end();
|
|
442
|
+
} catch (e: any) {
|
|
443
|
+
console.error("Warning: Could not auto-create DB:", e.message);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
main();
|
|
447
|
+
`;
|
|
448
|
+
await fs.writeFile(path.join(projectPath, 'create-db.ts'), createDbScript);
|
|
449
|
+
try {
|
|
450
|
+
await execAsync('npx tsx create-db.ts', { cwd: projectPath, shell });
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
// Ignore errors - DB might already exist
|
|
454
|
+
}
|
|
455
|
+
await fs.unlink(path.join(projectPath, 'create-db.ts')).catch(() => { });
|
|
456
|
+
// Step 10: Generate Prisma Client
|
|
457
|
+
try {
|
|
458
|
+
await execAsync('npx prisma generate', { cwd: projectPath, shell });
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// May fail if schema has issues - user will fix
|
|
462
|
+
}
|
|
463
|
+
return `✅ Prisma 7 initialized with PostgreSQL and Driver Adapter.
|
|
464
|
+
|
|
465
|
+
Files created:
|
|
466
|
+
1. prisma/schema.prisma (provider: "prisma-client", output: "../src/generated/prisma")
|
|
467
|
+
2. prisma.config.ts (Prisma 7 CLI config at project root)
|
|
468
|
+
3. src/lib/prisma.ts (singleton with @prisma/adapter-pg)
|
|
469
|
+
4. prisma/seed.ts (seed script template)
|
|
470
|
+
5. .env (DATABASE_URL configured)
|
|
471
|
+
|
|
472
|
+
Dependencies installed:
|
|
473
|
+
- prisma, tsx, @types/pg (dev)
|
|
474
|
+
- @prisma/client, @prisma/adapter-pg, pg, dotenv
|
|
475
|
+
|
|
476
|
+
IMPORTANT - Next Steps (in order):
|
|
477
|
+
1. Customize models in 'prisma/schema.prisma'
|
|
478
|
+
2. Run: npx prisma generate
|
|
479
|
+
3. Run: npx prisma migrate dev --name init
|
|
480
|
+
4. Run: npx prisma db seed (seeding is NOT automatic in Prisma 7)
|
|
481
|
+
`;
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
return `Error setting up Prisma: ${error.message}\n${error.stderr || ''}`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* Tool: Verify Project & Auto-fix
|
|
490
|
+
* Outputs structured JSON for AI to parse and take action
|
|
491
|
+
*/
|
|
492
|
+
export const verifyProjectTool = {
|
|
493
|
+
name: 'verify_project',
|
|
494
|
+
description: `Verify project setup (Next.js, Shadcn, Prisma) by running build and integration tests.
|
|
495
|
+
Returns structured JSON with:
|
|
496
|
+
- success: boolean indicating overall status
|
|
497
|
+
- checks: array of individual check results with status (pass/fail/warn/skip)
|
|
498
|
+
- suggestedFixes: actionable fixes with commands and tool names
|
|
499
|
+
- summary: counts of passed/failed/warnings
|
|
500
|
+
|
|
501
|
+
Use this output to determine next actions. Each failed check includes fixCommand or fixTool for remediation.`,
|
|
502
|
+
parameters: {
|
|
503
|
+
type: 'object',
|
|
504
|
+
properties: {
|
|
505
|
+
projectPath: {
|
|
506
|
+
type: 'string',
|
|
507
|
+
description: 'The project directory'
|
|
508
|
+
},
|
|
509
|
+
autoFix: {
|
|
510
|
+
type: 'string',
|
|
511
|
+
description: 'Set to "true" to attempt automatic fixes for missing configurations',
|
|
512
|
+
enum: ['true', 'false']
|
|
513
|
+
},
|
|
514
|
+
skipBuild: {
|
|
515
|
+
type: 'string',
|
|
516
|
+
description: 'Set to "true" to skip the build step (which can be slow)',
|
|
517
|
+
enum: ['true', 'false']
|
|
518
|
+
},
|
|
519
|
+
skipPrismaTest: {
|
|
520
|
+
type: 'string',
|
|
521
|
+
description: 'Set to "true" to skip Prisma integration test (useful when DB is not available)',
|
|
522
|
+
enum: ['true', 'false']
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
required: ['projectPath']
|
|
526
|
+
},
|
|
527
|
+
execute: async (input) => {
|
|
528
|
+
const result = {
|
|
529
|
+
success: true,
|
|
530
|
+
projectPath: input.projectPath,
|
|
531
|
+
summary: { total: 0, passed: 0, failed: 0, warnings: 0, skipped: 0 },
|
|
532
|
+
checks: [],
|
|
533
|
+
fixesApplied: [],
|
|
534
|
+
suggestedFixes: []
|
|
535
|
+
};
|
|
536
|
+
const addCheck = (check) => {
|
|
537
|
+
result.checks.push(check);
|
|
538
|
+
result.summary.total++;
|
|
539
|
+
switch (check.status) {
|
|
540
|
+
case 'pass':
|
|
541
|
+
result.summary.passed++;
|
|
542
|
+
break;
|
|
543
|
+
case 'fail':
|
|
544
|
+
result.summary.failed++;
|
|
545
|
+
result.success = false;
|
|
546
|
+
break;
|
|
547
|
+
case 'warn':
|
|
548
|
+
result.summary.warnings++;
|
|
549
|
+
break;
|
|
550
|
+
case 'skip':
|
|
551
|
+
result.summary.skipped++;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
try {
|
|
556
|
+
const { projectPath } = input;
|
|
557
|
+
const autoFix = input.autoFix === 'true';
|
|
558
|
+
const shell = process.platform === 'win32' ? 'powershell.exe' : '/bin/bash';
|
|
559
|
+
const checkFile = async (filePath) => {
|
|
560
|
+
try {
|
|
561
|
+
await fs.access(path.join(projectPath, filePath));
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
// 1. Verify Project Root (package.json)
|
|
569
|
+
const hasPackageJson = await checkFile('package.json');
|
|
570
|
+
if (hasPackageJson) {
|
|
571
|
+
addCheck({
|
|
572
|
+
name: 'project_root',
|
|
573
|
+
status: 'pass',
|
|
574
|
+
message: 'Valid Node.js project found',
|
|
575
|
+
filePath: 'package.json'
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
addCheck({
|
|
580
|
+
name: 'project_root',
|
|
581
|
+
status: 'fail',
|
|
582
|
+
message: 'Not a valid Node.js project - package.json missing',
|
|
583
|
+
filePath: 'package.json',
|
|
584
|
+
fixCommand: 'pnpm init'
|
|
585
|
+
});
|
|
586
|
+
result.suggestedFixes.push({
|
|
587
|
+
issue: 'Missing package.json',
|
|
588
|
+
command: 'pnpm init',
|
|
589
|
+
description: 'Initialize a new Node.js project'
|
|
590
|
+
});
|
|
591
|
+
return JSON.stringify(result, null, 2);
|
|
592
|
+
}
|
|
593
|
+
// 2. Verify Shadcn UI
|
|
594
|
+
const hasComponentsJson = await checkFile('components.json');
|
|
595
|
+
const hasUtilsSrc = await checkFile('src/lib/utils.ts');
|
|
596
|
+
const hasUtilsRoot = await checkFile('lib/utils.ts');
|
|
597
|
+
const hasUtils = hasUtilsSrc || hasUtilsRoot;
|
|
598
|
+
if (hasComponentsJson && hasUtils) {
|
|
599
|
+
addCheck({
|
|
600
|
+
name: 'shadcn_ui',
|
|
601
|
+
status: 'pass',
|
|
602
|
+
message: 'Shadcn/UI properly configured',
|
|
603
|
+
filePath: 'components.json'
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
const missingFiles = [];
|
|
608
|
+
if (!hasComponentsJson)
|
|
609
|
+
missingFiles.push('components.json');
|
|
610
|
+
if (!hasUtils)
|
|
611
|
+
missingFiles.push('src/lib/utils.ts');
|
|
612
|
+
addCheck({
|
|
613
|
+
name: 'shadcn_ui',
|
|
614
|
+
status: 'fail',
|
|
615
|
+
message: `Shadcn/UI configuration incomplete - missing: ${missingFiles.join(', ')}`,
|
|
616
|
+
filePath: missingFiles[0],
|
|
617
|
+
fixTool: 'setup_shadcn_ui',
|
|
618
|
+
fixCommand: 'pnpm dlx shadcn@latest init -d'
|
|
619
|
+
});
|
|
620
|
+
result.suggestedFixes.push({
|
|
621
|
+
issue: 'Shadcn/UI not configured',
|
|
622
|
+
tool: 'setup_shadcn_ui',
|
|
623
|
+
command: 'pnpm dlx shadcn@latest init -d',
|
|
624
|
+
description: 'Initialize Shadcn/UI with default configuration'
|
|
625
|
+
});
|
|
626
|
+
if (autoFix) {
|
|
627
|
+
try {
|
|
628
|
+
await setupShadcnTool.execute({ projectPath });
|
|
629
|
+
await installShadcnComponentsTool.execute({ projectPath });
|
|
630
|
+
result.fixesApplied.push('Shadcn/UI initialized and components installed');
|
|
631
|
+
}
|
|
632
|
+
catch (e) {
|
|
633
|
+
result.fixesApplied.push(`Shadcn/UI fix attempted but failed: ${e.message}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// 3. Verify Prisma Configuration
|
|
638
|
+
const hasSchema = await checkFile('prisma/schema.prisma');
|
|
639
|
+
const hasPrismaClient = await checkFile('src/lib/prisma.ts') || await checkFile('lib/prisma.ts');
|
|
640
|
+
let prismaReady = false;
|
|
641
|
+
if (hasSchema && hasPrismaClient) {
|
|
642
|
+
addCheck({
|
|
643
|
+
name: 'prisma_config',
|
|
644
|
+
status: 'pass',
|
|
645
|
+
message: 'Prisma configuration files present',
|
|
646
|
+
filePath: 'prisma/schema.prisma'
|
|
647
|
+
});
|
|
648
|
+
prismaReady = true;
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
const missingFiles = [];
|
|
652
|
+
if (!hasSchema)
|
|
653
|
+
missingFiles.push('prisma/schema.prisma');
|
|
654
|
+
if (!hasPrismaClient)
|
|
655
|
+
missingFiles.push('src/lib/prisma.ts');
|
|
656
|
+
addCheck({
|
|
657
|
+
name: 'prisma_config',
|
|
658
|
+
status: 'fail',
|
|
659
|
+
message: `Prisma configuration missing: ${missingFiles.join(', ')}`,
|
|
660
|
+
filePath: missingFiles[0],
|
|
661
|
+
fixTool: 'setup_prisma'
|
|
662
|
+
});
|
|
663
|
+
result.suggestedFixes.push({
|
|
664
|
+
issue: 'Prisma not configured',
|
|
665
|
+
tool: 'setup_prisma',
|
|
666
|
+
description: 'Initialize Prisma with PostgreSQL - requires DATABASE_URL parameter'
|
|
667
|
+
});
|
|
668
|
+
if (autoFix) {
|
|
669
|
+
try {
|
|
670
|
+
const res = await setupPrismaTool.execute({ projectPath });
|
|
671
|
+
result.fixesApplied.push(`Prisma setup: ${res}`);
|
|
672
|
+
prismaReady = true;
|
|
673
|
+
}
|
|
674
|
+
catch (e) {
|
|
675
|
+
result.fixesApplied.push(`Prisma fix failed: ${e.message}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// 4. Prisma Integration Test
|
|
680
|
+
if (input.skipPrismaTest === 'true') {
|
|
681
|
+
addCheck({
|
|
682
|
+
name: 'prisma_integration',
|
|
683
|
+
status: 'skip',
|
|
684
|
+
message: 'Prisma integration test skipped by user'
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
else if (prismaReady) {
|
|
688
|
+
try {
|
|
689
|
+
await execAsync('npx prisma generate', { cwd: projectPath, shell, timeout: 60000 });
|
|
690
|
+
const testScriptPath = path.join(projectPath, 'verify-db-temp.ts');
|
|
691
|
+
let schemaContent = '';
|
|
692
|
+
try {
|
|
693
|
+
schemaContent = await fs.readFile(path.join(projectPath, 'prisma/schema.prisma'), 'utf-8');
|
|
694
|
+
}
|
|
695
|
+
catch { }
|
|
696
|
+
const modelMatch = schemaContent.match(/model\s+(\w+)\s+{/);
|
|
697
|
+
const testModel = modelMatch ? modelMatch[1] : null;
|
|
698
|
+
// Determine correct import path for prisma client
|
|
699
|
+
const prismaImportPath = hasPrismaClient
|
|
700
|
+
? (await checkFile('src/lib/prisma.ts') ? './src/lib/prisma' : './lib/prisma')
|
|
701
|
+
: './src/lib/prisma';
|
|
702
|
+
const testCode = `
|
|
703
|
+
import prisma from "${prismaImportPath}";
|
|
704
|
+
|
|
705
|
+
async function main() {
|
|
706
|
+
await prisma.$connect();
|
|
707
|
+
await prisma.$queryRaw\`SELECT 1\`;
|
|
708
|
+
${testModel ? `await prisma.${testModel.toLowerCase()}.count();` : ''}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
main()
|
|
712
|
+
.catch((e) => { console.error(JSON.stringify({ error: e.message })); process.exit(1); })
|
|
713
|
+
.finally(() => prisma.$disconnect());
|
|
714
|
+
`;
|
|
715
|
+
await fs.writeFile(testScriptPath, testCode);
|
|
716
|
+
const { stdout, stderr } = await execAsync('npx tsx verify-db-temp.ts', {
|
|
717
|
+
cwd: projectPath, shell, timeout: 30000
|
|
718
|
+
});
|
|
719
|
+
await fs.unlink(testScriptPath).catch(() => { });
|
|
720
|
+
addCheck({
|
|
721
|
+
name: 'prisma_integration',
|
|
722
|
+
status: 'pass',
|
|
723
|
+
message: 'Database connection and query successful',
|
|
724
|
+
details: stdout.trim()
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
const errorMsg = error.stderr || error.stdout || error.message;
|
|
729
|
+
addCheck({
|
|
730
|
+
name: 'prisma_integration',
|
|
731
|
+
status: 'fail',
|
|
732
|
+
message: 'Database connection or query failed',
|
|
733
|
+
details: errorMsg,
|
|
734
|
+
fixCommand: 'npx prisma migrate dev --name init'
|
|
735
|
+
});
|
|
736
|
+
result.suggestedFixes.push({
|
|
737
|
+
issue: 'Database connection failed',
|
|
738
|
+
command: 'npx prisma migrate dev --name init',
|
|
739
|
+
description: 'Ensure DATABASE_URL in .env is correct, then run migrations'
|
|
740
|
+
});
|
|
741
|
+
// Cleanup temp file
|
|
742
|
+
await fs.unlink(path.join(projectPath, 'verify-db-temp.ts')).catch(() => { });
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// 5. TypeScript Check
|
|
746
|
+
try {
|
|
747
|
+
const { stdout, stderr } = await execAsync('npx tsc --noEmit 2>&1', {
|
|
748
|
+
cwd: projectPath, shell, timeout: 60000
|
|
749
|
+
});
|
|
750
|
+
const output = (stdout + stderr).trim();
|
|
751
|
+
if (output === '' || output.includes('0 errors')) {
|
|
752
|
+
addCheck({
|
|
753
|
+
name: 'typescript',
|
|
754
|
+
status: 'pass',
|
|
755
|
+
message: 'No TypeScript errors'
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
// Parse errors for structured output
|
|
760
|
+
const errorLines = output.split('\n').filter(l => l.includes('error TS'));
|
|
761
|
+
const errorCount = errorLines.length;
|
|
762
|
+
addCheck({
|
|
763
|
+
name: 'typescript',
|
|
764
|
+
status: 'fail',
|
|
765
|
+
message: `${errorCount} TypeScript error(s) found`,
|
|
766
|
+
details: errorLines.slice(0, 10).join('\n') + (errorCount > 10 ? `\n... and ${errorCount - 10} more` : ''),
|
|
767
|
+
fixCommand: 'npx tsc --noEmit'
|
|
768
|
+
});
|
|
769
|
+
result.suggestedFixes.push({
|
|
770
|
+
issue: `${errorCount} TypeScript errors`,
|
|
771
|
+
command: 'npx tsc --noEmit',
|
|
772
|
+
description: 'Review and fix type errors in the listed files'
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
778
|
+
const errorLines = output.split('\n').filter((l) => l.includes('error TS'));
|
|
779
|
+
addCheck({
|
|
780
|
+
name: 'typescript',
|
|
781
|
+
status: 'fail',
|
|
782
|
+
message: `TypeScript check failed with ${errorLines.length} error(s)`,
|
|
783
|
+
details: errorLines.slice(0, 10).join('\n'),
|
|
784
|
+
fixCommand: 'npx tsc --noEmit'
|
|
785
|
+
});
|
|
786
|
+
result.suggestedFixes.push({
|
|
787
|
+
issue: 'TypeScript errors',
|
|
788
|
+
command: 'npx tsc --noEmit',
|
|
789
|
+
description: 'Fix the type errors shown in details'
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
// 6. Build Verification
|
|
793
|
+
if (input.skipBuild === 'true') {
|
|
794
|
+
addCheck({
|
|
795
|
+
name: 'build',
|
|
796
|
+
status: 'skip',
|
|
797
|
+
message: 'Build verification skipped by user'
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
try {
|
|
802
|
+
await execAsync('pnpm build', {
|
|
803
|
+
cwd: projectPath, shell, timeout: 300000
|
|
804
|
+
});
|
|
805
|
+
addCheck({
|
|
806
|
+
name: 'build',
|
|
807
|
+
status: 'pass',
|
|
808
|
+
message: 'Project builds successfully'
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
const stderr = error.stderr || error.stdout || error.message;
|
|
813
|
+
// Extract meaningful error lines
|
|
814
|
+
const lines = stderr.split('\n');
|
|
815
|
+
const errorLines = lines.filter((l) => l.includes('Error') || l.includes('error') ||
|
|
816
|
+
l.includes('failed') || l.includes('TypeError') ||
|
|
817
|
+
l.includes('Cannot find') || l.includes('Module not found')).slice(0, 15);
|
|
818
|
+
addCheck({
|
|
819
|
+
name: 'build',
|
|
820
|
+
status: 'fail',
|
|
821
|
+
message: 'Build failed',
|
|
822
|
+
details: errorLines.join('\n') || lines.slice(-10).join('\n'),
|
|
823
|
+
fixCommand: 'pnpm build'
|
|
824
|
+
});
|
|
825
|
+
result.suggestedFixes.push({
|
|
826
|
+
issue: 'Build failure',
|
|
827
|
+
command: 'pnpm build',
|
|
828
|
+
description: 'Review build errors in details and fix the underlying issues'
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
// 7. ESLint Check (Production-ready)
|
|
833
|
+
try {
|
|
834
|
+
const { stdout, stderr } = await execAsync('pnpm lint 2>&1 || true', {
|
|
835
|
+
cwd: projectPath, shell, timeout: 60000
|
|
836
|
+
});
|
|
837
|
+
const output = (stdout + stderr).trim();
|
|
838
|
+
if (output === '' || output.includes('No ESLint') || !output.includes('error')) {
|
|
839
|
+
addCheck({
|
|
840
|
+
name: 'eslint',
|
|
841
|
+
status: 'pass',
|
|
842
|
+
message: 'No ESLint errors found'
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
const errorCount = (output.match(/error/gi) || []).length;
|
|
847
|
+
const warningCount = (output.match(/warning/gi) || []).length;
|
|
848
|
+
addCheck({
|
|
849
|
+
name: 'eslint',
|
|
850
|
+
status: errorCount > 0 ? 'fail' : 'warn',
|
|
851
|
+
message: `ESLint: ${errorCount} error(s), ${warningCount} warning(s)`,
|
|
852
|
+
details: output.split('\n').slice(0, 10).join('\n'),
|
|
853
|
+
fixCommand: 'pnpm lint --fix'
|
|
854
|
+
});
|
|
855
|
+
if (errorCount > 0) {
|
|
856
|
+
result.suggestedFixes.push({
|
|
857
|
+
issue: 'ESLint errors',
|
|
858
|
+
command: 'pnpm lint --fix',
|
|
859
|
+
description: 'Auto-fix ESLint issues where possible'
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
addCheck({
|
|
866
|
+
name: 'eslint',
|
|
867
|
+
status: 'warn',
|
|
868
|
+
message: 'ESLint check skipped (lint script may not exist)',
|
|
869
|
+
details: error.message
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
// 8. Security Audit (Production-ready)
|
|
873
|
+
try {
|
|
874
|
+
const { stdout, stderr } = await execAsync('pnpm audit --audit-level=high 2>&1 || true', {
|
|
875
|
+
cwd: projectPath, shell, timeout: 60000
|
|
876
|
+
});
|
|
877
|
+
const output = (stdout + stderr).trim();
|
|
878
|
+
if (output.includes('found 0 vulnerabilities') || output.includes('No known vulnerabilities')) {
|
|
879
|
+
addCheck({
|
|
880
|
+
name: 'security_audit',
|
|
881
|
+
status: 'pass',
|
|
882
|
+
message: 'No high/critical security vulnerabilities found'
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
else if (output.includes('high') || output.includes('critical')) {
|
|
886
|
+
const highCount = (output.match(/high/gi) || []).length;
|
|
887
|
+
const criticalCount = (output.match(/critical/gi) || []).length;
|
|
888
|
+
addCheck({
|
|
889
|
+
name: 'security_audit',
|
|
890
|
+
status: 'warn',
|
|
891
|
+
message: `Security: ${criticalCount} critical, ${highCount} high vulnerabilities`,
|
|
892
|
+
details: output.split('\n').slice(0, 10).join('\n'),
|
|
893
|
+
fixCommand: 'pnpm audit --fix'
|
|
894
|
+
});
|
|
895
|
+
result.suggestedFixes.push({
|
|
896
|
+
issue: 'Security vulnerabilities detected',
|
|
897
|
+
command: 'pnpm audit --fix',
|
|
898
|
+
description: 'Attempt to auto-fix security issues'
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
addCheck({
|
|
903
|
+
name: 'security_audit',
|
|
904
|
+
status: 'pass',
|
|
905
|
+
message: 'No high/critical security vulnerabilities'
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
catch (error) {
|
|
910
|
+
addCheck({
|
|
911
|
+
name: 'security_audit',
|
|
912
|
+
status: 'warn',
|
|
913
|
+
message: 'Security audit skipped',
|
|
914
|
+
details: error.message
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
// 9. Environment Variables Check (Production-ready)
|
|
918
|
+
const hasEnvLocal = await checkFile('.env.local');
|
|
919
|
+
const hasEnv = await checkFile('.env');
|
|
920
|
+
const hasEnvExample = await checkFile('.env.example');
|
|
921
|
+
if (hasEnvLocal || hasEnv) {
|
|
922
|
+
// Check if DATABASE_URL is set when Prisma is present
|
|
923
|
+
if (prismaReady) {
|
|
924
|
+
try {
|
|
925
|
+
const envContent = await fs.readFile(path.join(projectPath, hasEnvLocal ? '.env.local' : '.env'), 'utf-8');
|
|
926
|
+
if (envContent.includes('DATABASE_URL=') && !envContent.includes('DATABASE_URL=\n')) {
|
|
927
|
+
addCheck({
|
|
928
|
+
name: 'env_config',
|
|
929
|
+
status: 'pass',
|
|
930
|
+
message: 'Environment variables configured with DATABASE_URL'
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
addCheck({
|
|
935
|
+
name: 'env_config',
|
|
936
|
+
status: 'warn',
|
|
937
|
+
message: 'DATABASE_URL may not be set properly in .env',
|
|
938
|
+
fixCommand: 'Add DATABASE_URL=postgresql://... to .env'
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
catch {
|
|
943
|
+
addCheck({
|
|
944
|
+
name: 'env_config',
|
|
945
|
+
status: 'warn',
|
|
946
|
+
message: 'Could not read .env file'
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
addCheck({
|
|
952
|
+
name: 'env_config',
|
|
953
|
+
status: 'pass',
|
|
954
|
+
message: 'Environment file exists'
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
// Check for .env.example (production best practice)
|
|
958
|
+
if (!hasEnvExample) {
|
|
959
|
+
addCheck({
|
|
960
|
+
name: 'env_example',
|
|
961
|
+
status: 'warn',
|
|
962
|
+
message: 'Missing .env.example - recommended for production deployments',
|
|
963
|
+
details: 'Create .env.example with placeholder values for documentation'
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
addCheck({
|
|
969
|
+
name: 'env_config',
|
|
970
|
+
status: 'warn',
|
|
971
|
+
message: 'No .env or .env.local file found',
|
|
972
|
+
details: 'Create .env.local for local development'
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
// 10. Next.js Config Check (Production-ready)
|
|
976
|
+
const hasNextConfig = await checkFile('next.config.js') || await checkFile('next.config.mjs') || await checkFile('next.config.ts');
|
|
977
|
+
if (hasNextConfig) {
|
|
978
|
+
addCheck({
|
|
979
|
+
name: 'nextjs_config',
|
|
980
|
+
status: 'pass',
|
|
981
|
+
message: 'Next.js configuration file present'
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
addCheck({
|
|
986
|
+
name: 'nextjs_config',
|
|
987
|
+
status: 'warn',
|
|
988
|
+
message: 'No next.config found - using default settings',
|
|
989
|
+
details: 'Consider adding next.config.mjs for production optimizations'
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
// 11. Package.json Scripts Check (Production-ready)
|
|
993
|
+
try {
|
|
994
|
+
const pkgContent = await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8');
|
|
995
|
+
const pkg = JSON.parse(pkgContent);
|
|
996
|
+
const scripts = pkg.scripts || {};
|
|
997
|
+
const requiredScripts = ['dev', 'build', 'start'];
|
|
998
|
+
const recommendedScripts = ['lint', 'test'];
|
|
999
|
+
const missingRequired = requiredScripts.filter(s => !scripts[s]);
|
|
1000
|
+
const missingRecommended = recommendedScripts.filter(s => !scripts[s]);
|
|
1001
|
+
if (missingRequired.length === 0) {
|
|
1002
|
+
addCheck({
|
|
1003
|
+
name: 'npm_scripts',
|
|
1004
|
+
status: missingRecommended.length > 0 ? 'warn' : 'pass',
|
|
1005
|
+
message: missingRecommended.length > 0
|
|
1006
|
+
? `Scripts OK, but missing recommended: ${missingRecommended.join(', ')}`
|
|
1007
|
+
: 'All required and recommended npm scripts present',
|
|
1008
|
+
details: missingRecommended.length > 0
|
|
1009
|
+
? 'Consider adding: lint, test scripts for production readiness'
|
|
1010
|
+
: undefined
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
addCheck({
|
|
1015
|
+
name: 'npm_scripts',
|
|
1016
|
+
status: 'fail',
|
|
1017
|
+
message: `Missing required scripts: ${missingRequired.join(', ')}`,
|
|
1018
|
+
fixCommand: 'Check package.json scripts section'
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
catch (error) {
|
|
1023
|
+
addCheck({
|
|
1024
|
+
name: 'npm_scripts',
|
|
1025
|
+
status: 'fail',
|
|
1026
|
+
message: 'Could not read package.json',
|
|
1027
|
+
details: error.message
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
return JSON.stringify(result, null, 2);
|
|
1031
|
+
}
|
|
1032
|
+
catch (error) {
|
|
1033
|
+
result.success = false;
|
|
1034
|
+
result.checks.push({
|
|
1035
|
+
name: 'verification_error',
|
|
1036
|
+
status: 'fail',
|
|
1037
|
+
message: `Verification process error: ${error.message}`,
|
|
1038
|
+
details: error.stack
|
|
1039
|
+
});
|
|
1040
|
+
return JSON.stringify(result, null, 2);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
/**
|
|
1045
|
+
* Get all code tools
|
|
1046
|
+
* @returns Array of tools
|
|
1047
|
+
*/
|
|
1048
|
+
export function getCodeTools() {
|
|
1049
|
+
return [
|
|
1050
|
+
createProjectTool,
|
|
1051
|
+
installPackagesTool,
|
|
1052
|
+
checkTypescriptTool,
|
|
1053
|
+
checkBuildErrorTool,
|
|
1054
|
+
setupShadcnTool,
|
|
1055
|
+
installShadcnComponentsTool,
|
|
1056
|
+
setupPrismaTool,
|
|
1057
|
+
verifyProjectTool
|
|
1058
|
+
];
|
|
1059
|
+
}
|