@kenjura/ursa 0.41.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,15 @@
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
+
1
13
  # 0.41.0
2
14
  2025-12-13
3
15
 
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.41.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
@@ -607,12 +613,18 @@ async function getFooter(source, _source) {
607
613
  console.error(`Error reading ursa package.json: ${e.message}`);
608
614
  }
609
615
 
610
- // Build meta line: version, timestamp, "generated by ursa"
616
+ // Build meta line: version, build id, timestamp, "generated by ursa"
611
617
  const metaParts = [];
612
618
  if (docPackage?.version) {
613
619
  metaParts.push(`v${docPackage.version}`);
614
620
  }
615
- 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
+
616
628
  metaParts.push(`Generated by <a href="https://www.npmjs.com/package/@kenjura/ursa">ursa</a> v${ursaVersion}`);
617
629
 
618
630
  footerParts.push(`<div class="footer-meta">${metaParts.join(' • ')}</div>`);
@@ -628,5 +640,20 @@ async function getFooter(source, _source) {
628
640
  }
629
641
  }
630
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
+
631
658
  return footerParts.join('\n');
632
659
  }