Jacob
2026-10-12 61274af2f456188fa611dd871bc2bcc1a3a85db0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
 * HBuilderX CLI 命令行发布:按环境发布微信小程序
 *
 * 用法:
 *   node scripts/publish.js sit                 # 编译 + 上传到 SIT 小程序(微信 CI,无需打开微信开发者工具)
 *   node scripts/publish.js prod                # 编译 + 上传到 PROD 小程序
 *   node scripts/publish.js sit --local         # 只本地编译,不上传
 *   node scripts/publish.js sit --dry-run       # 只打印将要执行的命令
 *   node scripts/publish.js sit --version 2.0.0 --robot 3   # 额外参数原样透传给 cli
 *
 * 环境相关的值统一取自 config/env/<env>.js(唯一数据源):
 *   - 接口/埋点等运行时参数:包内自动按小程序 appid 识别,无需在此处理
 *   - APPID:作为 --appid 传给微信 CI 上传
 *
 * 密钥文件:keys/private.<env>.key
 *   获取方式:微信公众平台 → 开发管理 → 开发设置 → 小程序代码上传 → 生成上传密钥
 *   注意:密钥属于机密,已通过 .gitignore 排除,切勿提交
 */
const { spawnSync } = require('child_process');
const fs = require('fs');
const path = require('path');
 
const ROOT = path.resolve(__dirname, '..');
const ENV_DIR = path.join(ROOT, 'config', 'env');
 
// HBuilderX cli.exe 路径:优先读环境变量,便于换机器 / 换安装位置
const HX_CLI = process.env.HBUILDERX_CLI || 'C:\\Users\\Administrator\\Desktop\\HBuilderX\\cli.exe';
 
const argv = process.argv.slice(2);
const envName = (argv.find(function(a) { return a.charAt(0) !== '-'; }) || '').trim();
const localOnly = argv.indexOf('--local') > -1;
const dryRun = argv.indexOf('--dry-run') > -1;
// 默认不改 manifest.json:上传用 --appid 指定目标小程序,编译产物里的 appid 不参与校验。
// 若遇到上传失败/appid 不一致,加 --sync-manifest 先同步 manifest 再发布。
const syncManifest = argv.indexOf('--sync-manifest') > -1;
const OWN_ARGS = ['--local', '--dry-run', '--sync-manifest'];
// 透传给 cli 的额外参数(保留 "参数 值" 的成对关系,如 --robot 2)
const passthrough = [];
let envNameSkipped = false;
argv.forEach(function(a) {
   if (OWN_ARGS.indexOf(a) > -1) return;
   if (!envNameSkipped && a === envName) {
       envNameSkipped = true;
       return;
   }
   passthrough.push(a);
});
 
function fail(msg) {
   console.error('[publish] ' + msg);
   process.exit(1);
}
 
function gitShortHash() {
   const res = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: ROOT, encoding: 'utf8' });
   return res.status === 0 ? String(res.stdout).trim() : '';
}
 
function readManifestVersion() {
   try {
       const txt = fs.readFileSync(path.join(ROOT, 'manifest.json'), 'utf8');
       const m = txt.match(/"versionName"\s*:\s*"([^"]*)"/);
       return m ? m[1] : '1.0.0';
   } catch (e) {
       return '1.0.0';
   }
}
 
function main() {
   const available = fs.existsSync(ENV_DIR)
       ? fs.readdirSync(ENV_DIR).filter(f => f.endsWith('.js') && f !== 'index.js').map(f => f.replace(/\.js$/, ''))
       : [];
 
   if (!envName) fail('请指定环境名,可选:' + available.join(' / ') + '\n  例:node scripts/publish.js sit');
   if (available.indexOf(envName) < 0) fail('未找到 config/env/' + envName + '.js,可选:' + available.join(' / '));
 
   const env = require(path.join(ENV_DIR, envName + '.js'));
 
   // 1) 校验 cli.exe
   if (!fs.existsSync(HX_CLI)) {
       fail('未找到 HBuilderX cli.exe:' + HX_CLI +
           '\n  请确认 HBuilderX 安装路径,或设置环境变量 HBUILDERX_CLI 指向 cli.exe');
   }
 
   // 2) 组装 cli 参数
   const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT];
 
   if (!localOnly) {
       const keyFile = path.join(ROOT, 'keys', 'private.' + envName + '.key');
       if (!fs.existsSync(keyFile)) {
           fail('未找到上传密钥文件:' + keyFile +
               '\n  获取方式:微信公众平台 → 开发管理 → 开发设置 → 小程序代码上传 → 生成上传密钥' +
               '\n  若只想本地编译不上传,请加 --local');
       }
       const stamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
       const hash = gitShortHash();
       cliArgs.push('--upload', 'true');
       cliArgs.push('--appid', env.APPID);
       cliArgs.push('--privatekey', keyFile);
       cliArgs.push('--version', readManifestVersion());
       cliArgs.push('--description', '[' + env.ENV_NAME + '] ' + stamp + (hash ? ' ' + hash : ''));
   }
 
   console.log('[publish] 环境      : ' + envName + '(' + env.ENV_NAME + ')');
   console.log('[publish] 小程序appid: ' + env.APPID);
   console.log('[publish] 接口地址   : ' + env.HTTP_REQUEST_URL);
   console.log('[publish] 产物目录   : ' + path.join('unpackage', 'dist', 'build', 'mp-weixin'));
   console.log('[publish] 模式       : ' + (localOnly ? '仅本地编译' : '编译 + 上传到微信平台'));
   console.log('[publish] DCloud标识 : ' + env.UNI_APPID + '(manifest.json 里的值,仅影响非小程序端与云服务识别)');
   console.log('[publish] 命令       : "' + HX_CLI + '" ' + cliArgs.concat(passthrough).join(' '));
 
   if (dryRun) {
       console.log('[publish] --dry-run,未执行');
       return;
   }
 
   // 3) 可选:先把 appid 同步进 manifest.json(走 set-env 的同一套逻辑)
   if (syncManifest) {
       console.log('[publish] --sync-manifest:先同步 manifest.json 的 appid');
       const sync = spawnSync(process.execPath, [path.join(__dirname, 'set-env.js'), envName], { cwd: ROOT, stdio: 'inherit' });
       if (sync.status !== 0) fail('同步 manifest.json 失败');
   }
 
   // 4) 执行(HBuilderX 会在其控制台输出编译与上传日志)
   const res = spawnSync(HX_CLI, cliArgs.concat(passthrough), { cwd: ROOT, stdio: 'inherit' });
 
   if (res.error) fail('调用 cli.exe 失败:' + res.error.message);
   if (res.status !== 0) fail('cli.exe 退出码 ' + res.status + ',请查看 HBuilderX 控制台输出');
 
   console.log('[publish] 完成。若上传成功,可在微信公众平台 → 版本管理 中看到对应版本');
}
 
main();