@northegg/node-sea
Version:
154 lines (142 loc) • 4.66 kB
text/typescript
import { readFile, stat } from "fs/promises";
import ora from "ora";
import { homedir } from "os";
import { mkdir, writeFile, chmod } from "fs/promises";
import { join } from "path";
import debug from "debug";
// @ts-ignore
import ncc from "@vercel/ncc";
import { copyFileSync, existsSync, rmSync, writeFileSync } from "fs";
import AdmZip from "adm-zip";
import { extract } from "tar";
/** Check if file exists */
export async function is_file_exists(path: string) {
try {
return (await stat(path)).isFile();
} catch (e) {
return false;
}
}
/** Check if directory exists */
export async function is_directory_exists(path: string) {
try {
return (await stat(path)).isDirectory();
} catch (e) {
return false;
}
}
/** Show spinner while running async_callback */
export async function spinner_log(message: string, callback: () => Promise<any>) {
const spinner = ora(message).start();
try {
const result = await callback();
spinner.succeed();
return result;
} catch (e) {
spinner.fail();
throw e;
}
}
type Options = {
/** 临时文件存放目录 */
temp_dir: string;
/** ts文件仅转译,不进行检查。默认为 `false` */
transpileOnly?: boolean;
};
/** 打包ts/js到单文件 */
export async function nccPack(
/** 入口文件路径(包括入口文件名及扩展名) */
script_entry_path: string,
options: Options
) {
const { temp_dir, transpileOnly = false } = options;
// 为ncc提供配置支持
try {
const outputFilePath = join(temp_dir, "index.js");
const { code } = await ncc(script_entry_path, {
cache: false,
minify: true,
target: "es2024",
quiet: true,
esm: false,
transpileOnly,
});
await spinner_log(`执行 ncc 打包,输出 ncc 打包文件到 ${outputFilePath}`, async () => {
const fixedCode = await handleImportMeta(code);
await writeFile(outputFilePath, fixedCode);
});
return outputFilePath;
} catch (error) {
throw error;
}
}
/**获取指定平台 x64 node
* @param useSystemNode node 版本号 如 `23.9.0`
* @param nodeVersion node 版本号 如 `23.9.0`
* @param target 目标平台
* @param temp_dir node存放的目录
* @param arch 架构
* @param mirrorUrl 镜像下载地址 如:https://registry.npmmirror.com/-/binary/node/
*/
export async function get_node_executable(
/** 临时文件存放目录 */
temp_dir: string,
/** 是否使用本地的node,默认为 `true` */
useSystemNode: boolean = true,
/** 要下载的 node 版本,默认为 `22.14.0` */
nodeVersion: string = "22.14.0",
/** 目标平台,默认为当前平台 */
target: "win" | "darwin" | "linux" = process.platform as "win" | "darwin" | "linux",
/** node 架构,默认为 `x64` */
arch: "x64" | "arm64" = "x64",
/** node 镜像下载地址 如:https://registry.npmmirror.com/-/binary/node/ */
mirrorUrl?: string
) {
if (useSystemNode) return process.execPath;
const archiveBase = `node-v${nodeVersion}-${target}-${arch}`;
const isWindows = archiveBase.includes("-win");
const archiveFile = archiveBase + "." + (isWindows ? "zip" : "tar.gz");
const archivePath = join(temp_dir, archiveFile);
const execName = join(temp_dir, archiveBase);
if (existsSync(execName)) {
await spinner_log(`发现存在: ${archiveBase}`, async () => {});
return execName;
}
if (existsSync(archivePath)) {
await spinner_log(`找到现有文件: ${archiveFile}`, async () => {});
} else {
try {
// https://registry.npmmirror.com/-/binary/node/
const url = `${mirrorUrl || "https://nodejs.org/dist/"}v${nodeVersion}/${archiveFile}`;
const response = await fetch(url);
writeFileSync(archivePath, new Uint8Array(await response.arrayBuffer()));
} catch {
throw ["下载失败:", archiveBase];
}
}
if (isWindows) {
const zip = new AdmZip(archivePath);
const data = zip.readFile(archiveBase + "/node.exe");
if (!data) {
throw ["缺少 node 可执行文件:", archiveBase];
}
writeFileSync(execName, data);
return execName;
} else {
await extract({
file: archivePath,
gzip: true,
cwd: join(temp_dir),
});
const extracted = join(temp_dir, archiveBase);
return join(extracted, "bin/node");
}
}
/**对使用ESM脚本的 `import.meta.dirname` 和 `import.meta.filename` 进行处理
* @param code 需要打补丁的代码
*/
export async function handleImportMeta(code: string) {
code = code.replaceAll("import.meta.dirname", "__dirname");
code = code.replaceAll("import.meta.filename", "__filename");
return code;
}