Files
qiming/qiming-file-server/scripts/build.js

218 lines
7.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* qiming-file-server 发 npm 前编译脚本ESM
*
* 使用方法:
* - 发布(默认): node scripts/build.js
* - 本地调试: node scripts/build.js --all
*
* env 文件位于 src/,构建时复制 env.development、env.production 到 dist/
* 使用 --all 参数会额外复制其余 src/env.*(如 env.test
*/
import path from "path";
import fs from "fs";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, "..");
const distRoot = path.join(projectRoot, "dist");
const pkgPath = path.join(projectRoot, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
const version = pkg.version;
// 获取命令行参数
const args = process.argv.slice(2);
const debugMode = args.includes("--all");
/**
* 递归查找目录下所有 .js 文件
*/
function findJsFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const fullPath = path.join(dir, e.name);
if (e.isDirectory()) findJsFiles(fullPath, files);
else if (e.name.endsWith(".js")) files.push(fullPath);
}
return files;
}
/**
* 使用 esbuild 压缩 JS 文件
* @param {string} dir 目标目录
* @param {object} esbuild esbuild 实例
*/
async function compressJsFiles(esbuild, dir) {
if (!fs.existsSync(dir)) return;
const jsFiles = findJsFiles(dir);
let totalSaved = 0;
for (const file of jsFiles) {
const code = fs.readFileSync(file, "utf8");
const result = await esbuild.transform(code, {
minify: true,
target: "node22",
});
fs.writeFileSync(file, result.code, "utf8");
const originalSize = Buffer.byteLength(code, "utf8");
const compressedSize = Buffer.byteLength(result.code, "utf8");
const saved = originalSize - compressedSize;
totalSaved += saved;
console.log(`[build] 压缩 ${path.relative(distRoot, file)}: ${(originalSize / 1024).toFixed(1)}KB -> ${(compressedSize / 1024).toFixed(1)}KB`);
}
console.log(`[build] 压缩 ${path.relative(distRoot, dir)} 完成,节省 ${(totalSaved / 1024).toFixed(1)}KB`);
}
/**
* 复制单个文件
*/
function copyFileSync(src, dest) {
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(src, dest);
}
/**
* 复制目录内容(不包含顶层目录)
* srcDir/abc/xyz.js -> destDir/abc/xyz.js
*/
function copyDirContentsSync(srcDir, destDir) {
if (!fs.existsSync(srcDir)) return;
fs.mkdirSync(destDir, { recursive: true });
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
for (const e of entries) {
const srcPath = path.join(srcDir, e.name);
const destPath = path.join(destDir, e.name);
if (e.isDirectory()) {
copyDirContentsSync(srcPath, destPath);
} else if (e.isFile() && e.name.endsWith(".js")) {
fs.copyFileSync(srcPath, destPath);
}
}
}
async function build() {
const esbuild = (await import("esbuild")).default;
// ---------- 0. 清理旧目录 ----------
const oldSrcDir = path.join(distRoot, "src");
if (fs.existsSync(oldSrcDir)) {
fs.rmSync(oldSrcDir, { recursive: true, force: true });
console.log("[build] Old src/ directory deleted");
}
// ---------- 1. 打包 CLI ----------
await esbuild.build({
entryPoints: [path.join(projectRoot, "src", "cli.js")],
bundle: true,
platform: "node",
target: "node22",
outfile: path.join(distRoot, "cli.js"),
format: "esm",
external: ["commander", "cross-spawn", "fs-extra", "tree-kill"],
define: { __BUILD_VERSION__: JSON.stringify(version) },
minify: true,
sourcemap: false,
});
console.log("[build] dist/cli.js output");
// ---------- 2. 复制 src 下的子目录到 dist 根目录 ----------
// appConfig/* -> dist/appConfig/* 应用配置(环境变量等)
// config/* -> dist/config/* Swagger/API 文档
// routes/* -> dist/routes/*
// scheduler/* -> dist/scheduler/*
// service/* -> dist/service/*
// utils/* -> dist/utils/*
// server.js -> dist/server.js单独复制
const srcRoot = path.join(projectRoot, "src");
// 复制子目录(与 src 结构同步appConfig、routes、scheduler、service、utils
const subdirs = ["appConfig", "routes", "scheduler", "service", "utils"];
for (const subdir of subdirs) {
const srcPath = path.join(srcRoot, subdir);
const destPath = path.join(distRoot, subdir);
copyDirContentsSync(srcPath, destPath);
console.log(`[build] ${subdir}/ -> dist/${subdir}/ copied`);
}
// 单独复制 src/config/ 下的 swagger 相关文件(移除遗留的 dist/config/index.js应用配置已迁至 appConfig
const srcConfigPath = path.join(srcRoot, "config");
const destConfigPath = path.join(distRoot, "config");
const legacyConfigIndex = path.join(destConfigPath, "index.js");
if (fs.existsSync(legacyConfigIndex)) fs.rmSync(legacyConfigIndex);
copyDirContentsSync(srcConfigPath, destConfigPath);
console.log(`[build] config/ -> dist/config/ copied`);
// 单独复制 server.js 到 dist 根目录(无需修改导入路径,源码已用 ./appConfig
const serverJsSrc = path.join(srcRoot, "server.js");
const serverJsDest = path.join(distRoot, "server.js");
copyFileSync(serverJsSrc, serverJsDest);
console.log("[build] server.js -> dist/ copied");
// ---------- 3. 压缩 dist 下所有 JS 文件CLI 除外) ----------
const distJsFiles = findJsFiles(distRoot);
let totalSaved = 0;
for (const file of distJsFiles) {
// 跳过已压缩的 cli.js
if (path.basename(file) === "cli.js") continue;
const code = fs.readFileSync(file, "utf8");
const result = await esbuild.transform(code, {
minify: true,
target: "node22",
});
fs.writeFileSync(file, result.code, "utf8");
const originalSize = Buffer.byteLength(code, "utf8");
const compressedSize = Buffer.byteLength(result.code, "utf8");
const saved = originalSize - compressedSize;
totalSaved += saved;
console.log(`[build] 压缩 ${path.relative(distRoot, file)}: ${(originalSize / 1024).toFixed(1)}KB -> ${(compressedSize / 1024).toFixed(1)}KB`);
}
console.log(`[build] Compression completed, saving ${(totalSaved / 1024).toFixed(1)}KB`);
// ---------- 4. 复制 env 文件(从 src/ 到 dist/ ----------
// 发布包需包含 development + production以便 CLI --env production 能正常启动
const envToShip = ["env.development", "env.production"];
for (const envFile of envToShip) {
const src = path.join(srcRoot, envFile);
if (fs.existsSync(src)) {
fs.copyFileSync(src, path.join(distRoot, envFile));
console.log(`[build] ${envFile} -> dist/ copied`);
}
}
// 调试模式下额外复制其余 env.*(如 env.test
if (debugMode) {
const envFiles = fs.readdirSync(srcRoot).filter((f) => f.startsWith("env."));
for (const envFile of envFiles) {
if (!envToShip.includes(envFile)) {
fs.copyFileSync(path.join(srcRoot, envFile), path.join(distRoot, envFile));
console.log(`[build] ${envFile} -> dist/ copied`);
}
}
}
// ---------- 5. 输出启动说明 ----------
console.log("");
console.log("[build] =========================================");
console.log("[build] Build completed!");
console.log("[build] =========================================");
console.log("[build] Start service: cd dist && node server.js");
console.log("[build] Or use CLI: node cli.js start");
console.log("");
}
build().catch((err) => {
console.error(err);
process.exit(1);
});