@bhsd/common 0.11.0 → 0.13.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 ADDED
@@ -0,0 +1,4 @@
1
+ # @bhsd/common
2
+
3
+ [![npm version](https://badge.fury.io/js/@bhsd%2Fcommon.svg)](https://www.npmjs.com/package/@bhsd/common)
4
+ [![License](https://img.shields.io/badge/License-MIT-brightgreen.svg)](LICENSE)
package/dist/index.d.ts CHANGED
@@ -47,6 +47,16 @@ export declare const setObject: (key: string, value: unknown) => void;
47
47
  * @param baseVersion 基础版本号
48
48
  */
49
49
  export declare const compareVersion: (version: string, baseVersion: string) => boolean;
50
+ /**
51
+ * 加载 I18N
52
+ * @param url 下载地址
53
+ * @param cur 当前版本号
54
+ * @param languages 语言代码列表
55
+ * @param key 存储的键名
56
+ * @param i18n 已存储的I18N对象
57
+ * @throws `Error` 无法获取语言包
58
+ */
59
+ export declare const setI18N: (url: string, cur: string, languages: string[] | string, key: string, i18n?: Record<string, string>) => Promise<Record<string, string>>;
50
60
  /**
51
61
  * 加载 wikiparse
52
62
  * @param getConfig 获取解析配置的函数
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  rawurldecode: () => rawurldecode,
33
33
  refreshStdout: () => refreshStdout,
34
34
  sanitizeInlineStyle: () => sanitizeInlineStyle,
35
+ setI18N: () => setI18N,
35
36
  setObject: () => setObject,
36
37
  splitColors: () => splitColors
37
38
  });
@@ -114,6 +115,22 @@ const compareVersion = (version, baseVersion) => {
114
115
  const [major, minor] = parseVersion(version), [baseMajor, baseMinor] = parseVersion(baseVersion);
115
116
  return major > baseMajor || major === baseMajor && minor >= baseMinor;
116
117
  };
118
+ const setI18N = async (url, cur, languages, key, i18n = getObject(key) ?? {}) => {
119
+ const { version, lang } = i18n, langs = Array.isArray(languages) ? languages : [languages];
120
+ if (version === cur && langs.includes(lang)) {
121
+ return i18n;
122
+ }
123
+ for (const language of langs) {
124
+ try {
125
+ const res = await fetch(`${url}/${language.toLowerCase()}.json`);
126
+ Object.assign(i18n, await res.json(), { version: cur, lang: language });
127
+ setObject(key, i18n);
128
+ return i18n;
129
+ } catch {
130
+ }
131
+ }
132
+ throw new Error(`Failed to fetch the localization for ${langs[0]}.`);
133
+ };
117
134
  let configLoaded = false, i18nLoaded = false;
118
135
  const getWikiparse = async (getConfig, langs) => {
119
136
  const dir = "extensions/dist";
@@ -128,13 +145,11 @@ const getWikiparse = async (getConfig, langs) => {
128
145
  }
129
146
  if (!i18nLoaded && langs) {
130
147
  i18nLoaded = true;
131
- for (const lang of Array.isArray(langs) ? langs : [langs]) {
132
- try {
133
- const i18n = await (await fetch(`${wikiparse.CDN}/i18n/${lang.toLowerCase()}.json`)).json();
134
- wikiparse.setI18N(i18n);
135
- break;
136
- } catch {
137
- }
148
+ const key = "wikiparse-i18n", { version } = wikiparse;
149
+ try {
150
+ wikiparse.setI18N(await setI18N(`${wikiparse.CDN}/i18n`, version, langs, key));
151
+ } catch {
152
+ setObject(key, { version, lang: "en" });
138
153
  }
139
154
  }
140
155
  };
package/dist/index.mjs CHANGED
@@ -76,6 +76,22 @@ const compareVersion = (version, baseVersion) => {
76
76
  const [major, minor] = parseVersion(version), [baseMajor, baseMinor] = parseVersion(baseVersion);
77
77
  return major > baseMajor || major === baseMajor && minor >= baseMinor;
78
78
  };
79
+ const setI18N = async (url, cur, languages, key, i18n = getObject(key) ?? {}) => {
80
+ const { version, lang } = i18n, langs = Array.isArray(languages) ? languages : [languages];
81
+ if (version === cur && langs.includes(lang)) {
82
+ return i18n;
83
+ }
84
+ for (const language of langs) {
85
+ try {
86
+ const res = await fetch(`${url}/${language.toLowerCase()}.json`);
87
+ Object.assign(i18n, await res.json(), { version: cur, lang: language });
88
+ setObject(key, i18n);
89
+ return i18n;
90
+ } catch {
91
+ }
92
+ }
93
+ throw new Error(`Failed to fetch the localization for ${langs[0]}.`);
94
+ };
79
95
  let configLoaded = false, i18nLoaded = false;
80
96
  const getWikiparse = async (getConfig, langs) => {
81
97
  const dir = "extensions/dist";
@@ -90,13 +106,11 @@ const getWikiparse = async (getConfig, langs) => {
90
106
  }
91
107
  if (!i18nLoaded && langs) {
92
108
  i18nLoaded = true;
93
- for (const lang of Array.isArray(langs) ? langs : [langs]) {
94
- try {
95
- const i18n = await (await fetch(`${wikiparse.CDN}/i18n/${lang.toLowerCase()}.json`)).json();
96
- wikiparse.setI18N(i18n);
97
- break;
98
- } catch {
99
- }
109
+ const key = "wikiparse-i18n", { version } = wikiparse;
110
+ try {
111
+ wikiparse.setI18N(await setI18N(`${wikiparse.CDN}/i18n`, version, langs, key));
112
+ } catch {
113
+ setObject(key, { version, lang: "en" });
100
114
  }
101
115
  }
102
116
  };
@@ -143,6 +157,7 @@ export {
143
157
  rawurldecode,
144
158
  refreshStdout,
145
159
  sanitizeInlineStyle,
160
+ setI18N,
146
161
  setObject,
147
162
  splitColors
148
163
  };
package/dist/test.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ declare interface MediaWikiPage {
2
+ readonly pageid: number;
3
+ readonly title: string;
4
+ readonly ns: number;
5
+ readonly revisions?: {
6
+ readonly content: string;
7
+ readonly contentmodel: string;
8
+ }[];
9
+ }
10
+ export interface SimplePage extends Pick<MediaWikiPage, 'pageid' | 'title' | 'ns'> {
11
+ readonly content: string;
12
+ }
13
+ export interface Test {
14
+ desc: string;
15
+ wikitext?: string;
16
+ parsed?: string;
17
+ html?: string;
18
+ print?: string;
19
+ render?: string;
20
+ }
21
+ /**
22
+ * 获取最近更改的页面源代码
23
+ * @param url api.php网址
24
+ * @param site 站点名称
25
+ * @param grclimit 页面数上限
26
+ */
27
+ export declare const getPages: (url: string, site?: string, grclimit?: string) => Promise<SimplePage[]>;
28
+ /** 重置请求 */
29
+ export declare const reset: () => void;
30
+ /**
31
+ * 执行解析测试
32
+ * @param parse 解析函数
33
+ * @param retry 重试次数
34
+ * @param grclimit 页面数上限
35
+ */
36
+ export declare const execute: (parse: (wikitext: string) => unknown, retry?: number, grclimit?: string) => Promise<void>;
37
+ export declare const mochaTest: (results: unknown, parse: (wikitext: string) => string, beforeFn?: Mocha.Func | Mocha.AsyncFunc) => void;
38
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // test/test.ts
31
+ var test_exports = {};
32
+ __export(test_exports, {
33
+ execute: () => execute,
34
+ getPages: () => getPages,
35
+ mochaTest: () => mochaTest,
36
+ reset: () => reset
37
+ });
38
+ module.exports = __toCommonJS(test_exports);
39
+ var import_fs = __toESM(require("fs"));
40
+ var import_assert = __toESM(require("assert"));
41
+ var import_perf_hooks = require("perf_hooks");
42
+
43
+ // src/index.ts
44
+ var refreshStdout = (str) => {
45
+ process.stdout.write(`\x1B[K\x1B[?7l${str}\x1B[?7h\r`);
46
+ };
47
+
48
+ // test/test.ts
49
+ var apis = [
50
+ ["维基百科", "https://zh.wikipedia.org/w"],
51
+ ["Wikipedia", "https://en.wikipedia.org/w"],
52
+ ["ウィキペディア", "https://ja.wikipedia.org/w"]
53
+ ];
54
+ var c;
55
+ var getPages = async (url, site, grclimit = "max") => {
56
+ const qs = {
57
+ action: "query",
58
+ format: "json",
59
+ formatversion: "2",
60
+ errorformat: "plaintext",
61
+ generator: "recentchanges",
62
+ grcnamespace: site === "MediaWiki" ? "0|10|12|100|102|104|106" : "0|10",
63
+ grclimit,
64
+ grctype: "edit|new",
65
+ grctoponly: "1",
66
+ prop: "revisions",
67
+ rvprop: "contentmodel|content",
68
+ ...c
69
+ }, response = await (await fetch(`${url}?${String(new URLSearchParams(qs))}`)).json();
70
+ c = response.continue;
71
+ return response.query.pages.map(({ pageid, title, ns, revisions }) => ({
72
+ pageid,
73
+ title,
74
+ ns,
75
+ content: revisions?.[0]?.contentmodel === "wikitext" && revisions[0].content
76
+ })).filter((page) => page.content !== false);
77
+ };
78
+ var reset = () => {
79
+ c = void 0;
80
+ };
81
+ var execute = async (parse, retry = 10, grclimit) => {
82
+ const failures = /* @__PURE__ */ new Map();
83
+ for (const [site, url] of apis) {
84
+ console.log(`开始检查${site}:`);
85
+ let worst;
86
+ reset();
87
+ try {
88
+ let failed = 0, i = 0;
89
+ for (let j = 0; j < retry; j++) {
90
+ for (const { content, title } of await getPages(`${url}/api.php`, site, grclimit)) {
91
+ refreshStdout(`${i++} ${title}`);
92
+ try {
93
+ const start = import_perf_hooks.performance.now();
94
+ parse(content);
95
+ const duration = import_perf_hooks.performance.now() - start;
96
+ if (!worst || duration > worst.duration) {
97
+ worst = { title, duration };
98
+ }
99
+ } catch (e) {
100
+ console.error(`
101
+ 解析 ${title} 页面时出错!`, e);
102
+ failed++;
103
+ }
104
+ }
105
+ }
106
+ if (failed) {
107
+ failures.set(site, failed);
108
+ }
109
+ console.log(`
110
+ 最耗时页面:${worst.title} (${worst.duration.toFixed(3)}ms)`);
111
+ } catch (e) {
112
+ console.error(`访问${site}的API端口时出错!`, e);
113
+ }
114
+ }
115
+ if (failures.size > 0) {
116
+ let total = 0;
117
+ for (const [site, failed] of failures) {
118
+ console.error(`${site}:${failed} 个页面解析失败!`);
119
+ total += failed;
120
+ }
121
+ throw new Error(`共有 ${total} 个页面解析失败!`);
122
+ }
123
+ };
124
+ var tests = require("wikiparser-node/test/parserTests.json");
125
+ var split = (test) => test?.parsed?.split(/(?<=<\/>)(?!$)|(?<!^)(?=<\w)/u);
126
+ var mochaTest = (results, parse, beforeFn) => {
127
+ describe("Parser tests", () => {
128
+ for (let i = tests.length - 1; i >= 0; i--) {
129
+ const test = tests[i], { wikitext, desc } = test;
130
+ if (wikitext) {
131
+ it(desc, () => {
132
+ try {
133
+ delete test.html;
134
+ delete test.print;
135
+ delete test.render;
136
+ test.parsed = parse(wikitext);
137
+ import_assert.default.deepStrictEqual(
138
+ split(test),
139
+ split(results.find(({ desc: d }) => d === desc))
140
+ );
141
+ } catch (e) {
142
+ if (!(e instanceof import_assert.default.AssertionError)) {
143
+ tests.splice(i, 1);
144
+ }
145
+ if (e instanceof Error) {
146
+ Object.assign(e, { cause: { message: `
147
+ ${wikitext}` } });
148
+ }
149
+ throw e;
150
+ }
151
+ });
152
+ }
153
+ }
154
+ if (beforeFn) {
155
+ before(beforeFn);
156
+ }
157
+ after(() => {
158
+ import_fs.default.writeFileSync(
159
+ "test/parserTests.json",
160
+ `${JSON.stringify(tests, null, " ")}
161
+ `
162
+ );
163
+ });
164
+ });
165
+ };
166
+ // Annotate the CommonJS export names for ESM import in node:
167
+ 0 && (module.exports = {
168
+ execute,
169
+ getPages,
170
+ mochaTest,
171
+ reset
172
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhsd/common",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "author": "Bhsd",
6
6
  "files": [
@@ -24,17 +24,19 @@
24
24
  "import": "./dist/cm.mjs",
25
25
  "require": "./dist/cm.js"
26
26
  },
27
+ "./dist/test": {
28
+ "types": "./dist/test.d.ts",
29
+ "import": "./dist/test.js",
30
+ "require": "./dist/test.js"
31
+ },
27
32
  "./*.cjs": "./*.cjs",
28
33
  "./package.json": "./package.json"
29
34
  },
30
35
  "types": "./dist/index.d.ts",
31
36
  "sideEffects": false,
32
- "bin": {
33
- "copy-dev": "bin/dev.cjs"
34
- },
35
37
  "scripts": {
36
38
  "prepublishOnly": "npm run build",
37
- "build": "tsc --emitDeclarationOnly && esbuild src/* --charset=utf8 --target=es2024 --format=cjs --outdir=dist && esbuild src/* --charset=utf8 --target=es2024 --format=esm --outdir=dist --out-extension:.js=.mjs && rm dist/global.*",
39
+ "build": "tsc && esbuild src/* --charset=utf8 --target=es2024 --format=cjs --outdir=dist && esbuild test/* --charset=utf8 --target=es2024 --format=cjs --outdir=dist --bundle --platform=node --external:wikiparser-node && esbuild src/* --charset=utf8 --target=es2024 --format=esm --outdir=dist --out-extension:.js=.mjs && mv dist/*/*.d.ts dist/ && rm dist/global.*",
38
40
  "lint:ts": "tsc --noEmit && eslint --cache .",
39
41
  "lint": "npm run lint:ts"
40
42
  },
@@ -54,15 +56,12 @@
54
56
  "eslint-plugin-eslint-comments": "^3.2.0",
55
57
  "eslint-plugin-jsdoc": "^50.6.3",
56
58
  "eslint-plugin-json-es": "^1.6.0",
57
- "eslint-plugin-markdown": "4.0.1",
58
59
  "eslint-plugin-n": "^17.15.1",
59
60
  "eslint-plugin-promise": "^7.2.1",
60
61
  "eslint-plugin-regexp": "^2.7.0",
61
62
  "eslint-plugin-unicorn": "^56.0.1",
62
- "http-server": "^14.1.1",
63
- "mocha": "^11.1.0",
64
- "stylelint": "^16.19.1",
63
+ "stylelint": "^16.20.0",
65
64
  "typescript": "^5.8.2",
66
- "wikiparser-node": "^1.20.3"
65
+ "wikiparser-node": "^1.21.2"
67
66
  }
68
67
  }
package/bin/dev.cjs DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs'),
5
- {devDependencies, version} = require('@bhsd/common/package.json'),
6
- json = require(`${process.cwd()}/package.json`);
7
- const {name: n, dependencies, optionalDependencies} = json,
8
- others = {[n]: '', ...dependencies, ...optionalDependencies},
9
- dev = Object.fromEntries(
10
- Object.entries({...json.devDependencies, ...devDependencies}).filter(([k]) => !(k in others))
11
- .sort(([a], [b]) => a.localeCompare(b)),
12
- );
13
- (dependencies && '@bhsd/common' in dependencies ? dependencies : dev)['@bhsd/common'] = `^${version}`;
14
- json.devDependencies = dev;
15
- fs.writeFileSync('package.json', `${JSON.stringify(json, null, '\t')}\n`);