物业代码生成

This commit is contained in:
2025-06-18 11:03:42 +08:00
commit 1262d4c745
1881 changed files with 249599 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,43 @@
{
"name": "@vben/node-utils",
"version": "5.5.6",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/node-utils"
},
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
},
"files": [
"dist"
],
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./dist/index.mjs",
"default": "./dist/index.mjs"
}
},
"dependencies": {
"@changesets/git": "catalog:",
"@manypkg/get-packages": "catalog:",
"chalk": "catalog:",
"consola": "catalog:",
"dayjs": "catalog:",
"execa": "catalog:",
"find-up": "catalog:",
"ora": "catalog:",
"pkg-types": "catalog:",
"prettier": "catalog:",
"rimraf": "catalog:"
}
}

View File

@@ -0,0 +1,52 @@
import { createHash } from 'node:crypto';
import { describe, expect, it } from 'vitest';
import { generatorContentHash } from '../hash';
describe('generatorContentHash', () => {
it('should generate an MD5 hash for the content', () => {
const content = 'example content';
const expectedHash = createHash('md5')
.update(content, 'utf8')
.digest('hex');
const actualHash = generatorContentHash(content);
expect(actualHash).toBe(expectedHash);
});
it('should generate an MD5 hash with specified length', () => {
const content = 'example content';
const hashLength = 10;
const generatedHash = generatorContentHash(content, hashLength);
expect(generatedHash).toHaveLength(hashLength);
});
it('should correctly generate the hash with specified length', () => {
const content = 'example content';
const hashLength = 8;
const expectedHash = createHash('md5')
.update(content, 'utf8')
.digest('hex')
.slice(0, hashLength);
const generatedHash = generatorContentHash(content, hashLength);
expect(generatedHash).toBe(expectedHash);
});
it('should return full hash if hash length parameter is not provided', () => {
const content = 'example content';
const expectedHash = createHash('md5')
.update(content, 'utf8')
.digest('hex');
const actualHash = generatorContentHash(content);
expect(actualHash).toBe(expectedHash);
});
it('should handle empty content', () => {
const content = '';
const expectedHash = createHash('md5')
.update(content, 'utf8')
.digest('hex');
const actualHash = generatorContentHash(content);
expect(actualHash).toBe(expectedHash);
});
});

View File

@@ -0,0 +1,67 @@
// pathUtils.test.ts
import { describe, expect, it } from 'vitest';
import { toPosixPath } from '../path';
describe('toPosixPath', () => {
// 测试 Windows 风格路径到 POSIX 风格路径的转换
it('converts Windows-style paths to POSIX paths', () => {
const windowsPath = String.raw`C:\Users\Example\file.txt`;
const expectedPosixPath = 'C:/Users/Example/file.txt';
expect(toPosixPath(windowsPath)).toBe(expectedPosixPath);
});
// 确认 POSIX 风格路径不会被改变
it('leaves POSIX-style paths unchanged', () => {
const posixPath = '/home/user/file.txt';
expect(toPosixPath(posixPath)).toBe(posixPath);
});
// 测试带有多个分隔符的路径
it('converts paths with mixed separators', () => {
const mixedPath = String.raw`C:/Users\Example\file.txt`;
const expectedPosixPath = 'C:/Users/Example/file.txt';
expect(toPosixPath(mixedPath)).toBe(expectedPosixPath);
});
// 测试空字符串
it('handles empty strings', () => {
const emptyPath = '';
expect(toPosixPath(emptyPath)).toBe('');
});
// 测试仅包含分隔符的路径
it('handles path with only separators', () => {
const separatorsPath = '\\\\\\';
const expectedPosixPath = '///';
expect(toPosixPath(separatorsPath)).toBe(expectedPosixPath);
});
// 测试不包含任何分隔符的路径
it('handles path without separators', () => {
const noSeparatorPath = 'file.txt';
expect(toPosixPath(noSeparatorPath)).toBe('file.txt');
});
// 测试以分隔符结尾的路径
it('handles path ending with a separator', () => {
const endingSeparatorPath = 'C:\\Users\\Example\\';
const expectedPosixPath = 'C:/Users/Example/';
expect(toPosixPath(endingSeparatorPath)).toBe(expectedPosixPath);
});
// 测试以分隔符开头的路径
it('handles path starting with a separator', () => {
const startingSeparatorPath = String.raw`\Users\Example`;
const expectedPosixPath = '/Users/Example';
expect(toPosixPath(startingSeparatorPath)).toBe(expectedPosixPath);
});
// 测试包含非法字符的路径
it('handles path with invalid characters', () => {
const invalidCharsPath = String.raw`C:\Us*?ers\Ex<ample>|file.txt`;
const expectedPosixPath = 'C:/Us*?ers/Ex<ample>|file.txt';
expect(toPosixPath(invalidCharsPath)).toBe(expectedPosixPath);
});
});

View File

@@ -0,0 +1,6 @@
enum UNICODE {
FAILURE = '\u2716', // ✖
SUCCESS = '\u2714', // ✔
}
export { UNICODE };

View File

@@ -0,0 +1,12 @@
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Shanghai');
const dateUtil = dayjs;
export { dateUtil };

View File

@@ -0,0 +1,39 @@
import { promises as fs } from 'node:fs';
import { dirname } from 'node:path';
export async function outputJSON(
filePath: string,
data: any,
spaces: number = 2,
) {
try {
const dir = dirname(filePath);
await fs.mkdir(dir, { recursive: true });
const jsonData = JSON.stringify(data, null, spaces);
await fs.writeFile(filePath, jsonData, 'utf8');
} catch (error) {
console.error('Error writing JSON file:', error);
throw error;
}
}
export async function ensureFile(filePath: string) {
try {
const dir = dirname(filePath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(filePath, '', { flag: 'a' });
} catch (error) {
console.error('Error ensuring file:', error);
throw error;
}
}
export async function readJSON(filePath: string) {
try {
const data = await fs.readFile(filePath, 'utf8');
return JSON.parse(data);
} catch (error) {
console.error('Error reading JSON file:', error);
throw error;
}
}

View File

@@ -0,0 +1,34 @@
import path from 'node:path';
import { execa } from 'execa';
export * from '@changesets/git';
/**
* 获取暂存区文件
*/
async function getStagedFiles(): Promise<string[]> {
try {
const { stdout } = await execa('git', [
'-c',
'submodule.recurse=false',
'diff',
'--staged',
'--diff-filter=ACMR',
'--name-only',
'--ignore-submodules',
'-z',
]);
let changedList = stdout ? stdout.replace(/\0$/, '').split('\0') : [];
changedList = changedList.map((item) => path.resolve(process.cwd(), item));
const changedSet = new Set(changedList);
changedSet.delete('');
return [...changedSet];
} catch (error) {
console.error('Failed to get staged files:', error);
return [];
}
}
export { getStagedFiles };

View File

@@ -0,0 +1,18 @@
import { createHash } from 'node:crypto';
/**
* 生产基于内容的 hash可自定义长度
* @param content
* @param hashLSize
*/
function generatorContentHash(content: string, hashLSize?: number) {
const hash = createHash('md5').update(content, 'utf8').digest('hex');
if (hashLSize) {
return hash.slice(0, hashLSize);
}
return hash;
}
export { generatorContentHash };

View File

@@ -0,0 +1,19 @@
export * from './constants';
export * from './date';
export * from './fs';
export * from './git';
export { add as gitAdd, getStagedFiles } from './git';
export { generatorContentHash } from './hash';
export * from './monorepo';
export { toPosixPath } from './path';
export { prettierFormat } from './prettier';
export * from './spinner';
export type { Package } from '@manypkg/get-packages';
export { default as colors } from 'chalk';
export { consola } from 'consola';
export * from 'execa';
export { default as fs } from 'node:fs/promises';
export { type PackageJson, readPackageJSON } from 'pkg-types';
export { rimraf } from 'rimraf';

View File

@@ -0,0 +1,46 @@
import { dirname } from 'node:path';
import {
getPackages as getPackagesFunc,
getPackagesSync as getPackagesSyncFunc,
} from '@manypkg/get-packages';
import { findUpSync } from 'find-up';
/**
* 查找大仓的根目录
* @param cwd
*/
function findMonorepoRoot(cwd: string = process.cwd()) {
const lockFile = findUpSync('pnpm-lock.yaml', {
cwd,
type: 'file',
});
return dirname(lockFile || '');
}
/**
* 获取大仓的所有包
*/
function getPackagesSync() {
const root = findMonorepoRoot();
return getPackagesSyncFunc(root);
}
/**
* 获取大仓的所有包
*/
async function getPackages() {
const root = findMonorepoRoot();
return await getPackagesFunc(root);
}
/**
* 获取大仓指定的包
*/
async function getPackage(pkgName: string) {
const { packages } = await getPackages();
return packages.find((pkg) => pkg.packageJson.name === pkgName);
}
export { findMonorepoRoot, getPackage, getPackages, getPackagesSync };

View File

@@ -0,0 +1,11 @@
import { posix } from 'node:path';
/**
* 将给定的文件路径转换为 POSIX 风格。
* @param {string} pathname - 原始文件路径。
*/
function toPosixPath(pathname: string) {
return pathname.split(`\\`).join(posix.sep);
}
export { toPosixPath };

View File

@@ -0,0 +1,21 @@
import fs from 'node:fs/promises';
import { format, getFileInfo, resolveConfig } from 'prettier';
async function prettierFormat(filepath: string) {
const prettierOptions = await resolveConfig(filepath, {});
const fileInfo = await getFileInfo(filepath);
const input = await fs.readFile(filepath, 'utf8');
const output = await format(input, {
...prettierOptions,
parser: fileInfo.inferredParser as any,
});
if (output !== input) {
await fs.writeFile(filepath, output, 'utf8');
}
return output;
}
export { prettierFormat };

View File

@@ -0,0 +1,26 @@
import type { Ora } from 'ora';
import ora from 'ora';
interface SpinnerOptions {
failedText?: string;
successText?: string;
title: string;
}
export async function spinner<T>(
{ failedText, successText, title }: SpinnerOptions,
callback: () => Promise<T>,
): Promise<T> {
const loading: Ora = ora(title).start();
try {
const result = await callback();
loading.succeed(successText || 'Success!');
return result;
} catch (error) {
loading.fail(failedText || 'Failed!');
throw error;
} finally {
loading.stop();
}
}

View File

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