@canopy-iiif/app 1.5.17 → 1.6.1
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/lib/build/build.js +129 -69
- package/lib/build/iiif.js +366 -261
- package/lib/orchestrator.js +9 -0
- package/lib/search/search.js +27 -0
- package/package.json +1 -1
package/lib/build/build.js
CHANGED
|
@@ -11,22 +11,21 @@ const mdx = require("./mdx");
|
|
|
11
11
|
const iiif = require("./iiif");
|
|
12
12
|
const pages = require("./pages");
|
|
13
13
|
const searchBuild = require("./search");
|
|
14
|
-
const {
|
|
14
|
+
const {buildSearchIndex} = require("./search-index");
|
|
15
15
|
const runtimes = require("./runtimes");
|
|
16
|
-
const {
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} = require("./
|
|
21
|
-
const { ensureStyles } = require("./styles");
|
|
22
|
-
const { copyAssets } = require("./assets");
|
|
23
|
-
const { logLine } = require("./log");
|
|
16
|
+
const {writeSitemap} = require("./sitemap");
|
|
17
|
+
const {ensureSearchInitialized, finalizeSearch} = require("./search-workflow");
|
|
18
|
+
const {ensureStyles} = require("./styles");
|
|
19
|
+
const {copyAssets} = require("./assets");
|
|
20
|
+
const {logLine} = require("./log");
|
|
24
21
|
const navigation = require("../components/navigation");
|
|
25
22
|
const referenced = require("../components/referenced");
|
|
26
23
|
const bibliography = require("../components/bibliography");
|
|
27
24
|
|
|
28
25
|
// hold records between builds if skipping IIIF
|
|
29
26
|
let iiifRecordsCache = [];
|
|
27
|
+
let iiifManifestIdsCache = [];
|
|
28
|
+
let iiifCollectionIdsCache = [];
|
|
30
29
|
let pageRecords = [];
|
|
31
30
|
|
|
32
31
|
function nowMs() {
|
|
@@ -38,7 +37,7 @@ function nowMs() {
|
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
function formatDuration(ms) {
|
|
41
|
-
if (!Number.isFinite(ms) || ms < 0) return
|
|
40
|
+
if (!Number.isFinite(ms) || ms < 0) return "0ms";
|
|
42
41
|
if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;
|
|
43
42
|
return `${Math.round(ms)}ms`;
|
|
44
43
|
}
|
|
@@ -49,25 +48,33 @@ async function timeStage(label, store, fn) {
|
|
|
49
48
|
return await fn();
|
|
50
49
|
} finally {
|
|
51
50
|
const durationMs = nowMs() - start;
|
|
52
|
-
if (Array.isArray(store)) store.push({
|
|
51
|
+
if (Array.isArray(store)) store.push({label, durationMs});
|
|
53
52
|
try {
|
|
54
|
-
logLine(
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
logLine(
|
|
54
|
+
`⏱ ${label} completed in ${formatDuration(durationMs)}`,
|
|
55
|
+
"cyan",
|
|
56
|
+
{
|
|
57
|
+
dim: true,
|
|
58
|
+
},
|
|
59
|
+
);
|
|
57
60
|
} catch (_) {}
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
async function ensureNoJekyllMarker() {
|
|
62
65
|
try {
|
|
63
|
-
await fsp.mkdir(OUT_DIR, {
|
|
64
|
-
const markerPath = path.join(OUT_DIR,
|
|
65
|
-
await fsp.writeFile(markerPath,
|
|
66
|
+
await fsp.mkdir(OUT_DIR, {recursive: true});
|
|
67
|
+
const markerPath = path.join(OUT_DIR, ".nojekyll");
|
|
68
|
+
await fsp.writeFile(markerPath, "", "utf8");
|
|
66
69
|
} catch (_) {}
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
async function build(options = {}) {
|
|
70
|
-
const skipIiif = !!(
|
|
73
|
+
const skipIiif = !!(
|
|
74
|
+
options?.skipIiif ||
|
|
75
|
+
process.env.CANOPY_SKIP_IIIF === "1" ||
|
|
76
|
+
process.env.CANOPY_SKIP_IIIF === "true"
|
|
77
|
+
);
|
|
71
78
|
if (!fs.existsSync(CONTENT_DIR)) {
|
|
72
79
|
console.error("No content directory found at", CONTENT_DIR);
|
|
73
80
|
process.exit(1);
|
|
@@ -83,7 +90,7 @@ async function build(options = {}) {
|
|
|
83
90
|
bright: true,
|
|
84
91
|
underscore: true,
|
|
85
92
|
});
|
|
86
|
-
logLine("• Reset MDX cache", "blue", {
|
|
93
|
+
logLine("• Reset MDX cache", "blue", {dim: true});
|
|
87
94
|
mdx?.resetMdxCaches();
|
|
88
95
|
navigation?.resetNavigationCache?.();
|
|
89
96
|
referenced?.resetReferenceIndex?.();
|
|
@@ -91,9 +98,9 @@ async function build(options = {}) {
|
|
|
91
98
|
if (!skipIiif) {
|
|
92
99
|
await cleanDir(OUT_DIR);
|
|
93
100
|
await ensureNoJekyllMarker();
|
|
94
|
-
logLine(`• Cleaned output directory`, "blue", {
|
|
101
|
+
logLine(`• Cleaned output directory`, "blue", {dim: true});
|
|
95
102
|
} else {
|
|
96
|
-
logLine("• Retaining cache (skipping IIIF rebuild)", "blue", {
|
|
103
|
+
logLine("• Retaining cache (skipping IIIF rebuild)", "blue", {dim: true});
|
|
97
104
|
}
|
|
98
105
|
if (skipIiif) {
|
|
99
106
|
await ensureNoJekyllMarker();
|
|
@@ -107,29 +114,72 @@ async function build(options = {}) {
|
|
|
107
114
|
*/
|
|
108
115
|
let iiifRecords = [];
|
|
109
116
|
let CONFIG;
|
|
117
|
+
let currentManifestIds = [];
|
|
118
|
+
let currentCollectionIds = [];
|
|
110
119
|
await timeStage("Build IIIF Collection content", stageTimings, async () => {
|
|
111
120
|
logLine("\nBuild IIIF Collection content", "magenta", {
|
|
112
121
|
bright: true,
|
|
113
122
|
underscore: true,
|
|
114
123
|
});
|
|
115
124
|
CONFIG = await iiif.loadConfig();
|
|
116
|
-
|
|
125
|
+
const sources = iiif.resolveIiifSources(CONFIG);
|
|
126
|
+
const hasIiifSources =
|
|
127
|
+
Array.isArray(sources?.collections) && sources.collections.length
|
|
128
|
+
? true
|
|
129
|
+
: Array.isArray(sources?.manifests) && sources.manifests.length > 0;
|
|
130
|
+
if (!skipIiif && hasIiifSources) {
|
|
117
131
|
const results = await iiif.buildIiifCollectionPages(CONFIG);
|
|
118
132
|
iiifRecords = results?.iiifRecords;
|
|
119
133
|
iiifRecordsCache = Array.isArray(iiifRecords) ? iiifRecords : [];
|
|
134
|
+
currentManifestIds = Array.isArray(results?.manifestIds)
|
|
135
|
+
? results.manifestIds
|
|
136
|
+
: [];
|
|
137
|
+
currentCollectionIds = Array.isArray(results?.collectionIds)
|
|
138
|
+
? results.collectionIds
|
|
139
|
+
: [];
|
|
140
|
+
iiifManifestIdsCache = currentManifestIds;
|
|
141
|
+
iiifCollectionIdsCache = currentCollectionIds;
|
|
142
|
+
logLine(
|
|
143
|
+
`• IIIF records collected: ${Array.isArray(iiifRecords) ? iiifRecords.length : 0}`,
|
|
144
|
+
"blue",
|
|
145
|
+
{dim: true},
|
|
146
|
+
);
|
|
147
|
+
} else if (!skipIiif && !hasIiifSources) {
|
|
148
|
+
iiifRecords = [];
|
|
149
|
+
iiifRecordsCache = [];
|
|
150
|
+
currentManifestIds = [];
|
|
151
|
+
currentCollectionIds = [];
|
|
152
|
+
iiifManifestIdsCache = [];
|
|
153
|
+
iiifCollectionIdsCache = [];
|
|
154
|
+
logLine("• No IIIF sources configured; skipping", "blue", {dim: true});
|
|
120
155
|
} else {
|
|
121
156
|
iiifRecords = Array.isArray(iiifRecordsCache) ? iiifRecordsCache : [];
|
|
157
|
+
currentManifestIds = Array.isArray(iiifManifestIdsCache)
|
|
158
|
+
? iiifManifestIdsCache
|
|
159
|
+
: [];
|
|
160
|
+
currentCollectionIds = Array.isArray(iiifCollectionIdsCache)
|
|
161
|
+
? iiifCollectionIdsCache
|
|
162
|
+
: [];
|
|
122
163
|
logLine(
|
|
123
164
|
`• Reusing cached IIIF search records (${iiifRecords.length})`,
|
|
124
165
|
"blue",
|
|
125
|
-
{
|
|
166
|
+
{dim: true},
|
|
126
167
|
);
|
|
127
168
|
}
|
|
128
169
|
// Ensure any configured featured manifests are cached (and thumbnails computed)
|
|
129
170
|
// so SSR interstitials can resolve items even if they are not part of
|
|
130
171
|
// the traversed collection or when IIIF build is skipped during incremental rebuilds.
|
|
131
|
-
try {
|
|
132
|
-
|
|
172
|
+
try {
|
|
173
|
+
await iiif.ensureFeaturedInCache(CONFIG);
|
|
174
|
+
} catch (_) {}
|
|
175
|
+
if (!skipIiif && hasIiifSources && currentManifestIds.length) {
|
|
176
|
+
try {
|
|
177
|
+
await iiif.cleanupIiifCache({
|
|
178
|
+
allowedManifestIds: currentManifestIds,
|
|
179
|
+
allowedCollectionIds: currentCollectionIds,
|
|
180
|
+
});
|
|
181
|
+
} catch (_) {}
|
|
182
|
+
}
|
|
133
183
|
});
|
|
134
184
|
|
|
135
185
|
/**
|
|
@@ -137,25 +187,30 @@ async function build(options = {}) {
|
|
|
137
187
|
* This includes collecting page metadata for sitemap and search index,
|
|
138
188
|
* as well as building all MDX pages to HTML.
|
|
139
189
|
*/
|
|
140
|
-
await timeStage(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
190
|
+
await timeStage(
|
|
191
|
+
"Build contextual content from Markdown pages",
|
|
192
|
+
stageTimings,
|
|
193
|
+
async () => {
|
|
194
|
+
logLine("\nBuild contextual content from Markdown pages", "magenta", {
|
|
195
|
+
bright: true,
|
|
196
|
+
underscore: true,
|
|
197
|
+
});
|
|
198
|
+
// Interstitials read directly from the local IIIF cache; no API file needed
|
|
199
|
+
try {
|
|
200
|
+
bibliography?.buildBibliographyIndexSync?.();
|
|
201
|
+
} catch (err) {
|
|
202
|
+
logLine(
|
|
203
|
+
"• Failed to build bibliography index: " +
|
|
204
|
+
String(err && err.message ? err.message : err),
|
|
205
|
+
"red",
|
|
206
|
+
{dim: true},
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
pageRecords = await searchBuild.collectMdxPageRecords();
|
|
210
|
+
await pages.buildContentTree(CONTENT_DIR, pageRecords);
|
|
211
|
+
logLine("✓ MDX pages built", "green");
|
|
212
|
+
},
|
|
213
|
+
);
|
|
159
214
|
|
|
160
215
|
/**
|
|
161
216
|
* Build search index from IIIF and MDX records, then build or update
|
|
@@ -172,7 +227,7 @@ async function build(options = {}) {
|
|
|
172
227
|
const combined = await buildSearchIndex(iiifRecords, pageRecords);
|
|
173
228
|
await finalizeSearch(combined);
|
|
174
229
|
} catch (e) {
|
|
175
|
-
logLine("✗ Search index creation failed", "red", {
|
|
230
|
+
logLine("✗ Search index creation failed", "red", {bright: true});
|
|
176
231
|
logLine(" " + String(e), "red");
|
|
177
232
|
}
|
|
178
233
|
});
|
|
@@ -188,7 +243,7 @@ async function build(options = {}) {
|
|
|
188
243
|
try {
|
|
189
244
|
await writeSitemap(iiifRecords, pageRecords);
|
|
190
245
|
} catch (e) {
|
|
191
|
-
logLine("✗ Failed to write sitemap.xml", "red", {
|
|
246
|
+
logLine("✗ Failed to write sitemap.xml", "red", {bright: true});
|
|
192
247
|
logLine(" " + String(e), "red");
|
|
193
248
|
}
|
|
194
249
|
});
|
|
@@ -199,23 +254,27 @@ async function build(options = {}) {
|
|
|
199
254
|
* Prepare client runtimes (e.g. search) by bundling with esbuild.
|
|
200
255
|
* This is done early so that MDX content can reference runtime assets if needed.
|
|
201
256
|
*/
|
|
202
|
-
await timeStage(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
257
|
+
await timeStage(
|
|
258
|
+
"Prepare client runtimes and stylesheets",
|
|
259
|
+
stageTimings,
|
|
260
|
+
async () => {
|
|
261
|
+
logLine("\nPrepare client runtimes and stylesheets", "magenta", {
|
|
262
|
+
bright: true,
|
|
263
|
+
underscore: true,
|
|
264
|
+
});
|
|
265
|
+
const tasks = [];
|
|
266
|
+
if (!process.env.CANOPY_SKIP_STYLES) {
|
|
267
|
+
tasks.push(
|
|
268
|
+
(async () => {
|
|
269
|
+
await ensureStyles();
|
|
270
|
+
logLine("✓ Wrote styles.css", "cyan");
|
|
271
|
+
})(),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
tasks.push(runtimes.prepareAllRuntimes());
|
|
275
|
+
await Promise.all(tasks);
|
|
276
|
+
},
|
|
277
|
+
);
|
|
219
278
|
|
|
220
279
|
/**
|
|
221
280
|
* Copy static assets from the assets directory to the output directory.
|
|
@@ -229,15 +288,16 @@ async function build(options = {}) {
|
|
|
229
288
|
});
|
|
230
289
|
|
|
231
290
|
const totalMs = nowMs() - buildStart;
|
|
232
|
-
logLine("\nBuild phase timings", "magenta", {
|
|
233
|
-
for (const {
|
|
234
|
-
logLine(`• ${label}: ${formatDuration(durationMs)}`, "blue", {
|
|
291
|
+
logLine("\nBuild phase timings", "magenta", {bright: true, underscore: true});
|
|
292
|
+
for (const {label, durationMs} of stageTimings) {
|
|
293
|
+
logLine(`• ${label}: ${formatDuration(durationMs)}`, "blue", {dim: true});
|
|
235
294
|
}
|
|
236
|
-
logLine(`Total build time: ${formatDuration(totalMs)}`, "green", {
|
|
237
|
-
|
|
295
|
+
logLine(`Total build time: ${formatDuration(totalMs)}`, "green", {
|
|
296
|
+
bright: true,
|
|
297
|
+
});
|
|
238
298
|
}
|
|
239
299
|
|
|
240
|
-
module.exports = {
|
|
300
|
+
module.exports = {build};
|
|
241
301
|
|
|
242
302
|
if (require.main === module) {
|
|
243
303
|
build().catch((e) => {
|