feat: add pack module

This commit is contained in:
2024-11-21 02:08:35 +08:00
parent 89e4107800
commit 35cec990c6
6 changed files with 499 additions and 9 deletions

View File

@@ -8,7 +8,7 @@ import { getConfig } from '@/module/index.ts';
import inquirer from 'inquirer';
const command = new Command('deploy')
.description('deploy to server')
.description('把前端文件传到服务器')
.argument('<filePath>', 'Path to the file to be uploaded') // 定义文件路径参数
.option('-v, --version <version>', 'verbose')
.option('-k, --key <key>', 'key')

225
src/command/publish.ts Normal file
View File

@@ -0,0 +1,225 @@
import fs from 'fs/promises';
import path from 'path';
import * as tar from 'tar';
import glob from 'fast-glob';
import { program, Command } from '@/program.ts';
import { getConfig } from '@/module/index.ts';
import { fileIsExist } from '@/uitls/file.ts';
import ignore from 'ignore';
// 查找文件(忽略大小写)
async function findFileInsensitive(targetFile: string): Promise<string | null> {
const files = await fs.readdir('.');
const matchedFile = files.find((file) => file.toLowerCase() === targetFile.toLowerCase());
return matchedFile || null;
}
// 递归收集文件信息
async function collectFileInfo(filePath: string, baseDir = '.'): Promise<any[]> {
const stats = await fs.stat(filePath);
const relativePath = path.relative(baseDir, filePath);
if (stats.isFile()) {
return [{ path: relativePath, size: stats.size }];
}
if (stats.isDirectory()) {
const files = await fs.readdir(filePath);
const results = await Promise.all(files.map((file) => collectFileInfo(path.join(filePath, file), baseDir)));
return results.flat();
}
return [];
}
// 解析 .npmignore 文件
async function loadNpmIgnore(cwd: string): Promise<ignore.Ignore> {
const npmIgnorePath = path.join(cwd, '.npmignore');
const ig = ignore.default();
try {
const content = await fs.readFile(npmIgnorePath, 'utf-8');
ig.add(content);
} catch (err) {
console.warn('.npmignore not found, using default ignore rules');
// 如果没有 .npmignore 文件,使用默认规则
ig.add(['node_modules', '.git']);
}
return ig;
}
// 获取文件列表,兼容 .npmignore
async function getFiles(cwd: string, patterns: string[]): Promise<string[]> {
const ig = await loadNpmIgnore(cwd);
// 使用 fast-glob 匹配文件
const allFiles = await glob(patterns, {
cwd,
dot: true, // 包括隐藏文件
onlyFiles: false, // 包括目录
followSymbolicLinks: true,
});
// 过滤忽略的文件
const filteredFiles = allFiles.filter((file) => !ig.ignores(file));
return filteredFiles;
}
const pack = async () => {
const cwd = process.cwd();
const collection: Record<string, any> = {};
const packageJsonPath = path.join(cwd, 'package.json');
if (!(await fileIsExist(packageJsonPath))) {
console.error('package.json not found');
return;
}
let packageJson;
try {
const packageContent = await fs.readFile(packageJsonPath, 'utf-8');
packageJson = JSON.parse(packageContent);
} catch (error) {
console.error('Invalid package.json:', error);
return;
}
let outputFileName = `${packageJson.name}-${packageJson.version}.tgz`
.replace('@', '') // 替换特殊字符 @
.replace(/[\/\\:*?"<>|]/g, '-'); // 替换特殊字符
const outputFilePath = path.join(cwd, outputFileName);
// 从 package.json 的 files 字段收集文件
const filesToInclude = packageJson.files
? await glob(packageJson.files, {
cwd: cwd,
dot: true, // 包括隐藏文件
onlyFiles: false, // 包括目录
followSymbolicLinks: true, // 处理符号链接
})
: [];
// 确保 README.md 和 dist 存在(忽略大小写检测 README.md
const readmeFile = await findFileInsensitive('README.md');
if (readmeFile && !filesToInclude.includes(readmeFile)) {
filesToInclude.push(readmeFile);
}
const packageFile = await findFileInsensitive('package.json');
if (packageFile && !filesToInclude.includes(packageFile)) {
filesToInclude.push(packageFile);
}
const allFiles = (await Promise.all(filesToInclude.map((file) => collectFileInfo(file)))).flat();
// 输出文件详细信息
console.log('Tarball Contents:');
allFiles.forEach((file) => {
console.log(`${file.size}B ${file.path}`);
});
const totalSize = allFiles.reduce((sum, file) => sum + file.size, 0);
const packageSize = (totalSize / 1024).toFixed(2) + ' kB';
collection.files = allFiles;
collection.packageJson = packageJson;
collection.totalSize = totalSize;
console.log('\nTarball Details');
console.log(`name: ${packageJson.name}`);
console.log(`version: ${packageJson.version}`);
console.log(`filename: ${outputFileName}`);
console.log(`package size: ${packageSize}`);
console.log(`total files: ${allFiles.length}`);
console.log(`Created package: ${outputFileName}`);
try {
await tar.c(
{
gzip: true,
file: outputFilePath,
cwd: cwd,
},
filesToInclude,
);
} catch (error) {
console.error('Error creating tarball:', error);
}
return { collection, outputFilePath };
};
const packByIgnore = async () => {
let collection: Record<string, any> = {};
const cwd = process.cwd();
const patterns = ['**/*']; // 匹配所有文件
const packageJsonPath = path.join(cwd, 'package.json');
let packageJson;
try {
const packageContent = await fs.readFile(packageJsonPath, 'utf-8');
packageJson = JSON.parse(packageContent);
} catch (error) {
console.error('Invalid package.json:', error);
return;
}
let outputFileName = `${packageJson.name}-${packageJson.version}.tgz`
.replace('@', '') // 替换特殊字符 @
.replace(/[\/\\:*?"<>|]/g, '-'); // 替换特殊字符
const outputFilePath = path.join(cwd, outputFileName);
// 获取符合条件的文件列表
const files = await getFiles(cwd, patterns);
console.log('Files to include in the package:');
// files 获取 size 和 path
const filesInfo = await Promise.all(files.map((file) => collectFileInfo(file)));
const allFiles = filesInfo.flat();
allFiles.forEach((file) => {
console.log(`${file.size}B ${file.path}`);
});
const totalSize = allFiles.reduce((sum, file) => sum + file.size, 0);
const packageSize = (totalSize / 1024).toFixed(2) + ' kB';
collection.files = allFiles;
collection.packageJson = packageJson;
collection.totalSize = totalSize;
console.log('\nTarball Details');
console.log(`package size: ${packageSize}`);
console.log(`total files: ${allFiles.length}`);
const filesToInclude = files.map((file) => path.relative(cwd, file));
try {
await tar.c(
{
gzip: true,
file: outputFilePath,
cwd: cwd,
},
filesToInclude,
);
} catch (error) {
console.error('Error creating tarball:', error);
}
return { collection, outputFilePath };
};
const publishCommand = new Command('publish')
.description('发布应用')
.option('-k, --key <key>', '应用 key')
.option('-v, --version <version>', '应用版本')
.action(async (options) => {
const { key, version } = options;
const config = await getConfig();
const form = new FormData();
console.log('发布逻辑实现', { key, version, config });
});
const packCommand = new Command('pack')
.description('打包应用, 默认使用 package.json 中的 files 字段')
.option('-i, --ignore', '使用 .npmignore 文件模式去忽略文件进行打包, 不需要package.json中的files字段')
.action(async (opts) => {
let value: { collection: Record<string, any>; outputFilePath: string };
if (opts.ignore) {
value = await packByIgnore();
} else {
value = await pack();
}
//
});
program.addCommand(publishCommand);
program.addCommand(packCommand);

View File

@@ -9,6 +9,7 @@ import './command/config.ts';
import './command/web.ts';
import './command/router.ts';
import './command/npm.ts';
import './command/publish.ts';
// program.parse(process.argv);

0
src/module/pino.ts Normal file
View File