pict-docuserve 1.3.3 → 1.3.4

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": "pict-docuserve",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Pict Documentation Server - A single-page documentation viewer built on Pict",
5
5
  "main": "source/Pict-Application-Docuserve.js",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "pict-application": "^1.0.34",
35
35
  "pict-provider": "^1.0.13",
36
36
  "pict-section-code": "^1.0.11",
37
- "pict-section-content": "^1.0.3",
37
+ "pict-section-content": "^1.0.4",
38
38
  "pict-section-histogram": "^1.0.1",
39
39
  "pict-section-modal": "^1.1.1",
40
40
  "pict-section-theme": "^1.0.5",
@@ -42,7 +42,7 @@
42
42
  "pict-view": "^1.0.68"
43
43
  },
44
44
  "devDependencies": {
45
- "pict-docuserve": "^1.3.2",
45
+ "pict-docuserve": "^1.3.3",
46
46
  "quackage": "^1.2.4"
47
47
  },
48
48
  "copyFilesSettings": {
@@ -583,13 +583,13 @@ class DocuserveApplication extends libPictApplication
583
583
  // Show the README on success, else the original
584
584
  // not-found page (it names the path the user asked for).
585
585
  tmpContentView.displayContent(pReadmeError ? pHTML : pReadmeHTML);
586
- });
586
+ }, '', '', tmpReadmePath);
587
587
  return;
588
588
  }
589
589
  // fetchDocument always provides displayable HTML in pHTML,
590
590
  // even on error, so we can use it directly.
591
591
  tmpContentView.displayContent(pHTML);
592
- });
592
+ }, '', '', tmpPath);
593
593
  }
594
594
 
595
595
  /**
@@ -12,7 +12,8 @@ let _PictCLIProgram = new libCLIProgram(
12
12
  require('./commands/Docuserve-Command-Serve.js'),
13
13
  require('./commands/Docuserve-Command-Inject.js'),
14
14
  require('./commands/Docuserve-Command-PrepareLocal.js'),
15
- require('./commands/Docuserve-Command-StageExamples.js')
15
+ require('./commands/Docuserve-Command-StageExamples.js'),
16
+ require('./commands/Docuserve-Command-CheckLinks.js')
16
17
  ]);
17
18
 
18
19
  module.exports = _PictCLIProgram;
@@ -0,0 +1,428 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+
3
+ const libFS = require('fs');
4
+ const libPath = require('path');
5
+
6
+ /**
7
+ * Recursively collect every .md file beneath a folder.
8
+ *
9
+ * @param {string} pRoot - The folder to walk.
10
+ * @returns {string[]} Absolute paths of every markdown file found, sorted.
11
+ */
12
+ function collectMarkdownFiles(pRoot)
13
+ {
14
+ let tmpResults = [];
15
+ let tmpStack = [pRoot];
16
+
17
+ while (tmpStack.length > 0)
18
+ {
19
+ let tmpDir = tmpStack.pop();
20
+ let tmpEntries;
21
+ try
22
+ {
23
+ tmpEntries = libFS.readdirSync(tmpDir, { withFileTypes: true });
24
+ }
25
+ catch (pError)
26
+ {
27
+ continue;
28
+ }
29
+
30
+ for (let i = 0; i < tmpEntries.length; i++)
31
+ {
32
+ let tmpEntry = tmpEntries[i];
33
+ let tmpFullPath = libPath.join(tmpDir, tmpEntry.name);
34
+ if (tmpEntry.isDirectory())
35
+ {
36
+ tmpStack.push(tmpFullPath);
37
+ }
38
+ else if (tmpEntry.isFile() && /\.md$/i.test(tmpEntry.name))
39
+ {
40
+ tmpResults.push(tmpFullPath);
41
+ }
42
+ }
43
+ }
44
+
45
+ tmpResults.sort();
46
+ return tmpResults;
47
+ }
48
+
49
+ /**
50
+ * True when a file exists on disk and is a regular file.
51
+ */
52
+ function fileExists(pPath)
53
+ {
54
+ try
55
+ {
56
+ return libFS.statSync(pPath).isFile();
57
+ }
58
+ catch (pError)
59
+ {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * True when a path exists on disk and is a directory.
66
+ */
67
+ function directoryExists(pPath)
68
+ {
69
+ try
70
+ {
71
+ return libFS.statSync(pPath).isDirectory();
72
+ }
73
+ catch (pError)
74
+ {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Resolve a relative href against a base directory, collapsing "." and ".."
81
+ * segments. ".." is clamped at the docs root. Mirrors docuserve module-mode
82
+ * resolution (Pict-Provider-Docuserve-Documentation._resolveRelativeDocPath)
83
+ * so the checker resolves .md links exactly the way the viewer routes them.
84
+ *
85
+ * @param {string} pBaseDir - The directory the href is relative to (POSIX, docs-root-relative).
86
+ * @param {string} pHref - The href to resolve.
87
+ * @returns {string} The resolved docs-root-relative path (no leading slash).
88
+ */
89
+ function resolveRelativeDocPath(pBaseDir, pHref)
90
+ {
91
+ let tmpSegments = [];
92
+ let tmpCombined = (pBaseDir ? pBaseDir + '/' : '') + String(pHref || '');
93
+ let tmpParts = tmpCombined.split('/');
94
+ for (let i = 0; i < tmpParts.length; i++)
95
+ {
96
+ let tmpPart = tmpParts[i];
97
+ if ((tmpPart === '') || (tmpPart === '.'))
98
+ {
99
+ continue;
100
+ }
101
+ if (tmpPart === '..')
102
+ {
103
+ if (tmpSegments.length > 0)
104
+ {
105
+ tmpSegments.pop();
106
+ }
107
+ continue;
108
+ }
109
+ tmpSegments.push(tmpPart);
110
+ }
111
+ return tmpSegments.join('/');
112
+ }
113
+
114
+ /**
115
+ * True when a link target points outside the docs site and must never be
116
+ * resolved against the filesystem: an absolute or protocol-relative URL, a
117
+ * scheme such as mailto:/data:/tel:, or a pure #anchor.
118
+ */
119
+ function isExternalOrAnchor(pTarget)
120
+ {
121
+ if (!pTarget)
122
+ {
123
+ return true;
124
+ }
125
+ if (pTarget.charAt(0) === '#')
126
+ {
127
+ return true;
128
+ }
129
+ if (pTarget.indexOf('//') === 0)
130
+ {
131
+ return true;
132
+ }
133
+ if (/^[a-z][a-z0-9+.-]*:/i.test(pTarget))
134
+ {
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+
140
+ /**
141
+ * Normalize a raw link target extracted from markdown: drop a surrounding
142
+ * <...>, drop a link title (` "Title"`), drop a #fragment / ?query, and
143
+ * decode the %5F underscore escape that docuserve's mdSafeUrl introduces.
144
+ *
145
+ * @param {string} pRawTarget - The target captured between the link parens.
146
+ * @returns {string} The cleaned target, ready for filesystem resolution.
147
+ */
148
+ function cleanTarget(pRawTarget)
149
+ {
150
+ let tmpTarget = String(pRawTarget || '').trim();
151
+
152
+ // Drop an angle-bracket wrapper: [text](<url>) -> url
153
+ tmpTarget = tmpTarget.replace(/^<+/, '').replace(/>+$/, '');
154
+
155
+ // Drop a link title: (url "Title") -> url
156
+ let tmpSpaceIndex = tmpTarget.search(/\s/);
157
+ if (tmpSpaceIndex >= 0)
158
+ {
159
+ tmpTarget = tmpTarget.substring(0, tmpSpaceIndex);
160
+ }
161
+
162
+ // Drop a #fragment and/or ?query
163
+ tmpTarget = tmpTarget.split('#')[0].split('?')[0];
164
+
165
+ // docuserve's mdSafeUrl percent-encodes underscores; the HTTP layer
166
+ // decodes them back on fetch, so resolve against the decoded path.
167
+ tmpTarget = tmpTarget.replace(/%5[fF]/g, '_');
168
+
169
+ return tmpTarget.trim();
170
+ }
171
+
172
+ class DocuserveCommandCheckLinks extends libCommandLineCommand
173
+ {
174
+ constructor(pFable, pManifest, pServiceHash)
175
+ {
176
+ super(pFable, pManifest, pServiceHash);
177
+
178
+ this.options.CommandKeyword = 'check-links';
179
+ this.options.Description = 'Scan a docs folder for unresolvable local links and image/media references.';
180
+
181
+ this.options.CommandArguments.push({ Name: '[docs-path]', Description: 'Documentation folder to check (defaults to ./docs/).' });
182
+
183
+ this.addCommand();
184
+ }
185
+
186
+ onRun()
187
+ {
188
+ let tmpDocsRoot = libPath.resolve(this.ArgumentString || './docs/');
189
+
190
+ if (!directoryExists(tmpDocsRoot))
191
+ {
192
+ this.log.error(`Docs folder not found at [${tmpDocsRoot}].`);
193
+ process.exit(1);
194
+ return;
195
+ }
196
+
197
+ this.log.info(`Checking local links + media references under [${tmpDocsRoot}]...`);
198
+
199
+ let tmpFiles = collectMarkdownFiles(tmpDocsRoot);
200
+ if (tmpFiles.length < 1)
201
+ {
202
+ this.log.info(`No markdown files found; nothing to check.`);
203
+ return;
204
+ }
205
+
206
+ let tmpLinkCount = 0;
207
+ let tmpImageCount = 0;
208
+ let tmpBroken = [];
209
+
210
+ for (let f = 0; f < tmpFiles.length; f++)
211
+ {
212
+ let tmpFilePath = tmpFiles[f];
213
+ let tmpContent;
214
+ try
215
+ {
216
+ tmpContent = libFS.readFileSync(tmpFilePath, 'utf8');
217
+ }
218
+ catch (pError)
219
+ {
220
+ this.log.warn(`Could not read [${tmpFilePath}]: ${pError.message}`);
221
+ continue;
222
+ }
223
+
224
+ let tmpReferences = this._extractReferences(tmpContent);
225
+ for (let r = 0; r < tmpReferences.length; r++)
226
+ {
227
+ let tmpReference = tmpReferences[r];
228
+
229
+ if (tmpReference.IsImage)
230
+ {
231
+ tmpImageCount++;
232
+ }
233
+ else
234
+ {
235
+ tmpLinkCount++;
236
+ }
237
+
238
+ let tmpTarget = cleanTarget(tmpReference.Target);
239
+ if (isExternalOrAnchor(tmpTarget) || (tmpTarget === ''))
240
+ {
241
+ continue;
242
+ }
243
+
244
+ let tmpResolution = tmpReference.IsImage
245
+ ? this._resolveImageReference(tmpTarget, tmpFilePath, tmpDocsRoot)
246
+ : this._resolveDocumentLink(tmpTarget, tmpFilePath, tmpDocsRoot);
247
+
248
+ if (!tmpResolution.OK)
249
+ {
250
+ tmpBroken.push(
251
+ {
252
+ File: libPath.relative(tmpDocsRoot, tmpFilePath),
253
+ Line: tmpReference.Line,
254
+ Snippet: tmpReference.Snippet,
255
+ Reason: tmpResolution.Reason
256
+ });
257
+ }
258
+ }
259
+ }
260
+
261
+ this.log.info(`Checked ${tmpLinkCount} link(s) + ${tmpImageCount} image reference(s) across ${tmpFiles.length} file(s).`);
262
+
263
+ if (tmpBroken.length > 0)
264
+ {
265
+ this.log.error(`Found ${tmpBroken.length} broken reference(s):`);
266
+ for (let i = 0; i < tmpBroken.length; i++)
267
+ {
268
+ let tmpEntry = tmpBroken[i];
269
+ this.log.error(` ${tmpEntry.File}:${tmpEntry.Line} ${tmpEntry.Snippet} -> ${tmpEntry.Reason}`);
270
+ }
271
+ process.exit(1);
272
+ return;
273
+ }
274
+
275
+ this.log.info(`All local links + media references resolve.`);
276
+ }
277
+
278
+ /**
279
+ * Extract every inline link and image reference from a markdown document.
280
+ *
281
+ * Fenced code blocks and inline code spans are skipped so that example
282
+ * code containing `[text](target)` is never treated as a real link.
283
+ *
284
+ * @param {string} pContent - The raw markdown text.
285
+ * @returns {object[]} { Target, IsImage, Line, Snippet } per reference.
286
+ */
287
+ _extractReferences(pContent)
288
+ {
289
+ let tmpReferences = [];
290
+ let tmpLines = String(pContent).split(/\r?\n/);
291
+ let tmpInFence = false;
292
+
293
+ for (let i = 0; i < tmpLines.length; i++)
294
+ {
295
+ let tmpLine = tmpLines[i];
296
+
297
+ // Toggle on fenced code block boundaries (backtick fences, to
298
+ // match the content provider's markdown parser).
299
+ if (/^\s*`{3,}/.test(tmpLine))
300
+ {
301
+ tmpInFence = !tmpInFence;
302
+ continue;
303
+ }
304
+ if (tmpInFence)
305
+ {
306
+ continue;
307
+ }
308
+
309
+ // Drop inline code spans so `[x](y)` in backticks is not a link.
310
+ let tmpScrubbed = tmpLine.replace(/`[^`]*`/g, ' ');
311
+
312
+ // Images: ![alt](target)
313
+ let tmpImageRegEx = /!\[[^\]]*\]\(([^)]+)\)/g;
314
+ let tmpImageMatch;
315
+ while ((tmpImageMatch = tmpImageRegEx.exec(tmpScrubbed)) !== null)
316
+ {
317
+ tmpReferences.push(
318
+ {
319
+ Target: tmpImageMatch[1],
320
+ IsImage: true,
321
+ Line: i + 1,
322
+ Snippet: tmpImageMatch[0]
323
+ });
324
+ }
325
+
326
+ // Links: [text](target) — the [^!] guard excludes image links.
327
+ let tmpLinkRegEx = /(^|[^!])(\[[^\]]*\]\(([^)]+)\))/g;
328
+ let tmpLinkMatch;
329
+ while ((tmpLinkMatch = tmpLinkRegEx.exec(tmpScrubbed)) !== null)
330
+ {
331
+ tmpReferences.push(
332
+ {
333
+ Target: tmpLinkMatch[3],
334
+ IsImage: false,
335
+ Line: i + 1,
336
+ Snippet: tmpLinkMatch[2]
337
+ });
338
+ }
339
+ }
340
+
341
+ return tmpReferences;
342
+ }
343
+
344
+ /**
345
+ * Resolve a documentation link the way docuserve module-mode renders it,
346
+ * and report whether the target exists on disk.
347
+ *
348
+ * Every relative link — a .md page, a .html app, a directory, a media
349
+ * file — is resolved against the directory of the document that contains
350
+ * it, with ../ clamped at the docs root. A /-rooted link is resolved
351
+ * against the docs root.
352
+ *
353
+ * @param {string} pTarget - The cleaned link target.
354
+ * @param {string} pCurrentFile - Absolute path of the file the link is in.
355
+ * @param {string} pDocsRoot - The absolute docs root folder.
356
+ * @returns {object} { OK, Reason }
357
+ */
358
+ _resolveDocumentLink(pTarget, pCurrentFile, pDocsRoot)
359
+ {
360
+ let tmpBaseDir = '';
361
+ let tmpHref = pTarget;
362
+ if (pTarget.charAt(0) === '/')
363
+ {
364
+ tmpHref = pTarget.replace(/^\/+/, '');
365
+ }
366
+ else
367
+ {
368
+ tmpBaseDir = libPath.relative(pDocsRoot, libPath.dirname(pCurrentFile)).split(libPath.sep).join('/');
369
+ }
370
+ let tmpResolved = libPath.join(pDocsRoot, resolveRelativeDocPath(tmpBaseDir, tmpHref));
371
+
372
+ if (fileExists(tmpResolved) || directoryExists(tmpResolved))
373
+ {
374
+ return { OK: true };
375
+ }
376
+
377
+ // An extensionless target may be a directory or an extensionless page
378
+ // route — docuserve's page route also tries <target>.md and
379
+ // <target>/README.md, so accept those too.
380
+ let tmpBareTarget = pTarget.replace(/[#?].*$/, '').replace(/\/+$/, '');
381
+ if (/\.md$/i.test(pTarget) || !/\.[a-z0-9]+$/i.test(tmpBareTarget))
382
+ {
383
+ if (fileExists(tmpResolved + '.md') || fileExists(libPath.join(tmpResolved, 'README.md')))
384
+ {
385
+ return { OK: true };
386
+ }
387
+ }
388
+
389
+ return { OK: false, Reason: `no file at ${libPath.relative(pDocsRoot, tmpResolved) || '.'}` };
390
+ }
391
+
392
+ /**
393
+ * Resolve an image/media reference the way docuserve's image resolver does
394
+ * — relative to the containing document's directory, or docs-root-relative
395
+ * when the target is /-rooted — and report whether it exists on disk.
396
+ *
397
+ * @param {string} pTarget - The cleaned image target.
398
+ * @param {string} pCurrentFile - Absolute path of the file the ref is in.
399
+ * @param {string} pDocsRoot - The absolute docs root folder.
400
+ * @returns {object} { OK, Reason }
401
+ */
402
+ _resolveImageReference(pTarget, pCurrentFile, pDocsRoot)
403
+ {
404
+ let tmpResolved;
405
+ if (pTarget.charAt(0) === '/')
406
+ {
407
+ tmpResolved = libPath.normalize(libPath.join(pDocsRoot, pTarget.replace(/^\/+/, '')));
408
+ }
409
+ else
410
+ {
411
+ tmpResolved = libPath.normalize(libPath.join(libPath.dirname(pCurrentFile), pTarget));
412
+ }
413
+
414
+ if ((tmpResolved !== pDocsRoot) && (tmpResolved.indexOf(pDocsRoot + libPath.sep) !== 0))
415
+ {
416
+ return { OK: false, Reason: 'resolves outside the docs root' };
417
+ }
418
+
419
+ if (fileExists(tmpResolved))
420
+ {
421
+ return { OK: true };
422
+ }
423
+
424
+ return { OK: false, Reason: `no file at ${libPath.relative(pDocsRoot, tmpResolved) || '.'}` };
425
+ }
426
+ }
427
+
428
+ module.exports = DocuserveCommandCheckLinks;
@@ -275,7 +275,7 @@ class DocuserveCommandStageExamples extends libCommandLineCommand
275
275
  return;
276
276
  }
277
277
 
278
- // Regenerate the marker-delimited index + intro quick-links.
278
+ // Regenerate the marker-delimited examples index + the splash region.
279
279
  try
280
280
  {
281
281
  this._writeExamplesIndex(tmpStaged, tmpDocsFolder);
@@ -287,11 +287,11 @@ class DocuserveCommandStageExamples extends libCommandLineCommand
287
287
 
288
288
  try
289
289
  {
290
- this._writeIntroQuickLinks(tmpStaged, tmpDocsFolder);
290
+ this._writeCoverExamples(tmpStaged, tmpDocsFolder);
291
291
  }
292
292
  catch (pError)
293
293
  {
294
- this.log.warn(`Could not update intro quick-links: ${pError.message}`);
294
+ this.log.warn(`Could not update splash examples region: ${pError.message}`);
295
295
  }
296
296
 
297
297
  this.log.info(`Staged ${tmpStaged.length} example application(s): ${tmpStaged.map((pEx) => pEx.Name).join(', ')}`);
@@ -492,7 +492,10 @@ class DocuserveCommandStageExamples extends libCommandLineCommand
492
492
  let tmpTitle = pExample.Flag.Title || tmpName;
493
493
  let tmpSummary = pExample.Flag.Summary || pExample.Package.description || '';
494
494
 
495
- let tmpInner = `> **[&#9654; Launch the live app](${mdSafeUrl('examples/' + tmpName + '/index.html')})** — runs in your browser, opens in a new tab.`;
495
+ // The writeup lives at docs/examples/<name>/README.md, so index.html
496
+ // is its sibling — a document-relative href docuserve resolves the
497
+ // same way it resolves the writeup's other links.
498
+ let tmpInner = `> **[&#9654; Launch the live app](index.html)** — runs in your browser, opens in a new tab.`;
496
499
 
497
500
  let tmpText;
498
501
  if (libFS.existsSync(tmpReadmePath))
@@ -536,7 +539,9 @@ class DocuserveCommandStageExamples extends libCommandLineCommand
536
539
  let tmpRows = pStaged.map((pEx) =>
537
540
  {
538
541
  let tmpComplexity = escapeTableCell(pEx.Complexity) || '—';
539
- return `| [${escapeTableCell(pEx.Title)}](${mdSafeUrl('examples/' + pEx.Name + '/README.md')}) | ${tmpComplexity} | ${escapeTableCell(pEx.Summary)} | [&#9654; Launch](${mdSafeUrl('examples/' + pEx.Name + '/index.html')}) |`;
542
+ // Both links are relative to this index (docs/examples/README.md),
543
+ // so they omit the examples/ segment.
544
+ return `| [${escapeTableCell(pEx.Title)}](${mdSafeUrl(pEx.Name + '/README.md')}) | ${tmpComplexity} | ${escapeTableCell(pEx.Summary)} | [&#9654; Launch](${mdSafeUrl(pEx.Name + '/index.html')}) |`;
540
545
  });
541
546
  let tmpInner = [
542
547
  '| Example | Complexity | Summary | Live |',
@@ -558,43 +563,43 @@ class DocuserveCommandStageExamples extends libCommandLineCommand
558
563
  }
559
564
 
560
565
  /**
561
- * Maintain the generated "Example Applications" quick-links block on the
562
- * documentation intro page at docs/README.md.
566
+ * Maintain the generated examples region in docs/_cover.md. docuserve's
567
+ * Splash view renders this region as the "Interactive Examples" section of
568
+ * the landing page. When _cover.md is absent the region is not created —
569
+ * the splash simply shows no examples section.
563
570
  *
564
571
  * @param {object[]} pStaged - Staged example metadata.
565
572
  * @param {string} pDocsFolder - The target documentation folder.
566
573
  */
567
- _writeIntroQuickLinks(pStaged, pDocsFolder)
574
+ _writeCoverExamples(pStaged, pDocsFolder)
568
575
  {
569
- let tmpReadmePath = libPath.join(pDocsFolder, 'README.md');
576
+ let tmpCoverPath = libPath.join(pDocsFolder, '_cover.md');
577
+ if (!libFS.existsSync(tmpCoverPath))
578
+ {
579
+ this.log.info(`No _cover.md at [${tmpCoverPath}]; skipping splash examples region.`);
580
+ return;
581
+ }
570
582
 
571
- let tmpBullets = pStaged.map((pEx) =>
583
+ let tmpRows = pStaged.map((pEx) =>
572
584
  {
573
- let tmpSummary = String(pEx.Summary || '').replace(/\r?\n/g, ' ').trim();
574
- return `- **[${pEx.Title}](${mdSafeUrl('examples/' + pEx.Name + '/README.md')})** ${tmpSummary} · [&#9654; Launch live app](${mdSafeUrl('examples/' + pEx.Name + '/index.html')})`;
585
+ let tmpComplexity = escapeTableCell(pEx.Complexity) || '';
586
+ return `| [${escapeTableCell(pEx.Title)}](${mdSafeUrl('examples/' + pEx.Name + '/README.md')}) | ${tmpComplexity} | [&#9654; Launch](${mdSafeUrl('examples/' + pEx.Name + '/index.html')}) |`;
575
587
  });
576
- let tmpInner = '*Live, runnable example applications — each opens in a new browser tab:*\n\n' + tmpBullets.join('\n');
588
+ let tmpInner = [
589
+ '| Example | Complexity | Launch |',
590
+ '|---------|------------|--------|'
591
+ ].concat(tmpRows).join('\n');
577
592
 
578
- let tmpText = libFS.existsSync(tmpReadmePath)
579
- ? libFS.readFileSync(tmpReadmePath, 'utf8')
580
- : '# Documentation\n';
593
+ let tmpText = libFS.readFileSync(tmpCoverPath, 'utf8');
581
594
 
582
595
  tmpText = replaceRegion(tmpText, _Marker.QuickLinksStart, _Marker.QuickLinksEnd, tmpInner,
583
596
  (pBody, pRegion) =>
584
597
  {
585
- // Prefer to place the block directly under an existing
586
- // "Example Applications" heading.
587
- let tmpHeading = pBody.match(/^#{2,}\s+Example Applications\s*$/m);
588
- if (tmpHeading)
589
- {
590
- let tmpIndex = pBody.indexOf(tmpHeading[0]) + tmpHeading[0].length;
591
- return pBody.substring(0, tmpIndex) + '\n\n' + pRegion + '\n' + pBody.substring(tmpIndex);
592
- }
593
- // Otherwise append a new section at the end of the page.
594
- return pBody.replace(/\s*$/, '') + '\n\n## Example Applications\n\n' + pRegion + '\n';
598
+ // No markers yet — append the region at the end of the cover.
599
+ return pBody.replace(/\s*$/, '') + '\n\n' + pRegion + '\n';
595
600
  });
596
601
 
597
- libFS.writeFileSync(tmpReadmePath, tmpText);
602
+ libFS.writeFileSync(tmpCoverPath, tmpText);
598
603
  }
599
604
  }
600
605