hdoc-tools 0.57.1 → 0.57.3
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/hdoc-bump.js +123 -123
- package/hdoc-edit.js +1 -1
- package/hdoc-help.js +2 -1
- package/hdoc-spell.js +61 -0
- package/hdoc-toc.js +442 -0
- package/hdoc-validate-config.js +4 -1
- package/hdoc-ver.js +43 -43
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -1
- package/validateNodeVer.js +17 -17
- package/LICENSE +0 -21
package/hdoc-bump.js
CHANGED
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
(() => {
|
|
2
|
-
const fs = require("node:fs");
|
|
3
|
-
const path = require("node:path");
|
|
4
|
-
|
|
5
|
-
exports.run = (source_path, bump_type) => {
|
|
6
|
-
if (
|
|
7
|
-
bump_type !== "patch" &&
|
|
8
|
-
bump_type !== "minor" &&
|
|
9
|
-
bump_type !== "major"
|
|
10
|
-
) {
|
|
11
|
-
console.error(`Unsupported bump type: ${bump_type}`);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
console.log(`Bumping ${bump_type} book version...\n`);
|
|
15
|
-
|
|
16
|
-
// Get document ID
|
|
17
|
-
const hdocbook_project_config_path = path.join(
|
|
18
|
-
source_path,
|
|
19
|
-
"hdocbook-project.json",
|
|
20
|
-
);
|
|
21
|
-
let hdocbook_project;
|
|
22
|
-
try {
|
|
23
|
-
hdocbook_project = require(hdocbook_project_config_path);
|
|
24
|
-
} catch (e) {
|
|
25
|
-
console.error("File not found: hdocbook-project.json:");
|
|
26
|
-
console.error(e, "\n");
|
|
27
|
-
console.error("hdoc bump needs to be run in the root of a HDoc Book.\n");
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
const doc_id = hdocbook_project.docId;
|
|
31
|
-
|
|
32
|
-
const book_path = path.join(source_path, doc_id);
|
|
33
|
-
const hdocbook_path = path.join(book_path, "hdocbook.json");
|
|
34
|
-
|
|
35
|
-
let hdocbook_config;
|
|
36
|
-
try {
|
|
37
|
-
hdocbook_config = require(hdocbook_path);
|
|
38
|
-
} catch (e) {
|
|
39
|
-
console.error("File not found: hdocbook.json");
|
|
40
|
-
console.error(e, "\n");
|
|
41
|
-
console.error("hdoc bump needs to be run in the root of a HDoc Book.\n");
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
const initial_version = hdocbook_config.version;
|
|
45
|
-
const hdocbook_version = hdocbook_config.version.split(".");
|
|
46
|
-
if (hdocbook_version.length !== 3) {
|
|
47
|
-
console.error(
|
|
48
|
-
`Book version does not appear to be in a semantic versioning format: ${initial_version}`,
|
|
49
|
-
);
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (Number.isNaN(hdocbook_version[0])) {
|
|
54
|
-
console.error(
|
|
55
|
-
`Existing major version is not a number: ${hdocbook_version[0]}`,
|
|
56
|
-
);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
if (Number.isNaN(hdocbook_version[1])) {
|
|
60
|
-
console.error(
|
|
61
|
-
`Existing minor version is not a number: ${hdocbook_version[1]}`,
|
|
62
|
-
);
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
if (Number.isNaN(hdocbook_version[2])) {
|
|
66
|
-
console.error(
|
|
67
|
-
`Existing patch version is not a number: ${hdocbook_version[2]}`,
|
|
68
|
-
);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
switch (bump_type) {
|
|
73
|
-
case "major":
|
|
74
|
-
try {
|
|
75
|
-
hdocbook_version[0] = Number.parseInt(hdocbook_version[0], 10) + 1;
|
|
76
|
-
hdocbook_version[1] = 0;
|
|
77
|
-
hdocbook_version[2] = 0;
|
|
78
|
-
} catch (e) {
|
|
79
|
-
console.error("Failed to update major version:");
|
|
80
|
-
console.error(e);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
case "minor":
|
|
85
|
-
try {
|
|
86
|
-
hdocbook_version[0] = Number.parseInt(hdocbook_version[0], 10);
|
|
87
|
-
hdocbook_version[1] = Number.parseInt(hdocbook_version[1], 10) + 1;
|
|
88
|
-
hdocbook_version[2] = 0;
|
|
89
|
-
} catch (e) {
|
|
90
|
-
console.error("Failed to update minor version:");
|
|
91
|
-
console.error(e);
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
break;
|
|
95
|
-
default:
|
|
96
|
-
//case "patch" catered for with this default
|
|
97
|
-
try {
|
|
98
|
-
hdocbook_version[0] = Number.parseInt(hdocbook_version[0], 10);
|
|
99
|
-
hdocbook_version[1] = Number.parseInt(hdocbook_version[1], 10);
|
|
100
|
-
hdocbook_version[2] = Number.parseInt(hdocbook_version[2], 10) + 1;
|
|
101
|
-
} catch (e) {
|
|
102
|
-
console.error("Failed to update patch version:");
|
|
103
|
-
console.error(e);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
hdocbook_config.version = hdocbook_version.join(".");
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
fs.writeFileSync(hdocbook_path, JSON.stringify(hdocbook_config, null, 2));
|
|
113
|
-
} catch (e) {
|
|
114
|
-
console.error("Error writing bumped version to book config:", e);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
console.log(
|
|
119
|
-
`Book version updated from ${initial_version} to ${hdocbook_config.version}\n`,
|
|
120
|
-
);
|
|
121
|
-
return true;
|
|
122
|
-
};
|
|
123
|
-
})();
|
|
1
|
+
(() => {
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
exports.run = (source_path, bump_type) => {
|
|
6
|
+
if (
|
|
7
|
+
bump_type !== "patch" &&
|
|
8
|
+
bump_type !== "minor" &&
|
|
9
|
+
bump_type !== "major"
|
|
10
|
+
) {
|
|
11
|
+
console.error(`Unsupported bump type: ${bump_type}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
console.log(`Bumping ${bump_type} book version...\n`);
|
|
15
|
+
|
|
16
|
+
// Get document ID
|
|
17
|
+
const hdocbook_project_config_path = path.join(
|
|
18
|
+
source_path,
|
|
19
|
+
"hdocbook-project.json",
|
|
20
|
+
);
|
|
21
|
+
let hdocbook_project;
|
|
22
|
+
try {
|
|
23
|
+
hdocbook_project = require(hdocbook_project_config_path);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.error("File not found: hdocbook-project.json:");
|
|
26
|
+
console.error(e, "\n");
|
|
27
|
+
console.error("hdoc bump needs to be run in the root of a HDoc Book.\n");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const doc_id = hdocbook_project.docId;
|
|
31
|
+
|
|
32
|
+
const book_path = path.join(source_path, doc_id);
|
|
33
|
+
const hdocbook_path = path.join(book_path, "hdocbook.json");
|
|
34
|
+
|
|
35
|
+
let hdocbook_config;
|
|
36
|
+
try {
|
|
37
|
+
hdocbook_config = require(hdocbook_path);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error("File not found: hdocbook.json");
|
|
40
|
+
console.error(e, "\n");
|
|
41
|
+
console.error("hdoc bump needs to be run in the root of a HDoc Book.\n");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const initial_version = hdocbook_config.version;
|
|
45
|
+
const hdocbook_version = hdocbook_config.version.split(".");
|
|
46
|
+
if (hdocbook_version.length !== 3) {
|
|
47
|
+
console.error(
|
|
48
|
+
`Book version does not appear to be in a semantic versioning format: ${initial_version}`,
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (Number.isNaN(hdocbook_version[0])) {
|
|
54
|
+
console.error(
|
|
55
|
+
`Existing major version is not a number: ${hdocbook_version[0]}`,
|
|
56
|
+
);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
if (Number.isNaN(hdocbook_version[1])) {
|
|
60
|
+
console.error(
|
|
61
|
+
`Existing minor version is not a number: ${hdocbook_version[1]}`,
|
|
62
|
+
);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
if (Number.isNaN(hdocbook_version[2])) {
|
|
66
|
+
console.error(
|
|
67
|
+
`Existing patch version is not a number: ${hdocbook_version[2]}`,
|
|
68
|
+
);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
switch (bump_type) {
|
|
73
|
+
case "major":
|
|
74
|
+
try {
|
|
75
|
+
hdocbook_version[0] = Number.parseInt(hdocbook_version[0], 10) + 1;
|
|
76
|
+
hdocbook_version[1] = 0;
|
|
77
|
+
hdocbook_version[2] = 0;
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error("Failed to update major version:");
|
|
80
|
+
console.error(e);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "minor":
|
|
85
|
+
try {
|
|
86
|
+
hdocbook_version[0] = Number.parseInt(hdocbook_version[0], 10);
|
|
87
|
+
hdocbook_version[1] = Number.parseInt(hdocbook_version[1], 10) + 1;
|
|
88
|
+
hdocbook_version[2] = 0;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error("Failed to update minor version:");
|
|
91
|
+
console.error(e);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
//case "patch" catered for with this default
|
|
97
|
+
try {
|
|
98
|
+
hdocbook_version[0] = Number.parseInt(hdocbook_version[0], 10);
|
|
99
|
+
hdocbook_version[1] = Number.parseInt(hdocbook_version[1], 10);
|
|
100
|
+
hdocbook_version[2] = Number.parseInt(hdocbook_version[2], 10) + 1;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.error("Failed to update patch version:");
|
|
103
|
+
console.error(e);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
hdocbook_config.version = hdocbook_version.join(".");
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
fs.writeFileSync(hdocbook_path, JSON.stringify(hdocbook_config, null, 2));
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error("Error writing bumped version to book config:", e);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(
|
|
119
|
+
`Book version updated from ${initial_version} to ${hdocbook_config.version}\n`,
|
|
120
|
+
);
|
|
121
|
+
return true;
|
|
122
|
+
};
|
|
123
|
+
})();
|
package/hdoc-edit.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
const { TocModel } = require(path.join(__dirname, "hdoc-toc.js"));
|
|
32
32
|
const { dialect_findings } = require(path.join(__dirname, "hdoc-spell.js"));
|
|
33
33
|
|
|
34
|
-
let port =
|
|
34
|
+
let port = 3001;
|
|
35
35
|
|
|
36
36
|
// editor_path is the directory containing the editor SPA. For the stub this
|
|
37
37
|
// is editor/ (a hand-written placeholder index.html). Once the React app is
|
package/hdoc-help.js
CHANGED
|
@@ -18,7 +18,8 @@ Commands
|
|
|
18
18
|
Creates folder structure and markdown documents as defined in the HDocBook navigation item links
|
|
19
19
|
|
|
20
20
|
- edit
|
|
21
|
-
Starts the local structure/content editor on port
|
|
21
|
+
Starts the local structure/content editor on port 3001 (bound to 127.0.0.1), serving the editor UI alongside a live preview of the content. Supports a -port N to use a different port.
|
|
22
|
+
NOTE: This feature is experimental and not really for general use yet. GS
|
|
22
23
|
|
|
23
24
|
- help
|
|
24
25
|
Outputs available arguments and switches
|
package/hdoc-spell.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Dialect spell-check: flags British spellings and suggests the US form, using
|
|
2
|
+
// the same `american-british-english-translator` the build uses (hdoc-validate),
|
|
3
|
+
// so the editor and `hdoc validate` agree. Returns character-offset findings
|
|
4
|
+
// suitable for editor underlines.
|
|
5
|
+
|
|
6
|
+
let translator = null;
|
|
7
|
+
|
|
8
|
+
function line_starts(text) {
|
|
9
|
+
const starts = [0];
|
|
10
|
+
for (let i = 0; i < text.length; i++) {
|
|
11
|
+
if (text[i] === "\n") starts.push(i + 1);
|
|
12
|
+
}
|
|
13
|
+
return starts;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function escape_regex(s) {
|
|
17
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Returns [{ from, to, word, suggestion, type: 'dialect' }] for British spellings,
|
|
21
|
+
// skipping any word in `ignore` (case-insensitive).
|
|
22
|
+
function dialect_findings(text, ignore = []) {
|
|
23
|
+
if (!translator) translator = require("american-british-english-translator");
|
|
24
|
+
const out = translator.translate(text, { british: true, spelling: true });
|
|
25
|
+
const ignore_set = new Set(ignore.map((w) => String(w).toLowerCase()));
|
|
26
|
+
const starts = line_starts(text);
|
|
27
|
+
const findings = [];
|
|
28
|
+
|
|
29
|
+
for (const line_key of Object.keys(out)) {
|
|
30
|
+
const line_no = Number.parseInt(line_key, 10);
|
|
31
|
+
if (!line_no) continue;
|
|
32
|
+
const line_start = starts[line_no - 1];
|
|
33
|
+
if (line_start === undefined) continue;
|
|
34
|
+
const line_end = line_no < starts.length ? starts[line_no] : text.length;
|
|
35
|
+
const line_text = text.slice(line_start, line_end);
|
|
36
|
+
|
|
37
|
+
for (const entry of out[line_key]) {
|
|
38
|
+
for (const word of Object.keys(entry)) {
|
|
39
|
+
if (ignore_set.has(word.toLowerCase())) continue;
|
|
40
|
+
const suggestion = entry[word]?.details;
|
|
41
|
+
if (!suggestion) continue;
|
|
42
|
+
const re = new RegExp(`\\b${escape_regex(word)}\\b`, "gi");
|
|
43
|
+
let m;
|
|
44
|
+
// eslint-disable-next-line no-cond-assign
|
|
45
|
+
while ((m = re.exec(line_text)) !== null) {
|
|
46
|
+
const from = line_start + m.index;
|
|
47
|
+
findings.push({
|
|
48
|
+
from,
|
|
49
|
+
to: from + m[0].length,
|
|
50
|
+
word: m[0],
|
|
51
|
+
suggestion,
|
|
52
|
+
type: "dialect",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return findings;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.dialect_findings = dialect_findings;
|
package/hdoc-toc.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
// In-memory TOC working model for `hdoc edit`.
|
|
2
|
+
//
|
|
3
|
+
// Owns the book's navigation tree (hdocbook.json -> navigation.items) and the
|
|
4
|
+
// node-identity scheme. Each node gets a permanent, opaque, per-book-unique id
|
|
5
|
+
// (see README of the editor effort): identity is separated from location
|
|
6
|
+
// (link/slug) and from order (1.2.3 numbering is derived at render time only).
|
|
7
|
+
//
|
|
8
|
+
// Identity strategy (Phase 1): on load we assign an id to every node that lacks
|
|
9
|
+
// one, in memory. The model is the single source of truth for the editing
|
|
10
|
+
// session, so ids stay stable across /api/toc reads. The first structural save
|
|
11
|
+
// persists these ids back into hdocbook.json (lazy one-time migration).
|
|
12
|
+
//
|
|
13
|
+
// hdocbook.json is read with fs.readFileSync + JSON.parse (never require) because
|
|
14
|
+
// the editor mutates it and require() would cache a stale copy.
|
|
15
|
+
|
|
16
|
+
const fs = require("node:fs");
|
|
17
|
+
const path = require("node:path");
|
|
18
|
+
const crypto = require("node:crypto");
|
|
19
|
+
|
|
20
|
+
// Lowercase, URL-safe, no look-alike characters (drops 0/o/1/l/i). 30 symbols.
|
|
21
|
+
const ID_ALPHABET = "23456789abcdefghjkmnpqrstvwxyz";
|
|
22
|
+
const ID_LENGTH = 8;
|
|
23
|
+
// Largest multiple of the alphabet size that fits in a byte, for unbiased
|
|
24
|
+
// rejection sampling (256 -> 240 = 8 * 30).
|
|
25
|
+
const ID_REJECT_THRESHOLD =
|
|
26
|
+
Math.floor(256 / ID_ALPHABET.length) * ID_ALPHABET.length;
|
|
27
|
+
|
|
28
|
+
// Recognised image/asset extensions (for the Files browser + uploads).
|
|
29
|
+
const IMAGE_EXTS = [
|
|
30
|
+
".png",
|
|
31
|
+
".jpg",
|
|
32
|
+
".jpeg",
|
|
33
|
+
".gif",
|
|
34
|
+
".svg",
|
|
35
|
+
".webp",
|
|
36
|
+
".bmp",
|
|
37
|
+
".ico",
|
|
38
|
+
".avif",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// Classify a file by name for the Files tree: page (.md), image, or other.
|
|
42
|
+
function kind_of(name) {
|
|
43
|
+
const lower = name.toLowerCase();
|
|
44
|
+
if (lower.endsWith(".md")) return "page";
|
|
45
|
+
if (IMAGE_EXTS.some((e) => lower.endsWith(e))) return "image";
|
|
46
|
+
return "other";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function random_id_char() {
|
|
50
|
+
// Rejection-sample a byte to avoid modulo bias across the 30-symbol alphabet.
|
|
51
|
+
for (;;) {
|
|
52
|
+
const b = crypto.randomBytes(1)[0];
|
|
53
|
+
if (b < ID_REJECT_THRESHOLD) return ID_ALPHABET[b % ID_ALPHABET.length];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate an opaque id not present in `used` (a Set), and reserve it.
|
|
58
|
+
function generate_id(used) {
|
|
59
|
+
for (let attempt = 0; attempt < 10000; attempt++) {
|
|
60
|
+
let id = "";
|
|
61
|
+
for (let i = 0; i < ID_LENGTH; i++) id += random_id_char();
|
|
62
|
+
if (!used.has(id)) {
|
|
63
|
+
used.add(id);
|
|
64
|
+
return id;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error("Unable to generate a unique node id");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class TocModel {
|
|
71
|
+
constructor(source_path, docId) {
|
|
72
|
+
this.source_path = source_path;
|
|
73
|
+
this.docId = docId;
|
|
74
|
+
this.hdocbook_path = path.join(source_path, docId, "hdocbook.json");
|
|
75
|
+
// True when we assigned ids that are not yet written to disk. The first
|
|
76
|
+
// save will persist them (handled by a future write endpoint).
|
|
77
|
+
this.ids_pending_persist = false;
|
|
78
|
+
// Optional hook: called as (absPath, content) after we write hdocbook.json,
|
|
79
|
+
// so a file watcher can suppress our own writes.
|
|
80
|
+
this.on_persist = null;
|
|
81
|
+
this.load();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Re-read hdocbook.json from disk (e.g. after an external edit), keeping any
|
|
85
|
+
// existing ids and assigning ids to new nodes.
|
|
86
|
+
reload() {
|
|
87
|
+
this.load();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
load() {
|
|
91
|
+
const raw = JSON.parse(fs.readFileSync(this.hdocbook_path, "utf8"));
|
|
92
|
+
this.raw = raw;
|
|
93
|
+
const nav =
|
|
94
|
+
raw.navigation && Array.isArray(raw.navigation.items)
|
|
95
|
+
? raw.navigation.items
|
|
96
|
+
: [];
|
|
97
|
+
this.items = nav;
|
|
98
|
+
|
|
99
|
+
// Pass 1: collect ids already present so generated ones never collide.
|
|
100
|
+
this.used_ids = new Set();
|
|
101
|
+
this._walk(nav, (n) => {
|
|
102
|
+
if (typeof n.id === "string" && n.id) this.used_ids.add(n.id);
|
|
103
|
+
});
|
|
104
|
+
// Pass 2: assign opaque ids to any node that lacks one.
|
|
105
|
+
this._walk(nav, (n) => {
|
|
106
|
+
if (!(typeof n.id === "string" && n.id)) {
|
|
107
|
+
n.id = generate_id(this.used_ids);
|
|
108
|
+
this.ids_pending_persist = true;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_walk(nodes, fn) {
|
|
114
|
+
for (const n of nodes) {
|
|
115
|
+
fn(n);
|
|
116
|
+
if (Array.isArray(n.items)) this._walk(n.items, fn);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Resolve a leaf link (path without extension) to a source-relative file.
|
|
121
|
+
_resolve_file(link) {
|
|
122
|
+
const rel = `${link}.md`;
|
|
123
|
+
if (fs.existsSync(path.join(this.source_path, rel))) {
|
|
124
|
+
return { file: rel, fileExists: true };
|
|
125
|
+
}
|
|
126
|
+
const rel_index = path.join(link, "index.md");
|
|
127
|
+
if (fs.existsSync(path.join(this.source_path, rel_index))) {
|
|
128
|
+
return { file: rel_index, fileExists: true };
|
|
129
|
+
}
|
|
130
|
+
// Report the expected path even when missing, so the UI can flag it.
|
|
131
|
+
return { file: rel, fileExists: false };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Find a node anywhere in the tree by its opaque id (or null).
|
|
135
|
+
find_node(id) {
|
|
136
|
+
let found = null;
|
|
137
|
+
this._walk(this.items, (n) => {
|
|
138
|
+
if (n.id === id) found = n;
|
|
139
|
+
});
|
|
140
|
+
return found;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Resolve a node id to its source file. Returns null if the id is unknown.
|
|
144
|
+
// For a branch (or a leaf with no link) abs/file are null and exists is false.
|
|
145
|
+
resolve_file(id) {
|
|
146
|
+
const node = this.find_node(id);
|
|
147
|
+
if (!node) return null;
|
|
148
|
+
const link = typeof node.link === "string" ? node.link : null;
|
|
149
|
+
if (!link) {
|
|
150
|
+
return { node, link: null, file: null, abs: null, exists: false };
|
|
151
|
+
}
|
|
152
|
+
const r = this._resolve_file(link);
|
|
153
|
+
return {
|
|
154
|
+
node,
|
|
155
|
+
link,
|
|
156
|
+
file: r.file,
|
|
157
|
+
abs: path.join(this.source_path, r.file),
|
|
158
|
+
exists: r.fileExists,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- structural mutations (Phase 1b: nav-only — links/files never move) ---
|
|
163
|
+
|
|
164
|
+
// Locate a node by id: returns { node, parent (array), index } or null.
|
|
165
|
+
_locate(id) {
|
|
166
|
+
const search = (arr) => {
|
|
167
|
+
for (let i = 0; i < arr.length; i++) {
|
|
168
|
+
if (arr[i].id === id) return { node: arr[i], parent: arr, index: i };
|
|
169
|
+
if (Array.isArray(arr[i].items)) {
|
|
170
|
+
const found = search(arr[i].items);
|
|
171
|
+
if (found) return found;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
};
|
|
176
|
+
return search(this.items);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Is targetId the node itself or one of its descendants?
|
|
180
|
+
_contains(node, targetId) {
|
|
181
|
+
if (node.id === targetId) return true;
|
|
182
|
+
if (Array.isArray(node.items)) {
|
|
183
|
+
return node.items.some((c) => this._contains(c, targetId));
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Reorder/reparent a node. parentId null => top level. index is the insertion
|
|
189
|
+
// position in the destination AFTER the node has been removed from its old
|
|
190
|
+
// spot. Only navigation order/nesting changes; link/items/draft are preserved.
|
|
191
|
+
move(id, parentId, index) {
|
|
192
|
+
const loc = this._locate(id);
|
|
193
|
+
if (!loc) throw new Error(`Unknown node id: ${id}`);
|
|
194
|
+
|
|
195
|
+
let dest;
|
|
196
|
+
if (parentId === null || parentId === undefined) {
|
|
197
|
+
dest = this.items;
|
|
198
|
+
} else {
|
|
199
|
+
const parent = this.find_node(parentId);
|
|
200
|
+
if (!parent) throw new Error(`Unknown parent id: ${parentId}`);
|
|
201
|
+
if (!Array.isArray(parent.items)) {
|
|
202
|
+
throw new Error("Target node is a leaf and cannot contain children");
|
|
203
|
+
}
|
|
204
|
+
if (this._contains(loc.node, parentId)) {
|
|
205
|
+
throw new Error("Cannot move a node into its own subtree");
|
|
206
|
+
}
|
|
207
|
+
dest = parent.items;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
loc.parent.splice(loc.index, 1);
|
|
211
|
+
let idx = typeof index === "number" ? index : dest.length;
|
|
212
|
+
idx = Math.max(0, Math.min(idx, dest.length));
|
|
213
|
+
dest.splice(idx, 0, loc.node);
|
|
214
|
+
this.persist();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Rename a node's display label (text). No effect on link/slug or file.
|
|
218
|
+
rename(id, text) {
|
|
219
|
+
const node = this.find_node(id);
|
|
220
|
+
if (!node) throw new Error(`Unknown node id: ${id}`);
|
|
221
|
+
node.text = text;
|
|
222
|
+
this.persist();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Set a node's draft flag.
|
|
226
|
+
set_draft(id, draft) {
|
|
227
|
+
const node = this.find_node(id);
|
|
228
|
+
if (!node) throw new Error(`Unknown node id: ${id}`);
|
|
229
|
+
node.draft = !!draft;
|
|
230
|
+
this.persist();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Write the working model back to hdocbook.json in its native format
|
|
234
|
+
// (2-space indent, LF, no trailing newline). This is also where in-memory
|
|
235
|
+
// opaque ids first land on disk (the lazy one-time migration).
|
|
236
|
+
persist() {
|
|
237
|
+
if (!this.raw.navigation) this.raw.navigation = {};
|
|
238
|
+
this.raw.navigation.items = this.items;
|
|
239
|
+
const json = JSON.stringify(this.raw, null, 2);
|
|
240
|
+
fs.writeFileSync(this.hdocbook_path, json, "utf8");
|
|
241
|
+
this.ids_pending_persist = false;
|
|
242
|
+
if (this.on_persist) this.on_persist(this.hdocbook_path, json);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// --- pages on disk (decoupled from nav) ---
|
|
246
|
+
|
|
247
|
+
// Validate a source-relative path: it must live inside the book's docId
|
|
248
|
+
// folder, not traverse upward, and not touch special "_" folders/files.
|
|
249
|
+
// `exts` limits the allowed file extension (default .md for pages; pass null
|
|
250
|
+
// to allow any, or a list for assets). Returns { rel (normalised), abs }.
|
|
251
|
+
_safe_rel(rel, { dir = false, exts = [".md"] } = {}) {
|
|
252
|
+
if (typeof rel !== "string" || !rel.trim()) throw new Error("Missing path");
|
|
253
|
+
const norm = rel.split("\\").join("/").replace(/^\/+/, "");
|
|
254
|
+
const segs = norm.split("/").filter((s) => s.length > 0);
|
|
255
|
+
if (segs[0] !== this.docId) {
|
|
256
|
+
throw new Error(`Path must be inside the book folder: ${this.docId}`);
|
|
257
|
+
}
|
|
258
|
+
if (segs.includes("..") || segs.includes(".")) {
|
|
259
|
+
throw new Error("Invalid path");
|
|
260
|
+
}
|
|
261
|
+
if (segs.some((s) => s.startsWith("_"))) {
|
|
262
|
+
throw new Error("Cannot operate inside special (_) folders");
|
|
263
|
+
}
|
|
264
|
+
if (!dir && exts) {
|
|
265
|
+
const lower = norm.toLowerCase();
|
|
266
|
+
if (!exts.some((e) => lower.endsWith(e))) {
|
|
267
|
+
throw new Error(`File type not allowed (expected: ${exts.join(", ")})`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const root = path.join(this.source_path, this.docId);
|
|
271
|
+
const abs = path.join(this.source_path, norm);
|
|
272
|
+
const within = path.relative(root, abs);
|
|
273
|
+
if (within.startsWith("..") || path.isAbsolute(within)) {
|
|
274
|
+
throw new Error("Path escapes the book");
|
|
275
|
+
}
|
|
276
|
+
return { rel: norm, abs };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Source-relative files referenced by some nav leaf's link.
|
|
280
|
+
linked_files() {
|
|
281
|
+
const set = new Set();
|
|
282
|
+
this._walk(this.items, (n) => {
|
|
283
|
+
if (typeof n.link === "string" && n.link) {
|
|
284
|
+
const r = this._resolve_file(n.link);
|
|
285
|
+
if (r.file) set.add(r.file);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return set;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Every file on disk under the book (excluding "_" folders), each tagged with
|
|
292
|
+
// its kind (page/image/other). Pages also carry a linked flag (in nav or not).
|
|
293
|
+
list_files() {
|
|
294
|
+
const linked = this.linked_files();
|
|
295
|
+
const root = path.join(this.source_path, this.docId);
|
|
296
|
+
const files = [];
|
|
297
|
+
const dirs = [];
|
|
298
|
+
const walk = (absDir) => {
|
|
299
|
+
for (const e of fs.readdirSync(absDir, { withFileTypes: true })) {
|
|
300
|
+
if (e.name.startsWith("_")) continue; // skip special folders/files
|
|
301
|
+
const abs = path.join(absDir, e.name);
|
|
302
|
+
const rel = path.relative(this.source_path, abs).split(path.sep).join("/");
|
|
303
|
+
if (e.isDirectory()) {
|
|
304
|
+
dirs.push(rel);
|
|
305
|
+
walk(abs);
|
|
306
|
+
} else {
|
|
307
|
+
const kind = kind_of(e.name);
|
|
308
|
+
files.push({
|
|
309
|
+
file: rel,
|
|
310
|
+
kind,
|
|
311
|
+
linked: kind === "page" ? linked.has(rel) : false,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
if (fs.existsSync(root)) walk(root);
|
|
317
|
+
files.sort((a, b) => a.file.localeCompare(b.file));
|
|
318
|
+
dirs.sort((a, b) => a.localeCompare(b));
|
|
319
|
+
return { docId: this.docId, dirs, files };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
create_file(rel, content = "") {
|
|
323
|
+
const { abs, rel: r } = this._safe_rel(rel, { dir: false });
|
|
324
|
+
if (fs.existsSync(abs)) throw new Error("A file already exists at that path");
|
|
325
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
326
|
+
fs.writeFileSync(abs, content, "utf8");
|
|
327
|
+
return r;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
create_folder(rel) {
|
|
331
|
+
const { abs, rel: r } = this._safe_rel(rel, { dir: true });
|
|
332
|
+
fs.mkdirSync(abs, { recursive: true });
|
|
333
|
+
return r;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Delete any file (page, image, or other asset) inside the book.
|
|
337
|
+
delete_file(rel) {
|
|
338
|
+
const { abs, rel: r } = this._safe_rel(rel, { dir: false, exts: null });
|
|
339
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
|
|
340
|
+
throw new Error("File not found");
|
|
341
|
+
}
|
|
342
|
+
fs.unlinkSync(abs);
|
|
343
|
+
return r;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Validate + return the absolute path for an uploaded image/asset.
|
|
347
|
+
resolve_upload(rel) {
|
|
348
|
+
return this._safe_rel(rel, { dir: false, exts: IMAGE_EXTS });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// --- nav create / remove (Phase 1c) ---
|
|
352
|
+
|
|
353
|
+
_dest_array(parentId) {
|
|
354
|
+
if (parentId === null || parentId === undefined) return this.items;
|
|
355
|
+
const parent = this.find_node(parentId);
|
|
356
|
+
if (!parent) throw new Error(`Unknown parent id: ${parentId}`);
|
|
357
|
+
if (!Array.isArray(parent.items)) {
|
|
358
|
+
throw new Error("Target node is a leaf and cannot contain children");
|
|
359
|
+
}
|
|
360
|
+
return parent.items;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_insert(dest, index, node) {
|
|
364
|
+
let idx = typeof index === "number" ? index : dest.length;
|
|
365
|
+
idx = Math.max(0, Math.min(idx, dest.length));
|
|
366
|
+
dest.splice(idx, 0, node);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Add a nav leaf that links to an existing page.
|
|
370
|
+
create_leaf(parentId, index, text, link) {
|
|
371
|
+
const dest = this._dest_array(parentId);
|
|
372
|
+
const node = { id: generate_id(this.used_ids), text: text || "", link };
|
|
373
|
+
this._insert(dest, index, node);
|
|
374
|
+
this.persist();
|
|
375
|
+
return node.id;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Add an empty nav section (branch container).
|
|
379
|
+
add_section(parentId, index, text) {
|
|
380
|
+
const dest = this._dest_array(parentId);
|
|
381
|
+
const node = { id: generate_id(this.used_ids), text: text || "", items: [] };
|
|
382
|
+
this._insert(dest, index, node);
|
|
383
|
+
this.persist();
|
|
384
|
+
return node.id;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Remove a nav node (unlink). Returns the removed node so the caller can
|
|
388
|
+
// optionally delete its backing file.
|
|
389
|
+
remove_node(id) {
|
|
390
|
+
const loc = this._locate(id);
|
|
391
|
+
if (!loc) throw new Error(`Unknown node id: ${id}`);
|
|
392
|
+
loc.parent.splice(loc.index, 1);
|
|
393
|
+
this.persist();
|
|
394
|
+
return loc.node;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Normalized tree for the editor UI. Numbering is derived from sibling
|
|
398
|
+
// position (1, 1.1, 1.2, 2, ...) and never stored.
|
|
399
|
+
to_dto() {
|
|
400
|
+
const walk = (nodes, prefix) =>
|
|
401
|
+
nodes.map((n, i) => {
|
|
402
|
+
const number = prefix ? `${prefix}.${i + 1}` : `${i + 1}`;
|
|
403
|
+
const base = {
|
|
404
|
+
id: n.id,
|
|
405
|
+
text: typeof n.text === "string" ? n.text : "",
|
|
406
|
+
number,
|
|
407
|
+
draft: n.draft === true,
|
|
408
|
+
};
|
|
409
|
+
if (Array.isArray(n.items)) {
|
|
410
|
+
return {
|
|
411
|
+
...base,
|
|
412
|
+
type: "branch",
|
|
413
|
+
expand: n.expand === true,
|
|
414
|
+
children: walk(n.items, number),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
const link = typeof n.link === "string" ? n.link : null;
|
|
418
|
+
const resolved = link
|
|
419
|
+
? this._resolve_file(link)
|
|
420
|
+
: { file: null, fileExists: false };
|
|
421
|
+
return {
|
|
422
|
+
...base,
|
|
423
|
+
type: "leaf",
|
|
424
|
+
link,
|
|
425
|
+
file: resolved.file,
|
|
426
|
+
fileExists: resolved.fileExists,
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
docId: this.docId,
|
|
432
|
+
title: typeof this.raw.title === "string" ? this.raw.title : "",
|
|
433
|
+
idsPendingPersist: this.ids_pending_persist,
|
|
434
|
+
tree: walk(this.items, ""),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
exports.TocModel = TocModel;
|
|
440
|
+
exports.generate_id = generate_id;
|
|
441
|
+
exports.ID_ALPHABET = ID_ALPHABET;
|
|
442
|
+
exports.ID_LENGTH = ID_LENGTH;
|
package/hdoc-validate-config.js
CHANGED
|
@@ -28,10 +28,13 @@
|
|
|
28
28
|
errors.push(`hdocbook.json: "${path}" must be an object`);
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
check_extra_keys(item, ['text', 'link', 'expand', 'draft', 'items'], path, 'hdocbook.json', errors);
|
|
31
|
+
check_extra_keys(item, ['text', 'link', 'expand', 'draft', 'items', 'id'], path, 'hdocbook.json', errors);
|
|
32
32
|
if (!item.text || typeof item.text !== 'string') {
|
|
33
33
|
errors.push(`hdocbook.json: "${path}.text" is required and must be a non-empty string`);
|
|
34
34
|
}
|
|
35
|
+
if (item.id !== undefined && typeof item.id !== 'string') {
|
|
36
|
+
errors.push(`hdocbook.json: "${path}.id" must be a string`);
|
|
37
|
+
}
|
|
35
38
|
if (item.link !== undefined && typeof item.link !== 'string') {
|
|
36
39
|
errors.push(`hdocbook.json: "${path}.link" must be a string`);
|
|
37
40
|
}
|
package/hdoc-ver.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
(() => {
|
|
2
|
-
const fs = require("node:fs");
|
|
3
|
-
const path = require("node:path");
|
|
4
|
-
|
|
5
|
-
exports.run = (source_path) => {
|
|
6
|
-
console.log("Retrieving book version...\n");
|
|
7
|
-
|
|
8
|
-
// Get document ID
|
|
9
|
-
const hdocbook_project_config_path = path.join(
|
|
10
|
-
source_path,
|
|
11
|
-
"hdocbook-project.json",
|
|
12
|
-
);
|
|
13
|
-
let hdocbook_project;
|
|
14
|
-
try {
|
|
15
|
-
hdocbook_project = require(hdocbook_project_config_path);
|
|
16
|
-
} catch (e) {
|
|
17
|
-
console.error("File not found: hdocbook-project.json:");
|
|
18
|
-
console.log(e, "\n");
|
|
19
|
-
console.error("hdoc ver needs to be run in the root of a HDoc Book.\n");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
const doc_id = hdocbook_project.docId;
|
|
23
|
-
|
|
24
|
-
const book_path = path.join(source_path, doc_id);
|
|
25
|
-
const hdocbook_path = path.join(book_path, "hdocbook.json");
|
|
26
|
-
|
|
27
|
-
let hdocbook_config;
|
|
28
|
-
try {
|
|
29
|
-
hdocbook_config = require(hdocbook_path);
|
|
30
|
-
} catch (e) {
|
|
31
|
-
console.error("File not found: hdocbook.json");
|
|
32
|
-
console.log(e, "\n");
|
|
33
|
-
console.error("hdoc ver needs to be run in the root of a HDoc Book.\n");
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
if (hdocbook_config.version && hdocbook_config.version !== "") {
|
|
37
|
-
console.log(`Book version: ${hdocbook_config.version}\n`);
|
|
38
|
-
} else {
|
|
39
|
-
console.error("Error - this book has no version defined.\n");
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
})();
|
|
1
|
+
(() => {
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
exports.run = (source_path) => {
|
|
6
|
+
console.log("Retrieving book version...\n");
|
|
7
|
+
|
|
8
|
+
// Get document ID
|
|
9
|
+
const hdocbook_project_config_path = path.join(
|
|
10
|
+
source_path,
|
|
11
|
+
"hdocbook-project.json",
|
|
12
|
+
);
|
|
13
|
+
let hdocbook_project;
|
|
14
|
+
try {
|
|
15
|
+
hdocbook_project = require(hdocbook_project_config_path);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.error("File not found: hdocbook-project.json:");
|
|
18
|
+
console.log(e, "\n");
|
|
19
|
+
console.error("hdoc ver needs to be run in the root of a HDoc Book.\n");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const doc_id = hdocbook_project.docId;
|
|
23
|
+
|
|
24
|
+
const book_path = path.join(source_path, doc_id);
|
|
25
|
+
const hdocbook_path = path.join(book_path, "hdocbook.json");
|
|
26
|
+
|
|
27
|
+
let hdocbook_config;
|
|
28
|
+
try {
|
|
29
|
+
hdocbook_config = require(hdocbook_path);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error("File not found: hdocbook.json");
|
|
32
|
+
console.log(e, "\n");
|
|
33
|
+
console.error("hdoc ver needs to be run in the root of a HDoc Book.\n");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
if (hdocbook_config.version && hdocbook_config.version !== "") {
|
|
37
|
+
console.log(`Book version: ${hdocbook_config.version}\n`);
|
|
38
|
+
} else {
|
|
39
|
+
console.error("Error - this book has no version defined.\n");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
})();
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hdoc-tools",
|
|
3
|
-
"version": "0.57.
|
|
3
|
+
"version": "0.57.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "hdoc-tools",
|
|
9
|
-
"version": "0.57.
|
|
9
|
+
"version": "0.57.3",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hdoc-tools",
|
|
3
|
-
"version": "0.57.
|
|
3
|
+
"version": "0.57.3",
|
|
4
4
|
"description": "Hornbill HDocBook Development Support Tool",
|
|
5
5
|
"main": "hdoc.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"hdoc-mermaid.js",
|
|
24
24
|
"hdoc-module.js",
|
|
25
25
|
"hdoc-serve.js",
|
|
26
|
+
"hdoc-spell.js",
|
|
26
27
|
"hdoc-stats.js",
|
|
28
|
+
"hdoc-toc.js",
|
|
27
29
|
"hdoc-validate.js",
|
|
28
30
|
"hdoc-validate-config.js",
|
|
29
31
|
"hdoc-ver.js",
|
package/validateNodeVer.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
(() => {
|
|
2
|
-
const vers = process.versions;
|
|
3
|
-
const supportedVersion = 20;
|
|
4
|
-
if (vers?.node) {
|
|
5
|
-
if (Number.parseInt(vers.node, 10) < supportedVersion) {
|
|
6
|
-
console.log("\r\nHornbill HDocBook Tools\r\n");
|
|
7
|
-
console.error(
|
|
8
|
-
`\x1b[31mThis tool requires Node.js version >= ${supportedVersion}.x.\x1b[0m\r\n`,
|
|
9
|
-
);
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
} else {
|
|
13
|
-
console.log(
|
|
14
|
-
"\x1b[33mCould not read version of Node.js, continuing...\x1b[0m",
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
})();
|
|
1
|
+
(() => {
|
|
2
|
+
const vers = process.versions;
|
|
3
|
+
const supportedVersion = 20;
|
|
4
|
+
if (vers?.node) {
|
|
5
|
+
if (Number.parseInt(vers.node, 10) < supportedVersion) {
|
|
6
|
+
console.log("\r\nHornbill HDocBook Tools\r\n");
|
|
7
|
+
console.error(
|
|
8
|
+
`\x1b[31mThis tool requires Node.js version >= ${supportedVersion}.x.\x1b[0m\r\n`,
|
|
9
|
+
);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
} else {
|
|
13
|
+
console.log(
|
|
14
|
+
"\x1b[33mCould not read version of Node.js, continuing...\x1b[0m",
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
})();
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Hornbill Docs
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|