@canopy-iiif/app 0.7.0 → 0.7.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 +37 -93
- package/lib/build/dev.js +493 -204
- package/lib/build/iiif.js +26 -17
- package/lib/build/log.js +19 -52
- package/lib/build/search-workflow.js +61 -0
- package/lib/build/styles.js +82 -40
- package/package.json +2 -1
package/lib/build/dev.js
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
|
-
const fs = require(
|
|
1
|
+
const fs = require("fs");
|
|
2
2
|
const fsp = fs.promises;
|
|
3
|
-
const path = require(
|
|
4
|
-
const { spawn } = require(
|
|
5
|
-
const { build } = require(
|
|
6
|
-
const http = require(
|
|
7
|
-
const url = require(
|
|
8
|
-
const {
|
|
9
|
-
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { spawn } = require("child_process");
|
|
5
|
+
const { build } = require("../build/build");
|
|
6
|
+
const http = require("http");
|
|
7
|
+
const url = require("url");
|
|
8
|
+
const {
|
|
9
|
+
CONTENT_DIR,
|
|
10
|
+
OUT_DIR,
|
|
11
|
+
ASSETS_DIR,
|
|
12
|
+
ensureDirSync,
|
|
13
|
+
} = require("../common");
|
|
14
|
+
const twHelper = (() => {
|
|
15
|
+
try {
|
|
16
|
+
return require("../../helpers/build-tailwind");
|
|
17
|
+
} catch (_) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
})();
|
|
10
21
|
function resolveTailwindCli() {
|
|
11
22
|
try {
|
|
12
|
-
const cliJs = require.resolve(
|
|
23
|
+
const cliJs = require.resolve("tailwindcss/lib/cli.js");
|
|
13
24
|
return { cmd: process.execPath, args: [cliJs] };
|
|
14
25
|
} catch (_) {}
|
|
15
26
|
try {
|
|
16
|
-
const bin = path.join(
|
|
27
|
+
const bin = path.join(
|
|
28
|
+
process.cwd(),
|
|
29
|
+
"node_modules",
|
|
30
|
+
".bin",
|
|
31
|
+
process.platform === "win32" ? "tailwindcss.cmd" : "tailwindcss"
|
|
32
|
+
);
|
|
17
33
|
if (fs.existsSync(bin)) return { cmd: bin, args: [] };
|
|
18
34
|
} catch (_) {}
|
|
19
35
|
return null;
|
|
@@ -23,16 +39,18 @@ let onBuildSuccess = () => {};
|
|
|
23
39
|
let onBuildStart = () => {};
|
|
24
40
|
let onCssChange = () => {};
|
|
25
41
|
let nextBuildSkipIiif = false; // hint set by watchers
|
|
26
|
-
const UI_DIST_DIR = path.resolve(path.join(__dirname,
|
|
42
|
+
const UI_DIST_DIR = path.resolve(path.join(__dirname, "../../ui/dist"));
|
|
27
43
|
|
|
28
44
|
function prettyPath(p) {
|
|
29
45
|
try {
|
|
30
46
|
let rel = path.relative(process.cwd(), p);
|
|
31
|
-
if (!rel) rel =
|
|
32
|
-
rel = rel.split(path.sep).join(
|
|
33
|
-
if (!rel.startsWith(
|
|
47
|
+
if (!rel) rel = ".";
|
|
48
|
+
rel = rel.split(path.sep).join("/");
|
|
49
|
+
if (!rel.startsWith("./") && !rel.startsWith("../")) rel = "./" + rel;
|
|
34
50
|
return rel;
|
|
35
|
-
} catch (_) {
|
|
51
|
+
} catch (_) {
|
|
52
|
+
return p;
|
|
53
|
+
}
|
|
36
54
|
}
|
|
37
55
|
|
|
38
56
|
async function runBuild() {
|
|
@@ -40,22 +58,38 @@ async function runBuild() {
|
|
|
40
58
|
const hint = { skipIiif: !!nextBuildSkipIiif };
|
|
41
59
|
nextBuildSkipIiif = false;
|
|
42
60
|
await build(hint);
|
|
43
|
-
try {
|
|
61
|
+
try {
|
|
62
|
+
onBuildSuccess();
|
|
63
|
+
} catch (_) {}
|
|
44
64
|
} catch (e) {
|
|
45
|
-
console.error(
|
|
65
|
+
console.error("Build failed:", e && e.message ? e.message : e);
|
|
46
66
|
}
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
function tryRecursiveWatch() {
|
|
50
70
|
try {
|
|
51
|
-
const watcher = fs.watch(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
const watcher = fs.watch(
|
|
72
|
+
CONTENT_DIR,
|
|
73
|
+
{ recursive: true },
|
|
74
|
+
(eventType, filename) => {
|
|
75
|
+
if (!filename) return;
|
|
76
|
+
try {
|
|
77
|
+
console.log(
|
|
78
|
+
`[watch] ${eventType}: ${prettyPath(
|
|
79
|
+
path.join(CONTENT_DIR, filename)
|
|
80
|
+
)}`
|
|
81
|
+
);
|
|
82
|
+
} catch (_) {}
|
|
83
|
+
// If an MDX file changed, we can skip IIIF for the next build
|
|
84
|
+
try {
|
|
85
|
+
if (/\.mdx$/i.test(filename)) nextBuildSkipIiif = true;
|
|
86
|
+
} catch (_) {}
|
|
87
|
+
try {
|
|
88
|
+
onBuildStart();
|
|
89
|
+
} catch (_) {}
|
|
90
|
+
debounceBuild();
|
|
91
|
+
}
|
|
92
|
+
);
|
|
59
93
|
return watcher;
|
|
60
94
|
} catch (e) {
|
|
61
95
|
return null;
|
|
@@ -75,11 +109,21 @@ function watchPerDir() {
|
|
|
75
109
|
if (watchers.has(dir)) return;
|
|
76
110
|
try {
|
|
77
111
|
const w = fs.watch(dir, (eventType, filename) => {
|
|
78
|
-
try {
|
|
112
|
+
try {
|
|
113
|
+
console.log(
|
|
114
|
+
`[watch] ${eventType}: ${prettyPath(
|
|
115
|
+
path.join(dir, filename || "")
|
|
116
|
+
)}`
|
|
117
|
+
);
|
|
118
|
+
} catch (_) {}
|
|
79
119
|
// If a new directory appears, add a watcher for it on next scan
|
|
80
120
|
scan(dir);
|
|
81
|
-
try {
|
|
82
|
-
|
|
121
|
+
try {
|
|
122
|
+
if (filename && /\.mdx$/i.test(filename)) nextBuildSkipIiif = true;
|
|
123
|
+
} catch (_) {}
|
|
124
|
+
try {
|
|
125
|
+
onBuildStart();
|
|
126
|
+
} catch (_) {}
|
|
83
127
|
debounceBuild();
|
|
84
128
|
});
|
|
85
129
|
watchers.set(dir, w);
|
|
@@ -120,25 +164,46 @@ async function syncAsset(relativePath) {
|
|
|
120
164
|
}
|
|
121
165
|
ensureDirSync(path.dirname(dest));
|
|
122
166
|
await fsp.copyFile(src, dest);
|
|
123
|
-
console.log(
|
|
167
|
+
console.log(
|
|
168
|
+
`[assets] Copied ${relativePath} -> ${path.relative(
|
|
169
|
+
process.cwd(),
|
|
170
|
+
dest
|
|
171
|
+
)}`
|
|
172
|
+
);
|
|
124
173
|
} else {
|
|
125
174
|
// Removed or renamed away: remove dest
|
|
126
|
-
try {
|
|
175
|
+
try {
|
|
176
|
+
await fsp.rm(dest, { force: true, recursive: true });
|
|
177
|
+
} catch (_) {}
|
|
127
178
|
console.log(`[assets] Removed ${relativePath}`);
|
|
128
179
|
}
|
|
129
180
|
} catch (e) {
|
|
130
|
-
console.warn(
|
|
181
|
+
console.warn("[assets] sync failed:", e && e.message ? e.message : e);
|
|
131
182
|
}
|
|
132
183
|
}
|
|
133
184
|
|
|
134
185
|
function tryRecursiveWatchAssets() {
|
|
135
186
|
try {
|
|
136
|
-
const watcher = fs.watch(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
187
|
+
const watcher = fs.watch(
|
|
188
|
+
ASSETS_DIR,
|
|
189
|
+
{ recursive: true },
|
|
190
|
+
(eventType, filename) => {
|
|
191
|
+
if (!filename) return;
|
|
192
|
+
try {
|
|
193
|
+
console.log(
|
|
194
|
+
`[assets] ${eventType}: ${prettyPath(
|
|
195
|
+
path.join(ASSETS_DIR, filename)
|
|
196
|
+
)}`
|
|
197
|
+
);
|
|
198
|
+
} catch (_) {}
|
|
199
|
+
// Copy just the changed asset and trigger reload
|
|
200
|
+
syncAsset(filename).then(() => {
|
|
201
|
+
try {
|
|
202
|
+
onBuildSuccess();
|
|
203
|
+
} catch (_) {}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
);
|
|
142
207
|
return watcher;
|
|
143
208
|
} catch (e) {
|
|
144
209
|
return null;
|
|
@@ -152,11 +217,23 @@ function watchAssetsPerDir() {
|
|
|
152
217
|
if (watchers.has(dir)) return;
|
|
153
218
|
try {
|
|
154
219
|
const w = fs.watch(dir, (eventType, filename) => {
|
|
155
|
-
const rel = filename
|
|
156
|
-
|
|
220
|
+
const rel = filename
|
|
221
|
+
? path.relative(ASSETS_DIR, path.join(dir, filename))
|
|
222
|
+
: path.relative(ASSETS_DIR, dir);
|
|
223
|
+
try {
|
|
224
|
+
console.log(
|
|
225
|
+
`[assets] ${eventType}: ${prettyPath(
|
|
226
|
+
path.join(dir, filename || "")
|
|
227
|
+
)}`
|
|
228
|
+
);
|
|
229
|
+
} catch (_) {}
|
|
157
230
|
// If a new directory appears, add a watcher for it on next scan
|
|
158
231
|
scan(dir);
|
|
159
|
-
syncAsset(rel).then(() => {
|
|
232
|
+
syncAsset(rel).then(() => {
|
|
233
|
+
try {
|
|
234
|
+
onBuildSuccess();
|
|
235
|
+
} catch (_) {}
|
|
236
|
+
});
|
|
160
237
|
});
|
|
161
238
|
watchers.set(dir, w);
|
|
162
239
|
} catch (_) {
|
|
@@ -186,31 +263,43 @@ function watchAssetsPerDir() {
|
|
|
186
263
|
// When UI dist changes, rebuild the search runtime bundle and trigger a browser reload.
|
|
187
264
|
async function rebuildSearchBundle() {
|
|
188
265
|
try {
|
|
189
|
-
const search = require(
|
|
190
|
-
if (search && typeof search.ensureSearchRuntime ===
|
|
266
|
+
const search = require("./search");
|
|
267
|
+
if (search && typeof search.ensureSearchRuntime === "function") {
|
|
191
268
|
await search.ensureSearchRuntime();
|
|
192
269
|
}
|
|
193
270
|
} catch (_) {}
|
|
194
|
-
try {
|
|
271
|
+
try {
|
|
272
|
+
onBuildSuccess();
|
|
273
|
+
} catch (_) {}
|
|
195
274
|
}
|
|
196
275
|
|
|
197
276
|
function tryRecursiveWatchUiDist() {
|
|
198
277
|
try {
|
|
199
278
|
if (!fs.existsSync(UI_DIST_DIR)) return null;
|
|
200
|
-
const watcher = fs.watch(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
279
|
+
const watcher = fs.watch(
|
|
280
|
+
UI_DIST_DIR,
|
|
281
|
+
{ recursive: true },
|
|
282
|
+
(eventType, filename) => {
|
|
283
|
+
if (!filename) return;
|
|
284
|
+
try {
|
|
285
|
+
console.log(
|
|
286
|
+
`[ui] ${eventType}: ${prettyPath(path.join(UI_DIST_DIR, filename))}`
|
|
287
|
+
);
|
|
288
|
+
} catch (_) {}
|
|
289
|
+
// Lightweight path: rebuild only the search runtime bundle
|
|
290
|
+
rebuildSearchBundle();
|
|
291
|
+
// If the server-side UI bundle changed, trigger a site rebuild (skip IIIF)
|
|
292
|
+
try {
|
|
293
|
+
if (/server\.(js|mjs)$/.test(filename)) {
|
|
294
|
+
nextBuildSkipIiif = true;
|
|
295
|
+
try {
|
|
296
|
+
onBuildStart();
|
|
297
|
+
} catch (_) {}
|
|
298
|
+
debounceBuild();
|
|
299
|
+
}
|
|
300
|
+
} catch (_) {}
|
|
301
|
+
}
|
|
302
|
+
);
|
|
214
303
|
return watcher;
|
|
215
304
|
} catch (_) {
|
|
216
305
|
return null;
|
|
@@ -224,13 +313,19 @@ function watchUiDistPerDir() {
|
|
|
224
313
|
if (watchers.has(dir)) return;
|
|
225
314
|
try {
|
|
226
315
|
const w = fs.watch(dir, (eventType, filename) => {
|
|
227
|
-
try {
|
|
316
|
+
try {
|
|
317
|
+
console.log(
|
|
318
|
+
`[ui] ${eventType}: ${prettyPath(path.join(dir, filename || ""))}`
|
|
319
|
+
);
|
|
320
|
+
} catch (_) {}
|
|
228
321
|
scan(dir);
|
|
229
322
|
rebuildSearchBundle();
|
|
230
323
|
try {
|
|
231
|
-
if (/server\.(js|mjs)$/.test(filename ||
|
|
324
|
+
if (/server\.(js|mjs)$/.test(filename || "")) {
|
|
232
325
|
nextBuildSkipIiif = true;
|
|
233
|
-
try {
|
|
326
|
+
try {
|
|
327
|
+
onBuildStart();
|
|
328
|
+
} catch (_) {}
|
|
234
329
|
debounceBuild();
|
|
235
330
|
}
|
|
236
331
|
} catch (_) {}
|
|
@@ -247,44 +342,56 @@ function watchUiDistPerDir() {
|
|
|
247
342
|
}
|
|
248
343
|
watchDir(UI_DIST_DIR);
|
|
249
344
|
scan(UI_DIST_DIR);
|
|
250
|
-
return () => {
|
|
345
|
+
return () => {
|
|
346
|
+
for (const w of watchers.values()) w.close();
|
|
347
|
+
};
|
|
251
348
|
}
|
|
252
349
|
|
|
253
350
|
const MIME = {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
351
|
+
".html": "text/html; charset=utf-8",
|
|
352
|
+
".css": "text/css; charset=utf-8",
|
|
353
|
+
".js": "application/javascript; charset=utf-8",
|
|
354
|
+
".json": "application/json; charset=utf-8",
|
|
355
|
+
".png": "image/png",
|
|
356
|
+
".jpg": "image/jpeg",
|
|
357
|
+
".jpeg": "image/jpeg",
|
|
358
|
+
".gif": "image/gif",
|
|
359
|
+
".svg": "image/svg+xml",
|
|
360
|
+
".txt": "text/plain; charset=utf-8",
|
|
264
361
|
};
|
|
265
362
|
|
|
266
363
|
function startServer() {
|
|
267
364
|
const clients = new Set();
|
|
268
365
|
function broadcast(type) {
|
|
269
366
|
for (const res of clients) {
|
|
270
|
-
try {
|
|
367
|
+
try {
|
|
368
|
+
res.write(`data: ${type}\n\n`);
|
|
369
|
+
} catch (_) {}
|
|
271
370
|
}
|
|
272
371
|
}
|
|
273
|
-
onBuildStart = () => broadcast(
|
|
274
|
-
onBuildSuccess = () => broadcast(
|
|
275
|
-
onCssChange = () => broadcast(
|
|
372
|
+
onBuildStart = () => broadcast("building");
|
|
373
|
+
onBuildSuccess = () => broadcast("reload");
|
|
374
|
+
onCssChange = () => broadcast("css");
|
|
276
375
|
|
|
277
376
|
const server = http.createServer((req, res) => {
|
|
278
|
-
const parsed = url.parse(req.url ||
|
|
279
|
-
let pathname = decodeURI(parsed.pathname ||
|
|
377
|
+
const parsed = url.parse(req.url || "/");
|
|
378
|
+
let pathname = decodeURI(parsed.pathname || "/");
|
|
280
379
|
// Serve dev toast assets and config
|
|
281
|
-
if (pathname ===
|
|
282
|
-
res.writeHead(200, {
|
|
283
|
-
|
|
284
|
-
|
|
380
|
+
if (pathname === "/__livereload-config") {
|
|
381
|
+
res.writeHead(200, {
|
|
382
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
383
|
+
"Cache-Control": "no-cache",
|
|
384
|
+
});
|
|
385
|
+
const cfgPath = path.join(__dirname, "devtoast.config.json");
|
|
386
|
+
let cfg = {
|
|
387
|
+
buildingText: "Rebuilding…",
|
|
388
|
+
reloadedText: "Reloaded",
|
|
389
|
+
fadeMs: 800,
|
|
390
|
+
reloadDelayMs: 200,
|
|
391
|
+
};
|
|
285
392
|
try {
|
|
286
393
|
if (fs.existsSync(cfgPath)) {
|
|
287
|
-
const raw = fs.readFileSync(cfgPath,
|
|
394
|
+
const raw = fs.readFileSync(cfgPath, "utf8");
|
|
288
395
|
const parsedCfg = JSON.parse(raw);
|
|
289
396
|
cfg = { ...cfg, ...parsedCfg };
|
|
290
397
|
}
|
|
@@ -292,34 +399,39 @@ function startServer() {
|
|
|
292
399
|
res.end(JSON.stringify(cfg));
|
|
293
400
|
return;
|
|
294
401
|
}
|
|
295
|
-
if (pathname ===
|
|
296
|
-
res.writeHead(200, {
|
|
297
|
-
|
|
402
|
+
if (pathname === "/__livereload.css") {
|
|
403
|
+
res.writeHead(200, {
|
|
404
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
405
|
+
"Cache-Control": "no-cache",
|
|
406
|
+
});
|
|
407
|
+
const cssPath = path.join(__dirname, "devtoast.css");
|
|
298
408
|
let css = `#__lr_toast{position:fixed;bottom:12px;left:12px;background:rgba(0,0,0,.8);color:#fff;padding:6px 10px;border-radius:6px;font:12px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.3);opacity:0;transition:opacity .15s ease}`;
|
|
299
409
|
try {
|
|
300
|
-
if (fs.existsSync(cssPath)) css = fs.readFileSync(cssPath,
|
|
410
|
+
if (fs.existsSync(cssPath)) css = fs.readFileSync(cssPath, "utf8");
|
|
301
411
|
} catch (_) {}
|
|
302
412
|
res.end(css);
|
|
303
413
|
return;
|
|
304
414
|
}
|
|
305
|
-
if (pathname ===
|
|
415
|
+
if (pathname === "/__livereload") {
|
|
306
416
|
res.writeHead(200, {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
Connection:
|
|
417
|
+
"Content-Type": "text/event-stream",
|
|
418
|
+
"Cache-Control": "no-cache, no-transform",
|
|
419
|
+
Connection: "keep-alive",
|
|
310
420
|
});
|
|
311
|
-
res.write(
|
|
421
|
+
res.write(": connected\n\n");
|
|
312
422
|
clients.add(res);
|
|
313
423
|
const keepAlive = setInterval(() => {
|
|
314
|
-
try {
|
|
424
|
+
try {
|
|
425
|
+
res.write(": ping\n\n");
|
|
426
|
+
} catch (_) {}
|
|
315
427
|
}, 30000);
|
|
316
|
-
req.on(
|
|
428
|
+
req.on("close", () => {
|
|
317
429
|
clearInterval(keepAlive);
|
|
318
430
|
clients.delete(res);
|
|
319
431
|
});
|
|
320
432
|
return;
|
|
321
433
|
}
|
|
322
|
-
if (pathname ===
|
|
434
|
+
if (pathname === "/") pathname = "/index.html";
|
|
323
435
|
|
|
324
436
|
// Resolve candidate paths in order:
|
|
325
437
|
// 1) as-is
|
|
@@ -327,7 +439,7 @@ function startServer() {
|
|
|
327
439
|
// 3) if a directory, use its index.html
|
|
328
440
|
let filePath = null;
|
|
329
441
|
const candidateA = path.join(OUT_DIR, pathname);
|
|
330
|
-
const candidateB = path.join(OUT_DIR, pathname +
|
|
442
|
+
const candidateB = path.join(OUT_DIR, pathname + ".html");
|
|
331
443
|
if (fs.existsSync(candidateA)) {
|
|
332
444
|
filePath = candidateA;
|
|
333
445
|
} else if (fs.existsSync(candidateB)) {
|
|
@@ -340,7 +452,7 @@ function startServer() {
|
|
|
340
452
|
try {
|
|
341
453
|
const st = fs.statSync(maybeDir);
|
|
342
454
|
if (st.isDirectory()) {
|
|
343
|
-
const idx = path.join(maybeDir,
|
|
455
|
+
const idx = path.join(maybeDir, "index.html");
|
|
344
456
|
if (fs.existsSync(idx)) filePath = idx;
|
|
345
457
|
}
|
|
346
458
|
} catch (_) {}
|
|
@@ -348,8 +460,8 @@ function startServer() {
|
|
|
348
460
|
}
|
|
349
461
|
if (!filePath) {
|
|
350
462
|
res.statusCode = 404;
|
|
351
|
-
res.setHeader(
|
|
352
|
-
res.end(
|
|
463
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
464
|
+
res.end("Not Found");
|
|
353
465
|
return;
|
|
354
466
|
}
|
|
355
467
|
|
|
@@ -357,7 +469,7 @@ function startServer() {
|
|
|
357
469
|
let resolved = path.resolve(filePath);
|
|
358
470
|
if (!resolved.startsWith(OUT_DIR)) {
|
|
359
471
|
res.statusCode = 403;
|
|
360
|
-
res.end(
|
|
472
|
+
res.end("Forbidden");
|
|
361
473
|
return;
|
|
362
474
|
}
|
|
363
475
|
|
|
@@ -365,13 +477,13 @@ function startServer() {
|
|
|
365
477
|
try {
|
|
366
478
|
const st = fs.statSync(resolved);
|
|
367
479
|
if (st.isDirectory()) {
|
|
368
|
-
const idx = path.join(resolved,
|
|
480
|
+
const idx = path.join(resolved, "index.html");
|
|
369
481
|
if (fs.existsSync(idx)) {
|
|
370
482
|
filePath = idx;
|
|
371
483
|
resolved = path.resolve(filePath);
|
|
372
484
|
} else {
|
|
373
485
|
res.statusCode = 404;
|
|
374
|
-
res.end(
|
|
486
|
+
res.end("Not Found");
|
|
375
487
|
return;
|
|
376
488
|
}
|
|
377
489
|
}
|
|
@@ -381,14 +493,14 @@ function startServer() {
|
|
|
381
493
|
resolved = path.resolve(filePath);
|
|
382
494
|
const ext = path.extname(resolved).toLowerCase();
|
|
383
495
|
res.statusCode = 200;
|
|
384
|
-
res.setHeader(
|
|
496
|
+
res.setHeader("Content-Type", MIME[ext] || "application/octet-stream");
|
|
385
497
|
// Dev: always disable caching so reloads fetch fresh assets
|
|
386
|
-
res.setHeader(
|
|
387
|
-
res.setHeader(
|
|
388
|
-
res.setHeader(
|
|
389
|
-
if (ext ===
|
|
498
|
+
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
499
|
+
res.setHeader("Pragma", "no-cache");
|
|
500
|
+
res.setHeader("Expires", "0");
|
|
501
|
+
if (ext === ".html") {
|
|
390
502
|
try {
|
|
391
|
-
let html = fs.readFileSync(resolved,
|
|
503
|
+
let html = fs.readFileSync(resolved, "utf8");
|
|
392
504
|
const snippet = `
|
|
393
505
|
<link rel="stylesheet" href="/__livereload.css">
|
|
394
506
|
<script>(function(){
|
|
@@ -433,11 +545,13 @@ function startServer() {
|
|
|
433
545
|
};
|
|
434
546
|
window.addEventListener('beforeunload', function(){ try { es.close(); } catch(e) {} });
|
|
435
547
|
})();</script>`;
|
|
436
|
-
html = html.includes(
|
|
548
|
+
html = html.includes("</body>")
|
|
549
|
+
? html.replace("</body>", snippet + "</body>")
|
|
550
|
+
: html + snippet;
|
|
437
551
|
res.end(html);
|
|
438
552
|
} catch (e) {
|
|
439
553
|
res.statusCode = 500;
|
|
440
|
-
res.end(
|
|
554
|
+
res.end("Error serving HTML");
|
|
441
555
|
}
|
|
442
556
|
} else {
|
|
443
557
|
fs.createReadStream(resolved).pipe(res);
|
|
@@ -453,12 +567,12 @@ function startServer() {
|
|
|
453
567
|
|
|
454
568
|
async function dev() {
|
|
455
569
|
if (!fs.existsSync(CONTENT_DIR)) {
|
|
456
|
-
console.error(
|
|
570
|
+
console.error("No content directory found at", CONTENT_DIR);
|
|
457
571
|
process.exit(1);
|
|
458
572
|
}
|
|
459
573
|
// Start server before the initial build so build logs follow server standup
|
|
460
574
|
startServer();
|
|
461
|
-
console.log(
|
|
575
|
+
console.log("Initial build...");
|
|
462
576
|
// Expose a base URL for builders to construct absolute ids/links
|
|
463
577
|
if (!process.env.CANOPY_BASE_URL) {
|
|
464
578
|
process.env.CANOPY_BASE_URL = `http://localhost:${PORT}`;
|
|
@@ -466,12 +580,14 @@ async function dev() {
|
|
|
466
580
|
// In dev, let the Tailwind watcher own CSS generation to avoid duplicate
|
|
467
581
|
// one-off builds that print "Rebuilding..." messages. Skip ensureStyles()
|
|
468
582
|
// within build() by setting an environment flag.
|
|
469
|
-
process.env.CANOPY_SKIP_STYLES = process.env.DEV_ONCE ?
|
|
583
|
+
process.env.CANOPY_SKIP_STYLES = process.env.DEV_ONCE ? "" : "1";
|
|
470
584
|
// Suppress noisy Browserslist old data warning in dev/tailwind
|
|
471
|
-
process.env.BROWSERSLIST_IGNORE_OLD_DATA =
|
|
585
|
+
process.env.BROWSERSLIST_IGNORE_OLD_DATA = "1";
|
|
472
586
|
if (process.env.DEV_ONCE) {
|
|
473
587
|
// Build once and exit (used for tests/CI)
|
|
474
|
-
runBuild()
|
|
588
|
+
runBuild()
|
|
589
|
+
.then(() => process.exit(0))
|
|
590
|
+
.catch(() => process.exit(1));
|
|
475
591
|
return;
|
|
476
592
|
}
|
|
477
593
|
// Run the initial build synchronously now that the server is up
|
|
@@ -480,37 +596,68 @@ async function dev() {
|
|
|
480
596
|
// Start Tailwind watcher if config + input exist (after initial build)
|
|
481
597
|
try {
|
|
482
598
|
const root = process.cwd();
|
|
483
|
-
const appStylesDir = path.join(root,
|
|
484
|
-
const twConfigsRoot = [
|
|
485
|
-
.
|
|
486
|
-
|
|
487
|
-
.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
599
|
+
const appStylesDir = path.join(root, "app", "styles");
|
|
600
|
+
const twConfigsRoot = [
|
|
601
|
+
"tailwind.config.js",
|
|
602
|
+
"tailwind.config.cjs",
|
|
603
|
+
"tailwind.config.mjs",
|
|
604
|
+
"tailwind.config.ts",
|
|
605
|
+
].map((n) => path.join(root, n));
|
|
606
|
+
const twConfigsApp = [
|
|
607
|
+
"tailwind.config.js",
|
|
608
|
+
"tailwind.config.cjs",
|
|
609
|
+
"tailwind.config.mjs",
|
|
610
|
+
"tailwind.config.ts",
|
|
611
|
+
].map((n) => path.join(appStylesDir, n));
|
|
612
|
+
let configPath = [...twConfigsApp, ...twConfigsRoot].find((p) => {
|
|
613
|
+
try {
|
|
614
|
+
return fs.existsSync(p);
|
|
615
|
+
} catch (_) {
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
const inputCandidates = [
|
|
620
|
+
path.join(appStylesDir, "index.css"),
|
|
621
|
+
path.join(CONTENT_DIR, "_styles.css"),
|
|
622
|
+
];
|
|
623
|
+
let inputCss = inputCandidates.find((p) => {
|
|
624
|
+
try {
|
|
625
|
+
return fs.existsSync(p);
|
|
626
|
+
} catch (_) {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
});
|
|
491
630
|
// Generate fallback config and input if missing
|
|
492
631
|
if (!configPath) {
|
|
493
632
|
try {
|
|
494
|
-
const { CACHE_DIR } = require(
|
|
495
|
-
const genDir = path.join(CACHE_DIR,
|
|
633
|
+
const { CACHE_DIR } = require("./common");
|
|
634
|
+
const genDir = path.join(CACHE_DIR, "tailwind");
|
|
496
635
|
ensureDirSync(genDir);
|
|
497
|
-
const genCfg = path.join(genDir,
|
|
636
|
+
const genCfg = path.join(genDir, "tailwind.config.js");
|
|
498
637
|
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
|
-
fs.writeFileSync(genCfg, cfg,
|
|
638
|
+
fs.writeFileSync(genCfg, cfg, "utf8");
|
|
500
639
|
configPath = genCfg;
|
|
501
|
-
} catch (_) {
|
|
640
|
+
} catch (_) {
|
|
641
|
+
configPath = null;
|
|
642
|
+
}
|
|
502
643
|
}
|
|
503
644
|
if (!inputCss) {
|
|
504
645
|
try {
|
|
505
|
-
const { CACHE_DIR } = require(
|
|
506
|
-
const genDir = path.join(CACHE_DIR,
|
|
646
|
+
const { CACHE_DIR } = require("./common");
|
|
647
|
+
const genDir = path.join(CACHE_DIR, "tailwind");
|
|
507
648
|
ensureDirSync(genDir);
|
|
508
|
-
const genCss = path.join(genDir,
|
|
509
|
-
fs.writeFileSync(
|
|
649
|
+
const genCss = path.join(genDir, "index.css");
|
|
650
|
+
fs.writeFileSync(
|
|
651
|
+
genCss,
|
|
652
|
+
`@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`,
|
|
653
|
+
"utf8"
|
|
654
|
+
);
|
|
510
655
|
inputCss = genCss;
|
|
511
|
-
} catch (_) {
|
|
656
|
+
} catch (_) {
|
|
657
|
+
inputCss = null;
|
|
658
|
+
}
|
|
512
659
|
}
|
|
513
|
-
const outputCss = path.join(OUT_DIR,
|
|
660
|
+
const outputCss = path.join(OUT_DIR, "styles", "styles.css");
|
|
514
661
|
if (configPath && inputCss) {
|
|
515
662
|
// Ensure output dir exists and start watcher
|
|
516
663
|
ensureDirSync(path.dirname(outputCss));
|
|
@@ -521,35 +668,70 @@ async function dev() {
|
|
|
521
668
|
if (!fs.existsSync(outputCss)) {
|
|
522
669
|
const base = `:root{--max-w:760px;--muted:#6b7280}*{box-sizing:border-box}body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Helvetica,Arial,sans-serif;max-width:var(--max-w);margin:2rem auto;padding:0 1rem;line-height:1.6}a{color:#2563eb;text-decoration:none}a:hover{text-decoration:underline}`;
|
|
523
670
|
ensureDirSync(path.dirname(outputCss));
|
|
524
|
-
fs.writeFileSync(outputCss, base +
|
|
525
|
-
console.log(
|
|
671
|
+
fs.writeFileSync(outputCss, base + "\n", "utf8");
|
|
672
|
+
console.log(
|
|
673
|
+
"[tailwind] wrote fallback CSS to",
|
|
674
|
+
prettyPath(outputCss)
|
|
675
|
+
);
|
|
526
676
|
}
|
|
527
677
|
} catch (_) {}
|
|
528
678
|
}
|
|
529
|
-
function fileSizeKb(p) {
|
|
679
|
+
function fileSizeKb(p) {
|
|
680
|
+
try {
|
|
681
|
+
const st = fs.statSync(p);
|
|
682
|
+
return st && st.size ? (st.size / 1024).toFixed(1) : "0.0";
|
|
683
|
+
} catch (_) {
|
|
684
|
+
return "0.0";
|
|
685
|
+
}
|
|
686
|
+
}
|
|
530
687
|
// Initial one-off compile so the CSS exists before watcher starts
|
|
531
688
|
try {
|
|
532
689
|
const cliOnce = resolveTailwindCli();
|
|
533
690
|
if (cliOnce) {
|
|
534
|
-
const { spawnSync } = require(
|
|
535
|
-
const argsOnce = [
|
|
536
|
-
|
|
691
|
+
const { spawnSync } = require("child_process");
|
|
692
|
+
const argsOnce = [
|
|
693
|
+
"-i",
|
|
694
|
+
inputCss,
|
|
695
|
+
"-o",
|
|
696
|
+
outputCss,
|
|
697
|
+
"-c",
|
|
698
|
+
configPath,
|
|
699
|
+
"--minify",
|
|
700
|
+
];
|
|
701
|
+
const res = spawnSync(cliOnce.cmd, [...cliOnce.args, ...argsOnce], {
|
|
702
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
703
|
+
env: { ...process.env, BROWSERSLIST_IGNORE_OLD_DATA: "1" },
|
|
704
|
+
});
|
|
537
705
|
if (res && res.status === 0) {
|
|
538
|
-
console.log(
|
|
706
|
+
console.log(
|
|
707
|
+
`[tailwind] initial build ok (${fileSizeKb(outputCss)} KB) →`,
|
|
708
|
+
prettyPath(outputCss)
|
|
709
|
+
);
|
|
539
710
|
} else {
|
|
540
|
-
console.warn(
|
|
541
|
-
try {
|
|
711
|
+
console.warn("[tailwind] initial build failed; using fallback CSS");
|
|
712
|
+
try {
|
|
713
|
+
if (res && res.stderr) process.stderr.write(res.stderr);
|
|
714
|
+
} catch (_) {}
|
|
542
715
|
writeFallbackCssIfMissing();
|
|
543
716
|
}
|
|
544
717
|
} else {
|
|
545
|
-
console.warn(
|
|
718
|
+
console.warn("[tailwind] CLI not found; using fallback CSS");
|
|
546
719
|
writeFallbackCssIfMissing();
|
|
547
720
|
}
|
|
548
721
|
} catch (_) {}
|
|
549
722
|
// Prefer direct CLI spawn so we can mute initial rebuild logs
|
|
550
723
|
const cli = resolveTailwindCli();
|
|
551
724
|
if (cli) {
|
|
552
|
-
const args = [
|
|
725
|
+
const args = [
|
|
726
|
+
"-i",
|
|
727
|
+
inputCss,
|
|
728
|
+
"-o",
|
|
729
|
+
outputCss,
|
|
730
|
+
"--watch",
|
|
731
|
+
"-c",
|
|
732
|
+
configPath,
|
|
733
|
+
"--minify",
|
|
734
|
+
];
|
|
553
735
|
let unmuted = false;
|
|
554
736
|
let cssWatcherAttached = false;
|
|
555
737
|
function attachCssWatcherOnce() {
|
|
@@ -559,48 +741,94 @@ async function dev() {
|
|
|
559
741
|
fs.watch(outputCss, { persistent: false }, () => {
|
|
560
742
|
if (!unmuted) {
|
|
561
743
|
unmuted = true;
|
|
562
|
-
console.log(
|
|
744
|
+
console.log(
|
|
745
|
+
`[tailwind] watching ${prettyPath(
|
|
746
|
+
inputCss
|
|
747
|
+
)} — compiled (${fileSizeKb(outputCss)} KB)`
|
|
748
|
+
);
|
|
563
749
|
}
|
|
564
|
-
try {
|
|
750
|
+
try {
|
|
751
|
+
onCssChange();
|
|
752
|
+
} catch (_) {}
|
|
565
753
|
});
|
|
566
754
|
} catch (_) {}
|
|
567
755
|
}
|
|
568
756
|
function compileTailwindOnce() {
|
|
569
757
|
try {
|
|
570
|
-
const { spawnSync } = require(
|
|
571
|
-
const res = spawnSync(
|
|
758
|
+
const { spawnSync } = require("child_process");
|
|
759
|
+
const res = spawnSync(
|
|
760
|
+
cli.cmd,
|
|
761
|
+
[
|
|
762
|
+
...cli.args,
|
|
763
|
+
"-i",
|
|
764
|
+
inputCss,
|
|
765
|
+
"-o",
|
|
766
|
+
outputCss,
|
|
767
|
+
"-c",
|
|
768
|
+
configPath,
|
|
769
|
+
"--minify",
|
|
770
|
+
],
|
|
771
|
+
{
|
|
772
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
773
|
+
env: { ...process.env, BROWSERSLIST_IGNORE_OLD_DATA: "1" },
|
|
774
|
+
}
|
|
775
|
+
);
|
|
572
776
|
if (res && res.status === 0) {
|
|
573
|
-
console.log(
|
|
574
|
-
|
|
777
|
+
console.log(
|
|
778
|
+
`[tailwind] compiled (${fileSizeKb(outputCss)} KB) →`,
|
|
779
|
+
prettyPath(outputCss)
|
|
780
|
+
);
|
|
781
|
+
try {
|
|
782
|
+
onCssChange();
|
|
783
|
+
} catch (_) {}
|
|
575
784
|
} else {
|
|
576
|
-
console.warn(
|
|
577
|
-
try {
|
|
785
|
+
console.warn("[tailwind] on-demand compile failed");
|
|
786
|
+
try {
|
|
787
|
+
if (res && res.stderr) process.stderr.write(res.stderr);
|
|
788
|
+
} catch (_) {}
|
|
578
789
|
}
|
|
579
790
|
} catch (_) {}
|
|
580
791
|
}
|
|
581
792
|
function startTailwindWatcher() {
|
|
582
793
|
unmuted = false;
|
|
583
|
-
const proc = spawn(cli.cmd, [...cli.args, ...args], {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (!unmuted) {
|
|
587
|
-
if (/error/i.test(s)) { try { process.stdout.write('[tailwind] ' + s); } catch (_) {} }
|
|
588
|
-
} else {
|
|
589
|
-
try { process.stdout.write(s); } catch (_) {}
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
if (proc.stderr) proc.stderr.on('data', (d) => {
|
|
593
|
-
const s = d ? String(d) : '';
|
|
594
|
-
if (!unmuted) {
|
|
595
|
-
if (s.trim()) { try { process.stderr.write('[tailwind] ' + s); } catch (_) {} }
|
|
596
|
-
} else {
|
|
597
|
-
try { process.stderr.write(s); } catch (_) {}
|
|
598
|
-
}
|
|
794
|
+
const proc = spawn(cli.cmd, [...cli.args, ...args], {
|
|
795
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
796
|
+
env: { ...process.env, BROWSERSLIST_IGNORE_OLD_DATA: "1" },
|
|
599
797
|
});
|
|
600
|
-
proc.
|
|
798
|
+
if (proc.stdout)
|
|
799
|
+
proc.stdout.on("data", (d) => {
|
|
800
|
+
const s = d ? String(d) : "";
|
|
801
|
+
if (!unmuted) {
|
|
802
|
+
if (/error/i.test(s)) {
|
|
803
|
+
try {
|
|
804
|
+
process.stdout.write("[tailwind] " + s);
|
|
805
|
+
} catch (_) {}
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
try {
|
|
809
|
+
process.stdout.write(s);
|
|
810
|
+
} catch (_) {}
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
if (proc.stderr)
|
|
814
|
+
proc.stderr.on("data", (d) => {
|
|
815
|
+
const s = d ? String(d) : "";
|
|
816
|
+
if (!unmuted) {
|
|
817
|
+
if (s.trim()) {
|
|
818
|
+
try {
|
|
819
|
+
process.stderr.write("[tailwind] " + s);
|
|
820
|
+
} catch (_) {}
|
|
821
|
+
}
|
|
822
|
+
} else {
|
|
823
|
+
try {
|
|
824
|
+
process.stderr.write(s);
|
|
825
|
+
} catch (_) {}
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
proc.on("exit", (code) => {
|
|
601
829
|
// Ignore null exits (expected when we intentionally restart the watcher)
|
|
602
830
|
if (code !== 0 && code !== null) {
|
|
603
|
-
console.error(
|
|
831
|
+
console.error("[tailwind] watcher exited with code", code);
|
|
604
832
|
}
|
|
605
833
|
});
|
|
606
834
|
attachCssWatcherOnce();
|
|
@@ -610,55 +838,90 @@ async function dev() {
|
|
|
610
838
|
// Unmute Tailwind logs after the first successful CSS write
|
|
611
839
|
// Watch UI Tailwind plugin/preset files and restart Tailwind to pick up code changes
|
|
612
840
|
try {
|
|
613
|
-
const uiPlugin = path.join(
|
|
614
|
-
|
|
615
|
-
|
|
841
|
+
const uiPlugin = path.join(
|
|
842
|
+
__dirname,
|
|
843
|
+
"../ui",
|
|
844
|
+
"tailwind-canopy-iiif-plugin.js"
|
|
845
|
+
);
|
|
846
|
+
const uiPreset = path.join(
|
|
847
|
+
__dirname,
|
|
848
|
+
"../ui",
|
|
849
|
+
"tailwind-canopy-iiif-preset.js"
|
|
850
|
+
);
|
|
851
|
+
const uiStylesDir = path.join(__dirname, "../ui", "styles");
|
|
616
852
|
const files = [uiPlugin, uiPreset].filter((p) => {
|
|
617
|
-
try {
|
|
853
|
+
try {
|
|
854
|
+
return fs.existsSync(p);
|
|
855
|
+
} catch (_) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
618
858
|
});
|
|
619
859
|
let restartTimer = null;
|
|
620
860
|
const restart = () => {
|
|
621
861
|
clearTimeout(restartTimer);
|
|
622
862
|
restartTimer = setTimeout(() => {
|
|
623
|
-
console.log(
|
|
624
|
-
|
|
863
|
+
console.log(
|
|
864
|
+
"[tailwind] detected UI plugin/preset change — restarting Tailwind"
|
|
865
|
+
);
|
|
866
|
+
try {
|
|
867
|
+
if (child && !child.killed) child.kill();
|
|
868
|
+
} catch (_) {}
|
|
625
869
|
// Force a compile immediately so new CSS lands before reload
|
|
626
870
|
compileTailwindOnce();
|
|
627
871
|
child = startTailwindWatcher();
|
|
628
872
|
// Notify clients that a rebuild is in progress; CSS watcher will trigger reload on write
|
|
629
|
-
try {
|
|
873
|
+
try {
|
|
874
|
+
onBuildStart();
|
|
875
|
+
} catch (_) {}
|
|
630
876
|
}, 50);
|
|
631
877
|
};
|
|
632
878
|
for (const f of files) {
|
|
633
|
-
try {
|
|
879
|
+
try {
|
|
880
|
+
fs.watch(f, { persistent: false }, restart);
|
|
881
|
+
} catch (_) {}
|
|
634
882
|
}
|
|
635
883
|
// Watch UI styles directory (Sass partials used by the plugin); restart Tailwind on Sass changes
|
|
636
884
|
try {
|
|
637
885
|
if (fs.existsSync(uiStylesDir)) {
|
|
638
886
|
try {
|
|
639
|
-
fs.watch(
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
887
|
+
fs.watch(
|
|
888
|
+
uiStylesDir,
|
|
889
|
+
{ persistent: false, recursive: true },
|
|
890
|
+
(evt, fn) => {
|
|
891
|
+
try {
|
|
892
|
+
if (fn && /\.s[ac]ss$/i.test(String(fn))) restart();
|
|
893
|
+
} catch (_) {}
|
|
894
|
+
}
|
|
895
|
+
);
|
|
644
896
|
} catch (_) {
|
|
645
897
|
// Fallback: per-dir watch without recursion
|
|
646
898
|
const watchers = new Map();
|
|
647
899
|
const watchDir = (dir) => {
|
|
648
900
|
if (watchers.has(dir)) return;
|
|
649
901
|
try {
|
|
650
|
-
const w = fs.watch(
|
|
651
|
-
|
|
652
|
-
|
|
902
|
+
const w = fs.watch(
|
|
903
|
+
dir,
|
|
904
|
+
{ persistent: false },
|
|
905
|
+
(evt, fn) => {
|
|
906
|
+
try {
|
|
907
|
+
if (fn && /\.s[ac]ss$/i.test(String(fn))) restart();
|
|
908
|
+
} catch (_) {}
|
|
909
|
+
}
|
|
910
|
+
);
|
|
653
911
|
watchers.set(dir, w);
|
|
654
912
|
} catch (_) {}
|
|
655
913
|
};
|
|
656
914
|
const scan = (dir) => {
|
|
657
915
|
try {
|
|
658
|
-
const entries = fs.readdirSync(dir, {
|
|
916
|
+
const entries = fs.readdirSync(dir, {
|
|
917
|
+
withFileTypes: true,
|
|
918
|
+
});
|
|
659
919
|
for (const e of entries) {
|
|
660
920
|
const p = path.join(dir, e.name);
|
|
661
|
-
if (e.isDirectory()) {
|
|
921
|
+
if (e.isDirectory()) {
|
|
922
|
+
watchDir(p);
|
|
923
|
+
scan(p);
|
|
924
|
+
}
|
|
662
925
|
}
|
|
663
926
|
} catch (_) {}
|
|
664
927
|
};
|
|
@@ -668,49 +931,75 @@ async function dev() {
|
|
|
668
931
|
}
|
|
669
932
|
} catch (_) {}
|
|
670
933
|
// Also watch the app Tailwind config; restart Tailwind when it changes
|
|
671
|
-
try {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
934
|
+
try {
|
|
935
|
+
if (configPath && fs.existsSync(configPath))
|
|
936
|
+
fs.watch(configPath, { persistent: false }, () => {
|
|
937
|
+
console.log(
|
|
938
|
+
"[tailwind] tailwind.config change — restarting Tailwind"
|
|
939
|
+
);
|
|
940
|
+
restart();
|
|
941
|
+
});
|
|
942
|
+
} catch (_) {}
|
|
675
943
|
// If the input CSS lives under app/styles, watch the directory for direct edits to CSS/partials
|
|
676
944
|
try {
|
|
677
|
-
const stylesDir = path.dirname(inputCss ||
|
|
678
|
-
if (stylesDir && stylesDir.includes(path.join(
|
|
945
|
+
const stylesDir = path.dirname(inputCss || "");
|
|
946
|
+
if (stylesDir && stylesDir.includes(path.join("app", "styles"))) {
|
|
679
947
|
let cssDebounce = null;
|
|
680
948
|
fs.watch(stylesDir, { persistent: false }, (evt, fn) => {
|
|
681
949
|
clearTimeout(cssDebounce);
|
|
682
950
|
cssDebounce = setTimeout(() => {
|
|
683
|
-
try {
|
|
951
|
+
try {
|
|
952
|
+
onBuildStart();
|
|
953
|
+
} catch (_) {}
|
|
684
954
|
// Force a compile so changes in index.css or partials are reflected immediately
|
|
685
|
-
try {
|
|
686
|
-
|
|
955
|
+
try {
|
|
956
|
+
compileTailwindOnce();
|
|
957
|
+
} catch (_) {}
|
|
958
|
+
try {
|
|
959
|
+
onCssChange();
|
|
960
|
+
} catch (_) {}
|
|
687
961
|
}, 50);
|
|
688
962
|
});
|
|
689
963
|
}
|
|
690
964
|
} catch (_) {}
|
|
691
965
|
} catch (_) {}
|
|
692
|
-
} else if (twHelper && typeof twHelper.watchTailwind ===
|
|
966
|
+
} else if (twHelper && typeof twHelper.watchTailwind === "function") {
|
|
693
967
|
// Fallback to helper (cannot mute its initial logs)
|
|
694
|
-
child = twHelper.watchTailwind({
|
|
968
|
+
child = twHelper.watchTailwind({
|
|
969
|
+
input: inputCss,
|
|
970
|
+
output: outputCss,
|
|
971
|
+
config: configPath,
|
|
972
|
+
minify: false,
|
|
973
|
+
});
|
|
695
974
|
if (child) {
|
|
696
|
-
console.log(
|
|
697
|
-
try {
|
|
975
|
+
console.log("[tailwind] watching", prettyPath(inputCss));
|
|
976
|
+
try {
|
|
977
|
+
fs.watch(outputCss, { persistent: false }, () => {
|
|
978
|
+
try {
|
|
979
|
+
onCssChange();
|
|
980
|
+
} catch (_) {}
|
|
981
|
+
});
|
|
982
|
+
} catch (_) {}
|
|
698
983
|
}
|
|
699
984
|
}
|
|
700
985
|
}
|
|
701
986
|
} catch (_) {}
|
|
702
|
-
console.log(
|
|
987
|
+
console.log("[Watching]", prettyPath(CONTENT_DIR), "(Ctrl+C to stop)");
|
|
703
988
|
const rw = tryRecursiveWatch();
|
|
704
989
|
if (!rw) watchPerDir();
|
|
705
990
|
// Watch assets for live copy without full rebuild
|
|
706
991
|
if (fs.existsSync(ASSETS_DIR)) {
|
|
707
|
-
console.log(
|
|
992
|
+
console.log("[Watching]", prettyPath(ASSETS_DIR), "(assets live-reload)");
|
|
708
993
|
const arw = tryRecursiveWatchAssets();
|
|
709
994
|
if (!arw) watchAssetsPerDir();
|
|
710
995
|
}
|
|
711
996
|
// Watch UI dist for live-reload and targeted search runtime rebuilds
|
|
712
997
|
if (fs.existsSync(UI_DIST_DIR)) {
|
|
713
|
-
console.log(
|
|
998
|
+
console.log(
|
|
999
|
+
"[Watching]",
|
|
1000
|
+
prettyPath(UI_DIST_DIR),
|
|
1001
|
+
"(@canopy-iiif/app/ui dist)"
|
|
1002
|
+
);
|
|
714
1003
|
const urw = tryRecursiveWatchUiDist();
|
|
715
1004
|
if (!urw) watchUiDistPerDir();
|
|
716
1005
|
}
|