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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@
本地预览使用 Bun:

```bash
bun run dev
bun run dev # 默认 http://127.0.0.1:4173
PORT=4199 bun run dev # 换端口
HOST=0.0.0.0 bun run dev # 想让同局域网的其它设备也能打开时才用
```

打开 <http://localhost:4173>。在线站点是 <https://cplus.talkincode.net>,构建与发布方式见下文「构建与发布」。

开发服务器只监听 `127.0.0.1`,并且只服务线上真正会有的那三份内容(`index.html`、`glossary/`、`lessons/`)——这份清单与构建、线上 Worker 共用 [`src/site.ts`](./src/site.ts) 里的同一份定义。仓库里的 `.git/`、`package.json`、`wrangler.jsonc`、`AGENTS.md`、`docs/` 等在本地也是中文 404,和线上一致;少了末尾斜杠的目录地址(`/lessons/s1-01`)会像线上那样跳到带斜杠的地址。想把这些内容也给同一局域网的其他设备看,必须自己显式写 `HOST=0.0.0.0`,默认不会。

## 课件目录

每节课都有自己的目录:`lessons/<小写课程-id>/`。全部 40 个目录及其 `lesson.json` 已由 Bun 脚手架建立;40 节互动课件与随机测验目前均已实现(内容 `awaiting-user-review`,视频始终为仓库外交付):
Expand Down
1 change: 1 addition & 0 deletions docs/feature-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
| 本地进度保存 | 已完成 | 刷新后恢复;浏览器拒绝存储时会给出提示且不崩溃 |
| 学习尝试恢复 | 已完成 | 保存失败(含会话中途失败)当次会话立刻可见、可点“重试保存”;恢复后说明“本地保存已恢复”并把期间待写进度补写落盘,绝不显示成已保存 |
| 课程结构校验 | 已完成 | `bun test` 校验课程数据的完整性和阶段均衡 |
| 本地开发服务器内容边界 | 已完成 | `bun run dev` 只监听 `127.0.0.1`(可用 `HOST` 覆盖),并且只服务 `index.html`、`glossary/`、`lessons/` 这三份内容——内容清单与构建、线上 Worker 共用 `src/site.ts` 里同一份定义。`/.git/config`、`/.gitignore`、`/wrangler.jsonc`、`/AGENTS.md`、`/package.json`、`/docs/`、`/scripts/`、`/src/` 等仓库文件,以及越界与编码写法(`..`、`%2e%2e`、`%2f`、`%00`、坏编码),一律给出与线上一致的中文 404;少了末尾斜杠的目录地址与线上一样 307 跳转(目录不存在则不跳);`PORT`/`HOST` 非法或端口被占用会当场说清而不是静默换端口 |
| 互动页面脚本语法校验 | 已完成 | `bun test` 逐一编译首页与全部 40 课内嵌脚本,语法错误会立即失败,弥补此前只做字符串匹配的盲区 |
| 手工 E2E 验收 | 已完成 | 见 `tests/e2e/manual-checklist.md`;S1-01 的八个场景另可用 `bun run e2e:s1-01` 在无头 Chrome 自动重跑,S1-02 至 S1-08 的手写填空微练习可用 `bun run e2e:s1-typed` 自动重跑,S2-01 至 S2-08 的手写填空微练习可用 `bun run e2e:s2-typed` 自动重跑,S3-01 至 S3-08 用 `bun run e2e:s3-typed`,S4-01 至 S4-08 用 `bun run e2e:s4-typed`,S5-01 至 S5-08 用 `bun run e2e:s5-typed`,全部 39 节已迁移课用 `bun run e2e:typed` |
| 整课推进与随机小测浏览器复验 | 已完成 | `bun run e2e:flow` 把 S1-02 至 S5-08 全部 39 课从 `0 / N 已推进` 走到 `N / N`(741 项,每课 19 项),并逐课量「检查目标绑到本课学习目标原文、没答完不能提交、提交后才给解析与得分、没考住时按学习目标汇总错题并指回本节讲解且不倒退进度、没做完不提前引到下一课、完成后继续学习入口指向 `courseData` 的下一课且可达」;步骤与仍需人工确认的部分见 `tests/e2e/flow-manual-checklist.md` |
Expand Down
2 changes: 2 additions & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
- **S5-08 随机选择题小测**:从 7 道待人工审校选择题中随机抽取 3 道;同次无重复,提交后显示得分与解析。题库来源为本课 course-plan / 自编。
- **初学者常见问题词条**:`glossary/faq.json` 提供基础解释;S1-01 中的 `g++`、`.cpp`、`main`、`cout` 等可点开查看;S1-02 中的变量、`int`、`char`、赋值等同样可点开;S1-03 中的表达式、整除、取模、优先级和括号同样可点开;S1-04 中的 `cin`、换行、固定小数和提示语同样可点开;S1-05 中的 `if`、`else if`、`==` 和 `&&` 同样可点开;S1-06 中的 `for`、`while`、循环变量、累加和死循环同样可点开;S1-07 中的嵌套循环、外层、内层、`break` 和 `continue` 同样可点开;S1-08 中的编译错误、运行错误、答案错误、缩进、命名和分段输出同样可点开;S2-01 中的数组、下标、长度、初始化和越界同样可点开;S2-02 中的 `char`、`string`、长度、下标和 ASCII 同样可点开;S2-03 中的函数、参数、返回值、调用和定义同样可点开;S2-04 中的作用域、局部变量、全局变量、值传递和引用同样可点开;S2-05 中的结构体、成员、成员访问、结构体数组和按字段比较同样可点开;S2-06 中的冒泡思想、sort、比较规则、交换和稳定性同样可点开;S2-07 中的枚举、枚举范围、状态更新、模拟和漏分支同样可点开;S2-08 中的操作次数、O(n)、O(n²)、边界数据和测试点同样可点开;S3-01 中的整除、取模、数位、最大公约数和循环不变量同样可点开;S3-02 中的递归、递归边界、递归调用、调用栈和阶乘同样可点开;S3-03 中的二分查找、单调性、左右边界、中点和死循环同样可点开;S3-04 中的前缀和、前缀和数组、区间和、下标偏移和 O(n) 同样可点开;S3-05 中的双指针、滑动窗口、左指针、右指针和窗口条件同样可点开;S3-06 中的贪心选择、选择标准、反例、排序后决策和 sort 同样可点开;S3-07 中的栈、队列、后进先出、先进先出、括号匹配和广度优先直觉同样可点开;S3-08 中的动态规划、状态、转移、初始化和一维 DP 同样可点开;S4-01 中的竞赛程序、数据范围、样例、标准输入输出和干净输出同样可点开;S4-02 中的建模卡、已知与未知、样例反推、约束和算法候选同样可点开;S4-03 中的状态表、事件顺序、条件分支、样例追踪和边界数据同样可点开;S4-04 中的深度优先搜索、搜索树、回溯和剪枝同样可点开;S4-05 中的数据范围、sort、二分查找、计数数组和重复元素同样可点开;S4-06 中的状态、转移、初始化、选或不选、最优子结构和状态压缩同样可点开;S4-07 中的图、邻接表、访问标记、深度优先搜索、广度优先直觉和连通块同样可点开;S4-08 中的子任务、部分分、保底方案、先易后难和复杂度降级同样可点开;S5-01 中的限时解题流程、审题计时、建模草稿、样例检查和预留检查时间同样可点开;S5-02 中的系统化调试、最小复现、分段输出、断言思路和差分检查同样可点开;S5-03 中的溢出、long long、数组初始化、下标越界、运算优先级、== 比较相等和干净输出同样可点开;S5-04 中的暴力基线、子任务、特殊情况、复杂度升级、伪优化和差分检查同样可点开,S5-05 中的模拟赛、提交顺序、自测表和时间记录同样可点开;S5-06 中的错误分类、边界数据、改法和重做关键题同样可点开;S5-07 中的新题组、交叉检查、对照上一次和稳定习惯同样可点开;S5-08 中的知识地图、薄弱模块、赛前检查清单和 CSP-S 衔接条件同样可点开,并给出可选的权威外部链接。
- **静态站点构建与发布**:`bun run build` 把 `index.html`、`glossary/` 与 `lessons/` 重新复制到 `dist/`,`bun run deploy` 先构建再用 `bunx wrangler deploy` 发布到 Cloudflare Worker `csp-cpp-courseware`(自定义域名 <https://cplus.talkincode.net>;配置见 `wrangler.jsonc`,课程地址的末尾斜杠跳转由静态资源的 `html_handling` 负责,Worker 入口 `src/index.ts` 只把 404 换成中文说明)。产物只能来自仓库内容,因此不会再出现「仓库已经补到 40 节课、线上还停在 4 节课」的漂移——那次漂移的根因正是构建与部署只在某台机器上手工跑过一次、此后再没人重跑。`tests/deploy-assets.test.ts` 在 `bun test` 里守住这条链路:产物覆盖 `courseData` 的每一节课、旧产物不残留、课程地址的末尾斜杠跳转与中文 404 仍在、部署目标与文档一致。同一套浏览器复验也可以直接打在线上:`CSP_E2E_ORIGIN=https://cplus.talkincode.net bun tests/e2e/flow-cdp.ts` 会复用已在运行的服务、不再自启开发服务器,线上已跑过 S5-07/S5-08 的整课推进与 S5-08/S3-01 的微练习。发布默认不再靠人记得:main 上的推送由 `.github/workflows/test-and-deploy.yml` 自动跑「`bun test` → `bun run deploy` → 线上抽检(每一节课、末尾斜杠跳转、词条面板、中文 404)」,缺 `CLOUDFLARE_API_TOKEN` 时发布步骤明确失败而不是静默跳过。
- **本地开发服务器内容边界**:`bun run dev` 只监听 `127.0.0.1`(要改就用 `HOST`),并且只服务站点自己的那三份内容。以前它把整个仓库目录当网站:请求什么路径就拼出同名文件,于是 `/.git/config`、`/wrangler.jsonc`、`/AGENTS.md` 在本地都是 200、线上都是 404,同一个地址在两处表现不一致;`Bun.serve` 默认又监听所有网卡,同一局域网内的任何人都能这样把仓库连同 `.git` 读走。现在内容清单由 `src/site.ts` 的 `SITE_SOURCES` 定义一次,构建、开发服务器与线上 Worker 读的是同一份;站点之外的地址与缺课的地址给出与线上一致的中文 404,`/lessons/s1-01` 这类少了末尾斜杠的目录地址跟线上一样 307 跳到带斜杠的地址(目录不存在就不跳,直接 404,避免缺课地址多绕一圈)。`tests/dev-server.test.ts` 在 `bun test` 里守住这套边界。
- **内容结构校验**:`tests/curriculum.test.ts` 会校验 5 个阶段、40 节课、唯一标识及每节课的目标/内容/检验项结构。
- **手工端到端脚本**:`tests/e2e/manual-checklist.md` 覆盖导航、课件阅读和本地进度恢复的基础路径。

Expand Down Expand Up @@ -282,6 +283,7 @@
| 课后继续学习入口 | 中 | ✅ 39 节非末课走完 `N / N` 后自己打开 `#nextStep`,写明下一课是 `Sx-xx` 与课名、先练的那件事,链接直达下一课的 `index.html` 且真的可达;S5-08 作为最后一课换成课程收尾出口并指回课程路线。真实浏览器复验见 `bun run e2e:flow`(2026-09-21 Chrome 153 无头,每课 19 项) | ✅ 没做完之前入口是隐藏的,不会把还在这一课上的学习者提前引走;边界/失败路径另由 `tests/next-lesson.test.ts` 逐课看住:入口在「视频边界」说明之前、一页出现两个入口、链接指向 `../sX-XX/`(大写或大写目录名)、指向不存在的下一课、标题或先练目标与 `courseData` 漂移、末课仍留指向不存在的下一课、或出现成绩评定/证书/保过之类的说法,都会当场失败 | 不适用:无账号 | ✅ 清掉本地存储后入口重新隐藏、页面回到 `0 / N`,不依赖任何服务端状态;下一课链接用的是相对路径,开发服务器与静态托管下都成立 | [`tests/next-lesson.test.ts`](../tests/next-lesson.test.ts)、[`E2E-flow`](../tests/e2e/flow-cdp.ts)、[`flow 清单`](../tests/e2e/flow-manual-checklist.md#e2e-flow-05-课后继续学习入口) |

| 静态站点构建与发布 | 中 | ✅ `bun run build` 从仓库内容重建 `dist/`(40 节课页面 + 课程目录 + 词条面板),`bun run deploy` 先构建再发布到 `cplus.talkincode.net`;2026-09-21 发布后 40 节课地址全部返回 200、`/lessons/s5-08/` 可达、词条面板可载入,线上页面与本地构建逐字节一致(只有 Cloudflare 注入的脚本不同),并用 `CSP_E2E_ORIGIN=https://cplus.talkincode.net` 把真实浏览器复验直接打在线上:`e2e:typed` 的 s5-08 + s3-01 两课 34 项通过,`e2e:flow` 的 s5-07 + s5-08 两课 38 项通过 | ✅ 产物缺课、`dist/` 里残留上一次的旧页面、课程地址的末尾斜杠跳转(`html_handling`)或中文 404 丢失、部署目标(Worker 名 / `dist` 目录 / 域名)与文档漂移,都会由 [`tests/deploy-assets.test.ts`](../tests/deploy-assets.test.ts) 当场失败 | 不适用:无账号 | ✅ 构建每次清空重建 `dist/`,重复发布幂等;线上出错可用 `bunx wrangler rollback` 退回上一个版本;main 上的推送由 [`.github/workflows/test-and-deploy.yml`](../.github/workflows/test-and-deploy.yml) 自动发布(`bun test` 通过才发布,发布后抽检线上) | [`tests/deploy-assets.test.ts`](../tests/deploy-assets.test.ts)、[`tests/ci-workflow.test.ts`](../tests/ci-workflow.test.ts)、[`scripts/build.ts`](../scripts/build.ts)、[`src/index.ts`](../src/index.ts)、[`wrangler.jsonc`](../wrangler.jsonc)、[`.github/workflows/test-and-deploy.yml`](../.github/workflows/test-and-deploy.yml) |
| 本地开发服务器内容边界 | 中 | ✅ `bun run dev` 能打开站点内容:首页、课程页(含 `/lessons/s5-08/index.html` 这种直指文件的地址)、词条表与词条面板都返回 200 且带 UTF-8 类型头;少了末尾斜杠的目录地址与线上一致 307 跳到带斜杠的地址 | ✅ 站点之外的地址一律中文 404:`/.git/config`、`/.gitignore`、`/AGENTS.md`、`/README.md`、`/package.json`、`/wrangler.jsonc`、`/docs/`、`/scripts/`、`/src/`、`/tests/`、`/node_modules/`、`/.env`、`/.dev.vars`;越界与编码写法(`..`、`%2e%2e`、`%2f`、`%00`、坏掉的 `%`、`//`、`.` 开头段、不带前导斜杠)都被挡在内容边界之外;`PORT` 非整数或越界、`HOST` 为空、端口被占用都会当场失败并说清怎么办,不静默换端口 | 不适用:无账号 | ✅ 只监听 `127.0.0.1`,不再把仓库连同 `.git` 暴露给同局域网;换端口或换监听地址用 `PORT=4199` / `HOST=127.0.0.1`,直接重启即可,不写任何持久状态 | [`tests/dev-server.test.ts`](../tests/dev-server.test.ts)、[`scripts/dev.ts`](../scripts/dev.ts)、[`src/site.ts`](../src/site.ts) |

本项目当前无权限模型,故权限角色覆盖均不适用;一旦新增账号或教师端,相关行必须立即改为双角色验证,且在实现前不得宣称功能完成。

Expand Down
6 changes: 5 additions & 1 deletion scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { cp, mkdir, readdir, rm } from "node:fs/promises";
import { resolve } from "node:path";
import { SITE_SOURCES } from "../src/site";

const projectRoot = resolve(import.meta.dir, "..");

// 线上站点是纯静态资源:内容只来自仓库里的 index.html、glossary/ 与 lessons/。
// 这里曾经出过事——仓库已经补到 40 节课,cplus.talkincode.net 还停在 4 节课,
// 因为构建与部署只在某台机器上手工跑过一次。产物一律从仓库内容生成,
// 不允许手工拼装 dist/,否则「仓库有这节课、站点上还是 404」的漂移会再出现一次。
export const siteSources = ["index.html", "glossary", "lessons"] as const;
//
// 清单本身放在 src/site.ts:本地开发服务器与线上 Worker 读的是同一条边界,
// 各写一份就会重新长出「本地能打开、线上 404」这类不一致。
export const siteSources = SITE_SOURCES;

export async function buildStaticSite({
root = projectRoot,
Expand Down
Loading
Loading