@mgks/docmd 0.3.11 โ†’ 0.4.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.
Files changed (47) hide show
  1. package/README.md +15 -160
  2. package/bin/docmd.js +6 -69
  3. package/package.json +6 -79
  4. package/bin/postinstall.js +0 -14
  5. package/src/assets/css/docmd-highlight-dark.css +0 -86
  6. package/src/assets/css/docmd-highlight-light.css +0 -86
  7. package/src/assets/css/docmd-main.css +0 -1757
  8. package/src/assets/css/docmd-theme-retro.css +0 -867
  9. package/src/assets/css/docmd-theme-ruby.css +0 -629
  10. package/src/assets/css/docmd-theme-sky.css +0 -617
  11. package/src/assets/favicon.ico +0 -0
  12. package/src/assets/images/docmd-logo-dark.png +0 -0
  13. package/src/assets/images/docmd-logo-light.png +0 -0
  14. package/src/assets/js/docmd-image-lightbox.js +0 -74
  15. package/src/assets/js/docmd-main.js +0 -260
  16. package/src/assets/js/docmd-mermaid.js +0 -205
  17. package/src/assets/js/docmd-search.js +0 -218
  18. package/src/commands/build.js +0 -237
  19. package/src/commands/dev.js +0 -352
  20. package/src/commands/init.js +0 -277
  21. package/src/commands/live.js +0 -145
  22. package/src/core/asset-manager.js +0 -72
  23. package/src/core/config-loader.js +0 -58
  24. package/src/core/config-validator.js +0 -80
  25. package/src/core/file-processor.js +0 -103
  26. package/src/core/fs-utils.js +0 -40
  27. package/src/core/html-generator.js +0 -185
  28. package/src/core/icon-renderer.js +0 -106
  29. package/src/core/logger.js +0 -21
  30. package/src/core/markdown/containers.js +0 -94
  31. package/src/core/markdown/renderers.js +0 -90
  32. package/src/core/markdown/rules.js +0 -402
  33. package/src/core/markdown/setup.js +0 -113
  34. package/src/core/navigation-helper.js +0 -74
  35. package/src/index.js +0 -12
  36. package/src/live/core.js +0 -67
  37. package/src/live/index.html +0 -216
  38. package/src/live/live.css +0 -256
  39. package/src/live/shims.js +0 -1
  40. package/src/plugins/analytics.js +0 -48
  41. package/src/plugins/seo.js +0 -107
  42. package/src/plugins/sitemap.js +0 -127
  43. package/src/templates/layout.ejs +0 -187
  44. package/src/templates/navigation.ejs +0 -97
  45. package/src/templates/no-style.ejs +0 -166
  46. package/src/templates/partials/theme-init.js +0 -36
  47. package/src/templates/toc.ejs +0 -38
@@ -1,237 +0,0 @@
1
- // Source file from the docmd project โ€” https://github.com/docmd-io/docmd
2
-
3
- const path = require('path');
4
- const fs = require('../core/fs-utils');
5
- const MiniSearch = require('minisearch');
6
- const { loadConfig } = require('../core/config-loader');
7
- const { createMarkdownItInstance, findMarkdownFiles, processMarkdownFile } = require('../core/file-processor');
8
- const { generateHtmlPage, generateNavigationHtml } = require('../core/html-generator');
9
- const { clearWarnedIcons } = require('../core/icon-renderer');
10
- const { findPageNeighbors } = require('../core/navigation-helper');
11
- const { generateSitemap } = require('../plugins/sitemap');
12
- const { processAssets } = require('../core/asset-manager');
13
-
14
- // Add a global or scoped flag to track if the warning has been shown in the current dev session
15
- let highlightWarningShown = false;
16
-
17
- /**
18
- * Format paths for display to make them relative to CWD
19
- */
20
- function formatPathForDisplay(absolutePath, cwd) {
21
- const relativePath = path.relative(cwd, absolutePath);
22
- if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
23
- return `./${relativePath}`;
24
- }
25
- return relativePath;
26
- }
27
-
28
- async function buildSite(configPath, options = { isDev: false, preserve: false, noDoubleProcessing: false }) {
29
- clearWarnedIcons(); // Clear warnings at the start of every build
30
-
31
- const config = await loadConfig(configPath);
32
- const CWD = process.cwd();
33
- const SRC_DIR = path.resolve(CWD, config.srcDir);
34
- const OUTPUT_DIR = path.resolve(CWD, config.outputDir);
35
- const USER_ASSETS_DIR = path.resolve(CWD, 'assets');
36
- const md = createMarkdownItInstance(config);
37
- const shouldMinify = !options.isDev && config.minify !== false;
38
- const searchIndexData = [];
39
- const isOfflineMode = options.offline === true;
40
-
41
- if (!await fs.exists(SRC_DIR)) {
42
- throw new Error(`Source directory not found: ${formatPathForDisplay(SRC_DIR, CWD)}`);
43
- }
44
-
45
- // Create output directory if it doesn't exist
46
- await fs.ensureDir(OUTPUT_DIR);
47
-
48
- // Clean HTML files
49
- if (await fs.exists(OUTPUT_DIR)) {
50
- const cleanupFiles = await findFilesToCleanup(OUTPUT_DIR);
51
- for (const file of cleanupFiles) {
52
- await fs.remove(file);
53
- }
54
- if (!options.isDev) {
55
- console.log(`๐Ÿงน Cleaned HTML files from output directory: ${formatPathForDisplay(OUTPUT_DIR, CWD)}`);
56
- }
57
- }
58
-
59
- // --- ASSET PROCESSING ---
60
- const assetsDestDir = path.join(OUTPUT_DIR, 'assets');
61
- await fs.ensureDir(assetsDestDir);
62
-
63
- // 1. Process Internal Assets First (Base)
64
- const assetsSrcDir = path.join(__dirname, '..', 'assets');
65
- if (await fs.exists(assetsSrcDir)) {
66
- if (!options.isDev) console.log(`๐Ÿ“‚ Processing docmd assets...`);
67
- await processAssets(assetsSrcDir, assetsDestDir, { minify: shouldMinify });
68
- } else {
69
- console.warn(`โš ๏ธ Assets source directory not found: ${formatPathForDisplay(assetsSrcDir, CWD)}`);
70
- }
71
-
72
- // 2. Process User Assets Second (Overrides Internal)
73
- if (await fs.exists(USER_ASSETS_DIR)) {
74
- if (!options.isDev) console.log(`๐Ÿ“‚ Processing user assets...`);
75
- await processAssets(USER_ASSETS_DIR, assetsDestDir, { minify: shouldMinify });
76
-
77
- // Copy favicon to root if exists
78
- const userFavicon = path.join(USER_ASSETS_DIR, 'favicon.ico');
79
- if (await fs.exists(userFavicon)) {
80
- await fs.copy(userFavicon, path.join(OUTPUT_DIR, 'favicon.ico'));
81
- }
82
- }
83
-
84
- // Check for Highlight.js themes presence (sanity check)
85
- const lightThemePath = path.join(__dirname, '..', 'assets', 'css', 'docmd-highlight-light.css');
86
- if (!await fs.exists(lightThemePath)) {
87
- if (!options.isDev || (options.isDev && !highlightWarningShown)) {
88
- console.warn(`โš ๏ธ Highlight.js themes not found in assets. Syntax highlighting may break.`);
89
- if (options.isDev) highlightWarningShown = true;
90
- }
91
- }
92
-
93
- // 3. Process Markdown Content
94
- const processedPages = [];
95
- const processedFiles = new Set();
96
- const markdownFiles = await findMarkdownFiles(SRC_DIR);
97
- if (!options.isDev) console.log(`๐Ÿ“„ Found ${markdownFiles.length} markdown files.`);
98
-
99
- for (const filePath of markdownFiles) {
100
- try {
101
- const relativePath = path.relative(SRC_DIR, filePath);
102
-
103
- // Skip file if already processed in this dev build cycle
104
- if (options.noDoubleProcessing && processedFiles.has(relativePath)) continue;
105
- processedFiles.add(relativePath);
106
-
107
- const processedData = await processMarkdownFile(filePath, md, config);
108
- if (!processedData) continue;
109
-
110
- const { frontmatter: pageFrontmatter, htmlContent, headings, searchData } = processedData;
111
- const isIndexFile = path.basename(relativePath) === 'index.md';
112
-
113
- let outputHtmlPath;
114
- if (isIndexFile) {
115
- outputHtmlPath = path.join(path.dirname(relativePath), 'index.html');
116
- } else {
117
- outputHtmlPath = relativePath.replace(/\.md$/, '/index.html');
118
- }
119
-
120
- const finalOutputHtmlPath = path.join(OUTPUT_DIR, outputHtmlPath);
121
-
122
- let relativePathToRoot = path.relative(path.dirname(finalOutputHtmlPath), OUTPUT_DIR);
123
- relativePathToRoot = relativePathToRoot === '' ? './' : relativePathToRoot.replace(/\\/g, '/') + '/';
124
-
125
- let normalizedPath = path.relative(SRC_DIR, filePath).replace(/\\/g, '/');
126
- if (path.basename(normalizedPath) === 'index.md') {
127
- normalizedPath = path.dirname(normalizedPath);
128
- if (normalizedPath === '.') normalizedPath = '';
129
- } else {
130
- normalizedPath = normalizedPath.replace(/\.md$/, '');
131
- }
132
-
133
- if (!normalizedPath.startsWith('/')) normalizedPath = '/' + normalizedPath;
134
- if (normalizedPath.length > 1 && !normalizedPath.endsWith('/')) normalizedPath += '/';
135
-
136
- const navigationHtml = await generateNavigationHtml(
137
- config.navigation, normalizedPath, relativePathToRoot, config, isOfflineMode
138
- );
139
-
140
- const { prevPage, nextPage } = findPageNeighbors(config.navigation, normalizedPath);
141
-
142
- if (prevPage) prevPage.url = relativePathToRoot + prevPage.path.substring(1);
143
- if (nextPage) nextPage.url = relativePathToRoot + nextPage.path.substring(1);
144
-
145
- const pageDataForTemplate = {
146
- content: htmlContent,
147
- pageTitle: pageFrontmatter.title || 'Untitled',
148
- siteTitle: config.siteTitle,
149
- navigationHtml, relativePathToRoot, config, frontmatter: pageFrontmatter,
150
- outputPath: outputHtmlPath.replace(/\\/g, '/'),
151
- prevPage, nextPage, currentPagePath: normalizedPath,
152
- headings: headings || [], isOfflineMode,
153
- };
154
-
155
- const pageHtml = await generateHtmlPage(pageDataForTemplate, isOfflineMode);
156
- await fs.ensureDir(path.dirname(finalOutputHtmlPath));
157
- await fs.writeFile(finalOutputHtmlPath, pageHtml);
158
-
159
- const sitemapOutputPath = isIndexFile
160
- ? (path.dirname(relativePath) === '.' ? '' : path.dirname(relativePath) + '/')
161
- : relativePath.replace(/\.md$/, '/');
162
-
163
- processedPages.push({
164
- outputPath: sitemapOutputPath.replace(/\\/g, '/'),
165
- frontmatter: pageFrontmatter
166
- });
167
-
168
- // Collect Search Data
169
- if (searchData) {
170
- let pageUrl = outputHtmlPath.replace(/\\/g, '/');
171
- if (pageUrl.endsWith('/index.html')) pageUrl = pageUrl.substring(0, pageUrl.length - 10);
172
-
173
- searchIndexData.push({
174
- id: pageUrl,
175
- title: searchData.title,
176
- text: searchData.content,
177
- headings: searchData.headings.join(' ')
178
- });
179
- }
180
-
181
- } catch (error) {
182
- console.error(`โŒ Error processing ${path.relative(CWD, filePath)}:`, error);
183
- }
184
- }
185
-
186
- // 4. Generate Sitemap
187
- if (config.plugins?.sitemap !== false) {
188
- try {
189
- await generateSitemap(config, processedPages, OUTPUT_DIR, { isDev: options.isDev });
190
- } catch (error) {
191
- console.error(`โŒ Error generating sitemap: ${error.message}`);
192
- }
193
- }
194
-
195
- // 5. Generate Search Index
196
- if (config.search !== false) {
197
- if (!options.isDev) console.log('๐Ÿ” Generating search index...');
198
-
199
- // MiniSearch build process (server-side)
200
- const miniSearch = new MiniSearch({
201
- fields: ['title', 'headings', 'text'],
202
- storeFields: ['title', 'id', 'text'],
203
- searchOptions: { boost: { title: 2, headings: 1.5 }, fuzzy: 0.2 }
204
- });
205
-
206
- miniSearch.addAll(searchIndexData);
207
-
208
- // Save the index JSON
209
- await fs.writeFile(path.join(OUTPUT_DIR, 'search-index.json'), JSON.stringify(miniSearch.toJSON()));
210
-
211
- if (!options.isDev) console.log(`โœ… Search index generated.`);
212
- }
213
-
214
- return { config, processedPages, markdownFiles };
215
- }
216
-
217
- // Helper function to find HTML files and sitemap.xml to clean up
218
- async function findFilesToCleanup(dir) {
219
- let filesToRemove = [];
220
- const items = await fs.readdir(dir, { withFileTypes: true });
221
-
222
- for (const item of items) {
223
- const fullPath = path.join(dir, item.name);
224
-
225
- if (item.isDirectory()) {
226
- if (item.name !== 'assets') {
227
- const subDirFiles = await findFilesToCleanup(fullPath);
228
- filesToRemove = filesToRemove.concat(subDirFiles);
229
- }
230
- } else if (item.name.endsWith('.html') || item.name === 'sitemap.xml') {
231
- filesToRemove.push(fullPath);
232
- }
233
- }
234
- return filesToRemove;
235
- }
236
-
237
- module.exports = { buildSite };
@@ -1,352 +0,0 @@
1
- // Source file from the docmd project โ€” https://github.com/docmd-io/docmd
2
-
3
- const http = require('http');
4
- const WebSocket = require('ws');
5
- const chokidar = require('chokidar');
6
- const path = require('path');
7
- const fs = require('../core/fs-utils');
8
- const chalk = require('chalk');
9
- const os = require('os');
10
- const readline = require('readline');
11
- const { buildSite } = require('./build');
12
- const { loadConfig } = require('../core/config-loader');
13
-
14
- // --- 1. Native Static File Server ---
15
- const MIME_TYPES = {
16
- '.html': 'text/html',
17
- '.js': 'text/javascript',
18
- '.css': 'text/css',
19
- '.json': 'application/json',
20
- '.png': 'image/png',
21
- '.jpg': 'image/jpg',
22
- '.jpeg': 'image/jpg',
23
- '.gif': 'image/gif',
24
- '.svg': 'image/svg+xml',
25
- '.ico': 'image/x-icon',
26
- '.woff': 'application/font-woff',
27
- '.woff2': 'font/woff2',
28
- '.ttf': 'application/font-ttf',
29
- '.txt': 'text/plain',
30
- };
31
-
32
- async function serveStatic(req, res, rootDir) {
33
- // Normalize path and remove query strings
34
- let safePath = path.normalize(req.url).replace(/^(\.\.[\/\\])+/, '').split('?')[0].split('#')[0];
35
- if (safePath === '/' || safePath === '\\') safePath = 'index.html';
36
-
37
- let filePath = path.join(rootDir, safePath);
38
-
39
- try {
40
- let stats;
41
- try {
42
- stats = await fs.stat(filePath);
43
- } catch (e) {
44
- // If direct path fails, try appending .html (clean URLs support)
45
- if (path.extname(filePath) === '') {
46
- filePath += '.html';
47
- stats = await fs.stat(filePath);
48
- } else {
49
- throw e;
50
- }
51
- }
52
-
53
- if (stats.isDirectory()) {
54
- filePath = path.join(filePath, 'index.html');
55
- await fs.stat(filePath);
56
- }
57
-
58
- const ext = path.extname(filePath).toLowerCase();
59
- const contentType = MIME_TYPES[ext] || 'application/octet-stream';
60
- const content = await fs.readFile(filePath);
61
-
62
- res.writeHead(200, { 'Content-Type': contentType });
63
-
64
- // Inject Live Reload Script into HTML files only
65
- if (contentType === 'text/html') {
66
- const htmlStr = content.toString('utf-8');
67
- const liveReloadScript = `
68
- <script>
69
- (function() {
70
- let socket;
71
- let retryCount = 0;
72
- const maxRetries = 50;
73
-
74
- function connect() {
75
- // Avoid connecting if already connected
76
- if (socket && (socket.readyState === 0 || socket.readyState === 1)) return;
77
-
78
- socket = new WebSocket('ws://' + window.location.host);
79
-
80
- socket.onopen = () => {
81
- console.log('โšก docmd connected');
82
- retryCount = 0;
83
- };
84
-
85
- socket.onmessage = (e) => {
86
- if(e.data === 'reload') window.location.reload();
87
- };
88
-
89
- socket.onclose = () => {
90
- // Exponential backoff for reconnection
91
- if (retryCount < maxRetries) {
92
- retryCount++;
93
- const delay = Math.min(1000 * (1.5 ** retryCount), 5000);
94
- setTimeout(connect, delay);
95
- }
96
- };
97
-
98
- socket.onerror = (err) => {
99
- // Ignore errors, let onclose handle retry
100
- };
101
- }
102
- // Delay initial connection slightly to ensure page load
103
- setTimeout(connect, 500);
104
- })();
105
- </script></body>`;
106
- res.end(htmlStr.replace('</body>', liveReloadScript));
107
- } else {
108
- res.end(content);
109
- }
110
-
111
- } catch (err) {
112
- if (err.code === 'ENOENT') {
113
- res.writeHead(404, { 'Content-Type': 'text/html' });
114
- res.end('<h1>404 Not Found</h1><p>docmd dev server</p>');
115
- } else {
116
- res.writeHead(500);
117
- res.end(`Server Error: ${err.code}`);
118
- }
119
- }
120
- }
121
-
122
- // --- 2. Helper Utilities ---
123
-
124
- function formatPathForDisplay(absolutePath, cwd) {
125
- const relativePath = path.relative(cwd, absolutePath);
126
- if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
127
- return `./${relativePath}`;
128
- }
129
- return relativePath;
130
- }
131
-
132
- function getNetworkIp() {
133
- const interfaces = os.networkInterfaces();
134
- for (const name of Object.keys(interfaces)) {
135
- for (const iface of interfaces[name]) {
136
- if (iface.family === 'IPv4' && !iface.internal) {
137
- return iface.address;
138
- }
139
- }
140
- }
141
- return null;
142
- }
143
-
144
- // --- 3. Main Dev Function ---
145
-
146
- async function startDevServer(configPathOption, options = { preserve: false, port: undefined }) {
147
- let config = await loadConfig(configPathOption);
148
- const CWD = process.cwd();
149
-
150
- // Config Fallback Logic
151
- let actualConfigPath = path.resolve(CWD, configPathOption);
152
- if (configPathOption === 'docmd.config.js' && !await fs.pathExists(actualConfigPath)) {
153
- const legacyPath = path.resolve(CWD, 'config.js');
154
- if (await fs.pathExists(legacyPath)) {
155
- actualConfigPath = legacyPath;
156
- }
157
- }
158
-
159
- const resolveConfigPaths = (currentConfig) => {
160
- return {
161
- outputDir: path.resolve(CWD, currentConfig.outputDir),
162
- srcDirToWatch: path.resolve(CWD, currentConfig.srcDir),
163
- configFileToWatch: actualConfigPath,
164
- userAssetsDir: path.resolve(CWD, 'assets'),
165
- };
166
- };
167
-
168
- let paths = resolveConfigPaths(config);
169
- const DOCMD_ROOT = path.resolve(__dirname, '..');
170
-
171
- // --- Create Native Server ---
172
- const server = http.createServer((req, res) => {
173
- serveStatic(req, res, paths.outputDir);
174
- });
175
-
176
- let wss; // WebSocket instance (initialized later)
177
-
178
- function broadcastReload() {
179
- if (wss) {
180
- wss.clients.forEach((client) => {
181
- if (client.readyState === WebSocket.OPEN) {
182
- client.send('reload');
183
- }
184
- });
185
- }
186
- }
187
-
188
- // --- Initial Build ---
189
- console.log(chalk.blue('๐Ÿš€ Performing initial build...'));
190
- try {
191
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
192
- } catch (error) {
193
- console.error(chalk.red('โŒ Initial build failed:'), error.message);
194
- }
195
-
196
- // --- Watcher Setup ---
197
- const userAssetsDirExists = await fs.pathExists(paths.userAssetsDir);
198
- const watchedPaths = [paths.srcDirToWatch, paths.configFileToWatch];
199
- if (userAssetsDirExists) watchedPaths.push(paths.userAssetsDir);
200
-
201
- if (process.env.DOCMD_DEV === 'true') {
202
- watchedPaths.push(
203
- path.join(DOCMD_ROOT, 'templates'),
204
- path.join(DOCMD_ROOT, 'assets'),
205
- path.join(DOCMD_ROOT, 'core'),
206
- path.join(DOCMD_ROOT, 'plugins')
207
- );
208
- }
209
-
210
- console.log(chalk.dim('\n๐Ÿ‘€ Watching for changes in:'));
211
- console.log(chalk.dim(` - Source: ${chalk.cyan(formatPathForDisplay(paths.srcDirToWatch, CWD))}`));
212
- console.log(chalk.dim(` - Config: ${chalk.cyan(formatPathForDisplay(paths.configFileToWatch, CWD))}`));
213
- if (userAssetsDirExists) {
214
- console.log(chalk.dim(` - Assets: ${chalk.cyan(formatPathForDisplay(paths.userAssetsDir, CWD))}`));
215
- }
216
- console.log('');
217
-
218
- const watcher = chokidar.watch(watchedPaths, {
219
- ignored: /(^|[\/\\])\../,
220
- persistent: true,
221
- ignoreInitial: true,
222
- awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 100 }
223
- });
224
-
225
- watcher.on('all', async (event, filePath) => {
226
- const relativeFilePath = path.relative(CWD, filePath);
227
- process.stdout.write(chalk.dim(`โ†ป Change in ${relativeFilePath}... `));
228
-
229
- try {
230
- if (filePath === paths.configFileToWatch) {
231
- config = await loadConfig(configPathOption);
232
- // Note: With native server, we don't need to restart middleware,
233
- // serveStatic reads from disk dynamically on every request.
234
- paths = resolveConfigPaths(config);
235
- }
236
-
237
- await buildSite(configPathOption, { isDev: true, preserve: options.preserve, noDoubleProcessing: true });
238
- broadcastReload();
239
- process.stdout.write(chalk.green('Done.\n'));
240
-
241
- } catch (error) {
242
- console.error(chalk.red('\nโŒ Rebuild failed:'), error.message);
243
- }
244
- });
245
-
246
- // --- Server Startup Logic (Port Checking) ---
247
- const PORT = parseInt(options.port || process.env.PORT || 3000, 10);
248
- const MAX_PORT_ATTEMPTS = 10;
249
-
250
- function checkPortInUse(port) {
251
- return new Promise((resolve) => {
252
- const tester = http.createServer()
253
- .once('error', (err) => {
254
- if (err.code === 'EADDRINUSE') resolve(true);
255
- else resolve(false);
256
- })
257
- .once('listening', () => {
258
- tester.close(() => resolve(false));
259
- })
260
- .listen(port, '0.0.0.0');
261
- });
262
- }
263
-
264
- function askUserConfirmation() {
265
- return new Promise((resolve) => {
266
- const rl = readline.createInterface({
267
- input: process.stdin,
268
- output: process.stdout
269
- });
270
-
271
- console.log(chalk.yellow(`\nโš ๏ธ Port ${PORT} is already in use.`));
272
- console.log(chalk.yellow(` Another instance of docmd (or another app) might be running.`));
273
-
274
- rl.question(' Do you want to start another instance on a different port? (Y/n) ', (answer) => {
275
- rl.close();
276
- const isYes = answer.trim().toLowerCase() === 'y' || answer.trim() === '';
277
- resolve(isYes);
278
- });
279
- });
280
- }
281
-
282
- function tryStartServer(port, attempt = 1) {
283
- server.listen(port, '0.0.0.0')
284
- .on('listening', async () => {
285
- // Initialize WebSocket Server only AFTER successful listen
286
- wss = new WebSocket.Server({ server });
287
- wss.on('error', (e) => console.error('WebSocket Error:', e.message));
288
-
289
- const indexHtmlPath = path.join(paths.outputDir, 'index.html');
290
- const networkIp = getNetworkIp();
291
-
292
- const localUrl = `http://127.0.0.1:${port}`;
293
- const networkUrl = networkIp ? `http://${networkIp}:${port}` : null;
294
-
295
- const border = chalk.gray('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€');
296
- console.log(border);
297
- console.log(` ${chalk.bold.green('SERVER RUNNING')} ${chalk.dim(`(v${require('../../package.json').version})`)}`);
298
- console.log('');
299
- console.log(` ${chalk.bold('Local:')} ${chalk.cyan(localUrl)}`);
300
- if (networkUrl) {
301
- console.log(` ${chalk.bold('Network:')} ${chalk.cyan(networkUrl)}`);
302
- }
303
- console.log('');
304
- console.log(` ${chalk.dim('Serving:')} ${formatPathForDisplay(paths.outputDir, CWD)}`);
305
- console.log(border);
306
- console.log('');
307
-
308
- if (!await fs.pathExists(indexHtmlPath)) {
309
- console.warn(chalk.yellow(`โš ๏ธ Warning: Root index.html not found.`));
310
- }
311
- })
312
- .on('error', (err) => {
313
- if (err.code === 'EADDRINUSE') {
314
- server.close();
315
- tryStartServer(port + 1);
316
- } else {
317
- console.error(chalk.red(`Failed to start server: ${err.message}`));
318
- process.exit(1);
319
- }
320
- });
321
- }
322
-
323
- // --- Main Execution Flow ---
324
- (async () => {
325
- // Skip check if user manually specified port flag
326
- if (options.port) {
327
- tryStartServer(PORT);
328
- return;
329
- }
330
-
331
- const isBusy = await checkPortInUse(PORT);
332
-
333
- if (isBusy) {
334
- const shouldProceed = await askUserConfirmation();
335
- if (!shouldProceed) {
336
- console.log(chalk.dim('Cancelled.'));
337
- process.exit(0);
338
- }
339
- tryStartServer(PORT + 1);
340
- } else {
341
- tryStartServer(PORT);
342
- }
343
- })();
344
-
345
- process.on('SIGINT', () => {
346
- console.log(chalk.yellow('\n๐Ÿ›‘ Shutting down...'));
347
- watcher.close();
348
- process.exit(0);
349
- });
350
- }
351
-
352
- module.exports = { startDevServer };