From 3b104fb8a5c184d253d847998323e6b9c2573bcd Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Fri, 22 Jul 2022 15:22:31 -0400 Subject: Fixes Node adapter receiving a request body (#4023) * Fixes Node adapter receiving a request body * Updated lockfile --- packages/integrations/node/test/test-utils.js | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/integrations/node/test/test-utils.js (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js new file mode 100644 index 000000000..4bd42d557 --- /dev/null +++ b/packages/integrations/node/test/test-utils.js @@ -0,0 +1,41 @@ +import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; +import httpMocks from 'node-mocks-http'; +import { EventEmitter } from 'events'; + +/** + * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture + */ + +export function loadFixture(inlineConfig) { + if (!inlineConfig || !inlineConfig.root) + throw new Error("Must provide { root: './fixtures/...' }"); + + // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath + // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test` + return baseLoadFixture({ + ...inlineConfig, + root: new URL(inlineConfig.root, import.meta.url).toString(), + }); +} + +export function createRequestAndResponse(reqOptions) { + let req = httpMocks.createRequest(reqOptions); + + let res = httpMocks.createResponse({ + eventEmitter: EventEmitter, + req + }); + + let done = toPromise(res); + + return { req, res, done }; +} + +export function toPromise(res) { + return new Promise(resolve => { + res.on('end', () => { + let chunks = res._getChunks(); + resolve(chunks); + }); + }); +} -- cgit v1.2.3 From 3f716dba24c5ba4e230977d5209422ba3c85a0be Mon Sep 17 00:00:00 2001 From: matthewp Date: Fri, 22 Jul 2022 19:24:58 +0000 Subject: [ci] format --- packages/integrations/node/src/server.ts | 4 ++-- packages/integrations/node/test/api-route.test.js | 5 ++--- packages/integrations/node/test/test-utils.js | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/src/server.ts b/packages/integrations/node/src/server.ts index 453ecb2d2..12fcf0448 100644 --- a/packages/integrations/node/src/server.ts +++ b/packages/integrations/node/src/server.ts @@ -29,8 +29,8 @@ export function createExports(manifest: SSRManifest) { } else if (next) { return next(); } - } catch(err: unknown) { - if(!res.headersSent) { + } catch (err: unknown) { + if (!res.headersSent) { res.writeHead(500, `Server error`); res.end(); } diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index 963e0463a..a28b88e7f 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -2,7 +2,6 @@ import nodejs from '../dist/index.js'; import { loadFixture, createRequestAndResponse, toPromise } from './test-utils.js'; import { expect } from 'chai'; - describe('API routes', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -23,13 +22,13 @@ describe('API routes', () => { let { req, res, done } = createRequestAndResponse({ method: 'POST', - url: '/recipes' + url: '/recipes', }); handler(req, res); req.send(JSON.stringify({ id: 2 })); - let [ buffer ] = await done; + let [buffer] = await done; let json = JSON.parse(buffer.toString('utf-8')); expect(json.length).to.equal(1); expect(json[0].name).to.equal('Broccoli Soup'); diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 4bd42d557..0859c6acd 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -23,7 +23,7 @@ export function createRequestAndResponse(reqOptions) { let res = httpMocks.createResponse({ eventEmitter: EventEmitter, - req + req, }); let done = toPromise(res); @@ -32,7 +32,7 @@ export function createRequestAndResponse(reqOptions) { } export function toPromise(res) { - return new Promise(resolve => { + return new Promise((resolve) => { res.on('end', () => { let chunks = res._getChunks(); resolve(chunks); -- cgit v1.2.3 From 50ebfb9a623894f62e8281b4b24d5a13a66d3bd3 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:01:54 +0100 Subject: Drop Node 14 in CI for Node 16 and add Node 18 to the matrix (#5768) * ci(node): Move CI to Node 16 and add Node 18 to the matrix * fix(netlify): Fix set-cookie not working on Node 18 * fix(netlify): Handle if `set-cookie` is already somehow an array (apparently it can?) * test(node): Fix `toPromise` to match Astro's * fix(tests): Use the actual underlying ArrayBuffer instance to create the buffer in toPromise * chore: changeset --- packages/integrations/node/test/test-utils.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 0859c6acd..d3d7c17be 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -1,6 +1,6 @@ -import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; -import httpMocks from 'node-mocks-http'; import { EventEmitter } from 'events'; +import httpMocks from 'node-mocks-http'; +import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; /** * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture @@ -33,6 +33,15 @@ export function createRequestAndResponse(reqOptions) { export function toPromise(res) { return new Promise((resolve) => { + // node-mocks-http doesn't correctly handle non-Buffer typed arrays, + // so override the write method to fix it. + const write = res.write; + res.write = function (data, encoding) { + if (ArrayBuffer.isView(data) && !Buffer.isBuffer(data)) { + data = Buffer.from(data.buffer); + } + return write.call(this, data, encoding); + }; res.on('end', () => { let chunks = res._getChunks(); resolve(chunks); -- cgit v1.2.3 From eaa6c458d0176d229f9debc618f63ea62b6f0db1 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 12 Jan 2023 09:44:18 -0600 Subject: fix(core): handle encoded characters when matching routes (#5836) Co-authored-by: Nate Moore --- packages/integrations/node/test/encoded.test.js | 44 ++++++++++++++++++++++ .../node/test/fixtures/encoded/package.json | 9 +++++ .../src/pages/blog/\344\273\200\344\271\210.md" | 1 + .../src/pages/\344\273\200\344\271\210.astro" | 1 + packages/integrations/node/test/test-utils.js | 17 ++++++++- 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 packages/integrations/node/test/encoded.test.js create mode 100644 packages/integrations/node/test/fixtures/encoded/package.json create mode 100644 "packages/integrations/node/test/fixtures/encoded/src/pages/blog/\344\273\200\344\271\210.md" create mode 100644 "packages/integrations/node/test/fixtures/encoded/src/pages/\344\273\200\344\271\210.astro" (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/test/encoded.test.js b/packages/integrations/node/test/encoded.test.js new file mode 100644 index 000000000..162ebd6cc --- /dev/null +++ b/packages/integrations/node/test/encoded.test.js @@ -0,0 +1,44 @@ +import nodejs from '../dist/index.js'; +import { loadFixture, createRequestAndResponse } from './test-utils.js'; +import { expect } from 'chai'; + +describe('Encoded Pathname', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/encoded/', + output: 'server', + adapter: nodejs({ mode: 'middleware' }), + }); + await fixture.build(); + }); + + it('Can get an Astro file', async () => { + const { handler } = await import('./fixtures/encoded/dist/server/entry.mjs'); + let { req, res, text } = createRequestAndResponse({ + url: '/什么', + }); + + handler(req, res); + req.send(); + + const html = await text(); + expect(html).to.include('什么'); + }); + + it('Can get a Markdown file', async () => { + const { handler } = await import('./fixtures/encoded/dist/server/entry.mjs'); + + let { req, res, text } = createRequestAndResponse({ + url: '/blog/什么', + }); + + handler(req, res); + req.send(); + + const html = await text(); + expect(html).to.include('什么'); + }); +}); diff --git a/packages/integrations/node/test/fixtures/encoded/package.json b/packages/integrations/node/test/fixtures/encoded/package.json new file mode 100644 index 000000000..350077973 --- /dev/null +++ b/packages/integrations/node/test/fixtures/encoded/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/nodejs-encoded", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/node": "workspace:*" + } +} diff --git "a/packages/integrations/node/test/fixtures/encoded/src/pages/blog/\344\273\200\344\271\210.md" "b/packages/integrations/node/test/fixtures/encoded/src/pages/blog/\344\273\200\344\271\210.md" new file mode 100644 index 000000000..2820cf17e --- /dev/null +++ "b/packages/integrations/node/test/fixtures/encoded/src/pages/blog/\344\273\200\344\271\210.md" @@ -0,0 +1 @@ +# 什么 diff --git "a/packages/integrations/node/test/fixtures/encoded/src/pages/\344\273\200\344\271\210.astro" "b/packages/integrations/node/test/fixtures/encoded/src/pages/\344\273\200\344\271\210.astro" new file mode 100644 index 000000000..c8473f594 --- /dev/null +++ "b/packages/integrations/node/test/fixtures/encoded/src/pages/\344\273\200\344\271\210.astro" @@ -0,0 +1 @@ +

什么

diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index d3d7c17be..13e32a5e8 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -28,7 +28,13 @@ export function createRequestAndResponse(reqOptions) { let done = toPromise(res); - return { req, res, done }; + // Get the response as text + const text = async () => { + let chunks = await done; + return buffersToString(chunks); + }; + + return { req, res, done, text }; } export function toPromise(res) { @@ -48,3 +54,12 @@ export function toPromise(res) { }); }); } + +export function buffersToString(buffers) { + let decoder = new TextDecoder(); + let str = ''; + for (const buffer of buffers) { + str += decoder.decode(buffer); + } + return str; +} -- cgit v1.2.3 From ddeb8974231eff0589e5a3c843a84b93cac67677 Mon Sep 17 00:00:00 2001 From: Josh Goldberg ✨ Date: Mon, 3 Jul 2023 05:59:43 -0700 Subject: feat: use typescript-eslint@v6's reworked configs (#7425) --- packages/integrations/node/src/http-server.ts | 1 - packages/integrations/node/src/response-iterator.ts | 3 +-- packages/integrations/node/test/node-middleware.test.js | 2 +- packages/integrations/node/test/test-utils.js | 3 +-- 4 files changed, 3 insertions(+), 6 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/src/http-server.ts b/packages/integrations/node/src/http-server.ts index 8d463ba6f..2b0252c6e 100644 --- a/packages/integrations/node/src/http-server.ts +++ b/packages/integrations/node/src/http-server.ts @@ -46,7 +46,6 @@ export function createServer( stream.on('error', (err) => { if (forwardError) { - // eslint-disable-next-line no-console console.error(err.toString()); res.writeHead(500); res.end('Internal server error'); diff --git a/packages/integrations/node/src/response-iterator.ts b/packages/integrations/node/src/response-iterator.ts index f8941460b..01624d81d 100644 --- a/packages/integrations/node/src/response-iterator.ts +++ b/packages/integrations/node/src/response-iterator.ts @@ -29,8 +29,7 @@ const canUseAsyncIteratorSymbol = canUseSymbol && Symbol.asyncIterator; function isBuffer(value: any): value is Buffer { return ( - value != null && - value.constructor != null && + value?.constructor != null && typeof value.constructor.isBuffer === 'function' && value.constructor.isBuffer(value) ); diff --git a/packages/integrations/node/test/node-middleware.test.js b/packages/integrations/node/test/node-middleware.test.js index e96d29ed4..a658f93ef 100644 --- a/packages/integrations/node/test/node-middleware.test.js +++ b/packages/integrations/node/test/node-middleware.test.js @@ -1,5 +1,5 @@ import nodejs from '../dist/index.js'; -import { loadFixture, createRequestAndResponse } from './test-utils.js'; +import { loadFixture } from './test-utils.js'; import { expect } from 'chai'; import * as cheerio from 'cheerio'; diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 13e32a5e8..741564914 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -7,8 +7,7 @@ import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.j */ export function loadFixture(inlineConfig) { - if (!inlineConfig || !inlineConfig.root) - throw new Error("Must provide { root: './fixtures/...' }"); + if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }"); // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test` -- cgit v1.2.3 From 1320600592a8997b905fb12e8e5a7fca207ecd02 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Tue, 18 Jul 2023 02:17:59 +0200 Subject: nit: use `node:` prefix everywhere (#7692) * nit: use `node:` prefix everywhere * nit: fs/promises too * test: workaround issue in node builtin detection --- packages/integrations/node/README.md | 2 +- packages/integrations/node/src/createOutgoingHttpHeaders.ts | 2 +- packages/integrations/node/src/http-server.ts | 6 +++--- packages/integrations/node/src/nodeMiddleware.ts | 2 +- packages/integrations/node/src/preview.ts | 4 ++-- packages/integrations/node/src/standalone.ts | 4 ++-- packages/integrations/node/test/test-utils.js | 2 +- packages/integrations/node/test/url-protocol.test.js | 6 +++--- 8 files changed, 14 insertions(+), 14 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/README.md b/packages/integrations/node/README.md index 4c2f8a389..e2b78038f 100644 --- a/packages/integrations/node/README.md +++ b/packages/integrations/node/README.md @@ -108,7 +108,7 @@ Or, with Fastify (>4): import Fastify from 'fastify'; import fastifyMiddie from '@fastify/middie'; import fastifyStatic from '@fastify/static'; -import { fileURLToPath } from 'url'; +import { fileURLToPath } from 'node:url'; import { handler as ssrHandler } from './dist/server/entry.mjs'; const app = Fastify({ logger: true }); diff --git a/packages/integrations/node/src/createOutgoingHttpHeaders.ts b/packages/integrations/node/src/createOutgoingHttpHeaders.ts index 80269489e..e6c0c0ba4 100644 --- a/packages/integrations/node/src/createOutgoingHttpHeaders.ts +++ b/packages/integrations/node/src/createOutgoingHttpHeaders.ts @@ -1,4 +1,4 @@ -import type { OutgoingHttpHeaders } from 'http'; +import type { OutgoingHttpHeaders } from 'node:http'; /** * Takes in a nullable WebAPI Headers object and produces a NodeJS OutgoingHttpHeaders object suitable for usage diff --git a/packages/integrations/node/src/http-server.ts b/packages/integrations/node/src/http-server.ts index 2b0252c6e..2f2339cdf 100644 --- a/packages/integrations/node/src/http-server.ts +++ b/packages/integrations/node/src/http-server.ts @@ -1,9 +1,9 @@ -import fs from 'fs'; -import http from 'http'; import https from 'https'; +import fs from 'node:fs'; +import http from 'node:http'; +import { fileURLToPath } from 'node:url'; import send from 'send'; import enableDestroy from 'server-destroy'; -import { fileURLToPath } from 'url'; interface CreateServerOptions { client: URL; diff --git a/packages/integrations/node/src/nodeMiddleware.ts b/packages/integrations/node/src/nodeMiddleware.ts index 63ba246bd..4963afc9f 100644 --- a/packages/integrations/node/src/nodeMiddleware.ts +++ b/packages/integrations/node/src/nodeMiddleware.ts @@ -1,5 +1,5 @@ import type { NodeApp } from 'astro/app/node'; -import type { IncomingMessage, ServerResponse } from 'http'; +import type { IncomingMessage, ServerResponse } from 'node:http'; import type { Readable } from 'stream'; import { createOutgoingHttpHeaders } from './createOutgoingHttpHeaders'; import { responseIterator } from './response-iterator'; diff --git a/packages/integrations/node/src/preview.ts b/packages/integrations/node/src/preview.ts index 86e37f0d1..92f9b86ba 100644 --- a/packages/integrations/node/src/preview.ts +++ b/packages/integrations/node/src/preview.ts @@ -1,6 +1,6 @@ import type { CreatePreviewServer } from 'astro'; -import type http from 'http'; -import { fileURLToPath } from 'url'; +import type http from 'node:http'; +import { fileURLToPath } from 'node:url'; import { createServer } from './http-server.js'; import type { createExports } from './server'; diff --git a/packages/integrations/node/src/standalone.ts b/packages/integrations/node/src/standalone.ts index 85eb3822a..68b2cebcd 100644 --- a/packages/integrations/node/src/standalone.ts +++ b/packages/integrations/node/src/standalone.ts @@ -1,7 +1,7 @@ import type { NodeApp } from 'astro/app/node'; import https from 'https'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { createServer } from './http-server.js'; import middleware from './nodeMiddleware.js'; import type { Options } from './types'; diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 741564914..70ceaed25 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -1,5 +1,5 @@ -import { EventEmitter } from 'events'; import httpMocks from 'node-mocks-http'; +import { EventEmitter } from 'node:events'; import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; /** diff --git a/packages/integrations/node/test/url-protocol.test.js b/packages/integrations/node/test/url-protocol.test.js index 0da4bdeb0..a83cb2a41 100644 --- a/packages/integrations/node/test/url-protocol.test.js +++ b/packages/integrations/node/test/url-protocol.test.js @@ -1,7 +1,7 @@ -import { TLSSocket } from 'tls'; -import nodejs from '../dist/index.js'; -import { loadFixture, createRequestAndResponse } from './test-utils.js'; import { expect } from 'chai'; +import { TLSSocket } from 'node:tls'; +import nodejs from '../dist/index.js'; +import { createRequestAndResponse, loadFixture } from './test-utils.js'; describe('URL protocol', () => { /** @type {import('./test-utils').Fixture} */ -- cgit v1.2.3 From f61248895b8b7007c1fd5dccbd550f2d123f2da2 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 17 Jan 2024 13:10:43 +0000 Subject: Adapter enhancements (#9661) * quality of life updates for `App` (#9579) * feat(app): writeResponse for node-based adapters * add changeset * Apply suggestions from code review Co-authored-by: Emanuele Stoppa * Apply suggestions from code review Co-authored-by: Emanuele Stoppa * add examples for NodeApp static methods * unexpose createOutgoingHttpHeaders from public api * move headers test to core * clientAddress test * cookies test * destructure renderOptions right at the start --------- Co-authored-by: Emanuele Stoppa * Fallback node standalone to localhost (#9545) * Fallback node standalone to localhost * Update .changeset/tame-squids-film.md * quality of life updates for the node adapter (#9582) * descriptive names for files and functions * update tests * add changeset * appease linter * Apply suggestions from code review Co-authored-by: Nate Moore * `server-entrypoint.js` -> `server.js` * prevent crash on stream error (from PR 9533) * Apply suggestions from code review Co-authored-by: Luiz Ferraz * `127.0.0.1` -> `localhost` * add changeset for fryuni's fix * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Emanuele Stoppa --------- Co-authored-by: Nate Moore Co-authored-by: Luiz Ferraz Co-authored-by: Emanuele Stoppa * chore(vercel): delete request response conversion logic (#9583) * refactor * add changeset * bump peer dependencies * unexpose symbols (#9683) * Update .changeset/tame-squids-film.md Co-authored-by: Sarah Rainsberger --------- Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com> Co-authored-by: Bjorn Lu Co-authored-by: Nate Moore Co-authored-by: Luiz Ferraz Co-authored-by: Sarah Rainsberger --- packages/integrations/node/package.json | 2 +- .../node/src/createOutgoingHttpHeaders.ts | 34 ------ .../integrations/node/src/get-network-address.ts | 48 -------- packages/integrations/node/src/http-server.ts | 131 --------------------- packages/integrations/node/src/index.ts | 3 +- packages/integrations/node/src/log-listening-on.ts | 84 +++++++++++++ packages/integrations/node/src/middleware.ts | 43 +++++++ packages/integrations/node/src/nodeMiddleware.ts | 110 ----------------- packages/integrations/node/src/preview.ts | 73 ++++-------- packages/integrations/node/src/serve-app.ts | 27 +++++ packages/integrations/node/src/serve-static.ts | 86 ++++++++++++++ packages/integrations/node/src/server.ts | 10 +- packages/integrations/node/src/standalone.ts | 129 +++++++++++--------- packages/integrations/node/src/types.ts | 13 +- packages/integrations/node/test/bad-urls.test.js | 4 +- .../node/test/createOutgoingHttpHeaders.test.js | 76 ------------ .../integrations/node/test/node-middleware.test.js | 2 - .../node/test/prerender-404-500.test.js | 4 - packages/integrations/node/test/prerender.test.js | 4 - packages/integrations/node/test/test-utils.js | 2 + 20 files changed, 357 insertions(+), 528 deletions(-) delete mode 100644 packages/integrations/node/src/createOutgoingHttpHeaders.ts delete mode 100644 packages/integrations/node/src/get-network-address.ts delete mode 100644 packages/integrations/node/src/http-server.ts create mode 100644 packages/integrations/node/src/log-listening-on.ts create mode 100644 packages/integrations/node/src/middleware.ts delete mode 100644 packages/integrations/node/src/nodeMiddleware.ts create mode 100644 packages/integrations/node/src/serve-app.ts create mode 100644 packages/integrations/node/src/serve-static.ts delete mode 100644 packages/integrations/node/test/createOutgoingHttpHeaders.test.js (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index c3d952840..347eba0d6 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -37,7 +37,7 @@ "server-destroy": "^1.0.1" }, "peerDependencies": { - "astro": "^4.0.0" + "astro": "^4.2.0" }, "devDependencies": { "@types/node": "^18.17.8", diff --git a/packages/integrations/node/src/createOutgoingHttpHeaders.ts b/packages/integrations/node/src/createOutgoingHttpHeaders.ts deleted file mode 100644 index 44bbf81ca..000000000 --- a/packages/integrations/node/src/createOutgoingHttpHeaders.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { OutgoingHttpHeaders } from 'node:http'; - -/** - * Takes in a nullable WebAPI Headers object and produces a NodeJS OutgoingHttpHeaders object suitable for usage - * with ServerResponse.writeHead(..) or ServerResponse.setHeader(..) - * - * @param webHeaders WebAPI Headers object - * @returns NodeJS OutgoingHttpHeaders object with multiple set-cookie handled as an array of values - */ -export const createOutgoingHttpHeaders = ( - headers: Headers | undefined | null -): OutgoingHttpHeaders | undefined => { - if (!headers) { - return undefined; - } - - // at this point, a multi-value'd set-cookie header is invalid (it was concatenated as a single CSV, which is not valid for set-cookie) - const nodeHeaders: OutgoingHttpHeaders = Object.fromEntries(headers.entries()); - - if (Object.keys(nodeHeaders).length === 0) { - return undefined; - } - - // if there is > 1 set-cookie header, we have to fix it to be an array of values - if (headers.has('set-cookie')) { - const cookieHeaders = headers.getSetCookie(); - if (cookieHeaders.length > 1) { - // the Headers.entries() API already normalized all header names to lower case so we can safely index this as 'set-cookie' - nodeHeaders['set-cookie'] = cookieHeaders; - } - } - - return nodeHeaders; -}; diff --git a/packages/integrations/node/src/get-network-address.ts b/packages/integrations/node/src/get-network-address.ts deleted file mode 100644 index 3834c7617..000000000 --- a/packages/integrations/node/src/get-network-address.ts +++ /dev/null @@ -1,48 +0,0 @@ -import os from 'os'; -interface NetworkAddressOpt { - local: string[]; - network: string[]; -} - -const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000']); -type Protocol = 'http' | 'https'; - -// this code from vite https://github.com/vitejs/vite/blob/d09bbd093a4b893e78f0bbff5b17c7cf7821f403/packages/vite/src/node/utils.ts#L892-L914 -export function getNetworkAddress( - protocol: Protocol = 'http', - hostname: string | undefined, - port: number, - base?: string -) { - const NetworkAddress: NetworkAddressOpt = { - local: [], - network: [], - }; - Object.values(os.networkInterfaces()) - .flatMap((nInterface) => nInterface ?? []) - .filter( - (detail) => - detail && - detail.address && - (detail.family === 'IPv4' || - // @ts-expect-error Node 18.0 - 18.3 returns number - detail.family === 4) - ) - .forEach((detail) => { - let host = detail.address.replace( - '127.0.0.1', - hostname === undefined || wildcardHosts.has(hostname) ? 'localhost' : hostname - ); - // ipv6 host - if (host.includes(':')) { - host = `[${host}]`; - } - const url = `${protocol}://${host}:${port}${base ? base : ''}`; - if (detail.address.includes('127.0.0.1')) { - NetworkAddress.local.push(url); - } else { - NetworkAddress.network.push(url); - } - }); - return NetworkAddress; -} diff --git a/packages/integrations/node/src/http-server.ts b/packages/integrations/node/src/http-server.ts deleted file mode 100644 index 904937601..000000000 --- a/packages/integrations/node/src/http-server.ts +++ /dev/null @@ -1,131 +0,0 @@ -import https from 'https'; -import fs from 'node:fs'; -import http from 'node:http'; -import { fileURLToPath } from 'node:url'; -import send from 'send'; -import enableDestroy from 'server-destroy'; - -interface CreateServerOptions { - client: URL; - port: number; - host: string | undefined; - removeBase: (pathname: string) => string; - assets: string; -} - -function parsePathname(pathname: string, host: string | undefined, port: number) { - try { - const urlPathname = new URL(pathname, `http://${host}:${port}`).pathname; - return decodeURI(encodeURI(urlPathname)); - } catch (err) { - return undefined; - } -} - -export function createServer( - { client, port, host, removeBase, assets }: CreateServerOptions, - handler: http.RequestListener -) { - // The `base` is removed before passed to this function, so we don't - // need to check for it here. - const assetsPrefix = `/${assets}/`; - function isImmutableAsset(pathname: string) { - return pathname.startsWith(assetsPrefix); - } - - const listener: http.RequestListener = (req, res) => { - if (req.url) { - let pathname: string | undefined = removeBase(req.url); - pathname = pathname[0] === '/' ? pathname : '/' + pathname; - const encodedURI = parsePathname(pathname, host, port); - - if (!encodedURI) { - res.writeHead(400); - res.end('Bad request.'); - return res; - } - - const stream = send(req, encodedURI, { - root: fileURLToPath(client), - dotfiles: pathname.startsWith('/.well-known/') ? 'allow' : 'deny', - }); - - let forwardError = false; - - stream.on('error', (err) => { - if (forwardError) { - console.error(err.toString()); - res.writeHead(500); - res.end('Internal server error'); - return; - } - // File not found, forward to the SSR handler - handler(req, res); - }); - stream.on('headers', (_res: http.ServerResponse) => { - if (isImmutableAsset(encodedURI)) { - // Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable - _res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); - } - }); - stream.on('directory', () => { - // On directory find, redirect to the trailing slash - let location: string; - if (req.url!.includes('?')) { - const [url = '', search] = req.url!.split('?'); - location = `${url}/?${search}`; - } else { - location = req.url + '/'; - } - - res.statusCode = 301; - res.setHeader('Location', location); - res.end(location); - }); - stream.on('file', () => { - forwardError = true; - }); - stream.pipe(res); - } else { - handler(req, res); - } - }; - - let httpServer: - | http.Server - | https.Server; - - if (process.env.SERVER_CERT_PATH && process.env.SERVER_KEY_PATH) { - httpServer = https.createServer( - { - key: fs.readFileSync(process.env.SERVER_KEY_PATH), - cert: fs.readFileSync(process.env.SERVER_CERT_PATH), - }, - listener - ); - } else { - httpServer = http.createServer(listener); - } - httpServer.listen(port, host); - enableDestroy(httpServer); - - // Resolves once the server is closed - const closed = new Promise((resolve, reject) => { - httpServer.addListener('close', resolve); - httpServer.addListener('error', reject); - }); - - return { - host, - port, - closed() { - return closed; - }, - server: httpServer, - stop: async () => { - await new Promise((resolve, reject) => { - httpServer.destroy((err) => (err ? reject(err) : resolve(undefined))); - }); - }, - }; -} diff --git a/packages/integrations/node/src/index.ts b/packages/integrations/node/src/index.ts index bac5c25ef..e7d655403 100644 --- a/packages/integrations/node/src/index.ts +++ b/packages/integrations/node/src/index.ts @@ -1,6 +1,7 @@ -import type { AstroAdapter, AstroIntegration } from 'astro'; import { AstroError } from 'astro/errors'; +import type { AstroAdapter, AstroIntegration } from 'astro'; import type { Options, UserOptions } from './types.js'; + export function getAdapter(options: Options): AstroAdapter { return { name: '@astrojs/node', diff --git a/packages/integrations/node/src/log-listening-on.ts b/packages/integrations/node/src/log-listening-on.ts new file mode 100644 index 000000000..4f56b3ee8 --- /dev/null +++ b/packages/integrations/node/src/log-listening-on.ts @@ -0,0 +1,84 @@ +import os from "node:os"; +import type http from "node:http"; +import https from "node:https"; +import type { AstroIntegrationLogger } from "astro"; +import type { Options } from './types.js'; +import type { AddressInfo } from "node:net"; + +export async function logListeningOn(logger: AstroIntegrationLogger, server: http.Server | https.Server, options: Pick) { + await new Promise(resolve => server.once('listening', resolve)) + const protocol = server instanceof https.Server ? 'https' : 'http'; + // Allow to provide host value at runtime + const host = getResolvedHostForHttpServer( + process.env.HOST !== undefined && process.env.HOST !== '' ? process.env.HOST : options.host + ); + const { port } = server.address() as AddressInfo; + const address = getNetworkAddress(protocol, host, port); + + if (host === undefined) { + logger.info( + `Server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n` + ); + } else { + logger.info(`Server listening on ${address.local[0]}`); + } +} + +function getResolvedHostForHttpServer(host: string | boolean) { + if (host === false) { + // Use a secure default + return 'localhost'; + } else if (host === true) { + // If passed --host in the CLI without arguments + return undefined; // undefined typically means 0.0.0.0 or :: (listen on all IPs) + } else { + return host; + } +} + +interface NetworkAddressOpt { + local: string[]; + network: string[]; +} + +const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000']); + +// this code from vite https://github.com/vitejs/vite/blob/d09bbd093a4b893e78f0bbff5b17c7cf7821f403/packages/vite/src/node/utils.ts#L892-L914 +export function getNetworkAddress( + protocol: 'http' | 'https' = 'http', + hostname: string | undefined, + port: number, + base?: string +) { + const NetworkAddress: NetworkAddressOpt = { + local: [], + network: [], + }; + Object.values(os.networkInterfaces()) + .flatMap((nInterface) => nInterface ?? []) + .filter( + (detail) => + detail && + detail.address && + (detail.family === 'IPv4' || + // @ts-expect-error Node 18.0 - 18.3 returns number + detail.family === 4) + ) + .forEach((detail) => { + let host = detail.address.replace( + '127.0.0.1', + hostname === undefined || wildcardHosts.has(hostname) ? 'localhost' : hostname + ); + // ipv6 host + if (host.includes(':')) { + host = `[${host}]`; + } + const url = `${protocol}://${host}:${port}${base ? base : ''}`; + if (detail.address.includes('127.0.0.1')) { + NetworkAddress.local.push(url); + } else { + NetworkAddress.network.push(url); + } + }); + return NetworkAddress; +} diff --git a/packages/integrations/node/src/middleware.ts b/packages/integrations/node/src/middleware.ts new file mode 100644 index 000000000..a936dc5bc --- /dev/null +++ b/packages/integrations/node/src/middleware.ts @@ -0,0 +1,43 @@ +import { createAppHandler } from './serve-app.js'; +import type { RequestHandler } from "./types.js"; +import type { NodeApp } from "astro/app/node"; + +/** + * Creates a middleware that can be used with Express, Connect, etc. + * + * Similar to `createAppHandler` but can additionally be placed in the express + * chain as an error middleware. + * + * https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling + */ +export default function createMiddleware( + app: NodeApp, +): RequestHandler { + const handler = createAppHandler(app) + const logger = app.getAdapterLogger() + // using spread args because express trips up if the function's + // stringified body includes req, res, next, locals directly + return async function (...args) { + // assume normal invocation at first + const [req, res, next, locals] = args; + // short circuit if it is an error invocation + if (req instanceof Error) { + const error = req; + if (next) { + return next(error); + } else { + throw error; + } + } + try { + await handler(req, res, next, locals); + } catch (err) { + logger.error(`Could not render ${req.url}`); + console.error(err); + if (!res.headersSent) { + res.writeHead(500, `Server error`); + res.end(); + } + } + } +} diff --git a/packages/integrations/node/src/nodeMiddleware.ts b/packages/integrations/node/src/nodeMiddleware.ts deleted file mode 100644 index a13cc5da3..000000000 --- a/packages/integrations/node/src/nodeMiddleware.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { NodeApp } from 'astro/app/node'; -import type { ServerResponse } from 'node:http'; -import { createOutgoingHttpHeaders } from './createOutgoingHttpHeaders.js'; -import type { ErrorHandlerParams, Options, RequestHandlerParams } from './types.js'; -import type { AstroIntegrationLogger } from 'astro'; - -// Disable no-unused-vars to avoid breaking signature change -export default function (app: NodeApp, mode: Options['mode']) { - return async function (...args: RequestHandlerParams | ErrorHandlerParams) { - let error = null; - let locals; - let [req, res, next] = args as RequestHandlerParams; - if (mode === 'middleware') { - let { [3]: _locals } = args; - locals = _locals; - } - - if (args[0] instanceof Error) { - [error, req, res, next] = args as ErrorHandlerParams; - if (mode === 'middleware') { - let { [4]: _locals } = args as ErrorHandlerParams; - locals = _locals; - } - if (error) { - if (next) { - return next(error); - } else { - throw error; - } - } - } - - const logger = app.getAdapterLogger(); - - try { - const routeData = app.match(req); - if (routeData) { - try { - const response = await app.render(req, { routeData, locals }); - await writeWebResponse(app, res, response, logger); - } catch (err: unknown) { - if (next) { - next(err); - } else { - throw err; - } - } - } else if (next) { - return next(); - } else { - const response = await app.render(req); - await writeWebResponse(app, res, response, logger); - } - } catch (err: unknown) { - logger.error(`Could not render ${req.url}`); - console.error(err); - if (!res.headersSent) { - res.writeHead(500, `Server error`); - res.end(); - } - } - }; -} - -async function writeWebResponse( - app: NodeApp, - res: ServerResponse, - webResponse: Response, - logger: AstroIntegrationLogger -) { - const { status, headers, body } = webResponse; - - if (app.setCookieHeaders) { - const setCookieHeaders: Array = Array.from(app.setCookieHeaders(webResponse)); - - if (setCookieHeaders.length) { - for (const setCookieHeader of setCookieHeaders) { - headers.append('set-cookie', setCookieHeader); - } - } - } - - const nodeHeaders = createOutgoingHttpHeaders(headers); - res.writeHead(status, nodeHeaders); - if (body) { - try { - const reader = body.getReader(); - res.on('close', () => { - // Cancelling the reader may reject not just because of - // an error in the ReadableStream's cancel callback, but - // also because of an error anywhere in the stream. - reader.cancel().catch((err) => { - logger.error( - `There was an uncaught error in the middle of the stream while rendering ${res.req.url}.` - ); - console.error(err); - }); - }); - let result = await reader.read(); - while (!result.done) { - res.write(result.value); - result = await reader.read(); - } - // the error will be logged by the "on end" callback above - } catch { - res.write('Internal server error'); - } - } - res.end(); -} diff --git a/packages/integrations/node/src/preview.ts b/packages/integrations/node/src/preview.ts index 89baa1897..26b91756c 100644 --- a/packages/integrations/node/src/preview.ts +++ b/packages/integrations/node/src/preview.ts @@ -1,26 +1,19 @@ -import type { CreatePreviewServer } from 'astro'; -import { AstroError } from 'astro/errors'; -import type http from 'node:http'; import { fileURLToPath } from 'node:url'; -import { getNetworkAddress } from './get-network-address.js'; -import { createServer } from './http-server.js'; +import { AstroError } from 'astro/errors'; +import { logListeningOn } from './log-listening-on.js'; +import { createServer } from './standalone.js'; +import type { CreatePreviewServer } from 'astro'; import type { createExports } from './server.js'; -const preview: CreatePreviewServer = async function ({ - client, - serverEntrypoint, - host, - port, - base, - logger, -}) { - type ServerModule = ReturnType; - type MaybeServerModule = Partial; +type ServerModule = ReturnType; +type MaybeServerModule = Partial; + +const createPreviewServer: CreatePreviewServer = async function (preview) { let ssrHandler: ServerModule['handler']; let options: ServerModule['options']; try { process.env.ASTRO_NODE_AUTOSTART = 'disabled'; - const ssrModule: MaybeServerModule = await import(serverEntrypoint.toString()); + const ssrModule: MaybeServerModule = await import(preview.serverEntrypoint.toString()); if (typeof ssrModule.handler === 'function') { ssrHandler = ssrModule.handler; options = ssrModule.options!; @@ -33,49 +26,23 @@ const preview: CreatePreviewServer = async function ({ if ((err as any).code === 'ERR_MODULE_NOT_FOUND') { throw new AstroError( `The server entrypoint ${fileURLToPath( - serverEntrypoint + preview.serverEntrypoint )} does not exist. Have you ran a build yet?` ); } else { throw err; } } - - const handler: http.RequestListener = (req, res) => { - ssrHandler(req, res); - }; - - const baseWithoutTrailingSlash: string = base.endsWith('/') - ? base.slice(0, base.length - 1) - : base; - function removeBase(pathname: string): string { - if (pathname.startsWith(base)) { - return pathname.slice(baseWithoutTrailingSlash.length); - } - return pathname; - } - - const server = createServer( - { - client, - port, - host, - removeBase, - assets: options.assets, - }, - handler - ); - const address = getNetworkAddress('http', host, port); - - if (host === undefined) { - logger.info( - `Preview server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n` - ); - } else { - logger.info(`Preview server listening on ${address.local[0]}`); - } - + const host = preview.host ?? "localhost" + const port = preview.port ?? 4321 + const server = createServer(ssrHandler, host, port); + logListeningOn(preview.logger, server.server, options) + await new Promise((resolve, reject) => { + server.server.once('listening', resolve); + server.server.once('error', reject); + server.server.listen(port, host); + }); return server; }; -export { preview as default }; +export { createPreviewServer as default } diff --git a/packages/integrations/node/src/serve-app.ts b/packages/integrations/node/src/serve-app.ts new file mode 100644 index 000000000..51ef31575 --- /dev/null +++ b/packages/integrations/node/src/serve-app.ts @@ -0,0 +1,27 @@ +import { NodeApp } from "astro/app/node" +import type { RequestHandler } from "./types.js"; + +/** + * Creates a Node.js http listener for on-demand rendered pages, compatible with http.createServer and Connect middleware. + * If the next callback is provided, it will be called if the request does not have a matching route. + * Intended to be used in both standalone and middleware mode. + */ +export function createAppHandler(app: NodeApp): RequestHandler { + return async (req, res, next, locals) => { + const request = NodeApp.createRequest(req); + const routeData = app.match(request); + if (routeData) { + const response = await app.render(request, { + addCookieHeader: true, + locals, + routeData, + }); + await NodeApp.writeResponse(response, res); + } else if (next) { + return next(); + } else { + const response = await app.render(req); + await NodeApp.writeResponse(response, res); + } + } +} diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts new file mode 100644 index 000000000..ee3bdaf79 --- /dev/null +++ b/packages/integrations/node/src/serve-static.ts @@ -0,0 +1,86 @@ +import path from "node:path"; +import url from "node:url"; +import send from "send"; +import type { IncomingMessage, ServerResponse } from "node:http"; +import type { Options } from "./types.js"; +import type { NodeApp } from "astro/app/node"; + +/** + * Creates a Node.js http listener for static files and prerendered pages. + * In standalone mode, the static handler is queried first for the static files. + * If one matching the request path is not found, it relegates to the SSR handler. + * Intended to be used only in the standalone mode. + */ +export function createStaticHandler(app: NodeApp, options: Options) { + const client = resolveClientDir(options); + /** + * @param ssr The SSR handler to be called if the static handler does not find a matching file. + */ + return (req: IncomingMessage, res: ServerResponse, ssr: () => unknown) => { + if (req.url) { + let pathname = app.removeBase(req.url); + pathname = decodeURI(new URL(pathname, 'http://host').pathname); + + const stream = send(req, pathname, { + root: client, + dotfiles: pathname.startsWith('/.well-known/') ? 'allow' : 'deny', + }); + + let forwardError = false; + + stream.on('error', (err) => { + if (forwardError) { + console.error(err.toString()); + res.writeHead(500); + res.end('Internal server error'); + return; + } + // File not found, forward to the SSR handler + ssr(); + }); + stream.on('headers', (_res: ServerResponse) => { + // assets in dist/_astro are hashed and should get the immutable header + if (pathname.startsWith(`/${options.assets}/`)) { + // This is the "far future" cache header, used for static files whose name includes their digest hash. + // 1 year (31,536,000 seconds) is convention. + // Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable + _res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); + } + }); + stream.on('directory', () => { + // On directory find, redirect to the trailing slash + let location: string; + if (req.url!.includes('?')) { + const [url1 = '', search] = req.url!.split('?'); + location = `${url1}/?${search}`; + } else { + location = appendForwardSlash(req.url!); + } + + res.statusCode = 301; + res.setHeader('Location', location); + res.end(location); + }); + stream.on('file', () => { + forwardError = true; + }); + stream.pipe(res); + } else { + ssr(); + } + }; +} + +function resolveClientDir(options: Options) { + const clientURLRaw = new URL(options.client); + const serverURLRaw = new URL(options.server); + const rel = path.relative(url.fileURLToPath(serverURLRaw), url.fileURLToPath(clientURLRaw)); + const serverEntryURL = new URL(import.meta.url); + const clientURL = new URL(appendForwardSlash(rel), serverEntryURL); + const client = url.fileURLToPath(clientURL); + return client; +} + +function appendForwardSlash(pth: string) { + return pth.endsWith('/') ? pth : pth + '/'; +} diff --git a/packages/integrations/node/src/server.ts b/packages/integrations/node/src/server.ts index 88bcd7d62..5c2577ff8 100644 --- a/packages/integrations/node/src/server.ts +++ b/packages/integrations/node/src/server.ts @@ -1,7 +1,8 @@ -import type { SSRManifest } from 'astro'; import { NodeApp, applyPolyfills } from 'astro/app/node'; -import middleware from './nodeMiddleware.js'; +import { createStandaloneHandler } from './standalone.js'; import startServer from './standalone.js'; +import createMiddleware from './middleware.js'; +import type { SSRManifest } from 'astro'; import type { Options } from './types.js'; applyPolyfills(); @@ -9,7 +10,10 @@ export function createExports(manifest: SSRManifest, options: Options) { const app = new NodeApp(manifest); return { options: options, - handler: middleware(app, options.mode), + handler: + options.mode === "middleware" + ? createMiddleware(app) + : createStandaloneHandler(app, options), startServer: () => startServer(app, options), }; } diff --git a/packages/integrations/node/src/standalone.ts b/packages/integrations/node/src/standalone.ts index e167e8ab6..fc1875e97 100644 --- a/packages/integrations/node/src/standalone.ts +++ b/packages/integrations/node/src/standalone.ts @@ -1,75 +1,90 @@ -import type { NodeApp } from 'astro/app/node'; +import http from 'node:http'; import https from 'https'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { getNetworkAddress } from './get-network-address.js'; -import { createServer } from './http-server.js'; -import middleware from './nodeMiddleware.js'; +import fs from 'node:fs'; +import enableDestroy from 'server-destroy'; +import { createAppHandler } from './serve-app.js'; +import { createStaticHandler } from './serve-static.js'; +import { logListeningOn } from './log-listening-on.js'; +import type { NodeApp } from 'astro/app/node'; import type { Options } from './types.js'; +import type { PreviewServer } from 'astro'; -function resolvePaths(options: Options) { - const clientURLRaw = new URL(options.client); - const serverURLRaw = new URL(options.server); - const rel = path.relative(fileURLToPath(serverURLRaw), fileURLToPath(clientURLRaw)); - - const serverEntryURL = new URL(import.meta.url); - const clientURL = new URL(appendForwardSlash(rel), serverEntryURL); - +export default function standalone(app: NodeApp, options: Options) { + const port = process.env.PORT ? Number(process.env.PORT) : options.port ?? 8080; + // Allow to provide host value at runtime + const hostOptions = typeof options.host === "boolean" ? "localhost" : options.host + const host = process.env.HOST ?? hostOptions; + const handler = createStandaloneHandler(app, options); + const server = createServer(handler, host, port); + server.server.listen(port, host) + if (process.env.ASTRO_NODE_LOGGING !== "disabled") { + logListeningOn(app.getAdapterLogger(), server.server, options) + } return { - client: clientURL, + server, + done: server.closed(), }; } -function appendForwardSlash(pth: string) { - return pth.endsWith('/') ? pth : pth + '/'; -} - -export function getResolvedHostForHttpServer(host: string | boolean) { - if (host === false) { - // Use a secure default - return '127.0.0.1'; - } else if (host === true) { - // If passed --host in the CLI without arguments - return undefined; // undefined typically means 0.0.0.0 or :: (listen on all IPs) - } else { - return host; +// also used by server entrypoint +export function createStandaloneHandler(app: NodeApp, options: Options) { + const appHandler = createAppHandler(app); + const staticHandler = createStaticHandler(app, options); + return (req: http.IncomingMessage, res: http.ServerResponse) => { + try { + // validate request path + decodeURI(req.url!); + } catch { + res.writeHead(400); + res.end('Bad request.'); + return; + } + staticHandler(req, res, () => appHandler(req, res)); } } -export default function startServer(app: NodeApp, options: Options) { - const logger = app.getAdapterLogger(); - const port = process.env.PORT ? Number(process.env.PORT) : options.port ?? 8080; - const { client } = resolvePaths(options); - const handler = middleware(app, options.mode); - - // Allow to provide host value at runtime - const host = getResolvedHostForHttpServer( - process.env.HOST !== undefined && process.env.HOST !== '' ? process.env.HOST : options.host - ); - const server = createServer( - { - client, - port, - host, - removeBase: app.removeBase.bind(app), - assets: options.assets, - }, - handler - ); - - const protocol = server.server instanceof https.Server ? 'https' : 'http'; - const address = getNetworkAddress(protocol, host, port); +// also used by preview entrypoint +export function createServer( + listener: http.RequestListener, + host: string, + port: number +) { + let httpServer: http.Server | https.Server; - if (host === undefined) { - logger.info( - `Server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n` + if (process.env.SERVER_CERT_PATH && process.env.SERVER_KEY_PATH) { + httpServer = https.createServer( + { + key: fs.readFileSync(process.env.SERVER_KEY_PATH), + cert: fs.readFileSync(process.env.SERVER_CERT_PATH), + }, + listener ); } else { - logger.info(`Server listening on ${address.local[0]}`); + httpServer = http.createServer(listener); } + enableDestroy(httpServer); + + // Resolves once the server is closed + const closed = new Promise((resolve, reject) => { + httpServer.addListener('close', resolve); + httpServer.addListener('error', reject); + }); + + const previewable = { + host, + port, + closed() { + return closed; + }, + async stop() { + await new Promise((resolve, reject) => { + httpServer.destroy((err) => (err ? reject(err) : resolve(undefined))); + }); + } + } satisfies PreviewServer; return { - server, - done: server.closed(), + server: httpServer, + ...previewable, }; } diff --git a/packages/integrations/node/src/types.ts b/packages/integrations/node/src/types.ts index 273b80529..9e4f4ce91 100644 --- a/packages/integrations/node/src/types.ts +++ b/packages/integrations/node/src/types.ts @@ -1,3 +1,4 @@ +import type { NodeApp } from 'astro/app/node'; import type { IncomingMessage, ServerResponse } from 'node:http'; export interface UserOptions { @@ -18,11 +19,19 @@ export interface Options extends UserOptions { assets: string; } +export interface CreateServerOptions { + app: NodeApp; + assets: string; + client: URL; + port: number; + host: string | undefined; + removeBase: (pathname: string) => string; +} + +export type RequestHandler = (...args: RequestHandlerParams) => void | Promise; export type RequestHandlerParams = [ req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void, locals?: object, ]; - -export type ErrorHandlerParams = [unknown, ...RequestHandlerParams]; diff --git a/packages/integrations/node/test/bad-urls.test.js b/packages/integrations/node/test/bad-urls.test.js index 894729e36..bfef81278 100644 --- a/packages/integrations/node/test/bad-urls.test.js +++ b/packages/integrations/node/test/bad-urls.test.js @@ -34,9 +34,9 @@ describe('Bad URLs', () => { for (const weirdUrl of weirdURLs) { const fetchResult = await fixture.fetch(weirdUrl); - expect([400, 500]).to.include( + expect([400, 404, 500]).to.include( fetchResult.status, - `${weirdUrl} returned something else than 400 or 500` + `${weirdUrl} returned something else than 400, 404, or 500` ); } const stillWork = await fixture.fetch('/'); diff --git a/packages/integrations/node/test/createOutgoingHttpHeaders.test.js b/packages/integrations/node/test/createOutgoingHttpHeaders.test.js deleted file mode 100644 index 2f7063b1c..000000000 --- a/packages/integrations/node/test/createOutgoingHttpHeaders.test.js +++ /dev/null @@ -1,76 +0,0 @@ -import { expect } from 'chai'; - -import { createOutgoingHttpHeaders } from '../dist/createOutgoingHttpHeaders.js'; - -describe('createOutgoingHttpHeaders', () => { - it('undefined input headers', async () => { - const result = createOutgoingHttpHeaders(undefined); - expect(result).to.equal(undefined); - }); - - it('null input headers', async () => { - const result = createOutgoingHttpHeaders(undefined); - expect(result).to.equal(undefined); - }); - - it('Empty Headers', async () => { - const headers = new Headers(); - const result = createOutgoingHttpHeaders(headers); - expect(result).to.equal(undefined); - }); - - it('Headers with single key', async () => { - const headers = new Headers(); - headers.append('x-test', 'hello world'); - const result = createOutgoingHttpHeaders(headers); - expect(result).to.deep.equal({ 'x-test': 'hello world' }); - }); - - it('Headers with multiple keys', async () => { - const headers = new Headers(); - headers.append('x-test1', 'hello'); - headers.append('x-test2', 'world'); - const result = createOutgoingHttpHeaders(headers); - expect(result).to.deep.equal({ 'x-test1': 'hello', 'x-test2': 'world' }); - }); - - it('Headers with multiple values (not set-cookie)', async () => { - const headers = new Headers(); - headers.append('x-test', 'hello'); - headers.append('x-test', 'world'); - const result = createOutgoingHttpHeaders(headers); - expect(result).to.deep.equal({ 'x-test': 'hello, world' }); - }); - - it('Headers with multiple values (set-cookie special case)', async () => { - const headers = new Headers(); - headers.append('set-cookie', 'hello'); - headers.append('set-cookie', 'world'); - const result = createOutgoingHttpHeaders(headers); - expect(result).to.deep.equal({ 'set-cookie': ['hello', 'world'] }); - }); - - it('Headers with multiple values (set-cookie case handling)', async () => { - const headers = new Headers(); - headers.append('Set-cookie', 'hello'); - headers.append('Set-Cookie', 'world'); - const result = createOutgoingHttpHeaders(headers); - expect(result).to.deep.equal({ 'set-cookie': ['hello', 'world'] }); - }); - - it('Headers with all use cases', async () => { - const headers = new Headers(); - headers.append('x-single', 'single'); - headers.append('x-triple', 'one'); - headers.append('x-triple', 'two'); - headers.append('x-triple', 'three'); - headers.append('Set-cookie', 'hello'); - headers.append('Set-Cookie', 'world'); - const result = createOutgoingHttpHeaders(headers); - expect(result).to.deep.equal({ - 'x-single': 'single', - 'x-triple': 'one, two, three', - 'set-cookie': ['hello', 'world'], - }); - }); -}); diff --git a/packages/integrations/node/test/node-middleware.test.js b/packages/integrations/node/test/node-middleware.test.js index 009f403c2..6b6785953 100644 --- a/packages/integrations/node/test/node-middleware.test.js +++ b/packages/integrations/node/test/node-middleware.test.js @@ -21,7 +21,6 @@ describe('behavior from middleware, standalone', () => { let server; before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ root: './fixtures/node-middleware/', @@ -61,7 +60,6 @@ describe('behavior from middleware, middleware', () => { let server; before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ root: './fixtures/node-middleware/', diff --git a/packages/integrations/node/test/prerender-404-500.test.js b/packages/integrations/node/test/prerender-404-500.test.js index f8bf0778c..745a1958c 100644 --- a/packages/integrations/node/test/prerender-404-500.test.js +++ b/packages/integrations/node/test/prerender-404-500.test.js @@ -21,7 +21,6 @@ describe('Prerender 404', () => { describe('With base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = true; fixture = await loadFixture({ @@ -107,7 +106,6 @@ describe('Prerender 404', () => { describe('Without base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = true; fixture = await loadFixture({ @@ -171,7 +169,6 @@ describe('Hybrid 404', () => { describe('With base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ // inconsequential config that differs between tests @@ -229,7 +226,6 @@ describe('Hybrid 404', () => { describe('Without base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ // inconsequential config that differs between tests diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index 65e3b4cb2..0d87e7711 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -18,7 +18,6 @@ describe('Prerendering', () => { describe('With base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = true; fixture = await loadFixture({ @@ -86,7 +85,6 @@ describe('Prerendering', () => { describe('Without base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = true; fixture = await loadFixture({ @@ -151,7 +149,6 @@ describe('Hybrid rendering', () => { describe('With base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ base: '/some-base', @@ -217,7 +214,6 @@ describe('Hybrid rendering', () => { describe('Without base', async () => { before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.PRERENDER = false; fixture = await loadFixture({ root: './fixtures/prerender/', diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 70ceaed25..6c8c5d270 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -2,6 +2,8 @@ import httpMocks from 'node-mocks-http'; import { EventEmitter } from 'node:events'; import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; +process.env.ASTRO_NODE_AUTOSTART = "disabled"; +process.env.ASTRO_NODE_LOGGING = "disabled"; /** * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture */ -- cgit v1.2.3 From 73380661c2fece72694e10c49571a4a8e38c2b94 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 17 Jan 2024 13:11:46 +0000 Subject: [ci] format --- packages/integrations/node/src/log-listening-on.ts | 26 ++++++------ packages/integrations/node/src/middleware.ts | 46 +++++++++++----------- packages/integrations/node/src/preview.ts | 14 +++---- packages/integrations/node/src/serve-app.ts | 38 +++++++++--------- packages/integrations/node/src/serve-static.ts | 16 ++++---- packages/integrations/node/src/server.ts | 4 +- packages/integrations/node/src/standalone.ts | 20 ++++------ packages/integrations/node/test/test-utils.js | 4 +- 8 files changed, 82 insertions(+), 86 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/src/log-listening-on.ts b/packages/integrations/node/src/log-listening-on.ts index 4f56b3ee8..5ce93bfd7 100644 --- a/packages/integrations/node/src/log-listening-on.ts +++ b/packages/integrations/node/src/log-listening-on.ts @@ -1,20 +1,24 @@ -import os from "node:os"; -import type http from "node:http"; -import https from "node:https"; -import type { AstroIntegrationLogger } from "astro"; +import os from 'node:os'; +import type http from 'node:http'; +import https from 'node:https'; +import type { AstroIntegrationLogger } from 'astro'; import type { Options } from './types.js'; -import type { AddressInfo } from "node:net"; +import type { AddressInfo } from 'node:net'; -export async function logListeningOn(logger: AstroIntegrationLogger, server: http.Server | https.Server, options: Pick) { - await new Promise(resolve => server.once('listening', resolve)) - const protocol = server instanceof https.Server ? 'https' : 'http'; - // Allow to provide host value at runtime +export async function logListeningOn( + logger: AstroIntegrationLogger, + server: http.Server | https.Server, + options: Pick +) { + await new Promise((resolve) => server.once('listening', resolve)); + const protocol = server instanceof https.Server ? 'https' : 'http'; + // Allow to provide host value at runtime const host = getResolvedHostForHttpServer( process.env.HOST !== undefined && process.env.HOST !== '' ? process.env.HOST : options.host ); - const { port } = server.address() as AddressInfo; + const { port } = server.address() as AddressInfo; const address = getNetworkAddress(protocol, host, port); - + if (host === undefined) { logger.info( `Server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n` diff --git a/packages/integrations/node/src/middleware.ts b/packages/integrations/node/src/middleware.ts index a936dc5bc..6b6c2afee 100644 --- a/packages/integrations/node/src/middleware.ts +++ b/packages/integrations/node/src/middleware.ts @@ -1,34 +1,32 @@ import { createAppHandler } from './serve-app.js'; -import type { RequestHandler } from "./types.js"; -import type { NodeApp } from "astro/app/node"; +import type { RequestHandler } from './types.js'; +import type { NodeApp } from 'astro/app/node'; /** * Creates a middleware that can be used with Express, Connect, etc. - * + * * Similar to `createAppHandler` but can additionally be placed in the express * chain as an error middleware. - * + * * https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling */ -export default function createMiddleware( - app: NodeApp, -): RequestHandler { - const handler = createAppHandler(app) - const logger = app.getAdapterLogger() - // using spread args because express trips up if the function's - // stringified body includes req, res, next, locals directly - return async function (...args) { - // assume normal invocation at first - const [req, res, next, locals] = args; - // short circuit if it is an error invocation - if (req instanceof Error) { - const error = req; - if (next) { - return next(error); - } else { - throw error; - } - } +export default function createMiddleware(app: NodeApp): RequestHandler { + const handler = createAppHandler(app); + const logger = app.getAdapterLogger(); + // using spread args because express trips up if the function's + // stringified body includes req, res, next, locals directly + return async function (...args) { + // assume normal invocation at first + const [req, res, next, locals] = args; + // short circuit if it is an error invocation + if (req instanceof Error) { + const error = req; + if (next) { + return next(error); + } else { + throw error; + } + } try { await handler(req, res, next, locals); } catch (err) { @@ -39,5 +37,5 @@ export default function createMiddleware( res.end(); } } - } + }; } diff --git a/packages/integrations/node/src/preview.ts b/packages/integrations/node/src/preview.ts index 26b91756c..e8747ad0d 100644 --- a/packages/integrations/node/src/preview.ts +++ b/packages/integrations/node/src/preview.ts @@ -33,16 +33,16 @@ const createPreviewServer: CreatePreviewServer = async function (preview) { throw err; } } - const host = preview.host ?? "localhost" - const port = preview.port ?? 4321 + const host = preview.host ?? 'localhost'; + const port = preview.port ?? 4321; const server = createServer(ssrHandler, host, port); - logListeningOn(preview.logger, server.server, options) + logListeningOn(preview.logger, server.server, options); await new Promise((resolve, reject) => { - server.server.once('listening', resolve); - server.server.once('error', reject); - server.server.listen(port, host); + server.server.once('listening', resolve); + server.server.once('error', reject); + server.server.listen(port, host); }); return server; }; -export { createPreviewServer as default } +export { createPreviewServer as default }; diff --git a/packages/integrations/node/src/serve-app.ts b/packages/integrations/node/src/serve-app.ts index 51ef31575..f2fc61f01 100644 --- a/packages/integrations/node/src/serve-app.ts +++ b/packages/integrations/node/src/serve-app.ts @@ -1,5 +1,5 @@ -import { NodeApp } from "astro/app/node" -import type { RequestHandler } from "./types.js"; +import { NodeApp } from 'astro/app/node'; +import type { RequestHandler } from './types.js'; /** * Creates a Node.js http listener for on-demand rendered pages, compatible with http.createServer and Connect middleware. @@ -7,21 +7,21 @@ import type { RequestHandler } from "./types.js"; * Intended to be used in both standalone and middleware mode. */ export function createAppHandler(app: NodeApp): RequestHandler { - return async (req, res, next, locals) => { - const request = NodeApp.createRequest(req); - const routeData = app.match(request); - if (routeData) { - const response = await app.render(request, { - addCookieHeader: true, - locals, - routeData, - }); - await NodeApp.writeResponse(response, res); - } else if (next) { - return next(); - } else { - const response = await app.render(req); - await NodeApp.writeResponse(response, res); - } - } + return async (req, res, next, locals) => { + const request = NodeApp.createRequest(req); + const routeData = app.match(request); + if (routeData) { + const response = await app.render(request, { + addCookieHeader: true, + locals, + routeData, + }); + await NodeApp.writeResponse(response, res); + } else if (next) { + return next(); + } else { + const response = await app.render(req); + await NodeApp.writeResponse(response, res); + } + }; } diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts index ee3bdaf79..77de9b358 100644 --- a/packages/integrations/node/src/serve-static.ts +++ b/packages/integrations/node/src/serve-static.ts @@ -1,9 +1,9 @@ -import path from "node:path"; -import url from "node:url"; -import send from "send"; -import type { IncomingMessage, ServerResponse } from "node:http"; -import type { Options } from "./types.js"; -import type { NodeApp } from "astro/app/node"; +import path from 'node:path'; +import url from 'node:url'; +import send from 'send'; +import type { IncomingMessage, ServerResponse } from 'node:http'; +import type { Options } from './types.js'; +import type { NodeApp } from 'astro/app/node'; /** * Creates a Node.js http listener for static files and prerendered pages. @@ -16,7 +16,7 @@ export function createStaticHandler(app: NodeApp, options: Options) { /** * @param ssr The SSR handler to be called if the static handler does not find a matching file. */ - return (req: IncomingMessage, res: ServerResponse, ssr: () => unknown) => { + return (req: IncomingMessage, res: ServerResponse, ssr: () => unknown) => { if (req.url) { let pathname = app.removeBase(req.url); pathname = decodeURI(new URL(pathname, 'http://host').pathname); @@ -39,7 +39,7 @@ export function createStaticHandler(app: NodeApp, options: Options) { ssr(); }); stream.on('headers', (_res: ServerResponse) => { - // assets in dist/_astro are hashed and should get the immutable header + // assets in dist/_astro are hashed and should get the immutable header if (pathname.startsWith(`/${options.assets}/`)) { // This is the "far future" cache header, used for static files whose name includes their digest hash. // 1 year (31,536,000 seconds) is convention. diff --git a/packages/integrations/node/src/server.ts b/packages/integrations/node/src/server.ts index 5c2577ff8..d9f24cca5 100644 --- a/packages/integrations/node/src/server.ts +++ b/packages/integrations/node/src/server.ts @@ -11,9 +11,7 @@ export function createExports(manifest: SSRManifest, options: Options) { return { options: options, handler: - options.mode === "middleware" - ? createMiddleware(app) - : createStandaloneHandler(app, options), + options.mode === 'middleware' ? createMiddleware(app) : createStandaloneHandler(app, options), startServer: () => startServer(app, options), }; } diff --git a/packages/integrations/node/src/standalone.ts b/packages/integrations/node/src/standalone.ts index fc1875e97..9fa8bd88c 100644 --- a/packages/integrations/node/src/standalone.ts +++ b/packages/integrations/node/src/standalone.ts @@ -12,13 +12,13 @@ import type { PreviewServer } from 'astro'; export default function standalone(app: NodeApp, options: Options) { const port = process.env.PORT ? Number(process.env.PORT) : options.port ?? 8080; // Allow to provide host value at runtime - const hostOptions = typeof options.host === "boolean" ? "localhost" : options.host + const hostOptions = typeof options.host === 'boolean' ? 'localhost' : options.host; const host = process.env.HOST ?? hostOptions; const handler = createStandaloneHandler(app, options); const server = createServer(handler, host, port); - server.server.listen(port, host) - if (process.env.ASTRO_NODE_LOGGING !== "disabled") { - logListeningOn(app.getAdapterLogger(), server.server, options) + server.server.listen(port, host); + if (process.env.ASTRO_NODE_LOGGING !== 'disabled') { + logListeningOn(app.getAdapterLogger(), server.server, options); } return { server, @@ -40,15 +40,11 @@ export function createStandaloneHandler(app: NodeApp, options: Options) { return; } staticHandler(req, res, () => appHandler(req, res)); - } + }; } // also used by preview entrypoint -export function createServer( - listener: http.RequestListener, - host: string, - port: number -) { +export function createServer(listener: http.RequestListener, host: string, port: number) { let httpServer: http.Server | https.Server; if (process.env.SERVER_CERT_PATH && process.env.SERVER_KEY_PATH) { @@ -69,7 +65,7 @@ export function createServer( httpServer.addListener('close', resolve); httpServer.addListener('error', reject); }); - + const previewable = { host, port, @@ -80,7 +76,7 @@ export function createServer( await new Promise((resolve, reject) => { httpServer.destroy((err) => (err ? reject(err) : resolve(undefined))); }); - } + }, } satisfies PreviewServer; return { diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 6c8c5d270..8d830fee9 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -2,8 +2,8 @@ import httpMocks from 'node-mocks-http'; import { EventEmitter } from 'node:events'; import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; -process.env.ASTRO_NODE_AUTOSTART = "disabled"; -process.env.ASTRO_NODE_LOGGING = "disabled"; +process.env.ASTRO_NODE_AUTOSTART = 'disabled'; +process.env.ASTRO_NODE_LOGGING = 'disabled'; /** * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture */ -- cgit v1.2.3 From 4f010d71a1416b5ff5b956cd84bf7afe86bf0530 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 25 Jan 2024 16:17:31 +0000 Subject: chore(@astrojs/node): use Node.js for testing (#9758) * chore(@astrojs/node): use Node.js for testing * revert file * address feedback * feedback * Run tests in a single process (#9823) * Run tests in a single process * Make test less flaky * chore: remove module --------- Co-authored-by: Bjorn Lu --- packages/integrations/node/package.json | 4 +- packages/integrations/node/test/api-route.test.js | 13 ++-- packages/integrations/node/test/assets.test.js | 7 +- packages/integrations/node/test/bad-urls.test.js | 11 ++- packages/integrations/node/test/encoded.test.js | 7 +- packages/integrations/node/test/errors.test.js | 29 ++++---- packages/integrations/node/test/headers.test.js | 5 +- packages/integrations/node/test/image.test.js | 7 +- packages/integrations/node/test/locals.test.js | 11 +-- .../integrations/node/test/node-middleware.test.js | 14 ++-- .../node/test/prerender-404-500.test.js | 83 +++++++++++---------- packages/integrations/node/test/prerender.test.js | 85 ++++++++++++---------- packages/integrations/node/test/test-utils.js | 8 ++ .../integrations/node/test/url-protocol.test.js | 11 +-- .../node/test/well-known-locations.test.js | 9 ++- 15 files changed, 166 insertions(+), 138 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index d23d1f339..d1811ce6f 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -30,7 +30,7 @@ "build": "astro-scripts build \"src/**/*.ts\" && tsc", "build:ci": "astro-scripts build \"src/**/*.ts\"", "dev": "astro-scripts dev \"src/**/*.ts\"", - "test": "mocha --exit --timeout 20000 test/" + "test": "astro-scripts test \"test/**/*.test.js\"" }, "dependencies": { "send": "^0.18.0", @@ -45,10 +45,8 @@ "@types/server-destroy": "^1.0.3", "astro": "workspace:*", "astro-scripts": "workspace:*", - "chai": "^4.3.7", "cheerio": "1.0.0-rc.12", "express": "^4.18.2", - "mocha": "^10.2.0", "node-mocks-http": "^1.13.0" }, "publishConfig": { diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index 313819188..80e99a2cc 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -1,7 +1,8 @@ import nodejs from '../dist/index.js'; import { loadFixture, createRequestAndResponse } from './test-utils.js'; -import { expect } from 'chai'; import crypto from 'node:crypto'; +import { describe, it, before } from 'node:test'; +import * as assert from 'node:assert/strict'; describe('API routes', () => { /** @type {import('./test-utils').Fixture} */ @@ -33,9 +34,9 @@ describe('API routes', () => { let json = JSON.parse(buffer.toString('utf-8')); - expect(json.length).to.equal(1); + assert.equal(json.length, 1); - expect(json[0].name).to.equal('Broccoli Soup'); + assert.equal(json[0].name, 'Broccoli Soup'); }); it('Can get binary data', async () => { @@ -54,7 +55,7 @@ describe('API routes', () => { let [out] = await done; let arr = Array.from(new Uint8Array(out.buffer)); - expect(arr).to.deep.equal([5, 4, 3, 2, 1]); + assert.deepEqual(arr, [5, 4, 3, 2, 1]); }); it('Can post large binary data', async () => { @@ -87,7 +88,7 @@ describe('API routes', () => { }); let [out] = await done; - expect(new Uint8Array(out.buffer)).to.deep.equal(expectedDigest); + assert.deepEqual(new Uint8Array(out.buffer), new Uint8Array(expectedDigest)); }); it('Can bail on streaming', async () => { @@ -106,6 +107,6 @@ describe('API routes', () => { await done; - expect(locals).to.deep.include({ cancelledByTheServer: true }); + assert.deepEqual(locals, { cancelledByTheServer: true }); }); }); diff --git a/packages/integrations/node/test/assets.test.js b/packages/integrations/node/test/assets.test.js index 9e44ab31d..bcd9bb4bd 100644 --- a/packages/integrations/node/test/assets.test.js +++ b/packages/integrations/node/test/assets.test.js @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture } from './test-utils.js'; import * as cheerio from 'cheerio'; @@ -30,7 +31,7 @@ describe('Assets', () => { it('Assets within the _astro folder should be given immutable headers', async () => { let response = await fixture.fetch('/text-file'); let cacheControl = response.headers.get('cache-control'); - expect(cacheControl).to.equal(null); + assert.equal(cacheControl, null); const html = await response.text(); const $ = cheerio.load(html); @@ -38,6 +39,6 @@ describe('Assets', () => { const fileURL = $('a').attr('href'); response = await fixture.fetch(fileURL); cacheControl = response.headers.get('cache-control'); - expect(cacheControl).to.equal('public, max-age=31536000, immutable'); + assert.equal(cacheControl, 'public, max-age=31536000, immutable'); }); }); diff --git a/packages/integrations/node/test/bad-urls.test.js b/packages/integrations/node/test/bad-urls.test.js index bfef81278..6d6c0a2e9 100644 --- a/packages/integrations/node/test/bad-urls.test.js +++ b/packages/integrations/node/test/bad-urls.test.js @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture } from './test-utils.js'; @@ -32,15 +33,17 @@ describe('Bad URLs', () => { '%20foobar%', ]; + const statusCodes = [400, 404, 500]; for (const weirdUrl of weirdURLs) { const fetchResult = await fixture.fetch(weirdUrl); - expect([400, 404, 500]).to.include( - fetchResult.status, + assert.equal( + statusCodes.includes(fetchResult.status), + true, `${weirdUrl} returned something else than 400, 404, or 500` ); } const stillWork = await fixture.fetch('/'); const text = await stillWork.text(); - expect(text).to.equal('Hello!'); + assert.equal(text, 'Hello!'); }); }); diff --git a/packages/integrations/node/test/encoded.test.js b/packages/integrations/node/test/encoded.test.js index bbd264777..2739fcfb7 100644 --- a/packages/integrations/node/test/encoded.test.js +++ b/packages/integrations/node/test/encoded.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture, createRequestAndResponse } from './test-utils.js'; -import { expect } from 'chai'; describe('Encoded Pathname', () => { /** @type {import('./test-utils').Fixture} */ @@ -25,7 +26,7 @@ describe('Encoded Pathname', () => { req.send(); const html = await text(); - expect(html).to.include('什么'); + assert.equal(html.includes('什么'), true); }); it('Can get a Markdown file', async () => { @@ -39,6 +40,6 @@ describe('Encoded Pathname', () => { req.send(); const html = await text(); - expect(html).to.include('什么'); + assert.equal(html.includes('什么'), true); }); }); diff --git a/packages/integrations/node/test/errors.test.js b/packages/integrations/node/test/errors.test.js index 6bb93023a..983187475 100644 --- a/packages/integrations/node/test/errors.test.js +++ b/packages/integrations/node/test/errors.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture } from './test-utils.js'; -import { expect } from 'chai'; import * as cheerio from 'cheerio'; describe('Errors', () => { @@ -13,21 +14,19 @@ describe('Errors', () => { }); await fixture.build(); }); - describe('Within the stream', async () => { - let devPreview; + let devPreview; - before(async () => { - devPreview = await fixture.preview(); - }); - after(async () => { - await devPreview.stop(); - }); - it('when mode is standalone', async () => { - const res = await fixture.fetch('/in-stream'); - const html = await res.text(); - const $ = cheerio.load(html); + before(async () => { + devPreview = await fixture.preview(); + }); + after(async () => { + await devPreview.stop(); + }); + it('when mode is standalone', async () => { + const res = await fixture.fetch('/in-stream'); + const html = await res.text(); + const $ = cheerio.load(html); - expect($('p').text().trim()).to.equal('Internal server error'); - }); + assert.equal($('p').text().trim(), 'Internal server error'); }); }); diff --git a/packages/integrations/node/test/headers.test.js b/packages/integrations/node/test/headers.test.js index 17cfd3701..6a08dca22 100644 --- a/packages/integrations/node/test/headers.test.js +++ b/packages/integrations/node/test/headers.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture, createRequestAndResponse } from './test-utils.js'; -import { expect } from 'chai'; describe('Node Adapter Headers', () => { /** @type {import('./test-utils').Fixture} */ @@ -143,5 +144,5 @@ async function runTest(url, expectedHeaders) { await done; const headers = res.getHeaders(); - expect(headers).to.deep.equal(expectedHeaders); + assert.deepEqual(headers, expectedHeaders); } diff --git a/packages/integrations/node/test/image.test.js b/packages/integrations/node/test/image.test.js index 7bbdadc12..b315c1a30 100644 --- a/packages/integrations/node/test/image.test.js +++ b/packages/integrations/node/test/image.test.js @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture } from './test-utils.js'; @@ -24,12 +25,12 @@ describe.skip('Image endpoint', () => { it('it returns images', async () => { const res = await fixture.fetch('/'); - expect(res.status).to.equal(200); + assert.equal(res.status, 200); const resImage = await fixture.fetch( '/_image?href=/_astro/some_penguin.97ef5f92.png&w=50&f=webp' ); - expect(resImage.status).to.equal(200); + assert.equal(resImage.status, 200); }); }); diff --git a/packages/integrations/node/test/locals.test.js b/packages/integrations/node/test/locals.test.js index bba8b2d34..e2a531cce 100644 --- a/packages/integrations/node/test/locals.test.js +++ b/packages/integrations/node/test/locals.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture, createRequestAndResponse } from './test-utils.js'; -import { expect } from 'chai'; describe('API routes', () => { /** @type {import('./test-utils').Fixture} */ @@ -28,7 +29,7 @@ describe('API routes', () => { let html = await text(); - expect(html).to.contain('

bar

'); + assert.equal(html.includes('

bar

'), true); }); it('Throws an error when provided non-objects as locals', async () => { @@ -41,7 +42,7 @@ describe('API routes', () => { req.send(); await done; - expect(res).to.deep.include({ statusCode: 500 }); + assert.equal(res.statusCode, 500); }); it('Can use locals added by astro middleware', async () => { @@ -56,7 +57,7 @@ describe('API routes', () => { const html = await text(); - expect(html).to.contain('

baz

'); + assert.equal(html.includes('

baz

'), true); }); it('Can access locals in API', async () => { @@ -75,6 +76,6 @@ describe('API routes', () => { let json = JSON.parse(buffer.toString('utf-8')); - expect(json.foo).to.equal('bar'); + assert.equal(json.foo, 'bar'); }); }); diff --git a/packages/integrations/node/test/node-middleware.test.js b/packages/integrations/node/test/node-middleware.test.js index 6b6785953..889f72315 100644 --- a/packages/integrations/node/test/node-middleware.test.js +++ b/packages/integrations/node/test/node-middleware.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; -import { loadFixture } from './test-utils.js'; -import { expect } from 'chai'; +import { loadFixture, waitServerListen } from './test-utils.js'; import * as cheerio from 'cheerio'; import express from 'express'; @@ -31,6 +32,7 @@ describe('behavior from middleware, standalone', () => { const { startServer } = await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -43,13 +45,13 @@ describe('behavior from middleware, standalone', () => { it('when mode is standalone', async () => { const res = await fetch(`http://${server.host}:${server.port}/error-page`); - expect(res.status).to.equal(404); + assert.equal(res.status, 404); const html = await res.text(); const $ = cheerio.load(html); const body = $('body'); - expect(body.text()).to.equal('Page does not exist'); + assert.equal(body.text().includes('Page does not exist'), true); }); }); }); @@ -82,12 +84,12 @@ describe('behavior from middleware, middleware', () => { it('when mode is standalone', async () => { const res = await fetch(`http://localhost:8888/ssr`); - expect(res.status).to.equal(200); + assert.equal(res.status, 200); const html = await res.text(); const $ = cheerio.load(html); const body = $('body'); - expect(body.text()).to.contain("Here's a random number"); + assert.equal(body.text().includes("Here's a random number"), true); }); }); diff --git a/packages/integrations/node/test/prerender-404-500.test.js b/packages/integrations/node/test/prerender-404-500.test.js index 745a1958c..4195db0ec 100644 --- a/packages/integrations/node/test/prerender-404-500.test.js +++ b/packages/integrations/node/test/prerender-404-500.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; -import { loadFixture } from './test-utils.js'; -import { expect } from 'chai'; +import { loadFixture, waitServerListen } from './test-utils.js'; import * as cheerio from 'cheerio'; /** @@ -37,6 +38,7 @@ describe('Prerender 404', () => { const { startServer } = await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -50,8 +52,8 @@ describe('Prerender 404', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Hello world!'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Hello world!'); }); it('Can handle prerendered 404', async () => { @@ -60,20 +62,20 @@ describe('Prerender 404', () => { const res2 = await fetch(url); const res3 = await fetch(url); - expect(res1.status).to.equal(404); - expect(res2.status).to.equal(404); - expect(res3.status).to.equal(404); + assert.equal(res1.status, 404); + assert.equal(res2.status, 404); + assert.equal(res3.status, 404); const html1 = await res1.text(); const html2 = await res2.text(); const html3 = await res3.text(); - expect(html1).to.equal(html2); - expect(html2).to.equal(html3); + assert.equal(html1, html2); + assert.equal(html2, html3); const $ = cheerio.load(html1); - expect($('body').text()).to.equal('Page does not exist'); + assert.equal($('body').text(), 'Page does not exist'); }); it(' Can handle prerendered 500 called indirectly', async () => { @@ -82,16 +84,16 @@ describe('Prerender 404', () => { const response2 = await fetch(url); const response3 = await fetch(url); - expect(response1.status).to.equal(500); + assert.equal(response1.status, 500); const html1 = await response1.text(); const html2 = await response2.text(); const html3 = await response3.text(); - expect(html1).to.contain('Something went wrong'); + assert.equal(html1.includes('Something went wrong'), true); - expect(html1).to.equal(html2); - expect(html2).to.equal(html3); + assert.equal(html1, html2); + assert.equal(html2, html3); }); it('prerendered 500 page includes expected styles', async () => { @@ -100,7 +102,7 @@ describe('Prerender 404', () => { const $ = cheerio.load(html); // length will be 0 if the stylesheet does not get included - expect($('style')).to.have.a.lengthOf(1); + assert.equal($('style').length, 1); }); }); @@ -121,6 +123,7 @@ describe('Prerender 404', () => { const { startServer } = await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -134,8 +137,8 @@ describe('Prerender 404', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Hello world!'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Hello world!'); }); it('Can handle prerendered 404', async () => { @@ -144,20 +147,20 @@ describe('Prerender 404', () => { const res2 = await fetch(url); const res3 = await fetch(url); - expect(res1.status).to.equal(404); - expect(res2.status).to.equal(404); - expect(res3.status).to.equal(404); + assert.equal(res1.status, 404); + assert.equal(res2.status, 404); + assert.equal(res3.status, 404); const html1 = await res1.text(); const html2 = await res2.text(); const html3 = await res3.text(); - expect(html1).to.equal(html2); - expect(html2).to.equal(html3); + assert.equal(html1, html2); + assert.equal(html2, html3); const $ = cheerio.load(html1); - expect($('body').text()).to.equal('Page does not exist'); + assert.equal($('body').text(), 'Page does not exist'); }); }); }); @@ -184,6 +187,7 @@ describe('Hybrid 404', () => { const { startServer } = await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -197,8 +201,8 @@ describe('Hybrid 404', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Hello world!'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Hello world!'); }); it('Can handle prerendered 404', async () => { @@ -207,20 +211,20 @@ describe('Hybrid 404', () => { const res2 = await fetch(url); const res3 = await fetch(url); - expect(res1.status).to.equal(404); - expect(res2.status).to.equal(404); - expect(res3.status).to.equal(404); + assert.equal(res1.status, 404); + assert.equal(res2.status, 404); + assert.equal(res3.status, 404); const html1 = await res1.text(); const html2 = await res2.text(); const html3 = await res3.text(); - expect(html1).to.equal(html2); - expect(html2).to.equal(html3); + assert.equal(html1, html2); + assert.equal(html2, html3); const $ = cheerio.load(html1); - expect($('body').text()).to.equal('Page does not exist'); + assert.equal($('body').text(), 'Page does not exist'); }); }); @@ -240,6 +244,7 @@ describe('Hybrid 404', () => { const { startServer } = await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -253,8 +258,8 @@ describe('Hybrid 404', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Hello world!'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Hello world!'); }); it('Can handle prerendered 404', async () => { @@ -263,20 +268,20 @@ describe('Hybrid 404', () => { const res2 = await fetch(url); const res3 = await fetch(url); - expect(res1.status).to.equal(404); - expect(res2.status).to.equal(404); - expect(res3.status).to.equal(404); + assert.equal(res1.status, 404); + assert.equal(res2.status, 404); + assert.equal(res3.status, 404); const html1 = await res1.text(); const html2 = await res2.text(); const html3 = await res3.text(); - expect(html1).to.equal(html2); - expect(html2).to.equal(html3); + assert.equal(html1, html2); + assert.equal(html2, html3); const $ = cheerio.load(html1); - expect($('body').text()).to.equal('Page does not exist'); + assert.equal($('body').text(), 'Page does not exist'); }); }); }); diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index 86a7d3a65..ffa5563fa 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; -import { loadFixture } from './test-utils.js'; -import { expect } from 'chai'; +import { loadFixture, waitServerListen } from './test-utils.js'; import * as cheerio from 'cheerio'; /** @@ -30,6 +31,7 @@ describe('Prerendering', () => { const { startServer } = await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -43,8 +45,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); it('Can render prerendered route', async () => { @@ -52,8 +54,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); it('Can render prerendered route with redirect and query params', async () => { @@ -61,8 +63,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); it('Can render prerendered route with query params', async () => { @@ -70,8 +72,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); it('Can render prerendered route without trailing slash', async () => { @@ -80,8 +82,9 @@ describe('Prerendering', () => { }); const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 301); + assert.equal(res.headers.get('location'), '/some-base/two/'); + assert.equal($('h1').text(), "Two") }); }); @@ -98,6 +101,7 @@ describe('Prerendering', () => { const { startServer } = await await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -111,8 +115,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); it('Can render prerendered route', async () => { @@ -120,8 +124,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); it('Can render prerendered route with redirect and query params', async () => { @@ -129,8 +133,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); it('Can render prerendered route with query params', async () => { @@ -138,8 +142,8 @@ describe('Prerendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); }); @@ -200,6 +204,7 @@ describe('Hybrid rendering', () => { const { startServer } = await await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -212,8 +217,8 @@ describe('Hybrid rendering', () => { const res = await fetch(`http://${server.host}:${server.port}/some-base/two`); const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); it('Can render prerendered route', async () => { @@ -221,8 +226,8 @@ describe('Hybrid rendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); it('Can render prerendered route with redirect and query params', async () => { @@ -230,8 +235,8 @@ describe('Hybrid rendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); it('Can render prerendered route with query params', async () => { @@ -239,18 +244,17 @@ describe('Hybrid rendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); it('Can render prerendered route without trailing slash', async () => { const res = await fetch(`http://${server.host}:${server.port}/some-base/one`, { redirect: 'manual', }); - const html = await res.text(); - const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 301); + assert.equal(res.headers.get('location'), '/some-base/one/'); + assert.equal($('h1').text(), 'One'); }); }); @@ -266,6 +270,7 @@ describe('Hybrid rendering', () => { const { startServer } = await await load(); let res = startServer(); server = res.server; + await waitServerListen(server.server); }); after(async () => { @@ -279,8 +284,8 @@ describe('Hybrid rendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'Two'); }); it('Can render prerendered route', async () => { @@ -288,8 +293,8 @@ describe('Hybrid rendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); it('Can render prerendered route with redirect and query params', async () => { @@ -297,8 +302,8 @@ describe('Hybrid rendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); it('Can render prerendered route with query params', async () => { @@ -306,8 +311,8 @@ describe('Hybrid rendering', () => { const html = await res.text(); const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); + assert.equal(res.status, 200); + assert.equal($('h1').text(), 'One'); }); }); }); diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 8d830fee9..3d582f30b 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -64,3 +64,11 @@ export function buffersToString(buffers) { } return str; } + +export function waitServerListen(server) { + return new Promise((resolve) => { + server.on('listening', () => { + resolve(); + }); + }); +} diff --git a/packages/integrations/node/test/url-protocol.test.js b/packages/integrations/node/test/url-protocol.test.js index a83cb2a41..444d47ed5 100644 --- a/packages/integrations/node/test/url-protocol.test.js +++ b/packages/integrations/node/test/url-protocol.test.js @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import * as assert from 'node:assert/strict'; +import { describe, it, before } from 'node:test'; import { TLSSocket } from 'node:tls'; import nodejs from '../dist/index.js'; import { createRequestAndResponse, loadFixture } from './test-utils.js'; @@ -26,7 +27,7 @@ describe('URL protocol', () => { req.send(); const html = await text(); - expect(html).to.include('http:'); + assert.equal(html.includes('http:'), true); }); it('return https when secure', async () => { @@ -40,7 +41,7 @@ describe('URL protocol', () => { req.send(); const html = await text(); - expect(html).to.include('https:'); + assert.equal(html.includes('https:'), true); }); it('return http when the X-Forwarded-Proto header is set to http', async () => { @@ -54,7 +55,7 @@ describe('URL protocol', () => { req.send(); const html = await text(); - expect(html).to.include('http:'); + assert.equal(html.includes('http:'), true); }); it('return https when the X-Forwarded-Proto header is set to https', async () => { @@ -68,6 +69,6 @@ describe('URL protocol', () => { req.send(); const html = await text(); - expect(html).to.include('https:'); + assert.equal(html.includes('https:'), true); }); }); diff --git a/packages/integrations/node/test/well-known-locations.test.js b/packages/integrations/node/test/well-known-locations.test.js index 31f31bacd..934673cda 100644 --- a/packages/integrations/node/test/well-known-locations.test.js +++ b/packages/integrations/node/test/well-known-locations.test.js @@ -1,6 +1,7 @@ +import * as assert from 'node:assert/strict'; +import { describe, it, before, after } from 'node:test'; import nodejs from '../dist/index.js'; import { loadFixture } from './test-utils.js'; -import { expect } from 'chai'; describe('test URIs beginning with a dot', () => { /** @type {import('./test-utils').Fixture} */ @@ -29,17 +30,17 @@ describe('test URIs beginning with a dot', () => { it('can load a valid well-known URI', async () => { const res = await fixture.fetch('/.well-known/apple-app-site-association'); - expect(res.status).to.equal(200); + assert.equal(res.status, 200); const json = await res.json(); - expect(json).to.deep.equal({ applinks: {} }); + assert.notStrictEqual(json.applinks, {}); }); it('cannot load a dot folder that is not a well-known URI', async () => { const res = await fixture.fetch('/.hidden/file.json'); - expect(res.status).to.equal(404); + assert.equal(res.status, 404); }); }); }); -- cgit v1.2.3 From 37f8e3b901536bec47b0667d53edee33b6b2248f Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 22 Feb 2024 13:58:10 +0000 Subject: chore: import sorting for test folder and e2e folder (#10190) --- packages/integrations/node/test/test-utils.js | 2 +- packages/integrations/node/test/trailing-slash.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 3d582f30b..f27c3485b 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -1,5 +1,5 @@ -import httpMocks from 'node-mocks-http'; import { EventEmitter } from 'node:events'; +import httpMocks from 'node-mocks-http'; import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; process.env.ASTRO_NODE_AUTOSTART = 'disabled'; diff --git a/packages/integrations/node/test/trailing-slash.js b/packages/integrations/node/test/trailing-slash.js index c46945b5a..28bfe0f5d 100644 --- a/packages/integrations/node/test/trailing-slash.js +++ b/packages/integrations/node/test/trailing-slash.js @@ -1,7 +1,7 @@ -import nodejs from '../dist/index.js'; -import { loadFixture } from './test-utils.js'; import { expect } from 'chai'; import * as cheerio from 'cheerio'; +import nodejs from '../dist/index.js'; +import { loadFixture } from './test-utils.js'; /** * @typedef {import('../../../astro/test/test-utils').Fixture} Fixture -- cgit v1.2.3 From b6a572b75dccdc3aa6574ed05b6070f8c5f30558 Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:24:00 +0100 Subject: test(@astrojs/node) listen for server setup errors in test-utils (#10692) --- packages/integrations/node/test/test-utils.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index f27c3485b..10faba5cf 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -66,9 +66,16 @@ export function buffersToString(buffers) { } export function waitServerListen(server) { - return new Promise((resolve) => { - server.on('listening', () => { + return new Promise((resolve, reject) => { + function onListen() { + server.off('error', onError); resolve(); - }); + } + function onError(error) { + server.off('listening', onListen); + reject(error); + } + server.once('listening', onListen); + server.once('error', onError); }); } -- cgit v1.2.3 From 1b6f89c5b4b7a3e3c9cc0ca639e0a5dbfca01952 Mon Sep 17 00:00:00 2001 From: Alexander Niebuhr Date: Thu, 29 Aug 2024 08:17:42 +0200 Subject: chore: supress linting --- packages/integrations/node/CHANGELOG.md | 2 +- packages/integrations/node/package.json | 9 ++------- packages/integrations/node/src/index.ts | 2 +- packages/integrations/node/src/log-listening-on.ts | 17 +++++++++++------ packages/integrations/node/src/middleware.ts | 4 +++- packages/integrations/node/src/preview.ts | 10 ++++++---- packages/integrations/node/src/serve-app.ts | 2 +- packages/integrations/node/src/serve-static.ts | 12 +++++++++++- packages/integrations/node/src/standalone.ts | 3 ++- packages/integrations/node/test/api-route.test.js | 20 ++++++++++---------- packages/integrations/node/test/bad-urls.test.js | 2 +- packages/integrations/node/test/encoded.test.js | 4 ++-- packages/integrations/node/test/errors.test.js | 5 +++-- packages/integrations/node/test/headers.test.js | 2 +- packages/integrations/node/test/image.test.js | 2 +- packages/integrations/node/test/locals.test.js | 16 ++++++++-------- .../integrations/node/test/node-middleware.test.js | 5 ++++- .../node/test/prerender-404-500.test.js | 12 ++++++++---- packages/integrations/node/test/prerender.test.js | 21 +++++++++++++++------ packages/integrations/node/test/test-utils.js | 13 +++++++------ .../integrations/node/test/trailing-slash.test.js | 18 ++++++++++++------ packages/integrations/node/test/url.test.js | 12 ++++++------ packages/integrations/node/tsconfig.json | 4 +--- 23 files changed, 117 insertions(+), 80 deletions(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/CHANGELOG.md b/packages/integrations/node/CHANGELOG.md index 738cfc367..ed830f8de 100644 --- a/packages/integrations/node/CHANGELOG.md +++ b/packages/integrations/node/CHANGELOG.md @@ -734,7 +734,7 @@ { darkMode: true }, { expires: '1 month', - }, + } ); const prefs = Astro.cookies.get('prefs').json(); diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index f8042499b..2fc6c35b1 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -11,10 +11,7 @@ "url": "https://github.com/withastro/astro.git", "directory": "packages/integrations/node" }, - "keywords": [ - "withastro", - "astro-adapter" - ], + "keywords": ["withastro", "astro-adapter"], "bugs": "https://github.com/withastro/astro/issues", "homepage": "https://docs.astro.build/en/guides/integrations-guide/node/", "exports": { @@ -23,9 +20,7 @@ "./preview.js": "./dist/preview.js", "./package.json": "./package.json" }, - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build": "tsc", "test": "astro-scripts test \"test/**/*.test.js\"" diff --git a/packages/integrations/node/src/index.ts b/packages/integrations/node/src/index.ts index 36d9ee30f..eb3c98a9b 100644 --- a/packages/integrations/node/src/index.ts +++ b/packages/integrations/node/src/index.ts @@ -73,7 +73,7 @@ export default function createIntegration(userOptions: UserOptions): AstroIntegr if (config.output === 'static') { logger.warn( - `\`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`, + `\`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.` ); } }, diff --git a/packages/integrations/node/src/log-listening-on.ts b/packages/integrations/node/src/log-listening-on.ts index 7e299740c..2f774c31c 100644 --- a/packages/integrations/node/src/log-listening-on.ts +++ b/packages/integrations/node/src/log-listening-on.ts @@ -8,20 +8,20 @@ import type { Options } from './types.js'; export async function logListeningOn( logger: AstroIntegrationLogger, server: http.Server | https.Server, - options: Pick, + options: Pick ) { await new Promise((resolve) => server.once('listening', resolve)); const protocol = server instanceof https.Server ? 'https' : 'http'; // Allow to provide host value at runtime const host = getResolvedHostForHttpServer( - process.env.HOST !== undefined && process.env.HOST !== '' ? process.env.HOST : options.host, + process.env.HOST !== undefined && process.env.HOST !== '' ? process.env.HOST : options.host ); const { port } = server.address() as AddressInfo; const address = getNetworkAddress(protocol, host, port); if (host === undefined) { logger.info( - `Server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n`, + `Server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n` ); } else { logger.info(`Server listening on ${address.local[0]}`); @@ -32,9 +32,11 @@ function getResolvedHostForHttpServer(host: string | boolean) { if (host === false) { // Use a secure default return 'localhost'; + // biome-ignore lint/style/noUselessElse: } else if (host === true) { // If passed --host in the CLI without arguments return undefined; // undefined typically means 0.0.0.0 or :: (listen on all IPs) + // biome-ignore lint/style/noUselessElse: } else { return host; } @@ -49,29 +51,32 @@ const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0 // this code from vite https://github.com/vitejs/vite/blob/d09bbd093a4b893e78f0bbff5b17c7cf7821f403/packages/vite/src/node/utils.ts#L892-L914 export function getNetworkAddress( + // biome-ignore lint/style/useDefaultParameterLast: protocol: 'http' | 'https' = 'http', hostname: string | undefined, port: number, - base?: string, + base?: string ) { const NetworkAddress: NetworkAddressOpt = { local: [], network: [], }; + // biome-ignore lint/complexity/noForEach: Object.values(os.networkInterfaces()) .flatMap((nInterface) => nInterface ?? []) .filter( (detail) => + // biome-ignore lint/complexity/useOptionalChain: detail && detail.address && (detail.family === 'IPv4' || // @ts-expect-error Node 18.0 - 18.3 returns number - detail.family === 4), + detail.family === 4) ) .forEach((detail) => { let host = detail.address.replace( '127.0.0.1', - hostname === undefined || wildcardHosts.has(hostname) ? 'localhost' : hostname, + hostname === undefined || wildcardHosts.has(hostname) ? 'localhost' : hostname ); // ipv6 host if (host.includes(':')) { diff --git a/packages/integrations/node/src/middleware.ts b/packages/integrations/node/src/middleware.ts index 5cc4c4a46..5bb104914 100644 --- a/packages/integrations/node/src/middleware.ts +++ b/packages/integrations/node/src/middleware.ts @@ -15,7 +15,7 @@ export default function createMiddleware(app: NodeApp): RequestHandler { const logger = app.getAdapterLogger(); // using spread args because express trips up if the function's // stringified body includes req, res, next, locals directly - return async function (...args) { + return async (...args) => { // assume normal invocation at first const [req, res, next, locals] = args; // short circuit if it is an error invocation @@ -23,6 +23,7 @@ export default function createMiddleware(app: NodeApp): RequestHandler { const error = req; if (next) { return next(error); + // biome-ignore lint/style/noUselessElse: } else { throw error; } @@ -33,6 +34,7 @@ export default function createMiddleware(app: NodeApp): RequestHandler { logger.error(`Could not render ${req.url}`); console.error(err); if (!res.headersSent) { + // biome-ignore lint/style/noUnusedTemplateLiteral: res.writeHead(500, `Server error`); res.end(); } diff --git a/packages/integrations/node/src/preview.ts b/packages/integrations/node/src/preview.ts index 518155c4a..7e9415df8 100644 --- a/packages/integrations/node/src/preview.ts +++ b/packages/integrations/node/src/preview.ts @@ -8,7 +8,7 @@ import { createServer } from './standalone.js'; type ServerModule = ReturnType; type MaybeServerModule = Partial; -const createPreviewServer: CreatePreviewServer = async function (preview) { +const createPreviewServer: CreatePreviewServer = async (preview) => { let ssrHandler: ServerModule['handler']; let options: ServerModule['options']; try { @@ -16,19 +16,21 @@ const createPreviewServer: CreatePreviewServer = async function (preview) { const ssrModule: MaybeServerModule = await import(preview.serverEntrypoint.toString()); if (typeof ssrModule.handler === 'function') { ssrHandler = ssrModule.handler; + // biome-ignore lint/style/noNonNullAssertion: options = ssrModule.options!; } else { throw new AstroError( - `The server entrypoint doesn't have a handler. Are you sure this is the right file?`, + `The server entrypoint doesn't have a handler. Are you sure this is the right file?` ); } } catch (err) { if ((err as any).code === 'ERR_MODULE_NOT_FOUND') { throw new AstroError( `The server entrypoint ${fileURLToPath( - preview.serverEntrypoint, - )} does not exist. Have you ran a build yet?`, + preview.serverEntrypoint + )} does not exist. Have you ran a build yet?` ); + // biome-ignore lint/style/noUselessElse: } else { throw err; } diff --git a/packages/integrations/node/src/serve-app.ts b/packages/integrations/node/src/serve-app.ts index 72b4e0fd6..2934a01ab 100644 --- a/packages/integrations/node/src/serve-app.ts +++ b/packages/integrations/node/src/serve-app.ts @@ -39,7 +39,7 @@ export function createAppHandler(app: NodeApp): RequestHandler { addCookieHeader: true, locals, routeData, - }), + }) ); await NodeApp.writeResponse(response, res); } else if (next) { diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts index 725f7afa6..9221594d7 100644 --- a/packages/integrations/node/src/serve-static.ts +++ b/packages/integrations/node/src/serve-static.ts @@ -29,23 +29,28 @@ export function createStaticHandler(app: NodeApp, options: Options) { let isDirectory = false; try { isDirectory = fs.lstatSync(filePath).isDirectory(); - } catch {} + } catch { } const { trailingSlash = 'ignore' } = options; const hasSlash = urlPath.endsWith('/'); switch (trailingSlash) { case 'never': + // biome-ignore lint/suspicious/noDoubleEquals: if (isDirectory && urlPath != '/' && hasSlash) { + // biome-ignore lint/style/useTemplate: + // biome-ignore lint/suspicious/noFallthroughSwitchClause: pathname = urlPath.slice(0, -1) + (urlQuery ? '?' + urlQuery : ''); res.statusCode = 301; res.setHeader('Location', pathname); return res.end(); + // biome-ignore lint/style/noUselessElse: } else pathname = urlPath; // intentionally fall through case 'ignore': { if (isDirectory && !hasSlash) { + // biome-ignore lint/style/useTemplate: pathname = urlPath + '/index.html'; } else pathname = urlPath; } @@ -53,10 +58,12 @@ export function createStaticHandler(app: NodeApp, options: Options) { case 'always': // trailing slash is not added to "subresources" if (!hasSlash && !isSubresourceRegex.test(urlPath)) { + // biome-ignore lint/style/useTemplate: pathname = urlPath + '/' + (urlQuery ? '?' + urlQuery : ''); res.statusCode = 301; res.setHeader('Location', pathname); return res.end(); + // biome-ignore lint/style/noUselessElse: } else pathname = urlPath; break; } @@ -110,6 +117,7 @@ function resolveClientDir(options: Options) { while (!serverEntryFolderURL.endsWith(serverFolder)) { serverEntryFolderURL = path.dirname(serverEntryFolderURL); } + // biome-ignore lint/style/useTemplate: const serverEntryURL = serverEntryFolderURL + '/entry.mjs'; const clientURL = new URL(appendForwardSlash(rel), serverEntryURL); const client = url.fileURLToPath(clientURL); @@ -117,9 +125,11 @@ function resolveClientDir(options: Options) { } function prependForwardSlash(pth: string) { + // biome-ignore lint/style/useTemplate: return pth.startsWith('/') ? pth : '/' + pth; } function appendForwardSlash(pth: string) { + // biome-ignore lint/style/useTemplate: return pth.endsWith('/') ? pth : pth + '/'; } diff --git a/packages/integrations/node/src/standalone.ts b/packages/integrations/node/src/standalone.ts index 76e672d2f..8ae10a9ba 100644 --- a/packages/integrations/node/src/standalone.ts +++ b/packages/integrations/node/src/standalone.ts @@ -39,6 +39,7 @@ export function createStandaloneHandler(app: NodeApp, options: Options) { return (req: http.IncomingMessage, res: http.ServerResponse) => { try { // validate request path + // biome-ignore lint/style/noNonNullAssertion: decodeURI(req.url!); } catch { res.writeHead(400); @@ -59,7 +60,7 @@ export function createServer(listener: http.RequestListener, host: string, port: key: fs.readFileSync(process.env.SERVER_KEY_PATH), cert: fs.readFileSync(process.env.SERVER_CERT_PATH), }, - listener, + listener ); } else { httpServer = http.createServer(listener); diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index 804a5ccf4..5eca5c530 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -27,7 +27,7 @@ describe('API routes', () => { it('Can get the request body', async () => { const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); - let { req, res, done } = createRequestAndResponse({ + const { req, res, done } = createRequestAndResponse({ method: 'POST', url: '/recipes', }); @@ -38,9 +38,9 @@ describe('API routes', () => { handler(req, res); - let [buffer] = await done; + const [buffer] = await done; - let json = JSON.parse(buffer.toString('utf-8')); + const json = JSON.parse(buffer.toString('utf-8')); assert.equal(json.length, 1); @@ -50,7 +50,7 @@ describe('API routes', () => { it('Can get binary data', async () => { const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); - let { req, res, done } = createRequestAndResponse({ + const { req, res, done } = createRequestAndResponse({ method: 'POST', url: '/binary', }); @@ -61,15 +61,15 @@ describe('API routes', () => { handler(req, res); - let [out] = await done; - let arr = Array.from(new Uint8Array(out.buffer)); + const [out] = await done; + const arr = Array.from(new Uint8Array(out.buffer)); assert.deepEqual(arr, [5, 4, 3, 2, 1]); }); it('Can post large binary data', async () => { const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); - let { req, res, done } = createRequestAndResponse({ + const { req, res, done } = createRequestAndResponse({ method: 'POST', url: '/hash', }); @@ -95,17 +95,17 @@ describe('API routes', () => { expectedDigest = hash.digest(); }); - let [out] = await done; + const [out] = await done; assert.deepEqual(new Uint8Array(out.buffer), new Uint8Array(expectedDigest)); }); it('Can bail on streaming', async () => { const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); - let { req, res, done } = createRequestAndResponse({ + const { req, res, done } = createRequestAndResponse({ url: '/streaming', }); - let locals = { cancelledByTheServer: false }; + const locals = { cancelledByTheServer: false }; handler(req, res, () => {}, locals); req.send(); diff --git a/packages/integrations/node/test/bad-urls.test.js b/packages/integrations/node/test/bad-urls.test.js index 9323516e8..cdc0158ff 100644 --- a/packages/integrations/node/test/bad-urls.test.js +++ b/packages/integrations/node/test/bad-urls.test.js @@ -39,7 +39,7 @@ describe('Bad URLs', () => { assert.equal( statusCodes.includes(fetchResult.status), true, - `${weirdUrl} returned something else than 400, 404, or 500`, + `${weirdUrl} returned something else than 400, 404, or 500` ); } const stillWork = await fixture.fetch('/'); diff --git a/packages/integrations/node/test/encoded.test.js b/packages/integrations/node/test/encoded.test.js index edc6ae78b..4fc97cf7f 100644 --- a/packages/integrations/node/test/encoded.test.js +++ b/packages/integrations/node/test/encoded.test.js @@ -18,7 +18,7 @@ describe('Encoded Pathname', () => { it('Can get an Astro file', async () => { const { handler } = await import('./fixtures/encoded/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ url: '/什么', }); @@ -32,7 +32,7 @@ describe('Encoded Pathname', () => { it('Can get a Markdown file', async () => { const { handler } = await import('./fixtures/encoded/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ url: '/blog/什么', }); diff --git a/packages/integrations/node/test/errors.test.js b/packages/integrations/node/test/errors.test.js index 802fa6e25..9bf4aa29b 100644 --- a/packages/integrations/node/test/errors.test.js +++ b/packages/integrations/node/test/errors.test.js @@ -20,6 +20,7 @@ describe('Errors', () => { }); let devPreview; + // biome-ignore lint/suspicious/noDuplicateTestHooks: before(async () => { // The two tests that need the server to run are skipped // devPreview = await fixture.preview(); @@ -58,7 +59,7 @@ describe('Errors', () => { const $ = cheerio.load(html); assert.equal($('p').text().trim(), 'Internal server error'); - }, + } ); it( @@ -86,6 +87,6 @@ describe('Errors', () => { } else { throw new Error('The response should take at most 2 chunks.'); } - }, + } ); }); diff --git a/packages/integrations/node/test/headers.test.js b/packages/integrations/node/test/headers.test.js index 00b1766c7..f2753517e 100644 --- a/packages/integrations/node/test/headers.test.js +++ b/packages/integrations/node/test/headers.test.js @@ -132,7 +132,7 @@ describe('Node Adapter Headers', () => { async function runTest(url, expectedHeaders) { const { handler } = await import('./fixtures/headers/dist/server/entry.mjs'); - let { req, res, done } = createRequestAndResponse({ + const { req, res, done } = createRequestAndResponse({ method: 'GET', url, }); diff --git a/packages/integrations/node/test/image.test.js b/packages/integrations/node/test/image.test.js index 5303fe37e..c4758f96b 100644 --- a/packages/integrations/node/test/image.test.js +++ b/packages/integrations/node/test/image.test.js @@ -28,7 +28,7 @@ describe.skip('Image endpoint', () => { assert.equal(res.status, 200); const resImage = await fixture.fetch( - '/_image?href=/_astro/some_penguin.97ef5f92.png&w=50&f=webp', + '/_image?href=/_astro/some_penguin.97ef5f92.png&w=50&f=webp' ); assert.equal(resImage.status, 200); diff --git a/packages/integrations/node/test/locals.test.js b/packages/integrations/node/test/locals.test.js index 6d2776079..b8e3ed40f 100644 --- a/packages/integrations/node/test/locals.test.js +++ b/packages/integrations/node/test/locals.test.js @@ -18,23 +18,23 @@ describe('API routes', () => { it('Can use locals added by node middleware', async () => { const { handler } = await import('./fixtures/locals/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ url: '/from-node-middleware', }); - let locals = { foo: 'bar' }; + const locals = { foo: 'bar' }; handler(req, res, () => {}, locals); req.send(); - let html = await text(); + const html = await text(); assert.equal(html.includes('

bar

'), true); }); it('Throws an error when provided non-objects as locals', async () => { const { handler } = await import('./fixtures/locals/dist/server/entry.mjs'); - let { req, res, done } = createRequestAndResponse({ + const { req, res, done } = createRequestAndResponse({ url: '/from-node-middleware', }); @@ -62,19 +62,19 @@ describe('API routes', () => { it('Can access locals in API', async () => { const { handler } = await import('./fixtures/locals/dist/server/entry.mjs'); - let { req, res, done } = createRequestAndResponse({ + const { req, res, done } = createRequestAndResponse({ method: 'POST', url: '/api', }); - let locals = { foo: 'bar' }; + const locals = { foo: 'bar' }; handler(req, res, () => {}, locals); req.send(); - let [buffer] = await done; + const [buffer] = await done; - let json = JSON.parse(buffer.toString('utf-8')); + const json = JSON.parse(buffer.toString('utf-8')); assert.equal(json.foo, 'bar'); }); diff --git a/packages/integrations/node/test/node-middleware.test.js b/packages/integrations/node/test/node-middleware.test.js index d1b016a51..eeb193c73 100644 --- a/packages/integrations/node/test/node-middleware.test.js +++ b/packages/integrations/node/test/node-middleware.test.js @@ -23,7 +23,7 @@ describe('behavior from middleware, standalone', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -31,6 +31,7 @@ describe('behavior from middleware, standalone', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -71,10 +72,12 @@ describe('behavior from middleware, middleware', () => { after(async () => { server.close(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); it('when mode is standalone', async () => { + // biome-ignore lint/style/noUnusedTemplateLiteral: const res = await fetch(`http://localhost:8888/ssr`); assert.equal(res.status, 200); diff --git a/packages/integrations/node/test/prerender-404-500.test.js b/packages/integrations/node/test/prerender-404-500.test.js index 2535fcb35..86226c500 100644 --- a/packages/integrations/node/test/prerender-404-500.test.js +++ b/packages/integrations/node/test/prerender-404-500.test.js @@ -34,7 +34,7 @@ describe('Prerender 404', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -42,6 +42,7 @@ describe('Prerender 404', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -124,7 +125,7 @@ describe('Prerender 404', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -132,6 +133,7 @@ describe('Prerender 404', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -193,7 +195,7 @@ describe('Hybrid 404', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -201,6 +203,7 @@ describe('Hybrid 404', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -255,7 +258,7 @@ describe('Hybrid 404', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -263,6 +266,7 @@ describe('Hybrid 404', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index e699a1b3c..0684ff63a 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -30,7 +30,7 @@ describe('Prerendering', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -38,6 +38,7 @@ describe('Prerendering', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -105,7 +106,7 @@ describe('Prerendering', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -113,6 +114,7 @@ describe('Prerendering', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -181,7 +183,7 @@ describe('Prerendering', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -189,6 +191,7 @@ describe('Prerendering', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -233,10 +236,12 @@ describe('Prerendering', () => { after(async () => { await devServer.stop(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); it('Can render SSR route', async () => { + // biome-ignore lint/style/noUnusedTemplateLiteral: const res = await fixture.fetch(`/one`); const html = await res.text(); const $ = cheerio.load(html); @@ -246,6 +251,7 @@ describe('Prerendering', () => { }); it('Can render prerendered route', async () => { + // biome-ignore lint/style/noUnusedTemplateLiteral: const res = await fixture.fetch(`/two`); const html = await res.text(); const $ = cheerio.load(html); @@ -277,7 +283,7 @@ describe('Hybrid rendering', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -285,6 +291,7 @@ describe('Hybrid rendering', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -350,7 +357,7 @@ describe('Hybrid rendering', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -358,6 +365,7 @@ describe('Hybrid rendering', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -415,7 +423,7 @@ describe('Hybrid rendering', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -423,6 +431,7 @@ describe('Hybrid rendering', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 10faba5cf..4f273756c 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -20,18 +20,18 @@ export function loadFixture(inlineConfig) { } export function createRequestAndResponse(reqOptions) { - let req = httpMocks.createRequest(reqOptions); + const req = httpMocks.createRequest(reqOptions); - let res = httpMocks.createResponse({ + const res = httpMocks.createResponse({ eventEmitter: EventEmitter, req, }); - let done = toPromise(res); + const done = toPromise(res); // Get the response as text const text = async () => { - let chunks = await done; + const chunks = await done; return buffersToString(chunks); }; @@ -45,19 +45,20 @@ export function toPromise(res) { const write = res.write; res.write = function (data, encoding) { if (ArrayBuffer.isView(data) && !Buffer.isBuffer(data)) { + // biome-ignore lint/style/noParameterAssign: data = Buffer.from(data.buffer); } return write.call(this, data, encoding); }; res.on('end', () => { - let chunks = res._getChunks(); + const chunks = res._getChunks(); resolve(chunks); }); }); } export function buffersToString(buffers) { - let decoder = new TextDecoder(); + const decoder = new TextDecoder(); let str = ''; for (const buffer of buffers) { str += decoder.decode(buffer); diff --git a/packages/integrations/node/test/trailing-slash.test.js b/packages/integrations/node/test/trailing-slash.test.js index 9ea8fcddd..6f6a2a3ba 100644 --- a/packages/integrations/node/test/trailing-slash.test.js +++ b/packages/integrations/node/test/trailing-slash.test.js @@ -32,7 +32,7 @@ describe('Trailing slash', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -40,6 +40,7 @@ describe('Trailing slash', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -103,7 +104,7 @@ describe('Trailing slash', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -111,6 +112,7 @@ describe('Trailing slash', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -177,7 +179,7 @@ describe('Trailing slash', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -185,6 +187,7 @@ describe('Trailing slash', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -241,7 +244,7 @@ describe('Trailing slash', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -249,6 +252,7 @@ describe('Trailing slash', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -308,7 +312,7 @@ describe('Trailing slash', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -316,6 +320,7 @@ describe('Trailing slash', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); @@ -390,7 +395,7 @@ describe('Trailing slash', () => { }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); - let res = startServer(); + const res = startServer(); server = res.server; await waitServerListen(server.server); }); @@ -398,6 +403,7 @@ describe('Trailing slash', () => { after(async () => { await server.stop(); await fixture.clean(); + // biome-ignore lint/performance/noDelete: delete process.env.PRERENDER; }); diff --git a/packages/integrations/node/test/url.test.js b/packages/integrations/node/test/url.test.js index 77ca45836..81b357b71 100644 --- a/packages/integrations/node/test/url.test.js +++ b/packages/integrations/node/test/url.test.js @@ -20,7 +20,7 @@ describe('URL', () => { it('return http when non-secure', async () => { const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ url: '/', }); @@ -33,7 +33,7 @@ describe('URL', () => { it('return https when secure', async () => { const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ socket: new TLSSocket(), url: '/', }); @@ -47,7 +47,7 @@ describe('URL', () => { it('return http when the X-Forwarded-Proto header is set to http', async () => { const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ headers: { 'X-Forwarded-Proto': 'http' }, url: '/', }); @@ -61,7 +61,7 @@ describe('URL', () => { it('return https when the X-Forwarded-Proto header is set to https', async () => { const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ headers: { 'X-Forwarded-Proto': 'https' }, url: '/', }); @@ -75,7 +75,7 @@ describe('URL', () => { it('includes forwarded host and port in the url', async () => { const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ headers: { 'X-Forwarded-Proto': 'https', 'X-Forwarded-Host': 'abc.xyz', @@ -95,7 +95,7 @@ describe('URL', () => { it('accepts port in forwarded host and forwarded port', async () => { const { handler } = await import('./fixtures/url/dist/server/entry.mjs'); - let { req, res, text } = createRequestAndResponse({ + const { req, res, text } = createRequestAndResponse({ headers: { 'X-Forwarded-Proto': 'https', 'X-Forwarded-Host': 'abc.xyz:444', diff --git a/packages/integrations/node/tsconfig.json b/packages/integrations/node/tsconfig.json index b9feb9b9b..18443cddf 100644 --- a/packages/integrations/node/tsconfig.json +++ b/packages/integrations/node/tsconfig.json @@ -1,8 +1,6 @@ { "extends": "../../tsconfig.base.json", - "include": [ - "src" - ], + "include": ["src"], "compilerOptions": { "outDir": "./dist" } -- cgit v1.2.3 From c0aedf5b6aed5a31089eb1c150966e5fb6761d71 Mon Sep 17 00:00:00 2001 From: Alexander Niebuhr Date: Thu, 29 Aug 2024 08:25:56 +0200 Subject: chore: fix testutils --- packages/integrations/node/test/test-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index 4f273756c..f36b8d237 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -1,6 +1,6 @@ import { EventEmitter } from 'node:events'; import httpMocks from 'node-mocks-http'; -import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; +import { loadFixture as baseLoadFixture } from '@astrojs/test-utils'; process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.ASTRO_NODE_LOGGING = 'disabled'; -- cgit v1.2.3 From 0c6782048036bd4273be7a060f3077061f1a8536 Mon Sep 17 00:00:00 2001 From: Alexander Niebuhr Date: Thu, 29 Aug 2024 08:28:37 +0200 Subject: chore: format --- packages/integrations/node/test/test-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/integrations/node/test/test-utils.js') diff --git a/packages/integrations/node/test/test-utils.js b/packages/integrations/node/test/test-utils.js index f36b8d237..37389d6d7 100644 --- a/packages/integrations/node/test/test-utils.js +++ b/packages/integrations/node/test/test-utils.js @@ -1,6 +1,6 @@ import { EventEmitter } from 'node:events'; -import httpMocks from 'node-mocks-http'; import { loadFixture as baseLoadFixture } from '@astrojs/test-utils'; +import httpMocks from 'node-mocks-http'; process.env.ASTRO_NODE_AUTOSTART = 'disabled'; process.env.ASTRO_NODE_LOGGING = 'disabled'; -- cgit v1.2.3