Jacob
2026-10-12 7a4d39153891d743f5ef3edc7a3ab8d44a66dea3
scripts/publish.js
....@@ -1,20 +1,18 @@
11 /**
2
- * HBuilderX CLI 命令行发布:按环境发布微信小程序
2
+ * 命令行发行(不上传)+ 自动用微信开发者工具打开产物
33 *
44 * 用法:
5
- * node scripts/publish.js sit # 编译 + 上传到 SIT 小程序(微信 CI,无需打开微信开发者工具)
6
- * node scripts/publish.js prod # 编译 + 上传到 PROD 小程序
7
- * node scripts/publish.js sit --local # 只本地编译,不上传
8
- * node scripts/publish.js sit --dry-run # 只打印将要执行的命令
9
- * node scripts/publish.js sit --version 2.0.0 --robot 3 # 额外参数原样透传给 cli
5
+ * node scripts/publish.js sit # 发行 SIT 版本并打开微信开发者工具
6
+ * node scripts/publish.js prod # 发行 PROD 版本并打开微信开发者工具
7
+ * node scripts/publish.js sit --sync # 同时把 appid 同步进 manifest.json(默认不动 manifest)
8
+ * node scripts/publish.js sit --no-open # 只编译,不打开微信开发者工具
9
+ * node scripts/publish.js sit --dry-run # 只打印将要执行的命令
1010 *
11
- * 环境相关的值统一取自 config/env/<env>.js(唯一数据源):
12
- * - 接口/埋点等运行时参数:包内自动按小程序 appid 识别,无需在此处理
13
- * - APPID:作为 --appid 传给微信 CI 上传
11
+ * 环境值统一取自 config/env/<env>.js(唯一数据源),接口地址等运行时参数由包内自动识别,
12
+ * 本脚本只负责让“编译期 appid”与目标环境一致 —— 微信开发者工具打开的产物 appid,
13
+ * 就是 unpackage/dist/build/mp-weixin/project.config.json 里的 appid(构建时由 manifest 生成)。
1414 *
15
- * 密钥文件:keys/private.<env>.key
16
- * 获取方式:微信公众平台 → 开发管理 → 开发设置 → 小程序代码上传 → 生成上传密钥
17
- * 注意:密钥属于机密,已通过 .gitignore 排除,切勿提交
15
+ * 默认策略:不动 manifest.json(仓库保持 prod 基线,git 无噪音),只回写未跟踪的产物文件。
1816 */
1917 const { spawnSync } = require('child_process');
2018 const fs = require('fs');
....@@ -22,19 +20,21 @@
2220
2321 const ROOT = path.resolve(__dirname, '..');
2422 const ENV_DIR = path.join(ROOT, 'config', 'env');
23
+const DIST_DIR = path.join(ROOT, 'unpackage', 'dist', 'build', 'mp-weixin');
24
+const DIST_CONFIG = path.join(DIST_DIR, 'project.config.json');
2525
26
-// HBuilderX cli.exe 路径:优先读环境变量,便于换机器 / 换安装位置
26
+// 外部工具路径:可用环境变量覆盖,便于换机器
2727 const HX_CLI = process.env.HBUILDERX_CLI || 'C:\\Users\\Administrator\\Desktop\\HBuilderX\\cli.exe';
28
+const WX_CLI = process.env.WX_DEVTOOLS_CLI || 'D:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat';
2829
2930 const argv = process.argv.slice(2);
3031 const envName = (argv.find(function(a) { return a.charAt(0) !== '-'; }) || '').trim();
31
-const localOnly = argv.indexOf('--local') > -1;
32
+const syncManifest = argv.indexOf('--sync') > -1;
33
+const noOpen = argv.indexOf('--no-open') > -1;
3234 const dryRun = argv.indexOf('--dry-run') > -1;
33
-// 默认不改 manifest.json:上传用 --appid 指定目标小程序,编译产物里的 appid 不参与校验。
34
-// 若遇到上传失败/appid 不一致,加 --sync-manifest 先同步 manifest 再发布。
35
-const syncManifest = argv.indexOf('--sync-manifest') > -1;
36
-const OWN_ARGS = ['--local', '--dry-run', '--sync-manifest'];
37
-// 透传给 cli 的额外参数(保留 "参数 值" 的成对关系,如 --robot 2)
35
+
36
+const OWN_ARGS = ['--sync', '--no-open', '--dry-run'];
37
+// 透传给 HBuilderX cli 的额外参数(保留 "参数 值" 成对关系)
3838 const passthrough = [];
3939 let envNameSkipped = false;
4040 argv.forEach(function(a) {
....@@ -47,23 +47,26 @@
4747 });
4848
4949 function fail(msg) {
50
- console.error('[publish] ' + msg);
50
+ console.error('[release] ' + msg);
5151 process.exit(1);
5252 }
5353
54
-function gitShortHash() {
55
- const res = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: ROOT, encoding: 'utf8' });
56
- return res.status === 0 ? String(res.stdout).trim() : '';
54
+function readAppId(file) {
55
+ try {
56
+ return JSON.parse(fs.readFileSync(file, 'utf8')).appid || '';
57
+ } catch (e) {
58
+ return '';
59
+ }
5760 }
5861
59
-function readManifestVersion() {
60
- try {
61
- const txt = fs.readFileSync(path.join(ROOT, 'manifest.json'), 'utf8');
62
- const m = txt.match(/"versionName"\s*:\s*"([^"]*)"/);
63
- return m ? m[1] : '1.0.0';
64
- } catch (e) {
65
- return '1.0.0';
66
- }
62
+/** 回写 appid(正则替换,保留原格式) */
63
+function writeAppId(file, appid) {
64
+ const prev = fs.readFileSync(file, 'utf8');
65
+ const next = prev.replace(/"appid"(\s*:\s*)"[^"]*"/, function(m, sep) {
66
+ return '"appid"' + sep + '"' + appid + '"';
67
+ });
68
+ if (next !== prev) fs.writeFileSync(file, next, 'utf8');
69
+ return readAppId(file);
6770 }
6871
6972 function main() {
....@@ -76,58 +79,66 @@
7679
7780 const env = require(path.join(ENV_DIR, envName + '.js'));
7881
79
- // 1) 校验 cli.exe
8082 if (!fs.existsSync(HX_CLI)) {
8183 fail('未找到 HBuilderX cli.exe:' + HX_CLI +
82
- '\n 请确认 HBuilderX 安装路径,或设置环境变量 HBUILDERX_CLI 指向 cli.exe');
84
+ '\n 请确认安装路径,或设置环境变量 HBUILDERX_CLI 指向 cli.exe');
85
+ }
86
+ if (!noOpen && !fs.existsSync(WX_CLI)) {
87
+ fail('未找到微信开发者工具 cli.bat:' + WX_CLI +
88
+ '\n 请设置环境变量 WX_DEVTOOLS_CLI,或加 --no-open 自行打开产物目录');
8389 }
8490
85
- // 2) 组装 cli 参数
86
- const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT];
91
+ console.log('[release] 环境 : ' + envName + '(' + env.ENV_NAME + ')');
92
+ console.log('[release] 小程序appid : ' + env.APPID);
93
+ console.log('[release] 接口地址 : ' + env.HTTP_REQUEST_URL);
94
+ console.log('[release] 产物目录 : ' + DIST_DIR);
95
+ console.log('[release] manifest.json: ' + (syncManifest ? '同步为 ' + envName + ' 的 appid(会产生 git 改动)' : '保持不动(仓库维持基线,无 git 改动)'));
8796
88
- if (!localOnly) {
89
- const keyFile = path.join(ROOT, 'keys', 'private.' + envName + '.key');
90
- if (!fs.existsSync(keyFile)) {
91
- fail('未找到上传密钥文件:' + keyFile +
92
- '\n 获取方式:微信公众平台 → 开发管理 → 开发设置 → 小程序代码上传 → 生成上传密钥' +
93
- '\n 若只想本地编译不上传,请加 --local');
94
- }
95
- const stamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
96
- const hash = gitShortHash();
97
- cliArgs.push('--upload', 'true');
98
- cliArgs.push('--appid', env.APPID);
99
- cliArgs.push('--privatekey', keyFile);
100
- cliArgs.push('--version', readManifestVersion());
101
- cliArgs.push('--description', '[' + env.ENV_NAME + '] ' + stamp + (hash ? ' ' + hash : ''));
102
- }
103
-
104
- console.log('[publish] 环境 : ' + envName + '(' + env.ENV_NAME + ')');
105
- console.log('[publish] 小程序appid: ' + env.APPID);
106
- console.log('[publish] 接口地址 : ' + env.HTTP_REQUEST_URL);
107
- console.log('[publish] 产物目录 : ' + path.join('unpackage', 'dist', 'build', 'mp-weixin'));
108
- console.log('[publish] 模式 : ' + (localOnly ? '仅本地编译' : '编译 + 上传到微信平台'));
109
- console.log('[publish] DCloud标识 : ' + env.UNI_APPID + '(manifest.json 里的值,仅影响非小程序端与云服务识别)');
110
- console.log('[publish] 命令 : "' + HX_CLI + '" ' + cliArgs.concat(passthrough).join(' '));
97
+ const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT].concat(passthrough);
98
+ // 路径含空格/中文,交给 cmd 解析更稳
99
+ const openCmd = '"' + WX_CLI + '" open --project "' + DIST_DIR + '"';
111100
112101 if (dryRun) {
113
- console.log('[publish] --dry-run,未执行');
102
+ console.log('[release] 1) 编译: "' + HX_CLI + '" ' + cliArgs.join(' '));
103
+ console.log('[release] 2) 产物 appid 回写为 ' + env.APPID + ' → ' + DIST_CONFIG);
104
+ if (!noOpen) console.log('[release] 3) 打开: ' + openCmd);
105
+ console.log('[release] --dry-run,未执行');
114106 return;
115107 }
116108
117
- // 3) 可选:先把 appid 同步进 manifest.json(走 set-env 的同一套逻辑)
109
+ // 1) 可选:同步 manifest.json(会让被跟踪文件产生改动,默认不做)
118110 if (syncManifest) {
119
- console.log('[publish] --sync-manifest:先同步 manifest.json 的 appid');
111
+ console.log('[release] 同步 manifest.json 的 appid…');
120112 const sync = spawnSync(process.execPath, [path.join(__dirname, 'set-env.js'), envName], { cwd: ROOT, stdio: 'inherit' });
121113 if (sync.status !== 0) fail('同步 manifest.json 失败');
122114 }
123115
124
- // 4) 执行(HBuilderX 会在其控制台输出编译与上传日志)
125
- const res = spawnSync(HX_CLI, cliArgs.concat(passthrough), { cwd: ROOT, stdio: 'inherit' });
116
+ // 2) 发行编译(产物:unpackage/dist/build/mp-weixin)
117
+ console.log('[release] 开始发行编译,日志见 HBuilderX 控制台…');
118
+ const build = spawnSync(HX_CLI, cliArgs, { cwd: ROOT, stdio: 'inherit' });
119
+ if (build.error) fail('调用 cli.exe 失败:' + build.error.message);
120
+ if (build.status !== 0) fail('cli.exe 退出码 ' + build.status + ',请查看 HBuilderX 控制台输出');
126121
127
- if (res.error) fail('调用 cli.exe 失败:' + res.error.message);
128
- if (res.status !== 0) fail('cli.exe 退出码 ' + res.status + ',请查看 HBuilderX 控制台输出');
122
+ // 3) 校正产物 appid(--sync 时通常已一致,这里兜底保证)
123
+ if (!fs.existsSync(DIST_CONFIG)) {
124
+ fail('发行产物缺少 project.config.json:' + DIST_CONFIG + '\n 请确认 HBuilderX 编译是否成功');
125
+ }
126
+ const before = readAppId(DIST_CONFIG);
127
+ const after = writeAppId(DIST_CONFIG, env.APPID);
128
+ if (!after) fail('回写产物 appid 失败:' + DIST_CONFIG);
129
+ console.log('[release] 产物 appid : ' + before + (before === after ? '(无需调整)' : ' → ' + after));
129130
130
- console.log('[publish] 完成。若上传成功,可在微信公众平台 → 版本管理 中看到对应版本');
131
+ // 4) 用微信开发者工具打开产物(首次需在「设置-安全设置」中开启服务端口)
132
+ if (!noOpen) {
133
+ console.log('[release] 打开微信开发者工具…');
134
+ const open = spawnSync(openCmd, { cwd: ROOT, stdio: 'inherit', shell: true });
135
+ if (open.error || open.status !== 0) {
136
+ console.log('[release] 自动打开失败(常见原因:未开启服务端口、微信开发者工具版本较旧)');
137
+ console.log('[release] 请手动打开微信开发者工具 → 导入项目 → 选择目录:' + DIST_DIR);
138
+ }
139
+ }
140
+
141
+ console.log('[release] 完成。微信开发者工具中的项目 appid = ' + after + ',包内会自动使用 ' + env.HTTP_REQUEST_URL);
131142 }
132143
133144 main();