fix: 更新初始化和pack的 cmd

This commit is contained in:
2025-11-17 18:14:47 +08:00
parent 5df5a943ed
commit ba171fb744
5 changed files with 408 additions and 258 deletions

View File

@@ -6,7 +6,6 @@ import FormData from 'form-data';
import { getBaseURL, query, storage } from '@/module/query.ts';
import { getConfig } from '@/module/index.ts';
import inquirer from 'inquirer';
import { packLib, unpackLib } from './publish.ts';
import chalk from 'chalk';
import { installDeps } from '@/uitls/npm.ts';
import { upload } from '@/module/download/upload.ts';
@@ -293,42 +292,3 @@ const deployLoad = new Command('deploy-load')
});
app.addCommand(deployLoad);
const local = new Command('local')
.description('本地部署')
.argument('<key>', 'key')
.option('-i, --ignore', '使用 .npmignore 文件模式去忽略文件进行打包, 不需要package.json中的files字段')
.option('-u, --update', 'query查询 127.0.0.1:11015/api/router?path=local-apps&key=detect')
.action(async (key, opts) => {
console.log('local deploy');
const { outputFilePath } = await packLib(opts?.ignore);
const mainAppPath = getConfig().mainAppPath;
const appsPath = getConfig().appsPath || path.join(mainAppPath, 'apps');
if (!key) {
console.error(chalk.red('key is required'));
return;
}
const appPath = path.join(appsPath, key);
if (!fs.existsSync(appPath)) {
fs.mkdirSync(appPath, { recursive: true });
}
// 复制应用到apps目录下
if (outputFilePath) {
await unpackLib(outputFilePath, appPath);
fs.unlinkSync(outputFilePath);
installDeps({ appPath });
if (opts?.update) {
const res = await query.post(
{ path: 'local-apps', key: 'detect' },
{
url: `http://127.0.0.1:11015/api/router?path=local-apps&key=detect`,
},
);
if (res.code === 200) {
console.log('local deploy success');
} else {
console.error('local deploy failed', res.message);
}
}
}
});
app.addCommand(local);

View File

@@ -1,11 +1,9 @@
import fs, { createReadStream } from 'fs';
import fs from 'fs';
import path from 'path';
import * as tar from 'tar';
import glob from 'fast-glob';
import { program, Command } from '@/program.ts';
import { getConfig, query } from '@/module/index.ts';
import { fileIsExist } from '@/uitls/file.ts';
import ignore from 'ignore';
import { chalk } from '@/module/chalk.ts';
import * as backServices from '@/query/services/index.ts';
import inquirer from 'inquirer';
@@ -33,43 +31,11 @@ async function collectFileInfo(filePath: string, baseDir = '.'): Promise<any[]>
return [];
}
// 解析 .npmignore 文件
async function loadNpmIgnore(cwd: string): Promise<ignore.Ignore> {
const npmIgnorePath = path.join(cwd, '.npmignore');
const ig = ignore();
try {
const content = fs.readFileSync(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;
}
/**
* 复制文件到 pack-dist
* @param files 文件列表
* @param files 文件列表, 或者文件夹列表
* @param cwd 当前工作目录
* @param packDist 打包目录
* @param packDist 打包目录 pack-dist
*/
export const copyFilesToPackDist = async (files: string[], cwd: string, packDist = 'pack-dist') => {
const packDistPath = path.join(cwd, packDist);
@@ -80,10 +46,16 @@ export const copyFilesToPackDist = async (files: string[], cwd: string, packDist
}
files.forEach((file) => {
const stat = fs.statSync(path.join(cwd, file));
let outputFile = file;
if (file.startsWith('dist/')) {
outputFile = file.replace(/^dist\//, '');
} else if (file === 'dist') {
outputFile = '';
}
if (stat.isDirectory()) {
fs.cpSync(path.join(cwd, file), path.join(packDistPath, file), { recursive: true });
fs.cpSync(path.join(cwd, file), path.join(packDistPath, outputFile), { recursive: true });
} else {
fs.copyFileSync(path.join(cwd, file), path.join(packDistPath, file), fs.constants.COPYFILE_EXCL);
fs.copyFileSync(path.join(cwd, file), path.join(packDistPath, outputFile));
}
});
const packageInfo = await getPackageInfo();
@@ -117,10 +89,11 @@ ${filesString}
</body>
</html>`;
fs.writeFileSync(indexHtmlPath, indexHtmlContent);
if (!fileIsExist(indexHtmlPath)) {
fs.writeFileSync(indexHtmlPath, indexHtmlContent);
}
};
export const pack = async (opts: { isTar: boolean; packDist?: string }) => {
const isTar = opts.isTar;
export const pack = async (opts: { packDist?: string }) => {
const cwd = process.cwd();
const collection: Record<string, any> = {};
const packageJsonPath = path.join(cwd, 'package.json');
@@ -137,22 +110,17 @@ export const pack = async (opts: { isTar: boolean; packDist?: string }) => {
console.error('Invalid package.json:', error);
return;
}
let outputFileName = `${packageJson.name}-${packageJson.version}.tgz`
.replace('@', '') // 替换特殊字符 @
.replace(/[\/\\:*?"<>|]/g, '-'); // 替换特殊字符
// 当 opts.isTar 为 true 时,输出文件为 tgz 文件
const outputFilePath = path.join(cwd, outputFileName);
let files = packageJson.files;
// 从 package.json 的 files 字段收集文件
const filesToInclude = packageJson.files
? await glob(packageJson.files, {
cwd: cwd,
dot: true, // 包括隐藏文件
onlyFiles: false, // 包括目录
followSymbolicLinks: true, // 处理符号链接
})
const filesToInclude = files
? await glob(files, {
cwd: cwd,
dot: true, // 包括隐藏文件
onlyFiles: false, // 包括目录
followSymbolicLinks: true, // 处理符号链接
ignore: ['node_modules/**', ".git/**", opts.packDist ? opts.packDist + '/**' : ''],
})
: [];
// 确保 README.md 和 dist 存在(忽略大小写检测 README.md
const readmeFile = await findFileInsensitive('README.md');
@@ -166,38 +134,23 @@ export const pack = async (opts: { isTar: boolean; packDist?: string }) => {
const allFiles = (await Promise.all(filesToInclude.map((file) => collectFileInfo(file)))).flat();
// 输出文件详细信息
console.log('Tarball Contents:');
console.log('文件列表:');
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;
collection.tags = packageJson.app?.tags || packageJson.keywords || [];
console.log('\nTarball Details');
console.log('\n基本信息');
console.log(`name: ${packageJson.name}`);
console.log(`version: ${packageJson.version}`);
isTar && console.log(`filename: ${outputFileName}`);
isTar && console.log(`package size: ${packageSize}`);
console.log(`total files: ${allFiles.length}`);
opts?.isTar && console.log(`Created package: ${outputFileName}`);
try {
if (opts.isTar) {
await tar.c(
{
gzip: true,
file: outputFilePath,
cwd: cwd,
},
filesToInclude,
);
} else {
copyFilesToPackDist(filesToInclude, cwd, opts.packDist);
}
copyFilesToPackDist(filesToInclude, cwd, opts.packDist);
} catch (error) {
console.error('Error creating tarball:', error);
}
@@ -206,7 +159,7 @@ export const pack = async (opts: { isTar: boolean; packDist?: string }) => {
const readmeContent = fs.readFileSync(readme, 'utf-8');
collection.readme = readmeContent;
}
return { collection, outputFilePath, dir: cwd };
return { collection, dir: cwd };
};
export const getPackageInfo = async () => {
const cwd = process.cwd();
@@ -220,104 +173,16 @@ export const getPackageInfo = async () => {
}
};
type PackByIgnoreOpts = {
isTar: boolean;
packDist?: string;
};
export const packByIgnore = async (opts: PackByIgnoreOpts) => {
let collection: Record<string, any> = {};
const cwd = process.cwd();
const patterns = ['**/*']; // 匹配所有文件
const packageJsonPath = path.join(cwd, 'package.json');
let packageJson;
try {
const packageContent = fs.readFileSync(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;
collection.tags = packageJson.app?.tags || packageJson.keywords || [];
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 {
if (opts.isTar) {
await tar.c(
{
gzip: true,
file: outputFilePath,
cwd: cwd,
},
filesToInclude,
);
} else {
copyFilesToPackDist(filesToInclude, cwd, opts.packDist);
}
} catch (error) {
console.error('Error creating tarball:', error);
}
const readme = await findFileInsensitive('README.md');
if (readme) {
const readmeContent = fs.readFileSync(readme, 'utf-8');
collection.readme = readmeContent;
}
return { collection, outputFilePath, dir: cwd };
};
/**
* 打包应用
* @param ignore 是否忽略 .npmignore 文件
* @returns 打包结果
*/
export const packLib = async ({
ignore = false,
tar = false,
packDist = 'pack-dist',
}: {
ignore?: boolean;
tar?: boolean;
packDist?: string;
}): Promise<{ collection: Record<string, any>; outputFilePath: string; dir: string }> => {
if (ignore) {
return await packByIgnore({ isTar: tar, packDist });
}
return await pack({ isTar: tar, packDist });
};
export const unpackLib = async (filePath: string, cwd: string) => {
try {
await tar.x({
file: filePath,
cwd: cwd,
});
} catch (error) {
console.error('Error extracting tarball:', error);
}
}): Promise<{ collection: Record<string, any>; dir: string }> => {
return await pack({ packDist });
};
const publishCommand = new Command('publish')
.description('发布应用')
@@ -384,12 +249,10 @@ const deployLoadFn = async (id: string, fileKey: string, force = false, install
};
const packCommand = new Command('pack')
.description('打包应用, 默认使用 package.json 中的 files 字段')
.option('-i, --ignore', '使用 .npmignore 文件模式去忽略文件进行打包, 不需要package.json中的files字段')
.description('打包应用, 使用 package.json 中的 files 字段,如果是 dist 的路径,直接复制到 pack-dist 的根目录')
.option('-p, --publish', '打包并发布')
.option('-u, --update', '发布后显示更新命令, show command for deploy to server')
.option('-t, --tar', '打包为 tgz 文件')
.option('-d, --packDist <dist>', '打包目录')
.option('-d, --packDist <dist>', '打包到的目录')
.option('-y, --yes', '确定,直接打包', true)
.option('-c, --clean', '清理 package.json中的 devDependencies')
.action(async (opts) => {
@@ -435,8 +298,6 @@ const packCommand = new Command('pack')
appKey = answers.appKey || appKey;
}
let value = await packLib({
ignore: opts.ignore,
tar: opts.tar,
packDist,
});
if (opts?.clean) {
@@ -448,7 +309,7 @@ const packCommand = new Command('pack')
// 运行 deploy 命令
// const runDeployCommand = 'envision pack-deploy ' + value.outputFilePath + ' -k ' + appKey;
const [_app, _command] = process.argv;
let deployDist = opts.isTar ? value.outputFilePath : packDist;
let deployDist = packDist;
const deployCommand = [_app, _command, 'deploy', deployDist, '-k', appKey, '-v', version, '-u', '-d'];
if (opts.org) {
deployCommand.push('-o', opts.org);
@@ -504,10 +365,14 @@ enum AppType {
* pm2 启动
*/
Pm2SystemApp = 'pm2-system-app',
/**
* 脚本应用
*/
ScriptApp = 'script-app',
}
type ServiceItem = {
key: string;
status: 'inactive' | 'running' | 'stop' | 'error';
status: 'inactive' | 'running' | 'stop' | 'error' | 'unknown';
type: AppType;
description: string;
version: string;