From a4726728e4a907bb36809fc64a11e9caaeed480f Mon Sep 17 00:00:00 2001 From: flakey5 <73616808+flakey5@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:05:33 -0800 Subject: [PATCH 1/4] src: route `/dist/docs/` properly `nodejs.org/dist/docs/` and `nodejs.org/download/release/docs/` should symlink to `nodejs/docs/` in dist-prod Signed-off-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- e2e-tests/file.test.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ src/routes/index.ts | 21 +++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/e2e-tests/file.test.ts b/e2e-tests/file.test.ts index a4522a15..742b11d3 100644 --- a/e2e-tests/file.test.ts +++ b/e2e-tests/file.test.ts @@ -184,6 +184,64 @@ test('GET `/dist/asd123.json` returns 404', async () => { expect(await res.text()).toStrictEqual('File not found'); }); +for (const prefix of ['/dist', '/download/release']) { + test(`GET \`${prefix}/docs/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v0.0.1/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); + + // Dir that actually exists in `nodejs/docs/` + test(`GET \`${prefix}/docs/v0.0.1/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v0.0.1/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); + + // Dir that's just a symlink `nodejs/docs/` -> `nodejs/release/v20.0.0/docs/` + test(`GET \`${prefix}/docs/v20.0.0/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v20.0.0/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); +} + test('`if-modified-since` header', async () => { const ctx = createExecutionContext(); diff --git a/src/routes/index.ts b/src/routes/index.ts index ef685c2d..a9f0d050 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -48,6 +48,27 @@ export function registerRoutes(router: Router): void { router.head('/llms.txt', r2Middleware); router.get('/llms.txt', r2Middleware); + // `/dist/docs/...` is a symlink to `nodejs/docs/` + const distDocsDirToDocsDirRewrite = new SubtitutionMiddleware( + router, + '/dist/docs', + '/docs' + ); + router.head('/dist/docs*', distDocsDirToDocsDirRewrite); + router.get('/dist/docs*', distDocsDirToDocsDirRewrite); + + // `/download/release/docs/...` is a symlink to `nodejs/docs/` + const mainlineReleaseDocsDirToDocsDirRewrite = new SubtitutionMiddleware( + router, + '/download/release/docs', + '/docs' + ); + router.head( + '/download/release/docs*', + mainlineReleaseDocsDirToDocsDirRewrite + ); + router.get('/download/release/docs*', mainlineReleaseDocsDirToDocsDirRewrite); + router.head('/dist/:filePath+?', r2Middleware); router.get('/dist/:filePath+?', r2Middleware); From 1a16bdb96ebd8e80900af7762fe0ba6c68aa960d Mon Sep 17 00:00:00 2001 From: flakey5 <73616808+flakey5@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:25:39 -0800 Subject: [PATCH 2/4] code review Signed-off-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- e2e-tests/directory.kv.test.ts | 58 ++++++++++++++++++++++++++++++++++ e2e-tests/directory.s3.test.ts | 58 ++++++++++++++++++++++++++++++++++ e2e-tests/file.test.ts | 12 +++---- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/e2e-tests/directory.kv.test.ts b/e2e-tests/directory.kv.test.ts index 4eb5c7c0..ee9b8480 100644 --- a/e2e-tests/directory.kv.test.ts +++ b/e2e-tests/directory.kv.test.ts @@ -87,3 +87,61 @@ test('GET `/dist` redirects to `/dist/`', async () => { expect(res.status).toBe(301); expect(res.headers.get('location')).toStrictEqual('https://localhost/dist/'); }); + +for (const prefix of ['/dist', '/download/release']) { + test(`GET \`${prefix}/docs/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v0.0.1/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); + + // Dir that actually exists in `nodejs/docs/` + test(`GET \`${prefix}/docs/v0.0.1/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v0.0.1/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); + + // Dir that's just a symlink `nodejs/docs/` -> `nodejs/release/v20.0.0/docs/` + test(`GET \`${prefix}/docs/v20.0.0/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v20.0.0/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); +} diff --git a/e2e-tests/directory.s3.test.ts b/e2e-tests/directory.s3.test.ts index f1bca3e8..08c9d2ee 100644 --- a/e2e-tests/directory.s3.test.ts +++ b/e2e-tests/directory.s3.test.ts @@ -153,3 +153,61 @@ test('GET `/dist` redirects to `/dist/`', async () => { expect(res.status).toBe(301); expect(res.headers.get('location')).toStrictEqual('https://localhost/dist/'); }); + +for (const prefix of ['/dist', '/download/release']) { + test(`GET \`${prefix}/docs/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v0.0.1/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); + + // Dir that actually exists in `nodejs/docs/` + test(`GET \`${prefix}/docs/v0.0.1/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v0.0.1/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); + + // Dir that's just a symlink `nodejs/docs/` -> `nodejs/release/v20.0.0/docs/` + test(`GET \`${prefix}/docs/v20.0.0/\` returns 200`, async () => { + const ctx = createExecutionContext(); + + const res = await worker.fetch( + new Request(`https://localhost${prefix}/docs/v20.0.0/`), + mockedEnv, + ctx + ); + + // Consume the body promise + await res.text(); + + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toStrictEqual( + CACHE_HEADERS.mutable + ); + }); +} diff --git a/e2e-tests/file.test.ts b/e2e-tests/file.test.ts index 742b11d3..abd5301b 100644 --- a/e2e-tests/file.test.ts +++ b/e2e-tests/file.test.ts @@ -185,11 +185,11 @@ test('GET `/dist/asd123.json` returns 404', async () => { }); for (const prefix of ['/dist', '/download/release']) { - test(`GET \`${prefix}/docs/\` returns 200`, async () => { + test(`GET \`${prefix}/docs/index.html\` returns 200`, async () => { const ctx = createExecutionContext(); const res = await worker.fetch( - new Request(`https://localhost${prefix}/docs/v0.0.1/`), + new Request(`https://localhost${prefix}/docs/v0.0.1/index.html`), mockedEnv, ctx ); @@ -204,11 +204,11 @@ for (const prefix of ['/dist', '/download/release']) { }); // Dir that actually exists in `nodejs/docs/` - test(`GET \`${prefix}/docs/v0.0.1/\` returns 200`, async () => { + test(`GET \`${prefix}/docs/v0.0.1/index.html\` returns 200`, async () => { const ctx = createExecutionContext(); const res = await worker.fetch( - new Request(`https://localhost${prefix}/docs/v0.0.1/`), + new Request(`https://localhost${prefix}/docs/v0.0.1/index.html`), mockedEnv, ctx ); @@ -223,11 +223,11 @@ for (const prefix of ['/dist', '/download/release']) { }); // Dir that's just a symlink `nodejs/docs/` -> `nodejs/release/v20.0.0/docs/` - test(`GET \`${prefix}/docs/v20.0.0/\` returns 200`, async () => { + test(`GET \`${prefix}/docs/v20.0.0/index.html\` returns 200`, async () => { const ctx = createExecutionContext(); const res = await worker.fetch( - new Request(`https://localhost${prefix}/docs/v20.0.0/`), + new Request(`https://localhost${prefix}/docs/v20.0.0/index.html`), mockedEnv, ctx ); From b5735852704e26f9c70a3b8a8df3c5058eaaaca9 Mon Sep 17 00:00:00 2001 From: flakey5 <73616808+flakey5@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:42:08 -0800 Subject: [PATCH 3/4] code review Signed-off-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- e2e-tests/directory.s3.test.ts | 6 ++++++ e2e-tests/file.test.ts | 10 +++++----- src/utils/cache.test.ts | 6 ++++++ src/utils/cache.ts | 4 ++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/e2e-tests/directory.s3.test.ts b/e2e-tests/directory.s3.test.ts index 08c9d2ee..2152ad16 100644 --- a/e2e-tests/directory.s3.test.ts +++ b/e2e-tests/directory.s3.test.ts @@ -156,6 +156,8 @@ test('GET `/dist` redirects to `/dist/`', async () => { for (const prefix of ['/dist', '/download/release']) { test(`GET \`${prefix}/docs/\` returns 200`, async () => { + mockS3Response(S3_DIRECTORY_RESULT); + const ctx = createExecutionContext(); const res = await worker.fetch( @@ -175,6 +177,8 @@ for (const prefix of ['/dist', '/download/release']) { // Dir that actually exists in `nodejs/docs/` test(`GET \`${prefix}/docs/v0.0.1/\` returns 200`, async () => { + mockS3Response(S3_DIRECTORY_RESULT); + const ctx = createExecutionContext(); const res = await worker.fetch( @@ -194,6 +198,8 @@ for (const prefix of ['/dist', '/download/release']) { // Dir that's just a symlink `nodejs/docs/` -> `nodejs/release/v20.0.0/docs/` test(`GET \`${prefix}/docs/v20.0.0/\` returns 200`, async () => { + mockS3Response(S3_DIRECTORY_RESULT); + const ctx = createExecutionContext(); const res = await worker.fetch( diff --git a/e2e-tests/file.test.ts b/e2e-tests/file.test.ts index abd5301b..2b0008b3 100644 --- a/e2e-tests/file.test.ts +++ b/e2e-tests/file.test.ts @@ -199,7 +199,7 @@ for (const prefix of ['/dist', '/download/release']) { expect(res.status).toBe(200); expect(res.headers.get('cache-control')).toStrictEqual( - CACHE_HEADERS.mutable + CACHE_HEADERS.immutable ); }); @@ -218,16 +218,16 @@ for (const prefix of ['/dist', '/download/release']) { expect(res.status).toBe(200); expect(res.headers.get('cache-control')).toStrictEqual( - CACHE_HEADERS.mutable + CACHE_HEADERS.immutable ); }); // Dir that's just a symlink `nodejs/docs/` -> `nodejs/release/v20.0.0/docs/` - test(`GET \`${prefix}/docs/v20.0.0/index.html\` returns 200`, async () => { + test(`GET \`${prefix}/docs/v20.0.0/api/index.html\` returns 200`, async () => { const ctx = createExecutionContext(); const res = await worker.fetch( - new Request(`https://localhost${prefix}/docs/v20.0.0/index.html`), + new Request(`https://localhost${prefix}/docs/v20.0.0/api/index.html`), mockedEnv, ctx ); @@ -237,7 +237,7 @@ for (const prefix of ['/dist', '/download/release']) { expect(res.status).toBe(200); expect(res.headers.get('cache-control')).toStrictEqual( - CACHE_HEADERS.mutable + CACHE_HEADERS.immutable ); }); } diff --git a/src/utils/cache.test.ts b/src/utils/cache.test.ts index 503c46e1..db8ff830 100644 --- a/src/utils/cache.test.ts +++ b/src/utils/cache.test.ts @@ -7,6 +7,8 @@ describe('isImmutablePath', () => { '/dist/v20.0.0/node-v20.0.0-linux-x64.tar.gz', '/download/release/v20.0.0/node-v20.0.0.pkg', '/docs/v18.0.0/api/fs.html', + '/dist/docs/v20.0.0/api/index.html', + '/download/release/v20.0.0/api/index.html', ])('returns true for immutable asset `%s`', path => { expect(isImmutablePath(path)).toEqual(true); }); @@ -31,6 +33,10 @@ describe('isImmutablePath', () => { // `latest` aliases point at moving content '/dist/latest/node-v20.0.0-linux-x64.tar.gz', '/docs/latest/api/fs.html', + '/dist/docs/latest/api/index.html', + '/download/release/latest/api/index.html', + '/dist/docs/', + '/download/docs/', '/download/release/latest/win-x64/node_pdb.7z', '/dist/latest-v20.x/node-v20.0.0.tar.gz', // Legacy `latest-v0.X.x` aliases diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 0834e3dd..e562a2a5 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -44,6 +44,10 @@ export function isImmutablePath(pathname: string): boolean { return false; } + // if (pathname.startsWith('/download/docs') || pathname.startsWith('/dist/docs')) { + // return false; + // } + // The periodic download-counts run rewrites files under `/metrics/` at stable // URLs: the `summaries/.csv`/`.png` aggregates every run, and // `logs/nodejs.org-access.log.csv` for the current (not yet final) day. The From 42a76c6b6195fb0cc6e663f6d097f06227edba2a Mon Sep 17 00:00:00 2001 From: flakey5 <73616808+flakey5@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:43:02 -0800 Subject: [PATCH 4/4] code review Signed-off-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- src/utils/cache.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/cache.ts b/src/utils/cache.ts index e562a2a5..0834e3dd 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -44,10 +44,6 @@ export function isImmutablePath(pathname: string): boolean { return false; } - // if (pathname.startsWith('/download/docs') || pathname.startsWith('/dist/docs')) { - // return false; - // } - // The periodic download-counts run rewrites files under `/metrics/` at stable // URLs: the `summaries/.csv`/`.png` aggregates every run, and // `logs/nodejs.org-access.log.csv` for the current (not yet final) day. The