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.
- package/LICENSE +21 -0
- package/build-site.js +172 -0
- package/css/docuserve.css +73 -0
- package/dist/css/docuserve.css +73 -0
- package/dist/index.html +32 -0
- package/dist/js/pict.compatible.js +7793 -0
- package/dist/js/pict.compatible.js.map +1 -0
- package/dist/js/pict.compatible.min.js +12 -0
- package/dist/js/pict.compatible.min.js.map +1 -0
- package/dist/js/pict.js +7792 -0
- package/dist/js/pict.js.map +1 -0
- package/dist/js/pict.min.js +12 -0
- package/dist/js/pict.min.js.map +1 -0
- package/dist/pict-docuserve.compatible.js +5148 -0
- package/dist/pict-docuserve.compatible.js.map +1 -0
- package/dist/pict-docuserve.compatible.min.js +2 -0
- package/dist/pict-docuserve.compatible.min.js.map +1 -0
- package/dist/pict-docuserve.js +4670 -0
- package/dist/pict-docuserve.js.map +1 -0
- package/dist/pict-docuserve.min.js +2 -0
- package/dist/pict-docuserve.min.js.map +1 -0
- package/html/index.html +32 -0
- package/package.json +52 -0
- package/source/Pict-Application-Docuserve-Configuration.json +15 -0
- package/source/Pict-Application-Docuserve.js +246 -0
- package/source/cli/Docuserve-CLI-Program.js +16 -0
- package/source/cli/Docuserve-CLI-Run.js +3 -0
- package/source/cli/commands/Docuserve-Command-Inject.js +127 -0
- package/source/cli/commands/Docuserve-Command-Serve.js +135 -0
- package/source/providers/Pict-Provider-Docuserve-Documentation.js +1156 -0
- package/source/providers/PictRouter-Docuserve-Configuration.json +26 -0
- package/source/views/PictView-Docuserve-Content.js +208 -0
- package/source/views/PictView-Docuserve-Layout.js +105 -0
- package/source/views/PictView-Docuserve-Sidebar.js +362 -0
- package/source/views/PictView-Docuserve-Splash.js +264 -0
- package/source/views/PictView-Docuserve-TopBar.js +213 -0
package/html/index.html
ADDED
|
@@ -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,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;
|