Jacob
2026-10-12 7a4d39153891d743f5ef3edc7a3ab8d44a66dea3
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
134
135
136
137
138
139
140
141
142
143
144
/**
 * 命令行发行(不上传)+ 自动用微信开发者工具打开产物
 *
 * 用法:
 *   node scripts/publish.js sit           # 发行 SIT 版本并打开微信开发者工具
 *   node scripts/publish.js prod          # 发行 PROD 版本并打开微信开发者工具
 *   node scripts/publish.js sit --sync    # 同时把 appid 同步进 manifest.json(默认不动 manifest)
 *   node scripts/publish.js sit --no-open # 只编译,不打开微信开发者工具
 *   node scripts/publish.js sit --dry-run # 只打印将要执行的命令
 *
 * 环境值统一取自 config/env/<env>.js(唯一数据源),接口地址等运行时参数由包内自动识别,
 * 本脚本只负责让“编译期 appid”与目标环境一致 —— 微信开发者工具打开的产物 appid,
 * 就是 unpackage/dist/build/mp-weixin/project.config.json 里的 appid(构建时由 manifest 生成)。
 *
 * 默认策略:不动 manifest.json(仓库保持 prod 基线,git 无噪音),只回写未跟踪的产物文件。
 */
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');
const DIST_DIR = path.join(ROOT, 'unpackage', 'dist', 'build', 'mp-weixin');
const DIST_CONFIG = path.join(DIST_DIR, 'project.config.json');
 
// 外部工具路径:可用环境变量覆盖,便于换机器
const HX_CLI = process.env.HBUILDERX_CLI || 'C:\\Users\\Administrator\\Desktop\\HBuilderX\\cli.exe';
const WX_CLI = process.env.WX_DEVTOOLS_CLI || 'D:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat';
 
const argv = process.argv.slice(2);
const envName = (argv.find(function(a) { return a.charAt(0) !== '-'; }) || '').trim();
const syncManifest = argv.indexOf('--sync') > -1;
const noOpen = argv.indexOf('--no-open') > -1;
const dryRun = argv.indexOf('--dry-run') > -1;
 
const OWN_ARGS = ['--sync', '--no-open', '--dry-run'];
// 透传给 HBuilderX cli 的额外参数(保留 "参数 值" 成对关系)
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('[release] ' + msg);
   process.exit(1);
}
 
function readAppId(file) {
   try {
       return JSON.parse(fs.readFileSync(file, 'utf8')).appid || '';
   } catch (e) {
       return '';
   }
}
 
/** 回写 appid(正则替换,保留原格式) */
function writeAppId(file, appid) {
   const prev = fs.readFileSync(file, 'utf8');
   const next = prev.replace(/"appid"(\s*:\s*)"[^"]*"/, function(m, sep) {
       return '"appid"' + sep + '"' + appid + '"';
   });
   if (next !== prev) fs.writeFileSync(file, next, 'utf8');
   return readAppId(file);
}
 
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'));
 
   if (!fs.existsSync(HX_CLI)) {
       fail('未找到 HBuilderX cli.exe:' + HX_CLI +
           '\n  请确认安装路径,或设置环境变量 HBUILDERX_CLI 指向 cli.exe');
   }
   if (!noOpen && !fs.existsSync(WX_CLI)) {
       fail('未找到微信开发者工具 cli.bat:' + WX_CLI +
           '\n  请设置环境变量 WX_DEVTOOLS_CLI,或加 --no-open 自行打开产物目录');
   }
 
   console.log('[release] 环境         : ' + envName + '(' + env.ENV_NAME + ')');
   console.log('[release] 小程序appid  : ' + env.APPID);
   console.log('[release] 接口地址     : ' + env.HTTP_REQUEST_URL);
   console.log('[release] 产物目录     : ' + DIST_DIR);
   console.log('[release] manifest.json: ' + (syncManifest ? '同步为 ' + envName + ' 的 appid(会产生 git 改动)' : '保持不动(仓库维持基线,无 git 改动)'));
 
   const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT].concat(passthrough);
   // 路径含空格/中文,交给 cmd 解析更稳
   const openCmd = '"' + WX_CLI + '" open --project "' + DIST_DIR + '"';
 
   if (dryRun) {
       console.log('[release] 1) 编译: "' + HX_CLI + '" ' + cliArgs.join(' '));
       console.log('[release] 2) 产物 appid 回写为 ' + env.APPID + ' → ' + DIST_CONFIG);
       if (!noOpen) console.log('[release] 3) 打开: ' + openCmd);
       console.log('[release] --dry-run,未执行');
       return;
   }
 
   // 1) 可选:同步 manifest.json(会让被跟踪文件产生改动,默认不做)
   if (syncManifest) {
       console.log('[release] 同步 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 失败');
   }
 
   // 2) 发行编译(产物:unpackage/dist/build/mp-weixin)
   console.log('[release] 开始发行编译,日志见 HBuilderX 控制台…');
   const build = spawnSync(HX_CLI, cliArgs, { cwd: ROOT, stdio: 'inherit' });
   if (build.error) fail('调用 cli.exe 失败:' + build.error.message);
   if (build.status !== 0) fail('cli.exe 退出码 ' + build.status + ',请查看 HBuilderX 控制台输出');
 
   // 3) 校正产物 appid(--sync 时通常已一致,这里兜底保证)
   if (!fs.existsSync(DIST_CONFIG)) {
       fail('发行产物缺少 project.config.json:' + DIST_CONFIG + '\n  请确认 HBuilderX 编译是否成功');
   }
   const before = readAppId(DIST_CONFIG);
   const after = writeAppId(DIST_CONFIG, env.APPID);
   if (!after) fail('回写产物 appid 失败:' + DIST_CONFIG);
   console.log('[release] 产物 appid : ' + before + (before === after ? '(无需调整)' : ' → ' + after));
 
   // 4) 用微信开发者工具打开产物(首次需在「设置-安全设置」中开启服务端口)
   if (!noOpen) {
       console.log('[release] 打开微信开发者工具…');
       const open = spawnSync(openCmd, { cwd: ROOT, stdio: 'inherit', shell: true });
       if (open.error || open.status !== 0) {
           console.log('[release] 自动打开失败(常见原因:未开启服务端口、微信开发者工具版本较旧)');
           console.log('[release] 请手动打开微信开发者工具 → 导入项目 → 选择目录:' + DIST_DIR);
       }
   }
 
   console.log('[release] 完成。微信开发者工具中的项目 appid = ' + after + ',包内会自动使用 ' + env.HTTP_REQUEST_URL);
}
 
main();