chore(文档): CLAUDE.md 瘦身,后端专属的坑挪进 apps/api/CLAUDE.md,并改正「推 origin 不部署」的说法
Deploy / deploy (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
- 单二进制、runtime.ts、SQL 自 spawn、路由遮蔽、出参 satisfies、外键与迁移原样搬到 apps/api/CLAUDE.md,只在动后端文件时加载;根文件留一行指路 - Chrome 105 的 polyfill 审计细节挪到 apps/web/CLAUDE.md - 删掉 ls 就能看到的仓库结构表,以及 docs/database.md、docker/judge/README.md 已有的重复步骤 - origin 会镜像到 github,平时推 origin 通常也就部署了(CLAUDE.md 与 deploy.md 原来写的是不触发) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51c3c16fd5
commit
4be569d978
7 files changed
+104
-114
No files matched your search
@@ -24,16 +24,6 @@ OJ2 是判题狗(Online Judge)的后端重写:Django 6 → Bun + TypeScrip
|
||||
| `docker/judge/README.md` | 换判题沙箱镜像、升语言版本(gcc / Python …) |
|
||||
| `docs/specs/` | 三份设计文档:后端重写、课堂求助与协作编辑、题目详情页重设计(动题目页之前必读) |
|
||||
|
||||
## 仓库结构
|
||||
|
||||
| 目录 | 作用 |
|
||||
|---|---|
|
||||
| `apps/api/` | 后端。Hono + Drizzle + BullMQ,编译成单二进制 |
|
||||
| `apps/web/` | 前端。Vue 3 SPA,起点是 ojnext 的代码,之后大部分已重写 |
|
||||
| `packages/contract/` | 前后端共用的 Zod 契约 |
|
||||
| `docker/` | Dockerfile + 三套 compose(dev / debian / school)+ 部署与运维脚本 |
|
||||
| `docs/` | 上面那几份专题文档 + `specs/` 里的设计文档 |
|
||||
|
||||
## 本机环境
|
||||
|
||||
**Docker 可用,全套依赖都能在本机跑起来**(PostgreSQL、Redis、判题沙箱),
|
||||
@@ -89,7 +79,8 @@ references 的壳,真正的配置在 `tsconfig.app.json`),而 `vite build`
|
||||
## 提交
|
||||
|
||||
**直接在 `main` 上干活,不开特性分支。** 注意 push 到 `github` 这个 remote 的 `main`
|
||||
会触发 CI 部署(平时 push 的 `origin` 不会),见 `docs/deploy.md`。
|
||||
会触发 CI 部署;而 `origin` 会镜像到 `github`,**平时 push `origin` 通常几秒后也就部署了**
|
||||
(偶尔不跟,要 `git ls-remote github` 核实)。见 `docs/deploy.md`。
|
||||
|
||||
提交信息用中文,格式 `type(范围): 从用户视角描述现象`,例如
|
||||
`fix(流程图): 「重新判题」从来没成功过,而且会把原来的评分清掉`。
|
||||
@@ -97,37 +88,9 @@ references 的壳,真正的配置在 `tsconfig.app.json`),而 `vite build`
|
||||
|
||||
## 几件必须知道的事
|
||||
|
||||
### 单二进制是有代价的
|
||||
|
||||
`apps/api` 编译成 `bun build --compile` 的单二进制,所以**运行时不能依赖 node_modules**。
|
||||
任何 `require.resolve` / `Bun.resolveSync` / `__dirname` 去找文件的写法,本地都正常、编译后
|
||||
都会炸,而且**只在离开仓库目录后才炸**(在仓库里跑时它顺着 cwd 摸到了 node_modules,
|
||||
假装没事)。
|
||||
|
||||
资源要用 `with { type: "file" }` 内嵌。`.node` 原生模块还要额外注意:这个写法只有打包器认、
|
||||
`bun run` 不认,所以必须按形态分叉 —— 见 `apps/api/src/vendor/jieba.ts` 的注释,
|
||||
那里把坑写全了。
|
||||
|
||||
**改完这类代码,dev 和编译两种形态都要跑一遍。** 我吃过亏:只验了编译产物,dev 直接起不来。
|
||||
|
||||
### 路径解析看 `runtime.ts`
|
||||
|
||||
编译后 `import.meta.dir` 恒为 `/$bunfs/root`,往上三级就是文件系统根。
|
||||
相对路径一律走 `runtime.ts` 的 `pathBase`,别自己拼。
|
||||
|
||||
### SQL 判题会 spawn「自己」
|
||||
|
||||
`judge/sql/index.ts` 起的子进程是二进制自身 + `sql-child` 子命令(因为编译后磁盘上没有
|
||||
child.ts 可以 spawn)。所以**入口必须有 argv 分发**,否则「起自己」变成「把整个程序再跑
|
||||
一遍」→ 指数级 fork。这不是假想,开发时炸过一次开发机。`OJ2_SQL_CHILD` 那道递归闸不要删。
|
||||
|
||||
### 加路由要防遮蔽
|
||||
|
||||
**Hono 按注册顺序匹配,不是静态优先**(实测确认过,别凭直觉)。`/problems/:id` 注册在
|
||||
`/problems/random` 前面的话,后者永远进不去 —— 而且不报错、不警告,只是静默走进前一条的
|
||||
handler。阶段 4 真实发生过一次,两个教师用的分析端点被吃掉,一直到评审才发现。
|
||||
|
||||
加完路由跑 `bun run --filter '@oj2/api' check:routes`。
|
||||
后端专属的坑 —— 单二进制不能依赖 node_modules、路径走 `runtime.ts`、SQL 判题 spawn 自己、
|
||||
路由遮蔽、出参用 `satisfies` 不用 `parse`、外键删除动作与迁移 —— 在 **`apps/api/CLAUDE.md`**,
|
||||
动 `apps/api` 的文件时会自动加载。下面是全仓通用的。
|
||||
|
||||
### 判题状态码不能改
|
||||
|
||||
@@ -142,9 +105,7 @@ QingdaoU/JudgeServer 停更在 2024-04(官方镜像的 `latest` 和 `1.6.1`
|
||||
编译器停在 gcc-13),新工具链只能自己编。`docker/judge/` 里是只改版本的 Dockerfile
|
||||
分叉 + 构建脚本 + 冒烟测试,判题逻辑一行没动。
|
||||
|
||||
- 新机器、换镜像:先 `docker/judge/build.sh --save` → scp → `docker load`,再部署。
|
||||
**服务器和机房各有各的判题沙箱,两边都要装。**
|
||||
- 改工具链就把末尾序号 +1(下一版 `oj2-judge-3`)。`up -d` 不带 `--pull`,名字没变会静默用旧镜像。
|
||||
- 构建、分发、改名的步骤见 `docker/judge/README.md`。**服务器和机房各有各的判题沙箱,两边都要装。**
|
||||
- 编译/运行命令在 `apps/api/src/judge/languages.ts`,不在镜像里。gcc-14 把隐式函数
|
||||
声明等提成了 error(`-w` 压不住),那边的 `cLooseErrors` 几个 `-Wno-error=` 就是
|
||||
为此加的 —— 删掉它们等于让一批历史题解和 C 教程示例集体 CE。
|
||||
@@ -157,20 +118,6 @@ QingdaoU/JudgeServer 停更在 2024-04(官方镜像的 `latest` 和 `1.6.1`
|
||||
`judgeConfigFor()`,它带旧值别名;**回滚要连数据一起回**,只滚代码会让 Python 提交全炸。
|
||||
- 换完镜像跑 `bun docker/judge/smoke.ts`:三种语言、六种状态码、gcc 宽松度一起核。
|
||||
|
||||
### 出参不 `parse`,用 `satisfies`
|
||||
|
||||
**后端的响应一律 `satisfies XxxType`,不要写 `xxxSchema.parse({...})`。** 出参是后端自己刚
|
||||
拼出来的字面量,TS 已经在编译期校验过;再 parse 一遍拿不到任何新信息,唯一可能失败的输入是
|
||||
**库里的历史数据**,而失败的代价是 500 —— 这条规矩是被四次这样的线上故障换来的。
|
||||
|
||||
**闸设在写入侧**:入参 `safeParse`、`db/schema.ts` 的 `.$type<>()` 列收窄、
|
||||
语义校验函数(`astRulesError()` / `exerciseDataError`)。JSONB 原文
|
||||
(`submission.info` / `statistic_info` / `exercise.data`)一律放行,它们的形状真相在判题机
|
||||
那边。query 的筛选值走 `routes/helpers.ts` 的 `asFilterValue()`,那是纯类型交接、不加校验。
|
||||
|
||||
四次故障的细节、`.$type<>()` 断言该怎么核,见 `docs/contract.md`;
|
||||
前端为什么只在三处挂运行时闸门,见 `apps/web/CLAUDE.md`。
|
||||
|
||||
### AST 代码规则:一张表,外加一个机器检查
|
||||
|
||||
契约的 `AST_NODE_TARGETS_BY_LANGUAGE` 是**唯一**一张表(`label` 给界面、`node` 给判题机),
|
||||
@@ -192,13 +139,8 @@ bun run --filter '@oj2/api' check:ast # 升级 tree-sitter-* 之后一定要
|
||||
### 前端基线是 Chrome 105
|
||||
|
||||
机房**部分**电脑是 Chrome 105,其余更新 —— 按最低那档定基线。
|
||||
|
||||
- **`@vitejs/plugin-legacy` 留着,别删**:vite 8 的默认构建 target 是 `chrome111`,比 105 高。
|
||||
这个插件同时把 `build.target` 压到 `es2020/chrome105`、给现代产物补 core-js polyfill
|
||||
(`toSorted` / `Set` 运算 / 迭代器辅助那批是 Chrome 110+ 才有的)。`modernTargets` 不写,
|
||||
用插件自带的基线(`chrome>=105`),正好是这一档。polyfill 清单写死在 `vite.config.ts`,
|
||||
**升级前端依赖后重新审计**:先临时把 `modernPolyfills` 改成 `true`,再跑 `DEBUG=vite:legacy bun run build` 才会打印探测到的全集(写死清单时它只回显清单)。
|
||||
- **View Transitions 要 111,105 没有**,`darkTransition.ts` 的降级分支是真在用的。
|
||||
**`@vitejs/plugin-legacy` 和写死的 polyfill 清单别删**(vite 8 默认 target 是 `chrome111`)。
|
||||
升级前端依赖后要重新审计 polyfill 清单,步骤与 View Transitions 的降级见 `apps/web/CLAUDE.md`。
|
||||
|
||||
### 时间只有一个锚点:`apps/api/src/time.ts`
|
||||
|
||||
@@ -222,41 +164,6 @@ SQL 里要按日历切,用 `localTime(列)`(生成 `列 at time zone 'Asia/S
|
||||
**再动日历口径之前先读 `docs/timezone.md`**,那里有实测数据和核实方法;
|
||||
Dockerfile 的 `TZ` 和数据库连接的 `TimeZone` 是**刻意不设**的,别「顺手补上」。
|
||||
|
||||
## 数据库
|
||||
|
||||
Drizzle schema 最初是 `drizzle-kit pull` 从生产库拉出来的,所以它长得像 Django 建的表
|
||||
(表名、bigint/int4 混用),`schema.ts` 顶部记了哪些地方是手工修的。
|
||||
**schema 现在归 OJ2 独占**,结构变更走 migration 正常演进。
|
||||
|
||||
**外键的删除动作从 0010 起是显式的**,不再是 Django 留下的一律 NO ACTION:
|
||||
|
||||
- **CASCADE**:父行消失后子行必然无意义、且不构成「学生做过什么」的证据 —— 中间表
|
||||
(problem_tags)、题单/教程/成就的组成部分、一对一附属(user_profile)与可重算的缓存
|
||||
(user_stat)。
|
||||
- **NO ACTION(即拦住)**:需要人看见的删除 —— `submission.problem_id`、以及 `user` 的绝大
|
||||
多数外键。删用户撞外键会被 handler 翻译成「请改为禁用账号」,这是有意的。
|
||||
|
||||
**加新子表时必须回来想一遍该走哪一档**,别默认新外键会自己连坐 —— drizzle 不写
|
||||
`.onDelete()` 就是 NO ACTION,而 0010 只改了当时存在的那批。
|
||||
|
||||
### 改 schema 走 drizzle migration
|
||||
|
||||
`bun run db:generate`(造迁移文件)→ `bun run db:migrate`(按
|
||||
`drizzle.__drizzle_migrations` 增量执行),就是 Django `makemigrations` / `migrate` 的
|
||||
等价物。索引/结构变更走这条,不要再手写 SQL 往 `docs/` 里塞。
|
||||
|
||||
- **执行器是自己的**(`db/migrate.ts`,一条迁移一个事务),不是 drizzle 那个,
|
||||
`db:migrate` 和线上 `oj2-api migrate` 是同一条代码路径。
|
||||
- **部署时自动执行**:`docker/deploy.sh` 在构建镜像之后、起栈之前跑,失败就中止部署。
|
||||
- 迁移文件**不内嵌进二进制**,随镜像装在 `/usr/local/share/oj2/migrations`
|
||||
(见 `runtime.ts` 的 `migrationsDir`),所以新增迁移不用改任何代码。
|
||||
- **破坏性迁移默认拦截**(`DROP TABLE` / `DROP COLUMN` / `ALTER COLUMN ... TYPE` /
|
||||
`TRUNCATE`),退出 4,要显式放行:`OJ2_ALLOW_DESTRUCTIVE=1 docker/deploy.sh`。
|
||||
- **空库能自举**,直接从 `0000` 建起,新环境不需要先灌 schema dump。
|
||||
|
||||
`CREATE INDEX CONCURRENTLY` 怎么写、给已有库打基线的 SQL、`.op()` 会吞掉索引方向这类
|
||||
drizzle-kit 的坑,全在 `docs/database.md`。
|
||||
|
||||
## 部署
|
||||
|
||||
三套 compose 在 `docker/`:`dev`(本机)、`debian`(服务器)、`school`(机房)。
|
||||
@@ -264,10 +171,8 @@ drizzle-kit 的坑,全在 `docs/database.md`。
|
||||
**机房那套没有 postgres,连的是服务器的库。** 两个站点共用一个数据库,但各有各的 Redis
|
||||
和判题沙箱 —— 所以涉及两边的变更要一起做。
|
||||
|
||||
**服务器上的全部容器(含 postgres / redis)都由 `compose.debian.yml` 起**,升级哪个都只改
|
||||
仓库里的文件。以前库和 redis 归旧栈那份 `/root/OJDeploy/docker-compose.yml`、这边外接过去,
|
||||
那种形态已经删了(搬家脚本 `docker/move-db-into-oj2.sh`,一次性的)。库是 PostgreSQL 18,
|
||||
数据在 `$DATA_DIR/postgresql`;本机 dev 用同一个大版本,别单独换。
|
||||
库是 PostgreSQL 18,本机 dev 用同一个大版本,别单独换。迁移在部署时自动跑,**破坏性迁移
|
||||
默认拦截**,要 `OJ2_ALLOW_DESTRUCTIVE=1 docker/deploy.sh` 显式放行。
|
||||
|
||||
⚠️ **`DATA_DIR` 默认值 `../data` 是 `OJ2/data`,不是部署目录的 `data/`。** 沿用旧数据却忘了
|
||||
设它,会静默起一套空数据(空库、没测试点、图片 404),而且**不报错** —— 这是整个部署里
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# CLAUDE.md
|
||||
|
||||
OJ2 的后端(`apps/api`)。这里只放后端专属的坑;全仓通用的约束(旧仓库冻结、判题状态码、
|
||||
时间锚点、判题镜像、部署)在 `../../CLAUDE.md`。
|
||||
|
||||
## 单二进制是有代价的
|
||||
|
||||
`apps/api` 编译成 `bun build --compile` 的单二进制,所以**运行时不能依赖 node_modules**。
|
||||
任何 `require.resolve` / `Bun.resolveSync` / `__dirname` 去找文件的写法,本地都正常、编译后
|
||||
都会炸,而且**只在离开仓库目录后才炸**(在仓库里跑时它顺着 cwd 摸到了 node_modules,
|
||||
假装没事)。
|
||||
|
||||
资源要用 `with { type: "file" }` 内嵌。`.node` 原生模块还要额外注意:这个写法只有打包器认、
|
||||
`bun run` 不认,所以必须按形态分叉 —— 见 `src/vendor/jieba.ts` 的注释,那里把坑写全了。
|
||||
|
||||
**改完这类代码,dev 和编译两种形态都要跑一遍。** 我吃过亏:只验了编译产物,dev 直接起不来。
|
||||
|
||||
## 路径解析看 `runtime.ts`
|
||||
|
||||
编译后 `import.meta.dir` 恒为 `/$bunfs/root`,往上三级就是文件系统根。
|
||||
相对路径一律走 `runtime.ts` 的 `pathBase`,别自己拼。
|
||||
|
||||
## SQL 判题会 spawn「自己」
|
||||
|
||||
`judge/sql/index.ts` 起的子进程是二进制自身 + `sql-child` 子命令(因为编译后磁盘上没有
|
||||
child.ts 可以 spawn)。所以**入口必须有 argv 分发**,否则「起自己」变成「把整个程序再跑
|
||||
一遍」→ 指数级 fork。这不是假想,开发时炸过一次开发机。`OJ2_SQL_CHILD` 那道递归闸不要删。
|
||||
|
||||
## 加路由要防遮蔽
|
||||
|
||||
**Hono 按注册顺序匹配,不是静态优先**(实测确认过,别凭直觉)。`/problems/:id` 注册在
|
||||
`/problems/random` 前面的话,后者永远进不去 —— 而且不报错、不警告,只是静默走进前一条的
|
||||
handler。阶段 4 真实发生过一次,两个教师用的分析端点被吃掉,一直到评审才发现。
|
||||
|
||||
加完路由跑 `bun run --filter '@oj2/api' check:routes`。
|
||||
|
||||
## 出参不 `parse`,用 `satisfies`
|
||||
|
||||
**后端的响应一律 `satisfies XxxType`,不要写 `xxxSchema.parse({...})`。** 出参是后端自己刚
|
||||
拼出来的字面量,TS 已经在编译期校验过;再 parse 一遍拿不到任何新信息,唯一可能失败的输入是
|
||||
**库里的历史数据**,而失败的代价是 500 —— 这条规矩是被四次这样的线上故障换来的。
|
||||
|
||||
**闸设在写入侧**:入参 `safeParse`、`db/schema.ts` 的 `.$type<>()` 列收窄、
|
||||
语义校验函数(`astRulesError()` / `exerciseDataError`)。JSONB 原文
|
||||
(`submission.info` / `statistic_info` / `exercise.data`)一律放行,它们的形状真相在判题机
|
||||
那边。query 的筛选值走 `routes/helpers.ts` 的 `asFilterValue()`,那是纯类型交接、不加校验。
|
||||
|
||||
四次故障的细节、`.$type<>()` 断言该怎么核,见 `../../docs/contract.md`;
|
||||
前端为什么只在三处挂运行时闸门,见 `../web/CLAUDE.md`。
|
||||
|
||||
## 数据库
|
||||
|
||||
Drizzle schema 最初是 `drizzle-kit pull` 从生产库拉出来的,所以它长得像 Django 建的表
|
||||
(表名、bigint/int4 混用),`schema.ts` 顶部记了哪些地方是手工修的。
|
||||
**schema 现在归 OJ2 独占**,结构变更走 migration 正常演进。
|
||||
|
||||
**外键的删除动作从 0010 起是显式的**,不再是 Django 留下的一律 NO ACTION:
|
||||
|
||||
- **CASCADE**:父行消失后子行必然无意义、且不构成「学生做过什么」的证据 —— 中间表
|
||||
(problem_tags)、题单/教程/成就的组成部分、一对一附属(user_profile)与可重算的缓存
|
||||
(user_stat)。
|
||||
- **NO ACTION(即拦住)**:需要人看见的删除 —— `submission.problem_id`、以及 `user` 的绝大
|
||||
多数外键。删用户撞外键会被 handler 翻译成「请改为禁用账号」,这是有意的。
|
||||
|
||||
**加新子表时必须回来想一遍该走哪一档**,别默认新外键会自己连坐 —— drizzle 不写
|
||||
`.onDelete()` 就是 NO ACTION,而 0010 只改了当时存在的那批。
|
||||
|
||||
### 改 schema 走 drizzle migration
|
||||
|
||||
`bun run db:generate`(造迁移文件)→ `bun run db:migrate`(按
|
||||
`drizzle.__drizzle_migrations` 增量执行),就是 Django `makemigrations` / `migrate` 的
|
||||
等价物。索引/结构变更走这条,不要再手写 SQL 往 `docs/` 里塞。
|
||||
|
||||
- **部署时自动执行**:`docker/deploy.sh` 在构建镜像之后、起栈之前跑,失败就中止部署。
|
||||
- **破坏性迁移默认拦截**(`DROP TABLE` / `DROP COLUMN` / `ALTER COLUMN ... TYPE` /
|
||||
`TRUNCATE`),退出 4,要显式放行:`OJ2_ALLOW_DESTRUCTIVE=1 docker/deploy.sh`。
|
||||
|
||||
`CREATE INDEX CONCURRENTLY` 怎么写、给已有库打基线的 SQL、`.op()` 会吞掉索引方向这类
|
||||
drizzle-kit 的坑,全在 `../../docs/database.md`。
|
||||
@@ -37,7 +37,7 @@ export const pathBase = isCompiled ? process.cwd() : resolve(import.meta.dir, ".
|
||||
* 迁移文件(`0000_*.sql` … + `meta/_journal.json`)所在目录。
|
||||
*
|
||||
* 这些文件**不内嵌进二进制**,而是随镜像一起装到一个固定绝对路径下。
|
||||
* 之所以能这么做、也应该这么做:CLAUDE.md 里「单二进制是有代价的」那条讲的是
|
||||
* 之所以能这么做、也应该这么做:apps/api/CLAUDE.md 里「单二进制是有代价的」那条讲的是
|
||||
* 不能依赖 node_modules、不能拿 `import.meta.dir` 去推路径(编译后它恒为
|
||||
* `/$bunfs/root`,往上几级就跑到文件系统根)。按一个**显式给定的绝对路径**读一个
|
||||
* 数据目录不在此列。
|
||||
|
||||
+10
-5
@@ -8,10 +8,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
(`ojnext` 与 `../OnlineJudge` 都已下线且**完全冻结,一行都不改**)。Vue 3 + TypeScript,
|
||||
Vite(Rolldown 内核)、Naive UI、Pinia、Vue Router。
|
||||
|
||||
**浏览器基线是 Chrome 105**(机房部分电脑那一档):
|
||||
`vite.config.ts` 的 `@vitejs/plugin-legacy` 和写死的 polyfill 清单不能删 —— vite 8
|
||||
默认 target 是 chrome111,比机房高。
|
||||
理由写在该文件的注释里,详见 `../../CLAUDE.md`。
|
||||
**浏览器基线是 Chrome 105**(机房部分电脑那一档,其余更新 —— 按最低那档定基线):
|
||||
|
||||
- **`@vitejs/plugin-legacy` 留着,别删**:vite 8 的默认构建 target 是 `chrome111`,比 105 高。
|
||||
这个插件同时把 `build.target` 压到 `es2020/chrome105`、给现代产物补 core-js polyfill
|
||||
(`toSorted` / `Set` 运算 / 迭代器辅助那批是 Chrome 110+ 才有的)。`modernTargets` 不写,
|
||||
用插件自带的基线(`chrome>=105`),正好是这一档。polyfill 清单写死在 `vite.config.ts`,
|
||||
**升级前端依赖后重新审计**:先临时把 `modernPolyfills` 改成 `true`,再跑
|
||||
`DEBUG=vite:legacy bun run build` 才会打印探测到的全集(写死清单时它只回显清单)。
|
||||
- **View Transitions 要 111,105 没有**,`darkTransition.ts` 的降级分支是真在用的。
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -127,7 +132,7 @@ return contract("GET /problems/:id", problemDetailSchema, value)
|
||||
一道闸,一行脏数据能让整条练习列表 500。
|
||||
|
||||
**后端出参已经不 `parse` 了**(原来 136 处,全部改成 `satisfies`;撤的时候炸出两个
|
||||
一直存在的线上 500,见 `../../CLAUDE.md` 的「出参不 `parse`,用 `satisfies`」)。
|
||||
一直存在的线上 500,见 `../api/CLAUDE.md` 的「出参不 `parse`,用 `satisfies`」)。
|
||||
所以现在收紧一个字段的直接后果落在 **`tsc` 编译期**,而不再是运行时 500 —— 这是好事,
|
||||
但别因此就放心大胆收:契约里的形状仍然要对得上库里的存量数据,前端拿到对不上的值
|
||||
一样会渲染错。收紧任何字段之前,拿根目录那份生产备份把全量数据跑一遍,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 契约:闸门设在写入侧的来龙去脉
|
||||
|
||||
规矩在 `CLAUDE.md`「出参不 `parse`,用 `satisfies`」一节,前端那侧
|
||||
规矩在 `apps/api/CLAUDE.md`「出参不 `parse`,用 `satisfies`」一节,前端那侧
|
||||
(`utils/contract.ts` 为什么只挂三处)在 `apps/web/CLAUDE.md`。这里是证据。
|
||||
|
||||
## 读出侧校验自己造出来的四次故障
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 数据库与迁移
|
||||
|
||||
`CLAUDE.md` 里只留了日常要记住的那几条,这里是细节:迁移执行器为什么是自己的、
|
||||
`apps/api/CLAUDE.md` 里只留了日常要记住的那几条,这里是细节:迁移执行器为什么是自己的、
|
||||
空库怎么自举、给已有库打基线、以及 drizzle-kit 的几个坑。
|
||||
|
||||
## 迁移执行器是自己的,不是 drizzle 那个
|
||||
|
||||
+2
-1
@@ -58,7 +58,8 @@ docker exec -it oj-postgres psql -U onlinejudge -d postgres -c '\password online
|
||||
|
||||
`.github/workflows/deploy.yml` —— 在 runner 上编好产物、rsync 到服务器、在服务器上
|
||||
跑 `docker/deploy.sh --prebuilt`。触发的是 push 到 **github** 这个 remote
|
||||
(`origin` 是 `git.xuyue.cc`,平时那次 push 不触发 —— 那边是 Gitea,没有
|
||||
(`origin` 是 `git.xuyue.cc`,它会镜像到 github,所以**平时 push `origin` 通常几秒后
|
||||
也就部署了**,偶尔不跟、要 `git ls-remote github` 核实。Gitea 自己不跑这份工作流 —— 没有
|
||||
`.gitea/workflows` 时它的 Actions 会退回来读这份工作流,所以 job 上卡了
|
||||
`github.server_url == 'https://github.com'`,在 Gitea 上直接跳过):
|
||||
|
||||
|
||||
Reference in new issue
Block a user