/**
|
* manifest.json 生成器
|
*
|
* manifest.json 是 HBuilderX 的编译输入,但内含环境相关 appid,不适合直接提交;
|
* 因此模板入库、生成物忽略:
|
*
|
* manifest.json.demo(入库,模板) ──注入环境 appid──▶ manifest.json(.gitignore)
|
*
|
* appid 数据源唯一:config/env/<env>.js 的 UNI_APPID(DCloud 标识)、APPID(微信小程序 appid)
|
* 说明:模板里 mp-weixin 的 appid 允许写成 null 或不存在,生成时会自动写入/补齐。
|
*/
|
const fs = require('fs');
|
const path = require('path');
|
|
const ROOT = path.resolve(__dirname, '..');
|
const TEMPLATE = path.join(ROOT, 'manifest.json.demo');
|
const TARGET = path.join(ROOT, 'manifest.json');
|
|
function fail(msg) {
|
console.error('[manifest] ' + msg);
|
process.exit(1);
|
}
|
|
/** 只替换第一处 "appid" : <字符串或 null>,保留原有的空格/缩进风格 */
|
function replaceAppId(text, value) {
|
let done = false;
|
const result = text.replace(/"appid"(\s*:\s*)(?:"[^"]*"|null)/, function(match, sep) {
|
done = true;
|
return '"appid"' + sep + '"' + value + '"';
|
});
|
return { text: result, done: done };
|
}
|
|
/** 注入两个 appid:顶层(DCloud 标识)+ mp-weixin 块内(微信小程序 appid) */
|
function injectAppIds(text, env) {
|
// 1) 顶层 appid(第一处出现即为顶层)
|
const top = replaceAppId(text, env.UNI_APPID);
|
if (!top.done) fail('模板中未找到顶层 "appid" 字段:' + path.relative(ROOT, TEMPLATE));
|
text = top.text;
|
|
// 2) mp-weixin 下的 appid,只在该区块内替换第一处
|
const idx = text.indexOf('"mp-weixin"');
|
if (idx < 0) fail('模板中未找到 "mp-weixin" 配置块:' + path.relative(ROOT, TEMPLATE));
|
const head = text.slice(0, idx);
|
const body = text.slice(idx);
|
const scoped = replaceAppId(body, env.APPID);
|
if (scoped.done) return head + scoped.text;
|
|
// 3) 字段被删掉时,按同级子项缩进补一行
|
const m = body.match(/^("mp-weixin"\s*:\s*\{)(\r?\n)([ \t]*)/);
|
if (!m) fail('模板的 "mp-weixin" 块格式异常,无法写入 appid');
|
const nl = m[2];
|
const indent = m[3];
|
return head + m[1] + nl + indent + '"appid" : "' + env.APPID + '",' + nl + indent + body.slice(m[0].length);
|
}
|
|
/** 统计「除 appid 行以外」的差异(按行内容比较,不受行号位移影响) */
|
function otherDiff(a, b) {
|
function count(text) {
|
const m = new Map();
|
text.split(/\r?\n/).forEach(function(line) {
|
const l = line.replace(/\s+$/, '');
|
if (/^\s*"appid"\s*:/.test(l.trim())) return;
|
m.set(l, (m.get(l) || 0) + 1);
|
});
|
return m;
|
}
|
const ma = count(a);
|
const mb = count(b);
|
const keys = new Set(Array.from(ma.keys()).concat(Array.from(mb.keys())));
|
let diff = 0;
|
const sample = [];
|
keys.forEach(function(k) {
|
const d = Math.abs((ma.get(k) || 0) - (mb.get(k) || 0));
|
if (!d) return;
|
diff += d;
|
if (sample.length < 3 && k.trim()) sample.push(k.trim().slice(0, 40));
|
});
|
return { count: diff, sample: sample };
|
}
|
|
/**
|
* 按环境生成 manifest.json
|
* @param {object} env config/env/<env>.js 导出的配置
|
* @returns {{ file: string, changed: boolean }}
|
*/
|
function generate(env) {
|
if (!fs.existsSync(TEMPLATE)) {
|
fail('未找到模板 ' + path.relative(ROOT, TEMPLATE) +
|
'\n 请把 manifest.json 的模板保存为 manifest.json.demo(需入库)后再生成');
|
}
|
|
const rendered = injectAppIds(fs.readFileSync(TEMPLATE, 'utf8'), env);
|
const exists = fs.existsSync(TARGET);
|
|
if (exists) {
|
const prev = fs.readFileSync(TARGET, 'utf8');
|
// 行尾差异(CRLF/LF)不算变化,避免无意义重写
|
if (prev.replace(/\r\n/g, '\n') === rendered.replace(/\r\n/g, '\n')) {
|
console.log('[manifest] manifest.json 已是目标内容,无需重写');
|
return { file: TARGET, changed: false };
|
}
|
const diffs = otherDiff(prev, rendered);
|
if (diffs.count) {
|
console.log('[manifest] 注意:现有 manifest.json 有 ' + diffs.count + ' 行内容与模板不同,本次会被模板覆盖' +
|
(diffs.sample.length ? '(如 ' + diffs.sample.join(' | ') + ')' : ''));
|
console.log('[manifest] 需要长期保留的改动请写进 manifest.json.demo');
|
}
|
}
|
|
fs.writeFileSync(TARGET, rendered, 'utf8');
|
console.log('[manifest] 已由 manifest.json.demo 生成 manifest.json' +
|
'(DCloud ' + env.UNI_APPID + ' / 小程序 ' + env.APPID + ')');
|
return { file: TARGET, changed: true };
|
}
|
|
module.exports = {
|
TEMPLATE: TEMPLATE,
|
TARGET: TARGET,
|
generate: generate,
|
injectAppIds: injectAppIds
|
};
|