From eae3fb9a4302ddeaea6eeb389e481d7ac3c8a17e Mon Sep 17 00:00:00 2001 From: Nightt <87569709+nightt5879@users.noreply.github.com> Date: Sun, 3 May 2026 15:32:35 +0800 Subject: [PATCH] fix: stabilize cms news editing --- CHANGELOG.md | 11 ++++++++++ README.md | 2 ++ README_EN.md | 2 ++ docs/content-edit-guide.md | 10 ++++++--- docs/content-ops-checklist.md | 4 ++++ docs/go-live-rehearsal.md | 21 ++++++++++--------- docs/roadmap.md | 10 ++++++++- package-lock.json | 4 ++-- package.json | 2 +- scripts/smoke-build.mjs | 2 ++ scripts/validate-content.mjs | 21 +++++++++++++++++++ src/content/news/2026-02-10.en.md | 7 +++---- src/content/news/2026-02-10.zh.md | 7 +++---- src/content/news/2026-02-20.en.md | 1 + src/content/news/2026-02-20.zh.md | 1 + src/content/news/2026-02-28.en.md | 1 + src/content/news/2026-02-28.zh.md | 1 + .../news/2026-04-23-go-live-rehearsal.en.md | 1 + .../news/2026-04-23-go-live-rehearsal.zh.md | 1 + ...026-05-03-v1-3-0-cms-file-management.en.md | 1 + ...026-05-03-v1-3-0-cms-file-management.zh.md | 1 + src/utils/cms-config.ts | 6 +++--- 22 files changed, 89 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dc24a4..794e880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [1.3.2] - 2026-05-03 + +### Changed +- Bumped the package version from `1.3.1` to `1.3.2` for CMS stability hardening. +- Tightened the news CMS slug guidance so generated slugs start with a lowercase letter and keep the date out of the field. + +### Fixed +- Added stable `slug` frontmatter to existing news files so editing old entries in Decap no longer requires guessing a slug. +- Removed CMS validation test residue from the archived news sample. +- Extended content validation to catch missing or invalid news CMS slugs and known CMS test residue before build. + ## [1.3.1] - 2026-05-03 ### Added diff --git a/README.md b/README.md index 3cb6e98..d7c27d7 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ src/content/news/.en.md ```md --- +slug: "my-news" date: "2026-03-25" title: zh: "中文标题" @@ -222,6 +223,7 @@ venue: "Conference Name" - `1.2.1`:CMS 试点收口 / ops hardening,补 locale/news 校验与启用前置条件硬化 - `1.3.0`:CMS 文件管理后台,扩展到新闻、成员、论文、招生与现有项目文件 - `1.3.1`:CMS 线上启用跟进,补 Cloudflare Worker OAuth 代理部署包与生产环境变量验收说明 +- `1.3.2`:CMS 稳定性补丁,补齐旧新闻 `slug`、清理后台测试残留,并收紧新闻 slug 校验 ## License diff --git a/README_EN.md b/README_EN.md index e43e144..58bf513 100644 --- a/README_EN.md +++ b/README_EN.md @@ -163,6 +163,7 @@ Minimal body file: ```md --- +slug: "my-news" date: "2026-03-25" title: zh: "中文标题" @@ -223,6 +224,7 @@ venue: "Conference Name" - `1.2.1`: CMS pilot closure / ops hardening, plus locale/news validation and CMS enablement hardening - `1.3.0`: CMS file management for news, members, papers, recruitment pages, and existing project files - `1.3.1`: CMS live enablement follow-up with a Cloudflare Worker OAuth proxy package and production environment validation notes +- `1.3.2`: CMS stability patch that adds stable legacy news slugs, removes CMS test residue, and tightens news slug validation ## License diff --git a/docs/content-edit-guide.md b/docs/content-edit-guide.md index ca9ee9f..1cc5c8a 100644 --- a/docs/content-edit-guide.md +++ b/docs/content-edit-guide.md @@ -104,6 +104,7 @@ src/content/news/2026-03-25-my-news.en.md 新闻文件示例: ```md --- +slug: "my-news" date: "2026-03-25" title: zh: "中文标题" @@ -116,9 +117,12 @@ title: CMS 试点启用后的编辑步骤: 1. 打开站点的 `/admin/` -2. 新建一条 `news` 草稿,填写 `slug`、`date`、中英文标题和正文 -3. 保存草稿后,通过 GitHub Pull Request 走审核 -4. 审核通过后合并,站点会自动重新构建 +2. 新建或打开一条 `news` 草稿,填写 `slug`、`date`、中英文标题和正文 +3. `slug` 使用小写英文、数字和连字符,必须以小写字母开头;不要把日期写进 `slug` +4. 点击 `Save`,确认左上角从 unsaved 变成 saved +5. 把状态从 `Draft` 改成 `Ready` +6. 点击 `Publish` -> `Publish now`,让 CMS 创建 GitHub Pull Request +7. 检查 GitHub PR 和 Cloudflare Pages 预览,审核通过后合并,站点会自动重新构建 如果 CMS 还没启用,继续沿用当前的 Markdown 文件更新流程即可。手动新增新闻时仍要维护一对 `.zh.md` / `.en.md` 文件,并保证两边 `date`、`title.zh`、`title.en` 一致。 diff --git a/docs/content-ops-checklist.md b/docs/content-ops-checklist.md index d88108f..21480ed 100644 --- a/docs/content-ops-checklist.md +++ b/docs/content-ops-checklist.md @@ -74,6 +74,7 @@ git push origin content/YYYYMMDD-topic - `*.zh.md` - `*.en.md` - 必填字段: + - `slug` - `date` - `title.zh/en` @@ -88,10 +89,13 @@ git push origin content/YYYYMMDD-topic ## D. `1.3.0` CMS 文件管理启用后 - 编辑入口固定为 `/admin/` +- 新闻 `slug` 必须以小写字母开头,只使用小写英文、数字和连字符;CMS 会用日期生成文件名前缀,所以 `slug` 字段里不要再写日期 +- 编辑旧新闻时优先保留后台里已有的 `slug`,避免改动公开 URL - 新闻、成员、论文可新增 / 编辑 / 删除 - 招生与合作只编辑固定页面,不新增 / 删除 - 项目只编辑现有 overview/background 文件,不新增 / 删除项目 - 内容同学保存后会生成可审阅的 GitHub Pull Request,而不是直接写入 `main` +- 发布流程是 `Save` -> 状态改为 `Ready` -> `Publish` -> `Publish now`,随后到 GitHub PR 和 Cloudflare Pages 预览确认 - 编辑者需要目标仓库的写权限 - `1.3.1` 起,GitHub 登录依赖 `ops/cms-oauth-worker/` 中的 Cloudflare Worker OAuth 代理 - 首次线上闭环建议只微调已发布的 `v1.3.0` 新闻,验证登录、PR、预览和合并,不新增测试新闻 diff --git a/docs/go-live-rehearsal.md b/docs/go-live-rehearsal.md index 389f725..039d7a5 100644 --- a/docs/go-live-rehearsal.md +++ b/docs/go-live-rehearsal.md @@ -7,7 +7,8 @@ - 本地内容闭环已推进:已新增双语上线演练新闻,并清理一批明显会误导上线验收的假邮箱、`example.com` 外链和虚假成果表述。 - `1.3.0` 已将 CMS 从 `news` 扩展到新闻、成员、论文、招生固定页与现有项目文件。 - `1.3.1` 新增 Cloudflare Worker OAuth 代理部署包,用于补齐线上 Decap CMS GitHub 登录前提。 -- 线上 CMS 登录、GitHub PR 创建、Cloudflare Pages 预览与合并上线仍需要在配置好 OAuth App、Worker secrets 和 Pages 环境变量后完成。 +- 线上 CMS 登录、GitHub PR 创建、Cloudflare Pages 预览、合并上线与生产验证已在 2026-05-03 完成。 +- `1.3.2` 跟进清理首次后台验收留下的测试文字,并为旧新闻补齐稳定 `slug` frontmatter。 ## 2. 本地已完成证据 @@ -26,19 +27,20 @@ - `npm run test:seo` 通过 - `npm run verify` 通过 - 构建备注:Astro content sync 仍会输出少量 duplicate id warning,但本轮未导致 build、smoke 或 SEO 失败;后续可单独评估 content cache / collection sync 清理。 +- 线上闭环证据:CMS 创建的真实内容 PR 已触发 Cloudflare Pages 预览并合并上线,`v1.3.1` 已作为线上启用版本发布。 ## 3. 线上验收清单 | 项目 | 当前状态 | 上线前动作 | |---|---|---| -| `CMS_GITHUB_REPO` | 待线上核验 | 确认指向目标 GitHub 仓库 | +| `CMS_GITHUB_REPO` | 已验证 | 确认指向目标 GitHub 仓库 | | `CMS_BRANCH` | 默认为 `main` | 如生产分支不是 `main`,在 Pages 环境变量中覆盖 | -| `CMS_OAUTH_BASE_URL` | 待线上核验 | 确认 OAuth 代理可访问,并与回调地址匹配 | -| `PUBLIC_SITE_URL` | 待线上核验 | 确认生产域名、canonical、Open Graph 与 hreflang 一致 | -| GitHub OAuth App | 待线上创建 | Homepage 指向 Worker base URL,callback 指向 `/callback` | -| Worker secrets | 待线上配置 | `GITHUB_OAUTH_ID` 与 `GITHUB_OAUTH_SECRET` 必须作为 Cloudflare Worker secrets | -| GitHub 写权限 | 待线上核验 | 确认内容编辑者能通过 Decap 创建 PR | -| Cloudflare Pages 预览 | 待线上核验 | 确认 CMS 生成 PR 后能触发预览构建 | +| `CMS_OAUTH_BASE_URL` | 已验证 | 确认 OAuth 代理可访问,并与回调地址匹配 | +| `PUBLIC_SITE_URL` | 已验证 | 确认生产域名、canonical、Open Graph 与 hreflang 一致 | +| GitHub OAuth App | 已创建 | Homepage 指向 Worker base URL,callback 指向 `/callback` | +| Worker secrets | 已配置 | `GITHUB_OAUTH_ID` 与 `GITHUB_OAUTH_SECRET` 必须作为 Cloudflare Worker secrets | +| GitHub 写权限 | 已验证 | 确认内容编辑者能通过 Decap 创建 PR | +| Cloudflare Pages 预览 | 已验证 | 确认 CMS 生成 PR 后能触发预览构建 | ## 4. 手动 CMS 验收步骤 @@ -53,6 +55,5 @@ - 成员、论文、项目和招生仍缺真实素材;当前只做了误导性占位清理与替换清单。 - `/admin/` 依赖外部 Decap bundle,供应链加固仍是独立后续任务。 -- OAuth Worker 已有部署包,但生产环境仍需要真实 GitHub OAuth App client ID / secret。 - 真实 PR 审核时如果发现字段不顺手,只记录问题;本轮不扩展 CMS schema。 -- Astro duplicate id warning 尚未在 T-0015 范围内处理,因为当前统一验收链路仍通过。 +- 首次 CMS 验收留下的测试文字已在 `1.3.2` 分支清理,并加入内容校验防回归。 diff --git a/docs/roadmap.md b/docs/roadmap.md index af4cf1d..56186ec 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap -更新时间:2026-05-03(1.3.1 CMS live enablement) +更新时间:2026-05-03(1.3.2 CMS stability hardening) ## 1.0.1 基线修复 @@ -66,6 +66,14 @@ - 使用已发布的 `v1.3.0` 新闻完成一次真实 CMS 编辑 PR、预览、合并闭环 - 不扩展 CMS 内容模型,不引入数据库或服务端内容 API +## 1.3.2 CMS 稳定性补丁 + +已完成: +- 为旧新闻补齐稳定 `slug` frontmatter,避免 Decap 编辑既有条目时要求手工猜 slug +- 清理首次 CMS 验收留下的测试文字,避免测试内容进入长期生产内容 +- 收紧新闻 `slug` 提示、校验和 smoke 检查,要求以小写字母开头,不包含日期前缀 +- 文档补充 Save -> Ready -> Publish now 的后台发布流程说明 + ## T-0015 真实上线闭环演练 已完成: diff --git a/package-lock.json b/package-lock.json index e5f728a..b521358 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "double-duck-lab", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "double-duck-lab", - "version": "1.3.1", + "version": "1.3.2", "devDependencies": { "astro": "^5.13.0", "vite": "^6.4.1" diff --git a/package.json b/package.json index 4ec0e5c..3b13e14 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "double-duck-lab", "type": "module", - "version": "1.3.1", + "version": "1.3.2", "private": true, "scripts": { "dev": "astro dev", diff --git a/scripts/smoke-build.mjs b/scripts/smoke-build.mjs index 2c99da6..48c7513 100644 --- a/scripts/smoke-build.mjs +++ b/scripts/smoke-build.mjs @@ -117,6 +117,8 @@ const checks = [ 'publish_mode: editorial_workflow', 'media_folder: public/uploads', 'public_folder: /uploads', + 'Start with a lowercase letter', + '^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$', 'structure: multiple_files', ' - name: news', ' - name: members', diff --git a/scripts/validate-content.mjs b/scripts/validate-content.mjs index 0e56b77..8a94684 100644 --- a/scripts/validate-content.mjs +++ b/scripts/validate-content.mjs @@ -74,6 +74,16 @@ function assertLocalizedFrontmatterObject(frontmatter, field, message) { assert(hasLocalizedValue(block, 'zh') && hasLocalizedValue(block, 'en'), message); } +function getFrontmatterScalar(frontmatter, field) { + const matched = frontmatter.match(new RegExp(`^${field}:\\s*(?:"([^"\\n]+)"|'([^'\\n]+)'|(\\S.*))\\s*$`, 'm')); + return matched ? (matched[1] || matched[2] || matched[3] || '').trim() : ''; +} + +function expectedNewsCmsSlug(routeSlug) { + const matched = routeSlug.match(/^\d{4}-\d{2}-\d{2}-(.+)$/); + return matched ? matched[1] : ''; +} + function parseNewsFileInfo(relPath) { const normalized = relPath.replaceAll('\\', '/'); const parts = normalized.split('/'); @@ -147,6 +157,8 @@ try { assert(joinFolders.length > 0, 'No join folders in src/content/join'); const newsLangMap = new Map(); + const newsCmsSlugPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/; + const forbiddenCmsResidue = ['testttttttt', 'testttttt', '测试后台功能', '测试使用后台更新']; newsFiles.forEach((filePath) => { const rel = path.relative(newsDir, filePath).replaceAll('\\', '/'); const { slug, lang } = parseNewsFileInfo(rel); @@ -158,9 +170,18 @@ try { newsLangMap.get(slug).add(lang); const { frontmatter, body } = parseMarkdown(filePath); + const cmsSlug = getFrontmatterScalar(frontmatter, 'slug'); + const expectedCmsSlug = expectedNewsCmsSlug(slug); + assert(newsCmsSlugPattern.test(cmsSlug), `news frontmatter slug invalid: ${rel}`); + if (expectedCmsSlug) { + assert(cmsSlug === expectedCmsSlug, `news frontmatter slug must match filename suffix: ${rel}`); + } assertFrontmatter(/date:\s*['"]?\d{4}-\d{2}-\d{2}['"]?/, frontmatter, `news frontmatter date invalid: ${rel}`); assertLocalizedFrontmatterObject(frontmatter, 'title', `news frontmatter title zh\/en invalid: ${rel}`); assert(isString(body), `news body empty: ${rel}`); + forbiddenCmsResidue.forEach((needle) => { + assert(!body.includes(needle), `news body contains CMS test residue "${needle}": ${rel}`); + }); }); newsLangMap.forEach((langs, slug) => { diff --git a/src/content/news/2026-02-10.en.md b/src/content/news/2026-02-10.en.md index c0e583f..3774bcf 100644 --- a/src/content/news/2026-02-10.en.md +++ b/src/content/news/2026-02-10.en.md @@ -1,5 +1,5 @@ --- -slug: "123" +slug: "archived-news-sample" date: 2026-02-10 title: zh: 新闻内容样例归档 @@ -9,7 +9,6 @@ This archived entry remains as a news-detail sample so maintainers can verify im It no longer claims paper acceptances or research results. Before public launch, delete it or replace it with owner-approved news. -Keeping this sample allows content editors to test list pages, detail pages, image references, and bilingual pairing when real materials are still pending.\ -testttttttt +Keeping this sample allows content editors to test list pages, detail pages, image references, and bilingual pairing when real materials are still pending. -![News illustration(testttttt)](/news/news_template.png "News illustration") +![News illustration](/news/news_template.png "News illustration") diff --git a/src/content/news/2026-02-10.zh.md b/src/content/news/2026-02-10.zh.md index 59913cc..12251ee 100644 --- a/src/content/news/2026-02-10.zh.md +++ b/src/content/news/2026-02-10.zh.md @@ -1,5 +1,5 @@ --- -slug: "123" +slug: "archived-news-sample" date: 2026-02-10 title: zh: 新闻内容样例归档 @@ -9,7 +9,6 @@ title: 它不再声明任何论文接收或研究成果,正式上线前可以删除,或替换为经负责人确认的真实新闻。 -保留这条样例的目的,是让内容同学在没有真实素材时仍能验证列表页、详情页、图片引用与双语配对。\ -测试后台功能 +保留这条样例的目的,是让内容同学在没有真实素材时仍能验证列表页、详情页、图片引用与双语配对。 -![新闻配图示例(测试使用后台更新)](/news/news_template.png "新闻配图示例") +![新闻配图示例](/news/news_template.png "新闻配图示例") diff --git a/src/content/news/2026-02-20.en.md b/src/content/news/2026-02-20.en.md index 8ca6d43..9c1aa83 100644 --- a/src/content/news/2026-02-20.en.md +++ b/src/content/news/2026-02-20.en.md @@ -1,4 +1,5 @@ --- +slug: "recruitment-details-pending" date: "2026-02-20" title: zh: "招生信息待确认" diff --git a/src/content/news/2026-02-20.zh.md b/src/content/news/2026-02-20.zh.md index 8170342..87ad600 100644 --- a/src/content/news/2026-02-20.zh.md +++ b/src/content/news/2026-02-20.zh.md @@ -1,4 +1,5 @@ --- +slug: "recruitment-details-pending" date: "2026-02-20" title: zh: "招生信息待确认" diff --git a/src/content/news/2026-02-28.en.md b/src/content/news/2026-02-28.en.md index 1fc4f97..010779a 100644 --- a/src/content/news/2026-02-28.en.md +++ b/src/content/news/2026-02-28.en.md @@ -1,4 +1,5 @@ --- +slug: "astro-prototype-launched" date: "2026-02-28" title: zh: "官网 Astro 原型上线" diff --git a/src/content/news/2026-02-28.zh.md b/src/content/news/2026-02-28.zh.md index 3fb2a3d..8f0ecd4 100644 --- a/src/content/news/2026-02-28.zh.md +++ b/src/content/news/2026-02-28.zh.md @@ -1,4 +1,5 @@ --- +slug: "astro-prototype-launched" date: "2026-02-28" title: zh: "官网 Astro 原型上线" diff --git a/src/content/news/2026-04-23-go-live-rehearsal.en.md b/src/content/news/2026-04-23-go-live-rehearsal.en.md index d0dcd91..522f319 100644 --- a/src/content/news/2026-04-23-go-live-rehearsal.en.md +++ b/src/content/news/2026-04-23-go-live-rehearsal.en.md @@ -1,4 +1,5 @@ --- +slug: "go-live-rehearsal" date: "2026-04-23" title: zh: "官网上线演练记录" diff --git a/src/content/news/2026-04-23-go-live-rehearsal.zh.md b/src/content/news/2026-04-23-go-live-rehearsal.zh.md index 989c0ae..b6af930 100644 --- a/src/content/news/2026-04-23-go-live-rehearsal.zh.md +++ b/src/content/news/2026-04-23-go-live-rehearsal.zh.md @@ -1,4 +1,5 @@ --- +slug: "go-live-rehearsal" date: "2026-04-23" title: zh: "官网上线演练记录" diff --git a/src/content/news/2026-05-03-v1-3-0-cms-file-management.en.md b/src/content/news/2026-05-03-v1-3-0-cms-file-management.en.md index 0656b40..34debb5 100644 --- a/src/content/news/2026-05-03-v1-3-0-cms-file-management.en.md +++ b/src/content/news/2026-05-03-v1-3-0-cms-file-management.en.md @@ -1,4 +1,5 @@ --- +slug: "v1-3-0-cms-file-management" date: "2026-05-03" title: zh: "DoubleDuckLab 官网 v1.3.0 发布:内容文件管理后台上线" diff --git a/src/content/news/2026-05-03-v1-3-0-cms-file-management.zh.md b/src/content/news/2026-05-03-v1-3-0-cms-file-management.zh.md index 1f7b7cf..c0b75d1 100644 --- a/src/content/news/2026-05-03-v1-3-0-cms-file-management.zh.md +++ b/src/content/news/2026-05-03-v1-3-0-cms-file-management.zh.md @@ -1,4 +1,5 @@ --- +slug: "v1-3-0-cms-file-management" date: "2026-05-03" title: zh: "DoubleDuckLab 官网 v1.3.0 发布:内容文件管理后台上线" diff --git a/src/utils/cms-config.ts b/src/utils/cms-config.ts index 54fbdb1..0cdde0e 100644 --- a/src/utils/cms-config.ts +++ b/src/utils/cms-config.ts @@ -214,10 +214,10 @@ function cmsCollections() { ' name: slug', ' widget: string', ' i18n: duplicate', - ' hint: Use a short lowercase slug; Decap prefixes the selected date to build the final filename.', + ' hint: Use a short lowercase slug that starts with a letter. Do not include the date; Decap prefixes the selected date for new entries. Keep the prefilled value when editing an existing entry.', ' pattern:', - " - '^[a-z0-9]+(?:-[a-z0-9]+)*$'", - " - 'Use lowercase letters, numbers, and hyphens only.'", + " - '^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$'", + " - 'Start with a lowercase letter, then use lowercase letters, numbers, and hyphens only.'", ' - label: Date', ' name: date', ' widget: string',