Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin
This commit is contained in:
@@ -13,7 +13,7 @@ function stop_and_remove_container() {
|
||||
|
||||
function remove_image() {
|
||||
# Remove the existing image
|
||||
docker rmi vben-admin-pro >/dev/null 2>&1
|
||||
docker rmi ${IMAGE_NAME} >/dev/null 2>&1
|
||||
}
|
||||
|
||||
function install_dependencies() {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@vben/node-utils": "workspace:*",
|
||||
"cac": "catalog:",
|
||||
"circular-dependency-scanner": "catalog:",
|
||||
"depcheck": "catalog:",
|
||||
"knip": "catalog:",
|
||||
"publint": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,161 +1,133 @@
|
||||
import type { CAC } from 'cac';
|
||||
|
||||
import { getPackages } from '@vben/node-utils';
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
import depcheck from 'depcheck';
|
||||
import { execa } from '@vben/node-utils';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const knipMain = require.resolve('knip');
|
||||
const knipCli = join(dirname(knipMain), '..', 'bin', 'knip.js');
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG = {
|
||||
// 需要忽略的依赖匹配
|
||||
ignoreMatches: [
|
||||
'vite',
|
||||
'vitest',
|
||||
'tsdown',
|
||||
'@vben/tailwind-config',
|
||||
'@vben/tsconfig',
|
||||
'@vben/vite-config',
|
||||
'@types/*',
|
||||
'@vben-core/design',
|
||||
],
|
||||
ignore: ['dist/**', 'docs/**', 'node_modules/**', 'public/**'],
|
||||
ignoreBinaries: [] as string[],
|
||||
ignoreDependencies: ['@iconify/json', '@vben-core/design'],
|
||||
// 需要忽略的包
|
||||
ignorePackages: [
|
||||
'@vben/commitlint-config',
|
||||
'@vben/eslint-config',
|
||||
'@vben/node-utils',
|
||||
'@vben/oxfmt-config',
|
||||
'@vben/oxlint-config',
|
||||
'@vben/stylelint-config',
|
||||
'@vben/tsconfig',
|
||||
'@vben/tailwind-config',
|
||||
'@vben/vite-config',
|
||||
'@vben/vsh',
|
||||
'@vben/oxlint-config',
|
||||
'playwright',
|
||||
'rimraf',
|
||||
'tailwindcss',
|
||||
],
|
||||
// 需要忽略的文件模式
|
||||
ignorePatterns: ['dist', 'node_modules', 'public'],
|
||||
ignoreWorkspaces: ['internal/lint-configs/*', 'scripts/*'],
|
||||
};
|
||||
|
||||
interface DepcheckResult {
|
||||
dependencies: string[];
|
||||
devDependencies: string[];
|
||||
missing: Record<string, string[]>;
|
||||
interface KnipDependency {
|
||||
col: number;
|
||||
line: number;
|
||||
name: string;
|
||||
pos: number;
|
||||
}
|
||||
|
||||
interface DepcheckConfig {
|
||||
ignoreMatches?: string[];
|
||||
ignorePackages?: string[];
|
||||
ignorePatterns?: string[];
|
||||
interface KnipFileIssue {
|
||||
dependencies: KnipDependency[];
|
||||
devDependencies: KnipDependency[];
|
||||
file: string;
|
||||
optionalPeerDependencies: KnipDependency[];
|
||||
}
|
||||
|
||||
interface PackageInfo {
|
||||
dir: string;
|
||||
packageJson: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理依赖检查结果
|
||||
* @param unused - 依赖检查结果
|
||||
*/
|
||||
function cleanDepcheckResult(unused: DepcheckResult): void {
|
||||
// 删除file:前缀的依赖提示,该依赖是本地依赖
|
||||
Reflect.deleteProperty(unused.missing, 'file:');
|
||||
|
||||
// 清理路径依赖
|
||||
Object.keys(unused.missing).forEach((key) => {
|
||||
unused.missing[key] = (unused.missing[key] || []).filter(
|
||||
(item: string) => !item.startsWith('/'),
|
||||
);
|
||||
if (unused.missing[key].length === 0) {
|
||||
Reflect.deleteProperty(unused.missing, key);
|
||||
}
|
||||
});
|
||||
interface KnipResult {
|
||||
issues: KnipFileIssue[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化依赖检查结果
|
||||
* @param pkgName - 包名
|
||||
* @param unused - 依赖检查结果
|
||||
* @param result - 依赖检查结果
|
||||
*/
|
||||
function formatDepcheckResult(pkgName: string, unused: DepcheckResult): void {
|
||||
const hasIssues =
|
||||
Object.keys(unused.missing).length > 0 ||
|
||||
unused.dependencies.length > 0 ||
|
||||
unused.devDependencies.length > 0;
|
||||
function formatResult(result: KnipResult): void {
|
||||
let hasIssues = false;
|
||||
|
||||
for (const issue of result.issues) {
|
||||
const hasDeps = issue.dependencies.length > 0;
|
||||
const hasDevDeps = issue.devDependencies.length > 0;
|
||||
|
||||
if (!hasDeps && !hasDevDeps) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasIssues = true;
|
||||
console.log(`\n📦 ${issue.file}`);
|
||||
|
||||
if (hasDeps) {
|
||||
console.log('⚠️ Unused dependencies:');
|
||||
for (const dep of issue.dependencies) {
|
||||
console.log(` - ${dep.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDevDeps) {
|
||||
console.log('⚠️ Unused devDependencies:');
|
||||
for (const dep of issue.devDependencies) {
|
||||
console.log(` - ${dep.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasIssues) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('\n📦 Package:', pkgName);
|
||||
|
||||
if (Object.keys(unused.missing).length > 0) {
|
||||
console.log('❌ Missing dependencies:');
|
||||
Object.entries(unused.missing).forEach(([dep, files]) => {
|
||||
console.log(` - ${dep}:`);
|
||||
files.forEach((file) => console.log(` → ${file}`));
|
||||
});
|
||||
}
|
||||
|
||||
if (unused.dependencies.length > 0) {
|
||||
console.log('⚠️ Unused dependencies:');
|
||||
unused.dependencies.forEach((dep) => console.log(` - ${dep}`));
|
||||
}
|
||||
|
||||
if (unused.devDependencies.length > 0) {
|
||||
console.log('⚠️ Unused devDependencies:');
|
||||
unused.devDependencies.forEach((dep) => console.log(` - ${dep}`));
|
||||
console.log('\n✅ Dependency check completed, no issues found');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行依赖检查
|
||||
* @param config - 配置选项
|
||||
*/
|
||||
async function runDepcheck(config: DepcheckConfig = {}): Promise<void> {
|
||||
async function runKnipCheck(): Promise<void> {
|
||||
const cwd = process.cwd();
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'vsh-check-dep-'));
|
||||
const configFile = join(tempDir, 'knip.json');
|
||||
|
||||
try {
|
||||
const finalConfig = {
|
||||
...DEFAULT_CONFIG,
|
||||
...config,
|
||||
await writeFile(configFile, JSON.stringify(DEFAULT_CONFIG));
|
||||
|
||||
const args = [
|
||||
knipCli,
|
||||
'--config',
|
||||
configFile,
|
||||
'--include',
|
||||
'dependencies',
|
||||
'--reporter',
|
||||
'json',
|
||||
'--no-config-hints',
|
||||
];
|
||||
|
||||
await execa(process.execPath, args, { cwd });
|
||||
console.log('\n✅ Dependency check completed, no issues found');
|
||||
} catch (error: unknown) {
|
||||
const execaError = error as {
|
||||
exitCode?: number;
|
||||
stdout?: string;
|
||||
};
|
||||
|
||||
const { packages } = await getPackages();
|
||||
|
||||
let hasIssues = false;
|
||||
|
||||
await Promise.all(
|
||||
packages.map(async (pkg: PackageInfo) => {
|
||||
// 跳过需要忽略的包
|
||||
if (finalConfig.ignorePackages.includes(pkg.packageJson.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unused = await depcheck(pkg.dir, {
|
||||
ignoreMatches: finalConfig.ignoreMatches,
|
||||
ignorePatterns: finalConfig.ignorePatterns,
|
||||
});
|
||||
|
||||
cleanDepcheckResult(unused);
|
||||
|
||||
const pkgHasIssues =
|
||||
Object.keys(unused.missing).length > 0 ||
|
||||
unused.dependencies.length > 0 ||
|
||||
unused.devDependencies.length > 0;
|
||||
|
||||
if (pkgHasIssues) {
|
||||
hasIssues = true;
|
||||
formatDepcheckResult(pkg.packageJson.name, unused);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (!hasIssues) {
|
||||
console.log('\n✅ Dependency check completed, no issues found');
|
||||
if (execaError.exitCode === 1 && execaError.stdout) {
|
||||
const result: KnipResult = JSON.parse(execaError.stdout);
|
||||
formatResult(result);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
console.error(
|
||||
'❌ Dependency check failed:',
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
} finally {
|
||||
await rm(tempDir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,31 +135,13 @@ async function runDepcheck(config: DepcheckConfig = {}): Promise<void> {
|
||||
* 定义依赖检查命令
|
||||
* @param cac - CAC实例
|
||||
*/
|
||||
function defineDepcheckCommand(cac: CAC): void {
|
||||
function defineCheckDepCommand(cac: CAC): void {
|
||||
cac
|
||||
.command('check-dep')
|
||||
.option(
|
||||
'--ignore-packages <packages>',
|
||||
'Packages to ignore, comma separated',
|
||||
)
|
||||
.option(
|
||||
'--ignore-matches <matches>',
|
||||
'Dependency patterns to ignore, comma separated',
|
||||
)
|
||||
.option(
|
||||
'--ignore-patterns <patterns>',
|
||||
'File patterns to ignore, comma separated',
|
||||
)
|
||||
.usage('Analyze project dependencies')
|
||||
.action(async ({ ignoreMatches, ignorePackages, ignorePatterns }) => {
|
||||
const config: DepcheckConfig = {
|
||||
...(ignorePackages && { ignorePackages: ignorePackages.split(',') }),
|
||||
...(ignoreMatches && { ignoreMatches: ignoreMatches.split(',') }),
|
||||
...(ignorePatterns && { ignorePatterns: ignorePatterns.split(',') }),
|
||||
};
|
||||
|
||||
await runDepcheck(config);
|
||||
.usage('Analyze project dependencies using knip')
|
||||
.action(async () => {
|
||||
await runKnipCheck();
|
||||
});
|
||||
}
|
||||
|
||||
export { defineDepcheckCommand, type DepcheckConfig };
|
||||
export { defineCheckDepCommand };
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cac } from 'cac';
|
||||
|
||||
import { version } from '../package.json';
|
||||
import { defineCheckCircularCommand } from './check-circular';
|
||||
import { defineDepcheckCommand } from './check-dep';
|
||||
import { defineCheckDepCommand } from './check-dep';
|
||||
import { defineCodeWorkspaceCommand } from './code-workspace';
|
||||
import { defineLintCommand } from './lint';
|
||||
import { definePubLintCommand } from './publint';
|
||||
@@ -30,7 +30,7 @@ async function main(): Promise<void> {
|
||||
definePubLintCommand(vsh);
|
||||
defineCodeWorkspaceCommand(vsh);
|
||||
defineCheckCircularCommand(vsh);
|
||||
defineDepcheckCommand(vsh);
|
||||
defineCheckDepCommand(vsh);
|
||||
|
||||
// Set up CLI
|
||||
vsh.usage('vsh <command> [options]');
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { CAC } from 'cac';
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
import { execaCommand } from '@vben/node-utils';
|
||||
|
||||
interface LintCommandOptions {
|
||||
@@ -7,19 +9,24 @@ interface LintCommandOptions {
|
||||
* Format lint problem.
|
||||
*/
|
||||
format?: boolean;
|
||||
/**
|
||||
* Number of threads for oxfmt and oxlint (default: 2).
|
||||
*/
|
||||
threads?: number;
|
||||
}
|
||||
|
||||
async function runLint({ format }: LintCommandOptions) {
|
||||
async function runLint({ format, threads }: LintCommandOptions) {
|
||||
// process.env.FORCE_COLOR = '3';
|
||||
const threadsArg = threads ? ` --threads=${threads}` : ` --threads=2`;
|
||||
|
||||
if (format) {
|
||||
await execaCommand(`stylelint "**/*.{vue,css,less,scss}" --cache --fix`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await execaCommand(`oxfmt`, {
|
||||
await execaCommand(`oxfmt${threadsArg}`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await execaCommand(`oxlint --fix`, {
|
||||
await execaCommand(`oxlint --fix${threadsArg}`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await execaCommand(`eslint . --cache --fix`, {
|
||||
@@ -27,20 +34,33 @@ async function runLint({ format }: LintCommandOptions) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
await Promise.all([
|
||||
execaCommand(`oxfmt --check`, {
|
||||
stdio: 'inherit',
|
||||
}),
|
||||
execaCommand(`oxlint`, {
|
||||
stdio: 'inherit',
|
||||
}),
|
||||
execaCommand(`eslint . --cache`, {
|
||||
stdio: 'inherit',
|
||||
}),
|
||||
const subprocesses = [
|
||||
execaCommand(`oxlint${threadsArg}`, { stdio: 'inherit' }),
|
||||
execaCommand(`eslint . --cache`, { stdio: 'inherit' }),
|
||||
execaCommand(`stylelint "**/*.{vue,css,less,scss}" --cache`, {
|
||||
stdio: 'inherit',
|
||||
}),
|
||||
]);
|
||||
];
|
||||
|
||||
try {
|
||||
await Promise.all(subprocesses);
|
||||
} catch (error) {
|
||||
for (const subprocess of subprocesses) {
|
||||
try {
|
||||
if (process.platform === 'win32' && subprocess.pid) {
|
||||
execSync(`taskkill /F /T /PID ${subprocess.pid}`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
} else {
|
||||
subprocess.kill('SIGKILL');
|
||||
}
|
||||
} catch {
|
||||
// process may have already exited
|
||||
}
|
||||
}
|
||||
await Promise.allSettled(subprocesses);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function defineLintCommand(cac: CAC) {
|
||||
@@ -48,6 +68,7 @@ function defineLintCommand(cac: CAC) {
|
||||
.command('lint')
|
||||
.usage('Batch execute project lint check.')
|
||||
.option('--format', 'Format lint problem.')
|
||||
.option('--threads <count>', 'Number of threads for oxfmt and oxlint.')
|
||||
.action(runLint);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user