@kenjura/ursa 0.40.0 → 0.43.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 CHANGED
@@ -1,3 +1,20 @@
1
+ # 0.43.0
2
+ 2025-12-14
3
+
4
+ - Added buildId and .ursa.json
5
+ - Added full datetime to footer
6
+ - Added commit hash to footer as comment
7
+
8
+ # 0.42.0
9
+ 2025-12-14
10
+
11
+ - Automenu will now remove dashes from file names
12
+
13
+ # 0.41.0
14
+ 2025-12-13
15
+
16
+ - Fixed footer bug
17
+
1
18
  # 0.40.0
2
19
  2025-12-13
3
20
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@kenjura/ursa",
3
3
  "author": "Andrew London <andrew@kenjura.com>",
4
4
  "type": "module",
5
- "version": "0.40.0",
5
+ "version": "0.43.0",
6
6
  "description": "static site generator from MD/wikitext/YML",
7
7
  "main": "lib/index.js",
8
8
  "bin": {
@@ -14,6 +14,13 @@ const HOME_ICON = '🏠';
14
14
  // Index file extensions to check for folder links
15
15
  const INDEX_EXTENSIONS = ['.md', '.txt', '.yml', '.yaml'];
16
16
 
17
+ // Convert filename to display name (e.g., "foo-bar" -> "Foo Bar")
18
+ function toDisplayName(filename) {
19
+ return filename
20
+ .replace(/[-_]/g, ' ') // Replace dashes and underscores with spaces
21
+ .replace(/\b\w/g, c => c.toUpperCase()); // Capitalize first letter of each word
22
+ }
23
+
17
24
  function hasIndexFile(dirPath) {
18
25
  for (const ext of INDEX_EXTENSIONS) {
19
26
  const indexPath = join(dirPath, `index${ext}`);
@@ -151,7 +158,7 @@ function buildMenuData(tree, source, validPaths, parentPath = '') {
151
158
 
152
159
  // Get folder config for custom label and icon
153
160
  const folderConfig = hasChildren ? getFolderConfig(item.path) : null;
154
- const label = folderConfig?.label || baseName;
161
+ const label = folderConfig?.label || toDisplayName(baseName);
155
162
 
156
163
  let rawHref = null;
157
164
  if (hasChildren) {
@@ -0,0 +1,62 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ const CONFIG_FILENAME = '.ursa.json';
5
+
6
+ /**
7
+ * Get the path to the .ursa.json config file in the source directory
8
+ * @param {string} sourceDir - The source directory path
9
+ * @returns {string} Path to the config file
10
+ */
11
+ function getConfigPath(sourceDir) {
12
+ return join(sourceDir, CONFIG_FILENAME);
13
+ }
14
+
15
+ /**
16
+ * Load the ursa config from .ursa.json
17
+ * @param {string} sourceDir - The source directory path
18
+ * @returns {object} The config object (empty object if file doesn't exist)
19
+ */
20
+ export function loadUrsaConfig(sourceDir) {
21
+ const configPath = getConfigPath(sourceDir);
22
+ try {
23
+ if (existsSync(configPath)) {
24
+ const content = readFileSync(configPath, 'utf8');
25
+ return JSON.parse(content);
26
+ }
27
+ } catch (e) {
28
+ console.error(`Error reading ${CONFIG_FILENAME}: ${e.message}`);
29
+ }
30
+ return {};
31
+ }
32
+
33
+ /**
34
+ * Save the ursa config to .ursa.json
35
+ * @param {string} sourceDir - The source directory path
36
+ * @param {object} config - The config object to save
37
+ */
38
+ export function saveUrsaConfig(sourceDir, config) {
39
+ const configPath = getConfigPath(sourceDir);
40
+ try {
41
+ const content = JSON.stringify(config, null, 2);
42
+ writeFileSync(configPath, content, 'utf8');
43
+ } catch (e) {
44
+ console.error(`Error writing ${CONFIG_FILENAME}: ${e.message}`);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Get the current build ID and increment it for the next build
50
+ * @param {string} sourceDir - The source directory path
51
+ * @returns {number} The current build ID (starting from 1)
52
+ */
53
+ export function getAndIncrementBuildId(sourceDir) {
54
+ const config = loadUrsaConfig(sourceDir);
55
+ const currentBuildId = config.buildId || 0;
56
+ const newBuildId = currentBuildId + 1;
57
+
58
+ config.buildId = newBuildId;
59
+ saveUrsaConfig(sourceDir, config);
60
+
61
+ return newBuildId;
62
+ }
@@ -20,6 +20,7 @@ import {
20
20
  buildValidPaths,
21
21
  markInactiveLinks,
22
22
  } from "../helper/linkValidator.js";
23
+ import { getAndIncrementBuildId } from "../helper/ursaConfig.js";
23
24
 
24
25
  // Helper function to build search index from processed files
25
26
  function buildSearchIndex(jsonCache, source, output) {
@@ -128,8 +129,12 @@ export async function generate({
128
129
 
129
130
  const menu = await getMenu(allSourceFilenames, source, validPaths);
130
131
 
132
+ // Get and increment build ID from .ursa.json
133
+ const buildId = getAndIncrementBuildId(resolve(_source));
134
+ console.log(`Build #${buildId}`);
135
+
131
136
  // Generate footer content
132
- const footer = await getFooter(source, _source);
137
+ const footer = await getFooter(source, _source, buildId);
133
138
 
134
139
  // Load content hash cache from .ursa folder in source directory
135
140
  let hashCache = new Map();
@@ -551,8 +556,9 @@ function addTrailingSlash(somePath) {
551
556
  * Generate footer HTML from footer.md and package.json
552
557
  * @param {string} source - resolved source path with trailing slash
553
558
  * @param {string} _source - original source path
559
+ * @param {number} buildId - the current build ID
554
560
  */
555
- async function getFooter(source, _source) {
561
+ async function getFooter(source, _source, buildId) {
556
562
  const footerParts = [];
557
563
 
558
564
  // Try to read footer.md from source root
@@ -567,35 +573,58 @@ async function getFooter(source, _source) {
567
573
  console.error(`Error reading footer.md: ${e.message}`);
568
574
  }
569
575
 
570
- // Try to read package.json from doc repo
576
+ // Try to read package.json from doc repo (check both source dir and parent)
571
577
  let docPackage = null;
572
- const packagePath = join(resolve(_source), 'package.json');
573
- try {
574
- if (existsSync(packagePath)) {
575
- const packageJson = await readFile(packagePath, 'utf8');
576
- docPackage = JSON.parse(packageJson);
578
+ const sourceDir = resolve(_source);
579
+ const packagePaths = [
580
+ join(sourceDir, 'package.json'), // In source dir itself
581
+ join(sourceDir, '..', 'package.json'), // One level up (if docs is a subfolder)
582
+ ];
583
+
584
+ for (const packagePath of packagePaths) {
585
+ try {
586
+ if (existsSync(packagePath)) {
587
+ const packageJson = await readFile(packagePath, 'utf8');
588
+ docPackage = JSON.parse(packageJson);
589
+ console.log(`Found doc package.json at ${packagePath}`);
590
+ break;
591
+ }
592
+ } catch (e) {
593
+ // Continue to next path
577
594
  }
578
- } catch (e) {
579
- console.error(`Error reading doc package.json: ${e.message}`);
580
595
  }
581
596
 
582
597
  // Get ursa version from ursa's own package.json
598
+ // Use import.meta.url to find the package.json relative to this file
583
599
  let ursaVersion = 'unknown';
584
600
  try {
585
- const ursaPackagePath = new URL('../../../package.json', import.meta.url).pathname;
586
- const ursaPackageJson = await readFile(ursaPackagePath, 'utf8');
587
- const ursaPackage = JSON.parse(ursaPackageJson);
588
- ursaVersion = ursaPackage.version;
601
+ // From src/jobs/generate.js, go up to package root
602
+ const currentFileUrl = new URL(import.meta.url);
603
+ const currentDir = dirname(currentFileUrl.pathname);
604
+ const ursaPackagePath = resolve(currentDir, '..', '..', 'package.json');
605
+
606
+ if (existsSync(ursaPackagePath)) {
607
+ const ursaPackageJson = await readFile(ursaPackagePath, 'utf8');
608
+ const ursaPackage = JSON.parse(ursaPackageJson);
609
+ ursaVersion = ursaPackage.version;
610
+ console.log(`Found ursa package.json at ${ursaPackagePath}, version: ${ursaVersion}`);
611
+ }
589
612
  } catch (e) {
590
613
  console.error(`Error reading ursa package.json: ${e.message}`);
591
614
  }
592
615
 
593
- // Build meta line: version, timestamp, "generated by ursa"
616
+ // Build meta line: version, build id, timestamp, "generated by ursa"
594
617
  const metaParts = [];
595
618
  if (docPackage?.version) {
596
619
  metaParts.push(`v${docPackage.version}`);
597
620
  }
598
- metaParts.push(new Date().toISOString().split('T')[0]); // YYYY-MM-DD
621
+ metaParts.push(`build ${buildId}`);
622
+
623
+ // Full date/time in a readable format
624
+ const now = new Date();
625
+ const timestamp = now.toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, ' UTC');
626
+ metaParts.push(timestamp);
627
+
599
628
  metaParts.push(`Generated by <a href="https://www.npmjs.com/package/@kenjura/ursa">ursa</a> v${ursaVersion}`);
600
629
 
601
630
  footerParts.push(`<div class="footer-meta">${metaParts.join(' • ')}</div>`);
@@ -611,5 +640,20 @@ async function getFooter(source, _source) {
611
640
  }
612
641
  }
613
642
 
643
+ // Try to get git short hash of doc repo (as HTML comment)
644
+ try {
645
+ const { execSync } = await import('child_process');
646
+ const gitHash = execSync('git rev-parse --short HEAD', {
647
+ cwd: resolve(_source),
648
+ encoding: 'utf8',
649
+ stdio: ['pipe', 'pipe', 'pipe']
650
+ }).trim();
651
+ if (gitHash) {
652
+ footerParts.push(`<!-- git: ${gitHash} -->`);
653
+ }
654
+ } catch (e) {
655
+ // Not a git repo or git not available - silently skip
656
+ }
657
+
614
658
  return footerParts.join('\n');
615
659
  }