inital commit

This commit is contained in:
2026-01-01 15:25:19 +05:30
commit f0ae49465a
36361 changed files with 4894111 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
import { ACTION_REFRESH, ACTION_SERVER_ACTION, ACTION_NAVIGATE, ACTION_RESTORE } from '../../../client/components/router-reducer/router-reducer-types';
import { reducer } from '../../../client/components/router-reducer/router-reducer';
import { startTransition } from 'react';
import { isThenable } from '../is-thenable';
function runRemainingActions(actionQueue, setState) {
if (actionQueue.pending !== null) {
actionQueue.pending = actionQueue.pending.next;
if (actionQueue.pending !== null) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
runAction({
actionQueue,
action: actionQueue.pending,
setState
});
} else {
// No more actions are pending, check if a refresh is needed
if (actionQueue.needsRefresh) {
actionQueue.needsRefresh = false;
actionQueue.dispatch({
type: ACTION_REFRESH,
origin: window.location.origin
}, setState);
}
}
}
}
async function runAction(param) {
let { actionQueue, action, setState } = param;
const prevState = actionQueue.state;
actionQueue.pending = action;
const payload = action.payload;
const actionResult = actionQueue.action(prevState, payload);
function handleResult(nextState) {
// if we discarded this action, the state should also be discarded
if (action.discarded) {
return;
}
actionQueue.state = nextState;
runRemainingActions(actionQueue, setState);
action.resolve(nextState);
}
// if the action is a promise, set up a callback to resolve it
if (isThenable(actionResult)) {
actionResult.then(handleResult, (err)=>{
runRemainingActions(actionQueue, setState);
action.reject(err);
});
} else {
handleResult(actionResult);
}
}
function dispatchAction(actionQueue, payload, setState) {
let resolvers = {
resolve: setState,
reject: ()=>{}
};
// most of the action types are async with the exception of restore
// it's important that restore is handled quickly since it's fired on the popstate event
// and we don't want to add any delay on a back/forward nav
// this only creates a promise for the async actions
if (payload.type !== ACTION_RESTORE) {
// Create the promise and assign the resolvers to the object.
const deferredPromise = new Promise((resolve, reject)=>{
resolvers = {
resolve,
reject
};
});
startTransition(()=>{
// we immediately notify React of the pending promise -- the resolver is attached to the action node
// and will be called when the associated action promise resolves
setState(deferredPromise);
});
}
const newAction = {
payload,
next: null,
resolve: resolvers.resolve,
reject: resolvers.reject
};
// Check if the queue is empty
if (actionQueue.pending === null) {
// The queue is empty, so add the action and start it immediately
// Mark this action as the last in the queue
actionQueue.last = newAction;
runAction({
actionQueue,
action: newAction,
setState
});
} else if (payload.type === ACTION_NAVIGATE || payload.type === ACTION_RESTORE) {
// Navigations (including back/forward) take priority over any pending actions.
// Mark the pending action as discarded (so the state is never applied) and start the navigation action immediately.
actionQueue.pending.discarded = true;
// The rest of the current queue should still execute after this navigation.
// (Note that it can't contain any earlier navigations, because we always put those into `actionQueue.pending` by calling `runAction`)
newAction.next = actionQueue.pending.next;
// if the pending action was a server action, mark the queue as needing a refresh once events are processed
if (actionQueue.pending.payload.type === ACTION_SERVER_ACTION) {
actionQueue.needsRefresh = true;
}
runAction({
actionQueue,
action: newAction,
setState
});
} else {
// The queue is not empty, so add the action to the end of the queue
// It will be started by runRemainingActions after the previous action finishes
if (actionQueue.last !== null) {
actionQueue.last.next = newAction;
}
actionQueue.last = newAction;
}
}
let globalActionQueue = null;
export function createMutableActionQueue(initialState) {
const actionQueue = {
state: initialState,
dispatch: (payload, setState)=>dispatchAction(actionQueue, payload, setState),
action: async (state, action)=>{
const result = reducer(state, action);
return result;
},
pending: null,
last: null
};
if (typeof window !== 'undefined') {
// The action queue is lazily created on hydration, but after that point
// it doesn't change. So we can store it in a global rather than pass
// it around everywhere via props/context.
if (globalActionQueue !== null) {
throw Object.defineProperty(new Error('Internal Next.js Error: createMutableActionQueue was called more ' + 'than once'), "__NEXT_ERROR_CODE", {
value: "E624",
enumerable: false,
configurable: true
});
}
globalActionQueue = actionQueue;
}
return actionQueue;
}
export function getCurrentAppRouterState() {
return globalActionQueue !== null ? globalActionQueue.state : null;
}
//# sourceMappingURL=action-queue.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,113 @@
import { jsx as _jsx } from "react/jsx-runtime";
import React, { useMemo, useRef } from 'react';
import { PathnameContext } from '../hooks-client-context.shared-runtime';
import { isDynamicRoute } from './utils';
import { asPathToSearchParams } from './utils/as-path-to-search-params';
import { getRouteRegex } from './utils/route-regex';
/** It adapts a Pages Router (`NextRouter`) to the App Router Instance. */ export function adaptForAppRouterInstance(pagesRouter) {
return {
back () {
pagesRouter.back();
},
forward () {
pagesRouter.forward();
},
refresh () {
pagesRouter.reload();
},
hmrRefresh () {},
push (href, param) {
let { scroll } = param === void 0 ? {} : param;
void pagesRouter.push(href, undefined, {
scroll
});
},
replace (href, param) {
let { scroll } = param === void 0 ? {} : param;
void pagesRouter.replace(href, undefined, {
scroll
});
},
prefetch (href) {
void pagesRouter.prefetch(href);
}
};
}
/**
* adaptForSearchParams transforms the ParsedURLQuery into URLSearchParams.
*
* @param router the router that contains the query.
* @returns the search params in the URLSearchParams format
*/ export function adaptForSearchParams(router) {
if (!router.isReady || !router.query) {
return new URLSearchParams();
}
return asPathToSearchParams(router.asPath);
}
export function adaptForPathParams(router) {
if (!router.isReady || !router.query) {
return null;
}
const pathParams = {};
const routeRegex = getRouteRegex(router.pathname);
const keys = Object.keys(routeRegex.groups);
for (const key of keys){
pathParams[key] = router.query[key];
}
return pathParams;
}
export function PathnameContextProviderAdapter(param) {
let { children, router, ...props } = param;
const ref = useRef(props.isAutoExport);
const value = useMemo(()=>{
// isAutoExport is only ever `true` on the first render from the server,
// so reset it to `false` after we read it for the first time as `true`. If
// we don't use the value, then we don't need it.
const isAutoExport = ref.current;
if (isAutoExport) {
ref.current = false;
}
// When the route is a dynamic route, we need to do more processing to
// determine if we need to stop showing the pathname.
if (isDynamicRoute(router.pathname)) {
// When the router is rendering the fallback page, it can't possibly know
// the path, so return `null` here. Read more about fallback pages over
// at:
// https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-pages
if (router.isFallback) {
return null;
}
// When `isAutoExport` is true, meaning this is a page page has been
// automatically statically optimized, and the router is not ready, then
// we can't know the pathname yet. Read more about automatic static
// optimization at:
// https://nextjs.org/docs/advanced-features/automatic-static-optimization
if (isAutoExport && !router.isReady) {
return null;
}
}
// The `router.asPath` contains the pathname seen by the browser (including
// any query strings), so it should have that stripped. Read more about the
// `asPath` option over at:
// https://nextjs.org/docs/api-reference/next/router#router-object
let url;
try {
url = new URL(router.asPath, 'http://f');
} catch (_) {
// fallback to / for invalid asPath values e.g. //
return '/';
}
return url.pathname;
}, [
router.asPath,
router.isFallback,
router.isReady,
router.pathname
]);
return /*#__PURE__*/ _jsx(PathnameContext.Provider, {
value: value,
children: children
});
}
//# sourceMappingURL=adapters.js.map

File diff suppressed because one or more lines are too long

1766
node_modules/next/dist/esm/shared/lib/router/router.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
import { addPathPrefix } from './add-path-prefix';
import { pathHasPrefix } from './path-has-prefix';
/**
* For a given path and a locale, if the locale is given, it will prefix the
* locale. The path shouldn't be an API path. If a default locale is given the
* prefix will be omitted if the locale is already the default locale.
*/ export function addLocale(path, locale, defaultLocale, ignorePrefix) {
// If no locale was given or the locale is the default locale, we don't need
// to prefix the path.
if (!locale || locale === defaultLocale) return path;
const lower = path.toLowerCase();
// If the path is an API path or the path already has the locale prefix, we
// don't need to prefix the path.
if (!ignorePrefix) {
if (pathHasPrefix(lower, '/api')) return path;
if (pathHasPrefix(lower, "/" + locale.toLowerCase())) return path;
}
// Add the locale prefix to the path.
return addPathPrefix(path, "/" + locale);
}
//# sourceMappingURL=add-locale.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/add-locale.ts"],"sourcesContent":["import { addPathPrefix } from './add-path-prefix'\nimport { pathHasPrefix } from './path-has-prefix'\n\n/**\n * For a given path and a locale, if the locale is given, it will prefix the\n * locale. The path shouldn't be an API path. If a default locale is given the\n * prefix will be omitted if the locale is already the default locale.\n */\nexport function addLocale(\n path: string,\n locale?: string | false,\n defaultLocale?: string,\n ignorePrefix?: boolean\n) {\n // If no locale was given or the locale is the default locale, we don't need\n // to prefix the path.\n if (!locale || locale === defaultLocale) return path\n\n const lower = path.toLowerCase()\n\n // If the path is an API path or the path already has the locale prefix, we\n // don't need to prefix the path.\n if (!ignorePrefix) {\n if (pathHasPrefix(lower, '/api')) return path\n if (pathHasPrefix(lower, `/${locale.toLowerCase()}`)) return path\n }\n\n // Add the locale prefix to the path.\n return addPathPrefix(path, `/${locale}`)\n}\n"],"names":["addPathPrefix","pathHasPrefix","addLocale","path","locale","defaultLocale","ignorePrefix","lower","toLowerCase"],"mappings":"AAAA,SAASA,aAAa,QAAQ,oBAAmB;AACjD,SAASC,aAAa,QAAQ,oBAAmB;AAEjD;;;;CAIC,GACD,OAAO,SAASC,UACdC,IAAY,EACZC,MAAuB,EACvBC,aAAsB,EACtBC,YAAsB;IAEtB,4EAA4E;IAC5E,sBAAsB;IACtB,IAAI,CAACF,UAAUA,WAAWC,eAAe,OAAOF;IAEhD,MAAMI,QAAQJ,KAAKK,WAAW;IAE9B,2EAA2E;IAC3E,iCAAiC;IACjC,IAAI,CAACF,cAAc;QACjB,IAAIL,cAAcM,OAAO,SAAS,OAAOJ;QACzC,IAAIF,cAAcM,OAAO,AAAC,MAAGH,OAAOI,WAAW,KAAO,OAAOL;IAC/D;IAEA,qCAAqC;IACrC,OAAOH,cAAcG,MAAM,AAAC,MAAGC;AACjC"}

View File

@@ -0,0 +1,13 @@
import { parsePath } from './parse-path';
/**
* Adds the provided prefix to the given path. It first ensures that the path
* is indeed starting with a slash.
*/ export function addPathPrefix(path, prefix) {
if (!path.startsWith('/') || !prefix) {
return path;
}
const { pathname, query, hash } = parsePath(path);
return "" + prefix + pathname + query + hash;
}
//# sourceMappingURL=add-path-prefix.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/add-path-prefix.ts"],"sourcesContent":["import { parsePath } from './parse-path'\n\n/**\n * Adds the provided prefix to the given path. It first ensures that the path\n * is indeed starting with a slash.\n */\nexport function addPathPrefix(path: string, prefix?: string) {\n if (!path.startsWith('/') || !prefix) {\n return path\n }\n\n const { pathname, query, hash } = parsePath(path)\n return `${prefix}${pathname}${query}${hash}`\n}\n"],"names":["parsePath","addPathPrefix","path","prefix","startsWith","pathname","query","hash"],"mappings":"AAAA,SAASA,SAAS,QAAQ,eAAc;AAExC;;;CAGC,GACD,OAAO,SAASC,cAAcC,IAAY,EAAEC,MAAe;IACzD,IAAI,CAACD,KAAKE,UAAU,CAAC,QAAQ,CAACD,QAAQ;QACpC,OAAOD;IACT;IAEA,MAAM,EAAEG,QAAQ,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGP,UAAUE;IAC5C,OAAO,AAAC,KAAEC,SAASE,WAAWC,QAAQC;AACxC"}

View File

@@ -0,0 +1,14 @@
import { parsePath } from './parse-path';
/**
* Similarly to `addPathPrefix`, this function adds a suffix at the end on the
* provided path. It also works only for paths ensuring the argument starts
* with a slash.
*/ export function addPathSuffix(path, suffix) {
if (!path.startsWith('/') || !suffix) {
return path;
}
const { pathname, query, hash } = parsePath(path);
return "" + pathname + suffix + query + hash;
}
//# sourceMappingURL=add-path-suffix.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/add-path-suffix.ts"],"sourcesContent":["import { parsePath } from './parse-path'\n\n/**\n * Similarly to `addPathPrefix`, this function adds a suffix at the end on the\n * provided path. It also works only for paths ensuring the argument starts\n * with a slash.\n */\nexport function addPathSuffix(path: string, suffix?: string) {\n if (!path.startsWith('/') || !suffix) {\n return path\n }\n\n const { pathname, query, hash } = parsePath(path)\n return `${pathname}${suffix}${query}${hash}`\n}\n"],"names":["parsePath","addPathSuffix","path","suffix","startsWith","pathname","query","hash"],"mappings":"AAAA,SAASA,SAAS,QAAQ,eAAc;AAExC;;;;CAIC,GACD,OAAO,SAASC,cAAcC,IAAY,EAAEC,MAAe;IACzD,IAAI,CAACD,KAAKE,UAAU,CAAC,QAAQ,CAACD,QAAQ;QACpC,OAAOD;IACT;IAEA,MAAM,EAAEG,QAAQ,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGP,UAAUE;IAC5C,OAAO,AAAC,KAAEG,WAAWF,SAASG,QAAQC;AACxC"}

View File

@@ -0,0 +1,50 @@
import { ensureLeadingSlash } from '../../page-path/ensure-leading-slash';
import { isGroupSegment } from '../../segment';
/**
* Normalizes an app route so it represents the actual request path. Essentially
* performing the following transformations:
*
* - `/(dashboard)/user/[id]/page` to `/user/[id]`
* - `/(dashboard)/account/page` to `/account`
* - `/user/[id]/page` to `/user/[id]`
* - `/account/page` to `/account`
* - `/page` to `/`
* - `/(dashboard)/user/[id]/route` to `/user/[id]`
* - `/(dashboard)/account/route` to `/account`
* - `/user/[id]/route` to `/user/[id]`
* - `/account/route` to `/account`
* - `/route` to `/`
* - `/` to `/`
*
* @param route the app route to normalize
* @returns the normalized pathname
*/ export function normalizeAppPath(route) {
return ensureLeadingSlash(route.split('/').reduce((pathname, segment, index, segments)=>{
// Empty segments are ignored.
if (!segment) {
return pathname;
}
// Groups are ignored.
if (isGroupSegment(segment)) {
return pathname;
}
// Parallel segments are ignored.
if (segment[0] === '@') {
return pathname;
}
// The last segment (if it's a leaf) should be ignored.
if ((segment === 'page' || segment === 'route') && index === segments.length - 1) {
return pathname;
}
return pathname + "/" + segment;
}, ''));
}
/**
* Strips the `.rsc` extension if it's in the pathname.
* Since this function is used on full urls it checks `?` for searchParams handling.
*/ export function normalizeRscURL(url) {
return url.replace(/\.rsc($|\?)/, // $1 ensures `?` is preserved
'$1');
}
//# sourceMappingURL=app-paths.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/app-paths.ts"],"sourcesContent":["import { ensureLeadingSlash } from '../../page-path/ensure-leading-slash'\nimport { isGroupSegment } from '../../segment'\n\n/**\n * Normalizes an app route so it represents the actual request path. Essentially\n * performing the following transformations:\n *\n * - `/(dashboard)/user/[id]/page` to `/user/[id]`\n * - `/(dashboard)/account/page` to `/account`\n * - `/user/[id]/page` to `/user/[id]`\n * - `/account/page` to `/account`\n * - `/page` to `/`\n * - `/(dashboard)/user/[id]/route` to `/user/[id]`\n * - `/(dashboard)/account/route` to `/account`\n * - `/user/[id]/route` to `/user/[id]`\n * - `/account/route` to `/account`\n * - `/route` to `/`\n * - `/` to `/`\n *\n * @param route the app route to normalize\n * @returns the normalized pathname\n */\nexport function normalizeAppPath(route: string) {\n return ensureLeadingSlash(\n route.split('/').reduce((pathname, segment, index, segments) => {\n // Empty segments are ignored.\n if (!segment) {\n return pathname\n }\n\n // Groups are ignored.\n if (isGroupSegment(segment)) {\n return pathname\n }\n\n // Parallel segments are ignored.\n if (segment[0] === '@') {\n return pathname\n }\n\n // The last segment (if it's a leaf) should be ignored.\n if (\n (segment === 'page' || segment === 'route') &&\n index === segments.length - 1\n ) {\n return pathname\n }\n\n return `${pathname}/${segment}`\n }, '')\n )\n}\n\n/**\n * Strips the `.rsc` extension if it's in the pathname.\n * Since this function is used on full urls it checks `?` for searchParams handling.\n */\nexport function normalizeRscURL(url: string) {\n return url.replace(\n /\\.rsc($|\\?)/,\n // $1 ensures `?` is preserved\n '$1'\n )\n}\n"],"names":["ensureLeadingSlash","isGroupSegment","normalizeAppPath","route","split","reduce","pathname","segment","index","segments","length","normalizeRscURL","url","replace"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,uCAAsC;AACzE,SAASC,cAAc,QAAQ,gBAAe;AAE9C;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,iBAAiBC,KAAa;IAC5C,OAAOH,mBACLG,MAAMC,KAAK,CAAC,KAAKC,MAAM,CAAC,CAACC,UAAUC,SAASC,OAAOC;QACjD,8BAA8B;QAC9B,IAAI,CAACF,SAAS;YACZ,OAAOD;QACT;QAEA,sBAAsB;QACtB,IAAIL,eAAeM,UAAU;YAC3B,OAAOD;QACT;QAEA,iCAAiC;QACjC,IAAIC,OAAO,CAAC,EAAE,KAAK,KAAK;YACtB,OAAOD;QACT;QAEA,uDAAuD;QACvD,IACE,AAACC,CAAAA,YAAY,UAAUA,YAAY,OAAM,KACzCC,UAAUC,SAASC,MAAM,GAAG,GAC5B;YACA,OAAOJ;QACT;QAEA,OAAO,AAAGA,WAAS,MAAGC;IACxB,GAAG;AAEP;AAEA;;;CAGC,GACD,OAAO,SAASI,gBAAgBC,GAAW;IACzC,OAAOA,IAAIC,OAAO,CAChB,eACA,8BAA8B;IAC9B;AAEJ"}

View File

@@ -0,0 +1,7 @@
// Convert router.asPath to a URLSearchParams object
// example: /dynamic/[slug]?foo=bar -> { foo: 'bar' }
export function asPathToSearchParams(asPath) {
return new URL(asPath, 'http://n').searchParams;
}
//# sourceMappingURL=as-path-to-search-params.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/as-path-to-search-params.ts"],"sourcesContent":["// Convert router.asPath to a URLSearchParams object\n// example: /dynamic/[slug]?foo=bar -> { foo: 'bar' }\nexport function asPathToSearchParams(asPath: string): URLSearchParams {\n return new URL(asPath, 'http://n').searchParams\n}\n"],"names":["asPathToSearchParams","asPath","URL","searchParams"],"mappings":"AAAA,oDAAoD;AACpD,qDAAqD;AACrD,OAAO,SAASA,qBAAqBC,MAAc;IACjD,OAAO,IAAIC,IAAID,QAAQ,YAAYE,YAAY;AACjD"}

View File

@@ -0,0 +1,24 @@
export function compareRouterStates(a, b) {
const stateKeys = Object.keys(a);
if (stateKeys.length !== Object.keys(b).length) return false;
for(let i = stateKeys.length; i--;){
const key = stateKeys[i];
if (key === 'query') {
const queryKeys = Object.keys(a.query);
if (queryKeys.length !== Object.keys(b.query).length) {
return false;
}
for(let j = queryKeys.length; j--;){
const queryKey = queryKeys[j];
if (!b.query.hasOwnProperty(queryKey) || a.query[queryKey] !== b.query[queryKey]) {
return false;
}
}
} else if (!b.hasOwnProperty(key) || a[key] !== b[key]) {
return false;
}
}
return true;
}
//# sourceMappingURL=compare-states.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/compare-states.ts"],"sourcesContent":["import type { default as Router } from '../router'\n\nexport function compareRouterStates(a: Router['state'], b: Router['state']) {\n const stateKeys = Object.keys(a)\n if (stateKeys.length !== Object.keys(b).length) return false\n\n for (let i = stateKeys.length; i--; ) {\n const key = stateKeys[i]\n if (key === 'query') {\n const queryKeys = Object.keys(a.query)\n if (queryKeys.length !== Object.keys(b.query).length) {\n return false\n }\n for (let j = queryKeys.length; j--; ) {\n const queryKey = queryKeys[j]\n if (\n !b.query.hasOwnProperty(queryKey) ||\n a.query[queryKey] !== b.query[queryKey]\n ) {\n return false\n }\n }\n } else if (\n !b.hasOwnProperty(key) ||\n a[key as keyof Router['state']] !== b[key as keyof Router['state']]\n ) {\n return false\n }\n }\n\n return true\n}\n"],"names":["compareRouterStates","a","b","stateKeys","Object","keys","length","i","key","queryKeys","query","j","queryKey","hasOwnProperty"],"mappings":"AAEA,OAAO,SAASA,oBAAoBC,CAAkB,EAAEC,CAAkB;IACxE,MAAMC,YAAYC,OAAOC,IAAI,CAACJ;IAC9B,IAAIE,UAAUG,MAAM,KAAKF,OAAOC,IAAI,CAACH,GAAGI,MAAM,EAAE,OAAO;IAEvD,IAAK,IAAIC,IAAIJ,UAAUG,MAAM,EAAEC,KAAO;QACpC,MAAMC,MAAML,SAAS,CAACI,EAAE;QACxB,IAAIC,QAAQ,SAAS;YACnB,MAAMC,YAAYL,OAAOC,IAAI,CAACJ,EAAES,KAAK;YACrC,IAAID,UAAUH,MAAM,KAAKF,OAAOC,IAAI,CAACH,EAAEQ,KAAK,EAAEJ,MAAM,EAAE;gBACpD,OAAO;YACT;YACA,IAAK,IAAIK,IAAIF,UAAUH,MAAM,EAAEK,KAAO;gBACpC,MAAMC,WAAWH,SAAS,CAACE,EAAE;gBAC7B,IACE,CAACT,EAAEQ,KAAK,CAACG,cAAc,CAACD,aACxBX,EAAES,KAAK,CAACE,SAAS,KAAKV,EAAEQ,KAAK,CAACE,SAAS,EACvC;oBACA,OAAO;gBACT;YACF;QACF,OAAO,IACL,CAACV,EAAEW,cAAc,CAACL,QAClBP,CAAC,CAACO,IAA6B,KAAKN,CAAC,CAACM,IAA6B,EACnE;YACA,OAAO;QACT;IACF;IAEA,OAAO;AACT"}

View File

@@ -0,0 +1,6 @@
// escape delimiters used by path-to-regexp
export default function escapePathDelimiters(segment, escapeEncoded) {
return segment.replace(new RegExp("([/#?]" + (escapeEncoded ? '|%(2f|23|3f|5c)' : '') + ")", 'gi'), (char)=>encodeURIComponent(char));
}
//# sourceMappingURL=escape-path-delimiters.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/escape-path-delimiters.ts"],"sourcesContent":["// escape delimiters used by path-to-regexp\nexport default function escapePathDelimiters(\n segment: string,\n escapeEncoded?: boolean\n): string {\n return segment.replace(\n new RegExp(`([/#?]${escapeEncoded ? '|%(2f|23|3f|5c)' : ''})`, 'gi'),\n (char: string) => encodeURIComponent(char)\n )\n}\n"],"names":["escapePathDelimiters","segment","escapeEncoded","replace","RegExp","char","encodeURIComponent"],"mappings":"AAAA,2CAA2C;AAC3C,eAAe,SAASA,qBACtBC,OAAe,EACfC,aAAuB;IAEvB,OAAOD,QAAQE,OAAO,CACpB,IAAIC,OAAO,AAAC,WAAQF,CAAAA,gBAAgB,oBAAoB,EAAC,IAAE,KAAI,OAC/D,CAACG,OAAiBC,mBAAmBD;AAEzC"}

View File

@@ -0,0 +1,17 @@
import { removeTrailingSlash } from './remove-trailing-slash';
import { addPathPrefix } from './add-path-prefix';
import { addPathSuffix } from './add-path-suffix';
import { addLocale } from './add-locale';
export function formatNextPathnameInfo(info) {
let pathname = addLocale(info.pathname, info.locale, info.buildId ? undefined : info.defaultLocale, info.ignorePrefix);
if (info.buildId || !info.trailingSlash) {
pathname = removeTrailingSlash(pathname);
}
if (info.buildId) {
pathname = addPathSuffix(addPathPrefix(pathname, "/_next/data/" + info.buildId), info.pathname === '/' ? 'index.json' : '.json');
}
pathname = addPathPrefix(pathname, info.basePath);
return !info.buildId && info.trailingSlash ? !pathname.endsWith('/') ? addPathSuffix(pathname, '/') : pathname : removeTrailingSlash(pathname);
}
//# sourceMappingURL=format-next-pathname-info.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/format-next-pathname-info.ts"],"sourcesContent":["import type { NextPathnameInfo } from './get-next-pathname-info'\nimport { removeTrailingSlash } from './remove-trailing-slash'\nimport { addPathPrefix } from './add-path-prefix'\nimport { addPathSuffix } from './add-path-suffix'\nimport { addLocale } from './add-locale'\n\ninterface ExtendedInfo extends NextPathnameInfo {\n defaultLocale?: string\n ignorePrefix?: boolean\n}\n\nexport function formatNextPathnameInfo(info: ExtendedInfo) {\n let pathname = addLocale(\n info.pathname,\n info.locale,\n info.buildId ? undefined : info.defaultLocale,\n info.ignorePrefix\n )\n\n if (info.buildId || !info.trailingSlash) {\n pathname = removeTrailingSlash(pathname)\n }\n\n if (info.buildId) {\n pathname = addPathSuffix(\n addPathPrefix(pathname, `/_next/data/${info.buildId}`),\n info.pathname === '/' ? 'index.json' : '.json'\n )\n }\n\n pathname = addPathPrefix(pathname, info.basePath)\n return !info.buildId && info.trailingSlash\n ? !pathname.endsWith('/')\n ? addPathSuffix(pathname, '/')\n : pathname\n : removeTrailingSlash(pathname)\n}\n"],"names":["removeTrailingSlash","addPathPrefix","addPathSuffix","addLocale","formatNextPathnameInfo","info","pathname","locale","buildId","undefined","defaultLocale","ignorePrefix","trailingSlash","basePath","endsWith"],"mappings":"AACA,SAASA,mBAAmB,QAAQ,0BAAyB;AAC7D,SAASC,aAAa,QAAQ,oBAAmB;AACjD,SAASC,aAAa,QAAQ,oBAAmB;AACjD,SAASC,SAAS,QAAQ,eAAc;AAOxC,OAAO,SAASC,uBAAuBC,IAAkB;IACvD,IAAIC,WAAWH,UACbE,KAAKC,QAAQ,EACbD,KAAKE,MAAM,EACXF,KAAKG,OAAO,GAAGC,YAAYJ,KAAKK,aAAa,EAC7CL,KAAKM,YAAY;IAGnB,IAAIN,KAAKG,OAAO,IAAI,CAACH,KAAKO,aAAa,EAAE;QACvCN,WAAWN,oBAAoBM;IACjC;IAEA,IAAID,KAAKG,OAAO,EAAE;QAChBF,WAAWJ,cACTD,cAAcK,UAAU,AAAC,iBAAcD,KAAKG,OAAO,GACnDH,KAAKC,QAAQ,KAAK,MAAM,eAAe;IAE3C;IAEAA,WAAWL,cAAcK,UAAUD,KAAKQ,QAAQ;IAChD,OAAO,CAACR,KAAKG,OAAO,IAAIH,KAAKO,aAAa,GACtC,CAACN,SAASQ,QAAQ,CAAC,OACjBZ,cAAcI,UAAU,OACxBA,WACFN,oBAAoBM;AAC1B"}

View File

@@ -0,0 +1,84 @@
// Format function modified from nodejs
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
import * as querystring from './querystring';
const slashedProtocols = /https?|ftp|gopher|file/;
export function formatUrl(urlObj) {
let { auth, hostname } = urlObj;
let protocol = urlObj.protocol || '';
let pathname = urlObj.pathname || '';
let hash = urlObj.hash || '';
let query = urlObj.query || '';
let host = false;
auth = auth ? encodeURIComponent(auth).replace(/%3A/i, ':') + '@' : '';
if (urlObj.host) {
host = auth + urlObj.host;
} else if (hostname) {
host = auth + (~hostname.indexOf(':') ? "[" + hostname + "]" : hostname);
if (urlObj.port) {
host += ':' + urlObj.port;
}
}
if (query && typeof query === 'object') {
query = String(querystring.urlQueryToSearchParams(query));
}
let search = urlObj.search || query && "?" + query || '';
if (protocol && !protocol.endsWith(':')) protocol += ':';
if (urlObj.slashes || (!protocol || slashedProtocols.test(protocol)) && host !== false) {
host = '//' + (host || '');
if (pathname && pathname[0] !== '/') pathname = '/' + pathname;
} else if (!host) {
host = '';
}
if (hash && hash[0] !== '#') hash = '#' + hash;
if (search && search[0] !== '?') search = '?' + search;
pathname = pathname.replace(/[?#]/g, encodeURIComponent);
search = search.replace('#', '%23');
return "" + protocol + host + pathname + search + hash;
}
export const urlObjectKeys = [
'auth',
'hash',
'host',
'hostname',
'href',
'path',
'pathname',
'port',
'protocol',
'query',
'search',
'slashes'
];
export function formatWithValidation(url) {
if (process.env.NODE_ENV === 'development') {
if (url !== null && typeof url === 'object') {
Object.keys(url).forEach((key)=>{
if (!urlObjectKeys.includes(key)) {
console.warn("Unknown key passed via urlObject into url.format: " + key);
}
});
}
}
return formatUrl(url);
}
//# sourceMappingURL=format-url.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
// Translates a logical route into its pages asset path (relative from a common prefix)
// "asset path" being its javascript file, data file, prerendered html,...
export default function getAssetPathFromRoute(route, ext) {
if (ext === void 0) ext = '';
const path = route === '/' ? '/index' : /^\/index(\/|$)/.test(route) ? "/index" + route : route;
return path + ext;
}
//# sourceMappingURL=get-asset-path-from-route.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/get-asset-path-from-route.ts"],"sourcesContent":["// Translates a logical route into its pages asset path (relative from a common prefix)\n// \"asset path\" being its javascript file, data file, prerendered html,...\nexport default function getAssetPathFromRoute(\n route: string,\n ext: string = ''\n): string {\n const path =\n route === '/'\n ? '/index'\n : /^\\/index(\\/|$)/.test(route)\n ? `/index${route}`\n : route\n return path + ext\n}\n"],"names":["getAssetPathFromRoute","route","ext","path","test"],"mappings":"AAAA,uFAAuF;AACvF,0EAA0E;AAC1E,eAAe,SAASA,sBACtBC,KAAa,EACbC,GAAgB;IAAhBA,IAAAA,gBAAAA,MAAc;IAEd,MAAMC,OACJF,UAAU,MACN,WACA,iBAAiBG,IAAI,CAACH,SACpB,AAAC,WAAQA,QACTA;IACR,OAAOE,OAAOD;AAChB"}

View File

@@ -0,0 +1,44 @@
import { normalizeLocalePath } from '../../i18n/normalize-locale-path';
import { removePathPrefix } from './remove-path-prefix';
import { pathHasPrefix } from './path-has-prefix';
export function getNextPathnameInfo(pathname, options) {
var _options_nextConfig;
const { basePath, i18n, trailingSlash } = (_options_nextConfig = options.nextConfig) != null ? _options_nextConfig : {};
const info = {
pathname,
trailingSlash: pathname !== '/' ? pathname.endsWith('/') : trailingSlash
};
if (basePath && pathHasPrefix(info.pathname, basePath)) {
info.pathname = removePathPrefix(info.pathname, basePath);
info.basePath = basePath;
}
let pathnameNoDataPrefix = info.pathname;
if (info.pathname.startsWith('/_next/data/') && info.pathname.endsWith('.json')) {
const paths = info.pathname.replace(/^\/_next\/data\//, '').replace(/\.json$/, '').split('/');
const buildId = paths[0];
info.buildId = buildId;
pathnameNoDataPrefix = paths[1] !== 'index' ? "/" + paths.slice(1).join('/') : '/';
// update pathname with normalized if enabled although
// we use normalized to populate locale info still
if (options.parseData === true) {
info.pathname = pathnameNoDataPrefix;
}
}
// If provided, use the locale route normalizer to detect the locale instead
// of the function below.
if (i18n) {
let result = options.i18nProvider ? options.i18nProvider.analyze(info.pathname) : normalizeLocalePath(info.pathname, i18n.locales);
info.locale = result.detectedLocale;
var _result_pathname;
info.pathname = (_result_pathname = result.pathname) != null ? _result_pathname : info.pathname;
if (!result.detectedLocale && info.buildId) {
result = options.i18nProvider ? options.i18nProvider.analyze(pathnameNoDataPrefix) : normalizeLocalePath(pathnameNoDataPrefix, i18n.locales);
if (result.detectedLocale) {
info.locale = result.detectedLocale;
}
}
}
return info;
}
//# sourceMappingURL=get-next-pathname-info.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
// Translate a pages asset path (relative from a common prefix) back into its logical route
import { isDynamicRoute } from './is-dynamic';
// "asset path" being its javascript file, data file, prerendered html,...
export default function getRouteFromAssetPath(assetPath, ext) {
if (ext === void 0) ext = '';
assetPath = assetPath.replace(/\\/g, '/');
assetPath = ext && assetPath.endsWith(ext) ? assetPath.slice(0, -ext.length) : assetPath;
if (assetPath.startsWith('/index/') && !isDynamicRoute(assetPath)) {
assetPath = assetPath.slice(6);
} else if (assetPath === '/index') {
assetPath = '/';
}
return assetPath;
}
//# sourceMappingURL=get-route-from-asset-path.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/get-route-from-asset-path.ts"],"sourcesContent":["// Translate a pages asset path (relative from a common prefix) back into its logical route\n\nimport { isDynamicRoute } from './is-dynamic'\n\n// \"asset path\" being its javascript file, data file, prerendered html,...\nexport default function getRouteFromAssetPath(\n assetPath: string,\n ext: string = ''\n): string {\n assetPath = assetPath.replace(/\\\\/g, '/')\n assetPath =\n ext && assetPath.endsWith(ext) ? assetPath.slice(0, -ext.length) : assetPath\n if (assetPath.startsWith('/index/') && !isDynamicRoute(assetPath)) {\n assetPath = assetPath.slice(6)\n } else if (assetPath === '/index') {\n assetPath = '/'\n }\n return assetPath\n}\n"],"names":["isDynamicRoute","getRouteFromAssetPath","assetPath","ext","replace","endsWith","slice","length","startsWith"],"mappings":"AAAA,2FAA2F;AAE3F,SAASA,cAAc,QAAQ,eAAc;AAE7C,0EAA0E;AAC1E,eAAe,SAASC,sBACtBC,SAAiB,EACjBC,GAAgB;IAAhBA,IAAAA,gBAAAA,MAAc;IAEdD,YAAYA,UAAUE,OAAO,CAAC,OAAO;IACrCF,YACEC,OAAOD,UAAUG,QAAQ,CAACF,OAAOD,UAAUI,KAAK,CAAC,GAAG,CAACH,IAAII,MAAM,IAAIL;IACrE,IAAIA,UAAUM,UAAU,CAAC,cAAc,CAACR,eAAeE,YAAY;QACjEA,YAAYA,UAAUI,KAAK,CAAC;IAC9B,OAAO,IAAIJ,cAAc,UAAU;QACjCA,YAAY;IACd;IACA,OAAOA;AACT"}

View File

@@ -0,0 +1,25 @@
/**
* Run function with `scroll-behavior: auto` applied to `<html/>`.
* This css change will be reverted after the function finishes.
*/ export function handleSmoothScroll(fn, options) {
if (options === void 0) options = {};
// if only the hash is changed, we don't need to disable smooth scrolling
// we only care to prevent smooth scrolling when navigating to a new page to avoid jarring UX
if (options.onlyHashChange) {
fn();
return;
}
const htmlElement = document.documentElement;
const existing = htmlElement.style.scrollBehavior;
htmlElement.style.scrollBehavior = 'auto';
if (!options.dontForceLayout) {
// In Chrome-based browsers we need to force reflow before calling `scrollTo`.
// Otherwise it will not pickup the change in scrollBehavior
// More info here: https://github.com/vercel/next.js/issues/40719#issuecomment-1336248042
htmlElement.getClientRects();
}
fn();
htmlElement.style.scrollBehavior = existing;
}
//# sourceMappingURL=handle-smooth-scroll.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/handle-smooth-scroll.ts"],"sourcesContent":["/**\n * Run function with `scroll-behavior: auto` applied to `<html/>`.\n * This css change will be reverted after the function finishes.\n */\nexport function handleSmoothScroll(\n fn: () => void,\n options: { dontForceLayout?: boolean; onlyHashChange?: boolean } = {}\n) {\n // if only the hash is changed, we don't need to disable smooth scrolling\n // we only care to prevent smooth scrolling when navigating to a new page to avoid jarring UX\n if (options.onlyHashChange) {\n fn()\n return\n }\n const htmlElement = document.documentElement\n const existing = htmlElement.style.scrollBehavior\n htmlElement.style.scrollBehavior = 'auto'\n if (!options.dontForceLayout) {\n // In Chrome-based browsers we need to force reflow before calling `scrollTo`.\n // Otherwise it will not pickup the change in scrollBehavior\n // More info here: https://github.com/vercel/next.js/issues/40719#issuecomment-1336248042\n htmlElement.getClientRects()\n }\n fn()\n htmlElement.style.scrollBehavior = existing\n}\n"],"names":["handleSmoothScroll","fn","options","onlyHashChange","htmlElement","document","documentElement","existing","style","scrollBehavior","dontForceLayout","getClientRects"],"mappings":"AAAA;;;CAGC,GACD,OAAO,SAASA,mBACdC,EAAc,EACdC,OAAqE;IAArEA,IAAAA,oBAAAA,UAAmE,CAAC;IAEpE,yEAAyE;IACzE,6FAA6F;IAC7F,IAAIA,QAAQC,cAAc,EAAE;QAC1BF;QACA;IACF;IACA,MAAMG,cAAcC,SAASC,eAAe;IAC5C,MAAMC,WAAWH,YAAYI,KAAK,CAACC,cAAc;IACjDL,YAAYI,KAAK,CAACC,cAAc,GAAG;IACnC,IAAI,CAACP,QAAQQ,eAAe,EAAE;QAC5B,8EAA8E;QAC9E,4DAA4D;QAC5D,yFAAyF;QACzFN,YAAYO,cAAc;IAC5B;IACAV;IACAG,YAAYI,KAAK,CAACC,cAAc,GAAGF;AACrC"}

View File

@@ -0,0 +1,5 @@
// This regex contains the bots that we need to do a blocking render for and can't safely stream the response
// due to how they parse the DOM. For example, they might explicitly check for metadata in the `head` tag, so we can't stream metadata tags after the `head` was sent.
export const HTML_LIMITED_BOT_UA_RE = /Mediapartners-Google|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview/i;
//# sourceMappingURL=html-bots.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/html-bots.ts"],"sourcesContent":["// This regex contains the bots that we need to do a blocking render for and can't safely stream the response\n// due to how they parse the DOM. For example, they might explicitly check for metadata in the `head` tag, so we can't stream metadata tags after the `head` was sent.\nexport const HTML_LIMITED_BOT_UA_RE =\n /Mediapartners-Google|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview/i\n"],"names":["HTML_LIMITED_BOT_UA_RE"],"mappings":"AAAA,6GAA6G;AAC7G,sKAAsK;AACtK,OAAO,MAAMA,yBACX,yQAAwQ"}

View File

@@ -0,0 +1,4 @@
export { getSortedRoutes, getSortedRouteObjects } from './sorted-routes';
export { isDynamicRoute } from './is-dynamic';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/index.ts"],"sourcesContent":["export { getSortedRoutes, getSortedRouteObjects } from './sorted-routes'\nexport { isDynamicRoute } from './is-dynamic'\n"],"names":["getSortedRoutes","getSortedRouteObjects","isDynamicRoute"],"mappings":"AAAA,SAASA,eAAe,EAAEC,qBAAqB,QAAQ,kBAAiB;AACxE,SAASC,cAAc,QAAQ,eAAc"}

View File

@@ -0,0 +1,81 @@
import { normalizeAppPath } from './app-paths';
// order matters here, the first match will be used
export const INTERCEPTION_ROUTE_MARKERS = [
'(..)(..)',
'(.)',
'(..)',
'(...)'
];
export function isInterceptionRouteAppPath(path) {
// TODO-APP: add more serious validation
return path.split('/').find((segment)=>INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m))) !== undefined;
}
export function extractInterceptionRouteInformation(path) {
let interceptingRoute, marker, interceptedRoute;
for (const segment of path.split('/')){
marker = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
if (marker) {
;
[interceptingRoute, interceptedRoute] = path.split(marker, 2);
break;
}
}
if (!interceptingRoute || !marker || !interceptedRoute) {
throw Object.defineProperty(new Error("Invalid interception route: " + path + ". Must be in the format /<intercepting route>/(..|...|..)(..)/<intercepted route>"), "__NEXT_ERROR_CODE", {
value: "E269",
enumerable: false,
configurable: true
});
}
interceptingRoute = normalizeAppPath(interceptingRoute) // normalize the path, e.g. /(blog)/feed -> /feed
;
switch(marker){
case '(.)':
// (.) indicates that we should match with sibling routes, so we just need to append the intercepted route to the intercepting route
if (interceptingRoute === '/') {
interceptedRoute = "/" + interceptedRoute;
} else {
interceptedRoute = interceptingRoute + '/' + interceptedRoute;
}
break;
case '(..)':
// (..) indicates that we should match at one level up, so we need to remove the last segment of the intercepting route
if (interceptingRoute === '/') {
throw Object.defineProperty(new Error("Invalid interception route: " + path + ". Cannot use (..) marker at the root level, use (.) instead."), "__NEXT_ERROR_CODE", {
value: "E207",
enumerable: false,
configurable: true
});
}
interceptedRoute = interceptingRoute.split('/').slice(0, -1).concat(interceptedRoute).join('/');
break;
case '(...)':
// (...) will match the route segment in the root directory, so we need to use the root directory to prepend the intercepted route
interceptedRoute = '/' + interceptedRoute;
break;
case '(..)(..)':
// (..)(..) indicates that we should match at two levels up, so we need to remove the last two segments of the intercepting route
const splitInterceptingRoute = interceptingRoute.split('/');
if (splitInterceptingRoute.length <= 2) {
throw Object.defineProperty(new Error("Invalid interception route: " + path + ". Cannot use (..)(..) marker at the root level or one level up."), "__NEXT_ERROR_CODE", {
value: "E486",
enumerable: false,
configurable: true
});
}
interceptedRoute = splitInterceptingRoute.slice(0, -2).concat(interceptedRoute).join('/');
break;
default:
throw Object.defineProperty(new Error('Invariant: unexpected marker'), "__NEXT_ERROR_CODE", {
value: "E112",
enumerable: false,
configurable: true
});
}
return {
interceptingRoute,
interceptedRoute
};
}
//# sourceMappingURL=interception-routes.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,43 @@
import { getRouteMatcher } from './route-matcher';
import { getRouteRegex } from './route-regex';
export function interpolateAs(route, asPathname, query) {
let interpolatedRoute = '';
const dynamicRegex = getRouteRegex(route);
const dynamicGroups = dynamicRegex.groups;
const dynamicMatches = // Try to match the dynamic route against the asPath
(asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : '') || // Fall back to reading the values from the href
// TODO: should this take priority; also need to change in the router.
query;
interpolatedRoute = route;
const params = Object.keys(dynamicGroups);
if (!params.every((param)=>{
let value = dynamicMatches[param] || '';
const { repeat, optional } = dynamicGroups[param];
// support single-level catch-all
// TODO: more robust handling for user-error (passing `/`)
let replaced = "[" + (repeat ? '...' : '') + param + "]";
if (optional) {
replaced = (!value ? '/' : '') + "[" + replaced + "]";
}
if (repeat && !Array.isArray(value)) value = [
value
];
return (optional || param in dynamicMatches) && // Interpolate group into data URL if present
(interpolatedRoute = interpolatedRoute.replace(replaced, repeat ? value.map(// these values should be fully encoded instead of just
// path delimiter escaped since they are being inserted
// into the URL and we expect URL encoded segments
// when parsing dynamic route params
(segment)=>encodeURIComponent(segment)).join('/') : encodeURIComponent(value)) || '/');
})) {
interpolatedRoute = '' // did not satisfy all requirements
;
// n.b. We ignore this error because we handle warning for this case in
// development in the `<Link>` component directly.
}
return {
params,
result: interpolatedRoute
};
}
//# sourceMappingURL=interpolate-as.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/interpolate-as.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\n\nimport { getRouteMatcher } from './route-matcher'\nimport { getRouteRegex } from './route-regex'\n\nexport function interpolateAs(\n route: string,\n asPathname: string,\n query: ParsedUrlQuery\n) {\n let interpolatedRoute = ''\n\n const dynamicRegex = getRouteRegex(route)\n const dynamicGroups = dynamicRegex.groups\n const dynamicMatches =\n // Try to match the dynamic route against the asPath\n (asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : '') ||\n // Fall back to reading the values from the href\n // TODO: should this take priority; also need to change in the router.\n query\n\n interpolatedRoute = route\n const params = Object.keys(dynamicGroups)\n\n if (\n !params.every((param) => {\n let value = dynamicMatches[param] || ''\n const { repeat, optional } = dynamicGroups[param]\n\n // support single-level catch-all\n // TODO: more robust handling for user-error (passing `/`)\n let replaced = `[${repeat ? '...' : ''}${param}]`\n if (optional) {\n replaced = `${!value ? '/' : ''}[${replaced}]`\n }\n if (repeat && !Array.isArray(value)) value = [value]\n\n return (\n (optional || param in dynamicMatches) &&\n // Interpolate group into data URL if present\n (interpolatedRoute =\n interpolatedRoute!.replace(\n replaced,\n repeat\n ? (value as string[])\n .map(\n // these values should be fully encoded instead of just\n // path delimiter escaped since they are being inserted\n // into the URL and we expect URL encoded segments\n // when parsing dynamic route params\n (segment) => encodeURIComponent(segment)\n )\n .join('/')\n : encodeURIComponent(value as string)\n ) || '/')\n )\n })\n ) {\n interpolatedRoute = '' // did not satisfy all requirements\n\n // n.b. We ignore this error because we handle warning for this case in\n // development in the `<Link>` component directly.\n }\n return {\n params,\n result: interpolatedRoute,\n }\n}\n"],"names":["getRouteMatcher","getRouteRegex","interpolateAs","route","asPathname","query","interpolatedRoute","dynamicRegex","dynamicGroups","groups","dynamicMatches","params","Object","keys","every","param","value","repeat","optional","replaced","Array","isArray","replace","map","segment","encodeURIComponent","join","result"],"mappings":"AAEA,SAASA,eAAe,QAAQ,kBAAiB;AACjD,SAASC,aAAa,QAAQ,gBAAe;AAE7C,OAAO,SAASC,cACdC,KAAa,EACbC,UAAkB,EAClBC,KAAqB;IAErB,IAAIC,oBAAoB;IAExB,MAAMC,eAAeN,cAAcE;IACnC,MAAMK,gBAAgBD,aAAaE,MAAM;IACzC,MAAMC,iBAEJ,AADA,oDAAoD;IACnDN,CAAAA,eAAeD,QAAQH,gBAAgBO,cAAcH,cAAc,EAAC,KACrE,gDAAgD;IAChD,sEAAsE;IACtEC;IAEFC,oBAAoBH;IACpB,MAAMQ,SAASC,OAAOC,IAAI,CAACL;IAE3B,IACE,CAACG,OAAOG,KAAK,CAAC,CAACC;QACb,IAAIC,QAAQN,cAAc,CAACK,MAAM,IAAI;QACrC,MAAM,EAAEE,MAAM,EAAEC,QAAQ,EAAE,GAAGV,aAAa,CAACO,MAAM;QAEjD,iCAAiC;QACjC,0DAA0D;QAC1D,IAAII,WAAW,AAAC,MAAGF,CAAAA,SAAS,QAAQ,EAAC,IAAIF,QAAM;QAC/C,IAAIG,UAAU;YACZC,WAAW,AAAG,CAAA,CAACH,QAAQ,MAAM,EAAC,IAAE,MAAGG,WAAS;QAC9C;QACA,IAAIF,UAAU,CAACG,MAAMC,OAAO,CAACL,QAAQA,QAAQ;YAACA;SAAM;QAEpD,OACE,AAACE,CAAAA,YAAYH,SAASL,cAAa,KACnC,6CAA6C;QAC5CJ,CAAAA,oBACCA,kBAAmBgB,OAAO,CACxBH,UACAF,SACI,AAACD,MACEO,GAAG,CACF,uDAAuD;QACvD,uDAAuD;QACvD,kDAAkD;QAClD,oCAAoC;QACpC,CAACC,UAAYC,mBAAmBD,UAEjCE,IAAI,CAAC,OACRD,mBAAmBT,WACpB,GAAE;IAEb,IACA;QACAV,oBAAoB,GAAG,mCAAmC;;IAE1D,uEAAuE;IACvE,kDAAkD;IACpD;IACA,OAAO;QACLK;QACAgB,QAAQrB;IACV;AACF"}

View File

@@ -0,0 +1,25 @@
import { HTML_LIMITED_BOT_UA_RE } from './html-bots';
// Bot crawler that will spin up a headless browser and execute JS
const HEADLESS_BROWSER_BOT_UA_RE = /Googlebot|Google-PageRenderer|AdsBot-Google|googleweblight|Storebot-Google/i;
export const HTML_LIMITED_BOT_UA_RE_STRING = HTML_LIMITED_BOT_UA_RE.source;
export { HTML_LIMITED_BOT_UA_RE };
function isDomBotUA(userAgent) {
return HEADLESS_BROWSER_BOT_UA_RE.test(userAgent);
}
function isHtmlLimitedBotUA(userAgent) {
return HTML_LIMITED_BOT_UA_RE.test(userAgent);
}
export function isBot(userAgent) {
return isDomBotUA(userAgent) || isHtmlLimitedBotUA(userAgent);
}
export function getBotType(userAgent) {
if (isDomBotUA(userAgent)) {
return 'dom';
}
if (isHtmlLimitedBotUA(userAgent)) {
return 'html';
}
return undefined;
}
//# sourceMappingURL=is-bot.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/is-bot.ts"],"sourcesContent":["import { HTML_LIMITED_BOT_UA_RE } from './html-bots'\n\n// Bot crawler that will spin up a headless browser and execute JS\nconst HEADLESS_BROWSER_BOT_UA_RE =\n /Googlebot|Google-PageRenderer|AdsBot-Google|googleweblight|Storebot-Google/i\n\nexport const HTML_LIMITED_BOT_UA_RE_STRING = HTML_LIMITED_BOT_UA_RE.source\n\nexport { HTML_LIMITED_BOT_UA_RE }\n\nfunction isDomBotUA(userAgent: string) {\n return HEADLESS_BROWSER_BOT_UA_RE.test(userAgent)\n}\n\nfunction isHtmlLimitedBotUA(userAgent: string) {\n return HTML_LIMITED_BOT_UA_RE.test(userAgent)\n}\n\nexport function isBot(userAgent: string): boolean {\n return isDomBotUA(userAgent) || isHtmlLimitedBotUA(userAgent)\n}\n\nexport function getBotType(userAgent: string): 'dom' | 'html' | undefined {\n if (isDomBotUA(userAgent)) {\n return 'dom'\n }\n if (isHtmlLimitedBotUA(userAgent)) {\n return 'html'\n }\n return undefined\n}\n"],"names":["HTML_LIMITED_BOT_UA_RE","HEADLESS_BROWSER_BOT_UA_RE","HTML_LIMITED_BOT_UA_RE_STRING","source","isDomBotUA","userAgent","test","isHtmlLimitedBotUA","isBot","getBotType","undefined"],"mappings":"AAAA,SAASA,sBAAsB,QAAQ,cAAa;AAEpD,kEAAkE;AAClE,MAAMC,6BACJ;AAEF,OAAO,MAAMC,gCAAgCF,uBAAuBG,MAAM,CAAA;AAE1E,SAASH,sBAAsB,GAAE;AAEjC,SAASI,WAAWC,SAAiB;IACnC,OAAOJ,2BAA2BK,IAAI,CAACD;AACzC;AAEA,SAASE,mBAAmBF,SAAiB;IAC3C,OAAOL,uBAAuBM,IAAI,CAACD;AACrC;AAEA,OAAO,SAASG,MAAMH,SAAiB;IACrC,OAAOD,WAAWC,cAAcE,mBAAmBF;AACrD;AAEA,OAAO,SAASI,WAAWJ,SAAiB;IAC1C,IAAID,WAAWC,YAAY;QACzB,OAAO;IACT;IACA,IAAIE,mBAAmBF,YAAY;QACjC,OAAO;IACT;IACA,OAAOK;AACT"}

View File

@@ -0,0 +1,23 @@
import { extractInterceptionRouteInformation, isInterceptionRouteAppPath } from './interception-routes';
// Identify /.*[param].*/ in route string
const TEST_ROUTE = /\/[^/]*\[[^/]+\][^/]*(?=\/|$)/;
// Identify /[param]/ in route string
const TEST_STRICT_ROUTE = /\/\[[^/]+\](?=\/|$)/;
/**
* Check if a route is dynamic.
*
* @param route - The route to check.
* @param strict - Whether to use strict mode which prohibits segments with prefixes/suffixes (default: true).
* @returns Whether the route is dynamic.
*/ export function isDynamicRoute(route, strict) {
if (strict === void 0) strict = true;
if (isInterceptionRouteAppPath(route)) {
route = extractInterceptionRouteInformation(route).interceptedRoute;
}
if (strict) {
return TEST_STRICT_ROUTE.test(route);
}
return TEST_ROUTE.test(route);
}
//# sourceMappingURL=is-dynamic.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/is-dynamic.ts"],"sourcesContent":["import {\n extractInterceptionRouteInformation,\n isInterceptionRouteAppPath,\n} from './interception-routes'\n\n// Identify /.*[param].*/ in route string\nconst TEST_ROUTE = /\\/[^/]*\\[[^/]+\\][^/]*(?=\\/|$)/\n\n// Identify /[param]/ in route string\nconst TEST_STRICT_ROUTE = /\\/\\[[^/]+\\](?=\\/|$)/\n\n/**\n * Check if a route is dynamic.\n *\n * @param route - The route to check.\n * @param strict - Whether to use strict mode which prohibits segments with prefixes/suffixes (default: true).\n * @returns Whether the route is dynamic.\n */\nexport function isDynamicRoute(route: string, strict: boolean = true): boolean {\n if (isInterceptionRouteAppPath(route)) {\n route = extractInterceptionRouteInformation(route).interceptedRoute\n }\n\n if (strict) {\n return TEST_STRICT_ROUTE.test(route)\n }\n\n return TEST_ROUTE.test(route)\n}\n"],"names":["extractInterceptionRouteInformation","isInterceptionRouteAppPath","TEST_ROUTE","TEST_STRICT_ROUTE","isDynamicRoute","route","strict","interceptedRoute","test"],"mappings":"AAAA,SACEA,mCAAmC,EACnCC,0BAA0B,QACrB,wBAAuB;AAE9B,yCAAyC;AACzC,MAAMC,aAAa;AAEnB,qCAAqC;AACrC,MAAMC,oBAAoB;AAE1B;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,KAAa,EAAEC,MAAsB;IAAtBA,IAAAA,mBAAAA,SAAkB;IAC9D,IAAIL,2BAA2BI,QAAQ;QACrCA,QAAQL,oCAAoCK,OAAOE,gBAAgB;IACrE;IAEA,IAAID,QAAQ;QACV,OAAOH,kBAAkBK,IAAI,CAACH;IAChC;IAEA,OAAOH,WAAWM,IAAI,CAACH;AACzB"}

View File

@@ -0,0 +1,18 @@
import { isAbsoluteUrl, getLocationOrigin } from '../../utils';
import { hasBasePath } from '../../../../client/has-base-path';
/**
* Detects whether a given url is routable by the Next.js router (browser only).
*/ export function isLocalURL(url) {
// prevent a hydration mismatch on href for url with anchor refs
if (!isAbsoluteUrl(url)) return true;
try {
// absolute urls can be local if they are on the same origin
const locationOrigin = getLocationOrigin();
const resolved = new URL(url, locationOrigin);
return resolved.origin === locationOrigin && hasBasePath(resolved.pathname);
} catch (_) {
return false;
}
}
//# sourceMappingURL=is-local-url.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/is-local-url.ts"],"sourcesContent":["import { isAbsoluteUrl, getLocationOrigin } from '../../utils'\nimport { hasBasePath } from '../../../../client/has-base-path'\n\n/**\n * Detects whether a given url is routable by the Next.js router (browser only).\n */\nexport function isLocalURL(url: string): boolean {\n // prevent a hydration mismatch on href for url with anchor refs\n if (!isAbsoluteUrl(url)) return true\n try {\n // absolute urls can be local if they are on the same origin\n const locationOrigin = getLocationOrigin()\n const resolved = new URL(url, locationOrigin)\n return resolved.origin === locationOrigin && hasBasePath(resolved.pathname)\n } catch (_) {\n return false\n }\n}\n"],"names":["isAbsoluteUrl","getLocationOrigin","hasBasePath","isLocalURL","url","locationOrigin","resolved","URL","origin","pathname","_"],"mappings":"AAAA,SAASA,aAAa,EAAEC,iBAAiB,QAAQ,cAAa;AAC9D,SAASC,WAAW,QAAQ,mCAAkC;AAE9D;;CAEC,GACD,OAAO,SAASC,WAAWC,GAAW;IACpC,gEAAgE;IAChE,IAAI,CAACJ,cAAcI,MAAM,OAAO;IAChC,IAAI;QACF,4DAA4D;QAC5D,MAAMC,iBAAiBJ;QACvB,MAAMK,WAAW,IAAIC,IAAIH,KAAKC;QAC9B,OAAOC,SAASE,MAAM,KAAKH,kBAAkBH,YAAYI,SAASG,QAAQ;IAC5E,EAAE,OAAOC,GAAG;QACV,OAAO;IACT;AACF"}

View File

@@ -0,0 +1,21 @@
import { matchHas } from './prepare-destination';
export function getMiddlewareRouteMatcher(matchers) {
return (pathname, req, query)=>{
for (const matcher of matchers){
const routeMatch = new RegExp(matcher.regexp).exec(pathname);
if (!routeMatch) {
continue;
}
if (matcher.has || matcher.missing) {
const hasParams = matchHas(req, query, matcher.has, matcher.missing);
if (!hasParams) {
continue;
}
}
return true;
}
return false;
};
}
//# sourceMappingURL=middleware-route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/middleware-route-matcher.ts"],"sourcesContent":["import type { BaseNextRequest } from '../../../../server/base-http'\nimport type { MiddlewareMatcher } from '../../../../build/analysis/get-page-static-info'\nimport type { Params } from '../../../../server/request/params'\nimport { matchHas } from './prepare-destination'\n\nexport interface MiddlewareRouteMatch {\n (\n pathname: string | null | undefined,\n request: BaseNextRequest,\n query: Params\n ): boolean\n}\n\nexport function getMiddlewareRouteMatcher(\n matchers: MiddlewareMatcher[]\n): MiddlewareRouteMatch {\n return (\n pathname: string | null | undefined,\n req: BaseNextRequest,\n query: Params\n ) => {\n for (const matcher of matchers) {\n const routeMatch = new RegExp(matcher.regexp).exec(pathname!)\n if (!routeMatch) {\n continue\n }\n\n if (matcher.has || matcher.missing) {\n const hasParams = matchHas(req, query, matcher.has, matcher.missing)\n if (!hasParams) {\n continue\n }\n }\n\n return true\n }\n\n return false\n }\n}\n"],"names":["matchHas","getMiddlewareRouteMatcher","matchers","pathname","req","query","matcher","routeMatch","RegExp","regexp","exec","has","missing","hasParams"],"mappings":"AAGA,SAASA,QAAQ,QAAQ,wBAAuB;AAUhD,OAAO,SAASC,0BACdC,QAA6B;IAE7B,OAAO,CACLC,UACAC,KACAC;QAEA,KAAK,MAAMC,WAAWJ,SAAU;YAC9B,MAAMK,aAAa,IAAIC,OAAOF,QAAQG,MAAM,EAAEC,IAAI,CAACP;YACnD,IAAI,CAACI,YAAY;gBACf;YACF;YAEA,IAAID,QAAQK,GAAG,IAAIL,QAAQM,OAAO,EAAE;gBAClC,MAAMC,YAAYb,SAASI,KAAKC,OAAOC,QAAQK,GAAG,EAAEL,QAAQM,OAAO;gBACnE,IAAI,CAACC,WAAW;oBACd;gBACF;YACF;YAEA,OAAO;QACT;QAEA,OAAO;IACT;AACF"}

View File

@@ -0,0 +1,11 @@
export function omit(object, keys) {
const omitted = {};
Object.keys(object).forEach((key)=>{
if (!keys.includes(key)) {
omitted[key] = object[key];
}
});
return omitted;
}
//# sourceMappingURL=omit.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/omit.ts"],"sourcesContent":["export function omit<T extends { [key: string]: unknown }, K extends keyof T>(\n object: T,\n keys: K[]\n): Omit<T, K> {\n const omitted: { [key: string]: unknown } = {}\n Object.keys(object).forEach((key) => {\n if (!keys.includes(key as K)) {\n omitted[key] = object[key]\n }\n })\n return omitted as Omit<T, K>\n}\n"],"names":["omit","object","keys","omitted","Object","forEach","key","includes"],"mappings":"AAAA,OAAO,SAASA,KACdC,MAAS,EACTC,IAAS;IAET,MAAMC,UAAsC,CAAC;IAC7CC,OAAOF,IAAI,CAACD,QAAQI,OAAO,CAAC,CAACC;QAC3B,IAAI,CAACJ,KAAKK,QAAQ,CAACD,MAAW;YAC5BH,OAAO,CAACG,IAAI,GAAGL,MAAM,CAACK,IAAI;QAC5B;IACF;IACA,OAAOH;AACT"}

View File

@@ -0,0 +1,23 @@
/**
* Given a path this function will find the pathname, query and hash and return
* them. This is useful to parse full paths on the client side.
* @param path A path to parse e.g. /foo/bar?id=1#hash
*/ export function parsePath(path) {
const hashIndex = path.indexOf('#');
const queryIndex = path.indexOf('?');
const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex);
if (hasQuery || hashIndex > -1) {
return {
pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),
query: hasQuery ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : undefined) : '',
hash: hashIndex > -1 ? path.slice(hashIndex) : ''
};
}
return {
pathname: path,
query: '',
hash: ''
};
}
//# sourceMappingURL=parse-path.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/parse-path.ts"],"sourcesContent":["/**\n * Given a path this function will find the pathname, query and hash and return\n * them. This is useful to parse full paths on the client side.\n * @param path A path to parse e.g. /foo/bar?id=1#hash\n */\nexport function parsePath(path: string) {\n const hashIndex = path.indexOf('#')\n const queryIndex = path.indexOf('?')\n const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex)\n\n if (hasQuery || hashIndex > -1) {\n return {\n pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),\n query: hasQuery\n ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : undefined)\n : '',\n hash: hashIndex > -1 ? path.slice(hashIndex) : '',\n }\n }\n\n return { pathname: path, query: '', hash: '' }\n}\n"],"names":["parsePath","path","hashIndex","indexOf","queryIndex","hasQuery","pathname","substring","query","undefined","hash","slice"],"mappings":"AAAA;;;;CAIC,GACD,OAAO,SAASA,UAAUC,IAAY;IACpC,MAAMC,YAAYD,KAAKE,OAAO,CAAC;IAC/B,MAAMC,aAAaH,KAAKE,OAAO,CAAC;IAChC,MAAME,WAAWD,aAAa,CAAC,KAAMF,CAAAA,YAAY,KAAKE,aAAaF,SAAQ;IAE3E,IAAIG,YAAYH,YAAY,CAAC,GAAG;QAC9B,OAAO;YACLI,UAAUL,KAAKM,SAAS,CAAC,GAAGF,WAAWD,aAAaF;YACpDM,OAAOH,WACHJ,KAAKM,SAAS,CAACH,YAAYF,YAAY,CAAC,IAAIA,YAAYO,aACxD;YACJC,MAAMR,YAAY,CAAC,IAAID,KAAKU,KAAK,CAACT,aAAa;QACjD;IACF;IAEA,OAAO;QAAEI,UAAUL;QAAMO,OAAO;QAAIE,MAAM;IAAG;AAC/C"}

View File

@@ -0,0 +1,24 @@
import { getLocationOrigin } from '../../utils';
import { searchParamsToUrlQuery } from './querystring';
export function parseRelativeUrl(url, base, parseQuery) {
if (parseQuery === void 0) parseQuery = true;
const globalBase = new URL(typeof window === 'undefined' ? 'http://n' : getLocationOrigin());
const resolvedBase = base ? new URL(base, globalBase) : url.startsWith('.') ? new URL(typeof window === 'undefined' ? 'http://n' : window.location.href) : globalBase;
const { pathname, searchParams, search, hash, href, origin } = new URL(url, resolvedBase);
if (origin !== globalBase.origin) {
throw Object.defineProperty(new Error("invariant: invalid relative URL, router received " + url), "__NEXT_ERROR_CODE", {
value: "E159",
enumerable: false,
configurable: true
});
}
return {
pathname,
query: parseQuery ? searchParamsToUrlQuery(searchParams) : undefined,
search,
hash,
href: href.slice(origin.length)
};
}
//# sourceMappingURL=parse-relative-url.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/parse-relative-url.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\nimport { getLocationOrigin } from '../../utils'\nimport { searchParamsToUrlQuery } from './querystring'\n\nexport interface ParsedRelativeUrl {\n hash: string\n href: string\n pathname: string\n query: ParsedUrlQuery\n search: string\n}\n\n/**\n * Parses path-relative urls (e.g. `/hello/world?foo=bar`). If url isn't path-relative\n * (e.g. `./hello`) then at least base must be.\n * Absolute urls are rejected with one exception, in the browser, absolute urls that are on\n * the current origin will be parsed as relative\n */\nexport function parseRelativeUrl(\n url: string,\n base?: string,\n parseQuery?: true\n): ParsedRelativeUrl\nexport function parseRelativeUrl(\n url: string,\n base: string | undefined,\n parseQuery: false\n): Omit<ParsedRelativeUrl, 'query'>\nexport function parseRelativeUrl(\n url: string,\n base?: string,\n parseQuery = true\n): ParsedRelativeUrl | Omit<ParsedRelativeUrl, 'query'> {\n const globalBase = new URL(\n typeof window === 'undefined' ? 'http://n' : getLocationOrigin()\n )\n\n const resolvedBase = base\n ? new URL(base, globalBase)\n : url.startsWith('.')\n ? new URL(\n typeof window === 'undefined' ? 'http://n' : window.location.href\n )\n : globalBase\n\n const { pathname, searchParams, search, hash, href, origin } = new URL(\n url,\n resolvedBase\n )\n\n if (origin !== globalBase.origin) {\n throw new Error(`invariant: invalid relative URL, router received ${url}`)\n }\n\n return {\n pathname,\n query: parseQuery ? searchParamsToUrlQuery(searchParams) : undefined,\n search,\n hash,\n href: href.slice(origin.length),\n }\n}\n"],"names":["getLocationOrigin","searchParamsToUrlQuery","parseRelativeUrl","url","base","parseQuery","globalBase","URL","window","resolvedBase","startsWith","location","href","pathname","searchParams","search","hash","origin","Error","query","undefined","slice","length"],"mappings":"AACA,SAASA,iBAAiB,QAAQ,cAAa;AAC/C,SAASC,sBAAsB,QAAQ,gBAAe;AA0BtD,OAAO,SAASC,iBACdC,GAAW,EACXC,IAAa,EACbC,UAAiB;IAAjBA,IAAAA,uBAAAA,aAAa;IAEb,MAAMC,aAAa,IAAIC,IACrB,OAAOC,WAAW,cAAc,aAAaR;IAG/C,MAAMS,eAAeL,OACjB,IAAIG,IAAIH,MAAME,cACdH,IAAIO,UAAU,CAAC,OACb,IAAIH,IACF,OAAOC,WAAW,cAAc,aAAaA,OAAOG,QAAQ,CAACC,IAAI,IAEnEN;IAEN,MAAM,EAAEO,QAAQ,EAAEC,YAAY,EAAEC,MAAM,EAAEC,IAAI,EAAEJ,IAAI,EAAEK,MAAM,EAAE,GAAG,IAAIV,IACjEJ,KACAM;IAGF,IAAIQ,WAAWX,WAAWW,MAAM,EAAE;QAChC,MAAM,qBAAoE,CAApE,IAAIC,MAAM,AAAC,sDAAmDf,MAA9D,qBAAA;mBAAA;wBAAA;0BAAA;QAAmE;IAC3E;IAEA,OAAO;QACLU;QACAM,OAAOd,aAAaJ,uBAAuBa,gBAAgBM;QAC3DL;QACAC;QACAJ,MAAMA,KAAKS,KAAK,CAACJ,OAAOK,MAAM;IAChC;AACF"}

View File

@@ -0,0 +1,20 @@
import { searchParamsToUrlQuery } from './querystring';
import { parseRelativeUrl } from './parse-relative-url';
export function parseUrl(url) {
if (url.startsWith('/')) {
return parseRelativeUrl(url);
}
const parsedURL = new URL(url);
return {
hash: parsedURL.hash,
hostname: parsedURL.hostname,
href: parsedURL.href,
pathname: parsedURL.pathname,
port: parsedURL.port,
protocol: parsedURL.protocol,
query: searchParamsToUrlQuery(parsedURL.searchParams),
search: parsedURL.search
};
}
//# sourceMappingURL=parse-url.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/parse-url.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\n\nimport { searchParamsToUrlQuery } from './querystring'\nimport { parseRelativeUrl } from './parse-relative-url'\n\nexport interface ParsedUrl {\n hash: string\n hostname?: string | null\n href: string\n pathname: string\n port?: string | null\n protocol?: string | null\n query: ParsedUrlQuery\n search: string\n}\n\nexport function parseUrl(url: string): ParsedUrl {\n if (url.startsWith('/')) {\n return parseRelativeUrl(url)\n }\n\n const parsedURL = new URL(url)\n return {\n hash: parsedURL.hash,\n hostname: parsedURL.hostname,\n href: parsedURL.href,\n pathname: parsedURL.pathname,\n port: parsedURL.port,\n protocol: parsedURL.protocol,\n query: searchParamsToUrlQuery(parsedURL.searchParams),\n search: parsedURL.search,\n }\n}\n"],"names":["searchParamsToUrlQuery","parseRelativeUrl","parseUrl","url","startsWith","parsedURL","URL","hash","hostname","href","pathname","port","protocol","query","searchParams","search"],"mappings":"AAEA,SAASA,sBAAsB,QAAQ,gBAAe;AACtD,SAASC,gBAAgB,QAAQ,uBAAsB;AAavD,OAAO,SAASC,SAASC,GAAW;IAClC,IAAIA,IAAIC,UAAU,CAAC,MAAM;QACvB,OAAOH,iBAAiBE;IAC1B;IAEA,MAAME,YAAY,IAAIC,IAAIH;IAC1B,OAAO;QACLI,MAAMF,UAAUE,IAAI;QACpBC,UAAUH,UAAUG,QAAQ;QAC5BC,MAAMJ,UAAUI,IAAI;QACpBC,UAAUL,UAAUK,QAAQ;QAC5BC,MAAMN,UAAUM,IAAI;QACpBC,UAAUP,UAAUO,QAAQ;QAC5BC,OAAOb,uBAAuBK,UAAUS,YAAY;QACpDC,QAAQV,UAAUU,MAAM;IAC1B;AACF"}

View File

@@ -0,0 +1,16 @@
import { parsePath } from './parse-path';
/**
* Checks if a given path starts with a given prefix. It ensures it matches
* exactly without containing extra chars. e.g. prefix /docs should replace
* for /docs, /docs/, /docs/a but not /docsss
* @param path The path to check.
* @param prefix The prefix to check against.
*/ export function pathHasPrefix(path, prefix) {
if (typeof path !== 'string') {
return false;
}
const { pathname } = parsePath(path);
return pathname === prefix || pathname.startsWith(prefix + '/');
}
//# sourceMappingURL=path-has-prefix.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/path-has-prefix.ts"],"sourcesContent":["import { parsePath } from './parse-path'\n\n/**\n * Checks if a given path starts with a given prefix. It ensures it matches\n * exactly without containing extra chars. e.g. prefix /docs should replace\n * for /docs, /docs/, /docs/a but not /docsss\n * @param path The path to check.\n * @param prefix The prefix to check against.\n */\nexport function pathHasPrefix(path: string, prefix: string) {\n if (typeof path !== 'string') {\n return false\n }\n\n const { pathname } = parsePath(path)\n return pathname === prefix || pathname.startsWith(prefix + '/')\n}\n"],"names":["parsePath","pathHasPrefix","path","prefix","pathname","startsWith"],"mappings":"AAAA,SAASA,SAAS,QAAQ,eAAc;AAExC;;;;;;CAMC,GACD,OAAO,SAASC,cAAcC,IAAY,EAAEC,MAAc;IACxD,IAAI,OAAOD,SAAS,UAAU;QAC5B,OAAO;IACT;IAEA,MAAM,EAAEE,QAAQ,EAAE,GAAGJ,UAAUE;IAC/B,OAAOE,aAAaD,UAAUC,SAASC,UAAU,CAACF,SAAS;AAC7D"}

View File

@@ -0,0 +1,44 @@
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp';
import { regexpToFunction } from 'next/dist/compiled/path-to-regexp';
/**
* Generates a path matcher function for a given path and options based on
* path-to-regexp. By default the match will be case insensitive, non strict
* and delimited by `/`.
*/ export function getPathMatch(path, options) {
const keys = [];
const regexp = pathToRegexp(path, keys, {
delimiter: '/',
sensitive: typeof (options == null ? void 0 : options.sensitive) === 'boolean' ? options.sensitive : false,
strict: options == null ? void 0 : options.strict
});
const matcher = regexpToFunction((options == null ? void 0 : options.regexModifier) ? new RegExp(options.regexModifier(regexp.source), regexp.flags) : regexp, keys);
/**
* A matcher function that will check if a given pathname matches the path
* given in the builder function. When the path does not match it will return
* `false` but if it does it will return an object with the matched params
* merged with the params provided in the second argument.
*/ return (pathname, params)=>{
// If no pathname is provided it's not a match.
if (typeof pathname !== 'string') return false;
const match = matcher(pathname);
// If the path did not match `false` will be returned.
if (!match) return false;
/**
* If unnamed params are not allowed they must be removed from
* the matched parameters. path-to-regexp uses "string" for named and
* "number" for unnamed parameters.
*/ if (options == null ? void 0 : options.removeUnnamedParams) {
for (const key of keys){
if (typeof key.name === 'number') {
delete match.params[key.name];
}
}
}
return {
...params,
...match.params
};
};
}
//# sourceMappingURL=path-match.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/path-match.ts"],"sourcesContent":["import type { Key } from 'next/dist/compiled/path-to-regexp'\nimport { pathToRegexp } from 'next/dist/compiled/path-to-regexp'\nimport { regexpToFunction } from 'next/dist/compiled/path-to-regexp'\n\ninterface Options {\n /**\n * A transformer function that will be applied to the regexp generated\n * from the provided path and path-to-regexp.\n */\n regexModifier?: (regex: string) => string\n /**\n * When true the function will remove all unnamed parameters\n * from the matched parameters.\n */\n removeUnnamedParams?: boolean\n /**\n * When true the regexp won't allow an optional trailing delimiter\n * to match.\n */\n strict?: boolean\n\n /**\n * When true the matcher will be case-sensitive, defaults to false\n */\n sensitive?: boolean\n}\n\nexport type PatchMatcher = (\n pathname: string,\n params?: Record<string, any>\n) => Record<string, any> | false\n\n/**\n * Generates a path matcher function for a given path and options based on\n * path-to-regexp. By default the match will be case insensitive, non strict\n * and delimited by `/`.\n */\nexport function getPathMatch(path: string, options?: Options): PatchMatcher {\n const keys: Key[] = []\n const regexp = pathToRegexp(path, keys, {\n delimiter: '/',\n sensitive:\n typeof options?.sensitive === 'boolean' ? options.sensitive : false,\n strict: options?.strict,\n })\n\n const matcher = regexpToFunction<Record<string, any>>(\n options?.regexModifier\n ? new RegExp(options.regexModifier(regexp.source), regexp.flags)\n : regexp,\n keys\n )\n\n /**\n * A matcher function that will check if a given pathname matches the path\n * given in the builder function. When the path does not match it will return\n * `false` but if it does it will return an object with the matched params\n * merged with the params provided in the second argument.\n */\n return (pathname, params) => {\n // If no pathname is provided it's not a match.\n if (typeof pathname !== 'string') return false\n\n const match = matcher(pathname)\n\n // If the path did not match `false` will be returned.\n if (!match) return false\n\n /**\n * If unnamed params are not allowed they must be removed from\n * the matched parameters. path-to-regexp uses \"string\" for named and\n * \"number\" for unnamed parameters.\n */\n if (options?.removeUnnamedParams) {\n for (const key of keys) {\n if (typeof key.name === 'number') {\n delete match.params[key.name]\n }\n }\n }\n\n return { ...params, ...match.params }\n }\n}\n"],"names":["pathToRegexp","regexpToFunction","getPathMatch","path","options","keys","regexp","delimiter","sensitive","strict","matcher","regexModifier","RegExp","source","flags","pathname","params","match","removeUnnamedParams","key","name"],"mappings":"AACA,SAASA,YAAY,QAAQ,oCAAmC;AAChE,SAASC,gBAAgB,QAAQ,oCAAmC;AA8BpE;;;;CAIC,GACD,OAAO,SAASC,aAAaC,IAAY,EAAEC,OAAiB;IAC1D,MAAMC,OAAc,EAAE;IACtB,MAAMC,SAASN,aAAaG,MAAME,MAAM;QACtCE,WAAW;QACXC,WACE,QAAOJ,2BAAAA,QAASI,SAAS,MAAK,YAAYJ,QAAQI,SAAS,GAAG;QAChEC,MAAM,EAAEL,2BAAAA,QAASK,MAAM;IACzB;IAEA,MAAMC,UAAUT,iBACdG,CAAAA,2BAAAA,QAASO,aAAa,IAClB,IAAIC,OAAOR,QAAQO,aAAa,CAACL,OAAOO,MAAM,GAAGP,OAAOQ,KAAK,IAC7DR,QACJD;IAGF;;;;;GAKC,GACD,OAAO,CAACU,UAAUC;QAChB,+CAA+C;QAC/C,IAAI,OAAOD,aAAa,UAAU,OAAO;QAEzC,MAAME,QAAQP,QAAQK;QAEtB,sDAAsD;QACtD,IAAI,CAACE,OAAO,OAAO;QAEnB;;;;KAIC,GACD,IAAIb,2BAAAA,QAASc,mBAAmB,EAAE;YAChC,KAAK,MAAMC,OAAOd,KAAM;gBACtB,IAAI,OAAOc,IAAIC,IAAI,KAAK,UAAU;oBAChC,OAAOH,MAAMD,MAAM,CAACG,IAAIC,IAAI,CAAC;gBAC/B;YACF;QACF;QAEA,OAAO;YAAE,GAAGJ,MAAM;YAAE,GAAGC,MAAMD,MAAM;QAAC;IACtC;AACF"}

View File

@@ -0,0 +1,257 @@
import { compile, pathToRegexp } from 'next/dist/compiled/path-to-regexp';
import { escapeStringRegexp } from '../../escape-regexp';
import { parseUrl } from './parse-url';
import { INTERCEPTION_ROUTE_MARKERS, isInterceptionRouteAppPath } from './interception-routes';
import { NEXT_RSC_UNION_QUERY } from '../../../../client/components/app-router-headers';
import { getCookieParser } from '../../../../server/api-utils/get-cookie-parser';
/**
* Ensure only a-zA-Z are used for param names for proper interpolating
* with path-to-regexp
*/ function getSafeParamName(paramName) {
let newParamName = '';
for(let i = 0; i < paramName.length; i++){
const charCode = paramName.charCodeAt(i);
if (charCode > 64 && charCode < 91 || // A-Z
charCode > 96 && charCode < 123 // a-z
) {
newParamName += paramName[i];
}
}
return newParamName;
}
function escapeSegment(str, segmentName) {
return str.replace(new RegExp(":" + escapeStringRegexp(segmentName), 'g'), "__ESC_COLON_" + segmentName);
}
function unescapeSegments(str) {
return str.replace(/__ESC_COLON_/gi, ':');
}
export function matchHas(req, query, has, missing) {
if (has === void 0) has = [];
if (missing === void 0) missing = [];
const params = {};
const hasMatch = (hasItem)=>{
let value;
let key = hasItem.key;
switch(hasItem.type){
case 'header':
{
key = key.toLowerCase();
value = req.headers[key];
break;
}
case 'cookie':
{
if ('cookies' in req) {
value = req.cookies[hasItem.key];
} else {
const cookies = getCookieParser(req.headers)();
value = cookies[hasItem.key];
}
break;
}
case 'query':
{
value = query[key];
break;
}
case 'host':
{
const { host } = (req == null ? void 0 : req.headers) || {};
// remove port from host if present
const hostname = host == null ? void 0 : host.split(':', 1)[0].toLowerCase();
value = hostname;
break;
}
default:
{
break;
}
}
if (!hasItem.value && value) {
params[getSafeParamName(key)] = value;
return true;
} else if (value) {
const matcher = new RegExp("^" + hasItem.value + "$");
const matches = Array.isArray(value) ? value.slice(-1)[0].match(matcher) : value.match(matcher);
if (matches) {
if (Array.isArray(matches)) {
if (matches.groups) {
Object.keys(matches.groups).forEach((groupKey)=>{
params[groupKey] = matches.groups[groupKey];
});
} else if (hasItem.type === 'host' && matches[0]) {
params.host = matches[0];
}
}
return true;
}
}
return false;
};
const allMatch = has.every((item)=>hasMatch(item)) && !missing.some((item)=>hasMatch(item));
if (allMatch) {
return params;
}
return false;
}
export function compileNonPath(value, params) {
if (!value.includes(':')) {
return value;
}
for (const key of Object.keys(params)){
if (value.includes(":" + key)) {
value = value.replace(new RegExp(":" + key + "\\*", 'g'), ":" + key + "--ESCAPED_PARAM_ASTERISKS").replace(new RegExp(":" + key + "\\?", 'g'), ":" + key + "--ESCAPED_PARAM_QUESTION").replace(new RegExp(":" + key + "\\+", 'g'), ":" + key + "--ESCAPED_PARAM_PLUS").replace(new RegExp(":" + key + "(?!\\w)", 'g'), "--ESCAPED_PARAM_COLON" + key);
}
}
value = value.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, '\\$1').replace(/--ESCAPED_PARAM_PLUS/g, '+').replace(/--ESCAPED_PARAM_COLON/g, ':').replace(/--ESCAPED_PARAM_QUESTION/g, '?').replace(/--ESCAPED_PARAM_ASTERISKS/g, '*');
// the value needs to start with a forward-slash to be compiled
// correctly
return compile("/" + value, {
validate: false
})(params).slice(1);
}
export function parseDestination(args) {
let escaped = args.destination;
for (const param of Object.keys({
...args.params,
...args.query
})){
if (!param) continue;
escaped = escapeSegment(escaped, param);
}
const parsed = parseUrl(escaped);
let pathname = parsed.pathname;
if (pathname) {
pathname = unescapeSegments(pathname);
}
let href = parsed.href;
if (href) {
href = unescapeSegments(href);
}
let hostname = parsed.hostname;
if (hostname) {
hostname = unescapeSegments(hostname);
}
let hash = parsed.hash;
if (hash) {
hash = unescapeSegments(hash);
}
return {
...parsed,
pathname,
hostname,
href,
hash
};
}
export function prepareDestination(args) {
const query = Object.assign({}, args.query);
delete query[NEXT_RSC_UNION_QUERY];
const parsedDestination = parseDestination(args);
const { hostname: destHostname, query: destQuery } = parsedDestination;
// The following code assumes that the pathname here includes the hash if it's
// present.
let destPath = parsedDestination.pathname;
if (parsedDestination.hash) {
destPath = "" + destPath + parsedDestination.hash;
}
const destParams = [];
const destPathParamKeys = [];
pathToRegexp(destPath, destPathParamKeys);
for (const key of destPathParamKeys){
destParams.push(key.name);
}
if (destHostname) {
const destHostnameParamKeys = [];
pathToRegexp(destHostname, destHostnameParamKeys);
for (const key of destHostnameParamKeys){
destParams.push(key.name);
}
}
const destPathCompiler = compile(destPath, // we don't validate while compiling the destination since we should
// have already validated before we got to this point and validating
// breaks compiling destinations with named pattern params from the source
// e.g. /something:hello(.*) -> /another/:hello is broken with validation
// since compile validation is meant for reversing and not for inserting
// params from a separate path-regex into another
{
validate: false
});
let destHostnameCompiler;
if (destHostname) {
destHostnameCompiler = compile(destHostname, {
validate: false
});
}
// update any params in query values
for (const [key, strOrArray] of Object.entries(destQuery)){
// the value needs to start with a forward-slash to be compiled
// correctly
if (Array.isArray(strOrArray)) {
destQuery[key] = strOrArray.map((value)=>compileNonPath(unescapeSegments(value), args.params));
} else if (typeof strOrArray === 'string') {
destQuery[key] = compileNonPath(unescapeSegments(strOrArray), args.params);
}
}
// add path params to query if it's not a redirect and not
// already defined in destination query or path
let paramKeys = Object.keys(args.params).filter((name)=>name !== 'nextInternalLocale');
if (args.appendParamsToQuery && !paramKeys.some((key)=>destParams.includes(key))) {
for (const key of paramKeys){
if (!(key in destQuery)) {
destQuery[key] = args.params[key];
}
}
}
let newUrl;
// The compiler also that the interception route marker is an unnamed param, hence '0',
// so we need to add it to the params object.
if (isInterceptionRouteAppPath(destPath)) {
for (const segment of destPath.split('/')){
const marker = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
if (marker) {
if (marker === '(..)(..)') {
args.params['0'] = '(..)';
args.params['1'] = '(..)';
} else {
args.params['0'] = marker;
}
break;
}
}
}
try {
newUrl = destPathCompiler(args.params);
const [pathname, hash] = newUrl.split('#', 2);
if (destHostnameCompiler) {
parsedDestination.hostname = destHostnameCompiler(args.params);
}
parsedDestination.pathname = pathname;
parsedDestination.hash = "" + (hash ? '#' : '') + (hash || '');
delete parsedDestination.search;
} catch (err) {
if (err.message.match(/Expected .*? to not repeat, but got an array/)) {
throw Object.defineProperty(new Error("To use a multi-match in the destination you must add `*` at the end of the param name to signify it should repeat. https://nextjs.org/docs/messages/invalid-multi-match"), "__NEXT_ERROR_CODE", {
value: "E329",
enumerable: false,
configurable: true
});
}
throw err;
}
// Query merge order lowest priority to highest
// 1. initial URL query values
// 2. path segment values
// 3. destination specified query values
parsedDestination.query = {
...query,
...parsedDestination.query
};
return {
newUrl,
destQuery,
parsedDestination
};
}
//# sourceMappingURL=prepare-destination.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,56 @@
export function searchParamsToUrlQuery(searchParams) {
const query = {};
for (const [key, value] of searchParams.entries()){
const existing = query[key];
if (typeof existing === 'undefined') {
query[key] = value;
} else if (Array.isArray(existing)) {
existing.push(value);
} else {
query[key] = [
existing,
value
];
}
}
return query;
}
function stringifyUrlQueryParam(param) {
if (typeof param === 'string') {
return param;
}
if (typeof param === 'number' && !isNaN(param) || typeof param === 'boolean') {
return String(param);
} else {
return '';
}
}
export function urlQueryToSearchParams(query) {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(query)){
if (Array.isArray(value)) {
for (const item of value){
searchParams.append(key, stringifyUrlQueryParam(item));
}
} else {
searchParams.set(key, stringifyUrlQueryParam(value));
}
}
return searchParams;
}
export function assign(target) {
for(var _len = arguments.length, searchParamsList = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++){
searchParamsList[_key - 1] = arguments[_key];
}
for (const searchParams of searchParamsList){
for (const key of searchParams.keys()){
target.delete(key);
}
for (const [key, value] of searchParams.entries()){
target.append(key, value);
}
}
return target;
}
//# sourceMappingURL=querystring.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/querystring.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\n\nexport function searchParamsToUrlQuery(\n searchParams: URLSearchParams\n): ParsedUrlQuery {\n const query: ParsedUrlQuery = {}\n for (const [key, value] of searchParams.entries()) {\n const existing = query[key]\n if (typeof existing === 'undefined') {\n query[key] = value\n } else if (Array.isArray(existing)) {\n existing.push(value)\n } else {\n query[key] = [existing, value]\n }\n }\n return query\n}\n\nfunction stringifyUrlQueryParam(param: unknown): string {\n if (typeof param === 'string') {\n return param\n }\n\n if (\n (typeof param === 'number' && !isNaN(param)) ||\n typeof param === 'boolean'\n ) {\n return String(param)\n } else {\n return ''\n }\n}\n\nexport function urlQueryToSearchParams(query: ParsedUrlQuery): URLSearchParams {\n const searchParams = new URLSearchParams()\n for (const [key, value] of Object.entries(query)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n searchParams.append(key, stringifyUrlQueryParam(item))\n }\n } else {\n searchParams.set(key, stringifyUrlQueryParam(value))\n }\n }\n return searchParams\n}\n\nexport function assign(\n target: URLSearchParams,\n ...searchParamsList: URLSearchParams[]\n): URLSearchParams {\n for (const searchParams of searchParamsList) {\n for (const key of searchParams.keys()) {\n target.delete(key)\n }\n\n for (const [key, value] of searchParams.entries()) {\n target.append(key, value)\n }\n }\n\n return target\n}\n"],"names":["searchParamsToUrlQuery","searchParams","query","key","value","entries","existing","Array","isArray","push","stringifyUrlQueryParam","param","isNaN","String","urlQueryToSearchParams","URLSearchParams","Object","item","append","set","assign","target","searchParamsList","keys","delete"],"mappings":"AAEA,OAAO,SAASA,uBACdC,YAA6B;IAE7B,MAAMC,QAAwB,CAAC;IAC/B,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIH,aAAaI,OAAO,GAAI;QACjD,MAAMC,WAAWJ,KAAK,CAACC,IAAI;QAC3B,IAAI,OAAOG,aAAa,aAAa;YACnCJ,KAAK,CAACC,IAAI,GAAGC;QACf,OAAO,IAAIG,MAAMC,OAAO,CAACF,WAAW;YAClCA,SAASG,IAAI,CAACL;QAChB,OAAO;YACLF,KAAK,CAACC,IAAI,GAAG;gBAACG;gBAAUF;aAAM;QAChC;IACF;IACA,OAAOF;AACT;AAEA,SAASQ,uBAAuBC,KAAc;IAC5C,IAAI,OAAOA,UAAU,UAAU;QAC7B,OAAOA;IACT;IAEA,IACE,AAAC,OAAOA,UAAU,YAAY,CAACC,MAAMD,UACrC,OAAOA,UAAU,WACjB;QACA,OAAOE,OAAOF;IAChB,OAAO;QACL,OAAO;IACT;AACF;AAEA,OAAO,SAASG,uBAAuBZ,KAAqB;IAC1D,MAAMD,eAAe,IAAIc;IACzB,KAAK,MAAM,CAACZ,KAAKC,MAAM,IAAIY,OAAOX,OAAO,CAACH,OAAQ;QAChD,IAAIK,MAAMC,OAAO,CAACJ,QAAQ;YACxB,KAAK,MAAMa,QAAQb,MAAO;gBACxBH,aAAaiB,MAAM,CAACf,KAAKO,uBAAuBO;YAClD;QACF,OAAO;YACLhB,aAAakB,GAAG,CAAChB,KAAKO,uBAAuBN;QAC/C;IACF;IACA,OAAOH;AACT;AAEA,OAAO,SAASmB,OACdC,MAAuB;IACvB,IAAA,IAAA,OAAA,UAAA,QAAA,AAAGC,mBAAH,UAAA,OAAA,IAAA,OAAA,QAAA,OAAA,GAAA,OAAA,MAAA;QAAGA,iBAAH,OAAA,KAAA,SAAA,CAAA,KAAsC;;IAEtC,KAAK,MAAMrB,gBAAgBqB,iBAAkB;QAC3C,KAAK,MAAMnB,OAAOF,aAAasB,IAAI,GAAI;YACrCF,OAAOG,MAAM,CAACrB;QAChB;QAEA,KAAK,MAAM,CAACA,KAAKC,MAAM,IAAIH,aAAaI,OAAO,GAAI;YACjDgB,OAAOH,MAAM,CAACf,KAAKC;QACrB;IACF;IAEA,OAAOiB;AACT"}

View File

@@ -0,0 +1,22 @@
/**
* The result of parsing a URL relative to a base URL.
*/ export function parseRelativeURL(url, base) {
const baseURL = typeof base === 'string' ? new URL(base) : base;
const relative = new URL(url, base);
// The URL is relative if the origin is the same as the base URL.
const isRelative = relative.origin === baseURL.origin;
return {
url: isRelative ? relative.toString().slice(baseURL.origin.length) : relative.toString(),
isRelative
};
}
/**
* Given a URL as a string and a base URL it will make the URL relative
* if the parsed protocol and host is the same as the one in the base
* URL. Otherwise it returns the same URL string.
*/ export function getRelativeURL(url, base) {
const relative = parseRelativeURL(url, base);
return relative.url;
}
//# sourceMappingURL=relativize-url.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/relativize-url.ts"],"sourcesContent":["/**\n * The result of parsing a URL relative to a base URL.\n */\nexport type RelativeURL = {\n /**\n * The relative URL. Either a URL including the origin or a relative URL.\n */\n url: string\n\n /**\n * Whether the URL is relative to the base URL.\n */\n isRelative: boolean\n}\n\nexport function parseRelativeURL(\n url: string | URL,\n base: string | URL\n): RelativeURL {\n const baseURL = typeof base === 'string' ? new URL(base) : base\n const relative = new URL(url, base)\n\n // The URL is relative if the origin is the same as the base URL.\n const isRelative = relative.origin === baseURL.origin\n\n return {\n url: isRelative\n ? relative.toString().slice(baseURL.origin.length)\n : relative.toString(),\n isRelative,\n }\n}\n\n/**\n * Given a URL as a string and a base URL it will make the URL relative\n * if the parsed protocol and host is the same as the one in the base\n * URL. Otherwise it returns the same URL string.\n */\nexport function getRelativeURL(url: string | URL, base: string | URL): string {\n const relative = parseRelativeURL(url, base)\n return relative.url\n}\n"],"names":["parseRelativeURL","url","base","baseURL","URL","relative","isRelative","origin","toString","slice","length","getRelativeURL"],"mappings":"AAAA;;CAEC,GAaD,OAAO,SAASA,iBACdC,GAAiB,EACjBC,IAAkB;IAElB,MAAMC,UAAU,OAAOD,SAAS,WAAW,IAAIE,IAAIF,QAAQA;IAC3D,MAAMG,WAAW,IAAID,IAAIH,KAAKC;IAE9B,iEAAiE;IACjE,MAAMI,aAAaD,SAASE,MAAM,KAAKJ,QAAQI,MAAM;IAErD,OAAO;QACLN,KAAKK,aACDD,SAASG,QAAQ,GAAGC,KAAK,CAACN,QAAQI,MAAM,CAACG,MAAM,IAC/CL,SAASG,QAAQ;QACrBF;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASK,eAAeV,GAAiB,EAAEC,IAAkB;IAClE,MAAMG,WAAWL,iBAAiBC,KAAKC;IACvC,OAAOG,SAASJ,GAAG;AACrB"}

View File

@@ -0,0 +1,36 @@
import { pathHasPrefix } from './path-has-prefix';
/**
* Given a path and a prefix it will remove the prefix when it exists in the
* given path. It ensures it matches exactly without containing extra chars
* and if the prefix is not there it will be noop.
*
* @param path The path to remove the prefix from.
* @param prefix The prefix to be removed.
*/ export function removePathPrefix(path, prefix) {
// If the path doesn't start with the prefix we can return it as is. This
// protects us from situations where the prefix is a substring of the path
// prefix such as:
//
// For prefix: /blog
//
// /blog -> true
// /blog/ -> true
// /blog/1 -> true
// /blogging -> false
// /blogging/ -> false
// /blogging/1 -> false
if (!pathHasPrefix(path, prefix)) {
return path;
}
// Remove the prefix from the path via slicing.
const withoutPrefix = path.slice(prefix.length);
// If the path without the prefix starts with a `/` we can return it as is.
if (withoutPrefix.startsWith('/')) {
return withoutPrefix;
}
// If the path without the prefix doesn't start with a `/` we need to add it
// back to the path to make sure it's a valid path.
return "/" + withoutPrefix;
}
//# sourceMappingURL=remove-path-prefix.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/remove-path-prefix.ts"],"sourcesContent":["import { pathHasPrefix } from './path-has-prefix'\n\n/**\n * Given a path and a prefix it will remove the prefix when it exists in the\n * given path. It ensures it matches exactly without containing extra chars\n * and if the prefix is not there it will be noop.\n *\n * @param path The path to remove the prefix from.\n * @param prefix The prefix to be removed.\n */\nexport function removePathPrefix(path: string, prefix: string): string {\n // If the path doesn't start with the prefix we can return it as is. This\n // protects us from situations where the prefix is a substring of the path\n // prefix such as:\n //\n // For prefix: /blog\n //\n // /blog -> true\n // /blog/ -> true\n // /blog/1 -> true\n // /blogging -> false\n // /blogging/ -> false\n // /blogging/1 -> false\n if (!pathHasPrefix(path, prefix)) {\n return path\n }\n\n // Remove the prefix from the path via slicing.\n const withoutPrefix = path.slice(prefix.length)\n\n // If the path without the prefix starts with a `/` we can return it as is.\n if (withoutPrefix.startsWith('/')) {\n return withoutPrefix\n }\n\n // If the path without the prefix doesn't start with a `/` we need to add it\n // back to the path to make sure it's a valid path.\n return `/${withoutPrefix}`\n}\n"],"names":["pathHasPrefix","removePathPrefix","path","prefix","withoutPrefix","slice","length","startsWith"],"mappings":"AAAA,SAASA,aAAa,QAAQ,oBAAmB;AAEjD;;;;;;;CAOC,GACD,OAAO,SAASC,iBAAiBC,IAAY,EAAEC,MAAc;IAC3D,yEAAyE;IACzE,0EAA0E;IAC1E,kBAAkB;IAClB,EAAE;IACF,oBAAoB;IACpB,EAAE;IACF,kBAAkB;IAClB,mBAAmB;IACnB,oBAAoB;IACpB,uBAAuB;IACvB,wBAAwB;IACxB,yBAAyB;IACzB,IAAI,CAACH,cAAcE,MAAMC,SAAS;QAChC,OAAOD;IACT;IAEA,+CAA+C;IAC/C,MAAME,gBAAgBF,KAAKG,KAAK,CAACF,OAAOG,MAAM;IAE9C,2EAA2E;IAC3E,IAAIF,cAAcG,UAAU,CAAC,MAAM;QACjC,OAAOH;IACT;IAEA,4EAA4E;IAC5E,mDAAmD;IACnD,OAAO,AAAC,MAAGA;AACb"}

View File

@@ -0,0 +1,11 @@
/**
* Removes the trailing slash for a given route or page path. Preserves the
* root page. Examples:
* - `/foo/bar/` -> `/foo/bar`
* - `/foo/bar` -> `/foo/bar`
* - `/` -> `/`
*/ export function removeTrailingSlash(route) {
return route.replace(/\/$/, '') || '/';
}
//# sourceMappingURL=remove-trailing-slash.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/remove-trailing-slash.ts"],"sourcesContent":["/**\n * Removes the trailing slash for a given route or page path. Preserves the\n * root page. Examples:\n * - `/foo/bar/` -> `/foo/bar`\n * - `/foo/bar` -> `/foo/bar`\n * - `/` -> `/`\n */\nexport function removeTrailingSlash(route: string) {\n return route.replace(/\\/$/, '') || '/'\n}\n"],"names":["removeTrailingSlash","route","replace"],"mappings":"AAAA;;;;;;CAMC,GACD,OAAO,SAASA,oBAAoBC,KAAa;IAC/C,OAAOA,MAAMC,OAAO,CAAC,OAAO,OAAO;AACrC"}

View File

@@ -0,0 +1,108 @@
import { getPathMatch } from './path-match';
import { matchHas, prepareDestination } from './prepare-destination';
import { removeTrailingSlash } from './remove-trailing-slash';
import { normalizeLocalePath } from '../../i18n/normalize-locale-path';
import { removeBasePath } from '../../../../client/remove-base-path';
import { parseRelativeUrl } from './parse-relative-url';
export default function resolveRewrites(asPath, pages, rewrites, query, resolveHref, locales) {
let matchedPage = false;
let externalDest = false;
let parsedAs = parseRelativeUrl(asPath);
let fsPathname = removeTrailingSlash(normalizeLocalePath(removeBasePath(parsedAs.pathname), locales).pathname);
let resolvedHref;
const handleRewrite = (rewrite)=>{
const matcher = getPathMatch(rewrite.source + (process.env.__NEXT_TRAILING_SLASH ? '(/)?' : ''), {
removeUnnamedParams: true,
strict: true
});
let params = matcher(parsedAs.pathname);
if ((rewrite.has || rewrite.missing) && params) {
const hasParams = matchHas({
headers: {
host: document.location.hostname,
'user-agent': navigator.userAgent
},
cookies: document.cookie.split('; ').reduce((acc, item)=>{
const [key, ...value] = item.split('=');
acc[key] = value.join('=');
return acc;
}, {})
}, parsedAs.query, rewrite.has, rewrite.missing);
if (hasParams) {
Object.assign(params, hasParams);
} else {
params = false;
}
}
if (params) {
if (!rewrite.destination) {
// this is a proxied rewrite which isn't handled on the client
externalDest = true;
return true;
}
const destRes = prepareDestination({
appendParamsToQuery: true,
destination: rewrite.destination,
params: params,
query: query
});
parsedAs = destRes.parsedDestination;
asPath = destRes.newUrl;
Object.assign(query, destRes.parsedDestination.query);
fsPathname = removeTrailingSlash(normalizeLocalePath(removeBasePath(asPath), locales).pathname);
if (pages.includes(fsPathname)) {
// check if we now match a page as this means we are done
// resolving the rewrites
matchedPage = true;
resolvedHref = fsPathname;
return true;
}
// check if we match a dynamic-route, if so we break the rewrites chain
resolvedHref = resolveHref(fsPathname);
if (resolvedHref !== asPath && pages.includes(resolvedHref)) {
matchedPage = true;
return true;
}
}
};
let finished = false;
for(let i = 0; i < rewrites.beforeFiles.length; i++){
// we don't end after match in beforeFiles to allow
// continuing through all beforeFiles rewrites
handleRewrite(rewrites.beforeFiles[i]);
}
matchedPage = pages.includes(fsPathname);
if (!matchedPage) {
if (!finished) {
for(let i = 0; i < rewrites.afterFiles.length; i++){
if (handleRewrite(rewrites.afterFiles[i])) {
finished = true;
break;
}
}
}
// check dynamic route before processing fallback rewrites
if (!finished) {
resolvedHref = resolveHref(fsPathname);
matchedPage = pages.includes(resolvedHref);
finished = matchedPage;
}
if (!finished) {
for(let i = 0; i < rewrites.fallback.length; i++){
if (handleRewrite(rewrites.fallback[i])) {
finished = true;
break;
}
}
}
}
return {
asPath,
parsedAs,
matchedPage,
resolvedHref,
externalDest
};
}
//# sourceMappingURL=resolve-rewrites.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,33 @@
import { DecodeError } from '../../utils';
export function getRouteMatcher(param) {
let { re, groups } = param;
return (pathname)=>{
const routeMatch = re.exec(pathname);
if (!routeMatch) return false;
const decode = (param)=>{
try {
return decodeURIComponent(param);
} catch (e) {
throw Object.defineProperty(new DecodeError('failed to decode param'), "__NEXT_ERROR_CODE", {
value: "E528",
enumerable: false,
configurable: true
});
}
};
const params = {};
for (const [key, group] of Object.entries(groups)){
const match = routeMatch[group.pos];
if (match !== undefined) {
if (group.repeat) {
params[key] = match.split('/').map((entry)=>decode(entry));
} else {
params[key] = decode(match);
}
}
}
return params;
};
}
//# sourceMappingURL=route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/route-matcher.ts"],"sourcesContent":["import type { Group } from './route-regex'\nimport { DecodeError } from '../../utils'\nimport type { Params } from '../../../../server/request/params'\n\nexport interface RouteMatchFn {\n (pathname: string): false | Params\n}\n\ntype RouteMatcherOptions = {\n // We only use the exec method of the RegExp object. This helps us avoid using\n // type assertions that the passed in properties are of the correct type.\n re: Pick<RegExp, 'exec'>\n groups: Record<string, Group>\n}\n\nexport function getRouteMatcher({\n re,\n groups,\n}: RouteMatcherOptions): RouteMatchFn {\n return (pathname: string) => {\n const routeMatch = re.exec(pathname)\n if (!routeMatch) return false\n\n const decode = (param: string) => {\n try {\n return decodeURIComponent(param)\n } catch {\n throw new DecodeError('failed to decode param')\n }\n }\n\n const params: Params = {}\n for (const [key, group] of Object.entries(groups)) {\n const match = routeMatch[group.pos]\n if (match !== undefined) {\n if (group.repeat) {\n params[key] = match.split('/').map((entry) => decode(entry))\n } else {\n params[key] = decode(match)\n }\n }\n }\n\n return params\n }\n}\n"],"names":["DecodeError","getRouteMatcher","re","groups","pathname","routeMatch","exec","decode","param","decodeURIComponent","params","key","group","Object","entries","match","pos","undefined","repeat","split","map","entry"],"mappings":"AACA,SAASA,WAAW,QAAQ,cAAa;AAczC,OAAO,SAASC,gBAAgB,KAGV;IAHU,IAAA,EAC9BC,EAAE,EACFC,MAAM,EACc,GAHU;IAI9B,OAAO,CAACC;QACN,MAAMC,aAAaH,GAAGI,IAAI,CAACF;QAC3B,IAAI,CAACC,YAAY,OAAO;QAExB,MAAME,SAAS,CAACC;YACd,IAAI;gBACF,OAAOC,mBAAmBD;YAC5B,EAAE,UAAM;gBACN,MAAM,qBAAyC,CAAzC,IAAIR,YAAY,2BAAhB,qBAAA;2BAAA;gCAAA;kCAAA;gBAAwC;YAChD;QACF;QAEA,MAAMU,SAAiB,CAAC;QACxB,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACX,QAAS;YACjD,MAAMY,QAAQV,UAAU,CAACO,MAAMI,GAAG,CAAC;YACnC,IAAID,UAAUE,WAAW;gBACvB,IAAIL,MAAMM,MAAM,EAAE;oBAChBR,MAAM,CAACC,IAAI,GAAGI,MAAMI,KAAK,CAAC,KAAKC,GAAG,CAAC,CAACC,QAAUd,OAAOc;gBACvD,OAAO;oBACLX,MAAM,CAACC,IAAI,GAAGJ,OAAOQ;gBACvB;YACF;QACF;QAEA,OAAOL;IACT;AACF"}

View File

@@ -0,0 +1,263 @@
import { NEXT_INTERCEPTION_MARKER_PREFIX, NEXT_QUERY_PARAM_PREFIX } from '../../../../lib/constants';
import { INTERCEPTION_ROUTE_MARKERS } from './interception-routes';
import { escapeStringRegexp } from '../../escape-regexp';
import { removeTrailingSlash } from './remove-trailing-slash';
/**
* Regular expression pattern used to match route parameters.
* Matches both single parameters and parameter groups.
* Examples:
* - `[[...slug]]` matches parameter group with key 'slug', repeat: true, optional: true
* - `[...slug]` matches parameter group with key 'slug', repeat: true, optional: false
* - `[[foo]]` matches parameter with key 'foo', repeat: false, optional: true
* - `[bar]` matches parameter with key 'bar', repeat: false, optional: false
*/ const PARAMETER_PATTERN = /^([^[]*)\[((?:\[[^\]]*\])|[^\]]+)\](.*)$/;
/**
* Parses a given parameter from a route to a data structure that can be used
* to generate the parametrized route.
* Examples:
* - `[[...slug]]` -> `{ key: 'slug', repeat: true, optional: true }`
* - `[...slug]` -> `{ key: 'slug', repeat: true, optional: false }`
* - `[[foo]]` -> `{ key: 'foo', repeat: false, optional: true }`
* - `[bar]` -> `{ key: 'bar', repeat: false, optional: false }`
* - `fizz` -> `{ key: 'fizz', repeat: false, optional: false }`
* @param param - The parameter to parse.
* @returns The parsed parameter as a data structure.
*/ export function parseParameter(param) {
const match = param.match(PARAMETER_PATTERN);
if (!match) {
return parseMatchedParameter(param);
}
return parseMatchedParameter(match[2]);
}
/**
* Parses a matched parameter from the PARAMETER_PATTERN regex to a data structure that can be used
* to generate the parametrized route.
* Examples:
* - `[...slug]` -> `{ key: 'slug', repeat: true, optional: true }`
* - `...slug` -> `{ key: 'slug', repeat: true, optional: false }`
* - `[foo]` -> `{ key: 'foo', repeat: false, optional: true }`
* - `bar` -> `{ key: 'bar', repeat: false, optional: false }`
* @param param - The matched parameter to parse.
* @returns The parsed parameter as a data structure.
*/ function parseMatchedParameter(param) {
const optional = param.startsWith('[') && param.endsWith(']');
if (optional) {
param = param.slice(1, -1);
}
const repeat = param.startsWith('...');
if (repeat) {
param = param.slice(3);
}
return {
key: param,
repeat,
optional
};
}
function getParametrizedRoute(route, includeSuffix, includePrefix) {
const groups = {};
let groupIndex = 1;
const segments = [];
for (const segment of removeTrailingSlash(route).slice(1).split('/')){
const markerMatch = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
const paramMatches = segment.match(PARAMETER_PATTERN) // Check for parameters
;
if (markerMatch && paramMatches && paramMatches[2]) {
const { key, optional, repeat } = parseMatchedParameter(paramMatches[2]);
groups[key] = {
pos: groupIndex++,
repeat,
optional
};
segments.push("/" + escapeStringRegexp(markerMatch) + "([^/]+?)");
} else if (paramMatches && paramMatches[2]) {
const { key, repeat, optional } = parseMatchedParameter(paramMatches[2]);
groups[key] = {
pos: groupIndex++,
repeat,
optional
};
if (includePrefix && paramMatches[1]) {
segments.push("/" + escapeStringRegexp(paramMatches[1]));
}
let s = repeat ? optional ? '(?:/(.+?))?' : '/(.+?)' : '/([^/]+?)';
// Remove the leading slash if includePrefix already added it.
if (includePrefix && paramMatches[1]) {
s = s.substring(1);
}
segments.push(s);
} else {
segments.push("/" + escapeStringRegexp(segment));
}
// If there's a suffix, add it to the segments if it's enabled.
if (includeSuffix && paramMatches && paramMatches[3]) {
segments.push(escapeStringRegexp(paramMatches[3]));
}
}
return {
parameterizedRoute: segments.join(''),
groups
};
}
/**
* From a normalized route this function generates a regular expression and
* a corresponding groups object intended to be used to store matching groups
* from the regular expression.
*/ export function getRouteRegex(normalizedRoute, param) {
let { includeSuffix = false, includePrefix = false, excludeOptionalTrailingSlash = false } = param === void 0 ? {} : param;
const { parameterizedRoute, groups } = getParametrizedRoute(normalizedRoute, includeSuffix, includePrefix);
let re = parameterizedRoute;
if (!excludeOptionalTrailingSlash) {
re += '(?:/)?';
}
return {
re: new RegExp("^" + re + "$"),
groups: groups
};
}
/**
* Builds a function to generate a minimal routeKey using only a-z and minimal
* number of characters.
*/ function buildGetSafeRouteKey() {
let i = 0;
return ()=>{
let routeKey = '';
let j = ++i;
while(j > 0){
routeKey += String.fromCharCode(97 + (j - 1) % 26);
j = Math.floor((j - 1) / 26);
}
return routeKey;
};
}
function getSafeKeyFromSegment(param) {
let { interceptionMarker, getSafeRouteKey, segment, routeKeys, keyPrefix, backreferenceDuplicateKeys } = param;
const { key, optional, repeat } = parseMatchedParameter(segment);
// replace any non-word characters since they can break
// the named regex
let cleanedKey = key.replace(/\W/g, '');
if (keyPrefix) {
cleanedKey = "" + keyPrefix + cleanedKey;
}
let invalidKey = false;
// check if the key is still invalid and fallback to using a known
// safe key
if (cleanedKey.length === 0 || cleanedKey.length > 30) {
invalidKey = true;
}
if (!isNaN(parseInt(cleanedKey.slice(0, 1)))) {
invalidKey = true;
}
if (invalidKey) {
cleanedKey = getSafeRouteKey();
}
const duplicateKey = cleanedKey in routeKeys;
if (keyPrefix) {
routeKeys[cleanedKey] = "" + keyPrefix + key;
} else {
routeKeys[cleanedKey] = key;
}
// if the segment has an interception marker, make sure that's part of the regex pattern
// this is to ensure that the route with the interception marker doesn't incorrectly match
// the non-intercepted route (ie /app/(.)[username] should not match /app/[username])
const interceptionPrefix = interceptionMarker ? escapeStringRegexp(interceptionMarker) : '';
let pattern;
if (duplicateKey && backreferenceDuplicateKeys) {
// Use a backreference to the key to ensure that the key is the same value
// in each of the placeholders.
pattern = "\\k<" + cleanedKey + ">";
} else if (repeat) {
pattern = "(?<" + cleanedKey + ">.+?)";
} else {
pattern = "(?<" + cleanedKey + ">[^/]+?)";
}
return optional ? "(?:/" + interceptionPrefix + pattern + ")?" : "/" + interceptionPrefix + pattern;
}
function getNamedParametrizedRoute(route, prefixRouteKeys, includeSuffix, includePrefix, backreferenceDuplicateKeys) {
const getSafeRouteKey = buildGetSafeRouteKey();
const routeKeys = {};
const segments = [];
for (const segment of removeTrailingSlash(route).slice(1).split('/')){
const hasInterceptionMarker = INTERCEPTION_ROUTE_MARKERS.some((m)=>segment.startsWith(m));
const paramMatches = segment.match(PARAMETER_PATTERN) // Check for parameters
;
if (hasInterceptionMarker && paramMatches && paramMatches[2]) {
// If there's an interception marker, add it to the segments.
segments.push(getSafeKeyFromSegment({
getSafeRouteKey,
interceptionMarker: paramMatches[1],
segment: paramMatches[2],
routeKeys,
keyPrefix: prefixRouteKeys ? NEXT_INTERCEPTION_MARKER_PREFIX : undefined,
backreferenceDuplicateKeys
}));
} else if (paramMatches && paramMatches[2]) {
// If there's a prefix, add it to the segments if it's enabled.
if (includePrefix && paramMatches[1]) {
segments.push("/" + escapeStringRegexp(paramMatches[1]));
}
let s = getSafeKeyFromSegment({
getSafeRouteKey,
segment: paramMatches[2],
routeKeys,
keyPrefix: prefixRouteKeys ? NEXT_QUERY_PARAM_PREFIX : undefined,
backreferenceDuplicateKeys
});
// Remove the leading slash if includePrefix already added it.
if (includePrefix && paramMatches[1]) {
s = s.substring(1);
}
segments.push(s);
} else {
segments.push("/" + escapeStringRegexp(segment));
}
// If there's a suffix, add it to the segments if it's enabled.
if (includeSuffix && paramMatches && paramMatches[3]) {
segments.push(escapeStringRegexp(paramMatches[3]));
}
}
return {
namedParameterizedRoute: segments.join(''),
routeKeys
};
}
/**
* This function extends `getRouteRegex` generating also a named regexp where
* each group is named along with a routeKeys object that indexes the assigned
* named group with its corresponding key. When the routeKeys need to be
* prefixed to uniquely identify internally the "prefixRouteKey" arg should
* be "true" currently this is only the case when creating the routes-manifest
* during the build
*/ export function getNamedRouteRegex(normalizedRoute, options) {
var _options_includeSuffix, _options_includePrefix, _options_backreferenceDuplicateKeys;
const result = getNamedParametrizedRoute(normalizedRoute, options.prefixRouteKeys, (_options_includeSuffix = options.includeSuffix) != null ? _options_includeSuffix : false, (_options_includePrefix = options.includePrefix) != null ? _options_includePrefix : false, (_options_backreferenceDuplicateKeys = options.backreferenceDuplicateKeys) != null ? _options_backreferenceDuplicateKeys : false);
let namedRegex = result.namedParameterizedRoute;
if (!options.excludeOptionalTrailingSlash) {
namedRegex += '(?:/)?';
}
return {
...getRouteRegex(normalizedRoute, options),
namedRegex: "^" + namedRegex + "$",
routeKeys: result.routeKeys
};
}
/**
* Generates a named regexp.
* This is intended to be using for build time only.
*/ export function getNamedMiddlewareRegex(normalizedRoute, options) {
const { parameterizedRoute } = getParametrizedRoute(normalizedRoute, false, false);
const { catchAll = true } = options;
if (parameterizedRoute === '/') {
let catchAllRegex = catchAll ? '.*' : '';
return {
namedRegex: "^/" + catchAllRegex + "$"
};
}
const { namedParameterizedRoute } = getNamedParametrizedRoute(normalizedRoute, false, false, false, false);
let catchAllGroupedRegex = catchAll ? '(?:(/.*)?)' : '';
return {
namedRegex: "^" + namedParameterizedRoute + catchAllGroupedRegex + "$"
};
}
//# sourceMappingURL=route-regex.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,223 @@
class UrlNode {
insert(urlPath) {
this._insert(urlPath.split('/').filter(Boolean), [], false);
}
smoosh() {
return this._smoosh();
}
_smoosh(prefix) {
if (prefix === void 0) prefix = '/';
const childrenPaths = [
...this.children.keys()
].sort();
if (this.slugName !== null) {
childrenPaths.splice(childrenPaths.indexOf('[]'), 1);
}
if (this.restSlugName !== null) {
childrenPaths.splice(childrenPaths.indexOf('[...]'), 1);
}
if (this.optionalRestSlugName !== null) {
childrenPaths.splice(childrenPaths.indexOf('[[...]]'), 1);
}
const routes = childrenPaths.map((c)=>this.children.get(c)._smoosh("" + prefix + c + "/")).reduce((prev, curr)=>[
...prev,
...curr
], []);
if (this.slugName !== null) {
routes.push(...this.children.get('[]')._smoosh(prefix + "[" + this.slugName + "]/"));
}
if (!this.placeholder) {
const r = prefix === '/' ? '/' : prefix.slice(0, -1);
if (this.optionalRestSlugName != null) {
throw Object.defineProperty(new Error('You cannot define a route with the same specificity as a optional catch-all route ("' + r + '" and "' + r + "[[..." + this.optionalRestSlugName + ']]").'), "__NEXT_ERROR_CODE", {
value: "E458",
enumerable: false,
configurable: true
});
}
routes.unshift(r);
}
if (this.restSlugName !== null) {
routes.push(...this.children.get('[...]')._smoosh(prefix + "[..." + this.restSlugName + "]/"));
}
if (this.optionalRestSlugName !== null) {
routes.push(...this.children.get('[[...]]')._smoosh(prefix + "[[..." + this.optionalRestSlugName + "]]/"));
}
return routes;
}
_insert(urlPaths, slugNames, isCatchAll) {
if (urlPaths.length === 0) {
this.placeholder = false;
return;
}
if (isCatchAll) {
throw Object.defineProperty(new Error("Catch-all must be the last part of the URL."), "__NEXT_ERROR_CODE", {
value: "E392",
enumerable: false,
configurable: true
});
}
// The next segment in the urlPaths list
let nextSegment = urlPaths[0];
// Check if the segment matches `[something]`
if (nextSegment.startsWith('[') && nextSegment.endsWith(']')) {
// Strip `[` and `]`, leaving only `something`
let segmentName = nextSegment.slice(1, -1);
let isOptional = false;
if (segmentName.startsWith('[') && segmentName.endsWith(']')) {
// Strip optional `[` and `]`, leaving only `something`
segmentName = segmentName.slice(1, -1);
isOptional = true;
}
if (segmentName.startsWith('…')) {
throw Object.defineProperty(new Error("Detected a three-dot character ('…') at ('" + segmentName + "'). Did you mean ('...')?"), "__NEXT_ERROR_CODE", {
value: "E147",
enumerable: false,
configurable: true
});
}
if (segmentName.startsWith('...')) {
// Strip `...`, leaving only `something`
segmentName = segmentName.substring(3);
isCatchAll = true;
}
if (segmentName.startsWith('[') || segmentName.endsWith(']')) {
throw Object.defineProperty(new Error("Segment names may not start or end with extra brackets ('" + segmentName + "')."), "__NEXT_ERROR_CODE", {
value: "E421",
enumerable: false,
configurable: true
});
}
if (segmentName.startsWith('.')) {
throw Object.defineProperty(new Error("Segment names may not start with erroneous periods ('" + segmentName + "')."), "__NEXT_ERROR_CODE", {
value: "E288",
enumerable: false,
configurable: true
});
}
function handleSlug(previousSlug, nextSlug) {
if (previousSlug !== null) {
// If the specific segment already has a slug but the slug is not `something`
// This prevents collisions like:
// pages/[post]/index.js
// pages/[id]/index.js
// Because currently multiple dynamic params on the same segment level are not supported
if (previousSlug !== nextSlug) {
// TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment.
throw Object.defineProperty(new Error("You cannot use different slug names for the same dynamic path ('" + previousSlug + "' !== '" + nextSlug + "')."), "__NEXT_ERROR_CODE", {
value: "E337",
enumerable: false,
configurable: true
});
}
}
slugNames.forEach((slug)=>{
if (slug === nextSlug) {
throw Object.defineProperty(new Error('You cannot have the same slug name "' + nextSlug + '" repeat within a single dynamic path'), "__NEXT_ERROR_CODE", {
value: "E247",
enumerable: false,
configurable: true
});
}
if (slug.replace(/\W/g, '') === nextSegment.replace(/\W/g, '')) {
throw Object.defineProperty(new Error('You cannot have the slug names "' + slug + '" and "' + nextSlug + '" differ only by non-word symbols within a single dynamic path'), "__NEXT_ERROR_CODE", {
value: "E499",
enumerable: false,
configurable: true
});
}
});
slugNames.push(nextSlug);
}
if (isCatchAll) {
if (isOptional) {
if (this.restSlugName != null) {
throw Object.defineProperty(new Error('You cannot use both an required and optional catch-all route at the same level ("[...' + this.restSlugName + ']" and "' + urlPaths[0] + '" ).'), "__NEXT_ERROR_CODE", {
value: "E299",
enumerable: false,
configurable: true
});
}
handleSlug(this.optionalRestSlugName, segmentName);
// slugName is kept as it can only be one particular slugName
this.optionalRestSlugName = segmentName;
// nextSegment is overwritten to [[...]] so that it can later be sorted specifically
nextSegment = '[[...]]';
} else {
if (this.optionalRestSlugName != null) {
throw Object.defineProperty(new Error('You cannot use both an optional and required catch-all route at the same level ("[[...' + this.optionalRestSlugName + ']]" and "' + urlPaths[0] + '").'), "__NEXT_ERROR_CODE", {
value: "E300",
enumerable: false,
configurable: true
});
}
handleSlug(this.restSlugName, segmentName);
// slugName is kept as it can only be one particular slugName
this.restSlugName = segmentName;
// nextSegment is overwritten to [...] so that it can later be sorted specifically
nextSegment = '[...]';
}
} else {
if (isOptional) {
throw Object.defineProperty(new Error('Optional route parameters are not yet supported ("' + urlPaths[0] + '").'), "__NEXT_ERROR_CODE", {
value: "E435",
enumerable: false,
configurable: true
});
}
handleSlug(this.slugName, segmentName);
// slugName is kept as it can only be one particular slugName
this.slugName = segmentName;
// nextSegment is overwritten to [] so that it can later be sorted specifically
nextSegment = '[]';
}
}
// If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
if (!this.children.has(nextSegment)) {
this.children.set(nextSegment, new UrlNode());
}
this.children.get(nextSegment)._insert(urlPaths.slice(1), slugNames, isCatchAll);
}
constructor(){
this.placeholder = true;
this.children = new Map();
this.slugName = null;
this.restSlugName = null;
this.optionalRestSlugName = null;
}
}
export function getSortedRoutes(normalizedPages) {
// First the UrlNode is created, and every UrlNode can have only 1 dynamic segment
// Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js
// Only 1 dynamic segment per nesting level
// So in the case that is test/integration/dynamic-routing it'll be this:
// pages/[post]/comments.js
// pages/blog/[post]/comment/[id].js
// Both are fine because `pages/[post]` and `pages/blog` are on the same level
// So in this case `UrlNode` created here has `this.slugName === 'post'`
// And since your PR passed through `slugName` as an array basically it'd including it in too many possibilities
// Instead what has to be passed through is the upwards path's dynamic names
const root = new UrlNode();
// Here the `root` gets injected multiple paths, and insert will break them up into sublevels
normalizedPages.forEach((pagePath)=>root.insert(pagePath));
// Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
return root.smoosh();
}
export function getSortedRouteObjects(objects, getter) {
// We're assuming here that all the pathnames are unique, that way we can
// sort the list and use the index as the key.
const indexes = {};
const pathnames = [];
for(let i = 0; i < objects.length; i++){
const pathname = getter(objects[i]);
indexes[pathname] = i;
pathnames[i] = pathname;
}
// Sort the pathnames.
const sorted = getSortedRoutes(pathnames);
// Map the sorted pathnames back to the original objects using the new sorted
// index.
return sorted.map((pathname)=>objects[indexes[pathname]]);
}
//# sourceMappingURL=sorted-routes.js.map

File diff suppressed because one or more lines are too long