UNPKG

@northegg/node-sea

Version:
122 lines (121 loc) 5.69 kB
import { exec as exec_origin } from "child_process"; import util from "util"; import { basename, dirname, extname, join, resolve } from "path"; import { copyFile, writeFile, mkdir, readFile } from "fs/promises"; import { is_directory_exists, is_file_exists, spinner_log, get_node_executable, nccPack, } from "./utils.js"; import ora from "ora"; import { rimraf } from "rimraf"; import { randomUUID } from "crypto"; // @ts-ignore import { inject } from "postject"; // promisify exec, let exec block until the process exits const exec = util.promisify(exec_origin); export default async function sea( /** 入口文件路径(包括入口文件名及扩展名) */ script_entry_path, options = {}) { const { disableExperimentalSEAWarning = true, useSnapshot = false, useCodeCache = false, useSystemNode = true, nodeVersion = "22.14.0", arch = "x64", target = process.platform.includes("win") ? "win" : process.platform, assets = undefined, transpileOnly = false, mirrorUrl, executable_name, debug = false, } = options; let { executable_path } = options; const startDir = process.cwd(); // normalize the script_entry_path and executable_path script_entry_path = resolve(process.cwd(), script_entry_path); if (executable_path) { executable_path = resolve(process.cwd(), executable_path); } else { console.warn("使用默认输出目录"); executable_path = resolve(dirname(process.argv[1]), `./dist/${executable_name ? executable_name : basename(script_entry_path, extname(script_entry_path))}${target === "win" ? ".exe" : ""}`); if (await is_directory_exists(dirname(executable_path))) { console.warn("默认输出目录 dist 已存在"); } else { await spinner_log("创建输出文件目录 dist", async () => { await mkdir(dirname(executable_path)); }); } } // check if script_entry_path exists and is a file if (!(await is_file_exists(script_entry_path))) { throw new Error(`脚本文件 ${script_entry_path} 不存在`); } // check if executable directory exists if (!(await is_directory_exists(dirname(executable_path)))) { throw new Error(`输出执行文件目录 ${dirname(executable_path)} 不存在`); } // check if executable_path exists if (await is_file_exists(executable_path)) { console.warn(`可执行文件 ${executable_path} 已存在, 将被覆盖`); } // check node version, needs to be at least 20.0.0 if (process.version < "v20.0.0") { throw new Error(`系统 Node 版本 ${process.version} 太老了, 至少需要 v20.0.0`); } const uuid = randomUUID(); // create a temporary directory for the processing work const temp_dir = resolve(dirname(executable_path), `./${uuid}`); // create the temporary directory if it does not exist if (!(await is_directory_exists(temp_dir))) { await spinner_log(`创建临时目录 ${temp_dir}`, async () => { await mkdir(temp_dir); }); } try { // 获取 node 可执行文件 const node_executable = await spinner_log(`${useSystemNode ? "使用系统安装的 node" : `下载 node-v${nodeVersion}-${target}-${arch}`}`, async () => { return await get_node_executable(temp_dir, useSystemNode, nodeVersion, target, arch, mirrorUrl); }); // 复制可执行文件作为输出可执行文件 await copyFile(node_executable, executable_path); // 将工作目录更改为temp_dir process.chdir(temp_dir); /** 调用ncc打包文件 */ const packFilePath = await nccPack(script_entry_path, { temp_dir, transpileOnly }); if (!packFilePath) return; // Create a configuration file building a blob that can be injected into the single executable application const preparation_blob_path = join(temp_dir, "sea-prep.blob"); const sea_config_path = join(temp_dir, "sea-config.json"); const sea_config = { main: packFilePath, output: preparation_blob_path, disableExperimentalSEAWarning, useSnapshot, useCodeCache, assets, }; await spinner_log(`将配置文件写入 ${sea_config_path}`, async () => { await writeFile(sea_config_path, JSON.stringify(sea_config)); }); // Generate the blob to be injected await spinner_log(`生成blob到 ${preparation_blob_path}`, async () => { await exec(`node --experimental-sea-config "${sea_config_path}"`); }); // Inject the blob into the copied binary by running postject await spinner_log(`将 blob 注入 ${basename(executable_path)}`, async () => { const blob = await readFile(preparation_blob_path); await inject(executable_path, "NODE_SEA_BLOB", blob, { machoSegmentName: "NODE_SEA", sentinelFuse: "NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2", }); }); // Remove the temporary directory if (!debug) { await spinner_log(`删除临时目录 ${temp_dir}`, async () => { process.chdir(startDir); await rimraf(temp_dir); }); } ora("All done!").succeed(); } catch (error) { if (!debug) { await spinner_log(`删除临时目录 ${temp_dir}`, async () => { process.chdir(startDir); await rimraf(temp_dir); }); } ora("打包出错!").fail(); console.log(error); } }