Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ src/content/news/<slug>.en.md

```md
---
slug: "my-news"
date: "2026-03-25"
title:
zh: "中文标题"
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ Minimal body file:

```md
---
slug: "my-news"
date: "2026-03-25"
title:
zh: "中文标题"
Expand Down Expand Up @@ -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

Expand Down
10 changes: 7 additions & 3 deletions docs/content-edit-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ src/content/news/2026-03-25-my-news.en.md
新闻文件示例:
```md
---
slug: "my-news"
date: "2026-03-25"
title:
zh: "中文标题"
Expand All @@ -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` 一致。

Expand Down
4 changes: 4 additions & 0 deletions docs/content-ops-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ git push origin content/YYYYMMDD-topic
- `*.zh.md`
- `*.en.md`
- 必填字段:
- `slug`
- `date`
- `title.zh/en`

Expand All @@ -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、预览和合并,不新增测试新闻
Expand Down
21 changes: 11 additions & 10 deletions docs/go-live-rehearsal.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 本地已完成证据

Expand All @@ -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 指向 `<Worker base URL>/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 指向 `<Worker base URL>/callback` |
| Worker secrets | 已配置 | `GITHUB_OAUTH_ID` 与 `GITHUB_OAUTH_SECRET` 必须作为 Cloudflare Worker secrets |
| GitHub 写权限 | 已验证 | 确认内容编辑者能通过 Decap 创建 PR |
| Cloudflare Pages 预览 | 已验证 | 确认 CMS 生成 PR 后能触发预览构建 |

## 4. 手动 CMS 验收步骤

Expand All @@ -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` 分支清理,并加入内容校验防回归
10 changes: 9 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -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 基线修复

Expand Down Expand Up @@ -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 真实上线闭环演练

已完成:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "double-duck-lab",
"type": "module",
"version": "1.3.1",
"version": "1.3.2",
"private": true,
"scripts": {
"dev": "astro dev",
Expand Down
2 changes: 2 additions & 0 deletions scripts/smoke-build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
21 changes: 21 additions & 0 deletions scripts/validate-content.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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('/');
Expand Down Expand Up @@ -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);
Expand All @@ -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) => {
Expand Down
7 changes: 3 additions & 4 deletions src/content/news/2026-02-10.en.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
slug: "123"
slug: "archived-news-sample"
date: 2026-02-10
title:
zh: 新闻内容样例归档
Expand All @@ -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")
7 changes: 3 additions & 4 deletions src/content/news/2026-02-10.zh.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
slug: "123"
slug: "archived-news-sample"
date: 2026-02-10
title:
zh: 新闻内容样例归档
Expand All @@ -9,7 +9,6 @@ title:

它不再声明任何论文接收或研究成果,正式上线前可以删除,或替换为经负责人确认的真实新闻。

保留这条样例的目的,是让内容同学在没有真实素材时仍能验证列表页、详情页、图片引用与双语配对。\
测试后台功能
保留这条样例的目的,是让内容同学在没有真实素材时仍能验证列表页、详情页、图片引用与双语配对。

![新闻配图示例(测试使用后台更新)](/news/news_template.png "新闻配图示例")
![新闻配图示例](/news/news_template.png "新闻配图示例")
1 change: 1 addition & 0 deletions src/content/news/2026-02-20.en.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "recruitment-details-pending"
date: "2026-02-20"
title:
zh: "招生信息待确认"
Expand Down
1 change: 1 addition & 0 deletions src/content/news/2026-02-20.zh.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "recruitment-details-pending"
date: "2026-02-20"
title:
zh: "招生信息待确认"
Expand Down
1 change: 1 addition & 0 deletions src/content/news/2026-02-28.en.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "astro-prototype-launched"
date: "2026-02-28"
title:
zh: "官网 Astro 原型上线"
Expand Down
1 change: 1 addition & 0 deletions src/content/news/2026-02-28.zh.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "astro-prototype-launched"
date: "2026-02-28"
title:
zh: "官网 Astro 原型上线"
Expand Down
1 change: 1 addition & 0 deletions src/content/news/2026-04-23-go-live-rehearsal.en.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "go-live-rehearsal"
date: "2026-04-23"
title:
zh: "官网上线演练记录"
Expand Down
1 change: 1 addition & 0 deletions src/content/news/2026-04-23-go-live-rehearsal.zh.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "go-live-rehearsal"
date: "2026-04-23"
title:
zh: "官网上线演练记录"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "v1-3-0-cms-file-management"
date: "2026-05-03"
title:
zh: "DoubleDuckLab 官网 v1.3.0 发布:内容文件管理后台上线"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
slug: "v1-3-0-cms-file-management"
date: "2026-05-03"
title:
zh: "DoubleDuckLab 官网 v1.3.0 发布:内容文件管理后台上线"
Expand Down
6 changes: 3 additions & 3 deletions src/utils/cms-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading