Kỷ luật phụ thuộc
Ghi từ đợt rà thư viện 2026-08-25 (SRC-591). Bốn điều dưới đây đều là lỗi đã xảy ra thật trong chính đợt rà ấy, không phải lo xa.
1. Workspace nội bộ luôn là "*"
npm update --save đổi "@nemo12/design-system": "*" thành "^0.1.0" ở 10 package.json. Gói đó là private, không tồn tại trên registry — một dải phiên bản khiến npm đi tìm bên ngoài thay vì dùng bản trong repo.
Điều làm nó nguy hiểm: máy đã có node_modules thì không thấy gì cả. Nó chỉ nổ ở lần cài sạch, tức là ở CI, hoặc ở máy người mới. Cùng họ với bài học "check:docs local nói dối" trong CLAUDE.md.
Cổng: scripts/check-deps.mjs.
2. Một bản PATCH cũng đủ làm vỡ
@hono/zod-openapi 1.5.2 → 1.5.3 đổi kiểu Handler và làm đỏ 9 chỗ typecheck ở workers/api. Số hiệu phiên bản không hứa gì cả.
Vì thế gói này ghim tuyệt đối ("1.5.2", không ^ không ~). Muốn nâng thì nâng riêng nó, chạy npm run typecheck ngay, và sửa code trong cùng lần — không nâng kèm một lệnh update hàng loạt.
Cách truy khi một lệnh update làm đỏ: git stash phần package*.json → npm ci → typecheck. Sạch nghĩa là lỗi đến từ bản nâng, không phải từ code có sẵn.
Ghim vào
package.jsonchưa đủ nếupackage-lock.jsoncòn giữ nhánh cũ trongworkers/api/node_modules/...— phải gỡ khoá đó khỏi lock rồinpm installlại.
3. Cấm "latest" cho gói ngoài
Build phải tái lập được: cùng một commit, cài lại lúc nào cũng ra cùng cây phụ thuộc. "latest" phá đúng điều đó, và hỏng theo kiểu "hôm qua chạy được". Đã dọn @vitejs/plugin-react ở apps/web.
4. Lỗ hổng không phải lúc nào cũng vá được — và phải nói ra
npm audit báo 3 lỗ (2 moderate, 1 high) đến từ VitePress 1.6.4 kéo theo vite cũ và esbuild cũ. Bản 1.6.4 đã là mới nhất; đường vá duy nhất là 2.0.0-alpha.
Quyết định: không đưa alpha vào hai site tài liệu. Lý do đứng vững vì lỗ này thuộc dev server của esbuild — Pearl/Compass/Docs dựng tĩnh rồi đẩy lên Cloudflare, không chạy dev server ở đâu công khai. Ghi ra đây để lần audit sau không ai phải điều tra lại từ đầu; xem lại khi VitePress 2 ổn định.
4b. Nâng bốn major — làm được nhờ tra tài liệu TRƯỚC (SRC-592)
Đợt 2026-08-26 nâng ESLint 10 · TypeScript 7 · Vitest 4 · KaTeX 0.18, lần này có Context7 nên tra breaking change trước khi chạm vào package.json. Nhờ vậy biết ngay phần lớn không ảnh hưởng:
| Bản | Cái nó bỏ | Nemo12 có dính? |
|---|---|---|
| TypeScript 7 | moduleResolution: node/classic/node10, target: es5, module: amd/umd/system/none | không — 35 tsconfig đều bundler + ES2022 |
| TypeScript 7 | baseUrl gỡ hẳn | có — shadcn CLI vừa thêm hôm trước vào 6 tsconfig |
| Vitest 4 | option là tham số thứ BA của test/describe; workspace; coverage.all/extensions | không |
| KaTeX 0.17 | API nội bộ __defineFunction | không |
| ESLint 10 | (thêm rule) no-useless-assignment | có — 3 chỗ trong scripts/ |
Hai thứ phải sửa và cách sửa:
baseUrl: bỏ hẳn. TypeScript 7 giảipathstương đối với chínhtsconfig.json, nên"@/*": ["./src/*"]vẫn đúng mà không cầnbaseUrl.no-useless-assignment: cả 3 chỗ cùng một mẫu —let x = []; try { x = f(); } catch { continue; }. Giá trị khởi tạo không bao giờ được đọc. Khailet x;là đúng ý hơn: chưa có giá trị cho tới khi thử xong. Rule này bắt đúng, không phải càu nhàu phong cách.
Cách làm đáng giữ cho lần sau: tra tài liệu bản đích trước, đối chiếu với codebase bằng grep, rồi mới nâng. Nâng trước rồi đọc lỗi là cách chậm hơn và dễ hoảng.
5. Best practices đã rà trong đợt này
| Mặt | Kết quả |
|---|---|
React 19 (forwardRef, React.FC, defaultProps, useFormState, ref callback trả ngầm) | sạch — 0 chỗ |
Context.Provider → <Context> | sửa 1 chỗ (apps/learn/src/lang.tsx) |
Tailwind v4 (config JS, @tailwind, class đã bỏ) | sạch — đã CSS-first từ đầu |
<img> thiếu alt | sạch — 0 chỗ |
dangerouslySetInnerHTML | 5 chỗ: 4 là KaTeX (bắt buộc, đầu vào do hệ sinh), 1 là lỗ XSS thật đã vá — xem dưới |
Lỗ XSS đã vá. apps/marlins/src/ChildReport.tsx đổi **…** thành <strong> rồi nhét qua dangerouslySetInnerHTML. Câu ấy do server sinh nhưng có nhúng dữ liệu thật: tên Unit, và ở các nhánh khác là tên trường mục tiêu — thứ phụ huynh tự gõ. Nay tách chuỗi rồi để React dựng phần tử (BoldText.tsx); React tự thoát ký tự nên không còn đường nào để chữ biến thành mã.
Test không chạy cũng là test không tồn tại. apps/marlins có 3 file .test.ts nhưng package.json không có script test — chúng chưa từng chạy lần nào. Bật lên: 11 test có thật. Khi thêm test vào một workspace, kiểm luôn rằng npm test ở gốc có gọi tới nó.