| .. | .. |
|---|
| 1 | 1 | /** |
|---|
| 2 | | - * HBuilderX CLI 命令行发布:按环境发布微信小程序 |
|---|
| 2 | + * 命令行发行(不上传)+ 自动用微信开发者工具打开产物 |
|---|
| 3 | 3 | * |
|---|
| 4 | 4 | * 用法: |
|---|
| 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 # 只打印将要执行的命令 |
|---|
| 10 | 10 | * |
|---|
| 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 生成)。 |
|---|
| 14 | 14 | * |
|---|
| 15 | | - * 密钥文件:keys/private.<env>.key |
|---|
| 16 | | - * 获取方式:微信公众平台 → 开发管理 → 开发设置 → 小程序代码上传 → 生成上传密钥 |
|---|
| 17 | | - * 注意:密钥属于机密,已通过 .gitignore 排除,切勿提交 |
|---|
| 15 | + * 默认策略:不动 manifest.json(仓库保持基线,git 无噪音),只回写未跟踪的产物文件。 |
|---|
| 16 | + * |
|---|
| 17 | + * 外部工具(HBuilderX cli.exe、微信开发者工具 cli.bat)不写死绝对路径,按顺序自动查找: |
|---|
| 18 | + * 1) 环境变量 HBUILDERX_CLI / WX_DEVTOOLS_CLI |
|---|
| 19 | + * 2) 系统 PATH |
|---|
| 20 | + * 3) 常见安装目录(Program Files、用户目录、各磁盘常见软件目录) |
|---|
| 21 | + * 4) 磁盘根目录浅层扫描(≤2 层) |
|---|
| 22 | + * 找到后会把其所在目录加入本次进程 PATH(便于按名字直接调用),找不到则报错并给出解决办法。 |
|---|
| 18 | 23 | */ |
|---|
| 19 | 24 | const { spawnSync } = require('child_process'); |
|---|
| 20 | 25 | const fs = require('fs'); |
|---|
| .. | .. |
|---|
| 22 | 27 | |
|---|
| 23 | 28 | const ROOT = path.resolve(__dirname, '..'); |
|---|
| 24 | 29 | const ENV_DIR = path.join(ROOT, 'config', 'env'); |
|---|
| 30 | +const DIST_DIR = path.join(ROOT, 'unpackage', 'dist', 'build', 'mp-weixin'); |
|---|
| 31 | +const DIST_CONFIG = path.join(DIST_DIR, 'project.config.json'); |
|---|
| 25 | 32 | |
|---|
| 26 | | -// HBuilderX cli.exe 路径:优先读环境变量,便于换机器 / 换安装位置 |
|---|
| 27 | | -const HX_CLI = process.env.HBUILDERX_CLI || 'C:\\Users\\Administrator\\Desktop\\HBuilderX\\cli.exe'; |
|---|
| 33 | +/* ------------------------- 外部工具定位 ------------------------- */ |
|---|
| 34 | + |
|---|
| 35 | +// 扫描时要跳过的系统/无关目录 |
|---|
| 36 | +const SKIP_DIRS = /^(\$recycle\.bin|system volume information|windows|winsxs|node_modules|\.git|\.cache|programdata)$/i; |
|---|
| 37 | + |
|---|
| 38 | +const HX_TOOL = { |
|---|
| 39 | + envKey: 'HBUILDERX_CLI', |
|---|
| 40 | + label: 'HBuilderX 命令行 cli.exe', |
|---|
| 41 | + names: ['cli.exe'], |
|---|
| 42 | + subDirs: ['HBuilderX', 'HBuilderX\\HBuilderX'], |
|---|
| 43 | + dirPattern: /^hbuilderx/i, |
|---|
| 44 | + ok: function(file) { return /hbuilderx/i.test(file); }, |
|---|
| 45 | + hint: 'HBuilderX 安装/解压目录下的 cli.exe(如 X:\\HBuilderX\\cli.exe)', |
|---|
| 46 | + extra: '' |
|---|
| 47 | +}; |
|---|
| 48 | + |
|---|
| 49 | +const WX_TOOL = { |
|---|
| 50 | + envKey: 'WX_DEVTOOLS_CLI', |
|---|
| 51 | + label: '微信开发者工具命令行 cli.bat', |
|---|
| 52 | + names: ['cli.bat'], |
|---|
| 53 | + subDirs: ['微信web开发者工具', '微信开发者工具', 'Tencent\\微信web开发者工具', 'Tencent\\微信开发者工具'], |
|---|
| 54 | + dirPattern: /微信(web)?开发者工具/, |
|---|
| 55 | + hint: '微信开发者工具安装目录下的 cli.bat(如 X:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat)', |
|---|
| 56 | + extra: ' 3) 或加 --no-open,只编译不自动打开产物\n' |
|---|
| 57 | +}; |
|---|
| 58 | + |
|---|
| 59 | +function fail(msg) { |
|---|
| 60 | + console.error('[release] ' + msg); |
|---|
| 61 | + process.exit(1); |
|---|
| 62 | +} |
|---|
| 63 | + |
|---|
| 64 | +function uniq(list) { |
|---|
| 65 | + return list.filter(function(v, i) { return v && list.indexOf(v) === i; }); |
|---|
| 66 | +} |
|---|
| 67 | + |
|---|
| 68 | +function isFile(p) { |
|---|
| 69 | + try { return fs.statSync(p).isFile(); } catch (e) { return false; } |
|---|
| 70 | +} |
|---|
| 71 | + |
|---|
| 72 | +function isDir(p) { |
|---|
| 73 | + try { return fs.statSync(p).isDirectory(); } catch (e) { return false; } |
|---|
| 74 | +} |
|---|
| 75 | + |
|---|
| 76 | +/** 本地固定磁盘(C:、D: …);拿不到时退回环境变量里的盘符(结果缓存,只探测一次) */ |
|---|
| 77 | +let DRIVES = null; |
|---|
| 78 | +function fixedDrives() { |
|---|
| 79 | + if (DRIVES) return DRIVES; |
|---|
| 80 | + const ps = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', |
|---|
| 81 | + "[System.IO.DriveInfo]::GetDrives() | Where-Object { $_.DriveType -eq 'Fixed' } | ForEach-Object { $_.Name }" |
|---|
| 82 | + ], { encoding: 'utf8', windowsHide: true }); |
|---|
| 83 | + const fromPs = (ps.stdout || '').split(/\r?\n/) |
|---|
| 84 | + .map(function(s) { return s.trim().replace(/\\$/, ''); }) |
|---|
| 85 | + .filter(function(s) { return /^[A-Za-z]:$/.test(s); }); |
|---|
| 86 | + const fromEnv = [process.env.SystemDrive, process.env.HOMEDRIVE]; |
|---|
| 87 | + DRIVES = uniq(fromPs.concat(fromEnv).map(function(d) { return (d || '').replace(/\\$/, ''); })); |
|---|
| 88 | + return DRIVES; |
|---|
| 89 | +} |
|---|
| 90 | + |
|---|
| 91 | +/** 在系统 PATH 中按文件名查找,ok() 用于排除同名无关程序 */ |
|---|
| 92 | +function findOnPath(names, ok) { |
|---|
| 93 | + const dirs = (process.env.PATH || '').split(path.delimiter); |
|---|
| 94 | + for (let i = 0; i < dirs.length; i++) { |
|---|
| 95 | + const dir = dirs[i].replace(/^"|"$/g, ''); |
|---|
| 96 | + if (!dir) continue; |
|---|
| 97 | + for (let j = 0; j < names.length; j++) { |
|---|
| 98 | + const file = path.join(dir, names[j]); |
|---|
| 99 | + if (isFile(file) && (!ok || ok(file))) return file; |
|---|
| 100 | + } |
|---|
| 101 | + } |
|---|
| 102 | + return ''; |
|---|
| 103 | +} |
|---|
| 104 | + |
|---|
| 105 | +/** 常见安装根目录:Program Files、用户目录、各磁盘根与常见软件目录 */ |
|---|
| 106 | +function baseDirs() { |
|---|
| 107 | + const bases = [ |
|---|
| 108 | + process.env.ProgramFiles, |
|---|
| 109 | + process.env['ProgramFiles(x86)'], |
|---|
| 110 | + process.env.LOCALAPPDATA, |
|---|
| 111 | + process.env.APPDATA, |
|---|
| 112 | + process.env.USERPROFILE, |
|---|
| 113 | + process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, 'Programs'), |
|---|
| 114 | + process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Desktop'), |
|---|
| 115 | + process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Downloads') |
|---|
| 116 | + ]; |
|---|
| 117 | + fixedDrives().forEach(function(d) { |
|---|
| 118 | + bases.push(d + '\\', d + '\\Program Files', d + '\\Program Files (x86)', |
|---|
| 119 | + d + '\\soft', d + '\\software', d + '\\Programs', d + '\\develop'); |
|---|
| 120 | + }); |
|---|
| 121 | + return uniq(bases).filter(isDir); |
|---|
| 122 | +} |
|---|
| 123 | + |
|---|
| 124 | +/** 磁盘浅层扫描:找名字符合特征的目录(兜底手段) */ |
|---|
| 125 | +function scanPlausibleDirs(pattern) { |
|---|
| 126 | + const found = []; |
|---|
| 127 | + fixedDrives().forEach(function(root) { |
|---|
| 128 | + let level1 = []; |
|---|
| 129 | + try { level1 = fs.readdirSync(root + '\\', { withFileTypes: true }); } catch (e) { return; } |
|---|
| 130 | + level1.forEach(function(e1) { |
|---|
| 131 | + if (!e1.isDirectory() || SKIP_DIRS.test(e1.name)) return; |
|---|
| 132 | + const p1 = path.join(root + '\\', e1.name); |
|---|
| 133 | + if (pattern.test(e1.name)) { found.push(p1); return; } |
|---|
| 134 | + let level2 = []; |
|---|
| 135 | + try { level2 = fs.readdirSync(p1, { withFileTypes: true }); } catch (e) { return; } |
|---|
| 136 | + level2.forEach(function(e2) { |
|---|
| 137 | + if (e2.isDirectory() && pattern.test(e2.name)) found.push(path.join(p1, e2.name)); |
|---|
| 138 | + }); |
|---|
| 139 | + }); |
|---|
| 140 | + }); |
|---|
| 141 | + return found; |
|---|
| 142 | +} |
|---|
| 143 | + |
|---|
| 144 | +/** 定位工具:返回 { file, source },找不到返回 null */ |
|---|
| 145 | +function resolveTool(cfg) { |
|---|
| 146 | + // 1) 环境变量显式指定 |
|---|
| 147 | + const fromEnv = (process.env[cfg.envKey] || '').trim(); |
|---|
| 148 | + if (fromEnv) { |
|---|
| 149 | + if (isFile(fromEnv)) return { file: fromEnv, source: '环境变量 ' + cfg.envKey }; |
|---|
| 150 | + fail('环境变量 ' + cfg.envKey + ' 指向的文件不存在:' + fromEnv); |
|---|
| 151 | + } |
|---|
| 152 | + |
|---|
| 153 | + // 2) 系统 PATH |
|---|
| 154 | + const onPath = findOnPath(cfg.names, cfg.ok); |
|---|
| 155 | + if (onPath) return { file: onPath, source: '系统 PATH' }; |
|---|
| 156 | + |
|---|
| 157 | + // 3) 常见安装目录 + 磁盘浅层扫描 |
|---|
| 158 | + const dirs = baseDirs().concat(scanPlausibleDirs(cfg.dirPattern)); |
|---|
| 159 | + for (let i = 0; i < dirs.length; i++) { |
|---|
| 160 | + for (let j = 0; j < cfg.subDirs.length; j++) { |
|---|
| 161 | + for (let k = 0; k < cfg.names.length; k++) { |
|---|
| 162 | + const file = path.join(dirs[i], cfg.subDirs[j], cfg.names[k]); |
|---|
| 163 | + if (isFile(file)) return { file: file, source: '常见安装目录' }; |
|---|
| 164 | + } |
|---|
| 165 | + } |
|---|
| 166 | + } |
|---|
| 167 | + return null; |
|---|
| 168 | +} |
|---|
| 169 | + |
|---|
| 170 | +/** 找不到工具时的报错提示 */ |
|---|
| 171 | +function toolFail(cfg) { |
|---|
| 172 | + fail('未找到 ' + cfg.label + '\n' + |
|---|
| 173 | + ' 已查找:环境变量 ' + cfg.envKey + ' → 系统 PATH → 常见安装目录 → 磁盘浅层扫描\n' + |
|---|
| 174 | + ' 典型位置:' + cfg.hint + '\n' + |
|---|
| 175 | + ' 解决办法(任选其一):\n' + |
|---|
| 176 | + ' 1) 把安装目录加入 PATH:set PATH=%PATH%;<安装目录>\n' + |
|---|
| 177 | + ' 2) 用环境变量指定:set ' + cfg.envKey + '=<cli 完整路径>\n' + |
|---|
| 178 | + cfg.extra); |
|---|
| 179 | +} |
|---|
| 180 | + |
|---|
| 181 | +/** 把工具所在目录加入本次进程 PATH,便于按名字直接调用 */ |
|---|
| 182 | +function putOnPath(file) { |
|---|
| 183 | + const dir = path.dirname(file); |
|---|
| 184 | + const cur = process.env.PATH || ''; |
|---|
| 185 | + if (cur.split(path.delimiter).indexOf(dir) < 0) process.env.PATH = dir + path.delimiter + cur; |
|---|
| 186 | +} |
|---|
| 187 | + |
|---|
| 188 | +/* ------------------------- 参数解析 ------------------------- */ |
|---|
| 28 | 189 | |
|---|
| 29 | 190 | const argv = process.argv.slice(2); |
|---|
| 30 | 191 | const envName = (argv.find(function(a) { return a.charAt(0) !== '-'; }) || '').trim(); |
|---|
| 31 | | -const localOnly = argv.indexOf('--local') > -1; |
|---|
| 192 | +const syncManifest = argv.indexOf('--sync') > -1; |
|---|
| 193 | +const noOpen = argv.indexOf('--no-open') > -1; |
|---|
| 32 | 194 | 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) |
|---|
| 195 | + |
|---|
| 196 | +const OWN_ARGS = ['--sync', '--no-open', '--dry-run']; |
|---|
| 197 | +// 透传给 HBuilderX cli 的额外参数(保留 "参数 值" 成对关系) |
|---|
| 38 | 198 | const passthrough = []; |
|---|
| 39 | 199 | let envNameSkipped = false; |
|---|
| 40 | 200 | argv.forEach(function(a) { |
|---|
| .. | .. |
|---|
| 46 | 206 | passthrough.push(a); |
|---|
| 47 | 207 | }); |
|---|
| 48 | 208 | |
|---|
| 49 | | -function fail(msg) { |
|---|
| 50 | | - console.error('[publish] ' + msg); |
|---|
| 51 | | - process.exit(1); |
|---|
| 52 | | -} |
|---|
| 53 | | - |
|---|
| 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() : ''; |
|---|
| 57 | | -} |
|---|
| 58 | | - |
|---|
| 59 | | -function readManifestVersion() { |
|---|
| 209 | +function readAppId(file) { |
|---|
| 60 | 210 | 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'; |
|---|
| 211 | + return JSON.parse(fs.readFileSync(file, 'utf8')).appid || ''; |
|---|
| 64 | 212 | } catch (e) { |
|---|
| 65 | | - return '1.0.0'; |
|---|
| 213 | + return ''; |
|---|
| 66 | 214 | } |
|---|
| 215 | +} |
|---|
| 216 | + |
|---|
| 217 | +/** 回写 appid(正则替换,保留原格式) */ |
|---|
| 218 | +function writeAppId(file, appid) { |
|---|
| 219 | + const prev = fs.readFileSync(file, 'utf8'); |
|---|
| 220 | + const next = prev.replace(/"appid"(\s*:\s*)"[^"]*"/, function(m, sep) { |
|---|
| 221 | + return '"appid"' + sep + '"' + appid + '"'; |
|---|
| 222 | + }); |
|---|
| 223 | + if (next !== prev) fs.writeFileSync(file, next, 'utf8'); |
|---|
| 224 | + return readAppId(file); |
|---|
| 67 | 225 | } |
|---|
| 68 | 226 | |
|---|
| 69 | 227 | function main() { |
|---|
| .. | .. |
|---|
| 76 | 234 | |
|---|
| 77 | 235 | const env = require(path.join(ENV_DIR, envName + '.js')); |
|---|
| 78 | 236 | |
|---|
| 79 | | - // 1) 校验 cli.exe |
|---|
| 80 | | - if (!fs.existsSync(HX_CLI)) { |
|---|
| 81 | | - fail('未找到 HBuilderX cli.exe:' + HX_CLI + |
|---|
| 82 | | - '\n 请确认 HBuilderX 安装路径,或设置环境变量 HBUILDERX_CLI 指向 cli.exe'); |
|---|
| 83 | | - } |
|---|
| 237 | + // 定位外部工具(不写死路径) |
|---|
| 238 | + console.log('[release] 查找 HBuilderX / 微信开发者工具…'); |
|---|
| 239 | + const hx = resolveTool(HX_TOOL); |
|---|
| 240 | + if (!hx) toolFail(HX_TOOL); |
|---|
| 241 | + const wx = noOpen ? null : resolveTool(WX_TOOL); |
|---|
| 242 | + if (!noOpen && !wx) toolFail(WX_TOOL); |
|---|
| 243 | + putOnPath(hx.file); |
|---|
| 244 | + if (wx) putOnPath(wx.file); |
|---|
| 84 | 245 | |
|---|
| 85 | | - // 2) 组装 cli 参数 |
|---|
| 86 | | - const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT]; |
|---|
| 246 | + console.log('[release] 环境 : ' + envName + '(' + env.ENV_NAME + ')'); |
|---|
| 247 | + console.log('[release] 小程序appid : ' + env.APPID); |
|---|
| 248 | + console.log('[release] 接口地址 : ' + env.HTTP_REQUEST_URL); |
|---|
| 249 | + console.log('[release] 产物目录 : ' + DIST_DIR); |
|---|
| 250 | + console.log('[release] HBuilderX : ' + hx.file + '(' + hx.source + ')'); |
|---|
| 251 | + if (wx) console.log('[release] 开发者工具 : ' + wx.file + '(' + wx.source + ')'); |
|---|
| 252 | + console.log('[release] manifest.json: ' + (syncManifest ? '同步为 ' + envName + ' 的 appid(会产生 git 改动)' : '保持不动(仓库维持基线,无 git 改动)')); |
|---|
| 87 | 253 | |
|---|
| 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(' ')); |
|---|
| 254 | + const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT].concat(passthrough); |
|---|
| 255 | + // 路径含空格/中文,交给 cmd 解析更稳 |
|---|
| 256 | + const openCmd = '"' + wx.file + '" open --project "' + DIST_DIR + '"'; |
|---|
| 111 | 257 | |
|---|
| 112 | 258 | if (dryRun) { |
|---|
| 113 | | - console.log('[publish] --dry-run,未执行'); |
|---|
| 259 | + console.log('[release] 1) 编译: "' + hx.file + '" ' + cliArgs.join(' ')); |
|---|
| 260 | + console.log('[release] 2) 产物 appid 回写为 ' + env.APPID + ' → ' + DIST_CONFIG); |
|---|
| 261 | + if (!noOpen) console.log('[release] 3) 打开: ' + openCmd); |
|---|
| 262 | + console.log('[release] --dry-run,未执行'); |
|---|
| 114 | 263 | return; |
|---|
| 115 | 264 | } |
|---|
| 116 | 265 | |
|---|
| 117 | | - // 3) 可选:先把 appid 同步进 manifest.json(走 set-env 的同一套逻辑) |
|---|
| 266 | + // 1) 可选:同步 manifest.json(会让被跟踪文件产生改动,默认不做) |
|---|
| 118 | 267 | if (syncManifest) { |
|---|
| 119 | | - console.log('[publish] --sync-manifest:先同步 manifest.json 的 appid'); |
|---|
| 268 | + console.log('[release] 同步 manifest.json 的 appid…'); |
|---|
| 120 | 269 | const sync = spawnSync(process.execPath, [path.join(__dirname, 'set-env.js'), envName], { cwd: ROOT, stdio: 'inherit' }); |
|---|
| 121 | 270 | if (sync.status !== 0) fail('同步 manifest.json 失败'); |
|---|
| 122 | 271 | } |
|---|
| 123 | 272 | |
|---|
| 124 | | - // 4) 执行(HBuilderX 会在其控制台输出编译与上传日志) |
|---|
| 125 | | - const res = spawnSync(HX_CLI, cliArgs.concat(passthrough), { cwd: ROOT, stdio: 'inherit' }); |
|---|
| 273 | + // 2) 发行编译(产物:unpackage/dist/build/mp-weixin) |
|---|
| 274 | + console.log('[release] 开始发行编译,日志见 HBuilderX 控制台…'); |
|---|
| 275 | + const build = spawnSync(hx.file, cliArgs, { cwd: ROOT, stdio: 'inherit' }); |
|---|
| 276 | + if (build.error) fail('调用 cli.exe 失败:' + build.error.message); |
|---|
| 277 | + if (build.status !== 0) fail('cli.exe 退出码 ' + build.status + ',请查看 HBuilderX 控制台输出'); |
|---|
| 126 | 278 | |
|---|
| 127 | | - if (res.error) fail('调用 cli.exe 失败:' + res.error.message); |
|---|
| 128 | | - if (res.status !== 0) fail('cli.exe 退出码 ' + res.status + ',请查看 HBuilderX 控制台输出'); |
|---|
| 279 | + // 3) 校正产物 appid(--sync 时通常已一致,这里兜底保证) |
|---|
| 280 | + if (!fs.existsSync(DIST_CONFIG)) { |
|---|
| 281 | + fail('发行产物缺少 project.config.json:' + DIST_CONFIG + '\n 请确认 HBuilderX 编译是否成功'); |
|---|
| 282 | + } |
|---|
| 283 | + const before = readAppId(DIST_CONFIG); |
|---|
| 284 | + const after = writeAppId(DIST_CONFIG, env.APPID); |
|---|
| 285 | + if (!after) fail('回写产物 appid 失败:' + DIST_CONFIG); |
|---|
| 286 | + console.log('[release] 产物 appid : ' + before + (before === after ? '(无需调整)' : ' → ' + after)); |
|---|
| 129 | 287 | |
|---|
| 130 | | - console.log('[publish] 完成。若上传成功,可在微信公众平台 → 版本管理 中看到对应版本'); |
|---|
| 288 | + // 4) 用微信开发者工具打开产物(首次需在「设置-安全设置」中开启服务端口) |
|---|
| 289 | + if (!noOpen) { |
|---|
| 290 | + console.log('[release] 打开微信开发者工具…'); |
|---|
| 291 | + const open = spawnSync(openCmd, { cwd: ROOT, stdio: 'inherit', shell: true }); |
|---|
| 292 | + if (open.error || open.status !== 0) { |
|---|
| 293 | + console.log('[release] 自动打开失败(常见原因:未开启服务端口、微信开发者工具版本较旧)'); |
|---|
| 294 | + console.log('[release] 请手动打开微信开发者工具 → 导入项目 → 选择目录:' + DIST_DIR); |
|---|
| 295 | + } |
|---|
| 296 | + } |
|---|
| 297 | + |
|---|
| 298 | + console.log('[release] 完成。微信开发者工具中的项目 appid = ' + after + ',包内会自动使用 ' + env.HTTP_REQUEST_URL); |
|---|
| 131 | 299 | } |
|---|
| 132 | 300 | |
|---|
| 133 | 301 | main(); |
|---|