@wenyan-md/core 3.0.1 → 3.0.3

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/dist/core.js CHANGED
@@ -29,8 +29,8 @@ function stringToMap(str) {
29
29
  return map;
30
30
  }
31
31
  function replaceCSSVariables(css) {
32
- const variablePattern = /--([a-zA-Z0-9\-]+):\s*([^;()]*\((?:[^()]*|\([^()]*\))*\)[^;()]*|[^;]+);/g;
33
- const varPattern = /var\(--([a-zA-Z0-9\-]+)\)/g;
32
+ const variablePattern = /--([a-zA-Z0-9-]+):\s*([^;()]*\((?:[^()]*|\([^()]*\))*\)[^;()]*|[^;]+);/g;
33
+ const varPattern = /var\(--([a-zA-Z0-9-]+)\)/g;
34
34
  const cssVariables = {};
35
35
  let match;
36
36
  while ((match = variablePattern.exec(css)) !== null) {
@@ -48,7 +48,7 @@ function replaceCSSVariables(css) {
48
48
  if (resolved.has(value)) return value;
49
49
  resolved.add(value);
50
50
  let resolvedValue = value;
51
- const innerVarPattern = /var\(--([a-zA-Z0-9\-]+)\)/g;
51
+ const innerVarPattern = /var\(--([a-zA-Z0-9-]+)\)/g;
52
52
  resolvedValue = resolvedValue.replace(innerVarPattern, (match2, varName) => {
53
53
  if (variables[varName]) {
54
54
  return resolveVariable(variables[varName], variables, resolved);
@@ -61,7 +61,7 @@ function replaceCSSVariables(css) {
61
61
  const resolvedValue = resolveVariable(cssVariables[key], cssVariables);
62
62
  cssVariables[key] = resolvedValue;
63
63
  }
64
- let modifiedCSS = css.replace(varPattern, (match2, varName) => {
64
+ const modifiedCSS = css.replace(varPattern, (match2, varName) => {
65
65
  if (cssVariables[varName]) {
66
66
  return cssVariables[varName];
67
67
  }
@@ -285,7 +285,7 @@ function createCssApplier(css) {
285
285
  targets.forEach((el) => {
286
286
  declarations.forEach((decl) => {
287
287
  if (decl.type !== "Declaration") return;
288
- let value = csstree.generate(decl.value);
288
+ const value = csstree.generate(decl.value);
289
289
  const property = decl.property;
290
290
  const priority = decl.important ? "important" : "";
291
291
  el.style.setProperty(property, value, priority);
@@ -347,8 +347,8 @@ function buildPseudoElement(originalResults, document) {
347
347
  for (const [k, v] of beforeResults) {
348
348
  if (v.includes("url(")) {
349
349
  const svgMatch = v.match(/data:image\/svg\+xml;utf8,(.*<\/svg>)/);
350
- const base64SvgMatch = v.match(/data:image\/svg\+xml;base64,([^"'\)]*)["']?\)/);
351
- const httpMatch = v.match(/(?:"|')?(https?[^"'\)]*)(?:"|')?\)/);
350
+ const base64SvgMatch = v.match(/data:image\/svg\+xml;base64,([^"')]*)["']?\)/);
351
+ const httpMatch = v.match(/(?:"|')?(https?[^"')]*)(?:"|')?\)/);
352
352
  if (svgMatch) {
353
353
  const svgCode = decodeURIComponent(svgMatch[1]);
354
354
  section.innerHTML = svgCode;
@@ -587,6 +587,8 @@ function wechatPostRender(element) {
587
587
  }
588
588
  li.appendChild(section);
589
589
  });
590
+ element.style.color = "rgb(0, 0, 0)";
591
+ element.style.caretColor = "rgb(0, 0, 0)";
590
592
  }
591
593
  const themeModifier = createCssModifier({});
592
594
  function renderTheme(wenyanElement, themeCss) {
package/dist/publish.js CHANGED
@@ -62,6 +62,7 @@ class UploadCacheStore {
62
62
  cache = {};
63
63
  adapter;
64
64
  initPromise;
65
+ _saveQueue = Promise.resolve();
65
66
  constructor(adapter) {
66
67
  this.adapter = adapter;
67
68
  this.initPromise = this.load();
@@ -73,12 +74,13 @@ class UploadCacheStore {
73
74
  throw new Error(`无法加载上传缓存: ${error instanceof Error ? error.message : String(error)}`);
74
75
  }
75
76
  }
76
- async save() {
77
- try {
77
+ save() {
78
+ this._saveQueue = this._saveQueue.then(async () => {
78
79
  await this.adapter.saveCache(this.cache);
79
- } catch (error) {
80
- throw new Error(`无法保存上传缓存: ${error instanceof Error ? error.message : String(error)}`);
81
- }
80
+ }).catch((err) => {
81
+ console.error(err);
82
+ });
83
+ return this._saveQueue;
82
84
  }
83
85
  async waitForInit() {
84
86
  await this.initPromise;
@@ -154,6 +156,14 @@ class WechatPublisher {
154
156
  async publishToDraft(accessToken, options) {
155
157
  return await this.publishArticle(accessToken, options);
156
158
  }
159
+ async clearCache() {
160
+ if (this.tokenStore) {
161
+ await this.tokenStore.clear();
162
+ }
163
+ if (this.uploadCacheStore) {
164
+ await this.uploadCacheStore.clear();
165
+ }
166
+ }
157
167
  }
158
168
  export {
159
169
  TokenStore,
@@ -31,3 +31,4 @@ export * from "./platform/medium.js";
31
31
  export * from "./platform/zhihu.js";
32
32
  export * from "./platform/toutiao.js";
33
33
  export { addFootnotes } from "./renderer/footnotesRender.js";
34
+ export type WenyanCoreInstance = Awaited<ReturnType<typeof createWenyanCore>>;
@@ -1,5 +1,6 @@
1
1
  import { WechatPublishResponse } from "../wechat.js";
2
- import { ArticleOptions } from "../publish.js";
2
+ import { ArticleOptions, WechatPublisher } from "../publish.js";
3
+ export declare const wechatPublisher: WechatPublisher;
3
4
  interface PublishOptions {
4
5
  appId?: string;
5
6
  appSecret?: string;
@@ -19,6 +19,7 @@ export declare class WechatPublisher {
19
19
  getAccessTokenWithCache(appId: string, appSecret: string): Promise<string>;
20
20
  uploadImage(file: Blob, filename: string, accessToken: string): Promise<WechatUploadResponse>;
21
21
  publishToDraft(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
22
+ clearCache(): Promise<void>;
22
23
  }
23
24
  export * from "./tokenStore.js";
24
25
  export * from "./uploadCacheStore.js";
@@ -16,6 +16,7 @@ export declare class UploadCacheStore {
16
16
  private cache;
17
17
  private adapter;
18
18
  private initPromise;
19
+ private _saveQueue;
19
20
  constructor(adapter: UploadCacheStorageAdapter);
20
21
  private load;
21
22
  private save;
package/dist/wrapper.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import path from "node:path";
2
- import { Readable } from "node:stream";
3
- import { FormData, File } from "formdata-node";
4
- import { FormDataEncoder } from "form-data-encoder";
2
+ import http from "node:http";
3
+ import https from "node:https";
5
4
  import { JSDOM } from "jsdom";
6
5
  import fs, { stat } from "node:fs/promises";
7
6
  import crypto from "node:crypto";
8
7
  import { fileFromPath } from "formdata-node/file-from-path";
8
+ import { FormDataEncoder } from "form-data-encoder";
9
+ import { FormData } from "formdata-node";
10
+ import { Readable } from "node:stream";
9
11
  import os from "node:os";
10
12
  import { defaultTokenCache, WechatPublisher } from "./publish.js";
11
13
  import { createWenyanCore, getAllGzhThemes } from "./core.js";
@@ -107,6 +109,72 @@ const RuntimeEnv = {
107
109
  return normalizedInput;
108
110
  }
109
111
  };
112
+ async function chunkedUpload(serverUrl, headers, fileBuffer, filename, mimeType) {
113
+ const url = new URL(`${serverUrl}/upload`);
114
+ const boundary = "----FormBoundary" + Math.random().toString(36).substring(2);
115
+ const headerPart = `--${boundary}\r
116
+ Content-Disposition: form-data; name="file"; filename="${filename}"\r
117
+ Content-Type: ${mimeType}\r
118
+ \r
119
+ `;
120
+ const footerPart = `\r
121
+ --${boundary}--\r
122
+ `;
123
+ const headerBuf = Buffer.from(headerPart, "utf-8");
124
+ const footerBuf = Buffer.from(footerPart, "utf-8");
125
+ return new Promise((resolve, reject) => {
126
+ const requestModule = url.protocol === "https:" ? https : http;
127
+ const defaultPort = url.protocol === "https:" ? 443 : 80;
128
+ const req = requestModule.request(
129
+ {
130
+ hostname: url.hostname,
131
+ port: url.port || defaultPort,
132
+ path: url.pathname,
133
+ method: "POST",
134
+ headers: {
135
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
136
+ "Transfer-Encoding": "chunked",
137
+ ...headers
138
+ }
139
+ },
140
+ (res) => {
141
+ let body = "";
142
+ res.on("data", (chunk) => body += chunk);
143
+ res.on("end", () => {
144
+ if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
145
+ reject(new Error(`Server returned status ${res.statusCode}: ${body}`));
146
+ return;
147
+ }
148
+ try {
149
+ resolve(JSON.parse(body));
150
+ } catch (_e) {
151
+ reject(new Error(`Invalid server response: ${body}`));
152
+ }
153
+ });
154
+ }
155
+ );
156
+ req.on("error", reject);
157
+ req.write(headerBuf);
158
+ const CHUNK = 65536;
159
+ let offset = 0;
160
+ function writeNext() {
161
+ if (offset >= fileBuffer.length) {
162
+ req.write(footerBuf);
163
+ req.end();
164
+ return;
165
+ }
166
+ const end = Math.min(offset + CHUNK, fileBuffer.length);
167
+ if (req.write(fileBuffer.subarray(offset, end))) {
168
+ offset = end;
169
+ writeNext();
170
+ } else {
171
+ offset = end;
172
+ req.once("drain", writeNext);
173
+ }
174
+ }
175
+ writeNext();
176
+ });
177
+ }
110
178
  function getServerUrl(options) {
111
179
  let serverUrl = options.server || "http://localhost:3000";
112
180
  serverUrl = serverUrl.replace(/\/$/, "");
@@ -156,21 +224,10 @@ async function verifyAuth(serverUrl, headers) {
156
224
  }
157
225
  async function uploadStyledContent(gzhContent, serverUrl, headers) {
158
226
  const mdFilename = "publish_target.json";
159
- const mdForm = new FormData();
160
- mdForm.append(
161
- "file",
162
- new File([Buffer.from(JSON.stringify(gzhContent), "utf-8")], mdFilename, { type: "application/json" })
163
- );
164
- const mdEncoder = new FormDataEncoder(mdForm);
165
- const mdUploadRes = await fetch(`${serverUrl}/upload`, {
166
- method: "POST",
167
- headers: { ...headers, ...mdEncoder.headers },
168
- body: Readable.from(mdEncoder),
169
- duplex: "half"
170
- });
171
- const mdUploadData = await mdUploadRes.json();
172
- if (!mdUploadRes.ok || !mdUploadData.success) {
173
- throw new Error(`Upload Document Failed: ${mdUploadData.error || mdUploadData.desc || mdUploadRes.statusText}`);
227
+ const fileBuffer = Buffer.from(JSON.stringify(gzhContent), "utf-8");
228
+ const mdUploadData = await chunkedUpload(serverUrl, headers, fileBuffer, mdFilename, "application/json");
229
+ if (!mdUploadData.success) {
230
+ throw new Error(`Upload Document Failed: ${mdUploadData.error || mdUploadData.desc}`);
174
231
  }
175
232
  const mdFileId = mdUploadData.data.fileId;
176
233
  return mdFileId;
@@ -224,17 +281,8 @@ async function uploadLocalImage(originalUrl, serverUrl, headers, relativePath) {
224
281
  ".svg": "image/svg+xml"
225
282
  };
226
283
  const type = mimeTypes[ext] || "application/octet-stream";
227
- const form = new FormData();
228
- form.append("file", new File([fileBuffer], filename, { type }));
229
- const encoder = new FormDataEncoder(form);
230
- const uploadRes = await fetch(`${serverUrl}/upload`, {
231
- method: "POST",
232
- headers: { ...headers, ...encoder.headers },
233
- body: Readable.from(encoder),
234
- duplex: "half"
235
- });
236
- const uploadData = await uploadRes.json();
237
- if (uploadRes.ok && uploadData.success) {
284
+ const uploadData = await chunkedUpload(serverUrl, headers, fileBuffer, filename, type);
285
+ if (uploadData.success) {
238
286
  return `asset://${uploadData.data.fileId}`;
239
287
  } else {
240
288
  console.error(`[Client] Warning: Failed to upload ${filename}: ${uploadData.error || uploadData.desc}`);
@@ -469,7 +517,7 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
469
517
  }
470
518
  const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
471
519
  const { html, firstImageId } = await uploadImages(content, accessToken, relativePath);
472
- let thumbMediaId = "";
520
+ let thumbMediaId;
473
521
  if (cover) {
474
522
  const cachedThumbMediaId = mediaIdMapping.get(cover);
475
523
  if (cachedThumbMediaId) {
@@ -681,5 +729,6 @@ export {
681
729
  renderStyledContent,
682
730
  renderWithTheme,
683
731
  safeReadJson,
684
- safeWriteJson
732
+ safeWriteJson,
733
+ wechatPublisher
685
734
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/core",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Core library for Wenyan markdown rendering & publishing",
5
5
  "author": "Lei <caol64@gmail.com> (https://github.com/caol64)",
6
6
  "license": "Apache-2.0",
@@ -48,10 +48,14 @@
48
48
  }
49
49
  },
50
50
  "devDependencies": {
51
+ "@eslint/js": "^10.0.1",
51
52
  "@types/css-tree": "^2.3.11",
52
53
  "@types/jsdom": "^27.0.0",
53
54
  "@types/node": "^24.3.0",
55
+ "eslint": "^10.1.0",
56
+ "globals": "^17.4.0",
54
57
  "typescript": "^5.9.2",
58
+ "typescript-eslint": "^8.58.0",
55
59
  "vite": "^7.1.4",
56
60
  "vitest": "^3.2.4"
57
61
  },
@@ -80,12 +84,10 @@
80
84
  }
81
85
  },
82
86
  "scripts": {
83
- "check": "tsc --noEmit",
87
+ "typecheck": "tsc --noEmit",
84
88
  "build": "vite build && tsc",
85
- "test": "pnpm build && vitest",
86
- "test:wrapper": "vitest run tests/wrapper.test.ts",
87
- "test:publish": "vitest run tests/publish.test.ts",
88
- "test:realPublish": "vitest run tests/realPublish.test.ts",
89
- "test:runtimeEnv": "vitest run tests/runtimeEnv.test.ts"
89
+ "lint": "eslint . --fix",
90
+ "test": "vitest run",
91
+ "test:watch": "vitest"
90
92
  }
91
93
  }