first commit

This commit is contained in:
2025-09-22 01:39:01 +08:00
commit 8af3499484
1466 changed files with 126409 additions and 0 deletions

3
scripts/vsh/README.md Normal file
View File

@@ -0,0 +1,3 @@
# @vben/vsh
shell 脚本工具集合

3
scripts/vsh/bin/vsh.mjs Normal file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env node
import('../dist/index.mjs');

View File

@@ -0,0 +1,7 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index'],
});

31
scripts/vsh/package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "@vben/vsh",
"version": "5.5.4",
"private": true,
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
},
"files": [
"dist"
],
"bin": {
"vsh": "./bin/vsh.mjs"
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"exports": {
".": {
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"dependencies": {
"@vben/node-utils": "workspace:*",
"cac": "catalog:",
"circular-dependency-scanner": "catalog:",
"depcheck": "catalog:",
"publint": "catalog:"
}
}

View File

@@ -0,0 +1,80 @@
import type { CAC } from 'cac';
import { extname } from 'node:path';
import { getStagedFiles } from '@vben/node-utils';
import { circularDepsDetect, printCircles } from 'circular-dependency-scanner';
const IGNORE_DIR = [
'dist',
'.turbo',
'output',
'.cache',
'scripts',
'internal',
'packages/effects/request/src/',
'packages/@core/ui-kit/menu-ui/src/',
'packages/@core/ui-kit/popup-ui/src/',
].join(',');
const IGNORE = [`**/{${IGNORE_DIR}}/**`];
interface CommandOptions {
staged: boolean;
verbose: boolean;
}
async function checkCircular({ staged, verbose }: CommandOptions) {
const results = await circularDepsDetect({
absolute: staged,
cwd: process.cwd(),
ignore: IGNORE,
});
if (staged) {
let files = await getStagedFiles();
const allowedExtensions = new Set([
'.cjs',
'.js',
'.jsx',
'.mjs',
'.ts',
'.tsx',
'.vue',
]);
// 过滤文件列表
files = files.filter((file) => allowedExtensions.has(extname(file)));
const circularFiles: string[][] = [];
for (const file of files) {
for (const result of results) {
const resultFiles = result.flat();
if (resultFiles.includes(file)) {
circularFiles.push(result);
}
}
}
verbose && printCircles(circularFiles);
} else {
verbose && printCircles(results);
}
}
function defineCheckCircularCommand(cac: CAC) {
cac
.command('check-circular')
.option(
'--staged',
'Whether it is the staged commit mode, in which mode, if there is a circular dependency, an alarm will be given.',
)
.usage(`Analysis of project circular dependencies.`)
.action(async ({ staged }) => {
await checkCircular({ staged, verbose: true });
});
}
export { defineCheckCircularCommand };

View File

@@ -0,0 +1,85 @@
import type { CAC } from 'cac';
import { getPackages } from '@vben/node-utils';
import depcheck from 'depcheck';
async function runDepcheck() {
const { packages } = await getPackages();
await Promise.all(
packages.map(async (pkg) => {
if (
[
'@vben/backend-mock',
'@vben/commitlint-config',
'@vben/eslint-config',
'@vben/lint-staged-config',
'@vben/node-utils',
'@vben/prettier-config',
'@vben/stylelint-config',
'@vben/tailwind-config',
'@vben/tsconfig',
'@vben/vite-config',
'@vben/vite-config',
'@vben/vsh',
].includes(pkg.packageJson.name)
) {
return;
}
const unused = await depcheck(pkg.dir, {
ignoreMatches: [
'vite',
'vitest',
'unbuild',
'@vben/tsconfig',
'@vben/vite-config',
'@vben/tailwind-config',
'@types/*',
'@vben-core/design',
],
ignorePatterns: ['dist', 'node_modules', 'public'],
});
// 删除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);
}
});
if (
Object.keys(unused.missing).length === 0 &&
unused.dependencies.length === 0 &&
unused.devDependencies.length === 0
) {
return;
}
console.error(
'\n',
pkg.packageJson.name,
'\n missing:',
unused.missing,
'\n dependencies:',
unused.dependencies,
'\n devDependencies:',
unused.devDependencies,
);
}),
);
}
function defineDepcheckCommand(cac: CAC) {
cac
.command('check-dep')
.usage(`Analysis of project circular dependencies.`)
.action(async () => {
await runDepcheck();
});
}
export { defineDepcheckCommand };

View File

@@ -0,0 +1,78 @@
import type { CAC } from 'cac';
import { join, relative } from 'node:path';
import {
colors,
consola,
findMonorepoRoot,
getPackages,
gitAdd,
outputJSON,
prettierFormat,
toPosixPath,
} from '@vben/node-utils';
const CODE_WORKSPACE_FILE = join('vben-admin.code-workspace');
interface CodeWorkspaceCommandOptions {
autoCommit?: boolean;
spaces?: number;
}
async function createCodeWorkspace({
autoCommit = false,
spaces = 2,
}: CodeWorkspaceCommandOptions) {
const { packages, rootDir } = await getPackages();
let folders = packages.map((pkg) => {
const { dir, packageJson } = pkg;
return {
name: packageJson.name,
path: toPosixPath(relative(rootDir, dir)),
};
});
folders = folders.filter(Boolean);
const monorepoRoot = findMonorepoRoot();
const outputPath = join(monorepoRoot, CODE_WORKSPACE_FILE);
await outputJSON(outputPath, { folders }, spaces);
await prettierFormat(outputPath);
if (autoCommit) {
await gitAdd(CODE_WORKSPACE_FILE, monorepoRoot);
}
}
async function runCodeWorkspace({
autoCommit,
spaces,
}: CodeWorkspaceCommandOptions) {
await createCodeWorkspace({
autoCommit,
spaces,
});
if (autoCommit) {
return;
}
consola.log('');
consola.success(colors.green(`${CODE_WORKSPACE_FILE} is updated!`));
consola.log('');
}
function defineCodeWorkspaceCommand(cac: CAC) {
cac
.command('code-workspace')
.usage('Update the `.code-workspace` file')
.option('--spaces [number]', '.code-workspace JSON file spaces.', {
default: 2,
})
.option('--auto-commit', 'auto commit .code-workspace JSON file.', {
default: false,
})
.action(runCodeWorkspace);
}
export { defineCodeWorkspaceCommand };

41
scripts/vsh/src/index.ts Normal file
View File

@@ -0,0 +1,41 @@
import { colors, consola } from '@vben/node-utils';
import { cac } from 'cac';
import { defineCheckCircularCommand } from './check-circular';
import { defineDepcheckCommand } from './check-dep';
import { defineCodeWorkspaceCommand } from './code-workspace';
import { defineLintCommand } from './lint';
import { definePubLintCommand } from './publint';
try {
const vsh = cac('vsh');
// vsh lint
defineLintCommand(vsh);
// vsh publint
definePubLintCommand(vsh);
// vsh code-workspace
defineCodeWorkspaceCommand(vsh);
// vsh check-circular
defineCheckCircularCommand(vsh);
// vsh check-dep
defineDepcheckCommand(vsh);
// Invalid command
vsh.on('command:*', () => {
consola.error(colors.red('Invalid command!'));
process.exit(1);
});
vsh.usage('vsh');
vsh.help();
vsh.parse();
} catch (error) {
consola.error(error);
process.exit(1);
}

View File

@@ -0,0 +1,48 @@
import type { CAC } from 'cac';
import { execaCommand } from '@vben/node-utils';
interface LintCommandOptions {
/**
* Format lint problem.
*/
format?: boolean;
}
async function runLint({ format }: LintCommandOptions) {
// process.env.FORCE_COLOR = '3';
if (format) {
await execaCommand(`stylelint "**/*.{vue,css,less,scss}" --cache --fix`, {
stdio: 'inherit',
});
await execaCommand(`eslint . --cache --fix`, {
stdio: 'inherit',
});
await execaCommand(`prettier . --write --cache --log-level warn`, {
stdio: 'inherit',
});
return;
}
await Promise.all([
execaCommand(`eslint . --cache`, {
stdio: 'inherit',
}),
execaCommand(`prettier . --ignore-unknown --check --cache`, {
stdio: 'inherit',
}),
execaCommand(`stylelint "**/*.{vue,css,less,scss}" --cache`, {
stdio: 'inherit',
}),
]);
}
function defineLintCommand(cac: CAC) {
cac
.command('lint')
.usage('Batch execute project lint check.')
.option('--format', 'Format lint problem.')
.action(runLint);
}
export { defineLintCommand };

View File

@@ -0,0 +1,185 @@
import type { CAC } from 'cac';
import type { Result } from 'publint';
import { basename, dirname, join } from 'node:path';
import {
colors,
consola,
ensureFile,
findMonorepoRoot,
generatorContentHash,
getPackages,
outputJSON,
readJSON,
UNICODE,
} from '@vben/node-utils';
import { publint } from 'publint';
import { formatMessage } from 'publint/utils';
const CACHE_FILE = join(
'node_modules',
'.cache',
'publint',
'.pkglintcache.json',
);
interface PubLintCommandOptions {
/**
* Only errors are checked, no program exit is performed
*/
check?: boolean;
}
/**
* Get files that require lint
* @param files
*/
async function getLintFiles(files: string[] = []) {
const lintFiles: string[] = [];
if (files?.length > 0) {
return files.filter((file) => basename(file) === 'package.json');
}
const { packages } = await getPackages();
for (const { dir } of packages) {
lintFiles.push(join(dir, 'package.json'));
}
return lintFiles;
}
function getCacheFile() {
const root = findMonorepoRoot();
return join(root, CACHE_FILE);
}
async function readCache(cacheFile: string) {
try {
await ensureFile(cacheFile);
return await readJSON(cacheFile);
} catch {
return {};
}
}
async function runPublint(files: string[], { check }: PubLintCommandOptions) {
const lintFiles = await getLintFiles(files);
const cacheFile = getCacheFile();
const cacheData = await readCache(cacheFile);
const cache: Record<string, { hash: string; result: Result }> = cacheData;
const results = await Promise.all(
lintFiles.map(async (file) => {
try {
const pkgJson = await readJSON(file);
if (pkgJson.private) {
return null;
}
Reflect.deleteProperty(pkgJson, 'dependencies');
Reflect.deleteProperty(pkgJson, 'devDependencies');
Reflect.deleteProperty(pkgJson, 'peerDependencies');
const content = JSON.stringify(pkgJson);
const hash = generatorContentHash(content);
const publintResult: Result =
cache?.[file]?.hash === hash
? (cache?.[file]?.result ?? [])
: await publint({
level: 'suggestion',
pkgDir: dirname(file),
strict: true,
});
cache[file] = {
hash,
result: publintResult,
};
return { pkgJson, pkgPath: file, publintResult };
} catch {
return null;
}
}),
);
await outputJSON(cacheFile, cache);
printResult(results, check);
}
function printResult(
results: Array<null | {
pkgJson: Record<string, number | string>;
pkgPath: string;
publintResult: Result;
}>,
check?: boolean,
) {
let errorCount = 0;
let warningCount = 0;
let suggestionsCount = 0;
for (const result of results) {
if (!result) {
continue;
}
const { pkgJson, pkgPath, publintResult } = result;
const messages = publintResult?.messages ?? [];
if (messages?.length < 1) {
continue;
}
consola.log('');
consola.log(pkgPath);
for (const message of messages) {
switch (message.type) {
case 'error': {
errorCount++;
break;
}
case 'suggestion': {
suggestionsCount++;
break;
}
case 'warning': {
warningCount++;
break;
}
// No default
}
const ruleUrl = `https://publint.dev/rules#${message.code.toLocaleLowerCase()}`;
consola.log(
` ${formatMessage(message, pkgJson)}${colors.dim(` ${ruleUrl}`)}`,
);
}
}
const totalCount = warningCount + errorCount + suggestionsCount;
if (totalCount > 0) {
consola.error(
colors.red(
`${UNICODE.FAILURE} ${totalCount} problem (${errorCount} errors, ${warningCount} warnings, ${suggestionsCount} suggestions)`,
),
);
!check && process.exit(1);
} else {
consola.log(colors.green(`${UNICODE.SUCCESS} No problem`));
}
}
function definePubLintCommand(cac: CAC) {
cac
.command('publint [...files]')
.usage('Check if the monorepo package conforms to the publint standard.')
.option('--check', 'Only errors are checked, no program exit is performed.')
.action(runPublint);
}
export { definePubLintCommand };

View File

@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"include": ["src"],
"exclude": ["node_modules"]
}