diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6033d3df..40f56bb5 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,9 +13,8 @@ jobs: typescript: uses: ./.github/workflows/typescript-typecheck.yml - # todo: add tests - # tests: - # uses: ./.github/workflows/tests.yml + test: + uses: ./.github/workflows/test.yml website-preview: uses: ./.github/workflows/website.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..a3779e0f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,20 @@ +on: + workflow_call: + +jobs: + unit: + runs-on: ubuntu-24.04 + steps: + - name: checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 2 + + - name: setup bun + uses: oven-sh/setup-bun@v2 + + - name: install dependencies + run: bun install --frozen-lockfile + + - name: run tests + run: bun run test diff --git a/.gitignore b/.gitignore index dd38b978..ebdc6410 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ coverage out/ build dist +*.tsbuildinfo # Debug npm-debug.log* diff --git a/bun.lock b/bun.lock index e71183e3..af6f93d9 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,8 @@ "": { "name": "@scriptorium/monorepo", "devDependencies": { - "@hasparus/eslint-config": "2.0.12", + "@fumadocs/cli": "^1.2.2", + "@hasparus/eslint-config": "2.0.17", "@tsconfig/strictest": "2.0.8", "@types/bun": "latest", "docs": "workspace:*", @@ -40,6 +41,7 @@ "@tanstack/react-router": "1.147.1", "@tanstack/react-router-devtools": "1.147.1", "@tanstack/react-start": "1.147.1", + "class-variance-authority": "^0.7.1", "fumadocs-core": "16.4.7", "fumadocs-mdx": "14.2.5", "fumadocs-twoslash": "^3.1.12", @@ -53,6 +55,7 @@ "tailwind-merge": "^3.4.0", "twoslash": "^0.3.6", "vite": "^7.3.1", + "zod": "^4.3.5", }, "devDependencies": { "@tailwindcss/vite": "^4.1.18", @@ -130,6 +133,10 @@ "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="], + "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="], + + "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], + "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], @@ -220,9 +227,11 @@ "@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@6.7.2", "", {}, "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA=="], + "@fumadocs/cli": ["@fumadocs/cli@1.2.2", "", { "dependencies": { "@clack/prompts": "^0.11.0", "commander": "^14.0.2", "magic-string": "^0.30.21", "oxc-parser": "^0.107.0", "oxc-resolver": "^11.16.2", "package-manager-detector": "^1.6.0", "picocolors": "^1.1.1", "tinyexec": "^1.0.2", "zod": "^4.3.5" }, "bin": { "fumadocs": "dist/index.js" } }, "sha512-BjXhjV9oZc8MHaxVQUmmbjoS0at3Sz5wWhIbHAnYLEEyRivak5s5OKcJqAWQ2YY1aF+QQ32cghq/b8+Ld2dV+Q=="], + "@fumadocs/ui": ["@fumadocs/ui@16.4.7", "", { "dependencies": { "next-themes": "^0.4.6", "postcss-selector-parser": "^7.1.1", "tailwind-merge": "^3.4.0" }, "peerDependencies": { "@types/react": "*", "fumadocs-core": "16.4.7", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.0.0" }, "optionalPeers": ["@types/react", "next", "tailwindcss"] }, "sha512-NnkMIN5BzBRh2OzA9rp2SgbGEkEwfCfq0sE4vq2n+GkIDIggicGYUNgSl2gtIBQsKYKP/a4/0wrkQKdq4eUJlw=="], - "@hasparus/eslint-config": ["@hasparus/eslint-config@2.0.12", "", { "dependencies": { "@eslint/eslintrc": "3.3.3", "@eslint/js": "9.17.0", "@theguild/eslint-config": "0.13.4", "eslint-plugin-import": "2.32.0", "eslint-plugin-jsonc": "2.21.0", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-mdx": "3.6.2", "eslint-plugin-n": "17.23.1", "eslint-plugin-perfectionist": "4.6.0", "eslint-plugin-promise": "7.2.1", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-sonarjs": "3.0.5", "eslint-plugin-unicorn": "62.0.0", "eslint-plugin-yml": "1.19.1", "globals": "15.14.0", "typescript-eslint": "8.18.2" }, "peerDependencies": { "eslint": "^9.0.0", "typescript": "^5.0.0" } }, "sha512-5WfvspuBeu6bxVbi7YT7wQE9NJM+RGoKlzzSGleSPXTaxTiY5ywcfjprFc0fogsFLmwfrzyhDZrtQrX3qZkEEQ=="], + "@hasparus/eslint-config": ["@hasparus/eslint-config@2.0.17", "", { "dependencies": { "@eslint/eslintrc": "3.3.3", "@eslint/js": "9.17.0", "@theguild/eslint-config": "0.13.4", "eslint-plugin-import": "2.32.0", "eslint-plugin-jsonc": "2.21.0", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-mdx": "3.6.2", "eslint-plugin-n": "17.23.1", "eslint-plugin-perfectionist": "4.6.0", "eslint-plugin-promise": "7.2.1", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-sonarjs": "3.0.5", "eslint-plugin-unicorn": "62.0.0", "eslint-plugin-yml": "1.19.1", "globals": "15.14.0", "typescript-eslint": "8.18.2" }, "peerDependencies": { "eslint": "^9.0.0", "typescript": "^5.0.0" } }, "sha512-4EO7HPWHcgK3QV6Oa/rCrTzFUh/k/RIofCUP1voSicUCRaxsoStiWp12lD3vjfLwJhc562r7RfhA4Sr07QMNiQ=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], @@ -322,6 +331,88 @@ "@oxc-minify/binding-win32-x64-msvc": ["@oxc-minify/binding-win32-x64-msvc@0.110.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Hr3nK90+qXKJ2kepXwFIcNfQQIOBecB4FFCyaMMypthoEEhVP08heRynj4eSXZ8NL9hLjs3fQzH8PJXfpznRnQ=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.107.0", "", { "os": "android", "cpu": "arm" }, "sha512-Fhap02+E3+tBDLsBZcsr7289kCfR3hyQnBAjhi7RSTHc7Ikydh1hS5cIzjOtlidFZJ1Vz5edbfoKGWO3/DqJNw=="], + + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.107.0", "", { "os": "android", "cpu": "arm64" }, "sha512-3gXyxBdwNzOCSdbzN3FSncilXUe/OJP0SAovRz+e20q5FInUYfVvOZUJfpII01anSmg+7KWY7p69IAgDYZZepw=="], + + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.107.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-i8W2krLmBd6jWldW1Y4/12zke+euEYZGuUggijJhEFy5xTQbwOhgVDWpdUx3CgZ17Plzjkd/dB/Ga0b13i0kAg=="], + + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.107.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-JwDxozL+IPXeiP57GyRmC3coIKR7Duit69aHvhf63NZqMClnglI0gR8mI+JH4lNBP/o6AGaY22+8/rlfiMW5Pg=="], + + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.107.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-m8h7qkymDLqxRGARWPJQH9x/I4ZLlwMhigj9iVkKZ7db/J1wl9ha+a9DCBrm5kRYikl4dSwu7wZXykKmrOzVVA=="], + + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.107.0", "", { "os": "linux", "cpu": "arm" }, "sha512-QI9b9BvWcIk/vuBUGgas4eZZCXikd7yfXTppIFM2hNZN+omd2nCDMGZ5yMHy1r+TJw1hdxei8f8xzwmO1nTq3A=="], + + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.107.0", "", { "os": "linux", "cpu": "arm" }, "sha512-VMoeP+VZegiqRqcUa0RzopOErELVTSNDfdVIX/8No3ieZdxdHqvGlBmdCqqxIYZEYif2IZJ3VcIr2RvX4y8k9w=="], + + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.107.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-01yvXlhCB8aCu9xftIQCI9TGvVb2+md4ULJYmDSil4Qr4XfXa8soEJxfS/ywe+RiDnW7w8qomtz0DI+HT5sHRw=="], + + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.107.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pp2ovq2qxqGTyRclBe65/VD3IL0fwT+X5XJSKhdhO94BtNOPCcW0bZAgG3ILkoWPPdmtWUXT/y59cCkK+QNEYg=="], + + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.107.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1AgcnFazS00KBq38eQ8EW/vwjgtcNvVdbR/SnteVDY4j0klgSxaYe2/CQXnww4wVh8UjE3IHYYAfsudhggET0Q=="], + + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.107.0", "", { "os": "linux", "cpu": "none" }, "sha512-Yg/YyeaV9RiStZG2Rc50xhzrBIG2w1PuKJjlbVtJ+Mb2kY0zxhg2Pnifjt85ZKJqqJ9Bfao1LVXNweV2HYRAJA=="], + + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.107.0", "", { "os": "linux", "cpu": "none" }, "sha512-/KGiC2Ko1k0rQxTYqTP1MDipV5LCw5by9Yx+qUy5LL0eHtI06CkIZ9mPMua5+hwLygwMrv7Ry8MjpeTQ0qHpcQ=="], + + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.107.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-F4UKJ19+vTHTA7miSt7DWG04NwMGbLj4C7BfWY8V3LMX5zp68py/rcKYBusC7hcJQ4YBUKQzl1WLx9PMzyWiXg=="], + + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.107.0", "", { "os": "linux", "cpu": "x64" }, "sha512-vF4vemHhzCsKQhfaV/j7xS7AavMVkHy29zhlAE03r61lvKK4lQBr2VvT6qgSTn4eYGNEHEZbRoFNOcmtaPGjtA=="], + + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.107.0", "", { "os": "linux", "cpu": "x64" }, "sha512-p6jxLjIMiySYclrRuVQELSm6wT5lTfkPRmcZKbtmLhyMlAR2rhuILnoZ/iVoE3Ib/hpE4G6XkLhRZLvp6ZVazw=="], + + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.107.0", "", { "os": "none", "cpu": "arm64" }, "sha512-iCUiKTYwqSmA/qgBR300fmXLVVi9tmk43O2B4oeMaydvnqUNWmZTNciOPwAFfc6024ISxZ77y4ISHTE0plX3LQ=="], + + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.107.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-VxrwctWEUSI3eJkRAGHISNlikcx8xAoglvAYAW4cdC5HfXbwRMuEunzzXMNXpNUMrdlqjf25Ay6OaxaztAOKgQ=="], + + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.107.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-zJlOsumV4JpUs0PGMF0ycjfCcV91Tpr81N7Qn5O00+MjFxI3AlHmrkhYTFA2cFicUW6XXSPe6KvEG8v46BCIBA=="], + + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.107.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vH44IYIiqzAxq7la/O+IRNdB3XqgdMRjVVT1UqA4rmyHUEQcfmCYy6cbbP07m5eLY2xAHAmuDqxBJEnQDGGGJQ=="], + + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.107.0", "", { "os": "win32", "cpu": "x64" }, "sha512-8x6u+nIKEFR3WT5oHhSP7oPZGI8VLq3iVxOEeV75NfB5ubGUA7sNHcssZ37jmUfhYnkYzBiCGhEAIRa9bUMzBw=="], + + "@oxc-project/types": ["@oxc-project/types@0.107.0", "", {}, "sha512-QFDRbYfV2LVx8tyqtyiah3jQPUj1mK2+RYwxyFWyGoys6XJnwTdlzO6rdNNHOPorHAu5Uo34oWRKcvNpbJarmQ=="], + + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.16.3", "", { "os": "android", "cpu": "arm" }, "sha512-CVyWHu6ACDqDcJxR4nmGiG8vDF4TISJHqRNzac5z/gPQycs/QrP/1pDsJBy0MD7jSw8nVq2E5WqeHQKabBG/Jg=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.16.3", "", { "os": "android", "cpu": "arm64" }, "sha512-tTIoB7plLeh2o6Ay7NnV5CJb6QUXdxI7Shnsp2ECrLSV81k+oVE3WXYrQSh4ltWL75i0OgU5Bj3bsuyg5SMepw=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.16.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXKVH7uwYd3Rbw1s2yJZd6/w+6b01iaokZubYhDAq4tOYArr+YCS+lr81q1hsTPPRZeIsWE+rJLulmf1qHdYZA=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.16.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-WwjQ4WdnCxVYZYd3e3oY5XbV3JeLy9pPMK+eQQ2m8DtqUtbxnvPpAYC2Knv/2bS6q5JiktqOVJ2Hfia3OSo0/A=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.16.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4OHKFGJBBfOnuJnelbCS4eBorI6cj54FUxcZJwEXPeoLc8yzORBoJ2w+fQbwjlQcUUZLEg92uGhKCRiUoqznjg=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.16.3", "", { "os": "linux", "cpu": "arm" }, "sha512-OM3W0NLt9u7uKwG/yZbeXABansZC0oZeDF1nKgvcZoRw4/Yak6/l4S0onBfDFeYMY94eYeAt2bl60e30lgsb5A=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.16.3", "", { "os": "linux", "cpu": "arm" }, "sha512-MRs7D7i1t7ACsAdTuP81gLZES918EpBmiUyEl8fu302yQB+4L7L7z0Ui8BWnthUTQd3nAU9dXvENLK/SqRVH8A=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.16.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0eVYZxSceNqGADzhlV4ZRqkHF0fjWxRXQOB7Qwl5y1gN/XYUDvMfip+ngtzj4dM7zQT4U97hUhJ7PUKSy/JIGQ=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.16.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-B1BvLeZbgDdVN0FvU40l5Q7lej8310WlabCBaouk8jY7H7xbI8phtomTtk3Efmevgfy5hImaQJu6++OmcFb2NQ=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.16.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-q7khglic3Jqak7uDgA3MFnjDeI7krQT595GDZpvFq785fmFYSx8rlTkoHzmhQtUisYtl4XG7WUscwsoidFUI4w=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.16.3", "", { "os": "linux", "cpu": "none" }, "sha512-aFRNmQNPzDgQEbw2s3c8yJYRimacSDI+u9df8rn5nSKzTVitHmbEpZqfxpwNLCKIuLSNmozHR1z1OT+oZVeYqg=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.16.3", "", { "os": "linux", "cpu": "none" }, "sha512-vZI85SvSMADcEL9G1TIrV0Rlkc1fY5Mup0DdlVC5EHPysZB4hXXHpr+h09pjlK5y+5om5foIzDRxE1baUCaWOA=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.16.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-xiLBnaUlddFEzRHiHiSGEMbkg8EwZY6VD8F+3GfnFsiK3xg/4boaUV2bwXd+nUzl3UDQOMW1QcZJ4jJSb0qiJA=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.16.3", "", { "os": "linux", "cpu": "x64" }, "sha512-6y0b05wIazJJgwu7yU/AYGFswzQQudYJBOb/otDhiDacp1+6ye8egoxx63iVo9lSpDbipL++54AJQFlcOHCB+g=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.16.3", "", { "os": "linux", "cpu": "x64" }, "sha512-RmMgwuMa42c9logS7Pjprf5KCp8J1a1bFiuBFtG9/+yMu0BhY2t+0VR/um7pwtkNFvIQqAVh6gDOg/PnoKRcdQ=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.16.3", "", { "os": "none", "cpu": "arm64" }, "sha512-/7AYRkjjW7xu1nrHgWUFy99Duj4/ydOBVaHtODie9/M6fFngo+8uQDFFnzmr4q//sd/cchIerISp/8CQ5TsqIA=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.16.3", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-urM6aIPbi5di4BSlnpd/TWtDJgG6RD06HvLBuNM+qOYuFtY1/xPbzQ2LanBI2ycpqIoIZwsChyplALwAMdyfCQ=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.16.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-QuvLqGKf7frxWHQ5TnrcY0C/hJpANsaez99Q4dAk1hen7lDTD4FBPtBzPnntLFXeaVG3PnSmnVjlv0vMILwU7Q=="], + + "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.16.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QR/witXK6BmYTlEP8CCjC5fxeG5U9A6a50pNpC1nLnhAcJjtzFG8KcQ5etVy/XvCLiDc7fReaAWRNWtCaIhM8Q=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.16.3", "", { "os": "win32", "cpu": "x64" }, "sha512-bFuJRKOscsDAEZ/a8BezcTMAe2BQ/OBRfuMLFUuINfTR5qGVcm4a3xBIrQVepBaPxFj16SJdRjGe05vDiwZmFw=="], + "@oxc-transform/binding-android-arm-eabi": ["@oxc-transform/binding-android-arm-eabi@0.110.0", "", { "os": "android", "cpu": "arm" }, "sha512-sE9dxvqqAax1YYJ3t7j+h5ZSI9jl6dYuDfngl6ieZUrIy5P89/8JKVgAzgp8o3wQSo7ndpJvYsi1K4ZqrmbP7w=="], "@oxc-transform/binding-android-arm64": ["@oxc-transform/binding-android-arm64@0.110.0", "", { "os": "android", "cpu": "arm64" }, "sha512-nqtbP4aMCtsCZ6qpHlHaQoWVHSBtlKzwaAgwEOvR+9DWqHjk31BHvpGiDXlMeed6CVNpl3lCbWgygb3RcSjcfw=="], @@ -904,7 +995,7 @@ "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], - "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], @@ -1660,9 +1751,9 @@ "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], - "nf3": ["nf3@0.3.5", "", {}, "sha512-1VozaVz0lVfGL3c2wZ4c6bmQCm340gDiIYUU3lcg8vVGL/WeuTdrd6OhJiUHZWofc7fFdquhS8Gm+13c3Tumcw=="], + "nf3": ["nf3@0.3.6", "", {}, "sha512-/XRUUILTAyuy1XunyVQuqGp8aEmZ2TfRTn8Rji+FA4xqv20qzL4jV7Reqbuey2XucKgPeRVcEYGScmJM0UnB6Q=="], - "nitro": ["nitro-nightly@3.0.1-20260120-140218-d2383f00", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.3", "db0": "^0.3.4", "h3": "^2.0.1-rc.11", "jiti": "^2.6.1", "nf3": "^0.3.5", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.110.0", "oxc-transform": "^0.110.0", "srvx": "^0.10.1", "undici": "^7.18.2", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.5" }, "peerDependencies": { "rolldown": ">=1.0.0-beta.0", "rollup": "^4", "vite": "^7 || ^8 || >=8.0.0-0", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-5SWkL/b2r8x5ZyfkwhhZA7Ra3pM3lS6K8DIxCt5OIzQYFbfOjdzg9ANMKtSz1+Bfjb6u8BtSzQBLdgFJLgXrxQ=="], + "nitro": ["nitro-nightly@3.0.1-20260122-201913-dfdff9e9", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.3", "db0": "^0.3.4", "h3": "^2.0.1-rc.11", "jiti": "^2.6.1", "nf3": "^0.3.6", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.110.0", "oxc-transform": "^0.110.0", "srvx": "^0.10.1", "undici": "^7.18.2", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.5" }, "peerDependencies": { "rolldown": ">=1.0.0-beta.0", "rollup": "^4", "vite": "^7 || ^8 || >=8.0.0-0", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-s20CTdwzRl63TehtkAd5ip2hLCfjrhnYahFWNQpA2CdjWKKWRtvYXuNAHZxwouLi81VDvO6ckd+XY1pliM1plg=="], "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], @@ -1716,6 +1807,10 @@ "oxc-minify": ["oxc-minify@0.110.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm-eabi": "0.110.0", "@oxc-minify/binding-android-arm64": "0.110.0", "@oxc-minify/binding-darwin-arm64": "0.110.0", "@oxc-minify/binding-darwin-x64": "0.110.0", "@oxc-minify/binding-freebsd-x64": "0.110.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.110.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.110.0", "@oxc-minify/binding-linux-arm64-gnu": "0.110.0", "@oxc-minify/binding-linux-arm64-musl": "0.110.0", "@oxc-minify/binding-linux-ppc64-gnu": "0.110.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.110.0", "@oxc-minify/binding-linux-riscv64-musl": "0.110.0", "@oxc-minify/binding-linux-s390x-gnu": "0.110.0", "@oxc-minify/binding-linux-x64-gnu": "0.110.0", "@oxc-minify/binding-linux-x64-musl": "0.110.0", "@oxc-minify/binding-openharmony-arm64": "0.110.0", "@oxc-minify/binding-wasm32-wasi": "0.110.0", "@oxc-minify/binding-win32-arm64-msvc": "0.110.0", "@oxc-minify/binding-win32-ia32-msvc": "0.110.0", "@oxc-minify/binding-win32-x64-msvc": "0.110.0" } }, "sha512-KWGTzPo83QmGrXC4ml83PM9HDwUPtZFfasiclUvTV4i3/0j7xRRqINVkrL77CbQnoWura3CMxkRofjQKVDuhBw=="], + "oxc-parser": ["oxc-parser@0.107.0", "", { "dependencies": { "@oxc-project/types": "^0.107.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.107.0", "@oxc-parser/binding-android-arm64": "0.107.0", "@oxc-parser/binding-darwin-arm64": "0.107.0", "@oxc-parser/binding-darwin-x64": "0.107.0", "@oxc-parser/binding-freebsd-x64": "0.107.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.107.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.107.0", "@oxc-parser/binding-linux-arm64-gnu": "0.107.0", "@oxc-parser/binding-linux-arm64-musl": "0.107.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.107.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.107.0", "@oxc-parser/binding-linux-riscv64-musl": "0.107.0", "@oxc-parser/binding-linux-s390x-gnu": "0.107.0", "@oxc-parser/binding-linux-x64-gnu": "0.107.0", "@oxc-parser/binding-linux-x64-musl": "0.107.0", "@oxc-parser/binding-openharmony-arm64": "0.107.0", "@oxc-parser/binding-wasm32-wasi": "0.107.0", "@oxc-parser/binding-win32-arm64-msvc": "0.107.0", "@oxc-parser/binding-win32-ia32-msvc": "0.107.0", "@oxc-parser/binding-win32-x64-msvc": "0.107.0" } }, "sha512-3HuDitM2UIEDbCjEhXyLAC8LuQvneDq/0eioczXZFeY4f4ee91tUcavZ9U7s4ZIFZOoHmNtOyOCB6kOM4OAtOA=="], + + "oxc-resolver": ["oxc-resolver@11.16.3", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.16.3", "@oxc-resolver/binding-android-arm64": "11.16.3", "@oxc-resolver/binding-darwin-arm64": "11.16.3", "@oxc-resolver/binding-darwin-x64": "11.16.3", "@oxc-resolver/binding-freebsd-x64": "11.16.3", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.16.3", "@oxc-resolver/binding-linux-arm-musleabihf": "11.16.3", "@oxc-resolver/binding-linux-arm64-gnu": "11.16.3", "@oxc-resolver/binding-linux-arm64-musl": "11.16.3", "@oxc-resolver/binding-linux-ppc64-gnu": "11.16.3", "@oxc-resolver/binding-linux-riscv64-gnu": "11.16.3", "@oxc-resolver/binding-linux-riscv64-musl": "11.16.3", "@oxc-resolver/binding-linux-s390x-gnu": "11.16.3", "@oxc-resolver/binding-linux-x64-gnu": "11.16.3", "@oxc-resolver/binding-linux-x64-musl": "11.16.3", "@oxc-resolver/binding-openharmony-arm64": "11.16.3", "@oxc-resolver/binding-wasm32-wasi": "11.16.3", "@oxc-resolver/binding-win32-arm64-msvc": "11.16.3", "@oxc-resolver/binding-win32-ia32-msvc": "11.16.3", "@oxc-resolver/binding-win32-x64-msvc": "11.16.3" } }, "sha512-goLOJH3x69VouGWGp5CgCIHyksmOZzXr36lsRmQz1APg3SPFORrvV2q7nsUHMzLVa6ZJgNwkgUSJFsbCpAWkCA=="], + "oxc-transform": ["oxc-transform@0.110.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm-eabi": "0.110.0", "@oxc-transform/binding-android-arm64": "0.110.0", "@oxc-transform/binding-darwin-arm64": "0.110.0", "@oxc-transform/binding-darwin-x64": "0.110.0", "@oxc-transform/binding-freebsd-x64": "0.110.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.110.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.110.0", "@oxc-transform/binding-linux-arm64-gnu": "0.110.0", "@oxc-transform/binding-linux-arm64-musl": "0.110.0", "@oxc-transform/binding-linux-ppc64-gnu": "0.110.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.110.0", "@oxc-transform/binding-linux-riscv64-musl": "0.110.0", "@oxc-transform/binding-linux-s390x-gnu": "0.110.0", "@oxc-transform/binding-linux-x64-gnu": "0.110.0", "@oxc-transform/binding-linux-x64-musl": "0.110.0", "@oxc-transform/binding-openharmony-arm64": "0.110.0", "@oxc-transform/binding-wasm32-wasi": "0.110.0", "@oxc-transform/binding-win32-arm64-msvc": "0.110.0", "@oxc-transform/binding-win32-ia32-msvc": "0.110.0", "@oxc-transform/binding-win32-x64-msvc": "0.110.0" } }, "sha512-/fymQNzzUoKZweH0nC5yvbI2eR0yWYusT9TEKDYVgOgYrf9Qmdez9lUFyvxKR9ycx+PTHi/reIOzqf3wkShQsw=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -1936,6 +2031,8 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], @@ -2370,6 +2467,8 @@ "jsonc-eslint-parser/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], diff --git a/cli.json b/cli.json new file mode 100644 index 00000000..4849e337 --- /dev/null +++ b/cli.json @@ -0,0 +1,13 @@ +{ + "$schema": "node_modules/@fumadocs/cli/dist/schema/default.json", + "aliases": { + "uiDir": "./components/ui", + "componentsDir": "./components", + "blockDir": "./components", + "cssDir": "./styles", + "libDir": "./lib" + }, + "baseDir": "", + "uiLibrary": "radix-ui", + "commands": {} +} \ No newline at end of file diff --git a/package.json b/package.json index dbcbcad5..5fd3cafc 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@scriptorium/monorepo", + "name": "@graphql-hive/docs", "private": true, "engines": { "node": ">=24" @@ -18,10 +18,12 @@ "format": "prettier --write \"**/*.{ts,tsx,md}\"", "format:check": "prettier --check \"**/*.{ts,tsx,md}\"", "typecheck": "turbo run typecheck", - "postinstall": "node scripts/fix-nitro-nightly.mjs" + "postinstall": "node scripts/fix-nitro-nightly.mjs", + "test": "turbo run test" }, "devDependencies": { - "@hasparus/eslint-config": "2.0.12", + "@fumadocs/cli": "^1.2.2", + "@hasparus/eslint-config": "2.0.17", "@tsconfig/strictest": "2.0.8", "@types/bun": "latest", "docs": "workspace:*", diff --git a/packages/design-system/eslint.config.ts b/packages/design-system/eslint.config.ts index 8ae5cb3a..78e041bf 100644 --- a/packages/design-system/eslint.config.ts +++ b/packages/design-system/eslint.config.ts @@ -1 +1,25 @@ -export { default } from "@hasparus/eslint-config/the-guild"; +import type { Linter } from "eslint"; + +import baseConfig from "@hasparus/eslint-config/the-guild"; + +const config: Linter.Config[] = [ + ...baseConfig, + { + files: ["src/routes/**/*.tsx"], + rules: { + "unicorn/filename-case": "off", + }, + }, + { + languageOptions: { + parserOptions: { + project: "./tsconfig.json", + projectService: { + allowDefaultProject: ["*.config.ts"], + }, + }, + }, + }, +]; + +export default config; diff --git a/packages/design-system/tsconfig.json b/packages/design-system/tsconfig.json index 55d35163..9aeb1da2 100644 --- a/packages/design-system/tsconfig.json +++ b/packages/design-system/tsconfig.json @@ -2,9 +2,10 @@ "extends": "../../tsconfig.json", "compilerOptions": { "jsx": "react-jsx", - "outDir": "dist", - "noEmit": true + "composite": true, + "noEmit": false, + "outDir": "dist" }, - "include": ["src", "eslint.config.ts"], + "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/packages/documentation/eslint.config.ts b/packages/documentation/eslint.config.ts index 8ae5cb3a..c2057a86 100644 --- a/packages/documentation/eslint.config.ts +++ b/packages/documentation/eslint.config.ts @@ -1 +1,22 @@ -export { default } from "@hasparus/eslint-config/the-guild"; +import type { Linter } from "eslint"; + +import baseConfig from "@hasparus/eslint-config/the-guild"; + +const config: Linter.Config[] = [ + ...baseConfig, + { + files: ["./src/routes/**/*.ts", "./src/routes/**/*.tsx"], + rules: { + "unicorn/filename-case": "off", + }, + }, + { + languageOptions: { + parserOptions: { + project: "./tsconfig.json", + }, + }, + }, +]; + +export default config; diff --git a/packages/documentation/package.json b/packages/documentation/package.json index 3a371af9..86967cab 100644 --- a/packages/documentation/package.json +++ b/packages/documentation/package.json @@ -10,7 +10,8 @@ "typecheck": "bun --bun fumadocs-mdx && bun --bun tsc --noEmit", "postinstall": "bun --bun fumadocs-mdx && bun playwright install chromium", "lint": "eslint .", - "lint:fix": "eslint . --fix" + "lint:fix": "eslint . --fix", + "test": "bun test" }, "dependencies": { "@sparticuz/chromium": "^143.0.4", @@ -18,6 +19,7 @@ "@tanstack/react-router": "1.147.1", "@tanstack/react-router-devtools": "1.147.1", "@tanstack/react-start": "1.147.1", + "class-variance-authority": "^0.7.1", "fumadocs-core": "16.4.7", "fumadocs-mdx": "14.2.5", "fumadocs-twoslash": "^3.1.12", @@ -30,7 +32,8 @@ "rehype-mermaid": "^3.0.0", "tailwind-merge": "^3.4.0", "twoslash": "^0.3.6", - "vite": "^7.3.1" + "vite": "^7.3.1", + "zod": "^4.3.5" }, "devDependencies": { "@tailwindcss/vite": "^4.1.18", diff --git a/packages/documentation/source.config.ts b/packages/documentation/source.config.ts index e03ab6bc..f84cb843 100644 --- a/packages/documentation/source.config.ts +++ b/packages/documentation/source.config.ts @@ -5,11 +5,15 @@ import rehypeMermaid, { type RehypeMermaidOptions } from "rehype-mermaid"; export const docs = defineDocs({ dir: "content/docs", + docs: { + postprocess: { + includeProcessedMarkdown: true, + }, + }, }); export default defineConfig({ mdxOptions: { - // mermaid must run before shiki (rehypeCode) to find raw code blocks rehypeCodeOptions: { langs: ["js", "jsx", "ts", "tsx"], themes: { @@ -21,7 +25,13 @@ export default defineConfig({ transformerTwoslash(), ], }, - rehypePlugins: (plugins) => [mermaidConfig(), ...plugins], + rehypePlugins: (plugins) => [ + /** + * Mermaid must run before Shiki to find unprocessed code blocks. + */ + mermaidConfig(), + ...plugins, + ], }, }); diff --git a/packages/documentation/src/components/page-actions.tsx b/packages/documentation/src/components/page-actions.tsx new file mode 100644 index 00000000..242193f6 --- /dev/null +++ b/packages/documentation/src/components/page-actions.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useCopyButton } from "fumadocs-ui/utils/use-copy-button"; +import { Check, Copy, Github } from "lucide-react"; +import { useState } from "react"; + +const cache = new Map(); +const actionClass = + "inline-flex items-center gap-2 text-sm text-fd-muted-foreground transition-colors hover:text-fd-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring disabled:pointer-events-none disabled:opacity-50"; + +export function LLMCopyButton({ markdownUrl }: { markdownUrl: string }) { + const [loading, setLoading] = useState(false); + const [checked, onClick] = useCopyButton(async () => { + const cached = cache.get(markdownUrl); + if (cached) return navigator.clipboard.writeText(cached); + + setLoading(true); + + try { + await navigator.clipboard.write([ + new ClipboardItem({ + "text/plain": fetch(markdownUrl).then(async (res) => { + const content = await res.text(); + + if (!res.ok) { + // If we're rendering this page, we should definitely have Markdown for it. + // eslint-disable-next-line no-console + console.error(`Failed to fetch ${markdownUrl}`, res); + throw new Error( + `${markdownUrl} is unexpectedly missing, try again later`, + ); + } + + cache.set(markdownUrl, content); + return content; + }), + }), + ]); + } finally { + setLoading(false); + } + }); + + return ( + + ); +} + +export function PageActions({ + githubUrl, + markdownUrl, +}: { + githubUrl: string; + markdownUrl: string; +}) { + return ( +
+ + + + View on GitHub + +
+ ); +} diff --git a/packages/documentation/src/components/ui/button.tsx b/packages/documentation/src/components/ui/button.tsx new file mode 100644 index 00000000..7cb8cd69 --- /dev/null +++ b/packages/documentation/src/components/ui/button.tsx @@ -0,0 +1,28 @@ +import { cva, type VariantProps } from "class-variance-authority"; + +const variants = { + ghost: "hover:bg-fd-accent hover:text-fd-accent-foreground", + outline: "border hover:bg-fd-accent hover:text-fd-accent-foreground", + primary: "bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80", + secondary: + "border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground", +} as const; + +export const buttonVariants = cva( + "inline-flex items-center justify-center rounded-md p-2 text-sm font-medium transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring", + { + variants: { + variant: variants, + // fumadocs use `color` instead of `variant` + color: variants, + size: { + icon: "p-1.5 [&_svg]:size-5", + "icon-sm": "p-1.5 [&_svg]:size-4.5", + "icon-xs": "p-1 [&_svg]:size-4", + sm: "gap-1 px-2 py-1.5 text-xs", + }, + }, + }, +); + +export type ButtonProps = VariantProps; diff --git a/packages/documentation/src/lib/cn.ts b/packages/documentation/src/lib/cn.ts new file mode 100644 index 00000000..8e473dac --- /dev/null +++ b/packages/documentation/src/lib/cn.ts @@ -0,0 +1 @@ +export { twMerge as cn } from "tailwind-merge"; diff --git a/packages/documentation/src/lib/get-llm-text.ts b/packages/documentation/src/lib/get-llm-text.ts new file mode 100644 index 00000000..36926620 --- /dev/null +++ b/packages/documentation/src/lib/get-llm-text.ts @@ -0,0 +1,11 @@ +import type { InferPageType } from "fumadocs-core/source"; + +import { source } from "@/lib/source"; + +export async function getLLMText(page: InferPageType) { + const processed = await page.data.getText("processed"); + + return `# ${page.data.title} (${page.url}) + +${processed}`; +} diff --git a/packages/documentation/src/lib/source.ts b/packages/documentation/src/lib/source.ts index 4deacbbb..79b3c725 100644 --- a/packages/documentation/src/lib/source.ts +++ b/packages/documentation/src/lib/source.ts @@ -1,9 +1,24 @@ -import { loader } from "fumadocs-core/source"; +import type { + DocCollectionEntry, + MetaCollectionEntry, +} from "fumadocs-mdx/runtime/server"; + +import { + loader, + type MetaData, + type PageData, + type Source, +} from "fumadocs-core/source"; import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons"; import { docs } from "fumadocs-mdx:collections/server"; +const docsSource = docs.toFumadocsSource() as Source<{ + metaData: MetaCollectionEntry; + pageData: DocCollectionEntry<"docs", PageData>; +}>; + export const source = loader({ baseUrl: "/docs", plugins: [lucideIconsPlugin()], - source: docs.toFumadocsSource(), + source: docsSource, }); diff --git a/packages/documentation/src/routeTree.gen.ts b/packages/documentation/src/routeTree.gen.ts index 6a5e345a..737c52b3 100644 --- a/packages/documentation/src/routeTree.gen.ts +++ b/packages/documentation/src/routeTree.gen.ts @@ -9,10 +9,23 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as LlmsDottxtRouteImport } from './routes/llms[.]txt' +import { Route as LlmsFullDottxtRouteImport } from './routes/llms-full[.]txt' import { Route as IndexRouteImport } from './routes/index' import { Route as DocsSplatRouteImport } from './routes/docs/$' import { Route as ApiSearchRouteImport } from './routes/api/search' +import { Route as LlmsDotmdxDocsSplatRouteImport } from './routes/llms[.]mdx.docs.$' +const LlmsDottxtRoute = LlmsDottxtRouteImport.update({ + id: '/llms.txt', + path: '/llms.txt', + getParentRoute: () => rootRouteImport, +} as any) +const LlmsFullDottxtRoute = LlmsFullDottxtRouteImport.update({ + id: '/llms-full.txt', + path: '/llms-full.txt', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -28,39 +41,89 @@ const ApiSearchRoute = ApiSearchRouteImport.update({ path: '/api/search', getParentRoute: () => rootRouteImport, } as any) +const LlmsDotmdxDocsSplatRoute = LlmsDotmdxDocsSplatRouteImport.update({ + id: '/llms.mdx/docs/$', + path: '/llms.mdx/docs/$', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/llms-full.txt': typeof LlmsFullDottxtRoute + '/llms.txt': typeof LlmsDottxtRoute '/api/search': typeof ApiSearchRoute '/docs/$': typeof DocsSplatRoute + '/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/llms-full.txt': typeof LlmsFullDottxtRoute + '/llms.txt': typeof LlmsDottxtRoute '/api/search': typeof ApiSearchRoute '/docs/$': typeof DocsSplatRoute + '/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/llms-full.txt': typeof LlmsFullDottxtRoute + '/llms.txt': typeof LlmsDottxtRoute '/api/search': typeof ApiSearchRoute '/docs/$': typeof DocsSplatRoute + '/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/api/search' | '/docs/$' + fullPaths: + | '/' + | '/llms-full.txt' + | '/llms.txt' + | '/api/search' + | '/docs/$' + | '/llms.mdx/docs/$' fileRoutesByTo: FileRoutesByTo - to: '/' | '/api/search' | '/docs/$' - id: '__root__' | '/' | '/api/search' | '/docs/$' + to: + | '/' + | '/llms-full.txt' + | '/llms.txt' + | '/api/search' + | '/docs/$' + | '/llms.mdx/docs/$' + id: + | '__root__' + | '/' + | '/llms-full.txt' + | '/llms.txt' + | '/api/search' + | '/docs/$' + | '/llms.mdx/docs/$' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + LlmsFullDottxtRoute: typeof LlmsFullDottxtRoute + LlmsDottxtRoute: typeof LlmsDottxtRoute ApiSearchRoute: typeof ApiSearchRoute DocsSplatRoute: typeof DocsSplatRoute + LlmsDotmdxDocsSplatRoute: typeof LlmsDotmdxDocsSplatRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/llms.txt': { + id: '/llms.txt' + path: '/llms.txt' + fullPath: '/llms.txt' + preLoaderRoute: typeof LlmsDottxtRouteImport + parentRoute: typeof rootRouteImport + } + '/llms-full.txt': { + id: '/llms-full.txt' + path: '/llms-full.txt' + fullPath: '/llms-full.txt' + preLoaderRoute: typeof LlmsFullDottxtRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -82,23 +145,34 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiSearchRouteImport parentRoute: typeof rootRouteImport } + '/llms.mdx/docs/$': { + id: '/llms.mdx/docs/$' + path: '/llms.mdx/docs/$' + fullPath: '/llms.mdx/docs/$' + preLoaderRoute: typeof LlmsDotmdxDocsSplatRouteImport + parentRoute: typeof rootRouteImport + } } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + LlmsFullDottxtRoute: LlmsFullDottxtRoute, + LlmsDottxtRoute: LlmsDottxtRoute, ApiSearchRoute: ApiSearchRoute, DocsSplatRoute: DocsSplatRoute, + LlmsDotmdxDocsSplatRoute: LlmsDotmdxDocsSplatRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() import type { getRouter } from './router.tsx' -import type { createStart } from '@tanstack/react-start' +import type { startInstance } from './start.ts' declare module '@tanstack/react-start' { interface Register { ssr: true router: Awaited> + config: Awaited> } } diff --git a/packages/documentation/src/router.tsx b/packages/documentation/src/router.tsx index 8bade5db..a508f9b3 100644 --- a/packages/documentation/src/router.tsx +++ b/packages/documentation/src/router.tsx @@ -1,12 +1,32 @@ import { NotFound } from "@/components/not-found"; -import { createRouter as createTanStackRouter } from "@tanstack/react-router"; +import { createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen"; +const TEXT_FILE_REGEX = /\.(txt|md|mdx)$/; +const TEXT_FILE_ROUTES = new Set(["/llms-full.txt", "/llms.txt"]); + export function getRouter() { - return createTanStackRouter({ + return createRouter({ defaultNotFoundComponent: NotFound, defaultPreload: "intent", + rewrite: { + /** + * Rewrite .txt, .md, and .mdx paths + * so simple `curl http://localhost:1440/docs/article.txt` works. + * Content negotiation is handled by the middleware in `src/start.ts`, + * but we handle paths here to make sure accessing the raw text is foolproof. + */ + input: ({ url }) => { + if ( + TEXT_FILE_REGEX.test(url.pathname) && + !TEXT_FILE_ROUTES.has(url.pathname) + ) { + url.pathname = `/llms.mdx${url.pathname.slice(0, url.pathname.lastIndexOf("."))}`; + } + return url; + }, + }, routeTree, scrollRestoration: true, }); diff --git a/packages/documentation/src/routes/docs/$.tsx b/packages/documentation/src/routes/docs/$.tsx index a0212311..11aa5d20 100644 --- a/packages/documentation/src/routes/docs/$.tsx +++ b/packages/documentation/src/routes/docs/$.tsx @@ -1,3 +1,4 @@ +import { PageActions } from "@/components/page-actions"; import { baseOptions } from "@/lib/layout.shared"; import { source } from "@/lib/source"; import { createFileRoute, notFound } from "@tanstack/react-router"; @@ -17,7 +18,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx"; export const Route = createFileRoute("/docs/$")({ component: Page, loader: async ({ params }) => { - const slugs = params._splat?.split("/") ?? []; + const slugs = params._splat?.split("/").filter(Boolean) ?? []; const data = await serverLoader({ data: slugs }); await clientLoader.preload(data.path); return data; @@ -35,19 +36,34 @@ const serverLoader = createServerFn({ return { pageTree: await source.serializePageTree(source.getPageTree()), path: page.path, + url: page.url, }; }); -const clientLoader = browserCollections.docs.createClientLoader({ - component( - { default: MDX, frontmatter, toc }, - // you can define props for the component - props: { - className?: string; - }, - ) { +const clientLoader = browserCollections.docs.createClientLoader<{ + className?: string; + githubUrl: string; + markdownUrl: string; +}>({ + component(loaded, props) { + const { default: MDX, toc } = loaded; + const frontmatter = loaded.frontmatter as { + description?: string; + title: string; + }; return ( - + + ), + }} + toc={toc} + {...props} + > {frontmatter.title} {frontmatter.description} @@ -70,6 +86,8 @@ function Page() { {clientLoader.useContent(data.path, { className: "", + githubUrl: `https://github.com/graphql-hive/docs/blob/main/packages/documentation/content/docs/${data.path}`, + markdownUrl: `${data.url}.mdx`, })} ); diff --git a/packages/documentation/src/routes/docs/-$.test.ts b/packages/documentation/src/routes/docs/-$.test.ts new file mode 100644 index 00000000..83149862 --- /dev/null +++ b/packages/documentation/src/routes/docs/-$.test.ts @@ -0,0 +1,207 @@ +/** + * Integration tests for AI/LLM features. + * + * Run with: bun test src/routes/docs/-$.test.ts + */ +import { spawn, type Subprocess } from "bun"; +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; + +const TEST_PORT = 14_401; +const BASE_URL = process.env["TEST_URL"] || `http://localhost:${TEST_PORT}`; + +let devServer: Subprocess | null = null; + +async function waitForServer(maxAttempts = 30): Promise { + for (let i = 0; i < maxAttempts; i++) { + try { + const res = await fetch(BASE_URL, { signal: AbortSignal.timeout(1000) }); + if (res.ok || res.status < 500) return; + } catch { + // eslint-disable-next-line no-console + console.log(`Server not ready after ${i + 1}s, retrying...`); + } + await new Promise((r) => setTimeout(r, 1000)); + } + throw new Error(`Server not ready after ${maxAttempts}s`); +} + +beforeAll(async () => { + if (process.env["TEST_URL"]) return; // user-provided server + + devServer = spawn( + ["bun", "--bun", "vite", "dev", "--port", String(TEST_PORT)], + { + cwd: import.meta.dir + "/../../..", + stderr: "ignore", + stdout: "ignore", + }, + ); + + await waitForServer(); +}, 60_000); + +afterAll(() => { + devServer?.kill(); +}); + +describe("llms.txt", () => { + test("returns index of docs", async () => { + const res = await fetch(`${BASE_URL}/llms.txt`); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toContain("text/markdown"); + const text = await res.text(); + expect(text).toContain("# GraphQL Hive"); + expect(text).toContain(".md)"); + }); + + test("is not rewritten to a doc page", async () => { + const res = await fetch(`${BASE_URL}/llms.txt`); + const text = await res.text(); + // llms.txt should NOT start with frontmatter (doc pages do) + expect(text.startsWith("---")).toBe(false); + }); +}); + +describe("llms-full.txt", () => { + test("returns all docs as markdown", async () => { + const res = await fetch(`${BASE_URL}/llms-full.txt`); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toContain("text/markdown"); + const text = await res.text(); + expect(text).toContain("# "); + expect(text).toContain("(/docs"); + }); + + test("is not rewritten to a doc page", async () => { + const res = await fetch(`${BASE_URL}/llms-full.txt`); + expect(res.status).toBe(200); + // llms-full.txt contains multiple docs concatenated, not a single doc + const text = await res.text(); + expect(text).toContain("(/docs/"); + }); +}); + +describe(".txt extension", () => { + test("/docs.txt returns markdown for root docs page", async () => { + const res = await fetch(`${BASE_URL}/docs.txt`, { redirect: "follow" }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain("---"); + expect(text).toContain("title:"); + }); + + test("/docs/test.txt returns markdown for nested page", async () => { + const res = await fetch(`${BASE_URL}/docs/test.txt`, { + redirect: "follow", + }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain("---"); + expect(text).toContain("title:"); + }); +}); + +describe(".mdx extension", () => { + test("/docs.mdx returns markdown for root docs page", async () => { + const res = await fetch(`${BASE_URL}/docs.mdx`, { redirect: "follow" }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain("---"); + expect(text).toContain("title:"); + }); + + test("/docs/test.mdx returns markdown for nested page", async () => { + const res = await fetch(`${BASE_URL}/docs/test.mdx`, { + redirect: "follow", + }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain("---"); + expect(text).toContain("title:"); + }); +}); + +describe(".md extension", () => { + test("/docs.md returns markdown for root docs page", async () => { + const res = await fetch(`${BASE_URL}/docs.md`, { redirect: "follow" }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain("---"); + }); + + test("/docs/test.md returns markdown for nested page", async () => { + const res = await fetch(`${BASE_URL}/docs/test.md`, { redirect: "follow" }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain("---"); + }); +}); + +describe("Accept header negotiation", () => { + test("Accept: text/markdown returns markdown", async () => { + const res = await fetch(`${BASE_URL}/docs`, { + headers: { Accept: "text/markdown" }, + redirect: "follow", + }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain("---"); + }); + + test("Accept: text/plain returns markdown", async () => { + const res = await fetch(`${BASE_URL}/docs`, { + headers: { Accept: "text/plain" }, + redirect: "follow", + }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + }); + + test("Accept: text/markdown works for nested pages", async () => { + const res = await fetch(`${BASE_URL}/docs/test`, { + headers: { Accept: "text/markdown" }, + redirect: "follow", + }); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).toBe("text/markdown"); + }); + + test("regular browser request returns HTML", async () => { + const res = await fetch(`${BASE_URL}/docs`); + expect(res.status).toBe(200); + expect(res.headers.get("content-type")).not.toBe("text/markdown"); + const text = await res.text(); + expect(text).toContain(""); + }); +}); + +describe("404 handling", () => { + test(".mdx extension returns notFound for non-existent page", async () => { + const res = await fetch(`${BASE_URL}/docs/non-existent-xyz.mdx`, { + redirect: "follow", + }); + const json = await res.json(); + expect(json).toEqual({ isNotFound: true }); + }); + + test("Accept header returns notFound for non-existent page", async () => { + const res = await fetch(`${BASE_URL}/docs/non-existent-xyz`, { + headers: { Accept: "text/markdown" }, + redirect: "follow", + }); + const json = await res.json(); + expect(json).toEqual({ isNotFound: true }); + }); + + test("non-text extensions like .png are not rewritten", async () => { + const res = await fetch(`${BASE_URL}/docs.png`, { redirect: "follow" }); + expect(res.status).toBe(404); + }); +}); diff --git a/packages/documentation/src/routes/llms-full[.]txt.ts b/packages/documentation/src/routes/llms-full[.]txt.ts new file mode 100644 index 00000000..813e8e92 --- /dev/null +++ b/packages/documentation/src/routes/llms-full[.]txt.ts @@ -0,0 +1,17 @@ +import { getLLMText } from "@/lib/get-llm-text"; +import { source } from "@/lib/source"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/llms-full.txt")({ + server: { + handlers: { + GET: async () => { + const scan = source.getPages().map(getLLMText); + const scanned = await Promise.all(scan); + return new Response(scanned.join("\n\n"), { + headers: { "Content-Type": "text/markdown; charset=utf-8" }, + }); + }, + }, + }, +}); diff --git a/packages/documentation/src/routes/llms[.]mdx.docs.$.ts b/packages/documentation/src/routes/llms[.]mdx.docs.$.ts new file mode 100644 index 00000000..a74db2ae --- /dev/null +++ b/packages/documentation/src/routes/llms[.]mdx.docs.$.ts @@ -0,0 +1,31 @@ +import { source } from "@/lib/source"; +import { createFileRoute, notFound } from "@tanstack/react-router"; + +export const Route = createFileRoute("/llms.mdx/docs/$")({ + server: { + handlers: { + GET: async ({ params }) => { + const slugs = params._splat?.split("/").filter(Boolean) ?? []; + const page = source.getPage(slugs); + if (!page) throw notFound(); + + const processed = await page.data.getText("processed"); + // We're adding frontmatter back, but only the parts relevant to LLMs. + const frontmatter = [ + "---", + `title: ${page.data.title}`, + page.data.description && `description: ${page.data.description}`, + "---", + ] + .filter(Boolean) + .join("\n"); + + return new Response(`${frontmatter}\n${processed}`, { + headers: { + "Content-Type": "text/markdown", + }, + }); + }, + }, + }, +}); diff --git a/packages/documentation/src/routes/llms[.]txt.ts b/packages/documentation/src/routes/llms[.]txt.ts new file mode 100644 index 00000000..943d8b17 --- /dev/null +++ b/packages/documentation/src/routes/llms[.]txt.ts @@ -0,0 +1,32 @@ +import { source } from "@/lib/source"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/llms.txt")({ + server: { + handlers: { + GET: async ({ request }) => { + const baseUrl = new URL(request.url).origin; + const pages = source.getPages(); + + const lines = [ + "# GraphQL Hive", + "", + "> GraphQL Hive documentation", + "", + "## Docs", + "", + ...pages.map((page) => { + const desc = page.data.description + ? `: ${page.data.description}` + : ""; + return `- [${page.data.title}](${baseUrl}${page.url}.md)${desc}`; + }), + ]; + + return new Response(lines.join("\n"), { + headers: { "Content-Type": "text/markdown; charset=utf-8" }, + }); + }, + }, + }, +}); diff --git a/packages/documentation/src/start.ts b/packages/documentation/src/start.ts new file mode 100644 index 00000000..a5d0b6de --- /dev/null +++ b/packages/documentation/src/start.ts @@ -0,0 +1,38 @@ +import { redirect } from "@tanstack/react-router"; +import { createMiddleware, createStart } from "@tanstack/react-start"; +import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation"; + +const acceptRewrites = [ + rewritePath("/docs", "/llms.mdx/docs"), + rewritePath("/docs{/*path}", "/llms.mdx/docs{/*path}"), +]; + +function tryRewrite( + pathname: string, + configs: { rewrite: (path: string) => false | string }[], +): false | string { + for (const { rewrite } of configs) { + const result = rewrite(pathname); + if (result) return result; + } + return false; +} + +const llmMiddleware = createMiddleware().server(({ next, request }) => { + const url = new URL(request.url); + + if (isMarkdownPreferred(request)) { + const acceptPath = tryRewrite(url.pathname, acceptRewrites); + if (acceptPath) { + throw redirect({ href: new URL(acceptPath, url).href }); + } + } + + return next(); +}); + +export const startInstance = createStart(() => { + return { + requestMiddleware: [llmMiddleware], + }; +}); diff --git a/packages/documentation/src/types/fumadocs-mdx-collections.d.ts b/packages/documentation/src/types/fumadocs-mdx-collections.d.ts new file mode 100644 index 00000000..f24428d8 --- /dev/null +++ b/packages/documentation/src/types/fumadocs-mdx-collections.d.ts @@ -0,0 +1,15 @@ +declare module "fumadocs-mdx:collections/server" { + import type { DocsCollectionEntry } from "fumadocs-mdx/runtime/server"; + + export const docs: DocsCollectionEntry; +} + +declare module "fumadocs-mdx:collections/browser" { + import type { DocCollectionEntry } from "fumadocs-mdx/runtime/browser"; + + const browserCollections: { + docs: DocCollectionEntry; + }; + + export default browserCollections; +} diff --git a/packages/documentation/tsconfig.json b/packages/documentation/tsconfig.json index 35c013f2..06589865 100644 --- a/packages/documentation/tsconfig.json +++ b/packages/documentation/tsconfig.json @@ -1,13 +1,17 @@ { "extends": "../../tsconfig.json", - "include": ["**/*.ts", "**/*.tsx"], + "include": ["src/**/*.ts", "src/**/*.tsx", ".source/**/*.ts"], "compilerOptions": { - "types": ["vite/client"], + "types": ["vite/client", "bun"], + "rootDir": ".", "baseUrl": ".", "paths": { "@/*": ["./src/*"], - "fumadocs-mdx:collections/*": [".source/*"] + "fumadocs-mdx:collections/*": ["./.source/*"] }, - "noEmit": true + "jsx": "react-jsx", + "composite": true, + "noEmit": false, + "outDir": "dist" } } diff --git a/packages/documentation/vite.config.ts b/packages/documentation/vite.config.ts index 09b276e8..211f09cf 100644 --- a/packages/documentation/vite.config.ts +++ b/packages/documentation/vite.config.ts @@ -19,8 +19,7 @@ export default defineConfig({ }), tanstackStart({ prerender: { - // TanStack Start prerender has path bug with Vercel preset - enabled: false, + enabled: false, // todo: enable this }, }), react(), diff --git a/packages/documentation/todo.md b/todo.md similarity index 98% rename from packages/documentation/todo.md rename to todo.md index 01d6352d..dbe026b0 100644 --- a/packages/documentation/todo.md +++ b/todo.md @@ -1,3 +1,2 @@ -- - SEO https://tanstack.com/start/latest/docs/framework/react/guide/seo - LLMO https://tanstack.com/start/latest/docs/framework/react/guide/llmo diff --git a/tsconfig.json b/tsconfig.json index 3c7a503e..288f22e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,5 +7,10 @@ "target": "ESNext", "jsx": "react-jsx", "noEmit": true - } + }, + "exclude": ["node_modules"], + "references": [ + { "path": "./packages/design-system" }, + { "path": "./packages/documentation" } + ] } diff --git a/turbo.json b/turbo.json index 6ab62393..c39f388c 100644 --- a/turbo.json +++ b/turbo.json @@ -19,6 +19,9 @@ "dev": { "cache": false, "persistent": true + }, + "test": { + "dependsOn": ["^test"] } } }