[ci] format

This commit is contained in:
matthewp 2023-06-05 13:05:47 +00:00 committed by fredkbot
parent 57f8d14c02
commit eb459577f0
33 changed files with 225 additions and 202 deletions

View file

@ -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 [`<meta http-equiv="refresh">` 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<RouteData, 'generate' | 'pattern'> & {
generate: undefined;

View file

@ -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<ComponentInstance> {
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();

View file

@ -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<unknown> = {
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<SinglePageBuiltModule> {
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<string>
) {
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(
<title>Redirecting to: ${location}</title>
<meta http-equiv="refresh" content="0;url=${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 {

View file

@ -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;
}
}

View file

@ -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<string> = new Set();
for (const [path, pageData] of Object.entries(opts.allPages)) {
if(routeIsRedirect(pageData.route)) {
if (routeIsRedirect(pageData.route)) {
continue;
}
inputs.add(getVirtualModulePageNameFromPath(path));

View file

@ -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);

View file

@ -107,7 +107,7 @@ export function resolveFlags(flags: Partial<Flags>): CLIFlags {
experimentalMiddleware:
typeof flags.experimentalMiddleware === 'boolean' ? flags.experimentalMiddleware : undefined,
experimentalRedirects:
typeof flags.experimentalRedirects === 'boolean' ? flags.experimentalRedirects : undefined
typeof flags.experimentalRedirects === 'boolean' ? flags.experimentalRedirects : undefined,
};
}

View file

@ -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,

View file

@ -4,7 +4,7 @@ import type { ComponentInstance } from '../../@types/astro';
export const RedirectComponentInstance: ComponentInstance = {
default() {
return new Response(null, {
status: 301
status: 301,
});
}
},
};

View file

@ -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;

View file

@ -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';

View file

@ -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,
});
}

View file

@ -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),
},
});
}

View file

@ -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,
};
}

View file

@ -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());

View file

@ -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;
}

View file

@ -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');
})
})
});
});
});

View file

@ -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]');
})
});
});

View file

@ -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()

View file

@ -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);
}

View file

@ -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');
}
}
},
},
};
}

View file

@ -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();

View file

@ -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',
]);
});
});

View file

@ -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',
]);
});
});

View file

@ -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') {

View file

@ -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);

View file

@ -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';

View file

@ -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';

View file

@ -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.

View file

@ -46,24 +46,26 @@ export class Redirects {
}
function binaryInsert<T>(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;
}

View file

@ -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);

View file

@ -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']);
});
});

View file

@ -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);