@riebeckite/core 0.0.14 → 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 +13 -6
- package/dist/index.js +2037 -404
- 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/pipeline.d.ts +10 -0
- package/dist/src/plugin/plugin_runtime.d.ts +7 -1
- package/dist/src/types/content_manifest.d.ts +17 -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) {
|
|
@@ -721,9 +740,9 @@ var configDeprecations = [];
|
|
|
721
740
|
function collectConfigDeprecationDiagnostics(config, notices = configDeprecations) {
|
|
722
741
|
return notices.filter((notice) => hasConfigPath(config, notice.path)).map(createDeprecationDiagnostic);
|
|
723
742
|
}
|
|
724
|
-
function hasConfigPath(config,
|
|
743
|
+
function hasConfigPath(config, path5) {
|
|
725
744
|
let value = config;
|
|
726
|
-
for (const segment of
|
|
745
|
+
for (const segment of path5.split(".")) {
|
|
727
746
|
if (!isRecord2(value) || !(segment in value)) return false;
|
|
728
747
|
value = value[segment];
|
|
729
748
|
}
|
|
@@ -786,7 +805,7 @@ import fs from "node:fs/promises";
|
|
|
786
805
|
import path from "node:path";
|
|
787
806
|
|
|
788
807
|
// src/content/content_build_state.ts
|
|
789
|
-
var CONTENT_BUILD_STATE_VERSION =
|
|
808
|
+
var CONTENT_BUILD_STATE_VERSION = 6;
|
|
790
809
|
var CONTENT_BUILD_STATE_DIRECTORY = ".riebeckite";
|
|
791
810
|
var CONTENT_BUILD_STATE_EXCLUDE = `${CONTENT_BUILD_STATE_DIRECTORY}/**`;
|
|
792
811
|
|
|
@@ -826,12 +845,12 @@ async function readContentBuildStateStatus(statePath) {
|
|
|
826
845
|
entryCount: Object.keys(parsed.entries).length
|
|
827
846
|
};
|
|
828
847
|
}
|
|
829
|
-
async function saveContentBuildState(statePath,
|
|
848
|
+
async function saveContentBuildState(statePath, state2) {
|
|
830
849
|
const directory = path.dirname(statePath);
|
|
831
850
|
const temporaryPath = `${statePath}.${randomUUID()}.tmp`;
|
|
832
851
|
await fs.mkdir(directory, { recursive: true });
|
|
833
852
|
try {
|
|
834
|
-
await fs.writeFile(temporaryPath, JSON.stringify(
|
|
853
|
+
await fs.writeFile(temporaryPath, JSON.stringify(state2), "utf8");
|
|
835
854
|
await fs.rename(temporaryPath, statePath);
|
|
836
855
|
} catch (error) {
|
|
837
856
|
await fs.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
@@ -853,15 +872,33 @@ function isContentBuildState(value) {
|
|
|
853
872
|
return isRecord3(value) && value.version === CONTENT_BUILD_STATE_VERSION && isContentBuildStateShape(value);
|
|
854
873
|
}
|
|
855
874
|
function isContentBuildStateShape(value) {
|
|
856
|
-
if (!isRecord3(value.entries) || !isRecord3(value.contentIndex))
|
|
875
|
+
if (!isRecord3(value.entries) || !isRecord3(value.contentIndex) || value.outputs !== void 0 && !isOutputDescriptors(value.outputs)) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
857
878
|
return Object.values(value.entries).every(
|
|
858
|
-
(entry) => isRecord3(entry) && typeof entry.fingerprint === "string" && entry.fingerprint.length > 0 && Array.isArray(entry.dependencies) && entry.dependencies.every(
|
|
859
|
-
(dependency) => typeof dependency === "string"
|
|
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"
|
|
860
881
|
)
|
|
861
882
|
) && Object.values(value.contentIndex).every(
|
|
862
883
|
(entry) => typeof entry === "string"
|
|
863
884
|
);
|
|
864
885
|
}
|
|
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
|
+
}
|
|
865
902
|
function isRecord3(value) {
|
|
866
903
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
867
904
|
}
|
|
@@ -1173,9 +1210,9 @@ function buildContentCollections(entries, definitions) {
|
|
|
1173
1210
|
function normalizePageSize(value) {
|
|
1174
1211
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
1175
1212
|
}
|
|
1176
|
-
function withPage(
|
|
1177
|
-
if (index === 0) return
|
|
1178
|
-
const base =
|
|
1213
|
+
function withPage(path5, index) {
|
|
1214
|
+
if (index === 0) return path5;
|
|
1215
|
+
const base = path5 === "/" ? "" : path5;
|
|
1179
1216
|
return normalizePath(`${base}/page/${index + 1}`);
|
|
1180
1217
|
}
|
|
1181
1218
|
function defaultPath(context) {
|
|
@@ -1203,12 +1240,32 @@ function normalizePath(value) {
|
|
|
1203
1240
|
// src/content/content_fingerprint.ts
|
|
1204
1241
|
import { createHash } from "node:crypto";
|
|
1205
1242
|
async function fingerprintContentEntries(entries, read) {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
entry,
|
|
1209
|
-
fingerprint: await fingerprintContentEntry(entry, read)
|
|
1210
|
-
}))
|
|
1243
|
+
const sortedEntries = [...entries].sort(
|
|
1244
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
1211
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
|
+
)
|
|
1267
|
+
);
|
|
1268
|
+
return results;
|
|
1212
1269
|
}
|
|
1213
1270
|
async function fingerprintContentEntry(entry, read) {
|
|
1214
1271
|
if (entry.metadata?.hash) return `hash:${entry.metadata.hash}`;
|
|
@@ -1273,6 +1330,10 @@ function resolveDefaultContentLocation(content) {
|
|
|
1273
1330
|
};
|
|
1274
1331
|
}
|
|
1275
1332
|
|
|
1333
|
+
// src/content/content_manager.ts
|
|
1334
|
+
import { VFile as VFile2 } from "vfile";
|
|
1335
|
+
import { matter as matter3 } from "vfile-matter";
|
|
1336
|
+
|
|
1276
1337
|
// src/observability.ts
|
|
1277
1338
|
var NoopLogger = class {
|
|
1278
1339
|
debug() {
|
|
@@ -1414,12 +1475,334 @@ import remarkMath from "remark-math";
|
|
|
1414
1475
|
import remarkParse from "remark-parse";
|
|
1415
1476
|
import remarkRehype from "remark-rehype";
|
|
1416
1477
|
import { unified } from "unified";
|
|
1417
|
-
import { matter } from "vfile-matter";
|
|
1478
|
+
import { matter as matter2 } from "vfile-matter";
|
|
1418
1479
|
|
|
1419
|
-
// src/
|
|
1420
|
-
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";
|
|
1421
1529
|
import fs2 from "node:fs/promises";
|
|
1530
|
+
import os from "node:os";
|
|
1422
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";
|
|
1423
1806
|
function createUnavailablePluginCache() {
|
|
1424
1807
|
return {
|
|
1425
1808
|
get: unavailableAtRuntime,
|
|
@@ -1429,20 +1812,20 @@ function createUnavailablePluginCache() {
|
|
|
1429
1812
|
};
|
|
1430
1813
|
}
|
|
1431
1814
|
function createPluginCache(options) {
|
|
1432
|
-
const pluginDirectory =
|
|
1815
|
+
const pluginDirectory = path3.join(
|
|
1433
1816
|
options.cacheDirectory,
|
|
1434
1817
|
pluginNamespace(options.pluginName)
|
|
1435
1818
|
);
|
|
1436
|
-
const
|
|
1819
|
+
const versionDirectory2 = path3.join(
|
|
1437
1820
|
pluginDirectory,
|
|
1438
|
-
|
|
1821
|
+
hash3(options.cacheVersion ?? "1")
|
|
1439
1822
|
);
|
|
1440
1823
|
const writes = /* @__PURE__ */ new Map();
|
|
1441
1824
|
return {
|
|
1442
1825
|
async get(key) {
|
|
1443
1826
|
let raw;
|
|
1444
1827
|
try {
|
|
1445
|
-
raw = await
|
|
1828
|
+
raw = await fs3.readFile(cachePath(versionDirectory2, key), "utf8");
|
|
1446
1829
|
} catch (error) {
|
|
1447
1830
|
if (isNotFoundError2(error)) {
|
|
1448
1831
|
options.tracer?.event("cache.miss", { plugin: options.pluginName });
|
|
@@ -1465,13 +1848,13 @@ function createPluginCache(options) {
|
|
|
1465
1848
|
}
|
|
1466
1849
|
},
|
|
1467
1850
|
async set(key, value) {
|
|
1468
|
-
const targetPath = cachePath(
|
|
1851
|
+
const targetPath = cachePath(versionDirectory2, key);
|
|
1469
1852
|
const serialized = JSON.stringify(value);
|
|
1470
1853
|
if (serialized === void 0) {
|
|
1471
1854
|
throw new TypeError("Plugin cache values must be JSON serializable.");
|
|
1472
1855
|
}
|
|
1473
1856
|
const previousWrite = writes.get(targetPath) ?? Promise.resolve();
|
|
1474
|
-
const write = previousWrite.catch(() => void 0).then(() =>
|
|
1857
|
+
const write = previousWrite.catch(() => void 0).then(() => writeAtomically2(targetPath, versionDirectory2, serialized));
|
|
1475
1858
|
writes.set(targetPath, write);
|
|
1476
1859
|
try {
|
|
1477
1860
|
await write;
|
|
@@ -1482,37 +1865,37 @@ function createPluginCache(options) {
|
|
|
1482
1865
|
}
|
|
1483
1866
|
},
|
|
1484
1867
|
async delete(key) {
|
|
1485
|
-
await
|
|
1868
|
+
await fs3.rm(cachePath(versionDirectory2, key), { force: true });
|
|
1486
1869
|
},
|
|
1487
1870
|
async clear() {
|
|
1488
|
-
await
|
|
1871
|
+
await fs3.rm(pluginDirectory, { recursive: true, force: true });
|
|
1489
1872
|
}
|
|
1490
1873
|
};
|
|
1491
1874
|
}
|
|
1492
|
-
async function
|
|
1493
|
-
await
|
|
1875
|
+
async function writeAtomically2(targetPath, versionDirectory2, serialized) {
|
|
1876
|
+
await fs3.mkdir(versionDirectory2, { recursive: true });
|
|
1494
1877
|
const temporaryPath = `${targetPath}.${randomUUID2()}.tmp`;
|
|
1495
1878
|
try {
|
|
1496
|
-
await
|
|
1497
|
-
await
|
|
1879
|
+
await fs3.writeFile(temporaryPath, serialized, "utf8");
|
|
1880
|
+
await fs3.rename(temporaryPath, targetPath);
|
|
1498
1881
|
} catch (error) {
|
|
1499
|
-
await
|
|
1882
|
+
await fs3.rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
1500
1883
|
throw error;
|
|
1501
1884
|
}
|
|
1502
1885
|
}
|
|
1503
1886
|
function resolvePluginCacheDirectory(config) {
|
|
1504
|
-
if (!config) return
|
|
1505
|
-
if (config.buildDirectory) return
|
|
1506
|
-
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");
|
|
1507
1890
|
}
|
|
1508
|
-
function cachePath(
|
|
1509
|
-
return
|
|
1891
|
+
function cachePath(versionDirectory2, key) {
|
|
1892
|
+
return path3.join(versionDirectory2, `${hash3(key)}.json`);
|
|
1510
1893
|
}
|
|
1511
1894
|
function pluginNamespace(pluginName) {
|
|
1512
|
-
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);
|
|
1513
1896
|
}
|
|
1514
|
-
function
|
|
1515
|
-
return
|
|
1897
|
+
function hash3(value) {
|
|
1898
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
1516
1899
|
}
|
|
1517
1900
|
function isNotFoundError2(error) {
|
|
1518
1901
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -1524,16 +1907,20 @@ async function unavailableAtRuntime() {
|
|
|
1524
1907
|
}
|
|
1525
1908
|
|
|
1526
1909
|
// src/types/generated_output.ts
|
|
1910
|
+
var ATTACHMENTS_NAMESPACE = ATTACHMENTS_BASE_PATH.replace(/^\/+/, "");
|
|
1911
|
+
var STATIC_ASSETS_DIR = "assets";
|
|
1527
1912
|
function createUnavailableGeneratedOutputSink() {
|
|
1913
|
+
const unavailable = () => {
|
|
1914
|
+
throw new Error(
|
|
1915
|
+
"Generated output is available only during a build lifecycle."
|
|
1916
|
+
);
|
|
1917
|
+
};
|
|
1528
1918
|
return {
|
|
1529
|
-
emit
|
|
1530
|
-
|
|
1531
|
-
"Generated output is available only during a build lifecycle."
|
|
1532
|
-
);
|
|
1533
|
-
}
|
|
1919
|
+
emit: unavailable,
|
|
1920
|
+
emitAsset: unavailable
|
|
1534
1921
|
};
|
|
1535
1922
|
}
|
|
1536
|
-
function
|
|
1923
|
+
function normalizeOutputPathSegments(input) {
|
|
1537
1924
|
if (typeof input !== "string" || input.length === 0) {
|
|
1538
1925
|
throw new Error("Generated output path must be a non-empty string.");
|
|
1539
1926
|
}
|
|
@@ -1557,12 +1944,27 @@ function normalizeGeneratedOutputPath(input) {
|
|
|
1557
1944
|
);
|
|
1558
1945
|
}
|
|
1559
1946
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
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}/`)) {
|
|
1562
1952
|
throw new Error(
|
|
1563
1953
|
`Generated output path must not conflict with the static assets namespace: "${input}".`
|
|
1564
1954
|
);
|
|
1565
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) {
|
|
1566
1968
|
if (normalized === ".riebeckite" || normalized.startsWith(".riebeckite/")) {
|
|
1567
1969
|
throw new Error(
|
|
1568
1970
|
`Generated output path must not target internal Riebeckite files: "${input}".`
|
|
@@ -1579,6 +1981,16 @@ var Pipeline = class {
|
|
|
1579
1981
|
this.getMarkdownBySlug = getMarkdownBySlug;
|
|
1580
1982
|
this.options = options;
|
|
1581
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
|
+
}
|
|
1582
1994
|
}
|
|
1583
1995
|
contentIndex;
|
|
1584
1996
|
permalinks;
|
|
@@ -1586,23 +1998,106 @@ var Pipeline = class {
|
|
|
1586
1998
|
options;
|
|
1587
1999
|
isBuildTime;
|
|
1588
2000
|
pluginCaches = /* @__PURE__ */ new Map();
|
|
2001
|
+
persistentCache = null;
|
|
2002
|
+
pipelineFingerprint = null;
|
|
2003
|
+
resolvedPlugins = null;
|
|
1589
2004
|
async execute(markDownContent, context = {}) {
|
|
1590
2005
|
return await this.executeWithEmbedState(
|
|
1591
2006
|
markDownContent,
|
|
1592
2007
|
context,
|
|
1593
2008
|
0,
|
|
1594
|
-
new Set(context.sourceSlug ? [context.sourceSlug] : [])
|
|
2009
|
+
new Set(context.sourceSlug ? [context.sourceSlug] : []),
|
|
2010
|
+
this.options.contentSource ? createContentDependencyTracker(this.options.contentSource) : null
|
|
1595
2011
|
);
|
|
1596
2012
|
}
|
|
1597
|
-
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
|
+
}
|
|
1598
2093
|
const processor = unified();
|
|
1599
|
-
const plugins = resolvePlugins(
|
|
2094
|
+
const plugins = this.resolvePlugins();
|
|
1600
2095
|
this.use(processor, remarkParse);
|
|
1601
2096
|
this.use(processor, remarkDirective);
|
|
1602
2097
|
this.use(processor, remarkFrontmatter, ["yaml", "toml"]);
|
|
1603
2098
|
this.use(processor, function loadFrontmatter() {
|
|
1604
2099
|
return (_tree, file2) => {
|
|
1605
|
-
|
|
2100
|
+
matter2(file2);
|
|
1606
2101
|
};
|
|
1607
2102
|
});
|
|
1608
2103
|
this.use(processor, remarkMath);
|
|
@@ -1611,9 +2106,14 @@ var Pipeline = class {
|
|
|
1611
2106
|
sourceSlug: context.sourceSlug,
|
|
1612
2107
|
contentIndex: this.contentIndex,
|
|
1613
2108
|
resolvePermalink: (slug) => this.getPermalink(slug),
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2109
|
+
isRoutable: this.options.isRoutable,
|
|
2110
|
+
renderNoteEmbed: this.createNoteEmbedRenderer(
|
|
2111
|
+
embedDepth,
|
|
2112
|
+
embedTrail,
|
|
2113
|
+
dependencyTracker
|
|
2114
|
+
),
|
|
2115
|
+
renderContent: this.createContentRenderer(effectiveContentSource),
|
|
2116
|
+
contentSource: effectiveContentSource
|
|
1617
2117
|
};
|
|
1618
2118
|
for (const plugin of plugins) {
|
|
1619
2119
|
for (const remarkPlugin of plugin.remarkPlugins ?? []) {
|
|
@@ -1628,7 +2128,10 @@ var Pipeline = class {
|
|
|
1628
2128
|
markdownPipelineContext
|
|
1629
2129
|
);
|
|
1630
2130
|
}
|
|
1631
|
-
this.use(processor, normalizeMarkdownLinks,
|
|
2131
|
+
this.use(processor, normalizeMarkdownLinks, {
|
|
2132
|
+
...markdownPipelineContext,
|
|
2133
|
+
recordLinkResolution: dependencyTracker ? (id, value) => dependencyTracker.recordLinkResolution(id, value) : void 0
|
|
2134
|
+
});
|
|
1632
2135
|
this.use(processor, remarkRehype, { allowDangerousHtml: true });
|
|
1633
2136
|
this.use(processor, rehypeRaw);
|
|
1634
2137
|
for (const plugin of plugins) {
|
|
@@ -1646,10 +2149,43 @@ var Pipeline = class {
|
|
|
1646
2149
|
this.use(processor, rehypeFormat);
|
|
1647
2150
|
this.use(processor, rehypeStringify, { allowDangerousHtml: true });
|
|
1648
2151
|
const file = await processor.process(markDownContent.trim());
|
|
1649
|
-
|
|
2152
|
+
const result = {
|
|
1650
2153
|
frontmatter: file.data.matter || {},
|
|
1651
2154
|
html: String(file.value)
|
|
1652
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);
|
|
1653
2189
|
}
|
|
1654
2190
|
getPermalink(slug) {
|
|
1655
2191
|
const permalink = this.permalinks.get(slug);
|
|
@@ -1658,6 +2194,14 @@ var Pipeline = class {
|
|
|
1658
2194
|
}
|
|
1659
2195
|
return permalink;
|
|
1660
2196
|
}
|
|
2197
|
+
resolveLinkDependency(id) {
|
|
2198
|
+
return resolveLinkDependencyValue(
|
|
2199
|
+
id,
|
|
2200
|
+
this.contentIndex,
|
|
2201
|
+
(slug) => this.permalinks.get(slug),
|
|
2202
|
+
this.options.isRoutable
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
1661
2205
|
use(processor, plugin, options) {
|
|
1662
2206
|
const register = processor.use.bind(processor);
|
|
1663
2207
|
if (options === void 0) {
|
|
@@ -1666,26 +2210,37 @@ var Pipeline = class {
|
|
|
1666
2210
|
register(plugin, options);
|
|
1667
2211
|
}
|
|
1668
2212
|
}
|
|
1669
|
-
createNoteEmbedRenderer(embedDepth, embedTrail) {
|
|
2213
|
+
createNoteEmbedRenderer(embedDepth, embedTrail, dependencyTracker) {
|
|
1670
2214
|
if (!this.getMarkdownBySlug || embedDepth >= 3) return void 0;
|
|
1671
2215
|
return async (slug, fragment) => {
|
|
2216
|
+
if (this.options.isRoutable && !this.options.isRoutable(slug)) {
|
|
2217
|
+
return null;
|
|
2218
|
+
}
|
|
1672
2219
|
if (embedTrail.has(slug)) return null;
|
|
1673
2220
|
const sourceMarkdown = await this.getMarkdownBySlug?.(slug);
|
|
1674
2221
|
const markdown = sourceMarkdown ? selectEmbedMarkdownFragment(sourceMarkdown, fragment) : null;
|
|
1675
2222
|
if (!markdown) return null;
|
|
1676
2223
|
const nextEmbedTrail = new Set(embedTrail);
|
|
1677
2224
|
nextEmbedTrail.add(slug);
|
|
2225
|
+
if (dependencyTracker) {
|
|
2226
|
+
await dependencyTracker.readContent(slug, async () => markdown);
|
|
2227
|
+
}
|
|
1678
2228
|
const content = await this.executeWithEmbedState(
|
|
1679
2229
|
markdown,
|
|
1680
2230
|
{ sourceSlug: slug },
|
|
1681
2231
|
embedDepth + 1,
|
|
1682
|
-
nextEmbedTrail
|
|
2232
|
+
nextEmbedTrail,
|
|
2233
|
+
dependencyTracker
|
|
1683
2234
|
);
|
|
1684
2235
|
return content.html;
|
|
1685
2236
|
};
|
|
1686
2237
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
2238
|
+
resolvePlugins() {
|
|
2239
|
+
this.resolvedPlugins ??= resolvePlugins(this.options.plugins);
|
|
2240
|
+
return this.resolvedPlugins;
|
|
2241
|
+
}
|
|
2242
|
+
createContentRenderer(contentSource) {
|
|
2243
|
+
const renderers = this.resolvePlugins().flatMap(
|
|
1689
2244
|
(plugin) => (plugin.renderers ?? []).map((renderer) => ({ plugin, renderer }))
|
|
1690
2245
|
);
|
|
1691
2246
|
if (renderers.length === 0) return void 0;
|
|
@@ -1707,7 +2262,7 @@ var Pipeline = class {
|
|
|
1707
2262
|
output: createUnavailableGeneratedOutputSink(),
|
|
1708
2263
|
logger: observability.logger.child({ plugin: plugin.name }),
|
|
1709
2264
|
tracer: observability.tracer,
|
|
1710
|
-
contentSource
|
|
2265
|
+
contentSource,
|
|
1711
2266
|
...input
|
|
1712
2267
|
})
|
|
1713
2268
|
);
|
|
@@ -1750,6 +2305,21 @@ function normalizeMarkdownLinks(context) {
|
|
|
1750
2305
|
});
|
|
1751
2306
|
};
|
|
1752
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
|
+
}
|
|
1753
2323
|
function visitMarkdownLinkNodes(node, visitor) {
|
|
1754
2324
|
const linkNode = node;
|
|
1755
2325
|
if ((node.type === "link" || node.type === "definition") && typeof linkNode.url === "string") {
|
|
@@ -1762,7 +2332,7 @@ function visitMarkdownLinkNodes(node, visitor) {
|
|
|
1762
2332
|
function normalizeMarkdownLinkUrl(url, context) {
|
|
1763
2333
|
if (!context.sourceSlug) return null;
|
|
1764
2334
|
if (!isMarkdownContentLink(url)) return null;
|
|
1765
|
-
const [pathAndQuery,
|
|
2335
|
+
const [pathAndQuery, hash4 = ""] = splitOnce(url, "#");
|
|
1766
2336
|
const [rawPath, query = ""] = splitOnce(pathAndQuery, "?");
|
|
1767
2337
|
const decodedPath = decodeUriPath(rawPath);
|
|
1768
2338
|
if (!decodedPath?.toLowerCase().endsWith(".md")) return null;
|
|
@@ -1773,15 +2343,35 @@ function normalizeMarkdownLinkUrl(url, context) {
|
|
|
1773
2343
|
if (!resolvedSlug) return null;
|
|
1774
2344
|
const indexedSlug = getExactContentSlug(context, resolvedSlug);
|
|
1775
2345
|
if (!indexedSlug) return null;
|
|
1776
|
-
|
|
2346
|
+
recordLinkResolution(context, LINK_LOCATION_PREFIX, indexedSlug);
|
|
2347
|
+
if (context.isRoutable && !context.isRoutable(indexedSlug)) return null;
|
|
2348
|
+
const suffix = `${query ? `?${query}` : ""}${hash4 ? `#${hash4}` : ""}`;
|
|
1777
2349
|
return `${context.resolvePermalink(indexedSlug)}${suffix}`;
|
|
1778
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
|
+
}
|
|
1779
2367
|
function getExactContentSlug(context, slug) {
|
|
1780
2368
|
try {
|
|
1781
2369
|
context.resolvePermalink(slug);
|
|
1782
2370
|
return slug;
|
|
1783
2371
|
} catch {
|
|
1784
|
-
|
|
2372
|
+
const indexKey = slug.toLowerCase();
|
|
2373
|
+
recordLinkResolution(context, LINK_INDEX_PREFIX, indexKey);
|
|
2374
|
+
return context.contentIndex.get(indexKey) ?? null;
|
|
1785
2375
|
}
|
|
1786
2376
|
}
|
|
1787
2377
|
function isMarkdownContentLink(url) {
|
|
@@ -1805,9 +2395,9 @@ function resolveRelativeMarkdownSlug(sourceSlug, targetPath) {
|
|
|
1805
2395
|
}
|
|
1806
2396
|
return normalizedParts.join("/").replace(/\.md$/i, "");
|
|
1807
2397
|
}
|
|
1808
|
-
function decodeUriPath(
|
|
2398
|
+
function decodeUriPath(path5) {
|
|
1809
2399
|
try {
|
|
1810
|
-
return decodeURI(
|
|
2400
|
+
return decodeURI(path5);
|
|
1811
2401
|
} catch {
|
|
1812
2402
|
return null;
|
|
1813
2403
|
}
|
|
@@ -1895,17 +2485,27 @@ var GeneratedOutputRegistry = class {
|
|
|
1895
2485
|
outputs = [];
|
|
1896
2486
|
owners = /* @__PURE__ */ new Map();
|
|
1897
2487
|
sinkFor(pluginName) {
|
|
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
|
+
};
|
|
1898
2503
|
return {
|
|
1899
2504
|
emit: (output) => {
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
`Duplicate generated output path "${path4}" declared by "${owner}" and "${pluginName}".`
|
|
1905
|
-
);
|
|
1906
|
-
}
|
|
1907
|
-
this.owners.set(path4, pluginName);
|
|
1908
|
-
this.outputs.push({ path: path4, content: output.content, owner: pluginName });
|
|
2505
|
+
register(normalizeGeneratedOutputPath(output.path), output);
|
|
2506
|
+
},
|
|
2507
|
+
emitAsset: (output) => {
|
|
2508
|
+
register(normalizeContentAssetOutputPath(output.path), output);
|
|
1909
2509
|
}
|
|
1910
2510
|
};
|
|
1911
2511
|
}
|
|
@@ -1988,6 +2588,7 @@ var PluginRuntime = class {
|
|
|
1988
2588
|
pluginCaches = /* @__PURE__ */ new Map();
|
|
1989
2589
|
generatedOutputs = new GeneratedOutputRegistry();
|
|
1990
2590
|
isBuildTime = false;
|
|
2591
|
+
resolvedPlugins = null;
|
|
1991
2592
|
getDiagnostics() {
|
|
1992
2593
|
return this.diagnostics;
|
|
1993
2594
|
}
|
|
@@ -2108,20 +2709,26 @@ var PluginRuntime = class {
|
|
|
2108
2709
|
}
|
|
2109
2710
|
async resolvePage(pathname, manifest, contentIndex) {
|
|
2110
2711
|
const matches = [];
|
|
2111
|
-
const
|
|
2112
|
-
const context = {
|
|
2113
|
-
...this.createContext(contentIndex),
|
|
2114
|
-
manifest,
|
|
2115
|
-
pathname: normalizedPathname
|
|
2116
|
-
};
|
|
2712
|
+
const requestedPathname = normalizePagePath(pathname);
|
|
2117
2713
|
for (const plugin of this.plugins()) {
|
|
2118
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
|
+
};
|
|
2119
2721
|
const page = await pageType.resolve(
|
|
2120
2722
|
this.createPluginContext(plugin, context)
|
|
2121
2723
|
);
|
|
2122
2724
|
if (page) {
|
|
2123
2725
|
matches.push({
|
|
2124
|
-
page: {
|
|
2726
|
+
page: {
|
|
2727
|
+
...page,
|
|
2728
|
+
pathname: pageType.directoryIndex ? normalizeDirectoryPagePath(page.pathname) : page.pathname,
|
|
2729
|
+
type: pageType.id,
|
|
2730
|
+
pluginName: plugin.name
|
|
2731
|
+
},
|
|
2125
2732
|
priority: pageType.priority ?? 0
|
|
2126
2733
|
});
|
|
2127
2734
|
}
|
|
@@ -2134,7 +2741,7 @@ var PluginRuntime = class {
|
|
|
2134
2741
|
);
|
|
2135
2742
|
if (highest.length !== 1) {
|
|
2136
2743
|
throw new Error(
|
|
2137
|
-
`Multiple plugin page types match ${
|
|
2744
|
+
`Multiple plugin page types match ${requestedPathname} at priority ${highestPriority}: ${highest.map((match) => match.page.type).join(", ")}`
|
|
2138
2745
|
);
|
|
2139
2746
|
}
|
|
2140
2747
|
return highest[0].page;
|
|
@@ -2146,15 +2753,63 @@ var PluginRuntime = class {
|
|
|
2146
2753
|
for (const pageType of plugin.pageTypes ?? []) {
|
|
2147
2754
|
const declared = pageType.paths;
|
|
2148
2755
|
if (!declared) continue;
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
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
|
+
}
|
|
2152
2760
|
}
|
|
2153
2761
|
}
|
|
2154
|
-
return [...new Set(paths
|
|
2762
|
+
return [...new Set(paths)];
|
|
2155
2763
|
}
|
|
2156
|
-
async
|
|
2157
|
-
const
|
|
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
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
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 ?? []);
|
|
2810
|
+
}
|
|
2811
|
+
async collectDiagnostics(contentIndex) {
|
|
2812
|
+
const tracer = this.observability().tracer;
|
|
2158
2813
|
return await tracer.span("diagnostics.run", {}, async () => {
|
|
2159
2814
|
const results = [];
|
|
2160
2815
|
const context = this.createContext(contentIndex);
|
|
@@ -2209,6 +2864,7 @@ var PluginRuntime = class {
|
|
|
2209
2864
|
return {
|
|
2210
2865
|
config: this.pipelineOptions.config,
|
|
2211
2866
|
contentIndex,
|
|
2867
|
+
contentSource: this.pipelineOptions.contentSource,
|
|
2212
2868
|
diagnostics: this.diagnostics
|
|
2213
2869
|
};
|
|
2214
2870
|
}
|
|
@@ -2258,27 +2914,33 @@ var PluginRuntime = class {
|
|
|
2258
2914
|
return this.pipelineOptions.observability ?? noopObservability;
|
|
2259
2915
|
}
|
|
2260
2916
|
plugins() {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
if (declaredPageTypes !== void 0 && !Array.isArray(declaredPageTypes)) {
|
|
2266
|
-
throw new TypeError(`Plugin ${plugin.name} pageTypes must be an array`);
|
|
2267
|
-
}
|
|
2268
|
-
for (const pageType of declaredPageTypes ?? []) {
|
|
2269
|
-
validatePageType(plugin.name, pageType);
|
|
2270
|
-
const previous = pageTypes.get(pageType.id);
|
|
2271
|
-
if (previous) {
|
|
2272
|
-
throw new Error(
|
|
2273
|
-
`Plugin page type "${pageType.id}" is provided by both ${previous} and ${plugin.name}`
|
|
2274
|
-
);
|
|
2275
|
-
}
|
|
2276
|
-
pageTypes.set(pageType.id, plugin.name);
|
|
2277
|
-
}
|
|
2917
|
+
if (this.resolvedPlugins === null) {
|
|
2918
|
+
const resolved = resolvePlugins(this.pipelineOptions.plugins);
|
|
2919
|
+
collectPageTypeOwners(resolved);
|
|
2920
|
+
this.resolvedPlugins = resolved;
|
|
2278
2921
|
}
|
|
2279
|
-
return
|
|
2922
|
+
return this.resolvedPlugins;
|
|
2280
2923
|
}
|
|
2281
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
|
+
}
|
|
2282
2944
|
function validatePageType(pluginName, pageType) {
|
|
2283
2945
|
if (typeof pageType !== "object" || pageType === null) {
|
|
2284
2946
|
throw new TypeError(`Plugin ${pluginName} pageTypes must contain objects`);
|
|
@@ -2304,24 +2966,48 @@ function validatePageType(pluginName, pageType) {
|
|
|
2304
2966
|
`Plugin ${pluginName} page type "${candidate.id}" priority must be a finite number`
|
|
2305
2967
|
);
|
|
2306
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}/`;
|
|
2307
2986
|
}
|
|
2308
2987
|
function normalizePagePath(pathname) {
|
|
2309
|
-
const
|
|
2310
|
-
return
|
|
2988
|
+
const path5 = `/${pathname.split("/").filter(Boolean).join("/")}`;
|
|
2989
|
+
return path5 === "/" ? path5 : path5.replace(/\/$/, "");
|
|
2311
2990
|
}
|
|
2312
2991
|
function countSeverity(diagnostics, severity) {
|
|
2313
2992
|
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
2314
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
|
+
}
|
|
2315
3001
|
|
|
2316
3002
|
// src/content/affected_content.ts
|
|
2317
|
-
function determineAffectedContent(changeSet, previousState, currentPaths) {
|
|
3003
|
+
function determineAffectedContent(changeSet, previousState, currentPaths, currentContentIndex) {
|
|
2318
3004
|
const changedPaths = [
|
|
2319
3005
|
...changeSet.added,
|
|
2320
3006
|
...changeSet.changed,
|
|
2321
3007
|
...changeSet.removed
|
|
2322
3008
|
];
|
|
2323
3009
|
const direct = new Set(
|
|
2324
|
-
changedPaths.filter(isMarkdownPath2).map(
|
|
3010
|
+
changedPaths.filter(isMarkdownPath2).map(toDependencyId)
|
|
2325
3011
|
);
|
|
2326
3012
|
const dependent = /* @__PURE__ */ new Set();
|
|
2327
3013
|
if (!previousState) {
|
|
@@ -2329,40 +3015,81 @@ function determineAffectedContent(changeSet, previousState, currentPaths) {
|
|
|
2329
3015
|
return { direct, dependent };
|
|
2330
3016
|
}
|
|
2331
3017
|
if (changeSet.added.length > 0 || changeSet.removed.length > 0) {
|
|
2332
|
-
|
|
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
|
+
}
|
|
2333
3031
|
}
|
|
2334
|
-
const
|
|
2335
|
-
const
|
|
3032
|
+
const dependentsByDependency = buildDependentsByDependency(previousState);
|
|
3033
|
+
const pending = changedPaths.map(toDependency);
|
|
3034
|
+
const seen = new Set(pending.map(dependencyKey2));
|
|
2336
3035
|
while (pending.length > 0) {
|
|
2337
3036
|
const changed = pending.pop();
|
|
2338
3037
|
if (changed === void 0) continue;
|
|
2339
|
-
for (const
|
|
2340
|
-
if (!isMarkdownPath2(path4)) continue;
|
|
2341
|
-
const slug = toDependencyKey(path4);
|
|
3038
|
+
for (const slug of dependentsByDependency.get(dependencyKey2(changed)) ?? []) {
|
|
2342
3039
|
if (direct.has(slug) || dependent.has(slug)) continue;
|
|
2343
|
-
if (!entry.dependencies.includes(changed)) continue;
|
|
2344
3040
|
dependent.add(slug);
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
3041
|
+
const dependency = { kind: "content", id: slug };
|
|
3042
|
+
if (!seen.has(dependencyKey2(dependency))) {
|
|
3043
|
+
seen.add(dependencyKey2(dependency));
|
|
3044
|
+
pending.push(dependency);
|
|
2348
3045
|
}
|
|
2349
3046
|
}
|
|
2350
3047
|
}
|
|
2351
3048
|
return { direct, dependent };
|
|
2352
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
|
+
}
|
|
2353
3060
|
function addAllNotes(target, currentPaths, exclude) {
|
|
2354
|
-
for (const
|
|
2355
|
-
if (!isMarkdownPath2(
|
|
2356
|
-
const slug =
|
|
3061
|
+
for (const path5 of currentPaths) {
|
|
3062
|
+
if (!isMarkdownPath2(path5)) continue;
|
|
3063
|
+
const slug = toDependencyId(path5);
|
|
2357
3064
|
if (exclude?.has(slug)) continue;
|
|
2358
3065
|
target.add(slug);
|
|
2359
3066
|
}
|
|
2360
3067
|
}
|
|
2361
|
-
function isMarkdownPath2(
|
|
2362
|
-
return
|
|
3068
|
+
function isMarkdownPath2(path5) {
|
|
3069
|
+
return path5.endsWith(".md");
|
|
3070
|
+
}
|
|
3071
|
+
function toDependencyId(path5) {
|
|
3072
|
+
return path5.replace(/\.md$/, "");
|
|
2363
3073
|
}
|
|
2364
|
-
function
|
|
2365
|
-
return
|
|
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;
|
|
3090
|
+
}
|
|
3091
|
+
function dependencyKey2(dependency) {
|
|
3092
|
+
return `${dependency.kind}:${dependency.id}`;
|
|
2366
3093
|
}
|
|
2367
3094
|
|
|
2368
3095
|
// src/content/content_change_set.ts
|
|
@@ -2373,18 +3100,18 @@ function diffContentEntries(previous, current) {
|
|
|
2373
3100
|
const added = [];
|
|
2374
3101
|
const changed = [];
|
|
2375
3102
|
const unchanged = [];
|
|
2376
|
-
for (const [
|
|
2377
|
-
const previousEntry = previous.entries[
|
|
3103
|
+
for (const [path5, fingerprint] of currentFingerprints) {
|
|
3104
|
+
const previousEntry = previous.entries[path5];
|
|
2378
3105
|
if (!previousEntry) {
|
|
2379
|
-
added.push(
|
|
3106
|
+
added.push(path5);
|
|
2380
3107
|
} else if (previousEntry.fingerprint !== fingerprint) {
|
|
2381
|
-
changed.push(
|
|
3108
|
+
changed.push(path5);
|
|
2382
3109
|
} else {
|
|
2383
|
-
unchanged.push(
|
|
3110
|
+
unchanged.push(path5);
|
|
2384
3111
|
}
|
|
2385
3112
|
}
|
|
2386
3113
|
const removed = Object.keys(previous.entries).filter(
|
|
2387
|
-
(
|
|
3114
|
+
(path5) => !currentFingerprints.has(path5)
|
|
2388
3115
|
);
|
|
2389
3116
|
return sortChangeSet({ added, changed, removed, unchanged });
|
|
2390
3117
|
}
|
|
@@ -2408,119 +3135,6 @@ function sortChangeSet(changeSet) {
|
|
|
2408
3135
|
};
|
|
2409
3136
|
}
|
|
2410
3137
|
|
|
2411
|
-
// src/content/content_build_coordinator.ts
|
|
2412
|
-
var ContentBuildCoordinator = class {
|
|
2413
|
-
constructor(dependencies) {
|
|
2414
|
-
this.dependencies = dependencies;
|
|
2415
|
-
}
|
|
2416
|
-
dependencies;
|
|
2417
|
-
preparation = null;
|
|
2418
|
-
getPreparation(incremental) {
|
|
2419
|
-
this.preparation ??= this.prepare(incremental);
|
|
2420
|
-
return this.preparation;
|
|
2421
|
-
}
|
|
2422
|
-
async commit(preparation, manifest) {
|
|
2423
|
-
const entriesBySlug = new Map(
|
|
2424
|
-
manifest.entries.map((entry) => [entry.slug, entry])
|
|
2425
|
-
);
|
|
2426
|
-
const state = {
|
|
2427
|
-
version: CONTENT_BUILD_STATE_VERSION,
|
|
2428
|
-
entries: Object.fromEntries(
|
|
2429
|
-
preparation.currentEntries.map(({ entry, fingerprint }) => {
|
|
2430
|
-
const manifestEntry = entriesBySlug.get(toSlug(entry.path));
|
|
2431
|
-
return [
|
|
2432
|
-
entry.path,
|
|
2433
|
-
{
|
|
2434
|
-
fingerprint,
|
|
2435
|
-
dependencies: collectDependencies2(manifestEntry)
|
|
2436
|
-
}
|
|
2437
|
-
];
|
|
2438
|
-
})
|
|
2439
|
-
),
|
|
2440
|
-
contentIndex: Object.fromEntries(
|
|
2441
|
-
[...manifest.contentIndex.entries()].sort(
|
|
2442
|
-
([left], [right]) => left.localeCompare(right)
|
|
2443
|
-
)
|
|
2444
|
-
)
|
|
2445
|
-
};
|
|
2446
|
-
try {
|
|
2447
|
-
await saveContentBuildState(this.dependencies.buildStatePath, state);
|
|
2448
|
-
} catch {
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
async prepare(incremental) {
|
|
2452
|
-
const currentEntries = await fingerprintContentEntries(
|
|
2453
|
-
await this.dependencies.getEntries(),
|
|
2454
|
-
(entry) => this.dependencies.readEntry(entry)
|
|
2455
|
-
);
|
|
2456
|
-
const previousState = incremental === false ? void 0 : await loadContentBuildState(this.dependencies.buildStatePath);
|
|
2457
|
-
const changeSet = previousState ? diffContentEntries(previousState, currentEntries) : allContentChanged(currentEntries);
|
|
2458
|
-
const affected = determineAffectedContent(
|
|
2459
|
-
changeSet,
|
|
2460
|
-
previousState,
|
|
2461
|
-
currentEntries.map(({ entry }) => entry.path)
|
|
2462
|
-
);
|
|
2463
|
-
this.dependencies.observability.tracer.event("build.incremental", {
|
|
2464
|
-
incremental: incremental !== false,
|
|
2465
|
-
added: changeSet.added.length,
|
|
2466
|
-
changed: changeSet.changed.length,
|
|
2467
|
-
removed: changeSet.removed.length,
|
|
2468
|
-
unchanged: changeSet.unchanged.length,
|
|
2469
|
-
affected: affected.direct.size + affected.dependent.size
|
|
2470
|
-
});
|
|
2471
|
-
return { previousState, currentEntries, changeSet };
|
|
2472
|
-
}
|
|
2473
|
-
};
|
|
2474
|
-
function toSlug(path4) {
|
|
2475
|
-
return path4.replace(/\.md$/, "");
|
|
2476
|
-
}
|
|
2477
|
-
function collectDependencies2(manifestEntry) {
|
|
2478
|
-
if (!manifestEntry) return [];
|
|
2479
|
-
return [
|
|
2480
|
-
...new Set(
|
|
2481
|
-
manifestEntry.links.filter(
|
|
2482
|
-
(link) => link.slug !== null
|
|
2483
|
-
).map((link) => link.slug)
|
|
2484
|
-
)
|
|
2485
|
-
].sort();
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
// src/content/content_entry_reader.ts
|
|
2489
|
-
var ContentEntryReader = class {
|
|
2490
|
-
constructor(source, observability) {
|
|
2491
|
-
this.source = source;
|
|
2492
|
-
this.observability = observability;
|
|
2493
|
-
}
|
|
2494
|
-
source;
|
|
2495
|
-
observability;
|
|
2496
|
-
entries = null;
|
|
2497
|
-
texts = /* @__PURE__ */ new Map();
|
|
2498
|
-
async getEntries() {
|
|
2499
|
-
this.entries ??= await this.observability.tracer.span(
|
|
2500
|
-
"content.scan",
|
|
2501
|
-
{},
|
|
2502
|
-
() => this.source.scan()
|
|
2503
|
-
);
|
|
2504
|
-
return this.entries;
|
|
2505
|
-
}
|
|
2506
|
-
async readText(logicalPath) {
|
|
2507
|
-
const entry = (await this.getEntries()).find(
|
|
2508
|
-
(current) => current.path === logicalPath
|
|
2509
|
-
);
|
|
2510
|
-
if (!entry) throw new Error(`Content entry was not found: ${logicalPath}`);
|
|
2511
|
-
return await this.read(entry);
|
|
2512
|
-
}
|
|
2513
|
-
read(entry) {
|
|
2514
|
-
const cached = this.texts.get(entry.path);
|
|
2515
|
-
if (cached) return cached;
|
|
2516
|
-
const content = this.source.read(entry).then(
|
|
2517
|
-
(value) => typeof value === "string" ? value : new TextDecoder().decode(value)
|
|
2518
|
-
);
|
|
2519
|
-
this.texts.set(entry.path, content);
|
|
2520
|
-
return content;
|
|
2521
|
-
}
|
|
2522
|
-
};
|
|
2523
|
-
|
|
2524
3138
|
// src/utils/tags.ts
|
|
2525
3139
|
function normalizeTag(raw) {
|
|
2526
3140
|
const cleaned = raw.replace(/[/-]+$/, "");
|
|
@@ -2581,7 +3195,7 @@ function stripYamlQuotes(value) {
|
|
|
2581
3195
|
}
|
|
2582
3196
|
|
|
2583
3197
|
// src/content/content_index_builder.ts
|
|
2584
|
-
var ContentIndexBuilder = class {
|
|
3198
|
+
var ContentIndexBuilder = class _ContentIndexBuilder {
|
|
2585
3199
|
constructor(source) {
|
|
2586
3200
|
this.source = source;
|
|
2587
3201
|
}
|
|
@@ -2593,7 +3207,26 @@ var ContentIndexBuilder = class {
|
|
|
2593
3207
|
}
|
|
2594
3208
|
return index;
|
|
2595
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
|
+
}
|
|
2596
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) {
|
|
2597
3230
|
const contentPath = contentEntry.path;
|
|
2598
3231
|
const ext = contentPath.split(".").pop()?.toLowerCase() ?? "";
|
|
2599
3232
|
const value = ext === "md" ? contentPath.replace(/\.md$/, "") : contentPath;
|
|
@@ -2606,20 +3239,246 @@ var ContentIndexBuilder = class {
|
|
|
2606
3239
|
}
|
|
2607
3240
|
}
|
|
2608
3241
|
if (ext === "md") {
|
|
2609
|
-
const
|
|
2610
|
-
|
|
2611
|
-
|
|
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;
|
|
2612
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;
|
|
2613
3464
|
}
|
|
2614
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
|
+
}
|
|
2615
3481
|
};
|
|
2616
|
-
function readText(content) {
|
|
2617
|
-
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
2618
|
-
}
|
|
2619
|
-
function addIndexEntry(index, key, value) {
|
|
2620
|
-
const normalizedKey = key.toLowerCase();
|
|
2621
|
-
if (!index.has(normalizedKey)) index.set(normalizedKey, value);
|
|
2622
|
-
}
|
|
2623
3482
|
|
|
2624
3483
|
// src/content/content_location_resolver.ts
|
|
2625
3484
|
var ContentLocationResolver = class {
|
|
@@ -2628,19 +3487,37 @@ var ContentLocationResolver = class {
|
|
|
2628
3487
|
}
|
|
2629
3488
|
dependencies;
|
|
2630
3489
|
locations = null;
|
|
3490
|
+
locationsPromise = null;
|
|
3491
|
+
permalinks = null;
|
|
3492
|
+
permalinksPromise = null;
|
|
2631
3493
|
async getLocations() {
|
|
2632
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() {
|
|
2633
3508
|
const [entries, contentIndex] = await Promise.all([
|
|
2634
3509
|
this.dependencies.getEntries(),
|
|
2635
3510
|
this.dependencies.getContentIndex()
|
|
2636
3511
|
]);
|
|
2637
3512
|
await this.dependencies.pluginRuntime.startBuild(contentIndex);
|
|
2638
|
-
const inputs = await
|
|
2639
|
-
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) => ({
|
|
2640
3517
|
slug: toSlug2(entry.path),
|
|
2641
3518
|
path: entry.path,
|
|
2642
3519
|
markdown: await this.dependencies.readEntry(entry)
|
|
2643
|
-
})
|
|
3520
|
+
})
|
|
2644
3521
|
);
|
|
2645
3522
|
const locations = new Map(
|
|
2646
3523
|
inputs.map((input) => [input.slug, resolveDefaultContentLocation(input)])
|
|
@@ -2665,6 +3542,17 @@ var ContentLocationResolver = class {
|
|
|
2665
3542
|
return locations;
|
|
2666
3543
|
}
|
|
2667
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() {
|
|
2668
3556
|
return new Map(
|
|
2669
3557
|
Array.from(
|
|
2670
3558
|
(await this.getLocations()).entries(),
|
|
@@ -2680,13 +3568,45 @@ var ContentLocationResolver = class {
|
|
|
2680
3568
|
}
|
|
2681
3569
|
}
|
|
2682
3570
|
};
|
|
2683
|
-
function toSlug2(
|
|
2684
|
-
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;
|
|
2685
3592
|
}
|
|
2686
3593
|
|
|
2687
3594
|
// src/content/file_system_content_source.ts
|
|
2688
|
-
import
|
|
2689
|
-
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
|
+
];
|
|
2690
3610
|
var FileSystemContentSource = class {
|
|
2691
3611
|
constructor(contentDirectory, exclude = []) {
|
|
2692
3612
|
this.contentDirectory = contentDirectory;
|
|
@@ -2695,33 +3615,41 @@ var FileSystemContentSource = class {
|
|
|
2695
3615
|
contentDirectory;
|
|
2696
3616
|
exclude;
|
|
2697
3617
|
async scan() {
|
|
2698
|
-
|
|
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
|
+
}
|
|
2699
3626
|
}
|
|
2700
3627
|
async read(entry) {
|
|
2701
3628
|
const filePath = this.resolveFilePath(entry.path);
|
|
2702
|
-
const stats = await
|
|
3629
|
+
const stats = await fs4.lstat(filePath);
|
|
2703
3630
|
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
2704
3631
|
throw new Error(`Content entry is not a regular file: ${entry.path}`);
|
|
2705
3632
|
}
|
|
2706
|
-
return await
|
|
3633
|
+
return await fs4.readFile(filePath);
|
|
2707
3634
|
}
|
|
2708
|
-
async scanDirectory(directory,
|
|
2709
|
-
const directoryEntries = await
|
|
3635
|
+
async scanDirectory(directory, parentPath2) {
|
|
3636
|
+
const directoryEntries = await fs4.readdir(directory, {
|
|
2710
3637
|
withFileTypes: true
|
|
2711
3638
|
});
|
|
2712
3639
|
const entries = [];
|
|
2713
3640
|
for (const directoryEntry of directoryEntries) {
|
|
2714
3641
|
if (directoryEntry.isSymbolicLink()) continue;
|
|
2715
3642
|
const logicalPath = normalizeContentPath(
|
|
2716
|
-
|
|
3643
|
+
parentPath2 ? `${parentPath2}/${directoryEntry.name}` : directoryEntry.name
|
|
2717
3644
|
);
|
|
2718
|
-
const filePath =
|
|
3645
|
+
const filePath = path4.join(directory, directoryEntry.name);
|
|
3646
|
+
if (this.isExcluded(logicalPath)) continue;
|
|
2719
3647
|
if (directoryEntry.isDirectory()) {
|
|
2720
3648
|
entries.push(...await this.scanDirectory(filePath, logicalPath));
|
|
2721
3649
|
continue;
|
|
2722
3650
|
}
|
|
2723
|
-
if (!directoryEntry.isFile()
|
|
2724
|
-
const stats = await
|
|
3651
|
+
if (!directoryEntry.isFile()) continue;
|
|
3652
|
+
const stats = await fs4.stat(filePath);
|
|
2725
3653
|
entries.push({
|
|
2726
3654
|
path: logicalPath,
|
|
2727
3655
|
metadata: {
|
|
@@ -2733,26 +3661,44 @@ var FileSystemContentSource = class {
|
|
|
2733
3661
|
return entries;
|
|
2734
3662
|
}
|
|
2735
3663
|
isExcluded(logicalPath) {
|
|
2736
|
-
|
|
2737
|
-
return logicalPath.endsWith(".md") && isExcluded(this.exclude, logicalPath.slice(0, -3));
|
|
3664
|
+
return isIgnoredContentPath(logicalPath, this.exclude);
|
|
2738
3665
|
}
|
|
2739
3666
|
resolveFilePath(logicalPath) {
|
|
2740
3667
|
const normalizedPath = normalizeLogicalPath(logicalPath);
|
|
2741
|
-
const filePath =
|
|
2742
|
-
const relativePath =
|
|
2743
|
-
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)) {
|
|
2744
3671
|
throw new Error(`Invalid content path: ${logicalPath}`);
|
|
2745
3672
|
}
|
|
2746
3673
|
return filePath;
|
|
2747
3674
|
}
|
|
2748
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
|
+
}
|
|
2749
3681
|
function normalizeLogicalPath(logicalPath) {
|
|
2750
3682
|
const normalizedPath = normalizeContentPath(logicalPath);
|
|
2751
|
-
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 === "..")) {
|
|
2752
3684
|
throw new Error(`Invalid content path: ${logicalPath}`);
|
|
2753
3685
|
}
|
|
2754
3686
|
return normalizedPath;
|
|
2755
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
|
+
}
|
|
2756
3702
|
|
|
2757
3703
|
// src/content/content_links.ts
|
|
2758
3704
|
var WIKILINK_PATTERN = /(!)?\[\[([^\]|#^]+)(?:[#^][^\]|]+)?(?:\|[^\]]+)?\]\]/g;
|
|
@@ -2808,7 +3754,7 @@ function readOptionalStableId(value, field) {
|
|
|
2808
3754
|
|
|
2809
3755
|
// src/content/manifest_builder.ts
|
|
2810
3756
|
var ManifestBuilder = class {
|
|
2811
|
-
createEntry(slug, markdown, processed, contentIndex, location) {
|
|
3757
|
+
createEntry(slug, markdown, processed, contentIndex, location, publishing) {
|
|
2812
3758
|
const links = extractContentLinks(markdown, contentIndex);
|
|
2813
3759
|
const assets = links.filter(
|
|
2814
3760
|
(link) => (link.kind === "image" || link.kind === "attachment") && link.slug !== null
|
|
@@ -2821,6 +3767,7 @@ var ManifestBuilder = class {
|
|
|
2821
3767
|
publicLocation: location,
|
|
2822
3768
|
title: getManifestTitle(slug, processed.frontmatter.title),
|
|
2823
3769
|
frontmatter: processed.frontmatter,
|
|
3770
|
+
publishing,
|
|
2824
3771
|
html: processed.html,
|
|
2825
3772
|
tags: uniqueStrings([
|
|
2826
3773
|
...normalizeFrontmatterTags(processed.frontmatter.tags),
|
|
@@ -2858,9 +3805,11 @@ var ManifestBuilder = class {
|
|
|
2858
3805
|
const manifest = {
|
|
2859
3806
|
entries,
|
|
2860
3807
|
publicEntries: [...entries],
|
|
3808
|
+
discoverableEntries: [...entries],
|
|
2861
3809
|
bySlug,
|
|
2862
3810
|
byContentId,
|
|
2863
3811
|
byPermalink,
|
|
3812
|
+
byRoutablePermalink: new Map(byPermalink),
|
|
2864
3813
|
redirects: /* @__PURE__ */ new Map(),
|
|
2865
3814
|
publicRedirects: /* @__PURE__ */ new Map(),
|
|
2866
3815
|
byTag,
|
|
@@ -2872,6 +3821,8 @@ var ManifestBuilder = class {
|
|
|
2872
3821
|
clientEntries: [],
|
|
2873
3822
|
diagnostics: [],
|
|
2874
3823
|
generatedOutputs: [],
|
|
3824
|
+
folderLocations: /* @__PURE__ */ new Map(),
|
|
3825
|
+
pageRoutes: [],
|
|
2875
3826
|
pagePaths: []
|
|
2876
3827
|
};
|
|
2877
3828
|
return { ...manifest, graph: createContentGraph(manifest) };
|
|
@@ -2913,21 +3864,283 @@ function appendUniqueToMap(map, key, value) {
|
|
|
2913
3864
|
map.set(key, values);
|
|
2914
3865
|
}
|
|
2915
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
|
+
|
|
2916
4121
|
// src/content/content_manager.ts
|
|
4122
|
+
var CONTENT_PROCESSING_CONCURRENCY = 16;
|
|
2917
4123
|
var ContentManager = class {
|
|
2918
4124
|
constructor(content, exclude = [], pipelineOptions = {}) {
|
|
2919
4125
|
this.pipelineOptions = pipelineOptions;
|
|
2920
4126
|
this.pipelineOptions = {
|
|
2921
4127
|
...pipelineOptions,
|
|
2922
|
-
plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins
|
|
4128
|
+
plugins: pipelineOptions.plugins ?? pipelineOptions.config?.plugins,
|
|
4129
|
+
isRoutable: (slug) => this.isRoutable(slug)
|
|
2923
4130
|
};
|
|
4131
|
+
this.publishingBuildTime = resolvePublishingBuildTime(
|
|
4132
|
+
pipelineOptions.publishingBuildTime
|
|
4133
|
+
);
|
|
2924
4134
|
this.source = typeof content === "string" ? pipelineOptions.config?.content.source ?? new FileSystemContentSource(content, [
|
|
2925
4135
|
...exclude,
|
|
2926
4136
|
CONTENT_BUILD_STATE_EXCLUDE
|
|
2927
4137
|
]) : content;
|
|
2928
4138
|
this.pipelineOptions = {
|
|
2929
4139
|
...this.pipelineOptions,
|
|
2930
|
-
contentSource: this.source
|
|
4140
|
+
contentSource: this.source,
|
|
4141
|
+
onContentDependencies: (slug, dependencies) => {
|
|
4142
|
+
this.trackedContentDependencies.set(slug, dependencies);
|
|
4143
|
+
}
|
|
2931
4144
|
};
|
|
2932
4145
|
this.contentIndexBuilder = new ContentIndexBuilder(this.source);
|
|
2933
4146
|
this.pluginRuntime = new PluginRuntime(this.pipelineOptions);
|
|
@@ -2941,7 +4154,8 @@ var ContentManager = class {
|
|
|
2941
4154
|
getEntries: () => this.entryReader.getEntries(),
|
|
2942
4155
|
readEntry: (entry) => this.entryReader.read(entry),
|
|
2943
4156
|
getContentIndex: () => this.getContentIndex(),
|
|
2944
|
-
pluginRuntime: this.pluginRuntime
|
|
4157
|
+
pluginRuntime: this.pluginRuntime,
|
|
4158
|
+
observability
|
|
2945
4159
|
});
|
|
2946
4160
|
this.buildCoordinator = new ContentBuildCoordinator({
|
|
2947
4161
|
buildStatePath,
|
|
@@ -2956,13 +4170,22 @@ var ContentManager = class {
|
|
|
2956
4170
|
manifestBuilder = new ManifestBuilder();
|
|
2957
4171
|
pluginRuntime;
|
|
2958
4172
|
contentIndex = null;
|
|
4173
|
+
contentIndexPromise = null;
|
|
2959
4174
|
contentCache = /* @__PURE__ */ new Map();
|
|
2960
4175
|
manifest = null;
|
|
4176
|
+
manifestPromise = null;
|
|
4177
|
+
pipelineFingerprint = null;
|
|
2961
4178
|
pipeline = null;
|
|
2962
4179
|
entryReader;
|
|
2963
4180
|
locationResolver;
|
|
2964
4181
|
buildCoordinator;
|
|
2965
4182
|
isBuildTime = false;
|
|
4183
|
+
publishingBuildTime;
|
|
4184
|
+
routableSlugs = null;
|
|
4185
|
+
routableSlugsPromise = null;
|
|
4186
|
+
processedContentCount = 0;
|
|
4187
|
+
outputChangeSet = null;
|
|
4188
|
+
trackedContentDependencies = /* @__PURE__ */ new Map();
|
|
2966
4189
|
async getAllPosts() {
|
|
2967
4190
|
return (await this.scan()).filter((entry) => entry.path.endsWith(".md")).map((entry) => ({ slug: entry.path.replace(/\.md$/, "") }));
|
|
2968
4191
|
}
|
|
@@ -2974,17 +4197,33 @@ var ContentManager = class {
|
|
|
2974
4197
|
}
|
|
2975
4198
|
async getContentIndex(preparation) {
|
|
2976
4199
|
if (this.contentIndex) return this.contentIndex;
|
|
4200
|
+
if (!preparation && this.contentIndexPromise)
|
|
4201
|
+
return await this.contentIndexPromise;
|
|
2977
4202
|
if (preparation?.previousState && !hasContentChanges(preparation.changeSet)) {
|
|
2978
4203
|
this.contentIndex = new Map(
|
|
2979
4204
|
Object.entries(preparation.previousState.contentIndex)
|
|
2980
4205
|
);
|
|
2981
4206
|
return this.contentIndex;
|
|
2982
4207
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
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
|
+
)
|
|
2986
4219
|
);
|
|
2987
|
-
|
|
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
|
+
}
|
|
2988
4227
|
}
|
|
2989
4228
|
async getProcessedContent(slug) {
|
|
2990
4229
|
const cached = this.contentCache.get(slug);
|
|
@@ -3001,6 +4240,7 @@ var ContentManager = class {
|
|
|
3001
4240
|
]);
|
|
3002
4241
|
await this.pluginRuntime.startBuild(contentIndex);
|
|
3003
4242
|
await this.pluginRuntime.runContentLoaded(slug, rawPost, contentIndex);
|
|
4243
|
+
await this.ensureRoutableSlugs();
|
|
3004
4244
|
this.pipeline ??= new Pipeline(
|
|
3005
4245
|
contentIndex,
|
|
3006
4246
|
await this.locationResolver.getPermalinks(),
|
|
@@ -3011,6 +4251,12 @@ var ContentManager = class {
|
|
|
3011
4251
|
const content = await this.pipeline.execute(rawPost, {
|
|
3012
4252
|
sourceSlug: slug
|
|
3013
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
|
+
}
|
|
3014
4260
|
await this.pluginRuntime.runPostHook(
|
|
3015
4261
|
"onPostParsed",
|
|
3016
4262
|
slug,
|
|
@@ -3031,9 +4277,24 @@ var ContentManager = class {
|
|
|
3031
4277
|
);
|
|
3032
4278
|
}
|
|
3033
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) {
|
|
3034
4291
|
if (this.manifest) return this.manifest;
|
|
3035
4292
|
this.enableBuildTime();
|
|
3036
|
-
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;
|
|
3037
4298
|
return await this.observability().tracer.span(
|
|
3038
4299
|
"content.manifest",
|
|
3039
4300
|
{},
|
|
@@ -3043,26 +4304,38 @@ var ContentManager = class {
|
|
|
3043
4304
|
this.getContentIndex(preparation),
|
|
3044
4305
|
this.getContentLocations()
|
|
3045
4306
|
]);
|
|
3046
|
-
const entries = await
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
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
|
+
})
|
|
3056
4336
|
);
|
|
3057
4337
|
}
|
|
3058
|
-
|
|
3059
|
-
post.slug,
|
|
3060
|
-
rawPost,
|
|
3061
|
-
processed,
|
|
3062
|
-
contentIndex,
|
|
3063
|
-
location
|
|
3064
|
-
);
|
|
3065
|
-
})
|
|
4338
|
+
)
|
|
3066
4339
|
);
|
|
3067
4340
|
await this.pluginRuntime.runGraphHook(entries, contentIndex);
|
|
3068
4341
|
const manifest = await this.observability().tracer.span(
|
|
@@ -3070,12 +4343,17 @@ var ContentManager = class {
|
|
|
3070
4343
|
{},
|
|
3071
4344
|
() => this.manifestBuilder.build(entries, contentIndex)
|
|
3072
4345
|
);
|
|
4346
|
+
const cacheManifestEntries = structuredClone(manifest.entries);
|
|
3073
4347
|
this.locationResolver.populateRedirects(manifest, locations);
|
|
3074
|
-
await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
|
|
3075
4348
|
this.applyPublicView(manifest);
|
|
3076
4349
|
manifest.pagePaths = [
|
|
3077
4350
|
...await this.pluginRuntime.getPagePaths(manifest, contentIndex)
|
|
3078
4351
|
];
|
|
4352
|
+
manifest.pageRoutes = [
|
|
4353
|
+
...await this.pluginRuntime.getPageRoutes(manifest, contentIndex)
|
|
4354
|
+
];
|
|
4355
|
+
await this.pluginRuntime.runManifestCreated(manifest, contentIndex);
|
|
4356
|
+
this.synchronizeManifestHtml(manifest, cacheManifestEntries);
|
|
3079
4357
|
manifest.assets = this.pluginRuntime.collectAssets();
|
|
3080
4358
|
manifest.clientEntries = this.pluginRuntime.collectClientEntries();
|
|
3081
4359
|
manifest.diagnostics = [
|
|
@@ -3084,8 +4362,39 @@ var ContentManager = class {
|
|
|
3084
4362
|
];
|
|
3085
4363
|
await this.pluginRuntime.runBuildEnd(manifest, contentIndex);
|
|
3086
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
|
+
}
|
|
3087
4386
|
if (preparation)
|
|
3088
|
-
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
|
+
);
|
|
3089
4398
|
this.manifest = manifest;
|
|
3090
4399
|
return manifest;
|
|
3091
4400
|
}
|
|
@@ -3094,6 +4403,34 @@ var ContentManager = class {
|
|
|
3094
4403
|
async build(options = {}) {
|
|
3095
4404
|
return await this.getManifest(options);
|
|
3096
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
|
+
}
|
|
3097
4434
|
async getBacklinks(targetSlug) {
|
|
3098
4435
|
const graph = await this.getContentGraph();
|
|
3099
4436
|
return graph.incomingSlugs(targetSlug).map((slug) => ({
|
|
@@ -3148,19 +4485,106 @@ var ContentManager = class {
|
|
|
3148
4485
|
return await this.locationResolver.getLocations();
|
|
3149
4486
|
}
|
|
3150
4487
|
applyPublicView(manifest) {
|
|
3151
|
-
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
|
+
}
|
|
3152
4495
|
manifest.publicEntries = manifest.entries.filter(
|
|
3153
|
-
(entry) =>
|
|
4496
|
+
(entry) => entry.publishing.routable
|
|
4497
|
+
);
|
|
4498
|
+
manifest.discoverableEntries = manifest.entries.filter(
|
|
4499
|
+
(entry) => entry.publishing.discoverable
|
|
3154
4500
|
);
|
|
3155
4501
|
const publicSlugs = new Set(
|
|
3156
4502
|
manifest.publicEntries.map((entry) => entry.slug)
|
|
3157
4503
|
);
|
|
4504
|
+
const publicPermalinks = new Map(
|
|
4505
|
+
manifest.publicEntries.map((entry) => [entry.permalink, entry])
|
|
4506
|
+
);
|
|
4507
|
+
manifest.byRoutablePermalink = publicPermalinks;
|
|
3158
4508
|
manifest.publicRedirects = new Map(
|
|
3159
4509
|
[...manifest.redirects].filter(
|
|
3160
4510
|
([, redirect]) => publicSlugs.has(redirect.slug)
|
|
3161
4511
|
)
|
|
3162
4512
|
);
|
|
3163
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
|
+
}
|
|
3164
4588
|
enableBuildTime() {
|
|
3165
4589
|
this.isBuildTime = true;
|
|
3166
4590
|
this.pluginRuntime.enableBuildTime();
|
|
@@ -3169,6 +4593,43 @@ var ContentManager = class {
|
|
|
3169
4593
|
return this.pipelineOptions.observability ?? noopObservability;
|
|
3170
4594
|
}
|
|
3171
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
|
+
}
|
|
3172
4633
|
|
|
3173
4634
|
// src/content/content_source.ts
|
|
3174
4635
|
async function readContentSourceEntry(source, logicalPath) {
|
|
@@ -3179,6 +4640,74 @@ async function getContentSourceEntry(source, logicalPath) {
|
|
|
3179
4640
|
return (await source.scan()).find((candidate) => candidate.path === logicalPath) ?? null;
|
|
3180
4641
|
}
|
|
3181
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
|
+
|
|
3182
4711
|
// src/content/graph_layout.ts
|
|
3183
4712
|
function buildGraphEdges(nodes, visibleSlugs = new Set(nodes.map((node) => node.slug))) {
|
|
3184
4713
|
const edges = /* @__PURE__ */ new Map();
|
|
@@ -3215,9 +4744,143 @@ function layoutRadialGraph(nodes, options) {
|
|
|
3215
4744
|
});
|
|
3216
4745
|
return layout;
|
|
3217
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
|
+
}
|
|
3218
4877
|
function clamp(value, min, max) {
|
|
3219
4878
|
return Math.min(Math.max(value, min), max);
|
|
3220
4879
|
}
|
|
4880
|
+
function clampFinite(value, min, max) {
|
|
4881
|
+
if (!Number.isFinite(value)) return min;
|
|
4882
|
+
return clamp(value, min, max);
|
|
4883
|
+
}
|
|
3221
4884
|
|
|
3222
4885
|
// src/content/read_only_content_graph.ts
|
|
3223
4886
|
async function readOnlyContentGraph(source, locations) {
|
|
@@ -3227,7 +4890,7 @@ async function readOnlyContentGraph(source, locations) {
|
|
|
3227
4890
|
const markdownContents = await Promise.all(
|
|
3228
4891
|
markdownEntries.map(async (entry) => ({
|
|
3229
4892
|
entry,
|
|
3230
|
-
markdown: await
|
|
4893
|
+
markdown: await readText3(source, entry)
|
|
3231
4894
|
}))
|
|
3232
4895
|
);
|
|
3233
4896
|
const graphEntries = markdownContents.map(
|
|
@@ -3247,6 +4910,13 @@ function createGraphEntry(entry, markdown, contentIndex, locations) {
|
|
|
3247
4910
|
publicLocation: location,
|
|
3248
4911
|
title: slug,
|
|
3249
4912
|
frontmatter: {},
|
|
4913
|
+
publishing: resolvePublishingState(
|
|
4914
|
+
{},
|
|
4915
|
+
{
|
|
4916
|
+
strategy: "selective",
|
|
4917
|
+
buildTime: /* @__PURE__ */ new Date(0)
|
|
4918
|
+
}
|
|
4919
|
+
),
|
|
3250
4920
|
html: "",
|
|
3251
4921
|
tags: [],
|
|
3252
4922
|
links: extractContentLinks(markdown, contentIndex),
|
|
@@ -3254,60 +4924,11 @@ function createGraphEntry(entry, markdown, contentIndex, locations) {
|
|
|
3254
4924
|
assets: []
|
|
3255
4925
|
};
|
|
3256
4926
|
}
|
|
3257
|
-
async function
|
|
4927
|
+
async function readText3(source, entry) {
|
|
3258
4928
|
const content = await source.read(entry);
|
|
3259
4929
|
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
3260
4930
|
}
|
|
3261
4931
|
|
|
3262
|
-
// src/plugin/plugin_memo.ts
|
|
3263
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
3264
|
-
function createPluginMemo(cache, options = {}) {
|
|
3265
|
-
return {
|
|
3266
|
-
async memo(key, dependencies, compute) {
|
|
3267
|
-
const memoKey = `${key}:${hash2(stableStringify(dependencies))}`;
|
|
3268
|
-
try {
|
|
3269
|
-
const cached = await cache.get(memoKey);
|
|
3270
|
-
if (cached !== void 0) return cached;
|
|
3271
|
-
} catch (error) {
|
|
3272
|
-
warn(
|
|
3273
|
-
options.logger,
|
|
3274
|
-
"Plugin cache read failed; recomputing.",
|
|
3275
|
-
key,
|
|
3276
|
-
error
|
|
3277
|
-
);
|
|
3278
|
-
}
|
|
3279
|
-
const value = await compute();
|
|
3280
|
-
try {
|
|
3281
|
-
await cache.set(memoKey, value);
|
|
3282
|
-
} catch (error) {
|
|
3283
|
-
warn(
|
|
3284
|
-
options.logger,
|
|
3285
|
-
"Plugin cache write failed; result not cached.",
|
|
3286
|
-
key,
|
|
3287
|
-
error
|
|
3288
|
-
);
|
|
3289
|
-
}
|
|
3290
|
-
return value;
|
|
3291
|
-
}
|
|
3292
|
-
};
|
|
3293
|
-
}
|
|
3294
|
-
function stableStringify(value) {
|
|
3295
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3296
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
3297
|
-
const keys = Object.keys(value).sort();
|
|
3298
|
-
const body = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",");
|
|
3299
|
-
return `{${body}}`;
|
|
3300
|
-
}
|
|
3301
|
-
function hash2(value) {
|
|
3302
|
-
return createHash3("sha256").update(value).digest("hex");
|
|
3303
|
-
}
|
|
3304
|
-
function warn(logger, message, key, error) {
|
|
3305
|
-
logger?.warn(message, {
|
|
3306
|
-
memoKey: key,
|
|
3307
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3308
|
-
});
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
4932
|
// src/types/content_manifest.ts
|
|
3312
4933
|
function appendContentBodySlot(entry, slot, html) {
|
|
3313
4934
|
if (!html.trim()) return;
|
|
@@ -3320,12 +4941,12 @@ ${html}` : html
|
|
|
3320
4941
|
}
|
|
3321
4942
|
|
|
3322
4943
|
// src/types/plugin_endpoint.ts
|
|
3323
|
-
function defineEndpoint(
|
|
4944
|
+
function defineEndpoint(path5, handler, options = {}) {
|
|
3324
4945
|
if (options.cacheControl === void 0) {
|
|
3325
|
-
return { path:
|
|
4946
|
+
return { path: path5, handler };
|
|
3326
4947
|
}
|
|
3327
4948
|
return {
|
|
3328
|
-
path:
|
|
4949
|
+
path: path5,
|
|
3329
4950
|
handler: async (context) => {
|
|
3330
4951
|
const response = await handler(context);
|
|
3331
4952
|
const headers = { ...response.headers };
|
|
@@ -3367,6 +4988,7 @@ export {
|
|
|
3367
4988
|
ContentManager,
|
|
3368
4989
|
FileSystemContentSource,
|
|
3369
4990
|
IMAGE_EXTENSIONS,
|
|
4991
|
+
INTERNAL_CONTENT_IGNORE_PATTERNS,
|
|
3370
4992
|
NoopLogger,
|
|
3371
4993
|
NoopTracer,
|
|
3372
4994
|
Pipeline,
|
|
@@ -3376,6 +4998,7 @@ export {
|
|
|
3376
4998
|
attachmentUrl,
|
|
3377
4999
|
buildContentCollections,
|
|
3378
5000
|
buildGraphEdges,
|
|
5001
|
+
buildOutputInventory,
|
|
3379
5002
|
calculateReadingTime,
|
|
3380
5003
|
collectConfigDeprecationDiagnostics,
|
|
3381
5004
|
createClientEntry,
|
|
@@ -3388,6 +5011,7 @@ export {
|
|
|
3388
5011
|
defineEndpoint,
|
|
3389
5012
|
definePlugin,
|
|
3390
5013
|
defineTheme,
|
|
5014
|
+
determineOutputChanges,
|
|
3391
5015
|
escapeHtml,
|
|
3392
5016
|
escapeHtmlAttribute,
|
|
3393
5017
|
escapeScriptJson,
|
|
@@ -3398,14 +5022,18 @@ export {
|
|
|
3398
5022
|
getExtension,
|
|
3399
5023
|
getResolvedPluginMetadata,
|
|
3400
5024
|
groupContentEntries,
|
|
5025
|
+
htmlOutputPath,
|
|
3401
5026
|
isAttachmentPath,
|
|
3402
5027
|
isExcluded,
|
|
5028
|
+
isIgnoredContentPath,
|
|
3403
5029
|
isImagePath,
|
|
3404
5030
|
isMarkdownPath,
|
|
3405
5031
|
isPublishable,
|
|
3406
5032
|
isPublished,
|
|
5033
|
+
layoutForceGraph,
|
|
3407
5034
|
layoutRadialGraph,
|
|
3408
5035
|
loadContentBuildState,
|
|
5036
|
+
normalizeContentAssetOutputPath,
|
|
3409
5037
|
normalizeContentPath,
|
|
3410
5038
|
normalizeGeneratedOutputPath,
|
|
3411
5039
|
normalizeTag,
|
|
@@ -3420,7 +5048,12 @@ export {
|
|
|
3420
5048
|
resolveContentQueryPagination,
|
|
3421
5049
|
resolveContentStableId,
|
|
3422
5050
|
resolveDefaultContentLocation,
|
|
5051
|
+
resolveFolderEntry,
|
|
5052
|
+
resolveFolderLocation,
|
|
5053
|
+
resolveGeneratedFolderLocation,
|
|
3423
5054
|
resolvePlugins,
|
|
5055
|
+
resolvePublishingBuildTime,
|
|
5056
|
+
resolvePublishingState,
|
|
3424
5057
|
serializePublicClientConfig,
|
|
3425
5058
|
stableStringify,
|
|
3426
5059
|
stripHtml,
|