diff --git a/.changeset/tiny-poems-battle.md b/.changeset/tiny-poems-battle.md new file mode 100644 index 000000000000..0f289996c3b9 --- /dev/null +++ b/.changeset/tiny-poems-battle.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes internal server error when calling an Astro Action without arguments on Vercel. diff --git a/eslint.config.js b/eslint.config.js index ab7efa3df15a..f12bf49d1f74 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -111,6 +111,8 @@ export default [ // In some cases, using explicit letter-casing is more performant than the `i` flag 'regexp/use-ignore-case': 'off', + 'regexp/prefer-regexp-exec': 'warn', + 'regexp/prefer-regexp-test': 'warn', }, }, diff --git a/packages/astro/astro-4.13.1.tgz b/packages/astro/astro-4.13.1.tgz new file mode 100644 index 000000000000..ec456aa56564 Binary files /dev/null and b/packages/astro/astro-4.13.1.tgz differ diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js index 2f1358548974..9087ac484ae5 100644 --- a/packages/astro/e2e/errors.test.js +++ b/packages/astro/e2e/errors.test.js @@ -88,7 +88,7 @@ test.describe('Error display', () => { expect(fileExists).toBeTruthy(); const fileContent = await astro.readFile(absoluteFileUrl); - const lineNumber = absoluteFileLocation.match(/:(\d+):\d+$/)[1]; + const lineNumber = /:(\d+):\d+$/.exec(absoluteFileLocation)[1]; const highlightedLine = fileContent.split('\n')[lineNumber - 1]; expect(highlightedLine).toContain(`@use '../styles/inexistent' as *;`); diff --git a/packages/astro/package.json b/packages/astro/package.json index 06fc4f57a448..54326bb43ff5 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -84,9 +84,6 @@ }, "./virtual-modules/*": "./dist/virtual-modules/*" }, - "imports": { - "#astro/*": "./dist/*.js" - }, "bin": { "astro": "astro.js" }, diff --git a/packages/astro/src/actions/runtime/middleware.ts b/packages/astro/src/actions/runtime/middleware.ts index 2cc1b1e28a29..3d430b04a45a 100644 --- a/packages/astro/src/actions/runtime/middleware.ts +++ b/packages/astro/src/actions/runtime/middleware.ts @@ -32,6 +32,7 @@ export const onRequest = defineMiddleware(async (context, next) => { // Heuristic: If body is null, Astro might've reset this for prerendering. if (import.meta.env.DEV && request.method === 'POST' && request.body === null) { + // eslint-disable-next-line no-console console.warn( yellow('[astro:actions]'), 'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".' diff --git a/packages/astro/src/actions/runtime/route.ts b/packages/astro/src/actions/runtime/route.ts index a295fba611ba..e4e2ad1ce5b1 100644 --- a/packages/astro/src/actions/runtime/route.ts +++ b/packages/astro/src/actions/runtime/route.ts @@ -12,7 +12,7 @@ export const POST: APIRoute = async (context) => { const contentType = request.headers.get('Content-Type'); const contentLength = request.headers.get('Content-Length'); let args: unknown; - if (contentLength === '0') { + if (!contentType || contentLength === '0') { args = undefined; } else if (contentType && hasContentType(contentType, formContentTypes)) { args = await request.clone().formData(); diff --git a/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts b/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts index 11baaf6d2a85..a0099d0a0ad9 100644 --- a/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts +++ b/packages/astro/src/assets/utils/vendor/image-size/types/svg.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */ +/* eslint-disable regexp/prefer-regexp-exec */ import type { IImage, ISize } from './interface.ts' import { toUTF8String } from './utils.js' diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index a2b1d4f2e591..42027098f3fa 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -501,7 +501,7 @@ export class App { } #getDefaultStatusCode(routeData: RouteData, pathname: string): number { - if (!routeData.pattern.exec(pathname)) { + if (!routeData.pattern.test(pathname)) { for (const fallbackRoute of routeData.fallbackRoutes) { if (fallbackRoute.pattern.test(pathname)) { return 302; diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index de98093c7419..e3fca61206db 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -306,7 +306,7 @@ function getInvalidRouteSegmentError( route: RouteData, staticPath: GetStaticPathsItem ): AstroError { - const invalidParam = e.message.match(/^Expected "([^"]+)"/)?.[1]; + const invalidParam = /^Expected "([^"]+)"/.exec(e.message)?.[1]; const received = invalidParam ? staticPath.params[invalidParam] : undefined; let hint = 'Learn about dynamic routes at https://docs.astro.build/en/core-concepts/routing/#dynamic-routes'; @@ -421,7 +421,7 @@ async function generatePath( // always be rendered route.pathname !== '/' && // Check if there is a translated page with the same path - Object.values(options.allPages).some((val) => pathname.match(val.route.pattern)) + Object.values(options.allPages).some((val) => val.route.pattern.test(pathname)) ) { return; } @@ -503,7 +503,7 @@ function getPrettyRouteName(route: RouteData): string { } else if (route.component.includes('node_modules/')) { // For routes from node_modules (usually injected by integrations), // prettify it by only grabbing the part after the last `node_modules/` - return route.component.match(/.*node_modules\/(.+)/)?.[1] ?? route.component; + return /.*node_modules\/(.+)/.exec(route.component)?.[1] ?? route.component; } else { return route.component; } diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts index 1612754d0083..b63d696f50fd 100644 --- a/packages/astro/src/core/errors/dev/vite.ts +++ b/packages/astro/src/core/errors/dev/vite.ts @@ -41,7 +41,7 @@ export function enhanceViteSSRError({ // Vite has a fairly generic error message when it fails to load a module, let's try to enhance it a bit // https://github.com/vitejs/vite/blob/ee7c28a46a6563d54b828af42570c55f16b15d2c/packages/vite/src/node/ssr/ssrModuleLoader.ts#L91 let importName: string | undefined; - if ((importName = safeError.message.match(/Failed to load url (.*?) \(resolved id:/)?.[1])) { + if ((importName = /Failed to load url (.*?) \(resolved id:/.exec(safeError.message)?.[1])) { safeError.title = FailedToLoadModuleSSR.title; safeError.name = 'FailedToLoadModuleSSR'; safeError.message = FailedToLoadModuleSSR.message(importName); @@ -64,9 +64,10 @@ export function enhanceViteSSRError({ // Vite throws a syntax error trying to parse MDX without a plugin. // Suggest installing the MDX integration if none is found. if ( + fileId && !renderers?.find((r) => r.name === '@astrojs/mdx') && - safeError.message.match(/Syntax error/) && - fileId?.match(/\.mdx$/) + /Syntax error/.test(safeError.message) && + /.mdx$/.test(fileId) ) { safeError = new AstroError({ ...MdxIntegrationMissingError, @@ -78,7 +79,7 @@ export function enhanceViteSSRError({ // Since Astro.glob is a wrapper around Vite's import.meta.glob, errors don't show accurate information, let's fix that if (/Invalid glob/.test(safeError.message)) { - const globPattern = safeError.message.match(/glob: "(.+)" \(/)?.[1]; + const globPattern = /glob: "(.+)" \(/.exec(safeError.message)?.[1]; if (globPattern) { safeError.message = InvalidGlob.message(globPattern); diff --git a/packages/astro/src/events/error.ts b/packages/astro/src/events/error.ts index 8b8e9767e621..77de088c5797 100644 --- a/packages/astro/src/events/error.ts +++ b/packages/astro/src/events/error.ts @@ -28,7 +28,7 @@ interface ConfigErrorEventPayload extends ErrorEventPayload { */ const ANONYMIZE_MESSAGE_REGEX = /^(?:\w| )+/; function anonymizeErrorMessage(msg: string): string | undefined { - const matchedMessage = msg.match(ANONYMIZE_MESSAGE_REGEX); + const matchedMessage = ANONYMIZE_MESSAGE_REGEX.exec(msg); if (!matchedMessage?.[0]) { return undefined; } diff --git a/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts b/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts index a223bf1a848b..79508cc694e1 100644 --- a/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/ui-library/radio-checkbox.ts @@ -14,6 +14,7 @@ export class DevToolbarRadioCheckbox extends HTMLElement { set radioStyle(value) { if (!styles.includes(value)) { + // eslint-disable-next-line no-console console.error(`Invalid style: ${value}, expected one of ${styles.join(', ')}.`); return; } diff --git a/packages/astro/src/transitions/router.ts b/packages/astro/src/transitions/router.ts index b5e1a2235477..673eb4eb2317 100644 --- a/packages/astro/src/transitions/router.ts +++ b/packages/astro/src/transitions/router.ts @@ -542,6 +542,7 @@ async function transition( // This log doesn't make it worse than before, where we got error messages about uncaught exceptions, which can't be caught when the trigger was a click or history traversal. // Needs more investigation on root causes if errors still occur sporadically const err = e as Error; + // eslint-disable-next-line no-console console.log('[astro]', err.name, err.message, err.stack); } } diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs index 3c00fc9140c1..f38ba3fa96cf 100644 --- a/packages/astro/templates/actions.mjs +++ b/packages/astro/templates/actions.mjs @@ -65,15 +65,18 @@ async function handleAction(param, path, context) { let body = param; if (!(body instanceof FormData)) { try { - body = param ? JSON.stringify(param) : undefined; + body = JSON.stringify(param); } catch (e) { throw new ActionError({ code: 'BAD_REQUEST', message: `Failed to serialize request body to JSON. Full error: ${e.message}`, }); } - headers.set('Content-Type', 'application/json'); - headers.set('Content-Length', body?.length.toString() ?? '0'); + if (body) { + headers.set('Content-Type', 'application/json'); + } else { + headers.set('Content-Length', '0'); + } } const rawResult = await fetch(`/_actions/${path}`, { method: 'POST', diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js index aea929e1b8f3..36f6211ae0cf 100644 --- a/packages/astro/test/actions.test.js +++ b/packages/astro/test/actions.test.js @@ -234,11 +234,10 @@ describe('Astro Actions', () => { }); }); - it('Sets status to 204 when no content', async () => { + it('Sets status to 204 when content-length is 0', async () => { const req = new Request('http://example.com/_actions/fireAndForget', { method: 'POST', headers: { - 'Content-Type': 'application/json', 'Content-Length': '0', }, }); @@ -246,6 +245,26 @@ describe('Astro Actions', () => { assert.equal(res.status, 204); }); + it('Sets status to 204 when content-type is omitted', async () => { + const req = new Request('http://example.com/_actions/fireAndForget', { + method: 'POST', + }); + const res = await app.render(req); + assert.equal(res.status, 204); + }); + + it('Sets status to 415 when content-type is unexpected', async () => { + const req = new Request('http://example.com/_actions/fireAndForget', { + method: 'POST', + body: 'hey', + headers: { + 'Content-Type': 'text/plain', + }, + }); + const res = await app.render(req); + assert.equal(res.status, 415); + }); + it('Is callable from the server with rewrite', async () => { const req = new Request('http://example.com/rewrite'); const res = await app.render(req); diff --git a/packages/astro/test/css-order-import.test.js b/packages/astro/test/css-order-import.test.js index d8ee74a1d0bf..b64125d70a2b 100644 --- a/packages/astro/test/css-order-import.test.js +++ b/packages/astro/test/css-order-import.test.js @@ -126,7 +126,7 @@ describe('CSS ordering - import order', () => { const content = await Promise.all(getLinks(html).map((href) => getLinkContent(href))); const css = content.map((c) => c.css).join(''); - assert.equal(css.match(/\.astro-jsx/).length, 1, '.astro-jsx class is duplicated'); + assert.equal(/\.astro-jsx/.exec(css).length, 1, '.astro-jsx class is duplicated'); }); }); diff --git a/packages/astro/test/css-order.test.js b/packages/astro/test/css-order.test.js index ee2992a31ba4..4c1b41cfc751 100644 --- a/packages/astro/test/css-order.test.js +++ b/packages/astro/test/css-order.test.js @@ -92,8 +92,8 @@ describe('CSS production ordering', () => { assert.ok(content.length, 3, 'there are 3 stylesheets'); const [, sharedStyles, pageStyles] = content; - assert.ok(sharedStyles.css.match(/red/)); - assert.ok(pageStyles.css.match(/#00f/)); + assert.ok(/red/.exec(sharedStyles.css)); + assert.ok(/#00f/.exec(pageStyles.css)); }); it('CSS injected by injectScript comes first because of import order', async () => { diff --git a/packages/astro/test/solid-component.test.js b/packages/astro/test/solid-component.test.js index bd2c4f64f4f5..3d0eb316152c 100644 --- a/packages/astro/test/solid-component.test.js +++ b/packages/astro/test/solid-component.test.js @@ -183,11 +183,12 @@ describe.skip('Solid component dev', { todo: 'Check why the test hangs.', skip: const createHydrationScriptRegex = (flags) => new RegExp(/_\$HY=/, flags); function countHydrationScripts(/** @type {string} */ html) { + // eslint-disable-next-line regexp/prefer-regexp-exec return html.match(createHydrationScriptRegex('g'))?.length ?? 0; } function getFirstHydrationScriptLocation(/** @type {string} */ html) { - return html.match(createHydrationScriptRegex())?.index; + return createHydrationScriptRegex().exec(html)?.index; } /** @@ -202,9 +203,10 @@ function countHydrationEvents(/** @type {string} */ html) { // Number of times a component was hydrated during rendering // We look for the hint "_$HY.r[" + // eslint-disable-next-line regexp/prefer-regexp-exec return html.match(createHydrationEventRegex('g'))?.length ?? 0; } function getFirstHydrationEventLocation(/** @type {string} */ html) { - return html.match(createHydrationEventRegex())?.index; + return createHydrationEventRegex().exec(html)?.index; } diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js index 4c2e796e12bb..6c5d1ad09d88 100644 --- a/packages/astro/test/ssr-api-route.test.js +++ b/packages/astro/test/ssr-api-route.test.js @@ -118,7 +118,7 @@ describe('API routes in SSR', () => { let count = 0; let exp = /set-cookie:/g; - while (exp.exec(response)) { + while (exp.test(response)) { count++; } diff --git a/packages/astro/test/units/i18n/astro_i18n.test.js b/packages/astro/test/units/i18n/astro_i18n.test.js index 9d424f5b16a8..dab7cf1f72e9 100644 --- a/packages/astro/test/units/i18n/astro_i18n.test.js +++ b/packages/astro/test/units/i18n/astro_i18n.test.js @@ -1,15 +1,15 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { MissingLocale } from '#astro/core/errors/errors-data'; -import { AstroError } from '#astro/core/errors/index'; -import { toRoutingStrategy } from '#astro/i18n/utils'; import { validateConfig } from '../../../dist/core/config/validate.js'; +import { MissingLocale } from '../../../dist/core/errors/errors-data.js'; +import { AstroError } from '../../../dist/core/errors/index.js'; import { getLocaleAbsoluteUrl, getLocaleAbsoluteUrlList, getLocaleRelativeUrl, getLocaleRelativeUrlList, } from '../../../dist/i18n/index.js'; +import { toRoutingStrategy } from '../../../dist/i18n/utils.js'; import { parseLocale } from '../../../dist/i18n/utils.js'; describe('getLocaleRelativeUrl', () => { diff --git a/packages/create-astro/src/actions/verify.ts b/packages/create-astro/src/actions/verify.ts index a6c9cc7e1754..605b6959d78a 100644 --- a/packages/create-astro/src/actions/verify.ts +++ b/packages/create-astro/src/actions/verify.ts @@ -84,7 +84,7 @@ async function verifyTemplate(tmpl: string, ref?: string) { const GIT_RE = /^(?[\w.-]+\/[\w.-]+)(?[^#]+)?(?#[\w.-]+)?/; function parseGitURI(input: string) { - const m = input.match(GIT_RE)?.groups; + const m = GIT_RE.exec(input)?.groups; if (!m) throw new Error(`Unable to parse "${input}"`); return { repo: m.repo, diff --git a/packages/db/test/unit/column-queries.test.js b/packages/db/test/unit/column-queries.test.js index ebb865670297..bd59fd45833f 100644 --- a/packages/db/test/unit/column-queries.test.js +++ b/packages/db/test/unit/column-queries.test.js @@ -492,6 +492,5 @@ describe('column queries', () => { /** @param {string} query */ function getTempTableName(query) { - // eslint-disable-next-line regexp/no-unused-capturing-group - return query.match(/Users_([a-z\d]+)/)?.[0]; + return /Users_[a-z\d]+/.exec(query)?.[0]; } diff --git a/packages/db/test/unit/reference-queries.test.js b/packages/db/test/unit/reference-queries.test.js index 791344146b1c..3a457192f621 100644 --- a/packages/db/test/unit/reference-queries.test.js +++ b/packages/db/test/unit/reference-queries.test.js @@ -163,8 +163,7 @@ describe('reference queries', () => { }); }); -/** @param {string | undefined} query */ +/** @param {string} query */ function getTempTableName(query) { - // eslint-disable-next-line regexp/no-unused-capturing-group - return query.match(/User_([a-z\d]+)/)?.[0]; + return /User_[a-z\d]+/.exec(query)?.[0]; } diff --git a/packages/integrations/markdoc/src/content-entry-type.ts b/packages/integrations/markdoc/src/content-entry-type.ts index 8fc4bd77cceb..bd19e1ccda88 100644 --- a/packages/integrations/markdoc/src/content-entry-type.ts +++ b/packages/integrations/markdoc/src/content-entry-type.ts @@ -245,7 +245,7 @@ function raiseValidationErrors({ e.error.id !== 'variable-undefined' && // Ignore missing partial errors. // We will resolve these in `resolvePartials`. - !(e.error.id === 'attribute-value-invalid' && e.error.message.match(/^Partial .+ not found/)) + !(e.error.id === 'attribute-value-invalid' && /^Partial .+ not found/.test(e.error.message)) ); }); @@ -275,7 +275,7 @@ function getUsedTags(markdocAst: Node) { // This is our signal that a tag is being used! for (const { error } of validationErrors) { if (error.id === 'tag-undefined') { - const [, tagName] = error.message.match(/Undefined tag: '(.*)'/) ?? []; + const [, tagName] = /Undefined tag: '(.*)'/.exec(error.message) ?? []; tags.add(tagName); } } diff --git a/packages/integrations/mdx/test/units/rehype-optimize-static.test.js b/packages/integrations/mdx/test/units/rehype-optimize-static.test.js index 132f3849f5bf..675bc3478ea3 100644 --- a/packages/integrations/mdx/test/units/rehype-optimize-static.test.js +++ b/packages/integrations/mdx/test/units/rehype-optimize-static.test.js @@ -15,7 +15,7 @@ async function compile(mdxCode, options) { }); const code = result.toString(); // Capture the returned JSX code for testing - const jsx = code.match(/return (.+);\n\}\nexport default function MDXContent/s)?.[1]; + const jsx = /return (.+);\n\}\nexport default function MDXContent/s.exec(code)?.[1]; if (jsx == null) throw new Error('Could not find JSX code in compiled MDX'); return dedent(jsx); } diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts index 0ec129d9f995..8256c588ec20 100644 --- a/packages/integrations/node/src/serve-static.ts +++ b/packages/integrations/node/src/serve-static.ts @@ -52,7 +52,7 @@ export function createStaticHandler(app: NodeApp, options: Options) { break; case 'always': // trailing slash is not added to "subresources" - if (!hasSlash && !urlPath.match(isSubresourceRegex)) { + if (!hasSlash && !isSubresourceRegex.test(urlPath)) { pathname = urlPath + '/' + (urlQuery ? '?' + urlQuery : ''); res.statusCode = 301; res.setHeader('Location', pathname); diff --git a/packages/integrations/vue/src/editor.cts b/packages/integrations/vue/src/editor.cts index d4f10aab6661..64b34b45b1fa 100644 --- a/packages/integrations/vue/src/editor.cts +++ b/packages/integrations/vue/src/editor.cts @@ -24,7 +24,7 @@ export function toTSX(code: string, className: string): string { if (scriptSetup) { const codeWithoutComments = scriptSetup.content.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, ''); - const definePropsType = codeWithoutComments.match(/defineProps<([\s\S]+?)>\s?\(\)/); + const definePropsType = /defineProps<([\s\S]+?)>\s?\(\)/.exec(codeWithoutComments); const propsGeneric = scriptSetup.attrs.generic; const propsGenericType = propsGeneric ? `<${propsGeneric}>` : ''; @@ -41,7 +41,7 @@ export function toTSX(code: string, className: string): string { // TODO. Find a way to support generics when using defineProps without passing explicit types. // Right now something like this `defineProps({ prop: { type: Array as PropType } })` // won't be correctly typed in Astro. - const defineProps = codeWithoutComments.match(/defineProps\([\s\S]+?\)/); + const defineProps = /defineProps\([\s\S]+?\)/.exec(codeWithoutComments); if (defineProps) { result = ` import { defineProps } from 'vue'; diff --git a/packages/markdown/remark/src/highlight.ts b/packages/markdown/remark/src/highlight.ts index 41ec8880b236..ef1a734ba58f 100644 --- a/packages/markdown/remark/src/highlight.ts +++ b/packages/markdown/remark/src/highlight.ts @@ -43,14 +43,14 @@ export async function highlightCodeBlocks(tree: Root, highlighter: Highlighter) let languageMatch: RegExpMatchArray | null | undefined; let { className } = node.properties; if (typeof className === 'string') { - languageMatch = className.match(languagePattern); + languageMatch = languagePattern.exec(className); } else if (Array.isArray(className)) { for (const cls of className) { if (typeof cls !== 'string') { continue; } - languageMatch = cls.match(languagePattern); + languageMatch = languagePattern.exec(cls); if (languageMatch) { break; } diff --git a/packages/markdown/remark/src/rehype-collect-headings.ts b/packages/markdown/remark/src/rehype-collect-headings.ts index 8624005450ad..3bff443e2e42 100644 --- a/packages/markdown/remark/src/rehype-collect-headings.ts +++ b/packages/markdown/remark/src/rehype-collect-headings.ts @@ -20,7 +20,7 @@ export function rehypeHeadingIds(): ReturnType { if (node.type !== 'element') return; const { tagName } = node; if (tagName[0] !== 'h') return; - const [, level] = tagName.match(/h([0-6])/) ?? []; + const [, level] = /h([0-6])/.exec(tagName) ?? []; if (!level) return; const depth = Number.parseInt(level); @@ -30,7 +30,7 @@ export function rehypeHeadingIds(): ReturnType { return; } if (child.type === 'raw') { - if (child.value.match(/^\n?<.*>\n?$/)) { + if (/^\n?<.*>\n?$/.test(child.value)) { return; } }