feat: 更新去除之前的打包后端的模式

This commit is contained in:
2025-03-20 18:22:24 +08:00
parent f3f6d93b64
commit 3d45a83129
6 changed files with 412 additions and 304 deletions

View File

@@ -18,9 +18,10 @@ const command = new Command('deploy')
.option('-y, --yes <yes>', 'yes')
.option('-o, --org <org>', 'org')
.option('-u, --update', 'load current app. set current version in product')
.option('-s, --showBackend', 'show backend url')
.action(async (filePath, options) => {
try {
let { version, key, yes, update, org } = options;
let { version, key, yes, update, org, showBackend } = options;
if (!version || !key) {
const answers = await inquirer.prompt([
{
@@ -80,18 +81,24 @@ const command = new Command('deploy')
if (id && !update) {
console.log(chalk.green('id: '), id);
if (!org) {
console.log(chalk.green(`run to load: envision deploy-load ${id}`));
console.log(chalk.green(`更新为最新版本: envision deploy-load ${id}`));
} else {
console.log(chalk.green(`run to load: envision deploy-load ${id} -o ${org}`));
console.log(chalk.green(`更新为最新版本: envision deploy-load ${id} -o ${org}`));
}
} else if (id && update) {
deployLoadFn(id);
} else {
console.log('rest', JSON.stringify(rest, null, 2));
console.log('rest error', JSON.stringify(rest, null, 2));
}
if (id && showBackend) {
console.log('\n');
console.log(chalk.blue('服务端应用部署: '), 'envision pack-deploy', id, '-k <key>');
console.log('\n');
}
} else {
console.error('File upload failed', res?.message);
}
return res;
} catch (error) {
console.error('error', error);
}
@@ -171,13 +178,13 @@ const deployLoadFn = async (id: string, org?: string) => {
},
});
if (res.code === 200) {
console.log('deploy-load success. current version:', res.data?.version);
console.log(chalk.green('deploy-load success. current version:', res.data?.version));
// /:username/:appName
try {
const { user, key } = res.data;
const baseURL = getBaseURL();
const deployURL = new URL(`/${user}/${key}`, baseURL);
console.log('deployURL', deployURL.href);
const deployURL = new URL(`/${user}/${key}/`, baseURL);
console.log(chalk.blue('deployURL', deployURL.href));
} catch (error) {}
} else {
console.error('deploy-load failed', res.message);

View File

@@ -3,10 +3,9 @@ import path from 'path';
import * as tar from 'tar';
import glob from 'fast-glob';
import { program, Command } from '@/program.ts';
import { getBaseURL, getConfig, query } from '@/module/index.ts';
import { getConfig, query } from '@/module/index.ts';
import { fileIsExist } from '@/uitls/file.ts';
import ignore from 'ignore';
import FormData from 'form-data';
import { chalk } from '@/module/chalk.ts';
import * as backServices from '@/query/services/index.ts';
import inquirer from 'inquirer';
@@ -66,8 +65,61 @@ async function getFiles(cwd: string, patterns: string[]): Promise<string[]> {
const filteredFiles = allFiles.filter((file) => !ig.ignores(file));
return filteredFiles;
}
/**
* 复制文件到 pack-dist
* @param files 文件列表
* @param cwd 当前工作目录
* @param packDist 打包目录
*/
export const copyFilesToPackDist = async (files: string[], cwd: string, packDist = 'pack-dist') => {
const packDistPath = path.join(cwd, packDist);
if (!fileIsExist(packDistPath)) {
fs.mkdirSync(packDistPath, { recursive: true });
} else {
fs.rmSync(packDistPath, { recursive: true, force: true });
}
files.forEach((file) => {
const stat = fs.statSync(path.join(cwd, file));
if (stat.isDirectory()) {
fs.cpSync(path.join(cwd, file), path.join(packDistPath, file), { recursive: true });
} else {
fs.copyFileSync(path.join(cwd, file), path.join(packDistPath, file), fs.constants.COPYFILE_EXCL);
}
});
const packageInfo = await getPackageInfo();
// 根据所有文件生成一个index.html
const indexHtmlPath = path.join(packDistPath, 'index.html');
const collectionFiles = (await Promise.all(files.map((file) => collectFileInfo(file)))).flat();
const prettifySize = (size: number) => {
if (size < 1024) {
return `${size}B`;
}
if (size < 1024 * 1024) {
return `${(size / 1024).toFixed(2)}kB`;
}
return `${(size / 1024 / 1024).toFixed(2)}MB`;
};
const filesString = collectionFiles.map((file) => `<li><a href="${file.path}">${file.path}</a><span>${prettifySize(file.size)}</span></li>`).join('\n');
const indexHtmlContent = `
<!DOCTYPE html>
<html>
export const pack = async () => {
<head>
<title>${packageInfo.name}</title>
</head>
<body>
<h1>${packageInfo.name}</h1>
<ul>
${filesString}
</ul>
<pre>${JSON.stringify(packageInfo, null, 2)}</pre>
</body>
</html>`;
fs.writeFileSync(indexHtmlPath, indexHtmlContent);
};
export const pack = async (opts: { isTar: boolean; packDist?: string }) => {
const cwd = process.cwd();
const collection: Record<string, any> = {};
const packageJsonPath = path.join(cwd, 'package.json');
@@ -89,6 +141,7 @@ export const pack = async () => {
.replace('@', '') // 替换特殊字符 @
.replace(/[\/\\:*?"<>|]/g, '-'); // 替换特殊字符
// 当 opts.isTar 为 true 时,输出文件为 tgz 文件
const outputFilePath = path.join(cwd, outputFileName);
// 从 package.json 的 files 字段收集文件
@@ -130,16 +183,20 @@ export const pack = async () => {
console.log(`filename: ${outputFileName}`);
console.log(`package size: ${packageSize}`);
console.log(`total files: ${allFiles.length}`);
console.log(`Created package: ${outputFileName}`);
opts?.isTar && console.log(`Created package: ${outputFileName}`);
try {
await tar.c(
{
gzip: true,
file: outputFilePath,
cwd: cwd,
},
filesToInclude,
);
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);
}
@@ -150,7 +207,23 @@ export const pack = async () => {
}
return { collection, outputFilePath };
};
export const packByIgnore = async () => {
export const getPackageInfo = async () => {
const cwd = process.cwd();
const packageJsonPath = path.join(cwd, 'package.json');
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
return packageJson;
} catch (error) {
console.error('Invalid package.json:', error);
return {};
}
};
type PackByIgnoreOpts = {
isTar: boolean;
packDist?: string;
};
export const packByIgnore = async (opts: PackByIgnoreOpts) => {
let collection: Record<string, any> = {};
const cwd = process.cwd();
const patterns = ['**/*']; // 匹配所有文件
@@ -194,14 +267,18 @@ export const packByIgnore = async () => {
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,
);
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);
}
@@ -212,11 +289,16 @@ export const packByIgnore = async () => {
}
return { collection, outputFilePath };
};
export const packLib = async (ignore: boolean = false) => {
/**
* 打包应用
* @param ignore 是否忽略 .npmignore 文件
* @returns 打包结果
*/
export const packLib = async ({ ignore = false, tar = false, packDist = 'pack-dist' }: { ignore?: boolean; tar?: boolean; packDist?: string }) => {
if (ignore) {
return await packByIgnore();
return await packByIgnore({ isTar: tar, packDist });
}
return await pack();
return await pack({ isTar: tar, packDist });
};
export const unpackLib = async (filePath: string, cwd: string) => {
try {
@@ -237,60 +319,7 @@ const publishCommand = new Command('publish')
const config = await getConfig();
console.log('发布逻辑实现', { key, version, config });
});
const uploadFiles = async (filePath: string, collection: any): Promise<any> => {
const config = await getConfig();
const form = new FormData();
const filename = path.basename(filePath);
try {
console.log('upload file', filePath);
form.append('file', createReadStream(filePath));
} catch (error) {
console.error('Error reading file:', error);
return;
}
form.append('collection', JSON.stringify(collection));
console.log('upload file', filename);
return await new Promise((resolve) => {
const _baseURL = getBaseURL();
const url = new URL(_baseURL);
console.log('upload url', url.hostname, url.protocol, url.port);
form.submit(
{
path: '/api/micro-app/upload',
host: url.hostname,
protocol: url.protocol as any,
port: url.port,
method: 'POST',
headers: {
Authorization: 'Bearer ' + config.token,
...form.getHeaders(),
},
},
(err, res) => {
if (err) {
console.error('Error uploading file:', err.message);
return;
}
console.log('upload success');
// 处理服务器响应
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
try {
const res = JSON.parse(body);
console.log('upload success', res);
resolve(res);
} catch (e) {
resolve({ code: 500, message: body });
}
});
},
);
});
};
const deployLoadFn = async (id: string, fileKey: string, force = false, install = false) => {
if (!id) {
console.error(chalk.red('id is required'));
@@ -329,31 +358,76 @@ const deployLoadFn = async (id: string, fileKey: string, force = false, install
}
return res;
};
const packCommand = new Command('pack')
.description('打包应用, 默认使用 package.json 中的 files 字段')
.option('-i, --ignore', '使用 .npmignore 文件模式去忽略文件进行打包, 不需要package.json中的files字段')
.option('-p, --publish', '打包并发布')
.option('-u, --update', 'show command for deploy to server')
.option('-u, --update', '发布后显示更新命令, show command for deploy to server')
.option('-t, --tar', '打包为 tgz 文件')
.option('-d, --packDist <dist>', '打包目录')
.option('-y, --yes', '确定,直接打包', true)
.action(async (opts) => {
let value: { collection: Record<string, any>; outputFilePath: string } = await packLib(opts.ignore);
if (opts.publish && value?.collection) {
const { collection, outputFilePath } = value;
try {
const res = await uploadFiles(outputFilePath, collection);
if (res.code === 200 && opts?.update) {
const id = res.data.id;
if (opts?.update) {
console.log(chalk.blue('example: '), 'envision pack-deploy', id, '-k <key>');
console.log('envision pack-deploy', id);
}
} else {
console.error('Error uploading file:', res.message);
}
} catch (error) {
console.error('Error uploading file:', error);
}
const packDist = opts.packDist || 'pack-dist';
const packageInfo = await getPackageInfo();
if (!packageInfo) {
console.error('Invalid package.json:');
return;
}
let basename = packageInfo.basename || '';
let appKey: string | undefined;
let version = packageInfo.version || '';
if (!version) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'version',
message: 'Enter your version:',
},
]);
version = answers.version || version;
}
if (basename) {
if (basename.startsWith('/')) {
basename = basename.slice(1);
}
const basenameArr = basename.split('/');
appKey = basenameArr[1] || '';
}
if (!appKey) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'appKey',
message: 'Enter your appKey:',
},
]);
appKey = answers.appKey || appKey;
}
let value: { collection: Record<string, any>; outputFilePath: string } = await packLib({
ignore: opts.ignore,
tar: opts.tar,
packDist,
});
if (opts.publish) {
// 运行 deploy 命令
const runDeployCommand = 'envision pack-deploy ' + value.outputFilePath + ' -k ' + appKey;
const [_app, _command] = process.argv;
console.log(chalk.blue('example: '), runDeployCommand);
let deployDist = opts.isTar ? value.outputFilePath : packDist;
const deployCommand = [_app, _command, 'deploy', deployDist, '-k', appKey, '-v', version, '-u'];
if (opts.org) {
deployCommand.push('-o', opts.org);
}
if (opts.update) {
deployCommand.push('-s');
}
if (opts.yes) {
deployCommand.push('-y', 'yes');
}
program.parse(deployCommand);
}
//
});
const packDeployCommand = new Command('pack-deploy')
.argument('<id>', 'id')

View File

@@ -16,3 +16,12 @@ const ls = new Command('ls').description('List files in the current directory').
program.addCommand(ls);
export { program, Command };
/**
* 在命令行中运行程序
* @param args
*/
export const runProgram = (args: string[]) => {
const [_app, _command] = process.argv;
program.parse([_app, _command, ...args]);
};