feat: 分类锚点&质量检查&依赖治理
- 分类生成唯一 slug,模板/子菜单/滚动/扩展定位统一使用 data-id - lint 覆盖 src/scripts/test,CI 增量格式检查 - 清理冗余依赖,升级 esbuild,overrides 修复审计项 - 补充单测并更新修复清单
This commit is contained in:
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -2,7 +2,7 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@@ -11,6 +11,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -21,6 +23,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Format check (changed files)
|
||||
run: npm run format:check:changed
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
|
||||
347
package-lock.json
generated
347
package-lock.json
generated
@@ -9,27 +9,20 @@
|
||||
"version": "1.3.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"color-convert": "^2.0.1",
|
||||
"color-name": "^2.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"has-flag": "^5.0.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"mime-db": "^1.52.0",
|
||||
"rss-parser": "^3.13.0",
|
||||
"supports-color": "^9.4.0"
|
||||
"rss-parser": "^3.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.20.2",
|
||||
"esbuild": "^0.27.2",
|
||||
"prettier": "^3.4.2",
|
||||
"serve": "^14.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -40,13 +33,13 @@
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -57,13 +50,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -74,13 +67,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -91,13 +84,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -108,13 +101,13 @@
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -125,13 +118,13 @@
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -142,13 +135,13 @@
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -159,13 +152,13 @@
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -176,13 +169,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -193,13 +186,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -210,13 +203,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -227,13 +220,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -244,13 +237,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -261,13 +254,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -278,13 +271,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -295,13 +288,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -312,13 +305,30 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -329,13 +339,30 @@
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -346,13 +373,30 @@
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -363,13 +407,13 @@
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -380,13 +424,13 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -397,13 +441,13 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -414,7 +458,7 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@zeit/schemas": {
|
||||
@@ -500,6 +544,7 @@
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -512,6 +557,7 @@
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -585,9 +631,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -738,6 +784,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
@@ -750,17 +797,9 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.0.tgz",
|
||||
"integrity": "sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
@@ -879,9 +918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -889,32 +928,35 @@
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.20.2",
|
||||
"@esbuild/android-arm": "0.20.2",
|
||||
"@esbuild/android-arm64": "0.20.2",
|
||||
"@esbuild/android-x64": "0.20.2",
|
||||
"@esbuild/darwin-arm64": "0.20.2",
|
||||
"@esbuild/darwin-x64": "0.20.2",
|
||||
"@esbuild/freebsd-arm64": "0.20.2",
|
||||
"@esbuild/freebsd-x64": "0.20.2",
|
||||
"@esbuild/linux-arm": "0.20.2",
|
||||
"@esbuild/linux-arm64": "0.20.2",
|
||||
"@esbuild/linux-ia32": "0.20.2",
|
||||
"@esbuild/linux-loong64": "0.20.2",
|
||||
"@esbuild/linux-mips64el": "0.20.2",
|
||||
"@esbuild/linux-ppc64": "0.20.2",
|
||||
"@esbuild/linux-riscv64": "0.20.2",
|
||||
"@esbuild/linux-s390x": "0.20.2",
|
||||
"@esbuild/linux-x64": "0.20.2",
|
||||
"@esbuild/netbsd-x64": "0.20.2",
|
||||
"@esbuild/openbsd-x64": "0.20.2",
|
||||
"@esbuild/sunos-x64": "0.20.2",
|
||||
"@esbuild/win32-arm64": "0.20.2",
|
||||
"@esbuild/win32-ia32": "0.20.2",
|
||||
"@esbuild/win32-x64": "0.20.2"
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
@@ -982,18 +1024,6 @@
|
||||
"uglify-js": "^3.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz",
|
||||
"integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
@@ -1113,6 +1143,7 @@
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -1517,18 +1548,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
|
||||
"integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
|
||||
|
||||
15
package.json
15
package.json
@@ -14,9 +14,10 @@
|
||||
"sync-projects": "node ./scripts/sync-projects.js",
|
||||
"import-bookmarks": "node src/bookmark-processor.js",
|
||||
"test": "node --test test/*.js",
|
||||
"lint": "node --check \"src/generator.js\" && node --check \"src/bookmark-processor.js\" && node --check \"src/script.js\"",
|
||||
"lint": "node ./scripts/lint.js",
|
||||
"format": "prettier --write \"src/**/*.js\" \"scripts/**/*.js\" \"test/**/*.js\" \".github/**/*.yml\" \"*.{md,json}\" \"config/**/*.md\" \"config/**/*.yml\"",
|
||||
"format:check": "prettier --check \"src/**/*.js\" \"scripts/**/*.js\" \"test/**/*.js\" \".github/**/*.yml\" \"*.{md,json}\" \"config/**/*.md\" \"config/**/*.yml\"",
|
||||
"format:check:changed": "node ./scripts/format-check-changed.js",
|
||||
"check": "npm run lint && npm test && npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
@@ -29,18 +30,14 @@
|
||||
"dependencies": {
|
||||
"js-yaml": "^4.1.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"ansi-regex": "^6.0.1",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"supports-color": "^9.4.0",
|
||||
"has-flag": "^5.0.1",
|
||||
"color-convert": "^2.0.1",
|
||||
"color-name": "^2.0.0",
|
||||
"mime-db": "^1.52.0",
|
||||
"rss-parser": "^3.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.20.2",
|
||||
"esbuild": "^0.27.2",
|
||||
"prettier": "^3.4.2",
|
||||
"serve": "^14.2.5"
|
||||
},
|
||||
"overrides": {
|
||||
"brace-expansion": "1.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
131
scripts/format-check-changed.js
Normal file
131
scripts/format-check-changed.js
Normal file
@@ -0,0 +1,131 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
|
||||
function runGit(args, cwd) {
|
||||
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
|
||||
}
|
||||
|
||||
function tryReadGithubEvent(eventPath) {
|
||||
if (!eventPath) return null;
|
||||
try {
|
||||
const raw = fs.readFileSync(eventPath, 'utf8');
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isAllZerosSha(value) {
|
||||
return typeof value === 'string' && /^0{40}$/.test(value);
|
||||
}
|
||||
|
||||
function getDiffRangeFromGithubEvent(event) {
|
||||
if (!event || typeof event !== 'object') return null;
|
||||
|
||||
if (event.pull_request && event.pull_request.base && event.pull_request.head) {
|
||||
const base = event.pull_request.base.sha;
|
||||
const head = event.pull_request.head.sha;
|
||||
if (base && head) return { base, head };
|
||||
}
|
||||
|
||||
if (event.before && (event.after || event.head_commit)) {
|
||||
const base = event.before;
|
||||
const head = event.after || (event.head_commit && event.head_commit.id);
|
||||
if (base && head && !isAllZerosSha(base)) return { base, head };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectChangedFiles(repoRoot, range) {
|
||||
if (!range) return [];
|
||||
const output = runGit(
|
||||
['diff', '--name-only', '--diff-filter=ACMR', `${range.base}..${range.head}`],
|
||||
repoRoot
|
||||
);
|
||||
return output
|
||||
? output
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
}
|
||||
|
||||
function collectWorkingTreeChangedFiles(repoRoot) {
|
||||
const files = new Set();
|
||||
const unstaged = runGit(['diff', '--name-only', '--diff-filter=ACMR', 'HEAD'], repoRoot);
|
||||
const staged = runGit(['diff', '--cached', '--name-only', '--diff-filter=ACMR'], repoRoot);
|
||||
|
||||
[unstaged, staged].forEach((block) => {
|
||||
if (!block) return;
|
||||
block
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.forEach((filePath) => files.add(filePath));
|
||||
});
|
||||
|
||||
return Array.from(files).sort();
|
||||
}
|
||||
|
||||
function shouldCheckFile(filePath) {
|
||||
const normalized = filePath.split(path.sep).join('/');
|
||||
|
||||
if (normalized === 'package-lock.json') return false;
|
||||
|
||||
// 这两个文件历史上未统一为 Prettier 风格;避免为了启用检查产生巨量格式化 diff
|
||||
if (normalized === 'src/generator.js' || normalized === 'src/script.js') return false;
|
||||
|
||||
// 与现有 npm scripts 的检查范围对齐:不检查 docs/ 与 templates/
|
||||
const allowedRoots = ['src/', 'scripts/', 'test/', '.github/', 'config/'];
|
||||
const isRootFile = !normalized.includes('/');
|
||||
const hasAllowedRoot = allowedRoots.some((prefix) => normalized.startsWith(prefix));
|
||||
|
||||
const isAllowedPath =
|
||||
hasAllowedRoot || (isRootFile && (normalized.endsWith('.md') || normalized.endsWith('.json')));
|
||||
|
||||
if (!isAllowedPath) return false;
|
||||
|
||||
const ext = path.extname(normalized).toLowerCase();
|
||||
return ['.js', '.json', '.md', '.yml', '.yaml'].includes(ext);
|
||||
}
|
||||
|
||||
function resolvePrettierBin(repoRoot) {
|
||||
const base = path.join(repoRoot, 'node_modules', '.bin', 'prettier');
|
||||
if (fs.existsSync(base)) return base;
|
||||
if (fs.existsSync(`${base}.cmd`)) return `${base}.cmd`;
|
||||
return null;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
|
||||
const event = tryReadGithubEvent(process.env.GITHUB_EVENT_PATH);
|
||||
const range = getDiffRangeFromGithubEvent(event);
|
||||
|
||||
const candidateFiles = range
|
||||
? collectChangedFiles(repoRoot, range)
|
||||
: collectWorkingTreeChangedFiles(repoRoot);
|
||||
|
||||
const filesToCheck = candidateFiles.filter(shouldCheckFile);
|
||||
|
||||
if (filesToCheck.length === 0) {
|
||||
console.log('格式检查:未发现需要检查的文件,跳过。');
|
||||
return;
|
||||
}
|
||||
|
||||
const prettierBin = resolvePrettierBin(repoRoot);
|
||||
if (!prettierBin) {
|
||||
console.error('格式检查失败:未找到 prettier,可先运行 npm ci / npm install。');
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`格式检查:共 ${filesToCheck.length} 个文件`);
|
||||
filesToCheck.forEach((filePath) => console.log(`- ${filePath}`));
|
||||
|
||||
execFileSync(prettierBin, ['--check', ...filesToCheck], { cwd: repoRoot, stdio: 'inherit' });
|
||||
}
|
||||
|
||||
main();
|
||||
67
scripts/lint.js
Normal file
67
scripts/lint.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
|
||||
function collectJsFiles(rootDir) {
|
||||
const files = [];
|
||||
|
||||
const walk = (currentDir) => {
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
entries.forEach((entry) => {
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === 'node_modules' || entry.name === 'dist') return;
|
||||
walk(fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.isFile() && entry.name.endsWith('.js')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
walk(rootDir);
|
||||
return files;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
const targetDirs = ['src', 'scripts', 'test'].map((dir) => path.join(projectRoot, dir));
|
||||
|
||||
const jsFiles = targetDirs.flatMap((dir) => collectJsFiles(dir)).sort();
|
||||
|
||||
if (jsFiles.length === 0) {
|
||||
console.log('未发现需要检查的 .js 文件,跳过。');
|
||||
return;
|
||||
}
|
||||
|
||||
let hasError = false;
|
||||
jsFiles.forEach((filePath) => {
|
||||
const relativePath = path.relative(projectRoot, filePath);
|
||||
try {
|
||||
execFileSync(process.execPath, ['--check', filePath], { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
console.error(`\n语法检查失败:${relativePath}`);
|
||||
if (error && error.status) {
|
||||
console.error(`退出码:${error.status}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hasError) {
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log(`语法检查通过:${jsFiles.length} 个文件`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -413,6 +413,46 @@ function getSubmenuForNavItem(navItem, config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function makeCategorySlugBase(name) {
|
||||
const raw = typeof name === 'string' ? name : String(name ?? '');
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return 'category';
|
||||
|
||||
// 规则:尽量可读、跨平台稳定;保留字母/数字/下划线/短横线,其它字符替换为短横线
|
||||
// 注意:分类名允许中文等非 ASCII 字符,Node 18+ 支持 Unicode 属性类
|
||||
const normalized = trimmed
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase()
|
||||
.replace(/[^\p{L}\p{N}_-]+/gu, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
|
||||
return normalized || 'category';
|
||||
}
|
||||
|
||||
function makeUniqueSlug(base, usedSlugs) {
|
||||
const current = usedSlugs.get(base) || 0;
|
||||
const next = current + 1;
|
||||
usedSlugs.set(base, next);
|
||||
return next === 1 ? base : `${base}-${next}`;
|
||||
}
|
||||
|
||||
function assignCategorySlugs(categories, usedSlugs) {
|
||||
if (!Array.isArray(categories)) return;
|
||||
|
||||
categories.forEach(category => {
|
||||
if (!category || typeof category !== 'object') return;
|
||||
|
||||
const base = makeCategorySlugBase(category.name);
|
||||
const uniqueSlug = makeUniqueSlug(base, usedSlugs);
|
||||
category.slug = uniqueSlug;
|
||||
|
||||
if (Array.isArray(category.subcategories)) {
|
||||
assignCategorySlugs(category.subcategories, usedSlugs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 字符串安全嵌入到 <script> 中,避免出现 `</script>` 结束标签导致脚本块被提前终止。
|
||||
* 说明:返回值仍是合法 JSON,JSON.parse 后数据不变。
|
||||
@@ -825,6 +865,16 @@ function prepareRenderData(config) {
|
||||
// 首页(默认页)规则:navigation 顺序第一项即首页
|
||||
renderData.homePageId = renderData.navigation && renderData.navigation[0] ? renderData.navigation[0].id : null;
|
||||
|
||||
// 为每个页面的分类生成稳定锚点 slug(解决重名/空格/特殊字符导致的 hash 冲突)
|
||||
if (Array.isArray(renderData.navigation)) {
|
||||
renderData.navigation.forEach(navItem => {
|
||||
const pageConfig = renderData[navItem.id];
|
||||
if (pageConfig && Array.isArray(pageConfig.categories)) {
|
||||
assignCategorySlugs(pageConfig.categories, new Map());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加序列化的配置数据,用于浏览器扩展(确保包含 homePageId 等处理结果)
|
||||
renderData.configJSON = makeJsonSafeForHtmlScript(
|
||||
JSON.stringify({
|
||||
@@ -1382,6 +1432,11 @@ function renderPage(pageId, config) {
|
||||
if (config.profile.subtitle !== undefined) data.subtitle = config.profile.subtitle;
|
||||
}
|
||||
|
||||
// 分类锚点:为当前页面分类生成稳定 slug(用于 id/hash,避免重名/特殊字符冲突)
|
||||
if (Array.isArray(data.categories) && data.categories.length > 0) {
|
||||
assignCategorySlugs(data.categories, new Map());
|
||||
}
|
||||
|
||||
if (config[pageId] && config[pageId].template) {
|
||||
console.log(`页面 ${pageId} 使用指定模板: ${templateName}`);
|
||||
}
|
||||
|
||||
145
src/script.js
145
src/script.js
@@ -153,30 +153,42 @@ window.MeNav = {
|
||||
return menavConfigCacheValue;
|
||||
},
|
||||
|
||||
// 获取元素的唯一标识符
|
||||
_getElementId: function(element) {
|
||||
const type = element.getAttribute('data-type');
|
||||
if (type === 'nav-item') {
|
||||
return element.getAttribute('data-id');
|
||||
} else if (type === 'social-link') {
|
||||
return element.getAttribute('data-url');
|
||||
} else {
|
||||
return element.getAttribute('data-name');
|
||||
}
|
||||
},
|
||||
// 获取元素的唯一标识符
|
||||
_getElementId: function(element) {
|
||||
const type = element.getAttribute('data-type');
|
||||
if (type === 'nav-item') {
|
||||
return element.getAttribute('data-id');
|
||||
} else if (type === 'social-link') {
|
||||
return element.getAttribute('data-url');
|
||||
} else {
|
||||
// 优先使用 data-id(例如分类 slug),回退 data-name(兼容旧扩展/旧页面)
|
||||
return element.getAttribute('data-id') || element.getAttribute('data-name');
|
||||
}
|
||||
},
|
||||
|
||||
// 根据类型和ID查找元素
|
||||
_findElement: function(type, id) {
|
||||
let selector;
|
||||
if (type === 'nav-item') {
|
||||
selector = `[data-type="${type}"][data-id="${id}"]`;
|
||||
} else if (type === 'social-link') {
|
||||
selector = `[data-type="${type}"][data-url="${id}"]`;
|
||||
} else {
|
||||
selector = `[data-type="${type}"][data-name="${id}"]`;
|
||||
}
|
||||
return document.querySelector(selector);
|
||||
},
|
||||
// 根据类型和ID查找元素
|
||||
_findElement: function(type, id) {
|
||||
let selector;
|
||||
if (type === 'nav-item') {
|
||||
selector = `[data-type="${type}"][data-id="${id}"]`;
|
||||
} else if (type === 'social-link') {
|
||||
selector = `[data-type="${type}"][data-url="${id}"]`;
|
||||
} else if (type === 'site') {
|
||||
// 站点:优先用 data-url(更稳定),回退 data-id/data-name
|
||||
return (
|
||||
document.querySelector(`[data-type="${type}"][data-url="${id}"]`) ||
|
||||
document.querySelector(`[data-type="${type}"][data-id="${id}"]`) ||
|
||||
document.querySelector(`[data-type="${type}"][data-name="${id}"]`)
|
||||
);
|
||||
} else {
|
||||
// 其他:优先 data-id(例如分类 slug),回退 data-name(兼容旧扩展/旧页面)
|
||||
return (
|
||||
document.querySelector(`[data-type="${type}"][data-id="${id}"]`) ||
|
||||
document.querySelector(`[data-type="${type}"][data-name="${id}"]`)
|
||||
);
|
||||
}
|
||||
return document.querySelector(selector);
|
||||
},
|
||||
|
||||
// 更新DOM元素
|
||||
updateElement: function(type, id, newData) {
|
||||
@@ -1929,19 +1941,33 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 子菜单项点击效果
|
||||
submenuItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
// 子菜单项点击效果
|
||||
submenuItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 获取页面ID和分类名称
|
||||
const pageId = item.getAttribute('data-page');
|
||||
const categoryName = item.getAttribute('data-category');
|
||||
// 获取页面ID和分类名称
|
||||
const pageId = item.getAttribute('data-page');
|
||||
const categoryName = item.getAttribute('data-category');
|
||||
const categoryId = item.getAttribute('data-category-id');
|
||||
|
||||
if (pageId) {
|
||||
// 清除所有子菜单项的激活状态
|
||||
submenuItems.forEach(subItem => {
|
||||
subItem.classList.remove('active');
|
||||
const escapeSelector = value => {
|
||||
if (value === null || value === undefined) return '';
|
||||
const text = String(value);
|
||||
if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text);
|
||||
// 回退:尽量避免打断选择器(不追求完全覆盖所有边界字符)
|
||||
return text.replace(/[^a-zA-Z0-9_\u00A0-\uFFFF-]/g, '\\$&');
|
||||
};
|
||||
|
||||
const escapeAttrValue = value => {
|
||||
if (value === null || value === undefined) return '';
|
||||
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
};
|
||||
|
||||
if (pageId) {
|
||||
// 清除所有子菜单项的激活状态
|
||||
submenuItems.forEach(subItem => {
|
||||
subItem.classList.remove('active');
|
||||
});
|
||||
|
||||
// 激活当前子菜单项
|
||||
@@ -1955,20 +1981,45 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// 显示对应页面
|
||||
showPage(pageId);
|
||||
|
||||
// 等待页面切换完成后滚动到对应分类
|
||||
setTimeout(() => {
|
||||
// 查找目标分类元素
|
||||
const targetPage = document.getElementById(pageId);
|
||||
if (targetPage) {
|
||||
const targetCategory = Array.from(targetPage.querySelectorAll('.category h2')).find(
|
||||
heading => heading.textContent.trim().includes(categoryName)
|
||||
);
|
||||
// 等待页面切换完成后滚动到对应分类
|
||||
setTimeout(() => {
|
||||
// 查找目标分类元素
|
||||
const targetPage = document.getElementById(pageId);
|
||||
if (targetPage) {
|
||||
let targetCategory = null;
|
||||
|
||||
if (targetCategory) {
|
||||
// 优化的滚动实现:滚动到使目标分类位于视口1/4处(更靠近顶部位置)
|
||||
try {
|
||||
// 直接获取所需元素和属性,减少重复查询
|
||||
const contentElement = document.querySelector('.content');
|
||||
// 优先使用 slug/data-id 精准定位(解决重复命名始终命中第一个的问题)
|
||||
if (categoryId) {
|
||||
const escapedId = escapeSelector(categoryId);
|
||||
targetCategory =
|
||||
targetPage.querySelector(`#${escapedId}`) ||
|
||||
targetPage.querySelector(
|
||||
`[data-type="category"][data-id="${escapeAttrValue(categoryId)}"]`
|
||||
);
|
||||
}
|
||||
|
||||
// 回退:旧逻辑按文本包含匹配(兼容旧页面/旧数据)
|
||||
if (!targetCategory && categoryName) {
|
||||
targetCategory = Array.from(targetPage.querySelectorAll('.category h2')).find(
|
||||
heading => heading.textContent.trim().includes(categoryName)
|
||||
);
|
||||
}
|
||||
|
||||
if (targetCategory) {
|
||||
// 由于对子菜单 click 做了 preventDefault,这里手动同步 hash(不触发浏览器默认跳转)
|
||||
const nextHash = categoryId || categoryName;
|
||||
if (nextHash) {
|
||||
try {
|
||||
history.replaceState(null, '', `#${nextHash}`);
|
||||
} catch (error) {
|
||||
// 忽略 history API 失败,避免影响滚动体验
|
||||
}
|
||||
}
|
||||
|
||||
// 优化的滚动实现:滚动到使目标分类位于视口1/4处(更靠近顶部位置)
|
||||
try {
|
||||
// 直接获取所需元素和属性,减少重复查询
|
||||
const contentElement = document.querySelector('.content');
|
||||
|
||||
if (contentElement && contentElement.scrollHeight > contentElement.clientHeight) {
|
||||
// 获取目标元素相对于内容区域的位置
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<section class="category {{#if level}}category-level-{{level}}{{else}}category-level-1{{/if}}"
|
||||
id="{{name}}"
|
||||
id="{{#if slug}}{{slug}}{{else}}{{name}}{{/if}}"
|
||||
data-type="category"
|
||||
data-id="{{#if slug}}{{slug}}{{else}}{{name}}{{/if}}"
|
||||
data-name="{{name}}"
|
||||
data-icon="{{icon}}"
|
||||
data-level="{{#if level}}{{level}}{{else}}1{{/if}}"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{{#if submenu}}
|
||||
<div class="submenu">
|
||||
{{#each submenu}}
|
||||
<a href="#{{name}}" class="submenu-item" data-page="{{../id}}" data-category="{{name}}">
|
||||
<a href="#{{#if slug}}{{slug}}{{else}}{{name}}{{/if}}" class="submenu-item" data-page="{{../id}}" data-category="{{name}}" data-category-id="{{#if slug}}{{slug}}{{else}}{{name}}{{/if}}">
|
||||
<i class="{{icon}}"></i>
|
||||
<span>{{name}}</span>
|
||||
</a>
|
||||
|
||||
48
test/category-slug-dedup.node-test.js
Normal file
48
test/category-slug-dedup.node-test.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const { loadHandlebarsTemplates, generateAllPagesHTML } = require('../src/generator.js');
|
||||
|
||||
function withRepoRoot(fn) {
|
||||
const originalCwd = process.cwd();
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
}
|
||||
|
||||
test('P1-2:分类 slug 应稳定且可去重', () => {
|
||||
withRepoRoot(() => {
|
||||
loadHandlebarsTemplates();
|
||||
|
||||
const config = {
|
||||
site: { title: 'Test Site', description: '', author: '', favicon: '', logo_text: 'Test' },
|
||||
profile: { title: 'PROFILE_TITLE', subtitle: 'PROFILE_SUBTITLE' },
|
||||
social: [],
|
||||
navigation: [{ id: 'home', name: '首页', icon: 'fas fa-home' }],
|
||||
home: {
|
||||
title: 'HOME',
|
||||
subtitle: 'HOME_SUB',
|
||||
template: 'page',
|
||||
categories: [
|
||||
{ name: '重复 分类', icon: 'fas fa-tag', sites: [] },
|
||||
{ name: '重复 分类', icon: 'fas fa-tag', sites: [] },
|
||||
{ name: '含 空格/特殊#字符', icon: 'fas fa-tag', sites: [] },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const pages = generateAllPagesHTML(config);
|
||||
assert.ok(typeof pages.home === 'string' && pages.home.length > 0);
|
||||
|
||||
assert.ok(pages.home.includes('id="重复-分类"'), '首个重复分类应生成稳定 slug');
|
||||
assert.ok(pages.home.includes('id="重复-分类-2"'), '重复分类应通过后缀去重');
|
||||
assert.ok(pages.home.includes('id="含-空格-特殊-字符"'), '空格/特殊字符应被规范化为可用 slug');
|
||||
|
||||
assert.ok(pages.home.includes('data-id="重复-分类"'));
|
||||
assert.ok(pages.home.includes('data-id="重复-分类-2"'));
|
||||
});
|
||||
});
|
||||
43
test/navigation-submenu-uses-category-slug.node-test.js
Normal file
43
test/navigation-submenu-uses-category-slug.node-test.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const { loadHandlebarsTemplates, generateHTML } = require('../src/generator.js');
|
||||
|
||||
function withRepoRoot(fn) {
|
||||
const originalCwd = process.cwd();
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
}
|
||||
|
||||
test('P1-2:子菜单锚点应使用分类 slug(href + data-category-id)', () => {
|
||||
withRepoRoot(() => {
|
||||
loadHandlebarsTemplates();
|
||||
|
||||
const config = {
|
||||
site: { title: 'Test Site', description: '', author: '', favicon: '', logo_text: 'Test' },
|
||||
profile: { title: 'PROFILE_TITLE', subtitle: 'PROFILE_SUBTITLE' },
|
||||
social: [],
|
||||
navigation: [{ id: 'home', name: '首页', icon: 'fas fa-home' }],
|
||||
home: {
|
||||
title: 'HOME',
|
||||
subtitle: 'HOME_SUB',
|
||||
template: 'page',
|
||||
categories: [
|
||||
{ name: '重复 分类', icon: 'fas fa-tag', sites: [] },
|
||||
{ name: '重复 分类', icon: 'fas fa-tag', sites: [] },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const html = generateHTML(config);
|
||||
|
||||
assert.ok(html.includes('class="submenu-item"'), '应输出子菜单项');
|
||||
assert.ok(html.includes('href="#重复-分类"'), '子菜单 href 应指向 slug');
|
||||
assert.ok(html.includes('data-category-id="重复-分类"'), '子菜单应携带 data-category-id');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user