物业代码生成
This commit is contained in:
7
internal/node-utils/build.config.ts
Normal file
7
internal/node-utils/build.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
43
internal/node-utils/package.json
Normal file
43
internal/node-utils/package.json
Normal 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:"
|
||||
}
|
||||
}
|
52
internal/node-utils/src/__tests__/hash.test.ts
Normal file
52
internal/node-utils/src/__tests__/hash.test.ts
Normal 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);
|
||||
});
|
||||
});
|
67
internal/node-utils/src/__tests__/path.test.ts
Normal file
67
internal/node-utils/src/__tests__/path.test.ts
Normal 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);
|
||||
});
|
||||
});
|
6
internal/node-utils/src/constants.ts
Normal file
6
internal/node-utils/src/constants.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
enum UNICODE {
|
||||
FAILURE = '\u2716', // ✖
|
||||
SUCCESS = '\u2714', // ✔
|
||||
}
|
||||
|
||||
export { UNICODE };
|
12
internal/node-utils/src/date.ts
Normal file
12
internal/node-utils/src/date.ts
Normal 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 };
|
39
internal/node-utils/src/fs.ts
Normal file
39
internal/node-utils/src/fs.ts
Normal 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;
|
||||
}
|
||||
}
|
34
internal/node-utils/src/git.ts
Normal file
34
internal/node-utils/src/git.ts
Normal 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 };
|
18
internal/node-utils/src/hash.ts
Normal file
18
internal/node-utils/src/hash.ts
Normal 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 };
|
19
internal/node-utils/src/index.ts
Normal file
19
internal/node-utils/src/index.ts
Normal 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';
|
46
internal/node-utils/src/monorepo.ts
Normal file
46
internal/node-utils/src/monorepo.ts
Normal 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 };
|
11
internal/node-utils/src/path.ts
Normal file
11
internal/node-utils/src/path.ts
Normal 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 };
|
21
internal/node-utils/src/prettier.ts
Normal file
21
internal/node-utils/src/prettier.ts
Normal 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 };
|
26
internal/node-utils/src/spinner.ts
Normal file
26
internal/node-utils/src/spinner.ts
Normal 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();
|
||||
}
|
||||
}
|
6
internal/node-utils/tsconfig.json
Normal file
6
internal/node-utils/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