docugate 0.1.3 → 0.2.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/LICENSE +21 -21
- package/README.md +170 -68
- package/dist/api.js +61 -0
- package/dist/auth.js +97 -0
- package/dist/check.js +145 -0
- package/dist/cli.js +249 -39
- package/dist/init.js +296 -1
- package/dist/rules.js +1 -1
- package/dist/tour-init.js +204 -0
- package/dist/tour-install.js +133 -0
- package/dist/tour-server.js +225 -0
- package/dist/tour.js +179 -0
- package/package.json +9 -2
- package/pill/dist/pill.js +1042 -0
package/dist/tour.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a tour markdown file into a Tour object.
|
|
5
|
+
* `text` is the file contents, `file` is the path (used for error messages).
|
|
6
|
+
*/
|
|
7
|
+
export function parseTourFile(text, file) {
|
|
8
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
9
|
+
// --- front matter ---
|
|
10
|
+
let i = 0;
|
|
11
|
+
let route = '';
|
|
12
|
+
let title = '';
|
|
13
|
+
if (lines[0] === '---') {
|
|
14
|
+
i = 1;
|
|
15
|
+
while (i < lines.length && lines[i] !== '---') {
|
|
16
|
+
const m = lines[i].match(/^(\w+):\s*(.*)$/);
|
|
17
|
+
if (m) {
|
|
18
|
+
if (m[1] === 'route')
|
|
19
|
+
route = m[2].trim();
|
|
20
|
+
if (m[1] === 'title')
|
|
21
|
+
title = m[2].trim();
|
|
22
|
+
}
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
i++; // skip closing ---
|
|
26
|
+
}
|
|
27
|
+
if (!route)
|
|
28
|
+
throw new Error(`${file}: missing "route" in front matter`);
|
|
29
|
+
if (!title)
|
|
30
|
+
throw new Error(`${file}: missing "title" in front matter`);
|
|
31
|
+
const stops = [];
|
|
32
|
+
while (i < lines.length) {
|
|
33
|
+
// skip blank lines between stops
|
|
34
|
+
while (i < lines.length && lines[i].trim() === '')
|
|
35
|
+
i++;
|
|
36
|
+
if (i >= lines.length)
|
|
37
|
+
break;
|
|
38
|
+
const headingMatch = lines[i].match(/^##\s+(.+)$/);
|
|
39
|
+
if (!headingMatch) {
|
|
40
|
+
i++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const heading = headingMatch[1].trim();
|
|
44
|
+
i++;
|
|
45
|
+
// read key: value fields
|
|
46
|
+
let target = '';
|
|
47
|
+
let data;
|
|
48
|
+
let code = '';
|
|
49
|
+
let source;
|
|
50
|
+
let docs;
|
|
51
|
+
while (i < lines.length && lines[i].trim() !== '' && !lines[i].startsWith('##')) {
|
|
52
|
+
const fm = lines[i].match(/^(\w+):\s*(.*)$/);
|
|
53
|
+
if (fm) {
|
|
54
|
+
switch (fm[1]) {
|
|
55
|
+
case 'target':
|
|
56
|
+
target = fm[2].trim();
|
|
57
|
+
break;
|
|
58
|
+
case 'data':
|
|
59
|
+
data = fm[2].trim();
|
|
60
|
+
break;
|
|
61
|
+
case 'code':
|
|
62
|
+
code = fm[2].trim();
|
|
63
|
+
break;
|
|
64
|
+
case 'source':
|
|
65
|
+
source = fm[2].trim();
|
|
66
|
+
break;
|
|
67
|
+
case 'docs':
|
|
68
|
+
docs = fm[2].trim();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
if (!target)
|
|
75
|
+
throw new Error(`${file}: stop "${heading}" missing "target"`);
|
|
76
|
+
if (!code)
|
|
77
|
+
throw new Error(`${file}: stop "${heading}" missing "code"`);
|
|
78
|
+
// read prose (blank line, then text until next ## or end)
|
|
79
|
+
while (i < lines.length && lines[i].trim() === '')
|
|
80
|
+
i++;
|
|
81
|
+
const proseLines = [];
|
|
82
|
+
while (i < lines.length && !lines[i].startsWith('##')) {
|
|
83
|
+
proseLines.push(lines[i]);
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
// trim trailing blank lines
|
|
87
|
+
while (proseLines.length > 0 && proseLines[proseLines.length - 1].trim() === '') {
|
|
88
|
+
proseLines.pop();
|
|
89
|
+
}
|
|
90
|
+
stops.push({ heading, target, data, code, source, docs, prose: proseLines.join('\n') });
|
|
91
|
+
}
|
|
92
|
+
return { route, title, stops };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Serialize a Tour back to the markdown format parseTourFile reads.
|
|
96
|
+
*/
|
|
97
|
+
export function serializeTourFile(tour) {
|
|
98
|
+
const lines = [];
|
|
99
|
+
lines.push('---');
|
|
100
|
+
lines.push(`route: ${tour.route}`);
|
|
101
|
+
lines.push(`title: ${tour.title}`);
|
|
102
|
+
lines.push('---');
|
|
103
|
+
for (const stop of tour.stops) {
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(`## ${stop.heading}`);
|
|
106
|
+
lines.push(`target: ${stop.target}`);
|
|
107
|
+
if (stop.data !== undefined)
|
|
108
|
+
lines.push(`data: ${stop.data}`);
|
|
109
|
+
lines.push(`code: ${stop.code}`);
|
|
110
|
+
if (stop.source !== undefined)
|
|
111
|
+
lines.push(`source: ${stop.source}`);
|
|
112
|
+
if (stop.docs !== undefined)
|
|
113
|
+
lines.push(`docs: ${stop.docs}`);
|
|
114
|
+
if (stop.prose) {
|
|
115
|
+
lines.push('');
|
|
116
|
+
lines.push(stop.prose);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
lines.push('');
|
|
120
|
+
return lines.join('\n');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Read every .md file under `<root>/.docugate/tour/` and return an array of
|
|
124
|
+
* Tour objects. Returns [] when the directory does not exist.
|
|
125
|
+
*/
|
|
126
|
+
export function loadTours(root) {
|
|
127
|
+
const dir = join(root, '.docugate', 'tour');
|
|
128
|
+
if (!existsSync(dir))
|
|
129
|
+
return [];
|
|
130
|
+
const tours = [];
|
|
131
|
+
for (const name of readdirSync(dir)) {
|
|
132
|
+
if (!name.endsWith('.md'))
|
|
133
|
+
continue;
|
|
134
|
+
const file = join(dir, name);
|
|
135
|
+
const text = readFileSync(file, 'utf8');
|
|
136
|
+
const rel = `.docugate/tour/${name}`;
|
|
137
|
+
const tour = parseTourFile(text, rel);
|
|
138
|
+
tour.file = rel;
|
|
139
|
+
tours.push(tour);
|
|
140
|
+
}
|
|
141
|
+
return tours;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Match a tour route pattern (with `:param` segments) against a pathname.
|
|
145
|
+
* Returns true when they match.
|
|
146
|
+
*
|
|
147
|
+
* Examples:
|
|
148
|
+
* matchRoute('/invoices/:id', '/invoices/INV-001') → true
|
|
149
|
+
* matchRoute('/invoices/:id', '/invoices') → false
|
|
150
|
+
*/
|
|
151
|
+
export function matchRoute(route, pathname) {
|
|
152
|
+
const routeParts = route.split('/').filter(Boolean);
|
|
153
|
+
const pathParts = pathname.split('/').filter(Boolean);
|
|
154
|
+
if (routeParts.length !== pathParts.length)
|
|
155
|
+
return false;
|
|
156
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
157
|
+
if (routeParts[i].startsWith(':'))
|
|
158
|
+
continue;
|
|
159
|
+
if (routeParts[i] !== pathParts[i])
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Derive the tour filename from a route.
|
|
166
|
+
* Strips the leading `/`, replaces `/` and `:` with `-`, lower-cases the result,
|
|
167
|
+
* and appends `.md`. The root route `/` becomes `home.md`.
|
|
168
|
+
*
|
|
169
|
+
* Examples:
|
|
170
|
+
* routeToFilename('/') → 'home.md'
|
|
171
|
+
* routeToFilename('/invoices/:id') → 'invoices-id.md'
|
|
172
|
+
* routeToFilename('/orgs/:org/repos/:repo') → 'orgs-org-repos-repo.md'
|
|
173
|
+
*/
|
|
174
|
+
export function routeToFilename(route) {
|
|
175
|
+
const trimmed = route.replace(/^\//, '');
|
|
176
|
+
if (!trimmed)
|
|
177
|
+
return 'home.md';
|
|
178
|
+
return trimmed.replace(/[/:]/g, '-').replace(/-+/g, '-').replace(/-$/, '').toLowerCase() + '.md';
|
|
179
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docugate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Command-line tools for DocuGate: set up, check and prepare the documentation DocuGate reads straight from your repository.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
+
"pill/dist/pill.js",
|
|
11
12
|
"README.md",
|
|
12
13
|
"LICENSE"
|
|
13
14
|
],
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
"node": ">=18.17"
|
|
16
17
|
},
|
|
17
18
|
"scripts": {
|
|
18
|
-
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"build": "tsc -p tsconfig.json && npm run build:pill",
|
|
20
|
+
"build:pill": "tsc -p pill/tsconfig.json && node pill/scripts/bundle.mjs",
|
|
19
21
|
"test": "npm run build && node --test \"test/*.test.mjs\"",
|
|
20
22
|
"prepublishOnly": "npm test"
|
|
21
23
|
},
|
|
@@ -29,6 +31,11 @@
|
|
|
29
31
|
"cli"
|
|
30
32
|
],
|
|
31
33
|
"homepage": "https://www.trydocugate.site",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/Docugate/Docugate_package.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": "https://github.com/Docugate/Docugate_package/issues",
|
|
32
39
|
"license": "MIT",
|
|
33
40
|
"dependencies": {
|
|
34
41
|
"yaml": "^2.9.1"
|