@easy-cms/plugin-redirects 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +12 -2
- package/dist/index.js +37 -16
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TypedPlugin } from '@easy-cms/core';
|
|
1
|
+
import { TypedPlugin, RequestContext } from '@easy-cms/core';
|
|
2
2
|
|
|
3
3
|
type Doc$1 = Record<string, unknown>;
|
|
4
4
|
interface UrlArgs {
|
|
@@ -92,6 +92,9 @@ interface RedirectsCMS {
|
|
|
92
92
|
readonly handler: unknown;
|
|
93
93
|
}[];
|
|
94
94
|
};
|
|
95
|
+
uniqueScope?(collection: string, field: string, options?: {
|
|
96
|
+
context?: RequestContext;
|
|
97
|
+
}): Promise<Record<string, unknown>>;
|
|
95
98
|
find(collection: string, options: Record<string, unknown>): Promise<{
|
|
96
99
|
docs: Doc[];
|
|
97
100
|
hasNextPage: boolean;
|
|
@@ -108,6 +111,13 @@ interface Redirect {
|
|
|
108
111
|
* `null` when it isn't a path.
|
|
109
112
|
*/
|
|
110
113
|
declare function normalizePath(input: string): string | null;
|
|
114
|
+
interface ResolveRedirectOptions {
|
|
115
|
+
/**
|
|
116
|
+
* The request's context (e.g. its tenant, `cms.forRequest(request)`): with redirects kept per
|
|
117
|
+
* scope (`uniqueWithin` of `from`), only that scope's apply.
|
|
118
|
+
*/
|
|
119
|
+
readonly context?: RequestContext;
|
|
120
|
+
}
|
|
111
121
|
/** Forgets the cached redirects, so the next request reads them again. */
|
|
112
122
|
declare function clearRedirects(cms: object): void;
|
|
113
123
|
/**
|
|
@@ -120,7 +130,7 @@ declare function clearRedirects(cms: object): void;
|
|
|
120
130
|
* if (redirect) return Response.redirect(new URL(redirect.location, request.url), redirect.status)
|
|
121
131
|
* ```
|
|
122
132
|
*/
|
|
123
|
-
declare function resolveRedirect(cms: RedirectsCMS, input: string | URL): Promise<Redirect | null>;
|
|
133
|
+
declare function resolveRedirect(cms: RedirectsCMS, input: string | URL, options?: ResolveRedirectOptions): Promise<Redirect | null>;
|
|
124
134
|
|
|
125
135
|
declare const STATUSES: readonly [301, 302, 307, 308];
|
|
126
136
|
type RedirectStatus = (typeof STATUSES)[number];
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
// src/info.ts
|
|
7
7
|
var INFO = {
|
|
8
8
|
name: "@easy-cms/plugin-redirects",
|
|
9
|
-
...true ? { version: "0.
|
|
9
|
+
...true ? { version: "0.45.0" } : {}
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/shared.ts
|
|
@@ -31,12 +31,12 @@ var CACHE = /* @__PURE__ */ Symbol.for("easy-cms.plugin-redirects.cache");
|
|
|
31
31
|
function clearRedirects(cms) {
|
|
32
32
|
delete cms[CACHE];
|
|
33
33
|
}
|
|
34
|
-
async function resolveRedirect(cms, input) {
|
|
34
|
+
async function resolveRedirect(cms, input, options = {}) {
|
|
35
35
|
const url = typeof input === "string" ? safeUrl(input) : input;
|
|
36
36
|
if (!url) return null;
|
|
37
37
|
const path = normalizePath(url.pathname);
|
|
38
38
|
if (!path) return null;
|
|
39
|
-
const found = (await redirects(cms)).get(path);
|
|
39
|
+
const found = (await redirects(cms, options)).get(path);
|
|
40
40
|
if (!found) return null;
|
|
41
41
|
const location = url.search && !found.location.includes("?") ? found.location + url.search : found.location;
|
|
42
42
|
return { location, status: found.status };
|
|
@@ -55,22 +55,33 @@ function findSource(cms) {
|
|
|
55
55
|
}
|
|
56
56
|
throw new Error("resolveRedirect: add redirectsPlugin() to the plugins in your Easy CMS config");
|
|
57
57
|
}
|
|
58
|
-
function redirects(cms) {
|
|
58
|
+
async function redirects(cms, options) {
|
|
59
59
|
const source = findSource(cms);
|
|
60
|
+
const scope = options.context && cms.uniqueScope ? await cms.uniqueScope(source.slug, "from", { context: options.context }) : {};
|
|
61
|
+
const key = JSON.stringify(scope);
|
|
60
62
|
const holder = cms;
|
|
61
|
-
|
|
63
|
+
holder[CACHE] ??= /* @__PURE__ */ new Map();
|
|
64
|
+
const caches = holder[CACHE];
|
|
65
|
+
const cached = caches.get(key);
|
|
62
66
|
if (cached && Date.now() - cached.at < source.cacheTTL) return cached.map;
|
|
63
|
-
const map = load(cms, source);
|
|
64
|
-
|
|
67
|
+
const map = load(cms, source, scope);
|
|
68
|
+
caches.set(key, { at: Date.now(), map });
|
|
65
69
|
map.catch(() => {
|
|
66
|
-
if (
|
|
70
|
+
if (caches.get(key)?.map === map) caches.delete(key);
|
|
67
71
|
});
|
|
68
72
|
return map;
|
|
69
73
|
}
|
|
70
|
-
async function load(cms, source) {
|
|
74
|
+
async function load(cms, source, scope) {
|
|
71
75
|
const map = /* @__PURE__ */ new Map();
|
|
76
|
+
const where = Object.entries(scope).map(([name, value]) => ({ [name]: { equals: value } }));
|
|
72
77
|
for (let page = 1; ; page++) {
|
|
73
|
-
const result = await cms.find(source.slug, {
|
|
78
|
+
const result = await cms.find(source.slug, {
|
|
79
|
+
depth: 0,
|
|
80
|
+
sort: "id",
|
|
81
|
+
limit: 500,
|
|
82
|
+
page,
|
|
83
|
+
...where.length ? { where: { and: where } } : {}
|
|
84
|
+
});
|
|
74
85
|
for (const doc of result.docs) {
|
|
75
86
|
const from = typeof doc.from === "string" ? normalizePath(doc.from) : null;
|
|
76
87
|
if (!from) continue;
|
|
@@ -233,20 +244,30 @@ function redirectsPlugin(options = {}) {
|
|
|
233
244
|
const to = now[i];
|
|
234
245
|
if (!from || !to || from === to || done.has(from)) continue;
|
|
235
246
|
done.add(from);
|
|
236
|
-
await addRedirect(cms, { from, collection: s, id: doc.id, locale, current: to });
|
|
247
|
+
await addRedirect(cms, { from, collection: s, id: doc.id, locale, current: to, doc });
|
|
237
248
|
}
|
|
238
249
|
};
|
|
239
250
|
const addRedirect = async (cms, args) => {
|
|
240
|
-
const
|
|
251
|
+
const from = cms.config.collections.find((c) => c.slug === slug)?.fields.find((f) => f.name === "from");
|
|
252
|
+
const scope = Object.fromEntries(
|
|
253
|
+
(from?.uniqueWithin === void 0 ? [] : [from.uniqueWithin].flat()).map((name) => [
|
|
254
|
+
name,
|
|
255
|
+
args.doc[name] ?? null
|
|
256
|
+
])
|
|
257
|
+
);
|
|
258
|
+
const inScope = Object.entries(scope).map(([name, value]) => ({ [name]: { equals: value } }));
|
|
259
|
+
const where = (path) => ({ and: [{ from: { equals: path } }, ...inScope] });
|
|
260
|
+
const loops = await cms.find(slug, { where: where(args.current), limit: 100 });
|
|
241
261
|
for (const loop of loops.docs) await cms.delete(slug, loop.id);
|
|
242
262
|
const data = {
|
|
243
263
|
from: args.from,
|
|
244
264
|
to: null,
|
|
245
265
|
[targetField(args.collection)]: args.id,
|
|
246
266
|
...locales.length > 0 ? { locale: args.locale } : {},
|
|
247
|
-
type: "301"
|
|
267
|
+
type: "301",
|
|
268
|
+
...scope
|
|
248
269
|
};
|
|
249
|
-
const existing = await cms.find(slug, { where:
|
|
270
|
+
const existing = await cms.find(slug, { where: where(args.from), limit: 1 });
|
|
250
271
|
if (existing.docs[0]) await cms.update(slug, existing.docs[0].id, data);
|
|
251
272
|
else await cms.create(slug, data);
|
|
252
273
|
};
|
|
@@ -267,11 +288,11 @@ function redirectsPlugin(options = {}) {
|
|
|
267
288
|
method: "get",
|
|
268
289
|
// Lets `resolveRedirect(cms)` find these options from the resolved config.
|
|
269
290
|
handler: Object.assign(
|
|
270
|
-
async ({ url, cms }) => {
|
|
291
|
+
async ({ url, cms, context }) => {
|
|
271
292
|
const path = url.searchParams.get("path");
|
|
272
293
|
if (!path)
|
|
273
294
|
return Response.json({ errors: [{ message: "path is required" }] }, { status: 400 });
|
|
274
|
-
const redirect = await resolveRedirect(cms, path);
|
|
295
|
+
const redirect = await resolveRedirect(cms, path, { context });
|
|
275
296
|
if (!redirect)
|
|
276
297
|
return Response.json({ errors: [{ message: "No redirect" }] }, { status: 404 });
|
|
277
298
|
return Response.json(redirect, { headers: { "cache-control": "public, max-age=60" } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easy-cms/plugin-redirects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Redirects for Easy CMS: 301s managed in the admin, automatic redirects when a page's address changes",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@easy-cms/core": "^0.
|
|
19
|
+
"@easy-cms/core": "^0.45.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@easy-cms/core": "^0.
|
|
23
|
-
"@easy-cms/db-sqlite": "^0.
|
|
22
|
+
"@easy-cms/core": "^0.45.0",
|
|
23
|
+
"@easy-cms/db-sqlite": "^0.45.0"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=22.12"
|