@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 +17 -0
- package/package.json +1 -1
- package/src/helper/automenu.js +8 -1
- package/src/helper/ursaConfig.js +62 -0
- package/src/jobs/generate.js +60 -16
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
package/src/helper/automenu.js
CHANGED
|
@@ -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
|
+
}
|
package/src/jobs/generate.js
CHANGED
|
@@ -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
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
|
|
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(
|
|
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
|
}
|