Files
nodeagent/scripts/lib/bundle-agent.mjs
T
jkooe a0be43d76c refactor(pack): 被控端 esbuild 配置抽为单一实现
`agent.mjs` 现在有两个消费方:
  1. scripts/pack.mjs            → Windows 便携包 / 自更新载荷(Release 资产)
  2. scripts/prepare-console.mjs → 桌面控制台的随包资源(控制台要能托起本机被控端)

若各写一份 esbuild 配置就是「两套真相」——改了一处漏另一处时构建过、单测过,
只在真机产物里暴露(本项目已多次吃过这类亏)。故配置只此一份。

bundle-agent.mjs 导出 repoRoot / repoVersion / gitShortCommit / bundleAgent,
沿用原有全部约束:JS API(pnpm 隔离布局下 .bin/esbuild 在 Windows 不存在)、
不注入构建时间(否则每次打包字节不同,指纹比对失真)、
define 用 JSON.stringify 得纯双引号。

本次对 pack.mjs 的输出无语义变化(同一份 options 原样迁移)。
2026-10-11 11:41:38 +08:00

85 lines
3.4 KiB
JavaScript
Raw 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
/**
* 被控端单文件打包(esbuild)—— **单一实现**。
*
* 为什么抽出来:`agent.mjs` 现在有**两个消费方** ——
* 1. `scripts/pack.mjs` → Windows 便携包 / 自更新载荷(Release 资产)
* 2. `scripts/prepare-console.mjs` → 桌面控制台的随包资源(控制台要能托起本机被控端)
*
* 若各写一份 esbuild 配置,迟早漂移(本项目已多次吃过「两份真相」的亏:改了一处漏另一处,
* 构建过、单测过,只在真机暴露)。故配置只此一份。
*
* ## 几个「不这么写就会错」的细节
* - **用 esbuild 的 JS API,不 spawn `node_modules/.bin/esbuild`** —— pnpm 隔离布局下
* 那个 shim 在 Windows 上不存在(真机 CI 踩过 `ENOENT`);JS API 只依赖 `require('esbuild')`。
* - **不注入构建时间** —— 注入后同一份代码每次打包字节都不同,「指纹是否最新」永远失真。
* - `define` 的值按 **JS 表达式**解析,故用 `JSON.stringify` 得到纯双引号;写成单引号包裹
* 会让引号成为值的一部分(真机踩过:版本号显示成 `v"1.4.0"`)。
*/
import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
/** 仓库根目录(本文件在 scripts/lib/ 下)。 */
export const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
/** 被控端与协议包的版本源:**根** `package.json`(非 apps/agent 的)。 */
export function repoVersion(root = repoRoot) {
return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
}
/** 当前 git 短 sha(取不到时返回 `unknown`,不抛)。 */
export function gitShortCommit(root = repoRoot) {
try {
return execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: root }).toString().trim();
} catch {
return 'unknown';
}
}
/**
* 把 `apps/agent/dist/index.js` 打包成自包含单文件。
*
* @param {object} opts
* @param {string} opts.outfile 产物路径
* @param {string} [opts.root] 仓库根
* @param {string} [opts.logLevel]
* @returns {Promise<{ outfile: string, version: string, commit: string, bytes: number }>}
*/
export async function bundleAgent({ outfile, root = repoRoot, logLevel = 'info' }) {
const entry = join(root, 'apps', 'agent', 'dist', 'index.js');
if (!existsSync(entry)) {
throw new Error(
`未找到被控端构建产物:${entry}\n` +
' 先构建:pnpm --filter nodeagent-agent build(或 pnpm -r build)',
);
}
const version = repoVersion(root);
const commit = gitShortCommit(root);
// 动态 import:只有真正需要打包时才解析 esbuild(prepare-console 的其它分支不受影响)
const esbuild = await import('esbuild');
await esbuild.build({
entryPoints: [entry],
bundle: true,
platform: 'node',
target: 'node22',
format: 'esm',
banner: {
js: 'import { createRequire } from "node:module"; const require = createRequire(import.meta.url);',
},
define: {
__AGENT_VERSION__: JSON.stringify(version),
__BUILD_COMMIT__: JSON.stringify(commit),
},
outfile,
logLevel,
});
const { statSync } = await import('node:fs');
return { outfile, version, commit, bytes: statSync(outfile).size };
}