Jacob
2026-10-12 85aed6cbf2e5693f980e182febd113b3b13f8813
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
/**
 * 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
};