mirror of
https://github.com/withastro/astro.git
synced 2024-12-23 21:53:55 -05:00
Add support for custom non-html route encoding (#4549)
* Add custom encoding support for non-html routes * Add changeset * Add tests for png and json routes
This commit is contained in:
parent
c706d845eb
commit
255636cc7b
11 changed files with 93 additions and 3 deletions
5
.changeset/stale-camels-invent.md
Normal file
5
.changeset/stale-camels-invent.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Allow specifying custom encoding when using a non-html route. Only option before was 'utf-8' and now that is just the default.
|
|
@ -1085,6 +1085,7 @@ export interface APIContext {
|
||||||
|
|
||||||
export interface EndpointOutput {
|
export interface EndpointOutput {
|
||||||
body: Body;
|
body: Body;
|
||||||
|
encoding?: BufferEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type APIRoute = (
|
export type APIRoute = (
|
||||||
|
|
|
@ -391,6 +391,7 @@ async function generatePath(
|
||||||
};
|
};
|
||||||
|
|
||||||
let body: string;
|
let body: string;
|
||||||
|
let encoding: BufferEncoding | undefined;
|
||||||
if (pageData.route.type === 'endpoint') {
|
if (pageData.route.type === 'endpoint') {
|
||||||
const result = await callEndpoint(mod as unknown as EndpointHandler, options);
|
const result = await callEndpoint(mod as unknown as EndpointHandler, options);
|
||||||
|
|
||||||
|
@ -398,6 +399,7 @@ async function generatePath(
|
||||||
throw new Error(`Returning a Response from an endpoint is not supported in SSG mode.`);
|
throw new Error(`Returning a Response from an endpoint is not supported in SSG mode.`);
|
||||||
}
|
}
|
||||||
body = result.body;
|
body = result.body;
|
||||||
|
encoding = result.encoding;
|
||||||
} else {
|
} else {
|
||||||
const response = await render(options);
|
const response = await render(options);
|
||||||
|
|
||||||
|
@ -413,5 +415,5 @@ async function generatePath(
|
||||||
const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type);
|
const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type);
|
||||||
pageData.route.distURL = outFile;
|
pageData.route.distURL = outFile;
|
||||||
await fs.promises.mkdir(outFolder, { recursive: true });
|
await fs.promises.mkdir(outFolder, { recursive: true });
|
||||||
await fs.promises.writeFile(outFile, body, 'utf-8');
|
await fs.promises.writeFile(outFile, body, encoding ?? 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ type EndpointCallResult =
|
||||||
| {
|
| {
|
||||||
type: 'simple';
|
type: 'simple';
|
||||||
body: string;
|
body: string;
|
||||||
|
encoding?: BufferEncoding;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'response';
|
type: 'response';
|
||||||
|
@ -52,5 +53,6 @@ export async function call(
|
||||||
return {
|
return {
|
||||||
type: 'simple',
|
type: 'simple',
|
||||||
body: response.body,
|
body: response.body,
|
||||||
|
encoding: response.encoding,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
8
packages/astro/test/fixtures/non-html-pages/package.json
vendored
Normal file
8
packages/astro/test/fixtures/non-html-pages/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "@test/non-html-pages",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
BIN
packages/astro/test/fixtures/non-html-pages/src/images/placeholder.png
vendored
Normal file
BIN
packages/astro/test/fixtures/non-html-pages/src/images/placeholder.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 KiB |
11
packages/astro/test/fixtures/non-html-pages/src/pages/about.json.ts
vendored
Normal file
11
packages/astro/test/fixtures/non-html-pages/src/pages/about.json.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Returns the file body for this non-HTML file.
|
||||||
|
// The content type is based off of the extension in the filename,
|
||||||
|
// in this case: about.json.
|
||||||
|
export async function get() {
|
||||||
|
return {
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: 'Astro',
|
||||||
|
url: 'https://astro.build/',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
17
packages/astro/test/fixtures/non-html-pages/src/pages/placeholder.png.ts
vendored
Normal file
17
packages/astro/test/fixtures/non-html-pages/src/pages/placeholder.png.ts
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { promises as fs } from 'node:fs';
|
||||||
|
|
||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
|
||||||
|
export const get: APIRoute = async function get() {
|
||||||
|
try {
|
||||||
|
// Image is in the public domain. Sourced from
|
||||||
|
// https://en.wikipedia.org/wiki/File:Portrait_placeholder.png
|
||||||
|
const buffer = await fs.readFile('./test/fixtures/non-html-pages/src/images/placeholder.png');
|
||||||
|
return {
|
||||||
|
body: buffer.toString('binary'),
|
||||||
|
encoding: 'binary',
|
||||||
|
} as const;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
throw new Error(`Something went wrong in placeholder.png route!: ${error as string}`);
|
||||||
|
}
|
||||||
|
};
|
38
packages/astro/test/non-html-pages.test.js
Normal file
38
packages/astro/test/non-html-pages.test.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
describe('Non-HTML Pages', () => {
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({ root: './fixtures/non-html-pages/' });
|
||||||
|
await fixture.build();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('json', () => {
|
||||||
|
it('should match contents', async () => {
|
||||||
|
const json = JSON.parse(await fixture.readFile('/about.json'));
|
||||||
|
expect(json).to.have.property('name', 'Astro');
|
||||||
|
expect(json).to.have.property('url', 'https://astro.build/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('png', () => {
|
||||||
|
it('should not have had its encoding mangled', async () => {
|
||||||
|
const buffer = await fixture.readFile('/placeholder.png', 'base64');
|
||||||
|
|
||||||
|
// Sanity check the first byte
|
||||||
|
const hex = Buffer.from(buffer, 'base64').toString('hex');
|
||||||
|
const firstHexByte = hex.slice(0, 2);
|
||||||
|
// If we accidentally utf8 encode the png, the first byte (in hex) will be 'c2'
|
||||||
|
expect(firstHexByte).to.not.equal('c2');
|
||||||
|
// and if correctly encoded in binary, it should be '89'
|
||||||
|
expect(firstHexByte).to.equal('89');
|
||||||
|
|
||||||
|
// Make sure the whole buffer (in base64) matches this snapshot
|
||||||
|
expect(buffer).to.equal(
|
||||||
|
'iVBORw0KGgoAAAANSUhEUgAAAGQAAACWCAYAAAAouC1GAAAD10lEQVR4Xu3ZbW4iMRCE4c1RuP+ZEEfZFZHIAgHGH9Xtsv3m94yx6qHaM+HrfD7//cOfTQJfgNhYfG8EEC8PQMw8AAHELQGz/XCGAGKWgNl2aAggZgmYbYeGAGKWgNl2aAggZgmYbYeGAGKWgNl2aAggZgmYbYeGAGKWgNl2aAggZgmYbYeGAGKWgNl2aAggZgmYbYeGAGKWgNl2aAggZgmYbWe6hpxOp6oIL5dL1fWjL54CpBbhXagz4FiDqCCegZxhLEGiIGaAsQPJwrjhuLXFBiQbwrUtFiCjMZzaMhzEBcMFZSiIG4YDyjAQV4zRKENA3DFGoqSDzIIxCgWQgn9eZb6rpILM1o57qyyUNJCZMTLHFyAFI2s5kBXakYWS0hBAymsYDrISRkZLACn/8j5cGfXUFQqyYjuiWwJIY0Out0W0JAxk5XZEtgQQGtKRgOGt6rEV0pAdxlXU2AKks3U0pDPAiNuVKDREIGQNstP5EXGOyBsCSF/lAOnL7/tuRpYgRPUSKhQaIpIBRBSkahlAVEmK1gFEFKRqGUuQHR951e8i0kMdkP6+SUGu29kVxXJkAUJD+hMQrUBDREGqlgFElaRgHRXGdSsc6oAIEjBbgoYAUpfAbu8i1g3Z7V1EiRFyqANSN02er5Y/Zd0+YJexNUVDdmmJGiNsZAHSPrbCRtYOKFM1ZHWQCIzQkbX64Q5I+1iW3xmFkdKQFUcXIPLvePuCkRhpDVmpJcuArIASjZHakNmfujIwAKk4SpYFmXF0ZWEMachsoysTYyjIDE3JxhgO4owyAsMCxBFlFIYNiBPKSAxAnh57R2PYgLj9/j4SJvQXw5L3LjeM+z2PgBkG4gzx/EXKhEkHmQliRFvSQGaFyEZJAVkB4wYTPb7CQVbCyEAJA1kRImN8hYCsjhHZFDnILhhRKICUvL0eXKM86KUgu7Uj4kyRgeyMoRxfEhAw/neld3x1g4Dx+4DpQQFEcKi/WqIVpQuEdrzXTAcB47haLSjNDQHkGOR6RS1KEwgYZRgtj8PVIGDUYdS2BJD6fJvuKB1dVSC0o8ni56YSFED6Mq66WwpCO6qyf3vxEUpxQwAxAgFDg1HyGFzUEECMQMDQYhy15LAhgBiBgBGD8ent/WNDAIkDeYcCSGzmH1d/9U7yFoR25Eg9owCSk3vxmzsgM4AwrnKV7sfWy4YAAkhuAmaf9rEhtCNfC5D8zA8/8Yby6wyhIYfZhVwASEis7Yu+BKEd7YH23glIb4IB919RHs4QGhKQcsWSgFSElXEpIBkpV3zGAwjjqiK5oEsBCQq2Z9l/4WuAC09sfQEAAAAASUVORK5CYII='
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -146,8 +146,8 @@ export async function loadFixture(inlineConfig) {
|
||||||
const previewServer = await preview(config, { logging, telemetry, ...opts });
|
const previewServer = await preview(config, { logging, telemetry, ...opts });
|
||||||
return previewServer;
|
return previewServer;
|
||||||
},
|
},
|
||||||
readFile: (filePath) =>
|
readFile: (filePath, encoding) =>
|
||||||
fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'),
|
fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), encoding ?? 'utf8'),
|
||||||
readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)),
|
readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.outDir)),
|
||||||
glob: (p) =>
|
glob: (p) =>
|
||||||
fastGlob(p, {
|
fastGlob(p, {
|
||||||
|
|
|
@ -1666,6 +1666,12 @@ importers:
|
||||||
packages/astro/test/fixtures/multiple-renderers/renderers/two:
|
packages/astro/test/fixtures/multiple-renderers/renderers/two:
|
||||||
specifiers: {}
|
specifiers: {}
|
||||||
|
|
||||||
|
packages/astro/test/fixtures/non-html-pages:
|
||||||
|
specifiers:
|
||||||
|
astro: workspace:*
|
||||||
|
dependencies:
|
||||||
|
astro: link:../../..
|
||||||
|
|
||||||
packages/astro/test/fixtures/page-format:
|
packages/astro/test/fixtures/page-format:
|
||||||
specifiers:
|
specifiers:
|
||||||
astro: workspace:*
|
astro: workspace:*
|
||||||
|
|
Loading…
Reference in a new issue