ai-worktool 1.0.7

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.
@@ -0,0 +1,541 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectProjectConfig = detectProjectConfig;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * 检测项目配置信息
11
+ */
12
+ async function detectProjectConfig(projectRoot, defaultConfig) {
13
+ defaultConfig = defaultConfig || {};
14
+ const config = {
15
+ projectRoot: defaultConfig.projectRoot || projectRoot,
16
+ projectType: defaultConfig.projectType || 'unknown',
17
+ packageManager: defaultConfig.packageManager || 'unknown',
18
+ testFramework: defaultConfig.testFramework || 'unknown',
19
+ srcDir: defaultConfig.srcDir || undefined,
20
+ testDir: defaultConfig.testDir || undefined,
21
+ coverageDir: defaultConfig.coverageDir || undefined,
22
+ testFilePatterns: defaultConfig.testFilePatterns || [],
23
+ languages: defaultConfig.languages || [],
24
+ testConfigFile: defaultConfig.testConfigFile || undefined,
25
+ isInstalled: defaultConfig.isInstalled || undefined
26
+ };
27
+ // 首先检查项目是否已安装
28
+ await checkProjectInstalled(projectRoot, config);
29
+ // 只有项目已安装的情况下才进行其他检测
30
+ if (config.isInstalled) {
31
+ // 检测包管理工具
32
+ await detectPackageManager(projectRoot, config);
33
+ // 检测使用的开发语言
34
+ await detectLanguages(projectRoot, config);
35
+ // 检测源代码目录
36
+ await detectSrcDir(projectRoot, config);
37
+ // 检测测试相关配置
38
+ await detectTestConfig(projectRoot, config);
39
+ // 检测项目类型(前端/后端/全栈)
40
+ await detectProjectType(projectRoot, config);
41
+ // 检测框架信息
42
+ await detectFramework(projectRoot, config);
43
+ }
44
+ return config;
45
+ }
46
+ /**
47
+ * 检查项目是否已安装
48
+ * 项目已安装的标志:存在package.json和node_modules目录
49
+ */
50
+ async function checkProjectInstalled(projectRoot, config) {
51
+ // 检查package.json是否存在
52
+ const hasPackageJson = await fileExists(path_1.default.join(projectRoot, 'package.json'));
53
+ // 检查node_modules目录是否存在
54
+ const hasNodeModules = await directoryExists(path_1.default.join(projectRoot, 'node_modules'));
55
+ // 两个条件都满足才认为项目已安装
56
+ config.isInstalled = hasPackageJson && hasNodeModules;
57
+ }
58
+ /**
59
+ * 检测包管理工具
60
+ */
61
+ async function detectPackageManager(projectRoot, config) {
62
+ const lockFiles = [
63
+ { name: 'package-lock.json', manager: 'npm' },
64
+ { name: 'yarn.lock', manager: 'yarn' },
65
+ { name: 'pnpm-lock.yaml', manager: 'pnpm' },
66
+ { name: 'bun.lockb', manager: 'bun' }
67
+ ];
68
+ for (const lockFile of lockFiles) {
69
+ const lockFilePath = path_1.default.join(projectRoot, lockFile.name);
70
+ if (await fileExists(lockFilePath)) {
71
+ config.packageManager = lockFile.manager;
72
+ return;
73
+ }
74
+ }
75
+ // config.packageManager = 'unknown';
76
+ }
77
+ /**
78
+ * 检测开发语言 - 排除node_modules和隐藏目录
79
+ */
80
+ async function detectLanguages(projectRoot, config) {
81
+ const languages = [];
82
+ // 检查TypeScript配置文件(不受目录过滤影响)
83
+ const tsConfigPath = path_1.default.join(projectRoot, 'tsconfig.json');
84
+ if (await fileExists(tsConfigPath)) {
85
+ languages.push('typescript');
86
+ }
87
+ // 检查是否有JavaScript文件(排除node_modules和隐藏目录)
88
+ const hasJsFiles = await hasFilesWithExtensionFiltered(projectRoot, '.js', (dir) => dir !== 'node_modules' && !dir.startsWith('.'));
89
+ if (hasJsFiles) {
90
+ languages.push('javascript');
91
+ }
92
+ // 检查是否有CoffeeScript文件(排除node_modules和隐藏目录)
93
+ const hasCoffeeFiles = await hasFilesWithExtensionFiltered(projectRoot, '.coffee', (dir) => dir !== 'node_modules' && !dir.startsWith('.'));
94
+ if (hasCoffeeFiles) {
95
+ languages.push('coffeescript');
96
+ }
97
+ config.languages = languages;
98
+ }
99
+ /**
100
+ * 检测源代码目录
101
+ */
102
+ async function detectSrcDir(projectRoot, config) {
103
+ const possibleSrcDirs = ['src', 'lib', 'app', 'source'];
104
+ for (const dir of possibleSrcDirs) {
105
+ const dirPath = path_1.default.join(projectRoot, dir);
106
+ if (await directoryExists(dirPath)) {
107
+ config.srcDir = dir;
108
+ return;
109
+ }
110
+ }
111
+ config.srcDir = '';
112
+ }
113
+ /**
114
+ * 检测测试相关配置
115
+ */
116
+ async function detectTestConfig(projectRoot, config) {
117
+ // 检测测试框架
118
+ const packageJsonPath = path_1.default.join(projectRoot, 'package.json');
119
+ if (await fileExists(packageJsonPath)) {
120
+ const packageJson = JSON.parse(await promises_1.default.readFile(packageJsonPath, 'utf8'));
121
+ const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
122
+ // 检测测试框架
123
+ if (dependencies.jest) {
124
+ config.testFramework = 'jest';
125
+ }
126
+ else if (dependencies.mocha) {
127
+ config.testFramework = 'mocha';
128
+ }
129
+ else if (dependencies.vitest) {
130
+ config.testFramework = 'vitest';
131
+ }
132
+ else if (dependencies.jasmine) {
133
+ config.testFramework = 'jasmine';
134
+ }
135
+ else if (dependencies.cypress) {
136
+ config.testFramework = 'cypress';
137
+ }
138
+ else if (dependencies['@playwright/test']) {
139
+ config.testFramework = 'playwright';
140
+ }
141
+ }
142
+ // 检测测试目录
143
+ const possibleTestDirs = ['test', 'tests', '__tests__', 'spec', 'specs'];
144
+ for (const dir of possibleTestDirs) {
145
+ const dirPath = path_1.default.join(projectRoot, dir);
146
+ if (await directoryExists(dirPath)) {
147
+ config.testDir = dir;
148
+ break;
149
+ }
150
+ }
151
+ // 检测覆盖率报告目录
152
+ const possibleCoverageDirs = ['coverage', 'test-reports', 'reports'];
153
+ for (const dir of possibleCoverageDirs) {
154
+ const dirPath = path_1.default.join(projectRoot, dir);
155
+ if (await directoryExists(dirPath)) {
156
+ config.coverageDir = dir;
157
+ break;
158
+ }
159
+ }
160
+ // 检测测试框架配置文件
161
+ const testFrameworkConfigFiles = [
162
+ { framework: 'jest', files: ['jest.config.js', 'jest.config.ts', 'jest.config.json', '.jestrc.js', '.jestrc'] },
163
+ { framework: 'mocha', files: ['mocha.config.js', 'mocha.config.ts', '.mocharc.js', '.mocharc.json', 'mocha.opts'] },
164
+ { framework: 'vitest', files: ['vitest.config.js', 'vitest.config.ts', 'vite.config.js', 'vite.config.ts'] },
165
+ { framework: 'jasmine', files: ['jasmine.json', 'jasmine.config.js'] },
166
+ { framework: 'cypress', files: ['cypress.config.js', 'cypress.config.ts', 'cypress.json'] },
167
+ { framework: 'playwright', files: ['playwright.config.js', 'playwright.config.ts'] }
168
+ ];
169
+ // 如果已经检测到测试框架,优先检测对应框架的配置文件
170
+ if (config.testFramework !== 'unknown') {
171
+ const frameworkConfig = testFrameworkConfigFiles.find((item) => item.framework === config.testFramework);
172
+ if (frameworkConfig) {
173
+ for (const file of frameworkConfig.files) {
174
+ const filePath = path_1.default.join(projectRoot, file);
175
+ if (await fileExists(filePath)) {
176
+ config.testConfigFile = file;
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ }
182
+ // 如果未检测到特定框架的配置文件,检测所有可能的配置文件
183
+ for (const framework of testFrameworkConfigFiles) {
184
+ for (const file of framework.files) {
185
+ const filePath = path_1.default.join(projectRoot, file);
186
+ if (await fileExists(filePath)) {
187
+ config.testConfigFile = file;
188
+ // 如果找到了配置文件但尚未确定测试框架,可以根据配置文件推断
189
+ if (config.testFramework === 'unknown') {
190
+ config.testFramework = framework.framework;
191
+ }
192
+ break;
193
+ }
194
+ }
195
+ }
196
+ // 检测测试文件模式(优化部分)
197
+ config.testFilePatterns = [];
198
+ // 定义可能的测试文件模式模板
199
+ const patternTemplates = [
200
+ `*.test.{{ext}}`,
201
+ `*.spec.{{ext}}`,
202
+ `__tests__/**/*.{{ext}}`,
203
+ `test/**/*.{{ext}}`,
204
+ `tests/**/*.{{ext}}`,
205
+ `spec/**/*.{{ext}}`,
206
+ `specs/**/*.{{ext}}`
207
+ ];
208
+ // 根据项目语言生成对应扩展名的模式
209
+ const extensions = new Set();
210
+ if (config.languages.includes('javascript')) {
211
+ extensions.add('js');
212
+ }
213
+ if (config.languages.includes('typescript')) {
214
+ extensions.add('ts');
215
+ }
216
+ if (config.languages.includes('coffeescript')) {
217
+ extensions.add('coffee');
218
+ }
219
+ // 默认为js(如果没有检测到语言)
220
+ if (extensions.size === 0) {
221
+ extensions.add('js');
222
+ }
223
+ // 生成基础模式集合
224
+ const basePatterns = new Set();
225
+ extensions.forEach((ext) => {
226
+ patternTemplates.forEach((tpl) => {
227
+ basePatterns.add(tpl.replace('{{ext}}', ext));
228
+ });
229
+ });
230
+ // 1. 优先从package.json的test脚本中提取模式
231
+ if (await fileExists(packageJsonPath)) {
232
+ const packageJson = JSON.parse(await promises_1.default.readFile(packageJsonPath, 'utf8'));
233
+ if (packageJson.scripts && packageJson.scripts.test) {
234
+ const testScript = packageJson.scripts.test;
235
+ // 从脚本中提取符合基础模式的路径
236
+ Array.from(basePatterns).forEach((pattern) => {
237
+ // 简单匹配脚本中是否包含该模式(考虑可能有引号包裹的情况)
238
+ if (testScript.includes(pattern) ||
239
+ testScript.includes(`'${pattern}'`) ||
240
+ testScript.includes(`"${pattern}"`)) {
241
+ config.testFilePatterns.push(pattern);
242
+ }
243
+ });
244
+ }
245
+ }
246
+ // 2. 如果脚本中没有找到,检测实际存在的测试文件模式
247
+ if (config.testFilePatterns.length === 0 && config.testDir) {
248
+ const testDirPath = path_1.default.join(projectRoot, config.testDir);
249
+ if (await directoryExists(testDirPath)) {
250
+ // 检测测试目录中实际存在的文件模式
251
+ const detectedPatterns = await detectExistingTestPatterns(testDirPath, projectRoot, extensions);
252
+ config.testFilePatterns = detectedPatterns;
253
+ }
254
+ }
255
+ // 3. 如果仍未找到,使用基础模式
256
+ if (config.testFilePatterns.length === 0) {
257
+ config.testFilePatterns = Array.from(basePatterns);
258
+ }
259
+ }
260
+ /**
261
+ * 检测实际存在的测试文件模式
262
+ */
263
+ async function detectExistingTestPatterns(testDir, projectRoot, extensions) {
264
+ const patterns = new Set();
265
+ const relativeTestDir = path_1.default.relative(projectRoot, testDir);
266
+ // 递归检查测试目录中的文件
267
+ async function checkDir(currentDir) {
268
+ const entries = await promises_1.default.readdir(currentDir, { withFileTypes: true });
269
+ for (const entry of entries) {
270
+ const fullPath = path_1.default.join(currentDir, entry.name);
271
+ if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
272
+ await checkDir(fullPath);
273
+ }
274
+ else if (entry.isFile()) {
275
+ // 检查文件是否匹配测试文件命名规范
276
+ const extMatch = entry.name.match(/\.([^.]+)$/);
277
+ if (extMatch && extensions.has(extMatch[1])) {
278
+ const fileName = entry.name.replace(/\.[^.]+$/, '');
279
+ if (fileName.endsWith('.test') || fileName.endsWith('.spec')) {
280
+ // 生成对应的glob模式
281
+ const dirPart = path_1.default.relative(testDir, currentDir);
282
+ const patternDir = dirPart ? `${dirPart}/**` : '**';
283
+ const pattern = `${relativeTestDir}/${patternDir}/*.${extMatch[1]}`;
284
+ patterns.add(pattern);
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+ await checkDir(testDir);
291
+ return Array.from(patterns);
292
+ }
293
+ // 项目特征配置
294
+ const projectFeatures = {
295
+ frontend: {
296
+ dependencies: [
297
+ 'react',
298
+ 'vue',
299
+ 'angular',
300
+ 'svelte',
301
+ 'preact',
302
+ 'react-dom',
303
+ 'vue-router',
304
+ 'next',
305
+ 'nuxt',
306
+ 'gatsby',
307
+ 'webpack',
308
+ 'vite',
309
+ 'rollup',
310
+ 'parcel',
311
+ 'tailwindcss',
312
+ 'styled-components',
313
+ 'emotion',
314
+ 'sass',
315
+ 'less'
316
+ ],
317
+ configFiles: [
318
+ 'webpack.config.js',
319
+ 'vite.config.js',
320
+ 'rollup.config.js',
321
+ 'babel.config.js',
322
+ 'tsconfig.json',
323
+ '.eslintrc.js',
324
+ 'next.config.js',
325
+ 'nuxt.config.js',
326
+ 'gatsby-config.js'
327
+ ],
328
+ directories: ['src/components', 'src/pages', 'src/views', 'public', 'assets']
329
+ },
330
+ backend: {
331
+ dependencies: [
332
+ 'express',
333
+ 'koa',
334
+ 'nestjs',
335
+ 'fastify',
336
+ 'hapi',
337
+ 'koa-router',
338
+ 'express-router',
339
+ 'mongoose',
340
+ 'sequelize',
341
+ 'typeorm',
342
+ 'prisma',
343
+ 'pg',
344
+ 'mysql',
345
+ 'mongodb',
346
+ 'redis',
347
+ 'nodemailer',
348
+ 'passport',
349
+ 'jsonwebtoken'
350
+ ],
351
+ configFiles: [
352
+ 'server.js',
353
+ 'app.js',
354
+ 'nest-cli.json',
355
+ 'prisma/schema.prisma',
356
+ '.env',
357
+ '.env.example',
358
+ 'database.json'
359
+ ],
360
+ directories: ['src/controllers', 'src/services', 'src/routes', 'src/middleware', 'prisma']
361
+ },
362
+ cli: {
363
+ dependencies: ['commander', 'yargs', 'inquirer', 'cli-table', 'chalk', 'ora', 'figlet', 'meow', 'arg'],
364
+ configFiles: ['cli.js', 'bin/cli.js'],
365
+ directories: ['bin', 'src/commands']
366
+ }
367
+ };
368
+ /**
369
+ * 异步读取package.json依赖
370
+ * @returns 依赖和开发依赖数组
371
+ */
372
+ async function getDependencies(projectRoot) {
373
+ const packageJsonPath = path_1.default.join(projectRoot, 'package.json');
374
+ if (!(await fileExists(packageJsonPath))) {
375
+ return { dependencies: [], devDependencies: [] };
376
+ }
377
+ try {
378
+ const packageJsonContent = await promises_1.default.readFile(packageJsonPath, 'utf8');
379
+ const packageJson = JSON.parse(packageJsonContent);
380
+ return {
381
+ dependencies: packageJson.dependencies ? Object.keys(packageJson.dependencies) : [],
382
+ devDependencies: packageJson.devDependencies ? Object.keys(packageJson.devDependencies) : []
383
+ };
384
+ }
385
+ catch (err) {
386
+ console.error('读取package.json时出错:', err);
387
+ return { dependencies: [], devDependencies: [] };
388
+ }
389
+ }
390
+ /**
391
+ * 异步计算项目类型匹配分数
392
+ * @param type 项目类型
393
+ * @returns 匹配分数
394
+ */
395
+ async function calculateScore(projectRoot, type) {
396
+ const features = projectFeatures[type];
397
+ const { dependencies, devDependencies } = await getDependencies(projectRoot);
398
+ let score = 0;
399
+ // 检查依赖匹配
400
+ const allDeps = [...dependencies, ...devDependencies];
401
+ features.dependencies.forEach((dep) => {
402
+ if (allDeps.some((d) => d.includes(dep))) {
403
+ score += 3; // 依赖权重较高
404
+ }
405
+ });
406
+ // 检查配置文件匹配(并行处理)
407
+ const configFileChecks = features.configFiles.map((file) => fileExists(path_1.default.join(process.cwd(), file)));
408
+ const configFileResults = await Promise.all(configFileChecks);
409
+ score += configFileResults.filter(Boolean).length * 2; // 配置文件权重中等
410
+ // 检查目录结构匹配(并行处理)
411
+ const directoryChecks = features.directories.map((dir) => directoryExists(path_1.default.join(process.cwd(), dir)));
412
+ const directoryResults = await Promise.all(directoryChecks);
413
+ score += directoryResults.filter(Boolean).length * 1; // 目录权重较低
414
+ return score;
415
+ }
416
+ /**
417
+ * 异步检测项目类型
418
+ * @returns 检测结果
419
+ */
420
+ async function detectProjectType(projectRoot, config) {
421
+ // 并行计算各类型分数
422
+ const [frontendScore, backendScore, cliScore] = await Promise.all([
423
+ calculateScore(projectRoot, 'frontend'),
424
+ calculateScore(projectRoot, 'backend'),
425
+ calculateScore(projectRoot, 'cli')
426
+ ]);
427
+ // 计算分数
428
+ const scores = {
429
+ frontend: frontendScore,
430
+ backend: backendScore,
431
+ cli: cliScore
432
+ };
433
+ // 判断是否为全栈项目
434
+ const isFullstack = frontendScore > 0 && backendScore > 0 && Math.abs(frontendScore - backendScore) < frontendScore * 0.5;
435
+ if (isFullstack) {
436
+ config.projectType = 'fullstack';
437
+ }
438
+ // 确定最高分数的项目类型
439
+ const maxScore = Math.max(frontendScore, backendScore, cliScore);
440
+ if (maxScore === 0) {
441
+ config.projectType = 'unknown';
442
+ }
443
+ if (maxScore === frontendScore) {
444
+ config.projectType = 'frontend';
445
+ }
446
+ else if (maxScore === backendScore) {
447
+ config.projectType = 'backend';
448
+ }
449
+ else {
450
+ config.projectType = 'cli';
451
+ }
452
+ }
453
+ /**
454
+ * 检测框架信息
455
+ */
456
+ async function detectFramework(projectRoot, config) {
457
+ const packageJsonPath = path_1.default.join(projectRoot, 'package.json');
458
+ if (await fileExists(packageJsonPath)) {
459
+ const packageJson = JSON.parse(await promises_1.default.readFile(packageJsonPath, 'utf8'));
460
+ const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
461
+ // 检测前端框架
462
+ if (dependencies.react || dependencies['react-dom']) {
463
+ config.framework = 'react';
464
+ }
465
+ else if (dependencies.vue) {
466
+ config.framework = 'vue';
467
+ }
468
+ else if (dependencies.angular || dependencies['@angular/core']) {
469
+ config.framework = 'angular';
470
+ }
471
+ else if (dependencies.svelte) {
472
+ config.framework = 'svelte';
473
+ }
474
+ // 检测后端框架
475
+ else if (dependencies.express) {
476
+ config.framework = 'express';
477
+ }
478
+ else if (dependencies.nestjs || dependencies['@nestjs/core']) {
479
+ config.framework = 'nestjs';
480
+ }
481
+ else if (dependencies.koa) {
482
+ config.framework = 'koa';
483
+ }
484
+ else if (dependencies.fastify) {
485
+ config.framework = 'fastify';
486
+ }
487
+ }
488
+ }
489
+ /**
490
+ * 辅助函数:检查文件是否存在
491
+ */
492
+ async function fileExists(filePath) {
493
+ try {
494
+ const stats = await promises_1.default.stat(filePath);
495
+ return stats.isFile();
496
+ }
497
+ catch (err) {
498
+ return false;
499
+ }
500
+ }
501
+ /**
502
+ * 辅助函数:检查目录是否存在
503
+ */
504
+ async function directoryExists(dirPath) {
505
+ try {
506
+ const stats = await promises_1.default.stat(dirPath);
507
+ return stats.isDirectory();
508
+ }
509
+ catch (err) {
510
+ return false;
511
+ }
512
+ }
513
+ /**
514
+ * 辅助函数:检查是否有特定扩展名的文件(带目录过滤功能)
515
+ * @param rootDir 根目录
516
+ * @param extension 文件扩展名
517
+ * @param dirFilter 目录过滤函数,返回true表示需要处理该目录
518
+ */
519
+ async function hasFilesWithExtensionFiltered(rootDir, extension, dirFilter) {
520
+ try {
521
+ const entries = await promises_1.default.readdir(rootDir, { withFileTypes: true });
522
+ for (const entry of entries) {
523
+ // 检查文件
524
+ if (entry.isFile() && entry.name.endsWith(extension)) {
525
+ return true;
526
+ }
527
+ // 检查目录(应用过滤条件)
528
+ if (entry.isDirectory() && dirFilter(entry.name)) {
529
+ const hasFiles = await hasFilesWithExtensionFiltered(path_1.default.join(rootDir, entry.name), extension, dirFilter);
530
+ if (hasFiles) {
531
+ return true;
532
+ }
533
+ }
534
+ }
535
+ return false;
536
+ }
537
+ catch (err) {
538
+ return false;
539
+ }
540
+ }
541
+ //# sourceMappingURL=project.js.map