- 将wwjcloud目录重命名为wwjcloud-nest-v1作为项目根目录 - 将原nestjs目录重命名为wwjcloud作为NestJS后端目录 - 实现真正的前后端分离架构 - 恢复工作区中丢失的目录结构 - 更新相关配置文件路径引用 - 清理重复和嵌套目录问题 目录结构: wwjcloud-nest-v1/ ├── wwjcloud/ # NestJS 后端 ├── admin/ # 管理端前端 ├── web/ # PC端前端 ├── uni-app-x/ # 移动端前端 ├── wwjcloud-web/ # 部署根目录 ├── docker/ # Docker 配置 ├── docs/ # 文档 └── tools/ # 工具集
514 lines
16 KiB
JavaScript
514 lines
16 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* 代码分析工具
|
|
* 用于分析Java框架uni-app项目的结构和依赖关系
|
|
*/
|
|
class AnalysisUtils {
|
|
constructor() {
|
|
this.dependencies = new Map();
|
|
this.components = new Map();
|
|
this.pages = new Map();
|
|
this.hooks = new Map();
|
|
this.stores = new Map();
|
|
}
|
|
|
|
/**
|
|
* 分析整个项目结构
|
|
*/
|
|
async analyzeProject(projectPath) {
|
|
console.log(`🔍 开始分析项目: ${projectPath}`);
|
|
|
|
if (!fs.existsSync(projectPath)) {
|
|
throw new Error(`项目路径不存在: ${projectPath}`);
|
|
}
|
|
|
|
const analysis = {
|
|
basic: await this.analyzeBasicInfo(projectPath),
|
|
dependencies: await this.analyzeDependencies(projectPath),
|
|
structure: await this.analyzeStructure(projectPath),
|
|
components: await this.analyzeComponents(projectPath),
|
|
pages: await this.analyzePages(projectPath),
|
|
hooks: await this.analyzeHooks(projectPath),
|
|
styles: await this.analyzeStyles(projectPath)
|
|
};
|
|
|
|
console.log('✅ 项目分析完成');
|
|
return analysis;
|
|
}
|
|
|
|
/**
|
|
* 分析基础信息
|
|
*/
|
|
async analyzeBasicInfo(projectPath) {
|
|
const info = {
|
|
name: '',
|
|
version: '',
|
|
uniAppVersion: '',
|
|
vueVersion: '',
|
|
platform: '',
|
|
buildTool: ''
|
|
};
|
|
|
|
// 读取package.json
|
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
if (fs.existsSync(packageJsonPath)) {
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
info.name = packageJson.name || '';
|
|
info.version = packageJson.version || '';
|
|
info.uniAppVersion = packageJson.dependencies?.['@dcloudio/uni-app'] || '';
|
|
info.vueVersion = packageJson.dependencies?.vue || '';
|
|
|
|
// 检查构建工具
|
|
if (packageJson.devDependencies?.vite) {
|
|
info.buildTool = 'Vite';
|
|
} else if (packageJson.devDependencies?.webpack) {
|
|
info.buildTool = 'Webpack';
|
|
}
|
|
}
|
|
|
|
// 读取manifest.json确定平台
|
|
const manifestPath = path.join(projectPath, 'src/manifest.json');
|
|
if (fs.existsSync(manifestPath)) {
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
if (manifest['app-plus']) info.platform += 'App ';
|
|
if (manifest['mp-weixin']) info.platform += '微信小程序 ';
|
|
if (manifest['h5']) info.platform += 'H5 ';
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* 分析依赖关系
|
|
*/
|
|
async analyzeDependencies(projectPath) {
|
|
const dependencies = new Map();
|
|
|
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
if (fs.existsSync(packageJsonPath)) {
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
|
|
// 分析核心依赖
|
|
const coreDeps = packageJson.dependencies || {};
|
|
const devDeps = packageJson.devDependencies || {};
|
|
|
|
// uni-app相关依赖
|
|
Object.keys(coreDeps).forEach(dep => {
|
|
if (dep.includes('@dcloudio') || dep.includes('uni')) {
|
|
dependencies.set(dep, {
|
|
version: coreDeps[dep],
|
|
type: 'uni-app',
|
|
category: this.categorizeDependency(dep)
|
|
});
|
|
}
|
|
});
|
|
|
|
// Vue相关依赖
|
|
if (coreDeps.vue) {
|
|
dependencies.set('vue', {
|
|
version: coreDeps.vue,
|
|
type: 'framework',
|
|
category: 'core'
|
|
});
|
|
}
|
|
|
|
// UI组件库
|
|
['uview-plus', 'uni-ui', '@nutui/nutui'].forEach(uiLib => {
|
|
if (coreDeps[uiLib]) {
|
|
dependencies.set(uiLib, {
|
|
version: coreDeps[uiLib],
|
|
type: 'ui-library',
|
|
category: 'ui'
|
|
});
|
|
}
|
|
});
|
|
|
|
// 状态管理
|
|
['pinia', 'vuex'].forEach(stateLib => {
|
|
if (coreDeps[stateLib]) {
|
|
dependencies.set(stateLib, {
|
|
version: coreDeps[stateLib],
|
|
type: 'state-management',
|
|
category: 'state'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return dependencies;
|
|
}
|
|
|
|
/**
|
|
* 分析项目结构
|
|
*/
|
|
async analyzeStructure(projectPath) {
|
|
const structure = {
|
|
hasPages: false,
|
|
hasComponents: false,
|
|
hasUtils: false,
|
|
hasStores: false,
|
|
hasStyles: false,
|
|
directories: []
|
|
};
|
|
|
|
const srcPath = path.join(projectPath, 'src');
|
|
if (!fs.existsSync(srcPath)) return structure;
|
|
|
|
const items = fs.readdirSync(srcPath, { withFileTypes: true });
|
|
|
|
for (const item of items) {
|
|
if (item.isDirectory()) {
|
|
structure.directories.push(item.name);
|
|
|
|
switch (item.name) {
|
|
case 'pages':
|
|
case 'app':
|
|
structure.hasPages = true;
|
|
break;
|
|
case 'components':
|
|
case 'app/components':
|
|
structure.hasComponents = true;
|
|
break;
|
|
case 'utils':
|
|
structure.hasUtils = true;
|
|
break;
|
|
case 'stores':
|
|
case 'app/stores':
|
|
structure.hasStores = true;
|
|
break;
|
|
case 'styles':
|
|
structure.hasStyles = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return structure;
|
|
}
|
|
|
|
/**
|
|
* 分析组件
|
|
*/
|
|
async analyzeComponents(projectPath) {
|
|
const components = new Map();
|
|
const componentPaths = [
|
|
path.join(projectPath, 'src/components'),
|
|
path.join(projectPath, 'src/app/components'),
|
|
path.join(projectPath, 'src/addon/components')
|
|
];
|
|
|
|
for (const componentPath of componentPaths) {
|
|
if (fs.existsSync(componentPath)) {
|
|
await this.analyzeComponentDirectory(componentPath, components);
|
|
}
|
|
}
|
|
|
|
return components;
|
|
}
|
|
|
|
/**
|
|
* 分析组件目录
|
|
*/
|
|
async analyzeComponentDirectory(dirPath, components) {
|
|
const items = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
|
|
for (const item of items) {
|
|
const fullPath = path.join(dirPath, item.name);
|
|
|
|
if (item.isDirectory()) {
|
|
await this.analyzeComponentDirectory(fullPath, components);
|
|
} else if (item.name.endsWith('.vue')) {
|
|
const componentInfo = await this.analyzeVueFile(fullPath);
|
|
components.set(item.name.replace('.vue', ''), componentInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 分析Vue文件
|
|
*/
|
|
async analyzeVueFile(filePath) {
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
return {
|
|
path: filePath,
|
|
hasTemplate: content.includes('<template>'),
|
|
hasScript: content.includes('<script'),
|
|
hasStyle: content.includes('<style'),
|
|
scriptType: this.extractScriptType(content),
|
|
components: this.extractComponentImports(content),
|
|
hasCompositionApi: content.includes('setup'),
|
|
hasOptionsApi: content.includes('export default') && !content.includes('setup'),
|
|
styleLang: this.extractStyleLang(content)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 分析页面
|
|
*/
|
|
async analyzePages(projectPath) {
|
|
const pages = new Map();
|
|
const pagePaths = [
|
|
path.join(projectPath, 'src/pages'),
|
|
path.join(projectPath, 'src/app/pages')
|
|
];
|
|
|
|
for (const pagePath of pagePaths) {
|
|
if (fs.existsSync(pagePath)) {
|
|
await this.analyzePageDirectory(pagePath, pages);
|
|
}
|
|
}
|
|
|
|
return pages;
|
|
}
|
|
|
|
/**
|
|
* 分析页面目录
|
|
*/
|
|
async analyzePageDirectory(dirPath, pages) {
|
|
const items = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
|
|
for (const item of items) {
|
|
const fullPath = path.join(dirPath, item.name);
|
|
|
|
if (item.isDirectory()) {
|
|
await this.analyzePageDirectory(fullPath, pages);
|
|
} else if (item.name.endsWith('.vue')) {
|
|
const pageInfo = await this.analyzeVueFile(fullPath);
|
|
pageInfo.isPage = true;
|
|
pages.set(item.name.replace('.vue', ''), pageInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 分析Hooks
|
|
*/
|
|
async analyzeHooks(projectPath) {
|
|
const hooks = new Map();
|
|
const hookPaths = [
|
|
path.join(projectPath, 'src/hooks'),
|
|
path.join(projectPath, 'src/app/hooks')
|
|
];
|
|
|
|
for (const hookPath of hookPaths) {
|
|
if (fs.existsSync(hookPath)) {
|
|
const files = fs.readdirSync(hookPath);
|
|
|
|
for (const file of files) {
|
|
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
|
const content = fs.readFileSync(path.join(hookPath, file), 'utf-8');
|
|
|
|
hooks.set(file.replace(/\.(ts|js)$/, ''), {
|
|
path: path.join(hookPath, file),
|
|
exports: this.extractExports(content),
|
|
imports: this.extractImports(content),
|
|
hasTypes: file.endsWith('.ts')
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hooks;
|
|
}
|
|
|
|
/**
|
|
* 分析样式文件
|
|
*/
|
|
async analyzeStyles(projectPath) {
|
|
const styles = {
|
|
global: [],
|
|
components: [],
|
|
hasScss: false,
|
|
hasSass: false,
|
|
hasLess: false,
|
|
hasWindiCSS: false
|
|
};
|
|
|
|
const stylePaths = [
|
|
path.join(projectPath, 'src/styles'),
|
|
path.join(projectPath, 'src')
|
|
];
|
|
|
|
for (const stylePath of stylePaths) {
|
|
if (fs.existsSync(stylePath)) {
|
|
await this.analyzeStyleDirectory(stylePath, styles);
|
|
}
|
|
}
|
|
|
|
// 检查构建配置
|
|
const viteConfigPath = path.join(projectPath, 'vite.config.ts');
|
|
if (fs.existsSync(viteConfigPath)) {
|
|
const content = fs.readFileSync(viteConfigPath, 'utf-8');
|
|
styles.hasWindiCSS = content.includes('windicss') || content.includes('WindiCSS');
|
|
}
|
|
|
|
return styles;
|
|
}
|
|
|
|
/**
|
|
* 分析样式目录
|
|
*/
|
|
async analyzeStyleDirectory(dirPath, styles) {
|
|
const items = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
|
|
for (const item of items) {
|
|
const fullPath = path.join(dirPath, item.name);
|
|
|
|
if (item.isDirectory()) {
|
|
await this.analyzeStyleDirectory(fullPath, styles);
|
|
} else if (this.isStyleFile(item.name)) {
|
|
styles.global.push({
|
|
path: fullPath,
|
|
type: this.getStyleFileType(item.name),
|
|
size: fs.statSync(fullPath).size
|
|
});
|
|
|
|
// 检查样式类型
|
|
if (item.name.endsWith('.scss') || item.name.endsWith('.sass')) {
|
|
styles.hasScss = true;
|
|
}
|
|
if (item.name.endsWith('.less')) {
|
|
styles.hasLess = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 分类依赖
|
|
*/
|
|
categorizeDependency(depName) {
|
|
if (depName.includes('uni-app')) return 'core';
|
|
if (depName.includes('uni-mp')) return 'miniprogram';
|
|
if (depName.includes('uni-h5')) return 'h5';
|
|
if (depName.includes('uni-components')) return 'components';
|
|
return 'other';
|
|
}
|
|
|
|
/**
|
|
* 提取脚本类型
|
|
*/
|
|
extractScriptType(content) {
|
|
if (content.match(/<script[^>]*setup[^>]*>/)) return 'setup';
|
|
if (content.match(/<script[^>]*lang=['"]ts['"]/)) return 'typescript';
|
|
if (content.match(/<script/)) return 'javascript';
|
|
return 'none';
|
|
}
|
|
|
|
/**
|
|
* 提取组件导入
|
|
*/
|
|
extractComponentImports(content) {
|
|
const imports = [];
|
|
const match = content.match(/import\s+(.+?)\s+from\s+['"](.+?)['"];?/g);
|
|
if (match) {
|
|
match.forEach(imp => {
|
|
imports.push(imp.trim());
|
|
});
|
|
}
|
|
return imports;
|
|
}
|
|
|
|
/**
|
|
* 提取样式语言
|
|
*/
|
|
extractStyleLang(content) {
|
|
const match = content.match(/<style[^>]*lang=['"]([^'"]+)['"]/);
|
|
return match ? match[1] : 'css';
|
|
}
|
|
|
|
/**
|
|
* 提取导出
|
|
*/
|
|
extractExports(content) {
|
|
const exports = [];
|
|
const match = content.match(/export\s+(.+?);?/g);
|
|
if (match) {
|
|
match.forEach(exp => {
|
|
exports.push(exp.trim());
|
|
});
|
|
}
|
|
return exports;
|
|
}
|
|
|
|
/**
|
|
* 提取导入
|
|
*/
|
|
extractImports(content) {
|
|
const imports = [];
|
|
const match = content.match(/import\s+(.+?)\s+from\s+['"](.+?)['"];?/g);
|
|
if (match) {
|
|
match.forEach(imp => {
|
|
imports.push(imp.trim());
|
|
});
|
|
}
|
|
return imports;
|
|
}
|
|
|
|
/**
|
|
* 检查是否为样式文件
|
|
*/
|
|
isStyleFile(fileName) {
|
|
const styleExtensions = ['.css', '.scss', '.sass', '.less', '.styl'];
|
|
return styleExtensions.some(ext => fileName.endsWith(ext));
|
|
}
|
|
|
|
/**
|
|
* 获取样式文件类型
|
|
*/
|
|
getStyleFileType(fileName) {
|
|
if (fileName.endsWith('.scss')) return 'scss';
|
|
if (fileName.endsWith('.sass')) return 'sass';
|
|
if (fileName.endsWith('.less')) return 'less';
|
|
if (fileName.endsWith('.styl')) return 'stylus';
|
|
return 'css';
|
|
}
|
|
|
|
/**
|
|
* 生成分析报告
|
|
*/
|
|
generateReport(analysis) {
|
|
const report = {
|
|
summary: {
|
|
totalComponents: analysis.components.size,
|
|
totalPages: analysis.pages.size,
|
|
totalHooks: analysis.hooks.size,
|
|
hasTypeScript: Object.values(analysis.basic).some(v => v.includes('typescript')),
|
|
hasCompositionApi: false,
|
|
hasOptionsApi: false
|
|
},
|
|
recommendations: []
|
|
};
|
|
|
|
// 分析API使用情况
|
|
for (const [name, component] of analysis.components) {
|
|
if (component.hasCompositionApi) report.summary.hasCompositionApi = true;
|
|
if (component.hasOptionsApi) report.summary.hasOptionsApi = true;
|
|
}
|
|
|
|
for (const [name, page] of analysis.pages) {
|
|
if (page.hasCompositionApi) report.summary.hasCompositionApi = true;
|
|
if (page.hasOptionsApi) report.summary.hasOptionsApi = true;
|
|
}
|
|
|
|
// 生成建议
|
|
if (analysis.basic.uniAppVersion && !analysis.basic.uniAppVersion.includes('x')) {
|
|
report.recommendations.push('建议升级到uni-app x以获得更好的性能和原生体验');
|
|
}
|
|
|
|
if (report.summary.hasOptionsApi && !report.summary.hasCompositionApi) {
|
|
report.recommendations.push('建议迁移到Composition API以获得更好的TypeScript支持');
|
|
}
|
|
|
|
return report;
|
|
}
|
|
}
|
|
|
|
module.exports = AnalysisUtils;
|