Implement redirects in Cloudflare (#7198)
* Implement redirects in Cloudflare * Fix build * Update tests b/c of new ordering * Debug issue * Use posix.join * Update packages/underscore-redirects/package.json Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update based on review comments * Update broken test --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
parent
af2ceea276
commit
8b4d248a36
18 changed files with 492 additions and 167 deletions
|
@ -38,6 +38,7 @@
|
|||
"test": "mocha --exit --timeout 30000 test/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/underscore-redirects": "^0.1.0",
|
||||
"esbuild": "^0.17.12",
|
||||
"tiny-glob": "^0.2.9"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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';
|
||||
|
@ -88,7 +89,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
|||
vite.ssr.target = 'webworker';
|
||||
}
|
||||
},
|
||||
'astro:build:done': async ({ pages }) => {
|
||||
'astro:build:done': async ({ pages, routes, dir }) => {
|
||||
const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
|
||||
const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
|
||||
const buildPath = fileURLToPath(entryUrl);
|
||||
|
@ -197,6 +198,19 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
|||
}
|
||||
}
|
||||
|
||||
const redirectRoutes = routes.filter(r => r.type === 'redirect');
|
||||
const trueRedirects = createRedirectsFromAstroRoutes({
|
||||
config: _config,
|
||||
routes: redirectRoutes,
|
||||
dir,
|
||||
});
|
||||
if(!trueRedirects.empty()) {
|
||||
await fs.promises.appendFile(
|
||||
new URL('./_redirects', _config.outDir),
|
||||
trueRedirects.print()
|
||||
);
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(
|
||||
new URL('./_routes.json', _config.outDir),
|
||||
JSON.stringify(
|
||||
|
|
|
@ -11,6 +11,9 @@ describe('mode: "directory"', () => {
|
|||
root: './fixtures/basics/',
|
||||
output: 'server',
|
||||
adapter: cloudflare({ mode: 'directory' }),
|
||||
redirects: {
|
||||
'/old': '/'
|
||||
}
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
@ -19,4 +22,16 @@ describe('mode: "directory"', () => {
|
|||
expect(await fixture.pathExists('../functions')).to.be.true;
|
||||
expect(await fixture.pathExists('../functions/[[path]].js')).to.be.true;
|
||||
});
|
||||
|
||||
it('generates a redirects file', async () => {
|
||||
try {
|
||||
let _redirects = await fixture.readFile('/_redirects');
|
||||
let parts = _redirects.split(/\s+/);
|
||||
expect(parts).to.deep.equal([
|
||||
'/old', '/', '301'
|
||||
]);
|
||||
} catch {
|
||||
expect(false).to.equal(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@astrojs/webapi": "^2.1.1",
|
||||
"@astrojs/underscore-redirects": "^0.1.0",
|
||||
"@netlify/functions": "^1.0.0",
|
||||
"esbuild": "^0.15.18"
|
||||
},
|
||||
|
|
|
@ -1,20 +1,6 @@
|
|||
import type { AstroConfig, RouteData, ValidRedirectStatus } from 'astro';
|
||||
import fs from 'fs';
|
||||
|
||||
export type RedirectDefinition = {
|
||||
dynamic: boolean;
|
||||
input: string;
|
||||
target: string;
|
||||
weight: 0 | 1;
|
||||
status: 200 | 404 | ValidRedirectStatus;
|
||||
};
|
||||
|
||||
function getRedirectStatus(route: RouteData): ValidRedirectStatus {
|
||||
if(typeof route.redirect === 'object') {
|
||||
return route.redirect.status;
|
||||
}
|
||||
return 301;
|
||||
}
|
||||
import type { AstroConfig, RouteData } from 'astro';
|
||||
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
|
||||
import fs from 'node:fs';
|
||||
|
||||
export async function createRedirects(
|
||||
config: AstroConfig,
|
||||
|
@ -23,151 +9,17 @@ export async function createRedirects(
|
|||
entryFile: string,
|
||||
type: 'functions' | 'edge-functions' | 'builders' | 'static'
|
||||
) {
|
||||
const _redirectsURL = new URL('./_redirects', dir);
|
||||
const kind = type ?? 'functions';
|
||||
const dynamicTarget = `/.netlify/${kind}/${entryFile}`;
|
||||
const _redirectsURL = new URL('./_redirects', dir);
|
||||
|
||||
const definitions: RedirectDefinition[] = [];
|
||||
|
||||
for (const route of routes) {
|
||||
if (route.pathname) {
|
||||
if(route.redirect) {
|
||||
definitions.push({
|
||||
dynamic: false,
|
||||
input: route.pathname,
|
||||
target: typeof route.redirect === 'object' ? route.redirect.destination : route.redirect,
|
||||
status: getRedirectStatus(route),
|
||||
weight: 1
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(kind === 'static') {
|
||||
continue;
|
||||
}
|
||||
else if (route.distURL) {
|
||||
definitions.push({
|
||||
dynamic: false,
|
||||
input: route.pathname,
|
||||
target: prependForwardSlash(route.distURL.toString().replace(dir.toString(), '')),
|
||||
status: 200,
|
||||
weight: 1,
|
||||
});
|
||||
} else {
|
||||
definitions.push({
|
||||
dynamic: false,
|
||||
input: route.pathname,
|
||||
target: `/.netlify/${kind}/${entryFile}`,
|
||||
status: 200,
|
||||
weight: 1,
|
||||
});
|
||||
|
||||
if (route.route === '/404') {
|
||||
definitions.push({
|
||||
dynamic: true,
|
||||
input: '/*',
|
||||
target: `/.netlify/${kind}/${entryFile}`,
|
||||
status: 404,
|
||||
weight: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const pattern = generateDynamicPattern(route);
|
||||
|
||||
if (route.distURL) {
|
||||
const targetRoute = route.redirectRoute ?? route;
|
||||
const targetPattern = generateDynamicPattern(targetRoute);
|
||||
const target =
|
||||
`${targetPattern}` + (config.build.format === 'directory' ? '/index.html' : '.html');
|
||||
definitions.push({
|
||||
dynamic: true,
|
||||
input: pattern,
|
||||
target,
|
||||
status: route.type === 'redirect' ? 301 : 200,
|
||||
weight: 1,
|
||||
});
|
||||
} else {
|
||||
definitions.push({
|
||||
dynamic: true,
|
||||
input: pattern,
|
||||
target: `/.netlify/${kind}/${entryFile}`,
|
||||
status: 200,
|
||||
weight: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _redirects = prettify(definitions);
|
||||
const _redirects = createRedirectsFromAstroRoutes({
|
||||
config, routes, dir, dynamicTarget
|
||||
});
|
||||
const content = _redirects.print();
|
||||
|
||||
// Always use appendFile() because the redirects file could already exist,
|
||||
// e.g. due to a `/public/_redirects` file that got copied to the output dir.
|
||||
// If the file does not exist yet, appendFile() automatically creates it.
|
||||
await fs.promises.appendFile(_redirectsURL, _redirects, 'utf-8');
|
||||
}
|
||||
|
||||
function generateDynamicPattern(route: RouteData) {
|
||||
const pattern =
|
||||
'/' +
|
||||
route.segments
|
||||
.map(([part]) => {
|
||||
//(part.dynamic ? '*' : part.content)
|
||||
if (part.dynamic) {
|
||||
if (part.spread) {
|
||||
return '*';
|
||||
} else {
|
||||
return ':' + part.content;
|
||||
}
|
||||
} else {
|
||||
return part.content;
|
||||
}
|
||||
})
|
||||
.join('/');
|
||||
return pattern;
|
||||
}
|
||||
|
||||
function prettify(definitions: RedirectDefinition[]) {
|
||||
let minInputLength = 4,
|
||||
minTargetLength = 4;
|
||||
definitions.sort((a, b) => {
|
||||
// Find the longest input, so we can format things nicely
|
||||
if (a.input.length > minInputLength) {
|
||||
minInputLength = a.input.length;
|
||||
}
|
||||
if (b.input.length > minInputLength) {
|
||||
minInputLength = b.input.length;
|
||||
}
|
||||
|
||||
// Same for the target
|
||||
if (a.target.length > minTargetLength) {
|
||||
minTargetLength = a.target.length;
|
||||
}
|
||||
if (b.target.length > minTargetLength) {
|
||||
minTargetLength = b.target.length;
|
||||
}
|
||||
|
||||
// Sort dynamic routes on top
|
||||
return b.weight - a.weight;
|
||||
});
|
||||
|
||||
let _redirects = '';
|
||||
// Loop over the definitions
|
||||
definitions.forEach((defn, 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.
|
||||
let inputSpaces = minInputLength - defn.input.length + 4;
|
||||
let targetSpaces = minTargetLength - defn.target.length + 4;
|
||||
_redirects +=
|
||||
(i === 0 ? '' : '\n') +
|
||||
defn.input +
|
||||
' '.repeat(inputSpaces) +
|
||||
defn.target +
|
||||
' '.repeat(Math.abs(targetSpaces)) +
|
||||
defn.status;
|
||||
});
|
||||
return _redirects;
|
||||
}
|
||||
|
||||
function prependForwardSlash(str: string) {
|
||||
return str[0] === '/' ? str : '/' + str;
|
||||
await fs.promises.appendFile(_redirectsURL, content, 'utf-8');
|
||||
}
|
||||
|
|
|
@ -28,12 +28,11 @@ describe('SSG - Redirects', () => {
|
|||
let redirects = await fixture.readFile('/_redirects');
|
||||
let parts = redirects.split(/\s+/);
|
||||
expect(parts).to.deep.equal([
|
||||
'/other', '/', '301',
|
||||
'/', '/.netlify/functions/entry', '200',
|
||||
|
||||
// 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',
|
||||
'/other', '/', '301',
|
||||
|
||||
// A real route
|
||||
'/team/articles/*', '/.netlify/functions/entry', '200',
|
||||
|
|
|
@ -27,13 +27,14 @@ describe('SSG - Redirects', () => {
|
|||
|
||||
it('Creates a redirects file', async () => {
|
||||
let redirects = await fixture.readFile('/_redirects');
|
||||
console.log(redirects)
|
||||
let parts = redirects.split(/\s+/);
|
||||
expect(parts).to.deep.equal([
|
||||
'/blog/*', '/team/articles/*/index.html', '301',
|
||||
'/two', '/', '302',
|
||||
'/other', '/', '301',
|
||||
'/nope', '/', '301',
|
||||
'/team/articles/*', '/team/articles/*/index.html', '200'
|
||||
'/other', '/', '301',
|
||||
'/two', '/', '302',
|
||||
'/team/articles/*', '/team/articles/*/index.html', '200',
|
||||
'/blog/*', '/team/articles/*/index.html', '301',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
42
packages/underscore-redirects/package.json
Normal file
42
packages/underscore-redirects/package.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "@astrojs/underscore-redirects",
|
||||
"description": "Utilities to generate _redirects files in Astro projects",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"author": "withastro",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/withastro/astro.git",
|
||||
"directory": "packages/underscore-redirects"
|
||||
},
|
||||
"bugs": "https://github.com/withastro/astro/issues",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublish": "pnpm build",
|
||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc -p tsconfig.json",
|
||||
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
||||
"postbuild": "astro-scripts copy \"src/**/*.js\"",
|
||||
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
||||
"test": "mocha --exit --timeout 20000"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "workspace:*",
|
||||
"astro-scripts": "workspace:*",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.2"
|
||||
},
|
||||
"keywords": [
|
||||
"astro",
|
||||
"astro-component"
|
||||
]
|
||||
}
|
3
packages/underscore-redirects/readme.md
Normal file
3
packages/underscore-redirects/readme.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @astrojs/underscore-redirects
|
||||
|
||||
These are internal helpers used by core Astro packages. This package does not follow semver and should not be used externally.
|
145
packages/underscore-redirects/src/astro.ts
Normal file
145
packages/underscore-redirects/src/astro.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
import type { AstroConfig, RouteData, ValidRedirectStatus } from 'astro';
|
||||
import { Redirects } from './redirects.js';
|
||||
import { posix } from 'node:path';
|
||||
|
||||
const pathJoin = posix.join;
|
||||
|
||||
function getRedirectStatus(route: RouteData): ValidRedirectStatus {
|
||||
if(typeof route.redirect === 'object') {
|
||||
return route.redirect.status;
|
||||
}
|
||||
return 301;
|
||||
}
|
||||
|
||||
interface CreateRedirectsFromAstroRoutesParams {
|
||||
config: Pick<AstroConfig, 'output' | 'build'>;
|
||||
routes: RouteData[];
|
||||
dir: URL;
|
||||
dynamicTarget?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a set of routes and creates a Redirects object from them.
|
||||
*/
|
||||
export function createRedirectsFromAstroRoutes({
|
||||
config,
|
||||
routes,
|
||||
dir,
|
||||
dynamicTarget = '',
|
||||
}: CreateRedirectsFromAstroRoutesParams) {
|
||||
const output = config.output;
|
||||
const _redirects = new Redirects();
|
||||
|
||||
for (const route of routes) {
|
||||
// A route with a `pathname` is as static route.
|
||||
if (route.pathname) {
|
||||
if(route.redirect) {
|
||||
// A redirect route without dynamic parts. Get the redirect status
|
||||
// from the user if provided.
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: route.pathname,
|
||||
target: typeof route.redirect === 'object' ? route.redirect.destination : route.redirect,
|
||||
status: getRedirectStatus(route),
|
||||
weight: 2
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is a static build we don't want to add redirects to the HTML file.
|
||||
if(output === 'static') {
|
||||
continue;
|
||||
}
|
||||
|
||||
else if (route.distURL) {
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: route.pathname,
|
||||
target: prependForwardSlash(route.distURL.toString().replace(dir.toString(), '')),
|
||||
status: 200,
|
||||
weight: 2,
|
||||
});
|
||||
} else {
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: route.pathname,
|
||||
target: dynamicTarget,
|
||||
status: 200,
|
||||
weight: 2,
|
||||
});
|
||||
|
||||
if (route.route === '/404') {
|
||||
_redirects.add({
|
||||
dynamic: true,
|
||||
input: '/*',
|
||||
target: dynamicTarget,
|
||||
status: 404,
|
||||
weight: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is the dynamic route code. This generates a pattern from a dynamic
|
||||
// route formatted with *s in place of the Astro dynamic/spread syntax.
|
||||
const pattern = generateDynamicPattern(route);
|
||||
|
||||
// This route was prerendered and should be forwarded to the HTML file.
|
||||
if (route.distURL) {
|
||||
const targetRoute = route.redirectRoute ?? route;
|
||||
const targetPattern = generateDynamicPattern(targetRoute);
|
||||
let target = targetPattern;
|
||||
if(config.build.format === 'directory') {
|
||||
target = pathJoin(target, 'index.html');
|
||||
} else {
|
||||
target += '.html';
|
||||
}
|
||||
_redirects.add({
|
||||
dynamic: true,
|
||||
input: pattern,
|
||||
target,
|
||||
status: route.type === 'redirect' ? 301 : 200,
|
||||
weight: 1,
|
||||
});
|
||||
} else {
|
||||
_redirects.add({
|
||||
dynamic: true,
|
||||
input: pattern,
|
||||
target: dynamicTarget,
|
||||
status: 200,
|
||||
weight: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Astro dynamic route into one formatted like:
|
||||
* /team/articles/*
|
||||
* With stars replacing spread and :id syntax replacing [id]
|
||||
*/
|
||||
function generateDynamicPattern(route: RouteData) {
|
||||
const pattern =
|
||||
'/' +
|
||||
route.segments
|
||||
.map(([part]) => {
|
||||
//(part.dynamic ? '*' : part.content)
|
||||
if (part.dynamic) {
|
||||
if (part.spread) {
|
||||
return '*';
|
||||
} else {
|
||||
return ':' + part.content;
|
||||
}
|
||||
} else {
|
||||
return part.content;
|
||||
}
|
||||
})
|
||||
.join('/');
|
||||
return pattern;
|
||||
}
|
||||
|
||||
function prependForwardSlash(str: string) {
|
||||
return str[0] === '/' ? str : '/' + str;
|
||||
}
|
8
packages/underscore-redirects/src/index.ts
Normal file
8
packages/underscore-redirects/src/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export {
|
||||
Redirects,
|
||||
type RedirectDefinition
|
||||
} from './redirects.js';
|
||||
|
||||
export {
|
||||
createRedirectsFromAstroRoutes
|
||||
} from './astro.js';
|
36
packages/underscore-redirects/src/print.ts
Normal file
36
packages/underscore-redirects/src/print.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import type { RedirectDefinition } from './redirects';
|
||||
|
||||
/**
|
||||
* Pretty print a list of definitions into the output format. Keeps
|
||||
* things readable for humans. Ex:
|
||||
* /nope / 301
|
||||
* /other / 301
|
||||
* /two / 302
|
||||
* /team/articles/* /team/articles/*\/index.html 200
|
||||
* /blog/* /team/articles/*\/index.html 301
|
||||
*/
|
||||
export function print(
|
||||
definitions: RedirectDefinition[],
|
||||
minInputLength: number,
|
||||
minTargetLength: number
|
||||
) {
|
||||
let _redirects = '';
|
||||
|
||||
// Loop over the definitions
|
||||
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.
|
||||
let inputSpaces = minInputLength - definition.input.length + 4;
|
||||
let targetSpaces = minTargetLength - definition.target.length + 4;
|
||||
_redirects +=
|
||||
(i === 0 ? '' : '\n') +
|
||||
definition.input +
|
||||
' '.repeat(inputSpaces) +
|
||||
definition.target +
|
||||
' '.repeat(Math.abs(targetSpaces)) +
|
||||
definition.status;
|
||||
}
|
||||
|
||||
return _redirects;
|
||||
}
|
69
packages/underscore-redirects/src/redirects.ts
Normal file
69
packages/underscore-redirects/src/redirects.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import { print } from './print.js';
|
||||
|
||||
export type RedirectDefinition = {
|
||||
dynamic: boolean;
|
||||
input: string;
|
||||
target: string;
|
||||
// Allows specifying a weight to the definition.
|
||||
// This allows insertion of definitions out of order but having
|
||||
// a priority once inserted.
|
||||
weight: number;
|
||||
status: number;
|
||||
};
|
||||
|
||||
export class Redirects {
|
||||
public definitions: RedirectDefinition[] = [];
|
||||
public minInputLength = 4;
|
||||
public minTargetLength = 4;
|
||||
|
||||
/**
|
||||
* Adds a new definition by inserting it into the list of definitions
|
||||
* prioritized by the given weight. This keeps higher priority definitions
|
||||
* At the top of the list once printed.
|
||||
*/
|
||||
add(definition: RedirectDefinition) {
|
||||
// Find the longest input, so we can format things nicely
|
||||
if (definition.input.length > this.minInputLength) {
|
||||
this.minInputLength = definition.input.length;
|
||||
}
|
||||
// Same for the target
|
||||
if (definition.target.length > this.minTargetLength) {
|
||||
this.minTargetLength = definition.target.length;
|
||||
}
|
||||
|
||||
binaryInsert(this.definitions, definition, (a, b) => {
|
||||
return a.weight > b.weight;
|
||||
});
|
||||
}
|
||||
|
||||
print(): string {
|
||||
return print(this.definitions, this.minInputLength, this.minTargetLength);
|
||||
}
|
||||
|
||||
empty(): boolean {
|
||||
return this.definitions.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
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(comparator(sorted[mid], item)) {
|
||||
mid++;
|
||||
}
|
||||
|
||||
sorted.splice(mid, 0, item);
|
||||
return mid;
|
||||
}
|
25
packages/underscore-redirects/test/astro.test.js
Normal file
25
packages/underscore-redirects/test/astro.test.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { createRedirectsFromAstroRoutes } from '../dist/index.js';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('Astro', () => {
|
||||
const serverConfig = {
|
||||
output: 'server',
|
||||
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: [] }
|
||||
];
|
||||
const dynamicTarget = './.adapter/dist/entry.mjs';
|
||||
const _redirects = createRedirectsFromAstroRoutes({
|
||||
config: serverConfig,
|
||||
routes,
|
||||
dir: new URL(import.meta.url),
|
||||
dynamicTarget
|
||||
});
|
||||
|
||||
expect(_redirects.definitions).to.have.a.lengthOf(2);
|
||||
});
|
||||
});
|
44
packages/underscore-redirects/test/print.test.js
Normal file
44
packages/underscore-redirects/test/print.test.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { Redirects } from '../dist/index.js';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('Printing', () => {
|
||||
it('Formats long lines in a pretty way', () => {
|
||||
const _redirects = new Redirects();
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: '/a',
|
||||
target: '/b',
|
||||
weight: 0,
|
||||
status: 200
|
||||
});
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: '/some-pretty-long-input-line',
|
||||
target: '/b',
|
||||
weight: 0,
|
||||
status: 200
|
||||
});
|
||||
let out = _redirects.print();
|
||||
|
||||
let [lineOne, lineTwo] = out.split('\n');
|
||||
|
||||
expect(lineOne.indexOf('/b')).to.equal(lineTwo.indexOf('/b'), 'destinations lined up');
|
||||
expect(lineOne.indexOf('200')).to.equal(lineTwo.indexOf('200'), 'statuses lined up');
|
||||
});
|
||||
|
||||
it('Properly prints dynamic routes', () => {
|
||||
const _redirects = new Redirects();
|
||||
_redirects.add({
|
||||
dynamic: true,
|
||||
input: '/pets/:cat',
|
||||
target: '/pets/:cat/index.html',
|
||||
status: 200,
|
||||
weight: 1
|
||||
});
|
||||
let out = _redirects.print();
|
||||
let parts = out.split(/\s+/);
|
||||
expect(parts).to.deep.equal([
|
||||
'/pets/:cat', '/pets/:cat/index.html', '200',
|
||||
])
|
||||
});
|
||||
});
|
32
packages/underscore-redirects/test/weight.test.js
Normal file
32
packages/underscore-redirects/test/weight.test.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Redirects } from '../dist/index.js';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('Weight', () => {
|
||||
it('Puts higher weighted definitions on top', () => {
|
||||
const _redirects = new Redirects();
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: '/a',
|
||||
target: '/b',
|
||||
weight: 0,
|
||||
status: 200
|
||||
});
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: '/c',
|
||||
target: '/d',
|
||||
weight: 0,
|
||||
status: 200
|
||||
});
|
||||
_redirects.add({
|
||||
dynamic: false,
|
||||
input: '/e',
|
||||
target: '/f',
|
||||
weight: 1,
|
||||
status: 200
|
||||
});
|
||||
const firstDefn = _redirects.definitions[0];
|
||||
expect(firstDefn.weight).to.equal(1);
|
||||
expect(firstDefn.input).to.equal('/e');
|
||||
});
|
||||
});
|
10
packages/underscore-redirects/tsconfig.json
Normal file
10
packages/underscore-redirects/tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"target": "ES2021",
|
||||
"module": "ES2022",
|
||||
"outDir": "./dist"
|
||||
}
|
||||
}
|
|
@ -3630,6 +3630,9 @@ importers:
|
|||
|
||||
packages/integrations/cloudflare:
|
||||
dependencies:
|
||||
'@astrojs/underscore-redirects':
|
||||
specifier: ^0.1.0
|
||||
version: link:../../underscore-redirects
|
||||
esbuild:
|
||||
specifier: ^0.17.12
|
||||
version: 0.17.12
|
||||
|
@ -4360,6 +4363,9 @@ importers:
|
|||
|
||||
packages/integrations/netlify:
|
||||
dependencies:
|
||||
'@astrojs/underscore-redirects':
|
||||
specifier: ^0.1.0
|
||||
version: link:../../underscore-redirects
|
||||
'@astrojs/webapi':
|
||||
specifier: ^2.1.1
|
||||
version: link:../../webapi
|
||||
|
@ -5219,6 +5225,27 @@ importers:
|
|||
specifier: ^9.2.2
|
||||
version: 9.2.2
|
||||
|
||||
packages/underscore-redirects:
|
||||
devDependencies:
|
||||
'@types/chai':
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.3
|
||||
'@types/mocha':
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1
|
||||
astro:
|
||||
specifier: workspace:*
|
||||
version: link:../astro
|
||||
astro-scripts:
|
||||
specifier: workspace:*
|
||||
version: link:../../scripts
|
||||
chai:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
mocha:
|
||||
specifier: ^9.2.2
|
||||
version: 9.2.2
|
||||
|
||||
packages/webapi:
|
||||
dependencies:
|
||||
undici:
|
||||
|
@ -8783,11 +8810,12 @@ packages:
|
|||
/@types/chai-subset@1.3.3:
|
||||
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
|
||||
dependencies:
|
||||
'@types/chai': 4.3.3
|
||||
'@types/chai': 4.3.5
|
||||
dev: false
|
||||
|
||||
/@types/chai@4.3.3:
|
||||
resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==}
|
||||
dev: true
|
||||
|
||||
/@types/chai@4.3.5:
|
||||
resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==}
|
||||
|
|
Loading…
Reference in a new issue