pict-docuserve 0.0.1

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/build-site.js +172 -0
  3. package/css/docuserve.css +73 -0
  4. package/dist/css/docuserve.css +73 -0
  5. package/dist/index.html +32 -0
  6. package/dist/js/pict.compatible.js +7793 -0
  7. package/dist/js/pict.compatible.js.map +1 -0
  8. package/dist/js/pict.compatible.min.js +12 -0
  9. package/dist/js/pict.compatible.min.js.map +1 -0
  10. package/dist/js/pict.js +7792 -0
  11. package/dist/js/pict.js.map +1 -0
  12. package/dist/js/pict.min.js +12 -0
  13. package/dist/js/pict.min.js.map +1 -0
  14. package/dist/pict-docuserve.compatible.js +5148 -0
  15. package/dist/pict-docuserve.compatible.js.map +1 -0
  16. package/dist/pict-docuserve.compatible.min.js +2 -0
  17. package/dist/pict-docuserve.compatible.min.js.map +1 -0
  18. package/dist/pict-docuserve.js +4670 -0
  19. package/dist/pict-docuserve.js.map +1 -0
  20. package/dist/pict-docuserve.min.js +2 -0
  21. package/dist/pict-docuserve.min.js.map +1 -0
  22. package/html/index.html +32 -0
  23. package/package.json +52 -0
  24. package/source/Pict-Application-Docuserve-Configuration.json +15 -0
  25. package/source/Pict-Application-Docuserve.js +246 -0
  26. package/source/cli/Docuserve-CLI-Program.js +16 -0
  27. package/source/cli/Docuserve-CLI-Run.js +3 -0
  28. package/source/cli/commands/Docuserve-Command-Inject.js +127 -0
  29. package/source/cli/commands/Docuserve-Command-Serve.js +135 -0
  30. package/source/providers/Pict-Provider-Docuserve-Documentation.js +1156 -0
  31. package/source/providers/PictRouter-Docuserve-Configuration.json +26 -0
  32. package/source/views/PictView-Docuserve-Content.js +208 -0
  33. package/source/views/PictView-Docuserve-Layout.js +105 -0
  34. package/source/views/PictView-Docuserve-Sidebar.js +362 -0
  35. package/source/views/PictView-Docuserve-Splash.js +264 -0
  36. package/source/views/PictView-Docuserve-TopBar.js +213 -0
@@ -0,0 +1,32 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
+ <meta name="description" content="Retold Documentation - A suite of JavaScript/Node.js modules for building web applications and APIs">
8
+
9
+ <title>Retold Documentation</title>
10
+
11
+ <!-- Application Stylesheet -->
12
+ <link href="css/docuserve.css" rel="stylesheet">
13
+ <!-- PICT Dynamic View CSS Container -->
14
+ <style id="PICT-CSS"></style>
15
+
16
+ <!-- Load the PICT library -->
17
+ <script src="./js/pict.min.js" type="text/javascript"></script>
18
+ <!-- Bootstrap the Application -->
19
+ <script type="text/javascript">
20
+ //<![CDATA[
21
+ Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(PictDocuserve, 2)});
22
+ //]]>
23
+ </script>
24
+ </head>
25
+ <body>
26
+ <!-- The root container for the Pict application -->
27
+ <div id="Docuserve-Application-Container"></div>
28
+
29
+ <!-- Load the Docuserve PICT Application Bundle -->
30
+ <script src="./pict-docuserve.min.js" type="text/javascript"></script>
31
+ </body>
32
+ </html>
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "pict-docuserve",
3
+ "version": "0.0.1",
4
+ "description": "Pict Documentation Server - A single-page documentation viewer built on Pict",
5
+ "main": "source/Pict-Application-Docuserve.js",
6
+ "bin": {
7
+ "pict-docuserve": "source/cli/Docuserve-CLI-Run.js"
8
+ },
9
+ "files": [
10
+ "source/",
11
+ "dist/",
12
+ "css/",
13
+ "html/",
14
+ "build-site.js"
15
+ ],
16
+ "scripts": {
17
+ "start": "node source/Pict-Application-Docuserve.js",
18
+ "build": "npx quack build && npx quack copy",
19
+ "build-site": "npx quack build && npx quack copy && node build-site.js",
20
+ "build-examples": "npx quack build && npx quack copy && node build-site.js --docs-path example_applications/todo-app/docs --output-path example_applications/todo-app/site && node build-site.js --docs-path example_applications/contacts-app/docs --output-path example_applications/contacts-app/site && node build-site.js --docs-path example_applications/sports-stats-api/docs --output-path example_applications/sports-stats-api/site",
21
+ "test": "echo \"Error: no test specified\" && exit 0"
22
+ },
23
+ "author": "steven velozo <steven@velozo.com>",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "pict": "^1.0.343",
27
+ "pict-application": "^1.0.28",
28
+ "pict-view": "^1.0.64",
29
+ "pict-provider": "^1.0.3",
30
+ "pict-service-commandlineutility": "^1.0.17"
31
+ },
32
+ "devDependencies": {
33
+ "quackage": "^1.0.41"
34
+ },
35
+ "copyFilesSettings": {
36
+ "whenFileExists": "overwrite"
37
+ },
38
+ "copyFiles": [
39
+ {
40
+ "from": "./html/*",
41
+ "to": "./dist/"
42
+ },
43
+ {
44
+ "from": "./css/**",
45
+ "to": "./dist/css/"
46
+ },
47
+ {
48
+ "from": "./node_modules/pict/dist/*",
49
+ "to": "./dist/js/"
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "Name": "Pict Docuserve",
3
+ "Hash": "Docuserve",
4
+
5
+ "MainViewportViewIdentifier": "Docuserve-Layout",
6
+
7
+ "AutoSolveAfterInitialize": true,
8
+ "AutoRenderMainViewportViewAfterInitialize": false,
9
+ "AutoRenderViewsAfterInitialize": false,
10
+
11
+ "pict_configuration":
12
+ {
13
+ "Product": "Docuserve-Pict-Application"
14
+ }
15
+ }
@@ -0,0 +1,246 @@
1
+ const libPictApplication = require('pict-application');
2
+
3
+ // Provider
4
+ const libDocumentationProvider = require('./providers/Pict-Provider-Docuserve-Documentation.js');
5
+
6
+ // Views
7
+ const libViewLayout = require('./views/PictView-Docuserve-Layout.js');
8
+ const libViewTopBar = require('./views/PictView-Docuserve-TopBar.js');
9
+ const libViewSidebar = require('./views/PictView-Docuserve-Sidebar.js');
10
+ const libViewSplash = require('./views/PictView-Docuserve-Splash.js');
11
+ const libViewContent = require('./views/PictView-Docuserve-Content.js');
12
+
13
+ class DocuserveApplication extends libPictApplication
14
+ {
15
+ constructor(pFable, pOptions, pServiceHash)
16
+ {
17
+ super(pFable, pOptions, pServiceHash);
18
+
19
+ // Add the documentation provider
20
+ this.pict.addProvider('Docuserve-Documentation', libDocumentationProvider.default_configuration, libDocumentationProvider);
21
+
22
+ // Add views
23
+ this.pict.addView('Docuserve-Layout', libViewLayout.default_configuration, libViewLayout);
24
+ this.pict.addView('Docuserve-TopBar', libViewTopBar.default_configuration, libViewTopBar);
25
+ this.pict.addView('Docuserve-Sidebar', libViewSidebar.default_configuration, libViewSidebar);
26
+ this.pict.addView('Docuserve-Splash', libViewSplash.default_configuration, libViewSplash);
27
+ this.pict.addView('Docuserve-Content', libViewContent.default_configuration, libViewContent);
28
+ }
29
+
30
+ onAfterInitializeAsync(fCallback)
31
+ {
32
+ // Initialize application state
33
+ this.pict.AppData.Docuserve =
34
+ {
35
+ CatalogLoaded: false,
36
+ Catalog: null,
37
+ CoverLoaded: false,
38
+ Cover: null,
39
+ SidebarLoaded: false,
40
+ SidebarGroups: [],
41
+ TopBarLoaded: false,
42
+ TopBar: null,
43
+ ErrorPageLoaded: false,
44
+ ErrorPageHTML: null,
45
+ CurrentGroup: '',
46
+ CurrentModule: '',
47
+ CurrentPath: '',
48
+ // Whether the sidebar is currently visible
49
+ SidebarVisible: true,
50
+ // Base URL for local docs (relative to where the app is served)
51
+ DocsBaseURL: '',
52
+ // URL for the catalog JSON
53
+ CatalogURL: 'retold-catalog.json'
54
+ };
55
+
56
+ // Load the catalog, then render the layout
57
+ let tmpDocProvider = this.pict.providers['Docuserve-Documentation'];
58
+ tmpDocProvider.loadCatalog(() =>
59
+ {
60
+ // Render the layout shell, which triggers child view rendering
61
+ this.pict.views['Docuserve-Layout'].render();
62
+
63
+ return super.onAfterInitializeAsync(fCallback);
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Read the current window.location.hash and dispatch to the correct handler.
69
+ *
70
+ * Route patterns:
71
+ * #/Home -> showView('Docuserve-Splash')
72
+ * #/page/<docpath> -> navigateToPage(docpath)
73
+ * #/doc/<group>/<module> -> navigateToModule(group, module)
74
+ * #/doc/<group>/<module>/<path> -> navigateToModulePath(group, module, path)
75
+ */
76
+ resolveHash()
77
+ {
78
+ let tmpHash = (window.location.hash || '').replace(/^#\/?/, '');
79
+
80
+ if (!tmpHash || tmpHash === 'Home')
81
+ {
82
+ this.showView('Docuserve-Splash');
83
+ return;
84
+ }
85
+
86
+ let tmpParts = tmpHash.split('/');
87
+
88
+ if (tmpParts[0] === 'page' && tmpParts.length >= 2)
89
+ {
90
+ // Rejoin everything after 'page/' in case the path has slashes
91
+ let tmpDocPath = tmpParts.slice(1).join('/');
92
+ this.navigateToPage(tmpDocPath);
93
+ return;
94
+ }
95
+
96
+ if (tmpParts[0] === 'doc' && tmpParts.length >= 3)
97
+ {
98
+ let tmpGroup = tmpParts[1];
99
+ let tmpModule = tmpParts[2];
100
+ if (tmpParts.length >= 4)
101
+ {
102
+ let tmpPath = tmpParts.slice(3).join('/');
103
+ this.navigateToModulePath(tmpGroup, tmpModule, tmpPath);
104
+ }
105
+ else
106
+ {
107
+ this.navigateToModule(tmpGroup, tmpModule);
108
+ }
109
+ return;
110
+ }
111
+
112
+ // Unknown route — treat as a page
113
+ this.navigateToPage(tmpHash);
114
+ }
115
+
116
+ /**
117
+ * Navigate to a hash route.
118
+ *
119
+ * Sets window.location.hash, which triggers the hashchange listener in the
120
+ * layout view, which calls resolveHash() automatically.
121
+ *
122
+ * @param {string} pRoute - The route path (e.g. '/Home', '/page/quick-start')
123
+ */
124
+ navigateTo(pRoute)
125
+ {
126
+ window.location.hash = pRoute;
127
+ }
128
+
129
+ /**
130
+ * Show a specific view in the content area.
131
+ *
132
+ * @param {string} pViewIdentifier - The view identifier to render
133
+ */
134
+ showView(pViewIdentifier)
135
+ {
136
+ if (pViewIdentifier in this.pict.views)
137
+ {
138
+ this.pict.AppData.Docuserve.CurrentGroup = '';
139
+ this.pict.AppData.Docuserve.CurrentModule = '';
140
+ this.pict.AppData.Docuserve.CurrentPath = '';
141
+
142
+ this.pict.views[pViewIdentifier].render();
143
+
144
+ // Update sidebar to clear module nav and refresh active states
145
+ this.pict.views['Docuserve-Sidebar'].clearModuleNav();
146
+ this.pict.views['Docuserve-Sidebar'].renderSidebarGroups();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Navigate to a module's documentation.
152
+ *
153
+ * @param {string} pGroup - The group key
154
+ * @param {string} pModule - The module name
155
+ */
156
+ navigateToModule(pGroup, pModule)
157
+ {
158
+ this.navigateToModulePath(pGroup, pModule, 'README.md');
159
+ }
160
+
161
+ /**
162
+ * Navigate to a specific path within a module's documentation.
163
+ *
164
+ * @param {string} pGroup - The group key
165
+ * @param {string} pModule - The module name
166
+ * @param {string} pPath - The document path
167
+ */
168
+ navigateToModulePath(pGroup, pModule, pPath)
169
+ {
170
+ let tmpDocProvider = this.pict.providers['Docuserve-Documentation'];
171
+ let tmpContentView = this.pict.views['Docuserve-Content'];
172
+ let tmpSidebarView = this.pict.views['Docuserve-Sidebar'];
173
+
174
+ // Update current navigation state
175
+ this.pict.AppData.Docuserve.CurrentGroup = pGroup;
176
+ this.pict.AppData.Docuserve.CurrentModule = pModule;
177
+ this.pict.AppData.Docuserve.CurrentPath = pPath;
178
+
179
+ // Render the content view shell and show loading
180
+ tmpContentView.render();
181
+ tmpContentView.showLoading();
182
+
183
+ // Update sidebar to show active module and module-specific nav
184
+ tmpSidebarView.renderSidebarGroups();
185
+ tmpSidebarView.renderModuleNav(pGroup, pModule);
186
+
187
+ // Resolve the document URL and fetch it
188
+ let tmpURL = tmpDocProvider.resolveDocumentURL(pGroup, pModule, pPath || 'README.md');
189
+
190
+ if (!tmpURL)
191
+ {
192
+ tmpContentView.displayContent(tmpDocProvider.getErrorPageHTML(pGroup + '/' + pModule));
193
+ return;
194
+ }
195
+
196
+ tmpDocProvider.fetchDocument(tmpURL, (pError, pHTML) =>
197
+ {
198
+ // fetchDocument always provides displayable HTML in pHTML,
199
+ // even on error, so we can use it directly.
200
+ tmpContentView.displayContent(pHTML);
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Navigate to a local documentation page (e.g. architecture.md).
206
+ *
207
+ * @param {string} pDocPath - The doc path without extension
208
+ */
209
+ navigateToPage(pDocPath)
210
+ {
211
+ let tmpDocProvider = this.pict.providers['Docuserve-Documentation'];
212
+ let tmpContentView = this.pict.views['Docuserve-Content'];
213
+ let tmpSidebarView = this.pict.views['Docuserve-Sidebar'];
214
+
215
+ // Update state
216
+ this.pict.AppData.Docuserve.CurrentGroup = '';
217
+ this.pict.AppData.Docuserve.CurrentModule = '';
218
+ this.pict.AppData.Docuserve.CurrentPath = pDocPath;
219
+
220
+ // Render the content view shell and show loading
221
+ tmpContentView.render();
222
+ tmpContentView.showLoading();
223
+
224
+ // Clear module-specific sidebar nav
225
+ tmpSidebarView.clearModuleNav();
226
+ tmpSidebarView.renderSidebarGroups();
227
+
228
+ // Fetch the local document
229
+ let tmpPath = pDocPath;
230
+ if (!tmpPath.match(/\.md$/))
231
+ {
232
+ tmpPath = tmpPath + '.md';
233
+ }
234
+
235
+ tmpDocProvider.fetchLocalDocument(tmpPath, (pError, pHTML) =>
236
+ {
237
+ // fetchDocument always provides displayable HTML in pHTML,
238
+ // even on error, so we can use it directly.
239
+ tmpContentView.displayContent(pHTML);
240
+ });
241
+ }
242
+ }
243
+
244
+ module.exports = DocuserveApplication;
245
+
246
+ module.exports.default_configuration = require('./Pict-Application-Docuserve-Configuration.json');
@@ -0,0 +1,16 @@
1
+ const libCLIProgram = require('pict-service-commandlineutility');
2
+
3
+ let _PictCLIProgram = new libCLIProgram(
4
+ {
5
+ Product: 'pict-docuserve',
6
+ Version: require('../../package.json').version,
7
+
8
+ Command: 'pict-docuserve',
9
+ Description: 'Documentation viewer powered by Pict. Serve or inject documentation assets for any markdown folder.'
10
+ },
11
+ [
12
+ require('./commands/Docuserve-Command-Serve.js'),
13
+ require('./commands/Docuserve-Command-Inject.js')
14
+ ]);
15
+
16
+ module.exports = _PictCLIProgram;
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ const libDocuserveProgram = require('./Docuserve-CLI-Program.js');
3
+ libDocuserveProgram.run();
@@ -0,0 +1,127 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+
3
+ const libFS = require('fs');
4
+ const libPath = require('path');
5
+
6
+ /**
7
+ * Recursively create a directory if it does not exist.
8
+ */
9
+ function ensureDir(pPath)
10
+ {
11
+ if (!libFS.existsSync(pPath))
12
+ {
13
+ libFS.mkdirSync(pPath, { recursive: true });
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Recursively copy a directory's contents into a destination.
19
+ */
20
+ function copyDir(pSource, pDest)
21
+ {
22
+ ensureDir(pDest);
23
+ let tmpEntries = libFS.readdirSync(pSource, { withFileTypes: true });
24
+
25
+ for (let i = 0; i < tmpEntries.length; i++)
26
+ {
27
+ let tmpEntry = tmpEntries[i];
28
+ let tmpSourcePath = libPath.join(pSource, tmpEntry.name);
29
+ let tmpDestPath = libPath.join(pDest, tmpEntry.name);
30
+
31
+ if (tmpEntry.isDirectory())
32
+ {
33
+ copyDir(tmpSourcePath, tmpDestPath);
34
+ }
35
+ else
36
+ {
37
+ libFS.copyFileSync(tmpSourcePath, tmpDestPath);
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Copy a single file, creating parent directories as needed.
44
+ */
45
+ function copyFile(pSource, pDest)
46
+ {
47
+ ensureDir(libPath.dirname(pDest));
48
+ libFS.copyFileSync(pSource, pDest);
49
+ }
50
+
51
+ class DocuserveCommandInject extends libCommandLineCommand
52
+ {
53
+ constructor(pFable, pManifest, pServiceHash)
54
+ {
55
+ super(pFable, pManifest, pServiceHash);
56
+
57
+ this.options.CommandKeyword = 'inject';
58
+ this.options.Description = 'Copy docuserve app assets into a docs folder for static hosting.';
59
+
60
+ this.options.CommandArguments.push({ Name: '<docs-path>', Description: 'Target documentation folder to inject assets into.' });
61
+
62
+ this.addCommand();
63
+ }
64
+
65
+ onRun()
66
+ {
67
+ let tmpDocsPath = libPath.resolve(this.ArgumentString || '.');
68
+ let tmpDistPath = libPath.resolve(__dirname, '..', '..', '..', 'dist');
69
+
70
+ if (!libFS.existsSync(tmpDocsPath))
71
+ {
72
+ this.log.error(`Target folder not found at ${tmpDocsPath}`);
73
+ process.exit(1);
74
+ }
75
+ if (!libFS.existsSync(tmpDistPath))
76
+ {
77
+ this.log.error(`dist/ folder not found at ${tmpDistPath}. Run npm run build first.`);
78
+ process.exit(1);
79
+ }
80
+
81
+ this.log.info(`Injecting docuserve assets...`);
82
+ this.log.info(` From: ${tmpDistPath}`);
83
+ this.log.info(` Into: ${tmpDocsPath}`);
84
+
85
+ // Copy index.html
86
+ copyFile(
87
+ libPath.join(tmpDistPath, 'index.html'),
88
+ libPath.join(tmpDocsPath, 'index.html')
89
+ );
90
+
91
+ // Copy css/ folder
92
+ copyDir(
93
+ libPath.join(tmpDistPath, 'css'),
94
+ libPath.join(tmpDocsPath, 'css')
95
+ );
96
+
97
+ // Copy js/ folder (pict library)
98
+ copyDir(
99
+ libPath.join(tmpDistPath, 'js'),
100
+ libPath.join(tmpDocsPath, 'js')
101
+ );
102
+
103
+ // Copy all pict-docuserve bundle files (*.js and *.js.map)
104
+ let tmpDistEntries = libFS.readdirSync(tmpDistPath, { withFileTypes: true });
105
+ for (let i = 0; i < tmpDistEntries.length; i++)
106
+ {
107
+ let tmpEntry = tmpDistEntries[i];
108
+ if (tmpEntry.isFile() && tmpEntry.name.match(/^pict-docuserve\./))
109
+ {
110
+ copyFile(
111
+ libPath.join(tmpDistPath, tmpEntry.name),
112
+ libPath.join(tmpDocsPath, tmpEntry.name)
113
+ );
114
+ }
115
+ }
116
+
117
+ // Create .nojekyll for GitHub Pages compatibility
118
+ libFS.writeFileSync(libPath.join(tmpDocsPath, '.nojekyll'), '');
119
+
120
+ this.log.info('');
121
+ this.log.info('Injection complete! The docs folder is now self-contained.');
122
+ this.log.info('Deploy to any static host (GitHub Pages, Netlify, etc.) or serve with any HTTP server.');
123
+ this.log.info('');
124
+ }
125
+ }
126
+
127
+ module.exports = DocuserveCommandInject;
@@ -0,0 +1,135 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+
3
+ const libHTTP = require('http');
4
+ const libFS = require('fs');
5
+ const libPath = require('path');
6
+
7
+ const _MimeTypes = (
8
+ {
9
+ '.html': 'text/html',
10
+ '.js': 'text/javascript',
11
+ '.css': 'text/css',
12
+ '.json': 'application/json',
13
+ '.md': 'text/markdown',
14
+ '.map': 'application/json',
15
+ '.svg': 'image/svg+xml',
16
+ '.png': 'image/png',
17
+ '.jpg': 'image/jpeg',
18
+ '.jpeg': 'image/jpeg',
19
+ '.gif': 'image/gif',
20
+ '.ico': 'image/x-icon',
21
+ '.woff': 'font/woff',
22
+ '.woff2': 'font/woff2',
23
+ '.ttf': 'font/ttf',
24
+ '.eot': 'application/vnd.ms-fontobject',
25
+ '.txt': 'text/plain'
26
+ });
27
+
28
+ /**
29
+ * Attempt to serve a file from the given path.
30
+ *
31
+ * @param {string} pFilePath - Absolute path to the file
32
+ * @param {object} pResponse - HTTP response object
33
+ * @param {function} fNotFound - Callback if the file does not exist
34
+ */
35
+ function serveFile(pFilePath, pResponse, fNotFound)
36
+ {
37
+ libFS.stat(pFilePath, (pError, pStats) =>
38
+ {
39
+ if (pError || !pStats.isFile())
40
+ {
41
+ return fNotFound();
42
+ }
43
+
44
+ let tmpExt = libPath.extname(pFilePath).toLowerCase();
45
+ let tmpContentType = _MimeTypes[tmpExt] || 'application/octet-stream';
46
+
47
+ pResponse.writeHead(200, { 'Content-Type': tmpContentType });
48
+
49
+ let tmpStream = libFS.createReadStream(pFilePath);
50
+ tmpStream.pipe(pResponse);
51
+ });
52
+ }
53
+
54
+ class DocuserveCommandServe extends libCommandLineCommand
55
+ {
56
+ constructor(pFable, pManifest, pServiceHash)
57
+ {
58
+ super(pFable, pManifest, pServiceHash);
59
+
60
+ this.options.CommandKeyword = 'serve';
61
+ this.options.Description = 'Start a local HTTP server for a documentation folder.';
62
+
63
+ this.options.CommandArguments.push({ Name: '<docs-path>', Description: 'Path to the documentation folder containing markdown files.' });
64
+
65
+ this.options.CommandOptions.push({ Name: '-p, --port [port]', Description: 'Port to serve on.', Default: '3333' });
66
+
67
+ this.addCommand();
68
+ }
69
+
70
+ onRun()
71
+ {
72
+ let tmpDocsPath = libPath.resolve(this.ArgumentString || '.');
73
+ let tmpDistPath = libPath.resolve(__dirname, '..', '..', '..', 'dist');
74
+ let tmpPort = parseInt(this.CommandOptions.port, 10) || 3333;
75
+
76
+ if (!libFS.existsSync(tmpDocsPath))
77
+ {
78
+ this.log.error(`Docs folder not found at ${tmpDocsPath}`);
79
+ process.exit(1);
80
+ }
81
+ if (!libFS.existsSync(tmpDistPath))
82
+ {
83
+ this.log.error(`dist/ folder not found at ${tmpDistPath}. Run npm run build first.`);
84
+ process.exit(1);
85
+ }
86
+
87
+ let tmpServer = libHTTP.createServer((pRequest, pResponse) =>
88
+ {
89
+ let tmpURLPath = pRequest.url.split('?')[0];
90
+ tmpURLPath = decodeURIComponent(tmpURLPath);
91
+
92
+ // Reject path traversal attempts
93
+ if (tmpURLPath.indexOf('..') >= 0)
94
+ {
95
+ pResponse.writeHead(403, { 'Content-Type': 'text/plain' });
96
+ pResponse.end('Forbidden');
97
+ return;
98
+ }
99
+
100
+ // Rewrite root to index.html
101
+ if (tmpURLPath === '/')
102
+ {
103
+ tmpURLPath = '/index.html';
104
+ }
105
+
106
+ let tmpDocsFilePath = libPath.join(tmpDocsPath, tmpURLPath);
107
+ let tmpDistFilePath = libPath.join(tmpDistPath, tmpURLPath);
108
+
109
+ // Overlay: try the docs folder first, fall back to dist/
110
+ serveFile(tmpDocsFilePath, pResponse, () =>
111
+ {
112
+ serveFile(tmpDistFilePath, pResponse, () =>
113
+ {
114
+ pResponse.writeHead(404, { 'Content-Type': 'text/plain' });
115
+ pResponse.end('Not Found');
116
+ });
117
+ });
118
+ });
119
+
120
+ tmpServer.listen(tmpPort, '127.0.0.1', () =>
121
+ {
122
+ this.log.info('');
123
+ this.log.info(`pict-docuserve is running!`);
124
+ this.log.info('');
125
+ this.log.info(` Local: http://127.0.0.1:${tmpPort}`);
126
+ this.log.info(` Docs: ${tmpDocsPath}`);
127
+ this.log.info(` Assets: ${tmpDistPath}`);
128
+ this.log.info('');
129
+ this.log.info(' Press Ctrl+C to stop.');
130
+ this.log.info('');
131
+ });
132
+ }
133
+ }
134
+
135
+ module.exports = DocuserveCommandServe;