@mdsnai/sdk 0.1.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/cli/args.d.ts +8 -0
- package/dist/cli/args.js +63 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +19 -0
- package/dist/cli/commands/create.d.ts +2 -0
- package/dist/cli/commands/create.js +39 -0
- package/dist/cli/commands/dev.d.ts +10 -0
- package/dist/cli/commands/dev.js +13 -0
- package/dist/cli/commands/start.d.ts +9 -0
- package/dist/cli/commands/start.js +13 -0
- package/dist/cli/entry.d.ts +2 -0
- package/dist/cli/entry.js +8 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +58 -0
- package/dist/core/action/execution.d.ts +4 -0
- package/dist/core/action/execution.js +57 -0
- package/dist/core/action/index.d.ts +2 -0
- package/dist/core/action/index.js +7 -0
- package/dist/core/action/types.d.ts +19 -0
- package/dist/core/action/types.js +2 -0
- package/dist/core/document/frontmatter.d.ts +5 -0
- package/dist/core/document/frontmatter.js +41 -0
- package/dist/core/document/markdown.d.ts +5 -0
- package/dist/core/document/markdown.js +83 -0
- package/dist/core/document/page-definition.d.ts +2 -0
- package/dist/core/document/page-definition.js +24 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +5 -0
- package/dist/core/model/block.d.ts +30 -0
- package/dist/core/model/block.js +2 -0
- package/dist/core/model/document.d.ts +13 -0
- package/dist/core/model/document.js +2 -0
- package/dist/core/model/fragment.d.ts +4 -0
- package/dist/core/model/fragment.js +2 -0
- package/dist/core/model/index.d.ts +5 -0
- package/dist/core/model/index.js +2 -0
- package/dist/core/model/input.d.ts +11 -0
- package/dist/core/model/input.js +2 -0
- package/dist/core/model/schema.d.ts +4 -0
- package/dist/core/model/schema.js +2 -0
- package/dist/core/protocol/mdsn.d.ts +6 -0
- package/dist/core/protocol/mdsn.js +80 -0
- package/dist/core/protocol/statements.d.ts +12 -0
- package/dist/core/protocol/statements.js +140 -0
- package/dist/core/protocol/validation.d.ts +4 -0
- package/dist/core/protocol/validation.js +60 -0
- package/dist/framework/create-framework-app.d.ts +12 -0
- package/dist/framework/create-framework-app.js +11 -0
- package/dist/framework/hosted-app.d.ts +13 -0
- package/dist/framework/hosted-app.js +133 -0
- package/dist/framework/index.d.ts +4 -0
- package/dist/framework/index.js +7 -0
- package/dist/framework/site-app.d.ts +12 -0
- package/dist/framework/site-app.js +146 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +18 -0
- package/dist/server/action-host.d.ts +3 -0
- package/dist/server/action-host.js +8 -0
- package/dist/server/action-runtime.d.ts +8 -0
- package/dist/server/action-runtime.js +81 -0
- package/dist/server/action.d.ts +41 -0
- package/dist/server/action.js +97 -0
- package/dist/server/build.d.ts +10 -0
- package/dist/server/build.js +166 -0
- package/dist/server/config.d.ts +56 -0
- package/dist/server/config.js +42 -0
- package/dist/server/dev.d.ts +48 -0
- package/dist/server/dev.js +90 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.js +16 -0
- package/dist/server/init.d.ts +1 -0
- package/dist/server/init.js +176 -0
- package/dist/server/layout.d.ts +17 -0
- package/dist/server/layout.js +40 -0
- package/dist/server/markdown.d.ts +53 -0
- package/dist/server/markdown.js +76 -0
- package/dist/server/module-loader.d.ts +4 -0
- package/dist/server/module-loader.js +71 -0
- package/dist/server/negotiate.d.ts +3 -0
- package/dist/server/negotiate.js +55 -0
- package/dist/server/page-host.d.ts +21 -0
- package/dist/server/page-host.js +66 -0
- package/dist/server/page-links.d.ts +10 -0
- package/dist/server/page-links.js +80 -0
- package/dist/server/route-matcher.d.ts +6 -0
- package/dist/server/route-matcher.js +73 -0
- package/dist/server/routes.d.ts +6 -0
- package/dist/server/routes.js +73 -0
- package/dist/server/server.d.ts +27 -0
- package/dist/server/server.js +152 -0
- package/dist/server/site.d.ts +11 -0
- package/dist/server/site.js +59 -0
- package/dist/server/targets.d.ts +7 -0
- package/dist/server/targets.js +21 -0
- package/dist/web/block-runtime.d.ts +2 -0
- package/dist/web/block-runtime.js +27 -0
- package/dist/web/fragment-render.d.ts +10 -0
- package/dist/web/fragment-render.js +59 -0
- package/dist/web/headless.d.ts +95 -0
- package/dist/web/headless.js +370 -0
- package/dist/web/i18n.d.ts +31 -0
- package/dist/web/i18n.js +69 -0
- package/dist/web/index.d.ts +11 -0
- package/dist/web/index.js +22 -0
- package/dist/web/navigation.d.ts +3 -0
- package/dist/web/navigation.js +32 -0
- package/dist/web/page-bootstrap.d.ts +6 -0
- package/dist/web/page-bootstrap.js +29 -0
- package/dist/web/page-client-runtime.d.ts +15 -0
- package/dist/web/page-client-runtime.js +22 -0
- package/dist/web/page-client-script.d.ts +2 -0
- package/dist/web/page-client-script.js +567 -0
- package/dist/web/page-html.d.ts +8 -0
- package/dist/web/page-html.js +49 -0
- package/dist/web/page-render.d.ts +20 -0
- package/dist/web/page-render.js +92 -0
- package/dist/web/public-client-runtime.d.ts +1 -0
- package/dist/web/public-client-runtime.js +5 -0
- package/dist/web/public-render.d.ts +12 -0
- package/dist/web/public-render.js +18 -0
- package/dist/web/target-path.d.ts +1 -0
- package/dist/web/target-path.js +35 -0
- package/package.json +91 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wantsHtml = wantsHtml;
|
|
4
|
+
exports.wantsMarkdown = wantsMarkdown;
|
|
5
|
+
exports.isNotAcceptableRequest = isNotAcceptableRequest;
|
|
6
|
+
function parseAcceptHeader(acceptHeader) {
|
|
7
|
+
if (!acceptHeader || acceptHeader.trim().length === 0) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return acceptHeader
|
|
11
|
+
.split(",")
|
|
12
|
+
.map((raw) => raw.trim())
|
|
13
|
+
.filter((raw) => raw.length > 0)
|
|
14
|
+
.map((part) => {
|
|
15
|
+
const [mediaTypeToken, ...parameterTokens] = part.split(";");
|
|
16
|
+
const mediaType = mediaTypeToken.trim().toLowerCase();
|
|
17
|
+
const qualityToken = parameterTokens.find((parameter) => parameter.trim().toLowerCase().startsWith("q="));
|
|
18
|
+
const parsedQuality = qualityToken ? Number.parseFloat(qualityToken.split("=")[1] ?? "") : 1;
|
|
19
|
+
const quality = Number.isFinite(parsedQuality) ? Math.min(Math.max(parsedQuality, 0), 1) : 1;
|
|
20
|
+
return {
|
|
21
|
+
mediaType,
|
|
22
|
+
quality,
|
|
23
|
+
};
|
|
24
|
+
})
|
|
25
|
+
.filter((entry) => entry.mediaType.length > 0);
|
|
26
|
+
}
|
|
27
|
+
function hasAcceptableMediaType(acceptHeader, matcher) {
|
|
28
|
+
return parseAcceptHeader(acceptHeader).some((entry) => entry.quality > 0 && matcher(entry.mediaType));
|
|
29
|
+
}
|
|
30
|
+
function wantsHtml(acceptHeader) {
|
|
31
|
+
return hasAcceptableMediaType(acceptHeader, (mediaType) => mediaType === "text/html" || mediaType === "application/xhtml+xml");
|
|
32
|
+
}
|
|
33
|
+
function wantsMarkdown(acceptHeader) {
|
|
34
|
+
return hasAcceptableMediaType(acceptHeader, (mediaType) => mediaType === "text/markdown");
|
|
35
|
+
}
|
|
36
|
+
function supportsAnyRepresentation(mediaType) {
|
|
37
|
+
if (mediaType === "*/*")
|
|
38
|
+
return true;
|
|
39
|
+
if (mediaType === "text/*")
|
|
40
|
+
return true;
|
|
41
|
+
if (mediaType === "text/html")
|
|
42
|
+
return true;
|
|
43
|
+
if (mediaType === "application/xhtml+xml")
|
|
44
|
+
return true;
|
|
45
|
+
if (mediaType === "text/markdown")
|
|
46
|
+
return true;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
function isNotAcceptableRequest(acceptHeader) {
|
|
50
|
+
const entries = parseAcceptHeader(acceptHeader);
|
|
51
|
+
if (entries.length === 0) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return !entries.some((entry) => entry.quality > 0 && supportsAnyRepresentation(entry.mediaType));
|
|
55
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface HostedPageResponse {
|
|
2
|
+
status: number;
|
|
3
|
+
contentType: string;
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RenderHostedPageOptions {
|
|
7
|
+
accept?: string;
|
|
8
|
+
routePath?: string;
|
|
9
|
+
siteTitle?: string;
|
|
10
|
+
siteDescription?: string;
|
|
11
|
+
siteBaseUrl?: string;
|
|
12
|
+
locales?: string[];
|
|
13
|
+
defaultLocale?: string;
|
|
14
|
+
markdown?: {
|
|
15
|
+
linkify?: boolean;
|
|
16
|
+
typographer?: boolean;
|
|
17
|
+
};
|
|
18
|
+
mapActionTarget?: (target: string) => string;
|
|
19
|
+
layoutTemplate?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function renderHostedPage(rawPage: string, options: RenderHostedPageOptions): HostedPageResponse;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderHostedPage = renderHostedPage;
|
|
4
|
+
const page_definition_1 = require("../core/document/page-definition");
|
|
5
|
+
const page_render_1 = require("../web/page-render");
|
|
6
|
+
const page_html_1 = require("../web/page-html");
|
|
7
|
+
const layout_1 = require("./layout");
|
|
8
|
+
const page_links_1 = require("./page-links");
|
|
9
|
+
const negotiate_1 = require("./negotiate");
|
|
10
|
+
function renderHostedPage(rawPage, options) {
|
|
11
|
+
if ((0, negotiate_1.isNotAcceptableRequest)(options.accept)) {
|
|
12
|
+
return {
|
|
13
|
+
status: 406,
|
|
14
|
+
contentType: "text/plain; charset=utf-8",
|
|
15
|
+
body: "Not Acceptable",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (!(0, negotiate_1.wantsHtml)(options.accept)) {
|
|
19
|
+
return {
|
|
20
|
+
status: 200,
|
|
21
|
+
contentType: "text/markdown; charset=utf-8",
|
|
22
|
+
body: rawPage,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const document = (0, page_definition_1.parsePageDefinition)(rawPage);
|
|
26
|
+
const routePath = options.routePath ?? "/";
|
|
27
|
+
const model = (0, page_render_1.createPageRenderModel)(document, {
|
|
28
|
+
mapActionTarget: options.mapActionTarget,
|
|
29
|
+
markdown: options.markdown,
|
|
30
|
+
});
|
|
31
|
+
const locale = (0, layout_1.resolveLocaleForRoutePath)(routePath, options.locales ?? ["en"], options.defaultLocale ?? "en");
|
|
32
|
+
const title = typeof document.frontmatter.title === "string" && document.frontmatter.title.trim().length > 0
|
|
33
|
+
? document.frontmatter.title
|
|
34
|
+
: options.siteTitle;
|
|
35
|
+
const description = typeof document.frontmatter.description === "string" && document.frontmatter.description.trim().length > 0
|
|
36
|
+
? document.frontmatter.description
|
|
37
|
+
: options.siteDescription ?? "";
|
|
38
|
+
const defaultLocale = options.defaultLocale ?? locale;
|
|
39
|
+
const body = options.layoutTemplate
|
|
40
|
+
? (0, layout_1.applyLayoutTemplate)(options.layoutTemplate, {
|
|
41
|
+
title: title ?? "MDSN Page",
|
|
42
|
+
description,
|
|
43
|
+
content: (0, page_html_1.renderPageHtmlContent)(model),
|
|
44
|
+
locale,
|
|
45
|
+
defaultLocale,
|
|
46
|
+
pathname: routePath,
|
|
47
|
+
canonicalUrl: (0, page_links_1.resolveCanonicalUrl)(routePath, options.siteBaseUrl),
|
|
48
|
+
siteName: options.siteTitle,
|
|
49
|
+
markdownAlternateUrl: (0, page_links_1.resolveMarkdownAlternateUrl)(routePath, options.siteBaseUrl),
|
|
50
|
+
hreflangLinks: (0, page_links_1.resolveHreflangLinks)({
|
|
51
|
+
routePath,
|
|
52
|
+
locales: options.locales ?? ["en"],
|
|
53
|
+
defaultLocale,
|
|
54
|
+
siteBaseUrl: options.siteBaseUrl,
|
|
55
|
+
}),
|
|
56
|
+
})
|
|
57
|
+
: (0, page_html_1.renderPageHtmlDocument)(model, {
|
|
58
|
+
title,
|
|
59
|
+
lang: locale,
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
status: 200,
|
|
63
|
+
contentType: "text/html; charset=utf-8",
|
|
64
|
+
body,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function mapPageTargetToHttpPath(target: string): string;
|
|
2
|
+
export declare function joinUrl(baseUrl: string, routePath: string): string;
|
|
3
|
+
export declare function resolveCanonicalUrl(routePath: string, siteBaseUrl?: string): string | undefined;
|
|
4
|
+
export declare function resolveMarkdownAlternateUrl(routePath: string, siteBaseUrl?: string): string;
|
|
5
|
+
export declare function resolveHreflangLinks(options: {
|
|
6
|
+
routePath: string;
|
|
7
|
+
locales: string[];
|
|
8
|
+
defaultLocale: string;
|
|
9
|
+
siteBaseUrl?: string;
|
|
10
|
+
}): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapPageTargetToHttpPath = mapPageTargetToHttpPath;
|
|
4
|
+
exports.joinUrl = joinUrl;
|
|
5
|
+
exports.resolveCanonicalUrl = resolveCanonicalUrl;
|
|
6
|
+
exports.resolveMarkdownAlternateUrl = resolveMarkdownAlternateUrl;
|
|
7
|
+
exports.resolveHreflangLinks = resolveHreflangLinks;
|
|
8
|
+
const routes_1 = require("./routes");
|
|
9
|
+
function mapPageTargetToHttpPath(target) {
|
|
10
|
+
if (/^https?:\/\//i.test(target)) {
|
|
11
|
+
return target;
|
|
12
|
+
}
|
|
13
|
+
const markdownRoutePath = (0, routes_1.markdownPathToRoutePath)(target);
|
|
14
|
+
if (markdownRoutePath) {
|
|
15
|
+
return markdownRoutePath;
|
|
16
|
+
}
|
|
17
|
+
return target;
|
|
18
|
+
}
|
|
19
|
+
function joinUrl(baseUrl, routePath) {
|
|
20
|
+
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
21
|
+
const normalizedPath = routePath.startsWith("/") ? routePath : `/${routePath}`;
|
|
22
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
23
|
+
}
|
|
24
|
+
function resolveCanonicalUrl(routePath, siteBaseUrl) {
|
|
25
|
+
if (!siteBaseUrl) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return joinUrl(siteBaseUrl, routePath);
|
|
29
|
+
}
|
|
30
|
+
function resolveMarkdownAlternateUrl(routePath, siteBaseUrl) {
|
|
31
|
+
const markdownPath = (0, routes_1.routePathToMarkdownPath)(routePath);
|
|
32
|
+
if (!siteBaseUrl) {
|
|
33
|
+
return markdownPath;
|
|
34
|
+
}
|
|
35
|
+
return joinUrl(siteBaseUrl, markdownPath);
|
|
36
|
+
}
|
|
37
|
+
function normalizeRoutePath(routePath) {
|
|
38
|
+
if (!routePath || routePath === "/") {
|
|
39
|
+
return "/";
|
|
40
|
+
}
|
|
41
|
+
return routePath.replace(/\/+$/, "") || "/";
|
|
42
|
+
}
|
|
43
|
+
function resolveRouteSuffix(routePath, locales) {
|
|
44
|
+
const normalized = normalizeRoutePath(routePath);
|
|
45
|
+
if (normalized === "/")
|
|
46
|
+
return "/";
|
|
47
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
48
|
+
const first = segments[0];
|
|
49
|
+
if (first && locales.includes(first)) {
|
|
50
|
+
const suffix = `/${segments.slice(1).join("/")}`;
|
|
51
|
+
return suffix === "/" ? "/" : suffix.replace(/\/+$/, "") || "/";
|
|
52
|
+
}
|
|
53
|
+
return normalized;
|
|
54
|
+
}
|
|
55
|
+
function resolveLocalizedRoutePath(locale, suffix, defaultLocale) {
|
|
56
|
+
if (locale === defaultLocale) {
|
|
57
|
+
return suffix;
|
|
58
|
+
}
|
|
59
|
+
if (suffix === "/") {
|
|
60
|
+
return `/${locale}`;
|
|
61
|
+
}
|
|
62
|
+
return `/${locale}${suffix}`;
|
|
63
|
+
}
|
|
64
|
+
function resolveHreflangLinks(options) {
|
|
65
|
+
const suffix = resolveRouteSuffix(options.routePath, options.locales);
|
|
66
|
+
const links = [];
|
|
67
|
+
const toHref = (routePath) => {
|
|
68
|
+
if (!options.siteBaseUrl) {
|
|
69
|
+
return routePath;
|
|
70
|
+
}
|
|
71
|
+
return joinUrl(options.siteBaseUrl, routePath);
|
|
72
|
+
};
|
|
73
|
+
for (const locale of options.locales) {
|
|
74
|
+
const href = toHref(resolveLocalizedRoutePath(locale, suffix, options.defaultLocale));
|
|
75
|
+
links.push(`<link rel="alternate" hreflang="${locale}" href="${href}" />`);
|
|
76
|
+
}
|
|
77
|
+
const defaultHref = toHref(resolveLocalizedRoutePath(options.defaultLocale, suffix, options.defaultLocale));
|
|
78
|
+
links.push(`<link rel="alternate" hreflang="x-default" href="${defaultHref}" />`);
|
|
79
|
+
return links.join("\n ");
|
|
80
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type RoutablePage = {
|
|
2
|
+
routePath: string;
|
|
3
|
+
};
|
|
4
|
+
export declare function sortRoutedPagesForMatching<T extends RoutablePage>(routedPages: T[]): T[];
|
|
5
|
+
export declare function resolveRoutedPageForPath<T extends RoutablePage>(requestPath: string, routedPages: T[]): T | null;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sortRoutedPagesForMatching = sortRoutedPagesForMatching;
|
|
4
|
+
exports.resolveRoutedPageForPath = resolveRoutedPageForPath;
|
|
5
|
+
function normalizeRoutePath(pathname) {
|
|
6
|
+
if (!pathname)
|
|
7
|
+
return "/";
|
|
8
|
+
if (pathname === "/")
|
|
9
|
+
return pathname;
|
|
10
|
+
return pathname.replace(/\/+$/, "") || "/";
|
|
11
|
+
}
|
|
12
|
+
function isDynamicRouteSegment(segment) {
|
|
13
|
+
return /^\[[^\]/]+\]$/.test(segment);
|
|
14
|
+
}
|
|
15
|
+
function compareRouteSpecificity(left, right) {
|
|
16
|
+
const leftSegments = normalizeRoutePath(left.routePath).split("/").filter(Boolean);
|
|
17
|
+
const rightSegments = normalizeRoutePath(right.routePath).split("/").filter(Boolean);
|
|
18
|
+
const maxLength = Math.max(leftSegments.length, rightSegments.length);
|
|
19
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
20
|
+
const leftSegment = leftSegments[index];
|
|
21
|
+
const rightSegment = rightSegments[index];
|
|
22
|
+
const leftWeight = leftSegment
|
|
23
|
+
? (isDynamicRouteSegment(leftSegment) ? 1 : 2)
|
|
24
|
+
: 0;
|
|
25
|
+
const rightWeight = rightSegment
|
|
26
|
+
? (isDynamicRouteSegment(rightSegment) ? 1 : 2)
|
|
27
|
+
: 0;
|
|
28
|
+
if (leftWeight !== rightWeight) {
|
|
29
|
+
return rightWeight - leftWeight;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (leftSegments.length !== rightSegments.length) {
|
|
33
|
+
return rightSegments.length - leftSegments.length;
|
|
34
|
+
}
|
|
35
|
+
return left.routePath.localeCompare(right.routePath);
|
|
36
|
+
}
|
|
37
|
+
function routePathMatchesRequestPath(routePath, requestPath) {
|
|
38
|
+
const normalizedRoutePath = normalizeRoutePath(routePath);
|
|
39
|
+
const normalizedRequestPath = normalizeRoutePath(requestPath);
|
|
40
|
+
if (normalizedRoutePath === normalizedRequestPath) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (!normalizedRoutePath.includes("[")) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const routeSegments = normalizedRoutePath.split("/").filter(Boolean);
|
|
47
|
+
const requestSegments = normalizedRequestPath.split("/").filter(Boolean);
|
|
48
|
+
if (routeSegments.length !== requestSegments.length) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
for (let index = 0; index < routeSegments.length; index += 1) {
|
|
52
|
+
const routeSegment = routeSegments[index];
|
|
53
|
+
const requestSegment = requestSegments[index];
|
|
54
|
+
if (isDynamicRouteSegment(routeSegment)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (routeSegment !== requestSegment) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
function sortRoutedPagesForMatching(routedPages) {
|
|
64
|
+
return [...routedPages].sort(compareRouteSpecificity);
|
|
65
|
+
}
|
|
66
|
+
function resolveRoutedPageForPath(requestPath, routedPages) {
|
|
67
|
+
for (const page of routedPages) {
|
|
68
|
+
if (routePathMatchesRequestPath(page.routePath, requestPath)) {
|
|
69
|
+
return page;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function pagePathToRoutePath(filePath: string, pagesDir: string): string;
|
|
2
|
+
export declare function routePathToExpressPath(routePath: string): string;
|
|
3
|
+
export declare function routePathToMarkdownPath(routePath: string): string;
|
|
4
|
+
export declare function markdownPathToRoutePath(pathname: string): string | null;
|
|
5
|
+
export declare function extractRouteParamNames(routePath: string): string[];
|
|
6
|
+
export declare function defaultLocaleRouteToFallbackPath(routePath: string, defaultLocale: string): string | null;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pagePathToRoutePath = pagePathToRoutePath;
|
|
4
|
+
exports.routePathToExpressPath = routePathToExpressPath;
|
|
5
|
+
exports.routePathToMarkdownPath = routePathToMarkdownPath;
|
|
6
|
+
exports.markdownPathToRoutePath = markdownPathToRoutePath;
|
|
7
|
+
exports.extractRouteParamNames = extractRouteParamNames;
|
|
8
|
+
exports.defaultLocaleRouteToFallbackPath = defaultLocaleRouteToFallbackPath;
|
|
9
|
+
function normalizeSegments(relativePath) {
|
|
10
|
+
return relativePath.split("/").filter(Boolean);
|
|
11
|
+
}
|
|
12
|
+
function normalizeRoutePath(routePath) {
|
|
13
|
+
if (!routePath || routePath === "/") {
|
|
14
|
+
return "/";
|
|
15
|
+
}
|
|
16
|
+
return routePath.startsWith("/")
|
|
17
|
+
? (routePath.replace(/\/+$/, "") || "/")
|
|
18
|
+
: (`/${routePath}`.replace(/\/+$/, "") || "/");
|
|
19
|
+
}
|
|
20
|
+
function pagePathToRoutePath(filePath, pagesDir) {
|
|
21
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
22
|
+
const normalizedPagesDir = pagesDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
23
|
+
const withPrefix = `${normalizedPagesDir}/`;
|
|
24
|
+
const relativePath = normalizedFilePath.startsWith(withPrefix)
|
|
25
|
+
? normalizedFilePath.slice(withPrefix.length)
|
|
26
|
+
: normalizedFilePath;
|
|
27
|
+
const withoutExtension = relativePath.replace(/\.md$/, "");
|
|
28
|
+
const segments = normalizeSegments(withoutExtension);
|
|
29
|
+
if (segments[segments.length - 1] === "index") {
|
|
30
|
+
segments.pop();
|
|
31
|
+
}
|
|
32
|
+
if (segments.length === 0) {
|
|
33
|
+
return "/";
|
|
34
|
+
}
|
|
35
|
+
return `/${segments.join("/")}`;
|
|
36
|
+
}
|
|
37
|
+
function routePathToExpressPath(routePath) {
|
|
38
|
+
return routePath.replace(/\[([^\]]+)\]/g, ":$1");
|
|
39
|
+
}
|
|
40
|
+
function routePathToMarkdownPath(routePath) {
|
|
41
|
+
const normalized = normalizeRoutePath(routePath);
|
|
42
|
+
if (normalized === "/") {
|
|
43
|
+
return "/index.md";
|
|
44
|
+
}
|
|
45
|
+
return `${normalized}.md`;
|
|
46
|
+
}
|
|
47
|
+
function markdownPathToRoutePath(pathname) {
|
|
48
|
+
if (!pathname || !pathname.toLowerCase().endsWith(".md")) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const normalized = normalizeRoutePath(pathname);
|
|
52
|
+
if (normalized === "/index.md") {
|
|
53
|
+
return "/";
|
|
54
|
+
}
|
|
55
|
+
const stripped = normalized.slice(0, -3);
|
|
56
|
+
if (stripped.endsWith("/index")) {
|
|
57
|
+
return stripped.slice(0, -6) || "/";
|
|
58
|
+
}
|
|
59
|
+
return stripped.length > 0 ? stripped : "/";
|
|
60
|
+
}
|
|
61
|
+
function extractRouteParamNames(routePath) {
|
|
62
|
+
return Array.from(routePath.matchAll(/\[([^\]]+)\]/g), (match) => match[1]);
|
|
63
|
+
}
|
|
64
|
+
function defaultLocaleRouteToFallbackPath(routePath, defaultLocale) {
|
|
65
|
+
const localePrefix = `/${defaultLocale}`;
|
|
66
|
+
if (routePath === localePrefix) {
|
|
67
|
+
return "/";
|
|
68
|
+
}
|
|
69
|
+
if (routePath.startsWith(`${localePrefix}/`)) {
|
|
70
|
+
return routePath.slice(localePrefix.length);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
import { type CreateFrameworkAppOptions } from "../framework/create-framework-app";
|
|
3
|
+
import { type MdsnConfig } from "./config";
|
|
4
|
+
export type CreateFrameworkAppFn = (options: CreateFrameworkAppOptions) => Express;
|
|
5
|
+
export type ListenFn = (options: {
|
|
6
|
+
app: Express;
|
|
7
|
+
port: number;
|
|
8
|
+
log: (message: string) => void;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
export type OpenBrowserFn = (url: string) => Promise<void> | void;
|
|
11
|
+
export declare function loadUserConfig(rootDir: string): Promise<MdsnConfig>;
|
|
12
|
+
export declare function loadBuiltConfig(distDir: string): MdsnConfig | null;
|
|
13
|
+
export declare function listenOnPort(options: {
|
|
14
|
+
app: Express;
|
|
15
|
+
port: number;
|
|
16
|
+
log: (message: string) => void;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
export declare function openBrowser(url: string): void;
|
|
19
|
+
export declare function startFrameworkServer(options?: {
|
|
20
|
+
cwd?: string;
|
|
21
|
+
port?: number;
|
|
22
|
+
mode?: "dev" | "start";
|
|
23
|
+
createApp?: CreateFrameworkAppFn;
|
|
24
|
+
listen?: ListenFn;
|
|
25
|
+
openBrowser?: OpenBrowserFn;
|
|
26
|
+
log?: (message: string) => void;
|
|
27
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadUserConfig = loadUserConfig;
|
|
7
|
+
exports.loadBuiltConfig = loadBuiltConfig;
|
|
8
|
+
exports.listenOnPort = listenOnPort;
|
|
9
|
+
exports.openBrowser = openBrowser;
|
|
10
|
+
exports.startFrameworkServer = startFrameworkServer;
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const create_framework_app_1 = require("../framework/create-framework-app");
|
|
15
|
+
const config_1 = require("./config");
|
|
16
|
+
const dev_1 = require("./dev");
|
|
17
|
+
const module_loader_1 = require("./module-loader");
|
|
18
|
+
const USER_CONFIG_CANDIDATES = [
|
|
19
|
+
"mdsn.config.cjs",
|
|
20
|
+
"mdsn.config.js",
|
|
21
|
+
"mdsn.config.json",
|
|
22
|
+
"mdsn.config.ts",
|
|
23
|
+
"mdsn.config.mts",
|
|
24
|
+
"mdsn.config.cts",
|
|
25
|
+
];
|
|
26
|
+
function formatConfigLoadError(configPath, error) {
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
const extension = node_path_1.default.extname(configPath).toLowerCase();
|
|
29
|
+
const isTypeScriptConfig = extension === ".ts" || extension === ".mts" || extension === ".cts";
|
|
30
|
+
if (isTypeScriptConfig) {
|
|
31
|
+
return new Error(`Failed to load ${node_path_1.default.basename(configPath)}: ${message}. `
|
|
32
|
+
+ "If you are using the published mdsn CLI binary, prefer mdsn.config.cjs for portable runtime loading.");
|
|
33
|
+
}
|
|
34
|
+
return new Error(`Failed to load ${node_path_1.default.basename(configPath)}: ${message}`);
|
|
35
|
+
}
|
|
36
|
+
async function loadConfigFromFile(configPath) {
|
|
37
|
+
const extension = node_path_1.default.extname(configPath).toLowerCase();
|
|
38
|
+
if (extension === ".json") {
|
|
39
|
+
return JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf8"));
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const loadedModule = await (0, module_loader_1.importModuleFromFile)(configPath);
|
|
43
|
+
return (loadedModule.default ?? loadedModule);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw formatConfigLoadError(configPath, error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function loadUserConfig(rootDir) {
|
|
50
|
+
const availableConfigPaths = USER_CONFIG_CANDIDATES
|
|
51
|
+
.map((name) => node_path_1.default.join(rootDir, name))
|
|
52
|
+
.filter((candidatePath) => (0, node_fs_1.existsSync)(candidatePath));
|
|
53
|
+
if (availableConfigPaths.length === 0) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
const errors = [];
|
|
57
|
+
for (const configPath of availableConfigPaths) {
|
|
58
|
+
try {
|
|
59
|
+
return await loadConfigFromFile(configPath);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
errors.push(error instanceof Error ? error : new Error(String(error)));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (errors.length === 1) {
|
|
66
|
+
throw errors[0];
|
|
67
|
+
}
|
|
68
|
+
const details = errors.map((error) => `- ${error.message}`).join("\n");
|
|
69
|
+
throw new Error(`Failed to load user config from ${rootDir}:\n${details}`);
|
|
70
|
+
}
|
|
71
|
+
function loadBuiltConfig(distDir) {
|
|
72
|
+
const configPath = node_path_1.default.join(distDir, "mdsn.config.json");
|
|
73
|
+
if (!(0, node_fs_1.existsSync)(configPath)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf8"));
|
|
77
|
+
}
|
|
78
|
+
async function listenOnPort(options) {
|
|
79
|
+
const { app, port, log } = options;
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
const server = app.listen(port, () => {
|
|
82
|
+
log(`MDSN framework server listening on http://localhost:${port}`);
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
server.on("error", reject);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function openBrowser(url) {
|
|
89
|
+
const command = process.platform === "darwin"
|
|
90
|
+
? { file: "open", args: [url] }
|
|
91
|
+
: process.platform === "win32"
|
|
92
|
+
? { file: "cmd", args: ["/c", "start", "", url] }
|
|
93
|
+
: { file: "xdg-open", args: [url] };
|
|
94
|
+
const child = (0, node_child_process_1.spawn)(command.file, command.args, {
|
|
95
|
+
detached: true,
|
|
96
|
+
stdio: "ignore",
|
|
97
|
+
});
|
|
98
|
+
child.unref();
|
|
99
|
+
}
|
|
100
|
+
async function startFrameworkServer(options = {}) {
|
|
101
|
+
const cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
|
|
102
|
+
const distDir = node_path_1.default.join(cwd, "dist");
|
|
103
|
+
const shouldUseDist = options.mode === "start";
|
|
104
|
+
const builtConfig = shouldUseDist ? loadBuiltConfig(distDir) : null;
|
|
105
|
+
const rootDir = builtConfig ? distDir : cwd;
|
|
106
|
+
const config = builtConfig ?? await loadUserConfig(rootDir);
|
|
107
|
+
const resolvedConfig = (0, config_1.resolveConfig)(config);
|
|
108
|
+
const devState = options.mode === "dev" ? (0, dev_1.createDevState)() : undefined;
|
|
109
|
+
const app = (options.createApp ?? create_framework_app_1.createFrameworkApp)({
|
|
110
|
+
rootDir,
|
|
111
|
+
mode: options.mode,
|
|
112
|
+
config,
|
|
113
|
+
devState,
|
|
114
|
+
});
|
|
115
|
+
if (devState) {
|
|
116
|
+
for (const directoryName of [
|
|
117
|
+
resolvedConfig.dirs.pages,
|
|
118
|
+
resolvedConfig.dirs.server,
|
|
119
|
+
resolvedConfig.dirs.public,
|
|
120
|
+
resolvedConfig.dirs.layouts,
|
|
121
|
+
]) {
|
|
122
|
+
const targetDir = node_path_1.default.join(rootDir, directoryName);
|
|
123
|
+
if (!(0, node_fs_1.existsSync)(targetDir))
|
|
124
|
+
continue;
|
|
125
|
+
try {
|
|
126
|
+
(0, node_fs_1.watch)(targetDir, { recursive: true }, (_eventType, fileName) => {
|
|
127
|
+
const relativeName = typeof fileName === "string" && fileName
|
|
128
|
+
? node_path_1.default.join(directoryName, fileName).split(node_path_1.default.sep).join("/")
|
|
129
|
+
: directoryName;
|
|
130
|
+
devState.bumpVersion(relativeName);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Ignore watcher setup failures for now; the dev server remains usable without auto-reload.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const port = options.port ?? resolvedConfig.server.port;
|
|
139
|
+
await (options.listen ?? listenOnPort)({
|
|
140
|
+
app,
|
|
141
|
+
port,
|
|
142
|
+
log: options.log ?? console.log,
|
|
143
|
+
});
|
|
144
|
+
if (options.mode === "dev" && resolvedConfig.dev.openBrowser) {
|
|
145
|
+
try {
|
|
146
|
+
await (options.openBrowser ?? openBrowser)(`http://localhost:${port}`);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Keep the dev server usable even if the opener is unavailable.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type MdsnConfig } from "./config";
|
|
2
|
+
export type FrameworkSitePaths = {
|
|
3
|
+
rootDir: string;
|
|
4
|
+
pagesDir: string;
|
|
5
|
+
serverDir: string;
|
|
6
|
+
publicDir: string;
|
|
7
|
+
layoutsDir: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function resolveSitePaths(rootDir: string, config: MdsnConfig): FrameworkSitePaths;
|
|
10
|
+
export declare function findPageFiles(pagesDir: string): string[];
|
|
11
|
+
export declare function findActionFiles(serverDir: string): string[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveSitePaths = resolveSitePaths;
|
|
7
|
+
exports.findPageFiles = findPageFiles;
|
|
8
|
+
exports.findActionFiles = findActionFiles;
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const config_1 = require("./config");
|
|
12
|
+
function resolveSitePaths(rootDir, config) {
|
|
13
|
+
const resolved = (0, config_1.resolveConfig)(config);
|
|
14
|
+
return {
|
|
15
|
+
rootDir,
|
|
16
|
+
pagesDir: node_path_1.default.join(rootDir, resolved.dirs.pages),
|
|
17
|
+
serverDir: node_path_1.default.join(rootDir, resolved.dirs.server),
|
|
18
|
+
publicDir: node_path_1.default.join(rootDir, resolved.dirs.public),
|
|
19
|
+
layoutsDir: node_path_1.default.join(rootDir, resolved.dirs.layouts),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function walkFiles(rootDir) {
|
|
23
|
+
try {
|
|
24
|
+
const entries = (0, node_fs_1.readdirSync)(rootDir, { withFileTypes: true });
|
|
25
|
+
const files = [];
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const absolutePath = node_path_1.default.join(rootDir, entry.name);
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
files.push(...walkFiles(absolutePath));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (entry.isFile()) {
|
|
33
|
+
files.push(absolutePath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code === "ENOENT") {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function hasIgnoredPrivateSegment(rootDir, filePath) {
|
|
46
|
+
const relativeSegments = node_path_1.default.relative(rootDir, filePath).split(node_path_1.default.sep).filter(Boolean);
|
|
47
|
+
return relativeSegments.some((segment) => segment.startsWith("_") || segment === "lib");
|
|
48
|
+
}
|
|
49
|
+
function findPageFiles(pagesDir) {
|
|
50
|
+
return walkFiles(pagesDir)
|
|
51
|
+
.filter((filePath) => filePath.endsWith(".md"))
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
function findActionFiles(serverDir) {
|
|
55
|
+
return walkFiles(serverDir)
|
|
56
|
+
.filter((filePath) => /\.(js|mjs|cjs)$/.test(filePath))
|
|
57
|
+
.filter((filePath) => !hasIgnoredPrivateSegment(serverDir, filePath))
|
|
58
|
+
.sort();
|
|
59
|
+
}
|