mp-lens
Version:
微信小程序分析工具 (Unused Code, Dependencies, Visualization)
351 lines • 15.2 kB
JavaScript
;
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