@canopy-iiif/app 1.2.1 → 1.2.2

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.
@@ -12,6 +12,7 @@ const pages = require("./pages");
12
12
  const searchBuild = require("./search");
13
13
  const { buildSearchIndex } = require("./search-index");
14
14
  const runtimes = require("./runtimes");
15
+ const { writeSitemap } = require("./sitemap");
15
16
  const {
16
17
  ensureSearchInitialized,
17
18
  finalizeSearch,
@@ -112,6 +113,20 @@ async function build(options = {}) {
112
113
  logLine(" " + String(e), "red");
113
114
  }
114
115
 
116
+ /**
117
+ * Generate a sitemap so static hosts can discover every rendered page.
118
+ */
119
+ logLine("\nWrite sitemap", "magenta", {
120
+ bright: true,
121
+ underscore: true,
122
+ });
123
+ try {
124
+ await writeSitemap(iiifRecords, pageRecords);
125
+ } catch (e) {
126
+ logLine("✗ Failed to write sitemap.xml", "red", { bright: true });
127
+ logLine(" " + String(e), "red");
128
+ }
129
+
115
130
  // No-op: Featured API file no longer written (SSR reads from cache directly)
116
131
 
117
132
  /**
@@ -0,0 +1,75 @@
1
+ const { fsp, path, OUT_DIR, absoluteUrl, rootRelativeHref } = require('../common');
2
+ const { logLine } = require('./log');
3
+
4
+ const DEFAULT_CHANGEFREQ = 'monthly';
5
+ const DEFAULT_PRIORITY = '0.5';
6
+
7
+ function escapeXml(value) {
8
+ return String(value || '')
9
+ .replace(/&/g, '&')
10
+ .replace(/</g, '&lt;')
11
+ .replace(/>/g, '&gt;')
12
+ .replace(/"/g, '&quot;')
13
+ .replace(/'/g, '&apos;');
14
+ }
15
+
16
+ function normalizeHref(href) {
17
+ if (!href && href !== 0) return '';
18
+ const rel = rootRelativeHref(href);
19
+ if (!rel || rel === '#') return '';
20
+ if (rel.startsWith('?') || rel.startsWith('#')) return '';
21
+ return rel;
22
+ }
23
+
24
+ function collectAbsoluteUrls(iiifRecords, pageRecords) {
25
+ const urls = new Set();
26
+ const push = (href) => {
27
+ const rel = normalizeHref(href);
28
+ if (!rel) return;
29
+ try {
30
+ const abs = absoluteUrl(rel);
31
+ if (abs) urls.add(abs);
32
+ } catch (_) {}
33
+ };
34
+ (Array.isArray(pageRecords) ? pageRecords : []).forEach((page) => {
35
+ if (!page || !page.href) return;
36
+ push(page.href);
37
+ });
38
+ (Array.isArray(iiifRecords) ? iiifRecords : []).forEach((record) => {
39
+ if (!record || !record.href) return;
40
+ push(record.href);
41
+ });
42
+ // Ensure the search page is always present even though it is generated separately
43
+ push('/search.html');
44
+ return Array.from(urls.values()).sort((a, b) => a.localeCompare(b));
45
+ }
46
+
47
+ async function writeSitemap(iiifRecords, pageRecords) {
48
+ const urls = collectAbsoluteUrls(iiifRecords, pageRecords);
49
+ if (!urls.length) {
50
+ logLine('• No URLs to write to sitemap', 'yellow');
51
+ return;
52
+ }
53
+ const rows = urls.map((loc) => {
54
+ const escapedLoc = escapeXml(loc);
55
+ return [
56
+ ' <url>',
57
+ ` <loc>${escapedLoc}</loc>`,
58
+ ` <changefreq>${DEFAULT_CHANGEFREQ}</changefreq>`,
59
+ ` <priority>${DEFAULT_PRIORITY}</priority>`,
60
+ ' </url>',
61
+ ].join('\n');
62
+ });
63
+ const xml = [
64
+ '<?xml version="1.0" encoding="UTF-8"?>',
65
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
66
+ rows.join('\n'),
67
+ '</urlset>',
68
+ '',
69
+ ].join('\n');
70
+ const dest = path.join(OUT_DIR, 'sitemap.xml');
71
+ await fsp.writeFile(dest, xml, 'utf8');
72
+ logLine(`✓ Wrote sitemap.xml (${urls.length} urls)`, 'cyan');
73
+ }
74
+
75
+ module.exports = { writeSitemap };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",