UNPKG

mp-lens

Version:

微信小程序分析工具 (Unused Code, Dependencies, Visualization)

351 lines 15.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.initializeCommandContext = initializeCommandContext; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const config_loader_1 = require("./config-loader"); const debug_logger_1 = require("./debug-logger"); const errors_1 = require("./errors"); const fs_finder_1 = require("./fs-finder"); const tsconfig_helper_1 = require("./tsconfig-helper"); /** * Performs common initialization steps for CLI commands. * Resolves paths, loads config, merges options, extracts common settings. */ async function initializeCommandContext(cliOptions) { var _a, _b, _c, _d; // 1. Resolve Project Path and Set Logger Root const projectRoot = path.resolve(cliOptions.project); debug_logger_1.logger.setProjectRoot(projectRoot); debug_logger_1.logger.debug(`Resolved project root: ${projectRoot}`); if (!fs.existsSync(projectRoot)) { throw new errors_1.HandledError(`Project directory does not exist: ${projectRoot}`); } // 2. Load config file const fileConfig = await config_loader_1.ConfigLoader.loadConfig(cliOptions.config, projectRoot); debug_logger_1.logger.debug(`Loaded config file content:`, fileConfig); // 3. Merge options // Merge with care: CLI should only override when value is explicitly provided const mergedConfig = { ...(fileConfig || {}), ...Object.fromEntries(Object.entries(cliOptions).filter(([, v]) => v !== undefined && v !== null)), }; // 3. Path Resolution const resolvePathIfNeeded = (p) => { if (p && typeof p === 'string' && !path.isAbsolute(p)) { return path.resolve(projectRoot, p); } return p; }; mergedConfig.miniappRoot = resolvePathIfNeeded(mergedConfig.miniappRoot); mergedConfig.appJsonPath = resolvePathIfNeeded(mergedConfig.appJsonPath); // --- Start: Auto-detection logic --- if (!mergedConfig.miniappRoot && !mergedConfig.appJsonPath) { debug_logger_1.logger.debug('miniappRoot and appJsonPath not specified, attempting auto-detection...'); const detectedConfig = (0, fs_finder_1.findAppJsonConfig)(projectRoot); if (detectedConfig && detectedConfig !== 'ambiguous') { mergedConfig.miniappRoot = detectedConfig.miniappRoot; mergedConfig.appJsonPath = detectedConfig.appJsonPath; } else if (detectedConfig === 'ambiguous') { debug_logger_1.logger.debug('Auto-detection resulted in ambiguity, leaving miniappRoot and appJsonPath undefined.'); } else { debug_logger_1.logger.debug('Auto-detection did not find a suitable app.json.'); } } // --- End: Auto-detection logic --- debug_logger_1.logger.debug(`Final merged options:`, mergedConfig); // Process essential files const allEssentialFiles = processEssentialFiles(cliOptions, fileConfig, projectRoot); debug_logger_1.logger.debug(`Final essential files list (CLI/Config + tsconfig):`, allEssentialFiles); // Load and merge aliases from multiple sources const aliasesFromTsConfig = loadAliasesFromTsConfig(projectRoot); // Priority (low -> high): tsconfig < mp-lens.config.* (通过 ConfigLoader 加载) const mergedAliases = { ...aliasesFromTsConfig, ...((fileConfig === null || fileConfig === void 0 ? void 0 : fileConfig.aliases) || {}), }; const hasMergedAliases = Object.keys(mergedAliases).length > 0; if (hasMergedAliases) { debug_logger_1.logger.debug(`Loaded aliases from sources (merged):`, mergedAliases); } // 4. Extract common options const verbose = (_a = mergedConfig.verbose) !== null && _a !== void 0 ? _a : false; const verboseLevel = (_b = mergedConfig.verboseLevel) !== null && _b !== void 0 ? _b : 3; const miniappRoot = (_c = mergedConfig.miniappRoot) !== null && _c !== void 0 ? _c : projectRoot; const appJsonPath = mergedConfig.appJsonPath; // Exclude: centralized initialization (defaults + .gitignore + config + CLI) const excludePatterns = buildExcludePatterns(projectRoot, fileConfig === null || fileConfig === void 0 ? void 0 : fileConfig.exclude, cliOptions.exclude); // Essential files: use the fully merged result from processEssentialFiles const essentialFiles = allEssentialFiles; const includeAssets = (_d = mergedConfig.includeAssets) !== null && _d !== void 0 ? _d : false; // Basic logging (can be expanded) debug_logger_1.logger.debug(`Project path: ${projectRoot}`); if (miniappRoot) debug_logger_1.logger.debug(`Using Miniapp root directory: ${miniappRoot}`); if (appJsonPath) debug_logger_1.logger.debug(`Using specific entry file: ${appJsonPath}`); // Resolve App.json const { appJsonPath: resolvedAppJsonPath, appJsonContent } = resolveAppJson(miniappRoot, appJsonPath, fileConfig === null || fileConfig === void 0 ? void 0 : fileConfig.appJsonContent); return { projectRoot, miniappRoot, appJsonPath: resolvedAppJsonPath, appJsonContent, excludePatterns, essentialFiles, includeAssets, verboseLevel, verbose, aliases: mergedAliases, }; } /** * Extracts and processes essential files from CLI options and config file * * @param cliOptions CLI provided options that may contain essentialFiles * @param fileConfig Configuration file options that may contain essentialFiles * @param projectRoot Project root path for resolving relative paths * @returns Array of resolved essential file paths */ function processEssentialFiles(cliOptions, fileConfig, projectRoot) { // Extract essential files from CLI or config let essentialFilesFromCliOrConfig = []; let essentialFilesSource = undefined; if (cliOptions.essentialFiles !== undefined) { essentialFilesSource = cliOptions.essentialFiles; } else if (fileConfig === null || fileConfig === void 0 ? void 0 : fileConfig.essentialFiles) { essentialFilesSource = fileConfig.essentialFiles; } if (essentialFilesSource) { essentialFilesFromCliOrConfig = typeof essentialFilesSource === 'string' ? essentialFilesSource.split(',').map((f) => f.trim()) : Array.isArray(essentialFilesSource) ? essentialFilesSource : []; // Default to empty array if invalid type } // Resolve paths from CLI/Config const resolvedEssentialFromCliOrConfig = essentialFilesFromCliOrConfig.map((f) => path.resolve(projectRoot, f)); // Load essential files from tsconfig.types const essentialFromTsConfig = (0, tsconfig_helper_1.loadTsConfigTypes)(projectRoot); // Combine and deduplicate all essential files return [...new Set([...resolvedEssentialFromCliOrConfig, ...essentialFromTsConfig])]; } /** * Resolves the app.json path and content based on user options or defaults. */ function resolveAppJson(miniappRoot, rawAppJsonPath, appJsonContent) { // Result variables let appJsonPath = ''; let effectiveAppJsonContent = {}; // Default to empty object // Priority 1: Use provided app.json content if (appJsonContent && typeof appJsonContent === 'object' && Object.keys(appJsonContent).length > 0) { debug_logger_1.logger.info('使用提供的 appJsonContent 作为 app.json 结构。'); effectiveAppJsonContent = appJsonContent; // If a path hint was provided, try to match it to an existing file if (rawAppJsonPath) { const potentialPath = path.resolve(miniappRoot, rawAppJsonPath); if (fs.existsSync(potentialPath)) { appJsonPath = potentialPath; debug_logger_1.logger.debug(`Found potential app.json path matching appJsonPath hint: ${appJsonPath}`); } else { debug_logger_1.logger.debug(`EntryFile hint given (${rawAppJsonPath}), but file not found at ${potentialPath}.`); } } return { appJsonPath, appJsonContent: effectiveAppJsonContent }; } // Priority 2: Use provided entry file path if (rawAppJsonPath) { const potentialPath = path.resolve(miniappRoot, rawAppJsonPath); if (fs.existsSync(potentialPath)) { debug_logger_1.logger.info(`使用自定义入口文件作为 app.json: ${potentialPath}`); appJsonPath = potentialPath; try { const content = fs.readFileSync(appJsonPath, 'utf-8'); effectiveAppJsonContent = JSON.parse(content); return { appJsonPath, appJsonContent: effectiveAppJsonContent }; } catch (error) { debug_logger_1.logger.error(`Failed to read or parse custom entry file ${appJsonPath}:`, error); throw new errors_1.HandledError(`Failed to process entry file: ${appJsonPath}`); } } else { debug_logger_1.logger.warn(`Specified entry file '${rawAppJsonPath}' not found relative to miniapp root '${miniappRoot}'. Falling back to default app.json detection.`); } } // Priority 3: Find default app.json const defaultAppJsonPath = path.resolve(miniappRoot, 'app.json'); if (fs.existsSync(defaultAppJsonPath)) { debug_logger_1.logger.debug(`Found default app.json at: ${defaultAppJsonPath}`); appJsonPath = defaultAppJsonPath; try { const content = fs.readFileSync(appJsonPath, 'utf-8'); effectiveAppJsonContent = JSON.parse(content); } catch (error) { debug_logger_1.logger.error(`Failed to read or parse default app.json ${appJsonPath}:`, error); throw new errors_1.HandledError(`Failed to process default app.json: ${appJsonPath}`); } } else { debug_logger_1.logger.warn('Could not find default app.json and no valid appJsonPath or appJsonContent provided. Proceeding with empty app configuration.'); } return { appJsonPath, appJsonContent: effectiveAppJsonContent }; } // === Alias loading helpers (pure functions) === function loadAliasesFromTsConfig(projectRoot) { try { const fsPath = path.join(projectRoot, 'tsconfig.json'); if (!fs.existsSync(fsPath)) return {}; const tsconfig = JSON.parse(fs.readFileSync(fsPath, 'utf-8')); if (!tsconfig.compilerOptions || !tsconfig.compilerOptions.paths) return {}; const tsconfigDir = path.dirname(fsPath); const baseUrl = tsconfig.compilerOptions.baseUrl || '.'; const baseDir = path.resolve(tsconfigDir, baseUrl); const result = {}; for (const [alias, targets] of Object.entries(tsconfig.compilerOptions.paths)) { const normalizedAlias = alias.replace(/\/\*$/, ''); result[normalizedAlias] = targets.map((t) => { const targetPath = t.replace(/\/\*$/, ''); return path.resolve(baseDir, targetPath); }); } return result; } catch (e) { debug_logger_1.logger.warn(`无法解析 tsconfig.json 以加载别名: ${e.message}`); return {}; } } // === Exclude building helpers === const DEFAULT_EXCLUDE_PATTERNS = [ // Dependencies '**/node_modules/**', '**/miniprogram_npm/**', // VCS '.git/**', '.svn/**', '.hg/**', // Caches '.cache/**', '.parcel-cache/**', '.turbo/**', // Build outputs and artifacts 'dist/**', 'build/**', '.next/**', 'out/**', 'coverage/**', 'tmp/**', 'temp/**', // Tests (files/dirs typically not part of runtime) '**/__tests__/**', '**/*.spec.js', '**/*.spec.ts', '**/*.test.js', '**/*.test.ts', // Root-level project meta/config files 'package.json', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'tsconfig.json', 'tsconfig.*.json', 'jsconfig.json', '.gitignore', '.gitattributes', '.editorconfig', ]; function buildExcludePatterns(projectRoot, configExclude, cliExclude) { const gitignore = loadGitignoreExcludes(projectRoot); const parts = [ ...DEFAULT_EXCLUDE_PATTERNS, ...gitignore, ...(Array.isArray(configExclude) ? configExclude : []), ...(Array.isArray(cliExclude) ? cliExclude : []), ]; const seen = new Set(); const unique = []; for (const p of parts) { if (!p || seen.has(p)) continue; seen.add(p); unique.push(p); } debug_logger_1.logger.debug('Final merged exclude patterns:', unique); return unique; } function loadGitignoreExcludes(projectRoot) { const gitignorePath = path.join(projectRoot, '.gitignore'); if (!fs.existsSync(gitignorePath)) return []; try { const content = fs.readFileSync(gitignorePath, 'utf-8'); const lines = content.split(/\r?\n/); const globs = []; for (let line of lines) { line = line.trim(); if (!line || line.startsWith('#')) continue; if (line.startsWith('!')) continue; // Ignore negations for exclude context const isDir = line.endsWith('/'); let pattern = line.replace(/^\//, ''); if (isDir && !pattern.endsWith('**')) { pattern = pattern + '**'; } if (!pattern.includes('/') && !pattern.includes('*')) { pattern = `**/${pattern}/**`; } globs.push(pattern); } return globs; } catch (e) { debug_logger_1.logger.warn(`读取 .gitignore 失败: ${e.message}`); return []; } } //# sourceMappingURL=command-init.js.map