diff --git a/packages/website-router/package.json b/packages/website-router/package.json index 3fd32f99d..adccf3cd3 100644 --- a/packages/website-router/package.json +++ b/packages/website-router/package.json @@ -4,6 +4,7 @@ "main": "index.ts", "scripts": { "dev": "wrangler dev", + "test": "node --import @oxc-node/core/register --test \"src/**/*.test.ts\"", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -11,6 +12,7 @@ }, "devDependencies": { "@cloudflare/workers-types": "4.20250321.0", + "@oxc-node/core": "^0.0.35", "@types/node": "20.17.12", "typescript": "5.7.3", "wrangler": "3.78.7" diff --git a/packages/website-router/src/config.ts b/packages/website-router/src/config.ts index c6de83bda..764f43288 100644 --- a/packages/website-router/src/config.ts +++ b/packages/website-router/src/config.ts @@ -1,5 +1,6 @@ export type RewriteRecord = { rewrite: string; + preserveSearch?: boolean; crisp?: { segments: string[] }; sitemap?: boolean; banner?: string; @@ -102,6 +103,7 @@ export const jsonConfig = { }, '/graphql/hive-testing': { rewrite: 'hive-platform-docs.theguild.workers.dev', + preserveSearch: true, crisp: { segments: ['hive-website'] }, sitemap: false, }, diff --git a/packages/website-router/src/routing.test.ts b/packages/website-router/src/routing.test.ts new file mode 100644 index 000000000..c2ca19b0e --- /dev/null +++ b/packages/website-router/src/routing.test.ts @@ -0,0 +1,37 @@ +/// + +import { equal } from 'node:assert/strict'; +import { test } from 'node:test'; +import { buildUpstreamUrl } from './routing'; + +test('preserveSearch keeps the original query string', () => { + const upstreamUrl = buildUpstreamUrl({ + request: new Request( + 'https://the-guild.dev/graphql/hive-testing/_serverFn/test?payload=%7B%22foo%22%3A1%7D', + ), + record: { + preserveSearch: true, + rewrite: 'hive-platform-docs.theguild.workers.dev', + }, + upstreamPath: '/_serverFn/test', + }); + + equal( + upstreamUrl.toString(), + 'https://hive-platform-docs.theguild.workers.dev/_serverFn/test?payload=%7B%22foo%22%3A1%7D', + ); +}); + +test('default rewrite behavior still drops search params', () => { + const upstreamUrl = buildUpstreamUrl({ + request: new Request( + 'https://the-guild.dev/graphql/hive-testing/_serverFn/test?payload=%7B%22foo%22%3A1%7D', + ), + record: { + rewrite: 'hive-platform-docs.theguild.workers.dev', + }, + upstreamPath: '/_serverFn/test', + }); + + equal(upstreamUrl.toString(), 'https://hive-platform-docs.theguild.workers.dev/_serverFn/test'); +}); diff --git a/packages/website-router/src/routing.ts b/packages/website-router/src/routing.ts index c6eaae6a7..b06319a2b 100644 --- a/packages/website-router/src/routing.ts +++ b/packages/website-router/src/routing.ts @@ -102,6 +102,20 @@ export function redirect(sentry: Toucan, from: string, url: string, code = 301) export type ManipulateResponseFn = (record: RewriteRecord, response: Response) => Promise; +export function buildUpstreamUrl(options: { + request: Request; + record: RewriteRecord; + upstreamPath: string; +}) { + const upstreamUrl = new URL(`https://${options.record.rewrite}${options.upstreamPath || ''}`); + + if (options.record.preserveSearch) { + upstreamUrl.search = new URL(options.request.url).search; + } + + return upstreamUrl; +} + export async function handleRewrite(options: { request: Request; cacheStorageId: number; @@ -114,7 +128,7 @@ export async function handleRewrite(options: { match: string | null; publicDomain: string; }): Promise { - const url = `https://${options.record.rewrite}${options.upstreamPath || ''}`; + const url = buildUpstreamUrl(options).toString(); const cacheKey = new Request(url, options.request); const cache = await caches.open(String(options.cacheStorageId)); let response = await cache.match(cacheKey); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7bd3b699..2c0d4efb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,6 +229,9 @@ importers: '@cloudflare/workers-types': specifier: 4.20250321.0 version: 4.20250321.0 + '@oxc-node/core': + specifier: ^0.0.35 + version: 0.0.35 '@types/node': specifier: 20.17.12 version: 20.17.12 @@ -524,12 +527,21 @@ packages: '@emnapi/core@1.3.1': resolution: {integrity: sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/wasi-threads@1.0.1': resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emotion/is-prop-valid@0.8.8': resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} @@ -1280,6 +1292,9 @@ packages: '@napi-rs/wasm-runtime@0.2.7': resolution: {integrity: sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@next/bundle-analyzer@14.2.5': resolution: {integrity: sha512-BtBbI8VUnB7s4m9ut6CkeJ8Hyx+aq+86mbH+uAld7ZGG0/eH4+5hcPnkHKsQM/yj74iClazS0fninI8yZbIZWA==} @@ -1383,6 +1398,94 @@ packages: resolution: {integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==} engines: {node: ^16.14.0 || >=18.0.0} + '@oxc-node/core-android-arm-eabi@0.0.35': + resolution: {integrity: sha512-Vgw/DtArB1fZJ7LX4FX7YDpRvWzwv80lFNngTcamS+9Kbd83HpZb6Tg38t6f7Ubyc/+cL0TTMo55Kg8gwnQGHQ==} + cpu: [arm] + os: [android] + + '@oxc-node/core-android-arm64@0.0.35': + resolution: {integrity: sha512-Z/2jKqkTybSDnx2lBb44K0TLD2eUgLMi0te0pp5p5GVnsOZ8A+qSnhZpsPAR8GAbGERdMNOWrDYqj0/VYQt7sQ==} + cpu: [arm64] + os: [android] + + '@oxc-node/core-darwin-arm64@0.0.35': + resolution: {integrity: sha512-aeEG/a1zj8pA6GC0P7NypzdDY0c6AbZOdbxaGl9UQlwGgHmw6sOtq5PJO+7rCvzxUKPxBH9VZOVg0laFcncIFw==} + cpu: [arm64] + os: [darwin] + + '@oxc-node/core-darwin-x64@0.0.35': + resolution: {integrity: sha512-MtxGaUR2LBcUmqINyxzSYdx5om9KlFjyvN8cx/NizH6U5nYs7Wh/XAIoGpcQmkK2snT1FsgJeGR9L01Q1oqmng==} + cpu: [x64] + os: [darwin] + + '@oxc-node/core-freebsd-x64@0.0.35': + resolution: {integrity: sha512-xsZNqMeavNxi/WTxaQMZj6walhj7skCu36yff5q0RjsV7Dzp3RQ/SYK1t3ydw3cwPz2oZCx0AukAYJAj4i9vdw==} + cpu: [x64] + os: [freebsd] + + '@oxc-node/core-linux-arm-gnueabihf@0.0.35': + resolution: {integrity: sha512-F+d948mEzQMvcv0BQO3NN4DP0jsJwvvD5VGCFcR2mFa/z6L7UuRkS8yOKnP7tUfZjri7BwxXn37Szj0ZY7pEPA==} + cpu: [arm] + os: [linux] + + '@oxc-node/core-linux-arm64-gnu@0.0.35': + resolution: {integrity: sha512-wKbAstp6Ztq5UVBf/csnMTPMef+wGsrNykJCAtJuO/l88Okm4jn6wnhudeD3hf/426Vw93txBS8veqN2JSb6fg==} + cpu: [arm64] + os: [linux] + + '@oxc-node/core-linux-arm64-musl@0.0.35': + resolution: {integrity: sha512-IdLaYnFrDGRICQ86AoEQEv5Rfo//knhg4g9ABy7QE3C231C3YpbgwtY7YH7Qv+xHDuUHnTNo8Lo/iraSIY3tQA==} + cpu: [arm64] + os: [linux] + + '@oxc-node/core-linux-ppc64-gnu@0.0.35': + resolution: {integrity: sha512-yxfWpG2as+al6G9epDvFk8AX1UWy76WlwCP3pUGtpEUGuoAO63JAHUMDSXv1sSd1YatJmRJ75ptexU6c/xMdXg==} + cpu: [ppc64] + os: [linux] + + '@oxc-node/core-linux-s390x-gnu@0.0.35': + resolution: {integrity: sha512-nH3mnP6ger1i4LaroWhvtk3coquNYBJD9eqG3OEuJEFGo1Ao80irFcFoktQCLLq47uomYuNQxNJw5covYNHvLw==} + cpu: [s390x] + os: [linux] + + '@oxc-node/core-linux-x64-gnu@0.0.35': + resolution: {integrity: sha512-2VKErkkTxLViK/8xbdRoQ9+sid8ZGRROLkcmMtrggjQLU69EhL0wioUVztnDVjHfOPAN17lEAN7tUgxz+PAxCg==} + cpu: [x64] + os: [linux] + + '@oxc-node/core-linux-x64-musl@0.0.35': + resolution: {integrity: sha512-QDDZYWMbwB/1uyn0BPMYeqT6miWQBljzLCYESmsVcaHOps204yKHI1Ezp79n2BiYEghhu9RPWrOd4wZ7+Gqa7Q==} + cpu: [x64] + os: [linux] + + '@oxc-node/core-openharmony-arm64@0.0.35': + resolution: {integrity: sha512-ihb0W8mc0iM9SpfFwj9xY/1gVAPv2y7fGuW2w4jWOICCY2enJ8GnY2N9eYloPkHd2/2+S87M63H998psVZQquQ==} + cpu: [arm64] + os: [openharmony] + + '@oxc-node/core-wasm32-wasi@0.0.35': + resolution: {integrity: sha512-GoT1X1Rw3MXbvU25rsqT6gLhl9AKBdLe1ss6pVHxzps0Va6qrSD/2H4alGglUX+qccKcw0kCgJbPKJphM/0CrQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-node/core-win32-arm64-msvc@0.0.35': + resolution: {integrity: sha512-ObSjUyRd5md+hKg4j8ufhjaeXHGm4f+9cz1y20mOHr/HOkBIY6CNoPM7x5JEzZNerVZ9Ye62G6t6HNQZttBjsg==} + cpu: [arm64] + os: [win32] + + '@oxc-node/core-win32-ia32-msvc@0.0.35': + resolution: {integrity: sha512-ZE7/di30tfhh/2ItgcZim4aPLw1ve+TQrp6oJSqMRyYjq0k1AwFrxIqICbaAG9sk79ap9Sy1icFMfFgSkhnABQ==} + cpu: [ia32] + os: [win32] + + '@oxc-node/core-win32-x64-msvc@0.0.35': + resolution: {integrity: sha512-9OyyjY/ECi1icwq32baG0Uct7RuAHbVxzGDffJzNhRtBABpQiIQauoaVuYiSlNecqnA8qFYxh2wxbKaVlsR1YA==} + cpu: [x64] + os: [win32] + + '@oxc-node/core@0.0.35': + resolution: {integrity: sha512-PV46QRDI2wCDdaPzppEh4UfzFmmpSt+1dX32ooq8RWb0BuWX24+LKYicAmSrsk1ls8JRSkAqiWrjrYFHIGozGg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1800,6 +1903,9 @@ packages: postcss-lightningcss: ^1.0.1 tailwindcss: ^3.4.14 + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -2981,6 +3087,7 @@ packages: eslint-plugin-markdown@3.0.1: resolution: {integrity: sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: Please use @eslint/markdown instead peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3337,11 +3444,12 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -4012,6 +4120,7 @@ packages: mathjax-full@3.2.2: resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==} + deprecated: Version 4 replaces this package with the scoped package @mathjax/src md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} @@ -4448,6 +4557,7 @@ packages: next@14.2.5: resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} engines: {node: '>=18.17.0'} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details. hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -4726,6 +4836,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -6457,16 +6571,32 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.3.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.0.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/is-prop-valid@0.8.8': dependencies: '@emotion/memoize': 0.7.4 @@ -6993,6 +7123,13 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@next/bundle-analyzer@14.2.5': dependencies: webpack-bundle-analyzer: 4.10.1 @@ -7102,6 +7239,81 @@ snapshots: dependencies: which: 4.0.0 + '@oxc-node/core-android-arm-eabi@0.0.35': + optional: true + + '@oxc-node/core-android-arm64@0.0.35': + optional: true + + '@oxc-node/core-darwin-arm64@0.0.35': + optional: true + + '@oxc-node/core-darwin-x64@0.0.35': + optional: true + + '@oxc-node/core-freebsd-x64@0.0.35': + optional: true + + '@oxc-node/core-linux-arm-gnueabihf@0.0.35': + optional: true + + '@oxc-node/core-linux-arm64-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-arm64-musl@0.0.35': + optional: true + + '@oxc-node/core-linux-ppc64-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-s390x-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-x64-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-x64-musl@0.0.35': + optional: true + + '@oxc-node/core-openharmony-arm64@0.0.35': + optional: true + + '@oxc-node/core-wasm32-wasi@0.0.35': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-node/core-win32-arm64-msvc@0.0.35': + optional: true + + '@oxc-node/core-win32-ia32-msvc@0.0.35': + optional: true + + '@oxc-node/core-win32-x64-msvc@0.0.35': + optional: true + + '@oxc-node/core@0.0.35': + dependencies: + pirates: 4.0.7 + optionalDependencies: + '@oxc-node/core-android-arm-eabi': 0.0.35 + '@oxc-node/core-android-arm64': 0.0.35 + '@oxc-node/core-darwin-arm64': 0.0.35 + '@oxc-node/core-darwin-x64': 0.0.35 + '@oxc-node/core-freebsd-x64': 0.0.35 + '@oxc-node/core-linux-arm-gnueabihf': 0.0.35 + '@oxc-node/core-linux-arm64-gnu': 0.0.35 + '@oxc-node/core-linux-arm64-musl': 0.0.35 + '@oxc-node/core-linux-ppc64-gnu': 0.0.35 + '@oxc-node/core-linux-s390x-gnu': 0.0.35 + '@oxc-node/core-linux-x64-gnu': 0.0.35 + '@oxc-node/core-linux-x64-musl': 0.0.35 + '@oxc-node/core-openharmony-arm64': 0.0.35 + '@oxc-node/core-wasm32-wasi': 0.0.35 + '@oxc-node/core-win32-arm64-msvc': 0.0.35 + '@oxc-node/core-win32-ia32-msvc': 0.0.35 + '@oxc-node/core-win32-x64-msvc': 0.0.35 + '@pkgjs/parseargs@0.11.0': optional: true @@ -7502,6 +7714,11 @@ snapshots: postcss-lightningcss: 1.0.2(postcss@8.4.49) tailwindcss: 3.4.17 + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 @@ -11431,6 +11648,8 @@ snapshots: pirates@4.0.6: {} + pirates@4.0.7: {} + pluralize@8.0.0: {} possible-typed-array-names@1.0.0: {}