wuppycms 0.0.0-stage → 0.2.1

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.
Files changed (58) hide show
  1. package/dist/admin-fo4l4lu2.mjs +59 -0
  2. package/dist/astro.d.mts +20 -0
  3. package/dist/astro.mjs +302 -0
  4. package/dist/cli.d.mts +1 -0
  5. package/dist/cli.mjs +210 -0
  6. package/dist/components-BrGAEPly.mjs +39 -0
  7. package/dist/config-CM8_-x_W.mjs +1242 -0
  8. package/dist/dist-D7fysV_5.mjs +9048 -0
  9. package/dist/frontmatter-DGaz9--t.mjs +24 -0
  10. package/dist/generate-iX4B_2U2.mjs +473 -0
  11. package/dist/i18n-CgLAHd9Q.mjs +79 -0
  12. package/dist/index-B6AHmgP9.d.mts +34 -0
  13. package/dist/index.d.mts +8636 -0
  14. package/dist/index.mjs +83 -0
  15. package/dist/live-runtime.d.mts +120 -0
  16. package/dist/live-runtime.mjs +207 -0
  17. package/dist/markdown.mjs +4 -0
  18. package/dist/mdx.d.mts +68 -0
  19. package/dist/mdx.mjs +4 -0
  20. package/dist/media-DGfJXgBA.mjs +356 -0
  21. package/dist/page-cache-middleware.d.mts +8 -0
  22. package/dist/page-cache-middleware.mjs +101 -0
  23. package/dist/publishing.d.mts +17 -0
  24. package/dist/publishing.mjs +42 -0
  25. package/dist/read-Bh4-a3Ap.mjs +18 -0
  26. package/dist/redirect-middleware.d.mts +6 -0
  27. package/dist/redirect-middleware.mjs +37 -0
  28. package/dist/render-DzH7Wil3.mjs +116 -0
  29. package/dist/routes/admin.d.mts +7 -0
  30. package/dist/routes/admin.mjs +10 -0
  31. package/dist/routes/api.d.mts +7 -0
  32. package/dist/routes/api.mjs +97 -0
  33. package/dist/routes/icons.d.mts +12 -0
  34. package/dist/routes/icons.mjs +36 -0
  35. package/dist/routes/robots.d.mts +7 -0
  36. package/dist/routes/robots.mjs +15 -0
  37. package/dist/routes/rss.d.mts +7 -0
  38. package/dist/routes/rss.mjs +20 -0
  39. package/dist/routes/sitemap.d.mts +7 -0
  40. package/dist/routes/sitemap.mjs +23 -0
  41. package/dist/routes/uploads.d.mts +7 -0
  42. package/dist/routes/uploads.mjs +11 -0
  43. package/dist/schemas-dHRJ1HeG.mjs +275 -0
  44. package/dist/seo.d.mts +125 -0
  45. package/dist/seo.mjs +130 -0
  46. package/dist/server-D92ryW2y.mjs +5883 -0
  47. package/dist/service-CgcvCG7l.mjs +353 -0
  48. package/dist/site-dF-xVPqe.mjs +148 -0
  49. package/dist/site.d.mts +28 -0
  50. package/dist/site.mjs +44 -0
  51. package/dist/sitemap-Ba0vil6a.mjs +46 -0
  52. package/dist/theme-pages-CRsBm1EX.mjs +246 -0
  53. package/dist/theme-seo-middleware.d.mts +7 -0
  54. package/dist/theme-seo-middleware.mjs +62 -0
  55. package/dist/visual-middleware.d.mts +7 -0
  56. package/dist/visual-middleware.mjs +71 -0
  57. package/package.json +72 -3
  58. package/README.md +0 -3
@@ -0,0 +1,116 @@
1
+ import { S as stripComponentImports, b as findDisallowed, n as loadConfig, x as loadSatteri } from "./config-CM8_-x_W.mjs";
2
+ import { n as themeComponentNames } from "./components-BrGAEPly.mjs";
3
+ import { createRequire } from "node:module";
4
+ import { join, resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { Fragment, jsx } from "astro/jsx-runtime";
7
+ import { Renderer } from "astro/runtime/server/index.js";
8
+
9
+ //#region src/markdown/render.ts
10
+ const DEFAULTS = { shared: { syntaxHighlight: {
11
+ type: "shiki",
12
+ excludeLangs: ["math"]
13
+ } } };
14
+ const MDX_DEFAULTS = { shared: { syntaxHighlight: {
15
+ type: "shiki",
16
+ excludeLangs: ["math"]
17
+ } } };
18
+ const renderers = /* @__PURE__ */ new Map();
19
+ /**
20
+ * Astro's own Markdown renderer (heading ids, code highlighting, GFM), loaded from Astro's dependency at runtime: it
21
+ * has a native binding, which cannot be bundled.
22
+ */
23
+ function markdownRenderer(root = process.cwd(), settings = DEFAULTS) {
24
+ const key = JSON.stringify([root, settings]);
25
+ let renderer = renderers.get(key);
26
+ if (!renderer) {
27
+ renderer = (async () => {
28
+ const { satteri } = await import(pathToFileURL(createRequire(createRequire(join(root, "package.json")).resolve("astro/package.json")).resolve("@astrojs/markdown-satteri")).href);
29
+ return satteri({ features: settings.features }).createRenderer(settings.shared ?? {});
30
+ })();
31
+ renderers.set(key, renderer);
32
+ }
33
+ return renderer;
34
+ }
35
+ /**
36
+ * Markdown stored outside content collections (a section body, a custom widget) as HTML, the way Astro renders an
37
+ * `.md` entry: `const { html } = await markdownContent(section.body)` then `<Fragment set:html={html} />`. The text is
38
+ * checked first with the same rules as the admin, so a body changed outside the admin cannot inject scripts either.
39
+ */
40
+ async function markdownContent(source, options = {}) {
41
+ const found = await findDisallowed(source, [], "md");
42
+ if (found.length > 0) throw new Error(`[wuppy] Markdown not allowed: ${found.join(", ")}`);
43
+ const result = await (await markdownRenderer(options.root, options.markdown)).render(source, { frontmatter: {} });
44
+ return {
45
+ html: result.code,
46
+ headings: result.metadata.headings ?? []
47
+ };
48
+ }
49
+ let plugins;
50
+ const highlighters = /* @__PURE__ */ new Map();
51
+ /** Astro's own Markdown plugins (heading ids, code highlighting), loaded like satteri: from Astro's dependency. */
52
+ function astroPlugins(root) {
53
+ plugins ??= (async () => {
54
+ return await import(pathToFileURL(createRequire(createRequire(join(root, "package.json")).resolve("astro/package.json")).resolve("@astrojs/markdown-satteri")).href);
55
+ })();
56
+ return plugins;
57
+ }
58
+ /**
59
+ * MDX text stored at runtime (a section body, an entry edited since the last build) compiled to what Astro's `render()`
60
+ * gives: `const { Content, headings } = await mdxContent(section.body)` then `<Content components={{ Callout }} />`.
61
+ * The text is checked first with the same rules as the admin (no imports, exports or expressions; only the `components`
62
+ * listed in wuppy.config), so a body changed outside the admin cannot run code either. Headings get Astro's ids and
63
+ * code blocks its highlighting. Needs `@astrojs/mdx` in the site, which renders the result.
64
+ */
65
+ async function mdxContent(source, options = {}) {
66
+ const root = options.root ?? process.cwd();
67
+ const site = process.env.WUPPY_BASE_ROOT || root;
68
+ const themeMode = process.env.WUPPY_BASE_ROOT && resolve(site) !== resolve(root);
69
+ source = stripComponentImports(source);
70
+ const allowed = options.allowed ?? [...new Set([...(await loadConfig(site)).components, ...themeMode ? await themeComponentNames(root) : []])];
71
+ const found = await findDisallowed(source, allowed, "mdx");
72
+ if (found.length > 0) throw new Error(`[wuppy] MDX not allowed: ${found.join(", ")}`);
73
+ const [satteri, astro] = await Promise.all([loadSatteri(root), astroPlugins(root)]);
74
+ const { features = {}, shared = {} } = options.markdown ?? MDX_DEFAULTS;
75
+ const hastPlugins = [];
76
+ if (shared.syntaxHighlight) {
77
+ const key = JSON.stringify([shared.syntaxHighlight, shared.shikiConfig]);
78
+ if (!highlighters.has(key)) highlighters.set(key, astro.satteriCreateHighlightFn(shared.syntaxHighlight, shared.shikiConfig ?? {}));
79
+ const exclude = shared.syntaxHighlight.excludeLangs;
80
+ hastPlugins.push(astro.satteriHighlightPlugin(await highlighters.get(key), exclude));
81
+ }
82
+ hastPlugins.push(astro.satteriHeadingIdsPlugin());
83
+ const data = { astro: {
84
+ frontmatter: {},
85
+ headings: []
86
+ } };
87
+ const MDXContent = (await satteri.evaluate(source, {
88
+ Fragment,
89
+ jsx,
90
+ jsxs: jsx,
91
+ elementAttributeNameCase: "html",
92
+ hastPlugins,
93
+ data,
94
+ features: {
95
+ ...features,
96
+ gfm: shared.gfm !== false,
97
+ smartPunctuation: shared.smartypants !== false
98
+ }
99
+ })).default;
100
+ const Content = (props = {}) => MDXContent({
101
+ ...props,
102
+ components: {
103
+ Fragment,
104
+ ...props.components
105
+ }
106
+ });
107
+ Object.defineProperty(Content, Symbol.for("mdx-component"), { value: true });
108
+ Object.defineProperty(Content, Renderer, { value: "astro:jsx" });
109
+ return {
110
+ Content,
111
+ headings: data.astro.headings
112
+ };
113
+ }
114
+
115
+ //#endregion
116
+ export { markdownRenderer as n, mdxContent as r, markdownContent as t };
@@ -0,0 +1,7 @@
1
+ import { APIRoute } from "astro";
2
+
3
+ //#region src/astro/routes/admin.d.ts
4
+ declare const prerender = false;
5
+ declare const GET: APIRoute;
6
+ //#endregion
7
+ export { GET, prerender };
@@ -0,0 +1,10 @@
1
+ import { n as serveAdmin, t as adminDist } from "../admin-fo4l4lu2.mjs";
2
+ import { createRequire } from "node:module";
3
+
4
+ //#region src/astro/routes/admin.ts
5
+ const prerender = false;
6
+ const dist = adminDist(createRequire(import.meta.url).resolve);
7
+ const GET = ({ params }) => serveAdmin(dist, params.path ?? "");
8
+
9
+ //#endregion
10
+ export { GET, prerender };
@@ -0,0 +1,7 @@
1
+ import { APIRoute } from "astro";
2
+
3
+ //#region src/astro/routes/api.d.ts
4
+ declare const prerender = false;
5
+ declare const ALL: APIRoute;
6
+ //#endregion
7
+ export { ALL, prerender };
@@ -0,0 +1,97 @@
1
+ import "../config-CM8_-x_W.mjs";
2
+ import "../frontmatter-DGaz9--t.mjs";
3
+ import "../service-CgcvCG7l.mjs";
4
+ import { t as createServer } from "../server-D92ryW2y.mjs";
5
+ import "../generate-iX4B_2U2.mjs";
6
+ import "../theme-pages-CRsBm1EX.mjs";
7
+ import { l as themeRoot, s as siteRoot } from "../site-dF-xVPqe.mjs";
8
+ import "../schemas-dHRJ1HeG.mjs";
9
+ import "../render-DzH7Wil3.mjs";
10
+ import "../media-DGfJXgBA.mjs";
11
+ import { BlockList, isIP } from "node:net";
12
+
13
+ //#region src/config/ip.ts
14
+ const familyOf = (ip) => {
15
+ const version = isIP(ip);
16
+ return version === 4 ? "ipv4" : version === 6 ? "ipv6" : null;
17
+ };
18
+ /** Builds a matcher for exact proxy IPs and CIDR ranges; malformed entries are ignored. */
19
+ function proxyList(entries) {
20
+ const list = new BlockList();
21
+ let added = 0;
22
+ for (const entry of entries) {
23
+ const trimmed = entry.trim();
24
+ if (!trimmed) continue;
25
+ const slash = trimmed.lastIndexOf("/");
26
+ const address = slash === -1 ? trimmed : trimmed.slice(0, slash);
27
+ const family = familyOf(address);
28
+ if (!family) continue;
29
+ if (slash === -1) {
30
+ list.addAddress(address, family);
31
+ added++;
32
+ continue;
33
+ }
34
+ const prefix = Number(trimmed.slice(slash + 1));
35
+ const max = family === "ipv4" ? 32 : 128;
36
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > max) continue;
37
+ list.addSubnet(address, prefix, family);
38
+ added++;
39
+ }
40
+ return added > 0 ? list : null;
41
+ }
42
+ /**
43
+ * Resolves the client IP without trusting a client-supplied forwarded header.
44
+ * The immediate peer is used unless it is a configured proxy; only then is the
45
+ * forwarded chain walked right-to-left to the first untrusted hop.
46
+ */
47
+ function resolveClientIP({ peer, forwarded, trustedProxies = [] }) {
48
+ if (!peer) return null;
49
+ const peerFamily = familyOf(peer);
50
+ if (!peerFamily) return null;
51
+ const proxies = proxyList(trustedProxies);
52
+ if (!proxies || !proxies.check(peer, peerFamily)) return peer;
53
+ const chain = (forwarded ?? "").split(",").map((value) => value.trim()).filter(Boolean);
54
+ for (let i = chain.length - 1; i >= 0; i--) {
55
+ const candidate = chain[i];
56
+ if (!candidate) continue;
57
+ const family = familyOf(candidate);
58
+ if (!family) return peer;
59
+ if (proxies.check(candidate, family)) continue;
60
+ return candidate;
61
+ }
62
+ return peer;
63
+ }
64
+ /** Comma-separated `WUPPY_TRUSTED_PROXIES` (exact IPs or CIDR ranges). */
65
+ function trustedProxiesFromEnv(env = process.env) {
66
+ return (env.WUPPY_TRUSTED_PROXIES ?? "").split(",").map((value) => value.trim()).filter(Boolean);
67
+ }
68
+
69
+ //#endregion
70
+ //#region src/astro/routes/api.ts
71
+ const prerender = false;
72
+ let server;
73
+ const trustedProxies = trustedProxiesFromEnv();
74
+ const ALL = async ({ request, clientAddress }) => {
75
+ const headers = new Headers(request.headers);
76
+ const clientIP = resolveClientIP({
77
+ peer: clientAddress,
78
+ forwarded: request.headers.get("x-forwarded-for"),
79
+ trustedProxies
80
+ });
81
+ if (clientIP) headers.set("x-wuppy-client-ip", clientIP);
82
+ else headers.delete("x-wuppy-client-ip");
83
+ const forwardedRequest = new Request(request, {
84
+ headers,
85
+ duplex: "half"
86
+ });
87
+ server ??= createServer({
88
+ root: siteRoot(),
89
+ theme: themeRoot(),
90
+ secret: process.env.WUPPY_SECRET ?? import.meta.env.WUPPY_SECRET ?? "",
91
+ baseURL: process.env.WUPPY_URL ?? import.meta.env.WUPPY_URL ?? new URL(request.url).origin
92
+ });
93
+ return (await server).fetch(forwardedRequest);
94
+ };
95
+
96
+ //#endregion
97
+ export { ALL, prerender };
@@ -0,0 +1,12 @@
1
+ import { APIRoute } from "astro";
2
+
3
+ //#region src/astro/routes/icons.d.ts
4
+ declare const prerender = false;
5
+ /**
6
+ * Production SVG sprite generated by `zero-icons` in the admin package. The
7
+ * URL is stable while builds change its symbols, so it must revalidate; an
8
+ * immutable cache here hides newly added icons from returning browsers.
9
+ */
10
+ declare const GET: APIRoute;
11
+ //#endregion
12
+ export { GET, prerender };
@@ -0,0 +1,36 @@
1
+ import { t as adminDist } from "../admin-fo4l4lu2.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { readFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { createHash } from "node:crypto";
6
+
7
+ //#region src/astro/routes/icons.ts
8
+ const prerender = false;
9
+ const dist = adminDist(createRequire(import.meta.url).resolve);
10
+ /**
11
+ * Production SVG sprite generated by `zero-icons` in the admin package. The
12
+ * URL is stable while builds change its symbols, so it must revalidate; an
13
+ * immutable cache here hides newly added icons from returning browsers.
14
+ */
15
+ const GET = async ({ request }) => {
16
+ try {
17
+ const body = await readFile(join(dist, "icons.svg"));
18
+ const etag = `"${createHash("sha256").update(body).digest("base64url").slice(0, 16)}"`;
19
+ const headers = {
20
+ "content-type": "image/svg+xml",
21
+ "x-content-type-options": "nosniff",
22
+ "cache-control": "no-cache",
23
+ etag
24
+ };
25
+ if (request.headers.get("if-none-match") === etag) return new Response(null, {
26
+ status: 304,
27
+ headers
28
+ });
29
+ return new Response(body, { headers });
30
+ } catch {
31
+ return new Response("Not found", { status: 404 });
32
+ }
33
+ };
34
+
35
+ //#endregion
36
+ export { GET, prerender };
@@ -0,0 +1,7 @@
1
+ import { APIRoute } from "astro";
2
+
3
+ //#region src/astro/routes/robots.d.ts
4
+ declare const prerender = false;
5
+ declare const GET: APIRoute;
6
+ //#endregion
7
+ export { GET, prerender };
@@ -0,0 +1,15 @@
1
+ import { n as loadConfig } from "../config-CM8_-x_W.mjs";
2
+ import "../frontmatter-DGaz9--t.mjs";
3
+ import { s as siteRoot } from "../site-dF-xVPqe.mjs";
4
+ import { buildRobots } from "../seo.mjs";
5
+
6
+ //#region src/astro/routes/robots.ts
7
+ const prerender = false;
8
+ const GET = async () => {
9
+ const { site } = await loadConfig(siteRoot());
10
+ if (!site) return new Response("Set `site` in wuppy.config to enable robots.txt", { status: 404 });
11
+ return new Response(buildRobots(site.url), { headers: { "content-type": "text/plain; charset=utf-8" } });
12
+ };
13
+
14
+ //#endregion
15
+ export { GET, prerender };
@@ -0,0 +1,7 @@
1
+ import { APIRoute } from "astro";
2
+
3
+ //#region src/astro/routes/rss.d.ts
4
+ declare const prerender = false;
5
+ declare const GET: APIRoute;
6
+ //#endregion
7
+ export { GET, prerender };
@@ -0,0 +1,20 @@
1
+ import "../config-CM8_-x_W.mjs";
2
+ import "../frontmatter-DGaz9--t.mjs";
3
+ import { n as ContentService } from "../service-CgcvCG7l.mjs";
4
+ import { i as loadSiteConfig, l as themeRoot, s as siteRoot } from "../site-dF-xVPqe.mjs";
5
+ import { buildRss } from "../seo.mjs";
6
+ import { t as contentFeed } from "../sitemap-Ba0vil6a.mjs";
7
+ import { NoopGit } from "@wuppycms/git";
8
+
9
+ //#region src/astro/routes/rss.ts
10
+ const prerender = false;
11
+ const GET = async () => {
12
+ const root = siteRoot();
13
+ const config = await loadSiteConfig(root, themeRoot());
14
+ if (!config.site) return new Response("Set `site` in wuppy.config to enable the RSS feed", { status: 404 });
15
+ const items = await contentFeed(new ContentService(root, config.collections, new NoopGit()), config.site.url);
16
+ return new Response(buildRss(config.site, items), { headers: { "content-type": "application/rss+xml; charset=utf-8" } });
17
+ };
18
+
19
+ //#endregion
20
+ export { GET, prerender };
@@ -0,0 +1,7 @@
1
+ import { APIRoute } from "astro";
2
+
3
+ //#region src/astro/routes/sitemap.d.ts
4
+ declare const prerender = false;
5
+ declare const GET: APIRoute;
6
+ //#endregion
7
+ export { GET, prerender };
@@ -0,0 +1,23 @@
1
+ import "../config-CM8_-x_W.mjs";
2
+ import "../frontmatter-DGaz9--t.mjs";
3
+ import { n as ContentService } from "../service-CgcvCG7l.mjs";
4
+ import { i as loadSiteConfig, l as themeRoot, s as siteRoot } from "../site-dF-xVPqe.mjs";
5
+ import { i as settingsSchema } from "../schemas-dHRJ1HeG.mjs";
6
+ import { buildSitemap } from "../seo.mjs";
7
+ import { t as readData } from "../read-Bh4-a3Ap.mjs";
8
+ import { n as contentUrls } from "../sitemap-Ba0vil6a.mjs";
9
+ import { NoopGit } from "@wuppycms/git";
10
+
11
+ //#region src/astro/routes/sitemap.ts
12
+ const prerender = false;
13
+ const GET = async () => {
14
+ const root = siteRoot();
15
+ const config = await loadSiteConfig(root, themeRoot());
16
+ if (!config.site) return new Response("Set `site` in wuppy.config to enable the sitemap", { status: 404 });
17
+ const { seo } = settingsSchema.parse(await readData("settings", {}, root) ?? {});
18
+ const urls = await contentUrls(new ContentService(root, config.collections, new NoopGit()), config.site.url, seo.sitemap);
19
+ return new Response(buildSitemap([{ loc: new URL("/", config.site.url).href }, ...urls]), { headers: { "content-type": "application/xml; charset=utf-8" } });
20
+ };
21
+
22
+ //#endregion
23
+ export { GET, prerender };
@@ -0,0 +1,7 @@
1
+ import { APIRoute } from "astro";
2
+
3
+ //#region src/astro/routes/uploads.d.ts
4
+ declare const prerender = false;
5
+ declare const GET: APIRoute;
6
+ //#endregion
7
+ export { GET, prerender };
@@ -0,0 +1,11 @@
1
+ import "../config-CM8_-x_W.mjs";
2
+ import "../frontmatter-DGaz9--t.mjs";
3
+ import { s as siteRoot } from "../site-dF-xVPqe.mjs";
4
+ import { o as serveUpload } from "../media-DGfJXgBA.mjs";
5
+
6
+ //#region src/astro/routes/uploads.ts
7
+ const prerender = false;
8
+ const GET = ({ params, url }) => serveUpload(siteRoot(), params.path ?? "", url.searchParams);
9
+
10
+ //#endregion
11
+ export { GET, prerender };
@@ -0,0 +1,275 @@
1
+ import { C as collectionSchema, p as webhooksSchema } from "./config-CM8_-x_W.mjs";
2
+ import { z } from "zod";
3
+ import { defaultVisualData, visualDataSchema } from "@wuppycms/visual-editor";
4
+
5
+ //#region src/data/schemas.ts
6
+ const id = z.string().regex(/^[a-z0-9][a-z0-9-]*$/);
7
+ const href = z.string().min(1).refine((v) => v.startsWith("/") && !v.startsWith("//") || /^https?:\/\//i.test(v), "Expected a path or http(s) URL");
8
+ const menuSchema = z.object({
9
+ name: id,
10
+ label: z.string().min(1),
11
+ location: z.string().default("header"),
12
+ locale: z.string().optional(),
13
+ items: z.array(z.object({
14
+ label: z.string().min(1),
15
+ href,
16
+ kind: z.string().max(40).optional(),
17
+ children: z.array(z.object({
18
+ label: z.string().min(1),
19
+ href,
20
+ kind: z.string().max(40).optional()
21
+ })).default([])
22
+ })).default([])
23
+ });
24
+ const widgetSchema = z.object({
25
+ id: z.string(),
26
+ type: z.enum([
27
+ "text",
28
+ "html",
29
+ "menu",
30
+ "recent",
31
+ "taxonomy",
32
+ "custom"
33
+ ]),
34
+ title: z.string().default(""),
35
+ body: z.string().default(""),
36
+ menu: z.string().optional(),
37
+ taxonomy: z.string().optional(),
38
+ locales: z.array(z.string()).default([]),
39
+ collections: z.array(z.string()).default([]),
40
+ paths: z.array(z.string()).default([])
41
+ });
42
+ const widgetAreaSchema = z.object({
43
+ name: id,
44
+ label: z.string().min(1),
45
+ description: z.string().default(""),
46
+ widgets: z.array(widgetSchema).default([])
47
+ });
48
+ const sectionSchema = z.object({
49
+ slug: id,
50
+ title: z.string().min(1),
51
+ body: z.string().default(""),
52
+ draft: z.boolean().default(false),
53
+ locale: z.string().optional()
54
+ });
55
+ const taxonomyTermSchema = z.object({
56
+ slug: id,
57
+ name: z.string().min(1),
58
+ description: z.string().default(""),
59
+ parent: z.string().optional(),
60
+ order: z.number().int().default(0),
61
+ translations: z.record(z.string(), z.object({
62
+ name: z.string().min(1),
63
+ description: z.string().default("")
64
+ })).default({})
65
+ });
66
+ const taxonomySchema = z.object({
67
+ name: id,
68
+ label: z.string().min(1),
69
+ hierarchical: z.boolean().default(false),
70
+ collections: z.array(z.string()).default([]),
71
+ terms: z.array(taxonomyTermSchema).default([])
72
+ }).superRefine((t, ctx) => {
73
+ const slugs = /* @__PURE__ */ new Set();
74
+ for (const term of t.terms) {
75
+ if (slugs.has(term.slug)) ctx.addIssue({
76
+ code: "custom",
77
+ message: `Duplicate term: ${term.slug}`
78
+ });
79
+ slugs.add(term.slug);
80
+ }
81
+ for (const term of t.terms) {
82
+ if (!term.parent) continue;
83
+ if (!t.hierarchical) ctx.addIssue({
84
+ code: "custom",
85
+ message: `${term.slug}: this taxonomy is not hierarchical`
86
+ });
87
+ else if (!slugs.has(term.parent)) ctx.addIssue({
88
+ code: "custom",
89
+ message: `${term.slug}: unknown parent ${term.parent}`
90
+ });
91
+ }
92
+ const parentOf = new Map(t.terms.map((x) => [x.slug, x.parent]));
93
+ for (const term of t.terms) {
94
+ const seen = new Set([term.slug]);
95
+ for (let at = term.parent; at; at = parentOf.get(at)) {
96
+ if (seen.has(at)) {
97
+ ctx.addIssue({
98
+ code: "custom",
99
+ message: `Parent loop through ${term.slug}`
100
+ });
101
+ break;
102
+ }
103
+ seen.add(at);
104
+ }
105
+ }
106
+ });
107
+ const bylineSchema = z.object({
108
+ slug: id,
109
+ name: z.string().min(1),
110
+ bio: z.string().default(""),
111
+ avatar: z.string().default(""),
112
+ email: z.string().email().optional().or(z.literal("")),
113
+ website: z.string().url().optional().or(z.literal("")),
114
+ credits: z.array(z.string()).default([]),
115
+ extra: z.record(z.string(), z.string()).default({}),
116
+ translations: z.record(z.string(), z.object({
117
+ name: z.string().optional(),
118
+ bio: z.string().optional()
119
+ })).default({})
120
+ });
121
+ const bylineFieldSchema = z.object({
122
+ name: id,
123
+ label: z.string().min(1),
124
+ type: z.enum([
125
+ "string",
126
+ "text",
127
+ "image",
128
+ "url"
129
+ ]).default("string"),
130
+ required: z.boolean().default(false),
131
+ order: z.number().int().default(0)
132
+ });
133
+ const settingsSchema = z.object({
134
+ siteName: z.string().default("Wuppy"),
135
+ siteUrl: z.string().default(""),
136
+ description: z.string().default(""),
137
+ logo: z.string().default(""),
138
+ locale: z.string().default("en"),
139
+ timezone: z.string().default("UTC"),
140
+ seo: z.object({
141
+ title: z.string().default(""),
142
+ description: z.string().default(""),
143
+ image: z.string().default(""),
144
+ noindex: z.boolean().default(false),
145
+ titleTemplate: z.string().default("{title} | {site}"),
146
+ sitemap: z.object({
147
+ exclude: z.array(z.string()).default([]),
148
+ priority: z.record(z.string(), z.number().min(0).max(1)).default({})
149
+ }).default({
150
+ exclude: [],
151
+ priority: {}
152
+ })
153
+ }).default({
154
+ title: "",
155
+ description: "",
156
+ image: "",
157
+ noindex: false,
158
+ titleTemplate: "{title} | {site}",
159
+ sitemap: {
160
+ exclude: [],
161
+ priority: {}
162
+ }
163
+ }),
164
+ social: z.record(z.string(), z.string()).default({}),
165
+ email: z.object({
166
+ from: z.string().default(""),
167
+ provider: z.enum([
168
+ "console",
169
+ "smtp",
170
+ "resend"
171
+ ]).default("console"),
172
+ host: z.string().default(""),
173
+ port: z.number().default(587),
174
+ user: z.string().default(""),
175
+ templates: z.partialRecord(z.enum([
176
+ "invite",
177
+ "reset",
178
+ "magic"
179
+ ]), z.object({
180
+ subject: z.string().min(1).max(200),
181
+ text: z.string().min(1).max(5e3)
182
+ }).partial()).default({})
183
+ }).default({
184
+ from: "",
185
+ provider: "console",
186
+ host: "",
187
+ port: 587,
188
+ user: "",
189
+ templates: {}
190
+ }),
191
+ media: z.object({
192
+ maxBytes: z.number().default(5 * 1024 * 1024),
193
+ allowed: z.array(z.string()).default([
194
+ "image/png",
195
+ "image/jpeg",
196
+ "image/gif",
197
+ "image/webp"
198
+ ])
199
+ }).default({
200
+ maxBytes: 5 * 1024 * 1024,
201
+ allowed: [
202
+ "image/png",
203
+ "image/jpeg",
204
+ "image/gif",
205
+ "image/webp"
206
+ ]
207
+ }),
208
+ comments: z.object({
209
+ enabled: z.boolean().default(true),
210
+ moderated: z.boolean().default(true),
211
+ notifyEmail: z.union([z.email(), z.literal("")]).default("")
212
+ }).default({
213
+ enabled: true,
214
+ moderated: true,
215
+ notifyEmail: ""
216
+ }),
217
+ security: z.object({ allowedDomains: z.array(z.string().regex(/^[a-z0-9.-]+\.[a-z]{2,}$/i)).default([]) }).default({ allowedDomains: [] }),
218
+ backup: z.object({
219
+ enabled: z.boolean().default(false),
220
+ intervalHours: z.number().int().min(1).max(720).default(24),
221
+ keep: z.number().int().min(1).max(365).default(14)
222
+ }).default({
223
+ enabled: false,
224
+ intervalHours: 24,
225
+ keep: 14
226
+ })
227
+ });
228
+ const themeSchema = z.object({
229
+ id,
230
+ name: z.string().min(1),
231
+ version: z.string().default("0.0.0"),
232
+ active: z.boolean().default(false)
233
+ });
234
+ const dataSchemas = {
235
+ settings: settingsSchema,
236
+ menus: z.array(menuSchema),
237
+ widgets: z.array(widgetAreaSchema),
238
+ sections: z.array(sectionSchema),
239
+ taxonomies: z.array(taxonomySchema),
240
+ bylines: z.array(bylineSchema),
241
+ "byline-schema": z.array(bylineFieldSchema),
242
+ themes: z.array(themeSchema),
243
+ "content-types": z.array(collectionSchema),
244
+ webhooks: webhooksSchema,
245
+ visual: visualDataSchema
246
+ };
247
+ const dataNames = Object.keys(dataSchemas);
248
+ const dataDefaults = {
249
+ settings: settingsSchema.parse({}),
250
+ menus: [],
251
+ widgets: [],
252
+ sections: [],
253
+ taxonomies: [{
254
+ name: "categories",
255
+ label: "Categories",
256
+ hierarchical: true,
257
+ collections: [],
258
+ terms: []
259
+ }, {
260
+ name: "tags",
261
+ label: "Tags",
262
+ hierarchical: false,
263
+ collections: [],
264
+ terms: []
265
+ }],
266
+ bylines: [],
267
+ "byline-schema": [],
268
+ themes: [],
269
+ "content-types": [],
270
+ webhooks: [],
271
+ visual: defaultVisualData()
272
+ };
273
+
274
+ //#endregion
275
+ export { taxonomyTermSchema as a, settingsSchema as i, dataNames as n, dataSchemas as r, dataDefaults as t };