kizlo 0.2.1 → 0.3.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/README.md +7 -11
- package/dist/adapters/captcha.js +13 -5
- package/dist/cli/commands/dev.js +1 -1
- package/dist/cli/commands/init.js +51 -36
- package/dist/cli/commands/test.js +4 -1
- package/dist/cli/presets/base.js +17 -9
- package/dist/cli/presets/nextjs.js +61 -14
- package/dist/cli/wp/dev.js +1 -1
- package/dist/cli/wp/phpunit.js +104 -0
- package/dist/context.js +2 -2
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -3
- package/dist/integrations/nextjs/kizlo.server.d.ts +5 -6
- package/dist/integrations/nextjs/kizlo.server.js +7 -5
- package/dist/integrations/nextjs/revalidate.d.ts +28 -0
- package/dist/integrations/nextjs/revalidate.js +49 -0
- package/dist/integrations/nextjs/robots.d.ts +7 -0
- package/dist/integrations/nextjs/robots.js +23 -0
- package/dist/integrations/nextjs/seo.d.ts +7 -0
- package/dist/integrations/nextjs/seo.js +41 -0
- package/dist/integrations/nextjs/sitemap.d.ts +33 -0
- package/dist/integrations/nextjs/sitemap.js +31 -0
- package/dist/nextjs/server.d.ts +3 -1
- package/dist/nextjs/server.js +3 -1
- package/dist/nextjs.d.ts +2 -1
- package/dist/nextjs.js +2 -1
- package/dist/package.js +1 -1
- package/dist/post/index.js +2 -2
- package/dist/router.d.ts +71 -13
- package/dist/seo/index.js +71 -6
- package/dist/seo/robots.js +18 -0
- package/dist/seo/schema.d.ts +15 -1
- package/dist/seo/schema.js +5 -1
- package/dist/seo/sitemap.d.ts +39 -0
- package/dist/seo/sitemap.js +64 -0
- package/dist/seo/types.d.ts +5 -1
- package/dist/seo/utils.d.ts +6 -0
- package/dist/seo/utils.js +33 -1
- package/dist/settings/service.interface.d.ts +1 -0
- package/dist/shared/crypto.d.ts +11 -8
- package/dist/shared/crypto.js +26 -9
- package/dist/shared/error.d.ts +3 -3
- package/dist/shared/procedure.d.ts +10 -10
- package/dist/webhook/schema.d.ts +2 -9
- package/dist/webhook/schema.js +5 -16
- package/dist/wordpress/service.js +4 -1
- package/package.json +2 -2
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Kizlo, resolveKizloConfig } from "../../kizlo.js";
|
|
2
|
+
import { nextRevalidation } from "./revalidate.js";
|
|
2
3
|
|
|
3
4
|
//#region src/integrations/nextjs/kizlo.server.ts
|
|
4
|
-
/**
|
|
5
|
-
* Creates a Kizlo server for Next.js — same as the base `createKizlo`, but reads
|
|
6
|
-
* the public base URL from `NEXT_PUBLIC_KIZLO_BACKEND_URL`.
|
|
7
|
-
*/
|
|
8
5
|
function createKizlo(options) {
|
|
9
|
-
|
|
6
|
+
const revalidate = options?.revalidate;
|
|
7
|
+
const extensions = [...revalidate === false ? [] : [nextRevalidation(typeof revalidate === "object" ? revalidate : void 0)], ...options?.extensions ?? []];
|
|
8
|
+
return new Kizlo(resolveKizloConfig({
|
|
9
|
+
...options,
|
|
10
|
+
extensions
|
|
11
|
+
}, { baseUrlEnvKey: "NEXT_PUBLIC_KIZLO_BACKEND_URL" }));
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
//#endregion
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { KizloEvent } from "../../webhook/schema.js";
|
|
2
|
+
import "../../index.js";
|
|
3
|
+
import { Pathname } from "@kizlo/shared";
|
|
4
|
+
|
|
5
|
+
//#region src/integrations/nextjs/revalidate.d.ts
|
|
6
|
+
type RevalidatePathFn = (path: string, type?: "layout" | "page") => void;
|
|
7
|
+
type RevalidateTagFn = (tag: string, profile: string | {
|
|
8
|
+
expire?: number;
|
|
9
|
+
}) => void;
|
|
10
|
+
type RevalidateTarget = Pathname | {
|
|
11
|
+
path: Pathname;
|
|
12
|
+
type?: "layout" | "page";
|
|
13
|
+
};
|
|
14
|
+
interface NextRevalidateOptions {
|
|
15
|
+
revalidateTag?: RevalidateTagFn;
|
|
16
|
+
revalidatePath?: RevalidatePathFn;
|
|
17
|
+
/** Return extra paths to revalidate for a given event, on top of the event's own URL. */
|
|
18
|
+
paths?: (event: KizloEvent) => Pathname[] | Promise<Pathname[]>;
|
|
19
|
+
/**
|
|
20
|
+
* How to revalidate the sitemap. Defaults to the route `createSitemapRoute` serves
|
|
21
|
+
* (`/sitemaps/[sitemap]`, revalidated as a `layout` so the whole on-demand subtree
|
|
22
|
+
* refreshes at once). Override this only if you serve sitemaps from your own route(s)
|
|
23
|
+
* instead of `createSitemapRoute`; pass `false` to skip sitemap revalidation.
|
|
24
|
+
*/
|
|
25
|
+
sitemap?: false | RevalidateTarget | RevalidateTarget[];
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { NextRevalidateOptions };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { POST_EVENT_TYPES, TERM_EVENT_TYPES } from "../../webhook/schema.js";
|
|
2
|
+
import { createEventHandler } from "../../webhook/index.js";
|
|
3
|
+
import { createExtension } from "../../shared/extension.js";
|
|
4
|
+
import { ROBOTS_CACHE_TAG } from "./robots.js";
|
|
5
|
+
import { SITEMAP_ROUTE } from "./sitemap.js";
|
|
6
|
+
|
|
7
|
+
//#region src/integrations/nextjs/revalidate.ts
|
|
8
|
+
const DEFAULT_SITEMAP_TARGET = {
|
|
9
|
+
path: SITEMAP_ROUTE,
|
|
10
|
+
type: "layout"
|
|
11
|
+
};
|
|
12
|
+
const CONTENT_EVENT_TYPES = new Set([...POST_EVENT_TYPES, ...TERM_EVENT_TYPES]);
|
|
13
|
+
function nextRevalidation(options) {
|
|
14
|
+
return createExtension({
|
|
15
|
+
id: "nextjs-revalidation",
|
|
16
|
+
init: () => ({ events: [createEventHandler(async (event) => {
|
|
17
|
+
if (!event) return;
|
|
18
|
+
const revalidatePath = options?.revalidatePath ?? (await import("next/cache")).revalidatePath;
|
|
19
|
+
const revalidateTag = options?.revalidateTag ?? (await import("next/cache")).revalidateTag;
|
|
20
|
+
const paths = [
|
|
21
|
+
...await Promise.resolve(options?.paths?.(event) ?? []),
|
|
22
|
+
...event.data?.url ? [event.data.url] : [],
|
|
23
|
+
"/post"
|
|
24
|
+
];
|
|
25
|
+
for (const path of normalizePaths(paths)) revalidatePath(path);
|
|
26
|
+
if (event.type === "settings.saved") revalidateTag(ROBOTS_CACHE_TAG, { expire: 0 });
|
|
27
|
+
if (CONTENT_EVENT_TYPES.has(event.type)) for (const target of resolveSitemapTargets(options?.sitemap)) revalidatePath(normalizePath(target.path), target.type);
|
|
28
|
+
})] })
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function resolveSitemapTargets(option) {
|
|
32
|
+
if (option === false) return [];
|
|
33
|
+
return (option === void 0 ? [DEFAULT_SITEMAP_TARGET] : Array.isArray(option) ? option : [option]).map((target) => typeof target === "string" ? { path: target } : target);
|
|
34
|
+
}
|
|
35
|
+
function normalizePaths(paths) {
|
|
36
|
+
return Array.from(new Set((Array.isArray(paths) ? paths : paths ? [paths] : []).map(normalizePath).filter(Boolean)));
|
|
37
|
+
}
|
|
38
|
+
function normalizePath(path) {
|
|
39
|
+
try {
|
|
40
|
+
return normalizePath(new URL(path).pathname);
|
|
41
|
+
} catch {
|
|
42
|
+
const trimmed = path.trim();
|
|
43
|
+
if (!trimmed) return "";
|
|
44
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { nextRevalidation };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createRobotsRoute } from "../../seo/robots.js";
|
|
2
|
+
|
|
3
|
+
//#region src/integrations/nextjs/robots.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cache tag for the cached robots.txt body. The Next revalidation extension calls
|
|
6
|
+
* `revalidateTag(ROBOTS_CACHE_TAG)` on every webhook event so the route refreshes
|
|
7
|
+
* on-demand instead of on every request.
|
|
8
|
+
*/
|
|
9
|
+
const ROBOTS_CACHE_TAG = "kizlo:robots";
|
|
10
|
+
function createRobotsRoute$1(client) {
|
|
11
|
+
const handler = createRobotsRoute(client);
|
|
12
|
+
let getBody;
|
|
13
|
+
return async function GET(request) {
|
|
14
|
+
if (!getBody) {
|
|
15
|
+
const { unstable_cache } = await import("next/cache");
|
|
16
|
+
getBody = unstable_cache(async () => (await handler(request)).text(), [ROBOTS_CACHE_TAG], { tags: [ROBOTS_CACHE_TAG] });
|
|
17
|
+
}
|
|
18
|
+
return new Response(await getBody(), { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { ROBOTS_CACHE_TAG, createRobotsRoute$1 as createRobotsRoute };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/integrations/nextjs/seo.ts
|
|
2
|
+
function toMetadata(head) {
|
|
3
|
+
const robots = [
|
|
4
|
+
head.robots.index,
|
|
5
|
+
head.robots.follow,
|
|
6
|
+
head.robots.maxSnippet,
|
|
7
|
+
head.robots.maxImagePreview,
|
|
8
|
+
head.robots.maxVideoPreview
|
|
9
|
+
].join(", ");
|
|
10
|
+
return {
|
|
11
|
+
title: { absolute: head.title },
|
|
12
|
+
description: head.og.description || void 0,
|
|
13
|
+
alternates: { canonical: head.canonical },
|
|
14
|
+
robots,
|
|
15
|
+
openGraph: {
|
|
16
|
+
type: head.og.type,
|
|
17
|
+
title: head.og.title,
|
|
18
|
+
description: head.og.description || void 0,
|
|
19
|
+
url: head.og.url,
|
|
20
|
+
siteName: head.og.siteName,
|
|
21
|
+
locale: head.og.locale,
|
|
22
|
+
images: head.og.image ? [{
|
|
23
|
+
url: head.og.image.url,
|
|
24
|
+
width: head.og.image.width,
|
|
25
|
+
height: head.og.image.height,
|
|
26
|
+
alt: head.og.image.alt ?? void 0
|
|
27
|
+
}] : void 0
|
|
28
|
+
},
|
|
29
|
+
twitter: {
|
|
30
|
+
card: head.twitter.card,
|
|
31
|
+
title: head.twitter.title,
|
|
32
|
+
description: head.twitter.description || void 0,
|
|
33
|
+
site: head.twitter.site ?? void 0,
|
|
34
|
+
creator: head.twitter.creator ?? void 0,
|
|
35
|
+
images: head.twitter.image ? [head.twitter.image] : void 0
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { toMetadata };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { S2SClient } from "../../kizlo.js";
|
|
2
|
+
import { CreateSitemapRouteOptions } from "../../seo/sitemap.js";
|
|
3
|
+
|
|
4
|
+
//#region src/integrations/nextjs/sitemap.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The Next route the sitemap handler is mounted at: a single dynamic segment named
|
|
8
|
+
* `[sitemap]`. It is fixed (not configurable) so that one `revalidatePath(SITEMAP_ROUTE,
|
|
9
|
+
* "layout")` refreshes the index and every generated sitemap at once. A `layout`
|
|
10
|
+
* revalidation (not `page`) is required because the route has no `generateStaticParams`,
|
|
11
|
+
* so its slugs are generated on-demand and only a subtree invalidation reaches them.
|
|
12
|
+
* `[sitemap]` is Next's file-routing convention, which is why it lives in this integration
|
|
13
|
+
* and not in core.
|
|
14
|
+
*/
|
|
15
|
+
declare const SITEMAP_ROUTE: "/sitemaps/[sitemap]";
|
|
16
|
+
/** Next's dynamic-route context. `params` is a promise in the App Router. */
|
|
17
|
+
interface NextRouteContext {
|
|
18
|
+
params?: {
|
|
19
|
+
sitemap?: string;
|
|
20
|
+
} | Promise<{
|
|
21
|
+
sitemap?: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Next wrapper around the framework-agnostic {@link createCoreSitemapRoute}. It serves the
|
|
26
|
+
* same sitemap index and collection pages, and additionally asserts the route is mounted at
|
|
27
|
+
* {@link SITEMAP_ROUTE}: if the dynamic segment is not named `[sitemap]`, `params.sitemap`
|
|
28
|
+
* is missing and a wrong mount would silently break `revalidatePath(SITEMAP_ROUTE, "layout")`,
|
|
29
|
+
* so it throws loudly on the first request instead.
|
|
30
|
+
*/
|
|
31
|
+
declare function createSitemapRoute(client: S2SClient<[]>, options?: CreateSitemapRouteOptions): (request: Request, ctx?: NextRouteContext) => Promise<Response>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { SITEMAP_ROUTE, createSitemapRoute };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SITEMAP_BASE, createSitemapRoute } from "../../seo/sitemap.js";
|
|
2
|
+
|
|
3
|
+
//#region src/integrations/nextjs/sitemap.ts
|
|
4
|
+
/**
|
|
5
|
+
* The Next route the sitemap handler is mounted at: a single dynamic segment named
|
|
6
|
+
* `[sitemap]`. It is fixed (not configurable) so that one `revalidatePath(SITEMAP_ROUTE,
|
|
7
|
+
* "layout")` refreshes the index and every generated sitemap at once. A `layout`
|
|
8
|
+
* revalidation (not `page`) is required because the route has no `generateStaticParams`,
|
|
9
|
+
* so its slugs are generated on-demand and only a subtree invalidation reaches them.
|
|
10
|
+
* `[sitemap]` is Next's file-routing convention, which is why it lives in this integration
|
|
11
|
+
* and not in core.
|
|
12
|
+
*/
|
|
13
|
+
const SITEMAP_ROUTE = `${SITEMAP_BASE}/[sitemap]`;
|
|
14
|
+
/**
|
|
15
|
+
* Next wrapper around the framework-agnostic {@link createCoreSitemapRoute}. It serves the
|
|
16
|
+
* same sitemap index and collection pages, and additionally asserts the route is mounted at
|
|
17
|
+
* {@link SITEMAP_ROUTE}: if the dynamic segment is not named `[sitemap]`, `params.sitemap`
|
|
18
|
+
* is missing and a wrong mount would silently break `revalidatePath(SITEMAP_ROUTE, "layout")`,
|
|
19
|
+
* so it throws loudly on the first request instead.
|
|
20
|
+
*/
|
|
21
|
+
function createSitemapRoute$1(client, options) {
|
|
22
|
+
const handler = createSitemapRoute(client, options);
|
|
23
|
+
return async function GET(request, ctx) {
|
|
24
|
+
const params = ctx?.params ? await ctx.params : void 0;
|
|
25
|
+
if (params && params.sitemap === void 0) throw new Error(`createSitemapRoute must be mounted at "${SITEMAP_ROUTE}". The dynamic segment must be named [sitemap].`);
|
|
26
|
+
return handler(request);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { SITEMAP_ROUTE, createSitemapRoute$1 as createSitemapRoute };
|
package/dist/nextjs/server.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { KizloServerOptions, createKizlo } from "../integrations/nextjs/kizlo.server.js";
|
|
2
|
-
|
|
2
|
+
import { createRobotsRoute } from "../integrations/nextjs/robots.js";
|
|
3
|
+
import { SITEMAP_ROUTE, createSitemapRoute } from "../integrations/nextjs/sitemap.js";
|
|
4
|
+
export { KizloServerOptions, SITEMAP_ROUTE, createKizlo, createRobotsRoute, createSitemapRoute };
|
package/dist/nextjs/server.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createRobotsRoute } from "../integrations/nextjs/robots.js";
|
|
2
|
+
import { SITEMAP_ROUTE, createSitemapRoute } from "../integrations/nextjs/sitemap.js";
|
|
1
3
|
import { createKizlo } from "../integrations/nextjs/kizlo.server.js";
|
|
2
4
|
|
|
3
|
-
export { createKizlo };
|
|
5
|
+
export { SITEMAP_ROUTE, createKizlo, createRobotsRoute, createSitemapRoute };
|
package/dist/nextjs.d.ts
CHANGED
package/dist/nextjs.js
CHANGED
package/dist/package.js
CHANGED
package/dist/post/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createProcedure } from "../shared/procedure.js";
|
|
2
1
|
import { compare } from "../shared/crypto.js";
|
|
2
|
+
import { createProcedure } from "../shared/procedure.js";
|
|
3
3
|
import { deserializeListMetadata } from "../shared/serialize.js";
|
|
4
4
|
import { getPostTypeService } from "../post-type/service.js";
|
|
5
5
|
import { parseIdentifier } from "../shared/identifier.js";
|
|
@@ -59,7 +59,7 @@ const POST_ROUTER_MAP = {
|
|
|
59
59
|
}
|
|
60
60
|
const data = response.data;
|
|
61
61
|
if (input.query?.password && data.password) {
|
|
62
|
-
if (!compare(input.query.password, data.password)) throw errors.POST_PASSWORD_INVALID();
|
|
62
|
+
if (!await compare(input.query.password, data.password)) throw errors.POST_PASSWORD_INVALID();
|
|
63
63
|
return deserializePost({
|
|
64
64
|
...data,
|
|
65
65
|
password: ""
|
package/dist/router.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ declare const ROUTER_MAP: {
|
|
|
21
21
|
excerpt: string | null;
|
|
22
22
|
commentsEnabled: boolean;
|
|
23
23
|
protected: boolean;
|
|
24
|
-
format: "
|
|
24
|
+
format: "standard" | "aside" | "chat" | "gallery" | "link" | "image" | "quote" | "status" | "video" | "audio";
|
|
25
25
|
sticky: boolean;
|
|
26
26
|
author: {
|
|
27
27
|
id: number;
|
|
@@ -150,7 +150,7 @@ declare const ROUTER_MAP: {
|
|
|
150
150
|
include?: string | number | (string | number)[] | undefined;
|
|
151
151
|
offset?: string | number | undefined;
|
|
152
152
|
order?: "asc" | "desc" | undefined;
|
|
153
|
-
orderby?: "
|
|
153
|
+
orderby?: "date" | "id" | "slug" | "title" | "author" | "include" | "parent" | "relevance" | "include_slugs" | undefined;
|
|
154
154
|
searchColumns?: string[] | undefined;
|
|
155
155
|
slug?: string | string[] | undefined;
|
|
156
156
|
taxRelation?: "AND" | "OR" | undefined;
|
|
@@ -169,7 +169,7 @@ declare const ROUTER_MAP: {
|
|
|
169
169
|
excerpt: string | null;
|
|
170
170
|
commentsEnabled: boolean;
|
|
171
171
|
protected: boolean;
|
|
172
|
-
format: "
|
|
172
|
+
format: "standard" | "aside" | "chat" | "gallery" | "link" | "image" | "quote" | "status" | "video" | "audio";
|
|
173
173
|
sticky: boolean;
|
|
174
174
|
author: {
|
|
175
175
|
id: number;
|
|
@@ -299,12 +299,70 @@ declare const ROUTER_MAP: {
|
|
|
299
299
|
}>;
|
|
300
300
|
};
|
|
301
301
|
seo: {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
homepage: Procedure<"internal", {}, {
|
|
303
|
+
head: {
|
|
304
|
+
title: string;
|
|
305
|
+
canonical: string;
|
|
306
|
+
robots: {
|
|
307
|
+
index: string;
|
|
308
|
+
follow: string;
|
|
309
|
+
maxSnippet: string;
|
|
310
|
+
maxImagePreview: string;
|
|
311
|
+
maxVideoPreview: string;
|
|
312
|
+
};
|
|
313
|
+
og: {
|
|
314
|
+
locale: string;
|
|
315
|
+
type: string;
|
|
316
|
+
title: string;
|
|
317
|
+
url: string;
|
|
318
|
+
siteName: string;
|
|
319
|
+
description: string;
|
|
320
|
+
image: {
|
|
321
|
+
url: string;
|
|
322
|
+
width: string;
|
|
323
|
+
height: string;
|
|
324
|
+
type: string;
|
|
325
|
+
alt: string | null;
|
|
326
|
+
} | null;
|
|
327
|
+
};
|
|
328
|
+
twitter: {
|
|
329
|
+
card: "summary" | "summary_large_image";
|
|
330
|
+
title: string;
|
|
331
|
+
site: string | null;
|
|
332
|
+
creator: string | null;
|
|
333
|
+
description: string;
|
|
334
|
+
image: string | null;
|
|
335
|
+
};
|
|
336
|
+
article: {
|
|
337
|
+
publishedTime: string;
|
|
338
|
+
modifiedTime: string;
|
|
339
|
+
author: string;
|
|
340
|
+
authorUrl: string;
|
|
341
|
+
section: string;
|
|
342
|
+
} | null;
|
|
343
|
+
};
|
|
344
|
+
schema: {
|
|
345
|
+
"@context": "https://schema.org";
|
|
346
|
+
"@graph": Record<string, unknown>[];
|
|
347
|
+
};
|
|
348
|
+
}, DefinedErrorMapLike>;
|
|
349
|
+
sitemaps: {
|
|
350
|
+
list: Procedure<"internal", {}, {
|
|
351
|
+
key: string;
|
|
352
|
+
type: "author" | "post_type" | "taxonomy";
|
|
353
|
+
pages: number;
|
|
354
|
+
lastmod: string;
|
|
355
|
+
}[], DefinedErrorMapLike>;
|
|
356
|
+
index: Procedure<"internal", {}, {
|
|
357
|
+
origin: string;
|
|
358
|
+
sitemaps: {
|
|
359
|
+
key: string;
|
|
360
|
+
type: "author" | "post_type" | "taxonomy";
|
|
361
|
+
pages: number;
|
|
362
|
+
lastmod: string;
|
|
363
|
+
}[];
|
|
364
|
+
}, DefinedErrorMapLike>;
|
|
365
|
+
};
|
|
308
366
|
robots: Procedure<"internal", {}, {
|
|
309
367
|
sitemaps: string[];
|
|
310
368
|
rules: {
|
|
@@ -342,7 +400,7 @@ declare const ROUTER_MAP: {
|
|
|
342
400
|
include?: string | number | (string | number)[] | undefined;
|
|
343
401
|
offset?: string | number | undefined;
|
|
344
402
|
order?: "asc" | "desc" | undefined;
|
|
345
|
-
orderby?: "
|
|
403
|
+
orderby?: "date" | "id" | "slug" | "title" | "author" | "include" | "parent" | "relevance" | "include_slugs" | "menu_order" | undefined;
|
|
346
404
|
searchColumns?: string[] | undefined;
|
|
347
405
|
slug?: string | string[] | undefined;
|
|
348
406
|
taxRelation?: "AND" | "OR" | undefined;
|
|
@@ -355,7 +413,7 @@ declare const ROUTER_MAP: {
|
|
|
355
413
|
id: number;
|
|
356
414
|
href: string;
|
|
357
415
|
name: string;
|
|
358
|
-
type: "custom" | "
|
|
416
|
+
type: "custom" | "post" | "page" | "category" | "product" | "product_cat" | "product_tag";
|
|
359
417
|
description: string;
|
|
360
418
|
meta: {
|
|
361
419
|
[x: string]: string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | /*elided*/any | {
|
|
@@ -413,7 +471,7 @@ declare const ROUTER_MAP: {
|
|
|
413
471
|
include?: string | number | (string | number)[] | undefined;
|
|
414
472
|
offset?: string | number | undefined;
|
|
415
473
|
order?: "asc" | "desc" | undefined;
|
|
416
|
-
orderby?: "
|
|
474
|
+
orderby?: "date" | "id" | "slug" | "title" | "author" | "include" | "parent" | "relevance" | "include_slugs" | "menu_order" | undefined;
|
|
417
475
|
searchColumns?: string[] | undefined;
|
|
418
476
|
slug?: string | string[] | undefined;
|
|
419
477
|
taxRelation?: "AND" | "OR" | undefined;
|
|
@@ -503,7 +561,7 @@ declare const ROUTER_MAP: {
|
|
|
503
561
|
include?: string | number | (string | number)[] | undefined;
|
|
504
562
|
offset?: string | number | undefined;
|
|
505
563
|
order?: "asc" | "desc" | undefined;
|
|
506
|
-
orderby?: "date" | "id" | "
|
|
564
|
+
orderby?: "date" | "id" | "post" | "include" | "parent" | undefined;
|
|
507
565
|
parent?: string | number | (string | number)[] | undefined;
|
|
508
566
|
parentExclude?: string | number | (string | number)[] | undefined;
|
|
509
567
|
post?: string | number | (string | number)[] | undefined;
|
package/dist/seo/index.js
CHANGED
|
@@ -1,20 +1,85 @@
|
|
|
1
1
|
import { createProcedure } from "../shared/procedure.js";
|
|
2
2
|
import { WP_KIZLO_BASE } from "../wordpress/constants.js";
|
|
3
|
-
import { ListSitemapUrlInput, Robots, SitemapList, SitemapUrlList } from "./schema.js";
|
|
3
|
+
import { ListSitemapUrlInput, Robots, Seo, SitemapIndex, SitemapList, SitemapUrlList } from "./schema.js";
|
|
4
4
|
|
|
5
5
|
//#region src/seo/index.ts
|
|
6
6
|
const SEO_ROUTER_MAP = {
|
|
7
|
-
|
|
7
|
+
homepage: createProcedure({
|
|
8
8
|
scope: "internal",
|
|
9
|
-
output:
|
|
9
|
+
output: Seo
|
|
10
10
|
}, async ({ context, errors }) => {
|
|
11
|
-
const response = await context.service.wordpress.get("/seo/
|
|
11
|
+
const response = await context.service.wordpress.get("/seo/homepage", { base: WP_KIZLO_BASE });
|
|
12
12
|
if (response.error) {
|
|
13
|
-
context.logger.error("
|
|
13
|
+
context.logger.error("Get homepage seo unhandled error", response.error);
|
|
14
14
|
throw errors.INTERNAL_SERVER_ERROR();
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
const { head } = response.data;
|
|
17
|
+
return {
|
|
18
|
+
head: {
|
|
19
|
+
title: head.title,
|
|
20
|
+
canonical: head.canonical,
|
|
21
|
+
robots: {
|
|
22
|
+
index: head.robots.index,
|
|
23
|
+
follow: head.robots.follow,
|
|
24
|
+
maxSnippet: head.robots["max-snippet"],
|
|
25
|
+
maxImagePreview: head.robots["max-image-preview"],
|
|
26
|
+
maxVideoPreview: head.robots["max-video-preview"]
|
|
27
|
+
},
|
|
28
|
+
og: {
|
|
29
|
+
locale: head.og.locale,
|
|
30
|
+
type: head.og.type,
|
|
31
|
+
title: head.og.title,
|
|
32
|
+
url: head.og.url,
|
|
33
|
+
siteName: head.og.site_name,
|
|
34
|
+
description: head.og.description ?? "",
|
|
35
|
+
image: head.og.image
|
|
36
|
+
},
|
|
37
|
+
twitter: {
|
|
38
|
+
card: head.twitter.card,
|
|
39
|
+
title: head.twitter.title,
|
|
40
|
+
site: head.twitter.site,
|
|
41
|
+
creator: head.twitter.creator,
|
|
42
|
+
description: head.twitter.description ?? "",
|
|
43
|
+
image: head.twitter.image
|
|
44
|
+
},
|
|
45
|
+
article: head.article && {
|
|
46
|
+
publishedTime: head.article.published_time,
|
|
47
|
+
modifiedTime: head.article.modified_time,
|
|
48
|
+
author: head.article.author,
|
|
49
|
+
authorUrl: head.article.author_url,
|
|
50
|
+
section: head.article.section
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
schema: response.data.schema
|
|
54
|
+
};
|
|
17
55
|
}),
|
|
56
|
+
sitemaps: {
|
|
57
|
+
list: createProcedure({
|
|
58
|
+
scope: "internal",
|
|
59
|
+
output: SitemapList
|
|
60
|
+
}, async ({ context, errors }) => {
|
|
61
|
+
const response = await context.service.wordpress.get("/seo/sitemaps", { base: WP_KIZLO_BASE });
|
|
62
|
+
if (response.error) {
|
|
63
|
+
context.logger.error("List sitemaps unhandled error", response.error);
|
|
64
|
+
throw errors.INTERNAL_SERVER_ERROR();
|
|
65
|
+
}
|
|
66
|
+
return response.data;
|
|
67
|
+
}),
|
|
68
|
+
index: createProcedure({
|
|
69
|
+
scope: "internal",
|
|
70
|
+
output: SitemapIndex
|
|
71
|
+
}, async ({ context, errors }) => {
|
|
72
|
+
const response = await context.service.wordpress.get("/seo/sitemaps/index", { base: WP_KIZLO_BASE });
|
|
73
|
+
if (response.error) {
|
|
74
|
+
context.logger.error("Get sitemap index unhandled error", response.error);
|
|
75
|
+
throw errors.INTERNAL_SERVER_ERROR();
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
origin: response.data.origin,
|
|
79
|
+
sitemaps: response.data.sitemaps
|
|
80
|
+
};
|
|
81
|
+
})
|
|
82
|
+
},
|
|
18
83
|
robots: createProcedure({
|
|
19
84
|
scope: "internal",
|
|
20
85
|
output: Robots
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/seo/robots.ts
|
|
2
|
+
function createRobotsRoute(client) {
|
|
3
|
+
return async function GET(_request) {
|
|
4
|
+
const data = await client.seo.robots.call();
|
|
5
|
+
const groups = data.rules.map((rule) => {
|
|
6
|
+
const lines = [`User-agent: ${rule.userAgent}`];
|
|
7
|
+
for (const path of rule.allow) lines.push(`Allow: ${path}`);
|
|
8
|
+
for (const path of rule.disallow) lines.push(`Disallow: ${path}`);
|
|
9
|
+
return lines.join("\n");
|
|
10
|
+
});
|
|
11
|
+
const sitemaps = data.sitemaps.map((url) => `Sitemap: ${url}`).join("\n");
|
|
12
|
+
const blocks = [groups.join("\n\n"), sitemaps].filter(Boolean);
|
|
13
|
+
return new Response(`${blocks.join("\n\n")}\n`, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createRobotsRoute };
|
package/dist/seo/schema.d.ts
CHANGED
|
@@ -131,6 +131,20 @@ declare const SitemapList: z.ZodArray<z.ZodObject<{
|
|
|
131
131
|
lastmod: z.ZodString;
|
|
132
132
|
}, z.core.$strip>>;
|
|
133
133
|
type SitemapList = z.infer<typeof SitemapList>;
|
|
134
|
+
declare const SitemapIndex: z.ZodObject<{
|
|
135
|
+
origin: z.ZodString;
|
|
136
|
+
sitemaps: z.ZodArray<z.ZodObject<{
|
|
137
|
+
key: z.ZodString;
|
|
138
|
+
type: z.ZodEnum<{
|
|
139
|
+
author: "author";
|
|
140
|
+
post_type: "post_type";
|
|
141
|
+
taxonomy: "taxonomy";
|
|
142
|
+
}>;
|
|
143
|
+
pages: z.ZodNumber;
|
|
144
|
+
lastmod: z.ZodString;
|
|
145
|
+
}, z.core.$strip>>;
|
|
146
|
+
}, z.core.$strip>;
|
|
147
|
+
type SitemapIndex = z.infer<typeof SitemapIndex>;
|
|
134
148
|
declare const SitemapUrlImage: z.ZodObject<{
|
|
135
149
|
loc: z.ZodString;
|
|
136
150
|
title: z.ZodString;
|
|
@@ -193,4 +207,4 @@ declare const Robots: z.ZodObject<{
|
|
|
193
207
|
}, z.core.$strip>;
|
|
194
208
|
type Robots = z.infer<typeof Robots>;
|
|
195
209
|
//#endregion
|
|
196
|
-
export { CONTENT_TYPES, ContentType, ListSitemapUrlInput, ListSitemapUrlInputBase, Robots, RobotsRule, Seo, SeoHead, SeoSchema, Sitemap, SitemapList, SitemapUrl, SitemapUrlImage, SitemapUrlList };
|
|
210
|
+
export { CONTENT_TYPES, ContentType, ListSitemapUrlInput, ListSitemapUrlInputBase, Robots, RobotsRule, Seo, SeoHead, SeoSchema, Sitemap, SitemapIndex, SitemapList, SitemapUrl, SitemapUrlImage, SitemapUrlList };
|
package/dist/seo/schema.js
CHANGED
|
@@ -64,6 +64,10 @@ const Sitemap = z.object({
|
|
|
64
64
|
lastmod: z.string()
|
|
65
65
|
});
|
|
66
66
|
const SitemapList = z.array(Sitemap);
|
|
67
|
+
const SitemapIndex = z.object({
|
|
68
|
+
origin: z.string(),
|
|
69
|
+
sitemaps: SitemapList
|
|
70
|
+
});
|
|
67
71
|
const SitemapUrlImage = z.object({
|
|
68
72
|
loc: z.string(),
|
|
69
73
|
title: z.string()
|
|
@@ -93,4 +97,4 @@ const Robots = z.object({
|
|
|
93
97
|
});
|
|
94
98
|
|
|
95
99
|
//#endregion
|
|
96
|
-
export { CONTENT_TYPES, ContentType, ListSitemapUrlInput, ListSitemapUrlInputBase, Robots, RobotsRule, Seo, SeoHead, SeoSchema, Sitemap, SitemapList, SitemapUrl, SitemapUrlImage, SitemapUrlList };
|
|
100
|
+
export { CONTENT_TYPES, ContentType, ListSitemapUrlInput, ListSitemapUrlInputBase, Robots, RobotsRule, Seo, SeoHead, SeoSchema, Sitemap, SitemapIndex, SitemapList, SitemapUrl, SitemapUrlImage, SitemapUrlList };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { SitemapUrl } from "./schema.js";
|
|
2
|
+
import { SitemapIndexEntry } from "./utils.js";
|
|
3
|
+
import { S2SClient } from "../kizlo.js";
|
|
4
|
+
|
|
5
|
+
//#region src/seo/sitemap.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Every generated sitemap is served from `/sitemaps`: the index at `/sitemaps/index.xml`
|
|
9
|
+
* and each collection page at `/sitemaps/{key}.xml` (later pages `-{n}`). The base is fixed
|
|
10
|
+
* because the index `<loc>` entries (built by `sitemapEntryPath`) point at it, so the route
|
|
11
|
+
* must be mounted there for the links to resolve. How the route is mounted is a
|
|
12
|
+
* framework concern; this handler only cares about the trailing slug.
|
|
13
|
+
*/
|
|
14
|
+
declare const SITEMAP_BASE = "/sitemaps";
|
|
15
|
+
/** Slug that serves the sitemap index (`/sitemaps/index.xml`) rather than a collection. */
|
|
16
|
+
declare const SITEMAP_INDEX_SLUG = "index.xml";
|
|
17
|
+
/**
|
|
18
|
+
* A sitemap not backed by WordPress (e.g. docs pages living in the app). It joins the
|
|
19
|
+
* same index/urlset pipeline: `entry` adds it to the index, `urls` serves its pages.
|
|
20
|
+
*/
|
|
21
|
+
interface SitemapRouteExtra {
|
|
22
|
+
entry: SitemapIndexEntry;
|
|
23
|
+
urls: (origin: string, page: number) => SitemapUrl[] | Promise<SitemapUrl[]>;
|
|
24
|
+
}
|
|
25
|
+
interface CreateSitemapRouteOptions {
|
|
26
|
+
/** Extra, non-WordPress sitemaps to fold into the index and serve. */
|
|
27
|
+
extra?: SitemapRouteExtra[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds a Web `Request` -> `Response` handler that serves the sitemap index
|
|
31
|
+
* (`/sitemaps/index.xml`) and every collection page, merging any `extra` sitemaps in.
|
|
32
|
+
* Framework-agnostic: the slug is the last path segment of the request URL, so this runs
|
|
33
|
+
* as-is on any web-standard server (Hono, a plain fetch handler, a Next route handler).
|
|
34
|
+
* Mounting it at the right path is the caller's job; framework wrappers (e.g. the Next
|
|
35
|
+
* integration) add mount assertions on top.
|
|
36
|
+
*/
|
|
37
|
+
declare function createSitemapRoute(client: S2SClient<[]>, options?: CreateSitemapRouteOptions): (request: Request) => Promise<Response>;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { CreateSitemapRouteOptions, SITEMAP_BASE, SITEMAP_INDEX_SLUG, SitemapRouteExtra, createSitemapRoute };
|