From b774c608584da2adf03cf0a292c56e2b995a0986 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 24 Mar 2024 11:00:03 +0000 Subject: [PATCH] test: add full routes test (#14934) * test: add critical routes test * fix: youtube channels config * test: add full routes test * test: output full routes test result * test: build routes before test * test: use one reporter * test: fix cache test errors * test: fix cache test errors * chore: puppeteerrc.cjs * chore: puppeteerrc.cjs * chore: puppeteerrc.cjs * fix: build script * chore: puppeteerrc rename * test: route test names * chore: pass test result to docs --- .github/workflows/build-assets.yml | 6 +- .github/workflows/test.yml | 5 +- .puppeteerrc.js => .puppeteerrc.cjs | 0 Dockerfile | 2 +- lib/middleware/cache.test.ts | 15 +--- lib/registry.test.ts | 59 +------------- lib/registry.ts | 15 +--- lib/routes.test.ts | 78 ++++++++++++++++++ lib/routes/afdian/dynamic.ts | 1 + lib/routes/bilibili/dynamic.ts | 1 - lib/routes/test/index.ts | 10 +-- lib/routes/youtube/channel.ts | 7 +- package.json | 16 ++-- pnpm-lock.yaml | 11 --- scripts/workflow/build-docs.ts | 122 ++++++++++++++++++++++++++++ scripts/workflow/build-routes.ts | 88 +------------------- tsconfig.json | 9 +- 17 files changed, 247 insertions(+), 198 deletions(-) rename .puppeteerrc.js => .puppeteerrc.cjs (100%) create mode 100644 lib/routes.test.ts create mode 100644 scripts/workflow/build-docs.ts diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml index 0afe56e4664ddf..3b1f4febcdbe1e 100644 --- a/.github/workflows/build-assets.yml +++ b/.github/workflows/build-assets.yml @@ -33,7 +33,11 @@ jobs: - name: Install dependencies (yarn) run: pnpm i - name: Build assets - run: npm run build + run: pnpm build + - name: Build full routes test result + run: pnpm vitest:fullroutes + - name: Build docs + run: pnpm build:docs - name: Commit files run: | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 523d0c80f805b0..30a978ed7543d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ on: branches-ignore: - 'dependabot/**' paths: - - 'test/**' - 'lib/**' - 'package.json' - 'pnpm-lock.yaml' @@ -43,6 +42,8 @@ jobs: run: pnpm i - name: Run postinstall script for dependencies run: pnpm rb + - name: Build routes + run: pnpm build - name: Test all and generate coverage run: pnpm run vitest:coverage env: @@ -84,6 +85,8 @@ jobs: run: pnpm i - name: Run postinstall script for dependencies run: pnpm rb + - name: Build routes + run: pnpm build - name: Install Chromium if: ${{ matrix.chromium.dependency != '' }} # 'chromium-browser' from Ubuntu APT repo is a dummy package. Its version (85.0.4183.83) means diff --git a/.puppeteerrc.js b/.puppeteerrc.cjs similarity index 100% rename from .puppeteerrc.js rename to .puppeteerrc.cjs diff --git a/Dockerfile b/Dockerfile index 22a31a8bf3e2e2..228cf0c493b092 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,7 +84,7 @@ FROM node:21-bookworm-slim AS chromium-downloader # Yeah, downloading Chromium never needs those dependencies below. WORKDIR /app -COPY ./.puppeteerrc.js /app/ +COPY ./.puppeteerrc.cjs /app/ COPY --from=dep-version-parser /ver/.puppeteer_version /app/.puppeteer_version ARG TARGETPLATFORM diff --git a/lib/middleware/cache.test.ts b/lib/middleware/cache.test.ts index bb526daab7f318..7edce9fc5baf43 100644 --- a/lib/middleware/cache.test.ts +++ b/lib/middleware/cache.test.ts @@ -1,23 +1,16 @@ -import { describe, expect, it, vi, afterEach, afterAll, beforeAll } from 'vitest'; +import { describe, expect, it, vi, afterEach } from 'vitest'; import Parser from 'rss-parser'; import wait from '@/utils/wait'; -const parser = new Parser(); +process.env.CACHE_EXPIRE = '1'; +process.env.CACHE_CONTENT_EXPIRE = '3'; -beforeAll(() => { - process.env.CACHE_EXPIRE = '1'; - process.env.CACHE_CONTENT_EXPIRE = '3'; -}); +const parser = new Parser(); afterEach(() => { - delete process.env.CACHE_TYPE; vi.resetModules(); }); -afterAll(() => { - delete process.env.CACHE_EXPIRE; -}); - describe('cache', () => { it('memory', async () => { process.env.CACHE_TYPE = 'memory'; diff --git a/lib/registry.test.ts b/lib/registry.test.ts index ebc5c7526d56b7..d168eb59906f99 100644 --- a/lib/registry.test.ts +++ b/lib/registry.test.ts @@ -1,55 +1,8 @@ -import { describe, expect, it, afterAll } from 'vitest'; -process.env.SOCKET = 'socket'; - +import { describe, expect, it } from 'vitest'; import app from '@/app'; -import Parser from 'rss-parser'; -const parser = new Parser(); import { config } from '@/config'; -afterAll(() => { - delete process.env.SOCKET; -}); - -async function checkRSS(response) { - const checkDate = (date) => { - expect(date).toEqual(expect.any(String)); - expect(Date.parse(date)).toEqual(expect.any(Number)); - expect(Date.now() - +new Date(date)).toBeGreaterThan(-1000 * 60 * 60 * 24 * 5); - expect(Date.now() - +new Date(date)).toBeLessThan(1000 * 60 * 60 * 24 * 30 * 12 * 10); - }; - - const parsed = await parser.parseString(await response.text()); - - expect(parsed).toEqual(expect.any(Object)); - expect(parsed.title).toEqual(expect.any(String)); - expect(parsed.title).not.toBe('RSSHub'); - expect(parsed.description).toEqual(expect.any(String)); - expect(parsed.link).toEqual(expect.any(String)); - expect(parsed.lastBuildDate).toEqual(expect.any(String)); - expect(parsed.ttl).toEqual(Math.trunc(config.cache.routeExpire / 60) + ''); - expect(parsed.items).toEqual(expect.any(Array)); - checkDate(parsed.lastBuildDate); - - // check items - const guids: (string | undefined)[] = []; - for (const item of parsed.items) { - expect(item).toEqual(expect.any(Object)); - expect(item.title).toEqual(expect.any(String)); - expect(item.link).toEqual(expect.any(String)); - expect(item.content).toEqual(expect.any(String)); - expect(item.guid).toEqual(expect.any(String)); - if (item.pubDate) { - expect(item.pubDate).toEqual(expect.any(String)); - checkDate(item.pubDate); - } - - // guid must be unique - expect(guids).not.toContain(item.guid); - guids.push(item.guid); - } -} - -describe('router', () => { +describe('registry', () => { // root it(`/`, async () => { const response = await app.request('/'); @@ -58,14 +11,6 @@ describe('router', () => { expect(response.headers.get('cache-control')).toBe('no-cache'); }); - // route - it(`/test/1`, async () => { - const response = await app.request('/test/1'); - expect(response.status).toBe(200); - - await checkRSS(response); - }); - // robots.txt it('/robots.txt', async () => { config.disallowRobot = false; diff --git a/lib/registry.ts b/lib/registry.ts index 0fef532aedf4e6..74101502df2bd9 100644 --- a/lib/registry.ts +++ b/lib/registry.ts @@ -7,8 +7,6 @@ import { serveStatic } from '@hono/node-server/serve-static'; import index from '@/routes/index'; import robotstxt from '@/routes/robots.txt'; -import { namespace as testNamespace } from './routes/test/namespace'; -import { route as testRoute } from '@/routes/test/index'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -27,18 +25,9 @@ let namespaces: Record< switch (process.env.NODE_ENV) { case 'test': - modules = { - '/test/namespace.ts': { - namespace: testNamespace, - }, - '/test/index.ts': { - route: testRoute, - }, - }; - break; case 'production': - // eslint-disable-next-line n/no-unpublished-require - namespaces = require('../assets/build/routes.json'); + // @ts-expect-error + namespaces = await import('../assets/build/routes.json'); break; default: modules = directoryImport({ diff --git a/lib/routes.test.ts b/lib/routes.test.ts new file mode 100644 index 00000000000000..20fedf119bdc21 --- /dev/null +++ b/lib/routes.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest'; +import app from '@/app'; +import Parser from 'rss-parser'; +const parser = new Parser(); +import { config } from '@/config'; + +process.env.ALLOW_USER_SUPPLY_UNSAFE_DOMAIN = 'true'; + +const routes = { + '/test/:id': '/test/1', +}; +if (process.env.FULL_ROUTES_TEST) { + const { namespaces } = await import('@/registry'); + for (const namespace in namespaces) { + for (const route in namespaces[namespace].routes) { + const requireConfig = namespaces[namespace].routes[route].features?.requireConfig; + let configs; + if (typeof requireConfig !== 'boolean') { + configs = requireConfig + ?.filter((config) => !config.optional) + .map((config) => config.name) + .filter((name) => name !== 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN'); + } + if (namespaces[namespace].routes[route].example && !configs?.length) { + routes[`/${namespace}${route}`] = namespaces[namespace].routes[route].example; + } + } + } +} + +async function checkRSS(response) { + const checkDate = (date) => { + expect(date).toEqual(expect.any(String)); + expect(Date.parse(date)).toEqual(expect.any(Number)); + expect(Date.now() - +new Date(date)).toBeGreaterThan(-1000 * 60 * 60 * 24 * 5); + expect(Date.now() - +new Date(date)).toBeLessThan(1000 * 60 * 60 * 24 * 30 * 12 * 10); + }; + + const parsed = await parser.parseString(await response.text()); + + expect(parsed).toEqual(expect.any(Object)); + expect(parsed.title).toEqual(expect.any(String)); + expect(parsed.title).not.toBe('RSSHub'); + expect(parsed.description).toEqual(expect.any(String)); + expect(parsed.link).toEqual(expect.any(String)); + expect(parsed.lastBuildDate).toEqual(expect.any(String)); + expect(parsed.ttl).toEqual(Math.trunc(config.cache.routeExpire / 60) + ''); + expect(parsed.items).toEqual(expect.any(Array)); + checkDate(parsed.lastBuildDate); + + // check items + const guids: (string | undefined)[] = []; + for (const item of parsed.items) { + expect(item).toEqual(expect.any(Object)); + expect(item.title).toEqual(expect.any(String)); + expect(item.link).toEqual(expect.any(String)); + expect(item.content).toEqual(expect.any(String)); + expect(item.guid).toEqual(expect.any(String)); + if (item.pubDate) { + expect(item.pubDate).toEqual(expect.any(String)); + checkDate(item.pubDate); + } + + // guid must be unique + expect(guids).not.toContain(item.guid); + guids.push(item.guid); + } +} + +describe('routes', () => { + for (const route in routes) { + it.concurrent(route, async () => { + const response = await app.request(routes[route]); + expect(response.status).toBe(200); + await checkRSS(response); + }); + } +}); diff --git a/lib/routes/afdian/dynamic.ts b/lib/routes/afdian/dynamic.ts index f69f6edc1a962e..2819a48a9e5c65 100644 --- a/lib/routes/afdian/dynamic.ts +++ b/lib/routes/afdian/dynamic.ts @@ -1,4 +1,5 @@ import got from '@/utils/got'; +import type { Route } from '@/types'; export const route: Route = { path: '/dynamic/:uid?', diff --git a/lib/routes/bilibili/dynamic.ts b/lib/routes/bilibili/dynamic.ts index 0420620a69cd20..8e127506eb3bb5 100644 --- a/lib/routes/bilibili/dynamic.ts +++ b/lib/routes/bilibili/dynamic.ts @@ -121,7 +121,6 @@ async function handler(ctx) { headers: { Referer: `https://space.bilibili.com/${uid}/`, }, - transformResponse: [(data) => data], }); const cards = JSONbig.parse(response.body).data.cards; diff --git a/lib/routes/test/index.ts b/lib/routes/test/index.ts index 52c884ea0dda61..752fad595758f0 100644 --- a/lib/routes/test/index.ts +++ b/lib/routes/test/index.ts @@ -79,13 +79,9 @@ async function handler(ctx) { break; case 'cache': { - const description = await cache.tryGet( - 'test', - () => ({ - text: `Cache${++cacheIndex}`, - }), - config.cache.routeExpire * 2 - ); + const description = await cache.tryGet('test', () => ({ + text: `Cache${++cacheIndex}`, + })); item.push({ title: 'Cache Title', description: description.text, diff --git a/lib/routes/youtube/channel.ts b/lib/routes/youtube/channel.ts index 426958bc444693..dbfb5d1bbd3433 100644 --- a/lib/routes/youtube/channel.ts +++ b/lib/routes/youtube/channel.ts @@ -10,7 +10,12 @@ export const route: Route = { example: '/youtube/channel/UCDwDMPOZfxVV0x_dz0eQ8KQ', parameters: { id: 'YouTube channel id', embed: 'Default to embed the video, set to any value to disable embedding' }, features: { - requireConfig: false, + requireConfig: [ + { + name: 'YOUTUBE_KEY', + description: ' YouTube API Key, support multiple keys, split them with `,`, [API Key application](https://console.developers.google.com/)', + }, + ], requirePuppeteer: false, antiCrawler: false, supportBT: false, diff --git a/package.json b/package.json index f893d3a7a76c70..d3cedaeed56626 100644 --- a/package.json +++ b/package.json @@ -19,20 +19,23 @@ "files": [ "lib" ], + "type": "module", "scripts": { "build": "tsx scripts/workflow/build-routes.ts", - "dev": "cross-env NODE_ENV=dev tsx watch --no-cache lib/index.ts", - "dev:cache": "cross-env NODE_ENV=production tsx watch lib/index.ts", + "build:docs": "tsx scripts/workflow/build-docs.ts", + "dev": "NODE_ENV=dev tsx watch --no-cache lib/index.ts", + "dev:cache": "NODE_ENV=production tsx watch lib/index.ts", "format": "eslint --cache --fix \"**/*.{ts,js,yml}\" && prettier \"**/*.{ts,js,json}\" --write", "format:check": "eslint --cache \"**/*.{ts,js,yml}\" && prettier \"**/*.{ts,js,json}\" --check", "format:staged": "lint-staged", - "vitest": "cross-env NODE_ENV=test vitest", - "vitest:coverage": "cross-env NODE_ENV=test vitest --coverage.enabled --reporter=junit", - "vitest:watch": "cross-env NODE_ENV=test vitest --watch", + "vitest": "NODE_ENV=test vitest", + "vitest:fullroutes": "NODE_ENV=test FULL_ROUTES_TEST=true vitest --reporter=json --reporter=default --outputFile=\"./assets/build/test-full-routes.json\" routes", + "vitest:coverage": "NODE_ENV=test vitest --coverage.enabled --reporter=junit", + "vitest:watch": "NODE_ENV=test vitest --watch", "lint": "eslint --cache .", "prepare": "husky || true", "profiling": "NODE_ENV=production tsx --prof lib/index.ts", - "start": "cross-env NODE_ENV=production tsx lib/index.ts", + "start": "NODE_ENV=production tsx lib/index.ts", "test": "npm run format:check && npm run vitest:coverage" }, "lint-staged": { @@ -147,7 +150,6 @@ "@typescript-eslint/parser": "7.3.1", "@vercel/nft": "0.26.4", "@vitest/coverage-v8": "1.4.0", - "cross-env": "7.0.3", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-nibble": "8.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76e6faa3aa0568..243f04630a0f66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -301,9 +301,6 @@ devDependencies: '@vitest/coverage-v8': specifier: 1.4.0 version: 1.4.0(vitest@1.4.0) - cross-env: - specifier: 7.0.3 - version: 7.0.3 eslint: specifier: 8.57.0 version: 8.57.0 @@ -3901,14 +3898,6 @@ packages: typescript: 5.4.3 dev: false - /cross-env@7.0.3: - resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} - engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} - hasBin: true - dependencies: - cross-spawn: 7.0.3 - dev: true - /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: diff --git a/scripts/workflow/build-docs.ts b/scripts/workflow/build-docs.ts new file mode 100644 index 00000000000000..8f019ac928cf27 --- /dev/null +++ b/scripts/workflow/build-docs.ts @@ -0,0 +1,122 @@ +import { namespaces } from '../../lib/registry'; +import fs from 'node:fs'; +import * as path from 'node:path'; +import { categories } from './data'; +import fullTests from '../../assets/build/test-full-routes.json'; +import { getCurrentPath } from '../../lib/utils/helpers'; + +const testResult = fullTests.testResults[0].assertionResults; + +const __dirname = getCurrentPath(import.meta.url); + +const docs = {}; + +for (const namespace in namespaces) { + let defaultCategory = namespaces[namespace].categories?.[0]; + if (!defaultCategory) { + for (const path in namespaces[namespace].routes) { + if (namespaces[namespace].routes[path].categories) { + defaultCategory = namespaces[namespace].routes[path].categories[0]; + break; + } + } + } + if (!defaultCategory) { + defaultCategory = 'other'; + } + for (const path in namespaces[namespace].routes) { + const realPath = `/${namespace}${path}`; + const data = namespaces[namespace].routes[path]; + const categories = data.categories || namespaces[namespace].categories || [defaultCategory]; + // docs.json + for (const category of categories) { + if (!docs[category]) { + docs[category] = {}; + } + if (!docs[category][namespace]) { + docs[category][namespace] = { + routes: {}, + }; + } + docs[category][namespace].name = namespaces[namespace].name; + docs[category][namespace].url = namespaces[namespace].url; + docs[category][namespace].description = namespaces[namespace].description; + docs[category][namespace].routes[realPath] = data; + } + } +} + +// Generate markdown +const pinyinCompare = new Intl.Collator('zh-Hans-CN-u-co-pinyin').compare; +const isASCII = (str) => /^[\u0000-\u007F]*$/.test(str); + +function generateMd(lang) { + const md = {}; + for (const category in docs) { + const nameObj = categories.find((c) => c.link.includes(category)); + if (!nameObj) { + throw new Error(`Category not found: ${category}, please double check your spelling.`); + } + + md[category] = `# ${`${nameObj.icon} ${nameObj[lang]}`}\n\n`; + + const namespaces = Object.keys(docs[category]).sort((a, b) => { + const aname = docs[category][a].name[0]; + const bname = docs[category][b].name[0]; + const ia = isASCII(aname); + const ib = isASCII(bname); + if (ia && ib) { + return aname.toLowerCase() < bname.toLowerCase() ? -1 : 1; + } else if (ia || ib) { + return ia > ib ? -1 : 1; + } else { + return pinyinCompare(aname, bname); + } + }); + for (const namespace of namespaces) { + if (docs[category][namespace].name === 'Unknown') { + docs[category][namespace].name = namespace; + } + md[category] += `## ${docs[category][namespace].name || namespace} ${docs[category][namespace].url ? `` : ''}\n\n`; + if (docs[category][namespace].description) { + md[category] += `${docs[category][namespace].description}\n\n`; + } + + const realPaths = Object.keys(docs[category][namespace].routes).sort((a, b) => { + const aname = docs[category][namespace].routes[a].name[0]; + const bname = docs[category][namespace].routes[b].name[0]; + const ia = isASCII(aname); + const ib = isASCII(bname); + if (ia && ib) { + return aname.toLowerCase() < bname.toLowerCase() ? -1 : 1; + } else if (ia || ib) { + return ia > ib ? -1 : 1; + } else { + return pinyinCompare(aname, bname); + } + }); + + for (const realPath of realPaths) { + const data = docs[category][namespace].routes[realPath]; + const test = testResult.find((t) => t.title === realPath); + const parsedTest = test + ? { + code: test.status === 'passed' ? 0 : 1, + message: test.failureMessages?.[0], + } + : undefined; + md[category] += `### ${data.name} ${data.url || docs[category][namespace].url ? `` : ''}\n\n`; + md[category] += `\n\n`; + if (data.description) { + md[category] += `${data.description}\n\n`; + } + } + } + } + fs.mkdirSync(path.join(__dirname, `../../assets/build/docs/${lang}`), { recursive: true }); + for (const category in md) { + fs.writeFileSync(path.join(__dirname, `../../assets/build/docs/${lang}/${category}.md`), md[category]); + } +} +generateMd('en'); +generateMd('zh'); diff --git a/scripts/workflow/build-routes.ts b/scripts/workflow/build-routes.ts index af441dfd5fa304..3a4634cc1f288a 100644 --- a/scripts/workflow/build-routes.ts +++ b/scripts/workflow/build-routes.ts @@ -4,7 +4,9 @@ import { parse } from 'tldts'; import fs from 'node:fs'; import * as path from 'node:path'; import toSource from 'tosource'; -import { categories } from './data'; + +import { getCurrentPath } from '../../lib/utils/helpers'; +const __dirname = getCurrentPath(import.meta.url); const maintainers: Record = {}; const radar: { @@ -13,7 +15,6 @@ const radar: { [subdomain: string]: RadarItem[] | string; }; } = {}; -const docs = {}; for (const namespace in namespaces) { let defaultCategory = namespaces[namespace].categories?.[0]; @@ -63,21 +64,6 @@ for (const namespace in namespaces) { } } } - // docs.json - for (const category of categories) { - if (!docs[category]) { - docs[category] = {}; - } - if (!docs[category][namespace]) { - docs[category][namespace] = { - routes: {}, - }; - } - docs[category][namespace].name = namespaces[namespace].name; - docs[category][namespace].url = namespaces[namespace].url; - docs[category][namespace].description = namespaces[namespace].description; - docs[category][namespace].routes[realPath] = data; - } } } @@ -85,71 +71,3 @@ fs.writeFileSync(path.join(__dirname, '../../assets/build/radar-rules.json'), JS fs.writeFileSync(path.join(__dirname, '../../assets/build/radar-rules.js'), `(${toSource(radar)})`); fs.writeFileSync(path.join(__dirname, '../../assets/build/maintainers.json'), JSON.stringify(maintainers, null, 2)); fs.writeFileSync(path.join(__dirname, '../../assets/build/routes.json'), JSON.stringify(namespaces, null, 2)); - -// Generate markdown -const pinyinCompare = new Intl.Collator('zh-Hans-CN-u-co-pinyin').compare; -const isASCII = (str) => /^[\u0000-\u007F]*$/.test(str); - -function generateMd(lang) { - const md = {}; - for (const category in docs) { - const nameObj = categories.find((c) => c.link.includes(category)); - if (!nameObj) { - throw new Error(`Category not found: ${category}, please double check your spelling.`); - } - - md[category] = `# ${`${nameObj.icon} ${nameObj[lang]}`}\n\n`; - - const namespaces = Object.keys(docs[category]).sort((a, b) => { - const aname = docs[category][a].name[0]; - const bname = docs[category][b].name[0]; - const ia = isASCII(aname); - const ib = isASCII(bname); - if (ia && ib) { - return aname.toLowerCase() < bname.toLowerCase() ? -1 : 1; - } else if (ia || ib) { - return ia > ib ? -1 : 1; - } else { - return pinyinCompare(aname, bname); - } - }); - for (const namespace of namespaces) { - if (docs[category][namespace].name === 'Unknown') { - docs[category][namespace].name = namespace; - } - md[category] += `## ${docs[category][namespace].name || namespace} ${docs[category][namespace].url ? `` : ''}\n\n`; - if (docs[category][namespace].description) { - md[category] += `${docs[category][namespace].description}\n\n`; - } - - const realPaths = Object.keys(docs[category][namespace].routes).sort((a, b) => { - const aname = docs[category][namespace].routes[a].name[0]; - const bname = docs[category][namespace].routes[b].name[0]; - const ia = isASCII(aname); - const ib = isASCII(bname); - if (ia && ib) { - return aname.toLowerCase() < bname.toLowerCase() ? -1 : 1; - } else if (ia || ib) { - return ia > ib ? -1 : 1; - } else { - return pinyinCompare(aname, bname); - } - }); - - for (const realPath of realPaths) { - const data = docs[category][namespace].routes[realPath]; - md[category] += `### ${data.name} ${data.url || docs[category][namespace].url ? `` : ''}\n\n`; - md[category] += `\n\n`; - if (data.description) { - md[category] += `${data.description}\n\n`; - } - } - } - } - fs.mkdirSync(path.join(__dirname, `../../assets/build/docs/${lang}`), { recursive: true }); - for (const category in md) { - fs.writeFileSync(path.join(__dirname, `../../assets/build/docs/${lang}/${category}.md`), md[category]); - } -} -generateMd('en'); -generateMd('zh'); diff --git a/tsconfig.json b/tsconfig.json index 5135a96656c2f3..20bc583435996f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ESNext", "module": "ESNext", - "moduleResolution": "Bundler", + "moduleResolution": "node", "strict": true, "jsx": "react-jsx", "jsxImportSource": "hono/jsx", @@ -11,7 +11,12 @@ }, "esModuleInterop": true, "noImplicitAny": false, - "outDir": "./dist" + "outDir": "./dist", + "skipLibCheck": true, + "noEmit": true, + "incremental": true, + "resolveJsonModule": true, + "isolatedModules": true }, "include": ["./lib/**/*", "./api/vercel.ts"], "exclude": ["node_modules", "*.test.*"]