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/src/init.js CHANGED
@@ -1,219 +1,49 @@
1
- const { execSync } = require('child_process');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- function initProject(argv) {
6
- const directory = argv.directory;
7
-
8
- if (!directory) {
9
- console.error('Please provide a directory name.');
10
- return;
11
- }
12
-
13
- const projectPath = path.resolve(directory);
14
-
15
- if (fs.existsSync(projectPath)) {
16
- console.error(`Directory '${directory}' already exists.`);
17
- return;
18
- }
19
-
20
- // Create the main documentation folder
21
- fs.mkdirSync(projectPath);
22
- animatedLog(`✔ Created directory '${projectPath}'.`, 50);
23
-
24
- // Simulate folder creation with loading animation
25
- setTimeout(() => {
26
- const markdownFolder = path.join(projectPath, 'markdown');
27
- fs.mkdirSync(markdownFolder, { recursive: true });
28
- animatedLog(`✔ Created directory '${markdownFolder}'.`, 50);
29
-
30
- // Create sample markdown files
31
- createMarkdownFiles(markdownFolder);
32
-
33
- // Create config.json
34
- const configFilePath = path.join(projectPath, 'config.json');
35
- createConfigFile(configFilePath);
36
- animatedLog(`✔ Created file '${configFilePath}'.`, 50);
37
-
38
- // Create index.html
39
- const indexFilePath = path.join(projectPath, 'index.html');
40
- createIndexFile(indexFilePath);
41
- animatedLog(`✔ Created file '${indexFilePath}'.`, 50);
42
-
43
- // Create server.js
44
- const serverFilePath = path.join(projectPath, 'server.js');
45
- createServerFile(serverFilePath, projectPath);
46
- animatedLog(`✔ Created file '${serverFilePath}'.`, 50);
47
-
48
- // Initialize npm and install necessary packages
49
- initializeNpm(projectPath);
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;
@@ -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 { spawn } = require('child_process');
2
- const path = require('path');
3
- const fs = require('fs');
4
-
5
- function serveDocs(argv) {
6
- const directory = argv.directory || './docs';
7
-
8
- const serverFilePath = path.resolve(directory, 'server.js');
9
-
10
- if (!serverExists(serverFilePath)) {
11
- console.error(`Server file '${serverFilePath}' not found. Make sure to run 'init' first.`);
12
- return;
13
- }
14
-
15
- // Start the server using Node.js child process
16
- const serverProcess = spawn('node', [serverFilePath]);
17
-
18
- // Simulate server starting with loading animation
19
- animatedLog(`Starting server from '${serverFilePath}'`, 50);
20
-
21
- serverProcess.stdout.on('data', (data) => {
22
- // Log server stdout with tick emoji
23
- logWithTick(data.toString().trim());
24
- });
25
-
26
- serverProcess.stderr.on('data', (data) => {
27
- // Log server stderr with tick emoji for errors
28
- logWithTick(`Error: ${data.toString().trim()}`);
29
- });
30
-
31
- serverProcess.on('close', (code) => {
32
- console.log(`Server process exited with code ${code}`);
33
- });
34
- }
35
-
36
- // Function to check if server.js file exists
37
- function serverExists(filePath) {
38
- try {
39
- return fs.existsSync(filePath);
40
- } catch (err) {
41
- return false;
42
- }
43
- }
44
-
45
- // Function to animate console log with delay
46
- function animatedLog(message, delay) {
47
- const frames = ['⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈'];
48
- let frame = 0;
49
-
50
- const interval = setInterval(() => {
51
- process.stdout.write(`\r${frames[frame]} ${message}`);
52
- frame = (frame + 1) % frames.length;
53
- }, delay);
54
-
55
- setTimeout(() => {
56
- clearInterval(interval);
57
- process.stdout.write(`\r✔ ${message}\n`);
58
- }, delay * 20); // Simulate loading completion
59
- }
60
-
61
- // Function to log with tick emoji
62
- function logWithTick(message) {
63
- console.log(`✔ ${message}`);
64
- }
65
-
66
- module.exports = serveDocs;
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;