diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index e38d33f86d5e..35b6e498edeb 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -453,8 +453,6 @@ export interface AstroUserConfig { */ cacheDir?: string; - - /** * @docs * @name redirects (Experimental) @@ -462,12 +460,12 @@ export interface AstroUserConfig { * @default `{}` * @version 2.6.0 * @description Specify a mapping of redirects where the key is the route to match - * and the value is the path to redirect to. + * and the value is the path to redirect to. * * You can redirect both static and dynamic routes, but only to the same kind of route. * For example you cannot have a `'/article': '/blog/[...slug]'` redirect. - * - * + * + * * ```js * { * redirects: { @@ -477,16 +475,16 @@ export interface AstroUserConfig { * } * ``` * - * + * * For statically-generated sites with no adapter installed, this will produce a client redirect using a [`` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv) and does not support status codes. * * When using SSR or with a static adapter in `output: static` * mode, status codes are supported. * Astro will serve redirected GET requests with a status of `301` * and use a status of `308` for any other request method. - * + * * You can customize the [redirection status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages) using an object in the redirect config: - * + * * ```js * { * redirects: { @@ -791,8 +789,8 @@ export interface AstroUserConfig { * Specifies whether redirects will be output to HTML during the build. * This option only applies to `output: 'static'` mode; in SSR redirects * are treated the same as all responses. - * - * This option is mostly meant to be used by adapters that have special + * + * This option is mostly meant to be used by adapters that have special * configuration files for redirects and do not need/want HTML based redirects. * * ```js @@ -1270,7 +1268,7 @@ export interface AstroUserConfig { * } * ``` */ - redirects?: boolean; + redirects?: boolean; }; // Legacy options to be removed @@ -1924,10 +1922,12 @@ export interface RoutePart { spread: boolean; } -type RedirectConfig = string | { - status: ValidRedirectStatus; - destination: string; -} +type RedirectConfig = + | string + | { + status: ValidRedirectStatus; + destination: string; + }; export interface RouteData { route: string; @@ -1947,7 +1947,7 @@ export interface RouteData { export type RedirectRouteData = RouteData & { redirect: string; -} +}; export type SerializedRouteData = Omit & { generate: undefined; diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 5a849153de4a..e047c0360a4b 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -15,6 +15,7 @@ import { consoleLogDestination } from '../logger/console.js'; import { error, type LogOptions } from '../logger/core.js'; import { callMiddleware } from '../middleware/callMiddleware.js'; import { prependForwardSlash, removeTrailingForwardSlash } from '../path.js'; +import { RedirectComponentInstance } from '../redirects/index.js'; import { createEnvironment, createRenderContext, @@ -28,7 +29,6 @@ import { createStylesheetElementSet, } from '../render/ssr-element.js'; import { matchRoute } from '../routing/match.js'; -import { RedirectComponentInstance } from '../redirects/index.js'; export { deserializeManifest } from './common.js'; const clientLocalsSymbol = Symbol.for('astro.locals'); @@ -172,12 +172,14 @@ export class App { } async #getModuleForRoute(route: RouteData): Promise { - if(route.type === 'redirect') { + if (route.type === 'redirect') { return RedirectComponentInstance; } else { const importComponentInstance = this.#manifest.pageMap.get(route.component); - if(!importComponentInstance) { - throw new Error(`Unexpectedly unable to find a component instance for route ${route.route}`); + if (!importComponentInstance) { + throw new Error( + `Unexpectedly unable to find a component instance for route ${route.route}` + ); } const built = await importComponentInstance(); return built.page(); diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index e2e3a24f00f0..42f930ed3cfc 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -12,7 +12,6 @@ import type { EndpointOutput, ImageTransform, MiddlewareResponseHandler, - RedirectRouteData, RouteData, RouteType, SSRError, @@ -24,9 +23,9 @@ import { } from '../../assets/generate.js'; import { eachPageDataFromEntryPoint, + eachRedirectPageData, hasPrerenderedPages, type BuildInternals, - eachRedirectPageData, } from '../../core/build/internal.js'; import { prependForwardSlash, @@ -40,10 +39,14 @@ import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from '../en import { AstroError } from '../errors/index.js'; import { debug, info } from '../logger/core.js'; import { callMiddleware } from '../middleware/callMiddleware.js'; +import { + getRedirectLocationOrThrow, + RedirectComponentInstance, + routeIsRedirect, +} from '../redirects/index.js'; import { createEnvironment, createRenderContext, renderPage } from '../render/index.js'; import { callGetStaticPaths } from '../render/route-cache.js'; -import { getRedirectLocationOrThrow, RedirectComponentInstance, routeIsRedirect } from '../redirects/index.js'; -import { +import { createAssetLink, createModuleScriptsSet, createStylesheetElementSet, @@ -52,7 +55,12 @@ import { createRequest } from '../request.js'; import { matchRoute } from '../routing/match.js'; import { getOutputFilename } from '../util.js'; import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js'; -import { cssOrder, getPageDataByComponent, mergeInlineCss, getEntryFilePathFromComponentPath } from './internal.js'; +import { + cssOrder, + getEntryFilePathFromComponentPath, + getPageDataByComponent, + mergeInlineCss, +} from './internal.js'; import type { PageBuildData, SinglePageBuiltModule, @@ -62,24 +70,24 @@ import type { import { getTimeStat } from './util.js'; const StaticMiddlewareInstance: AstroMiddlewareInstance = { - onRequest: (ctx, next) => next() + onRequest: (ctx, next) => next(), }; function createEntryURL(filePath: string, outFolder: URL) { return new URL('./' + filePath + `?time=${Date.now()}`, outFolder); -} +} async function getEntryForRedirectRoute( route: RouteData, internals: BuildInternals, outFolder: URL ): Promise { - if(route.type !== 'redirect') { + if (route.type !== 'redirect') { throw new Error(`Expected a redirect route.`); } - if(route.redirectRoute) { + if (route.redirectRoute) { const filePath = getEntryFilePathFromComponentPath(internals, route.redirectRoute.component); - if(filePath) { + if (filePath) { const url = createEntryURL(filePath, outFolder); const ssrEntryPage: SinglePageBuiltModule = await import(url.toString()); return ssrEntryPage; @@ -89,8 +97,8 @@ async function getEntryForRedirectRoute( return { page: () => Promise.resolve(RedirectComponentInstance), middleware: StaticMiddlewareInstance, - renderers: [] - } + renderers: [], + }; } function shouldSkipDraft(pageModule: ComponentInstance, settings: AstroSettings): boolean { @@ -143,13 +151,13 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn if (ssr) { for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) { if (pageData.route.prerender) { - const ssrEntryURLPage =createEntryURL(filePath, outFolder); + const ssrEntryURLPage = createEntryURL(filePath, outFolder); const ssrEntryPage: SinglePageBuiltModule = await import(ssrEntryURLPage.toString()); await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths); } } - for(const pageData of eachRedirectPageData(internals)) { + for (const pageData of eachRedirectPageData(internals)) { const entry = await getEntryForRedirectRoute(pageData.route, internals, outFolder); await generatePage(opts, internals, pageData, entry, builtPaths); } @@ -160,7 +168,7 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths); } - for(const pageData of eachRedirectPageData(internals)) { + for (const pageData of eachRedirectPageData(internals)) { const entry = await getEntryForRedirectRoute(pageData.route, internals, outFolder); await generatePage(opts, internals, pageData, entry, builtPaths); } @@ -208,7 +216,7 @@ async function generatePage( ssrEntry: SinglePageBuiltModule, builtPaths: Set ) { - if(routeIsRedirect(pageData.route) &&!opts.settings.config.experimental.redirects) { + if (routeIsRedirect(pageData.route) && !opts.settings.config.experimental.redirects) { throw new Error(`To use redirects first set experimental.redirects to \`true\``); } @@ -578,9 +586,9 @@ async function generatePath( throw err; } - if(response.status >= 300 && response.status < 400) { + if (response.status >= 300 && response.status < 400) { // If redirects is set to false, don't output the HTML - if(!opts.settings.config.build.redirects) { + if (!opts.settings.config.build.redirects) { return; } const location = getRedirectLocationOrThrow(response.headers); @@ -588,7 +596,7 @@ async function generatePath( Redirecting to: ${location} `; // A dynamic redirect, set the location so that integrations know about it. - if(pageData.route.type !== 'redirect') { + if (pageData.route.type !== 'redirect') { pageData.route.redirect = location; } } else { diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts index 2383dd14af54..4cf40cb9ad83 100644 --- a/packages/astro/src/core/build/internal.ts +++ b/packages/astro/src/core/build/internal.ts @@ -3,7 +3,11 @@ import type { SSRResult } from '../../@types/astro'; import type { PageOptions } from '../../vite-plugin-astro/types'; import { prependForwardSlash, removeFileExtension } from '../path.js'; import { viteID } from '../util.js'; -import { ASTRO_PAGE_EXTENSION_POST_PATTERN, ASTRO_PAGE_MODULE_ID, getVirtualModulePageIdFromPath } from './plugins/plugin-pages.js'; +import { + ASTRO_PAGE_EXTENSION_POST_PATTERN, + ASTRO_PAGE_MODULE_ID, + getVirtualModulePageIdFromPath, +} from './plugins/plugin-pages.js'; import type { PageBuildData, StylesheetAsset, ViteID } from './types'; export interface BuildInternals { @@ -218,8 +222,8 @@ export function* eachPageData(internals: BuildInternals) { } export function* eachRedirectPageData(internals: BuildInternals) { - for(const pageData of eachPageData(internals)) { - if(pageData.route.type === 'redirect') { + for (const pageData of eachPageData(internals)) { + if (pageData.route.type === 'redirect') { yield pageData; } } diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index 79f19cd06b6e..d3ecfe7b8405 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -1,11 +1,11 @@ import { extname } from 'node:path'; import type { Plugin as VitePlugin } from 'vite'; +import { routeIsRedirect } from '../../redirects/index.js'; import { addRollupInput } from '../add-rollup-input.js'; import { type BuildInternals } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin'; import type { StaticBuildOptions } from '../types'; import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js'; -import { routeIsRedirect } from '../../redirects/index.js'; import { RENDERERS_MODULE_ID } from './plugin-renderers.js'; export const ASTRO_PAGE_MODULE_ID = '@astro-page:'; @@ -44,7 +44,7 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V const inputs: Set = new Set(); for (const [path, pageData] of Object.entries(opts.allPages)) { - if(routeIsRedirect(pageData.route)) { + if (routeIsRedirect(pageData.route)) { continue; } inputs.add(getVirtualModulePageNameFromPath(path)); diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index 50c08c642570..7099fe3feca5 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -7,6 +7,7 @@ import { isHybridOutput } from '../../../prerender/utils.js'; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js'; import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types'; import { joinPaths, prependForwardSlash } from '../../path.js'; +import { routeIsRedirect } from '../../redirects/index.js'; import { serializeRouteData } from '../../routing/index.js'; import { addRollupInput } from '../add-rollup-input.js'; import { getOutFile, getOutFolder } from '../common.js'; @@ -14,7 +15,6 @@ import { cssOrder, mergeInlineCss, type BuildInternals } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin'; import type { StaticBuildOptions } from '../types'; import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js'; -import { routeIsRedirect } from '../../redirects/index.js'; import { getVirtualModulePageNameFromPath } from './plugin-pages.js'; import { RENDERERS_MODULE_ID } from './plugin-renderers.js'; @@ -57,7 +57,7 @@ function vitePluginSSR( const pageMap: string[] = []; for (const [path, pageData] of Object.entries(allPages)) { - if(routeIsRedirect(pageData.route)) { + if (routeIsRedirect(pageData.route)) { continue; } const virtualModuleName = getVirtualModulePageNameFromPath(path); diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts index 81ec93d9b395..63adadc734fe 100644 --- a/packages/astro/src/core/config/config.ts +++ b/packages/astro/src/core/config/config.ts @@ -107,7 +107,7 @@ export function resolveFlags(flags: Partial): CLIFlags { experimentalMiddleware: typeof flags.experimentalMiddleware === 'boolean' ? flags.experimentalMiddleware : undefined, experimentalRedirects: - typeof flags.experimentalRedirects === 'boolean' ? flags.experimentalRedirects : undefined + typeof flags.experimentalRedirects === 'boolean' ? flags.experimentalRedirects : undefined, }; } diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index 13d4b57be166..be530c302461 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -750,12 +750,12 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati `\`Astro.glob(${globStr})\` did not return any matching files. Check the pattern for typos.`, }, /** - * @docs - * @see - * - [Astro.redirect](https://docs.astro.build/en/guides/server-side-rendering/#astroredirect) - * @description - * A redirect must be given a location with the `Location` header. - */ + * @docs + * @see + * - [Astro.redirect](https://docs.astro.build/en/guides/server-side-rendering/#astroredirect) + * @description + * A redirect must be given a location with the `Location` header. + */ RedirectWithNoLocation: { title: 'A redirect must be given a location with the `Location` header.', code: 3037, diff --git a/packages/astro/src/core/redirects/component.ts b/packages/astro/src/core/redirects/component.ts index 1471af1f40fd..3e279390f19a 100644 --- a/packages/astro/src/core/redirects/component.ts +++ b/packages/astro/src/core/redirects/component.ts @@ -4,7 +4,7 @@ import type { ComponentInstance } from '../../@types/astro'; export const RedirectComponentInstance: ComponentInstance = { default() { return new Response(null, { - status: 301 + status: 301, }); - } + }, }; diff --git a/packages/astro/src/core/redirects/helpers.ts b/packages/astro/src/core/redirects/helpers.ts index c5c54ee35d98..a8d5f9938eda 100644 --- a/packages/astro/src/core/redirects/helpers.ts +++ b/packages/astro/src/core/redirects/helpers.ts @@ -1,4 +1,4 @@ -import type { RouteData, RedirectRouteData, Params, ValidRedirectStatus } from '../../@types/astro'; +import type { Params, RedirectRouteData, RouteData, ValidRedirectStatus } from '../../@types/astro'; export function routeIsRedirect(route: RouteData | undefined): route is RedirectRouteData { return route?.type === 'redirect'; @@ -8,11 +8,11 @@ export function redirectRouteGenerate(redirectRoute: RouteData, data: Params): s const routeData = redirectRoute.redirectRoute; const route = redirectRoute.redirect; - if(typeof routeData !== 'undefined') { + if (typeof routeData !== 'undefined') { return routeData?.generate(data) || routeData?.pathname || '/'; - } else if(typeof route === 'string') { + } else if (typeof route === 'string') { return route; - } else if(typeof route === 'undefined') { + } else if (typeof route === 'undefined') { return '/'; } return route.destination; @@ -20,9 +20,9 @@ export function redirectRouteGenerate(redirectRoute: RouteData, data: Params): s export function redirectRouteStatus(redirectRoute: RouteData, method = 'GET'): ValidRedirectStatus { const routeData = redirectRoute.redirectRoute; - if(typeof routeData?.redirect === 'object') { + if (typeof routeData?.redirect === 'object') { return routeData.redirect.status; - } else if(method !== 'GET') { + } else if (method !== 'GET') { return 308; } return 301; diff --git a/packages/astro/src/core/redirects/index.ts b/packages/astro/src/core/redirects/index.ts index f494230f56d8..dfae00b94747 100644 --- a/packages/astro/src/core/redirects/index.ts +++ b/packages/astro/src/core/redirects/index.ts @@ -1,3 +1,3 @@ -export { getRedirectLocationOrThrow } from './validate.js'; -export { routeIsRedirect, redirectRouteGenerate, redirectRouteStatus } from './helpers.js'; export { RedirectComponentInstance } from './component.js'; +export { redirectRouteGenerate, redirectRouteStatus, routeIsRedirect } from './helpers.js'; +export { getRedirectLocationOrThrow } from './validate.js'; diff --git a/packages/astro/src/core/redirects/validate.ts b/packages/astro/src/core/redirects/validate.ts index 523d9f5783e8..3075e1f3b30a 100644 --- a/packages/astro/src/core/redirects/validate.ts +++ b/packages/astro/src/core/redirects/validate.ts @@ -3,9 +3,9 @@ import { AstroError, AstroErrorData } from '../errors/index.js'; export function getRedirectLocationOrThrow(headers: Headers): string { let location = headers.get('location'); - if(!location) { + if (!location) { throw new AstroError({ - ...AstroErrorData.RedirectWithNoLocation + ...AstroErrorData.RedirectWithNoLocation, }); } diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index a6c503a38ef9..282c78f2b903 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -3,12 +3,12 @@ import { renderPage as runtimeRenderPage } from '../../runtime/server/index.js'; import { attachToResponse } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import type { LogOptions } from '../logger/core.js'; +import { redirectRouteGenerate, redirectRouteStatus, routeIsRedirect } from '../redirects/index.js'; import { getParams } from '../routing/params.js'; import type { RenderContext } from './context.js'; import type { Environment } from './environment.js'; import { createResult } from './result.js'; import { callGetStaticPaths, findPathItemByKey, RouteCache } from './route-cache.js'; -import { routeIsRedirect, redirectRouteGenerate, redirectRouteStatus } from '../redirects/index.js'; interface GetParamsAndPropsOptions { mod: ComponentInstance; @@ -113,18 +113,18 @@ export type RenderPage = { }; export async function renderPage({ - mod, - renderContext, - env, - apiContext, - isCompressHTML = false, + mod, + renderContext, + env, + apiContext, + isCompressHTML = false, }: RenderPage) { - if(routeIsRedirect(renderContext.route)) { + if (routeIsRedirect(renderContext.route)) { return new Response(null, { status: redirectRouteStatus(renderContext.route, renderContext.request.method), headers: { - location: redirectRouteGenerate(renderContext.route, renderContext.params) - } + location: redirectRouteGenerate(renderContext.route, renderContext.params), + }, }); } diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index a193621299ac..874b8511bba9 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -212,12 +212,12 @@ function injectedRouteToItem( // Seeings if the two routes are siblings of each other, with `b` being the route // in focus. If it is in the same parent folder as `a`, they are siblings. function areSiblings(a: RouteData, b: RouteData) { - if(a.segments.length < b.segments.length) return false; - for(let i = 0; i < b.segments.length - 1; i++) { + if (a.segments.length < b.segments.length) return false; + for (let i = 0; i < b.segments.length - 1; i++) { let segment = b.segments[i]; - if(segment.length === a.segments[i].length) { - for(let j = 0; j < segment.length; j++) { - if(!areSamePart(segment[j], a.segments[i][j])) { + if (segment.length === a.segments[i].length) { + for (let j = 0; j < segment.length; j++) { + if (!areSamePart(segment[j], a.segments[i][j])) { return false; } } @@ -448,12 +448,12 @@ export function createRouteManifest( const trailingSlash = config.trailingSlash; const segments = removeLeadingForwardSlash(from) - .split(path.posix.sep) - .filter(Boolean) - .map((s: string) => { - validateSegment(s); - return getParts(s, from); - }); + .split(path.posix.sep) + .filter(Boolean) + .map((s: string) => { + validateSegment(s); + return getParts(s, from); + }); const pattern = getPattern(segments, settings.config.base, trailingSlash); const generate = getRouteGenerator(segments, trailingSlash); @@ -479,9 +479,9 @@ export function createRouteManifest( pathname: pathname || void 0, prerender: false, redirect: to, - redirectRoute: routes.find(r => r.route === to) + redirectRoute: routes.find((r) => r.route === to), }; - + // Push so that redirects are selected last. routes.push(routeData); }); @@ -490,4 +490,3 @@ export function createRouteManifest( routes, }; } - diff --git a/packages/astro/src/runtime/server/render/common.ts b/packages/astro/src/runtime/server/render/common.ts index ad6330f71163..50a99bc68d58 100644 --- a/packages/astro/src/runtime/server/render/common.ts +++ b/packages/astro/src/runtime/server/render/common.ts @@ -58,7 +58,7 @@ export function stringifyChunk( return renderAllHeadContent(result); } default: { - if(chunk instanceof Response) { + if (chunk instanceof Response) { return ''; } throw new Error(`Unknown chunk type: ${(chunk as any).type}`); @@ -108,7 +108,7 @@ export function chunkToByteArray( if (chunk instanceof Uint8Array) { return chunk as Uint8Array; } - + // stringify chunk might return a HTMLString let stringified = stringifyChunk(result, chunk); return encoder.encode(stringified.toString()); diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index ecf9a0c35f26..5388ec960514 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -129,10 +129,13 @@ export async function handleRoute( return handle404Response(origin, req, res); } - if(matchedRoute.route.type === 'redirect' && !settings.config.experimental.redirects) { - writeWebResponse(res, new Response(`To enable redirect set experimental.redirects to \`true\`.`, { - status: 400 - })); + if (matchedRoute.route.type === 'redirect' && !settings.config.experimental.redirects) { + writeWebResponse( + res, + new Response(`To enable redirect set experimental.redirects to \`true\`.`, { + status: 400, + }) + ); return; } diff --git a/packages/astro/test/redirects.test.js b/packages/astro/test/redirects.test.js index d5b3e5663997..e1891e5f35c7 100644 --- a/packages/astro/test/redirects.test.js +++ b/packages/astro/test/redirects.test.js @@ -13,7 +13,7 @@ describe('Astro.redirect', () => { output: 'server', adapter: testAdapter(), redirects: { - '/api/redirect': '/' + '/api/redirect': '/', }, experimental: { redirects: true, @@ -21,7 +21,7 @@ describe('Astro.redirect', () => { }); await fixture.build(); }); - + it('Returns a 302 status', async () => { const app = await fixture.loadTestAdapterApp(); const request = new Request('http://example.com/secret'); @@ -29,7 +29,7 @@ describe('Astro.redirect', () => { expect(response.status).to.equal(302); expect(response.headers.get('location')).to.equal('/login'); }); - + it('Warns when used inside a component', async () => { const app = await fixture.loadTestAdapterApp(); const request = new Request('http://example.com/late'); @@ -56,7 +56,7 @@ describe('Astro.redirect', () => { it('Uses 308 for non-GET methods', async () => { const app = await fixture.loadTestAdapterApp(); const request = new Request('http://example.com/api/redirect', { - method: 'POST' + method: 'POST', }); const response = await app.render(request); expect(response.status).to.equal(308); @@ -80,13 +80,13 @@ describe('Astro.redirect', () => { '/blog/[...slug]': '/articles/[...slug]', '/three': { status: 302, - destination: '/' - } - } + destination: '/', + }, + }, }); await fixture.build(); }); - + it('Includes the meta refresh tag in Astro.redirect pages', async () => { const html = await fixture.readFile('/secret/index.html'); expect(html).to.include('http-equiv="refresh'); @@ -131,10 +131,10 @@ describe('Astro.redirect', () => { root: './fixtures/ssr-redirect/', output: 'static', redirects: { - '/one': '/' + '/one': '/', }, build: { - redirects: false + redirects: false, }, experimental: { redirects: true, @@ -149,6 +149,6 @@ describe('Astro.redirect', () => { oneHtml = await fixture.readFile('/one/index.html'); } catch {} expect(oneHtml).be.an('undefined'); - }) - }) + }); + }); }); diff --git a/packages/astro/test/units/routing/manifest.test.js b/packages/astro/test/units/routing/manifest.test.js index 4a8a96175d99..7678ed77b231 100644 --- a/packages/astro/test/units/routing/manifest.test.js +++ b/packages/astro/test/units/routing/manifest.test.js @@ -47,7 +47,7 @@ describe('routing - createRouteManifest', () => { redirects: { '/blog/[...slug]': '/', '/blog/contributing': '/another', - } + }, }, root ); @@ -56,9 +56,9 @@ describe('routing - createRouteManifest', () => { settings, fsMod: fs, }); - + expect(manifest.routes[1].route).to.equal('/blog/contributing'); expect(manifest.routes[1].type).to.equal('page'); expect(manifest.routes[2].route).to.equal('/blog/[...slug]'); - }) + }); }); diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index ca755432ee8b..252dd778a530 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -1,5 +1,5 @@ +import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro'; -import { createRedirectsFromAstroRoutes, type Redirects } from '@astrojs/underscore-redirects'; import esbuild from 'esbuild'; import * as fs from 'fs'; import * as os from 'os'; @@ -199,13 +199,13 @@ export default function createIntegration(args?: Options): AstroIntegration { } } - const redirectRoutes = routes.filter(r => r.type === 'redirect'); + const redirectRoutes = routes.filter((r) => r.type === 'redirect'); const trueRedirects = createRedirectsFromAstroRoutes({ config: _config, routes: redirectRoutes, dir, }); - if(!trueRedirects.empty()) { + if (!trueRedirects.empty()) { await fs.promises.appendFile( new URL('./_redirects', _config.outDir), trueRedirects.print() diff --git a/packages/integrations/cloudflare/test/directory.test.js b/packages/integrations/cloudflare/test/directory.test.js index e88019401dca..371c5f3ff29c 100644 --- a/packages/integrations/cloudflare/test/directory.test.js +++ b/packages/integrations/cloudflare/test/directory.test.js @@ -12,7 +12,7 @@ describe('mode: "directory"', () => { output: 'server', adapter: cloudflare({ mode: 'directory' }), redirects: { - '/old': '/' + '/old': '/', }, experimental: { redirects: true, @@ -30,9 +30,7 @@ describe('mode: "directory"', () => { try { let _redirects = await fixture.readFile('/_redirects'); let parts = _redirects.split(/\s+/); - expect(parts).to.deep.equal([ - '/old', '/', '301' - ]); + expect(parts).to.deep.equal(['/old', '/', '301']); } catch { expect(false).to.equal(true); } diff --git a/packages/integrations/netlify/src/integration-static.ts b/packages/integrations/netlify/src/integration-static.ts index 8814f9d2af4d..78d0bb4b0324 100644 --- a/packages/integrations/netlify/src/integration-static.ts +++ b/packages/integrations/netlify/src/integration-static.ts @@ -1,5 +1,4 @@ -import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; -import type { Args } from './netlify-functions.js'; +import type { AstroIntegration } from 'astro'; import { createRedirects } from './shared.js'; export function netlifyStatic(): AstroIntegration { @@ -20,7 +19,7 @@ export function netlifyStatic(): AstroIntegration { }, 'astro:build:done': async ({ dir, routes }) => { await createRedirects(_config, routes, dir, '', 'static'); - } - } + }, + }, }; } diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts index d452ada10252..e4aabd8240d0 100644 --- a/packages/integrations/netlify/src/shared.ts +++ b/packages/integrations/netlify/src/shared.ts @@ -1,5 +1,5 @@ -import type { AstroConfig, RouteData } from 'astro'; import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; +import type { AstroConfig, RouteData } from 'astro'; import fs from 'node:fs'; export async function createRedirects( @@ -14,7 +14,10 @@ export async function createRedirects( const _redirectsURL = new URL('./_redirects', dir); const _redirects = createRedirectsFromAstroRoutes({ - config, routes, dir, dynamicTarget + config, + routes, + dir, + dynamicTarget, }); const content = _redirects.print(); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index 8a6d36694894..767e5b13b8a3 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -18,7 +18,7 @@ describe('SSG - Redirects', () => { site: `http://example.com`, integrations: [testIntegration()], redirects: { - '/other': '/' + '/other': '/', }, experimental: { redirects: true, @@ -31,14 +31,22 @@ describe('SSG - Redirects', () => { let redirects = await fixture.readFile('/_redirects'); let parts = redirects.split(/\s+/); expect(parts).to.deep.equal([ - '/other', '/', '301', + '/other', + '/', + '301', // This uses the dynamic Astro.redirect, so we don't know that it's a redirect // until runtime. This is correct! - '/nope', '/.netlify/functions/entry', '200', - '/', '/.netlify/functions/entry', '200', + '/nope', + '/.netlify/functions/entry', + '200', + '/', + '/.netlify/functions/entry', + '200', // A real route - '/team/articles/*', '/.netlify/functions/entry', '200', + '/team/articles/*', + '/.netlify/functions/entry', + '200', ]); }); }); diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index 0b153b31c0fd..d2f87d4f6267 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -20,10 +20,10 @@ describe('SSG - Redirects', () => { '/other': '/', '/two': { status: 302, - destination: '/' + destination: '/', }, - '/blog/[...slug]': '/team/articles/[...slug]' - } + '/blog/[...slug]': '/team/articles/[...slug]', + }, }); await fixture.build(); }); @@ -32,12 +32,22 @@ describe('SSG - Redirects', () => { let redirects = await fixture.readFile('/_redirects'); let parts = redirects.split(/\s+/); expect(parts).to.deep.equal([ - '/two', '/', '302', - '/other', '/', '301', - '/nope', '/', '301', + '/two', + '/', + '302', + '/other', + '/', + '301', + '/nope', + '/', + '301', - '/blog/*', '/team/articles/*/index.html', '301', - '/team/articles/*', '/team/articles/*/index.html', '200', + '/blog/*', + '/team/articles/*/index.html', + '301', + '/team/articles/*', + '/team/articles/*/index.html', + '200', ]); }); }); diff --git a/packages/integrations/vercel/src/lib/redirects.ts b/packages/integrations/vercel/src/lib/redirects.ts index 1ec19bfac36a..5c6bb605b57c 100644 --- a/packages/integrations/vercel/src/lib/redirects.ts +++ b/packages/integrations/vercel/src/lib/redirects.ts @@ -1,10 +1,9 @@ -import type { AstroConfig, RouteData, RoutePart } from 'astro'; import { appendForwardSlash } from '@astrojs/internal-helpers/path'; +import type { AstroConfig, RouteData, RoutePart } from 'astro'; import nodePath from 'node:path'; const pathJoin = nodePath.posix.join; - // https://vercel.com/docs/project-configuration#legacy/routes interface VercelRoute { src: string; @@ -60,19 +59,19 @@ function getReplacePattern(segments: RoutePart[][]) { } function getRedirectLocation(route: RouteData, config: AstroConfig): string { - if(route.redirectRoute) { + if (route.redirectRoute) { const pattern = getReplacePattern(route.redirectRoute.segments); - const path = (config.trailingSlash === 'always' ? appendForwardSlash(pattern) : pattern); + const path = config.trailingSlash === 'always' ? appendForwardSlash(pattern) : pattern; return pathJoin(config.base, path); - } else if(typeof route.redirect === 'object') { + } else if (typeof route.redirect === 'object') { return pathJoin(config.base, route.redirect.destination); } else { return pathJoin(config.base, route.redirect || ''); - } + } } function getRedirectStatus(route: RouteData): number { - if(typeof route.redirect === 'object') { + if (typeof route.redirect === 'object') { return route.redirect.status; } return 301; @@ -81,14 +80,12 @@ function getRedirectStatus(route: RouteData): number { export function getRedirects(routes: RouteData[], config: AstroConfig): VercelRoute[] { let redirects: VercelRoute[] = []; - - - for(const route of routes) { - if(route.type === 'redirect') { + for (const route of routes) { + if (route.type === 'redirect') { redirects.push({ src: config.base + getMatchPattern(route.segments), headers: { Location: getRedirectLocation(route, config) }, - status: getRedirectStatus(route) + status: getRedirectStatus(route), }); } else if (route.type === 'page') { if (config.trailingSlash === 'always') { diff --git a/packages/integrations/vercel/test/redirects.test.js b/packages/integrations/vercel/test/redirects.test.js index 0d54589fc564..16d845b044f8 100644 --- a/packages/integrations/vercel/test/redirects.test.js +++ b/packages/integrations/vercel/test/redirects.test.js @@ -14,7 +14,7 @@ describe('Redirects', () => { '/two': '/', '/three': { status: 302, - destination: '/' + destination: '/', }, '/blog/[...slug]': '/team/articles/[...slug]', }, @@ -35,15 +35,15 @@ describe('Redirects', () => { it('define static routes', async () => { const config = await getConfig(); - const oneRoute = config.routes.find(r => r.src === '/\\/one'); + const oneRoute = config.routes.find((r) => r.src === '/\\/one'); expect(oneRoute.headers.Location).to.equal('/'); expect(oneRoute.status).to.equal(301); - - const twoRoute = config.routes.find(r => r.src === '/\\/two'); + + const twoRoute = config.routes.find((r) => r.src === '/\\/two'); expect(twoRoute.headers.Location).to.equal('/'); expect(twoRoute.status).to.equal(301); - const threeRoute = config.routes.find(r => r.src === '/\\/three'); + const threeRoute = config.routes.find((r) => r.src === '/\\/three'); expect(threeRoute.headers.Location).to.equal('/'); expect(threeRoute.status).to.equal(302); }); @@ -51,7 +51,7 @@ describe('Redirects', () => { it('defines dynamic routes', async () => { const config = await getConfig(); - const blogRoute = config.routes.find(r => r.src.startsWith('/\\/blog')); + const blogRoute = config.routes.find((r) => r.src.startsWith('/\\/blog')); expect(blogRoute).to.not.be.undefined; expect(blogRoute.headers.Location.startsWith('/team/articles')).to.equal(true); expect(blogRoute.status).to.equal(301); diff --git a/packages/underscore-redirects/src/astro.ts b/packages/underscore-redirects/src/astro.ts index db84bb6e7f3d..fb28b048cc23 100644 --- a/packages/underscore-redirects/src/astro.ts +++ b/packages/underscore-redirects/src/astro.ts @@ -1,11 +1,11 @@ import type { AstroConfig, RouteData, ValidRedirectStatus } from 'astro'; -import { Redirects } from './redirects.js'; import { posix } from 'node:path'; +import { Redirects } from './redirects.js'; const pathJoin = posix.join; function getRedirectStatus(route: RouteData): ValidRedirectStatus { - if(typeof route.redirect === 'object') { + if (typeof route.redirect === 'object') { return route.redirect.status; } return 301; @@ -33,7 +33,7 @@ export function createRedirectsFromAstroRoutes({ for (const route of routes) { // A route with a `pathname` is as static route. if (route.pathname) { - if(route.redirect) { + if (route.redirect) { // A redirect route without dynamic parts. Get the redirect status // from the user if provided. _redirects.add({ @@ -41,17 +41,15 @@ export function createRedirectsFromAstroRoutes({ input: route.pathname, target: typeof route.redirect === 'object' ? route.redirect.destination : route.redirect, status: getRedirectStatus(route), - weight: 2 + weight: 2, }); continue; } // If this is a static build we don't want to add redirects to the HTML file. - if(output === 'static') { + if (output === 'static') { continue; - } - - else if (route.distURL) { + } else if (route.distURL) { _redirects.add({ dynamic: false, input: route.pathname, @@ -88,7 +86,7 @@ export function createRedirectsFromAstroRoutes({ const targetRoute = route.redirectRoute ?? route; const targetPattern = generateDynamicPattern(targetRoute); let target = targetPattern; - if(config.build.format === 'directory') { + if (config.build.format === 'directory') { target = pathJoin(target, 'index.html'); } else { target += '.html'; diff --git a/packages/underscore-redirects/src/index.ts b/packages/underscore-redirects/src/index.ts index 07e240218a0a..18a8a8ab481e 100644 --- a/packages/underscore-redirects/src/index.ts +++ b/packages/underscore-redirects/src/index.ts @@ -1,8 +1,2 @@ -export { - Redirects, - type RedirectDefinition -} from './redirects.js'; - -export { - createRedirectsFromAstroRoutes -} from './astro.js'; +export { createRedirectsFromAstroRoutes } from './astro.js'; +export { Redirects, type RedirectDefinition } from './redirects.js'; diff --git a/packages/underscore-redirects/src/print.ts b/packages/underscore-redirects/src/print.ts index a0d9564b907a..b407dc7eb644 100644 --- a/packages/underscore-redirects/src/print.ts +++ b/packages/underscore-redirects/src/print.ts @@ -17,7 +17,7 @@ export function print( let _redirects = ''; // Loop over the definitions - for(let i = 0; i < definitions.length; i++) { + for (let i = 0; i < definitions.length; i++) { let definition = definitions[i]; // Figure out the number of spaces to add. We want at least 4 spaces // after the input. This ensure that all targets line up together. diff --git a/packages/underscore-redirects/src/redirects.ts b/packages/underscore-redirects/src/redirects.ts index c46d41628251..c33e85b736d5 100644 --- a/packages/underscore-redirects/src/redirects.ts +++ b/packages/underscore-redirects/src/redirects.ts @@ -46,24 +46,26 @@ export class Redirects { } function binaryInsert(sorted: T[], item: T, comparator: (a: T, b: T) => boolean) { - if(sorted.length === 0) { - sorted.push(item); - return 0; - } - let low = 0, high = sorted.length - 1, mid = 0; - while (low <= high) { - mid = low + (high - low >> 1); - if(comparator(sorted[mid], item)) { - low = mid + 1; - } else { - high = mid -1; - } - } + if (sorted.length === 0) { + sorted.push(item); + return 0; + } + let low = 0, + high = sorted.length - 1, + mid = 0; + while (low <= high) { + mid = low + ((high - low) >> 1); + if (comparator(sorted[mid], item)) { + low = mid + 1; + } else { + high = mid - 1; + } + } - if(comparator(sorted[mid], item)) { - mid++; - } + if (comparator(sorted[mid], item)) { + mid++; + } - sorted.splice(mid, 0, item); - return mid; + sorted.splice(mid, 0, item); + return mid; } diff --git a/packages/underscore-redirects/test/astro.test.js b/packages/underscore-redirects/test/astro.test.js index 15a8aa0584d3..4ddd5c8e1c29 100644 --- a/packages/underscore-redirects/test/astro.test.js +++ b/packages/underscore-redirects/test/astro.test.js @@ -4,20 +4,20 @@ import { expect } from 'chai'; describe('Astro', () => { const serverConfig = { output: 'server', - build: { format: 'directory' } + build: { format: 'directory' }, }; it('Creates a Redirects object from routes', () => { const routes = [ { pathname: '/', distURL: new URL('./index.html', import.meta.url), segments: [] }, - { pathname: '/one', distURL: new URL('./one/index.html', import.meta.url), segments: [] } + { pathname: '/one', distURL: new URL('./one/index.html', import.meta.url), segments: [] }, ]; const dynamicTarget = './.adapter/dist/entry.mjs'; const _redirects = createRedirectsFromAstroRoutes({ config: serverConfig, routes, dir: new URL(import.meta.url), - dynamicTarget + dynamicTarget, }); expect(_redirects.definitions).to.have.a.lengthOf(2); diff --git a/packages/underscore-redirects/test/print.test.js b/packages/underscore-redirects/test/print.test.js index c04a8e9a9ebf..2714fa39925c 100644 --- a/packages/underscore-redirects/test/print.test.js +++ b/packages/underscore-redirects/test/print.test.js @@ -9,17 +9,17 @@ describe('Printing', () => { input: '/a', target: '/b', weight: 0, - status: 200 + status: 200, }); _redirects.add({ dynamic: false, input: '/some-pretty-long-input-line', target: '/b', weight: 0, - status: 200 + status: 200, }); let out = _redirects.print(); - + let [lineOne, lineTwo] = out.split('\n'); expect(lineOne.indexOf('/b')).to.equal(lineTwo.indexOf('/b'), 'destinations lined up'); @@ -33,12 +33,10 @@ describe('Printing', () => { input: '/pets/:cat', target: '/pets/:cat/index.html', status: 200, - weight: 1 + weight: 1, }); let out = _redirects.print(); let parts = out.split(/\s+/); - expect(parts).to.deep.equal([ - '/pets/:cat', '/pets/:cat/index.html', '200', - ]) + expect(parts).to.deep.equal(['/pets/:cat', '/pets/:cat/index.html', '200']); }); }); diff --git a/packages/underscore-redirects/test/weight.test.js b/packages/underscore-redirects/test/weight.test.js index 0c6014427e74..ed516f6ced8d 100644 --- a/packages/underscore-redirects/test/weight.test.js +++ b/packages/underscore-redirects/test/weight.test.js @@ -9,21 +9,21 @@ describe('Weight', () => { input: '/a', target: '/b', weight: 0, - status: 200 + status: 200, }); _redirects.add({ dynamic: false, input: '/c', target: '/d', weight: 0, - status: 200 + status: 200, }); _redirects.add({ dynamic: false, input: '/e', target: '/f', weight: 1, - status: 200 + status: 200, }); const firstDefn = _redirects.definitions[0]; expect(firstDefn.weight).to.equal(1);