@kenjura/ursa 0.75.0 → 0.77.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.
- package/CHANGELOG.md +85 -0
- package/meta/default.css +149 -3
- package/meta/templates/default-template/default.css +1268 -0
- package/meta/{default-template.html → templates/default-template/index.html} +49 -2
- package/meta/{menu.js → templates/default-template/menu.js} +1 -1
- package/meta/templates/default-template/sectionify.js +46 -0
- package/meta/templates/default-template/widgets.js +701 -0
- package/package.json +1 -1
- package/src/dev.js +125 -34
- package/src/helper/assetBundler.js +471 -0
- package/src/helper/build/autoIndex.js +26 -23
- package/src/helper/build/cacheBust.js +79 -0
- package/src/helper/build/navCache.js +4 -0
- package/src/helper/build/templates.js +176 -19
- package/src/helper/build/watchCache.js +7 -0
- package/src/helper/customMenu.js +4 -2
- package/src/helper/dependencyTracker.js +269 -0
- package/src/helper/findScriptJs.js +29 -0
- package/src/helper/findStyleCss.js +29 -0
- package/src/helper/portUtils.js +132 -0
- package/src/jobs/generate.js +276 -59
- package/src/serve.js +446 -162
- package/meta/character-sheet.css +0 -50
- package/meta/widgets.js +0 -376
- /package/meta/{goudy_bookletter_1911-webfont.woff → shared/goudy_bookletter_1911-webfont.woff} +0 -0
- /package/meta/{character-sheet/css → templates/character-sheet-template}/character-sheet.css +0 -0
- /package/meta/{character-sheet/js → templates/character-sheet-template}/components.js +0 -0
- /package/meta/{cssui.bundle.min.css → templates/character-sheet-template/cssui.bundle.min.css} +0 -0
- /package/meta/{character-sheet-template.html → templates/character-sheet-template/index.html} +0 -0
- /package/meta/{character-sheet/js → templates/character-sheet-template}/main.js +0 -0
- /package/meta/{character-sheet/js → templates/character-sheet-template}/model.js +0 -0
- /package/meta/{search.js → templates/default-template/search.js} +0 -0
- /package/meta/{sticky.js → templates/default-template/sticky.js} +0 -0
- /package/meta/{toc-generator.js → templates/default-template/toc-generator.js} +0 -0
- /package/meta/{toc.js → templates/default-template/toc.js} +0 -0
- /package/meta/{template2.html → templates/template2/index.html} +0 -0
package/src/dev.js
CHANGED
|
@@ -20,7 +20,7 @@ const { readFile, readdir, stat, mkdir } = promises;
|
|
|
20
20
|
// Import helper modules
|
|
21
21
|
import { renderFileAsync } from "./helper/fileRenderer.js";
|
|
22
22
|
import { findStyleCss } from "./helper/findStyleCss.js";
|
|
23
|
-
import {
|
|
23
|
+
import { findAllScriptJs } from "./helper/findScriptJs.js";
|
|
24
24
|
import { extractMetadata, getAutoIndexConfig, isMetadataOnly } from "./helper/metadataExtractor.js";
|
|
25
25
|
import { injectFrontmatterTable } from "./helper/frontmatterTable.js";
|
|
26
26
|
import { buildValidPaths, markInactiveLinks, resolveRelativeUrls } from "./helper/linkValidator.js";
|
|
@@ -33,9 +33,12 @@ import { extractImageReferences } from "./helper/imageExtractor.js";
|
|
|
33
33
|
import { recurse } from "./helper/recursive-readdir.js";
|
|
34
34
|
import { isFolderHidden, clearConfigCache } from "./helper/folderConfig.js";
|
|
35
35
|
import { extractSections } from "./helper/sectionExtractor.js";
|
|
36
|
-
import { getTemplates, getMenu, findAllCustomMenus, getCustomMenuForFile, getTransformedMetadata, getFooter, toTitleCase, addTrailingSlash, generateAutoIndexHtmlFromSource } from "./helper/build/index.js";
|
|
36
|
+
import { getTemplates, getMenu, findAllCustomMenus, getCustomMenuForFile, getTransformedMetadata, getFooter, toTitleCase, addTrailingSlash, generateAutoIndexHtmlFromSource, copyMetaAssets } from "./helper/build/index.js";
|
|
37
37
|
import { findCustomMenu, extractMenuFrontmatter, parseCustomMenu, combineAutoAndManualMenu } from "./helper/customMenu.js";
|
|
38
38
|
import { getAndIncrementBuildId } from "./helper/ursaConfig.js";
|
|
39
|
+
import { resolvePort } from "./helper/portUtils.js";
|
|
40
|
+
import { findAllStyleCss } from "./helper/findStyleCss.js";
|
|
41
|
+
import { bundleMetaTemplateAssets, generateSeparateCssTags, generateSeparateJsTags, clearMetaBundleCache } from "./helper/assetBundler.js";
|
|
39
42
|
|
|
40
43
|
// Dev mode state
|
|
41
44
|
const devState = {
|
|
@@ -58,6 +61,7 @@ const devState = {
|
|
|
58
61
|
footer: null,
|
|
59
62
|
fullTextIndex: null,
|
|
60
63
|
searchIndex: null,
|
|
64
|
+
recentActivity: null,
|
|
61
65
|
|
|
62
66
|
// Path → nearest menu.md mapping
|
|
63
67
|
menuPathMap: new Map(),
|
|
@@ -265,6 +269,23 @@ function findNearestMenu(dirPath) {
|
|
|
265
269
|
return null;
|
|
266
270
|
}
|
|
267
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Find all style.css files from docroot to dirPath (all levels)
|
|
274
|
+
*/
|
|
275
|
+
async function findAllStyles(dirPath) {
|
|
276
|
+
const { stylePathMap, source } = devState;
|
|
277
|
+
|
|
278
|
+
// Use a separate cache key to avoid collision with findNearestStyle
|
|
279
|
+
const cacheKey = `all:${dirPath}`;
|
|
280
|
+
if (stylePathMap.has(cacheKey)) {
|
|
281
|
+
return stylePathMap.get(cacheKey);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const stylePaths = await findAllStyleCss(dirPath, source);
|
|
285
|
+
stylePathMap.set(cacheKey, stylePaths);
|
|
286
|
+
return stylePaths;
|
|
287
|
+
}
|
|
288
|
+
|
|
268
289
|
/**
|
|
269
290
|
* Find the nearest style.css
|
|
270
291
|
*/
|
|
@@ -282,19 +303,19 @@ async function findNearestStyle(dirPath) {
|
|
|
282
303
|
}
|
|
283
304
|
|
|
284
305
|
/**
|
|
285
|
-
* Find
|
|
306
|
+
* Find all script.js files from docroot to dirPath
|
|
286
307
|
*/
|
|
287
|
-
async function
|
|
288
|
-
const { scriptPathMap } = devState;
|
|
308
|
+
async function findAllScripts(dirPath) {
|
|
309
|
+
const { scriptPathMap, source } = devState;
|
|
289
310
|
|
|
290
311
|
// Check cache first
|
|
291
312
|
if (scriptPathMap.has(dirPath)) {
|
|
292
313
|
return scriptPathMap.get(dirPath);
|
|
293
314
|
}
|
|
294
315
|
|
|
295
|
-
const
|
|
296
|
-
scriptPathMap.set(dirPath,
|
|
297
|
-
return
|
|
316
|
+
const scriptPaths = await findAllScriptJs(dirPath, source);
|
|
317
|
+
scriptPathMap.set(dirPath, scriptPaths);
|
|
318
|
+
return scriptPaths;
|
|
298
319
|
}
|
|
299
320
|
|
|
300
321
|
/**
|
|
@@ -439,32 +460,39 @@ async function wrapInTemplate(body, title, fileMeta, urlPath, sourcePath) {
|
|
|
439
460
|
throw new Error(`Template not found: ${requestedTemplateName || 'default-template'}`);
|
|
440
461
|
}
|
|
441
462
|
|
|
442
|
-
// Find
|
|
463
|
+
// Find all style.css files from docroot to current dir and serve as separate tags
|
|
464
|
+
// (Serve/dev mode: separate tags per level for individual invalidation)
|
|
443
465
|
let styleLink = "";
|
|
444
466
|
try {
|
|
445
467
|
const styleDir = sourcePath ? dirname(sourcePath) : source;
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
468
|
+
const cssPaths = await findAllStyles(styleDir);
|
|
469
|
+
if (cssPaths.length > 0) {
|
|
470
|
+
// Copy all CSS files to output
|
|
471
|
+
for (const cssPath of cssPaths) {
|
|
472
|
+
const cssOutputPath = cssPath.replace(source, output);
|
|
473
|
+
const cssContent = await readFile(cssPath, 'utf8');
|
|
474
|
+
await outputFile(cssOutputPath, cssContent);
|
|
475
|
+
}
|
|
476
|
+
styleLink = generateSeparateCssTags(cssPaths, source);
|
|
455
477
|
}
|
|
456
478
|
} catch (e) {
|
|
457
479
|
// Ignore CSS errors
|
|
458
480
|
}
|
|
459
481
|
|
|
460
|
-
// Find
|
|
482
|
+
// Find all script.js files from docroot to current dir and serve as separate external tags
|
|
483
|
+
// (Serve/dev mode: separate tags per level for individual invalidation)
|
|
461
484
|
let customScript = "";
|
|
462
485
|
try {
|
|
463
486
|
const scriptDir = sourcePath ? dirname(sourcePath) : source;
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
|
|
487
|
+
const scriptPaths = await findAllScripts(scriptDir);
|
|
488
|
+
if (scriptPaths.length > 0) {
|
|
489
|
+
// Copy all script files to output so they can be served
|
|
490
|
+
for (const scriptPath of scriptPaths) {
|
|
491
|
+
const scriptOutputPath = scriptPath.replace(source, output);
|
|
492
|
+
const scriptContent = await readFile(scriptPath, 'utf8');
|
|
493
|
+
await outputFile(scriptOutputPath, scriptContent);
|
|
494
|
+
}
|
|
495
|
+
customScript = generateSeparateJsTags(scriptPaths, source);
|
|
468
496
|
}
|
|
469
497
|
} catch (e) {
|
|
470
498
|
// Ignore script errors
|
|
@@ -614,9 +642,10 @@ async function buildBackgroundCaches() {
|
|
|
614
642
|
await findNearestStyle(dir);
|
|
615
643
|
}
|
|
616
644
|
|
|
617
|
-
// 7. Pre-load templates
|
|
645
|
+
// 7. Pre-load templates and re-bundle meta assets
|
|
618
646
|
console.log(' 📄 Pre-loading templates...');
|
|
619
|
-
|
|
647
|
+
const rawTemplatesBg = await getTemplates(meta);
|
|
648
|
+
devState.templates = await bundleMetaTemplateAssets(rawTemplatesBg, meta, resolve(output, 'public'), { minify: true, sourcemap: false });
|
|
620
649
|
|
|
621
650
|
// 8. Build footer
|
|
622
651
|
console.log(' 📝 Building footer...');
|
|
@@ -635,6 +664,7 @@ async function buildBackgroundCaches() {
|
|
|
635
664
|
|
|
636
665
|
const searchIndex = [];
|
|
637
666
|
const fullTextDocs = [];
|
|
667
|
+
const recentActivity = [];
|
|
638
668
|
|
|
639
669
|
for (const article of allArticles) {
|
|
640
670
|
try {
|
|
@@ -657,6 +687,16 @@ async function buildBackgroundCaches() {
|
|
|
657
687
|
title,
|
|
658
688
|
content
|
|
659
689
|
});
|
|
690
|
+
|
|
691
|
+
// Collect mtime for recent activity
|
|
692
|
+
try {
|
|
693
|
+
const articleStat = await stat(article);
|
|
694
|
+
recentActivity.push({
|
|
695
|
+
title,
|
|
696
|
+
url: relativePath.startsWith('/') ? relativePath : '/' + relativePath,
|
|
697
|
+
mtime: articleStat.mtimeMs
|
|
698
|
+
});
|
|
699
|
+
} catch (e) {}
|
|
660
700
|
} catch (e) {}
|
|
661
701
|
}
|
|
662
702
|
|
|
@@ -664,6 +704,10 @@ async function buildBackgroundCaches() {
|
|
|
664
704
|
devState.fullTextIndex = buildFullTextIndex(fullTextDocs);
|
|
665
705
|
devState.searchIndex = searchIndex;
|
|
666
706
|
|
|
707
|
+
// Build recent activity (top 10 by mtime)
|
|
708
|
+
recentActivity.sort((a, b) => b.mtime - a.mtime);
|
|
709
|
+
devState.recentActivity = recentActivity.slice(0, 10);
|
|
710
|
+
|
|
667
711
|
// Write search index files
|
|
668
712
|
const publicDir = join(output, 'public');
|
|
669
713
|
await mkdir(publicDir, { recursive: true });
|
|
@@ -671,6 +715,7 @@ async function buildBackgroundCaches() {
|
|
|
671
715
|
await outputFile(join(publicDir, 'search-index.json'), JSON.stringify(searchIndex));
|
|
672
716
|
await outputFile(join(publicDir, 'fulltext-index.json'), JSON.stringify(devState.fullTextIndex));
|
|
673
717
|
await outputFile(join(publicDir, 'menu-data.json'), JSON.stringify(devState.menuData));
|
|
718
|
+
await outputFile(join(publicDir, 'recent-activity.json'), JSON.stringify(devState.recentActivity));
|
|
674
719
|
|
|
675
720
|
devState.searchReady = true;
|
|
676
721
|
console.log('✅ Search index ready');
|
|
@@ -705,15 +750,20 @@ export async function dev({
|
|
|
705
750
|
console.log(`🎨 Meta: ${metaDir}`);
|
|
706
751
|
console.log(`📤 Output: ${outputDir}`);
|
|
707
752
|
console.log('━'.repeat(50));
|
|
708
|
-
|
|
753
|
+
|
|
754
|
+
// Resolve port (prompt user if occupied)
|
|
755
|
+
port = await resolvePort(port);
|
|
756
|
+
|
|
709
757
|
// Create output directory and copy meta files
|
|
710
758
|
await mkdir(outputDir, { recursive: true });
|
|
711
759
|
const publicDir = join(outputDir, 'public');
|
|
712
760
|
await mkdir(publicDir, { recursive: true });
|
|
713
|
-
await
|
|
761
|
+
await copyMetaAssets(metaDir, publicDir);
|
|
714
762
|
|
|
715
|
-
// Pre-load templates for
|
|
716
|
-
|
|
763
|
+
// Pre-load templates and bundle meta assets (minify without obfuscation for debuggability)
|
|
764
|
+
let rawTemplates = await getTemplates(metaDir);
|
|
765
|
+
devState.templates = await bundleMetaTemplateAssets(rawTemplates, metaDir, publicDir, { minify: true, sourcemap: false });
|
|
766
|
+
console.log('📦 Meta template assets bundled');
|
|
717
767
|
|
|
718
768
|
// Start server immediately
|
|
719
769
|
const app = express();
|
|
@@ -931,20 +981,29 @@ export async function dev({
|
|
|
931
981
|
devState.menuReady = false;
|
|
932
982
|
await buildBackgroundCaches();
|
|
933
983
|
} else if (isCssChange) {
|
|
934
|
-
// Copy CSS to output
|
|
984
|
+
// Copy CSS to output and clear style cache so next render picks up changes
|
|
985
|
+
devState.stylePathMap.clear();
|
|
935
986
|
try {
|
|
936
987
|
const relativePath = name.replace(sourceDir, '');
|
|
937
988
|
const outputPath = join(outputDir, relativePath);
|
|
938
989
|
const content = await readFile(name, 'utf8');
|
|
939
990
|
await outputFile(outputPath, content);
|
|
940
|
-
console.log(`✅ Copied ${relativePath}`);
|
|
991
|
+
console.log(`✅ Copied ${relativePath} (style cache cleared)`);
|
|
941
992
|
} catch (e) {
|
|
942
993
|
console.error(`Error copying CSS: ${e.message}`);
|
|
943
994
|
}
|
|
944
995
|
} else if (isScriptChange) {
|
|
945
|
-
//
|
|
996
|
+
// Copy script to output and clear script cache so next render picks up changes
|
|
946
997
|
devState.scriptPathMap.clear();
|
|
947
|
-
|
|
998
|
+
try {
|
|
999
|
+
const relativePath = name.replace(sourceDir, '');
|
|
1000
|
+
const outputPath = join(outputDir, relativePath);
|
|
1001
|
+
const content = await readFile(name, 'utf8');
|
|
1002
|
+
await outputFile(outputPath, content);
|
|
1003
|
+
console.log(`✅ Copied ${relativePath} (script cache cleared)`);
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
console.error(`Error copying script: ${e.message}`);
|
|
1006
|
+
}
|
|
948
1007
|
} else if (isComponentChange) {
|
|
949
1008
|
// Component files (.tsx, .jsx, .ts) may be imported by MDX files
|
|
950
1009
|
// Clear all MDX document caches so they re-compile with the updated component
|
|
@@ -956,6 +1015,33 @@ export async function dev({
|
|
|
956
1015
|
console.log(`✅ MDX caches cleared for component change: ${name}`);
|
|
957
1016
|
}
|
|
958
1017
|
|
|
1018
|
+
// Update recent activity for article changes
|
|
1019
|
+
const isArticleChange = name && /\.(md|mdx|txt|yml)$/.test(name);
|
|
1020
|
+
if (isArticleChange && devState.recentActivity) {
|
|
1021
|
+
try {
|
|
1022
|
+
const articleStat = await stat(name);
|
|
1023
|
+
const ext = extname(name);
|
|
1024
|
+
const base = basename(name, ext);
|
|
1025
|
+
const relativePath = name.replace(sourceDir + '/', '').replace(/\.(md|mdx|txt|yml)$/, '.html');
|
|
1026
|
+
const titleBase = (base === 'index' || base === 'home') ? basename(dirname(name)) : base;
|
|
1027
|
+
const title = toTitleCase(titleBase || base);
|
|
1028
|
+
const url = relativePath.startsWith('/') ? relativePath : '/' + relativePath;
|
|
1029
|
+
|
|
1030
|
+
// Remove old entry for this URL, add updated one
|
|
1031
|
+
let activity = devState.recentActivity.filter(r => r.url !== url);
|
|
1032
|
+
activity.push({ title, url, mtime: articleStat.mtimeMs });
|
|
1033
|
+
activity.sort((a, b) => b.mtime - a.mtime);
|
|
1034
|
+
devState.recentActivity = activity.slice(0, 10);
|
|
1035
|
+
|
|
1036
|
+
// Write updated file
|
|
1037
|
+
const publicDir = join(devState.output, 'public');
|
|
1038
|
+
await outputFile(join(publicDir, 'recent-activity.json'), JSON.stringify(devState.recentActivity));
|
|
1039
|
+
console.log(`✅ Recent activity updated for ${name}`);
|
|
1040
|
+
} catch (e) {
|
|
1041
|
+
// ignore
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
959
1045
|
// Broadcast reload
|
|
960
1046
|
broadcast('reload', { file: name });
|
|
961
1047
|
});
|
|
@@ -963,7 +1049,12 @@ export async function dev({
|
|
|
963
1049
|
// Watch meta directory for template changes
|
|
964
1050
|
watch(metaDir, { recursive: true }, async (evt, name) => {
|
|
965
1051
|
console.log(`🎨 Meta changed: ${name}`);
|
|
966
|
-
|
|
1052
|
+
clearMetaBundleCache();
|
|
1053
|
+
const rawMetaTemplates = await getTemplates(metaDir);
|
|
1054
|
+
// Re-copy meta files and re-bundle
|
|
1055
|
+
const pubDir = join(outputDir, 'public');
|
|
1056
|
+
await copyMetaAssets(metaDir, pubDir);
|
|
1057
|
+
devState.templates = await bundleMetaTemplateAssets(rawMetaTemplates, metaDir, pubDir, { minify: true, sourcemap: false });
|
|
967
1058
|
broadcast('reload', { file: name });
|
|
968
1059
|
});
|
|
969
1060
|
|