pict-docuserve 1.4.1 → 1.4.3

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.4.1",
3
+ "version": "1.4.3",
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": {
@@ -14,9 +14,7 @@
14
14
  "scripts": {
15
15
  "start": "node source/cli/Docuserve-CLI-Run.js serve",
16
16
  "brand": "node node_modules/pict-section-theme/bin/pict-section-theme-brand.js --manifest ../../../Retold-Modules-Manifest.json --module pict-docuserve",
17
- "prebuild": "npm run brand",
18
17
  "build": "npx quack build && npx quack copy",
19
- "prebuild-docs": "npm run brand",
20
18
  "build-docs": "npx quack build && npx quack copy && node source/cli/Docuserve-CLI-Run.js inject ./docs && node example_applications/build-examples.js stage-docs",
21
19
  "serve-docs": "node source/cli/Docuserve-CLI-Run.js serve ./docs",
22
20
  "serve-examples": "node example_applications/build-examples.js",
@@ -34,7 +32,7 @@
34
32
  "pict-application": "^1.0.34",
35
33
  "pict-provider": "^1.0.13",
36
34
  "pict-section-code": "^1.0.11",
37
- "pict-section-content": "^1.0.4",
35
+ "pict-section-content": "^1.0.6",
38
36
  "pict-section-histogram": "^1.0.1",
39
37
  "pict-section-modal": "^1.1.4",
40
38
  "pict-section-theme": "^1.0.6",
@@ -42,8 +40,8 @@
42
40
  "pict-view": "^1.0.68"
43
41
  },
44
42
  "devDependencies": {
45
- "pict-docuserve": "^1.4.0",
46
- "quackage": "^1.2.4"
43
+ "pict-docuserve": "^1.4.2",
44
+ "quackage": "^1.2.5"
47
45
  },
48
46
  "copyFilesSettings": {
49
47
  "whenFileExists": "overwrite"
@@ -13,6 +13,7 @@ let _PictCLIProgram = new libCLIProgram(
13
13
  require('./commands/Docuserve-Command-Inject.js'),
14
14
  require('./commands/Docuserve-Command-PrepareLocal.js'),
15
15
  require('./commands/Docuserve-Command-StageExamples.js'),
16
+ require('./commands/Docuserve-Command-StagePlayground.js'),
16
17
  require('./commands/Docuserve-Command-CheckLinks.js')
17
18
  ]);
18
19
 
@@ -0,0 +1,279 @@
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
+ * `pict-docuserve stage-playground <docs>` — read `<docs>/_playground.json`
19
+ * and copy every `Imports[]` entry with `Source: "local"` from its resolved
20
+ * source bundle into `<docs>/<Path>`.
21
+ *
22
+ * This is the per-module half of the section-playground story: each module's
23
+ * `_playground.json` declares which UMD bundles its iframe needs, this
24
+ * command stages them into the docs tree so the iframe can `<script src=...>`
25
+ * them relative to the docs root. Without it, the bundles either have to be
26
+ * hand-copied (the original section-form workflow) or loaded from the CDN —
27
+ * which doesn't work for un-published in-development versions.
28
+ *
29
+ * Resolution order for each Import's source bundle:
30
+ * 1. `<moduleRoot>/node_modules/<Name>/dist/<Name>.min.js` — the normal
31
+ * case for peer dependencies.
32
+ * 2. `<moduleRoot>/dist/<Name>.min.js` — when the Import IS the module
33
+ * being staged (e.g. pict-section-form staging itself).
34
+ * 3. Sibling monorepo checkout — walks up looking for a `modules/`
35
+ * directory and searches `modules/<group>/<Name>/dist/<Name>.min.js`.
36
+ * Lets in-monorepo dev workflows pick up a freshly-built bundle even
37
+ * when the umbrella `node_modules/` is stale.
38
+ *
39
+ * Clean no-op when `_playground.json` is absent — safe to wire into
40
+ * `quack prepare-docs` for every module.
41
+ */
42
+ class DocuserveCommandStagePlayground extends libCommandLineCommand
43
+ {
44
+ constructor(pFable, pManifest, pServiceHash)
45
+ {
46
+ super(pFable, pManifest, pServiceHash);
47
+
48
+ this.options.CommandKeyword = 'stage-playground';
49
+ this.options.Description = 'Stage local Imports referenced by docs/_playground.json into the docs playground runtime folder.';
50
+
51
+ this.options.CommandArguments.push({ Name: '[docs_folder]', Description: 'The documentation folder to stage into. Defaults to ./docs.' });
52
+ this.options.CommandOptions.push({ Name: '-m, --module_root [module_root]', Description: 'Root of the module being staged (where node_modules lives). Defaults to the docs folder\'s parent.', Default: '' });
53
+
54
+ this.options.Aliases.push('stage-playground-runtime');
55
+
56
+ this.addCommand();
57
+ }
58
+
59
+ onRun()
60
+ {
61
+ let tmpDocsFolder = libPath.resolve(this.ArgumentString || './docs');
62
+ let tmpModuleRoot = libPath.resolve(this.CommandOptions.module_root || libPath.dirname(tmpDocsFolder));
63
+
64
+ let tmpPlaygroundConfigPath = libPath.join(tmpDocsFolder, '_playground.json');
65
+ if (!libFS.existsSync(tmpPlaygroundConfigPath))
66
+ {
67
+ this.log.info(`No _playground.json at [${tmpPlaygroundConfigPath}]; nothing to stage.`);
68
+ return;
69
+ }
70
+
71
+ let tmpConfig;
72
+ try
73
+ {
74
+ tmpConfig = JSON.parse(libFS.readFileSync(tmpPlaygroundConfigPath, 'utf8'));
75
+ }
76
+ catch (pError)
77
+ {
78
+ this.log.error(`Failed to parse _playground.json [${tmpPlaygroundConfigPath}]: ${pError.message}`);
79
+ process.exit(1);
80
+ return;
81
+ }
82
+
83
+ let tmpImports = (tmpConfig && Array.isArray(tmpConfig.Imports)) ? tmpConfig.Imports : [];
84
+ let tmpStylesheets = (tmpConfig && Array.isArray(tmpConfig.Stylesheets)) ? tmpConfig.Stylesheets : [];
85
+
86
+ // Staging targets — every Import / Stylesheet with Source: "local".
87
+ // Each entry produces { kind, name, source, dest } once resolved.
88
+ let tmpJobs = [];
89
+ for (let i = 0; i < tmpImports.length; i++)
90
+ {
91
+ let tmpImp = tmpImports[i];
92
+ if (!tmpImp || tmpImp.Source !== 'local') { continue; }
93
+ tmpJobs.push({ kind: 'script', spec: tmpImp });
94
+ }
95
+ for (let i = 0; i < tmpStylesheets.length; i++)
96
+ {
97
+ let tmpSheet = tmpStylesheets[i];
98
+ if (!tmpSheet || tmpSheet.Source !== 'local') { continue; }
99
+ tmpJobs.push({ kind: 'stylesheet', spec: tmpSheet });
100
+ }
101
+
102
+ if (tmpJobs.length === 0)
103
+ {
104
+ this.log.info(`No local Imports or Stylesheets declared in _playground.json; nothing to stage.`);
105
+ return;
106
+ }
107
+
108
+ this.log.info(`Staging ${tmpJobs.length} playground runtime asset(s) from module root [${tmpModuleRoot}]...`);
109
+
110
+ let tmpCopied = 0;
111
+ let tmpFailed = 0;
112
+ for (let i = 0; i < tmpJobs.length; i++)
113
+ {
114
+ let tmpJob = tmpJobs[i];
115
+ let tmpKind = tmpJob.kind;
116
+ let tmpSpec = tmpJob.spec;
117
+ let tmpName = tmpSpec.Name;
118
+ let tmpExt = (tmpKind === 'stylesheet') ? 'css' : 'js';
119
+
120
+ if (!tmpName && !tmpSpec.Path)
121
+ {
122
+ this.log.warn(`${tmpKind} #${i} has neither Name nor Path; skipping.`);
123
+ tmpFailed++;
124
+ continue;
125
+ }
126
+
127
+ let tmpRelativePath = tmpSpec.Path || `playground/runtime/${tmpName}.min.${tmpExt}`;
128
+ let tmpDest = libPath.join(tmpDocsFolder, tmpRelativePath);
129
+
130
+ let tmpSource = this._resolveAssetSource(tmpKind, tmpName, tmpSpec.Path, tmpModuleRoot);
131
+ if (!tmpSource)
132
+ {
133
+ this.log.warn(`[${tmpName || tmpRelativePath}] No ${tmpKind} source found; skipping. Build it (npx quack build) and re-run prepare-docs.`);
134
+ tmpFailed++;
135
+ continue;
136
+ }
137
+
138
+ try
139
+ {
140
+ ensureDir(libPath.dirname(tmpDest));
141
+ libFS.copyFileSync(tmpSource, tmpDest);
142
+ this.log.info(`Staged ${tmpKind} ${tmpName || tmpRelativePath}: ${tmpSource} -> ${tmpDest}`);
143
+ tmpCopied++;
144
+ }
145
+ catch (pError)
146
+ {
147
+ this.log.error(`[${tmpName || tmpRelativePath}] Copy failed: ${pError.message}`);
148
+ tmpFailed++;
149
+ }
150
+ }
151
+
152
+ this.log.info(`Playground staging complete: ${tmpCopied} copied, ${tmpFailed} failed.`);
153
+ if (tmpFailed > 0 && tmpCopied === 0)
154
+ {
155
+ // Hard failure only when nothing landed — a partial copy is a
156
+ // warning so prepare-docs can keep going.
157
+ process.exit(1);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Resolve the source asset (script bundle or stylesheet) for an Import
163
+ * or Stylesheet entry. Scripts look at `<pkg>/dist/<pkg>.min.js`;
164
+ * stylesheets look at `<pkg>/dist/<file>` for whatever Path was declared
165
+ * (since CSS filenames don't follow a single naming convention).
166
+ *
167
+ * @param {string} pKind - "script" or "stylesheet".
168
+ * @param {string} pName - Package name (may be empty for ad-hoc paths).
169
+ * @param {string} pSpecPath - Optional Path from the spec; for stylesheets
170
+ * we use its basename to find the source CSS.
171
+ * @param {string} pModuleRoot - Root of the module being staged.
172
+ * @returns {string|null} Absolute source path, or null if not found.
173
+ */
174
+ _resolveAssetSource(pKind, pName, pSpecPath, pModuleRoot)
175
+ {
176
+ if (pKind === 'script')
177
+ {
178
+ return this._resolveBundleSource(pName, pModuleRoot);
179
+ }
180
+ // Stylesheet — look under the package's dist (or root) for a matching
181
+ // CSS file. The spec's Path basename hints at what file to find.
182
+ let tmpBasename = pSpecPath ? libPath.basename(pSpecPath) : (pName ? pName + '.min.css' : null);
183
+ if (!tmpBasename) { return null; }
184
+
185
+ let tmpCandidates = [];
186
+ if (pName)
187
+ {
188
+ tmpCandidates.push(libPath.join(pModuleRoot, 'node_modules', pName, 'dist', tmpBasename));
189
+ tmpCandidates.push(libPath.join(pModuleRoot, 'node_modules', pName, tmpBasename));
190
+ }
191
+ // Also check the module root in case the CSS lives alongside source/
192
+ tmpCandidates.push(libPath.join(pModuleRoot, 'dist', tmpBasename));
193
+ tmpCandidates.push(libPath.join(pModuleRoot, tmpBasename));
194
+
195
+ for (let i = 0; i < tmpCandidates.length; i++)
196
+ {
197
+ if (libFS.existsSync(tmpCandidates[i]))
198
+ {
199
+ return tmpCandidates[i];
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+
205
+ /**
206
+ * Resolve the source UMD bundle for an Import by Name.
207
+ *
208
+ * @param {string} pName - Package name (e.g. "pict", "pict-section-form").
209
+ * @param {string} pModuleRoot - Root of the module being staged.
210
+ * @returns {string|null} Absolute path to the bundle, or null if not found.
211
+ */
212
+ _resolveBundleSource(pName, pModuleRoot)
213
+ {
214
+ let tmpCandidates = [];
215
+
216
+ // 1. Peer dependency: <moduleRoot>/node_modules/<Name>/dist/<Name>.min.js
217
+ tmpCandidates.push(libPath.join(pModuleRoot, 'node_modules', pName, 'dist', `${pName}.min.js`));
218
+
219
+ // 2. Module IS the package being staged: <moduleRoot>/dist/<Name>.min.js
220
+ try
221
+ {
222
+ let tmpPkgPath = libPath.join(pModuleRoot, 'package.json');
223
+ if (libFS.existsSync(tmpPkgPath))
224
+ {
225
+ let tmpPkg = JSON.parse(libFS.readFileSync(tmpPkgPath, 'utf8'));
226
+ if (tmpPkg && tmpPkg.name === pName)
227
+ {
228
+ tmpCandidates.push(libPath.join(pModuleRoot, 'dist', `${pName}.min.js`));
229
+ }
230
+ }
231
+ }
232
+ catch (pError)
233
+ {
234
+ // Non-fatal — fall through to other candidates.
235
+ }
236
+
237
+ // 3. Sibling monorepo checkout — walk up until we find a `modules/`
238
+ // directory, then search modules/<group>/<Name>/dist/<Name>.min.js.
239
+ let tmpUp = pModuleRoot;
240
+ for (let i = 0; i < 5; i++)
241
+ {
242
+ let tmpModulesDir = libPath.join(tmpUp, 'modules');
243
+ if (libFS.existsSync(tmpModulesDir) && libFS.statSync(tmpModulesDir).isDirectory())
244
+ {
245
+ try
246
+ {
247
+ let tmpGroups = libFS.readdirSync(tmpModulesDir);
248
+ for (let g = 0; g < tmpGroups.length; g++)
249
+ {
250
+ let tmpCandidate = libPath.join(tmpModulesDir, tmpGroups[g], pName, 'dist', `${pName}.min.js`);
251
+ tmpCandidates.push(tmpCandidate);
252
+ }
253
+ }
254
+ catch (pError)
255
+ {
256
+ // readdirSync can fail on permission errors; skip silently.
257
+ }
258
+ break;
259
+ }
260
+ let tmpNext = libPath.dirname(tmpUp);
261
+ if (tmpNext === tmpUp)
262
+ {
263
+ break;
264
+ }
265
+ tmpUp = tmpNext;
266
+ }
267
+
268
+ for (let i = 0; i < tmpCandidates.length; i++)
269
+ {
270
+ if (libFS.existsSync(tmpCandidates[i]))
271
+ {
272
+ return tmpCandidates[i];
273
+ }
274
+ }
275
+ return null;
276
+ }
277
+ }
278
+
279
+ module.exports = DocuserveCommandStagePlayground;
@@ -1488,7 +1488,7 @@ class DocuserveDocumentationProvider extends libPictProvider
1488
1488
  * corresponding #/doc/ route so the link navigates within docuserve
1489
1489
  * instead of leaving to GitHub.
1490
1490
  *
1491
- * @param {string} pURL - A GitHub URL (e.g. "https://github.com/stevenvelozo/fable")
1491
+ * @param {string} pURL - A GitHub URL (e.g. "https://github.com/fable-retold/fable")
1492
1492
  * @returns {string|null} The hash route (e.g. "#/doc/fable/fable") or null if not a catalog module
1493
1493
  */
1494
1494
  resolveGitHubURLToRoute(pURL)
@@ -1535,7 +1535,7 @@ class DocuserveDocumentationProvider extends libPictProvider
1535
1535
  /**
1536
1536
  * Resolve the GitHub Pages documentation URL for a module.
1537
1537
  *
1538
- * Returns a URL like https://stevenvelozo.github.io/pict-view/ if the
1538
+ * Returns a URL like https://fable-retold.github.io/pict-view/ if the
1539
1539
  * module exists in the catalog.
1540
1540
  *
1541
1541
  * @param {string} pGroup - The group key