diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b7f1a4..7b21acf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,6 +63,9 @@ jobs: - name: Run @repo/crawlers Open-Core Intelligence Tests run: pnpm --filter @repo/crawlers test + - name: Verify @repo/crawlers dist entry points (consumed by DOS/Rate-Services) + run: pnpm --filter @repo/crawlers test:dist + - name: Build & Typecheck @repo/extension (MV3) run: pnpm --filter @repo/extension build diff --git a/AGENTS.md b/AGENTS.md index 578171e..3df1688 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,6 +115,8 @@ Hệ thống Rate được tách thành **2 repositories riêng biệt** (và d > ⚠️ **CẤM TUYỆT ĐỐI:** Không bao giờ commit code giải Captcha, xoay Proxy, sniff API nội bộ của sàn, hoặc khoá bí mật (`SUPABASE_SERVICE_ROLE_KEY`, `AI_API_KEY`, `MPROXY_*`) vào `DOS/Rate`. +> 🔗 **Một nguồn sự thật cho Engine:** `DOS/Rate-Services` dùng `@repo/crawlers` (`apps/importer`) và `@repo/shared-data` (`packages/shared-data`) qua git submodule `vendor/rate` trỏ về `DOS/Rate` (pnpm workspace, import theo tên package, ví dụ `@repo/crawlers` hoặc `@repo/crawlers/shopee/arena-spec-syncer`). **KHÔNG** copy file engine sang Rate-Services. Cần dùng thêm module nào thì khai báo subpath tường minh trong `exports` của `apps/importer/package.json` (không dùng wildcard `./shopee/*` vì các script test chạy ngay khi import) và cập nhật `test:dist`. Mọi thay đổi engine phải merge vào `DOS/Rate` trước, sau đó bump submodule + chạy `pnpm install` để cập nhật `pnpm-lock.yaml` bên Rate-Services. + --- ## 3. Kiến Trúc Kỹ Thuật Cốt Lõi & 4-Layer Backend (Zero-Strapi 2026) @@ -151,7 +153,11 @@ pnpm --filter @repo/crawlers exec tsx src/shopee/test-review-analyzer.ts pnpm --filter @repo/web typecheck pnpm --filter @repo/web build -# Tại D:\Projects\Rate-Services -npm run build -npm run test:enricher +pnpm --filter @repo/crawlers test:dist + +# Tại D:\Projects\Rate-Services (pnpm workspace + submodule vendor/rate) +git submodule update --init +pnpm install +pnpm build +pnpm test:enricher ``` diff --git a/CLAUDE.md b/CLAUDE.md index 346c4a5..7922316 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,7 @@ Hệ thống **Rate Platform (`rate.vn`)** đã loại bỏ hoàn toàn Strapi C 2. **🔒 `DOS/Rate-Services` (Private Enterprise - `D:\Projects\Rate-Services`):** - Chứa toàn bộ phần nhạy cảm: Playwright Stealth Scrapers (`shopee-scraper.ts`, `shopee-search-scraper.ts`, `sniff-search-api.ts`), Giải Captcha (`shopee-captcha-solver.ts`), Xoay Proxy (`proxy-manager.ts`), và Queue Workers (`queue-worker.ts`). - **TUYỆT ĐỐI KHÔNG** đưa code bypass anti-bot/captcha/proxy vào repo Public `DOS/Rate`. + - Engine Open-Core (`@repo/crawlers`, `@repo/shared-data`) được dùng qua git submodule `vendor/rate` (pnpm workspace). **KHÔNG** copy file engine sang Rate-Services; sửa engine tại `DOS/Rate` rồi bump submodule. --- diff --git a/Docs/Resources/ROADMAP.md b/Docs/Resources/ROADMAP.md index 912dc11..d7ea18d 100644 --- a/Docs/Resources/ROADMAP.md +++ b/Docs/Resources/ROADMAP.md @@ -250,3 +250,7 @@ Hệ thống **Rate Platform (`rate.vn`)** vận hành trên kiến trúc **4 T - [x] **5.18. Bộ Kiểm Toán Độ Lệch Giá Đa Sàn (`PriceSpreadAuditor`) & Bảng Xếp Hạng Chênh Lệch Giá 10 Sàn (`priceSpreadLeaders` - Issue #13 bên `DOS/Rate-Services` & Issue #92 bên `DOS/Rate`):** - Xây dựng `PriceSpreadAuditor` (`src/scrapers/price-spread-auditor.ts`) tích hợp vào Step 6 của `src/index.ts`, tự động tính toán độ lệch giá tuyệt đối (`spreadVnd = maxPrice - minPrice`), tỷ lệ chênh lệch (`spreadPct`), sàn rẻ nhất và sàn đắt nhất cho toàn bộ `36/36` Master Products trên 10 sàn, đồng bộ vào `key_specs.price_spread_audit` trên `rate.master_products`. - Mở rộng `getCrossPlatformDealRadar` (`apps/web/src/server/rate/deals.ts`) và giao diện Chrome Side Panel (`apps/extension/src/sidepanel/sidepanel.ts`) hiển thị bảng **📊 Chênh Lệch Giá Đa Sàn Lớn Nhất (`priceSpreadLeaders`)**. +- [x] **5.19. Một Nguồn Sự Thật Cho Engine Open-Core: Rate-Services Dùng `@repo/crawlers` & `@repo/shared-data` Qua Git Submodule `vendor/rate` (Issue #103, PR #105 & PR #107 bên `DOS/Rate`, Issue #14 & PR #15 bên `DOS/Rate-Services`):** + - Kiểm toán độ lệch giữa 2 bản copy engine: sau khi chuẩn hóa format bằng prettier, bản copy trong `DOS/Rate-Services` không còn dòng logic nào mà `DOS/Rate` thiếu (`DOS/Rate` là superset), nên xóa bản copy không làm mất logic. + - Chuẩn hóa entry point build cho `@repo/crawlers` (`rootDir: ./src`, `declaration: true`, `exports` tường minh cho `.`, `./shopee/arena-spec-syncer`, `./shopee/flagship-seed-data`; các script test chạy ngay khi import được giữ private, PR #105) và thêm smoke test `pnpm --filter @repo/crawlers test:dist` vào CI để chặn hồi quy (PR #107). + - Sửa lỗi build `@repo/crawlers` làm CI `dev` đỏ từ 5.10: 8 seed đa ngành hàng chưa có `expert_review_summary` / `expert_criteria_scores` nên 2 field này chuyển thành optional trong `FlagshipDeviceSeed` (web đã có fallback từ thông số). diff --git a/apps/importer/package.json b/apps/importer/package.json index 8986590..668b909 100644 --- a/apps/importer/package.json +++ b/apps/importer/package.json @@ -28,7 +28,8 @@ "dev": "tsx watch src/index.ts", "lint": "eslint . --ext .ts", "format": "prettier --write \"src/**/*.ts\"", - "test": "tsx src/shopee/__tests__/open-core-intelligence.test.ts && tsx src/shopee/test-review-analyzer.ts" + "test": "tsx src/shopee/__tests__/open-core-intelligence.test.ts && tsx src/shopee/test-review-analyzer.ts", + "test:dist": "node src/shopee/__tests__/dist-exports.smoke.mjs" }, "keywords": [ "product-normalizer", diff --git a/apps/importer/src/shopee/__tests__/dist-exports.smoke.mjs b/apps/importer/src/shopee/__tests__/dist-exports.smoke.mjs new file mode 100644 index 0000000..28b67ea --- /dev/null +++ b/apps/importer/src/shopee/__tests__/dist-exports.smoke.mjs @@ -0,0 +1,81 @@ +/** + * Smoke test for the built @repo/crawlers & @repo/shared-data entry points. + * Imports by package name (Node self-reference via "exports"), the same way + * DOS/Rate-Services consumes the engine from its vendor/rate checkout. + * Run after `pnpm --filter @repo/shared-data build && pnpm --filter @repo/crawlers build`. + */ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import { createRequire } from 'node:module'; + +const crawlers = await import('@repo/crawlers'); +for (const name of [ + 'ProductNormalizer', + 'detectCategorySlug', + 'EntityMatcher', + 'AiSpecEnricher', + 'sanitizeSpecifications', + 'AiReviewAnalyzer', + 'computeReviewWeight', + 'ShopeeIngestionPipeline', +]) { + assert.equal( + typeof crawlers[name], + 'function', + `@repo/crawlers is missing export ${name}` + ); +} + +const { ArenaSpecSyncer } = + await import('@repo/crawlers/shopee/arena-spec-syncer'); +assert.equal( + typeof ArenaSpecSyncer, + 'function', + 'ArenaSpecSyncer subpath export is broken' +); + +const { FLAGSHIP_DEVICES_TO_SEED } = + await import('@repo/crawlers/shopee/flagship-seed-data'); +assert.ok( + FLAGSHIP_DEVICES_TO_SEED.length > 0, + 'flagship-seed-data subpath export is empty' +); + +const { ProductSpecificationsSchema } = await import('@repo/shared-data'); +assert.equal( + typeof ProductSpecificationsSchema?.safeParse, + 'function', + '@repo/shared-data is broken' +); + +// Scripts that execute on import (test-ai-enricher, test-review-analyzer, +// run-ingestion-test) must stay private. Resolve only, never import them. +for (const script of [ + 'test-ai-enricher', + 'test-review-analyzer', + 'run-ingestion-test', +]) { + assert.throws( + () => import.meta.resolve(`@repo/crawlers/shopee/${script}`), + { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' }, + `@repo/crawlers/shopee/${script} must not be exported` + ); +} + +// Type declarations must ship with the entry points. +const require = createRequire(import.meta.url); +const pkgDir = require + .resolve('@repo/crawlers/package.json') + .replace(/package\.json$/, ''); +for (const dts of [ + 'dist/index.d.ts', + 'dist/shopee/types.d.ts', + 'dist/shopee/arena-spec-syncer.d.ts', + 'dist/shopee/flagship-seed-data.d.ts', +]) { + assert.ok(fs.existsSync(pkgDir + dts), `Missing type declarations: ${dts}`); +} + +console.log( + '✅ @repo/crawlers & @repo/shared-data dist entry points resolve by package name.' +);