Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
56
frontend-vben/scripts/clean.mjs
Normal file
56
frontend-vben/scripts/clean.mjs
Normal file
@@ -0,0 +1,56 @@
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { join, normalize } from 'node:path';
|
||||
|
||||
const rootDir = process.cwd();
|
||||
|
||||
/**
|
||||
* 递归查找并删除目标目录
|
||||
* @param {string} currentDir - 当前遍历的目录路径
|
||||
* @param {string[]} targets - 要删除的目标列表
|
||||
*/
|
||||
async function cleanTargetsRecursively(currentDir, targets) {
|
||||
const items = await fs.readdir(currentDir);
|
||||
|
||||
for (const item of items) {
|
||||
try {
|
||||
const itemPath = normalize(join(currentDir, item));
|
||||
const stat = await fs.lstat(itemPath);
|
||||
|
||||
if (targets.includes(item)) {
|
||||
// 匹配到目标目录或文件时直接删除
|
||||
await fs.rm(itemPath, { force: true, recursive: true });
|
||||
console.log(`Deleted: ${itemPath}`);
|
||||
} else if (stat.isDirectory()) {
|
||||
// 只对目录进行递归处理
|
||||
await cleanTargetsRecursively(itemPath, targets);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error handling item ${item} in ${currentDir}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(async function startCleanup() {
|
||||
// 要删除的目录及文件名称
|
||||
const targets = ['node_modules', 'dist', '.turbo', 'dist.zip'];
|
||||
const deleteLockFile = process.argv.includes('--del-lock');
|
||||
const cleanupTargets = [...targets];
|
||||
|
||||
if (deleteLockFile) {
|
||||
cleanupTargets.push('pnpm-lock.yaml');
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Starting cleanup of targets: ${cleanupTargets.join(', ')} from root: ${rootDir}`,
|
||||
);
|
||||
|
||||
try {
|
||||
await cleanTargetsRecursively(rootDir, cleanupTargets);
|
||||
console.log('Cleanup process completed successfully.');
|
||||
} catch (error) {
|
||||
console.error(`Unexpected error during cleanup: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
37
frontend-vben/scripts/deploy/Dockerfile
Normal file
37
frontend-vben/scripts/deploy/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM node:22-slim AS builder
|
||||
|
||||
# --max-old-space-size
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
ENV NODE_OPTIONS=--max-old-space-size=8192
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN npm i -g corepack
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# copy package.json and pnpm-lock.yaml to workspace
|
||||
COPY . /app
|
||||
|
||||
# 安装依赖
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
RUN pnpm run build --filter=\!./docs
|
||||
|
||||
RUN echo "Builder Success 🎉"
|
||||
|
||||
FROM nginx:stable-alpine AS production
|
||||
|
||||
# 配置 nginx
|
||||
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf \
|
||||
&& rm -rf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# 复制构建产物
|
||||
COPY --from=builder /app/playground/dist /usr/share/nginx/html
|
||||
|
||||
# 复制 nginx 配置
|
||||
COPY --from=builder /app/scripts/deploy/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# 启动 nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
55
frontend-vben/scripts/deploy/build-local-docker-image.sh
Executable file
55
frontend-vben/scripts/deploy/build-local-docker-image.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
LOG_FILE=${SCRIPT_DIR}/build-local-docker-image.log
|
||||
ERROR=""
|
||||
IMAGE_NAME="vben-admin-local"
|
||||
|
||||
function stop_and_remove_container() {
|
||||
# Stop and remove the existing container
|
||||
docker stop ${IMAGE_NAME} >/dev/null 2>&1
|
||||
docker rm ${IMAGE_NAME} >/dev/null 2>&1
|
||||
}
|
||||
|
||||
function remove_image() {
|
||||
# Remove the existing image
|
||||
docker rmi vben-admin-pro >/dev/null 2>&1
|
||||
}
|
||||
|
||||
function install_dependencies() {
|
||||
# Install all dependencies
|
||||
cd ${SCRIPT_DIR}
|
||||
pnpm install || ERROR="install_dependencies failed"
|
||||
}
|
||||
|
||||
function build_image() {
|
||||
# build docker
|
||||
docker build ../../ -f Dockerfile -t ${IMAGE_NAME} || ERROR="build_image failed"
|
||||
}
|
||||
|
||||
function log_message() {
|
||||
if [[ ${ERROR} != "" ]];
|
||||
then
|
||||
>&2 echo "build failed, Please check build-local-docker-image.log for more details"
|
||||
>&2 echo "ERROR: ${ERROR}"
|
||||
exit 1
|
||||
else
|
||||
echo "docker image with tag '${IMAGE_NAME}' built sussessfully. Use below sample command to run the container"
|
||||
echo ""
|
||||
echo "docker run -d -p 8010:8080 --name ${IMAGE_NAME} ${IMAGE_NAME}"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Info: Stopping and removing existing container and image" | tee ${LOG_FILE}
|
||||
stop_and_remove_container
|
||||
remove_image
|
||||
|
||||
echo "Info: Installing dependencies" | tee -a ${LOG_FILE}
|
||||
install_dependencies 1>> ${LOG_FILE} 2>> ${LOG_FILE}
|
||||
|
||||
if [[ ${ERROR} == "" ]]; then
|
||||
echo "Info: Building docker image" | tee -a ${LOG_FILE}
|
||||
build_image 1>> ${LOG_FILE} 2>> ${LOG_FILE}
|
||||
fi
|
||||
|
||||
log_message | tee -a ${LOG_FILE}
|
||||
75
frontend-vben/scripts/deploy/nginx.conf
Normal file
75
frontend-vben/scripts/deploy/nginx.conf
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
#user nobody;
|
||||
worker_processes 1;
|
||||
|
||||
#error_log logs/error.log;
|
||||
#error_log logs/error.log notice;
|
||||
#error_log logs/error.log info;
|
||||
|
||||
#pid logs/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
types {
|
||||
application/javascript js mjs;
|
||||
text/css css;
|
||||
text/html html;
|
||||
}
|
||||
|
||||
sendfile on;
|
||||
# tcp_nopush on;
|
||||
|
||||
#keepalive_timeout 0;
|
||||
# keepalive_timeout 65;
|
||||
|
||||
# gzip on;
|
||||
# gzip_buffers 32 16k;
|
||||
# gzip_comp_level 6;
|
||||
# gzip_min_length 1k;
|
||||
# gzip_static on;
|
||||
# gzip_types text/plain
|
||||
# text/css
|
||||
# application/javascript
|
||||
# application/json
|
||||
# application/x-javascript
|
||||
# text/xml
|
||||
# application/xml
|
||||
# application/xml+rss
|
||||
# text/javascript; #设置压缩的文件类型
|
||||
# gzip_vary on;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.html;
|
||||
# Enable CORS
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
frontend-vben/scripts/turbo-run/README.md
Normal file
59
frontend-vben/scripts/turbo-run/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# @vben/turbo-run
|
||||
|
||||
`turbo-run` 是一个命令行工具,允许你在多个包中并行运行命令。它提供了一个交互式的界面,让你可以选择要运行命令的包。
|
||||
|
||||
## 特性
|
||||
|
||||
- 🚀 交互式选择要运行的包
|
||||
- 📦 支持 monorepo 项目结构
|
||||
- 🔍 自动检测可用的命令
|
||||
- 🎯 精确过滤目标包
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pnpm add -D @vben/turbo-run
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
基本语法:
|
||||
|
||||
```bash
|
||||
turbo-run [script]
|
||||
```
|
||||
|
||||
例如,如果你想运行 `dev` 命令:
|
||||
|
||||
```bash
|
||||
turbo-run dev
|
||||
```
|
||||
|
||||
工具会自动检测哪些包有 `dev` 命令,并提供一个交互式界面让你选择要运行的包。
|
||||
|
||||
## 示例
|
||||
|
||||
假设你的项目中有以下包:
|
||||
|
||||
- `@vben/app`
|
||||
- `@vben/admin`
|
||||
- `@vben/website`
|
||||
|
||||
当你运行:
|
||||
|
||||
```bash
|
||||
turbo-run dev
|
||||
```
|
||||
|
||||
工具会:
|
||||
|
||||
1. 检测哪些包有 `dev` 命令
|
||||
2. 显示一个交互式选择界面
|
||||
3. 让你选择要运行命令的包
|
||||
4. 使用 `pnpm --filter` 在选定的包中运行命令
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保你的项目使用 pnpm 作为包管理器
|
||||
- 确保目标包在 `package.json` 中定义了相应的脚本命令
|
||||
- 该工具需要在 monorepo 项目的根目录下运行
|
||||
3
frontend-vben/scripts/turbo-run/bin/turbo-run.mjs
Executable file
3
frontend-vben/scripts/turbo-run/bin/turbo-run.mjs
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import('../dist/index.mjs');
|
||||
7
frontend-vben/scripts/turbo-run/build.config.ts
Normal file
7
frontend-vben/scripts/turbo-run/build.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
29
frontend-vben/scripts/turbo-run/package.json
Normal file
29
frontend-vben/scripts/turbo-run/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@vben/turbo-run",
|
||||
"version": "5.5.8",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"turbo-run": "./bin/turbo-run.mjs"
|
||||
},
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "catalog:",
|
||||
"@vben/node-utils": "workspace:*",
|
||||
"cac": "catalog:"
|
||||
}
|
||||
}
|
||||
29
frontend-vben/scripts/turbo-run/src/index.ts
Normal file
29
frontend-vben/scripts/turbo-run/src/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { colors, consola } from '@vben/node-utils';
|
||||
|
||||
import { cac } from 'cac';
|
||||
|
||||
import { run } from './run';
|
||||
|
||||
try {
|
||||
const turboRun = cac('turbo-run');
|
||||
|
||||
turboRun
|
||||
.command('[script]')
|
||||
.usage(`Run turbo interactively.`)
|
||||
.action(async (command: string) => {
|
||||
run({ command });
|
||||
});
|
||||
|
||||
// Invalid command
|
||||
turboRun.on('command:*', () => {
|
||||
consola.error(colors.red('Invalid command!'));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
turboRun.usage('turbo-run');
|
||||
turboRun.help();
|
||||
turboRun.parse();
|
||||
} catch (error) {
|
||||
consola.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
67
frontend-vben/scripts/turbo-run/src/run.ts
Normal file
67
frontend-vben/scripts/turbo-run/src/run.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { execaCommand, getPackages } from '@vben/node-utils';
|
||||
|
||||
import { cancel, isCancel, select } from '@clack/prompts';
|
||||
|
||||
interface RunOptions {
|
||||
command?: string;
|
||||
}
|
||||
|
||||
export async function run(options: RunOptions) {
|
||||
const { command } = options;
|
||||
if (!command) {
|
||||
console.error('Please enter the command to run');
|
||||
process.exit(1);
|
||||
}
|
||||
const { packages } = await getPackages();
|
||||
// const appPkgs = await findApps(process.cwd(), packages);
|
||||
// const websitePkg = packages.find(
|
||||
// (item) => item.packageJson.name === '@vben/website',
|
||||
// );
|
||||
|
||||
// 只显示有对应命令的包
|
||||
const selectPkgs = packages.filter((pkg) => {
|
||||
return (pkg?.packageJson as Record<string, any>)?.scripts?.[command];
|
||||
});
|
||||
|
||||
let selectPkg: string | symbol;
|
||||
if (selectPkgs.length > 1) {
|
||||
selectPkg = await select<string>({
|
||||
message: `Select the app you need to run [${command}]:`,
|
||||
options: selectPkgs.map((item) => ({
|
||||
label: item?.packageJson.name,
|
||||
value: item?.packageJson.name,
|
||||
})),
|
||||
});
|
||||
|
||||
if (isCancel(selectPkg) || !selectPkg) {
|
||||
cancel('👋 Has cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
} else {
|
||||
selectPkg = selectPkgs[0]?.packageJson?.name ?? '';
|
||||
}
|
||||
|
||||
if (!selectPkg) {
|
||||
console.error('No app found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
execaCommand(`pnpm --filter=${selectPkg} run ${command}`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤app包
|
||||
* @param root
|
||||
* @param packages
|
||||
*/
|
||||
// async function findApps(root: string, packages: Package[]) {
|
||||
// // apps内的
|
||||
// const appPackages = packages.filter((pkg) => {
|
||||
// const viteConfigExists = fs.existsSync(join(pkg.dir, 'vite.config.mts'));
|
||||
// return pkg.dir.startsWith(join(root, 'apps')) && viteConfigExists;
|
||||
// });
|
||||
|
||||
// return appPackages;
|
||||
// }
|
||||
6
frontend-vben/scripts/turbo-run/tsconfig.json
Normal file
6
frontend-vben/scripts/turbo-run/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
56
frontend-vben/scripts/vsh/README.md
Normal file
56
frontend-vben/scripts/vsh/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# @vben/vsh
|
||||
|
||||
一个 Shell 脚本工具集合,用于 Vue Vben Admin 项目的开发和管理。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🚀 基于 Node.js 的现代化 Shell 工具
|
||||
- 📦 支持模块化开发和按需加载
|
||||
- 🔍 提供依赖检查和分析功能
|
||||
- 🔄 支持循环依赖扫描
|
||||
- 📝 提供包发布检查功能
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# 使用 pnpm 安装
|
||||
pnpm add -D @vben/vsh
|
||||
|
||||
# 或者使用 npm
|
||||
npm install -D @vben/vsh
|
||||
|
||||
# 或者使用 yarn
|
||||
yarn add -D @vben/vsh
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 全局安装
|
||||
|
||||
```bash
|
||||
# 全局安装
|
||||
pnpm add -g @vben/vsh
|
||||
|
||||
# 使用 vsh 命令
|
||||
vsh [command]
|
||||
```
|
||||
|
||||
### 本地使用
|
||||
|
||||
```bash
|
||||
# 在 package.json 中添加脚本
|
||||
{
|
||||
"scripts": {
|
||||
"vsh": "vsh"
|
||||
}
|
||||
}
|
||||
|
||||
# 运行命令
|
||||
pnpm vsh [command]
|
||||
```
|
||||
|
||||
## 命令列表
|
||||
|
||||
- `vsh check-deps`: 检查项目依赖
|
||||
- `vsh scan-circular`: 扫描循环依赖
|
||||
- `vsh publish-check`: 检查包发布配置
|
||||
3
frontend-vben/scripts/vsh/bin/vsh.mjs
Executable file
3
frontend-vben/scripts/vsh/bin/vsh.mjs
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import('../dist/index.mjs');
|
||||
7
frontend-vben/scripts/vsh/build.config.ts
Normal file
7
frontend-vben/scripts/vsh/build.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
31
frontend-vben/scripts/vsh/package.json
Normal file
31
frontend-vben/scripts/vsh/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@vben/vsh",
|
||||
"version": "5.5.8",
|
||||
"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:"
|
||||
}
|
||||
}
|
||||
170
frontend-vben/scripts/vsh/src/check-circular/index.ts
Normal file
170
frontend-vben/scripts/vsh/src/check-circular/index.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import type { CAC } from 'cac';
|
||||
|
||||
import { extname } from 'node:path';
|
||||
|
||||
import { getStagedFiles } from '@vben/node-utils';
|
||||
|
||||
import { circularDepsDetect } from 'circular-dependency-scanner';
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG = {
|
||||
allowedExtensions: ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx', '.vue'],
|
||||
ignoreDirs: [
|
||||
'dist',
|
||||
'.turbo',
|
||||
'output',
|
||||
'.cache',
|
||||
'scripts',
|
||||
'internal',
|
||||
'packages/effects/request/src/',
|
||||
'packages/@core/ui-kit/menu-ui/src/',
|
||||
'packages/@core/ui-kit/popup-ui/src/',
|
||||
],
|
||||
threshold: 0, // 循环依赖的阈值
|
||||
} as const;
|
||||
|
||||
// 类型定义
|
||||
type CircularDependencyResult = string[];
|
||||
|
||||
interface CheckCircularConfig {
|
||||
allowedExtensions?: string[];
|
||||
ignoreDirs?: string[];
|
||||
threshold?: number;
|
||||
}
|
||||
|
||||
interface CommandOptions {
|
||||
config?: CheckCircularConfig;
|
||||
staged: boolean;
|
||||
verbose: boolean;
|
||||
}
|
||||
|
||||
// 缓存机制
|
||||
const cache = new Map<string, CircularDependencyResult[]>();
|
||||
|
||||
/**
|
||||
* 格式化循环依赖的输出
|
||||
* @param circles - 循环依赖结果
|
||||
*/
|
||||
function formatCircles(circles: CircularDependencyResult[]): void {
|
||||
if (circles.length === 0) {
|
||||
console.log('✅ No circular dependencies found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('⚠️ Circular dependencies found:');
|
||||
circles.forEach((circle, index) => {
|
||||
console.log(`\nCircular dependency #${index + 1}:`);
|
||||
circle.forEach((file) => console.log(` → ${file}`));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查项目中的循环依赖
|
||||
* @param options - 检查选项
|
||||
* @param options.staged - 是否只检查暂存区文件
|
||||
* @param options.verbose - 是否显示详细信息
|
||||
* @param options.config - 自定义配置
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async function checkCircular({
|
||||
config = {},
|
||||
staged,
|
||||
verbose,
|
||||
}: CommandOptions): Promise<void> {
|
||||
try {
|
||||
// 合并配置
|
||||
const finalConfig = {
|
||||
...DEFAULT_CONFIG,
|
||||
...config,
|
||||
};
|
||||
|
||||
// 生成忽略模式
|
||||
const ignorePattern = `**/{${finalConfig.ignoreDirs.join(',')}}/**`;
|
||||
|
||||
// 检查缓存
|
||||
const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`;
|
||||
if (cache.has(cacheKey)) {
|
||||
const cachedResults = cache.get(cacheKey);
|
||||
if (cachedResults) {
|
||||
verbose && formatCircles(cachedResults);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测循环依赖
|
||||
const results = await circularDepsDetect({
|
||||
absolute: staged,
|
||||
cwd: process.cwd(),
|
||||
ignore: [ignorePattern],
|
||||
});
|
||||
|
||||
if (staged) {
|
||||
let files = await getStagedFiles();
|
||||
const allowedExtensions = new Set(finalConfig.allowedExtensions);
|
||||
|
||||
// 过滤文件列表
|
||||
files = files.filter((file) => allowedExtensions.has(extname(file)));
|
||||
|
||||
const circularFiles: CircularDependencyResult[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
for (const result of results) {
|
||||
const resultFiles = result.flat();
|
||||
if (resultFiles.includes(file)) {
|
||||
circularFiles.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
cache.set(cacheKey, circularFiles);
|
||||
verbose && formatCircles(circularFiles);
|
||||
} else {
|
||||
// 更新缓存
|
||||
cache.set(cacheKey, results);
|
||||
verbose && formatCircles(results);
|
||||
}
|
||||
|
||||
// 如果发现循环依赖,只输出警告信息
|
||||
if (results.length > 0) {
|
||||
console.log(
|
||||
'\n⚠️ Warning: Circular dependencies found, please check and fix',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'❌ Error checking circular dependencies:',
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义检查循环依赖的命令
|
||||
* @param cac - CAC实例
|
||||
*/
|
||||
function defineCheckCircularCommand(cac: CAC): void {
|
||||
cac
|
||||
.command('check-circular')
|
||||
.option('--staged', 'Only check staged files')
|
||||
.option('--verbose', 'Show detailed information')
|
||||
.option('--threshold <number>', 'Threshold for circular dependencies', {
|
||||
default: 0,
|
||||
})
|
||||
.option('--ignore-dirs <dirs>', 'Directories to ignore, comma separated')
|
||||
.usage('Analyze project circular dependencies')
|
||||
.action(async ({ ignoreDirs, staged, threshold, verbose }) => {
|
||||
const config: CheckCircularConfig = {
|
||||
threshold: Number(threshold),
|
||||
...(ignoreDirs && { ignoreDirs: ignoreDirs.split(',') }),
|
||||
};
|
||||
|
||||
await checkCircular({
|
||||
config,
|
||||
staged,
|
||||
verbose: verbose ?? true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export { type CheckCircularConfig, defineCheckCircularCommand };
|
||||
194
frontend-vben/scripts/vsh/src/check-dep/index.ts
Normal file
194
frontend-vben/scripts/vsh/src/check-dep/index.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import type { CAC } from 'cac';
|
||||
|
||||
import { getPackages } from '@vben/node-utils';
|
||||
|
||||
import depcheck from 'depcheck';
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG = {
|
||||
// 需要忽略的依赖匹配
|
||||
ignoreMatches: [
|
||||
'vite',
|
||||
'vitest',
|
||||
'unbuild',
|
||||
'@vben/tsconfig',
|
||||
'@vben/vite-config',
|
||||
'@vben/tailwind-config',
|
||||
'@types/*',
|
||||
'@vben-core/design',
|
||||
],
|
||||
// 需要忽略的包
|
||||
ignorePackages: [
|
||||
'@vben/backend-mock',
|
||||
'@vben/commitlint-config',
|
||||
'@vben/eslint-config',
|
||||
'@vben/node-utils',
|
||||
'@vben/prettier-config',
|
||||
'@vben/stylelint-config',
|
||||
'@vben/tailwind-config',
|
||||
'@vben/tsconfig',
|
||||
'@vben/vite-config',
|
||||
'@vben/vsh',
|
||||
],
|
||||
// 需要忽略的文件模式
|
||||
ignorePatterns: ['dist', 'node_modules', 'public'],
|
||||
};
|
||||
|
||||
interface DepcheckResult {
|
||||
dependencies: string[];
|
||||
devDependencies: string[];
|
||||
missing: Record<string, string[]>;
|
||||
}
|
||||
|
||||
interface DepcheckConfig {
|
||||
ignoreMatches?: string[];
|
||||
ignorePackages?: string[];
|
||||
ignorePatterns?: string[];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化依赖检查结果
|
||||
* @param pkgName - 包名
|
||||
* @param unused - 依赖检查结果
|
||||
*/
|
||||
function formatDepcheckResult(pkgName: string, unused: DepcheckResult): void {
|
||||
const hasIssues =
|
||||
Object.keys(unused.missing).length > 0 ||
|
||||
unused.dependencies.length > 0 ||
|
||||
unused.devDependencies.length > 0;
|
||||
|
||||
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}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行依赖检查
|
||||
* @param config - 配置选项
|
||||
*/
|
||||
async function runDepcheck(config: DepcheckConfig = {}): Promise<void> {
|
||||
try {
|
||||
const finalConfig = {
|
||||
...DEFAULT_CONFIG,
|
||||
...config,
|
||||
};
|
||||
|
||||
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');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'❌ Dependency check failed:',
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义依赖检查命令
|
||||
* @param cac - CAC实例
|
||||
*/
|
||||
function defineDepcheckCommand(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);
|
||||
});
|
||||
}
|
||||
|
||||
export { defineDepcheckCommand, type DepcheckConfig };
|
||||
78
frontend-vben/scripts/vsh/src/code-workspace/index.ts
Normal file
78
frontend-vben/scripts/vsh/src/code-workspace/index.ts
Normal 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 };
|
||||
74
frontend-vben/scripts/vsh/src/index.ts
Normal file
74
frontend-vben/scripts/vsh/src/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { colors, consola } from '@vben/node-utils';
|
||||
|
||||
import { cac } from 'cac';
|
||||
|
||||
import { version } from '../package.json';
|
||||
import { defineCheckCircularCommand } from './check-circular';
|
||||
import { defineDepcheckCommand } from './check-dep';
|
||||
import { defineCodeWorkspaceCommand } from './code-workspace';
|
||||
import { defineLintCommand } from './lint';
|
||||
import { definePubLintCommand } from './publint';
|
||||
|
||||
// 命令描述
|
||||
const COMMAND_DESCRIPTIONS = {
|
||||
'check-circular': 'Check for circular dependencies',
|
||||
'check-dep': 'Check for unused dependencies',
|
||||
'code-workspace': 'Manage VS Code workspace settings',
|
||||
lint: 'Run linting on the project',
|
||||
publint: 'Check package.json files for publishing standards',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Initialize and run the CLI
|
||||
*/
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const vsh = cac('vsh');
|
||||
|
||||
// Register commands
|
||||
defineLintCommand(vsh);
|
||||
definePubLintCommand(vsh);
|
||||
defineCodeWorkspaceCommand(vsh);
|
||||
defineCheckCircularCommand(vsh);
|
||||
defineDepcheckCommand(vsh);
|
||||
|
||||
// Handle invalid commands
|
||||
vsh.on('command:*', ([cmd]) => {
|
||||
consola.error(
|
||||
colors.red(`Invalid command: ${cmd}`),
|
||||
'\n',
|
||||
colors.yellow('Available commands:'),
|
||||
'\n',
|
||||
Object.entries(COMMAND_DESCRIPTIONS)
|
||||
.map(([cmd, desc]) => ` ${colors.cyan(cmd)} - ${desc}`)
|
||||
.join('\n'),
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Set up CLI
|
||||
vsh.usage('vsh <command> [options]');
|
||||
vsh.help();
|
||||
vsh.version(version);
|
||||
|
||||
// Parse arguments
|
||||
vsh.parse();
|
||||
} catch (error) {
|
||||
consola.error(
|
||||
colors.red('An unexpected error occurred:'),
|
||||
'\n',
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the CLI
|
||||
main().catch((error) => {
|
||||
consola.error(
|
||||
colors.red('Failed to start CLI:'),
|
||||
'\n',
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
48
frontend-vben/scripts/vsh/src/lint/index.ts
Normal file
48
frontend-vben/scripts/vsh/src/lint/index.ts
Normal 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 };
|
||||
185
frontend-vben/scripts/vsh/src/publint/index.ts
Normal file
185
frontend-vben/scripts/vsh/src/publint/index.ts
Normal 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 };
|
||||
6
frontend-vben/scripts/vsh/tsconfig.json
Normal file
6
frontend-vben/scripts/vsh/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user