gengx
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@vben/node-utils",
|
||||
"version": "5.7.0",
|
||||
"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": "node ./scripts/build.mjs"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.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:",
|
||||
"rimraf": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const pnpmCommand =
|
||||
process.env.npm_execpath && process.env.npm_execpath.endsWith('.cjs')
|
||||
? [process.execPath, process.env.npm_execpath]
|
||||
: ['pnpm'];
|
||||
|
||||
const steps = [
|
||||
['exec', 'tsdown', '--no-dts'],
|
||||
[
|
||||
'exec',
|
||||
'tsc',
|
||||
'-p',
|
||||
'tsconfig.build.json',
|
||||
'--emitDeclarationOnly',
|
||||
'--declaration',
|
||||
'--outDir',
|
||||
'dist',
|
||||
],
|
||||
];
|
||||
|
||||
for (const args of steps) {
|
||||
const [command, ...commandArgs] = pnpmCommand;
|
||||
let cmd = command;
|
||||
if (cmd.includes(' ')) {
|
||||
cmd = `"${command}"`;
|
||||
}
|
||||
const result = spawnSync(cmd, [...commandArgs, ...args], {
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
enum UNICODE {
|
||||
FAILURE = '\u2716', // ✖
|
||||
SUCCESS = '\u2714', // ✔
|
||||
}
|
||||
|
||||
export { UNICODE };
|
||||
@@ -0,0 +1,12 @@
|
||||
import dayjs from 'dayjs';
|
||||
import timezone from 'dayjs/plugin/timezone.js';
|
||||
import utc from 'dayjs/plugin/utc.js';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
dayjs.tz.setDefault('Asia/Shanghai');
|
||||
|
||||
const dateUtil = dayjs;
|
||||
|
||||
export { dateUtil };
|
||||
@@ -0,0 +1,13 @@
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
import { execa } from 'execa';
|
||||
|
||||
async function formatFile(filepath: string) {
|
||||
await execa('oxfmt', [filepath], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
return await fs.readFile(filepath, 'utf8');
|
||||
}
|
||||
|
||||
export { formatFile };
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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',
|
||||
]);
|
||||
|
||||
const nullSeparator = '\u0000';
|
||||
const normalizedStdout = stdout.endsWith(nullSeparator)
|
||||
? stdout.slice(0, -1)
|
||||
: stdout;
|
||||
let changedList = normalizedStdout
|
||||
? normalizedStdout.split(nullSeparator)
|
||||
: [];
|
||||
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 };
|
||||
@@ -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 };
|
||||
@@ -0,0 +1,19 @@
|
||||
export * from './constants';
|
||||
export * from './date';
|
||||
export { formatFile } from './formatter';
|
||||
export * from './fs';
|
||||
export * from './git';
|
||||
export { getStagedFiles, add as gitAdd } from './git';
|
||||
export { generatorContentHash } from './hash';
|
||||
export * from './monorepo';
|
||||
export { toPosixPath } from './path';
|
||||
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';
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { Package } from '@manypkg/get-packages';
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
|
||||
import * as manypkg from '@manypkg/get-packages';
|
||||
const { getPackages: getPackagesFunc, getPackagesSync: getPackagesSyncFunc } =
|
||||
manypkg;
|
||||
|
||||
/**
|
||||
* 查找大仓的根目录
|
||||
* @param cwd
|
||||
*/
|
||||
function findMonorepoRoot(cwd: string = process.cwd()) {
|
||||
let currentDir = resolve(cwd);
|
||||
|
||||
while (true) {
|
||||
if (existsSync(join(currentDir, 'pnpm-lock.yaml'))) {
|
||||
return currentDir;
|
||||
}
|
||||
|
||||
const parentDir = dirname(currentDir);
|
||||
if (parentDir === currentDir) {
|
||||
return '';
|
||||
}
|
||||
|
||||
currentDir = parentDir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取大仓的所有包
|
||||
*/
|
||||
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: Package) => pkg.packageJson.name === pkgName);
|
||||
}
|
||||
|
||||
export { findMonorepoRoot, getPackage, getPackages, getPackagesSync };
|
||||
@@ -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 };
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"noEmit": false
|
||||
},
|
||||
"exclude": ["node_modules", "src/__tests__"]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: false,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
});
|
||||
Reference in New Issue
Block a user