@phtngyn/cms 0.1.0-beta.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 +13 -0
- package/README.md +90 -0
- package/dist/studio/assets/ActivityEntryRow-D--fRvWI.js +1 -0
- package/dist/studio/assets/ActivityView-D-XBBUHb.js +1 -0
- package/dist/studio/assets/CreateDocumentDialog-CH8UH1QM.js +1 -0
- package/dist/studio/assets/DocumentsView-CxRpmAxt.js +34 -0
- package/dist/studio/assets/DropdownMenu-BSb1l7fr.js +1 -0
- package/dist/studio/assets/Input-DVao38h7.js +3 -0
- package/dist/studio/assets/MediaView-BHrPIjxB.js +1 -0
- package/dist/studio/assets/Modal-VNrXGa5C.js +1 -0
- package/dist/studio/assets/OverviewView-DfP6-mLZ.js +1 -0
- package/dist/studio/assets/Select-BJZIs9sY.js +1 -0
- package/dist/studio/assets/SettingsView-CerHe-ua.js +1 -0
- package/dist/studio/assets/Skeleton-CWJNi3nS.js +1 -0
- package/dist/studio/assets/StudioPageHeader-DP9HDAF2.js +1 -0
- package/dist/studio/assets/UsersView-cP3REkH2.js +1 -0
- package/dist/studio/assets/collaboration-BLnCzfvV.js +4 -0
- package/dist/studio/assets/context-dY4fNbn3.js +1 -0
- package/dist/studio/assets/editor-CE07wW83.js +137 -0
- package/dist/studio/assets/gateway-BAyQgCse.js +10 -0
- package/dist/studio/assets/index-CjoaPLhZ.js +28 -0
- package/dist/studio/assets/index-s2rPqCmP.css +2 -0
- package/dist/studio/assets/render-BkxbK2St.js +83 -0
- package/dist/studio/assets/rolldown-runtime-DK3Fl9T5.js +1 -0
- package/dist/studio/assets/useMediaLibrary-D7qR7aBf.js +1 -0
- package/dist/studio/index.html +76 -0
- package/package.json +75 -0
- package/src/Authoring.d.ts +46 -0
- package/src/Authoring.js +1 -0
- package/src/AuthoringSchema-BQ5kbhVB.js +926 -0
- package/src/AuthoringSchema-BQ5kbhVB.js.map +1 -0
- package/src/Configuration-BGYFD2N1.js +150 -0
- package/src/Configuration-BGYFD2N1.js.map +1 -0
- package/src/Configuration.d.ts +44 -0
- package/src/Configuration.js +9 -0
- package/src/Configuration.js.map +1 -0
- package/src/DocumentEngine-Da0kWotL.js +851 -0
- package/src/DocumentEngine-Da0kWotL.js.map +1 -0
- package/src/DocumentModel.d.ts +30 -0
- package/src/DocumentModel.js +15 -0
- package/src/DocumentModel.js.map +1 -0
- package/src/HttpPaths-86qLGB90.js +15 -0
- package/src/HttpPaths-86qLGB90.js.map +1 -0
- package/src/Module.d.ts +12 -0
- package/src/Module.js +167 -0
- package/src/Module.js.map +1 -0
- package/src/StudioHttp-BTLLooh0.js +916 -0
- package/src/StudioHttp-BTLLooh0.js.map +1 -0
- package/src/connection-ZGtL54qY.js +94 -0
- package/src/connection-ZGtL54qY.js.map +1 -0
- package/src/installation-BNKaVjni.js +9128 -0
- package/src/installation-BNKaVjni.js.map +1 -0
- package/src/internal/authoring-compiler.js +120 -0
- package/src/internal/authoring-compiler.js.map +1 -0
- package/src/internal/authoring-metadata.js +163 -0
- package/src/internal/authoring-metadata.js.map +1 -0
- package/src/internal/cms-config.js +46 -0
- package/src/internal/cms-config.js.map +1 -0
- package/src/internal/component-metadata.js +37 -0
- package/src/internal/component-metadata.js.map +1 -0
- package/src/internal/configuration.js +93 -0
- package/src/internal/configuration.js.map +1 -0
- package/src/internal/document-model.js +236 -0
- package/src/internal/document-model.js.map +1 -0
- package/src/internal/field-metadata.js +6 -0
- package/src/internal/field-metadata.js.map +1 -0
- package/src/metadata-Czvi3E4K.js +190 -0
- package/src/metadata-Czvi3E4K.js.map +1 -0
- package/src/models-Citx8jBi.js +306 -0
- package/src/models-Citx8jBi.js.map +1 -0
- package/src/models-DS5ClMsH.js +153 -0
- package/src/models-DS5ClMsH.js.map +1 -0
- package/src/runtime/api.js +9 -0
- package/src/runtime/api.js.map +1 -0
- package/src/runtime/collaboration.js +40 -0
- package/src/runtime/collaboration.js.map +1 -0
- package/src/runtime/delivery/DocumentField.js +23 -0
- package/src/runtime/delivery/DocumentField.js.map +1 -0
- package/src/runtime/delivery/DocumentRenderer.js +51 -0
- package/src/runtime/delivery/DocumentRenderer.js.map +1 -0
- package/src/runtime/delivery/DocumentScope.js +41 -0
- package/src/runtime/delivery/DocumentScope.js.map +1 -0
- package/src/runtime/delivery/document-page.js +23 -0
- package/src/runtime/delivery/document-page.js.map +1 -0
- package/src/runtime/delivery/document.js +25 -0
- package/src/runtime/delivery/document.js.map +1 -0
- package/src/runtime/delivery/preview.d.ts +11 -0
- package/src/runtime/delivery/preview.js +6 -0
- package/src/runtime/delivery/preview.js.map +1 -0
- package/src/runtime/delivery/types.d.ts +80 -0
- package/src/runtime/delivery/types.js +1 -0
- package/src/runtime/delivery/useDocument.d.ts +6 -0
- package/src/runtime/delivery/useDocument.js +20 -0
- package/src/runtime/delivery/useDocument.js.map +1 -0
- package/src/runtime/delivery/useDocumentQuery.d.ts +7 -0
- package/src/runtime/delivery/useDocumentQuery.js +26 -0
- package/src/runtime/delivery/useDocumentQuery.js.map +1 -0
- package/src/runtime/installation.js +3 -0
- package/src/runtime/plugin.js +32 -0
- package/src/runtime/plugin.js.map +1 -0
- package/src/runtime/preview/CmsStudioPreviewMarker.js +15 -0
- package/src/runtime/preview/CmsStudioPreviewMarker.js.map +1 -0
- package/src/runtime/preview/comark-document.js +38 -0
- package/src/runtime/preview/comark-document.js.map +1 -0
- package/src/runtime/preview/connection.js +3 -0
- package/src/runtime/preview/controller.js +247 -0
- package/src/runtime/preview/controller.js.map +1 -0
- package/src/runtime/preview/plugin.client.js +70 -0
- package/src/runtime/preview/plugin.client.js.map +1 -0
- package/src/runtime/preview/preview.css +9 -0
- package/src/runtime/preview/protocol.d.ts +10 -0
- package/src/runtime/preview/protocol.js +11 -0
- package/src/runtime/preview/protocol.js.map +1 -0
- package/src/runtime/preview/store.js +4 -0
- package/src/runtime/preview/targets.js +102 -0
- package/src/runtime/preview/targets.js.map +1 -0
- package/src/runtime/studio-app.js +13 -0
- package/src/runtime/studio-app.js.map +1 -0
- package/src/store-Nl4uxEXx.js +221 -0
- package/src/store-Nl4uxEXx.js.map +1 -0
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
import { G as SourcePath, H as PublicPath, d as DocumentIssue, n as CmsMarkdownDocument, r as CompiledDocument, t as CmsDocumentAnalysis, v as DocumentSourceMapping, x as DocumentValidationResult } from "./models-Citx8jBi.js";
|
|
2
|
+
import { c as createCmsComarkRenderData, s as cmsComarkTextContent } from "./metadata-Czvi3E4K.js";
|
|
3
|
+
import { C as withCmsComarkProps, S as resolveCmsComarkAttributes, _ as createDocumentParser, b as matchesCmsComponentPropType, g as DOCUMENT_ENGINE_FINGERPRINT, r as validateAuthoringDocument, v as SOURCE_MAPPINGS_META_KEY, x as normalizeCmsComponentBindings } from "./AuthoringSchema-BQ5kbhVB.js";
|
|
4
|
+
import { Effect, Option, Predicate, Result, Schema } from "effect";
|
|
5
|
+
|
|
6
|
+
//#region ../protocol/src/internal/documents/address.ts
|
|
7
|
+
const addressTokenPattern = /:([a-z][\w.]*)(\*)?/giu;
|
|
8
|
+
function parseDocumentAddress(pattern) {
|
|
9
|
+
const tokens = [...pattern.matchAll(addressTokenPattern)].map((match) => ({
|
|
10
|
+
catchAll: match[2] === "*",
|
|
11
|
+
name: match[1] ?? ""
|
|
12
|
+
}));
|
|
13
|
+
if (pattern.replace(addressTokenPattern, "").includes(":") || tokens.some((token) => !token.name)) return Result.fail({
|
|
14
|
+
field: "addressPattern",
|
|
15
|
+
message: `Document address pattern "${pattern}" contains an invalid token.`
|
|
16
|
+
});
|
|
17
|
+
if (tokens.filter((token) => token.catchAll).length > 1) return Result.fail({
|
|
18
|
+
field: "addressPattern",
|
|
19
|
+
message: `Document address pattern "${pattern}" contains more than one catch-all token.`
|
|
20
|
+
});
|
|
21
|
+
if (new Set(tokens.map((token) => token.name)).size !== tokens.length) return Result.fail({
|
|
22
|
+
field: "addressPattern",
|
|
23
|
+
message: `Document address pattern "${pattern}" contains duplicate tokens.`
|
|
24
|
+
});
|
|
25
|
+
return Result.succeed({
|
|
26
|
+
pattern,
|
|
27
|
+
tokens
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function resolveDocumentAddress(address, relativeKey, data) {
|
|
31
|
+
const relativePath = relativeKey === "/" ? "" : relativeKey.slice(1);
|
|
32
|
+
let resolved = address.pattern;
|
|
33
|
+
for (const token of address.tokens) {
|
|
34
|
+
const authored = nestedValue$1(data, token.name);
|
|
35
|
+
const fallback = token.catchAll ? relativePath : void 0;
|
|
36
|
+
const value = authored === void 0 ? fallback : Predicate.isString(authored) && authored.trim() ? authored : void 0;
|
|
37
|
+
if (value === void 0) return Result.fail({
|
|
38
|
+
field: token.name,
|
|
39
|
+
message: authored === void 0 ? `Document address field "${token.name}" is required by pattern "${address.pattern}".` : `Document address field "${token.name}" must be a non-empty string.`
|
|
40
|
+
});
|
|
41
|
+
const normalized = token.catchAll ? normalizeDocumentRelativePath(value) : normalizeDocumentSegment(value);
|
|
42
|
+
if (Result.isFailure(normalized)) return Result.fail(normalized.failure);
|
|
43
|
+
resolved = resolved.replace(`:${token.name}${token.catchAll ? "*" : ""}`, normalized.success);
|
|
44
|
+
}
|
|
45
|
+
return normalizePublicPath(resolved);
|
|
46
|
+
}
|
|
47
|
+
function documentKeyFromSourcePath(relativeSourcePath) {
|
|
48
|
+
const extension = relativeSourcePath.match(/\.(?:md|mdc)$/iu)?.[0];
|
|
49
|
+
if (!extension) return Result.fail({
|
|
50
|
+
field: "sourcePath",
|
|
51
|
+
message: `Document source must be a Markdown file: "${relativeSourcePath}".`
|
|
52
|
+
});
|
|
53
|
+
const segments = relativeSourcePath.slice(0, -extension.length).split("/");
|
|
54
|
+
if (segments.at(-1)?.toLocaleLowerCase() === "index") segments.pop();
|
|
55
|
+
return normalizeDocumentRelativePath(segments.join("/")).pipe(Result.flatMap(normalizePublicPath));
|
|
56
|
+
}
|
|
57
|
+
function localizeDocumentPath(key, locale, configuration) {
|
|
58
|
+
if (!configuration || !locale || locale === configuration.defaultLocale && !configuration.prefixDefaultLocale) return key;
|
|
59
|
+
return PublicPath.make(`/${locale}${key === "/" ? "" : key}`);
|
|
60
|
+
}
|
|
61
|
+
function normalizePublicPath(value) {
|
|
62
|
+
const path = `/${value.replace(/\\/gu, "/").replace(/^\/+|\/+$/gu, "")}`.replace(/\/{2,}/gu, "/");
|
|
63
|
+
return Schema.decodeResult(PublicPath)(path).pipe(Result.mapError(() => ({
|
|
64
|
+
field: "path",
|
|
65
|
+
message: `Document path "${value}" cannot be normalized into a public path.`
|
|
66
|
+
})));
|
|
67
|
+
}
|
|
68
|
+
function normalizeDocumentRelativePath(value) {
|
|
69
|
+
if (!value.trim()) return Result.succeed("");
|
|
70
|
+
const segments = value.replace(/\\/gu, "/").split("/").filter(Boolean);
|
|
71
|
+
const normalized = [];
|
|
72
|
+
for (const segment of segments) {
|
|
73
|
+
const result = normalizeDocumentSegment(segment);
|
|
74
|
+
if (Result.isFailure(result)) return result;
|
|
75
|
+
normalized.push(result.success);
|
|
76
|
+
}
|
|
77
|
+
return Result.succeed(normalized.join("/"));
|
|
78
|
+
}
|
|
79
|
+
function normalizeDocumentSegment(value) {
|
|
80
|
+
const normalized = value.trim().normalize("NFKC").toLocaleLowerCase().replace(/[^\p{Letter}\p{Number}_-]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
81
|
+
return normalized ? Result.succeed(normalized) : Result.fail({ message: `Document path segment "${value}" is empty after normalization.` });
|
|
82
|
+
}
|
|
83
|
+
function nestedValue$1(value, path) {
|
|
84
|
+
return path.split(".").reduce((current, segment) => {
|
|
85
|
+
if (!Schema.is(Schema.JsonObject)(current)) return void 0;
|
|
86
|
+
return current[segment];
|
|
87
|
+
}, value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region ../document/src/internal/document-engine/errors.ts
|
|
92
|
+
var DocumentEngineConfigurationInvalid = class extends Schema.TaggedError()("DocumentEngine.ConfigurationInvalid", {
|
|
93
|
+
message: Schema.NonEmptyString,
|
|
94
|
+
field: Schema.optionalKey(Schema.NonEmptyString)
|
|
95
|
+
}) {};
|
|
96
|
+
var DocumentParseFailed = class extends Schema.TaggedError()("DocumentEngine.DocumentParseFailed", {
|
|
97
|
+
sourcePath: SourcePath,
|
|
98
|
+
cause: Schema.Defect()
|
|
99
|
+
}) {};
|
|
100
|
+
var DocumentCompilationFailed = class extends Schema.TaggedError()("DocumentEngine.DocumentCompilationFailed", { issues: Schema.Array(DocumentIssue) }) {};
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region ../document/src/internal/comark/analysis.ts
|
|
104
|
+
const CMS_STANDARD_TAGS = /* @__PURE__ */ new Set([
|
|
105
|
+
"a",
|
|
106
|
+
"abbr",
|
|
107
|
+
"address",
|
|
108
|
+
"article",
|
|
109
|
+
"aside",
|
|
110
|
+
"b",
|
|
111
|
+
"blockquote",
|
|
112
|
+
"br",
|
|
113
|
+
"button",
|
|
114
|
+
"caption",
|
|
115
|
+
"code",
|
|
116
|
+
"col",
|
|
117
|
+
"colgroup",
|
|
118
|
+
"dd",
|
|
119
|
+
"del",
|
|
120
|
+
"details",
|
|
121
|
+
"div",
|
|
122
|
+
"dl",
|
|
123
|
+
"dt",
|
|
124
|
+
"em",
|
|
125
|
+
"figcaption",
|
|
126
|
+
"figure",
|
|
127
|
+
"footer",
|
|
128
|
+
"h1",
|
|
129
|
+
"h2",
|
|
130
|
+
"h3",
|
|
131
|
+
"h4",
|
|
132
|
+
"h5",
|
|
133
|
+
"h6",
|
|
134
|
+
"header",
|
|
135
|
+
"hr",
|
|
136
|
+
"i",
|
|
137
|
+
"img",
|
|
138
|
+
"kbd",
|
|
139
|
+
"li",
|
|
140
|
+
"main",
|
|
141
|
+
"mark",
|
|
142
|
+
"nav",
|
|
143
|
+
"ol",
|
|
144
|
+
"p",
|
|
145
|
+
"pre",
|
|
146
|
+
"q",
|
|
147
|
+
"s",
|
|
148
|
+
"section",
|
|
149
|
+
"small",
|
|
150
|
+
"span",
|
|
151
|
+
"strong",
|
|
152
|
+
"sub",
|
|
153
|
+
"summary",
|
|
154
|
+
"sup",
|
|
155
|
+
"table",
|
|
156
|
+
"tbody",
|
|
157
|
+
"td",
|
|
158
|
+
"template",
|
|
159
|
+
"tfoot",
|
|
160
|
+
"th",
|
|
161
|
+
"thead",
|
|
162
|
+
"tr",
|
|
163
|
+
"u",
|
|
164
|
+
"ul"
|
|
165
|
+
]);
|
|
166
|
+
function analyzeCmsDocument(body, metadata) {
|
|
167
|
+
const headings = [];
|
|
168
|
+
const components = /* @__PURE__ */ new Set();
|
|
169
|
+
visit$1(body.nodes, (node) => {
|
|
170
|
+
const tag = node[0].toLocaleLowerCase();
|
|
171
|
+
if (/^h[1-6]$/u.test(tag)) headings.push({
|
|
172
|
+
depth: Number(tag.slice(1)),
|
|
173
|
+
id: Predicate.isString(node[1].id) ? node[1].id : "",
|
|
174
|
+
text: cmsComarkTextContent(node).trim()
|
|
175
|
+
});
|
|
176
|
+
else if (!CMS_STANDARD_TAGS.has(tag)) components.add(tag);
|
|
177
|
+
});
|
|
178
|
+
const bodyText = normalizeText(body.nodes.map(cmsComarkTextContent).join(" "));
|
|
179
|
+
const summary = normalizeText(metadata.description ?? firstParagraph(body.nodes) ?? metadata.title);
|
|
180
|
+
const searchText = normalizeText([
|
|
181
|
+
metadata.title,
|
|
182
|
+
metadata.description,
|
|
183
|
+
bodyText
|
|
184
|
+
].filter(Boolean).join(" "));
|
|
185
|
+
return CmsDocumentAnalysis.make({
|
|
186
|
+
components: [...components].sort(),
|
|
187
|
+
headings,
|
|
188
|
+
searchText,
|
|
189
|
+
summary
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function visit$1(nodes, inspect) {
|
|
193
|
+
for (const node of nodes) {
|
|
194
|
+
if (Predicate.isString(node) || node[0] === null) continue;
|
|
195
|
+
inspect(node);
|
|
196
|
+
const [, , ...children] = node;
|
|
197
|
+
visit$1(children, inspect);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function firstParagraph(nodes) {
|
|
201
|
+
const paragraph = nodes.find((node) => Array.isArray(node) && node[0] === "p");
|
|
202
|
+
if (!paragraph || Predicate.isString(paragraph) || paragraph[0] === null) return void 0;
|
|
203
|
+
return normalizeText(cmsComarkTextContent(paragraph)) || void 0;
|
|
204
|
+
}
|
|
205
|
+
function normalizeText(value) {
|
|
206
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region ../document/src/internal/document-engine/frontmatter-validation.ts
|
|
211
|
+
const validateFrontmatter = Effect.fn("DocumentEngine.validateFrontmatter")(function* (raw, schema, type, sourcePath) {
|
|
212
|
+
const projection = schema.project(raw);
|
|
213
|
+
const issues = projection.unknownPaths.toSorted().map((path) => unknownFieldIssue(path, type, sourcePath));
|
|
214
|
+
const result = yield* schema.validate(projection.value);
|
|
215
|
+
if ("issues" in result) {
|
|
216
|
+
issues.push(...result.issues.map((issue) => schemaIssue(issue, projection.value, type, sourcePath)));
|
|
217
|
+
return {
|
|
218
|
+
data: projection.value,
|
|
219
|
+
issues
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const parsed = Schema.decodeUnknownOption(Schema.JsonObject)(result.value);
|
|
223
|
+
if (parsed._tag === "None") {
|
|
224
|
+
issues.push(issue("schema.constraint", "The collection schema output must be a JSON-compatible object.", type, sourcePath, "frontmatter"));
|
|
225
|
+
return {
|
|
226
|
+
data: projection.value,
|
|
227
|
+
issues
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return issues.length === 0 ? {
|
|
231
|
+
data: parsed.value,
|
|
232
|
+
issues
|
|
233
|
+
} : {
|
|
234
|
+
data: projection.value,
|
|
235
|
+
issues
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
function unknownFieldIssue(path, type, sourcePath) {
|
|
239
|
+
if (path === "id") return issue("document.id", "Document identity is managed by the CMS and cannot be authored in frontmatter.", type, sourcePath, path);
|
|
240
|
+
return issue("schema.unknown", `Frontmatter field "${path}" is not declared by collection "${type.collection}".`, type, sourcePath, path);
|
|
241
|
+
}
|
|
242
|
+
function schemaIssue(error, input, type, sourcePath) {
|
|
243
|
+
const field = issuePath(error.path);
|
|
244
|
+
return issue(issueCode(error, input, field), error.message, type, sourcePath, field || void 0);
|
|
245
|
+
}
|
|
246
|
+
function issueCode(error, input, field) {
|
|
247
|
+
if (error.code === "invalid_type") return nestedValue(input, field) === void 0 ? "schema.required" : "schema.type";
|
|
248
|
+
if (error.code === "invalid_value") return "schema.option";
|
|
249
|
+
return "schema.constraint";
|
|
250
|
+
}
|
|
251
|
+
function issuePath(path) {
|
|
252
|
+
return (path ?? []).map((segment) => Predicate.hasProperty(segment, "key") ? segment.key : segment).map(String).join(".");
|
|
253
|
+
}
|
|
254
|
+
function nestedValue(value, path) {
|
|
255
|
+
if (!path) return value;
|
|
256
|
+
return path.split(".").reduce((current, segment) => {
|
|
257
|
+
if (Array.isArray(current)) {
|
|
258
|
+
const index = Number(segment);
|
|
259
|
+
return Number.isSafeInteger(index) ? current[index] : void 0;
|
|
260
|
+
}
|
|
261
|
+
return Schema.is(Schema.JsonObject)(current) ? current[segment] : void 0;
|
|
262
|
+
}, value);
|
|
263
|
+
}
|
|
264
|
+
function issue(code, message, type, sourcePath, field) {
|
|
265
|
+
return {
|
|
266
|
+
code,
|
|
267
|
+
collection: type.collection,
|
|
268
|
+
...field === void 0 ? void 0 : { field },
|
|
269
|
+
message,
|
|
270
|
+
severity: "error",
|
|
271
|
+
sourcePath
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region ../document/src/internal/document-engine/compilation.ts
|
|
277
|
+
const isDocumentParseFailed$1 = Schema.is(DocumentParseFailed);
|
|
278
|
+
const compileCompiledDocument = Effect.fn("DocumentEngine.compileDocument")(function* (configuration, parseMarkdown, source) {
|
|
279
|
+
const location = yield* resolveSourceLocation(configuration, source);
|
|
280
|
+
const parsedBody = yield* parseBody(parseMarkdown, source);
|
|
281
|
+
const mappings = yield* sourceMappings(parsedBody, source);
|
|
282
|
+
const body = normalizeCmsComponentBindings(withoutSourceMappings(parsedBody), configuration.components);
|
|
283
|
+
const relativeKey = yield* fromAddressResult(documentKeyFromSourcePath(location.relativeSourcePath), source.sourcePath);
|
|
284
|
+
const validation = yield* validateFrontmatter(body.frontmatter, location.type.schema, location.type.definition, source.sourcePath).pipe(Effect.mapError((cause) => new DocumentCompilationFailed({ issues: [{
|
|
285
|
+
code: "schema.constraint",
|
|
286
|
+
collection: location.type.definition.collection,
|
|
287
|
+
message: cause.message,
|
|
288
|
+
severity: "error",
|
|
289
|
+
sourcePath: source.sourcePath
|
|
290
|
+
}] })));
|
|
291
|
+
const address = resolveDocumentAddress(location.type.address, relativeKey, validation.data);
|
|
292
|
+
const key = Result.isSuccess(address) ? address.success : provisionalKey(source.documentId);
|
|
293
|
+
const path = localizeDocumentPath(key, location.locale, configuration.documents.locales);
|
|
294
|
+
const title = stringValue(validation.data.title) ?? stringValue(body.meta.title) ?? titleFromSource(location.relativeSourcePath);
|
|
295
|
+
const description = stringValue(validation.data.description) ?? stringValue(body.meta.description);
|
|
296
|
+
const issues = Result.isSuccess(address) ? validation.issues : [...validation.issues, addressIssue(address.failure, source.sourcePath)];
|
|
297
|
+
const canonicalBody = CmsMarkdownDocument.make({
|
|
298
|
+
frontmatter: validation.data,
|
|
299
|
+
meta: body.meta,
|
|
300
|
+
nodes: body.nodes
|
|
301
|
+
});
|
|
302
|
+
const analysis = analyzeCmsDocument(canonicalBody, description === void 0 ? { title } : {
|
|
303
|
+
description,
|
|
304
|
+
title
|
|
305
|
+
});
|
|
306
|
+
return CompiledDocument.make(Object.assign({
|
|
307
|
+
id: source.documentId,
|
|
308
|
+
key,
|
|
309
|
+
path,
|
|
310
|
+
collection: location.type.definition.collection,
|
|
311
|
+
draft: source.draft,
|
|
312
|
+
title,
|
|
313
|
+
data: validation.data,
|
|
314
|
+
issues: [...issues, ...validateAuthoringDocument(canonicalBody, configuration.components).map((message) => ({
|
|
315
|
+
code: "reference.component",
|
|
316
|
+
message,
|
|
317
|
+
severity: "error",
|
|
318
|
+
sourcePath: source.sourcePath
|
|
319
|
+
}))],
|
|
320
|
+
analysis,
|
|
321
|
+
body: canonicalBody,
|
|
322
|
+
engineFingerprint: DOCUMENT_ENGINE_FINGERPRINT,
|
|
323
|
+
source: {
|
|
324
|
+
path: source.sourcePath,
|
|
325
|
+
mappings
|
|
326
|
+
}
|
|
327
|
+
}, location.locale === void 0 ? void 0 : { locale: location.locale }, description === void 0 ? void 0 : { description }));
|
|
328
|
+
});
|
|
329
|
+
function provisionalKey(documentId) {
|
|
330
|
+
return PublicPath.make(`/_cms-invalid/${documentId}`);
|
|
331
|
+
}
|
|
332
|
+
function addressIssue(error, sourcePath) {
|
|
333
|
+
const issue = {
|
|
334
|
+
code: "document.invalid",
|
|
335
|
+
message: error.message,
|
|
336
|
+
severity: "error",
|
|
337
|
+
sourcePath
|
|
338
|
+
};
|
|
339
|
+
return error.field === void 0 ? issue : {
|
|
340
|
+
...issue,
|
|
341
|
+
field: error.field
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function resolveSourceLocation(configuration, source) {
|
|
345
|
+
const segments = source.sourcePath.split("/");
|
|
346
|
+
let locale;
|
|
347
|
+
if (configuration.documents.locales) {
|
|
348
|
+
const candidate = segments.shift();
|
|
349
|
+
locale = configuration.documents.locales.locales.find((value) => value === candidate);
|
|
350
|
+
if (!locale) return compilationFailure({
|
|
351
|
+
code: "document.invalid",
|
|
352
|
+
field: "locale",
|
|
353
|
+
message: `Localized document "${source.sourcePath}" must begin with a configured locale.`,
|
|
354
|
+
severity: "error",
|
|
355
|
+
sourcePath: source.sourcePath
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
const localizedSourcePath = segments.join("/");
|
|
359
|
+
const type = configuration.types.find(({ definition }) => definition.directory === "." || localizedSourcePath.startsWith(`${definition.directory}/`));
|
|
360
|
+
if (!type) return compilationFailure({
|
|
361
|
+
code: "document.invalid",
|
|
362
|
+
field: "sourcePath",
|
|
363
|
+
message: `No document collection contains "${source.sourcePath}".`,
|
|
364
|
+
severity: "error",
|
|
365
|
+
sourcePath: source.sourcePath
|
|
366
|
+
});
|
|
367
|
+
const relativeSourcePath = type.definition.directory === "." ? localizedSourcePath : localizedSourcePath.slice(type.definition.directory.length + 1);
|
|
368
|
+
if (!relativeSourcePath) return compilationFailure({
|
|
369
|
+
code: "document.invalid",
|
|
370
|
+
field: "sourcePath",
|
|
371
|
+
message: `Document source "${source.sourcePath}" has no collection-relative path.`,
|
|
372
|
+
severity: "error",
|
|
373
|
+
sourcePath: source.sourcePath
|
|
374
|
+
});
|
|
375
|
+
return Effect.succeed(locale === void 0 ? {
|
|
376
|
+
relativeSourcePath,
|
|
377
|
+
type
|
|
378
|
+
} : {
|
|
379
|
+
locale,
|
|
380
|
+
relativeSourcePath,
|
|
381
|
+
type
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function parseBody(parseMarkdown, source) {
|
|
385
|
+
return Effect.tryPromise({
|
|
386
|
+
try: () => parseMarkdown(source.markdown),
|
|
387
|
+
catch: (cause) => new DocumentParseFailed({
|
|
388
|
+
cause,
|
|
389
|
+
sourcePath: source.sourcePath
|
|
390
|
+
})
|
|
391
|
+
}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(CmsMarkdownDocument)), Effect.mapError((cause) => isDocumentParseFailed$1(cause) ? cause : new DocumentParseFailed({
|
|
392
|
+
cause,
|
|
393
|
+
sourcePath: source.sourcePath
|
|
394
|
+
})));
|
|
395
|
+
}
|
|
396
|
+
function sourceMappings(body, source) {
|
|
397
|
+
return Schema.decodeUnknownEffect(Schema.Array(DocumentSourceMapping))(body.meta["__cmsSourceMappings"] ?? []).pipe(Effect.mapError((cause) => new DocumentParseFailed({
|
|
398
|
+
cause,
|
|
399
|
+
sourcePath: source.sourcePath
|
|
400
|
+
})));
|
|
401
|
+
}
|
|
402
|
+
function withoutSourceMappings(body) {
|
|
403
|
+
const { [SOURCE_MAPPINGS_META_KEY]: _mappings, ...meta } = body.meta;
|
|
404
|
+
return CmsMarkdownDocument.make({
|
|
405
|
+
frontmatter: body.frontmatter,
|
|
406
|
+
meta,
|
|
407
|
+
nodes: body.nodes
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
function fromAddressResult(result, sourcePath) {
|
|
411
|
+
if (Result.isSuccess(result)) return Effect.succeed(result.success);
|
|
412
|
+
return compilationFailure(addressIssue(result.failure, sourcePath));
|
|
413
|
+
}
|
|
414
|
+
function compilationFailure(issue) {
|
|
415
|
+
return Effect.fail(new DocumentCompilationFailed({ issues: [issue] }));
|
|
416
|
+
}
|
|
417
|
+
function stringValue(value) {
|
|
418
|
+
return Predicate.isString(value) && value.trim() ? value.trim() : void 0;
|
|
419
|
+
}
|
|
420
|
+
function titleFromSource(sourcePath) {
|
|
421
|
+
const stem = (sourcePath.split("/").at(-1) ?? "content").replace(/\.(?:md|mdc)$/iu, "");
|
|
422
|
+
return (stem.toLocaleLowerCase() === "index" ? sourcePath.split("/").at(-2) ?? "home" : stem).replace(/[-_]+/gu, " ").replace(/^./u, (character) => character.toLocaleUpperCase());
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region ../document/src/internal/document-engine/catalog-lookup.ts
|
|
427
|
+
function createCatalogLookup(documents, configuration) {
|
|
428
|
+
const byPath = new Map(documents.map((document) => [document.path, document]));
|
|
429
|
+
const byVariant = new Map(documents.map((document) => [localeAndKey(document.locale, document.key), document]));
|
|
430
|
+
const findByKey = (input, locale) => {
|
|
431
|
+
const keyResult = normalizePublicPath(input);
|
|
432
|
+
if (Result.isFailure(keyResult)) return Option.none();
|
|
433
|
+
const key = keyResult.success;
|
|
434
|
+
const locales = configuration.documents.locales;
|
|
435
|
+
if (!locales) return Option.fromNullishOr(byVariant.get(localeAndKey(void 0, key)));
|
|
436
|
+
const requestedLocale = locale ?? locales.defaultLocale;
|
|
437
|
+
const localized = byVariant.get(localeAndKey(requestedLocale, key));
|
|
438
|
+
if (localized) return Option.some(localized);
|
|
439
|
+
if (!locales.fallbackLocale) return Option.none();
|
|
440
|
+
return Option.fromNullishOr(byVariant.get(localeAndKey(locales.fallbackLocale, key)));
|
|
441
|
+
};
|
|
442
|
+
const findByPath = (input) => {
|
|
443
|
+
const pathResult = normalizePublicPath(input);
|
|
444
|
+
if (Result.isFailure(pathResult)) return Option.none();
|
|
445
|
+
const path = pathResult.success;
|
|
446
|
+
const direct = byPath.get(path);
|
|
447
|
+
if (direct) return Option.some(direct);
|
|
448
|
+
const locales = configuration.documents.locales;
|
|
449
|
+
if (!locales) return Option.none();
|
|
450
|
+
const request = localizedRequest(path, locales.locales, locales.defaultLocale, locales.prefixDefaultLocale);
|
|
451
|
+
if (!request) return Option.none();
|
|
452
|
+
const localized = byVariant.get(localeAndKey(request.locale, request.key));
|
|
453
|
+
if (localized) return Option.some(localized);
|
|
454
|
+
if (!locales.fallbackLocale) return Option.none();
|
|
455
|
+
return Option.fromNullishOr(byVariant.get(localeAndKey(locales.fallbackLocale, request.key)));
|
|
456
|
+
};
|
|
457
|
+
return {
|
|
458
|
+
findByKey,
|
|
459
|
+
findByPath
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function localeAndKey(locale, key) {
|
|
463
|
+
return `${locale ?? ""}:${key}`;
|
|
464
|
+
}
|
|
465
|
+
function localizedRequest(path, locales, defaultLocale, prefixDefaultLocale) {
|
|
466
|
+
const segments = path === "/" ? [] : path.slice(1).split("/");
|
|
467
|
+
const possibleLocale = locales.find((locale) => locale === segments[0]);
|
|
468
|
+
const prefixedLocale = possibleLocale && (possibleLocale !== defaultLocale || prefixDefaultLocale) ? possibleLocale : void 0;
|
|
469
|
+
if (prefixedLocale) {
|
|
470
|
+
const keyResult = normalizePublicPath(segments.slice(1).join("/"));
|
|
471
|
+
return Result.isSuccess(keyResult) ? {
|
|
472
|
+
key: keyResult.success,
|
|
473
|
+
locale: prefixedLocale
|
|
474
|
+
} : void 0;
|
|
475
|
+
}
|
|
476
|
+
if (prefixDefaultLocale) return void 0;
|
|
477
|
+
return {
|
|
478
|
+
key: path,
|
|
479
|
+
locale: defaultLocale
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region ../document/src/internal/document-engine/catalog-issues.ts
|
|
485
|
+
function collisionIssues(documents) {
|
|
486
|
+
const issues = [];
|
|
487
|
+
const byId = /* @__PURE__ */ new Map();
|
|
488
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
489
|
+
const byVariant = /* @__PURE__ */ new Map();
|
|
490
|
+
for (const document of documents) {
|
|
491
|
+
const identityCollision = byId.get(document.id);
|
|
492
|
+
if (identityCollision) issues.push(collisionIssue(`Document identity collision for "${document.id}" between "${identityCollision.source.path}" and "${document.source.path}".`, document));
|
|
493
|
+
else byId.set(document.id, document);
|
|
494
|
+
const pathCollision = byPath.get(document.path);
|
|
495
|
+
if (pathCollision) issues.push(collisionIssue(`Document route collision at "${document.path}" between "${pathCollision.source.path}" and "${document.source.path}".`, document, document.path));
|
|
496
|
+
else byPath.set(document.path, document);
|
|
497
|
+
const variant = localeAndKey(document.locale, document.key);
|
|
498
|
+
const variantCollision = byVariant.get(variant);
|
|
499
|
+
if (variantCollision && variantCollision.path !== pathCollision?.path) issues.push(collisionIssue(`Document locale collision at "${document.key}" between "${variantCollision.source.path}" and "${document.source.path}".`, document, document.key));
|
|
500
|
+
else byVariant.set(variant, document);
|
|
501
|
+
}
|
|
502
|
+
return issues;
|
|
503
|
+
}
|
|
504
|
+
function translationIssues(documents, configuration) {
|
|
505
|
+
const locales = configuration.documents.locales;
|
|
506
|
+
if (!locales) return [];
|
|
507
|
+
const variants = new Set(documents.map((document) => localeAndKey(document.locale, document.key)));
|
|
508
|
+
return [...new Set(documents.map((document) => document.key))].flatMap((key) => locales.locales.flatMap((locale) => variants.has(localeAndKey(locale, key)) ? [] : [{
|
|
509
|
+
code: "i18n.missing-translation",
|
|
510
|
+
key,
|
|
511
|
+
locale,
|
|
512
|
+
message: `Document "${key}" is missing its "${locale}" translation.`,
|
|
513
|
+
severity: "warning"
|
|
514
|
+
}]));
|
|
515
|
+
}
|
|
516
|
+
function collisionIssue(message, document, key) {
|
|
517
|
+
const issue = {
|
|
518
|
+
code: "document.collision",
|
|
519
|
+
message,
|
|
520
|
+
severity: "error",
|
|
521
|
+
sourcePath: document.source.path
|
|
522
|
+
};
|
|
523
|
+
return key === void 0 ? issue : Object.assign(issue, { key });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region ../document/src/internal/document-engine/component-validation.ts
|
|
528
|
+
function componentValidationMessages(definition, attributes, children) {
|
|
529
|
+
if (definition.authoring) return [];
|
|
530
|
+
const messages = [];
|
|
531
|
+
const props = new Map(definition.props.map((prop) => [prop.name, prop]));
|
|
532
|
+
for (const prop of definition.props) if (prop.required && attributes.get(prop.name) === void 0) messages.push(`Document component "${definition.name}" requires prop "${prop.name}".`);
|
|
533
|
+
for (const [name, value] of attributes) {
|
|
534
|
+
if (name === "$" || name === "class" || name === "id" || name === "style") continue;
|
|
535
|
+
const prop = props.get(name);
|
|
536
|
+
if (!prop) messages.push(`Document component "${definition.name}" does not define prop "${name}".`);
|
|
537
|
+
else if (!matchesCmsComponentPropType(value, prop.type)) messages.push(`Document component "${definition.name}" prop "${name}" must be ${article(prop.type)} ${prop.type}.`);
|
|
538
|
+
}
|
|
539
|
+
const slots = new Set(definition.slots.map((slot) => slot.name));
|
|
540
|
+
const explicitSlots = children.flatMap((node) => {
|
|
541
|
+
if (Predicate.isString(node) || node[0] !== "template") return [];
|
|
542
|
+
return [Predicate.isString(node[1].name) ? node[1].name : "default"];
|
|
543
|
+
});
|
|
544
|
+
if (children.some((node) => Predicate.isString(node) ? Boolean(node.trim()) : node[0] !== "template") && !slots.has("default")) explicitSlots.push("default");
|
|
545
|
+
for (const name of new Set(explicitSlots)) if (!slots.has(name)) messages.push(`Document component "${definition.name}" does not define slot "${name}".`);
|
|
546
|
+
return messages;
|
|
547
|
+
}
|
|
548
|
+
function article(type) {
|
|
549
|
+
return type === "array" || type === "object" ? "an" : "a";
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region ../document/src/internal/document-engine/reference-target.ts
|
|
554
|
+
function referenceTarget(tag, attributes, documentPath, mediaBaseUrl) {
|
|
555
|
+
if (tag === "a") {
|
|
556
|
+
const path = documentTarget(attributes.get("href"), documentPath);
|
|
557
|
+
return path ? {
|
|
558
|
+
kind: "document",
|
|
559
|
+
path
|
|
560
|
+
} : void 0;
|
|
561
|
+
}
|
|
562
|
+
if (tag === "img") {
|
|
563
|
+
const key = mediaKey(attributes.get("src"), mediaBaseUrl);
|
|
564
|
+
return key ? {
|
|
565
|
+
kind: "media",
|
|
566
|
+
key
|
|
567
|
+
} : void 0;
|
|
568
|
+
}
|
|
569
|
+
const normalizedTag = tag.toLocaleLowerCase();
|
|
570
|
+
return CMS_STANDARD_TAGS.has(normalizedTag) ? void 0 : {
|
|
571
|
+
kind: "component",
|
|
572
|
+
name: normalizedTag
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function targetKey(target) {
|
|
576
|
+
switch (target.kind) {
|
|
577
|
+
case "component": return `component:${target.name.toLocaleLowerCase()}`;
|
|
578
|
+
case "document": return `document:${target.path}`;
|
|
579
|
+
case "media": return `media:${target.key}`;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function mediaKey(value, mediaBaseUrl) {
|
|
583
|
+
if (!Predicate.isString(value) || mediaBaseUrl === void 0) return void 0;
|
|
584
|
+
const prefix = `${mediaBaseUrl}/`;
|
|
585
|
+
if (!value.startsWith(prefix)) return void 0;
|
|
586
|
+
const encodedKey = value.slice(prefix.length).split(/[?#]/u, 1)[0];
|
|
587
|
+
if (!encodedKey) return void 0;
|
|
588
|
+
try {
|
|
589
|
+
const segments = encodedKey.split("/").map(decodeURIComponent);
|
|
590
|
+
if (segments.some((segment) => !segment || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\") || hasControlCharacter(segment))) return;
|
|
591
|
+
const key = segments.join("/");
|
|
592
|
+
return key.startsWith("media/") ? key : void 0;
|
|
593
|
+
} catch {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function documentTarget(value, documentPath) {
|
|
598
|
+
if (!Predicate.isString(value)) return void 0;
|
|
599
|
+
const href = value.trim();
|
|
600
|
+
if (!href || href.startsWith("#") || href.startsWith("?") || href.startsWith("//") || /^[a-z][a-z\d+.-]*:/iu.test(href)) return;
|
|
601
|
+
const resolvedPath = resolveRelativePath(href.split(/[?#]/u)[0] ?? "", documentPath);
|
|
602
|
+
if ((resolvedPath.split("/").at(-1) ?? "").includes(".")) return void 0;
|
|
603
|
+
const normalized = normalizePublicPath(resolvedPath);
|
|
604
|
+
return Result.isSuccess(normalized) ? normalized.success : void 0;
|
|
605
|
+
}
|
|
606
|
+
function resolveRelativePath(href, documentPath) {
|
|
607
|
+
const normalizedHref = href.replace(/\\/gu, "/");
|
|
608
|
+
const segments = normalizedHref.startsWith("/") ? [] : documentPath.split("/").filter(Boolean).slice(0, -1);
|
|
609
|
+
for (const segment of normalizedHref.split("/")) {
|
|
610
|
+
if (!segment || segment === ".") continue;
|
|
611
|
+
if (segment === "..") segments.pop();
|
|
612
|
+
else segments.push(segment);
|
|
613
|
+
}
|
|
614
|
+
return `/${segments.join("/")}`;
|
|
615
|
+
}
|
|
616
|
+
function hasControlCharacter(value) {
|
|
617
|
+
for (const character of value) {
|
|
618
|
+
const codePoint = character.codePointAt(0);
|
|
619
|
+
if (codePoint !== void 0 && (codePoint <= 31 || codePoint === 127)) return true;
|
|
620
|
+
}
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region ../document/src/internal/document-engine/references.ts
|
|
626
|
+
const noReferences = Object.freeze([]);
|
|
627
|
+
function createDocumentReferenceIndex(documents, configuration, findByPath) {
|
|
628
|
+
const all = [];
|
|
629
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
630
|
+
const issues = /* @__PURE__ */ new Map();
|
|
631
|
+
for (const document of documents) visit(document.body.nodes, createCmsComarkRenderData(document.body), (tag, attributes, children, nodePath) => {
|
|
632
|
+
const target = referenceTarget(tag, attributes, document.path, configuration.mediaBaseUrl);
|
|
633
|
+
if (!target) return;
|
|
634
|
+
const reference = freezeReference({
|
|
635
|
+
nodePath,
|
|
636
|
+
source: {
|
|
637
|
+
documentId: document.id,
|
|
638
|
+
path: document.path,
|
|
639
|
+
sourcePath: document.source.path
|
|
640
|
+
},
|
|
641
|
+
target
|
|
642
|
+
});
|
|
643
|
+
all.push(reference);
|
|
644
|
+
const key = targetKey(target);
|
|
645
|
+
const matching = byTarget.get(key) ?? [];
|
|
646
|
+
matching.push(reference);
|
|
647
|
+
byTarget.set(key, matching);
|
|
648
|
+
if (target.kind === "document" && Option.isNone(findByPath(target.path))) addIssue(issues, document.source.path, {
|
|
649
|
+
code: "reference.document",
|
|
650
|
+
key: target.path,
|
|
651
|
+
message: `Document link "${target.path}" does not resolve.`,
|
|
652
|
+
severity: "error",
|
|
653
|
+
sourcePath: document.source.path
|
|
654
|
+
});
|
|
655
|
+
else if (target.kind === "component" && configuration.components) {
|
|
656
|
+
const definition = configuration.components.get(target.name);
|
|
657
|
+
if (!definition) addIssue(issues, document.source.path, componentIssue(document.source.path, `Document component "${tag}" is not registered.`));
|
|
658
|
+
else for (const message of componentValidationMessages(definition, attributes, children)) addIssue(issues, document.source.path, componentIssue(document.source.path, message));
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
const frozenAll = Object.freeze(all);
|
|
662
|
+
const frozenByTarget = new Map([...byTarget].map(([key, references]) => [key, Object.freeze(references)]));
|
|
663
|
+
return {
|
|
664
|
+
index: Object.freeze({
|
|
665
|
+
all: frozenAll,
|
|
666
|
+
impact: (target) => frozenByTarget.get(targetKey(target)) ?? noReferences
|
|
667
|
+
}),
|
|
668
|
+
issues
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
function visit(nodes, renderData, onElement, parentPath = []) {
|
|
672
|
+
nodes.forEach((node, index) => {
|
|
673
|
+
if (Predicate.isString(node) || node[0] === null) return;
|
|
674
|
+
const [tag, attributes, ...children] = node;
|
|
675
|
+
const nodePath = [...parentPath, index];
|
|
676
|
+
const resolved = resolveCmsComarkAttributes(attributes, renderData);
|
|
677
|
+
onElement(tag, resolved, children, nodePath);
|
|
678
|
+
visit(children, withCmsComarkProps(renderData, resolved), onElement, nodePath);
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
function freezeReference(reference) {
|
|
682
|
+
return Object.freeze({
|
|
683
|
+
nodePath: Object.freeze([...reference.nodePath]),
|
|
684
|
+
source: Object.freeze({ ...reference.source }),
|
|
685
|
+
target: Object.freeze({ ...reference.target })
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function componentIssue(sourcePath, message) {
|
|
689
|
+
return {
|
|
690
|
+
code: "reference.component",
|
|
691
|
+
message,
|
|
692
|
+
severity: "error",
|
|
693
|
+
sourcePath
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function addIssue(issues, sourcePath, issue) {
|
|
697
|
+
const sourceIssues = issues.get(sourcePath) ?? [];
|
|
698
|
+
sourceIssues.push(issue);
|
|
699
|
+
issues.set(sourcePath, sourceIssues);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
//#endregion
|
|
703
|
+
//#region ../document/src/internal/document-engine/catalog.ts
|
|
704
|
+
function createDocumentCatalog(configuration, input) {
|
|
705
|
+
const documents = [...input].sort((left, right) => left.source.path.localeCompare(right.source.path));
|
|
706
|
+
const collisions = collisionIssues(documents);
|
|
707
|
+
if (collisions.length > 0) return Result.fail(new DocumentCompilationFailed({ issues: collisions }));
|
|
708
|
+
const lookup = createCatalogLookup(documents, configuration);
|
|
709
|
+
const references = createDocumentReferenceIndex(documents, configuration, lookup.findByPath);
|
|
710
|
+
const withReferenceIssues = documents.map((document) => {
|
|
711
|
+
const additional = references.issues.get(document.source.path) ?? [];
|
|
712
|
+
return additional.length === 0 ? document : CompiledDocument.make(Object.assign({}, document, { issues: [...document.issues, ...additional] }));
|
|
713
|
+
});
|
|
714
|
+
return Result.succeed({
|
|
715
|
+
documents: withReferenceIssues,
|
|
716
|
+
issues: [...withReferenceIssues.flatMap((document) => document.issues), ...translationIssues(withReferenceIssues, configuration)],
|
|
717
|
+
...createCatalogLookup(withReferenceIssues, configuration),
|
|
718
|
+
references: references.index
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region ../document/src/internal/document-engine/configuration.ts
|
|
724
|
+
function resolveDocumentEngineConfiguration(documents, options) {
|
|
725
|
+
if (documents.types.length === 0) return invalid("Documents must define at least one collection.", "types");
|
|
726
|
+
const collections = /* @__PURE__ */ new Set();
|
|
727
|
+
const directories = /* @__PURE__ */ new Map();
|
|
728
|
+
const resolved = [];
|
|
729
|
+
for (const definition of documents.types) {
|
|
730
|
+
if (collections.has(definition.collection)) return invalid(`Document collection "${definition.collection}" is defined more than once.`, "types");
|
|
731
|
+
collections.add(definition.collection);
|
|
732
|
+
const existing = directories.get(definition.directory);
|
|
733
|
+
if (existing) return invalid(`Document collections "${existing}" and "${definition.collection}" use the same directory "${definition.directory}".`, "types");
|
|
734
|
+
directories.set(definition.directory, definition.collection);
|
|
735
|
+
const addressResult = parseDocumentAddress(definition.addressPattern);
|
|
736
|
+
if (Result.isFailure(addressResult)) return invalid(addressResult.failure.message, addressResult.failure.field);
|
|
737
|
+
if (definition.hierarchy && !addressResult.success.tokens.some((token) => token.catchAll)) return invalid(`Document collection "${definition.collection}" can only enable hierarchy with a catch-all address.`, "types");
|
|
738
|
+
const schema = options.documentModel.types[definition.collection];
|
|
739
|
+
if (!schema) return invalid(`Document collection "${definition.collection}" has no executable schema.`, "documentModel");
|
|
740
|
+
resolved.push({
|
|
741
|
+
address: addressResult.success,
|
|
742
|
+
definition,
|
|
743
|
+
schema
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
const locales = documents.locales;
|
|
747
|
+
if (locales) {
|
|
748
|
+
if (new Set(locales.locales).size !== locales.locales.length) return invalid("Document locales must be unique.", "locales.locales");
|
|
749
|
+
if (!locales.locales.includes(locales.defaultLocale)) return invalid(`Default locale "${locales.defaultLocale}" is not configured.`, "locales.defaultLocale");
|
|
750
|
+
if (locales.fallbackLocale && !locales.locales.includes(locales.fallbackLocale)) return invalid(`Fallback locale "${locales.fallbackLocale}" is not configured.`, "locales.fallbackLocale");
|
|
751
|
+
}
|
|
752
|
+
let components;
|
|
753
|
+
if (options.components !== void 0) {
|
|
754
|
+
const result = resolveComponents(options.components);
|
|
755
|
+
if (Result.isFailure(result)) return Result.fail(result.failure);
|
|
756
|
+
components = result.success;
|
|
757
|
+
}
|
|
758
|
+
const mediaBaseUrl = options.mediaBaseUrl?.replace(/\/+$/u, "");
|
|
759
|
+
resolved.sort((left, right) => directoryDepth(right.definition.directory) - directoryDepth(left.definition.directory));
|
|
760
|
+
const configuration = {
|
|
761
|
+
documents,
|
|
762
|
+
types: resolved
|
|
763
|
+
};
|
|
764
|
+
if (components !== void 0) configuration.components = components;
|
|
765
|
+
if (mediaBaseUrl !== void 0) configuration.mediaBaseUrl = mediaBaseUrl;
|
|
766
|
+
return Result.succeed(configuration);
|
|
767
|
+
}
|
|
768
|
+
function resolveComponents(definitions) {
|
|
769
|
+
const components = /* @__PURE__ */ new Map();
|
|
770
|
+
for (const component of definitions) {
|
|
771
|
+
const name = component.name.toLocaleLowerCase();
|
|
772
|
+
if (components.has(name)) return invalid(`Document component "${component.name}" is defined more than once.`, "components");
|
|
773
|
+
components.set(name, component);
|
|
774
|
+
}
|
|
775
|
+
return Result.succeed(components);
|
|
776
|
+
}
|
|
777
|
+
function directoryDepth(directory) {
|
|
778
|
+
return directory === "." ? 0 : directory.split("/").length;
|
|
779
|
+
}
|
|
780
|
+
function invalid(message, field) {
|
|
781
|
+
return Result.fail(new DocumentEngineConfigurationInvalid(field === void 0 ? { message } : {
|
|
782
|
+
field,
|
|
783
|
+
message
|
|
784
|
+
}));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
//#endregion
|
|
788
|
+
//#region ../document/src/DocumentEngine.ts
|
|
789
|
+
const isDocumentParseFailed = Schema.is(DocumentParseFailed);
|
|
790
|
+
/** Resolves installation configuration once and rejects an unusable engine before any Source is compiled. */
|
|
791
|
+
const createDocumentEngine = Effect.fn("DocumentEngine.make")(function* (documents, options) {
|
|
792
|
+
const configuration = yield* Effect.fromResult(resolveDocumentEngineConfiguration(documents, options));
|
|
793
|
+
const parseMarkdown = createDocumentParser();
|
|
794
|
+
const compilations = /* @__PURE__ */ new WeakMap();
|
|
795
|
+
const compile = Effect.fn("DocumentEngine.compile")((source) => compileCompiledDocument(configuration, parseMarkdown, source));
|
|
796
|
+
const compileAll = Effect.fn("DocumentEngine.compileAll")(function* (input) {
|
|
797
|
+
const previous = input.previous ? compilations.get(input.previous) : void 0;
|
|
798
|
+
const compiled = yield* Effect.forEach(input.sources, (source) => {
|
|
799
|
+
const cached = previous?.get(source.documentId);
|
|
800
|
+
return cached && sameCompilationSource(cached.source, source) ? Effect.succeed(cached.document) : compile(source);
|
|
801
|
+
});
|
|
802
|
+
const catalog = yield* Effect.fromResult(createDocumentCatalog(configuration, compiled));
|
|
803
|
+
compilations.set(catalog, new Map(input.sources.map((source, index) => {
|
|
804
|
+
const document = compiled[index];
|
|
805
|
+
if (!document) throw new Error(`Compiled document "${source.documentId}" is missing.`);
|
|
806
|
+
return [source.documentId, {
|
|
807
|
+
document,
|
|
808
|
+
source
|
|
809
|
+
}];
|
|
810
|
+
})));
|
|
811
|
+
return catalog;
|
|
812
|
+
});
|
|
813
|
+
const validate = Effect.fn("DocumentEngine.validate")(function* (source) {
|
|
814
|
+
const result = yield* Effect.result(compile(source));
|
|
815
|
+
if (Result.isSuccess(result)) {
|
|
816
|
+
const document = result.success;
|
|
817
|
+
return DocumentValidationResult.make({
|
|
818
|
+
valid: !document.issues.some((issue) => issue.severity === "error"),
|
|
819
|
+
document,
|
|
820
|
+
issues: document.issues
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
const error = result.failure;
|
|
824
|
+
const issues = isDocumentParseFailed(error) ? [{
|
|
825
|
+
code: "markdown.parse",
|
|
826
|
+
message: errorMessage(error.cause),
|
|
827
|
+
severity: "error",
|
|
828
|
+
sourcePath: error.sourcePath
|
|
829
|
+
}] : error.issues;
|
|
830
|
+
return DocumentValidationResult.make({
|
|
831
|
+
valid: false,
|
|
832
|
+
issues
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
compile,
|
|
837
|
+
compileAll,
|
|
838
|
+
fingerprint: DOCUMENT_ENGINE_FINGERPRINT,
|
|
839
|
+
validate
|
|
840
|
+
};
|
|
841
|
+
});
|
|
842
|
+
function sameCompilationSource(left, right) {
|
|
843
|
+
return left.documentId === right.documentId && left.draft === right.draft && left.markdown === right.markdown && left.sourcePath === right.sourcePath;
|
|
844
|
+
}
|
|
845
|
+
function errorMessage(cause) {
|
|
846
|
+
return cause instanceof Error && cause.message ? cause.message : "Markdown could not be parsed.";
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
//#endregion
|
|
850
|
+
export { normalizePublicPath as a, normalizeDocumentRelativePath as i, DocumentCompilationFailed as n, parseDocumentAddress as o, localizeDocumentPath as r, resolveDocumentAddress as s, createDocumentEngine as t };
|
|
851
|
+
//# sourceMappingURL=DocumentEngine-Da0kWotL.js.map
|