@lyhue1991/wxgzh 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -9
- package/dist/cli/config.js +74 -22
- package/dist/cli/fix.js +10 -7
- package/dist/cli/index.js +6 -5
- package/dist/cli/md2html.js +2 -1
- package/dist/cli/publish.js +6 -6
- package/dist/core/converter.js +52 -1
- package/dist/core/parser.js +3 -0
- package/dist/utils/config.js +119 -12
- package/package.json +1 -1
- package/spec.md +67 -29
- package/styles/black.css +82 -98
- package/styles/brown.css +69 -105
- package/styles/default.css +559 -203
- package/styles/green.css +1 -1
- package/styles/orange.css +2 -2
- package/styles/red.css +1 -1
- package/styles/yellow.css +496 -81
package/dist/utils/config.js
CHANGED
|
@@ -4,9 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getUserConfigPath = getUserConfigPath;
|
|
7
|
+
exports.loadUserConfig = loadUserConfig;
|
|
7
8
|
exports.loadConfig = loadConfig;
|
|
8
|
-
exports.
|
|
9
|
+
exports.saveAccountConfig = saveAccountConfig;
|
|
10
|
+
exports.setDefaultAccount = setDefaultAccount;
|
|
11
|
+
exports.removeAccountConfig = removeAccountConfig;
|
|
9
12
|
exports.clearUserConfig = clearUserConfig;
|
|
13
|
+
exports.resolveWechatCredentials = resolveWechatCredentials;
|
|
10
14
|
exports.maskSecret = maskSecret;
|
|
11
15
|
const promises_1 = require("node:fs/promises");
|
|
12
16
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -33,7 +37,14 @@ async function readJson(filePath) {
|
|
|
33
37
|
const raw = await (0, promises_1.readFile)(filePath, 'utf8');
|
|
34
38
|
return JSON.parse(raw);
|
|
35
39
|
}
|
|
36
|
-
function
|
|
40
|
+
function isRecord(value) {
|
|
41
|
+
return typeof value === 'object' && value !== null;
|
|
42
|
+
}
|
|
43
|
+
function sanitizeAccountName(value) {
|
|
44
|
+
const trimmed = value?.trim();
|
|
45
|
+
return trimmed ? trimmed : undefined;
|
|
46
|
+
}
|
|
47
|
+
function sanitizeAccountConfig(input) {
|
|
37
48
|
const output = {};
|
|
38
49
|
if (typeof input.appid === 'string' && input.appid.trim()) {
|
|
39
50
|
output.appid = input.appid.trim();
|
|
@@ -52,32 +63,128 @@ function sanitizeConfig(input) {
|
|
|
52
63
|
}
|
|
53
64
|
return output;
|
|
54
65
|
}
|
|
66
|
+
function sanitizeUserConfig(input) {
|
|
67
|
+
const accounts = {};
|
|
68
|
+
if (isRecord(input.accounts)) {
|
|
69
|
+
for (const [accountName, accountValue] of Object.entries(input.accounts)) {
|
|
70
|
+
const sanitizedName = sanitizeAccountName(accountName);
|
|
71
|
+
if (!sanitizedName || !isRecord(accountValue)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const sanitizedAccount = sanitizeAccountConfig(accountValue);
|
|
75
|
+
if (Object.keys(sanitizedAccount).length > 0) {
|
|
76
|
+
accounts[sanitizedName] = sanitizedAccount;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const legacyAccount = sanitizeAccountConfig(input);
|
|
81
|
+
if (Object.keys(accounts).length === 0 && Object.keys(legacyAccount).length > 0) {
|
|
82
|
+
accounts.default = legacyAccount;
|
|
83
|
+
}
|
|
84
|
+
const currentAccount = sanitizeAccountName(typeof input.currentAccount === 'string' ? input.currentAccount : undefined)
|
|
85
|
+
?? (Object.keys(accounts).length === 1 ? Object.keys(accounts)[0] : undefined);
|
|
86
|
+
return {
|
|
87
|
+
...(currentAccount ? { currentAccount } : {}),
|
|
88
|
+
accounts
|
|
89
|
+
};
|
|
90
|
+
}
|
|
55
91
|
function envConfig() {
|
|
56
|
-
return
|
|
92
|
+
return sanitizeAccountConfig({
|
|
57
93
|
appid: process.env.WX_APPID,
|
|
58
94
|
appsecret: process.env.WX_APPSECRET
|
|
59
95
|
});
|
|
60
96
|
}
|
|
61
|
-
async function
|
|
97
|
+
async function writeUserConfig(config) {
|
|
98
|
+
await (0, promises_1.mkdir)(CONFIG_DIR, { recursive: true });
|
|
99
|
+
const next = sanitizeUserConfig(config);
|
|
100
|
+
await (0, promises_1.writeFile)(getUserConfigPath(), `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
101
|
+
return next;
|
|
102
|
+
}
|
|
103
|
+
async function loadUserConfig() {
|
|
104
|
+
return sanitizeUserConfig(await readJson(getUserConfigPath()));
|
|
105
|
+
}
|
|
106
|
+
async function loadConfig(options) {
|
|
62
107
|
const env = envConfig();
|
|
63
|
-
const user =
|
|
108
|
+
const user = await loadUserConfig();
|
|
109
|
+
const account = sanitizeAccountName(options?.account) ?? sanitizeAccountName(process.env.WX_ACCOUNT) ?? user.currentAccount;
|
|
110
|
+
const selectedAccountConfig = account ? user.accounts[account] ?? {} : {};
|
|
64
111
|
return {
|
|
112
|
+
...selectedAccountConfig,
|
|
65
113
|
...env,
|
|
66
|
-
...
|
|
114
|
+
...(account ? { account } : {}),
|
|
115
|
+
...(user.currentAccount ? { currentAccount: user.currentAccount } : {}),
|
|
116
|
+
accounts: user.accounts
|
|
67
117
|
};
|
|
68
118
|
}
|
|
69
|
-
async function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
119
|
+
async function saveAccountConfig(accountName, patch) {
|
|
120
|
+
const sanitizedAccountName = sanitizeAccountName(accountName);
|
|
121
|
+
if (!sanitizedAccountName) {
|
|
122
|
+
throw new Error('公众号账号名不能为空');
|
|
123
|
+
}
|
|
124
|
+
const current = await loadUserConfig();
|
|
125
|
+
const nextAccounts = {
|
|
126
|
+
...current.accounts,
|
|
127
|
+
[sanitizedAccountName]: sanitizeAccountConfig({
|
|
128
|
+
...(current.accounts[sanitizedAccountName] ?? {}),
|
|
129
|
+
...patch
|
|
130
|
+
})
|
|
131
|
+
};
|
|
132
|
+
return writeUserConfig({
|
|
133
|
+
currentAccount: current.currentAccount ?? sanitizedAccountName,
|
|
134
|
+
accounts: nextAccounts
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function setDefaultAccount(accountName) {
|
|
138
|
+
const sanitizedAccountName = sanitizeAccountName(accountName);
|
|
139
|
+
if (!sanitizedAccountName) {
|
|
140
|
+
throw new Error('公众号账号名不能为空');
|
|
141
|
+
}
|
|
142
|
+
const current = await loadUserConfig();
|
|
143
|
+
if (!current.accounts[sanitizedAccountName]) {
|
|
144
|
+
throw new Error(`未找到公众号账号 ${sanitizedAccountName},请先通过 --account ${sanitizedAccountName} 写入配置`);
|
|
145
|
+
}
|
|
146
|
+
return writeUserConfig({
|
|
147
|
+
...current,
|
|
148
|
+
currentAccount: sanitizedAccountName
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async function removeAccountConfig(accountName) {
|
|
152
|
+
const sanitizedAccountName = sanitizeAccountName(accountName);
|
|
153
|
+
if (!sanitizedAccountName) {
|
|
154
|
+
throw new Error('公众号账号名不能为空');
|
|
155
|
+
}
|
|
156
|
+
const current = await loadUserConfig();
|
|
157
|
+
if (!current.accounts[sanitizedAccountName]) {
|
|
158
|
+
throw new Error(`未找到公众号账号 ${sanitizedAccountName}`);
|
|
159
|
+
}
|
|
160
|
+
const nextAccounts = { ...current.accounts };
|
|
161
|
+
delete nextAccounts[sanitizedAccountName];
|
|
162
|
+
const remainingAccountNames = Object.keys(nextAccounts);
|
|
163
|
+
const nextCurrentAccount = current.currentAccount === sanitizedAccountName
|
|
164
|
+
? remainingAccountNames[0]
|
|
165
|
+
: current.currentAccount;
|
|
166
|
+
return writeUserConfig({
|
|
167
|
+
...(nextCurrentAccount ? { currentAccount: nextCurrentAccount } : {}),
|
|
168
|
+
accounts: nextAccounts
|
|
169
|
+
});
|
|
75
170
|
}
|
|
76
171
|
async function clearUserConfig() {
|
|
77
172
|
if (await pathExists(getUserConfigPath())) {
|
|
78
173
|
await (0, promises_1.rm)(getUserConfigPath(), { force: true });
|
|
79
174
|
}
|
|
80
175
|
}
|
|
176
|
+
function resolveWechatCredentials(config) {
|
|
177
|
+
if (!config.appid || !config.appsecret) {
|
|
178
|
+
if (config.account) {
|
|
179
|
+
throw new Error(`未找到公众号账号 ${config.account} 的完整微信认证信息,请先运行: wxgzh config --account ${config.account} --appid xxx --appsecret yyy`);
|
|
180
|
+
}
|
|
181
|
+
throw new Error('未配置微信认证信息,请先运行: wxgzh config --account <name> --appid xxx --appsecret yyy,或通过 --account <name> 指定已配置账号');
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
appid: config.appid,
|
|
185
|
+
appsecret: config.appsecret
|
|
186
|
+
};
|
|
187
|
+
}
|
|
81
188
|
function maskSecret(secret) {
|
|
82
189
|
if (!secret) {
|
|
83
190
|
return undefined;
|
package/package.json
CHANGED
package/spec.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- 基于内置背景图或自定义背景图生成封面。
|
|
13
13
|
- 上传封面并创建微信公众号草稿。
|
|
14
14
|
- 提供一键流程:`md2html -> fix -> cover -> publish`。
|
|
15
|
+
- 支持在同一套用户配置中管理多个微信公众号账号,并在发布时按文章或命令选择目标账号。
|
|
15
16
|
|
|
16
17
|
---
|
|
17
18
|
|
|
@@ -43,6 +44,7 @@ wxgzh [article.md] [options]
|
|
|
43
44
|
|
|
44
45
|
- `--theme <theme>`:覆盖主题名
|
|
45
46
|
- `--author <author>`:覆盖作者名
|
|
47
|
+
- `--account <name>`:指定本次发布使用的公众号账号名
|
|
46
48
|
- `--cover <cover.jpg>`:指定现成封面图
|
|
47
49
|
- `--no-cover`:禁用自动生成封面
|
|
48
50
|
- `--digest <digest>`:覆盖摘要
|
|
@@ -53,6 +55,7 @@ wxgzh [article.md] [options]
|
|
|
53
55
|
|
|
54
56
|
```bash
|
|
55
57
|
wxgzh article.md
|
|
58
|
+
wxgzh article.md --account 公众号名称1
|
|
56
59
|
wxgzh article.md --theme blue
|
|
57
60
|
wxgzh article.md --author "wxgzh" --digest "这是一篇摘要"
|
|
58
61
|
wxgzh article.md --cover ./cover.jpg
|
|
@@ -63,7 +66,8 @@ wxgzh article.md --no-cover
|
|
|
63
66
|
|
|
64
67
|
- 默认中间产物目录为文章同级的 `.wxgzh/`。
|
|
65
68
|
- 若未传 `--cover` 且未指定 `--no-cover`,会自动生成简易封面。
|
|
66
|
-
-
|
|
69
|
+
- 根命令会直接调用微信接口,因此要求已选中的账号已配置 `appid` 与 `appsecret`。
|
|
70
|
+
- 账号选择顺序为:命令行 `--account` > front matter `account` > 环境变量 `WX_ACCOUNT` > 用户配置中的默认账号。
|
|
67
71
|
|
|
68
72
|
---
|
|
69
73
|
|
|
@@ -77,13 +81,17 @@ wxgzh config [options]
|
|
|
77
81
|
|
|
78
82
|
支持参数:
|
|
79
83
|
|
|
80
|
-
- `--
|
|
81
|
-
- `--
|
|
82
|
-
- `--
|
|
83
|
-
- `--
|
|
84
|
-
- `--
|
|
85
|
-
- `--
|
|
86
|
-
- `--
|
|
84
|
+
- `--account <name>`:指定要查看或修改的账号名
|
|
85
|
+
- `--appid <appid>`:为目标账号设置微信公众号 appid
|
|
86
|
+
- `--appsecret <appsecret>`:为目标账号设置微信公众号 appsecret
|
|
87
|
+
- `--author <author>`:为目标账号设置默认作者名
|
|
88
|
+
- `--default-theme <theme>`:为目标账号设置默认主题
|
|
89
|
+
- `--enable-comment`:为目标账号默认开启评论
|
|
90
|
+
- `--disable-comment`:为目标账号默认关闭评论
|
|
91
|
+
- `--set-default-account <name>`:设置默认账号
|
|
92
|
+
- `--remove-account <name>`:删除指定账号配置
|
|
93
|
+
- `--list`:查看当前账号或指定账号的生效配置
|
|
94
|
+
- `--list-accounts`:列出全部已配置账号
|
|
87
95
|
- `--list-themes`:列出可用主题
|
|
88
96
|
- `--clear`:清空用户级配置文件
|
|
89
97
|
|
|
@@ -91,16 +99,23 @@ wxgzh config [options]
|
|
|
91
99
|
|
|
92
100
|
```bash
|
|
93
101
|
wxgzh config --list
|
|
102
|
+
wxgzh config --list-accounts
|
|
103
|
+
wxgzh config --account 公众号名称1 --appid wx123 --appsecret abc123
|
|
104
|
+
wxgzh config --account 公众号名称2 --author "技术团队" --default-theme blue
|
|
105
|
+
wxgzh config --set-default-account 公众号名称1
|
|
94
106
|
wxgzh config --list-themes
|
|
95
|
-
wxgzh config --
|
|
96
|
-
wxgzh config --enable-comment
|
|
107
|
+
wxgzh config --account 公众号名称1 --enable-comment
|
|
97
108
|
```
|
|
98
109
|
|
|
99
110
|
说明:
|
|
100
111
|
|
|
101
112
|
- 用户级配置文件路径为 `~/.config/wxgzh/wxgzh.json`。
|
|
113
|
+
- 配置文件支持多个账号,账号名由用户自定义,例如 `公众号名称1`、`公众号名称2`、`team-a`。
|
|
114
|
+
- **配置凭据时必须指定账号**:设置 `appid` 或 `appsecret` 时,必须同时通过 `--account` 指定目标账号名。
|
|
115
|
+
- 当执行写入类操作且未传 `--account` 时,默认作用于当前默认账号;若尚未设置默认账号,则首次新增账号时必须显式传入 `--account`。
|
|
116
|
+
- `config --list` 默认展示当前默认账号的生效配置;若同时传入 `--account`,则展示对应账号。
|
|
102
117
|
- 当前未实现项目级配置文件。
|
|
103
|
-
-
|
|
118
|
+
- 环境变量支持 `WX_ACCOUNT`、`WX_APPID`、`WX_APPSECRET`。
|
|
104
119
|
|
|
105
120
|
---
|
|
106
121
|
|
|
@@ -128,7 +143,7 @@ wxgzh md2html --from article.md --to .wxgzh/article.html --theme blue
|
|
|
128
143
|
|
|
129
144
|
说明:
|
|
130
145
|
|
|
131
|
-
- 支持 front matter:`title`、`author`、`digest`、`theme`、`cover`、`enableComment`。
|
|
146
|
+
- 支持 front matter:`title`、`author`、`digest`、`theme`、`cover`、`enableComment`、`account`。
|
|
132
147
|
- 若 front matter 中未设置 `title`,会尝试使用正文第一个一级标题。
|
|
133
148
|
- 若 front matter 中未设置 `digest`,会优先使用二级标题拼接生成摘要,兜底为标题或默认摘要。
|
|
134
149
|
- 会记录源 Markdown 所在目录,用于后续解析相对图片路径。
|
|
@@ -145,6 +160,7 @@ wxgzh fix <article.html> [options]
|
|
|
145
160
|
|
|
146
161
|
支持参数:
|
|
147
162
|
|
|
163
|
+
- `--account <name>`:指定上传正文图片时使用的公众号账号名
|
|
148
164
|
- `--no-upload`:只修复 HTML,不上传图片
|
|
149
165
|
- `--cdn <url>`:把本地图片路径替换为指定 CDN 地址
|
|
150
166
|
|
|
@@ -152,6 +168,7 @@ wxgzh fix <article.html> [options]
|
|
|
152
168
|
|
|
153
169
|
```bash
|
|
154
170
|
wxgzh fix .wxgzh/article.html
|
|
171
|
+
wxgzh fix .wxgzh/article.html --account 公众号名称1
|
|
155
172
|
wxgzh fix .wxgzh/article.html --no-upload
|
|
156
173
|
wxgzh fix .wxgzh/article.html --cdn https://cdn.example.com
|
|
157
174
|
```
|
|
@@ -162,6 +179,7 @@ wxgzh fix .wxgzh/article.html --cdn https://cdn.example.com
|
|
|
162
179
|
- 为图片补充适合微信正文的内联样式。
|
|
163
180
|
- 若开启上传,会把非微信 CDN 图片上传到微信素材接口。
|
|
164
181
|
- 若指定 `--cdn` 且未上传,则把本地图片替换为 CDN 地址。
|
|
182
|
+
- 若需要上传图片,账号选择顺序同根命令:`--account` > HTML 元数据中的 `account` > `WX_ACCOUNT` > 默认账号。
|
|
165
183
|
|
|
166
184
|
---
|
|
167
185
|
|
|
@@ -209,6 +227,7 @@ wxgzh publish --article <article.html> --cover <cover.jpg> [options]
|
|
|
209
227
|
|
|
210
228
|
- `--article <article.html>`:正文 HTML 路径
|
|
211
229
|
- `--cover <cover.jpg>`:封面图路径
|
|
230
|
+
- `--account <name>`:指定本次发布使用的公众号账号名
|
|
212
231
|
- `--title <title>`:覆盖标题
|
|
213
232
|
- `--author <author>`:覆盖作者名
|
|
214
233
|
- `--digest <digest>`:覆盖摘要
|
|
@@ -218,6 +237,7 @@ wxgzh publish --article <article.html> --cover <cover.jpg> [options]
|
|
|
218
237
|
|
|
219
238
|
```bash
|
|
220
239
|
wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg
|
|
240
|
+
wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg --account 公众号名称1
|
|
221
241
|
wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg --title "新的标题"
|
|
222
242
|
```
|
|
223
243
|
|
|
@@ -225,6 +245,7 @@ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg --title "
|
|
|
225
245
|
|
|
226
246
|
- 会先上传封面图,再创建草稿。
|
|
227
247
|
- 标题、作者、摘要、评论开关都支持命令参数覆盖 HTML 元数据与默认配置。
|
|
248
|
+
- 账号选择顺序为:命令行 `--account` > HTML 元数据中的 `account` > 环境变量 `WX_ACCOUNT` > 用户配置中的默认账号。
|
|
228
249
|
|
|
229
250
|
---
|
|
230
251
|
|
|
@@ -248,6 +269,7 @@ title: 我的文章标题
|
|
|
248
269
|
author: wxgzh
|
|
249
270
|
digest: 文章摘要
|
|
250
271
|
theme: default
|
|
272
|
+
account: 公众号名称1
|
|
251
273
|
cover: ./cover.jpg
|
|
252
274
|
enableComment: true
|
|
253
275
|
---
|
|
@@ -259,6 +281,7 @@ enableComment: true
|
|
|
259
281
|
- `author`:作者名
|
|
260
282
|
- `digest`:摘要
|
|
261
283
|
- `theme`:主题名
|
|
284
|
+
- `account`:目标公众号账号名,用于选择配置文件中的某个账号
|
|
262
285
|
- `cover`:封面图相对或绝对路径
|
|
263
286
|
- `enableComment`:是否开启评论
|
|
264
287
|
|
|
@@ -276,25 +299,48 @@ enableComment: true
|
|
|
276
299
|
|
|
277
300
|
```json
|
|
278
301
|
{
|
|
279
|
-
"
|
|
280
|
-
"
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
302
|
+
"currentAccount": "公众号名称1",
|
|
303
|
+
"accounts": {
|
|
304
|
+
"公众号名称1": {
|
|
305
|
+
"appid": "wx1234567890abcdef",
|
|
306
|
+
"appsecret": "1234567890abcdef1234567890abcdef",
|
|
307
|
+
"author": "wxgzh",
|
|
308
|
+
"defaultTheme": "default",
|
|
309
|
+
"enableComment": true
|
|
310
|
+
},
|
|
311
|
+
"公众号名称2": {
|
|
312
|
+
"appid": "wxabcdef1234567890",
|
|
313
|
+
"appsecret": "abcdef1234567890abcdef1234567890",
|
|
314
|
+
"author": "备用号",
|
|
315
|
+
"defaultTheme": "blue",
|
|
316
|
+
"enableComment": false
|
|
317
|
+
}
|
|
318
|
+
}
|
|
284
319
|
}
|
|
285
320
|
```
|
|
286
321
|
|
|
287
322
|
环境变量:
|
|
288
323
|
|
|
324
|
+
- `WX_ACCOUNT`
|
|
289
325
|
- `WX_APPID`
|
|
290
326
|
- `WX_APPSECRET`
|
|
291
327
|
|
|
292
328
|
说明:
|
|
293
329
|
|
|
294
|
-
- `
|
|
330
|
+
- `currentAccount` 表示默认账号名。
|
|
331
|
+
- `accounts` 以账号名为 key,保存每个公众号各自的凭据与默认发布参数。
|
|
332
|
+
- `config --list` 输出的是当前账号或指定账号的生效配置。
|
|
333
|
+
- 若同时设置 `WX_APPID` 与 `WX_APPSECRET`,则它们会覆盖所选账号中的凭据,仅对当前进程生效。
|
|
295
334
|
- `appsecret` 在输出时会自动脱敏。
|
|
296
335
|
- 微信 access token 会额外缓存到 `~/.config/wxgzh/token.<appid>.json`。
|
|
297
336
|
|
|
337
|
+
### 账号解析规则
|
|
338
|
+
|
|
339
|
+
- 需要访问微信接口的命令包括根命令、`fix`(上传图片时)与 `publish`。
|
|
340
|
+
- 账号名优先级:命令行 `--account` > front matter / HTML 元数据中的 `account` > 环境变量 `WX_ACCOUNT` > `currentAccount`。
|
|
341
|
+
- 账号凭据来源:若环境变量同时提供 `WX_APPID` 与 `WX_APPSECRET`,优先使用环境变量;否则读取已选中账号的配置。
|
|
342
|
+
- 若最终仍无法确定账号或缺少凭据,命令应报错,并给出明确提示,例如“未找到公众号账号 公众号名称1 的 appsecret 配置”。
|
|
343
|
+
|
|
298
344
|
---
|
|
299
345
|
|
|
300
346
|
## 技术栈
|
|
@@ -303,6 +349,7 @@ enableComment: true
|
|
|
303
349
|
|
|
304
350
|
| 包名 | 用途 |
|
|
305
351
|
|------|------|
|
|
352
|
+
| `@css-inline/css-inline` | CSS 内联到 HTML |
|
|
306
353
|
| `axios` | 微信接口与远程资源请求 |
|
|
307
354
|
| `cheerio` | HTML 结构处理 |
|
|
308
355
|
| `commander` | CLI 框架 |
|
|
@@ -310,10 +357,10 @@ enableComment: true
|
|
|
310
357
|
| `form-data` | 上传微信素材 |
|
|
311
358
|
| `gray-matter` | front matter 解析 |
|
|
312
359
|
| `highlight.js` | 代码高亮 |
|
|
313
|
-
| `juice` | CSS 内联 |
|
|
314
360
|
| `markdown-it` | Markdown 渲染 |
|
|
315
361
|
| `markdown-it-mathjax3` | 数学公式渲染 |
|
|
316
362
|
| `mathjax` | MathJax 支持 |
|
|
363
|
+
| `opentype.js` | 封面图字体渲染 |
|
|
317
364
|
| `sharp` | 封面图生成 |
|
|
318
365
|
|
|
319
366
|
### 开发依赖
|
|
@@ -449,7 +496,7 @@ POST https://api.weixin.qq.com/cgi-bin/draft/add
|
|
|
449
496
|
- 使用 `markdown-it` 渲染 Markdown。
|
|
450
497
|
- 使用 `highlight.js` 处理代码块高亮。
|
|
451
498
|
- 使用 `markdown-it-mathjax3` + `mathjax` 处理数学公式。
|
|
452
|
-
- 使用 `
|
|
499
|
+
- 使用 `@css-inline/css-inline` 把主题 CSS 与补充样式内联到最终 HTML。
|
|
453
500
|
- 会标准化图片、表格、引用块等结构,以适配微信公众号。
|
|
454
501
|
|
|
455
502
|
### 图片处理
|
|
@@ -464,15 +511,6 @@ POST https://api.weixin.qq.com/cgi-bin/draft/add
|
|
|
464
511
|
- 未传背景图时,从 `assets/backgrounds/` 中随机选择。
|
|
465
512
|
- 若没有可用背景图,使用内置渐变背景兜底。
|
|
466
513
|
|
|
467
|
-
---
|
|
468
|
-
|
|
469
|
-
## 已知限制
|
|
470
|
-
|
|
471
|
-
- 当前没有项目级配置文件,仅支持用户级配置和部分环境变量。
|
|
472
|
-
- 当前未接入自动化测试框架。
|
|
473
|
-
- 当前未接入 ESLint。
|
|
474
|
-
- 当前未实现 Mermaid 专用渲染,Mermaid 代码块会按普通代码块处理。
|
|
475
|
-
- 微信平台对外链、HTML 标签和样式能力本身仍有限制,最终展示以平台实际效果为准。
|
|
476
514
|
|
|
477
515
|
---
|
|
478
516
|
|