修复:统一主题切换并升级依赖与验证流程

This commit is contained in:
cieanyau committed 2026-11-06 01:37:00 +08:00
1 parent 22163a8b51
commit bcb47f102c
6 files changed
+146 -21

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
# 仅做前端验证,不包含发布、服务器登录或生产凭证。
name: 前端验证
on:
push:
pull_request:
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: 获取源码
uses: https://github.com/actions/checkout@v4
with:
persist-credentials: false
- name: 准备 Node.js
uses: https://github.com/actions/setup-node@v4
with:
node-version: "22"
cache: npm
- name: 安装锁定依赖
run: npm ci
- name: 安装测试浏览器
run: npx playwright install --with-deps chromium
- name: 检查源码并构建
run: |
npm run check
npm run build
- name: 验收真实生产构建
run: |
task_chrome_path="$(node --input-type=module -e 'import { chromium } from "playwright"; process.stdout.write(chromium.executablePath())')"
CHROME_PATH="$task_chrome_path" npm test
# deepmerge 尚无上游修复版本;低危传播项保留在审计输出,不通过降级框架隐藏。
- name: 阻止中危及以上依赖风险
run: npm audit --audit-level=moderate
+11 -2
View File
@@ -123,10 +123,19 @@ npm test
CHROME_PATH=/absolute/path/to/chrome npm test
```
现有 21 个离线场景覆盖登录协议、短信倒计时、路由保护、会话失效、双标签页退出、SSH 密钥管理、
请求超时、支付期限及旧登录入口等流程。测试产物保存在被忽略的 `tests/fixtures/runs/`。
离线场景覆盖登录协议、短信倒计时、路由保护、会话失效、双标签页退出、SSH 密钥管理、
请求超时、支付期限及旧登录入口,并逐步补充安全组、工单、镜像购买和商品字段回归。
测试产物保存在被忽略的 `tests/fixtures/runs/`。
详细使用方式见 [测试说明](tests/README.md)。
仓库包含 `.gitea/workflows/frontend.yml`:安装锁定依赖后运行源码检查、生产构建、浏览器测试和
`npm audit --audit-level=moderate`,不执行部署。Git 服务需启用 Actions 并配置兼容的 Linux runner;
仅提交工作流不代表远端任务已经运行通过。
传递依赖 `glob` 与 `minimatch` 通过精确 overrides 使用修复版本。
截至 2026-11-06,`deepmerge` 的 GHSA-gcqx-w48m-w2h4 尚无已发布修复版本,低危传播告警仍保留,
未用强制降级 Nuxt 或关闭审计来隐藏。禁止把通过中危及以上门禁解读为依赖零风险。
真实支付、第三方登录 / CAPTCHA、安全挑战、短信邮件投递、供应商接口和 VNC 网关仍需在测试环境联调。
## 维护与交付说明
+4 -6
View File
@@ -19,7 +19,7 @@ onMounted(() => siteConfig.load().catch(() => {}));
</script>
<template>
<!-- Keep the published CSS scope until its shared auth styles are migrated together. -->
<!-- 共享认证样式迁移前保留发布版作用域;主题入口遵循全站浅色/深色/跟随系统三态。 -->
<div class="blank-layout" data-v-69ca9392>
<header class="auth-topbar" data-v-69ca9392>
<NuxtLink to="/" class="auth-logo" data-v-69ca9392>
@@ -29,14 +29,12 @@ onMounted(() => siteConfig.load().catch(() => {}));
<button
class="auth-theme"
type="button"
:aria-label="theme.isDark.value ? '切换浅色模式' : '切换深色模式'"
:aria-label="`切换主题,当前${theme.label.value}`"
:title="`主题:${theme.label.value}(点击切换)`"
data-v-69ca9392
@click="theme.cycle"
>
<i
:class="theme.isDark.value ? 'pi pi-moon' : 'pi pi-sun'"
data-v-69ca9392
/>
<i :class="theme.icon.value" data-v-69ca9392 />
</button>
<NuxtLink
v-if="route.path !== '/login'"
+20 -13
View File
@@ -1076,6 +1076,12 @@
"node": ">=18.0.0"
}
},
"node_modules/@isaacs/nonrecursive-glob-match": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz",
"integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==",
"license": "BlueOak-1.0.0"
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -4744,14 +4750,14 @@
}
},
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-14.0.3.tgz",
"integrity": "sha512-A1vjIwC3qpdCso45Iw127gnp7sPhqgY8lUQhDrArIs+GbmfaaVPFuoyy7WyLT9EPZCRZ7j7aLdwUhQmHWesEow==",
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minimatch": "^11.0.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
"path-scurry": "^3.0.0"
},
"engines": {
"node": "18 || 20 || >=22"
@@ -5838,15 +5844,16 @@
}
},
"node_modules/minimatch": {
"version": "10.2.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.4.tgz",
"integrity": "sha512-/UJ1dEoVQTjPNDh6SGlUo+ueLMmoHTas4q+8yaAR/5t6nLAIiBDJtoYUh20IYleix7ehiQkgczb84PE3uzHkxg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.8"
"@isaacs/nonrecursive-glob-match": "^1.0.3",
"brace-expansion": "^5.0.12"
},
"engines": {
"node": "18 || 20 || >=22"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -6598,9 +6605,9 @@
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-3.0.0.tgz",
"integrity": "sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
+4
View File
@@ -38,6 +38,10 @@
"playwright": "^1.64.1",
"prettier": "^3.8.0"
},
"overrides": {
"glob": "14.0.3",
"minimatch": "11.0.4"
},
"engines": {
"node": ">=22.12.0"
}
+72
View File
@@ -0,0 +1,72 @@
/**
* 认证页复用全站三态主题协议;按钮必须如实说明当前模式,不能承诺下一次一定变深/浅色。
*/
import assert from "node:assert/strict";
import { test } from "node:test";
import { appOrigin, scenario } from "./helpers/console-browser.mjs";
test(
"auth theme control describes and persists the shared three-mode cycle",
{ timeout: 20000 },
(t) =>
scenario(t, {}, async (page) => {
await page.emulateMedia({ colorScheme: "light" });
await page.goto(`${appOrigin}/login`);
const themeButton = page.getByRole("button", {
name: "切换主题,当前跟随系统",
exact: true,
});
await themeButton.waitFor();
assert.equal(
await page
.locator("html")
.evaluate((html) => html.classList.contains("app-dark")),
false,
);
await themeButton.click();
await page
.getByRole("button", { name: "切换主题,当前浅色", exact: true })
.waitFor();
assert.equal(
await page.evaluate(() => localStorage.getItem("theme")),
"light",
);
await page
.getByRole("button", { name: "切换主题,当前浅色", exact: true })
.click();
await page
.getByRole("button", { name: "切换主题,当前深色", exact: true })
.waitFor();
assert.equal(
await page
.locator("html")
.evaluate((html) => html.classList.contains("app-dark")),
true,
);
await page.reload();
await page
.getByRole("button", { name: "切换主题,当前深色", exact: true })
.waitFor();
await page
.getByRole("button", { name: "切换主题,当前深色", exact: true })
.click();
await page
.getByRole("button", { name: "切换主题,当前跟随系统", exact: true })
.waitFor();
assert.equal(
await page
.locator("html")
.evaluate((html) => html.classList.contains("app-dark")),
false,
);
await page.emulateMedia({ colorScheme: "dark" });
await page.waitForFunction(() =>
document.documentElement.classList.contains("app-dark"),
);
return {
currentModeDescribed: true,
explicitModePersisted: true,
automaticModeTracksSystem: true,
};
}),
);