Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
16 changes: 15 additions & 1 deletion packages/script/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@ 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,
addImports,
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'
Expand Down Expand Up @@ -514,6 +518,16 @@ export default defineNuxtModule<ModuleOptions>({
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) {
Expand Down
3 changes: 1 addition & 2 deletions packages/script/src/runtime/server/bluesky-embed.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
1 change: 0 additions & 1 deletion packages/script/src/runtime/server/gravatar-proxy.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
3 changes: 1 addition & 2 deletions packages/script/src/runtime/server/instagram-embed.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -70,7 +69,7 @@ export default withSigning(defineEventHandler(async (event) => {
// The route is registered as `<prefix>/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
Expand Down
1 change: 0 additions & 1 deletion packages/script/src/runtime/server/proxy-handler.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Buffer } from 'node:buffer'
import { defineCachedFunction } from 'nitropack/runtime'
import { $fetch } from 'ofetch'
import { hash } from 'ohash'

Expand Down
3 changes: 1 addition & 2 deletions packages/script/src/runtime/server/utils/withSigning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Req extends EventHandlerRequest = EventHandlerRequest, Res extends EventHandlerResponse = EventHandlerResponse>(
handler: EventHandler<Req, Res>,
) {
return defineEventHandler<Req>(async (event) => {
const runtimeConfig = useRuntimeConfig(event)
const runtimeConfig = useRuntimeConfig()
const scriptsConfig = runtimeConfig['nuxt-scripts'] as { proxySecret?: string, pageTokenMaxAge?: number } | undefined
const secret = scriptsConfig?.proxySecret

Expand Down
3 changes: 1 addition & 2 deletions packages/script/src/runtime/server/x-embed.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/e2e/proxy-alias.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
1 change: 1 addition & 0 deletions test/fixtures/proxy-alias/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default defineNuxtConfig({

scripts: {
registry: {
instagramEmbed: {},
plausibleAnalytics: { domain: 'example.com' },
},
proxy: {
Expand Down
6 changes: 6 additions & 0 deletions test/unit/__mocks__/stub-nitro-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { vi } from 'vitest'

export function stubNitroRuntime(stubs: Record<string, unknown>) {
for (const [name, stub] of Object.entries(stubs))
vi.stubGlobal(name, stub)
}
5 changes: 3 additions & 2 deletions test/unit/cached-upstream.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
7 changes: 4 additions & 3 deletions test/unit/proxy-handler-alias.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
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
* third-party hostname. The handler must resolve `alias β†’ real domain` before
* validating the allowlist and forwarding upstream.
*/

vi.mock('nitropack/runtime', () => ({
stubNitroRuntime({
useRuntimeConfig: () => ({
'nuxt-scripts-proxy': {
proxyPrefix: '/_scripts/p',
Expand All @@ -26,7 +27,7 @@ vi.mock('nitropack/runtime', () => ({
useNitroApp: () => ({
hooks: { callHook: async () => {} },
}),
}))
})

describe('proxy handler - path aliases (#814)', () => {
let proxyServer: Server
Expand Down
7 changes: 4 additions & 3 deletions test/unit/proxy-handler-body.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -18,7 +19,7 @@ vi.mock('nitropack/runtime', () => ({
useNitroApp: () => ({
hooks: { callHook: async () => {} },
}),
}))
})

describe('proxy handler request bodies (#836)', () => {
let upstreamServer: Server
Expand Down
7 changes: 4 additions & 3 deletions test/unit/proxy-handler-hop-by-hop.test.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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',
Expand All @@ -27,7 +28,7 @@ vi.mock('nitropack/runtime', () => ({
useNitroApp: () => ({
hooks: { callHook: async () => {} },
}),
}))
})

describe('proxy handler - hop-by-hop request headers (#791)', () => {
let proxyServer: Server
Expand Down
7 changes: 4 additions & 3 deletions test/unit/with-signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => ({
Expand All @@ -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'
Expand Down
Loading