@riebeckite/core 0.0.13 → 0.0.15
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 +15 -6
- package/dist/index.js +2086 -406
- package/dist/src/config_deprecations.d.ts +8 -0
- 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 +35 -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/deprecation.d.ts +14 -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 +18 -1
- package/dist/src/types/generated_output.d.ts +9 -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 +4 -0
- package/dist/src/types/riebeckite_config.d.ts +4 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -109,41 +109,41 @@ function validateTheme(value, issues) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
function validateThemeConfig(value,
|
|
113
|
-
validateOptionalString(value.name, `${
|
|
112
|
+
function validateThemeConfig(value, path5, issues) {
|
|
113
|
+
validateOptionalString(value.name, `${path5}.name`, issues);
|
|
114
114
|
validateOneOf(
|
|
115
115
|
value.colorMode,
|
|
116
|
-
`${
|
|
116
|
+
`${path5}.colorMode`,
|
|
117
117
|
["light", "dark", "system"],
|
|
118
118
|
issues
|
|
119
119
|
);
|
|
120
120
|
validateOneOf(
|
|
121
121
|
value.typography,
|
|
122
|
-
`${
|
|
122
|
+
`${path5}.typography`,
|
|
123
123
|
["system", "serif", "sans"],
|
|
124
124
|
issues
|
|
125
125
|
);
|
|
126
126
|
validateOneOf(
|
|
127
127
|
value.articleLayout,
|
|
128
|
-
`${
|
|
128
|
+
`${path5}.articleLayout`,
|
|
129
129
|
["article", "sidebar", "full-width"],
|
|
130
130
|
issues
|
|
131
131
|
);
|
|
132
|
-
validateStringArray(value.userCss, `${
|
|
132
|
+
validateStringArray(value.userCss, `${path5}.userCss`, issues);
|
|
133
133
|
if (value.attributes !== void 0 && !isRecord(value.attributes)) {
|
|
134
|
-
issues.push({ path: `${
|
|
134
|
+
issues.push({ path: `${path5}.attributes`, message: "Expected an object." });
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
function validateThemeStyles(value,
|
|
137
|
+
function validateThemeStyles(value, path5, issues) {
|
|
138
138
|
if (value === void 0) return;
|
|
139
139
|
if (!Array.isArray(value)) {
|
|
140
|
-
issues.push({ path:
|
|
140
|
+
issues.push({ path: path5, message: "Expected an array." });
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
for (const [index, style] of value.entries()) {
|
|
144
144
|
if (!isRecord(style) || !isNonEmptyString(style.moduleSpecifier)) {
|
|
145
145
|
issues.push({
|
|
146
|
-
path: `${
|
|
146
|
+
path: `${path5}[${index}].moduleSpecifier`,
|
|
147
147
|
message: "Expected a non-empty string."
|
|
148
148
|
});
|
|
149
149
|
}
|
|
@@ -163,69 +163,69 @@ function validatePlugins(value, issues) {
|
|
|
163
163
|
continue;
|
|
164
164
|
}
|
|
165
165
|
const plugin = input;
|
|
166
|
-
const
|
|
166
|
+
const path5 = `plugins[${index}]`;
|
|
167
167
|
if (!isNonEmptyString(plugin.name)) {
|
|
168
168
|
issues.push({
|
|
169
|
-
path: `${
|
|
169
|
+
path: `${path5}.name`,
|
|
170
170
|
message: "Expected a non-empty string."
|
|
171
171
|
});
|
|
172
172
|
} else if (plugin.enabled !== false) {
|
|
173
173
|
const duplicateIndex = names.get(plugin.name);
|
|
174
174
|
if (duplicateIndex !== void 0) {
|
|
175
175
|
issues.push({
|
|
176
|
-
path: `${
|
|
176
|
+
path: `${path5}.name`,
|
|
177
177
|
message: `Duplicate plugin name "${plugin.name}" (also plugins[${duplicateIndex}]).`
|
|
178
178
|
});
|
|
179
179
|
} else {
|
|
180
180
|
names.set(plugin.name, index);
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
-
validateOptionalBoolean(plugin.enabled, `${
|
|
184
|
-
validateCapabilityMetadata(plugin,
|
|
185
|
-
if (plugin.enabled !== false) validatePluginOptions(plugin,
|
|
183
|
+
validateOptionalBoolean(plugin.enabled, `${path5}.enabled`, issues);
|
|
184
|
+
validateCapabilityMetadata(plugin, path5, issues);
|
|
185
|
+
if (plugin.enabled !== false) validatePluginOptions(plugin, path5, issues);
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
-
function validateCapabilityMetadata(plugin,
|
|
189
|
-
validateStringArray(plugin.provides, `${
|
|
190
|
-
validateStringArray(plugin.requires, `${
|
|
191
|
-
validateStringArray(plugin.optional, `${
|
|
188
|
+
function validateCapabilityMetadata(plugin, path5, issues) {
|
|
189
|
+
validateStringArray(plugin.provides, `${path5}.provides`, issues, true);
|
|
190
|
+
validateStringArray(plugin.requires, `${path5}.requires`, issues, true);
|
|
191
|
+
validateStringArray(plugin.optional, `${path5}.optional`, issues, true);
|
|
192
192
|
}
|
|
193
|
-
function validatePluginOptions(plugin,
|
|
193
|
+
function validatePluginOptions(plugin, path5, issues) {
|
|
194
194
|
if (!plugin.validateOptions) return;
|
|
195
195
|
try {
|
|
196
196
|
const optionIssues = plugin.validateOptions(plugin.options) ?? [];
|
|
197
197
|
for (const issue of optionIssues) {
|
|
198
198
|
issues.push({
|
|
199
|
-
path: issue.path ? `${
|
|
199
|
+
path: issue.path ? `${path5}.options.${issue.path}` : `${path5}.options`,
|
|
200
200
|
message: issue.message
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
203
|
} catch (error) {
|
|
204
204
|
issues.push({
|
|
205
|
-
path: `${
|
|
205
|
+
path: `${path5}.options`,
|
|
206
206
|
message: `Validator failed: ${toErrorMessage(error)}`
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
-
function validateRequiredString(value,
|
|
210
|
+
function validateRequiredString(value, path5, issues) {
|
|
211
211
|
if (!isNonEmptyString(value)) {
|
|
212
|
-
issues.push({ path:
|
|
212
|
+
issues.push({ path: path5, message: "Expected a non-empty string." });
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
function validateOptionalString(value,
|
|
215
|
+
function validateOptionalString(value, path5, issues) {
|
|
216
216
|
if (value !== void 0 && typeof value !== "string") {
|
|
217
|
-
issues.push({ path:
|
|
217
|
+
issues.push({ path: path5, message: "Expected a string." });
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
-
function validateOptionalBoolean(value,
|
|
220
|
+
function validateOptionalBoolean(value, path5, issues) {
|
|
221
221
|
if (value !== void 0 && typeof value !== "boolean") {
|
|
222
|
-
issues.push({ path:
|
|
222
|
+
issues.push({ path: path5, message: "Expected a boolean." });
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
|
-
function validateAbsoluteUrl(value,
|
|
225
|
+
function validateAbsoluteUrl(value, path5, issues) {
|
|
226
226
|
if (value === void 0 || value === "") return;
|
|
227
227
|
if (typeof value !== "string") {
|
|
228
|
-
issues.push({ path:
|
|
228
|
+
issues.push({ path: path5, message: "Expected an absolute URL." });
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
231
|
try {
|
|
@@ -233,27 +233,27 @@ function validateAbsoluteUrl(value, path4, issues) {
|
|
|
233
233
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
234
234
|
throw new Error();
|
|
235
235
|
} catch {
|
|
236
|
-
issues.push({ path:
|
|
236
|
+
issues.push({ path: path5, message: "Expected an absolute HTTP(S) URL." });
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
function validateStringArray(value,
|
|
239
|
+
function validateStringArray(value, path5, issues, nonEmpty = false) {
|
|
240
240
|
if (value === void 0) return;
|
|
241
241
|
if (!Array.isArray(value)) {
|
|
242
|
-
issues.push({ path:
|
|
242
|
+
issues.push({ path: path5, message: "Expected an array." });
|
|
243
243
|
return;
|
|
244
244
|
}
|
|
245
245
|
for (const [index, item] of value.entries()) {
|
|
246
246
|
if (typeof item !== "string" || nonEmpty && !item.trim()) {
|
|
247
247
|
issues.push({
|
|
248
|
-
path: `${
|
|
248
|
+
path: `${path5}[${index}]`,
|
|
249
249
|
message: nonEmpty ? "Expected a non-empty string." : "Expected a string."
|
|
250
250
|
});
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
|
-
function validateOneOf(value,
|
|
254
|
+
function validateOneOf(value, path5, choices, issues) {
|
|
255
255
|
if (value !== void 0 && (typeof value !== "string" || !choices.includes(value))) {
|
|
256
|
-
issues.push({ path:
|
|
256
|
+
issues.push({ path: path5, message: `Expected one of: ${choices.join(", ")}.` });
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
function formatValidationIssues(issues) {
|
|
@@ -538,8 +538,21 @@ function definePlugin(plugin) {
|
|
|
538
538
|
}
|
|
539
539
|
function resolvePlugins(plugins = []) {
|
|
540
540
|
const orderedPlugins = plugins.filter((plugin) => Boolean(plugin)).filter((plugin) => plugin.enabled !== false).toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
541
|
+
assertUniquePluginNames(orderedPlugins);
|
|
541
542
|
return resolvePluginDependencies(orderedPlugins);
|
|
542
543
|
}
|
|
544
|
+
function assertUniquePluginNames(plugins) {
|
|
545
|
+
const owners = /* @__PURE__ */ new Map();
|
|
546
|
+
plugins.forEach((plugin, index) => {
|
|
547
|
+
const previous = owners.get(plugin.name);
|
|
548
|
+
if (previous !== void 0) {
|
|
549
|
+
throw new Error(
|
|
550
|
+
`Plugin name "${plugin.name}" is used by both plugins[${previous}] and plugins[${index}]`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
owners.set(plugin.name, index);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
543
556
|
function getResolvedPluginMetadata(plugins) {
|
|
544
557
|
return plugins.map((plugin) => ({
|
|
545
558
|
name: plugin.name,
|
|
@@ -626,7 +639,11 @@ function resolveConfig(config) {
|
|
|
626
639
|
}
|
|
627
640
|
},
|
|
628
641
|
theme,
|
|
629
|
-
plugins: resolvePlugins(config.plugins)
|
|
642
|
+
plugins: resolvePlugins(config.plugins),
|
|
643
|
+
cache: {
|
|
644
|
+
enabled: config.cache?.enabled ?? true,
|
|
645
|
+
directory: config.cache?.directory ?? ""
|
|
646
|
+
}
|
|
630
647
|
};
|
|
631
648
|
}
|
|
632
649
|
function resolveThemeConfig(themeInput) {
|
|
@@ -659,6 +676,8 @@ function sanitizeThemeAttributes(attributes) {
|
|
|
659
676
|
);
|
|
660
677
|
}
|
|
661
678
|
function isPublished(config, frontmatter) {
|
|
679
|
+
if (frontmatter?.visibility === "unlisted") return true;
|
|
680
|
+
if (frontmatter?.visibility === "draft") return false;
|
|
662
681
|
return isPublishable(config.content.filters.publishStrategy, frontmatter);
|
|
663
682
|
}
|
|
664
683
|
function isExcluded(patterns, relativePath) {
|
|
@@ -693,6 +712,46 @@ function globToRegExp(glob) {
|
|
|
693
712
|
return new RegExp(`^${re}$`);
|
|
694
713
|
}
|
|
695
714
|
|
|
715
|
+
// src/deprecation.ts
|
|
716
|
+
function createDeprecationDiagnostic(notice) {
|
|
717
|
+
return {
|
|
718
|
+
code: `deprecated-${notice.kind}`,
|
|
719
|
+
severity: "warning",
|
|
720
|
+
message: formatDeprecationMessage(notice),
|
|
721
|
+
target: notice.target,
|
|
722
|
+
suggestion: notice.replacement ? `Use ${notice.replacement}. ${notice.action}` : notice.action,
|
|
723
|
+
meta: { deprecation: notice }
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function formatDeprecationMessage(notice) {
|
|
727
|
+
const lines = [
|
|
728
|
+
`Deprecated: ${notice.target}`,
|
|
729
|
+
`Deprecated since: ${notice.deprecatedSince}`
|
|
730
|
+
];
|
|
731
|
+
if (notice.replacement) lines.push(`Use: ${notice.replacement}`);
|
|
732
|
+
lines.push(`Migration: ${notice.action}`);
|
|
733
|
+
if (notice.documentationUrl) lines.push(`See: ${notice.documentationUrl}`);
|
|
734
|
+
if (notice.removedIn) lines.push(`Removal planned: ${notice.removedIn}`);
|
|
735
|
+
return lines.join("\n");
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// src/config_deprecations.ts
|
|
739
|
+
var configDeprecations = [];
|
|
740
|
+
function collectConfigDeprecationDiagnostics(config, notices = configDeprecations) {
|
|
741
|
+
return notices.filter((notice) => hasConfigPath(config, notice.path)).map(createDeprecationDiagnostic);
|
|
742
|
+
}
|
|
743
|
+
function hasConfigPath(config, path5) {
|
|
744
|
+
let value = config;
|
|
745
|
+
for (const segment of path5.split(".")) {
|
|
746
|
+
if (!isRecord2(value) || !(segment in value)) return false;
|
|
747
|
+
value = value[segment];
|
|
748
|
+
}
|
|
749
|
+
return value !== void 0;
|
|
750
|
+
}
|
|
751
|
+
function isRecord2(value) {
|
|
752
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
753
|
+
}
|
|
754
|
+
|
|
696
755
|
// src/content/image_extensions.ts
|
|
697
756
|
var IMAGE_EXTENSIONS = [
|
|
698
757
|
"png",
|
|
@@ -746,7 +805,7 @@ import fs from "node:fs/promises";
|
|
|
746
805
|
import path from "node:path";
|
|
747
806
|
|
|
748
807
|
// src/content/content_build_state.ts
|
|
749
|
-
var CONTENT_BUILD_STATE_VERSION =
|
|
808
|
+
var CONTENT_BUILD_STATE_VERSION = 6;
|
|
750
809
|
var CONTENT_BUILD_STATE_DIRECTORY = ".riebeckite";
|
|
751
810
|
var CONTENT_BUILD_STATE_EXCLUDE = `${CONTENT_BUILD_STATE_DIRECTORY}/**`;
|
|
752
811
|
|
|
@@ -770,7 +829,7 @@ async function readContentBuildStateStatus(statePath) {
|
|
|
770
829
|
}
|
|
771
830
|
throw error;
|
|
772
831
|
}
|
|
773
|
-
if (!
|
|
832
|
+
if (!isRecord3(parsed)) {
|
|
774
833
|
return { kind: "invalid", path: statePath, reason: "invalid-shape" };
|
|
775
834
|
}
|
|
776
835
|
if (parsed.version !== CONTENT_BUILD_STATE_VERSION) {
|
|
@@ -786,12 +845,12 @@ async function readContentBuildStateStatus(statePath) {
|
|
|
786
845
|
entryCount: Object.keys(parsed.entries).length
|
|
787
846
|
};
|
|
788
847
|
}
|
|
789
|
-
async function saveContentBuildState(statePath,
|
|
848
|
+
async function saveContentBuildState(statePath, state2) {
|
|
790
849
|
const directory = path.dirname(statePath);
|
|
791
850
|
const temporaryPath = `${statePath}.${randomUUID()}.tmp`;
|
|
792
851
|
await fs.mkdir(directory, { recursive: true });
|
|
793
852
|
try {
|
|
794
|
-
await fs.writeFile(temporaryPath, JSON.stringify(
|
|
853
|
+
await fs.writeFile(temporaryPath, JSON.stringify(state2), "utf8");
|
|
795
854
|
await fs.rename(temporaryPath, statePath);
|
|
796
855
|
} catch (error) {
|
|
797
856
|
await fs.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
@@ -810,19 +869,37 @@ function resolveContentBuildStatePath(config, contentDirectory) {
|
|
|
810
869
|
);
|
|
811
870
|
}
|
|
812
871
|
function isContentBuildState(value) {
|
|
813
|
-
return
|
|
872
|
+
return isRecord3(value) && value.version === CONTENT_BUILD_STATE_VERSION && isContentBuildStateShape(value);
|
|
814
873
|
}
|
|
815
874
|
function isContentBuildStateShape(value) {
|
|
816
|
-
if (!
|
|
875
|
+
if (!isRecord3(value.entries) || !isRecord3(value.contentIndex) || value.outputs !== void 0 && !isOutputDescriptors(value.outputs)) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
817
878
|
return Object.values(value.entries).every(
|
|
818
|
-
(entry) =>
|
|
819
|
-
(dependency) => typeof dependency === "string"
|
|
879
|
+
(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(
|
|
880
|
+
(dependency) => isRecord3(dependency) && (dependency.kind === "content" || dependency.kind === "file") && typeof dependency.id === "string"
|
|
820
881
|
)
|
|
821
882
|
) && Object.values(value.contentIndex).every(
|
|
822
883
|
(entry) => typeof entry === "string"
|
|
823
884
|
);
|
|
824
885
|
}
|
|
825
|
-
function
|
|
886
|
+
function isOutputDescriptors(value) {
|
|
887
|
+
return Array.isArray(value) && value.every(
|
|
888
|
+
(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)
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
function isOutputPath(value) {
|
|
892
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/")) {
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
return value.split("/").every((segment) => segment !== "." && segment !== "..");
|
|
896
|
+
}
|
|
897
|
+
function isOutputDependency(value) {
|
|
898
|
+
if (!isRecord3(value) || typeof value.type !== "string") return false;
|
|
899
|
+
if (value.type === "global" || value.type === "unknown") return true;
|
|
900
|
+
return value.type === "content" && typeof value.slug === "string" || value.type === "tag" && typeof value.tag === "string" || value.type === "folder" && typeof value.folder === "string";
|
|
901
|
+
}
|
|
902
|
+
function isRecord3(value) {
|
|
826
903
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
827
904
|
}
|
|
828
905
|
function isNotFoundError(error) {
|
|
@@ -1133,9 +1210,9 @@ function buildContentCollections(entries, definitions) {
|
|
|
1133
1210
|
function normalizePageSize(value) {
|
|
1134
1211
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
1135
1212
|
}
|
|
1136
|
-
function withPage(
|
|
1137
|
-
if (index === 0) return
|
|
1138
|
-
const base =
|
|
1213
|
+
function withPage(path5, index) {
|
|
1214
|
+
if (index === 0) return path5;
|
|
1215
|
+
const base = path5 === "/" ? "" : path5;
|
|
1139
1216
|
return normalizePath(`${base}/page/${index + 1}`);
|
|
1140
1217
|
}
|
|
1141
1218
|
function defaultPath(context) {
|
|
@@ -1163,12 +1240,32 @@ function normalizePath(value) {
|
|
|
1163
1240
|
// src/content/content_fingerprint.ts
|
|
1164
1241
|
import { createHash } from "node:crypto";
|
|
1165
1242
|
async function fingerprintContentEntries(entries, read) {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1243
|
+
const sortedEntries = [...entries].sort(
|
|
1244
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
1245
|
+
);
|
|
1246
|
+
return await mapConcurrent(sortedEntries, 64, async (entry) => ({
|
|
1247
|
+
entry,
|
|
1248
|
+
fingerprint: await fingerprintContentEntry(entry, read)
|
|
1249
|
+
}));
|
|
1250
|
+
}
|
|
1251
|
+
async function mapConcurrent(values, concurrency, map) {
|
|
1252
|
+
const results = new Array(values.length);
|
|
1253
|
+
let nextIndex = 0;
|
|
1254
|
+
async function worker() {
|
|
1255
|
+
while (nextIndex < values.length) {
|
|
1256
|
+
const index = nextIndex;
|
|
1257
|
+
nextIndex += 1;
|
|
1258
|
+
const value = values[index];
|
|
1259
|
+
if (value !== void 0) results[index] = await map(value);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
await Promise.all(
|
|
1263
|
+
Array.from(
|
|
1264
|
+
{ length: Math.min(concurrency, values.length) },
|
|
1265
|
+
() => worker()
|
|
1266
|
+
)
|
|
1171
1267
|
);
|
|
1268
|
+
return results;
|
|
1172
1269
|
}
|
|
1173
1270
|
async function fingerprintContentEntry(entry, read) {
|
|
1174
1271
|
if (entry.metadata?.hash) return `hash:${entry.metadata.hash}`;
|
|
@@ -1233,6 +1330,10 @@ function resolveDefaultContentLocation(content) {
|
|
|
1233
1330
|
};
|
|
1234
1331
|
}
|
|
1235
1332
|
|
|
1333
|
+
// src/content/content_manager.ts
|
|
1334
|
+
import { VFile as VFile2 } from "vfile";
|
|
1335
|
+
import { matter as matter3 } from "vfile-matter";
|
|
1336
|
+
|
|
1236
1337
|
// src/observability.ts
|
|
1237
1338
|
var NoopLogger = class {
|
|
1238
1339
|
debug() {
|
|
@@ -1374,12 +1475,334 @@ import remarkMath from "remark-math";
|
|
|
1374
1475
|
import remarkParse from "remark-parse";
|
|
1375
1476
|
import remarkRehype from "remark-rehype";
|
|
1376
1477
|
import { unified } from "unified";
|
|
1377
|
-
import { matter } from "vfile-matter";
|
|
1478
|
+
import { matter as matter2 } from "vfile-matter";
|
|
1378
1479
|
|
|
1379
|
-
// src/
|
|
1380
|
-
import { createHash as createHash2
|
|
1480
|
+
// src/content/content_dependency_tracker.ts
|
|
1481
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1482
|
+
function createContentDependencyTracker(source) {
|
|
1483
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
1484
|
+
const record = (identity, content) => {
|
|
1485
|
+
const normalized = normalizeDependencyIdentity(identity);
|
|
1486
|
+
dependencies.set(dependencyKey(normalized), {
|
|
1487
|
+
...normalized,
|
|
1488
|
+
fingerprint: fingerprintContent(content)
|
|
1489
|
+
});
|
|
1490
|
+
};
|
|
1491
|
+
return {
|
|
1492
|
+
contentSource: {
|
|
1493
|
+
scan: () => source.scan(),
|
|
1494
|
+
async read(entry) {
|
|
1495
|
+
const content = await source.read(entry);
|
|
1496
|
+
record({ kind: "file", id: entry.path }, content);
|
|
1497
|
+
return content;
|
|
1498
|
+
}
|
|
1499
|
+
},
|
|
1500
|
+
async readContent(slug, read) {
|
|
1501
|
+
const content = await read();
|
|
1502
|
+
record({ kind: "content", id: slug }, content);
|
|
1503
|
+
return content;
|
|
1504
|
+
},
|
|
1505
|
+
recordLinkResolution(id, value) {
|
|
1506
|
+
record({ kind: "link", id }, value);
|
|
1507
|
+
},
|
|
1508
|
+
dependencies: () => [...dependencies.values()].toSorted(
|
|
1509
|
+
(left, right) => dependencyKey(left).localeCompare(dependencyKey(right))
|
|
1510
|
+
)
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
function fingerprintContent(content) {
|
|
1514
|
+
return `sha256:${createHash2("sha256").update(typeof content === "string" ? content : content).digest("hex")}`;
|
|
1515
|
+
}
|
|
1516
|
+
function normalizeDependencyIdentity(dependency) {
|
|
1517
|
+
return {
|
|
1518
|
+
...dependency,
|
|
1519
|
+
id: dependency.kind === "file" ? dependency.id.replace(/\\/g, "/") : dependency.id
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
function dependencyKey(dependency) {
|
|
1523
|
+
const normalized = normalizeDependencyIdentity(dependency);
|
|
1524
|
+
return `${normalized.kind}:${normalized.id}`;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// src/content/content_persistent_cache.ts
|
|
1528
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1381
1529
|
import fs2 from "node:fs/promises";
|
|
1530
|
+
import os from "node:os";
|
|
1382
1531
|
import path2 from "node:path";
|
|
1532
|
+
import { VFile } from "vfile";
|
|
1533
|
+
import { matter } from "vfile-matter";
|
|
1534
|
+
|
|
1535
|
+
// src/plugin/plugin_memo.ts
|
|
1536
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1537
|
+
function createPluginMemo(cache, options = {}) {
|
|
1538
|
+
return {
|
|
1539
|
+
async memo(key, dependencies, compute) {
|
|
1540
|
+
const memoKey = `${key}:${hash(stableStringify(dependencies))}`;
|
|
1541
|
+
try {
|
|
1542
|
+
const cached = await cache.get(memoKey);
|
|
1543
|
+
if (cached !== void 0) return cached;
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
warn(
|
|
1546
|
+
options.logger,
|
|
1547
|
+
"Plugin cache read failed; recomputing.",
|
|
1548
|
+
key,
|
|
1549
|
+
error
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
const value = await compute();
|
|
1553
|
+
try {
|
|
1554
|
+
await cache.set(memoKey, value);
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
warn(
|
|
1557
|
+
options.logger,
|
|
1558
|
+
"Plugin cache write failed; result not cached.",
|
|
1559
|
+
key,
|
|
1560
|
+
error
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
return value;
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
function stableStringify(value) {
|
|
1568
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1569
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
1570
|
+
const keys = Object.keys(value).sort();
|
|
1571
|
+
const body = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",");
|
|
1572
|
+
return `{${body}}`;
|
|
1573
|
+
}
|
|
1574
|
+
function hash(value) {
|
|
1575
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
1576
|
+
}
|
|
1577
|
+
function warn(logger, message, key, error) {
|
|
1578
|
+
logger?.warn(message, {
|
|
1579
|
+
memoKey: key,
|
|
1580
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// src/content/content_persistent_cache.ts
|
|
1585
|
+
var CONTENT_CACHE_NAMESPACE = "content";
|
|
1586
|
+
var CONTENT_CACHE_SCHEMA_VERSION = 3;
|
|
1587
|
+
var CONTENT_CACHE_COMPATIBILITY_VERSION = 1;
|
|
1588
|
+
function hash2(value) {
|
|
1589
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
1590
|
+
}
|
|
1591
|
+
function cacheDirectory(config) {
|
|
1592
|
+
if (config.cache?.directory) {
|
|
1593
|
+
return path2.join(config.cache.directory, CONTENT_CACHE_NAMESPACE);
|
|
1594
|
+
}
|
|
1595
|
+
if (config.buildDirectory) {
|
|
1596
|
+
return path2.join(config.buildDirectory, "cache", CONTENT_CACHE_NAMESPACE);
|
|
1597
|
+
}
|
|
1598
|
+
return path2.join(
|
|
1599
|
+
os.tmpdir(),
|
|
1600
|
+
"riebeckite-content-cache",
|
|
1601
|
+
CONTENT_CACHE_NAMESPACE
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
function versionDirectory(baseDir) {
|
|
1605
|
+
return path2.join(baseDir, `v${CONTENT_CACHE_SCHEMA_VERSION}`);
|
|
1606
|
+
}
|
|
1607
|
+
function cacheFilePath(baseDir, key) {
|
|
1608
|
+
return path2.join(versionDirectory(baseDir), `${hash2(key)}.json`);
|
|
1609
|
+
}
|
|
1610
|
+
async function writeAtomically(targetPath, dir, serialized) {
|
|
1611
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
1612
|
+
const temporaryPath = `${targetPath}.${createHash4("sha256").update(`${Date.now()}-${Math.random()}`).digest("hex").slice(0, 12)}.tmp`;
|
|
1613
|
+
try {
|
|
1614
|
+
await fs2.writeFile(temporaryPath, serialized, "utf8");
|
|
1615
|
+
await fs2.rename(temporaryPath, targetPath);
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
await fs2.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
1618
|
+
throw error;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
function createPersistentContentCache(options) {
|
|
1622
|
+
const { config, logger, tracer } = options;
|
|
1623
|
+
const baseDir = cacheDirectory(config);
|
|
1624
|
+
const versionDir = versionDirectory(baseDir);
|
|
1625
|
+
return {
|
|
1626
|
+
async get(key) {
|
|
1627
|
+
if (!config.cache?.enabled) return void 0;
|
|
1628
|
+
const filePath = cacheFilePath(baseDir, key);
|
|
1629
|
+
try {
|
|
1630
|
+
const raw = await fs2.readFile(filePath, "utf8");
|
|
1631
|
+
const entry = JSON.parse(raw);
|
|
1632
|
+
if (entry.schemaVersion !== CONTENT_CACHE_SCHEMA_VERSION) {
|
|
1633
|
+
tracer?.event("persistentContentCache.schemaMismatch", { key });
|
|
1634
|
+
return void 0;
|
|
1635
|
+
}
|
|
1636
|
+
if (entry.key !== key) {
|
|
1637
|
+
tracer?.event("persistentContentCache.keyMismatch", { key });
|
|
1638
|
+
return void 0;
|
|
1639
|
+
}
|
|
1640
|
+
if (!isValidCacheEntry(entry)) {
|
|
1641
|
+
tracer?.event("persistentContentCache.invalidShape", { key });
|
|
1642
|
+
return void 0;
|
|
1643
|
+
}
|
|
1644
|
+
tracer?.event("persistentContentCache.found", { key });
|
|
1645
|
+
return entry;
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
1648
|
+
tracer?.event("persistentContentCache.miss", { key });
|
|
1649
|
+
return void 0;
|
|
1650
|
+
}
|
|
1651
|
+
if (error instanceof SyntaxError) {
|
|
1652
|
+
logger?.warn(
|
|
1653
|
+
"Persistent content cache entry is corrupted and will be ignored.",
|
|
1654
|
+
{
|
|
1655
|
+
key
|
|
1656
|
+
}
|
|
1657
|
+
);
|
|
1658
|
+
return void 0;
|
|
1659
|
+
}
|
|
1660
|
+
throw error;
|
|
1661
|
+
}
|
|
1662
|
+
},
|
|
1663
|
+
async set(key, value) {
|
|
1664
|
+
if (!config.cache?.enabled) return;
|
|
1665
|
+
const filePath = cacheFilePath(baseDir, key);
|
|
1666
|
+
const serialized = JSON.stringify(value);
|
|
1667
|
+
if (serialized === void 0) {
|
|
1668
|
+
throw new TypeError(
|
|
1669
|
+
"Persistent content cache values must be JSON serializable."
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
await writeAtomically(filePath, versionDir, serialized);
|
|
1673
|
+
tracer?.event("persistentContentCache.write", { key });
|
|
1674
|
+
},
|
|
1675
|
+
async clear() {
|
|
1676
|
+
try {
|
|
1677
|
+
await fs2.rm(baseDir, { recursive: true, force: true });
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
function isValidCacheEntry(entry) {
|
|
1684
|
+
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);
|
|
1685
|
+
}
|
|
1686
|
+
function isValidDependency(value) {
|
|
1687
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1688
|
+
const candidate = value;
|
|
1689
|
+
return (candidate.kind === "content" || candidate.kind === "file" || candidate.kind === "link") && typeof candidate.id === "string" && typeof candidate.fingerprint === "string";
|
|
1690
|
+
}
|
|
1691
|
+
function computeContentCacheKey(inputs) {
|
|
1692
|
+
const { slug, source, frontmatter, pipelineFingerprint } = inputs;
|
|
1693
|
+
const normalizedFrontmatter = normalizeFrontmatterForCache(frontmatter);
|
|
1694
|
+
const keyData = {
|
|
1695
|
+
schemaVersion: CONTENT_CACHE_SCHEMA_VERSION,
|
|
1696
|
+
slug,
|
|
1697
|
+
source,
|
|
1698
|
+
frontmatter: normalizedFrontmatter,
|
|
1699
|
+
pipelineFingerprint
|
|
1700
|
+
};
|
|
1701
|
+
return hash2(stableStringify(keyData));
|
|
1702
|
+
}
|
|
1703
|
+
function normalizeFrontmatterForCache(frontmatter) {
|
|
1704
|
+
const normalized = {};
|
|
1705
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
1706
|
+
if (value instanceof Date) {
|
|
1707
|
+
normalized[key] = value.toISOString();
|
|
1708
|
+
} else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1709
|
+
normalized[key] = normalizeFrontmatterForCache(
|
|
1710
|
+
value
|
|
1711
|
+
);
|
|
1712
|
+
} else {
|
|
1713
|
+
normalized[key] = value;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
return normalized;
|
|
1717
|
+
}
|
|
1718
|
+
function computePipelineFingerprint(config) {
|
|
1719
|
+
const plugins = config.plugins;
|
|
1720
|
+
const fingerprintData = {
|
|
1721
|
+
compatibilityVersion: CONTENT_CACHE_COMPATIBILITY_VERSION,
|
|
1722
|
+
plugins: plugins.map((p) => pluginFingerprint(p)),
|
|
1723
|
+
markdown: config.markdown,
|
|
1724
|
+
content: {
|
|
1725
|
+
filters: config.content.filters
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
return hash2(stableStringify(fingerprintData));
|
|
1729
|
+
}
|
|
1730
|
+
var pluginFingerprints = /* @__PURE__ */ new WeakMap();
|
|
1731
|
+
function pluginFingerprint(plugin) {
|
|
1732
|
+
const cached = pluginFingerprints.get(plugin);
|
|
1733
|
+
if (cached !== void 0) return cached;
|
|
1734
|
+
const fingerprint = sanitizeForFingerprint(plugin);
|
|
1735
|
+
pluginFingerprints.set(plugin, fingerprint);
|
|
1736
|
+
return fingerprint;
|
|
1737
|
+
}
|
|
1738
|
+
var functionSourceFingerprints = /* @__PURE__ */ new Map();
|
|
1739
|
+
function fingerprintFunctionSource(value) {
|
|
1740
|
+
const source = Function.prototype.toString.call(value);
|
|
1741
|
+
const cached = functionSourceFingerprints.get(source);
|
|
1742
|
+
if (cached !== void 0) return cached;
|
|
1743
|
+
const fingerprint = `fn:${hash2(source)}`;
|
|
1744
|
+
functionSourceFingerprints.set(source, fingerprint);
|
|
1745
|
+
return fingerprint;
|
|
1746
|
+
}
|
|
1747
|
+
function sanitizeForFingerprint(value) {
|
|
1748
|
+
if (value === null || value === void 0) return value;
|
|
1749
|
+
if (typeof value === "function") {
|
|
1750
|
+
return fingerprintFunctionSource(value);
|
|
1751
|
+
}
|
|
1752
|
+
if (typeof value === "symbol") return "[Symbol]";
|
|
1753
|
+
if (Array.isArray(value)) return value.map(sanitizeForFingerprint);
|
|
1754
|
+
if (value instanceof Map || value instanceof Set)
|
|
1755
|
+
return `[${value.constructor.name}]`;
|
|
1756
|
+
if (typeof value === "object") {
|
|
1757
|
+
const result = {};
|
|
1758
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1759
|
+
result[key] = sanitizeForFingerprint(val);
|
|
1760
|
+
}
|
|
1761
|
+
return result;
|
|
1762
|
+
}
|
|
1763
|
+
return value;
|
|
1764
|
+
}
|
|
1765
|
+
function isPersistentlyCacheable(_slug, config) {
|
|
1766
|
+
const hasL10n = config.plugins.some(
|
|
1767
|
+
(p) => p.name === "l10n" && p.enabled !== false
|
|
1768
|
+
);
|
|
1769
|
+
if (hasL10n) {
|
|
1770
|
+
return { cacheable: false, reason: "l10n plugin is enabled" };
|
|
1771
|
+
}
|
|
1772
|
+
const unsafePlugin = config.plugins.find((plugin) => {
|
|
1773
|
+
if (plugin.enabled === false) return false;
|
|
1774
|
+
if (!canAffectProcessedContent(plugin)) return false;
|
|
1775
|
+
return plugin.processedContentCache?.dependencyMode !== "none" && plugin.processedContentCache?.dependencyMode !== "tracked";
|
|
1776
|
+
});
|
|
1777
|
+
if (unsafePlugin) {
|
|
1778
|
+
if (unsafePlugin.processedContentCache?.dependencyMode === "unsafe") {
|
|
1779
|
+
return {
|
|
1780
|
+
cacheable: false,
|
|
1781
|
+
reason: `plugin ${unsafePlugin.name} explicitly marks processed content cache as unsafe`
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
return {
|
|
1785
|
+
cacheable: false,
|
|
1786
|
+
reason: `plugin ${unsafePlugin.name} can affect processed content without a processedContentCache contract`
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
return { cacheable: true };
|
|
1790
|
+
}
|
|
1791
|
+
function canAffectProcessedContent(plugin) {
|
|
1792
|
+
return Boolean(
|
|
1793
|
+
plugin.remarkPlugins?.length || plugin.rehypePlugins?.length || plugin.extendMarkdownPipeline || plugin.extendHtmlPipeline || plugin.renderers?.length || plugin.onContentLoaded || plugin.onPostParsed || plugin.onPostProcessed
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
function extractFrontmatter(markdown) {
|
|
1797
|
+
const vfile = new VFile({ value: markdown });
|
|
1798
|
+
matter(vfile);
|
|
1799
|
+
return vfile.data.matter ?? {};
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// src/plugin/plugin_cache.ts
|
|
1803
|
+
import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
|
|
1804
|
+
import fs3 from "node:fs/promises";
|
|
1805
|
+
import path3 from "node:path";
|
|
1383
1806
|
function createUnavailablePluginCache() {
|
|
1384
1807
|
return {
|
|
1385
1808
|
get: unavailableAtRuntime,
|
|
@@ -1389,20 +1812,20 @@ function createUnavailablePluginCache() {
|
|
|
1389
1812
|
};
|
|
1390
1813
|
}
|
|
1391
1814
|
function createPluginCache(options) {
|
|
1392
|
-
const pluginDirectory =
|
|
1815
|
+
const pluginDirectory = path3.join(
|
|
1393
1816
|
options.cacheDirectory,
|
|
1394
1817
|
pluginNamespace(options.pluginName)
|
|
1395
1818
|
);
|
|
1396
|
-
const
|
|
1819
|
+
const versionDirectory2 = path3.join(
|
|
1397
1820
|
pluginDirectory,
|
|
1398
|
-
|
|
1821
|
+
hash3(options.cacheVersion ?? "1")
|
|
1399
1822
|
);
|
|
1400
1823
|
const writes = /* @__PURE__ */ new Map();
|
|
1401
1824
|
return {
|
|
1402
1825
|
async get(key) {
|
|
1403
1826
|
let raw;
|
|
1404
1827
|
try {
|
|
1405
|
-
raw = await
|
|
1828
|
+
raw = await fs3.readFile(cachePath(versionDirectory2, key), "utf8");
|
|
1406
1829
|
} catch (error) {
|
|
1407
1830
|
if (isNotFoundError2(error)) {
|
|
1408
1831
|
options.tracer?.event("cache.miss", { plugin: options.pluginName });
|
|
@@ -1425,13 +1848,13 @@ function createPluginCache(options) {
|
|
|
1425
1848
|
}
|
|
1426
1849
|
},
|
|
1427
1850
|
async set(key, value) {
|
|
1428
|
-
const targetPath = cachePath(
|
|
1851
|
+
const targetPath = cachePath(versionDirectory2, key);
|
|
1429
1852
|
const serialized = JSON.stringify(value);
|
|
1430
1853
|
if (serialized === void 0) {
|
|
1431
1854
|
throw new TypeError("Plugin cache values must be JSON serializable.");
|
|
1432
1855
|
}
|
|
1433
1856
|
const previousWrite = writes.get(targetPath) ?? Promise.resolve();
|
|
1434
|
-
const write = previousWrite.catch(() => void 0).then(() =>
|
|
1857
|
+
const write = previousWrite.catch(() => void 0).then(() => writeAtomically2(targetPath, versionDirectory2, serialized));
|
|
1435
1858
|
writes.set(targetPath, write);
|
|
1436
1859
|
try {
|
|
1437
1860
|
await write;
|
|
@@ -1442,37 +1865,37 @@ function createPluginCache(options) {
|
|
|
1442
1865
|
}
|
|
1443
1866
|
},
|
|
1444
1867
|
async delete(key) {
|
|
1445
|
-
await
|
|
1868
|
+
await fs3.rm(cachePath(versionDirectory2, key), { force: true });
|
|
1446
1869
|
},
|
|
1447
1870
|
async clear() {
|
|
1448
|
-
await
|
|
1871
|
+
await fs3.rm(pluginDirectory, { recursive: true, force: true });
|
|
1449
1872
|
}
|
|
1450
1873
|
};
|
|
1451
1874
|
}
|
|
1452
|
-
async function
|
|
1453
|
-
await
|
|
1875
|
+
async function writeAtomically2(targetPath, versionDirectory2, serialized) {
|
|
1876
|
+
await fs3.mkdir(versionDirectory2, { recursive: true });
|
|
1454
1877
|
const temporaryPath = `${targetPath}.${randomUUID2()}.tmp`;
|
|
1455
1878
|
try {
|
|
1456
|
-
await
|
|
1457
|
-
await
|
|
1879
|
+
await fs3.writeFile(temporaryPath, serialized, "utf8");
|
|
1880
|
+
await fs3.rename(temporaryPath, targetPath);
|
|
1458
1881
|
} catch (error) {
|
|
1459
|
-
await
|
|
1882
|
+
await fs3.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
1460
1883
|
throw error;
|
|
1461
1884
|
}
|
|
1462
1885
|
}
|
|
1463
1886
|
function resolvePluginCacheDirectory(config) {
|
|
1464
|
-
if (!config) return
|
|
1465
|
-
if (config.buildDirectory) return
|
|
1466
|
-
return
|
|
1887
|
+
if (!config) return path3.resolve(process.cwd(), ".riebeckite", "cache");
|
|
1888
|
+
if (config.buildDirectory) return path3.join(config.buildDirectory, "cache");
|
|
1889
|
+
return path3.resolve(config.content.directory, "..", ".riebeckite", "cache");
|
|
1467
1890
|
}
|
|
1468
|
-
function cachePath(
|
|
1469
|
-
return
|
|
1891
|
+
function cachePath(versionDirectory2, key) {
|
|
1892
|
+
return path3.join(versionDirectory2, `${hash3(key)}.json`);
|
|
1470
1893
|
}
|
|
1471
1894
|
function pluginNamespace(pluginName) {
|
|
1472
|
-
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(pluginName) ? pluginName :
|
|
1895
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(pluginName) ? pluginName : hash3(pluginName);
|
|
1473
1896
|
}
|
|
1474
|
-
function
|
|
1475
|
-
return
|
|
1897
|
+
function hash3(value) {
|
|
1898
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
1476
1899
|
}
|
|
1477
1900
|
function isNotFoundError2(error) {
|
|
1478
1901
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -1484,16 +1907,20 @@ async function unavailableAtRuntime() {
|
|
|
1484
1907
|
}
|
|
1485
1908
|
|
|
1486
1909
|
// src/types/generated_output.ts
|
|
1910
|
+
var ATTACHMENTS_NAMESPACE = ATTACHMENTS_BASE_PATH.replace(/^\/+/, "");
|
|
1911
|
+
var STATIC_ASSETS_DIR = "assets";
|
|
1487
1912
|
function createUnavailableGeneratedOutputSink() {
|
|
1913
|
+
const unavailable = () => {
|
|
1914
|
+
throw new Error(
|
|
1915
|
+
"Generated output is available only during a build lifecycle."
|
|
1916
|
+
);
|
|
1917
|
+
};
|
|
1488
1918
|
return {
|
|
1489
|
-
emit
|
|
1490
|
-
|
|
1491
|
-
"Generated output is available only during a build lifecycle."
|
|
1492
|
-
);
|
|
1493
|
-
}
|
|
1919
|
+
emit: unavailable,
|
|
1920
|
+
emitAsset: unavailable
|
|
1494
1921
|
};
|
|
1495
1922
|
}
|
|
1496
|
-
function
|
|
1923
|
+
function normalizeOutputPathSegments(input) {
|
|
1497
1924
|
if (typeof input !== "string" || input.length === 0) {
|
|
1498
1925
|
throw new Error("Generated output path must be a non-empty string.");
|
|
1499
1926
|
}
|
|
@@ -1517,12 +1944,27 @@ function normalizeGeneratedOutputPath(input) {
|
|
|
1517
1944
|
);
|
|
1518
1945
|
}
|
|
1519
1946
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1947
|
+
return segments.join("/");
|
|
1948
|
+
}
|
|
1949
|
+
function normalizeGeneratedOutputPath(input) {
|
|
1950
|
+
const normalized = normalizeOutputPathSegments(input);
|
|
1951
|
+
if (normalized === STATIC_ASSETS_DIR || normalized.startsWith(`${STATIC_ASSETS_DIR}/`)) {
|
|
1522
1952
|
throw new Error(
|
|
1523
1953
|
`Generated output path must not conflict with the static assets namespace: "${input}".`
|
|
1524
1954
|
);
|
|
1525
1955
|
}
|
|
1956
|
+
return rejectInternalRiebeckitePath(normalized, input);
|
|
1957
|
+
}
|
|
1958
|
+
function normalizeContentAssetOutputPath(input) {
|
|
1959
|
+
const normalized = normalizeOutputPathSegments(input);
|
|
1960
|
+
if (normalized === ATTACHMENTS_NAMESPACE || normalized.startsWith(`${ATTACHMENTS_NAMESPACE}/`)) {
|
|
1961
|
+
throw new Error(
|
|
1962
|
+
`Generated output path must not conflict with the attachment namespace: "${input}".`
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
return rejectInternalRiebeckitePath(normalized, input);
|
|
1966
|
+
}
|
|
1967
|
+
function rejectInternalRiebeckitePath(normalized, input) {
|
|
1526
1968
|
if (normalized === ".riebeckite" || normalized.startsWith(".riebeckite/")) {
|
|
1527
1969
|
throw new Error(
|
|
1528
1970
|
`Generated output path must not target internal Riebeckite files: "${input}".`
|
|
@@ -1539,6 +1981,16 @@ var Pipeline = class {
|
|
|
1539
1981
|
this.getMarkdownBySlug = getMarkdownBySlug;
|
|
1540
1982
|
this.options = options;
|
|
1541
1983
|
this.isBuildTime = isBuildTime;
|
|
1984
|
+
if (this.options.config && this.options.config.cache?.enabled !== false) {
|
|
1985
|
+
this.persistentCache = createPersistentContentCache({
|
|
1986
|
+
config: this.options.config,
|
|
1987
|
+
logger: this.options.observability?.logger,
|
|
1988
|
+
tracer: this.options.observability?.tracer
|
|
1989
|
+
});
|
|
1990
|
+
this.pipelineFingerprint = computePipelineFingerprint(
|
|
1991
|
+
this.options.config
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1542
1994
|
}
|
|
1543
1995
|
contentIndex;
|
|
1544
1996
|
permalinks;
|
|
@@ -1546,23 +1998,106 @@ var Pipeline = class {
|
|
|
1546
1998
|
options;
|
|
1547
1999
|
isBuildTime;
|
|
1548
2000
|
pluginCaches = /* @__PURE__ */ new Map();
|
|
2001
|
+
persistentCache = null;
|
|
2002
|
+
pipelineFingerprint = null;
|
|
2003
|
+
resolvedPlugins = null;
|
|
1549
2004
|
async execute(markDownContent, context = {}) {
|
|
1550
2005
|
return await this.executeWithEmbedState(
|
|
1551
2006
|
markDownContent,
|
|
1552
2007
|
context,
|
|
1553
2008
|
0,
|
|
1554
|
-
new Set(context.sourceSlug ? [context.sourceSlug] : [])
|
|
2009
|
+
new Set(context.sourceSlug ? [context.sourceSlug] : []),
|
|
2010
|
+
this.options.contentSource ? createContentDependencyTracker(this.options.contentSource) : null
|
|
1555
2011
|
);
|
|
1556
2012
|
}
|
|
1557
|
-
async executeWithEmbedState(markDownContent, context, embedDepth, embedTrail) {
|
|
2013
|
+
async executeWithEmbedState(markDownContent, context, embedDepth, embedTrail, rootDependencyTracker) {
|
|
2014
|
+
const dependencyTracker = rootDependencyTracker;
|
|
2015
|
+
const effectiveContentSource = dependencyTracker ? dependencyTracker.contentSource : this.options.contentSource;
|
|
2016
|
+
const sourceSlug = context.sourceSlug;
|
|
2017
|
+
if (embedDepth === 0 && this.persistentCache && this.pipelineFingerprint && sourceSlug && this.options.contentSource && this.options.config) {
|
|
2018
|
+
const cacheable = isPersistentlyCacheable(
|
|
2019
|
+
sourceSlug,
|
|
2020
|
+
this.options.config
|
|
2021
|
+
);
|
|
2022
|
+
if (cacheable.cacheable) {
|
|
2023
|
+
const frontmatter = extractFrontmatter(markDownContent);
|
|
2024
|
+
const cacheKey = computeContentCacheKey({
|
|
2025
|
+
slug: sourceSlug,
|
|
2026
|
+
source: markDownContent,
|
|
2027
|
+
frontmatter,
|
|
2028
|
+
pipelineFingerprint: this.pipelineFingerprint
|
|
2029
|
+
});
|
|
2030
|
+
const cachedEntry = await this.persistentCache.get(cacheKey);
|
|
2031
|
+
if (cachedEntry) {
|
|
2032
|
+
const validationTracker = createContentDependencyTracker(
|
|
2033
|
+
this.options.contentSource
|
|
2034
|
+
);
|
|
2035
|
+
let valid = true;
|
|
2036
|
+
for (const dep of cachedEntry.dependencies) {
|
|
2037
|
+
if (dep.kind === "content") {
|
|
2038
|
+
const content = await validationTracker.readContent(
|
|
2039
|
+
dep.id,
|
|
2040
|
+
async () => await this.getMarkdownBySlug?.(dep.id) ?? ""
|
|
2041
|
+
);
|
|
2042
|
+
if (fingerprintContent(content) !== dep.fingerprint) {
|
|
2043
|
+
valid = false;
|
|
2044
|
+
break;
|
|
2045
|
+
}
|
|
2046
|
+
} else if (dep.kind === "file") {
|
|
2047
|
+
const fileContent = await validationTracker.contentSource.read({
|
|
2048
|
+
path: dep.id
|
|
2049
|
+
});
|
|
2050
|
+
if (fingerprintContent(fileContent) !== dep.fingerprint) {
|
|
2051
|
+
valid = false;
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
2054
|
+
} else if (dep.kind === "link") {
|
|
2055
|
+
if (fingerprintContent(this.resolveLinkDependency(dep.id)) !== dep.fingerprint) {
|
|
2056
|
+
valid = false;
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (valid) {
|
|
2062
|
+
this.reportContentDependencies(
|
|
2063
|
+
sourceSlug,
|
|
2064
|
+
cachedEntry.dependencies
|
|
2065
|
+
);
|
|
2066
|
+
this.options.observability?.tracer?.event(
|
|
2067
|
+
"persistentContentCache.hit",
|
|
2068
|
+
{ key: cacheKey, slug: sourceSlug }
|
|
2069
|
+
);
|
|
2070
|
+
this.options.onPersistentContentCacheResult?.("hit");
|
|
2071
|
+
return {
|
|
2072
|
+
frontmatter: cachedEntry.value.frontmatter,
|
|
2073
|
+
html: cachedEntry.value.html
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
this.options.observability?.tracer?.event(
|
|
2078
|
+
"persistentContentCache.miss",
|
|
2079
|
+
{ key: cacheKey, slug: sourceSlug }
|
|
2080
|
+
);
|
|
2081
|
+
this.options.onPersistentContentCacheResult?.("miss");
|
|
2082
|
+
} else {
|
|
2083
|
+
this.options.observability?.tracer?.event(
|
|
2084
|
+
"persistentContentCache.bypass",
|
|
2085
|
+
{
|
|
2086
|
+
slug: sourceSlug,
|
|
2087
|
+
...cacheable.reason ? { reason: cacheable.reason } : {}
|
|
2088
|
+
}
|
|
2089
|
+
);
|
|
2090
|
+
this.options.onPersistentContentCacheResult?.("bypass");
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
1558
2093
|
const processor = unified();
|
|
1559
|
-
const plugins = resolvePlugins(
|
|
2094
|
+
const plugins = this.resolvePlugins();
|
|
1560
2095
|
this.use(processor, remarkParse);
|
|
1561
2096
|
this.use(processor, remarkDirective);
|
|
1562
2097
|
this.use(processor, remarkFrontmatter, ["yaml", "toml"]);
|
|
1563
2098
|
this.use(processor, function loadFrontmatter() {
|
|
1564
2099
|
return (_tree, file2) => {
|
|
1565
|
-
|
|
2100
|
+
matter2(file2);
|
|
1566
2101
|
};
|
|
1567
2102
|
});
|
|
1568
2103
|
this.use(processor, remarkMath);
|
|
@@ -1571,9 +2106,14 @@ var Pipeline = class {
|
|
|
1571
2106
|
sourceSlug: context.sourceSlug,
|
|
1572
2107
|
contentIndex: this.contentIndex,
|
|
1573
2108
|
resolvePermalink: (slug) => this.getPermalink(slug),
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
2109
|
+
isRoutable: this.options.isRoutable,
|
|
2110
|
+
renderNoteEmbed: this.createNoteEmbedRenderer(
|
|
2111
|
+
embedDepth,
|
|
2112
|
+
embedTrail,
|
|
2113
|
+
dependencyTracker
|
|
2114
|
+
),
|
|
2115
|
+
renderContent: this.createContentRenderer(effectiveContentSource),
|
|
2116
|
+
contentSource: effectiveContentSource
|
|
1577
2117
|
};
|
|
1578
2118
|
for (const plugin of plugins) {
|
|
1579
2119
|
for (const remarkPlugin of plugin.remarkPlugins ?? []) {
|
|
@@ -1588,7 +2128,10 @@ var Pipeline = class {
|
|
|
1588
2128
|
markdownPipelineContext
|
|
1589
2129
|
);
|
|
1590
2130
|
}
|
|
1591
|
-
this.use(processor, normalizeMarkdownLinks,
|
|
2131
|
+
this.use(processor, normalizeMarkdownLinks, {
|
|
2132
|
+
...markdownPipelineContext,
|
|
2133
|
+
recordLinkResolution: dependencyTracker ? (id, value) => dependencyTracker.recordLinkResolution(id, value) : void 0
|
|
2134
|
+
});
|
|
1592
2135
|
this.use(processor, remarkRehype, { allowDangerousHtml: true });
|
|
1593
2136
|
this.use(processor, rehypeRaw);
|
|
1594
2137
|
for (const plugin of plugins) {
|
|
@@ -1606,10 +2149,43 @@ var Pipeline = class {
|
|
|
1606
2149
|
this.use(processor, rehypeFormat);
|
|
1607
2150
|
this.use(processor, rehypeStringify, { allowDangerousHtml: true });
|
|
1608
2151
|
const file = await processor.process(markDownContent.trim());
|
|
1609
|
-
|
|
2152
|
+
const result = {
|
|
1610
2153
|
frontmatter: file.data.matter || {},
|
|
1611
2154
|
html: String(file.value)
|
|
1612
2155
|
};
|
|
2156
|
+
if (sourceSlug && dependencyTracker) {
|
|
2157
|
+
this.reportContentDependencies(
|
|
2158
|
+
sourceSlug,
|
|
2159
|
+
dependencyTracker.dependencies()
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
if (embedDepth === 0 && this.persistentCache && this.pipelineFingerprint && sourceSlug && dependencyTracker && this.options.config) {
|
|
2163
|
+
const cacheable = isPersistentlyCacheable(
|
|
2164
|
+
sourceSlug,
|
|
2165
|
+
this.options.config
|
|
2166
|
+
);
|
|
2167
|
+
if (cacheable.cacheable) {
|
|
2168
|
+
const frontmatter = extractFrontmatter(markDownContent);
|
|
2169
|
+
const cacheKey = computeContentCacheKey({
|
|
2170
|
+
slug: sourceSlug,
|
|
2171
|
+
source: markDownContent,
|
|
2172
|
+
frontmatter,
|
|
2173
|
+
pipelineFingerprint: this.pipelineFingerprint
|
|
2174
|
+
});
|
|
2175
|
+
const dependencies = dependencyTracker.dependencies();
|
|
2176
|
+
const entry = {
|
|
2177
|
+
schemaVersion: CONTENT_CACHE_SCHEMA_VERSION,
|
|
2178
|
+
key: cacheKey,
|
|
2179
|
+
dependencies,
|
|
2180
|
+
value: result
|
|
2181
|
+
};
|
|
2182
|
+
await this.persistentCache.set(cacheKey, entry);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return result;
|
|
2186
|
+
}
|
|
2187
|
+
reportContentDependencies(slug, dependencies) {
|
|
2188
|
+
this.options.onContentDependencies?.(slug, dependencies);
|
|
1613
2189
|
}
|
|
1614
2190
|
getPermalink(slug) {
|
|
1615
2191
|
const permalink = this.permalinks.get(slug);
|
|
@@ -1618,6 +2194,14 @@ var Pipeline = class {
|
|
|
1618
2194
|
}
|
|
1619
2195
|
return permalink;
|
|
1620
2196
|
}
|
|
2197
|
+
resolveLinkDependency(id) {
|
|
2198
|
+
return resolveLinkDependencyValue(
|
|
2199
|
+
id,
|
|
2200
|
+
this.contentIndex,
|
|
2201
|
+
(slug) => this.permalinks.get(slug),
|
|
2202
|
+
this.options.isRoutable
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
1621
2205
|
use(processor, plugin, options) {
|
|
1622
2206
|
const register = processor.use.bind(processor);
|
|
1623
2207
|
if (options === void 0) {
|
|
@@ -1626,26 +2210,37 @@ var Pipeline = class {
|
|
|
1626
2210
|
register(plugin, options);
|
|
1627
2211
|
}
|
|
1628
2212
|
}
|
|
1629
|
-
createNoteEmbedRenderer(embedDepth, embedTrail) {
|
|
2213
|
+
createNoteEmbedRenderer(embedDepth, embedTrail, dependencyTracker) {
|
|
1630
2214
|
if (!this.getMarkdownBySlug || embedDepth >= 3) return void 0;
|
|
1631
2215
|
return async (slug, fragment) => {
|
|
2216
|
+
if (this.options.isRoutable && !this.options.isRoutable(slug)) {
|
|
2217
|
+
return null;
|
|
2218
|
+
}
|
|
1632
2219
|
if (embedTrail.has(slug)) return null;
|
|
1633
2220
|
const sourceMarkdown = await this.getMarkdownBySlug?.(slug);
|
|
1634
2221
|
const markdown = sourceMarkdown ? selectEmbedMarkdownFragment(sourceMarkdown, fragment) : null;
|
|
1635
2222
|
if (!markdown) return null;
|
|
1636
2223
|
const nextEmbedTrail = new Set(embedTrail);
|
|
1637
2224
|
nextEmbedTrail.add(slug);
|
|
2225
|
+
if (dependencyTracker) {
|
|
2226
|
+
await dependencyTracker.readContent(slug, async () => markdown);
|
|
2227
|
+
}
|
|
1638
2228
|
const content = await this.executeWithEmbedState(
|
|
1639
2229
|
markdown,
|
|
1640
2230
|
{ sourceSlug: slug },
|
|
1641
2231
|
embedDepth + 1,
|
|
1642
|
-
nextEmbedTrail
|
|
2232
|
+
nextEmbedTrail,
|
|
2233
|
+
dependencyTracker
|
|
1643
2234
|
);
|
|
1644
2235
|
return content.html;
|
|
1645
2236
|
};
|
|
1646
2237
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
2238
|
+
resolvePlugins() {
|
|
2239
|
+
this.resolvedPlugins ??= resolvePlugins(this.options.plugins);
|
|
2240
|
+
return this.resolvedPlugins;
|
|
2241
|
+
}
|
|
2242
|
+
createContentRenderer(contentSource) {
|
|
2243
|
+
const renderers = this.resolvePlugins().flatMap(
|
|
1649
2244
|
(plugin) => (plugin.renderers ?? []).map((renderer) => ({ plugin, renderer }))
|
|
1650
2245
|
);
|
|
1651
2246
|
if (renderers.length === 0) return void 0;
|
|
@@ -1667,7 +2262,7 @@ var Pipeline = class {
|
|
|
1667
2262
|
output: createUnavailableGeneratedOutputSink(),
|
|
1668
2263
|
logger: observability.logger.child({ plugin: plugin.name }),
|
|
1669
2264
|
tracer: observability.tracer,
|
|
1670
|
-
contentSource
|
|
2265
|
+
contentSource,
|
|
1671
2266
|
...input
|
|
1672
2267
|
})
|
|
1673
2268
|
);
|
|
@@ -1710,6 +2305,21 @@ function normalizeMarkdownLinks(context) {
|
|
|
1710
2305
|
});
|
|
1711
2306
|
};
|
|
1712
2307
|
}
|
|
2308
|
+
var LINK_INDEX_PREFIX = "index:";
|
|
2309
|
+
var LINK_LOCATION_PREFIX = "location:";
|
|
2310
|
+
function resolveLinkDependencyValue(id, contentIndex, lookupPermalink, isRoutable) {
|
|
2311
|
+
if (id.startsWith(LINK_INDEX_PREFIX)) {
|
|
2312
|
+
return contentIndex.get(id.slice(LINK_INDEX_PREFIX.length)) ?? "";
|
|
2313
|
+
}
|
|
2314
|
+
if (id.startsWith(LINK_LOCATION_PREFIX)) {
|
|
2315
|
+
const slug = id.slice(LINK_LOCATION_PREFIX.length);
|
|
2316
|
+
const permalink = lookupPermalink(slug);
|
|
2317
|
+
if (permalink === void 0) return "";
|
|
2318
|
+
if (isRoutable && !isRoutable(slug)) return "";
|
|
2319
|
+
return permalink;
|
|
2320
|
+
}
|
|
2321
|
+
return "";
|
|
2322
|
+
}
|
|
1713
2323
|
function visitMarkdownLinkNodes(node, visitor) {
|
|
1714
2324
|
const linkNode = node;
|
|
1715
2325
|
if ((node.type === "link" || node.type === "definition") && typeof linkNode.url === "string") {
|
|
@@ -1722,7 +2332,7 @@ function visitMarkdownLinkNodes(node, visitor) {
|
|
|
1722
2332
|
function normalizeMarkdownLinkUrl(url, context) {
|
|
1723
2333
|
if (!context.sourceSlug) return null;
|
|
1724
2334
|
if (!isMarkdownContentLink(url)) return null;
|
|
1725
|
-
const [pathAndQuery,
|
|
2335
|
+
const [pathAndQuery, hash4 = ""] = splitOnce(url, "#");
|
|
1726
2336
|
const [rawPath, query = ""] = splitOnce(pathAndQuery, "?");
|
|
1727
2337
|
const decodedPath = decodeUriPath(rawPath);
|
|
1728
2338
|
if (!decodedPath?.toLowerCase().endsWith(".md")) return null;
|
|
@@ -1733,15 +2343,35 @@ function normalizeMarkdownLinkUrl(url, context) {
|
|
|
1733
2343
|
if (!resolvedSlug) return null;
|
|
1734
2344
|
const indexedSlug = getExactContentSlug(context, resolvedSlug);
|
|
1735
2345
|
if (!indexedSlug) return null;
|
|
1736
|
-
|
|
2346
|
+
recordLinkResolution(context, LINK_LOCATION_PREFIX, indexedSlug);
|
|
2347
|
+
if (context.isRoutable && !context.isRoutable(indexedSlug)) return null;
|
|
2348
|
+
const suffix = `${query ? `?${query}` : ""}${hash4 ? `#${hash4}` : ""}`;
|
|
1737
2349
|
return `${context.resolvePermalink(indexedSlug)}${suffix}`;
|
|
1738
2350
|
}
|
|
2351
|
+
function recordLinkResolution(context, prefix, key) {
|
|
2352
|
+
if (!context.recordLinkResolution) return;
|
|
2353
|
+
const value = prefix === LINK_INDEX_PREFIX ? context.contentIndex.get(key) ?? "" : resolveLinkDependencyValue(
|
|
2354
|
+
`${prefix}${key}`,
|
|
2355
|
+
context.contentIndex,
|
|
2356
|
+
(slug) => {
|
|
2357
|
+
try {
|
|
2358
|
+
return context.resolvePermalink(slug);
|
|
2359
|
+
} catch {
|
|
2360
|
+
return void 0;
|
|
2361
|
+
}
|
|
2362
|
+
},
|
|
2363
|
+
context.isRoutable
|
|
2364
|
+
);
|
|
2365
|
+
context.recordLinkResolution(`${prefix}${key}`, value);
|
|
2366
|
+
}
|
|
1739
2367
|
function getExactContentSlug(context, slug) {
|
|
1740
2368
|
try {
|
|
1741
2369
|
context.resolvePermalink(slug);
|
|
1742
2370
|
return slug;
|
|
1743
2371
|
} catch {
|
|
1744
|
-
|
|
2372
|
+
const indexKey = slug.toLowerCase();
|
|
2373
|
+
recordLinkResolution(context, LINK_INDEX_PREFIX, indexKey);
|
|
2374
|
+
return context.contentIndex.get(indexKey) ?? null;
|
|
1745
2375
|
}
|
|
1746
2376
|
}
|
|
1747
2377
|
function isMarkdownContentLink(url) {
|
|
@@ -1765,9 +2395,9 @@ function resolveRelativeMarkdownSlug(sourceSlug, targetPath) {
|
|
|
1765
2395
|
}
|
|
1766
2396
|
return normalizedParts.join("/").replace(/\.md$/i, "");
|
|
1767
2397
|
}
|
|
1768
|
-
function decodeUriPath(
|
|
2398
|
+
function decodeUriPath(path5) {
|
|
1769
2399
|
try {
|
|
1770
|
-
return decodeURI(
|
|
2400
|
+
return decodeURI(path5);
|
|
1771
2401
|
} catch {
|
|
1772
2402
|
return null;
|
|
1773
2403
|
}
|
|
@@ -1855,17 +2485,27 @@ var GeneratedOutputRegistry = class {
|
|
|
1855
2485
|
outputs = [];
|
|
1856
2486
|
owners = /* @__PURE__ */ new Map();
|
|
1857
2487
|
sinkFor(pluginName) {
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2488
|
+
const register = (path5, output) => {
|
|
2489
|
+
const owner = this.owners.get(path5);
|
|
2490
|
+
if (owner) {
|
|
2491
|
+
throw new Error(
|
|
2492
|
+
`Duplicate generated output path "${path5}" declared by "${owner}" and "${pluginName}".`
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
this.owners.set(path5, pluginName);
|
|
2496
|
+
this.outputs.push({
|
|
2497
|
+
path: path5,
|
|
2498
|
+
content: output.content,
|
|
2499
|
+
dependencies: output.dependencies,
|
|
2500
|
+
owner: pluginName
|
|
2501
|
+
});
|
|
2502
|
+
};
|
|
2503
|
+
return {
|
|
2504
|
+
emit: (output) => {
|
|
2505
|
+
register(normalizeGeneratedOutputPath(output.path), output);
|
|
2506
|
+
},
|
|
2507
|
+
emitAsset: (output) => {
|
|
2508
|
+
register(normalizeContentAssetOutputPath(output.path), output);
|
|
1869
2509
|
}
|
|
1870
2510
|
};
|
|
1871
2511
|
}
|
|
@@ -1948,6 +2588,7 @@ var PluginRuntime = class {
|
|
|
1948
2588
|
pluginCaches = /* @__PURE__ */ new Map();
|
|
1949
2589
|
generatedOutputs = new GeneratedOutputRegistry();
|
|
1950
2590
|
isBuildTime = false;
|
|
2591
|
+
resolvedPlugins = null;
|
|
1951
2592
|
getDiagnostics() {
|
|
1952
2593
|
return this.diagnostics;
|
|
1953
2594
|
}
|
|
@@ -2068,20 +2709,26 @@ var PluginRuntime = class {
|
|
|
2068
2709
|
}
|
|
2069
2710
|
async resolvePage(pathname, manifest, contentIndex) {
|
|
2070
2711
|
const matches = [];
|
|
2071
|
-
const
|
|
2072
|
-
const context = {
|
|
2073
|
-
...this.createContext(contentIndex),
|
|
2074
|
-
manifest,
|
|
2075
|
-
pathname: normalizedPathname
|
|
2076
|
-
};
|
|
2712
|
+
const requestedPathname = normalizePagePath(pathname);
|
|
2077
2713
|
for (const plugin of this.plugins()) {
|
|
2078
2714
|
for (const pageType of plugin.pageTypes ?? []) {
|
|
2715
|
+
const pagePathname = normalizePageTypePath(pathname, pageType);
|
|
2716
|
+
const context = {
|
|
2717
|
+
...this.createContext(contentIndex),
|
|
2718
|
+
manifest,
|
|
2719
|
+
pathname: pagePathname
|
|
2720
|
+
};
|
|
2079
2721
|
const page = await pageType.resolve(
|
|
2080
2722
|
this.createPluginContext(plugin, context)
|
|
2081
2723
|
);
|
|
2082
2724
|
if (page) {
|
|
2083
2725
|
matches.push({
|
|
2084
|
-
page: {
|
|
2726
|
+
page: {
|
|
2727
|
+
...page,
|
|
2728
|
+
pathname: pageType.directoryIndex ? normalizeDirectoryPagePath(page.pathname) : page.pathname,
|
|
2729
|
+
type: pageType.id,
|
|
2730
|
+
pluginName: plugin.name
|
|
2731
|
+
},
|
|
2085
2732
|
priority: pageType.priority ?? 0
|
|
2086
2733
|
});
|
|
2087
2734
|
}
|
|
@@ -2094,7 +2741,7 @@ var PluginRuntime = class {
|
|
|
2094
2741
|
);
|
|
2095
2742
|
if (highest.length !== 1) {
|
|
2096
2743
|
throw new Error(
|
|
2097
|
-
`Multiple plugin page types match ${
|
|
2744
|
+
`Multiple plugin page types match ${requestedPathname} at priority ${highestPriority}: ${highest.map((match) => match.page.type).join(", ")}`
|
|
2098
2745
|
);
|
|
2099
2746
|
}
|
|
2100
2747
|
return highest[0].page;
|
|
@@ -2106,12 +2753,60 @@ var PluginRuntime = class {
|
|
|
2106
2753
|
for (const pageType of plugin.pageTypes ?? []) {
|
|
2107
2754
|
const declared = pageType.paths;
|
|
2108
2755
|
if (!declared) continue;
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2756
|
+
const resolved = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
|
|
2757
|
+
for (const rawPath of resolved) {
|
|
2758
|
+
paths.push(normalizePageTypePath(rawPath, pageType));
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return [...new Set(paths)];
|
|
2763
|
+
}
|
|
2764
|
+
async getPageRoutes(manifest, contentIndex) {
|
|
2765
|
+
const routes = [];
|
|
2766
|
+
const context = { ...this.createContext(contentIndex), manifest };
|
|
2767
|
+
for (const plugin of this.plugins()) {
|
|
2768
|
+
for (const pageType of plugin.pageTypes ?? []) {
|
|
2769
|
+
const declared = pageType.paths;
|
|
2770
|
+
if (!declared) continue;
|
|
2771
|
+
const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
|
|
2772
|
+
for (const pathname of paths) {
|
|
2773
|
+
routes.push({
|
|
2774
|
+
pathname: normalizePageTypePath(pathname, pageType),
|
|
2775
|
+
pluginName: plugin.name,
|
|
2776
|
+
pageType: pageType.id
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2112
2779
|
}
|
|
2113
2780
|
}
|
|
2114
|
-
return
|
|
2781
|
+
return routes;
|
|
2782
|
+
}
|
|
2783
|
+
async getPageOutputs(manifest, contentIndex) {
|
|
2784
|
+
const outputs = [];
|
|
2785
|
+
const context = { ...this.createContext(contentIndex), manifest };
|
|
2786
|
+
for (const plugin of this.plugins()) {
|
|
2787
|
+
for (const pageType of plugin.pageTypes ?? []) {
|
|
2788
|
+
const declared = pageType.paths;
|
|
2789
|
+
if (!declared) continue;
|
|
2790
|
+
const paths = typeof declared === "function" ? await declared(this.createPluginContext(plugin, context)) : declared;
|
|
2791
|
+
for (const rawPath of paths) {
|
|
2792
|
+
const pathname = normalizePageTypePath(rawPath, pageType);
|
|
2793
|
+
const dependencyContext = { ...context, pathname };
|
|
2794
|
+
const dependencies = pageType.outputDependencies ? typeof pageType.outputDependencies === "function" ? await pageType.outputDependencies(
|
|
2795
|
+
this.createPluginContext(plugin, dependencyContext)
|
|
2796
|
+
) : pageType.outputDependencies : [{ type: "unknown" }];
|
|
2797
|
+
outputs.push({
|
|
2798
|
+
kind: "plugin-page",
|
|
2799
|
+
path: pathname,
|
|
2800
|
+
producer: `plugin:${plugin.name}:page:${pageType.id}`,
|
|
2801
|
+
dependencies
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
return uniqueOutputs(outputs);
|
|
2807
|
+
}
|
|
2808
|
+
getContentOutputDependencies() {
|
|
2809
|
+
return this.plugins().flatMap((plugin) => plugin.outputDependencies ?? []);
|
|
2115
2810
|
}
|
|
2116
2811
|
async collectDiagnostics(contentIndex) {
|
|
2117
2812
|
const tracer = this.observability().tracer;
|
|
@@ -2169,6 +2864,7 @@ var PluginRuntime = class {
|
|
|
2169
2864
|
return {
|
|
2170
2865
|
config: this.pipelineOptions.config,
|
|
2171
2866
|
contentIndex,
|
|
2867
|
+
contentSource: this.pipelineOptions.contentSource,
|
|
2172
2868
|
diagnostics: this.diagnostics
|
|
2173
2869
|
};
|
|
2174
2870
|
}
|
|
@@ -2218,27 +2914,33 @@ var PluginRuntime = class {
|
|
|
2218
2914
|
return this.pipelineOptions.observability ?? noopObservability;
|
|
2219
2915
|
}
|
|
2220
2916
|
plugins() {
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
if (declaredPageTypes !== void 0 && !Array.isArray(declaredPageTypes)) {
|
|
2226
|
-
throw new TypeError(`Plugin ${plugin.name} pageTypes must be an array`);
|
|
2227
|
-
}
|
|
2228
|
-
for (const pageType of declaredPageTypes ?? []) {
|
|
2229
|
-
validatePageType(plugin.name, pageType);
|
|
2230
|
-
const previous = pageTypes.get(pageType.id);
|
|
2231
|
-
if (previous) {
|
|
2232
|
-
throw new Error(
|
|
2233
|
-
`Plugin page type "${pageType.id}" is provided by both ${previous} and ${plugin.name}`
|
|
2234
|
-
);
|
|
2235
|
-
}
|
|
2236
|
-
pageTypes.set(pageType.id, plugin.name);
|
|
2237
|
-
}
|
|
2917
|
+
if (this.resolvedPlugins === null) {
|
|
2918
|
+
const resolved = resolvePlugins(this.pipelineOptions.plugins);
|
|
2919
|
+
collectPageTypeOwners(resolved);
|
|
2920
|
+
this.resolvedPlugins = resolved;
|
|
2238
2921
|
}
|
|
2239
|
-
return
|
|
2922
|
+
return this.resolvedPlugins;
|
|
2240
2923
|
}
|
|
2241
2924
|
};
|
|
2925
|
+
function collectPageTypeOwners(plugins) {
|
|
2926
|
+
const pageTypes = /* @__PURE__ */ new Map();
|
|
2927
|
+
for (const plugin of plugins) {
|
|
2928
|
+
const declaredPageTypes = plugin.pageTypes;
|
|
2929
|
+
if (declaredPageTypes !== void 0 && !Array.isArray(declaredPageTypes)) {
|
|
2930
|
+
throw new TypeError(`Plugin ${plugin.name} pageTypes must be an array`);
|
|
2931
|
+
}
|
|
2932
|
+
for (const pageType of declaredPageTypes ?? []) {
|
|
2933
|
+
validatePageType(plugin.name, pageType);
|
|
2934
|
+
const previous = pageTypes.get(pageType.id);
|
|
2935
|
+
if (previous) {
|
|
2936
|
+
throw new Error(
|
|
2937
|
+
`Plugin page type "${pageType.id}" is provided by both ${previous} and ${plugin.name}`
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
pageTypes.set(pageType.id, plugin.name);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2242
2944
|
function validatePageType(pluginName, pageType) {
|
|
2243
2945
|
if (typeof pageType !== "object" || pageType === null) {
|
|
2244
2946
|
throw new TypeError(`Plugin ${pluginName} pageTypes must contain objects`);
|
|
@@ -2264,24 +2966,48 @@ function validatePageType(pluginName, pageType) {
|
|
|
2264
2966
|
`Plugin ${pluginName} page type "${candidate.id}" priority must be a finite number`
|
|
2265
2967
|
);
|
|
2266
2968
|
}
|
|
2969
|
+
if (candidate.outputDependencies !== void 0 && !Array.isArray(candidate.outputDependencies) && typeof candidate.outputDependencies !== "function") {
|
|
2970
|
+
throw new TypeError(
|
|
2971
|
+
`Plugin ${pluginName} page type "${candidate.id}" outputDependencies must be an array or function`
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
if (candidate.directoryIndex !== void 0 && typeof candidate.directoryIndex !== "boolean") {
|
|
2975
|
+
throw new TypeError(
|
|
2976
|
+
`Plugin ${pluginName} page type "${candidate.id}" directoryIndex must be a boolean`
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
function normalizePageTypePath(pathname, pageType) {
|
|
2981
|
+
return pageType.directoryIndex ? normalizeDirectoryPagePath(pathname) : normalizePagePath(pathname);
|
|
2982
|
+
}
|
|
2983
|
+
function normalizeDirectoryPagePath(pathname) {
|
|
2984
|
+
const path5 = normalizePagePath(pathname);
|
|
2985
|
+
return path5 === "/" ? path5 : `${path5}/`;
|
|
2267
2986
|
}
|
|
2268
2987
|
function normalizePagePath(pathname) {
|
|
2269
|
-
const
|
|
2270
|
-
return
|
|
2988
|
+
const path5 = `/${pathname.split("/").filter(Boolean).join("/")}`;
|
|
2989
|
+
return path5 === "/" ? path5 : path5.replace(/\/$/, "");
|
|
2271
2990
|
}
|
|
2272
2991
|
function countSeverity(diagnostics, severity) {
|
|
2273
2992
|
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
2274
2993
|
}
|
|
2994
|
+
function uniqueOutputs(outputs) {
|
|
2995
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
2996
|
+
for (const output of outputs) byPath.set(output.path, output);
|
|
2997
|
+
return [...byPath.values()].sort(
|
|
2998
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
2999
|
+
);
|
|
3000
|
+
}
|
|
2275
3001
|
|
|
2276
3002
|
// src/content/affected_content.ts
|
|
2277
|
-
function determineAffectedContent(changeSet, previousState, currentPaths) {
|
|
3003
|
+
function determineAffectedContent(changeSet, previousState, currentPaths, currentContentIndex) {
|
|
2278
3004
|
const changedPaths = [
|
|
2279
3005
|
...changeSet.added,
|
|
2280
3006
|
...changeSet.changed,
|
|
2281
3007
|
...changeSet.removed
|
|
2282
3008
|
];
|
|
2283
3009
|
const direct = new Set(
|
|
2284
|
-
changedPaths.filter(isMarkdownPath2).map(
|
|
3010
|
+
changedPaths.filter(isMarkdownPath2).map(toDependencyId)
|
|
2285
3011
|
);
|
|
2286
3012
|
const dependent = /* @__PURE__ */ new Set();
|
|
2287
3013
|
if (!previousState) {
|
|
@@ -2289,40 +3015,81 @@ function determineAffectedContent(changeSet, previousState, currentPaths) {
|
|
|
2289
3015
|
return { direct, dependent };
|
|
2290
3016
|
}
|
|
2291
3017
|
if (changeSet.added.length > 0 || changeSet.removed.length > 0) {
|
|
2292
|
-
|
|
3018
|
+
const changedIndexKeys = findChangedIndexKeys(
|
|
3019
|
+
previousState.contentIndex,
|
|
3020
|
+
currentContentIndex
|
|
3021
|
+
);
|
|
3022
|
+
for (const [path5, entry] of Object.entries(previousState.entries)) {
|
|
3023
|
+
if (!isMarkdownPath2(path5)) continue;
|
|
3024
|
+
const slug = toDependencyId(path5);
|
|
3025
|
+
if (direct.has(slug) || dependent.has(slug)) continue;
|
|
3026
|
+
if (!entry.linkTargets?.some((target) => changedIndexKeys.has(target))) {
|
|
3027
|
+
continue;
|
|
3028
|
+
}
|
|
3029
|
+
dependent.add(slug);
|
|
3030
|
+
}
|
|
2293
3031
|
}
|
|
2294
|
-
const
|
|
2295
|
-
const
|
|
3032
|
+
const dependentsByDependency = buildDependentsByDependency(previousState);
|
|
3033
|
+
const pending = changedPaths.map(toDependency);
|
|
3034
|
+
const seen = new Set(pending.map(dependencyKey2));
|
|
2296
3035
|
while (pending.length > 0) {
|
|
2297
3036
|
const changed = pending.pop();
|
|
2298
3037
|
if (changed === void 0) continue;
|
|
2299
|
-
for (const
|
|
2300
|
-
if (!isMarkdownPath2(path4)) continue;
|
|
2301
|
-
const slug = toDependencyKey(path4);
|
|
3038
|
+
for (const slug of dependentsByDependency.get(dependencyKey2(changed)) ?? []) {
|
|
2302
3039
|
if (direct.has(slug) || dependent.has(slug)) continue;
|
|
2303
|
-
if (!entry.dependencies.includes(changed)) continue;
|
|
2304
3040
|
dependent.add(slug);
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
3041
|
+
const dependency = { kind: "content", id: slug };
|
|
3042
|
+
if (!seen.has(dependencyKey2(dependency))) {
|
|
3043
|
+
seen.add(dependencyKey2(dependency));
|
|
3044
|
+
pending.push(dependency);
|
|
2308
3045
|
}
|
|
2309
3046
|
}
|
|
2310
3047
|
}
|
|
2311
3048
|
return { direct, dependent };
|
|
2312
3049
|
}
|
|
3050
|
+
function findChangedIndexKeys(previousIndex, currentIndex) {
|
|
3051
|
+
const changed = /* @__PURE__ */ new Set();
|
|
3052
|
+
for (const [key, previousValue] of Object.entries(previousIndex)) {
|
|
3053
|
+
if (currentIndex.get(key) !== previousValue) changed.add(key);
|
|
3054
|
+
}
|
|
3055
|
+
for (const key of currentIndex.keys()) {
|
|
3056
|
+
if (!(key in previousIndex)) changed.add(key);
|
|
3057
|
+
}
|
|
3058
|
+
return changed;
|
|
3059
|
+
}
|
|
2313
3060
|
function addAllNotes(target, currentPaths, exclude) {
|
|
2314
|
-
for (const
|
|
2315
|
-
if (!isMarkdownPath2(
|
|
2316
|
-
const slug =
|
|
3061
|
+
for (const path5 of currentPaths) {
|
|
3062
|
+
if (!isMarkdownPath2(path5)) continue;
|
|
3063
|
+
const slug = toDependencyId(path5);
|
|
2317
3064
|
if (exclude?.has(slug)) continue;
|
|
2318
3065
|
target.add(slug);
|
|
2319
3066
|
}
|
|
2320
3067
|
}
|
|
2321
|
-
function isMarkdownPath2(
|
|
2322
|
-
return
|
|
3068
|
+
function isMarkdownPath2(path5) {
|
|
3069
|
+
return path5.endsWith(".md");
|
|
3070
|
+
}
|
|
3071
|
+
function toDependencyId(path5) {
|
|
3072
|
+
return path5.replace(/\.md$/, "");
|
|
3073
|
+
}
|
|
3074
|
+
function toDependency(path5) {
|
|
3075
|
+
return isMarkdownPath2(path5) ? { kind: "content", id: toDependencyId(path5) } : { kind: "file", id: path5.replace(/\\/g, "/") };
|
|
3076
|
+
}
|
|
3077
|
+
function buildDependentsByDependency(state2) {
|
|
3078
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
3079
|
+
for (const [path5, entry] of Object.entries(state2.entries)) {
|
|
3080
|
+
if (!isMarkdownPath2(path5)) continue;
|
|
3081
|
+
const slug = toDependencyId(path5);
|
|
3082
|
+
for (const dependency of entry.dependencies) {
|
|
3083
|
+
const key = dependencyKey2(dependency);
|
|
3084
|
+
const values = dependents.get(key) ?? /* @__PURE__ */ new Set();
|
|
3085
|
+
values.add(slug);
|
|
3086
|
+
dependents.set(key, values);
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
return dependents;
|
|
2323
3090
|
}
|
|
2324
|
-
function
|
|
2325
|
-
return
|
|
3091
|
+
function dependencyKey2(dependency) {
|
|
3092
|
+
return `${dependency.kind}:${dependency.id}`;
|
|
2326
3093
|
}
|
|
2327
3094
|
|
|
2328
3095
|
// src/content/content_change_set.ts
|
|
@@ -2333,18 +3100,18 @@ function diffContentEntries(previous, current) {
|
|
|
2333
3100
|
const added = [];
|
|
2334
3101
|
const changed = [];
|
|
2335
3102
|
const unchanged = [];
|
|
2336
|
-
for (const [
|
|
2337
|
-
const previousEntry = previous.entries[
|
|
3103
|
+
for (const [path5, fingerprint] of currentFingerprints) {
|
|
3104
|
+
const previousEntry = previous.entries[path5];
|
|
2338
3105
|
if (!previousEntry) {
|
|
2339
|
-
added.push(
|
|
3106
|
+
added.push(path5);
|
|
2340
3107
|
} else if (previousEntry.fingerprint !== fingerprint) {
|
|
2341
|
-
changed.push(
|
|
3108
|
+
changed.push(path5);
|
|
2342
3109
|
} else {
|
|
2343
|
-
unchanged.push(
|
|
3110
|
+
unchanged.push(path5);
|
|
2344
3111
|
}
|
|
2345
3112
|
}
|
|
2346
3113
|
const removed = Object.keys(previous.entries).filter(
|
|
2347
|
-
(
|
|
3114
|
+
(path5) => !currentFingerprints.has(path5)
|
|
2348
3115
|
);
|
|
2349
3116
|
return sortChangeSet({ added, changed, removed, unchanged });
|
|
2350
3117
|
}
|
|
@@ -2368,119 +3135,6 @@ function sortChangeSet(changeSet) {
|
|
|
2368
3135
|
};
|
|
2369
3136
|
}
|
|
2370
3137
|
|
|
2371
|
-
// src/content/content_build_coordinator.ts
|
|
2372
|
-
var ContentBuildCoordinator = class {
|
|
2373
|
-
constructor(dependencies) {
|
|
2374
|
-
this.dependencies = dependencies;
|
|
2375
|
-
}
|
|
2376
|
-
dependencies;
|
|
2377
|
-
preparation = null;
|
|
2378
|
-
getPreparation(incremental) {
|
|
2379
|
-
this.preparation ??= this.prepare(incremental);
|
|
2380
|
-
return this.preparation;
|
|
2381
|
-
}
|
|
2382
|
-
async commit(preparation, manifest) {
|
|
2383
|
-
const entriesBySlug = new Map(
|
|
2384
|
-
manifest.entries.map((entry) => [entry.slug, entry])
|
|
2385
|
-
);
|
|
2386
|
-
const state = {
|
|
2387
|
-
version: CONTENT_BUILD_STATE_VERSION,
|
|
2388
|
-
entries: Object.fromEntries(
|
|
2389
|
-
preparation.currentEntries.map(({ entry, fingerprint }) => {
|
|
2390
|
-
const manifestEntry = entriesBySlug.get(toSlug(entry.path));
|
|
2391
|
-
return [
|
|
2392
|
-
entry.path,
|
|
2393
|
-
{
|
|
2394
|
-
fingerprint,
|
|
2395
|
-
dependencies: collectDependencies2(manifestEntry)
|
|
2396
|
-
}
|
|
2397
|
-
];
|
|
2398
|
-
})
|
|
2399
|
-
),
|
|
2400
|
-
contentIndex: Object.fromEntries(
|
|
2401
|
-
[...manifest.contentIndex.entries()].sort(
|
|
2402
|
-
([left], [right]) => left.localeCompare(right)
|
|
2403
|
-
)
|
|
2404
|
-
)
|
|
2405
|
-
};
|
|
2406
|
-
try {
|
|
2407
|
-
await saveContentBuildState(this.dependencies.buildStatePath, state);
|
|
2408
|
-
} catch {
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
async prepare(incremental) {
|
|
2412
|
-
const currentEntries = await fingerprintContentEntries(
|
|
2413
|
-
await this.dependencies.getEntries(),
|
|
2414
|
-
(entry) => this.dependencies.readEntry(entry)
|
|
2415
|
-
);
|
|
2416
|
-
const previousState = incremental === false ? void 0 : await loadContentBuildState(this.dependencies.buildStatePath);
|
|
2417
|
-
const changeSet = previousState ? diffContentEntries(previousState, currentEntries) : allContentChanged(currentEntries);
|
|
2418
|
-
const affected = determineAffectedContent(
|
|
2419
|
-
changeSet,
|
|
2420
|
-
previousState,
|
|
2421
|
-
currentEntries.map(({ entry }) => entry.path)
|
|
2422
|
-
);
|
|
2423
|
-
this.dependencies.observability.tracer.event("build.incremental", {
|
|
2424
|
-
incremental: incremental !== false,
|
|
2425
|
-
added: changeSet.added.length,
|
|
2426
|
-
changed: changeSet.changed.length,
|
|
2427
|
-
removed: changeSet.removed.length,
|
|
2428
|
-
unchanged: changeSet.unchanged.length,
|
|
2429
|
-
affected: affected.direct.size + affected.dependent.size
|
|
2430
|
-
});
|
|
2431
|
-
return { previousState, currentEntries, changeSet };
|
|
2432
|
-
}
|
|
2433
|
-
};
|
|
2434
|
-
function toSlug(path4) {
|
|
2435
|
-
return path4.replace(/\.md$/, "");
|
|
2436
|
-
}
|
|
2437
|
-
function collectDependencies2(manifestEntry) {
|
|
2438
|
-
if (!manifestEntry) return [];
|
|
2439
|
-
return [
|
|
2440
|
-
...new Set(
|
|
2441
|
-
manifestEntry.links.filter(
|
|
2442
|
-
(link) => link.slug !== null
|
|
2443
|
-
).map((link) => link.slug)
|
|
2444
|
-
)
|
|
2445
|
-
].sort();
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
// src/content/content_entry_reader.ts
|
|
2449
|
-
var ContentEntryReader = class {
|
|
2450
|
-
constructor(source, observability) {
|
|
2451
|
-
this.source = source;
|
|
2452
|
-
this.observability = observability;
|
|
2453
|
-
}
|
|
2454
|
-
source;
|
|
2455
|
-
observability;
|
|
2456
|
-
entries = null;
|
|
2457
|
-
texts = /* @__PURE__ */ new Map();
|
|
2458
|
-
async getEntries() {
|
|
2459
|
-
this.entries ??= await this.observability.tracer.span(
|
|
2460
|
-
"content.scan",
|
|
2461
|
-
{},
|
|
2462
|
-
() => this.source.scan()
|
|
2463
|
-
);
|
|
2464
|
-
return this.entries;
|
|
2465
|
-
}
|
|
2466
|
-
async readText(logicalPath) {
|
|
2467
|
-
const entry = (await this.getEntries()).find(
|
|
2468
|
-
(current) => current.path === logicalPath
|
|
2469
|
-
);
|
|
2470
|
-
if (!entry) throw new Error(`Content entry was not found: ${logicalPath}`);
|
|
2471
|
-
return await this.read(entry);
|
|
2472
|
-
}
|
|
2473
|
-
read(entry) {
|
|
2474
|
-
const cached = this.texts.get(entry.path);
|
|
2475
|
-
if (cached) return cached;
|
|
2476
|
-
const content = this.source.read(entry).then(
|
|
2477
|
-
(value) => typeof value === "string" ? value : new TextDecoder().decode(value)
|
|
2478
|
-
);
|
|
2479
|
-
this.texts.set(entry.path, content);
|
|
2480
|
-
return content;
|
|
2481
|
-
}
|
|
2482
|
-
};
|
|
2483
|
-
|
|
2484
3138
|
// src/utils/tags.ts
|
|
2485
3139
|
function normalizeTag(raw) {
|
|
2486
3140
|
const cleaned = raw.replace(/[/-]+$/, "");
|
|
@@ -2541,7 +3195,7 @@ function stripYamlQuotes(value) {
|
|
|
2541
3195
|
}
|
|
2542
3196
|
|
|
2543
3197
|
// src/content/content_index_builder.ts
|
|
2544
|
-
var ContentIndexBuilder = class {
|
|
3198
|
+
var ContentIndexBuilder = class _ContentIndexBuilder {
|
|
2545
3199
|
constructor(source) {
|
|
2546
3200
|
this.source = source;
|
|
2547
3201
|
}
|
|
@@ -2553,7 +3207,26 @@ var ContentIndexBuilder = class {
|
|
|
2553
3207
|
}
|
|
2554
3208
|
return index;
|
|
2555
3209
|
}
|
|
3210
|
+
static buildFromAliases(contentEntries, aliasesByPath) {
|
|
3211
|
+
const index = /* @__PURE__ */ new Map();
|
|
3212
|
+
for (const contentEntry of contentEntries) {
|
|
3213
|
+
_ContentIndexBuilder.indexContentEntryFromAliases(
|
|
3214
|
+
index,
|
|
3215
|
+
contentEntry,
|
|
3216
|
+
aliasesByPath.get(contentEntry.path) ?? []
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
return index;
|
|
3220
|
+
}
|
|
2556
3221
|
async indexContentEntry(index, contentEntry, read) {
|
|
3222
|
+
const aliases = contentEntry.path.toLowerCase().endsWith(".md") ? extractFrontmatterAliases(readText(await read(contentEntry))) : [];
|
|
3223
|
+
_ContentIndexBuilder.indexContentEntryFromAliases(
|
|
3224
|
+
index,
|
|
3225
|
+
contentEntry,
|
|
3226
|
+
aliases
|
|
3227
|
+
);
|
|
3228
|
+
}
|
|
3229
|
+
static indexContentEntryFromAliases(index, contentEntry, aliases) {
|
|
2557
3230
|
const contentPath = contentEntry.path;
|
|
2558
3231
|
const ext = contentPath.split(".").pop()?.toLowerCase() ?? "";
|
|
2559
3232
|
const value = ext === "md" ? contentPath.replace(/\.md$/, "") : contentPath;
|
|
@@ -2566,20 +3239,246 @@ var ContentIndexBuilder = class {
|
|
|
2566
3239
|
}
|
|
2567
3240
|
}
|
|
2568
3241
|
if (ext === "md") {
|
|
2569
|
-
const
|
|
2570
|
-
|
|
2571
|
-
|
|
3242
|
+
for (const alias of aliases) addIndexEntry(index, alias, value);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
function readText(content) {
|
|
3247
|
+
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
3248
|
+
}
|
|
3249
|
+
function addIndexEntry(index, key, value) {
|
|
3250
|
+
const normalizedKey = key.toLowerCase();
|
|
3251
|
+
if (!index.has(normalizedKey)) index.set(normalizedKey, value);
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
// src/content/content_build_coordinator.ts
|
|
3255
|
+
var ContentBuildCoordinator = class {
|
|
3256
|
+
constructor(dependencies) {
|
|
3257
|
+
this.dependencies = dependencies;
|
|
3258
|
+
}
|
|
3259
|
+
dependencies;
|
|
3260
|
+
preparation = null;
|
|
3261
|
+
getPreparation(incremental, pipelineFingerprint, fullContentRegenerationRequired = false) {
|
|
3262
|
+
this.preparation ??= this.prepare(
|
|
3263
|
+
incremental,
|
|
3264
|
+
pipelineFingerprint,
|
|
3265
|
+
fullContentRegenerationRequired
|
|
3266
|
+
);
|
|
3267
|
+
return this.preparation;
|
|
3268
|
+
}
|
|
3269
|
+
async commit(preparation, manifest, pipelineFingerprint, outputs = [], manifestEntries = manifest.entries, trackedDependencies = /* @__PURE__ */ new Map()) {
|
|
3270
|
+
const entriesBySlug = new Map(
|
|
3271
|
+
manifest.entries.map((entry) => [entry.slug, entry])
|
|
3272
|
+
);
|
|
3273
|
+
const state2 = {
|
|
3274
|
+
version: CONTENT_BUILD_STATE_VERSION,
|
|
3275
|
+
entries: Object.fromEntries(
|
|
3276
|
+
preparation.currentEntries.map(({ entry, fingerprint }) => {
|
|
3277
|
+
const manifestEntry = entriesBySlug.get(toSlug(entry.path));
|
|
3278
|
+
return [
|
|
3279
|
+
entry.path,
|
|
3280
|
+
{
|
|
3281
|
+
fingerprint,
|
|
3282
|
+
aliases: preparation.currentContentAliases.get(entry.path) ?? [],
|
|
3283
|
+
dependencies: collectDependencies2(
|
|
3284
|
+
manifestEntry,
|
|
3285
|
+
trackedDependencies.get(toSlug(entry.path)) ?? preparation.previousState?.entries[entry.path]?.dependencies ?? []
|
|
3286
|
+
),
|
|
3287
|
+
linkTargets: collectLinkTargets(manifestEntry)
|
|
3288
|
+
}
|
|
3289
|
+
];
|
|
3290
|
+
})
|
|
3291
|
+
),
|
|
3292
|
+
contentIndex: Object.fromEntries(
|
|
3293
|
+
[...manifest.contentIndex.entries()].sort(
|
|
3294
|
+
([left], [right]) => left.localeCompare(right)
|
|
3295
|
+
)
|
|
3296
|
+
),
|
|
3297
|
+
pipelineFingerprint,
|
|
3298
|
+
manifestEntries,
|
|
3299
|
+
outputs
|
|
3300
|
+
};
|
|
3301
|
+
try {
|
|
3302
|
+
await saveContentBuildState(this.dependencies.buildStatePath, state2);
|
|
3303
|
+
} catch (error) {
|
|
3304
|
+
this.dependencies.observability.logger.warn(
|
|
3305
|
+
"Incremental build state could not be persisted; the next build regenerates every note instead of reusing stale output.",
|
|
3306
|
+
{
|
|
3307
|
+
path: this.dependencies.buildStatePath,
|
|
3308
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
3309
|
+
}
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
async prepare(incremental, pipelineFingerprint, fullContentRegenerationRequired) {
|
|
3314
|
+
const entries = await this.dependencies.observability.tracer.span(
|
|
3315
|
+
"content.discovery",
|
|
3316
|
+
{},
|
|
3317
|
+
() => this.dependencies.getEntries()
|
|
3318
|
+
);
|
|
3319
|
+
const currentEntries = await this.dependencies.observability.tracer.span(
|
|
3320
|
+
"content.fingerprint",
|
|
3321
|
+
{},
|
|
3322
|
+
() => fingerprintContentEntries(
|
|
3323
|
+
entries,
|
|
3324
|
+
(entry) => this.dependencies.readEntry(entry)
|
|
3325
|
+
)
|
|
3326
|
+
);
|
|
3327
|
+
const previousState = incremental === false ? void 0 : await loadContentBuildState(this.dependencies.buildStatePath);
|
|
3328
|
+
const changeSet = previousState ? diffContentEntries(previousState, currentEntries) : allContentChanged(currentEntries);
|
|
3329
|
+
const currentContentAliases = await this.getContentAliases(
|
|
3330
|
+
currentEntries,
|
|
3331
|
+
previousState,
|
|
3332
|
+
changeSet
|
|
3333
|
+
);
|
|
3334
|
+
const currentContentIndex = await this.dependencies.observability.tracer.span(
|
|
3335
|
+
"content.index",
|
|
3336
|
+
{},
|
|
3337
|
+
() => ContentIndexBuilder.buildFromAliases(
|
|
3338
|
+
currentEntries.map(({ entry }) => entry),
|
|
3339
|
+
currentContentAliases
|
|
3340
|
+
)
|
|
3341
|
+
);
|
|
3342
|
+
const affected = !fullContentRegenerationRequired && previousState?.pipelineFingerprint === pipelineFingerprint ? determineAffectedContent(
|
|
3343
|
+
changeSet,
|
|
3344
|
+
previousState,
|
|
3345
|
+
currentEntries.map(({ entry }) => entry.path),
|
|
3346
|
+
currentContentIndex
|
|
3347
|
+
) : determineAffectedContent(
|
|
3348
|
+
allContentChanged(currentEntries),
|
|
3349
|
+
void 0,
|
|
3350
|
+
currentEntries.map(({ entry }) => entry.path),
|
|
3351
|
+
currentContentIndex
|
|
3352
|
+
);
|
|
3353
|
+
this.dependencies.observability.tracer.event("build.incremental", {
|
|
3354
|
+
incremental: incremental !== false,
|
|
3355
|
+
added: changeSet.added.length,
|
|
3356
|
+
changed: changeSet.changed.length,
|
|
3357
|
+
removed: changeSet.removed.length,
|
|
3358
|
+
unchanged: changeSet.unchanged.length,
|
|
3359
|
+
affected: affected.direct.size + affected.dependent.size
|
|
3360
|
+
});
|
|
3361
|
+
return {
|
|
3362
|
+
previousState,
|
|
3363
|
+
previousManifestEntriesBySlug: new Map(
|
|
3364
|
+
(previousState?.manifestEntries ?? []).map((entry) => [
|
|
3365
|
+
entry.slug,
|
|
3366
|
+
entry
|
|
3367
|
+
])
|
|
3368
|
+
),
|
|
3369
|
+
currentEntries,
|
|
3370
|
+
currentContentAliases,
|
|
3371
|
+
currentContentIndex,
|
|
3372
|
+
changeSet,
|
|
3373
|
+
affectedContent: affected
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
async getContentAliases(currentEntries, previousState, changeSet) {
|
|
3377
|
+
const changedPaths = /* @__PURE__ */ new Set([...changeSet.added, ...changeSet.changed]);
|
|
3378
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
3379
|
+
for (const { entry } of currentEntries) {
|
|
3380
|
+
if (!entry.path.toLowerCase().endsWith(".md")) continue;
|
|
3381
|
+
const previousAliases = previousState?.entries[entry.path]?.aliases;
|
|
3382
|
+
if (!changedPaths.has(entry.path) && previousAliases) {
|
|
3383
|
+
aliases.set(entry.path, previousAliases);
|
|
3384
|
+
continue;
|
|
2572
3385
|
}
|
|
3386
|
+
aliases.set(
|
|
3387
|
+
entry.path,
|
|
3388
|
+
extractFrontmatterAliases(
|
|
3389
|
+
readText2(await this.dependencies.readEntry(entry))
|
|
3390
|
+
)
|
|
3391
|
+
);
|
|
3392
|
+
}
|
|
3393
|
+
return aliases;
|
|
3394
|
+
}
|
|
3395
|
+
};
|
|
3396
|
+
function readText2(content) {
|
|
3397
|
+
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
3398
|
+
}
|
|
3399
|
+
function toSlug(path5) {
|
|
3400
|
+
return path5.replace(/\.md$/, "");
|
|
3401
|
+
}
|
|
3402
|
+
function collectDependencies2(manifestEntry, trackedDependencies) {
|
|
3403
|
+
if (!manifestEntry) return [];
|
|
3404
|
+
return [
|
|
3405
|
+
...new Map(
|
|
3406
|
+
[
|
|
3407
|
+
...manifestEntry.links.filter(
|
|
3408
|
+
(link) => link.slug !== null
|
|
3409
|
+
).map((link) => ({ kind: "content", id: link.slug })),
|
|
3410
|
+
...manifestEntry.assets.map((asset) => ({
|
|
3411
|
+
kind: "file",
|
|
3412
|
+
id: asset.path.replace(/\\/g, "/")
|
|
3413
|
+
})),
|
|
3414
|
+
...trackedDependencies.filter(
|
|
3415
|
+
(dependency) => dependency.kind === "content" || dependency.kind === "file"
|
|
3416
|
+
).map((dependency) => ({
|
|
3417
|
+
kind: dependency.kind,
|
|
3418
|
+
id: dependency.id.replace(/\\/g, "/")
|
|
3419
|
+
}))
|
|
3420
|
+
].map((dependency) => [
|
|
3421
|
+
`${dependency.kind}:${dependency.id}`,
|
|
3422
|
+
dependency
|
|
3423
|
+
])
|
|
3424
|
+
).values()
|
|
3425
|
+
].toSorted(
|
|
3426
|
+
(left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`)
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
function collectLinkTargets(manifestEntry) {
|
|
3430
|
+
if (!manifestEntry) return [];
|
|
3431
|
+
return [
|
|
3432
|
+
...new Set(manifestEntry.links.map((link) => link.raw.toLowerCase()))
|
|
3433
|
+
].sort();
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
// src/content/content_entry_reader.ts
|
|
3437
|
+
var ContentEntryReader = class {
|
|
3438
|
+
constructor(source, observability) {
|
|
3439
|
+
this.source = source;
|
|
3440
|
+
this.observability = observability;
|
|
3441
|
+
}
|
|
3442
|
+
source;
|
|
3443
|
+
observability;
|
|
3444
|
+
entries = null;
|
|
3445
|
+
entriesPromise = null;
|
|
3446
|
+
entriesByPath = null;
|
|
3447
|
+
texts = /* @__PURE__ */ new Map();
|
|
3448
|
+
async getEntries() {
|
|
3449
|
+
if (this.entries) return this.entries;
|
|
3450
|
+
if (this.entriesPromise) return await this.entriesPromise;
|
|
3451
|
+
this.entriesPromise = this.observability.tracer.span(
|
|
3452
|
+
"content.scan",
|
|
3453
|
+
{},
|
|
3454
|
+
() => this.source.scan()
|
|
3455
|
+
);
|
|
3456
|
+
try {
|
|
3457
|
+
this.entries = await this.entriesPromise;
|
|
3458
|
+
this.entriesByPath = new Map(
|
|
3459
|
+
this.entries.map((entry) => [entry.path, entry])
|
|
3460
|
+
);
|
|
3461
|
+
return this.entries;
|
|
3462
|
+
} finally {
|
|
3463
|
+
this.entriesPromise = null;
|
|
2573
3464
|
}
|
|
2574
3465
|
}
|
|
3466
|
+
async readText(logicalPath) {
|
|
3467
|
+
await this.getEntries();
|
|
3468
|
+
const entry = this.entriesByPath?.get(logicalPath);
|
|
3469
|
+
if (!entry) throw new Error(`Content entry was not found: ${logicalPath}`);
|
|
3470
|
+
return await this.read(entry);
|
|
3471
|
+
}
|
|
3472
|
+
read(entry) {
|
|
3473
|
+
const cached = this.texts.get(entry.path);
|
|
3474
|
+
if (cached) return cached;
|
|
3475
|
+
const content = this.source.read(entry).then(
|
|
3476
|
+
(value) => typeof value === "string" ? value : new TextDecoder().decode(value)
|
|
3477
|
+
).finally(() => this.texts.delete(entry.path));
|
|
3478
|
+
this.texts.set(entry.path, content);
|
|
3479
|
+
return content;
|
|
3480
|
+
}
|
|
2575
3481
|
};
|
|
2576
|
-
function readText(content) {
|
|
2577
|
-
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
2578
|
-
}
|
|
2579
|
-
function addIndexEntry(index, key, value) {
|
|
2580
|
-
const normalizedKey = key.toLowerCase();
|
|
2581
|
-
if (!index.has(normalizedKey)) index.set(normalizedKey, value);
|
|
2582
|
-
}
|
|
2583
3482
|
|
|
2584
3483
|
// src/content/content_location_resolver.ts
|
|
2585
3484
|
var ContentLocationResolver = class {
|
|
@@ -2588,19 +3487,37 @@ var ContentLocationResolver = class {
|
|
|
2588
3487
|
}
|
|
2589
3488
|
dependencies;
|
|
2590
3489
|
locations = null;
|
|
3490
|
+
locationsPromise = null;
|
|
3491
|
+
permalinks = null;
|
|
3492
|
+
permalinksPromise = null;
|
|
2591
3493
|
async getLocations() {
|
|
2592
3494
|
if (this.locations) return this.locations;
|
|
3495
|
+
if (this.locationsPromise) return await this.locationsPromise;
|
|
3496
|
+
this.locationsPromise = this.dependencies.observability.tracer.span(
|
|
3497
|
+
"content.locations",
|
|
3498
|
+
{},
|
|
3499
|
+
() => this.resolveLocations()
|
|
3500
|
+
);
|
|
3501
|
+
try {
|
|
3502
|
+
return await this.locationsPromise;
|
|
3503
|
+
} finally {
|
|
3504
|
+
this.locationsPromise = null;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
async resolveLocations() {
|
|
2593
3508
|
const [entries, contentIndex] = await Promise.all([
|
|
2594
3509
|
this.dependencies.getEntries(),
|
|
2595
3510
|
this.dependencies.getContentIndex()
|
|
2596
3511
|
]);
|
|
2597
3512
|
await this.dependencies.pluginRuntime.startBuild(contentIndex);
|
|
2598
|
-
const inputs = await
|
|
2599
|
-
entries.filter((entry) => entry.path.endsWith(".md"))
|
|
3513
|
+
const inputs = await mapConcurrent2(
|
|
3514
|
+
entries.filter((entry) => entry.path.endsWith(".md")),
|
|
3515
|
+
64,
|
|
3516
|
+
async (entry) => ({
|
|
2600
3517
|
slug: toSlug2(entry.path),
|
|
2601
3518
|
path: entry.path,
|
|
2602
3519
|
markdown: await this.dependencies.readEntry(entry)
|
|
2603
|
-
})
|
|
3520
|
+
})
|
|
2604
3521
|
);
|
|
2605
3522
|
const locations = new Map(
|
|
2606
3523
|
inputs.map((input) => [input.slug, resolveDefaultContentLocation(input)])
|
|
@@ -2625,6 +3542,17 @@ var ContentLocationResolver = class {
|
|
|
2625
3542
|
return locations;
|
|
2626
3543
|
}
|
|
2627
3544
|
async getPermalinks() {
|
|
3545
|
+
if (this.permalinks) return this.permalinks;
|
|
3546
|
+
if (this.permalinksPromise) return await this.permalinksPromise;
|
|
3547
|
+
this.permalinksPromise = this.createPermalinks();
|
|
3548
|
+
try {
|
|
3549
|
+
this.permalinks = await this.permalinksPromise;
|
|
3550
|
+
return this.permalinks;
|
|
3551
|
+
} finally {
|
|
3552
|
+
this.permalinksPromise = null;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
async createPermalinks() {
|
|
2628
3556
|
return new Map(
|
|
2629
3557
|
Array.from(
|
|
2630
3558
|
(await this.getLocations()).entries(),
|
|
@@ -2640,13 +3568,45 @@ var ContentLocationResolver = class {
|
|
|
2640
3568
|
}
|
|
2641
3569
|
}
|
|
2642
3570
|
};
|
|
2643
|
-
function toSlug2(
|
|
2644
|
-
return
|
|
3571
|
+
function toSlug2(path5) {
|
|
3572
|
+
return path5.replace(/\.md$/, "");
|
|
3573
|
+
}
|
|
3574
|
+
async function mapConcurrent2(values, concurrency, map) {
|
|
3575
|
+
const results = new Array(values.length);
|
|
3576
|
+
let nextIndex = 0;
|
|
3577
|
+
async function worker() {
|
|
3578
|
+
while (nextIndex < values.length) {
|
|
3579
|
+
const index = nextIndex;
|
|
3580
|
+
nextIndex += 1;
|
|
3581
|
+
const value = values[index];
|
|
3582
|
+
if (value !== void 0) results[index] = await map(value);
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
await Promise.all(
|
|
3586
|
+
Array.from(
|
|
3587
|
+
{ length: Math.min(concurrency, values.length) },
|
|
3588
|
+
() => worker()
|
|
3589
|
+
)
|
|
3590
|
+
);
|
|
3591
|
+
return results;
|
|
2645
3592
|
}
|
|
2646
3593
|
|
|
2647
3594
|
// src/content/file_system_content_source.ts
|
|
2648
|
-
import
|
|
2649
|
-
import
|
|
3595
|
+
import fs4 from "node:fs/promises";
|
|
3596
|
+
import path4 from "node:path";
|
|
3597
|
+
var INTERNAL_CONTENT_IGNORE_PATTERNS = [
|
|
3598
|
+
".git",
|
|
3599
|
+
".git/**",
|
|
3600
|
+
".github",
|
|
3601
|
+
".github/**",
|
|
3602
|
+
".obsidian",
|
|
3603
|
+
".obsidian/**",
|
|
3604
|
+
"node_modules",
|
|
3605
|
+
"node_modules/**",
|
|
3606
|
+
"**/.DS_Store",
|
|
3607
|
+
"**/Thumbs.db",
|
|
3608
|
+
"**/desktop.ini"
|
|
3609
|
+
];
|
|
2650
3610
|
var FileSystemContentSource = class {
|
|
2651
3611
|
constructor(contentDirectory, exclude = []) {
|
|
2652
3612
|
this.contentDirectory = contentDirectory;
|
|
@@ -2655,33 +3615,41 @@ var FileSystemContentSource = class {
|
|
|
2655
3615
|
contentDirectory;
|
|
2656
3616
|
exclude;
|
|
2657
3617
|
async scan() {
|
|
2658
|
-
|
|
3618
|
+
try {
|
|
3619
|
+
return await this.scanDirectory(this.contentDirectory, "");
|
|
3620
|
+
} catch (error) {
|
|
3621
|
+
if (isMissingDirectoryError(error, this.contentDirectory)) {
|
|
3622
|
+
throw missingContentDirectoryError(this.contentDirectory);
|
|
3623
|
+
}
|
|
3624
|
+
throw error;
|
|
3625
|
+
}
|
|
2659
3626
|
}
|
|
2660
3627
|
async read(entry) {
|
|
2661
3628
|
const filePath = this.resolveFilePath(entry.path);
|
|
2662
|
-
const stats = await
|
|
3629
|
+
const stats = await fs4.lstat(filePath);
|
|
2663
3630
|
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
2664
3631
|
throw new Error(`Content entry is not a regular file: ${entry.path}`);
|
|
2665
3632
|
}
|
|
2666
|
-
return await
|
|
3633
|
+
return await fs4.readFile(filePath);
|
|
2667
3634
|
}
|
|
2668
|
-
async scanDirectory(directory,
|
|
2669
|
-
const directoryEntries = await
|
|
3635
|
+
async scanDirectory(directory, parentPath2) {
|
|
3636
|
+
const directoryEntries = await fs4.readdir(directory, {
|
|
2670
3637
|
withFileTypes: true
|
|
2671
3638
|
});
|
|
2672
3639
|
const entries = [];
|
|
2673
3640
|
for (const directoryEntry of directoryEntries) {
|
|
2674
3641
|
if (directoryEntry.isSymbolicLink()) continue;
|
|
2675
3642
|
const logicalPath = normalizeContentPath(
|
|
2676
|
-
|
|
3643
|
+
parentPath2 ? `${parentPath2}/${directoryEntry.name}` : directoryEntry.name
|
|
2677
3644
|
);
|
|
2678
|
-
const filePath =
|
|
3645
|
+
const filePath = path4.join(directory, directoryEntry.name);
|
|
3646
|
+
if (this.isExcluded(logicalPath)) continue;
|
|
2679
3647
|
if (directoryEntry.isDirectory()) {
|
|
2680
3648
|
entries.push(...await this.scanDirectory(filePath, logicalPath));
|
|
2681
3649
|
continue;
|
|
2682
3650
|
}
|
|
2683
|
-
if (!directoryEntry.isFile()
|
|
2684
|
-
const stats = await
|
|
3651
|
+
if (!directoryEntry.isFile()) continue;
|
|
3652
|
+
const stats = await fs4.stat(filePath);
|
|
2685
3653
|
entries.push({
|
|
2686
3654
|
path: logicalPath,
|
|
2687
3655
|
metadata: {
|
|
@@ -2693,26 +3661,44 @@ var FileSystemContentSource = class {
|
|
|
2693
3661
|
return entries;
|
|
2694
3662
|
}
|
|
2695
3663
|
isExcluded(logicalPath) {
|
|
2696
|
-
|
|
2697
|
-
return logicalPath.endsWith(".md") && isExcluded(this.exclude, logicalPath.slice(0, -3));
|
|
3664
|
+
return isIgnoredContentPath(logicalPath, this.exclude);
|
|
2698
3665
|
}
|
|
2699
3666
|
resolveFilePath(logicalPath) {
|
|
2700
3667
|
const normalizedPath = normalizeLogicalPath(logicalPath);
|
|
2701
|
-
const filePath =
|
|
2702
|
-
const relativePath =
|
|
2703
|
-
if (relativePath.startsWith("..") ||
|
|
3668
|
+
const filePath = path4.resolve(this.contentDirectory, normalizedPath);
|
|
3669
|
+
const relativePath = path4.relative(this.contentDirectory, filePath);
|
|
3670
|
+
if (relativePath.startsWith("..") || path4.isAbsolute(relativePath)) {
|
|
2704
3671
|
throw new Error(`Invalid content path: ${logicalPath}`);
|
|
2705
3672
|
}
|
|
2706
3673
|
return filePath;
|
|
2707
3674
|
}
|
|
2708
3675
|
};
|
|
3676
|
+
function isIgnoredContentPath(logicalPath, exclude = []) {
|
|
3677
|
+
if (isExcluded(INTERNAL_CONTENT_IGNORE_PATTERNS, logicalPath)) return true;
|
|
3678
|
+
if (isExcluded(exclude, logicalPath)) return true;
|
|
3679
|
+
return logicalPath.endsWith(".md") && isExcluded(exclude, logicalPath.slice(0, -3));
|
|
3680
|
+
}
|
|
2709
3681
|
function normalizeLogicalPath(logicalPath) {
|
|
2710
3682
|
const normalizedPath = normalizeContentPath(logicalPath);
|
|
2711
|
-
if (normalizedPath.length === 0 || normalizedPath.startsWith("/") ||
|
|
3683
|
+
if (normalizedPath.length === 0 || normalizedPath.startsWith("/") || path4.isAbsolute(normalizedPath) || path4.win32.isAbsolute(normalizedPath) || normalizedPath.split("/").some((segment) => segment === "..")) {
|
|
2712
3684
|
throw new Error(`Invalid content path: ${logicalPath}`);
|
|
2713
3685
|
}
|
|
2714
3686
|
return normalizedPath;
|
|
2715
3687
|
}
|
|
3688
|
+
function isMissingDirectoryError(error, directory) {
|
|
3689
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT" && "path" in error && error.path === directory;
|
|
3690
|
+
}
|
|
3691
|
+
function missingContentDirectoryError(directory) {
|
|
3692
|
+
const relative = path4.relative(process.cwd(), directory);
|
|
3693
|
+
const display = relative && !relative.startsWith("..") ? relative : directory;
|
|
3694
|
+
const error = new Error(
|
|
3695
|
+
`Could not find the content directory:
|
|
3696
|
+
|
|
3697
|
+
${display}`
|
|
3698
|
+
);
|
|
3699
|
+
error.hint = "Check `content.directory` in your Riebeckite configuration.";
|
|
3700
|
+
return error;
|
|
3701
|
+
}
|
|
2716
3702
|
|
|
2717
3703
|
// src/content/content_links.ts
|
|
2718
3704
|
var WIKILINK_PATTERN = /(!)?\[\[([^\]|#^]+)(?:[#^][^\]|]+)?(?:\|[^\]]+)?\]\]/g;
|
|
@@ -2768,7 +3754,7 @@ function readOptionalStableId(value, field) {
|
|
|
2768
3754
|
|
|
2769
3755
|
// src/content/manifest_builder.ts
|
|
2770
3756
|
var ManifestBuilder = class {
|
|
2771
|
-
createEntry(slug, markdown, processed, contentIndex, location) {
|
|
3757
|
+
createEntry(slug, markdown, processed, contentIndex, location, publishing) {
|
|
2772
3758
|
const links = extractContentLinks(markdown, contentIndex);
|
|
2773
3759
|
const assets = links.filter(
|
|
2774
3760
|
(link) => (link.kind === "image" || link.kind === "attachment") && link.slug !== null
|
|
@@ -2781,6 +3767,7 @@ var ManifestBuilder = class {
|
|
|
2781
3767
|
publicLocation: location,
|
|
2782
3768
|
title: getManifestTitle(slug, processed.frontmatter.title),
|
|
2783
3769
|
frontmatter: processed.frontmatter,
|
|
3770
|
+
publishing,
|
|
2784
3771
|
html: processed.html,
|
|
2785
3772
|
tags: uniqueStrings([
|
|
2786
3773
|
...normalizeFrontmatterTags(processed.frontmatter.tags),
|
|
@@ -2818,9 +3805,11 @@ var ManifestBuilder = class {
|
|
|
2818
3805
|
const manifest = {
|
|
2819
3806
|
entries,
|
|
2820
3807
|
publicEntries: [...entries],
|
|
3808
|
+
discoverableEntries: [...entries],
|
|
2821
3809
|
bySlug,
|
|
2822
3810
|
byContentId,
|
|
2823
3811
|
byPermalink,
|
|
3812
|
+
byRoutablePermalink: new Map(byPermalink),
|
|
2824
3813
|
redirects: /* @__PURE__ */ new Map(),
|
|
2825
3814
|
publicRedirects: /* @__PURE__ */ new Map(),
|
|
2826
3815
|
byTag,
|
|
@@ -2831,7 +3820,10 @@ var ManifestBuilder = class {
|
|
|
2831
3820
|
assets: [],
|
|
2832
3821
|
clientEntries: [],
|
|
2833
3822
|
diagnostics: [],
|
|
2834
|
-
generatedOutputs: []
|
|
3823
|
+
generatedOutputs: [],
|
|
3824
|
+
folderLocations: /* @__PURE__ */ new Map(),
|
|
3825
|
+
pageRoutes: [],
|
|
3826
|
+
pagePaths: []
|
|
2835
3827
|
};
|
|
2836
3828
|
return { ...manifest, graph: createContentGraph(manifest) };
|
|
2837
3829
|
}
|
|
@@ -2872,21 +3864,283 @@ function appendUniqueToMap(map, key, value) {
|
|
|
2872
3864
|
map.set(key, values);
|
|
2873
3865
|
}
|
|
2874
3866
|
|
|
3867
|
+
// src/content/output_dependency.ts
|
|
3868
|
+
function determineOutputChanges(input) {
|
|
3869
|
+
const current = buildOutputInventory(
|
|
3870
|
+
input.manifest,
|
|
3871
|
+
input.pluginPageOutputs,
|
|
3872
|
+
input.contentOutputDependencies
|
|
3873
|
+
);
|
|
3874
|
+
const previous = new Map(
|
|
3875
|
+
(input.previousState?.outputs ?? []).map((output) => [output.path, output])
|
|
3876
|
+
);
|
|
3877
|
+
const changedContent = collectChangedContent(input);
|
|
3878
|
+
const changedTags = collectChangedTags(
|
|
3879
|
+
input.previousState,
|
|
3880
|
+
input.manifest,
|
|
3881
|
+
changedContent
|
|
3882
|
+
);
|
|
3883
|
+
const changedFolders = collectChangedFolders(
|
|
3884
|
+
input.previousState,
|
|
3885
|
+
input.manifest,
|
|
3886
|
+
changedContent
|
|
3887
|
+
);
|
|
3888
|
+
const affected = /* @__PURE__ */ new Map();
|
|
3889
|
+
const unchanged = [];
|
|
3890
|
+
for (const output of current) {
|
|
3891
|
+
if (isOutputAffected(output, changedContent, changedTags, changedFolders)) {
|
|
3892
|
+
affected.set(output.path, output);
|
|
3893
|
+
} else {
|
|
3894
|
+
unchanged.push(output);
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
for (const output of current) {
|
|
3898
|
+
const previousOutput = previous.get(output.path);
|
|
3899
|
+
if (!previousOutput || output.kind !== previousOutput.kind) {
|
|
3900
|
+
affected.set(output.path, output);
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
const currentPaths = new Set(current.map((output) => output.path));
|
|
3904
|
+
const removed = [...previous.values()].filter(
|
|
3905
|
+
(output) => !currentPaths.has(output.path)
|
|
3906
|
+
);
|
|
3907
|
+
const fullRegenerationRequired = current.some(
|
|
3908
|
+
(output) => output.dependencies.some((dependency) => dependency.type === "unknown")
|
|
3909
|
+
);
|
|
3910
|
+
const affectedValues = [...affected.values()].sort(compareOutput);
|
|
3911
|
+
const unchangedValues = unchanged.filter((output) => !affected.has(output.path)).sort(compareOutput);
|
|
3912
|
+
return {
|
|
3913
|
+
affected: affectedValues,
|
|
3914
|
+
removed: removed.sort(compareOutput),
|
|
3915
|
+
unchanged: unchangedValues,
|
|
3916
|
+
fullRegenerationRequired,
|
|
3917
|
+
candidateOutputCount: current.length,
|
|
3918
|
+
affectedOutputCount: affectedValues.length,
|
|
3919
|
+
removedOutputCount: removed.length,
|
|
3920
|
+
unchangedOutputCount: unchangedValues.length
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
function buildOutputInventory(manifest, pluginPageOutputs = [], contentOutputDependencies = []) {
|
|
3924
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
3925
|
+
for (const entry of manifest.publicEntries) {
|
|
3926
|
+
addOutput(outputs, contentOutput(entry, contentOutputDependencies));
|
|
3927
|
+
}
|
|
3928
|
+
for (const [path5, redirect] of manifest.publicRedirects) {
|
|
3929
|
+
addOutput(outputs, {
|
|
3930
|
+
kind: "redirect",
|
|
3931
|
+
path: htmlOutputPath(path5),
|
|
3932
|
+
producer: `content:${redirect.slug}:redirect`,
|
|
3933
|
+
dependencies: [{ type: "content", slug: redirect.slug }]
|
|
3934
|
+
});
|
|
3935
|
+
}
|
|
3936
|
+
for (const output of pluginPageOutputs) {
|
|
3937
|
+
addOutput(outputs, { ...output, path: htmlOutputPath(output.path) });
|
|
3938
|
+
}
|
|
3939
|
+
for (const output of manifest.generatedOutputs) {
|
|
3940
|
+
addOutput(outputs, {
|
|
3941
|
+
kind: "generated",
|
|
3942
|
+
path: output.path,
|
|
3943
|
+
producer: `plugin:${output.owner}`,
|
|
3944
|
+
dependencies: output.dependencies ?? [{ type: "unknown" }]
|
|
3945
|
+
});
|
|
3946
|
+
}
|
|
3947
|
+
return [...outputs.values()].sort(compareOutput);
|
|
3948
|
+
}
|
|
3949
|
+
function htmlOutputPath(pathname) {
|
|
3950
|
+
const normalized = normalizePublicPath(pathname);
|
|
3951
|
+
if (normalized === "/") return "index.html";
|
|
3952
|
+
const withoutSlash = normalized.replace(/^\//, "");
|
|
3953
|
+
if (pathname.endsWith("/")) return `${withoutSlash}/index.html`;
|
|
3954
|
+
return `${withoutSlash}.html`;
|
|
3955
|
+
}
|
|
3956
|
+
function contentOutput(entry, dependencies) {
|
|
3957
|
+
return {
|
|
3958
|
+
kind: "content",
|
|
3959
|
+
path: htmlOutputPath(entry.permalink),
|
|
3960
|
+
producer: `content:${entry.slug}`,
|
|
3961
|
+
dependencies: [{ type: "content", slug: entry.slug }, ...dependencies]
|
|
3962
|
+
};
|
|
3963
|
+
}
|
|
3964
|
+
function isOutputAffected(output, changedContent, changedTags, changedFolders) {
|
|
3965
|
+
for (const dependency of output.dependencies) {
|
|
3966
|
+
if (dependency.type === "unknown") return true;
|
|
3967
|
+
if (dependency.type === "global") return changedContent.size > 0;
|
|
3968
|
+
if (dependency.type === "content" && changedContent.has(dependency.slug)) {
|
|
3969
|
+
return true;
|
|
3970
|
+
}
|
|
3971
|
+
if (dependency.type === "tag" && changedTags.has(dependency.tag))
|
|
3972
|
+
return true;
|
|
3973
|
+
if (dependency.type === "folder" && changedFolders.has(dependency.folder)) {
|
|
3974
|
+
return true;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
return false;
|
|
3978
|
+
}
|
|
3979
|
+
function collectChangedContent(input) {
|
|
3980
|
+
const changed = /* @__PURE__ */ new Set([
|
|
3981
|
+
...input.affectedContent.direct,
|
|
3982
|
+
...input.affectedContent.dependent,
|
|
3983
|
+
...input.changeSet.removed.filter(isMarkdownPath3).map(toSlug3)
|
|
3984
|
+
]);
|
|
3985
|
+
const previousEntries = input.previousState?.manifestEntries ?? [];
|
|
3986
|
+
const currentEntries = input.manifest.entries;
|
|
3987
|
+
const previousBySlug = new Map(
|
|
3988
|
+
previousEntries.map((entry) => [entry.slug, entry])
|
|
3989
|
+
);
|
|
3990
|
+
const currentBySlug = new Map(
|
|
3991
|
+
currentEntries.map((entry) => [entry.slug, entry])
|
|
3992
|
+
);
|
|
3993
|
+
for (const slug of changed) {
|
|
3994
|
+
for (const target of linkTargets(previousBySlug.get(slug)))
|
|
3995
|
+
changed.add(target);
|
|
3996
|
+
for (const target of linkTargets(currentBySlug.get(slug)))
|
|
3997
|
+
changed.add(target);
|
|
3998
|
+
}
|
|
3999
|
+
return changed;
|
|
4000
|
+
}
|
|
4001
|
+
function collectChangedTags(previousState, manifest, changedContent) {
|
|
4002
|
+
const previousBySlug = new Map(
|
|
4003
|
+
(previousState?.manifestEntries ?? []).map((entry) => [entry.slug, entry])
|
|
4004
|
+
);
|
|
4005
|
+
const currentBySlug = new Map(
|
|
4006
|
+
manifest.entries.map((entry) => [entry.slug, entry])
|
|
4007
|
+
);
|
|
4008
|
+
const tags = /* @__PURE__ */ new Set();
|
|
4009
|
+
for (const slug of changedContent) {
|
|
4010
|
+
for (const tag of previousBySlug.get(slug)?.tags ?? []) tags.add(tag);
|
|
4011
|
+
for (const tag of currentBySlug.get(slug)?.tags ?? []) tags.add(tag);
|
|
4012
|
+
}
|
|
4013
|
+
return tags;
|
|
4014
|
+
}
|
|
4015
|
+
function collectChangedFolders(previousState, manifest, changedContent) {
|
|
4016
|
+
const previousBySlug = new Map(
|
|
4017
|
+
(previousState?.manifestEntries ?? []).map((entry) => [entry.slug, entry])
|
|
4018
|
+
);
|
|
4019
|
+
const currentBySlug = new Map(
|
|
4020
|
+
manifest.entries.map((entry) => [entry.slug, entry])
|
|
4021
|
+
);
|
|
4022
|
+
const folders = /* @__PURE__ */ new Set();
|
|
4023
|
+
for (const slug of changedContent) {
|
|
4024
|
+
addFolderAncestors(folders, previousBySlug.get(slug)?.slug ?? slug);
|
|
4025
|
+
addFolderAncestors(folders, currentBySlug.get(slug)?.slug ?? slug);
|
|
4026
|
+
}
|
|
4027
|
+
return folders;
|
|
4028
|
+
}
|
|
4029
|
+
function addFolderAncestors(folders, slug) {
|
|
4030
|
+
const segments = slug.split("/");
|
|
4031
|
+
segments.pop();
|
|
4032
|
+
while (true) {
|
|
4033
|
+
folders.add(segments.join("/"));
|
|
4034
|
+
if (segments.length === 0) return;
|
|
4035
|
+
segments.pop();
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
function linkTargets(entry) {
|
|
4039
|
+
if (!entry) return [];
|
|
4040
|
+
return entry.links.filter(
|
|
4041
|
+
(link) => link.slug !== null
|
|
4042
|
+
).map((link) => link.slug);
|
|
4043
|
+
}
|
|
4044
|
+
function addOutput(outputs, output) {
|
|
4045
|
+
outputs.set(output.path, output);
|
|
4046
|
+
}
|
|
4047
|
+
function normalizePublicPath(pathname) {
|
|
4048
|
+
const path5 = `/${pathname.split("/").filter(Boolean).join("/")}`;
|
|
4049
|
+
return path5 === "/" ? path5 : path5.replace(/\/$/, "");
|
|
4050
|
+
}
|
|
4051
|
+
function isMarkdownPath3(path5) {
|
|
4052
|
+
return path5.endsWith(".md");
|
|
4053
|
+
}
|
|
4054
|
+
function toSlug3(path5) {
|
|
4055
|
+
return path5.replace(/\.md$/, "");
|
|
4056
|
+
}
|
|
4057
|
+
function compareOutput(left, right) {
|
|
4058
|
+
return left.path.localeCompare(right.path) || left.kind.localeCompare(right.kind);
|
|
4059
|
+
}
|
|
4060
|
+
|
|
4061
|
+
// src/content/publishing.ts
|
|
4062
|
+
function resolvePublishingState(frontmatter, options) {
|
|
4063
|
+
const explicitVisibility = resolveExplicitVisibility(frontmatter?.visibility);
|
|
4064
|
+
const publishAt = resolvePublishAt(frontmatter?.publishAt);
|
|
4065
|
+
if (explicitVisibility === "draft")
|
|
4066
|
+
return state("draft", false, false, publishAt);
|
|
4067
|
+
if (publishAt && publishAt.getTime() > options.buildTime.getTime()) {
|
|
4068
|
+
return state("scheduled", false, false, publishAt);
|
|
4069
|
+
}
|
|
4070
|
+
if (explicitVisibility === "unlisted") {
|
|
4071
|
+
return state("unlisted", true, false, publishAt);
|
|
4072
|
+
}
|
|
4073
|
+
if (explicitVisibility === "public" || publishAt) {
|
|
4074
|
+
return state("public", true, true, publishAt);
|
|
4075
|
+
}
|
|
4076
|
+
const routable = isPublishable(options.strategy, frontmatter);
|
|
4077
|
+
return state(routable ? "public" : "draft", routable, routable, publishAt);
|
|
4078
|
+
}
|
|
4079
|
+
function resolvePublishingBuildTime(input) {
|
|
4080
|
+
if (input === void 0) return /* @__PURE__ */ new Date();
|
|
4081
|
+
if (input instanceof Date) {
|
|
4082
|
+
if (Number.isNaN(input.getTime()))
|
|
4083
|
+
throw new Error("Invalid publishing build time.");
|
|
4084
|
+
return new Date(input.getTime());
|
|
4085
|
+
}
|
|
4086
|
+
const parsed = new Date(input);
|
|
4087
|
+
if (Number.isNaN(parsed.getTime()))
|
|
4088
|
+
throw new Error("Invalid publishing build time.");
|
|
4089
|
+
return parsed;
|
|
4090
|
+
}
|
|
4091
|
+
function resolveExplicitVisibility(value) {
|
|
4092
|
+
if (value === void 0) return void 0;
|
|
4093
|
+
if (value === "public" || value === "unlisted" || value === "draft")
|
|
4094
|
+
return value;
|
|
4095
|
+
throw new Error(
|
|
4096
|
+
`Invalid content visibility "${String(value)}". Expected public, unlisted, or draft.`
|
|
4097
|
+
);
|
|
4098
|
+
}
|
|
4099
|
+
function resolvePublishAt(value) {
|
|
4100
|
+
if (value === void 0) return void 0;
|
|
4101
|
+
if (!(typeof value === "string" || value instanceof Date)) {
|
|
4102
|
+
throw new Error("Invalid publishAt. Expected an ISO 8601 date string.");
|
|
4103
|
+
}
|
|
4104
|
+
const parsed = value instanceof Date ? value : new Date(value);
|
|
4105
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
4106
|
+
throw new Error(
|
|
4107
|
+
`Invalid publishAt "${String(value)}". Expected an ISO 8601 date string.`
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4110
|
+
return new Date(parsed.getTime());
|
|
4111
|
+
}
|
|
4112
|
+
function state(visibility, routable, discoverable, publishAt) {
|
|
4113
|
+
return {
|
|
4114
|
+
visibility,
|
|
4115
|
+
routable,
|
|
4116
|
+
discoverable,
|
|
4117
|
+
...publishAt ? { publishAt } : {}
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
|
|
2875
4121
|
// src/content/content_manager.ts
|
|
4122
|
+
var CONTENT_PROCESSING_CONCURRENCY = 16;
|
|
2876
4123
|
var ContentManager = class {
|
|
2877
4124
|
constructor(content, exclude = [], pipelineOptions = {}) {
|
|
2878
4125
|
this.pipelineOptions = pipelineOptions;
|
|
2879
4126
|
this.pipelineOptions = {
|
|
2880
4127
|
...pipelineOptions,
|
|
2881
|
-
plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins
|
|
4128
|
+
plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins,
|
|
4129
|
+
isRoutable: (slug) => this.isRoutable(slug)
|
|
2882
4130
|
};
|
|
4131
|
+
this.publishingBuildTime = resolvePublishingBuildTime(
|
|
4132
|
+
pipelineOptions.publishingBuildTime
|
|
4133
|
+
);
|
|
2883
4134
|
this.source = typeof content === "string" ? pipelineOptions.config?.content.source ?? new FileSystemContentSource(content, [
|
|
2884
4135
|
...exclude,
|
|
2885
4136
|
CONTENT_BUILD_STATE_EXCLUDE
|
|
2886
4137
|
]) : content;
|
|
2887
4138
|
this.pipelineOptions = {
|
|
2888
4139
|
...this.pipelineOptions,
|
|
2889
|
-
contentSource: this.source
|
|
4140
|
+
contentSource: this.source,
|
|
4141
|
+
onContentDependencies: (slug, dependencies) => {
|
|
4142
|
+
this.trackedContentDependencies.set(slug, dependencies);
|
|
4143
|
+
}
|
|
2890
4144
|
};
|
|
2891
4145
|
this.contentIndexBuilder = new ContentIndexBuilder(this.source);
|
|
2892
4146
|
this.pluginRuntime = new PluginRuntime(this.pipelineOptions);
|
|
@@ -2900,7 +4154,8 @@ var ContentManager = class {
|
|
|
2900
4154
|
getEntries: () => this.entryReader.getEntries(),
|
|
2901
4155
|
readEntry: (entry) => this.entryReader.read(entry),
|
|
2902
4156
|
getContentIndex: () => this.getContentIndex(),
|
|
2903
|
-
pluginRuntime: this.pluginRuntime
|
|
4157
|
+
pluginRuntime: this.pluginRuntime,
|
|
4158
|
+
observability
|
|
2904
4159
|
});
|
|
2905
4160
|
this.buildCoordinator = new ContentBuildCoordinator({
|
|
2906
4161
|
buildStatePath,
|
|
@@ -2915,13 +4170,22 @@ var ContentManager = class {
|
|
|
2915
4170
|
manifestBuilder = new ManifestBuilder();
|
|
2916
4171
|
pluginRuntime;
|
|
2917
4172
|
contentIndex = null;
|
|
4173
|
+
contentIndexPromise = null;
|
|
2918
4174
|
contentCache = /* @__PURE__ */ new Map();
|
|
2919
4175
|
manifest = null;
|
|
4176
|
+
manifestPromise = null;
|
|
4177
|
+
pipelineFingerprint = null;
|
|
2920
4178
|
pipeline = null;
|
|
2921
4179
|
entryReader;
|
|
2922
4180
|
locationResolver;
|
|
2923
4181
|
buildCoordinator;
|
|
2924
4182
|
isBuildTime = false;
|
|
4183
|
+
publishingBuildTime;
|
|
4184
|
+
routableSlugs = null;
|
|
4185
|
+
routableSlugsPromise = null;
|
|
4186
|
+
processedContentCount = 0;
|
|
4187
|
+
outputChangeSet = null;
|
|
4188
|
+
trackedContentDependencies = /* @__PURE__ */ new Map();
|
|
2925
4189
|
async getAllPosts() {
|
|
2926
4190
|
return (await this.scan()).filter((entry) => entry.path.endsWith(".md")).map((entry) => ({ slug: entry.path.replace(/\.md$/, "") }));
|
|
2927
4191
|
}
|
|
@@ -2933,17 +4197,33 @@ var ContentManager = class {
|
|
|
2933
4197
|
}
|
|
2934
4198
|
async getContentIndex(preparation) {
|
|
2935
4199
|
if (this.contentIndex) return this.contentIndex;
|
|
4200
|
+
if (!preparation && this.contentIndexPromise)
|
|
4201
|
+
return await this.contentIndexPromise;
|
|
2936
4202
|
if (preparation?.previousState && !hasContentChanges(preparation.changeSet)) {
|
|
2937
4203
|
this.contentIndex = new Map(
|
|
2938
4204
|
Object.entries(preparation.previousState.contentIndex)
|
|
2939
4205
|
);
|
|
2940
4206
|
return this.contentIndex;
|
|
2941
4207
|
}
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
4208
|
+
if (preparation) {
|
|
4209
|
+
this.contentIndex = preparation.currentContentIndex;
|
|
4210
|
+
return this.contentIndex;
|
|
4211
|
+
}
|
|
4212
|
+
const promise = this.observability().tracer.span(
|
|
4213
|
+
"content.index",
|
|
4214
|
+
{},
|
|
4215
|
+
async () => await this.contentIndexBuilder.build(
|
|
4216
|
+
await this.entryReader.getEntries(),
|
|
4217
|
+
(entry) => this.entryReader.read(entry)
|
|
4218
|
+
)
|
|
2945
4219
|
);
|
|
2946
|
-
|
|
4220
|
+
if (!preparation) this.contentIndexPromise = promise;
|
|
4221
|
+
try {
|
|
4222
|
+
this.contentIndex = await promise;
|
|
4223
|
+
return this.contentIndex;
|
|
4224
|
+
} finally {
|
|
4225
|
+
if (!preparation) this.contentIndexPromise = null;
|
|
4226
|
+
}
|
|
2947
4227
|
}
|
|
2948
4228
|
async getProcessedContent(slug) {
|
|
2949
4229
|
const cached = this.contentCache.get(slug);
|
|
@@ -2960,6 +4240,7 @@ var ContentManager = class {
|
|
|
2960
4240
|
]);
|
|
2961
4241
|
await this.pluginRuntime.startBuild(contentIndex);
|
|
2962
4242
|
await this.pluginRuntime.runContentLoaded(slug, rawPost, contentIndex);
|
|
4243
|
+
await this.ensureRoutableSlugs();
|
|
2963
4244
|
this.pipeline ??= new Pipeline(
|
|
2964
4245
|
contentIndex,
|
|
2965
4246
|
await this.locationResolver.getPermalinks(),
|
|
@@ -2970,6 +4251,12 @@ var ContentManager = class {
|
|
|
2970
4251
|
const content = await this.pipeline.execute(rawPost, {
|
|
2971
4252
|
sourceSlug: slug
|
|
2972
4253
|
});
|
|
4254
|
+
this.processedContentCount += 1;
|
|
4255
|
+
if (this.processedContentCount % 100 === 0) {
|
|
4256
|
+
this.observability().tracer.event("content.process.sample", {
|
|
4257
|
+
processed: this.processedContentCount
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
2973
4260
|
await this.pluginRuntime.runPostHook(
|
|
2974
4261
|
"onPostParsed",
|
|
2975
4262
|
slug,
|
|
@@ -2990,9 +4277,24 @@ var ContentManager = class {
|
|
|
2990
4277
|
);
|
|
2991
4278
|
}
|
|
2992
4279
|
async getManifest(options) {
|
|
4280
|
+
if (this.manifest) return this.manifest;
|
|
4281
|
+
if (this.manifestPromise) return await this.manifestPromise;
|
|
4282
|
+
this.manifestPromise = this.buildManifest(options);
|
|
4283
|
+
try {
|
|
4284
|
+
return await this.manifestPromise;
|
|
4285
|
+
} catch (error) {
|
|
4286
|
+
this.manifestPromise = null;
|
|
4287
|
+
throw error;
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
async buildManifest(options) {
|
|
2993
4291
|
if (this.manifest) return this.manifest;
|
|
2994
4292
|
this.enableBuildTime();
|
|
2995
|
-
const preparation = options ? await this.buildCoordinator.getPreparation(
|
|
4293
|
+
const preparation = options ? await this.buildCoordinator.getPreparation(
|
|
4294
|
+
options.incremental,
|
|
4295
|
+
this.getPipelineFingerprint(),
|
|
4296
|
+
this.requiresFullContentRegeneration()
|
|
4297
|
+
) : void 0;
|
|
2996
4298
|
return await this.observability().tracer.span(
|
|
2997
4299
|
"content.manifest",
|
|
2998
4300
|
{},
|
|
@@ -3002,26 +4304,38 @@ var ContentManager = class {
|
|
|
3002
4304
|
this.getContentIndex(preparation),
|
|
3003
4305
|
this.getContentLocations()
|
|
3004
4306
|
]);
|
|
3005
|
-
const entries = await
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
4307
|
+
const entries = await this.observability().tracer.span(
|
|
4308
|
+
"content.manifest.entries",
|
|
4309
|
+
{},
|
|
4310
|
+
() => mapConcurrent3(
|
|
4311
|
+
posts,
|
|
4312
|
+
CONTENT_PROCESSING_CONCURRENCY,
|
|
4313
|
+
async (post) => {
|
|
4314
|
+
const reused = this.reuseManifestEntry(post.slug, preparation);
|
|
4315
|
+
if (reused) return reused;
|
|
4316
|
+
const [rawPost, processed] = await Promise.all([
|
|
4317
|
+
this.getPost(post.slug),
|
|
4318
|
+
this.getProcessedContent(post.slug)
|
|
4319
|
+
]);
|
|
4320
|
+
const location = locations.get(post.slug);
|
|
4321
|
+
if (!location) {
|
|
4322
|
+
throw new Error(
|
|
4323
|
+
`Content public location was not resolved: ${post.slug}`
|
|
4324
|
+
);
|
|
4325
|
+
}
|
|
4326
|
+
return this.manifestBuilder.createEntry(
|
|
4327
|
+
post.slug,
|
|
4328
|
+
rawPost,
|
|
4329
|
+
processed,
|
|
4330
|
+
contentIndex,
|
|
4331
|
+
location,
|
|
4332
|
+
resolvePublishingState(processed.frontmatter, {
|
|
4333
|
+
strategy: this.publishStrategy(),
|
|
4334
|
+
buildTime: this.publishingBuildTime
|
|
4335
|
+
})
|
|
3015
4336
|
);
|
|
3016
4337
|
}
|
|
3017
|
-
|
|
3018
|
-
post.slug,
|
|
3019
|
-
rawPost,
|
|
3020
|
-
processed,
|
|
3021
|
-
contentIndex,
|
|
3022
|
-
location
|
|
3023
|
-
);
|
|
3024
|
-
})
|
|
4338
|
+
)
|
|
3025
4339
|
);
|
|
3026
4340
|
await this.pluginRuntime.runGraphHook(entries, contentIndex);
|
|
3027
4341
|
const manifest = await this.observability().tracer.span(
|
|
@@ -3029,9 +4343,17 @@ var ContentManager = class {
|
|
|
3029
4343
|
{},
|
|
3030
4344
|
() => this.manifestBuilder.build(entries, contentIndex)
|
|
3031
4345
|
);
|
|
4346
|
+
const cacheManifestEntries = structuredClone(manifest.entries);
|
|
3032
4347
|
this.locationResolver.populateRedirects(manifest, locations);
|
|
3033
|
-
await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
|
|
3034
4348
|
this.applyPublicView(manifest);
|
|
4349
|
+
manifest.pagePaths = [
|
|
4350
|
+
...await this.pluginRuntime.getPagePaths(manifest, contentIndex)
|
|
4351
|
+
];
|
|
4352
|
+
manifest.pageRoutes = [
|
|
4353
|
+
...await this.pluginRuntime.getPageRoutes(manifest, contentIndex)
|
|
4354
|
+
];
|
|
4355
|
+
await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
|
|
4356
|
+
this.synchronizeManifestHtml(manifest, cacheManifestEntries);
|
|
3035
4357
|
manifest.assets = this.pluginRuntime.collectAssets();
|
|
3036
4358
|
manifest.clientEntries = this.pluginRuntime.collectClientEntries();
|
|
3037
4359
|
manifest.diagnostics = [
|
|
@@ -3040,8 +4362,39 @@ var ContentManager = class {
|
|
|
3040
4362
|
];
|
|
3041
4363
|
await this.pluginRuntime.runBuildEnd(manifest, contentIndex);
|
|
3042
4364
|
manifest.generatedOutputs = this.pluginRuntime.collectGeneratedOutputs();
|
|
4365
|
+
const pluginPageOutputs = await this.pluginRuntime.getPageOutputs(
|
|
4366
|
+
manifest,
|
|
4367
|
+
contentIndex
|
|
4368
|
+
);
|
|
4369
|
+
this.outputChangeSet = preparation ? determineOutputChanges({
|
|
4370
|
+
manifest,
|
|
4371
|
+
previousState: preparation.previousState,
|
|
4372
|
+
changeSet: preparation.changeSet,
|
|
4373
|
+
affectedContent: preparation.affectedContent,
|
|
4374
|
+
pluginPageOutputs,
|
|
4375
|
+
contentOutputDependencies: this.pluginRuntime.getContentOutputDependencies()
|
|
4376
|
+
}) : null;
|
|
4377
|
+
if (this.outputChangeSet) {
|
|
4378
|
+
this.observability().tracer.event("build.incremental.outputs", {
|
|
4379
|
+
candidateOutputCount: this.outputChangeSet.candidateOutputCount,
|
|
4380
|
+
affectedOutputCount: this.outputChangeSet.affectedOutputCount,
|
|
4381
|
+
removedOutputCount: this.outputChangeSet.removedOutputCount,
|
|
4382
|
+
unchangedOutputCount: this.outputChangeSet.unchangedOutputCount,
|
|
4383
|
+
fullRegenerationRequired: this.outputChangeSet.fullRegenerationRequired
|
|
4384
|
+
});
|
|
4385
|
+
}
|
|
3043
4386
|
if (preparation)
|
|
3044
|
-
await this.buildCoordinator.commit(
|
|
4387
|
+
await this.buildCoordinator.commit(
|
|
4388
|
+
preparation,
|
|
4389
|
+
manifest,
|
|
4390
|
+
this.getPipelineFingerprint(),
|
|
4391
|
+
this.outputChangeSet ? [
|
|
4392
|
+
...this.outputChangeSet.affected,
|
|
4393
|
+
...this.outputChangeSet.unchanged
|
|
4394
|
+
] : [],
|
|
4395
|
+
cacheManifestEntries,
|
|
4396
|
+
this.trackedContentDependencies
|
|
4397
|
+
);
|
|
3045
4398
|
this.manifest = manifest;
|
|
3046
4399
|
return manifest;
|
|
3047
4400
|
}
|
|
@@ -3050,6 +4403,34 @@ var ContentManager = class {
|
|
|
3050
4403
|
async build(options = {}) {
|
|
3051
4404
|
return await this.getManifest(options);
|
|
3052
4405
|
}
|
|
4406
|
+
async getOutputChangeSet(options = {}) {
|
|
4407
|
+
await this.getManifest(options);
|
|
4408
|
+
if (!this.outputChangeSet) {
|
|
4409
|
+
const manifest = await this.getManifest();
|
|
4410
|
+
const contentIndex = await this.getContentIndex();
|
|
4411
|
+
const pluginPageOutputs = await this.pluginRuntime.getPageOutputs(
|
|
4412
|
+
manifest,
|
|
4413
|
+
contentIndex
|
|
4414
|
+
);
|
|
4415
|
+
return determineOutputChanges({
|
|
4416
|
+
manifest,
|
|
4417
|
+
previousState: void 0,
|
|
4418
|
+
changeSet: {
|
|
4419
|
+
added: manifest.entries.map((entry) => `${entry.slug}.md`),
|
|
4420
|
+
changed: [],
|
|
4421
|
+
removed: [],
|
|
4422
|
+
unchanged: []
|
|
4423
|
+
},
|
|
4424
|
+
affectedContent: {
|
|
4425
|
+
direct: new Set(manifest.entries.map((entry) => entry.slug)),
|
|
4426
|
+
dependent: /* @__PURE__ */ new Set()
|
|
4427
|
+
},
|
|
4428
|
+
pluginPageOutputs,
|
|
4429
|
+
contentOutputDependencies: this.pluginRuntime.getContentOutputDependencies()
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4432
|
+
return this.outputChangeSet;
|
|
4433
|
+
}
|
|
3053
4434
|
async getBacklinks(targetSlug) {
|
|
3054
4435
|
const graph = await this.getContentGraph();
|
|
3055
4436
|
return graph.incomingSlugs(targetSlug).map((slug) => ({
|
|
@@ -3104,19 +4485,106 @@ var ContentManager = class {
|
|
|
3104
4485
|
return await this.locationResolver.getLocations();
|
|
3105
4486
|
}
|
|
3106
4487
|
applyPublicView(manifest) {
|
|
3107
|
-
const strategy = this.
|
|
4488
|
+
const strategy = this.publishStrategy();
|
|
4489
|
+
for (const entry of manifest.entries) {
|
|
4490
|
+
entry.publishing = resolvePublishingState(entry.frontmatter, {
|
|
4491
|
+
strategy,
|
|
4492
|
+
buildTime: this.publishingBuildTime
|
|
4493
|
+
});
|
|
4494
|
+
}
|
|
3108
4495
|
manifest.publicEntries = manifest.entries.filter(
|
|
3109
|
-
(entry) =>
|
|
4496
|
+
(entry) => entry.publishing.routable
|
|
4497
|
+
);
|
|
4498
|
+
manifest.discoverableEntries = manifest.entries.filter(
|
|
4499
|
+
(entry) => entry.publishing.discoverable
|
|
3110
4500
|
);
|
|
3111
4501
|
const publicSlugs = new Set(
|
|
3112
4502
|
manifest.publicEntries.map((entry) => entry.slug)
|
|
3113
4503
|
);
|
|
4504
|
+
const publicPermalinks = new Map(
|
|
4505
|
+
manifest.publicEntries.map((entry) => [entry.permalink, entry])
|
|
4506
|
+
);
|
|
4507
|
+
manifest.byRoutablePermalink = publicPermalinks;
|
|
3114
4508
|
manifest.publicRedirects = new Map(
|
|
3115
4509
|
[...manifest.redirects].filter(
|
|
3116
4510
|
([, redirect]) => publicSlugs.has(redirect.slug)
|
|
3117
4511
|
)
|
|
3118
4512
|
);
|
|
3119
4513
|
}
|
|
4514
|
+
synchronizeManifestHtml(manifest, preHookEntries) {
|
|
4515
|
+
const preHookHtml = new Map(
|
|
4516
|
+
preHookEntries.map((entry) => [entry.slug, entry.html])
|
|
4517
|
+
);
|
|
4518
|
+
for (const entry of manifest.entries) {
|
|
4519
|
+
const changed = preHookHtml.get(entry.slug) !== entry.html;
|
|
4520
|
+
const cached = this.contentCache.get(entry.slug);
|
|
4521
|
+
if (cached) {
|
|
4522
|
+
if (changed) cached.html = entry.html;
|
|
4523
|
+
continue;
|
|
4524
|
+
}
|
|
4525
|
+
this.contentCache.set(entry.slug, {
|
|
4526
|
+
frontmatter: entry.frontmatter,
|
|
4527
|
+
html: entry.html
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
publishStrategy() {
|
|
4532
|
+
return this.pipelineOptions.config?.content.filters.publishStrategy ?? "explicit";
|
|
4533
|
+
}
|
|
4534
|
+
reuseManifestEntry(slug, preparation) {
|
|
4535
|
+
if (!preparation?.previousState?.manifestEntries) return null;
|
|
4536
|
+
if (preparation.previousState.pipelineFingerprint !== this.getPipelineFingerprint()) {
|
|
4537
|
+
return null;
|
|
4538
|
+
}
|
|
4539
|
+
if (preparation.affectedContent.direct.has(slug)) return null;
|
|
4540
|
+
if (preparation.affectedContent.dependent.has(slug)) return null;
|
|
4541
|
+
const entry = preparation.previousManifestEntriesBySlug.get(slug);
|
|
4542
|
+
if (!entry) return null;
|
|
4543
|
+
this.observability().tracer.event("content.reuse", { slug });
|
|
4544
|
+
return cloneManifestEntry(entry);
|
|
4545
|
+
}
|
|
4546
|
+
getPipelineFingerprint() {
|
|
4547
|
+
if (!this.pipelineOptions.config) return void 0;
|
|
4548
|
+
this.pipelineFingerprint ??= computePipelineFingerprint(
|
|
4549
|
+
this.pipelineOptions.config
|
|
4550
|
+
);
|
|
4551
|
+
return this.pipelineFingerprint;
|
|
4552
|
+
}
|
|
4553
|
+
requiresFullContentRegeneration() {
|
|
4554
|
+
const config = this.pipelineOptions.config;
|
|
4555
|
+
return config ? !isPersistentlyCacheable("", config).cacheable : false;
|
|
4556
|
+
}
|
|
4557
|
+
isRoutable(slug) {
|
|
4558
|
+
if (!this.routableSlugs) {
|
|
4559
|
+
throw new Error(`Publishing state was not resolved: ${slug}`);
|
|
4560
|
+
}
|
|
4561
|
+
return this.routableSlugs.has(slug);
|
|
4562
|
+
}
|
|
4563
|
+
async ensureRoutableSlugs() {
|
|
4564
|
+
if (this.routableSlugs) return this.routableSlugs;
|
|
4565
|
+
if (this.routableSlugsPromise) return await this.routableSlugsPromise;
|
|
4566
|
+
this.routableSlugsPromise = this.resolveRoutableSlugs();
|
|
4567
|
+
try {
|
|
4568
|
+
this.routableSlugs = await this.routableSlugsPromise;
|
|
4569
|
+
return this.routableSlugs;
|
|
4570
|
+
} finally {
|
|
4571
|
+
this.routableSlugsPromise = null;
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
async resolveRoutableSlugs() {
|
|
4575
|
+
const strategy = this.publishStrategy();
|
|
4576
|
+
const posts = await this.getAllPosts();
|
|
4577
|
+
const routableSlugs = /* @__PURE__ */ new Set();
|
|
4578
|
+
await mapConcurrent3(posts, CONTENT_PROCESSING_CONCURRENCY, async (post) => {
|
|
4579
|
+
const markdown = await this.getPost(post.slug);
|
|
4580
|
+
const publishing = resolvePublishingState(readFrontmatter(markdown), {
|
|
4581
|
+
strategy,
|
|
4582
|
+
buildTime: this.publishingBuildTime
|
|
4583
|
+
});
|
|
4584
|
+
if (publishing.routable) routableSlugs.add(post.slug);
|
|
4585
|
+
});
|
|
4586
|
+
return routableSlugs;
|
|
4587
|
+
}
|
|
3120
4588
|
enableBuildTime() {
|
|
3121
4589
|
this.isBuildTime = true;
|
|
3122
4590
|
this.pluginRuntime.enableBuildTime();
|
|
@@ -3125,6 +4593,43 @@ var ContentManager = class {
|
|
|
3125
4593
|
return this.pipelineOptions.observability ?? noopObservability;
|
|
3126
4594
|
}
|
|
3127
4595
|
};
|
|
4596
|
+
function readFrontmatter(markdown) {
|
|
4597
|
+
const file = new VFile2({ value: markdown });
|
|
4598
|
+
matter3(file);
|
|
4599
|
+
return file.data.matter ?? {};
|
|
4600
|
+
}
|
|
4601
|
+
function cloneManifestEntry(entry) {
|
|
4602
|
+
return {
|
|
4603
|
+
...entry,
|
|
4604
|
+
publicLocation: { ...entry.publicLocation },
|
|
4605
|
+
frontmatter: { ...entry.frontmatter },
|
|
4606
|
+
tags: [...entry.tags],
|
|
4607
|
+
links: entry.links.map((link) => ({ ...link })),
|
|
4608
|
+
backlinks: [...entry.backlinks],
|
|
4609
|
+
assets: entry.assets.map((asset) => ({ ...asset })),
|
|
4610
|
+
...entry.headTags ? { headTags: [...entry.headTags] } : {},
|
|
4611
|
+
...entry.bodySlots ? { bodySlots: { ...entry.bodySlots } } : {}
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
async function mapConcurrent3(values, concurrency, map) {
|
|
4615
|
+
const results = new Array(values.length);
|
|
4616
|
+
let nextIndex = 0;
|
|
4617
|
+
async function worker() {
|
|
4618
|
+
while (nextIndex < values.length) {
|
|
4619
|
+
const index = nextIndex;
|
|
4620
|
+
nextIndex += 1;
|
|
4621
|
+
const value = values[index];
|
|
4622
|
+
if (value !== void 0) results[index] = await map(value);
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
await Promise.all(
|
|
4626
|
+
Array.from(
|
|
4627
|
+
{ length: Math.min(concurrency, values.length) },
|
|
4628
|
+
() => worker()
|
|
4629
|
+
)
|
|
4630
|
+
);
|
|
4631
|
+
return results;
|
|
4632
|
+
}
|
|
3128
4633
|
|
|
3129
4634
|
// src/content/content_source.ts
|
|
3130
4635
|
async function readContentSourceEntry(source, logicalPath) {
|
|
@@ -3135,6 +4640,74 @@ async function getContentSourceEntry(source, logicalPath) {
|
|
|
3135
4640
|
return (await source.scan()).find((candidate) => candidate.path === logicalPath) ?? null;
|
|
3136
4641
|
}
|
|
3137
4642
|
|
|
4643
|
+
// src/content/folder_entry.ts
|
|
4644
|
+
function resolveFolderEntry(manifest, folder, options = {}) {
|
|
4645
|
+
const base = normalizeFolder2(folder);
|
|
4646
|
+
const scope = options.scope ?? "routable";
|
|
4647
|
+
const candidates = [];
|
|
4648
|
+
for (const [kind, slug] of folderEntrySlugs(base)) {
|
|
4649
|
+
if (slug === null) continue;
|
|
4650
|
+
const entry = manifest.bySlug.get(slug);
|
|
4651
|
+
if (!entry || !isInScope(entry, scope)) continue;
|
|
4652
|
+
candidates.push({ kind, entry });
|
|
4653
|
+
}
|
|
4654
|
+
if (candidates.length === 0) return { type: "none" };
|
|
4655
|
+
const candidate = candidates[0];
|
|
4656
|
+
if (candidates.length === 1 && candidate) {
|
|
4657
|
+
return { type: "resolved", kind: candidate.kind, entry: candidate.entry };
|
|
4658
|
+
}
|
|
4659
|
+
return { type: "ambiguous", candidates };
|
|
4660
|
+
}
|
|
4661
|
+
function resolveFolderLocation(manifest, folder) {
|
|
4662
|
+
const resolution = resolveFolderEntry(manifest, folder);
|
|
4663
|
+
const candidates = resolution.type === "ambiguous" ? resolution.candidates.filter((candidate) => candidate.kind !== "folder") : resolution.type === "resolved" && resolution.kind !== "folder" ? [{ kind: resolution.kind, entry: resolution.entry }] : [];
|
|
4664
|
+
if (candidates.length === 1) {
|
|
4665
|
+
const candidate = candidates[0];
|
|
4666
|
+
if (candidate) return { type: "content", entry: candidate.entry };
|
|
4667
|
+
}
|
|
4668
|
+
if (candidates.length > 1) return { type: "ambiguous", candidates };
|
|
4669
|
+
const location = manifest.folderLocations.get(normalizeFolder2(folder));
|
|
4670
|
+
return location ? { type: "generated", pathname: location.pathname } : { type: "none" };
|
|
4671
|
+
}
|
|
4672
|
+
function resolveGeneratedFolderLocation(manifest, folder) {
|
|
4673
|
+
const normalizedFolder = normalizeFolder2(folder);
|
|
4674
|
+
if (!normalizedFolder) return null;
|
|
4675
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4676
|
+
const prefix = `${normalizedFolder}/`;
|
|
4677
|
+
for (const entry of manifest.publicEntries) {
|
|
4678
|
+
if (!entry.slug.startsWith(prefix)) continue;
|
|
4679
|
+
const remainder = entry.slug.slice(prefix.length).split("/").filter(Boolean);
|
|
4680
|
+
const pathname = parentPath(
|
|
4681
|
+
entry.publicLocation.permalink,
|
|
4682
|
+
remainder.length
|
|
4683
|
+
);
|
|
4684
|
+
if (pathname) candidates.add(pathname);
|
|
4685
|
+
}
|
|
4686
|
+
return candidates.size === 1 ? [...candidates][0] ?? null : null;
|
|
4687
|
+
}
|
|
4688
|
+
function folderEntrySlugs(base) {
|
|
4689
|
+
const prefix = base === "" ? "" : `${base}/`;
|
|
4690
|
+
return [
|
|
4691
|
+
["readme", `${prefix}README`],
|
|
4692
|
+
["index", `${prefix}index`],
|
|
4693
|
+
["folder", base === "" ? null : base]
|
|
4694
|
+
];
|
|
4695
|
+
}
|
|
4696
|
+
function isInScope(entry, scope) {
|
|
4697
|
+
if (scope === "all") return true;
|
|
4698
|
+
if (scope === "discoverable") return entry.publishing.discoverable;
|
|
4699
|
+
return entry.publishing.routable;
|
|
4700
|
+
}
|
|
4701
|
+
function normalizeFolder2(folder) {
|
|
4702
|
+
return folder.split("/").filter(Boolean).join("/");
|
|
4703
|
+
}
|
|
4704
|
+
function parentPath(pathname, levels) {
|
|
4705
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
4706
|
+
if (segments.length < levels) return null;
|
|
4707
|
+
const parent = segments.slice(0, -levels);
|
|
4708
|
+
return parent.length === 0 ? "/" : `/${parent.join("/")}/`;
|
|
4709
|
+
}
|
|
4710
|
+
|
|
3138
4711
|
// src/content/graph_layout.ts
|
|
3139
4712
|
function buildGraphEdges(nodes, visibleSlugs = new Set(nodes.map((node) => node.slug))) {
|
|
3140
4713
|
const edges = /* @__PURE__ */ new Map();
|
|
@@ -3171,9 +4744,143 @@ function layoutRadialGraph(nodes, options) {
|
|
|
3171
4744
|
});
|
|
3172
4745
|
return layout;
|
|
3173
4746
|
}
|
|
4747
|
+
function layoutForceGraph(nodes, options) {
|
|
4748
|
+
const safeOptions = resolveForceGraphLayoutOptions(options);
|
|
4749
|
+
const layoutNodes = [...nodes].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
4750
|
+
const layout = createInitialLayout(layoutNodes, safeOptions);
|
|
4751
|
+
const visibleSlugs = new Set(layoutNodes.map((node) => node.slug));
|
|
4752
|
+
const edges = buildGraphEdges(layoutNodes, visibleSlugs).filter(
|
|
4753
|
+
(edge) => edge.source !== edge.target
|
|
4754
|
+
);
|
|
4755
|
+
const velocities = new Map(
|
|
4756
|
+
layoutNodes.map((node) => [node.slug, { x: 0, y: 0 }])
|
|
4757
|
+
);
|
|
4758
|
+
const centerX = safeOptions.width / 2;
|
|
4759
|
+
const centerY = safeOptions.height / 2;
|
|
4760
|
+
for (let iteration = 0; iteration < safeOptions.iterations; iteration++) {
|
|
4761
|
+
const alpha = 1 - iteration / safeOptions.iterations;
|
|
4762
|
+
applyRepulsion(
|
|
4763
|
+
layoutNodes,
|
|
4764
|
+
layout,
|
|
4765
|
+
velocities,
|
|
4766
|
+
safeOptions.repulsion * alpha
|
|
4767
|
+
);
|
|
4768
|
+
applyLinks(edges, layout, velocities, safeOptions.linkDistance, alpha);
|
|
4769
|
+
applyCentering(layoutNodes, layout, velocities, centerX, centerY, alpha);
|
|
4770
|
+
integrate(
|
|
4771
|
+
layoutNodes,
|
|
4772
|
+
layout,
|
|
4773
|
+
velocities,
|
|
4774
|
+
safeOptions,
|
|
4775
|
+
safeOptions.damping
|
|
4776
|
+
);
|
|
4777
|
+
}
|
|
4778
|
+
return layout;
|
|
4779
|
+
}
|
|
4780
|
+
function resolveForceGraphLayoutOptions(options) {
|
|
4781
|
+
const minNodeRadius = clampFinite(options.minNodeRadius ?? 8, 1, 64);
|
|
4782
|
+
const maxNodeRadius = Math.max(
|
|
4783
|
+
minNodeRadius,
|
|
4784
|
+
clampFinite(options.maxNodeRadius ?? 14, minNodeRadius, 96)
|
|
4785
|
+
);
|
|
4786
|
+
return {
|
|
4787
|
+
width: clampFinite(options.width, maxNodeRadius * 2, 2e4),
|
|
4788
|
+
height: clampFinite(options.height, maxNodeRadius * 2, 2e4),
|
|
4789
|
+
centerSlug: options.centerSlug,
|
|
4790
|
+
linkDistance: clampFinite(options.linkDistance ?? 84, 1, 2e3),
|
|
4791
|
+
repulsion: clampFinite(options.repulsion ?? 1800, 0, 1e5),
|
|
4792
|
+
iterations: Math.round(clampFinite(options.iterations ?? 160, 0, 1e3)),
|
|
4793
|
+
damping: clampFinite(options.damping ?? 0.82, 0, 1),
|
|
4794
|
+
minNodeRadius,
|
|
4795
|
+
maxNodeRadius
|
|
4796
|
+
};
|
|
4797
|
+
}
|
|
4798
|
+
function createInitialLayout(nodes, options) {
|
|
4799
|
+
return layoutRadialGraph(nodes, {
|
|
4800
|
+
width: options.width,
|
|
4801
|
+
height: options.height,
|
|
4802
|
+
centerSlug: options.centerSlug ?? nodes[0]?.slug ?? "",
|
|
4803
|
+
radiusRatio: 0.28,
|
|
4804
|
+
minNodeRadius: options.minNodeRadius,
|
|
4805
|
+
maxNodeRadius: options.maxNodeRadius
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
function applyRepulsion(nodes, layout, velocities, strength) {
|
|
4809
|
+
for (let sourceIndex = 0; sourceIndex < nodes.length; sourceIndex++) {
|
|
4810
|
+
for (let targetIndex = sourceIndex + 1; targetIndex < nodes.length; targetIndex++) {
|
|
4811
|
+
const source = layout.get(nodes[sourceIndex]?.slug ?? "");
|
|
4812
|
+
const target = layout.get(nodes[targetIndex]?.slug ?? "");
|
|
4813
|
+
const sourceVelocity = velocities.get(nodes[sourceIndex]?.slug ?? "");
|
|
4814
|
+
const targetVelocity = velocities.get(nodes[targetIndex]?.slug ?? "");
|
|
4815
|
+
if (!source || !target || !sourceVelocity || !targetVelocity) continue;
|
|
4816
|
+
const dx = target.x - source.x || 0.01;
|
|
4817
|
+
const dy = target.y - source.y || 0.01;
|
|
4818
|
+
const distanceSquared = Math.max(dx * dx + dy * dy, 64);
|
|
4819
|
+
const force = strength / distanceSquared;
|
|
4820
|
+
const distance = Math.sqrt(distanceSquared);
|
|
4821
|
+
const fx = dx / distance * force;
|
|
4822
|
+
const fy = dy / distance * force;
|
|
4823
|
+
sourceVelocity.x -= fx;
|
|
4824
|
+
sourceVelocity.y -= fy;
|
|
4825
|
+
targetVelocity.x += fx;
|
|
4826
|
+
targetVelocity.y += fy;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
function applyLinks(edges, layout, velocities, linkDistance, alpha) {
|
|
4831
|
+
for (const edge of edges) {
|
|
4832
|
+
const source = layout.get(edge.source);
|
|
4833
|
+
const target = layout.get(edge.target);
|
|
4834
|
+
const sourceVelocity = velocities.get(edge.source);
|
|
4835
|
+
const targetVelocity = velocities.get(edge.target);
|
|
4836
|
+
if (!source || !target || !sourceVelocity || !targetVelocity) continue;
|
|
4837
|
+
const dx = target.x - source.x || 0.01;
|
|
4838
|
+
const dy = target.y - source.y || 0.01;
|
|
4839
|
+
const distance = Math.max(Math.sqrt(dx * dx + dy * dy), 1);
|
|
4840
|
+
const force = (distance - linkDistance) * 0.018 * alpha;
|
|
4841
|
+
const fx = dx / distance * force;
|
|
4842
|
+
const fy = dy / distance * force;
|
|
4843
|
+
sourceVelocity.x += fx;
|
|
4844
|
+
sourceVelocity.y += fy;
|
|
4845
|
+
targetVelocity.x -= fx;
|
|
4846
|
+
targetVelocity.y -= fy;
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
function applyCentering(nodes, layout, velocities, centerX, centerY, alpha) {
|
|
4850
|
+
for (const node of nodes) {
|
|
4851
|
+
const point = layout.get(node.slug);
|
|
4852
|
+
const velocity = velocities.get(node.slug);
|
|
4853
|
+
if (!point || !velocity) continue;
|
|
4854
|
+
velocity.x += (centerX - point.x) * 4e-3 * alpha;
|
|
4855
|
+
velocity.y += (centerY - point.y) * 4e-3 * alpha;
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
function integrate(nodes, layout, velocities, options, damping) {
|
|
4859
|
+
for (const node of nodes) {
|
|
4860
|
+
const point = layout.get(node.slug);
|
|
4861
|
+
const velocity = velocities.get(node.slug);
|
|
4862
|
+
if (!point || !velocity) continue;
|
|
4863
|
+
velocity.x *= damping;
|
|
4864
|
+
velocity.y *= damping;
|
|
4865
|
+
point.x = clamp(
|
|
4866
|
+
point.x + velocity.x,
|
|
4867
|
+
point.radius,
|
|
4868
|
+
options.width - point.radius
|
|
4869
|
+
);
|
|
4870
|
+
point.y = clamp(
|
|
4871
|
+
point.y + velocity.y,
|
|
4872
|
+
point.radius,
|
|
4873
|
+
options.height - point.radius
|
|
4874
|
+
);
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
3174
4877
|
function clamp(value, min, max) {
|
|
3175
4878
|
return Math.min(Math.max(value, min), max);
|
|
3176
4879
|
}
|
|
4880
|
+
function clampFinite(value, min, max) {
|
|
4881
|
+
if (!Number.isFinite(value)) return min;
|
|
4882
|
+
return clamp(value, min, max);
|
|
4883
|
+
}
|
|
3177
4884
|
|
|
3178
4885
|
// src/content/read_only_content_graph.ts
|
|
3179
4886
|
async function readOnlyContentGraph(source, locations) {
|
|
@@ -3183,7 +4890,7 @@ async function readOnlyContentGraph(source, locations) {
|
|
|
3183
4890
|
const markdownContents = await Promise.all(
|
|
3184
4891
|
markdownEntries.map(async (entry) => ({
|
|
3185
4892
|
entry,
|
|
3186
|
-
markdown: await
|
|
4893
|
+
markdown: await readText3(source, entry)
|
|
3187
4894
|
}))
|
|
3188
4895
|
);
|
|
3189
4896
|
const graphEntries = markdownContents.map(
|
|
@@ -3203,6 +4910,13 @@ function createGraphEntry(entry, markdown, contentIndex, locations) {
|
|
|
3203
4910
|
publicLocation: location,
|
|
3204
4911
|
title: slug,
|
|
3205
4912
|
frontmatter: {},
|
|
4913
|
+
publishing: resolvePublishingState(
|
|
4914
|
+
{},
|
|
4915
|
+
{
|
|
4916
|
+
strategy: "selective",
|
|
4917
|
+
buildTime: /* @__PURE__ */ new Date(0)
|
|
4918
|
+
}
|
|
4919
|
+
),
|
|
3206
4920
|
html: "",
|
|
3207
4921
|
tags: [],
|
|
3208
4922
|
links: extractContentLinks(markdown, contentIndex),
|
|
@@ -3210,60 +4924,11 @@ function createGraphEntry(entry, markdown, contentIndex, locations) {
|
|
|
3210
4924
|
assets: []
|
|
3211
4925
|
};
|
|
3212
4926
|
}
|
|
3213
|
-
async function
|
|
4927
|
+
async function readText3(source, entry) {
|
|
3214
4928
|
const content = await source.read(entry);
|
|
3215
4929
|
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
3216
4930
|
}
|
|
3217
4931
|
|
|
3218
|
-
// src/plugin/plugin_memo.ts
|
|
3219
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
3220
|
-
function createPluginMemo(cache, options = {}) {
|
|
3221
|
-
return {
|
|
3222
|
-
async memo(key, dependencies, compute) {
|
|
3223
|
-
const memoKey = `${key}:${hash2(stableStringify(dependencies))}`;
|
|
3224
|
-
try {
|
|
3225
|
-
const cached = await cache.get(memoKey);
|
|
3226
|
-
if (cached !== void 0) return cached;
|
|
3227
|
-
} catch (error) {
|
|
3228
|
-
warn(
|
|
3229
|
-
options.logger,
|
|
3230
|
-
"Plugin cache read failed; recomputing.",
|
|
3231
|
-
key,
|
|
3232
|
-
error
|
|
3233
|
-
);
|
|
3234
|
-
}
|
|
3235
|
-
const value = await compute();
|
|
3236
|
-
try {
|
|
3237
|
-
await cache.set(memoKey, value);
|
|
3238
|
-
} catch (error) {
|
|
3239
|
-
warn(
|
|
3240
|
-
options.logger,
|
|
3241
|
-
"Plugin cache write failed; result not cached.",
|
|
3242
|
-
key,
|
|
3243
|
-
error
|
|
3244
|
-
);
|
|
3245
|
-
}
|
|
3246
|
-
return value;
|
|
3247
|
-
}
|
|
3248
|
-
};
|
|
3249
|
-
}
|
|
3250
|
-
function stableStringify(value) {
|
|
3251
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3252
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
3253
|
-
const keys = Object.keys(value).sort();
|
|
3254
|
-
const body = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",");
|
|
3255
|
-
return `{${body}}`;
|
|
3256
|
-
}
|
|
3257
|
-
function hash2(value) {
|
|
3258
|
-
return createHash3("sha256").update(value).digest("hex");
|
|
3259
|
-
}
|
|
3260
|
-
function warn(logger, message, key, error) {
|
|
3261
|
-
logger?.warn(message, {
|
|
3262
|
-
memoKey: key,
|
|
3263
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3264
|
-
});
|
|
3265
|
-
}
|
|
3266
|
-
|
|
3267
4932
|
// src/types/content_manifest.ts
|
|
3268
4933
|
function appendContentBodySlot(entry, slot, html) {
|
|
3269
4934
|
if (!html.trim()) return;
|
|
@@ -3276,12 +4941,12 @@ ${html}` : html
|
|
|
3276
4941
|
}
|
|
3277
4942
|
|
|
3278
4943
|
// src/types/plugin_endpoint.ts
|
|
3279
|
-
function defineEndpoint(
|
|
4944
|
+
function defineEndpoint(path5, handler, options = {}) {
|
|
3280
4945
|
if (options.cacheControl === void 0) {
|
|
3281
|
-
return { path:
|
|
4946
|
+
return { path: path5, handler };
|
|
3282
4947
|
}
|
|
3283
4948
|
return {
|
|
3284
|
-
path:
|
|
4949
|
+
path: path5,
|
|
3285
4950
|
handler: async (context) => {
|
|
3286
4951
|
const response = await handler(context);
|
|
3287
4952
|
const headers = { ...response.headers };
|
|
@@ -3323,6 +4988,7 @@ export {
|
|
|
3323
4988
|
ContentManager,
|
|
3324
4989
|
FileSystemContentSource,
|
|
3325
4990
|
IMAGE_EXTENSIONS,
|
|
4991
|
+
INTERNAL_CONTENT_IGNORE_PATTERNS,
|
|
3326
4992
|
NoopLogger,
|
|
3327
4993
|
NoopTracer,
|
|
3328
4994
|
Pipeline,
|
|
@@ -3332,9 +4998,12 @@ export {
|
|
|
3332
4998
|
attachmentUrl,
|
|
3333
4999
|
buildContentCollections,
|
|
3334
5000
|
buildGraphEdges,
|
|
5001
|
+
buildOutputInventory,
|
|
3335
5002
|
calculateReadingTime,
|
|
5003
|
+
collectConfigDeprecationDiagnostics,
|
|
3336
5004
|
createClientEntry,
|
|
3337
5005
|
createContentGraph,
|
|
5006
|
+
createDeprecationDiagnostic,
|
|
3338
5007
|
createPluginMemo,
|
|
3339
5008
|
createStyleAsset,
|
|
3340
5009
|
createUnavailableGeneratedOutputSink,
|
|
@@ -3342,23 +5011,29 @@ export {
|
|
|
3342
5011
|
defineEndpoint,
|
|
3343
5012
|
definePlugin,
|
|
3344
5013
|
defineTheme,
|
|
5014
|
+
determineOutputChanges,
|
|
3345
5015
|
escapeHtml,
|
|
3346
5016
|
escapeHtmlAttribute,
|
|
3347
5017
|
escapeScriptJson,
|
|
3348
5018
|
extractFrontmatterAliases,
|
|
3349
5019
|
fingerprintContentEntries,
|
|
5020
|
+
formatDeprecationMessage,
|
|
3350
5021
|
getContentSourceEntry,
|
|
3351
5022
|
getExtension,
|
|
3352
5023
|
getResolvedPluginMetadata,
|
|
3353
5024
|
groupContentEntries,
|
|
5025
|
+
htmlOutputPath,
|
|
3354
5026
|
isAttachmentPath,
|
|
3355
5027
|
isExcluded,
|
|
5028
|
+
isIgnoredContentPath,
|
|
3356
5029
|
isImagePath,
|
|
3357
5030
|
isMarkdownPath,
|
|
3358
5031
|
isPublishable,
|
|
3359
5032
|
isPublished,
|
|
5033
|
+
layoutForceGraph,
|
|
3360
5034
|
layoutRadialGraph,
|
|
3361
5035
|
loadContentBuildState,
|
|
5036
|
+
normalizeContentAssetOutputPath,
|
|
3362
5037
|
normalizeContentPath,
|
|
3363
5038
|
normalizeGeneratedOutputPath,
|
|
3364
5039
|
normalizeTag,
|
|
@@ -3373,7 +5048,12 @@ export {
|
|
|
3373
5048
|
resolveContentQueryPagination,
|
|
3374
5049
|
resolveContentStableId,
|
|
3375
5050
|
resolveDefaultContentLocation,
|
|
5051
|
+
resolveFolderEntry,
|
|
5052
|
+
resolveFolderLocation,
|
|
5053
|
+
resolveGeneratedFolderLocation,
|
|
3376
5054
|
resolvePlugins,
|
|
5055
|
+
resolvePublishingBuildTime,
|
|
5056
|
+
resolvePublishingState,
|
|
3377
5057
|
serializePublicClientConfig,
|
|
3378
5058
|
stableStringify,
|
|
3379
5059
|
stripHtml,
|