@canopy-iiif/app 0.6.28 → 0.7.0

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.
@@ -0,0 +1,30 @@
1
+ const { fs, fsp, path, ASSETS_DIR, OUT_DIR, ensureDirSync } = require('../common');
2
+ const { logLine } = require('./log');
3
+
4
+ async function copyAssets() {
5
+ try {
6
+ if (!fs.existsSync(ASSETS_DIR)) return;
7
+ } catch (_) {
8
+ return;
9
+ }
10
+ async function walk(dir) {
11
+ const entries = await fsp.readdir(dir, { withFileTypes: true });
12
+ for (const e of entries) {
13
+ const src = path.join(dir, e.name);
14
+ const rel = path.relative(ASSETS_DIR, src);
15
+ const dest = path.join(OUT_DIR, rel);
16
+ if (e.isDirectory()) { ensureDirSync(dest); await walk(src); }
17
+ else if (e.isFile()) {
18
+ ensureDirSync(path.dirname(dest));
19
+ await fsp.copyFile(src, dest);
20
+ try { logLine(`• Asset ${path.relative(process.cwd(), dest)}`, 'cyan', { dim: true }); } catch (_) {}
21
+ }
22
+ }
23
+ }
24
+ try { logLine('• Copying assets...', 'blue', { bright: true }); } catch (_) {}
25
+ await walk(ASSETS_DIR);
26
+ try { logLine('✓ Assets copied', 'green'); } catch (_) {}
27
+ }
28
+
29
+ module.exports = { copyAssets };
30
+
@@ -0,0 +1,184 @@
1
+ const {
2
+ fs,
3
+ path,
4
+ CONTENT_DIR,
5
+ OUT_DIR,
6
+ ensureDirSync,
7
+ cleanDir,
8
+ } = require("../common");
9
+ const mdx = require("./mdx");
10
+ const iiif = require("./iiif");
11
+ const pages = require("./pages");
12
+ const search = require("../search/search");
13
+ const searchBuild = require("./search");
14
+ const { buildSearchIndex } = require("./search-index");
15
+ const { generateFacets } = require("./facets");
16
+ const runtimes = require("./runtimes");
17
+ const { ensureStyles } = require("./styles");
18
+ const { copyAssets } = require("./assets");
19
+ const { logLine } = require("./log");
20
+
21
+ // hold records between builds if skipping IIIF
22
+ let iiifRecordsCache = [];
23
+ let pageRecords = [];
24
+
25
+ async function build(options = {}) {
26
+ const skipIiif = !!options?.skipIiif;
27
+ if (!fs.existsSync(CONTENT_DIR)) {
28
+ console.error("No content directory found at", CONTENT_DIR);
29
+ process.exit(1);
30
+ }
31
+
32
+ /**
33
+ * Clean and prepare output directory
34
+ */
35
+ logLine("\n[1/6] Clean and prepare directories", "magenta", {
36
+ bright: true,
37
+ });
38
+ mdx?.resetMdxCaches();
39
+ if (!skipIiif) {
40
+ await cleanDir(OUT_DIR);
41
+ logLine("✓ Cleaned output directory\n", "cyan");
42
+ } else {
43
+ logLine("• Incremental rebuild\n", "blue");
44
+ }
45
+
46
+ /**
47
+ * Build IIIF Collection content from configured source(s).
48
+ * This includes building IIIF manifests for works and collections,
49
+ * as well as collecting search records for works.
50
+ */
51
+ logLine("\n[2/6] Build IIIF Collection content", "magenta", {
52
+ bright: true,
53
+ });
54
+ let searchRecords = [];
55
+ if (!skipIiif) {
56
+ const CONFIG = await iiif.loadConfig();
57
+ const res = await iiif.buildIiifCollectionPages(CONFIG);
58
+ searchRecords = Array.isArray(res && res.searchRecords)
59
+ ? res.searchRecords
60
+ : [];
61
+ iiifRecordsCache = searchRecords;
62
+ } else {
63
+ searchRecords = Array.isArray(iiifRecordsCache) ? iiifRecordsCache : [];
64
+ }
65
+
66
+ /**
67
+ * Build contextual MDX content from the content directory.
68
+ * This includes collecting page metadata for sitemap and search index,
69
+ * as well as building all MDX pages to HTML.
70
+ */
71
+ logLine("\n[3/6] Build contextual content from Markdown pages", "magenta", {
72
+ bright: true,
73
+ });
74
+ // Collect pages metadata for sitemap injection
75
+ pageRecords = await searchBuild.collectMdxPageRecords();
76
+ // Build all MDX and assets
77
+ logLine("\n• Building MDX pages...", "blue", { bright: true });
78
+ await pages.buildContentTree(CONTENT_DIR, pageRecords);
79
+ logLine("✓ MDX pages built\n", "green");
80
+
81
+ /**
82
+ * Build search index from IIIF and MDX records, then build or update
83
+ * the search.html page and search runtime bundle.
84
+ * This is done after all content is built so that the index is comprehensive.
85
+ */
86
+ logLine("\n[4/6] Create search indices", "magenta", { bright: true });
87
+ try {
88
+ const searchPath = path.join(OUT_DIR, "search.html");
89
+ const needCreatePage = !fs.existsSync(searchPath);
90
+ if (needCreatePage) {
91
+ try {
92
+ logLine("• Preparing search (initial)...", "blue", { bright: true });
93
+ } catch (_) {}
94
+ // Build result item template (if present) up-front so it can be inlined
95
+ try {
96
+ await search.ensureResultTemplate();
97
+ } catch (_) {}
98
+ try {
99
+ logLine(" - Writing empty index...", "blue");
100
+ } catch (_) {}
101
+ await search.writeSearchIndex([]);
102
+ try {
103
+ logLine(" - Writing runtime...", "blue");
104
+ } catch (_) {}
105
+ const timeoutMs = Number(process.env.CANOPY_BUNDLE_TIMEOUT || 10000);
106
+ let timedOut = false;
107
+ await runtimes.prepareSearchRuntime(
108
+ process.env.CANOPY_BUNDLE_TIMEOUT || 10000,
109
+ "initial"
110
+ );
111
+ try {
112
+ logLine(" - Building search.html...", "blue");
113
+ } catch (_) {}
114
+ await search.buildSearchPage();
115
+ logLine("✓ Created search page", "cyan");
116
+ }
117
+ // Always (re)write the search index combining IIIF and MDX pages
118
+ const combined = await buildSearchIndex(searchRecords, pageRecords);
119
+ // Build facets for IIIF works based on configured metadata labels
120
+ await generateFacets(combined);
121
+ try {
122
+ logLine("• Writing search runtime (final)...", "blue", { bright: true });
123
+ } catch (_) {}
124
+ await runtimes.prepareSearchRuntime(
125
+ process.env.CANOPY_BUNDLE_TIMEOUT || 10000,
126
+ "final"
127
+ );
128
+ // Rebuild result item template after content processing to capture latest
129
+ try {
130
+ await search.ensureResultTemplate();
131
+ } catch (_) {}
132
+ // Rebuild search.html to inline the latest result template
133
+ try {
134
+ logLine("• Updating search.html...", "blue");
135
+ } catch (_) {}
136
+ await search.buildSearchPage();
137
+ try {
138
+ logLine("✓ Search page updated", "cyan");
139
+ } catch (_) {}
140
+ // Itemize counts by type for a clearer summary
141
+ const counts = new Map();
142
+ for (const r of combined) {
143
+ const t = String((r && r.type) || "page").toLowerCase();
144
+ counts.set(t, (counts.get(t) || 0) + 1);
145
+ }
146
+ const parts = Array.from(counts.entries())
147
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
148
+ .map(([t, n]) => `${t}: ${n}`);
149
+ const breakdown = parts.length ? `: ${parts.join(", ")}` : "";
150
+ logLine(
151
+ `✓ Search index: ${combined.length} total records${breakdown}`,
152
+ "cyan"
153
+ );
154
+ } catch (_) {}
155
+
156
+ /**
157
+ * Prepare client runtimes (e.g. search) by bundling with esbuild.
158
+ * This is done early so that MDX content can reference runtime assets if needed.
159
+ */
160
+ logLine("\n[5/6] Prepare client runtimes and stylesheets", "magenta", {
161
+ bright: true,
162
+ });
163
+ await runtimes.prepareAllRuntimes();
164
+ ensureDirSync(path.join(OUT_DIR, "styles"));
165
+ if (!process.env.CANOPY_SKIP_STYLES) {
166
+ await ensureStyles();
167
+ logLine("✓ Wrote styles.css\n", "cyan");
168
+ }
169
+
170
+ /**
171
+ * Copy static assets from the assets directory to the output directory.
172
+ */
173
+ logLine("\n[6/6] Copy static assets", "magenta", { bright: true });
174
+ await copyAssets();
175
+ }
176
+
177
+ module.exports = { build };
178
+
179
+ if (require.main === module) {
180
+ build().catch((e) => {
181
+ console.error(e);
182
+ process.exit(1);
183
+ });
184
+ }
@@ -2,11 +2,11 @@ const fs = require('fs');
2
2
  const fsp = fs.promises;
3
3
  const path = require('path');
4
4
  const { spawn } = require('child_process');
5
- const { build } = require('./build');
5
+ const { build } = require('../build/build');
6
6
  const http = require('http');
7
7
  const url = require('url');
8
- const { CONTENT_DIR, OUT_DIR, ASSETS_DIR, ensureDirSync } = require('./common');
9
- const twHelper = (() => { try { return require('../helpers/build-tailwind'); } catch (_) { return null; } })();
8
+ const { CONTENT_DIR, OUT_DIR, ASSETS_DIR, ensureDirSync } = require('../common');
9
+ const twHelper = (() => { try { return require('../../helpers/build-tailwind'); } catch (_) { return null; } })();
10
10
  function resolveTailwindCli() {
11
11
  try {
12
12
  const cliJs = require.resolve('tailwindcss/lib/cli.js');
@@ -23,7 +23,7 @@ let onBuildSuccess = () => {};
23
23
  let onBuildStart = () => {};
24
24
  let onCssChange = () => {};
25
25
  let nextBuildSkipIiif = false; // hint set by watchers
26
- const UI_DIST_DIR = path.resolve(path.join(__dirname, '../ui/dist'));
26
+ const UI_DIST_DIR = path.resolve(path.join(__dirname, '../../ui/dist'));
27
27
 
28
28
  function prettyPath(p) {
29
29
  try {
@@ -495,7 +495,7 @@ async function dev() {
495
495
  const genDir = path.join(CACHE_DIR, 'tailwind');
496
496
  ensureDirSync(genDir);
497
497
  const genCfg = path.join(genDir, 'tailwind.config.js');
498
- const cfg = `module.exports = {\n presets: [require('@canopy-iiif/app/ui/canopy-iiif-preset')],\n content: [\n './content/**/*.{mdx,html}',\n './site/**/*.html',\n './site/**/*.js',\n './packages/app/ui/**/*.{js,jsx,ts,tsx}',\n './packages/app/lib/components/**/*.{js,jsx}',\n ],\n theme: { extend: {} },\n plugins: [require('@canopy-iiif/app/ui/canopy-iiif-plugin')],\n};\n`;
498
+ const cfg = `module.exports = {\n presets: [require('@canopy-iiif/app/ui/canopy-iiif-preset')],\n content: [\n './content/**/*.{mdx,html}',\n './site/**/*.html',\n './site/**/*.js',\n './packages/app/ui/**/*.{js,jsx,ts,tsx}',\n './packages/app/lib/iiif/components/**/*.{js,jsx}',\n ],\n theme: { extend: {} },\n plugins: [require('@canopy-iiif/app/ui/canopy-iiif-plugin')],\n};\n`;
499
499
  fs.writeFileSync(genCfg, cfg, 'utf8');
500
500
  configPath = genCfg;
501
501
  } catch (_) { configPath = null; }
@@ -0,0 +1,19 @@
1
+
2
+ const { logLine } = require('./log');
3
+
4
+ async function generateFacets(combined) {
5
+ try {
6
+ const { loadConfig } = require('./iiif');
7
+ const searchBuild = require('./search');
8
+ const cfg = await loadConfig();
9
+ const labels = Array.isArray(cfg && cfg.metadata) ? cfg.metadata : [];
10
+ await searchBuild.buildFacetsForWorks(combined, labels);
11
+ await searchBuild.writeFacetCollections(labels, combined);
12
+ await searchBuild.writeFacetsSearchApi();
13
+ try { logLine('✓ Facets generated', 'cyan'); } catch (_) {}
14
+ } catch (e) {
15
+ try { logLine('! Facets generation skipped', 'yellow'); } catch (_) {}
16
+ }
17
+ }
18
+
19
+ module.exports = { generateFacets };