Skip to content

Commit 415a9c4

Browse files
authored
Merge branch 'main' into arena/019f6307-astro
2 parents cb67ca8 + cbefed9 commit 415a9c4

5 files changed

Lines changed: 74 additions & 8 deletions

File tree

.changeset/green-clocks-greet.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes dev server returning 404 for `?url` imported assets when accessed via browser navigation

packages/astro/src/assets/fonts/infra/fs-font-file-content-resolver.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ export class FsFontFileContentResolver implements FontFileContentResolver {
1717
return url;
1818
}
1919
try {
20-
// We use the url and the file content for the id generation because:
21-
// - The URL is not hashed unlike remote providers
22-
// - A font file can renamed and swapped so we would incorrectly cache it
23-
return url + this.#readFileSync(url);
20+
// We only use the file content for the id generation to ensure
21+
// deterministic output filenames regardless of the project's location
22+
// on disk. The absolute path is excluded so that the same font file
23+
// produces the same hash across different checkout directories.
24+
return this.#readFileSync(url);
2425
} catch (cause) {
2526
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause });
2627
}

packages/astro/src/vite-plugin-astro-server/route-guard.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,11 @@ export function routeGuardMiddleware(settings: AstroSettings): vite.Connect.Next
112112
return next();
113113
}
114114

115+
const resolved = new URL('.' + pathname, config.root);
115116
const fsInfo: RouteGuardFsInfo = {
116-
existsInPublic: fs.existsSync(new URL('.' + pathname, config.publicDir)),
117-
existsInSrc: fs.existsSync(new URL('.' + pathname, config.srcDir)),
117+
existsInPublic:
118+
resolved.pathname.startsWith(config.publicDir.pathname) && fs.existsSync(resolved),
119+
existsInSrc: resolved.pathname.startsWith(config.srcDir.pathname) && fs.existsSync(resolved),
118120
existsAtRootAsFile: false,
119121
};
120122

packages/astro/test/units/assets/fonts/infra.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -767,12 +767,12 @@ describe('fonts infra', () => {
767767
assert.equal(fontFileIdContentResolver.resolve(url), url);
768768
});
769769

770-
it('returns url and content when absolute', () => {
770+
it('returns content only when absolute', () => {
771771
const url = fileURLToPath(new URL(import.meta.url));
772772
const fontFileIdContentResolver = new FsFontFileContentResolver({
773773
readFileSync: () => 'content',
774774
});
775-
assert.equal(fontFileIdContentResolver.resolve(url), url + 'content');
775+
assert.equal(fontFileIdContentResolver.resolve(url), 'content');
776776
});
777777
});
778778

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import assert from 'node:assert/strict';
2+
import { after, describe, it } from 'node:test';
3+
import { routeGuardMiddleware } from '../../../dist/vite-plugin-astro-server/route-guard.js';
4+
import { createBasicSettings, createFixture, createRequestAndResponse } from '../test-utils.ts';
5+
6+
describe('routeGuardMiddleware — filesystem resolution', () => {
7+
let fixture: Awaited<ReturnType<typeof createFixture>>;
8+
9+
after(async () => {
10+
await fixture?.rm();
11+
});
12+
13+
it('allows src/ files through on browser navigation (Accept: text/html)', async () => {
14+
fixture = await createFixture({
15+
'src/downloads/a.pdf': '%PDF-1.4 test',
16+
});
17+
const settings = await createBasicSettings({ root: fixture.path });
18+
const middleware = routeGuardMiddleware(settings);
19+
20+
// Simulate browser navigation to /src/downloads/a.pdf
21+
const { req, res } = createRequestAndResponse({
22+
method: 'GET',
23+
url: '/src/downloads/a.pdf',
24+
headers: { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' },
25+
});
26+
27+
let nextCalled = false;
28+
middleware(req, res, () => {
29+
nextCalled = true;
30+
});
31+
32+
assert.equal(nextCalled, true, 'next() should be called — file is inside srcDir');
33+
assert.notEqual((res as any).statusCode, 404, 'should not return 404 for files inside srcDir');
34+
});
35+
36+
it('blocks root-level files outside srcDir/publicDir', async () => {
37+
fixture = await createFixture({
38+
'README.md': '# Test',
39+
'src/pages/index.astro': '',
40+
});
41+
const settings = await createBasicSettings({ root: fixture.path });
42+
const middleware = routeGuardMiddleware(settings);
43+
44+
const { req, res } = createRequestAndResponse({
45+
method: 'GET',
46+
url: '/README.md',
47+
headers: { accept: 'text/html' },
48+
});
49+
50+
let nextCalled = false;
51+
middleware(req, res, () => {
52+
nextCalled = true;
53+
});
54+
55+
assert.equal(nextCalled, false, 'next() should NOT be called — file is at root');
56+
assert.equal((res as any).statusCode, 404, 'should return 404 for root-level files');
57+
});
58+
});

0 commit comments

Comments
 (0)