refactor: replace depcheck with knip for dependency checking

Migrate vsh check-dep from depcheck to knip, reducing
315 transitive dependencies. Config is inlined in
DEFAULT_CONFIG, matching the check-circular pattern.
Also re-export CollapsibleParams from @vben/common-ui.
master^2
xingyu4j 2026-05-20 16:14:21 +08:00
parent f71094e878
commit 9ec98f0846
8 changed files with 617 additions and 429 deletions

View File

@ -21,6 +21,7 @@ export {
VbenButtonGroup, VbenButtonGroup,
VbenCheckbox, VbenCheckbox,
VbenCheckButtonGroup, VbenCheckButtonGroup,
VbenCollapsibleParams,
VbenContextMenu, VbenContextMenu,
VbenCountToAnimator, VbenCountToAnimator,
VbenFullScreen, VbenFullScreen,
@ -33,5 +34,9 @@ export {
VbenSpinner, VbenSpinner,
} from '@vben-core/shadcn-ui'; } from '@vben-core/shadcn-ui';
export type { FlattenedItem } from '@vben-core/shadcn-ui'; export type {
CollapsibleParamSchema,
CollapsibleParamsProps,
FlattenedItem,
} from '@vben-core/shadcn-ui';
export { globalShareState } from '@vben-core/shared/global-state'; export { globalShareState } from '@vben-core/shared/global-state';

View File

@ -36,14 +36,13 @@ import type { Component, Ref } from 'vue';
import type { import type {
ApiComponentSharedProps, ApiComponentSharedProps,
BaseFormComponentType, BaseFormComponentType,
CollapsibleParamsProps,
IconPickerProps, IconPickerProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Sortable } from '@vben/hooks'; import type { Sortable } from '@vben/hooks';
import type { TipTapProps } from '@vben/plugins/tiptap'; import type { TipTapProps } from '@vben/plugins/tiptap';
import type { Recordable } from '@vben/types'; import type { Recordable } from '@vben/types';
import type { CollapsibleParamsProps } from '@vben-core/shadcn-ui';
import { import {
computed, computed,
defineAsyncComponent, defineAsyncComponent,
@ -62,6 +61,7 @@ import {
ApiComponent, ApiComponent,
globalShareState, globalShareState,
IconPicker, IconPicker,
VbenCollapsibleParams,
VCropper, VCropper,
} from '@vben/common-ui'; } from '@vben/common-ui';
import { useSortable } from '@vben/hooks'; import { useSortable } from '@vben/hooks';
@ -70,8 +70,6 @@ import { $t } from '@vben/locales';
import { VbenTiptap } from '@vben/plugins/tiptap'; import { VbenTiptap } from '@vben/plugins/tiptap';
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
import { VbenCollapsibleParams } from '@vben-core/shadcn-ui';
import { message, Modal, notification } from 'antdv-next'; import { message, Modal, notification } from 'antdv-next';
import { upload_file } from '#/api/examples/upload'; import { upload_file } from '#/api/examples/upload';

View File

@ -1,15 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { RadioGroupProps } from 'antdv-next'; import type { RadioGroupProps } from 'antdv-next';
import type { FormLayout } from '@vben/common-ui'; import type { CollapsibleParamSchema, FormLayout } from '@vben/common-ui';
import type { CollapsibleParamSchema } from '@vben-core/shadcn-ui';
import { h, ref } from 'vue'; import { h, ref } from 'vue';
import { Page } from '@vben/common-ui'; import { Page, VbenCollapsibleParams } from '@vben/common-ui';
import { VbenCollapsibleParams } from '@vben-core/shadcn-ui';
import { Button, Card, message, RadioGroup } from 'antdv-next'; import { Button, Card, message, RadioGroup } from 'antdv-next';

File diff suppressed because it is too large Load Diff

View File

@ -115,7 +115,6 @@ catalog:
czg: ^1.13.1 czg: ^1.13.1
dayjs: ^1.11.20 dayjs: ^1.11.20
defu: ^6.1.7 defu: ^6.1.7
depcheck: ^1.4.7
dotenv: ^17.4.2 dotenv: ^17.4.2
echarts: ^6.1.0 echarts: ^6.1.0
element-plus: ^2.14.0 element-plus: ^2.14.0
@ -141,6 +140,7 @@ catalog:
is-ci: ^4.1.0 is-ci: ^4.1.0
json-bigint: ^1.0.0 json-bigint: ^1.0.0
jsonwebtoken: ^9.0.3 jsonwebtoken: ^9.0.3
knip: ^6.14.1
lefthook: ^2.1.8 lefthook: ^2.1.8
lodash.clonedeep: ^4.5.0 lodash.clonedeep: ^4.5.0
lucide-vue-next: ^0.577.0 lucide-vue-next: ^0.577.0

View File

@ -25,7 +25,7 @@
"@vben/node-utils": "workspace:*", "@vben/node-utils": "workspace:*",
"cac": "catalog:", "cac": "catalog:",
"circular-dependency-scanner": "catalog:", "circular-dependency-scanner": "catalog:",
"depcheck": "catalog:", "knip": "catalog:",
"publint": "catalog:" "publint": "catalog:"
} }
} }

View File

@ -1,162 +1,133 @@
import type { CAC } from 'cac'; 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 = { const DEFAULT_CONFIG = {
// 需要忽略的依赖匹配 ignore: ['dist/**', 'docs/**', 'node_modules/**', 'public/**'],
ignoreMatches: [ ignoreBinaries: [] as string[],
'vite', ignoreDependencies: [
'vitest', '@iconify/json',
'tsdown',
'@vben/tailwind-config',
'@vben/tsconfig',
'@vben/vite-config',
'@types/*',
'@vben-core/design', '@vben-core/design',
],
// 需要忽略的包
ignorePackages: [
'@vben/backend-mock',
'@vben/commitlint-config', '@vben/commitlint-config',
'@vben/eslint-config', '@vben/eslint-config',
'@vben/node-utils',
'@vben/oxfmt-config',
'@vben/oxlint-config',
'@vben/stylelint-config', '@vben/stylelint-config',
'@vben/tsconfig', '@vben/tailwind-config',
'@vben/vite-config', '@vben/vite-config',
'@vben/vsh', '@vben/oxlint-config',
'playwright',
'rimraf',
'tailwindcss',
], ],
// 需要忽略的文件模式 ignoreWorkspaces: ['internal/lint-configs/*', 'scripts/*'],
ignorePatterns: ['dist', 'node_modules', 'public'],
}; };
interface DepcheckResult { interface KnipDependency {
dependencies: string[]; col: number;
devDependencies: string[]; line: number;
missing: Record<string, string[]>; name: string;
pos: number;
} }
interface DepcheckConfig { interface KnipFileIssue {
ignoreMatches?: string[]; dependencies: KnipDependency[];
ignorePackages?: string[]; devDependencies: KnipDependency[];
ignorePatterns?: string[]; file: string;
optionalPeerDependencies: KnipDependency[];
} }
interface PackageInfo { interface KnipResult {
dir: string; issues: KnipFileIssue[];
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);
}
});
} }
/** /**
* *
* @param pkgName - * @param result -
* @param unused -
*/ */
function formatDepcheckResult(pkgName: string, unused: DepcheckResult): void { function formatResult(result: KnipResult): void {
const hasIssues = let hasIssues = false;
Object.keys(unused.missing).length > 0 ||
unused.dependencies.length > 0 || for (const issue of result.issues) {
unused.devDependencies.length > 0; 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) { if (!hasIssues) {
return; console.log('\n✅ Dependency check completed, no issues found');
}
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}`));
} }
} }
/** /**
* *
* @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 { try {
const finalConfig = { await writeFile(configFile, JSON.stringify(DEFAULT_CONFIG));
...DEFAULT_CONFIG,
...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(); if (execaError.exitCode === 1 && execaError.stdout) {
const result: KnipResult = JSON.parse(execaError.stdout);
let hasIssues = false; formatResult(result);
return;
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');
} }
} catch (error) {
console.error( console.error(
'❌ Dependency check failed:', '❌ Dependency check failed:',
error instanceof Error ? error.message : error, error instanceof Error ? error.message : error,
); );
} finally {
await rm(tempDir, { force: true, recursive: true });
} }
} }
@ -164,31 +135,13 @@ async function runDepcheck(config: DepcheckConfig = {}): Promise<void> {
* *
* @param cac - CAC * @param cac - CAC
*/ */
function defineDepcheckCommand(cac: CAC): void { function defineCheckDepCommand(cac: CAC): void {
cac cac
.command('check-dep') .command('check-dep')
.option( .usage('Analyze project dependencies using knip')
'--ignore-packages <packages>', .action(async () => {
'Packages to ignore, comma separated', await runKnipCheck();
)
.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);
}); });
} }
export { defineDepcheckCommand, type DepcheckConfig }; export { defineCheckDepCommand };

View File

@ -4,7 +4,7 @@ import { cac } from 'cac';
import { version } from '../package.json'; import { version } from '../package.json';
import { defineCheckCircularCommand } from './check-circular'; import { defineCheckCircularCommand } from './check-circular';
import { defineDepcheckCommand } from './check-dep'; import { defineCheckDepCommand } from './check-dep';
import { defineCodeWorkspaceCommand } from './code-workspace'; import { defineCodeWorkspaceCommand } from './code-workspace';
import { defineLintCommand } from './lint'; import { defineLintCommand } from './lint';
import { definePubLintCommand } from './publint'; import { definePubLintCommand } from './publint';
@ -30,7 +30,7 @@ async function main(): Promise<void> {
definePubLintCommand(vsh); definePubLintCommand(vsh);
defineCodeWorkspaceCommand(vsh); defineCodeWorkspaceCommand(vsh);
defineCheckCircularCommand(vsh); defineCheckCircularCommand(vsh);
defineDepcheckCommand(vsh); defineCheckDepCommand(vsh);
// Set up CLI // Set up CLI
vsh.usage('vsh <command> [options]'); vsh.usage('vsh <command> [options]');