From 38b70de7e1c4e45049afd6621c12fbe66a897607 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 02:01:06 +0100 Subject: [PATCH 01/21] refactor(web/server): simplify env handling and remove utils Replaced manual environment variable parsing with `Schema`-based parsing for better type safety and reduced boilerplate. Removed `parseNumber` utility as it is no longer needed, consolidating logic into the new `Config` class. Streamlined the server initialization and improved readability. --- apps/web/server/server.ts | 59 +++++++++++++++++++++++---------------- apps/web/server/utils.ts | 10 ------- 2 files changed, 35 insertions(+), 34 deletions(-) delete mode 100644 apps/web/server/utils.ts diff --git a/apps/web/server/server.ts b/apps/web/server/server.ts index 1f2884dd..3be74d7e 100644 --- a/apps/web/server/server.ts +++ b/apps/web/server/server.ts @@ -5,16 +5,22 @@ import process from 'node:process' import url from 'node:url' import compression from 'compression' +import { Schema } from 'effect' import express from 'express' import getPort from 'get-port' import morgan from 'morgan' import sourceMapSupport from 'source-map-support' import type { ViteDevServer } from 'vite' -import { parseNumber } from './utils.ts' - -// biome-ignore lint/complexity/useLiteralKeys: -process.env['NODE_ENV'] = process.env['NODE_ENV'] ?? 'production' +class Config extends Schema.Class('Config')({ + NODE_ENV: Schema.optionalWith(Schema.Literal('development', 'production'), { + default: () => 'production', + }), + PORT: Schema.optional(Schema.NumberFromString.pipe(Schema.int())), + HOST: Schema.optional(Schema.String), +}) { + static decodeUnknownSync = Schema.decodeUnknownSync(this) +} sourceMapSupport.install({ retrieveSourceMap(source: string): { url: string; map: string } | null { @@ -57,20 +63,23 @@ sourceMapSupport.install({ * server/main.ts ./build/server/index.js * ``` */ -async function run(): Promise { - // biome-ignore lint/complexity/useLiteralKeys: - // biome-ignore lint/style/useNamingConvention: - const DEVELOPMENT = process.env['NODE_ENV'] === 'development' - // biome-ignore lint/complexity/useLiteralKeys: - // biome-ignore lint/style/useNamingConvention: - const PORT = parseNumber(process.env['PORT']) ?? (await getPort({ port: 3000 })) - - const buildPathArg = process.argv[2] +async function run({ + NODE_ENV, + PORT, + HOST, + buildPathArg, +}: { + NODE_ENV: 'development' | 'production' + PORT: number + HOST: undefined | string + buildPathArg: undefined | string +}): Promise { + const isDevelopment = NODE_ENV === 'development' if (!buildPathArg) { // biome-ignore lint/suspicious/noConsole: console.error(` - Usage: react-router-serve - e.g. react-router-serve build/server/index.js`) + Usage: node server/server.ts `) process.exit(1) } @@ -78,8 +87,7 @@ async function run(): Promise { const onListen = (): void => { const address = - // biome-ignore lint/complexity/useLiteralKeys: - process.env['HOST'] || + HOST || Object.values(os.networkInterfaces()) .flat() .find(ip => String(ip?.family).includes('4') && !ip?.internal)?.address @@ -95,13 +103,13 @@ async function run(): Promise { } } - const app = express() + const app: express.Express = express() app.disable('x-powered-by') app.use(compression()) - if (DEVELOPMENT) { + if (isDevelopment) { console.log('Starting development server') const viteDevServer: ViteDevServer = await import('vite').then(vite => @@ -136,15 +144,18 @@ async function run(): Promise { app.use(morgan('tiny')) - // biome-ignore lint/complexity/useLiteralKeys: - const server = process.env['HOST'] - ? // biome-ignore lint/complexity/useLiteralKeys: - app.listen(PORT, process.env['HOST'], onListen) - : app.listen(PORT, onListen) + const server = HOST ? app.listen(PORT, HOST, onListen) : app.listen(PORT, onListen) for (const signal of ['SIGTERM', 'SIGINT']) { process.once(signal, () => server?.close(console.error)) } } -run() +const env = Config.decodeUnknownSync(process.env) + +run({ + NODE_ENV: env.NODE_ENV, + PORT: await getPort({ port: env.PORT ?? 5173 }), + HOST: env.HOST, + buildPathArg: process.argv[2], +}) diff --git a/apps/web/server/utils.ts b/apps/web/server/utils.ts deleted file mode 100644 index 39ce8a47..00000000 --- a/apps/web/server/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function parseNumber(raw?: string): number | undefined { - if (raw === undefined) { - return undefined - } - const maybe = Number(raw) - if (Number.isNaN(maybe)) { - return undefined - } - return maybe -} From fcb81b40ae993ce19b322a98a6d36452828e2218 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 02:25:21 +0100 Subject: [PATCH 02/21] feat(web): update biome config for server-specific rules Added a new configuration section in biome.json for server-side TypeScript files. This includes disabling specific linter rules for better compatibility with server-side development needs. --- apps/web/biome.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/web/biome.json b/apps/web/biome.json index eb194b42..16dba33c 100644 --- a/apps/web/biome.json +++ b/apps/web/biome.json @@ -37,6 +37,23 @@ "formatter": { "enabled": false } + }, + { + "include": ["server/**/*.ts"], + "linter": { + "rules": { + "suspicious": { + "noConsole": "off", + "noConsoleLog": "off" + }, + "correctness": { + "noNodejsModules": "off" + }, + "nursery": { + "useExplicitType": "off" + } + } + } } ] } From 858008e5b753088947c1c17aa345cc209f2e8332 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 02:26:34 +0100 Subject: [PATCH 03/21] refactor(web): improve server configuration and startup logic Enhanced server configuration schema with annotations and streamlined `NODE_ENV` handling. Refactored code to replace redundant logic with a cleaner `switch` statement for environment handling. Updated build path argument parsing to ensure correct application startup. --- apps/web/server/server.ts | 124 ++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 60 deletions(-) diff --git a/apps/web/server/server.ts b/apps/web/server/server.ts index 3be74d7e..8a0c14a9 100644 --- a/apps/web/server/server.ts +++ b/apps/web/server/server.ts @@ -15,9 +15,19 @@ import type { ViteDevServer } from 'vite' class Config extends Schema.Class('Config')({ NODE_ENV: Schema.optionalWith(Schema.Literal('development', 'production'), { default: () => 'production', + }).annotations({ + description: 'Environment to run the server in', + }), + PORT: Schema.optional(Schema.NumberFromString.pipe(Schema.int())).annotations({ + description: 'Port to run the server on', + }), + HOST: Schema.optional(Schema.String).annotations({ + description: 'Host to run the server on', + }), + buildPathArg: Schema.String.annotations({ + description: 'Path to the server build', + message: () => 'Usage: node server/server.ts ', }), - PORT: Schema.optional(Schema.NumberFromString.pipe(Schema.int())), - HOST: Schema.optional(Schema.String), }) { static decodeUnknownSync = Schema.decodeUnknownSync(this) } @@ -72,18 +82,57 @@ async function run({ NODE_ENV: 'development' | 'production' PORT: number HOST: undefined | string - buildPathArg: undefined | string + buildPathArg: string }): Promise { - const isDevelopment = NODE_ENV === 'development' + const app: express.Express = express() + + app.disable('x-powered-by') + + app.use(compression()) + + switch (NODE_ENV) { + case 'development': { + console.log('Starting development server') + + const viteDevServer: ViteDevServer = await import('vite').then(vite => + vite.createServer({ + server: { middlewareMode: true }, + }), + ) + + app.use(viteDevServer.middlewares) + app.use(async (req, res, next) => { + try { + const source = (await viteDevServer.ssrLoadModule('./server/app.ts')) as { + app: express.Express + } + return await source.app(req, res, next) + } catch (error: unknown) { + if (typeof error === 'object' && error instanceof Error) { + viteDevServer.ssrFixStacktrace(error) + } + next(error) + } + }) + + break + } + case 'production': { + console.log('Starting production server') + + app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' })) + + app.use(express.static('build/client', { maxAge: '1h' })) - if (!buildPathArg) { - // biome-ignore lint/suspicious/noConsole: - console.error(` - Usage: node server/server.ts `) - process.exit(1) + app.use(await import(path.resolve(buildPathArg)).then(mod => mod.app)) + + break + } + default: + throw new Error(`Unknown NODE_ENV: ${NODE_ENV}`) } - const buildPath = path.resolve(buildPathArg) + app.use(morgan('tiny')) const onListen = (): void => { const address = @@ -93,57 +142,12 @@ async function run({ .find(ip => String(ip?.family).includes('4') && !ip?.internal)?.address if (address) { - // biome-ignore lint/suspicious/noConsoleLog: - // biome-ignore lint/suspicious/noConsole: console.log(`[react-router-serve] http://localhost:${PORT} (http://${address}:${PORT})`) } else { - // biome-ignore lint/suspicious/noConsole: - // biome-ignore lint/suspicious/noConsoleLog: console.log(`[react-router-serve] http://localhost:${PORT}`) } } - const app: express.Express = express() - - app.disable('x-powered-by') - - app.use(compression()) - - if (isDevelopment) { - console.log('Starting development server') - - const viteDevServer: ViteDevServer = await import('vite').then(vite => - vite.createServer({ - server: { middlewareMode: true }, - }), - ) - - app.use(viteDevServer.middlewares) - app.use(async (req, res, next) => { - try { - const source = (await viteDevServer.ssrLoadModule('./server/app.ts')) as { - app: express.Express - } - return await source.app(req, res, next) - } catch (error: unknown) { - if (typeof error === 'object' && error instanceof Error) { - viteDevServer.ssrFixStacktrace(error) - } - next(error) - } - }) - } else { - console.log('Starting production server') - - app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' })) - - app.use(express.static('build/client', { maxAge: '1h' })) - - app.use(await import(buildPath).then(mod => mod.app)) - } - - app.use(morgan('tiny')) - const server = HOST ? app.listen(PORT, HOST, onListen) : app.listen(PORT, onListen) for (const signal of ['SIGTERM', 'SIGINT']) { @@ -151,11 +155,11 @@ async function run({ } } -const env = Config.decodeUnknownSync(process.env) +const config = Config.decodeUnknownSync({ ...process.env, buildPathArg: process.argv[2] }) run({ - NODE_ENV: env.NODE_ENV, - PORT: await getPort({ port: env.PORT ?? 5173 }), - HOST: env.HOST, - buildPathArg: process.argv[2], + NODE_ENV: config.NODE_ENV, + PORT: await getPort({ port: config.PORT ?? 5173 }), + HOST: config.HOST, + buildPathArg: config.buildPathArg, }) From bc8487389f948f95af080a11e6be6132a5a74cb8 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:38:25 +0100 Subject: [PATCH 04/21] docs(web): add server setup documentation Introduce detailed documentation for the server setup within the web module. This includes configuration, key components, dependencies, and testing objectives for development and production environments. Aimed at improving clarity for server implementation and testing. --- apps/web/server/server.md | 75 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 apps/web/server/server.md diff --git a/apps/web/server/server.md b/apps/web/server/server.md new file mode 100644 index 00000000..247074fc --- /dev/null +++ b/apps/web/server/server.md @@ -0,0 +1,75 @@ +# Server Setup Documentation + +## Overview +The server is an Express application that serves a React Router build. It supports both development and production environments, dynamically configuring the server port and host. + +## Key Components + +### Configuration (`Config` class) +- Manages environment variables and server build path. +- Uses `effect` library for schema validation. + +### Source Map Support +- Uses `source-map-support` to enhance error stack traces. + +### Express Application +- Uses `compression` for response compression. +- Uses `morgan` for HTTP request logging. +- Serves static files and handles all incoming requests. + +### Development Mode +- Uses `vite` for development server with middleware mode. +- Dynamically loads and handles requests using Vite's SSR capabilities. + +### Production Mode +- Serves static assets with caching. +- Loads the server build from the specified path. + +### Request Handling +- Uses `react-router` for handling requests and responses. +- Custom request handler created using `react-router-express`. + +## External Dependencies +- `compression`: Middleware for response compression. +- `effect`: Library for schema validation. +- `express`: Web framework for building the server. +- `get-port`: Utility to get an available port. +- `morgan`: HTTP request logger. +- `source-map-support`: Enhances error stack traces. +- `vite`: Development server and build tool. +- `react-router`: Library for handling routing and SSR. + +# Unit Tests + +## Objectives +- Validate the configuration schema and default values. +- Ensure the server starts correctly in both development and production modes. +- Verify middleware and request handling logic. +- Test static file serving and caching behavior. +- Validate error handling and logging. + +## Test Cases + +### Configuration Tests +- Validate default values for `NODE_ENV`, `PORT`, and `HOST`. +- Test schema validation for different configurations. + +### Server Initialization Tests +- Ensure the server starts on the specified port and host. +- Verify the correct middleware is applied based on the environment. + +### Middleware Tests +- Test `compression` middleware for response compression. +- Verify `morgan` logs HTTP requests correctly. + +### Request Handling Tests +- Test request handling in development mode using Vite. +- Verify request handling in production mode with static file serving. + +### Static File Serving Tests +- Ensure static assets are served with correct caching headers. +- Test serving of client-side assets from the build directory. + +### Error Handling Tests +- Verify error handling and logging for different scenarios. +- Test source map support for enhanced error stack traces. From 63ab2892b88f9058164e5f40a0a9ddffeb68437d Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:38:40 +0100 Subject: [PATCH 05/21] refactor(web): reorganize biome.json overrides Reordered and updated rules in biome.json to streamline override configurations. Adjusted file inclusion paths and moved related settings together for better maintainability. Ensured no behavior changes were introduced. --- apps/web/biome.json | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/web/biome.json b/apps/web/biome.json index 16dba33c..98ef2a74 100644 --- a/apps/web/biome.json +++ b/apps/web/biome.json @@ -23,25 +23,12 @@ }, "overrides": [ { - "include": ["app/routes/**/*.tsx"], + "include": ["./server/**/*.ts"], "linter": { "rules": { "style": { - "noDefaultExport": "off" - } - } - } - }, - { - "include": ["**/*.snapshot.json"], - "formatter": { - "enabled": false - } - }, - { - "include": ["server/**/*.ts"], - "linter": { - "rules": { + "useNamingConvention": "info" + }, "suspicious": { "noConsole": "off", "noConsoleLog": "off" @@ -54,6 +41,23 @@ } } } + }, + + { + "include": ["app/routes/**/*.tsx"], + "linter": { + "rules": { + "style": { + "noDefaultExport": "off" + } + } + } + }, + { + "include": ["**/*.snapshot.json"], + "formatter": { + "enabled": false + } } ] } From 111141ea6f39fc1fa602510e111340a1d4adf10b Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:26:28 +0100 Subject: [PATCH 06/21] docs(web/server): add details on middleware and dev server Expand documentation with explanations for middleware like compression, logging, and static file serving. Include details on Vite Dev Server, SSR module loading, graceful shutdown, and additional configurations. This enhances clarity for server setup and behavior. --- apps/web/server/server.md | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/apps/web/server/server.md b/apps/web/server/server.md index 247074fc..07ff1c5f 100644 --- a/apps/web/server/server.md +++ b/apps/web/server/server.md @@ -29,6 +29,57 @@ The server is an Express application that serves a React Router build. It suppor - Uses `react-router` for handling requests and responses. - Custom request handler created using `react-router-express`. +## Middleware Behavior + +### Compression Middleware (`compression`) +- Compresses response bodies for all requests. +- Adds `Content-Encoding` headers and compresses the body. + +### Morgan Middleware (`morgan`) +- Logs HTTP requests. +- Does not modify the `Request` or `Response` but logs details like method, URL, status, and response time. + +### Static File Serving (`express.static`) +- Serves static files from the specified directories. +- Adds appropriate headers (e.g., `Cache-Control`) and serves files. + +### Vite Dev Server Middleware (`viteDevServer.middlewares`) +- Handles requests in development mode. +- Attaches custom middlewares and acts as a handler function for custom HTTP servers. +- Provides features like HMR, module loading, and development-specific optimizations. + +## Vite Dev Server in Development Mode + +- A connect app instance that attaches custom middlewares to the dev server. +- Acts as a handler function for custom HTTP servers or as middleware in connect-style Node.js frameworks. +- Provides HMR, module loading, and development-specific optimizations. + +## `viteDevServer.ssrLoadModule('./server/app.ts')` and `app.ts` + +### `ssrLoadModule` +- Loads a given URL as an instantiated module for SSR. + +### `app.ts` +- Sets up the Express application and request handler. +- Defines a request handler using `createRequestHandler` from `react-router-express`. +- Configures the request handler to load the server build and provide context values. + +## Handling OS Signals for Graceful Shutdown + +- Listens for termination signals (`SIGTERM` and `SIGINT`). +- Closes the server instance and cleans up resources when a signal is received. + +## Additional Details + +### Configuration +- Dynamically configures the port and host based on environment variables or default settings. + +### Source Map Support +- Enhances error stack traces by retrieving source maps for better debugging. + +### Request Handling +- Uses `react-router` to handle requests and responses, providing SSR capabilities. + ## External Dependencies - `compression`: Middleware for response compression. - `effect`: Library for schema validation. From df731c068ef4b1d3dc40bc74b4d934fc111cc6f6 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:28:21 +0100 Subject: [PATCH 07/21] feat(web): refactor server to export `run` and return `http.Server` This change modifies the `run` function to export it and ensures it returns an `http.Server` instance. It also updates the configuration handling, simplifying input decoding and assigning a default port if not provided. These adjustments improve reusability and clarity. --- apps/web/server/server.ts | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/web/server/server.ts b/apps/web/server/server.ts index 8a0c14a9..36fd9768 100644 --- a/apps/web/server/server.ts +++ b/apps/web/server/server.ts @@ -1,4 +1,5 @@ import fs from 'node:fs' +import type * as http from 'node:http' import os from 'node:os' import path from 'node:path/posix' import process from 'node:process' @@ -73,17 +74,20 @@ sourceMapSupport.install({ * server/main.ts ./build/server/index.js * ``` */ -async function run({ - NODE_ENV, - PORT, - HOST, - buildPathArg, -}: { - NODE_ENV: 'development' | 'production' - PORT: number - HOST: undefined | string - buildPathArg: string -}): Promise { +export async function run(): Promise { + const { + NODE_ENV, + PORT: _port, + HOST, + buildPathArg, + } = Config.decodeUnknownSync({ + ...process.env, + buildPathArg: process.argv[2], + }) + + // biome-ignore lint/style/useNamingConvention: + const PORT = await getPort({ port: _port ?? 5173 }) + const app: express.Express = express() app.disable('x-powered-by') @@ -153,13 +157,8 @@ async function run({ for (const signal of ['SIGTERM', 'SIGINT']) { process.once(signal, () => server?.close(console.error)) } -} -const config = Config.decodeUnknownSync({ ...process.env, buildPathArg: process.argv[2] }) + return server +} -run({ - NODE_ENV: config.NODE_ENV, - PORT: await getPort({ port: config.PORT ?? 5173 }), - HOST: config.HOST, - buildPathArg: config.buildPathArg, -}) +run() From 25ddf0b0229abf644f10a7fa8bafdb42d0722947 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:30:47 +0100 Subject: [PATCH 08/21] feat(web): add msw for API mocking support Adds `msw` version 2.7.0 to `apps/web`. This facilitates mocking API responses during development and testing, improving developer experience. Updates corresponding dependencies in the lockfile to ensure compatibility. --- apps/web/package.json | 1 + pnpm-lock.yaml | 331 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 325 insertions(+), 7 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 06e81c73..75e3dc68 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -51,6 +51,7 @@ "@vitest/ui": "3.0.0-beta.2", "babel-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124", "globals": "15.13.0", + "msw": "2.7.0", "react-router-devtools": "2.0.0-beta.0", "tailwindcss": "4.0.0-beta.8", "tailwindcss-animate": "1.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94d0c5a6..0821d54c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,9 @@ importers: globals: specifier: 15.13.0 version: 15.13.0 + msw: + specifier: 2.7.0 + version: 2.7.0(@types/node@22.10.2)(typescript@5.7.2) react-router-devtools: specifier: 2.0.0-beta.0 version: 2.0.0-beta.0(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.28.2)) @@ -182,7 +185,7 @@ importers: version: 5.1.4(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.28.2)) vitest: specifier: 3.0.0-beta.2 - version: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2) + version: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2)) packages/config-typescript: dependencies: @@ -642,6 +645,15 @@ packages: react: ^19.0.0 react-dom: 19.0.0 + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@chromatic-com/storybook@3.2.2': resolution: {integrity: sha512-xmXt/GW0hAPbzNTrxYuVo43Adrtjue4DeVrsoIIEeJdGaPNNeNf+DHMlJKOBdlHmCnFUoe9R/0mLM9zUp5bKWw==} engines: {node: '>=16.0.0', yarn: '>=1.22.18'} @@ -1078,6 +1090,26 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} + '@inquirer/confirm@5.1.0': + resolution: {integrity: sha512-osaBbIMEqVFjTX5exoqPXs6PilWQdjaLhGtMDXMXg/yxkHXNq43GlxGyTA35lK2HpzUgDN+Cjh/2AmqCN0QJpw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + + '@inquirer/core@10.1.1': + resolution: {integrity: sha512-rmZVXy9iZvO3ZStEe/ayuuwIJ23LSF13aPMlLMTQARX6lGUBDHGV8UB5i9MRrfy0+mZwt5/9bdy8llszSD3NQA==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.8': + resolution: {integrity: sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.1': + resolution: {integrity: sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + '@internationalized/date@3.6.0': resolution: {integrity: sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==} @@ -1140,6 +1172,10 @@ packages: '@mjackson/node-fetch-server@0.2.0': resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} + '@mswjs/interceptors@0.37.3': + resolution: {integrity: sha512-USvgCL/uOGFtVa6SVyRrC8kIAedzRohxIXN5LISlg5C5vLZCn7dgMFVSNhSF9cuBEFrm/O2spDWEZeMnw4ZXYg==} + engines: {node: '>=18'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1248,6 +1284,15 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3034,6 +3079,12 @@ packages: '@types/source-map-support@0.5.10': resolution: {integrity: sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} @@ -3213,6 +3264,10 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3431,9 +3486,17 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone@2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} @@ -3492,6 +3555,10 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} @@ -4020,6 +4087,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.2.6: resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} engines: {node: '>= 0.4'} @@ -4085,6 +4156,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.10.0: + resolution: {integrity: sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + gunzip-maybe@1.4.2: resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} hasBin: true @@ -4115,6 +4190,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -4259,6 +4337,9 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number-object@1.1.0: resolution: {integrity: sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==} engines: {node: '>= 0.4'} @@ -4613,6 +4694,20 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.7.0: + resolution: {integrity: sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4708,6 +4803,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -4756,6 +4854,9 @@ packages: path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4843,6 +4944,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} @@ -4860,6 +4964,9 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -5090,6 +5197,13 @@ packages: zod: optional: true + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@2.0.0: resolution: {integrity: sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==} engines: {node: '>=0.10.0'} @@ -5275,6 +5389,9 @@ packages: stream-slice@0.1.2: resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -5408,6 +5525,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} @@ -5486,6 +5607,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -5549,6 +5674,10 @@ packages: universal-user-agent@7.0.2: resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -5570,6 +5699,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + use-callback-ref@1.3.2: resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} @@ -5776,6 +5908,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -5803,6 +5939,10 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -5810,10 +5950,22 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + zod-validation-error@3.4.0: resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} engines: {node: '>=18.0.0'} @@ -6121,6 +6273,19 @@ snapshots: react-lifecycles-compat: 3.0.4 warning: 3.0.0 + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + '@chromatic-com/storybook@3.2.2(react@19.0.0)(storybook@8.4.7(prettier@2.8.8))': dependencies: chromatic: 11.16.1 @@ -6456,6 +6621,32 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} + '@inquirer/confirm@5.1.0(@types/node@22.10.2)': + dependencies: + '@inquirer/core': 10.1.1(@types/node@22.10.2) + '@inquirer/type': 3.0.1(@types/node@22.10.2) + '@types/node': 22.10.2 + + '@inquirer/core@10.1.1(@types/node@22.10.2)': + dependencies: + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@22.10.2) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + + '@inquirer/figures@1.0.8': {} + + '@inquirer/type@3.0.1(@types/node@22.10.2)': + dependencies: + '@types/node': 22.10.2 + '@internationalized/date@3.6.0': dependencies: '@swc/helpers': 0.5.13 @@ -6521,6 +6712,15 @@ snapshots: '@mjackson/node-fetch-server@0.2.0': {} + '@mswjs/interceptors@0.37.3': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6665,6 +6865,15 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -8910,6 +9119,10 @@ snapshots: dependencies: source-map: 0.6.1 + '@types/statuses@2.0.5': {} + + '@types/tough-cookie@4.0.5': {} + '@types/uuid@9.0.8': {} '@typescript-eslint/eslint-plugin@8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.1))(typescript@5.7.2)': @@ -9051,7 +9264,7 @@ snapshots: std-env: 3.8.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2) + vitest: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2)) transitivePeerDependencies: - supports-color @@ -9061,7 +9274,7 @@ snapshots: eslint: 9.17.0(jiti@2.4.1) optionalDependencies: typescript: 5.7.2 - vitest: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2) + vitest: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2)) '@vitest/expect@2.0.5': dependencies: @@ -9077,12 +9290,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@3.0.0-beta.2(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.28.2))': + '@vitest/mocker@3.0.0-beta.2(msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2))(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.28.2))': dependencies: '@vitest/spy': 3.0.0-beta.2 estree-walker: 3.0.3 magic-string: 0.30.14 optionalDependencies: + msw: 2.7.0(@types/node@22.10.2)(typescript@5.7.2) vite: 6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.28.2) '@vitest/pretty-format@2.0.5': @@ -9125,7 +9339,7 @@ snapshots: sirv: 3.0.0 tinyglobby: 0.2.10 tinyrainbow: 1.2.0 - vitest: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2) + vitest: 3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2)) '@vitest/utils@2.0.5': dependencies: @@ -9168,6 +9382,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -9416,8 +9634,16 @@ snapshots: classnames@2.5.1: {} + cli-width@4.1.0: {} + client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone@2.1.2: {} clsx@2.1.1: {} @@ -9474,6 +9700,8 @@ snapshots: cookie@0.7.1: {} + cookie@0.7.2: {} + cookie@1.0.2: {} core-util-is@1.0.3: {} @@ -10128,6 +10356,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.2.6: dependencies: call-bind-apply-helpers: 1.0.1 @@ -10199,6 +10429,8 @@ snapshots: graphemer@1.4.0: {} + graphql@16.10.0: {} + gunzip-maybe@1.4.2: dependencies: browserify-zlib: 0.1.4 @@ -10228,6 +10460,8 @@ snapshots: dependencies: function-bind: 1.1.2 + headers-polyfill@4.0.3: {} + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -10363,6 +10597,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-node-process@1.2.0: {} + is-number-object@1.1.0: dependencies: call-bind: 1.0.8 @@ -10677,6 +10913,33 @@ snapshots: ms@2.1.3: {} + msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.0(@types/node@22.10.2) + '@mswjs/interceptors': 0.37.3 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + graphql: 16.10.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.30.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + nanoid@3.3.7: {} natural-compare@1.4.0: {} @@ -10779,6 +11042,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + outvariant@1.4.3: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -10819,6 +11084,8 @@ snapshots: path-to-regexp@0.1.12: {} + path-to-regexp@6.3.0: {} + path-type@4.0.0: {} pathe@1.1.2: {} @@ -10890,6 +11157,10 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + psl@1.15.0: + dependencies: + punycode: 2.3.1 + pump@2.0.1: dependencies: end-of-stream: 1.4.4 @@ -10909,6 +11180,8 @@ snapshots: dependencies: side-channel: 1.1.0 + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} range-parser@1.2.1: {} @@ -11268,6 +11541,10 @@ snapshots: react-router: 7.0.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) zod: 3.23.8 + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + resolve-from@2.0.0: {} resolve-from@4.0.0: {} @@ -11493,6 +11770,8 @@ snapshots: stream-slice@0.1.2: {} + strict-event-emitter@0.5.1: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -11633,6 +11912,13 @@ snapshots: totalist@3.0.1: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + ts-api-utils@1.4.3(typescript@5.7.2): dependencies: typescript: 5.7.2 @@ -11690,6 +11976,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.21.3: {} + type-fest@2.19.0: {} type-fest@4.30.0: {} @@ -11765,6 +12053,8 @@ snapshots: universal-user-agent@7.0.2: {} + universalify@0.2.0: {} + universalify@2.0.1: {} unpipe@1.0.0: {} @@ -11784,6 +12074,11 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + use-callback-ref@1.3.2(@types/react@19.0.1)(react@19.0.0): dependencies: react: 19.0.0 @@ -11908,10 +12203,10 @@ snapshots: jiti: 2.4.1 lightningcss: 1.28.2 - vitest@3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2): + vitest@3.0.0-beta.2(@types/node@22.10.2)(@vitest/ui@3.0.0-beta.2)(jiti@2.4.1)(lightningcss@1.28.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2)): dependencies: '@vitest/expect': 3.0.0-beta.2 - '@vitest/mocker': 3.0.0-beta.2(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.28.2)) + '@vitest/mocker': 3.0.0-beta.2(msw@2.7.0(@types/node@22.10.2)(typescript@5.7.2))(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.28.2)) '@vitest/pretty-format': 3.0.0-beta.2 '@vitest/runner': 3.0.0-beta.2 '@vitest/snapshot': 3.0.0-beta.2 @@ -12015,6 +12310,12 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -12033,12 +12334,28 @@ snapshots: xtend@4.0.2: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yaml@1.10.2: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.2: {} + zod-validation-error@3.4.0(zod@3.23.8): dependencies: zod: 3.23.8 From f043950fe3421cd6b7e18e5c824466d3cd473d3b Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:31:43 +0100 Subject: [PATCH 09/21] feat(web): add MSW setup for testing environment Introduced MSW (Mock Service Worker) for request handling in tests. Added setup, handlers, and server initialization to support controlled API mocking. Updated relevant configs to integrate MSW into the application's testing flow. --- apps/web/mocks/handlers.ts | 7 +++++++ apps/web/mocks/node.ts | 5 +++++ apps/web/tsconfig.node.json | 10 ++++++++-- apps/web/vite.config.ts | 1 + apps/web/vitest.setup.ts | 16 ++++++++++++++++ 5 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 apps/web/mocks/handlers.ts create mode 100644 apps/web/mocks/node.ts create mode 100644 apps/web/vitest.setup.ts diff --git a/apps/web/mocks/handlers.ts b/apps/web/mocks/handlers.ts new file mode 100644 index 00000000..7f2b0105 --- /dev/null +++ b/apps/web/mocks/handlers.ts @@ -0,0 +1,7 @@ +import { http, type HttpHandler } from 'msw' + +export const handlers = [ + http.all('*', ({ request }) => { + console.log(request.method, request.url) + }), +] satisfies HttpHandler[] diff --git a/apps/web/mocks/node.ts b/apps/web/mocks/node.ts new file mode 100644 index 00000000..8dec6fcb --- /dev/null +++ b/apps/web/mocks/node.ts @@ -0,0 +1,5 @@ +import { setupServer } from 'msw/node' + +import { handlers } from './handlers.ts' + +export const server = setupServer(...handlers) diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json index ee3f81a7..ce0e0e0c 100644 --- a/apps/web/tsconfig.node.json +++ b/apps/web/tsconfig.node.json @@ -1,6 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig", - "extends": ["./tsconfig.json", "@suddenlygiovanni/config-typescript/library.json"], "compilerOptions": { "composite": true, "target": "esnext", @@ -10,5 +9,12 @@ "verbatimModuleSyntax": true, "types": ["node"] }, - "include": ["vite.config.ts", "./server/**/*.ts"] + "extends": ["./tsconfig.json", "@suddenlygiovanni/config-typescript/library.json"], + "include": [ + "./vite.config.ts", + "./handlers.ts", + "./vitest.setup.ts", + "./server/**/*.ts", + "./mocks/**/*.ts" + ] } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 7a807a48..a5e0fbbc 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -104,6 +104,7 @@ export default defineConfig(({ isSsrBuild }) => ({ test: { // biome-ignore lint/complexity/useLiteralKeys: TS4111: Property 'CODECOV_TOKEN' comes from an index signature, so it must be accessed with ['CODECOV_TOKEN']. reporters: process.env['GITHUB_ACTIONS'] ? ['dot', 'github-actions'] : ['default'], + setupFiles: ['./vitest.setup.ts'], globalSetup: './src/tests/test-globals.ts', coverage: { provider: 'v8', diff --git a/apps/web/vitest.setup.ts b/apps/web/vitest.setup.ts new file mode 100644 index 00000000..48d8d7e9 --- /dev/null +++ b/apps/web/vitest.setup.ts @@ -0,0 +1,16 @@ +import { afterAll, afterEach, beforeAll } from 'vitest' +import { server } from './mocks/node.ts' + +beforeAll(() => { + server.listen({ + onUnhandledRequest: 'error', + }) +}) + +afterEach(() => { + server.resetHandlers() +}) + +afterAll(() => { + server.close() +}) From 9ee8b432fd54367aad830e5c0f6457d59f9638b3 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:32:24 +0100 Subject: [PATCH 10/21] test(web): add initial test placeholders for server setup Added test placeholders covering server configuration, initialization, middleware, request handling, static file serving, error handling, and graceful shutdown. These outline the scope of testing needed for the server module. --- apps/web/server/server.spec.ts | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/web/server/server.spec.ts diff --git a/apps/web/server/server.spec.ts b/apps/web/server/server.spec.ts new file mode 100644 index 00000000..94d4d224 --- /dev/null +++ b/apps/web/server/server.spec.ts @@ -0,0 +1,37 @@ +import { describe, it } from 'vitest' + +describe('Server Setup', () => { + describe('Configuration Tests', () => { + it.todo('should validate default values for NODE_ENV, PORT, and HOST') + it.todo('should test schema validation for different configurations') + }) + + describe('Server Initialization Tests', () => { + it.todo('should ensure the server starts on the specified port and host') + it.todo('should verify the correct middleware is applied based on the environment') + }) + + describe('Middleware Tests', () => { + it.todo('should test compression middleware for response compression') + it.todo('should verify morgan logs HTTP requests correctly') + }) + + describe('Request Handling Tests', () => { + it.todo('should test request handling in development mode using Vite') + it.todo('should verify request handling in production mode with static file serving') + }) + + describe('Static File Serving Tests', () => { + it.todo('should ensure static assets are served with correct caching headers') + it.todo('should test serving of client-side assets from the build directory') + }) + + describe('Error Handling Tests', () => { + it.todo('should verify error handling and logging for different scenarios') + it.todo('should test source map support for enhanced error stack traces') + }) + + describe('Graceful Shutdown Tests', () => { + it.todo('should handle OS signals to gracefully shut down the server') + }) +}) From b17494da3c89cf10f0e9370309a1787d190d19c7 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:49:11 +0100 Subject: [PATCH 11/21] feat(web-server): improve error handling and graceful shutdown Replaces `decodeUnknownSync` with `decodeUnknownPromise` for async parsing, adding detailed error logging and process exit on failure. Implements a new graceful shutdown logic to properly handle termination signals (`SIGTERM` and `SIGINT`), ensuring server resources are released cleanly. --- apps/web/server/server.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/web/server/server.ts b/apps/web/server/server.ts index 36fd9768..7bca4446 100644 --- a/apps/web/server/server.ts +++ b/apps/web/server/server.ts @@ -6,7 +6,7 @@ import process from 'node:process' import url from 'node:url' import compression from 'compression' -import { Schema } from 'effect' +import { type ParseResult, Schema } from 'effect' import express from 'express' import getPort from 'get-port' import morgan from 'morgan' @@ -30,7 +30,7 @@ class Config extends Schema.Class('Config')({ message: () => 'Usage: node server/server.ts ', }), }) { - static decodeUnknownSync = Schema.decodeUnknownSync(this) + static decodeUnknownPromise = Schema.decodeUnknownPromise(this) } sourceMapSupport.install({ @@ -80,9 +80,12 @@ export async function run(): Promise { PORT: _port, HOST, buildPathArg, - } = Config.decodeUnknownSync({ + } = await Config.decodeUnknownPromise({ ...process.env, buildPathArg: process.argv[2], + }).catch((error: ParseResult.ParseError) => { + console.error(error.message, error.cause) + process.exit(1) }) // biome-ignore lint/style/useNamingConvention: @@ -154,8 +157,20 @@ export async function run(): Promise { const server = HOST ? app.listen(PORT, HOST, onListen) : app.listen(PORT, onListen) + const gracefulShutdown = (signal: 'SIGTERM' | 'SIGINT'): void => { + console.log(`Received shutdown signal "${signal}", closing server gracefully...`) + server?.close(err => { + if (err) { + console.error('Error during server shutdown:', err) + process.exit(1) + } + console.log('Server closed gracefully.') + process.exit(0) + }) + } + for (const signal of ['SIGTERM', 'SIGINT']) { - process.once(signal, () => server?.close(console.error)) + process.once(signal, gracefulShutdown) } return server From db22118087cb3ddf942f9741ff91a71d46f4833c Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:49:24 +0100 Subject: [PATCH 12/21] test(web/server): add test for missing server build path Introduce a test to verify error handling when the server build path is not provided. This ensures proper logging and a clean process exit in such cases. Enhanced test setup and teardown were also added for consistent environment mocking and cleanup. --- apps/web/server/server.spec.ts | 71 +++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/apps/web/server/server.spec.ts b/apps/web/server/server.spec.ts index 94d4d224..5f90fb06 100644 --- a/apps/web/server/server.spec.ts +++ b/apps/web/server/server.spec.ts @@ -1,37 +1,90 @@ -import { describe, it } from 'vitest' +import { + type MockInstance, + afterAll, + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest' + +import { run } from './server.ts' + +describe('server setup', () => { + let consoleErrorSpy: MockInstance<(...args: unknown[]) => void> + + beforeEach(() => { + vi.stubEnv('NODE_ENV', undefined) + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + vi.spyOn(process, 'exit').mockImplementation((code?: number) => { + throw new Error(`process.exit(${code})`) + }) + }) + + afterEach(() => { + vi.resetAllMocks() + vi.unstubAllEnvs() + vi.unstubAllGlobals() + }) + + afterAll(() => { + vi.restoreAllMocks() + }) + + describe('configuration tests', () => { + it('should throw an error if process.argv[2] is empty', async () => { + // Act & Assert + await expect(run()).rejects.toThrow('process.exit(1)') + expect(consoleErrorSpy).toHaveBeenCalledWith( + ` +(Config (Encoded side) <-> Config) +└─ Encoded side transformation failure + └─ Config (Encoded side) + └─ Encoded side transformation failure + └─ Struct (Encoded side) + └─ ["buildPathArg"] + └─ Usage: node server/server.ts `.trimStart(), + undefined, + ) + }) -describe('Server Setup', () => { - describe('Configuration Tests', () => { it.todo('should validate default values for NODE_ENV, PORT, and HOST') + it.todo('should test schema validation for different configurations') }) - describe('Server Initialization Tests', () => { + describe('server Initialization Tests', () => { it.todo('should ensure the server starts on the specified port and host') + it.todo('should verify the correct middleware is applied based on the environment') }) - describe('Middleware Tests', () => { + describe('middleware Tests', () => { it.todo('should test compression middleware for response compression') + it.todo('should verify morgan logs HTTP requests correctly') }) - describe('Request Handling Tests', () => { + describe('request Handling Tests', () => { it.todo('should test request handling in development mode using Vite') + it.todo('should verify request handling in production mode with static file serving') }) - describe('Static File Serving Tests', () => { + describe('static File Serving Tests', () => { it.todo('should ensure static assets are served with correct caching headers') + it.todo('should test serving of client-side assets from the build directory') }) - describe('Error Handling Tests', () => { + describe('error Handling Tests', () => { it.todo('should verify error handling and logging for different scenarios') + it.todo('should test source map support for enhanced error stack traces') }) - describe('Graceful Shutdown Tests', () => { + describe('graceful Shutdown Tests', () => { it.todo('should handle OS signals to gracefully shut down the server') }) }) From e1b1af3b6769467912367199d99b79d42e3d4d5e Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:17:38 +0100 Subject: [PATCH 13/21] fix(pnpm-lock): remove deprecated warnings from lockfile Removed deprecated warnings for `glob` and `inflight` packages in the `pnpm-lock.yaml`. This cleanup ensures a more concise and clearer lockfile while maintaining functionality. --- pnpm-lock.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0821d54c..b7cdb47b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4125,7 +4125,6 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -4236,7 +4235,6 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} From 21f2686a0748da81ca9397b1161b5b0ed1b9c558 Mon Sep 17 00:00:00 2001 From: Giovanni Ravalico <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 01:37:41 +0100 Subject: [PATCH 14/21] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/web/server/server.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/web/server/server.md b/apps/web/server/server.md index 07ff1c5f..f7b49285 100644 --- a/apps/web/server/server.md +++ b/apps/web/server/server.md @@ -1,49 +1,60 @@ # Server Setup Documentation ## Overview + The server is an Express application that serves a React Router build. It supports both development and production environments, dynamically configuring the server port and host. ## Key Components ### Configuration (`Config` class) + - Manages environment variables and server build path. - Uses `effect` library for schema validation. ### Source Map Support + - Uses `source-map-support` to enhance error stack traces. ### Express Application + - Uses `compression` for response compression. - Uses `morgan` for HTTP request logging. - Serves static files and handles all incoming requests. ### Development Mode + - Uses `vite` for development server with middleware mode. - Dynamically loads and handles requests using Vite's SSR capabilities. ### Production Mode + - Serves static assets with caching. - Loads the server build from the specified path. ### Request Handling + - Uses `react-router` for handling requests and responses. - Custom request handler created using `react-router-express`. ## Middleware Behavior ### Compression Middleware (`compression`) + - Compresses response bodies for all requests. - Adds `Content-Encoding` headers and compresses the body. ### Morgan Middleware (`morgan`) + - Logs HTTP requests. - Does not modify the `Request` or `Response` but logs details like method, URL, status, and response time. ### Static File Serving (`express.static`) + - Serves static files from the specified directories. - Adds appropriate headers (e.g., `Cache-Control`) and serves files. ### Vite Dev Server Middleware (`viteDevServer.middlewares`) + - Handles requests in development mode. - Attaches custom middlewares and acts as a handler function for custom HTTP servers. - Provides features like HMR, module loading, and development-specific optimizations. @@ -57,9 +68,11 @@ The server is an Express application that serves a React Router build. It suppor ## `viteDevServer.ssrLoadModule('./server/app.ts')` and `app.ts` ### `ssrLoadModule` + - Loads a given URL as an instantiated module for SSR. ### `app.ts` + - Sets up the Express application and request handler. - Defines a request handler using `createRequestHandler` from `react-router-express`. - Configures the request handler to load the server build and provide context values. @@ -72,15 +85,19 @@ The server is an Express application that serves a React Router build. It suppor ## Additional Details ### Configuration + - Dynamically configures the port and host based on environment variables or default settings. ### Source Map Support + - Enhances error stack traces by retrieving source maps for better debugging. ### Request Handling + - Uses `react-router` to handle requests and responses, providing SSR capabilities. ## External Dependencies + - `compression`: Middleware for response compression. - `effect`: Library for schema validation. - `express`: Web framework for building the server. @@ -93,6 +110,7 @@ The server is an Express application that serves a React Router build. It suppor # Unit Tests ## Objectives + - Validate the configuration schema and default values. - Ensure the server starts correctly in both development and production modes. - Verify middleware and request handling logic. @@ -102,25 +120,31 @@ The server is an Express application that serves a React Router build. It suppor ## Test Cases ### Configuration Tests + - Validate default values for `NODE_ENV`, `PORT`, and `HOST`. - Test schema validation for different configurations. ### Server Initialization Tests + - Ensure the server starts on the specified port and host. - Verify the correct middleware is applied based on the environment. ### Middleware Tests + - Test `compression` middleware for response compression. - Verify `morgan` logs HTTP requests correctly. ### Request Handling Tests + - Test request handling in development mode using Vite. - Verify request handling in production mode with static file serving. ### Static File Serving Tests + - Ensure static assets are served with correct caching headers. - Test serving of client-side assets from the build directory. ### Error Handling Tests + - Verify error handling and logging for different scenarios. - Test source map support for enhanced error stack traces. From 68b8b436846c8ba06c3468b9bdff512ca052342e Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 02:16:57 +0100 Subject: [PATCH 15/21] fix(web): set NODE_ENV to production in start script The "start" script now explicitly sets NODE_ENV to "production". This ensures the application runs in the correct environment, avoiding potential issues caused by an undefined NODE_ENV variable. --- apps/web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/package.json b/apps/web/package.json index 75e3dc68..71da2d06 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -74,7 +74,7 @@ "format": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true .", "format:write": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true --write .", "lint": "biome ci --vcs-enabled=true --vcs-use-ignore-file=true --vcs-root='../../' --formatter-enabled=false --linter-enabled=true --organize-imports-enabled=false --no-errors-on-unmatched .", - "start": "node --experimental-strip-types --experimental-transform-types server/server.ts build/server/index.js", + "start": "NODE_ENV=production node --experimental-strip-types --experimental-transform-types server/server.ts build/server/index.js", "test": "vitest", "test:unit": "vitest", "typecheck": "react-router typegen && tsc" From ce049cd47922529c89cd9ca6670779438edbe3dc Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 02:51:45 +0100 Subject: [PATCH 16/21] feat(web): add environment-specific .env files Introduce `.development.env` and `.production.env` for clearer environment separation. Updated the `start` script to use ` .production.env` for better maintainability and reduced risk of configuration errors. --- apps/web/.development.env | 1 + apps/web/.production.env | 1 + apps/web/package.json | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 apps/web/.development.env create mode 100644 apps/web/.production.env diff --git a/apps/web/.development.env b/apps/web/.development.env new file mode 100644 index 00000000..c0d66521 --- /dev/null +++ b/apps/web/.development.env @@ -0,0 +1 @@ +NODE_ENV=development diff --git a/apps/web/.production.env b/apps/web/.production.env new file mode 100644 index 00000000..cbde1cca --- /dev/null +++ b/apps/web/.production.env @@ -0,0 +1 @@ +NODE_ENV=production diff --git a/apps/web/package.json b/apps/web/package.json index 71da2d06..3a897c71 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -74,7 +74,7 @@ "format": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true .", "format:write": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true --write .", "lint": "biome ci --vcs-enabled=true --vcs-use-ignore-file=true --vcs-root='../../' --formatter-enabled=false --linter-enabled=true --organize-imports-enabled=false --no-errors-on-unmatched .", - "start": "NODE_ENV=production node --experimental-strip-types --experimental-transform-types server/server.ts build/server/index.js", + "start": "node --env-file=.production.env --experimental-strip-types --experimental-transform-types server/server.ts build/server/index.js", "test": "vitest", "test:unit": "vitest", "typecheck": "react-router typegen && tsc" From eb2ca75416b44561af8b2b68ad68a79fb2ba7cb1 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 04:12:56 +0100 Subject: [PATCH 17/21] chore(web): adjust Node.js options in scripts and env files Updated `NODE_OPTIONS` in environment files to improve debugging and experimental feature usage. Simplified `dev` and `start` scripts by removing redundant options and ensuring consistency across configurations. --- apps/web/.development.env | 1 + apps/web/.production.env | 1 + apps/web/package.json | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/web/.development.env b/apps/web/.development.env index c0d66521..bb8be2c4 100644 --- a/apps/web/.development.env +++ b/apps/web/.development.env @@ -1 +1,2 @@ NODE_ENV=development +NODE_OPTIONS="--trace-warnings --inspect --experimental-strip-types --experimental-transform-types --watch-preserve-output" diff --git a/apps/web/.production.env b/apps/web/.production.env index cbde1cca..8a2eb077 100644 --- a/apps/web/.production.env +++ b/apps/web/.production.env @@ -1 +1,2 @@ NODE_ENV=production +NODE_OPTIONS=--trace-warnings --inspect --experimental-strip-types --experimental-transform-types diff --git a/apps/web/package.json b/apps/web/package.json index 3a897c71..552caaa4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -70,11 +70,11 @@ "check": "biome check .", "check:fix": "biome check --apply .", "clean": "scripty", - "dev": "NODE_ENV=development node --watch-path=./server --watch-preserve-output --inspect --experimental-network-inspection --experimental-strip-types --experimental-transform-types --trace-warnings server/server.ts build/server/index.js", + "dev": "node --env-file=.development.env --experimental-network-inspection server/server.ts build/server/index.js", "format": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true .", "format:write": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true --write .", "lint": "biome ci --vcs-enabled=true --vcs-use-ignore-file=true --vcs-root='../../' --formatter-enabled=false --linter-enabled=true --organize-imports-enabled=false --no-errors-on-unmatched .", - "start": "node --env-file=.production.env --experimental-strip-types --experimental-transform-types server/server.ts build/server/index.js", + "start": "node --env-file=.production.env server/server.ts build/server/index.js", "test": "vitest", "test:unit": "vitest", "typecheck": "react-router typegen && tsc" From 43fa3624fff48202974db5c19a0ee4b775a808ee Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 04:20:47 +0100 Subject: [PATCH 18/21] refactor(web): simplify server build configuration Removed redundant server build path argument and improved build configuration for production server setup. Updated scripts and TypeScript configuration to align with these changes. This streamlines the development and production workflows. --- apps/web/package.json | 4 ++-- apps/web/react-router.config.ts | 1 + apps/web/server/server.ts | 39 +++++++++++++++++++++------------ apps/web/tsconfig.node.json | 7 +++--- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 552caaa4..d7087ede 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -70,11 +70,11 @@ "check": "biome check .", "check:fix": "biome check --apply .", "clean": "scripty", - "dev": "node --env-file=.development.env --experimental-network-inspection server/server.ts build/server/index.js", + "dev": "node --env-file=.development.env --experimental-network-inspection server/server.ts", "format": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true .", "format:write": "biome check --vcs-enabled=true --vcs-use-ignore-file=true --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=true --write .", "lint": "biome ci --vcs-enabled=true --vcs-use-ignore-file=true --vcs-root='../../' --formatter-enabled=false --linter-enabled=true --organize-imports-enabled=false --no-errors-on-unmatched .", - "start": "node --env-file=.production.env server/server.ts build/server/index.js", + "start": "node --env-file=.production.env server/server.ts", "test": "vitest", "test:unit": "vitest", "typecheck": "react-router typegen && tsc" diff --git a/apps/web/react-router.config.ts b/apps/web/react-router.config.ts index 9a229aa4..1447e4e6 100644 --- a/apps/web/react-router.config.ts +++ b/apps/web/react-router.config.ts @@ -3,6 +3,7 @@ import type { Config } from '@react-router/dev/config' export default { appDirectory: 'src', serverModuleFormat: 'esm', + serverBuildFile: 'index.js', buildDirectory: 'build', ssr: true, future: { diff --git a/apps/web/server/server.ts b/apps/web/server/server.ts index 7bca4446..1c79f98d 100644 --- a/apps/web/server/server.ts +++ b/apps/web/server/server.ts @@ -25,10 +25,6 @@ class Config extends Schema.Class('Config')({ HOST: Schema.optional(Schema.String).annotations({ description: 'Host to run the server on', }), - buildPathArg: Schema.String.annotations({ - description: 'Path to the server build', - message: () => 'Usage: node server/server.ts ', - }), }) { static decodeUnknownPromise = Schema.decodeUnknownPromise(this) } @@ -50,6 +46,24 @@ sourceMapSupport.install({ }, }) +/** + * Retrieves the production server application instance. + * + * This method dynamically imports the required configuration and server files + * to set up and return the production server application. + * + * @return {Promise} A promise resolving to the Express application instance. + */ +async function getProductionServer(): Promise { + return import('../react-router.config.ts') + .then(mod => mod.default) + .then(({ buildDirectory, serverBuildFile }) => + path.resolve(path.join(buildDirectory, 'server', serverBuildFile)), + ) + .then(serverBuildPath => import(serverBuildPath)) + .then(mod => mod.app) +} + /** * Initiates and runs a server application for serving a React Router build. * The method performs the following operations: @@ -79,11 +93,7 @@ export async function run(): Promise { NODE_ENV, PORT: _port, HOST, - buildPathArg, - } = await Config.decodeUnknownPromise({ - ...process.env, - buildPathArg: process.argv[2], - }).catch((error: ParseResult.ParseError) => { + } = await Config.decodeUnknownPromise(process.env).catch((error: ParseResult.ParseError) => { console.error(error.message, error.cause) process.exit(1) }) @@ -108,12 +118,12 @@ export async function run(): Promise { ) app.use(viteDevServer.middlewares) + app.use(async (req, res, next) => { try { - const source = (await viteDevServer.ssrLoadModule('./server/app.ts')) as { - app: express.Express - } - return await source.app(req, res, next) + const source = await viteDevServer.ssrLoadModule('./server/app.ts') + // biome-ignore lint/complexity/useLiteralKeys: + return await source['app'](req, res, next) } catch (error: unknown) { if (typeof error === 'object' && error instanceof Error) { viteDevServer.ssrFixStacktrace(error) @@ -126,12 +136,13 @@ export async function run(): Promise { } case 'production': { console.log('Starting production server') + const handler = await getProductionServer() app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' })) app.use(express.static('build/client', { maxAge: '1h' })) - app.use(await import(path.resolve(buildPathArg)).then(mod => mod.app)) + app.use(handler) break } diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json index ce0e0e0c..55ec2429 100644 --- a/apps/web/tsconfig.node.json +++ b/apps/web/tsconfig.node.json @@ -11,10 +11,11 @@ }, "extends": ["./tsconfig.json", "@suddenlygiovanni/config-typescript/library.json"], "include": [ - "./vite.config.ts", "./handlers.ts", - "./vitest.setup.ts", + "./mocks/**/*.ts", + "./react-router.config.ts", "./server/**/*.ts", - "./mocks/**/*.ts" + "./vite.config.ts", + "./vitest.setup.ts" ] } From 7568e0e81dca738b7b0587fb58ec66a88e1b0473 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 04:34:29 +0100 Subject: [PATCH 19/21] test(web): remove obsolete test for missing build path argument Removed a test case that validated process.argv[2] for the server build path, as it is no longer relevant. Simplified imports for better readability and streamlined code structure. --- apps/web/server/server.spec.ts | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/apps/web/server/server.spec.ts b/apps/web/server/server.spec.ts index 5f90fb06..77d4875c 100644 --- a/apps/web/server/server.spec.ts +++ b/apps/web/server/server.spec.ts @@ -1,15 +1,4 @@ -import { - type MockInstance, - afterAll, - afterEach, - beforeEach, - describe, - expect, - it, - vi, -} from 'vitest' - -import { run } from './server.ts' +import { type MockInstance, afterAll, afterEach, beforeEach, describe, it, vi } from 'vitest' describe('server setup', () => { let consoleErrorSpy: MockInstance<(...args: unknown[]) => void> @@ -33,22 +22,6 @@ describe('server setup', () => { }) describe('configuration tests', () => { - it('should throw an error if process.argv[2] is empty', async () => { - // Act & Assert - await expect(run()).rejects.toThrow('process.exit(1)') - expect(consoleErrorSpy).toHaveBeenCalledWith( - ` -(Config (Encoded side) <-> Config) -└─ Encoded side transformation failure - └─ Config (Encoded side) - └─ Encoded side transformation failure - └─ Struct (Encoded side) - └─ ["buildPathArg"] - └─ Usage: node server/server.ts `.trimStart(), - undefined, - ) - }) - it.todo('should validate default values for NODE_ENV, PORT, and HOST') it.todo('should test schema validation for different configurations') From 075f338952fd83b8759fe0c1fab0dcba44d18770 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 05:06:19 +0100 Subject: [PATCH 20/21] fix(web): update Dockerfile to correct CMD instruction The CMD instruction incorrectly referenced a non-existent file. Removed the redundant "build/server/index.js" path. This ensures the container starts without errors. --- apps/web/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 490d84b4..356cd19f 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -83,4 +83,4 @@ USER node EXPOSE 5173 -CMD ["node", "--experimental-strip-types", "--experimental-transform-types", "server/server.ts", "build/server/index.js"] +CMD ["node", "--experimental-strip-types", "--experimental-transform-types", "server/server.ts"] From dcb300e3011522780d4bdf93b51a55fb186510d7 Mon Sep 17 00:00:00 2001 From: suddenlyGiovanni <15946771+suddenlyGiovanni@users.noreply.github.com> Date: Wed, 18 Dec 2024 05:28:01 +0100 Subject: [PATCH 21/21] feat(web/server): enhance middleware and add graceful shutdown Introduce compression, logger, and static file serving middleware for improved performance and logging. Add React Router middleware for development and production environments. Implement a graceful shutdown mechanism to handle termination signals safely. --- apps/web/server/server.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/web/server/server.ts b/apps/web/server/server.ts index 1c79f98d..9a0ae74c 100644 --- a/apps/web/server/server.ts +++ b/apps/web/server/server.ts @@ -105,6 +105,9 @@ export async function run(): Promise { app.disable('x-powered-by') + /** + * Add compression middleware + */ app.use(compression()) switch (NODE_ENV) { @@ -117,6 +120,9 @@ export async function run(): Promise { }), ) + /** + * Add React Router development middleware + */ app.use(viteDevServer.middlewares) app.use(async (req, res, next) => { @@ -138,10 +144,19 @@ export async function run(): Promise { console.log('Starting production server') const handler = await getProductionServer() + /** + * Serve assets files from build/client/assets + */ app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' })) + /** + * Serve public files + */ app.use(express.static('build/client', { maxAge: '1h' })) + /** + * Add React Router production middleware + */ app.use(handler) break @@ -150,6 +165,9 @@ export async function run(): Promise { throw new Error(`Unknown NODE_ENV: ${NODE_ENV}`) } + /** + * Add logger middleware + */ app.use(morgan('tiny')) const onListen = (): void => { @@ -166,8 +184,19 @@ export async function run(): Promise { } } - const server = HOST ? app.listen(PORT, HOST, onListen) : app.listen(PORT, onListen) - + const server: http.Server = HOST ? app.listen(PORT, HOST, onListen) : app.listen(PORT, onListen) + + /** + * Handles application shutdown gracefully upon receiving specific termination signals. + * + * This function listens for termination signals ('SIGTERM' or 'SIGINT') and performs + * necessary actions to close the server safely. It logs the received signal, proceeds + * to close the server, and exits the process with an appropriate exit code depending + * on whether the shutdown was successful or encountered errors. + * + * @param {('SIGTERM' | 'SIGINT')} signal The termination signal received, triggering the graceful shutdown process. + * @returns {void} Does not return a value. + */ const gracefulShutdown = (signal: 'SIGTERM' | 'SIGINT'): void => { console.log(`Received shutdown signal "${signal}", closing server gracefully...`) server?.close(err => {