| scripts/manifest.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| scripts/publish.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| scripts/set-env.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| switch-prod.bat | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| switch-sit.bat | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
scripts/manifest.js
.. .. @@ -0,0 +1,121 @@ 1 +/**2 + * manifest.json 生成器3 + *4 + * manifest.json 是 HBuilderX 的编译输入,但内含环境相关 appid,不适合直接提交;5 + * 因此模板入库、生成物忽略:6 + *7 + * manifest.json.demo(入库,模板) ──注入环境 appid──▶ manifest.json(.gitignore)8 + *9 + * appid 数据源唯一:config/env/<env>.js 的 UNI_APPID(DCloud 标识)、APPID(微信小程序 appid)10 + * 说明:模板里 mp-weixin 的 appid 允许写成 null 或不存在,生成时会自动写入/补齐。11 + */12 +const fs = require('fs');13 +const path = require('path');14 +15 +const ROOT = path.resolve(__dirname, '..');16 +const TEMPLATE = path.join(ROOT, 'manifest.json.demo');17 +const TARGET = path.join(ROOT, 'manifest.json');18 +19 +function fail(msg) {20 + console.error('[manifest] ' + msg);21 + process.exit(1);22 +}23 +24 +/** 只替换第一处 "appid" : <字符串或 null>,保留原有的空格/缩进风格 */25 +function replaceAppId(text, value) {26 + let done = false;27 + const result = text.replace(/"appid"(\s*:\s*)(?:"[^"]*"|null)/, function(match, sep) {28 + done = true;29 + return '"appid"' + sep + '"' + value + '"';30 + });31 + return { text: result, done: done };32 +}33 +34 +/** 注入两个 appid:顶层(DCloud 标识)+ mp-weixin 块内(微信小程序 appid) */35 +function injectAppIds(text, env) {36 + // 1) 顶层 appid(第一处出现即为顶层)37 + const top = replaceAppId(text, env.UNI_APPID);38 + if (!top.done) fail('模板中未找到顶层 "appid" 字段:' + path.relative(ROOT, TEMPLATE));39 + text = top.text;40 +41 + // 2) mp-weixin 下的 appid,只在该区块内替换第一处42 + const idx = text.indexOf('"mp-weixin"');43 + if (idx < 0) fail('模板中未找到 "mp-weixin" 配置块:' + path.relative(ROOT, TEMPLATE));44 + const head = text.slice(0, idx);45 + const body = text.slice(idx);46 + const scoped = replaceAppId(body, env.APPID);47 + if (scoped.done) return head + scoped.text;48 +49 + // 3) 字段被删掉时,按同级子项缩进补一行50 + const m = body.match(/^("mp-weixin"\s*:\s*\{)(\r?\n)([ \t]*)/);51 + if (!m) fail('模板的 "mp-weixin" 块格式异常,无法写入 appid');52 + const nl = m[2];53 + const indent = m[3];54 + return head + m[1] + nl + indent + '"appid" : "' + env.APPID + '",' + nl + indent + body.slice(m[0].length);55 +}56 +57 +/** 统计「除 appid 行以外」的差异(按行内容比较,不受行号位移影响) */58 +function otherDiff(a, b) {59 + function count(text) {60 + const m = new Map();61 + text.split(/\r?\n/).forEach(function(line) {62 + const l = line.replace(/\s+$/, '');63 + if (/^\s*"appid"\s*:/.test(l.trim())) return;64 + m.set(l, (m.get(l) || 0) + 1);65 + });66 + return m;67 + }68 + const ma = count(a);69 + const mb = count(b);70 + const keys = new Set(Array.from(ma.keys()).concat(Array.from(mb.keys())));71 + let diff = 0;72 + const sample = [];73 + keys.forEach(function(k) {74 + const d = Math.abs((ma.get(k) || 0) - (mb.get(k) || 0));75 + if (!d) return;76 + diff += d;77 + if (sample.length < 3 && k.trim()) sample.push(k.trim().slice(0, 40));78 + });79 + return { count: diff, sample: sample };80 +}81 +82 +/**83 + * 按环境生成 manifest.json84 + * @param {object} env config/env/<env>.js 导出的配置85 + * @returns {{ file: string, changed: boolean }}86 + */87 +function generate(env) {88 + if (!fs.existsSync(TEMPLATE)) {89 + fail('未找到模板 ' + path.relative(ROOT, TEMPLATE) +90 + '\n 请把 manifest.json 的模板保存为 manifest.json.demo(需入库)后再生成');91 + }92 +93 + const rendered = injectAppIds(fs.readFileSync(TEMPLATE, 'utf8'), env);94 + const exists = fs.existsSync(TARGET);95 +96 + if (exists) {97 + const prev = fs.readFileSync(TARGET, 'utf8');98 + if (prev === rendered) {99 + console.log('[manifest] manifest.json 已是目标内容,无需重写');100 + return { file: TARGET, changed: false };101 + }102 + const diffs = otherDiff(prev, rendered);103 + if (diffs.count) {104 + console.log('[manifest] 注意:现有 manifest.json 有 ' + diffs.count + ' 行内容与模板不同,本次会被模板覆盖' +105 + (diffs.sample.length ? '(如 ' + diffs.sample.join(' | ') + ')' : ''));106 + console.log('[manifest] 需要长期保留的改动请写进 manifest.json.demo');107 + }108 + }109 +110 + fs.writeFileSync(TARGET, rendered, 'utf8');111 + console.log('[manifest] 已由 manifest.json.demo 生成 manifest.json' +112 + '(DCloud ' + env.UNI_APPID + ' / 小程序 ' + env.APPID + ')');113 + return { file: TARGET, changed: true };114 +}115 +116 +module.exports = {117 + TEMPLATE: TEMPLATE,118 + TARGET: TARGET,119 + generate: generate,120 + injectAppIds: injectAppIds121 +};scripts/publish.js
.. .. @@ -2,17 +2,19 @@ 2 2 * 命令行发行(不上传)+ 自动用微信开发者工具打开产物 3 3 * 4 4 * 用法: 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 # 只打印将要执行的命令5 + * node scripts/publish.js sit # 发行 SIT 版本并打开微信开发者工具6 + * node scripts/publish.js prod # 发行 PROD 版本并打开微信开发者工具7 + * node scripts/publish.js sit --no-gen # 不改 manifest.json(要求它已存在)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”与目标环境一致 —— 微信开发者工具打开的产物 appid,13 - * 就是 unpackage/dist/build/mp-weixin/project.config.json 里的 appid(构建时由 manifest 生成)。11 + * 流程:从 manifest.json.demo 生成 manifest.json(按目标环境注入 appid)→ 发行编译 →12 + * 校正产物 appid → 用微信开发者工具打开产物14 13 * 15 - * 默认策略:不动 manifest.json(仓库保持基线,git 无噪音),只回写未跟踪的产物文件。14 + * 环境值统一取自 config/env/<env>.js(唯一数据源),接口地址等运行时参数由包内按 appid 自动识别;15 + * manifest.json 是生成物(已 .gitignore),因此 git 永远没有环境噪音。16 + * 微信开发者工具打开的产物 appid = unpackage/dist/build/mp-weixin/project.config.json 的 appid17 + * (构建时由 manifest 生成,这里再兜底校正一次)。16 18 * 17 19 * 外部工具(HBuilderX cli.exe、微信开发者工具 cli.bat)不写死绝对路径,按顺序自动查找: 18 20 * 1) 环境变量 HBUILDERX_CLI / WX_DEVTOOLS_CLI .. .. @@ -24,6 +26,7 @@ 24 26 const { spawnSync } = require('child_process'); 25 27 const fs = require('fs'); 26 28 const path = require('path'); 29 +const manifest = require('./manifest');27 30 28 31 const ROOT = path.resolve(__dirname, '..'); 29 32 const ENV_DIR = path.join(ROOT, 'config', 'env'); .. .. @@ -189,11 +192,11 @@ 189 192 190 193 const argv = process.argv.slice(2); 191 194 const envName = (argv.find(function(a) { return a.charAt(0) !== '-'; }) || '').trim(); 192 -const syncManifest = argv.indexOf('--sync') > -1;195 +const noGen = argv.indexOf('--no-gen') > -1;193 196 const noOpen = argv.indexOf('--no-open') > -1; 194 197 const dryRun = argv.indexOf('--dry-run') > -1; 195 198 196 -const OWN_ARGS = ['--sync', '--no-open', '--dry-run'];199 +const OWN_ARGS = ['--no-gen', '--no-open', '--dry-run'];197 200 // 透传给 HBuilderX cli 的额外参数(保留 "参数 值" 成对关系) 198 201 const passthrough = []; 199 202 let envNameSkipped = false; .. .. @@ -243,19 +246,27 @@ 243 246 putOnPath(hx.file); 244 247 if (wx) putOnPath(wx.file); 245 248 249 + // 目标编译产物由 manifest.json 决定,而 manifest.json 是生成物250 + if (noGen && !fs.existsSync(manifest.TARGET)) {251 + fail('--no-gen 需要 manifest.json 已存在,请先运行:node scripts/set-env.js ' + envName);252 + }253 +246 254 console.log('[release] 环境 : ' + envName + '(' + env.ENV_NAME + ')'); 247 255 console.log('[release] 小程序appid : ' + env.APPID); 248 256 console.log('[release] 接口地址 : ' + env.HTTP_REQUEST_URL); 249 257 console.log('[release] 产物目录 : ' + DIST_DIR); 250 258 console.log('[release] HBuilderX : ' + hx.file + '(' + hx.source + ')'); 251 259 if (wx) console.log('[release] 开发者工具 : ' + wx.file + '(' + wx.source + ')'); 252 - console.log('[release] manifest.json: ' + (syncManifest ? '同步为 ' + envName + ' 的 appid(会产生 git 改动)' : '保持不动(仓库维持基线,无 git 改动)'));260 + console.log('[release] manifest.json: ' + (noGen261 + ? '不改动(--no-gen)'262 + : '由 manifest.json.demo 生成(appid ' + env.APPID + ',生成物不入库)'));253 263 254 264 const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT].concat(passthrough); 255 265 // 路径含空格/中文,交给 cmd 解析更稳 256 266 const openCmd = '"' + wx.file + '" open --project "' + DIST_DIR + '"'; 257 267 258 268 if (dryRun) { 269 + console.log('[release] 0) 生成: manifest.json ← manifest.json.demo' + (noGen ? '(已指定 --no-gen,跳过)' : ''));259 270 console.log('[release] 1) 编译: "' + hx.file + '" ' + cliArgs.join(' ')); 260 271 console.log('[release] 2) 产物 appid 回写为 ' + env.APPID + ' → ' + DIST_CONFIG); 261 272 if (!noOpen) console.log('[release] 3) 打开: ' + openCmd); .. .. @@ -263,11 +274,11 @@ 263 274 return; 264 275 } 265 276 266 - // 1) 可选:同步 manifest.json(会让被跟踪文件产生改动,默认不做)267 - if (syncManifest) {268 - console.log('[release] 同步 manifest.json 的 appid…');269 - const sync = spawnSync(process.execPath, [path.join(__dirname, 'set-env.js'), envName], { cwd: ROOT, stdio: 'inherit' });270 - if (sync.status !== 0) fail('同步 manifest.json 失败');277 + // 1) 生成 manifest.json(HBuilderX 的编译输入,模板:manifest.json.demo)278 + if (noGen) {279 + console.log('[release] 跳过 manifest.json 生成(--no-gen)');280 + } else {281 + manifest.generate(env);271 282 } 272 283 273 284 // 2) 发行编译(产物:unpackage/dist/build/mp-weixin) .. .. @@ -276,7 +287,7 @@ 276 287 if (build.error) fail('调用 cli.exe 失败:' + build.error.message); 277 288 if (build.status !== 0) fail('cli.exe 退出码 ' + build.status + ',请查看 HBuilderX 控制台输出'); 278 289 279 - // 3) 校正产物 appid(--sync 时通常已一致,这里兜底保证)290 + // 3) 校正产物 appid(正常应与 manifest 一致,这里兜底保证)280 291 if (!fs.existsSync(DIST_CONFIG)) { 281 292 fail('发行产物缺少 project.config.json:' + DIST_CONFIG + '\n 请确认 HBuilderX 编译是否成功'); 282 293 } scripts/set-env.js
.. .. @@ -1,78 +1,31 @@ 1 1 /** 2 - * 环境切换脚本:把 config/env/<env>.js 里的 appid 同步到编译期配置文件2 + * 环境切换:按 config/env/<env>.js 从模板生成 manifest.json3 3 * 4 4 * 用法: 5 5 * npm run env:sit 等价于 node scripts/set-env.js sit 6 6 * npm run env:prod 等价于 node scripts/set-env.js prod 7 7 * 8 8 * 说明: 9 - * appid 是编译期值(HBuilderX 的编译输入就是 manifest.json),无法像接口地址那样10 - * 运行时切换,所以这里做“一次命令写回”。改完必须重新编译一次,产物里的11 - * project.config.json 才会更新。9 + * manifest.json 是 HBuilderX 的编译输入(appid 属于编译期值,无法像接口地址那样运行时切换),10 + * 但它含环境相关 appid,不适合直接提交,所以:11 + * manifest.json.demo(入库模板) --生成--> manifest.json(已 .gitignore)12 + * 生成后必须重新编译一次,并在微信开发者工具中重新打开项目(appid 变更需重开)。12 13 * 13 - * 日常推荐:mp-weixin.appid 保持 null,只让发行脚本回写产物 appid(git 零噪音)。14 - * 本脚本用于需要在 HBuilderX 里直接运行调试、或要出 App 包(顶层 appid)的场景。14 + * 如需在 HBuilderX 里直接编译/运行调试,先跑一次本脚本;15 + * 走命令行发行则直接用 scripts/publish.js(内部会自动生成)。15 16 * 16 - * 唯一数据源:config/env/<env>.js 中的 UNI_APPID(DCloud 标识)、APPID(小程序 appid)17 + * appid 数据源唯一:config/env/<env>.js 中的 UNI_APPID(DCloud 标识)、APPID(小程序 appid)17 18 */ 18 19 const fs = require('fs'); 19 20 const path = require('path'); 21 +const manifest = require('./manifest');20 22 21 23 const ROOT = path.resolve(__dirname, '..'); 22 24 const ENV_DIR = path.join(ROOT, 'config', 'env'); 23 25 24 -// 根目录 project.config.json 不参与 mp-weixin 编译且已不入库,这里只维护 manifest.json25 -const files = {26 - manifest: path.join(ROOT, 'manifest.json')27 -};28 -29 26 function fail(msg) { 30 27 console.error('[set-env] ' + msg); 31 28 process.exit(1); 32 -}33 -34 -/** 只替换第一处 "appid" : <字符串或 null>,保留原有空格/缩进风格 */35 -function replaceAppId(text, value) {36 - let done = false;37 - const result = text.replace(/"appid"(\s*:\s*)(?:"[^"]*"|null)/, function(match, sep) {38 - done = true;39 - return '"appid"' + sep + '"' + value + '"';40 - });41 - return { text: result, done: done };42 -}43 -44 -/** 替换 manifest.json 顶层 appid(第一处出现即为顶层) */45 -function patchManifest(text, env) {46 - // 1) 顶层 appid(DCloud 应用标识)47 - const top = replaceAppId(text, env.UNI_APPID);48 - if (!top.done) fail('manifest.json 中未找到顶层 "appid" 字段');49 - text = top.text;50 -51 - // 2) mp-weixin 下的 appid(微信小程序 appid),只在该区块内替换第一处52 - const idx = text.indexOf('"mp-weixin"');53 - if (idx < 0) fail('manifest.json 中未找到 "mp-weixin" 配置块');54 - const head = text.slice(0, idx);55 - const body = text.slice(idx);56 - const scoped = replaceAppId(body, env.APPID);57 - if (scoped.done) return head + scoped.text;58 -59 - // 3) 字段被删掉时,按同级子项缩进补一行60 - const m = body.match(/^("mp-weixin"\s*:\s*\{)(\r?\n)([ \t]*)/);61 - if (!m) fail('manifest.json 的 "mp-weixin" 块格式异常,无法写入 appid');62 - const nl = m[2];63 - const indent = m[3];64 - return head + m[1] + nl + indent + '"appid" : "' + env.APPID + '",' + nl + indent + body.slice(m[0].length);65 -}66 -67 -function writeIfChanged(file, next) {68 - const prev = fs.readFileSync(file, 'utf8');69 - if (prev === next) {70 - console.log(' - ' + path.relative(ROOT, file) + ' 已是目标值,无需改动');71 - return false;72 - }73 - fs.writeFileSync(file, next, 'utf8');74 - console.log(' √ ' + path.relative(ROOT, file) + ' 已更新');75 - return true;76 29 } 77 30 78 31 function main() { .. .. @@ -98,11 +51,11 @@ 98 51 console.log(' 小程序 appid : ' + env.APPID); 99 52 console.log(' 接口地址 : ' + env.HTTP_REQUEST_URL); 100 53 101 - const changed = writeIfChanged(files.manifest, patchManifest(fs.readFileSync(files.manifest, 'utf8'), env));54 + const res = manifest.generate(env);102 55 103 - console.log(changed56 + console.log(res.changed104 57 ? '[set-env] 完成,请在 HBuilderX 中重新编译一次,并在微信开发者工具中重新打开项目' 105 - : '[set-env] 无需改动,当前已是 ' + envName + ' 环境配置');58 + : '[set-env] 无需改动,当前 manifest.json 已是 ' + envName + ' 环境');106 59 } 107 60 108 61 main(); switch-prod.bat
.. .. @@ -7,8 +7,8 @@ 7 7 echo 接口 : https://acmall.bluedeer.com.cn/crmeb/front 8 8 echo ============================================ 9 9 echo. 10 -echo 说明:改写 manifest.json / project.config.json 的编译期 appid11 -echo (接口地址是运行时按 appid 自动识别的,无需改动)10 +echo 说明:按 config/env/prod.js,从 manifest.json.demo 生成 manifest.json11 +echo (manifest.json 是生成物、已不入库;接口地址运行时按 appid 自动识别)12 12 echo. 13 13 node "scripts\set-env.js" prod 14 14 echo. switch-sit.bat
.. .. @@ -7,8 +7,8 @@ 7 7 echo 接口 : https://sit.tohours.com/crmeb/front 8 8 echo ============================================ 9 9 echo. 10 -echo 说明:改写 manifest.json / project.config.json 的编译期 appid11 -echo (接口地址是运行时按 appid 自动识别的,无需改动)10 +echo 说明:按 config/env/sit.js,从 manifest.json.demo 生成 manifest.json11 +echo (manifest.json 是生成物、已不入库;接口地址运行时按 appid 自动识别)12 12 echo. 13 13 node "scripts\set-env.js" sit 14 14 echo.