wuppycms 0.0.0-stage → 0.2.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/admin-fo4l4lu2.mjs +59 -0
- package/dist/astro.d.mts +20 -0
- package/dist/astro.mjs +302 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +210 -0
- package/dist/components-BrGAEPly.mjs +39 -0
- package/dist/config-CM8_-x_W.mjs +1242 -0
- package/dist/dist-D7fysV_5.mjs +9048 -0
- package/dist/frontmatter-DGaz9--t.mjs +24 -0
- package/dist/generate-iX4B_2U2.mjs +473 -0
- package/dist/i18n-CgLAHd9Q.mjs +79 -0
- package/dist/index-B6AHmgP9.d.mts +34 -0
- package/dist/index.d.mts +8636 -0
- package/dist/index.mjs +83 -0
- package/dist/live-runtime.d.mts +120 -0
- package/dist/live-runtime.mjs +207 -0
- package/dist/markdown.mjs +4 -0
- package/dist/mdx.d.mts +68 -0
- package/dist/mdx.mjs +4 -0
- package/dist/media-DGfJXgBA.mjs +356 -0
- package/dist/page-cache-middleware.d.mts +8 -0
- package/dist/page-cache-middleware.mjs +101 -0
- package/dist/publishing.d.mts +17 -0
- package/dist/publishing.mjs +42 -0
- package/dist/read-Bh4-a3Ap.mjs +18 -0
- package/dist/redirect-middleware.d.mts +6 -0
- package/dist/redirect-middleware.mjs +37 -0
- package/dist/render-DzH7Wil3.mjs +116 -0
- package/dist/routes/admin.d.mts +7 -0
- package/dist/routes/admin.mjs +10 -0
- package/dist/routes/api.d.mts +7 -0
- package/dist/routes/api.mjs +97 -0
- package/dist/routes/icons.d.mts +12 -0
- package/dist/routes/icons.mjs +36 -0
- package/dist/routes/robots.d.mts +7 -0
- package/dist/routes/robots.mjs +15 -0
- package/dist/routes/rss.d.mts +7 -0
- package/dist/routes/rss.mjs +20 -0
- package/dist/routes/sitemap.d.mts +7 -0
- package/dist/routes/sitemap.mjs +23 -0
- package/dist/routes/uploads.d.mts +7 -0
- package/dist/routes/uploads.mjs +11 -0
- package/dist/schemas-dHRJ1HeG.mjs +275 -0
- package/dist/seo.d.mts +125 -0
- package/dist/seo.mjs +130 -0
- package/dist/server-D92ryW2y.mjs +5883 -0
- package/dist/service-CgcvCG7l.mjs +353 -0
- package/dist/site-dF-xVPqe.mjs +148 -0
- package/dist/site.d.mts +28 -0
- package/dist/site.mjs +44 -0
- package/dist/sitemap-Ba0vil6a.mjs +46 -0
- package/dist/theme-pages-CRsBm1EX.mjs +246 -0
- package/dist/theme-seo-middleware.d.mts +7 -0
- package/dist/theme-seo-middleware.mjs +62 -0
- package/dist/visual-middleware.d.mts +7 -0
- package/dist/visual-middleware.mjs +71 -0
- package/package.json +72 -3
- 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,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,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,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,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,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,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 };
|