quackage 1.0.51 → 1.0.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quackage",
3
- "version": "1.0.51",
3
+ "version": "1.0.54",
4
4
  "description": "Building. Testing. Quacking. Reloading.",
5
5
  "main": "source/Quackage-CLIProgram.js",
6
6
  "scripts": {
@@ -61,6 +61,8 @@
61
61
 
62
62
  "build": "npx quack build",
63
63
 
64
+ "example": "npx quack example",
65
+
64
66
  "docs": "npx quack prepare-docs ./docs -d ./modules",
65
67
  "docs-serve": "npx quack docs-serve ./docs"
66
68
  },
@@ -49,7 +49,12 @@ let _Pict = new libCLIProgram(
49
49
  require('./commands/Quackage-Command-DocuserveInject.js'),
50
50
  require('./commands/Quackage-Command-DocuservePrepareLocal.js'),
51
51
  require('./commands/Quackage-Command-PrepareDocs.js'),
52
- require('./commands/Quackage-Command-DocuserveServe.js')
52
+ require('./commands/Quackage-Command-DocuserveServe.js'),
53
+
54
+ // HTML example application building and serving
55
+ require('./commands/html_example_serving/Quackage-Command-ExamplesBuild.js'),
56
+ require('./commands/html_example_serving/Quackage-Command-ExamplesServe.js'),
57
+ require('./commands/html_example_serving/Quackage-Command-Examples.js')
53
58
  ]);
54
59
 
55
60
  // Instantiate the file persistence service
@@ -57,6 +62,8 @@ _Pict.instantiateServiceProvider('FilePersistence');
57
62
  _Pict.instantiateServiceProvider('DataGeneration');
58
63
  // Add the Quackage Process Management service
59
64
  _Pict.addAndInstantiateServiceType('QuackageProcess', require('./services/Quackage-Execute-Process.js'));
65
+ // Add the Quackage Example Service for building and serving HTML examples
66
+ _Pict.addAndInstantiateServiceType('QuackageExampleService', require('./services/Quackage-ExampleService.js'));
60
67
 
61
68
  // Grab the current working directory for the quackage
62
69
  _Pict.AppData.CWD = _Pict.QuackageProcess.cwd();
@@ -0,0 +1,41 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+ const libPath = require('path');
3
+
4
+ class QuackageCommandExamples extends libCommandLineCommand
5
+ {
6
+ constructor(pFable, pManifest, pServiceHash)
7
+ {
8
+ super(pFable, pManifest, pServiceHash);
9
+
10
+ this.options.CommandKeyword = 'examples';
11
+ this.options.Description = 'Build all example applications then serve them with an auto-generated index page.';
12
+
13
+ this.options.CommandArguments.push({ Name: '[examples_folder]', Description: 'The examples folder (defaults to ./example_applications).' });
14
+
15
+ this.options.CommandOptions.push({ Name: '-p, --port [port]', Description: 'Port to serve on (default: auto-hashed from project name between 9000-9500).', Default: '' });
16
+
17
+ this.addCommand();
18
+ }
19
+
20
+ onRunAsync(fCallback)
21
+ {
22
+ let tmpExamplesFolder = libPath.resolve(this.ArgumentString || './example_applications');
23
+ let tmpPort = this.CommandOptions.port ? parseInt(this.CommandOptions.port, 10) : 0;
24
+
25
+ this.log.info(`Building and serving examples from [${tmpExamplesFolder}] ...`);
26
+
27
+ this.fable.QuackageExampleService.buildExamples(tmpExamplesFolder,
28
+ (pBuildError) =>
29
+ {
30
+ if (pBuildError)
31
+ {
32
+ this.log.warn(`Some examples had build errors, but continuing to serve what we have...`);
33
+ }
34
+
35
+ // examples-serve doesn't call back (long-lived server), so neither do we
36
+ this.fable.QuackageExampleService.serveExamples(tmpExamplesFolder, tmpPort, fCallback);
37
+ });
38
+ }
39
+ }
40
+
41
+ module.exports = QuackageCommandExamples;
@@ -0,0 +1,25 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+ const libPath = require('path');
3
+
4
+ class QuackageCommandExamplesBuild extends libCommandLineCommand
5
+ {
6
+ constructor(pFable, pManifest, pServiceHash)
7
+ {
8
+ super(pFable, pManifest, pServiceHash);
9
+
10
+ this.options.CommandKeyword = 'examples-build';
11
+ this.options.Description = 'Build all example applications in the example_applications and debug folders.';
12
+
13
+ this.options.CommandArguments.push({ Name: '[examples_folder]', Description: 'The examples folder (defaults to ./example_applications).' });
14
+
15
+ this.addCommand();
16
+ }
17
+
18
+ onRunAsync(fCallback)
19
+ {
20
+ let tmpExamplesFolder = libPath.resolve(this.ArgumentString || './example_applications');
21
+ this.fable.QuackageExampleService.buildExamples(tmpExamplesFolder, fCallback);
22
+ }
23
+ }
24
+
25
+ module.exports = QuackageCommandExamplesBuild;
@@ -0,0 +1,28 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+ const libPath = require('path');
3
+
4
+ class QuackageCommandExamplesServe extends libCommandLineCommand
5
+ {
6
+ constructor(pFable, pManifest, pServiceHash)
7
+ {
8
+ super(pFable, pManifest, pServiceHash);
9
+
10
+ this.options.CommandKeyword = 'examples-serve';
11
+ this.options.Description = 'Serve example applications with an auto-generated index page.';
12
+
13
+ this.options.CommandArguments.push({ Name: '[examples_folder]', Description: 'The examples folder (defaults to ./example_applications).' });
14
+
15
+ this.options.CommandOptions.push({ Name: '-p, --port [port]', Description: 'Port to serve on (default: auto-hashed from project name between 9000-9500).', Default: '' });
16
+
17
+ this.addCommand();
18
+ }
19
+
20
+ onRunAsync(fCallback)
21
+ {
22
+ let tmpExamplesFolder = libPath.resolve(this.ArgumentString || './example_applications');
23
+ let tmpPort = this.CommandOptions.port ? parseInt(this.CommandOptions.port, 10) : 0;
24
+ this.fable.QuackageExampleService.serveExamples(tmpExamplesFolder, tmpPort, fCallback);
25
+ }
26
+ }
27
+
28
+ module.exports = QuackageCommandExamplesServe;
@@ -0,0 +1,487 @@
1
+ const libPict = require('pict');
2
+ const libFS = require('fs');
3
+ const libPath = require('path');
4
+ const libHTTP = require('http');
5
+
6
+ class QuackageExampleService extends libPict.ServiceProviderBase
7
+ {
8
+ constructor(pFable, pManifest, pServiceHash)
9
+ {
10
+ super(pFable, pManifest, pServiceHash);
11
+
12
+ this.serviceType = 'QuackageExampleService';
13
+ }
14
+
15
+ // --- Folder scanning ---
16
+
17
+ gatherExampleFolders(pBasePath)
18
+ {
19
+ let tmpExampleFolders = [];
20
+
21
+ if (!libFS.existsSync(pBasePath))
22
+ {
23
+ return tmpExampleFolders;
24
+ }
25
+
26
+ let tmpEntries = libFS.readdirSync(pBasePath, { withFileTypes: true });
27
+ for (let i = 0; i < tmpEntries.length; i++)
28
+ {
29
+ if (!tmpEntries[i].isDirectory())
30
+ {
31
+ continue;
32
+ }
33
+
34
+ let tmpDirName = tmpEntries[i].name;
35
+
36
+ // Skip node_modules and hidden folders
37
+ if (tmpDirName === 'node_modules' || tmpDirName.startsWith('.'))
38
+ {
39
+ continue;
40
+ }
41
+
42
+ let tmpFolderPath = libPath.join(pBasePath, tmpDirName);
43
+ let tmpPackagePath = libPath.join(tmpFolderPath, 'package.json');
44
+
45
+ if (libFS.existsSync(tmpPackagePath))
46
+ {
47
+ tmpExampleFolders.push(
48
+ {
49
+ Name: tmpDirName,
50
+ Path: tmpFolderPath,
51
+ PackagePath: tmpPackagePath
52
+ });
53
+ }
54
+ }
55
+
56
+ return tmpExampleFolders;
57
+ }
58
+
59
+ gatherServableExamples(pBasePath)
60
+ {
61
+ let tmpExamples = [];
62
+
63
+ if (!libFS.existsSync(pBasePath))
64
+ {
65
+ return tmpExamples;
66
+ }
67
+
68
+ let tmpEntries = libFS.readdirSync(pBasePath, { withFileTypes: true });
69
+ for (let i = 0; i < tmpEntries.length; i++)
70
+ {
71
+ if (!tmpEntries[i].isDirectory())
72
+ {
73
+ continue;
74
+ }
75
+
76
+ let tmpDirName = tmpEntries[i].name;
77
+
78
+ // Skip node_modules and hidden folders
79
+ if (tmpDirName === 'node_modules' || tmpDirName.startsWith('.'))
80
+ {
81
+ continue;
82
+ }
83
+
84
+ let tmpFolderPath = libPath.join(pBasePath, tmpDirName);
85
+
86
+ // Check for dist/index.html (standard example pattern)
87
+ let tmpDistIndex = libPath.join(tmpFolderPath, 'dist', 'index.html');
88
+ if (libFS.existsSync(tmpDistIndex))
89
+ {
90
+ // Try to get a nice name from package.json
91
+ let tmpDisplayName = this.formatDisplayName(tmpDirName);
92
+ let tmpPackagePath = libPath.join(tmpFolderPath, 'package.json');
93
+ if (libFS.existsSync(tmpPackagePath))
94
+ {
95
+ try
96
+ {
97
+ let tmpPkg = JSON.parse(libFS.readFileSync(tmpPackagePath, 'utf8'));
98
+ if (tmpPkg.description)
99
+ {
100
+ tmpDisplayName = tmpPkg.description;
101
+ }
102
+ }
103
+ catch (pError)
104
+ {
105
+ // Use the formatted folder name
106
+ }
107
+ }
108
+
109
+ tmpExamples.push(
110
+ {
111
+ Name: tmpDirName,
112
+ DisplayName: tmpDisplayName,
113
+ RelativePath: `${tmpDirName}/dist/index.html`,
114
+ Type: 'example'
115
+ });
116
+ continue;
117
+ }
118
+
119
+ // Check for direct index.html (debug folder pattern)
120
+ let tmpDirectIndex = libPath.join(tmpFolderPath, 'index.html');
121
+ if (libFS.existsSync(tmpDirectIndex))
122
+ {
123
+ tmpExamples.push(
124
+ {
125
+ Name: tmpDirName,
126
+ DisplayName: this.formatDisplayName(tmpDirName),
127
+ RelativePath: `${tmpDirName}/index.html`,
128
+ Type: 'debug'
129
+ });
130
+ }
131
+ }
132
+
133
+ return tmpExamples;
134
+ }
135
+
136
+ // --- Display helpers ---
137
+
138
+ formatDisplayName(pFolderName)
139
+ {
140
+ // Convert folder_name to Title Case Display Name
141
+ return pFolderName
142
+ .split(/[-_]/)
143
+ .map((pWord) => pWord.charAt(0).toUpperCase() + pWord.slice(1))
144
+ .join(' ');
145
+ }
146
+
147
+ hashProjectNameToPort(pProjectName)
148
+ {
149
+ let tmpHash = 0;
150
+ for (let i = 0; i < pProjectName.length; i++)
151
+ {
152
+ let tmpChar = pProjectName.charCodeAt(i);
153
+ tmpHash = ((tmpHash << 5) - tmpHash) + tmpChar;
154
+ tmpHash = tmpHash & tmpHash; // Convert to 32-bit integer
155
+ }
156
+ // Map to range 9000-9500
157
+ return 9000 + (Math.abs(tmpHash) % 501);
158
+ }
159
+
160
+ getMimeType(pExtension)
161
+ {
162
+ let tmpMimeTypes =
163
+ {
164
+ '.html': 'text/html',
165
+ '.js': 'text/javascript',
166
+ '.css': 'text/css',
167
+ '.json': 'application/json',
168
+ '.png': 'image/png',
169
+ '.jpg': 'image/jpeg',
170
+ '.jpeg': 'image/jpeg',
171
+ '.gif': 'image/gif',
172
+ '.svg': 'image/svg+xml',
173
+ '.ico': 'image/x-icon',
174
+ '.woff': 'font/woff',
175
+ '.woff2': 'font/woff2',
176
+ '.ttf': 'font/ttf',
177
+ '.map': 'application/json'
178
+ };
179
+ return tmpMimeTypes[pExtension] || 'application/octet-stream';
180
+ }
181
+
182
+ // --- HTML generation ---
183
+
184
+ generateIndexHTML(pProjectName, pExamples, pPort)
185
+ {
186
+ let tmpExampleListItems = '';
187
+ for (let i = 0; i < pExamples.length; i++)
188
+ {
189
+ let tmpExample = pExamples[i];
190
+ let tmpTypeLabel = tmpExample.Type === 'debug' ? ' <span class="type-badge debug">debug</span>' : '';
191
+ tmpExampleListItems += `\t\t\t<li><a href="${tmpExample.RelativePath}">${tmpExample.DisplayName}${tmpTypeLabel}</a></li>\n`;
192
+ }
193
+
194
+ return `<!DOCTYPE html>
195
+ <html lang="en">
196
+ <head>
197
+ <meta charset="UTF-8">
198
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
199
+ <title>Examples - ${pProjectName}</title>
200
+ <style>
201
+ *, *::before, *::after { box-sizing: border-box; }
202
+ body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #FAEDCD; color: #264653; }
203
+
204
+ /* --- Header Bar --- */
205
+ .pict-example-header { display: flex; align-items: stretch; background: #264653; border-bottom: 3px solid #E76F51; }
206
+ .pict-example-badge { background: #E76F51; color: #fff; padding: 0.6rem 1rem; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em; display: flex; align-items: center; gap: 0.5rem; }
207
+ .pict-example-badge svg { width: 14px; height: 14px; fill: #fff; flex-shrink: 0; }
208
+ .pict-example-app-name { padding: 0.6rem 1rem; color: #FAEDCD; font-size: 1.1rem; font-weight: 600; display: flex; align-items: center; }
209
+ .pict-example-module { margin-left: auto; padding: 0.6rem 1rem; color: #D4A373; font-size: 0.75rem; display: flex; align-items: center; letter-spacing: 0.03em; }
210
+
211
+ /* --- Content Area --- */
212
+ .pict-example-content { max-width: 800px; margin: 0 auto; padding: 2rem 1.5rem; }
213
+ .pict-example-content h1 { color: #264653; font-size: 1.5rem; margin: 0 0 0.5rem; }
214
+ .pict-example-content .subtitle { color: #6B705C; font-size: 0.85rem; margin: 0 0 1.5rem; }
215
+
216
+ /* --- Example List --- */
217
+ .example-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.5rem; }
218
+ .example-list li { background: #fff; border: 1px solid #D4A373; border-left: 4px solid #E76F51; border-radius: 4px; transition: border-color 0.15s, box-shadow 0.15s; }
219
+ .example-list li:hover { border-left-color: #264653; box-shadow: 0 2px 8px rgba(38,70,83,0.1); }
220
+ .example-list a { display: block; padding: 0.75rem 1rem; text-decoration: none; color: #264653; font-weight: 500; font-size: 0.95rem; }
221
+ .example-list a:hover { color: #E76F51; }
222
+
223
+ /* --- Type Badge --- */
224
+ .type-badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 3px; font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; vertical-align: middle; margin-left: 0.5rem; }
225
+ .type-badge.debug { background: #264653; color: #FAEDCD; }
226
+
227
+ /* --- Footer --- */
228
+ .pict-example-footer { text-align: center; padding: 1.5rem; color: #6B705C; font-size: 0.75rem; border-top: 1px solid #D4A373; margin-top: 2rem; }
229
+ </style>
230
+ </head>
231
+ <body>
232
+ <div class="pict-example-header">
233
+ <div class="pict-example-badge">
234
+ <svg viewBox="0 0 16 16"><polygon points="8,1 10,6 16,6 11,9.5 13,15 8,11.5 3,15 5,9.5 0,6 6,6"/></svg>
235
+ Pict Example
236
+ </div>
237
+ <div class="pict-example-app-name">Example Index</div>
238
+ <div class="pict-example-module">${pProjectName}</div>
239
+ </div>
240
+ <div class="pict-example-content">
241
+ <h1>Example Applications</h1>
242
+ <p class="subtitle">${pExamples.length} example(s) found &mdash; served on port ${pPort}</p>
243
+ <ul class="example-list">
244
+ ${tmpExampleListItems} </ul>
245
+ </div>
246
+ <div class="pict-example-footer">
247
+ Served by quackage examples-serve
248
+ </div>
249
+ </body>
250
+ </html>`;
251
+ }
252
+
253
+ // --- Executable resolution ---
254
+
255
+ resolveExecutable(pName)
256
+ {
257
+ let tmpLocations =
258
+ [
259
+ `${this.fable.AppData.CWD}/node_modules/.bin/${pName}`,
260
+ `${__dirname}/../../../.bin/${pName}`,
261
+ `${__dirname}/../../node_modules/.bin/${pName}`
262
+ ];
263
+
264
+ for (let i = 0; i < tmpLocations.length; i++)
265
+ {
266
+ if (libFS.existsSync(tmpLocations[i]))
267
+ {
268
+ return tmpLocations[i];
269
+ }
270
+ }
271
+
272
+ // Fall back to just the bare command name (rely on PATH)
273
+ return pName;
274
+ }
275
+
276
+ // --- Build all examples ---
277
+
278
+ buildExamples(pExamplesFolder, fCallback)
279
+ {
280
+ let tmpExamplesFolder = libPath.resolve(pExamplesFolder || './example_applications');
281
+
282
+ this.log.info(`Building all examples in [${tmpExamplesFolder}] ...`);
283
+
284
+ // Gather example folders from example_applications and debug
285
+ let tmpExampleFolders = this.gatherExampleFolders(tmpExamplesFolder);
286
+ let tmpDebugFolder = libPath.join(tmpExamplesFolder, 'debug');
287
+ let tmpDebugPackage = libPath.join(tmpDebugFolder, 'package.json');
288
+ if (libFS.existsSync(tmpDebugPackage))
289
+ {
290
+ let tmpAlreadyIncluded = tmpExampleFolders.some((pFolder) => pFolder.Name === 'debug');
291
+ if (!tmpAlreadyIncluded)
292
+ {
293
+ tmpExampleFolders.push(
294
+ {
295
+ Name: 'debug',
296
+ Path: tmpDebugFolder,
297
+ PackagePath: tmpDebugPackage
298
+ });
299
+ }
300
+ }
301
+
302
+ if (tmpExampleFolders.length < 1)
303
+ {
304
+ this.log.warn(`No example application folders with package.json found in [${tmpExamplesFolder}].`);
305
+ return fCallback();
306
+ }
307
+
308
+ this.log.info(`Found ${tmpExampleFolders.length} example application(s) to build.`);
309
+
310
+ // Build each example in series
311
+ this.fable.Utility.eachLimit(
312
+ tmpExampleFolders, 1,
313
+ (pExample, fExampleCallback) =>
314
+ {
315
+ this.log.info(`####### Building example: ${pExample.Name} #######`);
316
+
317
+ let tmpPackage;
318
+ try
319
+ {
320
+ tmpPackage = JSON.parse(libFS.readFileSync(pExample.PackagePath, 'utf8'));
321
+ }
322
+ catch (pError)
323
+ {
324
+ this.log.error(`Error reading package.json for [${pExample.Name}]: ${pError.message}`);
325
+ return fExampleCallback();
326
+ }
327
+
328
+ if (!tmpPackage.scripts || !tmpPackage.scripts.build)
329
+ {
330
+ this.log.warn(`No build script in [${pExample.Name}] -- skipping.`);
331
+ return fExampleCallback();
332
+ }
333
+
334
+ let tmpNpxLocation = this.resolveExecutable('npx');
335
+
336
+ this.fable.QuackageProcess.execute(
337
+ tmpNpxLocation,
338
+ ['quack', 'build'],
339
+ { cwd: pExample.Path },
340
+ (pBuildError) =>
341
+ {
342
+ if (pBuildError)
343
+ {
344
+ this.log.error(`Build error in [${pExample.Name}]: ${pBuildError.message}`);
345
+ }
346
+
347
+ if (tmpPackage.copyFiles || tmpPackage.copyFilesSettings)
348
+ {
349
+ this.log.info(`Copying files for [${pExample.Name}] ...`);
350
+ this.fable.QuackageProcess.execute(
351
+ tmpNpxLocation,
352
+ ['quack', 'copy'],
353
+ { cwd: pExample.Path },
354
+ (pCopyError) =>
355
+ {
356
+ if (pCopyError)
357
+ {
358
+ this.log.error(`Copy error in [${pExample.Name}]: ${pCopyError.message}`);
359
+ }
360
+ return fExampleCallback();
361
+ });
362
+ }
363
+ else
364
+ {
365
+ return fExampleCallback();
366
+ }
367
+ });
368
+ },
369
+ (pError) =>
370
+ {
371
+ if (pError)
372
+ {
373
+ this.log.error(`Error building examples: ${pError.message}`);
374
+ }
375
+ else
376
+ {
377
+ this.log.info(`All examples built successfully!`);
378
+ }
379
+ return fCallback(pError);
380
+ });
381
+ }
382
+
383
+ // --- Serve examples ---
384
+
385
+ serveExamples(pExamplesFolder, pPort, fCallback)
386
+ {
387
+ let tmpExamplesFolder = libPath.resolve(pExamplesFolder || './example_applications');
388
+ let tmpProjectName = this.fable.AppData.Package.name || 'unknown-project';
389
+ let tmpPort = pPort || this.hashProjectNameToPort(tmpProjectName);
390
+
391
+ this.log.info(`Scanning for example applications in [${tmpExamplesFolder}] ...`);
392
+
393
+ let tmpExamples = this.gatherServableExamples(tmpExamplesFolder);
394
+
395
+ if (tmpExamples.length < 1)
396
+ {
397
+ this.log.warn(`No servable examples found in [${tmpExamplesFolder}]. Looking for subfolders with dist/index.html or index.html.`);
398
+ return fCallback();
399
+ }
400
+
401
+ this.log.info(`Found ${tmpExamples.length} servable example(s).`);
402
+
403
+ let tmpIndexHTML = this.generateIndexHTML(tmpProjectName, tmpExamples, tmpPort);
404
+
405
+ let tmpServer = libHTTP.createServer(
406
+ (pRequest, pResponse) =>
407
+ {
408
+ let tmpRequestURL = pRequest.url.split('?')[0];
409
+
410
+ if (tmpRequestURL === '/' || tmpRequestURL === '/index.html')
411
+ {
412
+ pResponse.writeHead(200, { 'Content-Type': 'text/html' });
413
+ pResponse.end(tmpIndexHTML);
414
+ return;
415
+ }
416
+
417
+ let tmpFilePath = libPath.join(tmpExamplesFolder, decodeURIComponent(tmpRequestURL));
418
+
419
+ if (!tmpFilePath.startsWith(tmpExamplesFolder))
420
+ {
421
+ pResponse.writeHead(403);
422
+ pResponse.end('Forbidden');
423
+ return;
424
+ }
425
+
426
+ if (libFS.existsSync(tmpFilePath) && libFS.statSync(tmpFilePath).isDirectory())
427
+ {
428
+ tmpFilePath = libPath.join(tmpFilePath, 'index.html');
429
+ }
430
+
431
+ if (!libFS.existsSync(tmpFilePath))
432
+ {
433
+ pResponse.writeHead(404);
434
+ pResponse.end('Not Found');
435
+ return;
436
+ }
437
+
438
+ let tmpExtension = libPath.extname(tmpFilePath).toLowerCase();
439
+ let tmpMimeType = this.getMimeType(tmpExtension);
440
+
441
+ try
442
+ {
443
+ let tmpContent = libFS.readFileSync(tmpFilePath);
444
+ pResponse.writeHead(200, { 'Content-Type': tmpMimeType });
445
+ pResponse.end(tmpContent);
446
+ }
447
+ catch (pError)
448
+ {
449
+ pResponse.writeHead(500);
450
+ pResponse.end('Internal Server Error');
451
+ }
452
+ });
453
+
454
+ tmpServer.listen(tmpPort,
455
+ () =>
456
+ {
457
+ this.log.info(`##############################################`);
458
+ this.log.info(` Example server running at http://localhost:${tmpPort}/`);
459
+ this.log.info(` Project: ${tmpProjectName}`);
460
+ this.log.info(` Serving ${tmpExamples.length} example(s):`);
461
+ for (let i = 0; i < tmpExamples.length; i++)
462
+ {
463
+ this.log.info(` - ${tmpExamples[i].DisplayName}: http://localhost:${tmpPort}/${tmpExamples[i].RelativePath}`);
464
+ }
465
+ this.log.info(`##############################################`);
466
+ this.log.info(`Press Ctrl+C to stop.`);
467
+ });
468
+
469
+ tmpServer.on('error',
470
+ (pError) =>
471
+ {
472
+ if (pError.code === 'EADDRINUSE')
473
+ {
474
+ this.log.error(`Port ${tmpPort} is already in use. Try specifying a different port with -p.`);
475
+ }
476
+ else
477
+ {
478
+ this.log.error(`Server error: ${pError.message}`);
479
+ }
480
+ return fCallback(pError);
481
+ });
482
+
483
+ // Keep the process running (don't call fCallback -- server is long-lived)
484
+ }
485
+ }
486
+
487
+ module.exports = QuackageExampleService;