@lyhue1991/wxgzh 0.1.0
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 +432 -0
- package/assets/backgrounds//345/217/214/347/214/253.jpg +0 -0
- package/assets/backgrounds//345/217/244/351/243/216.jpg +0 -0
- package/assets/backgrounds//345/217/244/351/243/2162.jpg +0 -0
- package/assets/backgrounds//345/260/221/345/245/263/346/230/237/347/251/272.jpg +0 -0
- package/assets/backgrounds//345/277/203/345/277/203/347/233/270/345/215/260.jpg +0 -0
- package/assets/backgrounds//346/230/237/347/251/272.jpg +0 -0
- package/assets/backgrounds//346/240/221/346/234/250.jpg +0 -0
- package/assets/backgrounds//346/260/264/345/275/251.jpg +0 -0
- package/assets/backgrounds//346/260/264/346/274/253.jpg +0 -0
- package/assets/backgrounds//346/265/267/346/230/237.jpg +0 -0
- package/assets/backgrounds//346/265/267/346/273/251.jpg +0 -0
- package/assets/backgrounds//346/265/267/350/261/232.jpg +0 -0
- package/assets/backgrounds//346/270/205/346/231/250.jpg +0 -0
- package/assets/backgrounds//347/203/255/346/260/224/347/220/203.jpg +0 -0
- package/assets/backgrounds//347/214/253/345/222/252/345/245/263/345/255/251.jpg +0 -0
- package/assets/backgrounds//347/223/246/345/212/233.jpg +0 -0
- package/assets/backgrounds//347/272/242/345/217/266/351/243/230/351/243/230.jpg +0 -0
- package/assets/backgrounds//350/212/261/346/234/265.jpg +0 -0
- package/assets/backgrounds//351/243/216/350/275/246.jpg +0 -0
- package/assets/backgrounds//351/273/204/346/230/217.jpg +0 -0
- package/bin/wxgzh.js +3 -0
- package/dist/cli/config.js +62 -0
- package/dist/cli/cover.js +39 -0
- package/dist/cli/fix.js +38 -0
- package/dist/cli/index.js +161 -0
- package/dist/cli/md2html.js +49 -0
- package/dist/cli/publish.js +56 -0
- package/dist/core/converter.js +656 -0
- package/dist/core/cover.js +164 -0
- package/dist/core/fixer.js +97 -0
- package/dist/core/parser.js +105 -0
- package/dist/core/themes.js +59 -0
- package/dist/core/wechat.js +162 -0
- package/dist/types.js +2 -0
- package/dist/utils/config.js +89 -0
- package/dist/utils/fs.js +20 -0
- package/dist/utils/logger.js +18 -0
- package/dist/utils/tls.js +46 -0
- package/package.json +47 -0
- package/spec.md +490 -0
- package/styles/black.css +600 -0
- package/styles/blue.css +616 -0
- package/styles/brown.css +620 -0
- package/styles/custom.css +8 -0
- package/styles/default.css +227 -0
- package/styles/green.css +617 -0
- package/styles/orange.css +617 -0
- package/styles/red.css +617 -0
- package/styles/yellow.css +202 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const promises_1 = require("node:fs/promises");
|
|
40
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
41
|
+
const commander_1 = require("commander");
|
|
42
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
43
|
+
const cover_1 = require("../core/cover");
|
|
44
|
+
const parser_1 = require("../core/parser");
|
|
45
|
+
const themes_1 = require("../core/themes");
|
|
46
|
+
const config_1 = require("../utils/config");
|
|
47
|
+
const fs_1 = require("../utils/fs");
|
|
48
|
+
const logger_1 = require("../utils/logger");
|
|
49
|
+
const config_2 = require("./config");
|
|
50
|
+
const cover_2 = require("./cover");
|
|
51
|
+
const fix_1 = require("./fix");
|
|
52
|
+
const md2html_1 = require("./md2html");
|
|
53
|
+
const publish_1 = require("./publish");
|
|
54
|
+
dotenv_1.default.config();
|
|
55
|
+
const EXAMPLES_TEXT = [
|
|
56
|
+
'常用示例:',
|
|
57
|
+
' $ wxgzh article.md',
|
|
58
|
+
' $ wxgzh article.md --theme blue',
|
|
59
|
+
' $ wxgzh article.md --author "wxgzh" --digest "这是一篇摘要"',
|
|
60
|
+
' $ wxgzh article.md --cover ./cover.jpg',
|
|
61
|
+
' $ wxgzh config --list',
|
|
62
|
+
' $ wxgzh config --list-themes',
|
|
63
|
+
' $ wxgzh md2html --from article.md --to .wxgzh/article.html',
|
|
64
|
+
' $ wxgzh fix .wxgzh/article.html --no-upload',
|
|
65
|
+
' $ wxgzh cover --title "我的文章" --to .wxgzh/cover.jpg',
|
|
66
|
+
' $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg'
|
|
67
|
+
].join('\n');
|
|
68
|
+
function resolveCoverValue(value) {
|
|
69
|
+
return typeof value === 'string' ? value : undefined;
|
|
70
|
+
}
|
|
71
|
+
async function runDefaultPipeline(articlePath, options) {
|
|
72
|
+
const absoluteArticle = node_path_1.default.resolve(articlePath);
|
|
73
|
+
const outputDir = node_path_1.default.resolve(options.outputDir ?? node_path_1.default.join(node_path_1.default.dirname(absoluteArticle), '.wxgzh'));
|
|
74
|
+
const articleBaseName = node_path_1.default.basename(absoluteArticle, node_path_1.default.extname(absoluteArticle));
|
|
75
|
+
const htmlPath = node_path_1.default.join(outputDir, `${articleBaseName}.html`);
|
|
76
|
+
const coverPath = node_path_1.default.join(outputDir, `${articleBaseName}.cover.jpg`);
|
|
77
|
+
const config = await (0, config_1.loadConfig)();
|
|
78
|
+
const rawMarkdown = await (0, promises_1.readFile)(absoluteArticle, 'utf8');
|
|
79
|
+
const parsed = (0, parser_1.parseMarkdown)(rawMarkdown);
|
|
80
|
+
const title = parsed.metadata.title ?? articleBaseName;
|
|
81
|
+
const author = options.author ?? parsed.metadata.author ?? config.author ?? 'wxgzh';
|
|
82
|
+
const digest = options.digest ?? parsed.metadata.digest ?? '由 wxgzh 自动生成的公众号草稿';
|
|
83
|
+
const autoCoverDisabled = options.cover === false;
|
|
84
|
+
const resolvedTheme = options.theme ?? parsed.metadata.theme ?? config.defaultTheme;
|
|
85
|
+
await (0, md2html_1.convertMarkdownFile)(absoluteArticle, htmlPath, {
|
|
86
|
+
theme: resolvedTheme ? (0, themes_1.assertThemeExists)(resolvedTheme) : undefined,
|
|
87
|
+
author
|
|
88
|
+
});
|
|
89
|
+
(0, logger_1.info)(`已生成 HTML: ${htmlPath}`);
|
|
90
|
+
const { fixHtmlFile } = await Promise.resolve().then(() => __importStar(require('../core/fixer')));
|
|
91
|
+
const { WechatClient } = await Promise.resolve().then(() => __importStar(require('../core/wechat')));
|
|
92
|
+
if (!config.appid || !config.appsecret) {
|
|
93
|
+
throw new Error('未配置微信认证信息,请先运行: wxgzh config --appid xxx --appsecret yyy');
|
|
94
|
+
}
|
|
95
|
+
const wechat = new WechatClient({ appid: config.appid, appsecret: config.appsecret });
|
|
96
|
+
await fixHtmlFile(htmlPath, { upload: true, wechat });
|
|
97
|
+
(0, logger_1.info)(`已修复并上传正文图片: ${htmlPath}`);
|
|
98
|
+
let finalCoverPath = resolveCoverValue(options.cover) ?? parsed.metadata.cover;
|
|
99
|
+
if (finalCoverPath && !node_path_1.default.isAbsolute(finalCoverPath)) {
|
|
100
|
+
finalCoverPath = node_path_1.default.resolve(node_path_1.default.dirname(absoluteArticle), finalCoverPath);
|
|
101
|
+
}
|
|
102
|
+
if (!finalCoverPath && !autoCoverDisabled) {
|
|
103
|
+
await (0, fs_1.ensureParentDir)(coverPath);
|
|
104
|
+
await (0, cover_1.createCover)({ title, author, outputPath: coverPath });
|
|
105
|
+
finalCoverPath = coverPath;
|
|
106
|
+
(0, logger_1.info)(`已生成封面图: ${coverPath}`);
|
|
107
|
+
}
|
|
108
|
+
if (!finalCoverPath) {
|
|
109
|
+
throw new Error('当前未提供封面图。请通过 --cover 指定,或去掉 --no-cover 以自动生成简易封面。');
|
|
110
|
+
}
|
|
111
|
+
const result = await (0, publish_1.publishDraft)(htmlPath, finalCoverPath, {
|
|
112
|
+
title,
|
|
113
|
+
author,
|
|
114
|
+
digest,
|
|
115
|
+
enableComment: options.enableComment ?? parsed.metadata.enableComment ?? config.enableComment
|
|
116
|
+
});
|
|
117
|
+
console.log(JSON.stringify(result, null, 2));
|
|
118
|
+
}
|
|
119
|
+
async function main() {
|
|
120
|
+
const program = new commander_1.Command();
|
|
121
|
+
program
|
|
122
|
+
.name('wxgzh')
|
|
123
|
+
.description('把 Markdown 文章处理成微信公众号草稿,支持主题、图片修复、封面生成与一键发布')
|
|
124
|
+
.usage('[article.md] [options]')
|
|
125
|
+
.helpOption('-h, --help', '查看帮助')
|
|
126
|
+
.showHelpAfterError('(使用 --help 查看完整帮助)')
|
|
127
|
+
.argument('[article]', 'Markdown 文件路径;传入后会自动执行 md2html -> fix -> cover -> publish')
|
|
128
|
+
.option('--theme <theme>', `指定主题名,可用值:${(0, themes_1.listAvailableThemes)().join(', ')}`)
|
|
129
|
+
.option('--author <author>', '覆盖作者名;未传时优先使用 front matter 或 config 中的 author')
|
|
130
|
+
.option('--cover <cover.jpg>', '指定现成封面图;未传时默认自动生成')
|
|
131
|
+
.option('--no-cover', '禁用自动封面生成;适合 front matter 已配置 cover 的场景')
|
|
132
|
+
.option('--digest <digest>', '覆盖文章摘要;未传时自动从正文提取')
|
|
133
|
+
.option('--output-dir <dir>', '指定中间产物输出目录,默认写入文章同级 .wxgzh/')
|
|
134
|
+
.option('--enable-comment', '为本次草稿开启评论')
|
|
135
|
+
.addHelpText('after', `\n${EXAMPLES_TEXT}\n`)
|
|
136
|
+
.action(async (article, options) => {
|
|
137
|
+
if (!article) {
|
|
138
|
+
program.outputHelp();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await runDefaultPipeline(article, options);
|
|
142
|
+
});
|
|
143
|
+
const examplesCommand = new commander_1.Command('examples')
|
|
144
|
+
.description('打印常用命令示例')
|
|
145
|
+
.helpOption('-h, --help', '查看帮助')
|
|
146
|
+
.action(() => {
|
|
147
|
+
console.log(EXAMPLES_TEXT);
|
|
148
|
+
});
|
|
149
|
+
program.addCommand(examplesCommand);
|
|
150
|
+
(0, config_2.registerConfigCommand)(program);
|
|
151
|
+
(0, md2html_1.registerMd2HtmlCommand)(program);
|
|
152
|
+
(0, fix_1.registerFixCommand)(program);
|
|
153
|
+
(0, cover_2.registerCoverCommand)(program);
|
|
154
|
+
(0, publish_1.registerPublishCommand)(program);
|
|
155
|
+
await program.parseAsync(process.argv);
|
|
156
|
+
}
|
|
157
|
+
main().catch((err) => {
|
|
158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
159
|
+
console.error(`❌ ${message}`);
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.convertMarkdownFile = convertMarkdownFile;
|
|
7
|
+
exports.registerMd2HtmlCommand = registerMd2HtmlCommand;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const converter_1 = require("../core/converter");
|
|
11
|
+
const parser_1 = require("../core/parser");
|
|
12
|
+
const themes_1 = require("../core/themes");
|
|
13
|
+
const config_1 = require("../utils/config");
|
|
14
|
+
const fs_1 = require("../utils/fs");
|
|
15
|
+
const logger_1 = require("../utils/logger");
|
|
16
|
+
async function convertMarkdownFile(inputPath, outputPath, options) {
|
|
17
|
+
const config = await (0, config_1.loadConfig)();
|
|
18
|
+
const absoluteInputPath = node_path_1.default.resolve(inputPath);
|
|
19
|
+
const raw = await (0, promises_1.readFile)(absoluteInputPath, 'utf8');
|
|
20
|
+
const parsed = (0, parser_1.parseMarkdown)(raw);
|
|
21
|
+
const resolvedTheme = options?.theme ?? parsed.metadata.theme ?? config.defaultTheme ?? 'default';
|
|
22
|
+
const html = await (0, converter_1.renderMarkdownToHtml)(parsed.body, {
|
|
23
|
+
...parsed.metadata,
|
|
24
|
+
theme: (0, themes_1.assertThemeExists)(resolvedTheme),
|
|
25
|
+
author: options?.author ?? parsed.metadata.author ?? config.author,
|
|
26
|
+
sourceDir: node_path_1.default.dirname(absoluteInputPath)
|
|
27
|
+
});
|
|
28
|
+
await (0, fs_1.writeTextFile)(node_path_1.default.resolve(outputPath), html);
|
|
29
|
+
}
|
|
30
|
+
function registerMd2HtmlCommand(program) {
|
|
31
|
+
program
|
|
32
|
+
.command('md2html')
|
|
33
|
+
.description('把 Markdown 转成适合微信公众号编辑器粘贴和发布的 HTML')
|
|
34
|
+
.usage('--from <input.md> --to <output.html> [options]')
|
|
35
|
+
.helpOption('-h, --help', '查看帮助')
|
|
36
|
+
.requiredOption('--from <input.md>', '输入 Markdown 文件路径')
|
|
37
|
+
.requiredOption('--to <output.html>', '输出 HTML 文件路径')
|
|
38
|
+
.option('--theme <theme>', '覆盖主题名;未传时使用 front matter 或默认配置')
|
|
39
|
+
.option('--author <author>', '覆盖作者名;会写入 HTML 元数据')
|
|
40
|
+
.addHelpText('after', '\n示例:\n $ wxgzh md2html --from article.md --to .wxgzh/article.html\n $ wxgzh md2html --from article.md --to .wxgzh/article.html --theme blue\n')
|
|
41
|
+
.action(async (options, command) => {
|
|
42
|
+
const mergedOptions = command.optsWithGlobals();
|
|
43
|
+
await convertMarkdownFile(options.from, options.to, {
|
|
44
|
+
theme: mergedOptions.theme,
|
|
45
|
+
author: mergedOptions.author
|
|
46
|
+
});
|
|
47
|
+
(0, logger_1.success)(`已生成 HTML: ${node_path_1.default.resolve(options.to)}`);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.publishDraft = publishDraft;
|
|
7
|
+
exports.registerPublishCommand = registerPublishCommand;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const converter_1 = require("../core/converter");
|
|
10
|
+
const wechat_1 = require("../core/wechat");
|
|
11
|
+
const config_1 = require("../utils/config");
|
|
12
|
+
const fs_1 = require("../utils/fs");
|
|
13
|
+
const logger_1 = require("../utils/logger");
|
|
14
|
+
async function publishDraft(articlePath, coverPath, options) {
|
|
15
|
+
const config = await (0, config_1.loadConfig)();
|
|
16
|
+
if (!config.appid || !config.appsecret) {
|
|
17
|
+
(0, logger_1.error)('未配置微信认证信息,请先运行: wxgzh config --appid xxx --appsecret yyy');
|
|
18
|
+
}
|
|
19
|
+
const html = await (0, fs_1.readTextFile)(node_path_1.default.resolve(articlePath));
|
|
20
|
+
const metadata = (0, converter_1.readHtmlMetadata)(html);
|
|
21
|
+
const publishableContent = (0, converter_1.extractPublishableContent)(html);
|
|
22
|
+
const client = new wechat_1.WechatClient({ appid: config.appid, appsecret: config.appsecret });
|
|
23
|
+
const cover = await client.uploadCoverImage(node_path_1.default.resolve(coverPath));
|
|
24
|
+
return client.createDraft({
|
|
25
|
+
title: options?.title ?? metadata.title ?? node_path_1.default.basename(articlePath, node_path_1.default.extname(articlePath)),
|
|
26
|
+
author: options?.author ?? metadata.author ?? config.author ?? 'wxgzh',
|
|
27
|
+
digest: options?.digest ?? metadata.digest ?? '由 wxgzh 自动生成的公众号草稿',
|
|
28
|
+
content: publishableContent,
|
|
29
|
+
thumbMediaId: cover.mediaId,
|
|
30
|
+
enableComment: options?.enableComment ?? metadata.enableComment ?? config.enableComment ?? true
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function registerPublishCommand(program) {
|
|
34
|
+
program
|
|
35
|
+
.command('publish')
|
|
36
|
+
.description('把 HTML 正文和封面图提交到微信公众号草稿箱')
|
|
37
|
+
.usage('--article <article.html> --cover <cover.jpg> [options]')
|
|
38
|
+
.helpOption('-h, --help', '查看帮助')
|
|
39
|
+
.requiredOption('--article <article.html>', '已处理好的 HTML 正文路径')
|
|
40
|
+
.requiredOption('--cover <cover.jpg>', '封面图路径')
|
|
41
|
+
.option('--title <title>', '覆盖文章标题')
|
|
42
|
+
.option('--author <author>', '覆盖作者名')
|
|
43
|
+
.option('--digest <digest>', '覆盖文章摘要')
|
|
44
|
+
.option('--enable-comment', '为这篇草稿开启评论')
|
|
45
|
+
.addHelpText('after', '\n示例:\n $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg\n $ wxgzh publish --article .wxgzh/article.html --cover .wxgzh/cover.jpg --title "新的标题"\n')
|
|
46
|
+
.action(async (options) => {
|
|
47
|
+
const result = await publishDraft(options.article, options.cover, {
|
|
48
|
+
title: options.title,
|
|
49
|
+
author: options.author,
|
|
50
|
+
digest: options.digest,
|
|
51
|
+
enableComment: options.enableComment
|
|
52
|
+
});
|
|
53
|
+
(0, logger_1.success)('草稿创建成功');
|
|
54
|
+
console.log(JSON.stringify(result, null, 2));
|
|
55
|
+
});
|
|
56
|
+
}
|