@riebeckite/core 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/README_ja.md +9 -8
- package/dist/client.d.ts +2 -2
- package/dist/client.js +142 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.js +2149 -439
- package/dist/src/content/affected_content.d.ts +1 -1
- package/dist/src/content/content_build_coordinator.d.ts +11 -3
- package/dist/src/content/content_build_state.d.ts +13 -2
- package/dist/src/content/content_dependency_tracker.d.ts +18 -0
- package/dist/src/content/content_entry_reader.d.ts +2 -0
- package/dist/src/content/content_index_builder.d.ts +2 -0
- package/dist/src/content/content_location_resolver.d.ts +7 -0
- package/dist/src/content/content_manager.d.ts +21 -0
- package/dist/src/content/content_persistent_cache.d.ts +43 -0
- package/dist/src/content/file_system_content_source.d.ts +2 -0
- package/dist/src/content/folder_entry.d.ts +36 -0
- package/dist/src/content/graph_layout.d.ts +20 -0
- package/dist/src/content/manifest_builder.d.ts +2 -1
- package/dist/src/content/output_dependency.d.ts +34 -0
- package/dist/src/content/publishing.d.ts +15 -0
- package/dist/src/pipeline.d.ts +10 -0
- package/dist/src/plugin/plugin_runtime.d.ts +7 -1
- package/dist/src/types/content_manifest.d.ts +19 -1
- package/dist/src/types/generated_output.d.ts +9 -0
- package/dist/src/types/navigation_config.d.ts +10 -0
- package/dist/src/types/output_dependency.d.ts +14 -0
- package/dist/src/types/plugin.d.ts +19 -0
- package/dist/src/types/plugin_page.d.ts +6 -0
- package/dist/src/types/plugin_pipeline.d.ts +1 -0
- package/dist/src/types/post_content.d.ts +2 -0
- package/dist/src/types/resolved_riebeckite_config.d.ts +6 -0
- package/dist/src/types/riebeckite_config.d.ts +6 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18,11 +18,55 @@ function validateConfigIssues(config) {
|
|
|
18
18
|
return [{ path: "config", message: "Expected an object." }];
|
|
19
19
|
}
|
|
20
20
|
validateSite(value.site, issues);
|
|
21
|
+
validateNavigation(value.navigation, issues);
|
|
21
22
|
validateContent(value.content, issues);
|
|
22
23
|
validateTheme(value.theme, issues);
|
|
23
24
|
validatePlugins(value.plugins, issues);
|
|
24
25
|
return issues;
|
|
25
26
|
}
|
|
27
|
+
function validateNavigation(value, issues) {
|
|
28
|
+
if (value === void 0) return;
|
|
29
|
+
if (!isRecord(value)) {
|
|
30
|
+
issues.push({ path: "navigation", message: "Expected an object." });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
validateNavigationItems(value.header, "navigation.header", issues, /* @__PURE__ */ new Set());
|
|
34
|
+
validateNavigationItems(value.footer, "navigation.footer", issues, /* @__PURE__ */ new Set());
|
|
35
|
+
}
|
|
36
|
+
function validateNavigationItems(value, path5, issues, ancestors) {
|
|
37
|
+
if (value === void 0) return;
|
|
38
|
+
if (!Array.isArray(value)) {
|
|
39
|
+
issues.push({ path: path5, message: "Expected an array." });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const [index, item] of value.entries()) {
|
|
43
|
+
const itemPath = `${path5}[${index}]`;
|
|
44
|
+
if (!isRecord(item)) {
|
|
45
|
+
issues.push({ path: itemPath, message: "Expected an object." });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (ancestors.has(item)) {
|
|
49
|
+
issues.push({
|
|
50
|
+
path: itemPath,
|
|
51
|
+
message: "Navigation children must not be recursive."
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
validateRequiredString(item.label, `${itemPath}.label`, issues);
|
|
56
|
+
validateRequiredString(item.href, `${itemPath}.href`, issues);
|
|
57
|
+
validateOptionalBoolean(item.external, `${itemPath}.external`, issues);
|
|
58
|
+
if (item.children !== void 0) {
|
|
59
|
+
const nextAncestors = new Set(ancestors);
|
|
60
|
+
nextAncestors.add(item);
|
|
61
|
+
validateNavigationItems(
|
|
62
|
+
item.children,
|
|
63
|
+
`${itemPath}.children`,
|
|
64
|
+
issues,
|
|
65
|
+
nextAncestors
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
26
70
|
function validateSite(value, issues) {
|
|
27
71
|
if (!isRecord(value)) {
|
|
28
72
|
issues.push({ path: "site", message: "Expected an object." });
|
|
@@ -109,41 +153,41 @@ function validateTheme(value, issues) {
|
|
|
109
153
|
}
|
|
110
154
|
}
|
|
111
155
|
}
|
|
112
|
-
function validateThemeConfig(value,
|
|
113
|
-
validateOptionalString(value.name, `${
|
|
156
|
+
function validateThemeConfig(value, path5, issues) {
|
|
157
|
+
validateOptionalString(value.name, `${path5}.name`, issues);
|
|
114
158
|
validateOneOf(
|
|
115
159
|
value.colorMode,
|
|
116
|
-
`${
|
|
160
|
+
`${path5}.colorMode`,
|
|
117
161
|
["light", "dark", "system"],
|
|
118
162
|
issues
|
|
119
163
|
);
|
|
120
164
|
validateOneOf(
|
|
121
165
|
value.typography,
|
|
122
|
-
`${
|
|
166
|
+
`${path5}.typography`,
|
|
123
167
|
["system", "serif", "sans"],
|
|
124
168
|
issues
|
|
125
169
|
);
|
|
126
170
|
validateOneOf(
|
|
127
171
|
value.articleLayout,
|
|
128
|
-
`${
|
|
172
|
+
`${path5}.articleLayout`,
|
|
129
173
|
["article", "sidebar", "full-width"],
|
|
130
174
|
issues
|
|
131
175
|
);
|
|
132
|
-
validateStringArray(value.userCss, `${
|
|
176
|
+
validateStringArray(value.userCss, `${path5}.userCss`, issues);
|
|
133
177
|
if (value.attributes !== void 0 && !isRecord(value.attributes)) {
|
|
134
|
-
issues.push({ path: `${
|
|
178
|
+
issues.push({ path: `${path5}.attributes`, message: "Expected an object." });
|
|
135
179
|
}
|
|
136
180
|
}
|
|
137
|
-
function validateThemeStyles(value,
|
|
181
|
+
function validateThemeStyles(value, path5, issues) {
|
|
138
182
|
if (value === void 0) return;
|
|
139
183
|
if (!Array.isArray(value)) {
|
|
140
|
-
issues.push({ path:
|
|
184
|
+
issues.push({ path: path5, message: "Expected an array." });
|
|
141
185
|
return;
|
|
142
186
|
}
|
|
143
187
|
for (const [index, style] of value.entries()) {
|
|
144
188
|
if (!isRecord(style) || !isNonEmptyString(style.moduleSpecifier)) {
|
|
145
189
|
issues.push({
|
|
146
|
-
path: `${
|
|
190
|
+
path: `${path5}[${index}].moduleSpecifier`,
|
|
147
191
|
message: "Expected a non-empty string."
|
|
148
192
|
});
|
|
149
193
|
}
|
|
@@ -163,69 +207,69 @@ function validatePlugins(value, issues) {
|
|
|
163
207
|
continue;
|
|
164
208
|
}
|
|
165
209
|
const plugin = input;
|
|
166
|
-
const
|
|
210
|
+
const path5 = `plugins[${index}]`;
|
|
167
211
|
if (!isNonEmptyString(plugin.name)) {
|
|
168
212
|
issues.push({
|
|
169
|
-
path: `${
|
|
213
|
+
path: `${path5}.name`,
|
|
170
214
|
message: "Expected a non-empty string."
|
|
171
215
|
});
|
|
172
216
|
} else if (plugin.enabled !== false) {
|
|
173
217
|
const duplicateIndex = names.get(plugin.name);
|
|
174
218
|
if (duplicateIndex !== void 0) {
|
|
175
219
|
issues.push({
|
|
176
|
-
path: `${
|
|
220
|
+
path: `${path5}.name`,
|
|
177
221
|
message: `Duplicate plugin name "${plugin.name}" (also plugins[${duplicateIndex}]).`
|
|
178
222
|
});
|
|
179
223
|
} else {
|
|
180
224
|
names.set(plugin.name, index);
|
|
181
225
|
}
|
|
182
226
|
}
|
|
183
|
-
validateOptionalBoolean(plugin.enabled, `${
|
|
184
|
-
validateCapabilityMetadata(plugin,
|
|
185
|
-
if (plugin.enabled !== false) validatePluginOptions(plugin,
|
|
227
|
+
validateOptionalBoolean(plugin.enabled, `${path5}.enabled`, issues);
|
|
228
|
+
validateCapabilityMetadata(plugin, path5, issues);
|
|
229
|
+
if (plugin.enabled !== false) validatePluginOptions(plugin, path5, issues);
|
|
186
230
|
}
|
|
187
231
|
}
|
|
188
|
-
function validateCapabilityMetadata(plugin,
|
|
189
|
-
validateStringArray(plugin.provides, `${
|
|
190
|
-
validateStringArray(plugin.requires, `${
|
|
191
|
-
validateStringArray(plugin.optional, `${
|
|
232
|
+
function validateCapabilityMetadata(plugin, path5, issues) {
|
|
233
|
+
validateStringArray(plugin.provides, `${path5}.provides`, issues, true);
|
|
234
|
+
validateStringArray(plugin.requires, `${path5}.requires`, issues, true);
|
|
235
|
+
validateStringArray(plugin.optional, `${path5}.optional`, issues, true);
|
|
192
236
|
}
|
|
193
|
-
function validatePluginOptions(plugin,
|
|
237
|
+
function validatePluginOptions(plugin, path5, issues) {
|
|
194
238
|
if (!plugin.validateOptions) return;
|
|
195
239
|
try {
|
|
196
240
|
const optionIssues = plugin.validateOptions(plugin.options) ?? [];
|
|
197
241
|
for (const issue of optionIssues) {
|
|
198
242
|
issues.push({
|
|
199
|
-
path: issue.path ? `${
|
|
243
|
+
path: issue.path ? `${path5}.options.${issue.path}` : `${path5}.options`,
|
|
200
244
|
message: issue.message
|
|
201
245
|
});
|
|
202
246
|
}
|
|
203
247
|
} catch (error) {
|
|
204
248
|
issues.push({
|
|
205
|
-
path: `${
|
|
249
|
+
path: `${path5}.options`,
|
|
206
250
|
message: `Validator failed: ${toErrorMessage(error)}`
|
|
207
251
|
});
|
|
208
252
|
}
|
|
209
253
|
}
|
|
210
|
-
function validateRequiredString(value,
|
|
254
|
+
function validateRequiredString(value, path5, issues) {
|
|
211
255
|
if (!isNonEmptyString(value)) {
|
|
212
|
-
issues.push({ path:
|
|
256
|
+
issues.push({ path: path5, message: "Expected a non-empty string." });
|
|
213
257
|
}
|
|
214
258
|
}
|
|
215
|
-
function validateOptionalString(value,
|
|
259
|
+
function validateOptionalString(value, path5, issues) {
|
|
216
260
|
if (value !== void 0 && typeof value !== "string") {
|
|
217
|
-
issues.push({ path:
|
|
261
|
+
issues.push({ path: path5, message: "Expected a string." });
|
|
218
262
|
}
|
|
219
263
|
}
|
|
220
|
-
function validateOptionalBoolean(value,
|
|
264
|
+
function validateOptionalBoolean(value, path5, issues) {
|
|
221
265
|
if (value !== void 0 && typeof value !== "boolean") {
|
|
222
|
-
issues.push({ path:
|
|
266
|
+
issues.push({ path: path5, message: "Expected a boolean." });
|
|
223
267
|
}
|
|
224
268
|
}
|
|
225
|
-
function validateAbsoluteUrl(value,
|
|
269
|
+
function validateAbsoluteUrl(value, path5, issues) {
|
|
226
270
|
if (value === void 0 || value === "") return;
|
|
227
271
|
if (typeof value !== "string") {
|
|
228
|
-
issues.push({ path:
|
|
272
|
+
issues.push({ path: path5, message: "Expected an absolute URL." });
|
|
229
273
|
return;
|
|
230
274
|
}
|
|
231
275
|
try {
|
|
@@ -233,27 +277,27 @@ function validateAbsoluteUrl(value, path4, issues) {
|
|
|
233
277
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
234
278
|
throw new Error();
|
|
235
279
|
} catch {
|
|
236
|
-
issues.push({ path:
|
|
280
|
+
issues.push({ path: path5, message: "Expected an absolute HTTP(S) URL." });
|
|
237
281
|
}
|
|
238
282
|
}
|
|
239
|
-
function validateStringArray(value,
|
|
283
|
+
function validateStringArray(value, path5, issues, nonEmpty = false) {
|
|
240
284
|
if (value === void 0) return;
|
|
241
285
|
if (!Array.isArray(value)) {
|
|
242
|
-
issues.push({ path:
|
|
286
|
+
issues.push({ path: path5, message: "Expected an array." });
|
|
243
287
|
return;
|
|
244
288
|
}
|
|
245
289
|
for (const [index, item] of value.entries()) {
|
|
246
290
|
if (typeof item !== "string" || nonEmpty && !item.trim()) {
|
|
247
291
|
issues.push({
|
|
248
|
-
path: `${
|
|
292
|
+
path: `${path5}[${index}]`,
|
|
249
293
|
message: nonEmpty ? "Expected a non-empty string." : "Expected a string."
|
|
250
294
|
});
|
|
251
295
|
}
|
|
252
296
|
}
|
|
253
297
|
}
|
|
254
|
-
function validateOneOf(value,
|
|
298
|
+
function validateOneOf(value, path5, choices, issues) {
|
|
255
299
|
if (value !== void 0 && (typeof value !== "string" || !choices.includes(value))) {
|
|
256
|
-
issues.push({ path:
|
|
300
|
+
issues.push({ path: path5, message: `Expected one of: ${choices.join(", ")}.` });
|
|
257
301
|
}
|
|
258
302
|
}
|
|
259
303
|
function formatValidationIssues(issues) {
|
|
@@ -538,8 +582,21 @@ function definePlugin(plugin) {
|
|
|
538
582
|
}
|
|
539
583
|
function resolvePlugins(plugins = []) {
|
|
540
584
|
const orderedPlugins = plugins.filter((plugin) => Boolean(plugin)).filter((plugin) => plugin.enabled !== false).toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
585
|
+
assertUniquePluginNames(orderedPlugins);
|
|
541
586
|
return resolvePluginDependencies(orderedPlugins);
|
|
542
587
|
}
|
|
588
|
+
function assertUniquePluginNames(plugins) {
|
|
589
|
+
const owners = /* @__PURE__ */ new Map();
|
|
590
|
+
plugins.forEach((plugin, index) => {
|
|
591
|
+
const previous = owners.get(plugin.name);
|
|
592
|
+
if (previous !== void 0) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`Plugin name "${plugin.name}" is used by both plugins[${previous}] and plugins[${index}]`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
owners.set(plugin.name, index);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
543
600
|
function getResolvedPluginMetadata(plugins) {
|
|
544
601
|
return plugins.map((plugin) => ({
|
|
545
602
|
name: plugin.name,
|
|
@@ -612,6 +669,10 @@ function resolveConfig(config) {
|
|
|
612
669
|
language: config.site?.feed?.language ?? config.site?.locale ?? "en"
|
|
613
670
|
}
|
|
614
671
|
},
|
|
672
|
+
navigation: {
|
|
673
|
+
header: config.navigation?.header ?? [],
|
|
674
|
+
footer: config.navigation?.footer ?? []
|
|
675
|
+
},
|
|
615
676
|
content: {
|
|
616
677
|
directory: config.content?.directory ?? "../../content",
|
|
617
678
|
source: config.content?.source,
|
|
@@ -626,7 +687,11 @@ function resolveConfig(config) {
|
|
|
626
687
|
}
|
|
627
688
|
},
|
|
628
689
|
theme,
|
|
629
|
-
plugins: resolvePlugins(config.plugins)
|
|
690
|
+
plugins: resolvePlugins(config.plugins),
|
|
691
|
+
cache: {
|
|
692
|
+
enabled: config.cache?.enabled ?? true,
|
|
693
|
+
directory: config.cache?.directory ?? ""
|
|
694
|
+
}
|
|
630
695
|
};
|
|
631
696
|
}
|
|
632
697
|
function resolveThemeConfig(themeInput) {
|
|
@@ -659,6 +724,8 @@ function sanitizeThemeAttributes(attributes) {
|
|
|
659
724
|
);
|
|
660
725
|
}
|
|
661
726
|
function isPublished(config, frontmatter) {
|
|
727
|
+
if (frontmatter?.visibility === "unlisted") return true;
|
|
728
|
+
if (frontmatter?.visibility === "draft") return false;
|
|
662
729
|
return isPublishable(config.content.filters.publishStrategy, frontmatter);
|
|
663
730
|
}
|
|
664
731
|
function isExcluded(patterns, relativePath) {
|
|
@@ -721,9 +788,9 @@ var configDeprecations = [];
|
|
|
721
788
|
function collectConfigDeprecationDiagnostics(config, notices = configDeprecations) {
|
|
722
789
|
return notices.filter((notice) => hasConfigPath(config, notice.path)).map(createDeprecationDiagnostic);
|
|
723
790
|
}
|
|
724
|
-
function hasConfigPath(config,
|
|
791
|
+
function hasConfigPath(config, path5) {
|
|
725
792
|
let value = config;
|
|
726
|
-
for (const segment of
|
|
793
|
+
for (const segment of path5.split(".")) {
|
|
727
794
|
if (!isRecord2(value) || !(segment in value)) return false;
|
|
728
795
|
value = value[segment];
|
|
729
796
|
}
|
|
@@ -786,7 +853,7 @@ import fs from "node:fs/promises";
|
|
|
786
853
|
import path from "node:path";
|
|
787
854
|
|
|
788
855
|
// src/content/content_build_state.ts
|
|
789
|
-
var CONTENT_BUILD_STATE_VERSION =
|
|
856
|
+
var CONTENT_BUILD_STATE_VERSION = 6;
|
|
790
857
|
var CONTENT_BUILD_STATE_DIRECTORY = ".riebeckite";
|
|
791
858
|
var CONTENT_BUILD_STATE_EXCLUDE = `${CONTENT_BUILD_STATE_DIRECTORY}/**`;
|
|
792
859
|
|
|
@@ -826,12 +893,12 @@ async function readContentBuildStateStatus(statePath) {
|
|
|
826
893
|
entryCount: Object.keys(parsed.entries).length
|
|
827
894
|
};
|
|
828
895
|
}
|
|
829
|
-
async function saveContentBuildState(statePath,
|
|
896
|
+
async function saveContentBuildState(statePath, state2) {
|
|
830
897
|
const directory = path.dirname(statePath);
|
|
831
898
|
const temporaryPath = `${statePath}.${randomUUID()}.tmp`;
|
|
832
899
|
await fs.mkdir(directory, { recursive: true });
|
|
833
900
|
try {
|
|
834
|
-
await fs.writeFile(temporaryPath, JSON.stringify(
|
|
901
|
+
await fs.writeFile(temporaryPath, JSON.stringify(state2), "utf8");
|
|
835
902
|
await fs.rename(temporaryPath, statePath);
|
|
836
903
|
} catch (error) {
|
|
837
904
|
await fs.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
@@ -853,15 +920,33 @@ function isContentBuildState(value) {
|
|
|
853
920
|
return isRecord3(value) && value.version === CONTENT_BUILD_STATE_VERSION && isContentBuildStateShape(value);
|
|
854
921
|
}
|
|
855
922
|
function isContentBuildStateShape(value) {
|
|
856
|
-
if (!isRecord3(value.entries) || !isRecord3(value.contentIndex))
|
|
923
|
+
if (!isRecord3(value.entries) || !isRecord3(value.contentIndex) || value.outputs !== void 0 && !isOutputDescriptors(value.outputs)) {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
857
926
|
return Object.values(value.entries).every(
|
|
858
|
-
(entry) => isRecord3(entry) && typeof entry.fingerprint === "string" && entry.fingerprint.length > 0 && Array.isArray(entry.dependencies) && entry.dependencies.every(
|
|
859
|
-
(dependency) => typeof dependency === "string"
|
|
927
|
+
(entry) => isRecord3(entry) && typeof entry.fingerprint === "string" && entry.fingerprint.length > 0 && Array.isArray(entry.aliases) && entry.aliases.every((alias) => typeof alias === "string") && Array.isArray(entry.dependencies) && entry.dependencies.every(
|
|
928
|
+
(dependency) => isRecord3(dependency) && (dependency.kind === "content" || dependency.kind === "file") && typeof dependency.id === "string"
|
|
860
929
|
)
|
|
861
930
|
) && Object.values(value.contentIndex).every(
|
|
862
931
|
(entry) => typeof entry === "string"
|
|
863
932
|
);
|
|
864
933
|
}
|
|
934
|
+
function isOutputDescriptors(value) {
|
|
935
|
+
return Array.isArray(value) && value.every(
|
|
936
|
+
(output) => isRecord3(output) && (output.kind === "content" || output.kind === "redirect" || output.kind === "plugin-page" || output.kind === "generated") && isOutputPath(output.path) && typeof output.producer === "string" && Array.isArray(output.dependencies) && output.dependencies.every(isOutputDependency)
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function isOutputPath(value) {
|
|
940
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/")) {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
return value.split("/").every((segment) => segment !== "." && segment !== "..");
|
|
944
|
+
}
|
|
945
|
+
function isOutputDependency(value) {
|
|
946
|
+
if (!isRecord3(value) || typeof value.type !== "string") return false;
|
|
947
|
+
if (value.type === "global" || value.type === "unknown") return true;
|
|
948
|
+
return value.type === "content" && typeof value.slug === "string" || value.type === "tag" && typeof value.tag === "string" || value.type === "folder" && typeof value.folder === "string";
|
|
949
|
+
}
|
|
865
950
|
function isRecord3(value) {
|
|
866
951
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
867
952
|
}
|
|
@@ -1173,9 +1258,9 @@ function buildContentCollections(entries, definitions) {
|
|
|
1173
1258
|
function normalizePageSize(value) {
|
|
1174
1259
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
1175
1260
|
}
|
|
1176
|
-
function withPage(
|
|
1177
|
-
if (index === 0) return
|
|
1178
|
-
const base =
|
|
1261
|
+
function withPage(path5, index) {
|
|
1262
|
+
if (index === 0) return path5;
|
|
1263
|
+
const base = path5 === "/" ? "" : path5;
|
|
1179
1264
|
return normalizePath(`${base}/page/${index + 1}`);
|
|
1180
1265
|
}
|
|
1181
1266
|
function defaultPath(context) {
|
|
@@ -1203,12 +1288,32 @@ function normalizePath(value) {
|
|
|
1203
1288
|
// src/content/content_fingerprint.ts
|
|
1204
1289
|
import { createHash } from "node:crypto";
|
|
1205
1290
|
async function fingerprintContentEntries(entries, read) {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1291
|
+
const sortedEntries = [...entries].sort(
|
|
1292
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
1293
|
+
);
|
|
1294
|
+
return await mapConcurrent(sortedEntries, 64, async (entry) => ({
|
|
1295
|
+
entry,
|
|
1296
|
+
fingerprint: await fingerprintContentEntry(entry, read)
|
|
1297
|
+
}));
|
|
1298
|
+
}
|
|
1299
|
+
async function mapConcurrent(values, concurrency, map) {
|
|
1300
|
+
const results = new Array(values.length);
|
|
1301
|
+
let nextIndex = 0;
|
|
1302
|
+
async function worker() {
|
|
1303
|
+
while (nextIndex < values.length) {
|
|
1304
|
+
const index = nextIndex;
|
|
1305
|
+
nextIndex += 1;
|
|
1306
|
+
const value = values[index];
|
|
1307
|
+
if (value !== void 0) results[index] = await map(value);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
await Promise.all(
|
|
1311
|
+
Array.from(
|
|
1312
|
+
{ length: Math.min(concurrency, values.length) },
|
|
1313
|
+
() => worker()
|
|
1314
|
+
)
|
|
1211
1315
|
);
|
|
1316
|
+
return results;
|
|
1212
1317
|
}
|
|
1213
1318
|
async function fingerprintContentEntry(entry, read) {
|
|
1214
1319
|
if (entry.metadata?.hash) return `hash:${entry.metadata.hash}`;
|
|
@@ -1273,6 +1378,10 @@ function resolveDefaultContentLocation(content) {
|
|
|
1273
1378
|
};
|
|
1274
1379
|
}
|
|
1275
1380
|
|
|
1381
|
+
// src/content/content_manager.ts
|
|
1382
|
+
import { VFile as VFile2 } from "vfile";
|
|
1383
|
+
import { matter as matter3 } from "vfile-matter";
|
|
1384
|
+
|
|
1276
1385
|
// src/observability.ts
|
|
1277
1386
|
var NoopLogger = class {
|
|
1278
1387
|
debug() {
|
|
@@ -1414,12 +1523,334 @@ import remarkMath from "remark-math";
|
|
|
1414
1523
|
import remarkParse from "remark-parse";
|
|
1415
1524
|
import remarkRehype from "remark-rehype";
|
|
1416
1525
|
import { unified } from "unified";
|
|
1417
|
-
import { matter } from "vfile-matter";
|
|
1526
|
+
import { matter as matter2 } from "vfile-matter";
|
|
1418
1527
|
|
|
1419
|
-
// src/
|
|
1420
|
-
import { createHash as createHash2
|
|
1528
|
+
// src/content/content_dependency_tracker.ts
|
|
1529
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1530
|
+
function createContentDependencyTracker(source) {
|
|
1531
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
1532
|
+
const record = (identity, content) => {
|
|
1533
|
+
const normalized = normalizeDependencyIdentity(identity);
|
|
1534
|
+
dependencies.set(dependencyKey(normalized), {
|
|
1535
|
+
...normalized,
|
|
1536
|
+
fingerprint: fingerprintContent(content)
|
|
1537
|
+
});
|
|
1538
|
+
};
|
|
1539
|
+
return {
|
|
1540
|
+
contentSource: {
|
|
1541
|
+
scan: () => source.scan(),
|
|
1542
|
+
async read(entry) {
|
|
1543
|
+
const content = await source.read(entry);
|
|
1544
|
+
record({ kind: "file", id: entry.path }, content);
|
|
1545
|
+
return content;
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
async readContent(slug, read) {
|
|
1549
|
+
const content = await read();
|
|
1550
|
+
record({ kind: "content", id: slug }, content);
|
|
1551
|
+
return content;
|
|
1552
|
+
},
|
|
1553
|
+
recordLinkResolution(id, value) {
|
|
1554
|
+
record({ kind: "link", id }, value);
|
|
1555
|
+
},
|
|
1556
|
+
dependencies: () => [...dependencies.values()].toSorted(
|
|
1557
|
+
(left, right) => dependencyKey(left).localeCompare(dependencyKey(right))
|
|
1558
|
+
)
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
function fingerprintContent(content) {
|
|
1562
|
+
return `sha256:${createHash2("sha256").update(typeof content === "string" ? content : content).digest("hex")}`;
|
|
1563
|
+
}
|
|
1564
|
+
function normalizeDependencyIdentity(dependency) {
|
|
1565
|
+
return {
|
|
1566
|
+
...dependency,
|
|
1567
|
+
id: dependency.kind === "file" ? dependency.id.replace(/\\/g, "/") : dependency.id
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
function dependencyKey(dependency) {
|
|
1571
|
+
const normalized = normalizeDependencyIdentity(dependency);
|
|
1572
|
+
return `${normalized.kind}:${normalized.id}`;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/content/content_persistent_cache.ts
|
|
1576
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1421
1577
|
import fs2 from "node:fs/promises";
|
|
1578
|
+
import os from "node:os";
|
|
1422
1579
|
import path2 from "node:path";
|
|
1580
|
+
import { VFile } from "vfile";
|
|
1581
|
+
import { matter } from "vfile-matter";
|
|
1582
|
+
|
|
1583
|
+
// src/plugin/plugin_memo.ts
|
|
1584
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1585
|
+
function createPluginMemo(cache, options = {}) {
|
|
1586
|
+
return {
|
|
1587
|
+
async memo(key, dependencies, compute) {
|
|
1588
|
+
const memoKey = `${key}:${hash(stableStringify(dependencies))}`;
|
|
1589
|
+
try {
|
|
1590
|
+
const cached = await cache.get(memoKey);
|
|
1591
|
+
if (cached !== void 0) return cached;
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
warn(
|
|
1594
|
+
options.logger,
|
|
1595
|
+
"Plugin cache read failed; recomputing.",
|
|
1596
|
+
key,
|
|
1597
|
+
error
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
const value = await compute();
|
|
1601
|
+
try {
|
|
1602
|
+
await cache.set(memoKey, value);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
warn(
|
|
1605
|
+
options.logger,
|
|
1606
|
+
"Plugin cache write failed; result not cached.",
|
|
1607
|
+
key,
|
|
1608
|
+
error
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
return value;
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
function stableStringify(value) {
|
|
1616
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1617
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
1618
|
+
const keys = Object.keys(value).sort();
|
|
1619
|
+
const body = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",");
|
|
1620
|
+
return `{${body}}`;
|
|
1621
|
+
}
|
|
1622
|
+
function hash(value) {
|
|
1623
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
1624
|
+
}
|
|
1625
|
+
function warn(logger, message, key, error) {
|
|
1626
|
+
logger?.warn(message, {
|
|
1627
|
+
memoKey: key,
|
|
1628
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/content/content_persistent_cache.ts
|
|
1633
|
+
var CONTENT_CACHE_NAMESPACE = "content";
|
|
1634
|
+
var CONTENT_CACHE_SCHEMA_VERSION = 3;
|
|
1635
|
+
var CONTENT_CACHE_COMPATIBILITY_VERSION = 1;
|
|
1636
|
+
function hash2(value) {
|
|
1637
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
1638
|
+
}
|
|
1639
|
+
function cacheDirectory(config) {
|
|
1640
|
+
if (config.cache?.directory) {
|
|
1641
|
+
return path2.join(config.cache.directory, CONTENT_CACHE_NAMESPACE);
|
|
1642
|
+
}
|
|
1643
|
+
if (config.buildDirectory) {
|
|
1644
|
+
return path2.join(config.buildDirectory, "cache", CONTENT_CACHE_NAMESPACE);
|
|
1645
|
+
}
|
|
1646
|
+
return path2.join(
|
|
1647
|
+
os.tmpdir(),
|
|
1648
|
+
"riebeckite-content-cache",
|
|
1649
|
+
CONTENT_CACHE_NAMESPACE
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
function versionDirectory(baseDir) {
|
|
1653
|
+
return path2.join(baseDir, `v${CONTENT_CACHE_SCHEMA_VERSION}`);
|
|
1654
|
+
}
|
|
1655
|
+
function cacheFilePath(baseDir, key) {
|
|
1656
|
+
return path2.join(versionDirectory(baseDir), `${hash2(key)}.json`);
|
|
1657
|
+
}
|
|
1658
|
+
async function writeAtomically(targetPath, dir, serialized) {
|
|
1659
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
1660
|
+
const temporaryPath = `${targetPath}.${createHash4("sha256").update(`${Date.now()}-${Math.random()}`).digest("hex").slice(0, 12)}.tmp`;
|
|
1661
|
+
try {
|
|
1662
|
+
await fs2.writeFile(temporaryPath, serialized, "utf8");
|
|
1663
|
+
await fs2.rename(temporaryPath, targetPath);
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
await fs2.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
1666
|
+
throw error;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
function createPersistentContentCache(options) {
|
|
1670
|
+
const { config, logger, tracer } = options;
|
|
1671
|
+
const baseDir = cacheDirectory(config);
|
|
1672
|
+
const versionDir = versionDirectory(baseDir);
|
|
1673
|
+
return {
|
|
1674
|
+
async get(key) {
|
|
1675
|
+
if (!config.cache?.enabled) return void 0;
|
|
1676
|
+
const filePath = cacheFilePath(baseDir, key);
|
|
1677
|
+
try {
|
|
1678
|
+
const raw = await fs2.readFile(filePath, "utf8");
|
|
1679
|
+
const entry = JSON.parse(raw);
|
|
1680
|
+
if (entry.schemaVersion !== CONTENT_CACHE_SCHEMA_VERSION) {
|
|
1681
|
+
tracer?.event("persistentContentCache.schemaMismatch", { key });
|
|
1682
|
+
return void 0;
|
|
1683
|
+
}
|
|
1684
|
+
if (entry.key !== key) {
|
|
1685
|
+
tracer?.event("persistentContentCache.keyMismatch", { key });
|
|
1686
|
+
return void 0;
|
|
1687
|
+
}
|
|
1688
|
+
if (!isValidCacheEntry(entry)) {
|
|
1689
|
+
tracer?.event("persistentContentCache.invalidShape", { key });
|
|
1690
|
+
return void 0;
|
|
1691
|
+
}
|
|
1692
|
+
tracer?.event("persistentContentCache.found", { key });
|
|
1693
|
+
return entry;
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
1696
|
+
tracer?.event("persistentContentCache.miss", { key });
|
|
1697
|
+
return void 0;
|
|
1698
|
+
}
|
|
1699
|
+
if (error instanceof SyntaxError) {
|
|
1700
|
+
logger?.warn(
|
|
1701
|
+
"Persistent content cache entry is corrupted and will be ignored.",
|
|
1702
|
+
{
|
|
1703
|
+
key
|
|
1704
|
+
}
|
|
1705
|
+
);
|
|
1706
|
+
return void 0;
|
|
1707
|
+
}
|
|
1708
|
+
throw error;
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
async set(key, value) {
|
|
1712
|
+
if (!config.cache?.enabled) return;
|
|
1713
|
+
const filePath = cacheFilePath(baseDir, key);
|
|
1714
|
+
const serialized = JSON.stringify(value);
|
|
1715
|
+
if (serialized === void 0) {
|
|
1716
|
+
throw new TypeError(
|
|
1717
|
+
"Persistent content cache values must be JSON serializable."
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
await writeAtomically(filePath, versionDir, serialized);
|
|
1721
|
+
tracer?.event("persistentContentCache.write", { key });
|
|
1722
|
+
},
|
|
1723
|
+
async clear() {
|
|
1724
|
+
try {
|
|
1725
|
+
await fs2.rm(baseDir, { recursive: true, force: true });
|
|
1726
|
+
} catch {
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
function isValidCacheEntry(entry) {
|
|
1732
|
+
return typeof entry === "object" && entry !== null && typeof entry.key === "string" && typeof entry.schemaVersion === "number" && Array.isArray(entry.dependencies) && entry.dependencies.every(isValidDependency) && typeof entry.value === "object" && entry.value !== null && typeof entry.value.html === "string" && typeof entry.value.frontmatter === "object" && entry.value.frontmatter !== null && !Array.isArray(entry.value.frontmatter);
|
|
1733
|
+
}
|
|
1734
|
+
function isValidDependency(value) {
|
|
1735
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1736
|
+
const candidate = value;
|
|
1737
|
+
return (candidate.kind === "content" || candidate.kind === "file" || candidate.kind === "link") && typeof candidate.id === "string" && typeof candidate.fingerprint === "string";
|
|
1738
|
+
}
|
|
1739
|
+
function computeContentCacheKey(inputs) {
|
|
1740
|
+
const { slug, source, frontmatter, pipelineFingerprint } = inputs;
|
|
1741
|
+
const normalizedFrontmatter = normalizeFrontmatterForCache(frontmatter);
|
|
1742
|
+
const keyData = {
|
|
1743
|
+
schemaVersion: CONTENT_CACHE_SCHEMA_VERSION,
|
|
1744
|
+
slug,
|
|
1745
|
+
source,
|
|
1746
|
+
frontmatter: normalizedFrontmatter,
|
|
1747
|
+
pipelineFingerprint
|
|
1748
|
+
};
|
|
1749
|
+
return hash2(stableStringify(keyData));
|
|
1750
|
+
}
|
|
1751
|
+
function normalizeFrontmatterForCache(frontmatter) {
|
|
1752
|
+
const normalized = {};
|
|
1753
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
1754
|
+
if (value instanceof Date) {
|
|
1755
|
+
normalized[key] = value.toISOString();
|
|
1756
|
+
} else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1757
|
+
normalized[key] = normalizeFrontmatterForCache(
|
|
1758
|
+
value
|
|
1759
|
+
);
|
|
1760
|
+
} else {
|
|
1761
|
+
normalized[key] = value;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return normalized;
|
|
1765
|
+
}
|
|
1766
|
+
function computePipelineFingerprint(config) {
|
|
1767
|
+
const plugins = config.plugins;
|
|
1768
|
+
const fingerprintData = {
|
|
1769
|
+
compatibilityVersion: CONTENT_CACHE_COMPATIBILITY_VERSION,
|
|
1770
|
+
plugins: plugins.map((p) => pluginFingerprint(p)),
|
|
1771
|
+
markdown: config.markdown,
|
|
1772
|
+
content: {
|
|
1773
|
+
filters: config.content.filters
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
return hash2(stableStringify(fingerprintData));
|
|
1777
|
+
}
|
|
1778
|
+
var pluginFingerprints = /* @__PURE__ */ new WeakMap();
|
|
1779
|
+
function pluginFingerprint(plugin) {
|
|
1780
|
+
const cached = pluginFingerprints.get(plugin);
|
|
1781
|
+
if (cached !== void 0) return cached;
|
|
1782
|
+
const fingerprint = sanitizeForFingerprint(plugin);
|
|
1783
|
+
pluginFingerprints.set(plugin, fingerprint);
|
|
1784
|
+
return fingerprint;
|
|
1785
|
+
}
|
|
1786
|
+
var functionSourceFingerprints = /* @__PURE__ */ new Map();
|
|
1787
|
+
function fingerprintFunctionSource(value) {
|
|
1788
|
+
const source = Function.prototype.toString.call(value);
|
|
1789
|
+
const cached = functionSourceFingerprints.get(source);
|
|
1790
|
+
if (cached !== void 0) return cached;
|
|
1791
|
+
const fingerprint = `fn:${hash2(source)}`;
|
|
1792
|
+
functionSourceFingerprints.set(source, fingerprint);
|
|
1793
|
+
return fingerprint;
|
|
1794
|
+
}
|
|
1795
|
+
function sanitizeForFingerprint(value) {
|
|
1796
|
+
if (value === null || value === void 0) return value;
|
|
1797
|
+
if (typeof value === "function") {
|
|
1798
|
+
return fingerprintFunctionSource(value);
|
|
1799
|
+
}
|
|
1800
|
+
if (typeof value === "symbol") return "[Symbol]";
|
|
1801
|
+
if (Array.isArray(value)) return value.map(sanitizeForFingerprint);
|
|
1802
|
+
if (value instanceof Map || value instanceof Set)
|
|
1803
|
+
return `[${value.constructor.name}]`;
|
|
1804
|
+
if (typeof value === "object") {
|
|
1805
|
+
const result = {};
|
|
1806
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1807
|
+
result[key] = sanitizeForFingerprint(val);
|
|
1808
|
+
}
|
|
1809
|
+
return result;
|
|
1810
|
+
}
|
|
1811
|
+
return value;
|
|
1812
|
+
}
|
|
1813
|
+
function isPersistentlyCacheable(_slug, config) {
|
|
1814
|
+
const hasL10n = config.plugins.some(
|
|
1815
|
+
(p) => p.name === "l10n" && p.enabled !== false
|
|
1816
|
+
);
|
|
1817
|
+
if (hasL10n) {
|
|
1818
|
+
return { cacheable: false, reason: "l10n plugin is enabled" };
|
|
1819
|
+
}
|
|
1820
|
+
const unsafePlugin = config.plugins.find((plugin) => {
|
|
1821
|
+
if (plugin.enabled === false) return false;
|
|
1822
|
+
if (!canAffectProcessedContent(plugin)) return false;
|
|
1823
|
+
return plugin.processedContentCache?.dependencyMode !== "none" && plugin.processedContentCache?.dependencyMode !== "tracked";
|
|
1824
|
+
});
|
|
1825
|
+
if (unsafePlugin) {
|
|
1826
|
+
if (unsafePlugin.processedContentCache?.dependencyMode === "unsafe") {
|
|
1827
|
+
return {
|
|
1828
|
+
cacheable: false,
|
|
1829
|
+
reason: `plugin ${unsafePlugin.name} explicitly marks processed content cache as unsafe`
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
return {
|
|
1833
|
+
cacheable: false,
|
|
1834
|
+
reason: `plugin ${unsafePlugin.name} can affect processed content without a processedContentCache contract`
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
return { cacheable: true };
|
|
1838
|
+
}
|
|
1839
|
+
function canAffectProcessedContent(plugin) {
|
|
1840
|
+
return Boolean(
|
|
1841
|
+
plugin.remarkPlugins?.length || plugin.rehypePlugins?.length || plugin.extendMarkdownPipeline || plugin.extendHtmlPipeline || plugin.renderers?.length || plugin.onContentLoaded || plugin.onPostParsed || plugin.onPostProcessed
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
function extractFrontmatter(markdown) {
|
|
1845
|
+
const vfile = new VFile({ value: markdown });
|
|
1846
|
+
matter(vfile);
|
|
1847
|
+
return vfile.data.matter ?? {};
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// src/plugin/plugin_cache.ts
|
|
1851
|
+
import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
|
|
1852
|
+
import fs3 from "node:fs/promises";
|
|
1853
|
+
import path3 from "node:path";
|
|
1423
1854
|
function createUnavailablePluginCache() {
|
|
1424
1855
|
return {
|
|
1425
1856
|
get: unavailableAtRuntime,
|
|
@@ -1429,20 +1860,20 @@ function createUnavailablePluginCache() {
|
|
|
1429
1860
|
};
|
|
1430
1861
|
}
|
|
1431
1862
|
function createPluginCache(options) {
|
|
1432
|
-
const pluginDirectory =
|
|
1863
|
+
const pluginDirectory = path3.join(
|
|
1433
1864
|
options.cacheDirectory,
|
|
1434
1865
|
pluginNamespace(options.pluginName)
|
|
1435
1866
|
);
|
|
1436
|
-
const
|
|
1867
|
+
const versionDirectory2 = path3.join(
|
|
1437
1868
|
pluginDirectory,
|
|
1438
|
-
|
|
1869
|
+
hash3(options.cacheVersion ?? "1")
|
|
1439
1870
|
);
|
|
1440
1871
|
const writes = /* @__PURE__ */ new Map();
|
|
1441
1872
|
return {
|
|
1442
1873
|
async get(key) {
|
|
1443
1874
|
let raw;
|
|
1444
1875
|
try {
|
|
1445
|
-
raw = await
|
|
1876
|
+
raw = await fs3.readFile(cachePath(versionDirectory2, key), "utf8");
|
|
1446
1877
|
} catch (error) {
|
|
1447
1878
|
if (isNotFoundError2(error)) {
|
|
1448
1879
|
options.tracer?.event("cache.miss", { plugin: options.pluginName });
|
|
@@ -1465,13 +1896,13 @@ function createPluginCache(options) {
|
|
|
1465
1896
|
}
|
|
1466
1897
|
},
|
|
1467
1898
|
async set(key, value) {
|
|
1468
|
-
const targetPath = cachePath(
|
|
1899
|
+
const targetPath = cachePath(versionDirectory2, key);
|
|
1469
1900
|
const serialized = JSON.stringify(value);
|
|
1470
1901
|
if (serialized === void 0) {
|
|
1471
1902
|
throw new TypeError("Plugin cache values must be JSON serializable.");
|
|
1472
1903
|
}
|
|
1473
1904
|
const previousWrite = writes.get(targetPath) ?? Promise.resolve();
|
|
1474
|
-
const write = previousWrite.catch(() => void 0).then(() =>
|
|
1905
|
+
const write = previousWrite.catch(() => void 0).then(() => writeAtomically2(targetPath, versionDirectory2, serialized));
|
|
1475
1906
|
writes.set(targetPath, write);
|
|
1476
1907
|
try {
|
|
1477
1908
|
await write;
|
|
@@ -1482,37 +1913,37 @@ function createPluginCache(options) {
|
|
|
1482
1913
|
}
|
|
1483
1914
|
},
|
|
1484
1915
|
async delete(key) {
|
|
1485
|
-
await
|
|
1916
|
+
await fs3.rm(cachePath(versionDirectory2, key), { force: true });
|
|
1486
1917
|
},
|
|
1487
1918
|
async clear() {
|
|
1488
|
-
await
|
|
1919
|
+
await fs3.rm(pluginDirectory, { recursive: true, force: true });
|
|
1489
1920
|
}
|
|
1490
1921
|
};
|
|
1491
1922
|
}
|
|
1492
|
-
async function
|
|
1493
|
-
await
|
|
1923
|
+
async function writeAtomically2(targetPath, versionDirectory2, serialized) {
|
|
1924
|
+
await fs3.mkdir(versionDirectory2, { recursive: true });
|
|
1494
1925
|
const temporaryPath = `${targetPath}.${randomUUID2()}.tmp`;
|
|
1495
1926
|
try {
|
|
1496
|
-
await
|
|
1497
|
-
await
|
|
1927
|
+
await fs3.writeFile(temporaryPath, serialized, "utf8");
|
|
1928
|
+
await fs3.rename(temporaryPath, targetPath);
|
|
1498
1929
|
} catch (error) {
|
|
1499
|
-
await
|
|
1930
|
+
await fs3.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
1500
1931
|
throw error;
|
|
1501
1932
|
}
|
|
1502
1933
|
}
|
|
1503
1934
|
function resolvePluginCacheDirectory(config) {
|
|
1504
|
-
if (!config) return
|
|
1505
|
-
if (config.buildDirectory) return
|
|
1506
|
-
return
|
|
1935
|
+
if (!config) return path3.resolve(process.cwd(), ".riebeckite", "cache");
|
|
1936
|
+
if (config.buildDirectory) return path3.join(config.buildDirectory, "cache");
|
|
1937
|
+
return path3.resolve(config.content.directory, "..", ".riebeckite", "cache");
|
|
1507
1938
|
}
|
|
1508
|
-
function cachePath(
|
|
1509
|
-
return
|
|
1939
|
+
function cachePath(versionDirectory2, key) {
|
|
1940
|
+
return path3.join(versionDirectory2, `${hash3(key)}.json`);
|
|
1510
1941
|
}
|
|
1511
1942
|
function pluginNamespace(pluginName) {
|
|
1512
|
-
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(pluginName) ? pluginName :
|
|
1943
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(pluginName) ? pluginName : hash3(pluginName);
|
|
1513
1944
|
}
|
|
1514
|
-
function
|
|
1515
|
-
return
|
|
1945
|
+
function hash3(value) {
|
|
1946
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
1516
1947
|
}
|
|
1517
1948
|
function isNotFoundError2(error) {
|
|
1518
1949
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -1524,16 +1955,20 @@ async function unavailableAtRuntime() {
|
|
|
1524
1955
|
}
|
|
1525
1956
|
|
|
1526
1957
|
// src/types/generated_output.ts
|
|
1958
|
+
var ATTACHMENTS_NAMESPACE = ATTACHMENTS_BASE_PATH.replace(/^\/+/, "");
|
|
1959
|
+
var STATIC_ASSETS_DIR = "assets";
|
|
1527
1960
|
function createUnavailableGeneratedOutputSink() {
|
|
1961
|
+
const unavailable = () => {
|
|
1962
|
+
throw new Error(
|
|
1963
|
+
"Generated output is available only during a build lifecycle."
|
|
1964
|
+
);
|
|
1965
|
+
};
|
|
1528
1966
|
return {
|
|
1529
|
-
emit
|
|
1530
|
-
|
|
1531
|
-
"Generated output is available only during a build lifecycle."
|
|
1532
|
-
);
|
|
1533
|
-
}
|
|
1967
|
+
emit: unavailable,
|
|
1968
|
+
emitAsset: unavailable
|
|
1534
1969
|
};
|
|
1535
1970
|
}
|
|
1536
|
-
function
|
|
1971
|
+
function normalizeOutputPathSegments(input) {
|
|
1537
1972
|
if (typeof input !== "string" || input.length === 0) {
|
|
1538
1973
|
throw new Error("Generated output path must be a non-empty string.");
|
|
1539
1974
|
}
|
|
@@ -1557,12 +1992,27 @@ function normalizeGeneratedOutputPath(input) {
|
|
|
1557
1992
|
);
|
|
1558
1993
|
}
|
|
1559
1994
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1995
|
+
return segments.join("/");
|
|
1996
|
+
}
|
|
1997
|
+
function normalizeGeneratedOutputPath(input) {
|
|
1998
|
+
const normalized = normalizeOutputPathSegments(input);
|
|
1999
|
+
if (normalized === STATIC_ASSETS_DIR || normalized.startsWith(`${STATIC_ASSETS_DIR}/`)) {
|
|
1562
2000
|
throw new Error(
|
|
1563
2001
|
`Generated output path must not conflict with the static assets namespace: "${input}".`
|
|
1564
2002
|
);
|
|
1565
2003
|
}
|
|
2004
|
+
return rejectInternalRiebeckitePath(normalized, input);
|
|
2005
|
+
}
|
|
2006
|
+
function normalizeContentAssetOutputPath(input) {
|
|
2007
|
+
const normalized = normalizeOutputPathSegments(input);
|
|
2008
|
+
if (normalized === ATTACHMENTS_NAMESPACE || normalized.startsWith(`${ATTACHMENTS_NAMESPACE}/`)) {
|
|
2009
|
+
throw new Error(
|
|
2010
|
+
`Generated output path must not conflict with the attachment namespace: "${input}".`
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
return rejectInternalRiebeckitePath(normalized, input);
|
|
2014
|
+
}
|
|
2015
|
+
function rejectInternalRiebeckitePath(normalized, input) {
|
|
1566
2016
|
if (normalized === ".riebeckite" || normalized.startsWith(".riebeckite/")) {
|
|
1567
2017
|
throw new Error(
|
|
1568
2018
|
`Generated output path must not target internal Riebeckite files: "${input}".`
|
|
@@ -1579,6 +2029,16 @@ var Pipeline = class {
|
|
|
1579
2029
|
this.getMarkdownBySlug = getMarkdownBySlug;
|
|
1580
2030
|
this.options = options;
|
|
1581
2031
|
this.isBuildTime = isBuildTime;
|
|
2032
|
+
if (this.options.config && this.options.config.cache?.enabled !== false) {
|
|
2033
|
+
this.persistentCache = createPersistentContentCache({
|
|
2034
|
+
config: this.options.config,
|
|
2035
|
+
logger: this.options.observability?.logger,
|
|
2036
|
+
tracer: this.options.observability?.tracer
|
|
2037
|
+
});
|
|
2038
|
+
this.pipelineFingerprint = computePipelineFingerprint(
|
|
2039
|
+
this.options.config
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
1582
2042
|
}
|
|
1583
2043
|
contentIndex;
|
|
1584
2044
|
permalinks;
|
|
@@ -1586,23 +2046,106 @@ var Pipeline = class {
|
|
|
1586
2046
|
options;
|
|
1587
2047
|
isBuildTime;
|
|
1588
2048
|
pluginCaches = /* @__PURE__ */ new Map();
|
|
2049
|
+
persistentCache = null;
|
|
2050
|
+
pipelineFingerprint = null;
|
|
2051
|
+
resolvedPlugins = null;
|
|
1589
2052
|
async execute(markDownContent, context = {}) {
|
|
1590
2053
|
return await this.executeWithEmbedState(
|
|
1591
2054
|
markDownContent,
|
|
1592
2055
|
context,
|
|
1593
2056
|
0,
|
|
1594
|
-
new Set(context.sourceSlug ? [context.sourceSlug] : [])
|
|
2057
|
+
new Set(context.sourceSlug ? [context.sourceSlug] : []),
|
|
2058
|
+
this.options.contentSource ? createContentDependencyTracker(this.options.contentSource) : null
|
|
1595
2059
|
);
|
|
1596
2060
|
}
|
|
1597
|
-
async executeWithEmbedState(markDownContent, context, embedDepth, embedTrail) {
|
|
2061
|
+
async executeWithEmbedState(markDownContent, context, embedDepth, embedTrail, rootDependencyTracker) {
|
|
2062
|
+
const dependencyTracker = rootDependencyTracker;
|
|
2063
|
+
const effectiveContentSource = dependencyTracker ? dependencyTracker.contentSource : this.options.contentSource;
|
|
2064
|
+
const sourceSlug = context.sourceSlug;
|
|
2065
|
+
if (embedDepth === 0 && this.persistentCache && this.pipelineFingerprint && sourceSlug && this.options.contentSource && this.options.config) {
|
|
2066
|
+
const cacheable = isPersistentlyCacheable(
|
|
2067
|
+
sourceSlug,
|
|
2068
|
+
this.options.config
|
|
2069
|
+
);
|
|
2070
|
+
if (cacheable.cacheable) {
|
|
2071
|
+
const frontmatter = extractFrontmatter(markDownContent);
|
|
2072
|
+
const cacheKey = computeContentCacheKey({
|
|
2073
|
+
slug: sourceSlug,
|
|
2074
|
+
source: markDownContent,
|
|
2075
|
+
frontmatter,
|
|
2076
|
+
pipelineFingerprint: this.pipelineFingerprint
|
|
2077
|
+
});
|
|
2078
|
+
const cachedEntry = await this.persistentCache.get(cacheKey);
|
|
2079
|
+
if (cachedEntry) {
|
|
2080
|
+
const validationTracker = createContentDependencyTracker(
|
|
2081
|
+
this.options.contentSource
|
|
2082
|
+
);
|
|
2083
|
+
let valid = true;
|
|
2084
|
+
for (const dep of cachedEntry.dependencies) {
|
|
2085
|
+
if (dep.kind === "content") {
|
|
2086
|
+
const content = await validationTracker.readContent(
|
|
2087
|
+
dep.id,
|
|
2088
|
+
async () => await this.getMarkdownBySlug?.(dep.id) ?? ""
|
|
2089
|
+
);
|
|
2090
|
+
if (fingerprintContent(content) !== dep.fingerprint) {
|
|
2091
|
+
valid = false;
|
|
2092
|
+
break;
|
|
2093
|
+
}
|
|
2094
|
+
} else if (dep.kind === "file") {
|
|
2095
|
+
const fileContent = await validationTracker.contentSource.read({
|
|
2096
|
+
path: dep.id
|
|
2097
|
+
});
|
|
2098
|
+
if (fingerprintContent(fileContent) !== dep.fingerprint) {
|
|
2099
|
+
valid = false;
|
|
2100
|
+
break;
|
|
2101
|
+
}
|
|
2102
|
+
} else if (dep.kind === "link") {
|
|
2103
|
+
if (fingerprintContent(this.resolveLinkDependency(dep.id)) !== dep.fingerprint) {
|
|
2104
|
+
valid = false;
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
if (valid) {
|
|
2110
|
+
this.reportContentDependencies(
|
|
2111
|
+
sourceSlug,
|
|
2112
|
+
cachedEntry.dependencies
|
|
2113
|
+
);
|
|
2114
|
+
this.options.observability?.tracer?.event(
|
|
2115
|
+
"persistentContentCache.hit",
|
|
2116
|
+
{ key: cacheKey, slug: sourceSlug }
|
|
2117
|
+
);
|
|
2118
|
+
this.options.onPersistentContentCacheResult?.("hit");
|
|
2119
|
+
return {
|
|
2120
|
+
frontmatter: cachedEntry.value.frontmatter,
|
|
2121
|
+
html: cachedEntry.value.html
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
this.options.observability?.tracer?.event(
|
|
2126
|
+
"persistentContentCache.miss",
|
|
2127
|
+
{ key: cacheKey, slug: sourceSlug }
|
|
2128
|
+
);
|
|
2129
|
+
this.options.onPersistentContentCacheResult?.("miss");
|
|
2130
|
+
} else {
|
|
2131
|
+
this.options.observability?.tracer?.event(
|
|
2132
|
+
"persistentContentCache.bypass",
|
|
2133
|
+
{
|
|
2134
|
+
slug: sourceSlug,
|
|
2135
|
+
...cacheable.reason ? { reason: cacheable.reason } : {}
|
|
2136
|
+
}
|
|
2137
|
+
);
|
|
2138
|
+
this.options.onPersistentContentCacheResult?.("bypass");
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
1598
2141
|
const processor = unified();
|
|
1599
|
-
const plugins = resolvePlugins(
|
|
2142
|
+
const plugins = this.resolvePlugins();
|
|
1600
2143
|
this.use(processor, remarkParse);
|
|
1601
2144
|
this.use(processor, remarkDirective);
|
|
1602
2145
|
this.use(processor, remarkFrontmatter, ["yaml", "toml"]);
|
|
1603
2146
|
this.use(processor, function loadFrontmatter() {
|
|
1604
2147
|
return (_tree, file2) => {
|
|
1605
|
-
|
|
2148
|
+
matter2(file2);
|
|
1606
2149
|
};
|
|
1607
2150
|
});
|
|
1608
2151
|
this.use(processor, remarkMath);
|
|
@@ -1611,9 +2154,14 @@ var Pipeline = class {
|
|
|
1611
2154
|
sourceSlug: context.sourceSlug,
|
|
1612
2155
|
contentIndex: this.contentIndex,
|
|
1613
2156
|
resolvePermalink: (slug) => this.getPermalink(slug),
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2157
|
+
isRoutable: this.options.isRoutable,
|
|
2158
|
+
renderNoteEmbed: this.createNoteEmbedRenderer(
|
|
2159
|
+
embedDepth,
|
|
2160
|
+
embedTrail,
|
|
2161
|
+
dependencyTracker
|
|
2162
|
+
),
|
|
2163
|
+
renderContent: this.createContentRenderer(effectiveContentSource),
|
|
2164
|
+
contentSource: effectiveContentSource
|
|
1617
2165
|
};
|
|
1618
2166
|
for (const plugin of plugins) {
|
|
1619
2167
|
for (const remarkPlugin of plugin.remarkPlugins ?? []) {
|
|
@@ -1628,7 +2176,10 @@ var Pipeline = class {
|
|
|
1628
2176
|
markdownPipelineContext
|
|
1629
2177
|
);
|
|
1630
2178
|
}
|
|
1631
|
-
this.use(processor, normalizeMarkdownLinks,
|
|
2179
|
+
this.use(processor, normalizeMarkdownLinks, {
|
|
2180
|
+
...markdownPipelineContext,
|
|
2181
|
+
recordLinkResolution: dependencyTracker ? (id, value) => dependencyTracker.recordLinkResolution(id, value) : void 0
|
|
2182
|
+
});
|
|
1632
2183
|
this.use(processor, remarkRehype, { allowDangerousHtml: true });
|
|
1633
2184
|
this.use(processor, rehypeRaw);
|
|
1634
2185
|
for (const plugin of plugins) {
|
|
@@ -1646,10 +2197,43 @@ var Pipeline = class {
|
|
|
1646
2197
|
this.use(processor, rehypeFormat);
|
|
1647
2198
|
this.use(processor, rehypeStringify, { allowDangerousHtml: true });
|
|
1648
2199
|
const file = await processor.process(markDownContent.trim());
|
|
1649
|
-
|
|
2200
|
+
const result = {
|
|
1650
2201
|
frontmatter: file.data.matter || {},
|
|
1651
2202
|
html: String(file.value)
|
|
1652
2203
|
};
|
|
2204
|
+
if (sourceSlug && dependencyTracker) {
|
|
2205
|
+
this.reportContentDependencies(
|
|
2206
|
+
sourceSlug,
|
|
2207
|
+
dependencyTracker.dependencies()
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
if (embedDepth === 0 && this.persistentCache && this.pipelineFingerprint && sourceSlug && dependencyTracker && this.options.config) {
|
|
2211
|
+
const cacheable = isPersistentlyCacheable(
|
|
2212
|
+
sourceSlug,
|
|
2213
|
+
this.options.config
|
|
2214
|
+
);
|
|
2215
|
+
if (cacheable.cacheable) {
|
|
2216
|
+
const frontmatter = extractFrontmatter(markDownContent);
|
|
2217
|
+
const cacheKey = computeContentCacheKey({
|
|
2218
|
+
slug: sourceSlug,
|
|
2219
|
+
source: markDownContent,
|
|
2220
|
+
frontmatter,
|
|
2221
|
+
pipelineFingerprint: this.pipelineFingerprint
|
|
2222
|
+
});
|
|
2223
|
+
const dependencies = dependencyTracker.dependencies();
|
|
2224
|
+
const entry = {
|
|
2225
|
+
schemaVersion: CONTENT_CACHE_SCHEMA_VERSION,
|
|
2226
|
+
key: cacheKey,
|
|
2227
|
+
dependencies,
|
|
2228
|
+
value: result
|
|
2229
|
+
};
|
|
2230
|
+
await this.persistentCache.set(cacheKey, entry);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return result;
|
|
2234
|
+
}
|
|
2235
|
+
reportContentDependencies(slug, dependencies) {
|
|
2236
|
+
this.options.onContentDependencies?.(slug, dependencies);
|
|
1653
2237
|
}
|
|
1654
2238
|
getPermalink(slug) {
|
|
1655
2239
|
const permalink = this.permalinks.get(slug);
|
|
@@ -1658,6 +2242,14 @@ var Pipeline = class {
|
|
|
1658
2242
|
}
|
|
1659
2243
|
return permalink;
|
|
1660
2244
|
}
|
|
2245
|
+
resolveLinkDependency(id) {
|
|
2246
|
+
return resolveLinkDependencyValue(
|
|
2247
|
+
id,
|
|
2248
|
+
this.contentIndex,
|
|
2249
|
+
(slug) => this.permalinks.get(slug),
|
|
2250
|
+
this.options.isRoutable
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
1661
2253
|
use(processor, plugin, options) {
|
|
1662
2254
|
const register = processor.use.bind(processor);
|
|
1663
2255
|
if (options === void 0) {
|
|
@@ -1666,26 +2258,37 @@ var Pipeline = class {
|
|
|
1666
2258
|
register(plugin, options);
|
|
1667
2259
|
}
|
|
1668
2260
|
}
|
|
1669
|
-
createNoteEmbedRenderer(embedDepth, embedTrail) {
|
|
2261
|
+
createNoteEmbedRenderer(embedDepth, embedTrail, dependencyTracker) {
|
|
1670
2262
|
if (!this.getMarkdownBySlug || embedDepth >= 3) return void 0;
|
|
1671
2263
|
return async (slug, fragment) => {
|
|
2264
|
+
if (this.options.isRoutable && !this.options.isRoutable(slug)) {
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
1672
2267
|
if (embedTrail.has(slug)) return null;
|
|
1673
2268
|
const sourceMarkdown = await this.getMarkdownBySlug?.(slug);
|
|
1674
2269
|
const markdown = sourceMarkdown ? selectEmbedMarkdownFragment(sourceMarkdown, fragment) : null;
|
|
1675
2270
|
if (!markdown) return null;
|
|
1676
2271
|
const nextEmbedTrail = new Set(embedTrail);
|
|
1677
2272
|
nextEmbedTrail.add(slug);
|
|
2273
|
+
if (dependencyTracker) {
|
|
2274
|
+
await dependencyTracker.readContent(slug, async () => markdown);
|
|
2275
|
+
}
|
|
1678
2276
|
const content = await this.executeWithEmbedState(
|
|
1679
2277
|
markdown,
|
|
1680
2278
|
{ sourceSlug: slug },
|
|
1681
2279
|
embedDepth + 1,
|
|
1682
|
-
nextEmbedTrail
|
|
2280
|
+
nextEmbedTrail,
|
|
2281
|
+
dependencyTracker
|
|
1683
2282
|
);
|
|
1684
2283
|
return content.html;
|
|
1685
2284
|
};
|
|
1686
2285
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
2286
|
+
resolvePlugins() {
|
|
2287
|
+
this.resolvedPlugins ??= resolvePlugins(this.options.plugins);
|
|
2288
|
+
return this.resolvedPlugins;
|
|
2289
|
+
}
|
|
2290
|
+
createContentRenderer(contentSource) {
|
|
2291
|
+
const renderers = this.resolvePlugins().flatMap(
|
|
1689
2292
|
(plugin) => (plugin.renderers ?? []).map((renderer) => ({ plugin, renderer }))
|
|
1690
2293
|
);
|
|
1691
2294
|
if (renderers.length === 0) return void 0;
|
|
@@ -1707,7 +2310,7 @@ var Pipeline = class {
|
|
|
1707
2310
|
output: createUnavailableGeneratedOutputSink(),
|
|
1708
2311
|
logger: observability.logger.child({ plugin: plugin.name }),
|
|
1709
2312
|
tracer: observability.tracer,
|
|
1710
|
-
contentSource
|
|
2313
|
+
contentSource,
|
|
1711
2314
|
...input
|
|
1712
2315
|
})
|
|
1713
2316
|
);
|
|
@@ -1750,6 +2353,21 @@ function normalizeMarkdownLinks(context) {
|
|
|
1750
2353
|
});
|
|
1751
2354
|
};
|
|
1752
2355
|
}
|
|
2356
|
+
var LINK_INDEX_PREFIX = "index:";
|
|
2357
|
+
var LINK_LOCATION_PREFIX = "location:";
|
|
2358
|
+
function resolveLinkDependencyValue(id, contentIndex, lookupPermalink, isRoutable) {
|
|
2359
|
+
if (id.startsWith(LINK_INDEX_PREFIX)) {
|
|
2360
|
+
return contentIndex.get(id.slice(LINK_INDEX_PREFIX.length)) ?? "";
|
|
2361
|
+
}
|
|
2362
|
+
if (id.startsWith(LINK_LOCATION_PREFIX)) {
|
|
2363
|
+
const slug = id.slice(LINK_LOCATION_PREFIX.length);
|
|
2364
|
+
const permalink = lookupPermalink(slug);
|
|
2365
|
+
if (permalink === void 0) return "";
|
|
2366
|
+
if (isRoutable && !isRoutable(slug)) return "";
|
|
2367
|
+
return permalink;
|
|
2368
|
+
}
|
|
2369
|
+
return "";
|
|
2370
|
+
}
|
|
1753
2371
|
function visitMarkdownLinkNodes(node, visitor) {
|
|
1754
2372
|
const linkNode = node;
|
|
1755
2373
|
if ((node.type === "link" || node.type === "definition") && typeof linkNode.url === "string") {
|
|
@@ -1762,7 +2380,7 @@ function visitMarkdownLinkNodes(node, visitor) {
|
|
|
1762
2380
|
function normalizeMarkdownLinkUrl(url, context) {
|
|
1763
2381
|
if (!context.sourceSlug) return null;
|
|
1764
2382
|
if (!isMarkdownContentLink(url)) return null;
|
|
1765
|
-
const [pathAndQuery,
|
|
2383
|
+
const [pathAndQuery, hash4 = ""] = splitOnce(url, "#");
|
|
1766
2384
|
const [rawPath, query = ""] = splitOnce(pathAndQuery, "?");
|
|
1767
2385
|
const decodedPath = decodeUriPath(rawPath);
|
|
1768
2386
|
if (!decodedPath?.toLowerCase().endsWith(".md")) return null;
|
|
@@ -1773,15 +2391,35 @@ function normalizeMarkdownLinkUrl(url, context) {
|
|
|
1773
2391
|
if (!resolvedSlug) return null;
|
|
1774
2392
|
const indexedSlug = getExactContentSlug(context, resolvedSlug);
|
|
1775
2393
|
if (!indexedSlug) return null;
|
|
1776
|
-
|
|
2394
|
+
recordLinkResolution(context, LINK_LOCATION_PREFIX, indexedSlug);
|
|
2395
|
+
if (context.isRoutable && !context.isRoutable(indexedSlug)) return null;
|
|
2396
|
+
const suffix = `${query ? `?${query}` : ""}${hash4 ? `#${hash4}` : ""}`;
|
|
1777
2397
|
return `${context.resolvePermalink(indexedSlug)}${suffix}`;
|
|
1778
2398
|
}
|
|
1779
|
-
function
|
|
1780
|
-
|
|
2399
|
+
function recordLinkResolution(context, prefix, key) {
|
|
2400
|
+
if (!context.recordLinkResolution) return;
|
|
2401
|
+
const value = prefix === LINK_INDEX_PREFIX ? context.contentIndex.get(key) ?? "" : resolveLinkDependencyValue(
|
|
2402
|
+
`${prefix}${key}`,
|
|
2403
|
+
context.contentIndex,
|
|
2404
|
+
(slug) => {
|
|
2405
|
+
try {
|
|
2406
|
+
return context.resolvePermalink(slug);
|
|
2407
|
+
} catch {
|
|
2408
|
+
return void 0;
|
|
2409
|
+
}
|
|
2410
|
+
},
|
|
2411
|
+
context.isRoutable
|
|
2412
|
+
);
|
|
2413
|
+
context.recordLinkResolution(`${prefix}${key}`, value);
|
|
2414
|
+
}
|
|
2415
|
+
function getExactContentSlug(context, slug) {
|
|
2416
|
+
try {
|
|
1781
2417
|
context.resolvePermalink(slug);
|
|
1782
2418
|
return slug;
|
|
1783
2419
|
} catch {
|
|
1784
|
-
|
|
2420
|
+
const indexKey = slug.toLowerCase();
|
|
2421
|
+
recordLinkResolution(context, LINK_INDEX_PREFIX, indexKey);
|
|
2422
|
+
return context.contentIndex.get(indexKey) ?? null;
|
|
1785
2423
|
}
|
|
1786
2424
|
}
|
|
1787
2425
|
function isMarkdownContentLink(url) {
|
|
@@ -1805,9 +2443,9 @@ function resolveRelativeMarkdownSlug(sourceSlug, targetPath) {
|
|
|
1805
2443
|
}
|
|
1806
2444
|
return normalizedParts.join("/").replace(/\.md$/i, "");
|
|
1807
2445
|
}
|
|
1808
|
-
function decodeUriPath(
|
|
2446
|
+
function decodeUriPath(path5) {
|
|
1809
2447
|
try {
|
|
1810
|
-
return decodeURI(
|
|
2448
|
+
return decodeURI(path5);
|
|
1811
2449
|
} catch {
|
|
1812
2450
|
return null;
|
|
1813
2451
|
}
|
|
@@ -1895,17 +2533,27 @@ var GeneratedOutputRegistry = class {
|
|
|
1895
2533
|
outputs = [];
|
|
1896
2534
|
owners = /* @__PURE__ */ new Map();
|
|
1897
2535
|
sinkFor(pluginName) {
|
|
2536
|
+
const register = (path5, output) => {
|
|
2537
|
+
const owner = this.owners.get(path5);
|
|
2538
|
+
if (owner) {
|
|
2539
|
+
throw new Error(
|
|
2540
|
+
`Duplicate generated output path "${path5}" declared by "${owner}" and "${pluginName}".`
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
this.owners.set(path5, pluginName);
|
|
2544
|
+
this.outputs.push({
|
|
2545
|
+
path: path5,
|
|
2546
|
+
content: output.content,
|
|
2547
|
+
dependencies: output.dependencies,
|
|
2548
|
+
owner: pluginName
|
|
2549
|
+
});
|
|
2550
|
+
};
|
|
1898
2551
|
return {
|
|
1899
2552
|
emit: (output) => {
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
`Duplicate generated output path "${path4}" declared by "${owner}" and "${pluginName}".`
|
|
1905
|
-
);
|
|
1906
|
-
}
|
|
1907
|
-
this.owners.set(path4, pluginName);
|
|
1908
|
-
this.outputs.push({ path: path4, content: output.content, owner: pluginName });
|
|
2553
|
+
register(normalizeGeneratedOutputPath(output.path), output);
|
|
2554
|
+
},
|
|
2555
|
+
emitAsset: (output) => {
|
|
2556
|
+
register(normalizeContentAssetOutputPath(output.path), output);
|
|
1909
2557
|
}
|
|
1910
2558
|
};
|
|
1911
2559
|
}
|
|
@@ -1988,6 +2636,7 @@ var PluginRuntime = class {
|
|
|
1988
2636
|
pluginCaches = /* @__PURE__ */ new Map();
|
|
1989
2637
|
generatedOutputs = new GeneratedOutputRegistry();
|
|
1990
2638
|
isBuildTime = false;
|
|
2639
|
+
resolvedPlugins = null;
|
|
1991
2640
|
getDiagnostics() {
|
|
1992
2641
|
return this.diagnostics;
|
|
1993
2642
|
}
|
|
@@ -2108,20 +2757,26 @@ var PluginRuntime = class {
|
|
|
2108
2757
|
}
|
|
2109
2758
|
async resolvePage(pathname, manifest, contentIndex) {
|
|
2110
2759
|
const matches = [];
|
|
2111
|
-
const
|
|
2112
|
-
const context = {
|
|
2113
|
-
...this.createContext(contentIndex),
|
|
2114
|
-
manifest,
|
|
2115
|
-
pathname: normalizedPathname
|
|
2116
|
-
};
|
|
2760
|
+
const requestedPathname = normalizePagePath(pathname);
|
|
2117
2761
|
for (const plugin of this.plugins()) {
|
|
2118
2762
|
for (const pageType of plugin.pageTypes ?? []) {
|
|
2763
|
+
const pagePathname = normalizePageTypePath(pathname, pageType);
|
|
2764
|
+
const context = {
|
|
2765
|
+
...this.createContext(contentIndex),
|
|
2766
|
+
manifest,
|
|
2767
|
+
pathname: pagePathname
|
|
2768
|
+
};
|
|
2119
2769
|
const page = await pageType.resolve(
|
|
2120
2770
|
this.createPluginContext(plugin, context)
|
|
2121
2771
|
);
|
|
2122
2772
|
if (page) {
|
|
2123
2773
|
matches.push({
|
|
2124
|
-
page: {
|
|
2774
|
+
page: {
|
|
2775
|
+
...page,
|
|
2776
|
+
pathname: pageType.directoryIndex ? normalizeDirectoryPagePath(page.pathname) : page.pathname,
|
|
2777
|
+
type: pageType.id,
|
|
2778
|
+
pluginName: plugin.name
|
|
2779
|
+
},
|
|
2125
2780
|
priority: pageType.priority ?? 0
|
|
2126
2781
|
});
|
|
2127
2782
|
}
|
|
@@ -2134,7 +2789,7 @@ var PluginRuntime = class {
|
|
|
2134
2789
|
);
|
|
2135
2790
|
if (highest.length !== 1) {
|
|
2136
2791
|
throw new Error(
|
|
2137
|
-
`Multiple plugin page types match ${
|
|
2792
|
+
`Multiple plugin page types match ${requestedPathname} at priority ${highestPriority}: ${highest.map((match) => match.page.type).join(", ")}`
|
|
2138
2793
|
);
|
|
2139
2794
|
}
|
|
2140
2795
|
return highest[0].page;
|
|
@@ -2146,12 +2801,60 @@ var PluginRuntime = class {
|
|
|
2146
2801
|
for (const pageType of plugin.pageTypes ?? []) {
|
|
2147
2802
|
const declared = pageType.paths;
|
|
2148
2803
|
if (!declared) continue;
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2804
|
+
const resolved = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
|
|
2805
|
+
for (const rawPath of resolved) {
|
|
2806
|
+
paths.push(normalizePageTypePath(rawPath, pageType));
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
return [...new Set(paths)];
|
|
2811
|
+
}
|
|
2812
|
+
async getPageRoutes(manifest, contentIndex) {
|
|
2813
|
+
const routes = [];
|
|
2814
|
+
const context = { ...this.createContext(contentIndex), manifest };
|
|
2815
|
+
for (const plugin of this.plugins()) {
|
|
2816
|
+
for (const pageType of plugin.pageTypes ?? []) {
|
|
2817
|
+
const declared = pageType.paths;
|
|
2818
|
+
if (!declared) continue;
|
|
2819
|
+
const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
|
|
2820
|
+
for (const pathname of paths) {
|
|
2821
|
+
routes.push({
|
|
2822
|
+
pathname: normalizePageTypePath(pathname, pageType),
|
|
2823
|
+
pluginName: plugin.name,
|
|
2824
|
+
pageType: pageType.id
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
return routes;
|
|
2830
|
+
}
|
|
2831
|
+
async getPageOutputs(manifest, contentIndex) {
|
|
2832
|
+
const outputs = [];
|
|
2833
|
+
const context = { ...this.createContext(contentIndex), manifest };
|
|
2834
|
+
for (const plugin of this.plugins()) {
|
|
2835
|
+
for (const pageType of plugin.pageTypes ?? []) {
|
|
2836
|
+
const declared = pageType.paths;
|
|
2837
|
+
if (!declared) continue;
|
|
2838
|
+
const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
|
|
2839
|
+
for (const rawPath of paths) {
|
|
2840
|
+
const pathname = normalizePageTypePath(rawPath, pageType);
|
|
2841
|
+
const dependencyContext = { ...context, pathname };
|
|
2842
|
+
const dependencies = pageType.outputDependencies ? typeof pageType.outputDependencies === "function" ? await pageType.outputDependencies(
|
|
2843
|
+
this.createPluginContext(plugin, dependencyContext)
|
|
2844
|
+
) : pageType.outputDependencies : [{ type: "unknown" }];
|
|
2845
|
+
outputs.push({
|
|
2846
|
+
kind: "plugin-page",
|
|
2847
|
+
path: pathname,
|
|
2848
|
+
producer: `plugin:${plugin.name}:page:${pageType.id}`,
|
|
2849
|
+
dependencies
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2152
2852
|
}
|
|
2153
2853
|
}
|
|
2154
|
-
return
|
|
2854
|
+
return uniqueOutputs(outputs);
|
|
2855
|
+
}
|
|
2856
|
+
getContentOutputDependencies() {
|
|
2857
|
+
return this.plugins().flatMap((plugin) => plugin.outputDependencies ?? []);
|
|
2155
2858
|
}
|
|
2156
2859
|
async collectDiagnostics(contentIndex) {
|
|
2157
2860
|
const tracer = this.observability().tracer;
|
|
@@ -2209,6 +2912,7 @@ var PluginRuntime = class {
|
|
|
2209
2912
|
return {
|
|
2210
2913
|
config: this.pipelineOptions.config,
|
|
2211
2914
|
contentIndex,
|
|
2915
|
+
contentSource: this.pipelineOptions.contentSource,
|
|
2212
2916
|
diagnostics: this.diagnostics
|
|
2213
2917
|
};
|
|
2214
2918
|
}
|
|
@@ -2258,27 +2962,33 @@ var PluginRuntime = class {
|
|
|
2258
2962
|
return this.pipelineOptions.observability ?? noopObservability;
|
|
2259
2963
|
}
|
|
2260
2964
|
plugins() {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
if (declaredPageTypes !== void 0 && !Array.isArray(declaredPageTypes)) {
|
|
2266
|
-
throw new TypeError(`Plugin ${plugin.name} pageTypes must be an array`);
|
|
2267
|
-
}
|
|
2268
|
-
for (const pageType of declaredPageTypes ?? []) {
|
|
2269
|
-
validatePageType(plugin.name, pageType);
|
|
2270
|
-
const previous = pageTypes.get(pageType.id);
|
|
2271
|
-
if (previous) {
|
|
2272
|
-
throw new Error(
|
|
2273
|
-
`Plugin page type "${pageType.id}" is provided by both ${previous} and ${plugin.name}`
|
|
2274
|
-
);
|
|
2275
|
-
}
|
|
2276
|
-
pageTypes.set(pageType.id, plugin.name);
|
|
2277
|
-
}
|
|
2965
|
+
if (this.resolvedPlugins === null) {
|
|
2966
|
+
const resolved = resolvePlugins(this.pipelineOptions.plugins);
|
|
2967
|
+
collectPageTypeOwners(resolved);
|
|
2968
|
+
this.resolvedPlugins = resolved;
|
|
2278
2969
|
}
|
|
2279
|
-
return
|
|
2970
|
+
return this.resolvedPlugins;
|
|
2280
2971
|
}
|
|
2281
2972
|
};
|
|
2973
|
+
function collectPageTypeOwners(plugins) {
|
|
2974
|
+
const pageTypes = /* @__PURE__ */ new Map();
|
|
2975
|
+
for (const plugin of plugins) {
|
|
2976
|
+
const declaredPageTypes = plugin.pageTypes;
|
|
2977
|
+
if (declaredPageTypes !== void 0 && !Array.isArray(declaredPageTypes)) {
|
|
2978
|
+
throw new TypeError(`Plugin ${plugin.name} pageTypes must be an array`);
|
|
2979
|
+
}
|
|
2980
|
+
for (const pageType of declaredPageTypes ?? []) {
|
|
2981
|
+
validatePageType(plugin.name, pageType);
|
|
2982
|
+
const previous = pageTypes.get(pageType.id);
|
|
2983
|
+
if (previous) {
|
|
2984
|
+
throw new Error(
|
|
2985
|
+
`Plugin page type "${pageType.id}" is provided by both ${previous} and ${plugin.name}`
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
pageTypes.set(pageType.id, plugin.name);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2282
2992
|
function validatePageType(pluginName, pageType) {
|
|
2283
2993
|
if (typeof pageType !== "object" || pageType === null) {
|
|
2284
2994
|
throw new TypeError(`Plugin ${pluginName} pageTypes must contain objects`);
|
|
@@ -2304,24 +3014,48 @@ function validatePageType(pluginName, pageType) {
|
|
|
2304
3014
|
`Plugin ${pluginName} page type "${candidate.id}" priority must be a finite number`
|
|
2305
3015
|
);
|
|
2306
3016
|
}
|
|
3017
|
+
if (candidate.outputDependencies !== void 0 && !Array.isArray(candidate.outputDependencies) && typeof candidate.outputDependencies !== "function") {
|
|
3018
|
+
throw new TypeError(
|
|
3019
|
+
`Plugin ${pluginName} page type "${candidate.id}" outputDependencies must be an array or function`
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
if (candidate.directoryIndex !== void 0 && typeof candidate.directoryIndex !== "boolean") {
|
|
3023
|
+
throw new TypeError(
|
|
3024
|
+
`Plugin ${pluginName} page type "${candidate.id}" directoryIndex must be a boolean`
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
function normalizePageTypePath(pathname, pageType) {
|
|
3029
|
+
return pageType.directoryIndex ? normalizeDirectoryPagePath(pathname) : normalizePagePath(pathname);
|
|
3030
|
+
}
|
|
3031
|
+
function normalizeDirectoryPagePath(pathname) {
|
|
3032
|
+
const path5 = normalizePagePath(pathname);
|
|
3033
|
+
return path5 === "/" ? path5 : `${path5}/`;
|
|
2307
3034
|
}
|
|
2308
3035
|
function normalizePagePath(pathname) {
|
|
2309
|
-
const
|
|
2310
|
-
return
|
|
3036
|
+
const path5 = `/${pathname.split("/").filter(Boolean).join("/")}`;
|
|
3037
|
+
return path5 === "/" ? path5 : path5.replace(/\/$/, "");
|
|
2311
3038
|
}
|
|
2312
3039
|
function countSeverity(diagnostics, severity) {
|
|
2313
3040
|
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
2314
3041
|
}
|
|
3042
|
+
function uniqueOutputs(outputs) {
|
|
3043
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
3044
|
+
for (const output of outputs) byPath.set(output.path, output);
|
|
3045
|
+
return [...byPath.values()].sort(
|
|
3046
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
2315
3049
|
|
|
2316
3050
|
// src/content/affected_content.ts
|
|
2317
|
-
function determineAffectedContent(changeSet, previousState, currentPaths) {
|
|
3051
|
+
function determineAffectedContent(changeSet, previousState, currentPaths, currentContentIndex) {
|
|
2318
3052
|
const changedPaths = [
|
|
2319
3053
|
...changeSet.added,
|
|
2320
3054
|
...changeSet.changed,
|
|
2321
3055
|
...changeSet.removed
|
|
2322
3056
|
];
|
|
2323
3057
|
const direct = new Set(
|
|
2324
|
-
changedPaths.filter(isMarkdownPath2).map(
|
|
3058
|
+
changedPaths.filter(isMarkdownPath2).map(toDependencyId)
|
|
2325
3059
|
);
|
|
2326
3060
|
const dependent = /* @__PURE__ */ new Set();
|
|
2327
3061
|
if (!previousState) {
|
|
@@ -2329,40 +3063,81 @@ function determineAffectedContent(changeSet, previousState, currentPaths) {
|
|
|
2329
3063
|
return { direct, dependent };
|
|
2330
3064
|
}
|
|
2331
3065
|
if (changeSet.added.length > 0 || changeSet.removed.length > 0) {
|
|
2332
|
-
|
|
3066
|
+
const changedIndexKeys = findChangedIndexKeys(
|
|
3067
|
+
previousState.contentIndex,
|
|
3068
|
+
currentContentIndex
|
|
3069
|
+
);
|
|
3070
|
+
for (const [path5, entry] of Object.entries(previousState.entries)) {
|
|
3071
|
+
if (!isMarkdownPath2(path5)) continue;
|
|
3072
|
+
const slug = toDependencyId(path5);
|
|
3073
|
+
if (direct.has(slug) || dependent.has(slug)) continue;
|
|
3074
|
+
if (!entry.linkTargets?.some((target) => changedIndexKeys.has(target))) {
|
|
3075
|
+
continue;
|
|
3076
|
+
}
|
|
3077
|
+
dependent.add(slug);
|
|
3078
|
+
}
|
|
2333
3079
|
}
|
|
2334
|
-
const
|
|
2335
|
-
const
|
|
3080
|
+
const dependentsByDependency = buildDependentsByDependency(previousState);
|
|
3081
|
+
const pending = changedPaths.map(toDependency);
|
|
3082
|
+
const seen = new Set(pending.map(dependencyKey2));
|
|
2336
3083
|
while (pending.length > 0) {
|
|
2337
3084
|
const changed = pending.pop();
|
|
2338
3085
|
if (changed === void 0) continue;
|
|
2339
|
-
for (const
|
|
2340
|
-
if (!isMarkdownPath2(path4)) continue;
|
|
2341
|
-
const slug = toDependencyKey(path4);
|
|
3086
|
+
for (const slug of dependentsByDependency.get(dependencyKey2(changed)) ?? []) {
|
|
2342
3087
|
if (direct.has(slug) || dependent.has(slug)) continue;
|
|
2343
|
-
if (!entry.dependencies.includes(changed)) continue;
|
|
2344
3088
|
dependent.add(slug);
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
3089
|
+
const dependency = { kind: "content", id: slug };
|
|
3090
|
+
if (!seen.has(dependencyKey2(dependency))) {
|
|
3091
|
+
seen.add(dependencyKey2(dependency));
|
|
3092
|
+
pending.push(dependency);
|
|
2348
3093
|
}
|
|
2349
3094
|
}
|
|
2350
3095
|
}
|
|
2351
3096
|
return { direct, dependent };
|
|
2352
3097
|
}
|
|
3098
|
+
function findChangedIndexKeys(previousIndex, currentIndex) {
|
|
3099
|
+
const changed = /* @__PURE__ */ new Set();
|
|
3100
|
+
for (const [key, previousValue] of Object.entries(previousIndex)) {
|
|
3101
|
+
if (currentIndex.get(key) !== previousValue) changed.add(key);
|
|
3102
|
+
}
|
|
3103
|
+
for (const key of currentIndex.keys()) {
|
|
3104
|
+
if (!(key in previousIndex)) changed.add(key);
|
|
3105
|
+
}
|
|
3106
|
+
return changed;
|
|
3107
|
+
}
|
|
2353
3108
|
function addAllNotes(target, currentPaths, exclude) {
|
|
2354
|
-
for (const
|
|
2355
|
-
if (!isMarkdownPath2(
|
|
2356
|
-
const slug =
|
|
3109
|
+
for (const path5 of currentPaths) {
|
|
3110
|
+
if (!isMarkdownPath2(path5)) continue;
|
|
3111
|
+
const slug = toDependencyId(path5);
|
|
2357
3112
|
if (exclude?.has(slug)) continue;
|
|
2358
3113
|
target.add(slug);
|
|
2359
3114
|
}
|
|
2360
3115
|
}
|
|
2361
|
-
function isMarkdownPath2(
|
|
2362
|
-
return
|
|
3116
|
+
function isMarkdownPath2(path5) {
|
|
3117
|
+
return path5.endsWith(".md");
|
|
3118
|
+
}
|
|
3119
|
+
function toDependencyId(path5) {
|
|
3120
|
+
return path5.replace(/\.md$/, "");
|
|
3121
|
+
}
|
|
3122
|
+
function toDependency(path5) {
|
|
3123
|
+
return isMarkdownPath2(path5) ? { kind: "content", id: toDependencyId(path5) } : { kind: "file", id: path5.replace(/\\/g, "/") };
|
|
3124
|
+
}
|
|
3125
|
+
function buildDependentsByDependency(state2) {
|
|
3126
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
3127
|
+
for (const [path5, entry] of Object.entries(state2.entries)) {
|
|
3128
|
+
if (!isMarkdownPath2(path5)) continue;
|
|
3129
|
+
const slug = toDependencyId(path5);
|
|
3130
|
+
for (const dependency of entry.dependencies) {
|
|
3131
|
+
const key = dependencyKey2(dependency);
|
|
3132
|
+
const values = dependents.get(key) ?? /* @__PURE__ */ new Set();
|
|
3133
|
+
values.add(slug);
|
|
3134
|
+
dependents.set(key, values);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
return dependents;
|
|
2363
3138
|
}
|
|
2364
|
-
function
|
|
2365
|
-
return
|
|
3139
|
+
function dependencyKey2(dependency) {
|
|
3140
|
+
return `${dependency.kind}:${dependency.id}`;
|
|
2366
3141
|
}
|
|
2367
3142
|
|
|
2368
3143
|
// src/content/content_change_set.ts
|
|
@@ -2373,18 +3148,18 @@ function diffContentEntries(previous, current) {
|
|
|
2373
3148
|
const added = [];
|
|
2374
3149
|
const changed = [];
|
|
2375
3150
|
const unchanged = [];
|
|
2376
|
-
for (const [
|
|
2377
|
-
const previousEntry = previous.entries[
|
|
3151
|
+
for (const [path5, fingerprint] of currentFingerprints) {
|
|
3152
|
+
const previousEntry = previous.entries[path5];
|
|
2378
3153
|
if (!previousEntry) {
|
|
2379
|
-
added.push(
|
|
3154
|
+
added.push(path5);
|
|
2380
3155
|
} else if (previousEntry.fingerprint !== fingerprint) {
|
|
2381
|
-
changed.push(
|
|
3156
|
+
changed.push(path5);
|
|
2382
3157
|
} else {
|
|
2383
|
-
unchanged.push(
|
|
3158
|
+
unchanged.push(path5);
|
|
2384
3159
|
}
|
|
2385
3160
|
}
|
|
2386
3161
|
const removed = Object.keys(previous.entries).filter(
|
|
2387
|
-
(
|
|
3162
|
+
(path5) => !currentFingerprints.has(path5)
|
|
2388
3163
|
);
|
|
2389
3164
|
return sortChangeSet({ added, changed, removed, unchanged });
|
|
2390
3165
|
}
|
|
@@ -2408,119 +3183,6 @@ function sortChangeSet(changeSet) {
|
|
|
2408
3183
|
};
|
|
2409
3184
|
}
|
|
2410
3185
|
|
|
2411
|
-
// src/content/content_build_coordinator.ts
|
|
2412
|
-
var ContentBuildCoordinator = class {
|
|
2413
|
-
constructor(dependencies) {
|
|
2414
|
-
this.dependencies = dependencies;
|
|
2415
|
-
}
|
|
2416
|
-
dependencies;
|
|
2417
|
-
preparation = null;
|
|
2418
|
-
getPreparation(incremental) {
|
|
2419
|
-
this.preparation ??= this.prepare(incremental);
|
|
2420
|
-
return this.preparation;
|
|
2421
|
-
}
|
|
2422
|
-
async commit(preparation, manifest) {
|
|
2423
|
-
const entriesBySlug = new Map(
|
|
2424
|
-
manifest.entries.map((entry) => [entry.slug, entry])
|
|
2425
|
-
);
|
|
2426
|
-
const state = {
|
|
2427
|
-
version: CONTENT_BUILD_STATE_VERSION,
|
|
2428
|
-
entries: Object.fromEntries(
|
|
2429
|
-
preparation.currentEntries.map(({ entry, fingerprint }) => {
|
|
2430
|
-
const manifestEntry = entriesBySlug.get(toSlug(entry.path));
|
|
2431
|
-
return [
|
|
2432
|
-
entry.path,
|
|
2433
|
-
{
|
|
2434
|
-
fingerprint,
|
|
2435
|
-
dependencies: collectDependencies2(manifestEntry)
|
|
2436
|
-
}
|
|
2437
|
-
];
|
|
2438
|
-
})
|
|
2439
|
-
),
|
|
2440
|
-
contentIndex: Object.fromEntries(
|
|
2441
|
-
[...manifest.contentIndex.entries()].sort(
|
|
2442
|
-
([left], [right]) => left.localeCompare(right)
|
|
2443
|
-
)
|
|
2444
|
-
)
|
|
2445
|
-
};
|
|
2446
|
-
try {
|
|
2447
|
-
await saveContentBuildState(this.dependencies.buildStatePath, state);
|
|
2448
|
-
} catch {
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
async prepare(incremental) {
|
|
2452
|
-
const currentEntries = await fingerprintContentEntries(
|
|
2453
|
-
await this.dependencies.getEntries(),
|
|
2454
|
-
(entry) => this.dependencies.readEntry(entry)
|
|
2455
|
-
);
|
|
2456
|
-
const previousState = incremental === false ? void 0 : await loadContentBuildState(this.dependencies.buildStatePath);
|
|
2457
|
-
const changeSet = previousState ? diffContentEntries(previousState, currentEntries) : allContentChanged(currentEntries);
|
|
2458
|
-
const affected = determineAffectedContent(
|
|
2459
|
-
changeSet,
|
|
2460
|
-
previousState,
|
|
2461
|
-
currentEntries.map(({ entry }) => entry.path)
|
|
2462
|
-
);
|
|
2463
|
-
this.dependencies.observability.tracer.event("build.incremental", {
|
|
2464
|
-
incremental: incremental !== false,
|
|
2465
|
-
added: changeSet.added.length,
|
|
2466
|
-
changed: changeSet.changed.length,
|
|
2467
|
-
removed: changeSet.removed.length,
|
|
2468
|
-
unchanged: changeSet.unchanged.length,
|
|
2469
|
-
affected: affected.direct.size + affected.dependent.size
|
|
2470
|
-
});
|
|
2471
|
-
return { previousState, currentEntries, changeSet };
|
|
2472
|
-
}
|
|
2473
|
-
};
|
|
2474
|
-
function toSlug(path4) {
|
|
2475
|
-
return path4.replace(/\.md$/, "");
|
|
2476
|
-
}
|
|
2477
|
-
function collectDependencies2(manifestEntry) {
|
|
2478
|
-
if (!manifestEntry) return [];
|
|
2479
|
-
return [
|
|
2480
|
-
...new Set(
|
|
2481
|
-
manifestEntry.links.filter(
|
|
2482
|
-
(link) => link.slug !== null
|
|
2483
|
-
).map((link) => link.slug)
|
|
2484
|
-
)
|
|
2485
|
-
].sort();
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
// src/content/content_entry_reader.ts
|
|
2489
|
-
var ContentEntryReader = class {
|
|
2490
|
-
constructor(source, observability) {
|
|
2491
|
-
this.source = source;
|
|
2492
|
-
this.observability = observability;
|
|
2493
|
-
}
|
|
2494
|
-
source;
|
|
2495
|
-
observability;
|
|
2496
|
-
entries = null;
|
|
2497
|
-
texts = /* @__PURE__ */ new Map();
|
|
2498
|
-
async getEntries() {
|
|
2499
|
-
this.entries ??= await this.observability.tracer.span(
|
|
2500
|
-
"content.scan",
|
|
2501
|
-
{},
|
|
2502
|
-
() => this.source.scan()
|
|
2503
|
-
);
|
|
2504
|
-
return this.entries;
|
|
2505
|
-
}
|
|
2506
|
-
async readText(logicalPath) {
|
|
2507
|
-
const entry = (await this.getEntries()).find(
|
|
2508
|
-
(current) => current.path === logicalPath
|
|
2509
|
-
);
|
|
2510
|
-
if (!entry) throw new Error(`Content entry was not found: ${logicalPath}`);
|
|
2511
|
-
return await this.read(entry);
|
|
2512
|
-
}
|
|
2513
|
-
read(entry) {
|
|
2514
|
-
const cached = this.texts.get(entry.path);
|
|
2515
|
-
if (cached) return cached;
|
|
2516
|
-
const content = this.source.read(entry).then(
|
|
2517
|
-
(value) => typeof value === "string" ? value : new TextDecoder().decode(value)
|
|
2518
|
-
);
|
|
2519
|
-
this.texts.set(entry.path, content);
|
|
2520
|
-
return content;
|
|
2521
|
-
}
|
|
2522
|
-
};
|
|
2523
|
-
|
|
2524
3186
|
// src/utils/tags.ts
|
|
2525
3187
|
function normalizeTag(raw) {
|
|
2526
3188
|
const cleaned = raw.replace(/[/-]+$/, "");
|
|
@@ -2581,7 +3243,7 @@ function stripYamlQuotes(value) {
|
|
|
2581
3243
|
}
|
|
2582
3244
|
|
|
2583
3245
|
// src/content/content_index_builder.ts
|
|
2584
|
-
var ContentIndexBuilder = class {
|
|
3246
|
+
var ContentIndexBuilder = class _ContentIndexBuilder {
|
|
2585
3247
|
constructor(source) {
|
|
2586
3248
|
this.source = source;
|
|
2587
3249
|
}
|
|
@@ -2593,7 +3255,26 @@ var ContentIndexBuilder = class {
|
|
|
2593
3255
|
}
|
|
2594
3256
|
return index;
|
|
2595
3257
|
}
|
|
3258
|
+
static buildFromAliases(contentEntries, aliasesByPath) {
|
|
3259
|
+
const index = /* @__PURE__ */ new Map();
|
|
3260
|
+
for (const contentEntry of contentEntries) {
|
|
3261
|
+
_ContentIndexBuilder.indexContentEntryFromAliases(
|
|
3262
|
+
index,
|
|
3263
|
+
contentEntry,
|
|
3264
|
+
aliasesByPath.get(contentEntry.path) ?? []
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3267
|
+
return index;
|
|
3268
|
+
}
|
|
2596
3269
|
async indexContentEntry(index, contentEntry, read) {
|
|
3270
|
+
const aliases = contentEntry.path.toLowerCase().endsWith(".md") ? extractFrontmatterAliases(readText(await read(contentEntry))) : [];
|
|
3271
|
+
_ContentIndexBuilder.indexContentEntryFromAliases(
|
|
3272
|
+
index,
|
|
3273
|
+
contentEntry,
|
|
3274
|
+
aliases
|
|
3275
|
+
);
|
|
3276
|
+
}
|
|
3277
|
+
static indexContentEntryFromAliases(index, contentEntry, aliases) {
|
|
2597
3278
|
const contentPath = contentEntry.path;
|
|
2598
3279
|
const ext = contentPath.split(".").pop()?.toLowerCase() ?? "";
|
|
2599
3280
|
const value = ext === "md" ? contentPath.replace(/\.md$/, "") : contentPath;
|
|
@@ -2606,10 +3287,7 @@ var ContentIndexBuilder = class {
|
|
|
2606
3287
|
}
|
|
2607
3288
|
}
|
|
2608
3289
|
if (ext === "md") {
|
|
2609
|
-
const
|
|
2610
|
-
for (const alias of extractFrontmatterAliases(markdown)) {
|
|
2611
|
-
addIndexEntry(index, alias, value);
|
|
2612
|
-
}
|
|
3290
|
+
for (const alias of aliases) addIndexEntry(index, alias, value);
|
|
2613
3291
|
}
|
|
2614
3292
|
}
|
|
2615
3293
|
};
|
|
@@ -2621,72 +3299,362 @@ function addIndexEntry(index, key, value) {
|
|
|
2621
3299
|
if (!index.has(normalizedKey)) index.set(normalizedKey, value);
|
|
2622
3300
|
}
|
|
2623
3301
|
|
|
2624
|
-
// src/content/
|
|
2625
|
-
var
|
|
3302
|
+
// src/content/content_build_coordinator.ts
|
|
3303
|
+
var ContentBuildCoordinator = class {
|
|
2626
3304
|
constructor(dependencies) {
|
|
2627
3305
|
this.dependencies = dependencies;
|
|
2628
3306
|
}
|
|
2629
3307
|
dependencies;
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
]);
|
|
2637
|
-
await this.dependencies.pluginRuntime.startBuild(contentIndex);
|
|
2638
|
-
const inputs = await Promise.all(
|
|
2639
|
-
entries.filter((entry) => entry.path.endsWith(".md")).map(async (entry) => ({
|
|
2640
|
-
slug: toSlug2(entry.path),
|
|
2641
|
-
path: entry.path,
|
|
2642
|
-
markdown: await this.dependencies.readEntry(entry)
|
|
2643
|
-
}))
|
|
3308
|
+
preparation = null;
|
|
3309
|
+
getPreparation(incremental, pipelineFingerprint, fullContentRegenerationRequired = false) {
|
|
3310
|
+
this.preparation ??= this.prepare(
|
|
3311
|
+
incremental,
|
|
3312
|
+
pipelineFingerprint,
|
|
3313
|
+
fullContentRegenerationRequired
|
|
2644
3314
|
);
|
|
2645
|
-
|
|
2646
|
-
|
|
3315
|
+
return this.preparation;
|
|
3316
|
+
}
|
|
3317
|
+
async commit(preparation, manifest, pipelineFingerprint, outputs = [], manifestEntries = manifest.entries, trackedDependencies = /* @__PURE__ */ new Map()) {
|
|
3318
|
+
const entriesBySlug = new Map(
|
|
3319
|
+
manifest.entries.map((entry) => [entry.slug, entry])
|
|
2647
3320
|
);
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
3321
|
+
const state2 = {
|
|
3322
|
+
version: CONTENT_BUILD_STATE_VERSION,
|
|
3323
|
+
entries: Object.fromEntries(
|
|
3324
|
+
preparation.currentEntries.map(({ entry, fingerprint }) => {
|
|
3325
|
+
const manifestEntry = entriesBySlug.get(toSlug(entry.path));
|
|
3326
|
+
return [
|
|
3327
|
+
entry.path,
|
|
3328
|
+
{
|
|
3329
|
+
fingerprint,
|
|
3330
|
+
aliases: preparation.currentContentAliases.get(entry.path) ?? [],
|
|
3331
|
+
dependencies: collectDependencies2(
|
|
3332
|
+
manifestEntry,
|
|
3333
|
+
trackedDependencies.get(toSlug(entry.path)) ?? preparation.previousState?.entries[entry.path]?.dependencies ?? []
|
|
3334
|
+
),
|
|
3335
|
+
linkTargets: collectLinkTargets(manifestEntry)
|
|
3336
|
+
}
|
|
3337
|
+
];
|
|
3338
|
+
})
|
|
3339
|
+
),
|
|
3340
|
+
contentIndex: Object.fromEntries(
|
|
3341
|
+
[...manifest.contentIndex.entries()].sort(
|
|
3342
|
+
([left], [right]) => left.localeCompare(right)
|
|
3343
|
+
)
|
|
3344
|
+
),
|
|
3345
|
+
pipelineFingerprint,
|
|
3346
|
+
manifestEntries,
|
|
3347
|
+
outputs
|
|
3348
|
+
};
|
|
3349
|
+
try {
|
|
3350
|
+
await saveContentBuildState(this.dependencies.buildStatePath, state2);
|
|
3351
|
+
} catch (error) {
|
|
3352
|
+
this.dependencies.observability.logger.warn(
|
|
3353
|
+
"Incremental build state could not be persisted; the next build regenerates every note instead of reusing stale output.",
|
|
3354
|
+
{
|
|
3355
|
+
path: this.dependencies.buildStatePath,
|
|
3356
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
3357
|
+
}
|
|
3358
|
+
);
|
|
2658
3359
|
}
|
|
2659
|
-
await this.dependencies.pluginRuntime.extendContentLocations(
|
|
2660
|
-
inputs,
|
|
2661
|
-
locations,
|
|
2662
|
-
contentIndex
|
|
2663
|
-
);
|
|
2664
|
-
this.locations = locations;
|
|
2665
|
-
return locations;
|
|
2666
3360
|
}
|
|
2667
|
-
async
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
3361
|
+
async prepare(incremental, pipelineFingerprint, fullContentRegenerationRequired) {
|
|
3362
|
+
const entries = await this.dependencies.observability.tracer.span(
|
|
3363
|
+
"content.discovery",
|
|
3364
|
+
{},
|
|
3365
|
+
() => this.dependencies.getEntries()
|
|
3366
|
+
);
|
|
3367
|
+
const currentEntries = await this.dependencies.observability.tracer.span(
|
|
3368
|
+
"content.fingerprint",
|
|
3369
|
+
{},
|
|
3370
|
+
() => fingerprintContentEntries(
|
|
3371
|
+
entries,
|
|
3372
|
+
(entry) => this.dependencies.readEntry(entry)
|
|
2672
3373
|
)
|
|
2673
3374
|
);
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
3375
|
+
const previousState = incremental === false ? void 0 : await loadContentBuildState(this.dependencies.buildStatePath);
|
|
3376
|
+
const changeSet = previousState ? diffContentEntries(previousState, currentEntries) : allContentChanged(currentEntries);
|
|
3377
|
+
const currentContentAliases = await this.getContentAliases(
|
|
3378
|
+
currentEntries,
|
|
3379
|
+
previousState,
|
|
3380
|
+
changeSet
|
|
3381
|
+
);
|
|
3382
|
+
const currentContentIndex = await this.dependencies.observability.tracer.span(
|
|
3383
|
+
"content.index",
|
|
3384
|
+
{},
|
|
3385
|
+
() => ContentIndexBuilder.buildFromAliases(
|
|
3386
|
+
currentEntries.map(({ entry }) => entry),
|
|
3387
|
+
currentContentAliases
|
|
3388
|
+
)
|
|
3389
|
+
);
|
|
3390
|
+
const affected = !fullContentRegenerationRequired && previousState?.pipelineFingerprint === pipelineFingerprint ? determineAffectedContent(
|
|
3391
|
+
changeSet,
|
|
3392
|
+
previousState,
|
|
3393
|
+
currentEntries.map(({ entry }) => entry.path),
|
|
3394
|
+
currentContentIndex
|
|
3395
|
+
) : determineAffectedContent(
|
|
3396
|
+
allContentChanged(currentEntries),
|
|
3397
|
+
void 0,
|
|
3398
|
+
currentEntries.map(({ entry }) => entry.path),
|
|
3399
|
+
currentContentIndex
|
|
3400
|
+
);
|
|
3401
|
+
this.dependencies.observability.tracer.event("build.incremental", {
|
|
3402
|
+
incremental: incremental !== false,
|
|
3403
|
+
added: changeSet.added.length,
|
|
3404
|
+
changed: changeSet.changed.length,
|
|
3405
|
+
removed: changeSet.removed.length,
|
|
3406
|
+
unchanged: changeSet.unchanged.length,
|
|
3407
|
+
affected: affected.direct.size + affected.dependent.size
|
|
3408
|
+
});
|
|
3409
|
+
return {
|
|
3410
|
+
previousState,
|
|
3411
|
+
previousManifestEntriesBySlug: new Map(
|
|
3412
|
+
(previousState?.manifestEntries ?? []).map((entry) => [
|
|
3413
|
+
entry.slug,
|
|
3414
|
+
entry
|
|
3415
|
+
])
|
|
3416
|
+
),
|
|
3417
|
+
currentEntries,
|
|
3418
|
+
currentContentAliases,
|
|
3419
|
+
currentContentIndex,
|
|
3420
|
+
changeSet,
|
|
3421
|
+
affectedContent: affected
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
async getContentAliases(currentEntries, previousState, changeSet) {
|
|
3425
|
+
const changedPaths = /* @__PURE__ */ new Set([...changeSet.added, ...changeSet.changed]);
|
|
3426
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
3427
|
+
for (const { entry } of currentEntries) {
|
|
3428
|
+
if (!entry.path.toLowerCase().endsWith(".md")) continue;
|
|
3429
|
+
const previousAliases = previousState?.entries[entry.path]?.aliases;
|
|
3430
|
+
if (!changedPaths.has(entry.path) && previousAliases) {
|
|
3431
|
+
aliases.set(entry.path, previousAliases);
|
|
3432
|
+
continue;
|
|
3433
|
+
}
|
|
3434
|
+
aliases.set(
|
|
3435
|
+
entry.path,
|
|
3436
|
+
extractFrontmatterAliases(
|
|
3437
|
+
readText2(await this.dependencies.readEntry(entry))
|
|
3438
|
+
)
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3441
|
+
return aliases;
|
|
3442
|
+
}
|
|
3443
|
+
};
|
|
3444
|
+
function readText2(content) {
|
|
3445
|
+
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
3446
|
+
}
|
|
3447
|
+
function toSlug(path5) {
|
|
3448
|
+
return path5.replace(/\.md$/, "");
|
|
3449
|
+
}
|
|
3450
|
+
function collectDependencies2(manifestEntry, trackedDependencies) {
|
|
3451
|
+
if (!manifestEntry) return [];
|
|
3452
|
+
return [
|
|
3453
|
+
...new Map(
|
|
3454
|
+
[
|
|
3455
|
+
...manifestEntry.links.filter(
|
|
3456
|
+
(link) => link.slug !== null
|
|
3457
|
+
).map((link) => ({ kind: "content", id: link.slug })),
|
|
3458
|
+
...manifestEntry.assets.map((asset) => ({
|
|
3459
|
+
kind: "file",
|
|
3460
|
+
id: asset.path.replace(/\\/g, "/")
|
|
3461
|
+
})),
|
|
3462
|
+
...trackedDependencies.filter(
|
|
3463
|
+
(dependency) => dependency.kind === "content" || dependency.kind === "file"
|
|
3464
|
+
).map((dependency) => ({
|
|
3465
|
+
kind: dependency.kind,
|
|
3466
|
+
id: dependency.id.replace(/\\/g, "/")
|
|
3467
|
+
}))
|
|
3468
|
+
].map((dependency) => [
|
|
3469
|
+
`${dependency.kind}:${dependency.id}`,
|
|
3470
|
+
dependency
|
|
3471
|
+
])
|
|
3472
|
+
).values()
|
|
3473
|
+
].toSorted(
|
|
3474
|
+
(left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`)
|
|
3475
|
+
);
|
|
3476
|
+
}
|
|
3477
|
+
function collectLinkTargets(manifestEntry) {
|
|
3478
|
+
if (!manifestEntry) return [];
|
|
3479
|
+
return [
|
|
3480
|
+
...new Set(manifestEntry.links.map((link) => link.raw.toLowerCase()))
|
|
3481
|
+
].sort();
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
// src/content/content_entry_reader.ts
|
|
3485
|
+
var ContentEntryReader = class {
|
|
3486
|
+
constructor(source, observability) {
|
|
3487
|
+
this.source = source;
|
|
3488
|
+
this.observability = observability;
|
|
3489
|
+
}
|
|
3490
|
+
source;
|
|
3491
|
+
observability;
|
|
3492
|
+
entries = null;
|
|
3493
|
+
entriesPromise = null;
|
|
3494
|
+
entriesByPath = null;
|
|
3495
|
+
texts = /* @__PURE__ */ new Map();
|
|
3496
|
+
async getEntries() {
|
|
3497
|
+
if (this.entries) return this.entries;
|
|
3498
|
+
if (this.entriesPromise) return await this.entriesPromise;
|
|
3499
|
+
this.entriesPromise = this.observability.tracer.span(
|
|
3500
|
+
"content.scan",
|
|
3501
|
+
{},
|
|
3502
|
+
() => this.source.scan()
|
|
3503
|
+
);
|
|
3504
|
+
try {
|
|
3505
|
+
this.entries = await this.entriesPromise;
|
|
3506
|
+
this.entriesByPath = new Map(
|
|
3507
|
+
this.entries.map((entry) => [entry.path, entry])
|
|
3508
|
+
);
|
|
3509
|
+
return this.entries;
|
|
3510
|
+
} finally {
|
|
3511
|
+
this.entriesPromise = null;
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
async readText(logicalPath) {
|
|
3515
|
+
await this.getEntries();
|
|
3516
|
+
const entry = this.entriesByPath?.get(logicalPath);
|
|
3517
|
+
if (!entry) throw new Error(`Content entry was not found: ${logicalPath}`);
|
|
3518
|
+
return await this.read(entry);
|
|
3519
|
+
}
|
|
3520
|
+
read(entry) {
|
|
3521
|
+
const cached = this.texts.get(entry.path);
|
|
3522
|
+
if (cached) return cached;
|
|
3523
|
+
const content = this.source.read(entry).then(
|
|
3524
|
+
(value) => typeof value === "string" ? value : new TextDecoder().decode(value)
|
|
3525
|
+
).finally(() => this.texts.delete(entry.path));
|
|
3526
|
+
this.texts.set(entry.path, content);
|
|
3527
|
+
return content;
|
|
3528
|
+
}
|
|
3529
|
+
};
|
|
3530
|
+
|
|
3531
|
+
// src/content/content_location_resolver.ts
|
|
3532
|
+
var ContentLocationResolver = class {
|
|
3533
|
+
constructor(dependencies) {
|
|
3534
|
+
this.dependencies = dependencies;
|
|
3535
|
+
}
|
|
3536
|
+
dependencies;
|
|
3537
|
+
locations = null;
|
|
3538
|
+
locationsPromise = null;
|
|
3539
|
+
permalinks = null;
|
|
3540
|
+
permalinksPromise = null;
|
|
3541
|
+
async getLocations() {
|
|
3542
|
+
if (this.locations) return this.locations;
|
|
3543
|
+
if (this.locationsPromise) return await this.locationsPromise;
|
|
3544
|
+
this.locationsPromise = this.dependencies.observability.tracer.span(
|
|
3545
|
+
"content.locations",
|
|
3546
|
+
{},
|
|
3547
|
+
() => this.resolveLocations()
|
|
3548
|
+
);
|
|
3549
|
+
try {
|
|
3550
|
+
return await this.locationsPromise;
|
|
3551
|
+
} finally {
|
|
3552
|
+
this.locationsPromise = null;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
async resolveLocations() {
|
|
3556
|
+
const [entries, contentIndex] = await Promise.all([
|
|
3557
|
+
this.dependencies.getEntries(),
|
|
3558
|
+
this.dependencies.getContentIndex()
|
|
3559
|
+
]);
|
|
3560
|
+
await this.dependencies.pluginRuntime.startBuild(contentIndex);
|
|
3561
|
+
const inputs = await mapConcurrent2(
|
|
3562
|
+
entries.filter((entry) => entry.path.endsWith(".md")),
|
|
3563
|
+
64,
|
|
3564
|
+
async (entry) => ({
|
|
3565
|
+
slug: toSlug2(entry.path),
|
|
3566
|
+
path: entry.path,
|
|
3567
|
+
markdown: await this.dependencies.readEntry(entry)
|
|
3568
|
+
})
|
|
3569
|
+
);
|
|
3570
|
+
const locations = new Map(
|
|
3571
|
+
inputs.map((input) => [input.slug, resolveDefaultContentLocation(input)])
|
|
3572
|
+
);
|
|
3573
|
+
for (const location of await this.dependencies.pluginRuntime.resolveContentLocations(
|
|
3574
|
+
inputs,
|
|
3575
|
+
contentIndex
|
|
3576
|
+
)) {
|
|
3577
|
+
if (!locations.has(location.slug)) {
|
|
3578
|
+
throw new Error(
|
|
3579
|
+
`Content public location references an unknown slug: ${location.slug}`
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
locations.set(location.slug, location);
|
|
3583
|
+
}
|
|
3584
|
+
await this.dependencies.pluginRuntime.extendContentLocations(
|
|
3585
|
+
inputs,
|
|
3586
|
+
locations,
|
|
3587
|
+
contentIndex
|
|
3588
|
+
);
|
|
3589
|
+
this.locations = locations;
|
|
3590
|
+
return locations;
|
|
3591
|
+
}
|
|
3592
|
+
async getPermalinks() {
|
|
3593
|
+
if (this.permalinks) return this.permalinks;
|
|
3594
|
+
if (this.permalinksPromise) return await this.permalinksPromise;
|
|
3595
|
+
this.permalinksPromise = this.createPermalinks();
|
|
3596
|
+
try {
|
|
3597
|
+
this.permalinks = await this.permalinksPromise;
|
|
3598
|
+
return this.permalinks;
|
|
3599
|
+
} finally {
|
|
3600
|
+
this.permalinksPromise = null;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
async createPermalinks() {
|
|
3604
|
+
return new Map(
|
|
3605
|
+
Array.from(
|
|
3606
|
+
(await this.getLocations()).entries(),
|
|
3607
|
+
([slug, location]) => [slug, location.permalink]
|
|
3608
|
+
)
|
|
3609
|
+
);
|
|
3610
|
+
}
|
|
3611
|
+
populateRedirects(manifest, locations) {
|
|
3612
|
+
for (const [slug, location] of locations) {
|
|
3613
|
+
for (const redirect of location.redirects ?? []) {
|
|
2678
3614
|
manifest.redirects.set(redirect.path, { ...redirect, slug });
|
|
2679
3615
|
}
|
|
2680
3616
|
}
|
|
2681
3617
|
}
|
|
2682
3618
|
};
|
|
2683
|
-
function toSlug2(
|
|
2684
|
-
return
|
|
3619
|
+
function toSlug2(path5) {
|
|
3620
|
+
return path5.replace(/\.md$/, "");
|
|
3621
|
+
}
|
|
3622
|
+
async function mapConcurrent2(values, concurrency, map) {
|
|
3623
|
+
const results = new Array(values.length);
|
|
3624
|
+
let nextIndex = 0;
|
|
3625
|
+
async function worker() {
|
|
3626
|
+
while (nextIndex < values.length) {
|
|
3627
|
+
const index = nextIndex;
|
|
3628
|
+
nextIndex += 1;
|
|
3629
|
+
const value = values[index];
|
|
3630
|
+
if (value !== void 0) results[index] = await map(value);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
await Promise.all(
|
|
3634
|
+
Array.from(
|
|
3635
|
+
{ length: Math.min(concurrency, values.length) },
|
|
3636
|
+
() => worker()
|
|
3637
|
+
)
|
|
3638
|
+
);
|
|
3639
|
+
return results;
|
|
2685
3640
|
}
|
|
2686
3641
|
|
|
2687
3642
|
// src/content/file_system_content_source.ts
|
|
2688
|
-
import
|
|
2689
|
-
import
|
|
3643
|
+
import fs4 from "node:fs/promises";
|
|
3644
|
+
import path4 from "node:path";
|
|
3645
|
+
var INTERNAL_CONTENT_IGNORE_PATTERNS = [
|
|
3646
|
+
".git",
|
|
3647
|
+
".git/**",
|
|
3648
|
+
".github",
|
|
3649
|
+
".github/**",
|
|
3650
|
+
".obsidian",
|
|
3651
|
+
".obsidian/**",
|
|
3652
|
+
"node_modules",
|
|
3653
|
+
"node_modules/**",
|
|
3654
|
+
"**/.DS_Store",
|
|
3655
|
+
"**/Thumbs.db",
|
|
3656
|
+
"**/desktop.ini"
|
|
3657
|
+
];
|
|
2690
3658
|
var FileSystemContentSource = class {
|
|
2691
3659
|
constructor(contentDirectory, exclude = []) {
|
|
2692
3660
|
this.contentDirectory = contentDirectory;
|
|
@@ -2695,33 +3663,41 @@ var FileSystemContentSource = class {
|
|
|
2695
3663
|
contentDirectory;
|
|
2696
3664
|
exclude;
|
|
2697
3665
|
async scan() {
|
|
2698
|
-
|
|
3666
|
+
try {
|
|
3667
|
+
return await this.scanDirectory(this.contentDirectory, "");
|
|
3668
|
+
} catch (error) {
|
|
3669
|
+
if (isMissingDirectoryError(error, this.contentDirectory)) {
|
|
3670
|
+
throw missingContentDirectoryError(this.contentDirectory);
|
|
3671
|
+
}
|
|
3672
|
+
throw error;
|
|
3673
|
+
}
|
|
2699
3674
|
}
|
|
2700
3675
|
async read(entry) {
|
|
2701
3676
|
const filePath = this.resolveFilePath(entry.path);
|
|
2702
|
-
const stats = await
|
|
3677
|
+
const stats = await fs4.lstat(filePath);
|
|
2703
3678
|
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
2704
3679
|
throw new Error(`Content entry is not a regular file: ${entry.path}`);
|
|
2705
3680
|
}
|
|
2706
|
-
return await
|
|
3681
|
+
return await fs4.readFile(filePath);
|
|
2707
3682
|
}
|
|
2708
|
-
async scanDirectory(directory,
|
|
2709
|
-
const directoryEntries = await
|
|
3683
|
+
async scanDirectory(directory, parentPath2) {
|
|
3684
|
+
const directoryEntries = await fs4.readdir(directory, {
|
|
2710
3685
|
withFileTypes: true
|
|
2711
3686
|
});
|
|
2712
3687
|
const entries = [];
|
|
2713
3688
|
for (const directoryEntry of directoryEntries) {
|
|
2714
3689
|
if (directoryEntry.isSymbolicLink()) continue;
|
|
2715
3690
|
const logicalPath = normalizeContentPath(
|
|
2716
|
-
|
|
3691
|
+
parentPath2 ? `${parentPath2}/${directoryEntry.name}` : directoryEntry.name
|
|
2717
3692
|
);
|
|
2718
|
-
const filePath =
|
|
3693
|
+
const filePath = path4.join(directory, directoryEntry.name);
|
|
3694
|
+
if (this.isExcluded(logicalPath)) continue;
|
|
2719
3695
|
if (directoryEntry.isDirectory()) {
|
|
2720
3696
|
entries.push(...await this.scanDirectory(filePath, logicalPath));
|
|
2721
3697
|
continue;
|
|
2722
3698
|
}
|
|
2723
|
-
if (!directoryEntry.isFile()
|
|
2724
|
-
const stats = await
|
|
3699
|
+
if (!directoryEntry.isFile()) continue;
|
|
3700
|
+
const stats = await fs4.stat(filePath);
|
|
2725
3701
|
entries.push({
|
|
2726
3702
|
path: logicalPath,
|
|
2727
3703
|
metadata: {
|
|
@@ -2733,26 +3709,44 @@ var FileSystemContentSource = class {
|
|
|
2733
3709
|
return entries;
|
|
2734
3710
|
}
|
|
2735
3711
|
isExcluded(logicalPath) {
|
|
2736
|
-
|
|
2737
|
-
return logicalPath.endsWith(".md") && isExcluded(this.exclude, logicalPath.slice(0, -3));
|
|
3712
|
+
return isIgnoredContentPath(logicalPath, this.exclude);
|
|
2738
3713
|
}
|
|
2739
3714
|
resolveFilePath(logicalPath) {
|
|
2740
3715
|
const normalizedPath = normalizeLogicalPath(logicalPath);
|
|
2741
|
-
const filePath =
|
|
2742
|
-
const relativePath =
|
|
2743
|
-
if (relativePath.startsWith("..") ||
|
|
3716
|
+
const filePath = path4.resolve(this.contentDirectory, normalizedPath);
|
|
3717
|
+
const relativePath = path4.relative(this.contentDirectory, filePath);
|
|
3718
|
+
if (relativePath.startsWith("..") || path4.isAbsolute(relativePath)) {
|
|
2744
3719
|
throw new Error(`Invalid content path: ${logicalPath}`);
|
|
2745
3720
|
}
|
|
2746
3721
|
return filePath;
|
|
2747
3722
|
}
|
|
2748
3723
|
};
|
|
3724
|
+
function isIgnoredContentPath(logicalPath, exclude = []) {
|
|
3725
|
+
if (isExcluded(INTERNAL_CONTENT_IGNORE_PATTERNS, logicalPath)) return true;
|
|
3726
|
+
if (isExcluded(exclude, logicalPath)) return true;
|
|
3727
|
+
return logicalPath.endsWith(".md") && isExcluded(exclude, logicalPath.slice(0, -3));
|
|
3728
|
+
}
|
|
2749
3729
|
function normalizeLogicalPath(logicalPath) {
|
|
2750
3730
|
const normalizedPath = normalizeContentPath(logicalPath);
|
|
2751
|
-
if (normalizedPath.length === 0 || normalizedPath.startsWith("/") ||
|
|
3731
|
+
if (normalizedPath.length === 0 || normalizedPath.startsWith("/") || path4.isAbsolute(normalizedPath) || path4.win32.isAbsolute(normalizedPath) || normalizedPath.split("/").some((segment) => segment === "..")) {
|
|
2752
3732
|
throw new Error(`Invalid content path: ${logicalPath}`);
|
|
2753
3733
|
}
|
|
2754
3734
|
return normalizedPath;
|
|
2755
3735
|
}
|
|
3736
|
+
function isMissingDirectoryError(error, directory) {
|
|
3737
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT" && "path" in error && error.path === directory;
|
|
3738
|
+
}
|
|
3739
|
+
function missingContentDirectoryError(directory) {
|
|
3740
|
+
const relative = path4.relative(process.cwd(), directory);
|
|
3741
|
+
const display = relative && !relative.startsWith("..") ? relative : directory;
|
|
3742
|
+
const error = new Error(
|
|
3743
|
+
`Could not find the content directory:
|
|
3744
|
+
|
|
3745
|
+
${display}`
|
|
3746
|
+
);
|
|
3747
|
+
error.hint = "Check `content.directory` in your Riebeckite configuration.";
|
|
3748
|
+
return error;
|
|
3749
|
+
}
|
|
2756
3750
|
|
|
2757
3751
|
// src/content/content_links.ts
|
|
2758
3752
|
var WIKILINK_PATTERN = /(!)?\[\[([^\]|#^]+)(?:[#^][^\]|]+)?(?:\|[^\]]+)?\]\]/g;
|
|
@@ -2808,7 +3802,7 @@ function readOptionalStableId(value, field) {
|
|
|
2808
3802
|
|
|
2809
3803
|
// src/content/manifest_builder.ts
|
|
2810
3804
|
var ManifestBuilder = class {
|
|
2811
|
-
createEntry(slug, markdown, processed, contentIndex, location) {
|
|
3805
|
+
createEntry(slug, markdown, processed, contentIndex, location, publishing) {
|
|
2812
3806
|
const links = extractContentLinks(markdown, contentIndex);
|
|
2813
3807
|
const assets = links.filter(
|
|
2814
3808
|
(link) => (link.kind === "image" || link.kind === "attachment") && link.slug !== null
|
|
@@ -2820,7 +3814,9 @@ var ManifestBuilder = class {
|
|
|
2820
3814
|
permalink: location.permalink,
|
|
2821
3815
|
publicLocation: location,
|
|
2822
3816
|
title: getManifestTitle(slug, processed.frontmatter.title),
|
|
3817
|
+
aliases: extractFrontmatterAliases(markdown),
|
|
2823
3818
|
frontmatter: processed.frontmatter,
|
|
3819
|
+
publishing,
|
|
2824
3820
|
html: processed.html,
|
|
2825
3821
|
tags: uniqueStrings([
|
|
2826
3822
|
...normalizeFrontmatterTags(processed.frontmatter.tags),
|
|
@@ -2834,6 +3830,7 @@ var ManifestBuilder = class {
|
|
|
2834
3830
|
build(entries, contentIndex) {
|
|
2835
3831
|
const bySlug = new Map(entries.map((entry) => [entry.slug, entry]));
|
|
2836
3832
|
const byContentId = createContentIdIndex(entries);
|
|
3833
|
+
const byAlias = createAliasIndex(entries);
|
|
2837
3834
|
const byPermalink = new Map(
|
|
2838
3835
|
entries.map((entry) => [entry.permalink, entry])
|
|
2839
3836
|
);
|
|
@@ -2858,9 +3855,12 @@ var ManifestBuilder = class {
|
|
|
2858
3855
|
const manifest = {
|
|
2859
3856
|
entries,
|
|
2860
3857
|
publicEntries: [...entries],
|
|
3858
|
+
discoverableEntries: [...entries],
|
|
2861
3859
|
bySlug,
|
|
2862
3860
|
byContentId,
|
|
3861
|
+
byAlias,
|
|
2863
3862
|
byPermalink,
|
|
3863
|
+
byRoutablePermalink: new Map(byPermalink),
|
|
2864
3864
|
redirects: /* @__PURE__ */ new Map(),
|
|
2865
3865
|
publicRedirects: /* @__PURE__ */ new Map(),
|
|
2866
3866
|
byTag,
|
|
@@ -2872,11 +3872,21 @@ var ManifestBuilder = class {
|
|
|
2872
3872
|
clientEntries: [],
|
|
2873
3873
|
diagnostics: [],
|
|
2874
3874
|
generatedOutputs: [],
|
|
3875
|
+
folderLocations: /* @__PURE__ */ new Map(),
|
|
3876
|
+
pageRoutes: [],
|
|
2875
3877
|
pagePaths: []
|
|
2876
3878
|
};
|
|
2877
3879
|
return { ...manifest, graph: createContentGraph(manifest) };
|
|
2878
3880
|
}
|
|
2879
3881
|
};
|
|
3882
|
+
function createAliasIndex(entries) {
|
|
3883
|
+
const byAlias = /* @__PURE__ */ new Map();
|
|
3884
|
+
for (const entry of entries) {
|
|
3885
|
+
for (const alias of entry.aliases ?? [])
|
|
3886
|
+
appendToMap(byAlias, alias.toLowerCase(), entry);
|
|
3887
|
+
}
|
|
3888
|
+
return byAlias;
|
|
3889
|
+
}
|
|
2880
3890
|
function createContentIdIndex(entries) {
|
|
2881
3891
|
const byContentId = /* @__PURE__ */ new Map();
|
|
2882
3892
|
for (const entry of entries) {
|
|
@@ -2913,21 +3923,283 @@ function appendUniqueToMap(map, key, value) {
|
|
|
2913
3923
|
map.set(key, values);
|
|
2914
3924
|
}
|
|
2915
3925
|
|
|
3926
|
+
// src/content/output_dependency.ts
|
|
3927
|
+
function determineOutputChanges(input) {
|
|
3928
|
+
const current = buildOutputInventory(
|
|
3929
|
+
input.manifest,
|
|
3930
|
+
input.pluginPageOutputs,
|
|
3931
|
+
input.contentOutputDependencies
|
|
3932
|
+
);
|
|
3933
|
+
const previous = new Map(
|
|
3934
|
+
(input.previousState?.outputs ?? []).map((output) => [output.path, output])
|
|
3935
|
+
);
|
|
3936
|
+
const changedContent = collectChangedContent(input);
|
|
3937
|
+
const changedTags = collectChangedTags(
|
|
3938
|
+
input.previousState,
|
|
3939
|
+
input.manifest,
|
|
3940
|
+
changedContent
|
|
3941
|
+
);
|
|
3942
|
+
const changedFolders = collectChangedFolders(
|
|
3943
|
+
input.previousState,
|
|
3944
|
+
input.manifest,
|
|
3945
|
+
changedContent
|
|
3946
|
+
);
|
|
3947
|
+
const affected = /* @__PURE__ */ new Map();
|
|
3948
|
+
const unchanged = [];
|
|
3949
|
+
for (const output of current) {
|
|
3950
|
+
if (isOutputAffected(output, changedContent, changedTags, changedFolders)) {
|
|
3951
|
+
affected.set(output.path, output);
|
|
3952
|
+
} else {
|
|
3953
|
+
unchanged.push(output);
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
for (const output of current) {
|
|
3957
|
+
const previousOutput = previous.get(output.path);
|
|
3958
|
+
if (!previousOutput || output.kind !== previousOutput.kind) {
|
|
3959
|
+
affected.set(output.path, output);
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
const currentPaths = new Set(current.map((output) => output.path));
|
|
3963
|
+
const removed = [...previous.values()].filter(
|
|
3964
|
+
(output) => !currentPaths.has(output.path)
|
|
3965
|
+
);
|
|
3966
|
+
const fullRegenerationRequired = current.some(
|
|
3967
|
+
(output) => output.dependencies.some((dependency) => dependency.type === "unknown")
|
|
3968
|
+
);
|
|
3969
|
+
const affectedValues = [...affected.values()].sort(compareOutput);
|
|
3970
|
+
const unchangedValues = unchanged.filter((output) => !affected.has(output.path)).sort(compareOutput);
|
|
3971
|
+
return {
|
|
3972
|
+
affected: affectedValues,
|
|
3973
|
+
removed: removed.sort(compareOutput),
|
|
3974
|
+
unchanged: unchangedValues,
|
|
3975
|
+
fullRegenerationRequired,
|
|
3976
|
+
candidateOutputCount: current.length,
|
|
3977
|
+
affectedOutputCount: affectedValues.length,
|
|
3978
|
+
removedOutputCount: removed.length,
|
|
3979
|
+
unchangedOutputCount: unchangedValues.length
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
function buildOutputInventory(manifest, pluginPageOutputs = [], contentOutputDependencies = []) {
|
|
3983
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
3984
|
+
for (const entry of manifest.publicEntries) {
|
|
3985
|
+
addOutput(outputs, contentOutput(entry, contentOutputDependencies));
|
|
3986
|
+
}
|
|
3987
|
+
for (const [path5, redirect] of manifest.publicRedirects) {
|
|
3988
|
+
addOutput(outputs, {
|
|
3989
|
+
kind: "redirect",
|
|
3990
|
+
path: htmlOutputPath(path5),
|
|
3991
|
+
producer: `content:${redirect.slug}:redirect`,
|
|
3992
|
+
dependencies: [{ type: "content", slug: redirect.slug }]
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
for (const output of pluginPageOutputs) {
|
|
3996
|
+
addOutput(outputs, { ...output, path: htmlOutputPath(output.path) });
|
|
3997
|
+
}
|
|
3998
|
+
for (const output of manifest.generatedOutputs) {
|
|
3999
|
+
addOutput(outputs, {
|
|
4000
|
+
kind: "generated",
|
|
4001
|
+
path: output.path,
|
|
4002
|
+
producer: `plugin:${output.owner}`,
|
|
4003
|
+
dependencies: output.dependencies ?? [{ type: "unknown" }]
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
return [...outputs.values()].sort(compareOutput);
|
|
4007
|
+
}
|
|
4008
|
+
function htmlOutputPath(pathname) {
|
|
4009
|
+
const normalized = normalizePublicPath(pathname);
|
|
4010
|
+
if (normalized === "/") return "index.html";
|
|
4011
|
+
const withoutSlash = normalized.replace(/^\//, "");
|
|
4012
|
+
if (pathname.endsWith("/")) return `${withoutSlash}/index.html`;
|
|
4013
|
+
return `${withoutSlash}.html`;
|
|
4014
|
+
}
|
|
4015
|
+
function contentOutput(entry, dependencies) {
|
|
4016
|
+
return {
|
|
4017
|
+
kind: "content",
|
|
4018
|
+
path: htmlOutputPath(entry.permalink),
|
|
4019
|
+
producer: `content:${entry.slug}`,
|
|
4020
|
+
dependencies: [{ type: "content", slug: entry.slug }, ...dependencies]
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
function isOutputAffected(output, changedContent, changedTags, changedFolders) {
|
|
4024
|
+
for (const dependency of output.dependencies) {
|
|
4025
|
+
if (dependency.type === "unknown") return true;
|
|
4026
|
+
if (dependency.type === "global") return changedContent.size > 0;
|
|
4027
|
+
if (dependency.type === "content" && changedContent.has(dependency.slug)) {
|
|
4028
|
+
return true;
|
|
4029
|
+
}
|
|
4030
|
+
if (dependency.type === "tag" && changedTags.has(dependency.tag))
|
|
4031
|
+
return true;
|
|
4032
|
+
if (dependency.type === "folder" && changedFolders.has(dependency.folder)) {
|
|
4033
|
+
return true;
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
return false;
|
|
4037
|
+
}
|
|
4038
|
+
function collectChangedContent(input) {
|
|
4039
|
+
const changed = /* @__PURE__ */ new Set([
|
|
4040
|
+
...input.affectedContent.direct,
|
|
4041
|
+
...input.affectedContent.dependent,
|
|
4042
|
+
...input.changeSet.removed.filter(isMarkdownPath3).map(toSlug3)
|
|
4043
|
+
]);
|
|
4044
|
+
const previousEntries = input.previousState?.manifestEntries ?? [];
|
|
4045
|
+
const currentEntries = input.manifest.entries;
|
|
4046
|
+
const previousBySlug = new Map(
|
|
4047
|
+
previousEntries.map((entry) => [entry.slug, entry])
|
|
4048
|
+
);
|
|
4049
|
+
const currentBySlug = new Map(
|
|
4050
|
+
currentEntries.map((entry) => [entry.slug, entry])
|
|
4051
|
+
);
|
|
4052
|
+
for (const slug of changed) {
|
|
4053
|
+
for (const target of linkTargets(previousBySlug.get(slug)))
|
|
4054
|
+
changed.add(target);
|
|
4055
|
+
for (const target of linkTargets(currentBySlug.get(slug)))
|
|
4056
|
+
changed.add(target);
|
|
4057
|
+
}
|
|
4058
|
+
return changed;
|
|
4059
|
+
}
|
|
4060
|
+
function collectChangedTags(previousState, manifest, changedContent) {
|
|
4061
|
+
const previousBySlug = new Map(
|
|
4062
|
+
(previousState?.manifestEntries ?? []).map((entry) => [entry.slug, entry])
|
|
4063
|
+
);
|
|
4064
|
+
const currentBySlug = new Map(
|
|
4065
|
+
manifest.entries.map((entry) => [entry.slug, entry])
|
|
4066
|
+
);
|
|
4067
|
+
const tags = /* @__PURE__ */ new Set();
|
|
4068
|
+
for (const slug of changedContent) {
|
|
4069
|
+
for (const tag of previousBySlug.get(slug)?.tags ?? []) tags.add(tag);
|
|
4070
|
+
for (const tag of currentBySlug.get(slug)?.tags ?? []) tags.add(tag);
|
|
4071
|
+
}
|
|
4072
|
+
return tags;
|
|
4073
|
+
}
|
|
4074
|
+
function collectChangedFolders(previousState, manifest, changedContent) {
|
|
4075
|
+
const previousBySlug = new Map(
|
|
4076
|
+
(previousState?.manifestEntries ?? []).map((entry) => [entry.slug, entry])
|
|
4077
|
+
);
|
|
4078
|
+
const currentBySlug = new Map(
|
|
4079
|
+
manifest.entries.map((entry) => [entry.slug, entry])
|
|
4080
|
+
);
|
|
4081
|
+
const folders = /* @__PURE__ */ new Set();
|
|
4082
|
+
for (const slug of changedContent) {
|
|
4083
|
+
addFolderAncestors(folders, previousBySlug.get(slug)?.slug ?? slug);
|
|
4084
|
+
addFolderAncestors(folders, currentBySlug.get(slug)?.slug ?? slug);
|
|
4085
|
+
}
|
|
4086
|
+
return folders;
|
|
4087
|
+
}
|
|
4088
|
+
function addFolderAncestors(folders, slug) {
|
|
4089
|
+
const segments = slug.split("/");
|
|
4090
|
+
segments.pop();
|
|
4091
|
+
while (true) {
|
|
4092
|
+
folders.add(segments.join("/"));
|
|
4093
|
+
if (segments.length === 0) return;
|
|
4094
|
+
segments.pop();
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
function linkTargets(entry) {
|
|
4098
|
+
if (!entry) return [];
|
|
4099
|
+
return entry.links.filter(
|
|
4100
|
+
(link) => link.slug !== null
|
|
4101
|
+
).map((link) => link.slug);
|
|
4102
|
+
}
|
|
4103
|
+
function addOutput(outputs, output) {
|
|
4104
|
+
outputs.set(output.path, output);
|
|
4105
|
+
}
|
|
4106
|
+
function normalizePublicPath(pathname) {
|
|
4107
|
+
const path5 = `/${pathname.split("/").filter(Boolean).join("/")}`;
|
|
4108
|
+
return path5 === "/" ? path5 : path5.replace(/\/$/, "");
|
|
4109
|
+
}
|
|
4110
|
+
function isMarkdownPath3(path5) {
|
|
4111
|
+
return path5.endsWith(".md");
|
|
4112
|
+
}
|
|
4113
|
+
function toSlug3(path5) {
|
|
4114
|
+
return path5.replace(/\.md$/, "");
|
|
4115
|
+
}
|
|
4116
|
+
function compareOutput(left, right) {
|
|
4117
|
+
return left.path.localeCompare(right.path) || left.kind.localeCompare(right.kind);
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
// src/content/publishing.ts
|
|
4121
|
+
function resolvePublishingState(frontmatter, options) {
|
|
4122
|
+
const explicitVisibility = resolveExplicitVisibility(frontmatter?.visibility);
|
|
4123
|
+
const publishAt = resolvePublishAt(frontmatter?.publishAt);
|
|
4124
|
+
if (explicitVisibility === "draft")
|
|
4125
|
+
return state("draft", false, false, publishAt);
|
|
4126
|
+
if (publishAt && publishAt.getTime() > options.buildTime.getTime()) {
|
|
4127
|
+
return state("scheduled", false, false, publishAt);
|
|
4128
|
+
}
|
|
4129
|
+
if (explicitVisibility === "unlisted") {
|
|
4130
|
+
return state("unlisted", true, false, publishAt);
|
|
4131
|
+
}
|
|
4132
|
+
if (explicitVisibility === "public" || publishAt) {
|
|
4133
|
+
return state("public", true, true, publishAt);
|
|
4134
|
+
}
|
|
4135
|
+
const routable = isPublishable(options.strategy, frontmatter);
|
|
4136
|
+
return state(routable ? "public" : "draft", routable, routable, publishAt);
|
|
4137
|
+
}
|
|
4138
|
+
function resolvePublishingBuildTime(input) {
|
|
4139
|
+
if (input === void 0) return /* @__PURE__ */ new Date();
|
|
4140
|
+
if (input instanceof Date) {
|
|
4141
|
+
if (Number.isNaN(input.getTime()))
|
|
4142
|
+
throw new Error("Invalid publishing build time.");
|
|
4143
|
+
return new Date(input.getTime());
|
|
4144
|
+
}
|
|
4145
|
+
const parsed = new Date(input);
|
|
4146
|
+
if (Number.isNaN(parsed.getTime()))
|
|
4147
|
+
throw new Error("Invalid publishing build time.");
|
|
4148
|
+
return parsed;
|
|
4149
|
+
}
|
|
4150
|
+
function resolveExplicitVisibility(value) {
|
|
4151
|
+
if (value === void 0) return void 0;
|
|
4152
|
+
if (value === "public" || value === "unlisted" || value === "draft")
|
|
4153
|
+
return value;
|
|
4154
|
+
throw new Error(
|
|
4155
|
+
`Invalid content visibility "${String(value)}". Expected public, unlisted, or draft.`
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
function resolvePublishAt(value) {
|
|
4159
|
+
if (value === void 0) return void 0;
|
|
4160
|
+
if (!(typeof value === "string" || value instanceof Date)) {
|
|
4161
|
+
throw new Error("Invalid publishAt. Expected an ISO 8601 date string.");
|
|
4162
|
+
}
|
|
4163
|
+
const parsed = value instanceof Date ? value : new Date(value);
|
|
4164
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
4165
|
+
throw new Error(
|
|
4166
|
+
`Invalid publishAt "${String(value)}". Expected an ISO 8601 date string.`
|
|
4167
|
+
);
|
|
4168
|
+
}
|
|
4169
|
+
return new Date(parsed.getTime());
|
|
4170
|
+
}
|
|
4171
|
+
function state(visibility, routable, discoverable, publishAt) {
|
|
4172
|
+
return {
|
|
4173
|
+
visibility,
|
|
4174
|
+
routable,
|
|
4175
|
+
discoverable,
|
|
4176
|
+
...publishAt ? { publishAt } : {}
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
|
|
2916
4180
|
// src/content/content_manager.ts
|
|
4181
|
+
var CONTENT_PROCESSING_CONCURRENCY = 16;
|
|
2917
4182
|
var ContentManager = class {
|
|
2918
4183
|
constructor(content, exclude = [], pipelineOptions = {}) {
|
|
2919
4184
|
this.pipelineOptions = pipelineOptions;
|
|
2920
4185
|
this.pipelineOptions = {
|
|
2921
4186
|
...pipelineOptions,
|
|
2922
|
-
plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins
|
|
4187
|
+
plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins,
|
|
4188
|
+
isRoutable: (slug) => this.isRoutable(slug)
|
|
2923
4189
|
};
|
|
4190
|
+
this.publishingBuildTime = resolvePublishingBuildTime(
|
|
4191
|
+
pipelineOptions.publishingBuildTime
|
|
4192
|
+
);
|
|
2924
4193
|
this.source = typeof content === "string" ? pipelineOptions.config?.content.source ?? new FileSystemContentSource(content, [
|
|
2925
4194
|
...exclude,
|
|
2926
4195
|
CONTENT_BUILD_STATE_EXCLUDE
|
|
2927
4196
|
]) : content;
|
|
2928
4197
|
this.pipelineOptions = {
|
|
2929
4198
|
...this.pipelineOptions,
|
|
2930
|
-
contentSource: this.source
|
|
4199
|
+
contentSource: this.source,
|
|
4200
|
+
onContentDependencies: (slug, dependencies) => {
|
|
4201
|
+
this.trackedContentDependencies.set(slug, dependencies);
|
|
4202
|
+
}
|
|
2931
4203
|
};
|
|
2932
4204
|
this.contentIndexBuilder = new ContentIndexBuilder(this.source);
|
|
2933
4205
|
this.pluginRuntime = new PluginRuntime(this.pipelineOptions);
|
|
@@ -2941,7 +4213,8 @@ var ContentManager = class {
|
|
|
2941
4213
|
getEntries: () => this.entryReader.getEntries(),
|
|
2942
4214
|
readEntry: (entry) => this.entryReader.read(entry),
|
|
2943
4215
|
getContentIndex: () => this.getContentIndex(),
|
|
2944
|
-
pluginRuntime: this.pluginRuntime
|
|
4216
|
+
pluginRuntime: this.pluginRuntime,
|
|
4217
|
+
observability
|
|
2945
4218
|
});
|
|
2946
4219
|
this.buildCoordinator = new ContentBuildCoordinator({
|
|
2947
4220
|
buildStatePath,
|
|
@@ -2956,13 +4229,22 @@ var ContentManager = class {
|
|
|
2956
4229
|
manifestBuilder = new ManifestBuilder();
|
|
2957
4230
|
pluginRuntime;
|
|
2958
4231
|
contentIndex = null;
|
|
4232
|
+
contentIndexPromise = null;
|
|
2959
4233
|
contentCache = /* @__PURE__ */ new Map();
|
|
2960
4234
|
manifest = null;
|
|
4235
|
+
manifestPromise = null;
|
|
4236
|
+
pipelineFingerprint = null;
|
|
2961
4237
|
pipeline = null;
|
|
2962
4238
|
entryReader;
|
|
2963
4239
|
locationResolver;
|
|
2964
4240
|
buildCoordinator;
|
|
2965
4241
|
isBuildTime = false;
|
|
4242
|
+
publishingBuildTime;
|
|
4243
|
+
routableSlugs = null;
|
|
4244
|
+
routableSlugsPromise = null;
|
|
4245
|
+
processedContentCount = 0;
|
|
4246
|
+
outputChangeSet = null;
|
|
4247
|
+
trackedContentDependencies = /* @__PURE__ */ new Map();
|
|
2966
4248
|
async getAllPosts() {
|
|
2967
4249
|
return (await this.scan()).filter((entry) => entry.path.endsWith(".md")).map((entry) => ({ slug: entry.path.replace(/\.md$/, "") }));
|
|
2968
4250
|
}
|
|
@@ -2974,17 +4256,33 @@ var ContentManager = class {
|
|
|
2974
4256
|
}
|
|
2975
4257
|
async getContentIndex(preparation) {
|
|
2976
4258
|
if (this.contentIndex) return this.contentIndex;
|
|
4259
|
+
if (!preparation && this.contentIndexPromise)
|
|
4260
|
+
return await this.contentIndexPromise;
|
|
2977
4261
|
if (preparation?.previousState && !hasContentChanges(preparation.changeSet)) {
|
|
2978
4262
|
this.contentIndex = new Map(
|
|
2979
4263
|
Object.entries(preparation.previousState.contentIndex)
|
|
2980
4264
|
);
|
|
2981
4265
|
return this.contentIndex;
|
|
2982
4266
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
4267
|
+
if (preparation) {
|
|
4268
|
+
this.contentIndex = preparation.currentContentIndex;
|
|
4269
|
+
return this.contentIndex;
|
|
4270
|
+
}
|
|
4271
|
+
const promise = this.observability().tracer.span(
|
|
4272
|
+
"content.index",
|
|
4273
|
+
{},
|
|
4274
|
+
async () => await this.contentIndexBuilder.build(
|
|
4275
|
+
await this.entryReader.getEntries(),
|
|
4276
|
+
(entry) => this.entryReader.read(entry)
|
|
4277
|
+
)
|
|
2986
4278
|
);
|
|
2987
|
-
|
|
4279
|
+
if (!preparation) this.contentIndexPromise = promise;
|
|
4280
|
+
try {
|
|
4281
|
+
this.contentIndex = await promise;
|
|
4282
|
+
return this.contentIndex;
|
|
4283
|
+
} finally {
|
|
4284
|
+
if (!preparation) this.contentIndexPromise = null;
|
|
4285
|
+
}
|
|
2988
4286
|
}
|
|
2989
4287
|
async getProcessedContent(slug) {
|
|
2990
4288
|
const cached = this.contentCache.get(slug);
|
|
@@ -3001,6 +4299,7 @@ var ContentManager = class {
|
|
|
3001
4299
|
]);
|
|
3002
4300
|
await this.pluginRuntime.startBuild(contentIndex);
|
|
3003
4301
|
await this.pluginRuntime.runContentLoaded(slug, rawPost, contentIndex);
|
|
4302
|
+
await this.ensureRoutableSlugs();
|
|
3004
4303
|
this.pipeline ??= new Pipeline(
|
|
3005
4304
|
contentIndex,
|
|
3006
4305
|
await this.locationResolver.getPermalinks(),
|
|
@@ -3011,6 +4310,12 @@ var ContentManager = class {
|
|
|
3011
4310
|
const content = await this.pipeline.execute(rawPost, {
|
|
3012
4311
|
sourceSlug: slug
|
|
3013
4312
|
});
|
|
4313
|
+
this.processedContentCount += 1;
|
|
4314
|
+
if (this.processedContentCount % 100 === 0) {
|
|
4315
|
+
this.observability().tracer.event("content.process.sample", {
|
|
4316
|
+
processed: this.processedContentCount
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
3014
4319
|
await this.pluginRuntime.runPostHook(
|
|
3015
4320
|
"onPostParsed",
|
|
3016
4321
|
slug,
|
|
@@ -3031,9 +4336,24 @@ var ContentManager = class {
|
|
|
3031
4336
|
);
|
|
3032
4337
|
}
|
|
3033
4338
|
async getManifest(options) {
|
|
4339
|
+
if (this.manifest) return this.manifest;
|
|
4340
|
+
if (this.manifestPromise) return await this.manifestPromise;
|
|
4341
|
+
this.manifestPromise = this.buildManifest(options);
|
|
4342
|
+
try {
|
|
4343
|
+
return await this.manifestPromise;
|
|
4344
|
+
} catch (error) {
|
|
4345
|
+
this.manifestPromise = null;
|
|
4346
|
+
throw error;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
async buildManifest(options) {
|
|
3034
4350
|
if (this.manifest) return this.manifest;
|
|
3035
4351
|
this.enableBuildTime();
|
|
3036
|
-
const preparation = options ? await this.buildCoordinator.getPreparation(
|
|
4352
|
+
const preparation = options ? await this.buildCoordinator.getPreparation(
|
|
4353
|
+
options.incremental,
|
|
4354
|
+
this.getPipelineFingerprint(),
|
|
4355
|
+
this.requiresFullContentRegeneration()
|
|
4356
|
+
) : void 0;
|
|
3037
4357
|
return await this.observability().tracer.span(
|
|
3038
4358
|
"content.manifest",
|
|
3039
4359
|
{},
|
|
@@ -3043,26 +4363,38 @@ var ContentManager = class {
|
|
|
3043
4363
|
this.getContentIndex(preparation),
|
|
3044
4364
|
this.getContentLocations()
|
|
3045
4365
|
]);
|
|
3046
|
-
const entries = await
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
4366
|
+
const entries = await this.observability().tracer.span(
|
|
4367
|
+
"content.manifest.entries",
|
|
4368
|
+
{},
|
|
4369
|
+
() => mapConcurrent3(
|
|
4370
|
+
posts,
|
|
4371
|
+
CONTENT_PROCESSING_CONCURRENCY,
|
|
4372
|
+
async (post) => {
|
|
4373
|
+
const reused = this.reuseManifestEntry(post.slug, preparation);
|
|
4374
|
+
if (reused) return reused;
|
|
4375
|
+
const [rawPost, processed] = await Promise.all([
|
|
4376
|
+
this.getPost(post.slug),
|
|
4377
|
+
this.getProcessedContent(post.slug)
|
|
4378
|
+
]);
|
|
4379
|
+
const location = locations.get(post.slug);
|
|
4380
|
+
if (!location) {
|
|
4381
|
+
throw new Error(
|
|
4382
|
+
`Content public location was not resolved: ${post.slug}`
|
|
4383
|
+
);
|
|
4384
|
+
}
|
|
4385
|
+
return this.manifestBuilder.createEntry(
|
|
4386
|
+
post.slug,
|
|
4387
|
+
rawPost,
|
|
4388
|
+
processed,
|
|
4389
|
+
contentIndex,
|
|
4390
|
+
location,
|
|
4391
|
+
resolvePublishingState(processed.frontmatter, {
|
|
4392
|
+
strategy: this.publishStrategy(),
|
|
4393
|
+
buildTime: this.publishingBuildTime
|
|
4394
|
+
})
|
|
3056
4395
|
);
|
|
3057
4396
|
}
|
|
3058
|
-
|
|
3059
|
-
post.slug,
|
|
3060
|
-
rawPost,
|
|
3061
|
-
processed,
|
|
3062
|
-
contentIndex,
|
|
3063
|
-
location
|
|
3064
|
-
);
|
|
3065
|
-
})
|
|
4397
|
+
)
|
|
3066
4398
|
);
|
|
3067
4399
|
await this.pluginRuntime.runGraphHook(entries, contentIndex);
|
|
3068
4400
|
const manifest = await this.observability().tracer.span(
|
|
@@ -3070,12 +4402,17 @@ var ContentManager = class {
|
|
|
3070
4402
|
{},
|
|
3071
4403
|
() => this.manifestBuilder.build(entries, contentIndex)
|
|
3072
4404
|
);
|
|
4405
|
+
const cacheManifestEntries = structuredClone(manifest.entries);
|
|
3073
4406
|
this.locationResolver.populateRedirects(manifest, locations);
|
|
3074
|
-
await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
|
|
3075
4407
|
this.applyPublicView(manifest);
|
|
3076
4408
|
manifest.pagePaths = [
|
|
3077
4409
|
...await this.pluginRuntime.getPagePaths(manifest, contentIndex)
|
|
3078
4410
|
];
|
|
4411
|
+
manifest.pageRoutes = [
|
|
4412
|
+
...await this.pluginRuntime.getPageRoutes(manifest, contentIndex)
|
|
4413
|
+
];
|
|
4414
|
+
await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
|
|
4415
|
+
this.synchronizeManifestHtml(manifest, cacheManifestEntries);
|
|
3079
4416
|
manifest.assets = this.pluginRuntime.collectAssets();
|
|
3080
4417
|
manifest.clientEntries = this.pluginRuntime.collectClientEntries();
|
|
3081
4418
|
manifest.diagnostics = [
|
|
@@ -3084,8 +4421,39 @@ var ContentManager = class {
|
|
|
3084
4421
|
];
|
|
3085
4422
|
await this.pluginRuntime.runBuildEnd(manifest, contentIndex);
|
|
3086
4423
|
manifest.generatedOutputs = this.pluginRuntime.collectGeneratedOutputs();
|
|
4424
|
+
const pluginPageOutputs = await this.pluginRuntime.getPageOutputs(
|
|
4425
|
+
manifest,
|
|
4426
|
+
contentIndex
|
|
4427
|
+
);
|
|
4428
|
+
this.outputChangeSet = preparation ? determineOutputChanges({
|
|
4429
|
+
manifest,
|
|
4430
|
+
previousState: preparation.previousState,
|
|
4431
|
+
changeSet: preparation.changeSet,
|
|
4432
|
+
affectedContent: preparation.affectedContent,
|
|
4433
|
+
pluginPageOutputs,
|
|
4434
|
+
contentOutputDependencies: this.pluginRuntime.getContentOutputDependencies()
|
|
4435
|
+
}) : null;
|
|
4436
|
+
if (this.outputChangeSet) {
|
|
4437
|
+
this.observability().tracer.event("build.incremental.outputs", {
|
|
4438
|
+
candidateOutputCount: this.outputChangeSet.candidateOutputCount,
|
|
4439
|
+
affectedOutputCount: this.outputChangeSet.affectedOutputCount,
|
|
4440
|
+
removedOutputCount: this.outputChangeSet.removedOutputCount,
|
|
4441
|
+
unchangedOutputCount: this.outputChangeSet.unchangedOutputCount,
|
|
4442
|
+
fullRegenerationRequired: this.outputChangeSet.fullRegenerationRequired
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
3087
4445
|
if (preparation)
|
|
3088
|
-
await this.buildCoordinator.commit(
|
|
4446
|
+
await this.buildCoordinator.commit(
|
|
4447
|
+
preparation,
|
|
4448
|
+
manifest,
|
|
4449
|
+
this.getPipelineFingerprint(),
|
|
4450
|
+
this.outputChangeSet ? [
|
|
4451
|
+
...this.outputChangeSet.affected,
|
|
4452
|
+
...this.outputChangeSet.unchanged
|
|
4453
|
+
] : [],
|
|
4454
|
+
cacheManifestEntries,
|
|
4455
|
+
this.trackedContentDependencies
|
|
4456
|
+
);
|
|
3089
4457
|
this.manifest = manifest;
|
|
3090
4458
|
return manifest;
|
|
3091
4459
|
}
|
|
@@ -3094,6 +4462,34 @@ var ContentManager = class {
|
|
|
3094
4462
|
async build(options = {}) {
|
|
3095
4463
|
return await this.getManifest(options);
|
|
3096
4464
|
}
|
|
4465
|
+
async getOutputChangeSet(options = {}) {
|
|
4466
|
+
await this.getManifest(options);
|
|
4467
|
+
if (!this.outputChangeSet) {
|
|
4468
|
+
const manifest = await this.getManifest();
|
|
4469
|
+
const contentIndex = await this.getContentIndex();
|
|
4470
|
+
const pluginPageOutputs = await this.pluginRuntime.getPageOutputs(
|
|
4471
|
+
manifest,
|
|
4472
|
+
contentIndex
|
|
4473
|
+
);
|
|
4474
|
+
return determineOutputChanges({
|
|
4475
|
+
manifest,
|
|
4476
|
+
previousState: void 0,
|
|
4477
|
+
changeSet: {
|
|
4478
|
+
added: manifest.entries.map((entry) => `${entry.slug}.md`),
|
|
4479
|
+
changed: [],
|
|
4480
|
+
removed: [],
|
|
4481
|
+
unchanged: []
|
|
4482
|
+
},
|
|
4483
|
+
affectedContent: {
|
|
4484
|
+
direct: new Set(manifest.entries.map((entry) => entry.slug)),
|
|
4485
|
+
dependent: /* @__PURE__ */ new Set()
|
|
4486
|
+
},
|
|
4487
|
+
pluginPageOutputs,
|
|
4488
|
+
contentOutputDependencies: this.pluginRuntime.getContentOutputDependencies()
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
return this.outputChangeSet;
|
|
4492
|
+
}
|
|
3097
4493
|
async getBacklinks(targetSlug) {
|
|
3098
4494
|
const graph = await this.getContentGraph();
|
|
3099
4495
|
return graph.incomingSlugs(targetSlug).map((slug) => ({
|
|
@@ -3148,19 +4544,106 @@ var ContentManager = class {
|
|
|
3148
4544
|
return await this.locationResolver.getLocations();
|
|
3149
4545
|
}
|
|
3150
4546
|
applyPublicView(manifest) {
|
|
3151
|
-
const strategy = this.
|
|
4547
|
+
const strategy = this.publishStrategy();
|
|
4548
|
+
for (const entry of manifest.entries) {
|
|
4549
|
+
entry.publishing = resolvePublishingState(entry.frontmatter, {
|
|
4550
|
+
strategy,
|
|
4551
|
+
buildTime: this.publishingBuildTime
|
|
4552
|
+
});
|
|
4553
|
+
}
|
|
3152
4554
|
manifest.publicEntries = manifest.entries.filter(
|
|
3153
|
-
(entry) =>
|
|
4555
|
+
(entry) => entry.publishing.routable
|
|
4556
|
+
);
|
|
4557
|
+
manifest.discoverableEntries = manifest.entries.filter(
|
|
4558
|
+
(entry) => entry.publishing.discoverable
|
|
3154
4559
|
);
|
|
3155
4560
|
const publicSlugs = new Set(
|
|
3156
4561
|
manifest.publicEntries.map((entry) => entry.slug)
|
|
3157
4562
|
);
|
|
4563
|
+
const publicPermalinks = new Map(
|
|
4564
|
+
manifest.publicEntries.map((entry) => [entry.permalink, entry])
|
|
4565
|
+
);
|
|
4566
|
+
manifest.byRoutablePermalink = publicPermalinks;
|
|
3158
4567
|
manifest.publicRedirects = new Map(
|
|
3159
4568
|
[...manifest.redirects].filter(
|
|
3160
4569
|
([, redirect]) => publicSlugs.has(redirect.slug)
|
|
3161
4570
|
)
|
|
3162
4571
|
);
|
|
3163
4572
|
}
|
|
4573
|
+
synchronizeManifestHtml(manifest, preHookEntries) {
|
|
4574
|
+
const preHookHtml = new Map(
|
|
4575
|
+
preHookEntries.map((entry) => [entry.slug, entry.html])
|
|
4576
|
+
);
|
|
4577
|
+
for (const entry of manifest.entries) {
|
|
4578
|
+
const changed = preHookHtml.get(entry.slug) !== entry.html;
|
|
4579
|
+
const cached = this.contentCache.get(entry.slug);
|
|
4580
|
+
if (cached) {
|
|
4581
|
+
if (changed) cached.html = entry.html;
|
|
4582
|
+
continue;
|
|
4583
|
+
}
|
|
4584
|
+
this.contentCache.set(entry.slug, {
|
|
4585
|
+
frontmatter: entry.frontmatter,
|
|
4586
|
+
html: entry.html
|
|
4587
|
+
});
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
publishStrategy() {
|
|
4591
|
+
return this.pipelineOptions.config?.content.filters.publishStrategy ?? "explicit";
|
|
4592
|
+
}
|
|
4593
|
+
reuseManifestEntry(slug, preparation) {
|
|
4594
|
+
if (!preparation?.previousState?.manifestEntries) return null;
|
|
4595
|
+
if (preparation.previousState.pipelineFingerprint !== this.getPipelineFingerprint()) {
|
|
4596
|
+
return null;
|
|
4597
|
+
}
|
|
4598
|
+
if (preparation.affectedContent.direct.has(slug)) return null;
|
|
4599
|
+
if (preparation.affectedContent.dependent.has(slug)) return null;
|
|
4600
|
+
const entry = preparation.previousManifestEntriesBySlug.get(slug);
|
|
4601
|
+
if (!entry) return null;
|
|
4602
|
+
this.observability().tracer.event("content.reuse", { slug });
|
|
4603
|
+
return cloneManifestEntry(entry);
|
|
4604
|
+
}
|
|
4605
|
+
getPipelineFingerprint() {
|
|
4606
|
+
if (!this.pipelineOptions.config) return void 0;
|
|
4607
|
+
this.pipelineFingerprint ??= computePipelineFingerprint(
|
|
4608
|
+
this.pipelineOptions.config
|
|
4609
|
+
);
|
|
4610
|
+
return this.pipelineFingerprint;
|
|
4611
|
+
}
|
|
4612
|
+
requiresFullContentRegeneration() {
|
|
4613
|
+
const config = this.pipelineOptions.config;
|
|
4614
|
+
return config ? !isPersistentlyCacheable("", config).cacheable : false;
|
|
4615
|
+
}
|
|
4616
|
+
isRoutable(slug) {
|
|
4617
|
+
if (!this.routableSlugs) {
|
|
4618
|
+
throw new Error(`Publishing state was not resolved: ${slug}`);
|
|
4619
|
+
}
|
|
4620
|
+
return this.routableSlugs.has(slug);
|
|
4621
|
+
}
|
|
4622
|
+
async ensureRoutableSlugs() {
|
|
4623
|
+
if (this.routableSlugs) return this.routableSlugs;
|
|
4624
|
+
if (this.routableSlugsPromise) return await this.routableSlugsPromise;
|
|
4625
|
+
this.routableSlugsPromise = this.resolveRoutableSlugs();
|
|
4626
|
+
try {
|
|
4627
|
+
this.routableSlugs = await this.routableSlugsPromise;
|
|
4628
|
+
return this.routableSlugs;
|
|
4629
|
+
} finally {
|
|
4630
|
+
this.routableSlugsPromise = null;
|
|
4631
|
+
}
|
|
4632
|
+
}
|
|
4633
|
+
async resolveRoutableSlugs() {
|
|
4634
|
+
const strategy = this.publishStrategy();
|
|
4635
|
+
const posts = await this.getAllPosts();
|
|
4636
|
+
const routableSlugs = /* @__PURE__ */ new Set();
|
|
4637
|
+
await mapConcurrent3(posts, CONTENT_PROCESSING_CONCURRENCY, async (post) => {
|
|
4638
|
+
const markdown = await this.getPost(post.slug);
|
|
4639
|
+
const publishing = resolvePublishingState(readFrontmatter(markdown), {
|
|
4640
|
+
strategy,
|
|
4641
|
+
buildTime: this.publishingBuildTime
|
|
4642
|
+
});
|
|
4643
|
+
if (publishing.routable) routableSlugs.add(post.slug);
|
|
4644
|
+
});
|
|
4645
|
+
return routableSlugs;
|
|
4646
|
+
}
|
|
3164
4647
|
enableBuildTime() {
|
|
3165
4648
|
this.isBuildTime = true;
|
|
3166
4649
|
this.pluginRuntime.enableBuildTime();
|
|
@@ -3169,6 +4652,44 @@ var ContentManager = class {
|
|
|
3169
4652
|
return this.pipelineOptions.observability ?? noopObservability;
|
|
3170
4653
|
}
|
|
3171
4654
|
};
|
|
4655
|
+
function readFrontmatter(markdown) {
|
|
4656
|
+
const file = new VFile2({ value: markdown });
|
|
4657
|
+
matter3(file);
|
|
4658
|
+
return file.data.matter ?? {};
|
|
4659
|
+
}
|
|
4660
|
+
function cloneManifestEntry(entry) {
|
|
4661
|
+
return {
|
|
4662
|
+
...entry,
|
|
4663
|
+
publicLocation: { ...entry.publicLocation },
|
|
4664
|
+
frontmatter: { ...entry.frontmatter },
|
|
4665
|
+
aliases: [...entry.aliases ?? []],
|
|
4666
|
+
tags: [...entry.tags],
|
|
4667
|
+
links: entry.links.map((link) => ({ ...link })),
|
|
4668
|
+
backlinks: [...entry.backlinks],
|
|
4669
|
+
assets: entry.assets.map((asset) => ({ ...asset })),
|
|
4670
|
+
...entry.headTags ? { headTags: [...entry.headTags] } : {},
|
|
4671
|
+
...entry.bodySlots ? { bodySlots: { ...entry.bodySlots } } : {}
|
|
4672
|
+
};
|
|
4673
|
+
}
|
|
4674
|
+
async function mapConcurrent3(values, concurrency, map) {
|
|
4675
|
+
const results = new Array(values.length);
|
|
4676
|
+
let nextIndex = 0;
|
|
4677
|
+
async function worker() {
|
|
4678
|
+
while (nextIndex < values.length) {
|
|
4679
|
+
const index = nextIndex;
|
|
4680
|
+
nextIndex += 1;
|
|
4681
|
+
const value = values[index];
|
|
4682
|
+
if (value !== void 0) results[index] = await map(value);
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
await Promise.all(
|
|
4686
|
+
Array.from(
|
|
4687
|
+
{ length: Math.min(concurrency, values.length) },
|
|
4688
|
+
() => worker()
|
|
4689
|
+
)
|
|
4690
|
+
);
|
|
4691
|
+
return results;
|
|
4692
|
+
}
|
|
3172
4693
|
|
|
3173
4694
|
// src/content/content_source.ts
|
|
3174
4695
|
async function readContentSourceEntry(source, logicalPath) {
|
|
@@ -3179,6 +4700,89 @@ async function getContentSourceEntry(source, logicalPath) {
|
|
|
3179
4700
|
return (await source.scan()).find((candidate) => candidate.path === logicalPath) ?? null;
|
|
3180
4701
|
}
|
|
3181
4702
|
|
|
4703
|
+
// src/content/folder_entry.ts
|
|
4704
|
+
function resolveFolderEntry(manifest, folder, options = {}) {
|
|
4705
|
+
const base = normalizeFolder2(folder);
|
|
4706
|
+
const scope = options.scope ?? "routable";
|
|
4707
|
+
const candidates = [];
|
|
4708
|
+
for (const [kind, slug] of folderEntrySlugs(base)) {
|
|
4709
|
+
if (slug === null) continue;
|
|
4710
|
+
const entry = manifest.bySlug.get(slug);
|
|
4711
|
+
if (!entry || !isInScope(entry, scope)) continue;
|
|
4712
|
+
candidates.push({ kind, entry });
|
|
4713
|
+
}
|
|
4714
|
+
if (candidates.length === 0) return { type: "none" };
|
|
4715
|
+
const candidate = candidates[0];
|
|
4716
|
+
if (candidates.length === 1 && candidate) {
|
|
4717
|
+
return { type: "resolved", kind: candidate.kind, entry: candidate.entry };
|
|
4718
|
+
}
|
|
4719
|
+
return { type: "ambiguous", candidates };
|
|
4720
|
+
}
|
|
4721
|
+
function resolveFolderLocation(manifest, folder) {
|
|
4722
|
+
const resolution = resolveFolderEntry(manifest, folder);
|
|
4723
|
+
const candidates = resolution.type === "ambiguous" ? resolution.candidates.filter((candidate) => candidate.kind !== "folder") : resolution.type === "resolved" && resolution.kind !== "folder" ? [{ kind: resolution.kind, entry: resolution.entry }] : [];
|
|
4724
|
+
if (candidates.length === 1) {
|
|
4725
|
+
const candidate = candidates[0];
|
|
4726
|
+
if (candidate) return { type: "content", entry: candidate.entry };
|
|
4727
|
+
}
|
|
4728
|
+
if (candidates.length > 1) return { type: "ambiguous", candidates };
|
|
4729
|
+
const location = manifest.folderLocations.get(normalizeFolder2(folder));
|
|
4730
|
+
return location ? { type: "generated", pathname: location.pathname } : { type: "none" };
|
|
4731
|
+
}
|
|
4732
|
+
function resolvePublicFolderLocation(manifest, pathname) {
|
|
4733
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
4734
|
+
const entry = manifest.publicEntries.find(
|
|
4735
|
+
(candidate) => normalizePathname(candidate.publicLocation.permalink) === normalizedPathname
|
|
4736
|
+
);
|
|
4737
|
+
if (entry) return { type: "content", entry };
|
|
4738
|
+
const location = [...manifest.folderLocations.values()].find(
|
|
4739
|
+
(candidate) => normalizePathname(candidate.pathname) === normalizedPathname
|
|
4740
|
+
);
|
|
4741
|
+
return location ? { type: "generated", pathname: location.pathname } : { type: "none" };
|
|
4742
|
+
}
|
|
4743
|
+
function resolveGeneratedFolderLocation(manifest, folder) {
|
|
4744
|
+
const normalizedFolder = normalizeFolder2(folder);
|
|
4745
|
+
if (!normalizedFolder) return null;
|
|
4746
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4747
|
+
const prefix = `${normalizedFolder}/`;
|
|
4748
|
+
for (const entry of manifest.publicEntries) {
|
|
4749
|
+
if (!entry.slug.startsWith(prefix)) continue;
|
|
4750
|
+
const remainder = entry.slug.slice(prefix.length).split("/").filter(Boolean);
|
|
4751
|
+
const pathname = parentPath(
|
|
4752
|
+
entry.publicLocation.permalink,
|
|
4753
|
+
remainder.length
|
|
4754
|
+
);
|
|
4755
|
+
if (pathname) candidates.add(pathname);
|
|
4756
|
+
}
|
|
4757
|
+
return candidates.size === 1 ? [...candidates][0] ?? null : null;
|
|
4758
|
+
}
|
|
4759
|
+
function folderEntrySlugs(base) {
|
|
4760
|
+
const prefix = base === "" ? "" : `${base}/`;
|
|
4761
|
+
return [
|
|
4762
|
+
["readme", `${prefix}README`],
|
|
4763
|
+
["index", `${prefix}index`],
|
|
4764
|
+
["folder", base === "" ? null : base]
|
|
4765
|
+
];
|
|
4766
|
+
}
|
|
4767
|
+
function isInScope(entry, scope) {
|
|
4768
|
+
if (scope === "all") return true;
|
|
4769
|
+
if (scope === "discoverable") return entry.publishing.discoverable;
|
|
4770
|
+
return entry.publishing.routable;
|
|
4771
|
+
}
|
|
4772
|
+
function normalizeFolder2(folder) {
|
|
4773
|
+
return folder.split("/").filter(Boolean).join("/");
|
|
4774
|
+
}
|
|
4775
|
+
function normalizePathname(pathname) {
|
|
4776
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
4777
|
+
return segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
4778
|
+
}
|
|
4779
|
+
function parentPath(pathname, levels) {
|
|
4780
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
4781
|
+
if (segments.length < levels) return null;
|
|
4782
|
+
const parent = segments.slice(0, -levels);
|
|
4783
|
+
return parent.length === 0 ? "/" : `/${parent.join("/")}/`;
|
|
4784
|
+
}
|
|
4785
|
+
|
|
3182
4786
|
// src/content/graph_layout.ts
|
|
3183
4787
|
function buildGraphEdges(nodes, visibleSlugs = new Set(nodes.map((node) => node.slug))) {
|
|
3184
4788
|
const edges = /* @__PURE__ */ new Map();
|
|
@@ -3215,9 +4819,143 @@ function layoutRadialGraph(nodes, options) {
|
|
|
3215
4819
|
});
|
|
3216
4820
|
return layout;
|
|
3217
4821
|
}
|
|
4822
|
+
function layoutForceGraph(nodes, options) {
|
|
4823
|
+
const safeOptions = resolveForceGraphLayoutOptions(options);
|
|
4824
|
+
const layoutNodes = [...nodes].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
4825
|
+
const layout = createInitialLayout(layoutNodes, safeOptions);
|
|
4826
|
+
const visibleSlugs = new Set(layoutNodes.map((node) => node.slug));
|
|
4827
|
+
const edges = buildGraphEdges(layoutNodes, visibleSlugs).filter(
|
|
4828
|
+
(edge) => edge.source !== edge.target
|
|
4829
|
+
);
|
|
4830
|
+
const velocities = new Map(
|
|
4831
|
+
layoutNodes.map((node) => [node.slug, { x: 0, y: 0 }])
|
|
4832
|
+
);
|
|
4833
|
+
const centerX = safeOptions.width / 2;
|
|
4834
|
+
const centerY = safeOptions.height / 2;
|
|
4835
|
+
for (let iteration = 0; iteration < safeOptions.iterations; iteration++) {
|
|
4836
|
+
const alpha = 1 - iteration / safeOptions.iterations;
|
|
4837
|
+
applyRepulsion(
|
|
4838
|
+
layoutNodes,
|
|
4839
|
+
layout,
|
|
4840
|
+
velocities,
|
|
4841
|
+
safeOptions.repulsion * alpha
|
|
4842
|
+
);
|
|
4843
|
+
applyLinks(edges, layout, velocities, safeOptions.linkDistance, alpha);
|
|
4844
|
+
applyCentering(layoutNodes, layout, velocities, centerX, centerY, alpha);
|
|
4845
|
+
integrate(
|
|
4846
|
+
layoutNodes,
|
|
4847
|
+
layout,
|
|
4848
|
+
velocities,
|
|
4849
|
+
safeOptions,
|
|
4850
|
+
safeOptions.damping
|
|
4851
|
+
);
|
|
4852
|
+
}
|
|
4853
|
+
return layout;
|
|
4854
|
+
}
|
|
4855
|
+
function resolveForceGraphLayoutOptions(options) {
|
|
4856
|
+
const minNodeRadius = clampFinite(options.minNodeRadius ?? 8, 1, 64);
|
|
4857
|
+
const maxNodeRadius = Math.max(
|
|
4858
|
+
minNodeRadius,
|
|
4859
|
+
clampFinite(options.maxNodeRadius ?? 14, minNodeRadius, 96)
|
|
4860
|
+
);
|
|
4861
|
+
return {
|
|
4862
|
+
width: clampFinite(options.width, maxNodeRadius * 2, 2e4),
|
|
4863
|
+
height: clampFinite(options.height, maxNodeRadius * 2, 2e4),
|
|
4864
|
+
centerSlug: options.centerSlug,
|
|
4865
|
+
linkDistance: clampFinite(options.linkDistance ?? 84, 1, 2e3),
|
|
4866
|
+
repulsion: clampFinite(options.repulsion ?? 1800, 0, 1e5),
|
|
4867
|
+
iterations: Math.round(clampFinite(options.iterations ?? 160, 0, 1e3)),
|
|
4868
|
+
damping: clampFinite(options.damping ?? 0.82, 0, 1),
|
|
4869
|
+
minNodeRadius,
|
|
4870
|
+
maxNodeRadius
|
|
4871
|
+
};
|
|
4872
|
+
}
|
|
4873
|
+
function createInitialLayout(nodes, options) {
|
|
4874
|
+
return layoutRadialGraph(nodes, {
|
|
4875
|
+
width: options.width,
|
|
4876
|
+
height: options.height,
|
|
4877
|
+
centerSlug: options.centerSlug ?? nodes[0]?.slug ?? "",
|
|
4878
|
+
radiusRatio: 0.28,
|
|
4879
|
+
minNodeRadius: options.minNodeRadius,
|
|
4880
|
+
maxNodeRadius: options.maxNodeRadius
|
|
4881
|
+
});
|
|
4882
|
+
}
|
|
4883
|
+
function applyRepulsion(nodes, layout, velocities, strength) {
|
|
4884
|
+
for (let sourceIndex = 0; sourceIndex < nodes.length; sourceIndex++) {
|
|
4885
|
+
for (let targetIndex = sourceIndex + 1; targetIndex < nodes.length; targetIndex++) {
|
|
4886
|
+
const source = layout.get(nodes[sourceIndex]?.slug ?? "");
|
|
4887
|
+
const target = layout.get(nodes[targetIndex]?.slug ?? "");
|
|
4888
|
+
const sourceVelocity = velocities.get(nodes[sourceIndex]?.slug ?? "");
|
|
4889
|
+
const targetVelocity = velocities.get(nodes[targetIndex]?.slug ?? "");
|
|
4890
|
+
if (!source || !target || !sourceVelocity || !targetVelocity) continue;
|
|
4891
|
+
const dx = target.x - source.x || 0.01;
|
|
4892
|
+
const dy = target.y - source.y || 0.01;
|
|
4893
|
+
const distanceSquared = Math.max(dx * dx + dy * dy, 64);
|
|
4894
|
+
const force = strength / distanceSquared;
|
|
4895
|
+
const distance = Math.sqrt(distanceSquared);
|
|
4896
|
+
const fx = dx / distance * force;
|
|
4897
|
+
const fy = dy / distance * force;
|
|
4898
|
+
sourceVelocity.x -= fx;
|
|
4899
|
+
sourceVelocity.y -= fy;
|
|
4900
|
+
targetVelocity.x += fx;
|
|
4901
|
+
targetVelocity.y += fy;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
function applyLinks(edges, layout, velocities, linkDistance, alpha) {
|
|
4906
|
+
for (const edge of edges) {
|
|
4907
|
+
const source = layout.get(edge.source);
|
|
4908
|
+
const target = layout.get(edge.target);
|
|
4909
|
+
const sourceVelocity = velocities.get(edge.source);
|
|
4910
|
+
const targetVelocity = velocities.get(edge.target);
|
|
4911
|
+
if (!source || !target || !sourceVelocity || !targetVelocity) continue;
|
|
4912
|
+
const dx = target.x - source.x || 0.01;
|
|
4913
|
+
const dy = target.y - source.y || 0.01;
|
|
4914
|
+
const distance = Math.max(Math.sqrt(dx * dx + dy * dy), 1);
|
|
4915
|
+
const force = (distance - linkDistance) * 0.018 * alpha;
|
|
4916
|
+
const fx = dx / distance * force;
|
|
4917
|
+
const fy = dy / distance * force;
|
|
4918
|
+
sourceVelocity.x += fx;
|
|
4919
|
+
sourceVelocity.y += fy;
|
|
4920
|
+
targetVelocity.x -= fx;
|
|
4921
|
+
targetVelocity.y -= fy;
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
function applyCentering(nodes, layout, velocities, centerX, centerY, alpha) {
|
|
4925
|
+
for (const node of nodes) {
|
|
4926
|
+
const point = layout.get(node.slug);
|
|
4927
|
+
const velocity = velocities.get(node.slug);
|
|
4928
|
+
if (!point || !velocity) continue;
|
|
4929
|
+
velocity.x += (centerX - point.x) * 4e-3 * alpha;
|
|
4930
|
+
velocity.y += (centerY - point.y) * 4e-3 * alpha;
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
function integrate(nodes, layout, velocities, options, damping) {
|
|
4934
|
+
for (const node of nodes) {
|
|
4935
|
+
const point = layout.get(node.slug);
|
|
4936
|
+
const velocity = velocities.get(node.slug);
|
|
4937
|
+
if (!point || !velocity) continue;
|
|
4938
|
+
velocity.x *= damping;
|
|
4939
|
+
velocity.y *= damping;
|
|
4940
|
+
point.x = clamp(
|
|
4941
|
+
point.x + velocity.x,
|
|
4942
|
+
point.radius,
|
|
4943
|
+
options.width - point.radius
|
|
4944
|
+
);
|
|
4945
|
+
point.y = clamp(
|
|
4946
|
+
point.y + velocity.y,
|
|
4947
|
+
point.radius,
|
|
4948
|
+
options.height - point.radius
|
|
4949
|
+
);
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
3218
4952
|
function clamp(value, min, max) {
|
|
3219
4953
|
return Math.min(Math.max(value, min), max);
|
|
3220
4954
|
}
|
|
4955
|
+
function clampFinite(value, min, max) {
|
|
4956
|
+
if (!Number.isFinite(value)) return min;
|
|
4957
|
+
return clamp(value, min, max);
|
|
4958
|
+
}
|
|
3221
4959
|
|
|
3222
4960
|
// src/content/read_only_content_graph.ts
|
|
3223
4961
|
async function readOnlyContentGraph(source, locations) {
|
|
@@ -3227,7 +4965,7 @@ async function readOnlyContentGraph(source, locations) {
|
|
|
3227
4965
|
const markdownContents = await Promise.all(
|
|
3228
4966
|
markdownEntries.map(async (entry) => ({
|
|
3229
4967
|
entry,
|
|
3230
|
-
markdown: await
|
|
4968
|
+
markdown: await readText3(source, entry)
|
|
3231
4969
|
}))
|
|
3232
4970
|
);
|
|
3233
4971
|
const graphEntries = markdownContents.map(
|
|
@@ -3246,7 +4984,15 @@ function createGraphEntry(entry, markdown, contentIndex, locations) {
|
|
|
3246
4984
|
permalink: location.permalink,
|
|
3247
4985
|
publicLocation: location,
|
|
3248
4986
|
title: slug,
|
|
4987
|
+
aliases: [],
|
|
3249
4988
|
frontmatter: {},
|
|
4989
|
+
publishing: resolvePublishingState(
|
|
4990
|
+
{},
|
|
4991
|
+
{
|
|
4992
|
+
strategy: "selective",
|
|
4993
|
+
buildTime: /* @__PURE__ */ new Date(0)
|
|
4994
|
+
}
|
|
4995
|
+
),
|
|
3250
4996
|
html: "",
|
|
3251
4997
|
tags: [],
|
|
3252
4998
|
links: extractContentLinks(markdown, contentIndex),
|
|
@@ -3254,60 +5000,11 @@ function createGraphEntry(entry, markdown, contentIndex, locations) {
|
|
|
3254
5000
|
assets: []
|
|
3255
5001
|
};
|
|
3256
5002
|
}
|
|
3257
|
-
async function
|
|
5003
|
+
async function readText3(source, entry) {
|
|
3258
5004
|
const content = await source.read(entry);
|
|
3259
5005
|
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
3260
5006
|
}
|
|
3261
5007
|
|
|
3262
|
-
// src/plugin/plugin_memo.ts
|
|
3263
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
3264
|
-
function createPluginMemo(cache, options = {}) {
|
|
3265
|
-
return {
|
|
3266
|
-
async memo(key, dependencies, compute) {
|
|
3267
|
-
const memoKey = `${key}:${hash2(stableStringify(dependencies))}`;
|
|
3268
|
-
try {
|
|
3269
|
-
const cached = await cache.get(memoKey);
|
|
3270
|
-
if (cached !== void 0) return cached;
|
|
3271
|
-
} catch (error) {
|
|
3272
|
-
warn(
|
|
3273
|
-
options.logger,
|
|
3274
|
-
"Plugin cache read failed; recomputing.",
|
|
3275
|
-
key,
|
|
3276
|
-
error
|
|
3277
|
-
);
|
|
3278
|
-
}
|
|
3279
|
-
const value = await compute();
|
|
3280
|
-
try {
|
|
3281
|
-
await cache.set(memoKey, value);
|
|
3282
|
-
} catch (error) {
|
|
3283
|
-
warn(
|
|
3284
|
-
options.logger,
|
|
3285
|
-
"Plugin cache write failed; result not cached.",
|
|
3286
|
-
key,
|
|
3287
|
-
error
|
|
3288
|
-
);
|
|
3289
|
-
}
|
|
3290
|
-
return value;
|
|
3291
|
-
}
|
|
3292
|
-
};
|
|
3293
|
-
}
|
|
3294
|
-
function stableStringify(value) {
|
|
3295
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3296
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
3297
|
-
const keys = Object.keys(value).sort();
|
|
3298
|
-
const body = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",");
|
|
3299
|
-
return `{${body}}`;
|
|
3300
|
-
}
|
|
3301
|
-
function hash2(value) {
|
|
3302
|
-
return createHash3("sha256").update(value).digest("hex");
|
|
3303
|
-
}
|
|
3304
|
-
function warn(logger, message, key, error) {
|
|
3305
|
-
logger?.warn(message, {
|
|
3306
|
-
memoKey: key,
|
|
3307
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3308
|
-
});
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
5008
|
// src/types/content_manifest.ts
|
|
3312
5009
|
function appendContentBodySlot(entry, slot, html) {
|
|
3313
5010
|
if (!html.trim()) return;
|
|
@@ -3320,12 +5017,12 @@ ${html}` : html
|
|
|
3320
5017
|
}
|
|
3321
5018
|
|
|
3322
5019
|
// src/types/plugin_endpoint.ts
|
|
3323
|
-
function defineEndpoint(
|
|
5020
|
+
function defineEndpoint(path5, handler, options = {}) {
|
|
3324
5021
|
if (options.cacheControl === void 0) {
|
|
3325
|
-
return { path:
|
|
5022
|
+
return { path: path5, handler };
|
|
3326
5023
|
}
|
|
3327
5024
|
return {
|
|
3328
|
-
path:
|
|
5025
|
+
path: path5,
|
|
3329
5026
|
handler: async (context) => {
|
|
3330
5027
|
const response = await handler(context);
|
|
3331
5028
|
const headers = { ...response.headers };
|
|
@@ -3367,6 +5064,7 @@ export {
|
|
|
3367
5064
|
ContentManager,
|
|
3368
5065
|
FileSystemContentSource,
|
|
3369
5066
|
IMAGE_EXTENSIONS,
|
|
5067
|
+
INTERNAL_CONTENT_IGNORE_PATTERNS,
|
|
3370
5068
|
NoopLogger,
|
|
3371
5069
|
NoopTracer,
|
|
3372
5070
|
Pipeline,
|
|
@@ -3376,6 +5074,7 @@ export {
|
|
|
3376
5074
|
attachmentUrl,
|
|
3377
5075
|
buildContentCollections,
|
|
3378
5076
|
buildGraphEdges,
|
|
5077
|
+
buildOutputInventory,
|
|
3379
5078
|
calculateReadingTime,
|
|
3380
5079
|
collectConfigDeprecationDiagnostics,
|
|
3381
5080
|
createClientEntry,
|
|
@@ -3388,6 +5087,7 @@ export {
|
|
|
3388
5087
|
defineEndpoint,
|
|
3389
5088
|
definePlugin,
|
|
3390
5089
|
defineTheme,
|
|
5090
|
+
determineOutputChanges,
|
|
3391
5091
|
escapeHtml,
|
|
3392
5092
|
escapeHtmlAttribute,
|
|
3393
5093
|
escapeScriptJson,
|
|
@@ -3398,14 +5098,18 @@ export {
|
|
|
3398
5098
|
getExtension,
|
|
3399
5099
|
getResolvedPluginMetadata,
|
|
3400
5100
|
groupContentEntries,
|
|
5101
|
+
htmlOutputPath,
|
|
3401
5102
|
isAttachmentPath,
|
|
3402
5103
|
isExcluded,
|
|
5104
|
+
isIgnoredContentPath,
|
|
3403
5105
|
isImagePath,
|
|
3404
5106
|
isMarkdownPath,
|
|
3405
5107
|
isPublishable,
|
|
3406
5108
|
isPublished,
|
|
5109
|
+
layoutForceGraph,
|
|
3407
5110
|
layoutRadialGraph,
|
|
3408
5111
|
loadContentBuildState,
|
|
5112
|
+
normalizeContentAssetOutputPath,
|
|
3409
5113
|
normalizeContentPath,
|
|
3410
5114
|
normalizeGeneratedOutputPath,
|
|
3411
5115
|
normalizeTag,
|
|
@@ -3420,7 +5124,13 @@ export {
|
|
|
3420
5124
|
resolveContentQueryPagination,
|
|
3421
5125
|
resolveContentStableId,
|
|
3422
5126
|
resolveDefaultContentLocation,
|
|
5127
|
+
resolveFolderEntry,
|
|
5128
|
+
resolveFolderLocation,
|
|
5129
|
+
resolveGeneratedFolderLocation,
|
|
3423
5130
|
resolvePlugins,
|
|
5131
|
+
resolvePublicFolderLocation,
|
|
5132
|
+
resolvePublishingBuildTime,
|
|
5133
|
+
resolvePublishingState,
|
|
3424
5134
|
serializePublicClientConfig,
|
|
3425
5135
|
stableStringify,
|
|
3426
5136
|
stripHtml,
|