diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b84c90e6e..65e9e2e000 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,50 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## v2.11.4 + +[compare changes](https://github.com/nitrojs/nitro/compare/v2.11.3...v2.11.4) + +### 🩹 Fixes + +- **dev:** Auto fallback to port if socket listening failed ([#3165](https://github.com/nitrojs/nitro/pull/3165)) +- **dev:** Polyfill `globalThis.crypto` for Node.js 18 ([#3166](https://github.com/nitrojs/nitro/pull/3166)) +- **cli:** Add `globalThis.crypto` polyfill for Node.js 18 ([#3167](https://github.com/nitrojs/nitro/pull/3167)) +- **dev:** Polyfill `globalThis.crypto` for Node.js 18 ([#3168](https://github.com/nitrojs/nitro/pull/3168)) +- **dev:** Try normal socket for CI ([a4569493](https://github.com/nitrojs/nitro/commit/a4569493)) + +### 📦 Build + +- Inline youch dependency ([#3169](https://github.com/nitrojs/nitro/pull/3169)) +- Use `youch-redist` ([#3172](https://github.com/nitrojs/nitro/pull/3172)) + +### ❤️ Contributors + +- Pooya Parsa ([@pi0](https://github.com/pi0)) + +## v2.11.3 + +[compare changes](https://github.com/nitrojs/nitro/compare/v2.11.2...v2.11.3) + +### 🩹 Fixes + +- **cloudflare:** Support `wrangler.jsonc` ([#3162](https://github.com/nitrojs/nitro/pull/3162)) + +### 💅 Refactors + +- Expose default error to custom handler ([#3161](https://github.com/nitrojs/nitro/pull/3161)) +- **dev:** Only show force close warn in debug mode ([06147e7a](https://github.com/nitrojs/nitro/commit/06147e7a)) + +### 🏡 Chore + +- **release:** V2.11.2 ([e21ce69d](https://github.com/nitrojs/nitro/commit/e21ce69d)) +- Update devcontainer config ([92f1a37a](https://github.com/nitrojs/nitro/commit/92f1a37a)) +- Lowercase header ([e3866d04](https://github.com/nitrojs/nitro/commit/e3866d04)) + +### ❤️ Contributors + +- Pooya Parsa ([@pi0](https://github.com/pi0)) + ## v2.11.2 [compare changes](https://github.com/nitrojs/nitro/compare/v2.11.1...v2.11.2) diff --git a/package.json b/package.json index f702da18d2..7e520598da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nitropack", - "version": "2.11.2", + "version": "2.11.4", "description": "Build and Deploy Universal JavaScript Servers", "repository": "nitrojs/nitro", "license": "MIT", @@ -170,8 +170,7 @@ "unstorage": "^1.15.0", "untyped": "^2.0.0", "unwasm": "^0.3.9", - "youch": "4.1.0-beta.5", - "youch-core": "^0.3.1" + "youch-redist": "4.1.0-beta.5-1" }, "devDependencies": { "@azure/functions": "^3.5.1", @@ -207,7 +206,9 @@ "unbuild": "^3.5.0", "undici": "^7.4.0", "vitest": "^3.0.7", - "xml2js": "^0.6.2" + "xml2js": "^0.6.2", + "youch": "^4.1.0-beta.5", + "youch-core": "^0.3.1" }, "peerDependencies": { "xml2js": "^0.6.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fa1b76d35..063ed2824e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,12 +227,9 @@ importers: unwasm: specifier: ^0.3.9 version: 0.3.9 - youch: - specifier: 4.1.0-beta.5 - version: 4.1.0-beta.5 - youch-core: - specifier: ^0.3.1 - version: 0.3.1 + youch-redist: + specifier: 4.1.0-beta.5-1 + version: 4.1.0-beta.5-1 devDependencies: '@azure/functions': specifier: ^3.5.1 @@ -336,6 +333,12 @@ importers: xml2js: specifier: ^0.6.2 version: 0.6.2 + youch: + specifier: ^4.1.0-beta.5 + version: 4.1.0-beta.5 + youch-core: + specifier: ^0.3.1 + version: 0.3.1 examples/api-routes: devDependencies: @@ -5897,6 +5900,9 @@ packages: resolution: {integrity: sha512-KOAmtABz17fgK+uBBJYIzaPpIgX+JgTRgY4t3zXH18akc5rRtFkRmcNTMCuSxLdbOJDY9+T/O3nyA/EQuN4EWA==} engines: {node: '>=20.6.0'} + youch-redist@4.1.0-beta.5-1: + resolution: {integrity: sha512-IlNgclEfelti10dh7yzGfQxhowEasVPr4zEN/At+uOzfWv74HiRx2Uz4gyvCRcez0dk1PDerl5aQhKxUKXKClg==} + youch@3.2.3: resolution: {integrity: sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw==} @@ -12219,6 +12225,8 @@ snapshots: '@poppinss/exception': 1.2.0 error-stack-parser-es: 0.1.5 + youch-redist@4.1.0-beta.5-1: {} + youch@3.2.3: dependencies: cookie: 0.5.0 diff --git a/src/cli/commands/build.ts b/src/cli/commands/build.ts index 343f6dd052..c6853ffd10 100644 --- a/src/cli/commands/build.ts +++ b/src/cli/commands/build.ts @@ -1,3 +1,4 @@ +import nodeCrypto from "node:crypto"; import { defineCommand } from "citty"; import type { DateString } from "compatx"; import { @@ -10,6 +11,11 @@ import { import { resolve } from "pathe"; import { commonArgs } from "../common"; +// globalThis.crypto support for Node.js 18 +if (!globalThis.crypto) { + globalThis.crypto = nodeCrypto as unknown as Crypto; +} + export default defineCommand({ meta: { name: "build", diff --git a/src/cli/commands/dev.ts b/src/cli/commands/dev.ts index 23b09caa2f..0f8e9c93b1 100644 --- a/src/cli/commands/dev.ts +++ b/src/cli/commands/dev.ts @@ -1,3 +1,4 @@ +import nodeCrypto from "node:crypto"; import { defineCommand } from "citty"; import { consola } from "consola"; import { getArgs, parseArgs } from "listhen/cli"; @@ -8,6 +9,11 @@ import { commonArgs } from "../common"; const hmrKeyRe = /^runtimeConfig\.|routeRules\./; +// globalThis.crypto support for Node.js 18 +if (!globalThis.crypto) { + globalThis.crypto = nodeCrypto as unknown as Crypto; +} + export default defineCommand({ meta: { name: "dev", diff --git a/src/presets/_nitro/runtime/nitro-dev.ts b/src/presets/_nitro/runtime/nitro-dev.ts index feb3b8fa5f..8390bd92f3 100644 --- a/src/presets/_nitro/runtime/nitro-dev.ts +++ b/src/presets/_nitro/runtime/nitro-dev.ts @@ -6,8 +6,10 @@ import { startScheduleRunner } from "nitropack/runtime/internal"; import { scheduledTasks, tasks } from "#nitro-internal-virtual/tasks"; import { Server } from "node:http"; import { join } from "node:path"; +import nodeCrypto from "node:crypto"; import { parentPort, threadId } from "node:worker_threads"; import wsAdapter from "crossws/adapters/node"; +import { isCI } from "std-env"; import { defineEventHandler, getQuery, @@ -16,15 +18,39 @@ import { toNodeListener, } from "h3"; +// globalThis.crypto support for Node.js 18 +if (!globalThis.crypto) { + globalThis.crypto = nodeCrypto as unknown as Crypto; +} + const { NITRO_NO_UNIX_SOCKET, NITRO_DEV_WORKER_DIR = ".", NITRO_DEV_WORKER_ID, } = process.env; +// Trap unhandled errors +trapUnhandledNodeErrors(); + +// Listen for shutdown signal from runner +parentPort?.on("message", (msg) => { + if (msg && msg.event === "shutdown") { + shutdown(); + } +}); + const nitroApp = useNitroApp(); const server = new Server(toNodeListener(nitroApp.h3App)); +let listener: Server | undefined; + +listen() + .catch(() => listen(true /* use random port */)) + // eslint-disable-next-line unicorn/prefer-top-level-await + .catch((error) => { + console.error("Dev worker failed to listen:", error); + return shutdown(); + }); // https://crossws.unjs.io/adapters/node if (import.meta._websocket) { @@ -32,39 +58,6 @@ if (import.meta._websocket) { server.on("upgrade", handleUpgrade); } -function getAddress() { - if (NITRO_NO_UNIX_SOCKET || process.versions.webcontainer) { - return 0; - } - - const socketName = `worker-${process.pid}-${threadId}-${Math.round(Math.random() * 10_000)}-${NITRO_DEV_WORKER_ID}.sock`; - const socketPath = join(NITRO_DEV_WORKER_DIR, socketName); - - switch (process.platform) { - case "win32": { - return join(String.raw`\\.\pipe\nitro`, socketPath); - } - case "linux": { - return `\0${socketPath}`; - } - default: { - return socketPath; - } - } -} - -const listenAddress = getAddress(); -const listener = server.listen(listenAddress, () => { - const _address = server.address(); - parentPort?.postMessage({ - event: "listen", - address: - typeof _address === "string" - ? { socketPath: _address } - : { host: "localhost", port: _address?.port }, - }); -}); - // Register tasks handlers nitroApp.router.get( "/_nitro/tasks", @@ -95,26 +88,57 @@ nitroApp.router.use( }) ); -// Trap unhandled errors -trapUnhandledNodeErrors(); +// Scheduled tasks +if (import.meta._tasks) { + startScheduleRunner(); +} -// Force shutdown -async function onShutdown() { - server.closeAllConnections?.(); - await Promise.all([ - new Promise((resolve) => listener.close(resolve)), - nitroApp.hooks.callHook("close").catch(console.error), - ]); +// --- utils --- + +function listen( + useRandomPort: boolean = Boolean( + NITRO_NO_UNIX_SOCKET || process.versions.webcontainer + ) +) { + return new Promise((resolve, reject) => { + try { + listener = server.listen(useRandomPort ? 0 : getSocketAddress(), () => { + const address = server.address(); + parentPort?.postMessage({ + event: "listen", + address: + typeof address === "string" + ? { socketPath: address } + : { host: "localhost", port: address?.port }, + }); + resolve(); + }); + } catch (error) { + reject(error); + } + }); } -parentPort?.on("message", async (msg) => { - if (msg && msg.event === "shutdown") { - await onShutdown(); - parentPort?.postMessage({ event: "exit" }); +function getSocketAddress() { + const socketName = `worker-${process.pid}-${threadId}-${Math.round(Math.random() * 10_000)}-${NITRO_DEV_WORKER_ID}.sock`; + // Windows: pipe + const socketPath = join(NITRO_DEV_WORKER_DIR, socketName); + if (process.platform === "win32") { + return join(String.raw`\\.\pipe\nitro`, socketPath); } -}); + // Linux: abstract namespace + if (process.platform === "linux" && !isCI) { + return `\0${socketPath}`; + } + // MacOS and CI: Unix socket + return socketPath; +} -// Scheduled tasks -if (import.meta._tasks) { - startScheduleRunner(); +async function shutdown() { + server.closeAllConnections?.(); + await Promise.all([ + new Promise((resolve) => listener?.close(resolve)), + nitroApp.hooks.callHook("close").catch(console.error), + ]); + parentPort?.postMessage({ event: "exit" }); } diff --git a/src/runtime/internal/error/dev.ts b/src/runtime/internal/error/dev.ts index d93d264570..85fd54d875 100644 --- a/src/runtime/internal/error/dev.ts +++ b/src/runtime/internal/error/dev.ts @@ -9,14 +9,22 @@ import { setResponseHeaders, setResponseStatus, } from "h3"; +import nodeCrypto from "node:crypto"; import { readFile } from "node:fs/promises"; import { resolve, dirname } from "node:path"; import consola from "consola"; -import { ErrorParser } from "youch-core"; -import { Youch } from "youch"; +import type { ErrorParser as ErrorParserT } from "youch-core"; +import type { Youch as YouchT } from "youch"; +// @ts-ignore +import * as _youch from "youch-redist"; import { SourceMapConsumer } from "source-map"; import { defineNitroErrorHandler, type InternalHandlerResponse } from "./utils"; +const { Youch, ErrorParser } = _youch as { + Youch: { new (): YouchT }; + ErrorParser: { new (): ErrorParserT }; +}; + export default defineNitroErrorHandler( async function defaultNitroErrorHandler(error, event) { const res = await defaultHandler(error, event); @@ -96,6 +104,11 @@ export async function defaultHandler( headers["cache-control"] = "no-cache"; } + // Crypto polyfill for Node.18 (used by youch > @poppinss+dumper) + if (!globalThis.crypto && !useJSON) { + globalThis.crypto = nodeCrypto as unknown as Crypto; + } + // Prepare body const body = useJSON ? { @@ -145,7 +158,7 @@ export async function loadStackTrace(error: any) { } } -type SourceLoader = Parameters[0]; +type SourceLoader = Parameters[0]; type StackFrame = Parameters[0]; async function sourceLoader(frame: StackFrame) { if (!frame.fileName || frame.fileType !== "fs" || frame.type === "native") {