fix: failing type errors in build (#38761)

* fix(www): types for awaited params

* fix(docs): no extraneous exports from route page

* fix(studio): api handler types, no non-handlers allowed in pages
This commit is contained in:
Charis authored and GitHub committed 2025-09-17 15:55:38 +10:00
1 parent 872a372119
commit 283b125369
16 files changed
+141 -117

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ import { headers } from 'next/headers'
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest'
import { _handleRevalidateRequest } from './route'
import { _handleRevalidateRequest } from './route.utils'
// Mock Next.js modules
vi.mock('next/cache', () => ({
+1 -101
View File
@@ -1,109 +1,9 @@
import { createClient } from '@supabase/supabase-js'
import { type Database } from 'common'
import { revalidateTag } from 'next/cache'
import { headers } from 'next/headers'
import { type NextRequest } from 'next/server'
import { z } from 'zod'
import { VALID_REVALIDATION_TAGS } from '~/features/helpers.fetch'
enum AuthorizationLevel {
Unauthorized,
Basic,
Override,
}
const requestBodySchema = z.object({
tags: z.array(z.enum(VALID_REVALIDATION_TAGS)),
})
import { _handleRevalidateRequest } from './route.utils'
export const POST = handleError(_handleRevalidateRequest)
export async function _handleRevalidateRequest(request: NextRequest) {
const requestHeaders = await headers()
const authorization = requestHeaders.get('Authorization')
if (!authorization) {
return new Response('Missing Authorization header', { status: 401 })
}
const basicKeys = process.env.DOCS_REVALIDATION_KEYS?.split(/\s*,\s*/) ?? []
const overrideKeys = process.env.DOCS_REVALIDATION_OVERRIDE_KEYS?.split(/\s*,\s*/) ?? []
if (basicKeys.length === 0 && overrideKeys.length === 0) {
console.error('No keys configured for revalidation')
return new Response('Internal server error', {
status: 500,
})
}
let authorizationLevel = AuthorizationLevel.Unauthorized
const token = authorization.replace(/^Bearer /, '')
if (overrideKeys.includes(token)) {
authorizationLevel = AuthorizationLevel.Override
} else if (basicKeys.includes(token)) {
authorizationLevel = AuthorizationLevel.Basic
}
if (authorizationLevel === AuthorizationLevel.Unauthorized) {
return new Response('Invalid Authorization header', { status: 401 })
}
let result: z.infer<typeof requestBodySchema>
try {
result = requestBodySchema.parse(await request.json())
} catch (error) {
console.error(error)
return new Response(
'Malformed request body: should be a JSON object with a "tags" array of strings.',
{ status: 400 }
)
}
const supabaseAdmin = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!
)
if (authorizationLevel === AuthorizationLevel.Basic) {
const { data: lastRevalidation, error } = await supabaseAdmin.rpc(
'get_last_revalidation_for_tags',
{
tags: result.tags,
}
)
if (error) {
console.error(error)
return new Response('Internal server error', { status: 500 })
}
const sixHoursAgo = new Date()
sixHoursAgo.setHours(sixHoursAgo.getHours() - 6)
if (lastRevalidation?.some((revalidation) => new Date(revalidation.created_at) > sixHoursAgo)) {
return new Response(
'Your request includes a tag that has been revalidated within the last 6 hours. You can override this limit by authenticating with Override permissions.',
{
status: 429,
}
)
}
}
const { error } = await supabaseAdmin
.from('validation_history')
.insert(result.tags.map((tag) => ({ tag })))
if (error) {
console.error('Failed to update revalidation table: %o', error)
}
result.tags.forEach((tag) => {
revalidateTag(tag)
})
return new Response(null, {
status: 204,
headers: {
'Cache-Control': 'no-cache',
},
})
}
function handleError(handleRequest: (request: NextRequest) => Promise<Response>) {
return async function (request: NextRequest) {
try {
+103
View File
@@ -0,0 +1,103 @@
import { createClient } from '@supabase/supabase-js'
import { type Database } from 'common'
import { revalidateTag } from 'next/cache'
import { headers } from 'next/headers'
import { type NextRequest } from 'next/server'
import { z } from 'zod'
import { VALID_REVALIDATION_TAGS } from '~/features/helpers.fetch'
enum AuthorizationLevel {
Unauthorized,
Basic,
Override,
}
const requestBodySchema = z.object({
tags: z.array(z.enum(VALID_REVALIDATION_TAGS)),
})
export async function _handleRevalidateRequest(request: NextRequest) {
const requestHeaders = await headers()
const authorization = requestHeaders.get('Authorization')
if (!authorization) {
return new Response('Missing Authorization header', { status: 401 })
}
const basicKeys = process.env.DOCS_REVALIDATION_KEYS?.split(/\s*,\s*/) ?? []
const overrideKeys = process.env.DOCS_REVALIDATION_OVERRIDE_KEYS?.split(/\s*,\s*/) ?? []
if (basicKeys.length === 0 && overrideKeys.length === 0) {
console.error('No keys configured for revalidation')
return new Response('Internal server error', {
status: 500,
})
}
let authorizationLevel = AuthorizationLevel.Unauthorized
const token = authorization.replace(/^Bearer /, '')
if (overrideKeys.includes(token)) {
authorizationLevel = AuthorizationLevel.Override
} else if (basicKeys.includes(token)) {
authorizationLevel = AuthorizationLevel.Basic
}
if (authorizationLevel === AuthorizationLevel.Unauthorized) {
return new Response('Invalid Authorization header', { status: 401 })
}
let result: z.infer<typeof requestBodySchema>
try {
result = requestBodySchema.parse(await request.json())
} catch (error) {
console.error(error)
return new Response(
'Malformed request body: should be a JSON object with a "tags" array of strings.',
{ status: 400 }
)
}
const supabaseAdmin = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!
)
if (authorizationLevel === AuthorizationLevel.Basic) {
const { data: lastRevalidation, error } = await supabaseAdmin.rpc(
'get_last_revalidation_for_tags',
{
tags: result.tags,
}
)
if (error) {
console.error(error)
return new Response('Internal server error', { status: 500 })
}
const sixHoursAgo = new Date()
sixHoursAgo.setHours(sixHoursAgo.getHours() - 6)
if (lastRevalidation?.some((revalidation) => new Date(revalidation.created_at) > sixHoursAgo)) {
return new Response(
'Your request includes a tag that has been revalidated within the last 6 hours. You can override this limit by authenticating with Override permissions.',
{
status: 429,
}
)
}
}
const { error } = await supabaseAdmin
.from('validation_history')
.insert(result.tags.map((tag) => ({ tag })))
if (error) {
console.error('Failed to update revalidation table: %o', error)
}
result.tags.forEach((tag) => {
revalidateTag(tag)
})
return new Response(null, {
status: 204,
headers: {
'Cache-Control': 'no-cache',
},
})
}
+3 -3
View File
@@ -1,4 +1,4 @@
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import type { NextApiRequest, NextApiResponse } from 'next'
import { ResponseError, ResponseFailure } from 'types'
import { IS_PLATFORM } from '../constants'
@@ -26,9 +26,9 @@ export function isResponseOk<T>(response: T | ResponseFailure | undefined): resp
export default async function apiWrapper(
req: NextApiRequest,
res: NextApiResponse,
handler: NextApiHandler,
handler: (req: NextApiRequest, res: NextApiResponse) => Promise<Response | void>,
options?: { withAuth: boolean }
) {
): Promise<Response | void> {
try {
const { withAuth } = options || {}
File renamed without changes.
+1
View File
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
@@ -1,7 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next'
import apiWrapper from 'lib/api/apiWrapper'
import { DEFAULT_PROJECT } from '../../constants'
import { DEFAULT_PROJECT } from 'lib/constants/api'
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
@@ -1,6 +1,6 @@
import apiWrapper from 'lib/api/apiWrapper'
import { NextApiRequest, NextApiResponse } from 'next'
import { PROJECT_ANALYTICS_URL } from 'pages/api/constants'
import { PROJECT_ANALYTICS_URL } from 'lib/constants/api'
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
@@ -2,7 +2,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { paths } from 'api-types'
import apiWrapper from 'lib/api/apiWrapper'
import { PROJECT_REST_URL } from 'pages/api/constants'
import { PROJECT_REST_URL } from 'lib/constants/api'
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
@@ -1,7 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next'
import apiWrapper from 'lib/api/apiWrapper'
import { DEFAULT_PROJECT, PROJECT_REST_URL } from 'pages/api/constants'
import { DEFAULT_PROJECT, PROJECT_REST_URL } from 'lib/constants/api'
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
@@ -2,7 +2,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { components } from 'api-types'
import apiWrapper from 'lib/api/apiWrapper'
import { PROJECT_ENDPOINT, PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants'
import { PROJECT_ENDPOINT, PROJECT_ENDPOINT_PROTOCOL } from 'lib/constants/api'
type ProjectAppConfig = components['schemas']['ProjectSettingsResponse']['app_config'] & {
protocol?: string
@@ -1,7 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next'
import apiWrapper from 'lib/api/apiWrapper'
import { DEFAULT_PROJECT } from '../../constants'
import { DEFAULT_PROJECT } from 'lib/constants/api'
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
@@ -6,7 +6,7 @@ import {
PROJECT_ENDPOINT,
PROJECT_ENDPOINT_PROTOCOL,
PROJECT_REST_URL,
} from 'pages/api/constants'
} from 'lib/constants/api'
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
@@ -1,7 +1,7 @@
import { NextApiRequest, NextApiResponse } from 'next'
import apiWrapper from 'lib/api/apiWrapper'
import { DEFAULT_PROJECT } from 'pages/api/constants'
import { DEFAULT_PROJECT } from 'lib/constants/api'
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
@@ -18,7 +18,13 @@ export async function generateStaticParams() {
export const revalidate = 30
export const dynamic = 'force-static'
export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {
export async function generateMetadata({
params: paramsPromise,
}: {
params: Promise<Params>
}): Promise<Metadata> {
const params = await paramsPromise
const capitalizedCategory = capitalize(params?.category.replaceAll('-', ' '))
return {
title: `Blog | ${capitalizedCategory}`,
@@ -26,7 +32,13 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
}
}
export default async function CategoriesPage({ params }: { params: Params }) {
export default async function CategoriesPage({
params: paramsPromise,
}: {
params: Promise<Params>
}) {
const params = await paramsPromise
const staticPosts = getSortedPosts({
directory: '_blog',
limit: 0,
+10 -2
View File
@@ -18,7 +18,13 @@ export async function generateStaticParams() {
export const revalidate = 30
export const dynamic = 'force-static'
export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {
export async function generateMetadata({
params: paramsPromise,
}: {
params: Promise<Params>
}): Promise<Metadata> {
const params = await paramsPromise
const capitalizedTag = capitalize(params?.tag.replaceAll('-', ' '))
return {
title: `Blog | ${capitalizedTag}`,
@@ -26,7 +32,9 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
}
}
export default async function TagPage({ params }: { params: Params }) {
export default async function TagPage({ params: paramsPromise }: { params: Promise<Params> }) {
const params = await paramsPromise
const staticPosts = getSortedPosts({ directory: '_blog', limit: 0, tags: [params.tag] })
const cmsPosts = await getAllCMSPosts({ tags: [params.tag] })
const blogs = [...(staticPosts as any[]), ...(cmsPosts as any[])] as unknown as PostTypes[]