From 346d651978090fdca90b3225fc9d7fa523165f25 Mon Sep 17 00:00:00 2001 From: onmax Date: Fri, 24 Jul 2026 15:17:31 +0700 Subject: [PATCH] fix: support Nitro 2 and 3 server runtimes --- .github/workflows/ci.yml | 19 +++++++++++++++++++ packages/script/src/module.ts | 16 +++++++++++++++- .../src/runtime/server/bluesky-embed.ts | 3 +-- .../server/google-maps-geocode-proxy.ts | 1 - .../server/google-static-maps-proxy.ts | 1 - .../src/runtime/server/gravatar-proxy.ts | 1 - .../src/runtime/server/instagram-embed.ts | 3 +-- .../src/runtime/server/proxy-handler.ts | 1 - .../runtime/server/utils/cached-upstream.ts | 1 - .../src/runtime/server/utils/withSigning.ts | 3 +-- packages/script/src/runtime/server/x-embed.ts | 3 +-- test/e2e/proxy-alias.test.ts | 1 + test/fixtures/proxy-alias/nuxt.config.ts | 1 + test/unit/__mocks__/stub-nitro-runtime.ts | 6 ++++++ test/unit/cached-upstream.test.ts | 5 +++-- test/unit/proxy-handler-alias.test.ts | 7 ++++--- test/unit/proxy-handler-body.test.ts | 7 ++++--- test/unit/proxy-handler-hop-by-hop.test.ts | 7 ++++--- test/unit/with-signing.test.ts | 7 ++++--- 19 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 test/unit/__mocks__/stub-nitro-runtime.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6703e9942..ad4e51ce4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,3 +83,22 @@ jobs: - run: pnpm run --if-present prepare:fixtures - run: pnpm vitest run --project typecheck --project unit --project e2e --project nuxt-runtime + + nitro-3: + runs-on: ubuntu-24.04-arm + name: Nitro 3 compatibility + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + cache: pnpm + - run: pnpm i + - name: Install Nuxt 5 + run: | + pnpm add --workspace-root --save-dev "nuxt@npm:nuxt-nightly@5.0.0-29738334.1405742f" "@nuxt/kit@npm:@nuxt/kit-nightly@5.0.0-29738334.1405742f" + pnpm --filter @nuxt/scripts add --save-dev "@nuxt/kit@npm:@nuxt/kit-nightly@5.0.0-29738334.1405742f" + - run: pnpm --filter @nuxt/scripts dev:prepare + - run: pnpm exec nuxt prepare + - run: pnpm vitest run --project e2e test/e2e/proxy-alias.test.ts -t 'auto-injects|resolves' diff --git a/packages/script/src/module.ts b/packages/script/src/module.ts index cdc99f7c5..b7dd847a4 100644 --- a/packages/script/src/module.ts +++ b/packages/script/src/module.ts @@ -16,6 +16,7 @@ import type { } from './runtime/types' import { randomBytes } from 'node:crypto' import { appendFileSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs' +import { pathToFileURL } from 'node:url' import { addBuildPlugin, addComponentsDir, @@ -23,13 +24,16 @@ import { addPlugin, addPluginTemplate, addServerHandler, + addServerImports, addTemplate, createResolver, defineNuxtModule, + getNuxtVersion, hasNuxtModule, + resolvePath as resolveNuxtPath, } from '@nuxt/kit' import { defu } from 'defu' -import { resolve as resolvePath_ } from 'pathe' +import { dirname, resolve as resolvePath_ } from 'pathe' import { readPackageJSON } from 'pkg-types' import { setupPublicAssetStrategy } from './assets' import { buildDevtoolsData, buildDevtoolsEntry, setupDevtools } from './devtools' @@ -514,6 +518,16 @@ export default defineNuxtModule({ logger.debug('The module is disabled, skipping setup.') return } + if (Number.parseInt(getNuxtVersion(nuxt), 10) >= 5) { + const nuxtDir = dirname(await resolveNuxtPath('nuxt/package.json')) + const nitroDir = dirname(await resolveNuxtPath('@nuxt/nitro-server/package.json', { cwd: nuxtDir })) + const resolveNitroImport = async (id: string) => pathToFileURL(await resolveNuxtPath(id, { cwd: nitroDir })).href + addServerImports([ + { name: 'useNitroApp', from: await resolveNitroImport('nitro/app') }, + { name: 'defineCachedFunction', from: await resolveNitroImport('nitro/cache') }, + { name: 'useRuntimeConfig', from: await resolveNitroImport('nitro/runtime-config') }, + ]) + } if (nuxt.options.dev) { setupDevtools(nuxt, { standalone: config._standaloneDevtools }) if (config._standaloneDevtools) { diff --git a/packages/script/src/runtime/server/bluesky-embed.ts b/packages/script/src/runtime/server/bluesky-embed.ts index 45090faa1..33a6bfb95 100644 --- a/packages/script/src/runtime/server/bluesky-embed.ts +++ b/packages/script/src/runtime/server/bluesky-embed.ts @@ -1,5 +1,4 @@ import { createError, defineEventHandler, getQuery, setHeader } from 'h3' -import { useRuntimeConfig } from 'nitropack/runtime' import { createCachedJsonFetch } from './utils/cached-upstream' import { rewriteBlueskyPostImages } from './utils/embed-rewriters' import { withSigning } from './utils/withSigning' @@ -115,7 +114,7 @@ export default withSigning(defineEventHandler(async (event) => { const handlerPath = event.path?.split('?')[0] || '' const prefix = handlerPath.replace(EMBED_BSKY_SUFFIX_RE, '') || '/_scripts' const imagePath = `${prefix}/embed/bluesky-image` - const secret = (useRuntimeConfig(event)['nuxt-scripts'] as { proxySecret?: string } | undefined)?.proxySecret + const secret = (useRuntimeConfig()['nuxt-scripts'] as { proxySecret?: string } | undefined)?.proxySecret rewriteBlueskyPostImages(post, imagePath, secret) // Cache for 10 minutes diff --git a/packages/script/src/runtime/server/google-maps-geocode-proxy.ts b/packages/script/src/runtime/server/google-maps-geocode-proxy.ts index 79df25499..2c10c7d12 100644 --- a/packages/script/src/runtime/server/google-maps-geocode-proxy.ts +++ b/packages/script/src/runtime/server/google-maps-geocode-proxy.ts @@ -1,5 +1,4 @@ import { createError, defineEventHandler, getQuery, setHeader } from 'h3' -import { useRuntimeConfig } from 'nitropack/runtime' import { withQuery } from 'ufo' import { createCachedJsonFetch } from './utils/cached-upstream' import { withSigning } from './utils/withSigning' diff --git a/packages/script/src/runtime/server/google-static-maps-proxy.ts b/packages/script/src/runtime/server/google-static-maps-proxy.ts index e79486f99..8e27ec1f7 100644 --- a/packages/script/src/runtime/server/google-static-maps-proxy.ts +++ b/packages/script/src/runtime/server/google-static-maps-proxy.ts @@ -1,5 +1,4 @@ import { createError, defineEventHandler, getQuery, setHeader } from 'h3' -import { useRuntimeConfig } from 'nitropack/runtime' import { withQuery } from 'ufo' import { createCachedBinaryFetch } from './utils/cached-upstream' import { PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_PARAM } from './utils/sign-constants' diff --git a/packages/script/src/runtime/server/gravatar-proxy.ts b/packages/script/src/runtime/server/gravatar-proxy.ts index 693ae3a59..b4dcdfb17 100644 --- a/packages/script/src/runtime/server/gravatar-proxy.ts +++ b/packages/script/src/runtime/server/gravatar-proxy.ts @@ -1,5 +1,4 @@ import { createError, defineEventHandler, getQuery, setHeader } from 'h3' -import { useRuntimeConfig } from 'nitropack/runtime' import { withQuery } from 'ufo' import { createCachedBinaryFetch } from './utils/cached-upstream' import { withSigning } from './utils/withSigning' diff --git a/packages/script/src/runtime/server/instagram-embed.ts b/packages/script/src/runtime/server/instagram-embed.ts index 56d207bda..0bc22639f 100644 --- a/packages/script/src/runtime/server/instagram-embed.ts +++ b/packages/script/src/runtime/server/instagram-embed.ts @@ -1,5 +1,4 @@ import { createError, defineEventHandler, getQuery, setHeader } from 'h3' -import { defineCachedFunction, useRuntimeConfig } from 'nitropack/runtime' import { $fetch } from 'ofetch' import { hash } from 'ohash' import { ELEMENT_NODE, parse, renderSync, TEXT_NODE, walkSync } from 'ultrahtml' @@ -70,7 +69,7 @@ export default withSigning(defineEventHandler(async (event) => { // The route is registered as `/embed/instagram`, so strip `/embed/instagram`. const handlerPath = event.path?.split('?')[0] || '' const prefix = handlerPath.replace(EMBED_INSTAGRAM_SUFFIX_RE, '') || '/_scripts' - const secret = (useRuntimeConfig(event)['nuxt-scripts'] as { proxySecret?: string } | undefined)?.proxySecret + const secret = (useRuntimeConfig()['nuxt-scripts'] as { proxySecret?: string } | undefined)?.proxySecret const query = getQuery(event) const postUrl = query.url as string diff --git a/packages/script/src/runtime/server/proxy-handler.ts b/packages/script/src/runtime/server/proxy-handler.ts index 5c272a30b..4730939a9 100644 --- a/packages/script/src/runtime/server/proxy-handler.ts +++ b/packages/script/src/runtime/server/proxy-handler.ts @@ -1,6 +1,5 @@ import type { ProxyPrivacyInput, ResolvedProxyPrivacy } from './utils/privacy' import { createError, defineEventHandler, getHeaders, getQuery, getRequestIP, getRequestWebStream, readBody, readRawBody, setResponseHeader, setResponseStatus } from 'h3' -import { useNitroApp, useRuntimeConfig } from 'nitropack/runtime' import { matchDomain } from './utils/match-domain' import { anonymizeIP, diff --git a/packages/script/src/runtime/server/utils/cached-upstream.ts b/packages/script/src/runtime/server/utils/cached-upstream.ts index 931d66f23..ee027d839 100644 --- a/packages/script/src/runtime/server/utils/cached-upstream.ts +++ b/packages/script/src/runtime/server/utils/cached-upstream.ts @@ -1,5 +1,4 @@ import { Buffer } from 'node:buffer' -import { defineCachedFunction } from 'nitropack/runtime' import { $fetch } from 'ofetch' import { hash } from 'ohash' diff --git a/packages/script/src/runtime/server/utils/withSigning.ts b/packages/script/src/runtime/server/utils/withSigning.ts index 495362226..3dc0f99a4 100644 --- a/packages/script/src/runtime/server/utils/withSigning.ts +++ b/packages/script/src/runtime/server/utils/withSigning.ts @@ -22,14 +22,13 @@ import type { EventHandler, EventHandlerRequest, EventHandlerResponse } from 'h3' import { createError, defineEventHandler } from 'h3' -import { useRuntimeConfig } from 'nitropack/runtime' import { verifyProxyRequest } from './sign' export function withSigning( handler: EventHandler, ) { return defineEventHandler(async (event) => { - const runtimeConfig = useRuntimeConfig(event) + const runtimeConfig = useRuntimeConfig() const scriptsConfig = runtimeConfig['nuxt-scripts'] as { proxySecret?: string, pageTokenMaxAge?: number } | undefined const secret = scriptsConfig?.proxySecret diff --git a/packages/script/src/runtime/server/x-embed.ts b/packages/script/src/runtime/server/x-embed.ts index bca7c8bd4..821e972d5 100644 --- a/packages/script/src/runtime/server/x-embed.ts +++ b/packages/script/src/runtime/server/x-embed.ts @@ -1,5 +1,4 @@ import { createError, defineEventHandler, getQuery, setHeader } from 'h3' -import { useRuntimeConfig } from 'nitropack/runtime' import { createCachedJsonFetch } from './utils/cached-upstream' import { rewriteTweetImages } from './utils/embed-rewriters' import { withSigning } from './utils/withSigning' @@ -104,7 +103,7 @@ export default withSigning(defineEventHandler(async (event) => { const handlerPath = event.path?.split('?')[0] || '' const prefix = handlerPath.replace(EMBED_X_SUFFIX_RE, '') || '/_scripts' const imagePath = `${prefix}/embed/x-image` - const secret = (useRuntimeConfig(event)['nuxt-scripts'] as { proxySecret?: string } | undefined)?.proxySecret + const secret = (useRuntimeConfig()['nuxt-scripts'] as { proxySecret?: string } | undefined)?.proxySecret rewriteTweetImages(tweetData, imagePath, secret) // Cache for 10 minutes diff --git a/test/e2e/proxy-alias.test.ts b/test/e2e/proxy-alias.test.ts index 11e2351b4..b2ced7cae 100644 --- a/test/e2e/proxy-alias.test.ts +++ b/test/e2e/proxy-alias.test.ts @@ -38,5 +38,6 @@ describe('proxy path aliases', () => { // genuine test failure; a resolved alias always yields an HTTP response. expect(res).not.toBeNull() expect(res!.status).not.toBe(403) + expect(res!.status).not.toBe(500) }, 30000) }) diff --git a/test/fixtures/proxy-alias/nuxt.config.ts b/test/fixtures/proxy-alias/nuxt.config.ts index 3649889b6..f4f3f4091 100644 --- a/test/fixtures/proxy-alias/nuxt.config.ts +++ b/test/fixtures/proxy-alias/nuxt.config.ts @@ -20,6 +20,7 @@ export default defineNuxtConfig({ scripts: { registry: { + instagramEmbed: {}, plausibleAnalytics: { domain: 'example.com' }, }, proxy: { diff --git a/test/unit/__mocks__/stub-nitro-runtime.ts b/test/unit/__mocks__/stub-nitro-runtime.ts new file mode 100644 index 000000000..ddeef2854 --- /dev/null +++ b/test/unit/__mocks__/stub-nitro-runtime.ts @@ -0,0 +1,6 @@ +import { vi } from 'vitest' + +export function stubNitroRuntime(stubs: Record) { + for (const [name, stub] of Object.entries(stubs)) + vi.stubGlobal(name, stub) +} diff --git a/test/unit/cached-upstream.test.ts b/test/unit/cached-upstream.test.ts index b69ccebf6..289b1d95c 100644 --- a/test/unit/cached-upstream.test.ts +++ b/test/unit/cached-upstream.test.ts @@ -1,16 +1,17 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import { stubNitroRuntime } from './__mocks__/stub-nitro-runtime' const { cacheDefinitions, hashMock } = vi.hoisted(() => ({ cacheDefinitions: [] as Array<{ getKey?: (...args: any[]) => string }>, hashMock: vi.fn((value: unknown) => `hashed:${JSON.stringify(value)}`), })) -vi.mock('nitropack/runtime', () => ({ +stubNitroRuntime({ defineCachedFunction: vi.fn((handler, options) => { cacheDefinitions.push(options) return handler }), -})) +}) vi.mock('ohash', () => ({ hash: hashMock, diff --git a/test/unit/proxy-handler-alias.test.ts b/test/unit/proxy-handler-alias.test.ts index 4b5be523b..c718efafa 100644 --- a/test/unit/proxy-handler-alias.test.ts +++ b/test/unit/proxy-handler-alias.test.ts @@ -1,7 +1,8 @@ import type { Server } from 'node:http' import { createServer } from 'node:http' import { createApp, toNodeListener } from 'h3' -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' +import { stubNitroRuntime } from './__mocks__/stub-nitro-runtime' /** * Issue #814: proxy paths may use opaque/custom aliases instead of the verbatim @@ -9,7 +10,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vites * validating the allowlist and forwarding upstream. */ -vi.mock('nitropack/runtime', () => ({ +stubNitroRuntime({ useRuntimeConfig: () => ({ 'nuxt-scripts-proxy': { proxyPrefix: '/_scripts/p', @@ -26,7 +27,7 @@ vi.mock('nitropack/runtime', () => ({ useNitroApp: () => ({ hooks: { callHook: async () => {} }, }), -})) +}) describe('proxy handler - path aliases (#814)', () => { let proxyServer: Server diff --git a/test/unit/proxy-handler-body.test.ts b/test/unit/proxy-handler-body.test.ts index f1932946f..079720810 100644 --- a/test/unit/proxy-handler-body.test.ts +++ b/test/unit/proxy-handler-body.test.ts @@ -2,10 +2,11 @@ import type { Server } from 'node:http' import { createServer } from 'node:http' import { gzipSync } from 'node:zlib' import { createApp, defineEventHandler, readRawBody, toNodeListener } from 'h3' -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' import proxyHandler from '../../packages/script/src/runtime/server/proxy-handler' +import { stubNitroRuntime } from './__mocks__/stub-nitro-runtime' -vi.mock('nitropack/runtime', () => ({ +stubNitroRuntime({ useRuntimeConfig: () => ({ 'nuxt-scripts-proxy': { proxyPrefix: '/_scripts/p', @@ -18,7 +19,7 @@ vi.mock('nitropack/runtime', () => ({ useNitroApp: () => ({ hooks: { callHook: async () => {} }, }), -})) +}) describe('proxy handler request bodies (#836)', () => { let upstreamServer: Server diff --git a/test/unit/proxy-handler-hop-by-hop.test.ts b/test/unit/proxy-handler-hop-by-hop.test.ts index 7ddaf12b1..f0a48f5bd 100644 --- a/test/unit/proxy-handler-hop-by-hop.test.ts +++ b/test/unit/proxy-handler-hop-by-hop.test.ts @@ -1,7 +1,8 @@ import type { Server } from 'node:http' import { createServer, request as httpRequest } from 'node:http' import { createApp, toNodeListener } from 'h3' -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' +import { stubNitroRuntime } from './__mocks__/stub-nitro-runtime' /** * Tests for #791: proxy handler must strip hop-by-hop request headers per RFC 7230 §6.1. @@ -13,7 +14,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vites * Additionally, any header named in the `Connection` header value must also be stripped. */ -vi.mock('nitropack/runtime', () => ({ +stubNitroRuntime({ useRuntimeConfig: () => ({ 'nuxt-scripts-proxy': { proxyPrefix: '/_scripts/p', @@ -27,7 +28,7 @@ vi.mock('nitropack/runtime', () => ({ useNitroApp: () => ({ hooks: { callHook: async () => {} }, }), -})) +}) describe('proxy handler - hop-by-hop request headers (#791)', () => { let proxyServer: Server diff --git a/test/unit/with-signing.test.ts b/test/unit/with-signing.test.ts index 151144d4b..9036d9536 100644 --- a/test/unit/with-signing.test.ts +++ b/test/unit/with-signing.test.ts @@ -8,6 +8,7 @@ import { PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, } from '../../packages/script/src/runtime/server/utils/sign' +import { stubNitroRuntime } from './__mocks__/stub-nitro-runtime' // Hoisted runtime config mock — swapped between tests via `runtimeConfigMock`. const { runtimeConfigMock } = vi.hoisted(() => ({ @@ -16,11 +17,11 @@ const { runtimeConfigMock } = vi.hoisted(() => ({ }, })) -vi.mock('nitropack/runtime', () => ({ +stubNitroRuntime({ useRuntimeConfig: () => runtimeConfigMock.current, -})) +}) -// Import AFTER vi.mock so withSigning resolves against the mocked module. +// Import after installing the runtime config stub. const { withSigning } = await import('../../packages/script/src/runtime/server/utils/withSigning') const SECRET = 'with-signing-test-secret'