dochub 1.0.4 → 2.0.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/README.md +198 -52
- package/index.js +85 -31
- package/package.json +27 -10
- package/src/build.js +90 -0
- package/src/init.js +49 -219
- package/src/markdown.js +23 -0
- package/src/serve.js +134 -66
- package/src/template.js +390 -0
- package/src/utils.js +111 -0
- package/templates/README.md +69 -43
- package/templates/folder/README.md +27 -3
- package/templates/guide.md +75 -3
- package/templates/server.js +0 -271
package/src/init.js
CHANGED
|
@@ -1,219 +1,49 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
console.error('
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
installPackages(projectPath);
|
|
51
|
-
|
|
52
|
-
console.log(`✔ Initialized new DocHub project in '${projectPath}'.`);
|
|
53
|
-
}, 1000); // Simulate some delay for a fun loading effect
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Function to create sample markdown files by copying from templates directory
|
|
57
|
-
function createMarkdownFiles(folderPath) {
|
|
58
|
-
const templatesDir = path.join(__dirname, '..', 'templates');
|
|
59
|
-
|
|
60
|
-
const markdownFiles = [
|
|
61
|
-
{
|
|
62
|
-
name: 'README.md',
|
|
63
|
-
templatePath: path.join(templatesDir, 'README.md')
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'guide.md',
|
|
67
|
-
templatePath: path.join(templatesDir, 'guide.md')
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: 'folder/README.md',
|
|
71
|
-
templatePath: path.join(templatesDir, 'folder', 'README.md')
|
|
72
|
-
}
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
markdownFiles.forEach(file => {
|
|
76
|
-
const filePath = path.join(folderPath, file.name);
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
// Ensure directory exists before writing file
|
|
80
|
-
const dirname = path.dirname(filePath);
|
|
81
|
-
if (!fs.existsSync(dirname)) {
|
|
82
|
-
fs.mkdirSync(dirname, { recursive: true });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const templateContent = fs.readFileSync(file.templatePath, 'utf8');
|
|
86
|
-
fs.writeFileSync(filePath, templateContent);
|
|
87
|
-
console.log(`✔ Created file: ${filePath}`);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error(`✘ Error creating file ${filePath}:`, err);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// Function to create config.json
|
|
96
|
-
function createConfigFile(filePath) {
|
|
97
|
-
const configData = {
|
|
98
|
-
title: 'DocHub Project',
|
|
99
|
-
description: "Change this to your description!"
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
fs.writeFileSync(filePath, JSON.stringify(configData, null, 2));
|
|
103
|
-
animatedLog(`✔ Created file '${filePath}'.`, 50);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Function to create index.html
|
|
107
|
-
function createIndexFile(filePath) {
|
|
108
|
-
const htmlContent = `
|
|
109
|
-
<!DOCTYPE html>
|
|
110
|
-
<html lang="en">
|
|
111
|
-
<head>
|
|
112
|
-
<meta charset="UTF-8">
|
|
113
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
114
|
-
<title>Documentation Project</title>
|
|
115
|
-
</head>
|
|
116
|
-
<body>
|
|
117
|
-
<h1>Welcome to Documentation Project</h1>
|
|
118
|
-
<p>This is the main index.html file for your documentation project.</p>
|
|
119
|
-
</body>
|
|
120
|
-
</html>
|
|
121
|
-
`;
|
|
122
|
-
|
|
123
|
-
fs.writeFileSync(filePath, htmlContent.trim());
|
|
124
|
-
animatedLog(`✔ Created file '${filePath}'.`, 50);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Function to create server.js using a template
|
|
128
|
-
function createServerFile(filePath) {
|
|
129
|
-
const templatePath = path.join(__dirname, '..', 'templates', 'server.js');
|
|
130
|
-
|
|
131
|
-
// Read the server.js template file
|
|
132
|
-
fs.readFile(templatePath, 'utf8', (err, data) => {
|
|
133
|
-
if (err) {
|
|
134
|
-
console.error(`✘ Error reading template file: ${err}`);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Write the template content to the specified filePath
|
|
139
|
-
fs.writeFileSync(filePath, data.trim());
|
|
140
|
-
console.log(`✔ Server file generated at ${filePath}`);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Function to initialize npm in the project directory
|
|
145
|
-
function initializeNpm(projectPath) {
|
|
146
|
-
const packageJson = {
|
|
147
|
-
name: path.basename(projectPath),
|
|
148
|
-
version: '1.0.0',
|
|
149
|
-
description: 'DocHub project',
|
|
150
|
-
scripts: {
|
|
151
|
-
start: 'node server.js'
|
|
152
|
-
},
|
|
153
|
-
keywords: [],
|
|
154
|
-
"main": "server.js",
|
|
155
|
-
author: '',
|
|
156
|
-
license: 'ISC'
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
160
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
execSync('npm init -y', { cwd: projectPath, stdio: 'ignore' });
|
|
164
|
-
animatedLog('✔ Initialized npm.', 50);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
console.error('✘ Failed to initialize npm.');
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Function to install necessary packages
|
|
171
|
-
function installPackages(projectPath) {
|
|
172
|
-
const dependencies = ['http', 'fs', 'path', 'highlight.js', 'socket.io'];
|
|
173
|
-
|
|
174
|
-
animatedLog(`✔ Installing packages: ${dependencies.join(', ')}`, 50);
|
|
175
|
-
const progressBar = createProgressBar(20);
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
execSync(`npm install --save ${dependencies.join(' ')}`, { cwd: projectPath, stdio: 'inherit' });
|
|
179
|
-
progressBar.stop();
|
|
180
|
-
console.log('\n✔ Installed necessary packages.');
|
|
181
|
-
} catch (error) {
|
|
182
|
-
progressBar.stop();
|
|
183
|
-
console.error('\n✘ Failed to install necessary packages.');
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Function to create a progress bar
|
|
188
|
-
function createProgressBar(totalFrames) {
|
|
189
|
-
let currentFrame = 0;
|
|
190
|
-
const progressBar = setInterval(() => {
|
|
191
|
-
process.stdout.write(`\r${frames[currentFrame]} Installing...`);
|
|
192
|
-
currentFrame = (currentFrame + 1) % totalFrames;
|
|
193
|
-
}, 50);
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
stop: () => {
|
|
197
|
-
clearInterval(progressBar);
|
|
198
|
-
process.stdout.write(`\r✔ Installation complete.\n`);
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Function to animate console log with delay
|
|
204
|
-
function animatedLog(message, delay) {
|
|
205
|
-
const frames = ['⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈'];
|
|
206
|
-
let frame = 0;
|
|
207
|
-
|
|
208
|
-
const interval = setInterval(() => {
|
|
209
|
-
process.stdout.write(`\r${frames[frame]} ${message}`);
|
|
210
|
-
frame = (frame + 1) % frames.length;
|
|
211
|
-
}, delay);
|
|
212
|
-
|
|
213
|
-
setTimeout(() => {
|
|
214
|
-
clearInterval(interval);
|
|
215
|
-
process.stdout.write(`\r${message}\n`);
|
|
216
|
-
}, delay * 20); // Simulate loading completion
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
module.exports = initProject;
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function initProject(argv) {
|
|
5
|
+
const directory = argv.directory || 'my-docs';
|
|
6
|
+
const projectPath = path.resolve(directory);
|
|
7
|
+
|
|
8
|
+
if (fs.existsSync(projectPath)) {
|
|
9
|
+
console.error(`Directory '${directory}' already exists.`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
14
|
+
|
|
15
|
+
const markdownDir = path.join(projectPath, 'markdown');
|
|
16
|
+
fs.mkdirSync(markdownDir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
const templatesDir = path.join(__dirname, '..', 'templates');
|
|
19
|
+
const copyFiles = [
|
|
20
|
+
{ src: path.join(templatesDir, 'README.md'), dest: path.join(markdownDir, 'README.md') },
|
|
21
|
+
{ src: path.join(templatesDir, 'guide.md'), dest: path.join(markdownDir, 'guide.md') },
|
|
22
|
+
{ src: path.join(templatesDir, 'folder', 'README.md'), dest: path.join(markdownDir, 'folder', 'README.md') },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (const f of copyFiles) {
|
|
26
|
+
try {
|
|
27
|
+
fs.mkdirSync(path.dirname(f.dest), { recursive: true });
|
|
28
|
+
const content = fs.readFileSync(f.src, 'utf8');
|
|
29
|
+
fs.writeFileSync(f.dest, content, 'utf8');
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(` \x1b[31m!\x1b[0m Failed to create ${f.dest}: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const configContent = `module.exports = {
|
|
36
|
+
title: '${path.basename(projectPath)}',
|
|
37
|
+
description: 'A DocHub documentation site',
|
|
38
|
+
};
|
|
39
|
+
`;
|
|
40
|
+
fs.writeFileSync(path.join(projectPath, 'dochub.config.js'), configContent, 'utf8');
|
|
41
|
+
|
|
42
|
+
const relPath = path.relative(process.cwd(), projectPath);
|
|
43
|
+
console.log(`\n \x1b[1mDocHub\x1b[0m project initialized`);
|
|
44
|
+
console.log(` \x1b[36m>\x1b[0m Location: \x1b[1m${relPath}\x1b[0m`);
|
|
45
|
+
console.log(` \x1b[36m>\x1b[0m Serve: \x1b[1mdochub serve ${relPath}\x1b[0m`);
|
|
46
|
+
console.log(` \x1b[36m>\x1b[0m Build: \x1b[1mdochub build ${relPath}\x1b[0m\n`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = initProject;
|
package/src/markdown.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { marked } = require('marked');
|
|
2
|
+
const hljs = require('highlight.js');
|
|
3
|
+
|
|
4
|
+
marked.use({
|
|
5
|
+
renderer: {
|
|
6
|
+
code({ text, lang }) {
|
|
7
|
+
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
|
8
|
+
let highlighted;
|
|
9
|
+
try {
|
|
10
|
+
highlighted = hljs.highlight(text, { language }).value;
|
|
11
|
+
} catch {
|
|
12
|
+
highlighted = text;
|
|
13
|
+
}
|
|
14
|
+
return `<pre class="code-block"><code class="hljs language-${language}">${highlighted}</code></pre>`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function renderMarkdown(content) {
|
|
20
|
+
return marked.parse(content);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { renderMarkdown };
|
package/src/serve.js
CHANGED
|
@@ -1,66 +1,134 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { WebSocketServer } = require('ws');
|
|
5
|
+
const chokidar = require('chokidar');
|
|
6
|
+
const { renderMarkdown } = require('./markdown');
|
|
7
|
+
const { generatePageHtml } = require('./template');
|
|
8
|
+
const { loadConfig, getSearchData, generateSidebarHtml } = require('./utils');
|
|
9
|
+
|
|
10
|
+
function serveDocs(argv) {
|
|
11
|
+
const directory = path.resolve(argv.directory || '.');
|
|
12
|
+
const port = argv.port || 3000;
|
|
13
|
+
const markdownDir = path.join(directory, 'markdown');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(markdownDir)) {
|
|
16
|
+
console.error(`No 'markdown' directory found in ${directory}.`);
|
|
17
|
+
console.error('Run `dochub init` first to scaffold a project.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const config = loadConfig(directory);
|
|
22
|
+
|
|
23
|
+
const notFoundHtml = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>404 - ${config.title}</title><style>body{font-family:-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#0b1120;color:#f1f5f9;text-align:center}.wrap{max-width:480px}h1{font-size:4rem;margin:0;color:#60a5fa}p{color:#94a3b8;margin:1rem 0 2rem}a{color:#60a5fa;text-decoration:none;border:1px solid #60a5fa;padding:.5rem 1.5rem;border-radius:8px}a:hover{background:#60a5fa;color:#0b1120}</style></head><body><div class="wrap"><h1>404</h1><p>The page you're looking for doesn't exist.</p><a href="/">Go Home</a></div></body></html>`;
|
|
24
|
+
|
|
25
|
+
const server = http.createServer((req, res) => {
|
|
26
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
27
|
+
let pathname = url.pathname;
|
|
28
|
+
|
|
29
|
+
if (pathname === '/favicon.ico') {
|
|
30
|
+
res.writeHead(204);
|
|
31
|
+
res.end();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const baseDir = path.resolve(markdownDir);
|
|
36
|
+
const normalized = pathname.replace(/\/$/, '') || '/';
|
|
37
|
+
|
|
38
|
+
const resolveFile = (cb) => {
|
|
39
|
+
if (normalized === '/') {
|
|
40
|
+
return cb(path.join(baseDir, 'README.md'));
|
|
41
|
+
}
|
|
42
|
+
const rel = normalized.slice(1);
|
|
43
|
+
const candidates = [
|
|
44
|
+
rel + '.md',
|
|
45
|
+
path.join(rel, 'README.md'),
|
|
46
|
+
path.join(rel, 'index.md'),
|
|
47
|
+
];
|
|
48
|
+
let i = 0;
|
|
49
|
+
const next = () => {
|
|
50
|
+
if (i >= candidates.length) return cb(null);
|
|
51
|
+
const resolved = path.resolve(baseDir, candidates[i]);
|
|
52
|
+
if (!resolved.startsWith(baseDir)) { i++; return next(); }
|
|
53
|
+
fs.access(resolved, fs.constants.R_OK, (err) => {
|
|
54
|
+
if (err) { i++; return next(); }
|
|
55
|
+
cb(resolved);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
next();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
resolveFile((resolvedPath) => {
|
|
62
|
+
if (!resolvedPath) {
|
|
63
|
+
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
64
|
+
res.end(notFoundHtml);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fs.readFile(resolvedPath, 'utf8', (err, content) => {
|
|
69
|
+
if (err) {
|
|
70
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
71
|
+
res.end('<h1>500 Internal Server Error</h1>');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const contentHtml = renderMarkdown(content);
|
|
76
|
+
const searchData = getSearchData(directory);
|
|
77
|
+
const sidebarHtml = generateSidebarHtml(directory, pathname);
|
|
78
|
+
|
|
79
|
+
const html = generatePageHtml({
|
|
80
|
+
config,
|
|
81
|
+
sidebarHtml,
|
|
82
|
+
contentHtml,
|
|
83
|
+
searchData,
|
|
84
|
+
currentPath: pathname,
|
|
85
|
+
isDev: true,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
89
|
+
res.end(html);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const wss = new WebSocketServer({ server });
|
|
95
|
+
|
|
96
|
+
wss.on('connection', (ws) => {
|
|
97
|
+
ws.on('close', () => {});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const watcher = chokidar.watch(markdownDir, {
|
|
101
|
+
ignored: /(^|[\/\\])\../,
|
|
102
|
+
persistent: true,
|
|
103
|
+
ignoreInitial: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const debounceTimers = new Map();
|
|
107
|
+
watcher.on('all', (event, filePath) => {
|
|
108
|
+
const key = filePath;
|
|
109
|
+
if (debounceTimers.has(key)) clearTimeout(debounceTimers.get(key));
|
|
110
|
+
debounceTimers.set(key, setTimeout(() => {
|
|
111
|
+
debounceTimers.delete(key);
|
|
112
|
+
const relative = path.relative(markdownDir, filePath);
|
|
113
|
+
console.log(`\x1b[36m[i]\x1b[0m File changed: ${relative}`);
|
|
114
|
+
wss.clients.forEach(client => {
|
|
115
|
+
if (client.readyState === 1) client.send('refresh');
|
|
116
|
+
});
|
|
117
|
+
}, 100));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
server.listen(port, () => {
|
|
121
|
+
console.log(`\n \x1b[1mDocHub\x1b[0m dev server running`);
|
|
122
|
+
console.log(` \x1b[36m>\x1b[0m Local: \x1b[1mhttp://localhost:${port}\x1b[0m`);
|
|
123
|
+
console.log(` \x1b[36m>\x1b[0m Dir: ${markdownDir}\n`);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
process.on('SIGINT', () => {
|
|
127
|
+
watcher.close();
|
|
128
|
+
wss.close();
|
|
129
|
+
server.close();
|
|
130
|
+
process.exit(0);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = serveDocs;
|