@forsakringskassan/docs-generator 2.11.4 → 2.13.0

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/README.md CHANGED
@@ -11,10 +11,17 @@ npm install --save-dev --save-exact @forsakringskassan/docs-generator
11
11
  You can start a development server with watch for incremental builds using `.serve()`.
12
12
 
13
13
  ```ts
14
+ import {
15
+ Generator,
16
+ livereloadProcessor,
17
+ } from "@forsakringskassan/docs-generator";
18
+
14
19
  const docs = new Generator({
15
- /* ... */
20
+ site: {
21
+ name: "My Awesome Site",
22
+ },
23
+ setupPath: "docs/src/setup.ts",
16
24
  processors: [livereloadProcessor({ enabled: true })],
17
- /* ... */
18
25
  });
19
26
 
20
27
  docs.build([
@@ -29,8 +36,17 @@ docs.serve();
29
36
  Vendor alias is optional, it lets you substitute one package for another when bundling. The example below substitutes the package `vue` with the package `vue/dist/esm.bundle.js`. When an alias is given the package can be imported using the given alias as well as the original package name. For instance, when using the following configuration "vue" can be imported and resolved as "vue/dist/esm.bundle.js".
30
37
 
31
38
  ```ts
39
+ import {
40
+ Generator,
41
+ livereloadProcessor,
42
+ } from "@forsakringskassan/docs-generator";
43
+
32
44
  const docs = new Generator({
33
- /* ... */
45
+ site: {
46
+ name: "My Awesome Site",
47
+ },
48
+ setupPath: "docs/src/setup.ts",
49
+
34
50
  vendor: [
35
51
  {
36
52
  package: "vue",
@@ -39,7 +55,6 @@ const docs = new Generator({
39
55
  alias: "vue/dist/esm.bundle.js",
40
56
  },
41
57
  ],
42
- /* ... */
43
58
  });
44
59
  ```
45
60
 
@@ -55,7 +70,7 @@ npm install --save-dev --save-exact @vue/component-compiler
55
70
 
56
71
  Create the `setup` callback in `docs/src/setup.ts`:
57
72
 
58
- ```ts
73
+ ```ts nocompile
59
74
  import Vue from "vue";
60
75
  import { type SetupOptions } from "@forsakringskassan/docs-generator";
61
76
 
@@ -73,7 +88,7 @@ export function setup(options: SetupOptions): void {
73
88
 
74
89
  Create the `setup` callback in `docs/src/setup.ts`:
75
90
 
76
- ```ts
91
+ ```ts nocompile
77
92
  import { createApp } from "vue";
78
93
  import { type SetupOptions } from "@forsakringskassan/docs-generator";
79
94
 
@@ -102,8 +117,13 @@ Three CSS files are provided:
102
117
  Style can be compiled with `compileStyle(name, src, [options])`:
103
118
 
104
119
  ```ts
120
+ import { Generator } from "@forsakringskassan/docs-generator";
121
+
105
122
  const docs = new Generator({
106
- /* ... */
123
+ site: {
124
+ name: "My Awesome Site",
125
+ },
126
+ setupPath: "docs/src/setup.ts",
107
127
  });
108
128
 
109
129
  docs.compileStyle("docs", "./docs/src/style.scss", {
@@ -274,7 +294,7 @@ Defaults to `@fkui/icon-lib-default`.
274
294
  Makes it possible to dynamically load icons and get access to icon metadata.
275
295
  The library must have been built using `@fkui/icon-lib-builder`.
276
296
 
277
- ```js
297
+ ```js nocompile
278
298
  const icons = await import(process.env.DOCS_ICON_LIB);
279
299
 
280
300
  for (const entry of Object.values(icons)) {
@@ -3,10 +3,10 @@
3
3
  var path$1 = require('node:path');
4
4
  var esbuild = require('esbuild');
5
5
  var vue = require('vue');
6
- var vue3 = require('./vue3-CGUitvvB.js');
6
+ var vue3 = require('./vue3-BqgRpcI0.js');
7
7
  var path = require('node:path/posix');
8
8
  require('@vue/compiler-sfc');
9
- require('./vendor-WsR_tA03.js');
9
+ require('./vendor-Bb38Rlio.js');
10
10
  require('node:url');
11
11
  require('fs');
12
12
  require('node:fs');
@@ -14,6 +14,7 @@ require('node:fs/promises');
14
14
  require('node:events');
15
15
  require('node:stream');
16
16
  require('node:string_decoder');
17
+ require('typescript');
17
18
  require('constants');
18
19
  require('stream');
19
20
  require('util');
@@ -25,7 +26,6 @@ require('node:process');
25
26
  require('node:util');
26
27
  require('node:child_process');
27
28
  require('node:stream/promises');
28
- require('typescript');
29
29
  require('fs/promises');
30
30
  require('os');
31
31
  require('http');
@@ -1,11 +1,14 @@
1
1
  'use strict';
2
2
 
3
- var vendor = require('./vendor-WsR_tA03.js');
3
+ var vendor = require('./vendor-Bb38Rlio.js');
4
4
  var path = require('node:path/posix');
5
5
  var path$1 = require('node:path');
6
6
  require('crypto');
7
7
  var crypto = require('node:crypto');
8
8
  require('node:child_process');
9
+ var fs = require('node:fs');
10
+ var vue = require('vue');
11
+ var vue3 = require('./vue3-BqgRpcI0.js');
9
12
 
10
13
  function findDocument(haystack, needle) {
11
14
  const match = haystack.find((it) => {
@@ -65,6 +68,226 @@ function getOutputFilePath(outputFolder, fileInfo) {
65
68
  return normalizePath(outputFolder, path, outputName);
66
69
  }
67
70
 
71
+ function parseImport(raw) {
72
+ const comments = [];
73
+ const stripped = raw.replace(/<!--.*?-->/gms, (match) => {
74
+ comments.push(match);
75
+ return "";
76
+ });
77
+ const filename = stripped.trim();
78
+ const extension = path$1.parse(filename).ext.slice(1);
79
+ return {
80
+ filename,
81
+ extension,
82
+ comments
83
+ };
84
+ }
85
+
86
+ function getExampleImport(searchDirs, filename) {
87
+ const pattern = searchDirs.map((dir) => `${dir}/**/${filename}`);
88
+ const matches = vendor.globSync(pattern);
89
+ if (matches.length === 0) {
90
+ const message = `No files matched import "${filename}"`;
91
+ throw new Error(message);
92
+ } else if (matches.length > 1) {
93
+ const message = `Multiple files matched import "${filename}"`;
94
+ throw new Error(message);
95
+ } else {
96
+ return normalizePath(matches[0]);
97
+ }
98
+ }
99
+
100
+ function getExampleName(filename) {
101
+ return path$1.parse(filename).name;
102
+ }
103
+
104
+ function removeLinesInline(lines, { begin, end }) {
105
+ for (let i = begin; i <= end; i++) {
106
+ lines[i] = "";
107
+ }
108
+ if (lines[begin - 1] === "\n") {
109
+ lines[begin - 1] = "";
110
+ begin--;
111
+ }
112
+ if (lines[end + 1] === "\n") {
113
+ lines[end + 1] = "";
114
+ }
115
+ if (end === lines.length - 1 && begin > 0) {
116
+ lines[begin - 1] = lines[begin - 1].slice(0, -1);
117
+ }
118
+ }
119
+ function cutSnippets(code) {
120
+ const lines = code.split(/\n/).map((it, index, lines2) => index < lines2.length - 1 ? `${it}
121
+ ` : it);
122
+ const directive = /\/\* +--- +cut (above|below|begin|end) +--- +\*\//;
123
+ let buffer = -1;
124
+ lines.forEach((line, index) => {
125
+ const match = line.match(directive);
126
+ const [, instruction] = match ?? ["", null];
127
+ if (!instruction) {
128
+ return;
129
+ }
130
+ switch (instruction) {
131
+ /* --- cut above --- */
132
+ case "above":
133
+ removeLinesInline(lines, {
134
+ begin: 0,
135
+ end: index
136
+ });
137
+ break;
138
+ /* --- cut below --- */
139
+ case "below":
140
+ removeLinesInline(lines, {
141
+ begin: index,
142
+ end: lines.length - 1
143
+ });
144
+ break;
145
+ /* --- cut begin --- */
146
+ case "begin":
147
+ if (buffer === -1) {
148
+ buffer = index;
149
+ }
150
+ break;
151
+ /* --- cut end --- */
152
+ case "end":
153
+ removeLinesInline(lines, {
154
+ begin: Math.max(buffer, 0),
155
+ end: index
156
+ });
157
+ buffer = -1;
158
+ break;
159
+ }
160
+ });
161
+ if (buffer >= 0) {
162
+ removeLinesInline(lines, {
163
+ begin: buffer,
164
+ end: lines.length - 1
165
+ });
166
+ }
167
+ return lines.join("");
168
+ }
169
+ function stripEslintComments(code) {
170
+ const matchLine = /^[ \t]*(\/\* eslint-disable[^*]*\*\/|\/\/ eslint-disable.*)\n/gm;
171
+ const matchEmbedded = /[ \t]*(\/\* eslint-disable[^*]*\*\/|\/\/ eslint-disable.*)/g;
172
+ return code.replace(matchLine, "").replace(matchEmbedded, "");
173
+ }
174
+ const transformations = {
175
+ javascript: [cutSnippets, stripEslintComments],
176
+ typescript: [cutSnippets, stripEslintComments]
177
+ };
178
+ function transformCode(code, lang) {
179
+ const fns = transformations[lang] ?? [];
180
+ for (const fn of fns) {
181
+ code = fn(code);
182
+ }
183
+ return code;
184
+ }
185
+
186
+ const vueMajor = parseInt(vue.version.split(".", 2)[0], 10);
187
+ function vueGenerator() {
188
+ switch (vueMajor) {
189
+ case 2:
190
+ throw new Error(
191
+ "Vue 2 is no longer supported, upgrade to Vue 3 or downgrade docs-generator"
192
+ );
193
+ case 3:
194
+ return vue3.generateCode;
195
+ }
196
+ }
197
+ function generateExample(options) {
198
+ const { language } = options;
199
+ if (language === "import") {
200
+ const parsed = parseImport(options.source);
201
+ const filename = getExampleImport(
202
+ options.exampleFolders,
203
+ parsed.filename
204
+ );
205
+ const language2 = parsed.extension;
206
+ const comments = parsed.comments;
207
+ const content = fs.readFileSync(filename, "utf-8");
208
+ const source = transformCode(content, language2);
209
+ const example = generateExample({
210
+ ...options,
211
+ source,
212
+ language: language2,
213
+ filename
214
+ });
215
+ return { ...example, comments };
216
+ }
217
+ switch (language) {
218
+ case "vue":
219
+ return generateVueExample(options);
220
+ case "html":
221
+ return generateStaticExample(options);
222
+ default:
223
+ return generateStaticExample(options);
224
+ }
225
+ }
226
+ function generateVueExample(options) {
227
+ const { filename, source, parent, setupPath, tags } = options;
228
+ const fn = vueGenerator();
229
+ const slug = getExampleName(filename);
230
+ const fingerprint = getFingerprint(source);
231
+ const { markup, sourcecode, output } = fn({
232
+ filename,
233
+ slug,
234
+ fingerprint,
235
+ code: source,
236
+ setupPath
237
+ });
238
+ return {
239
+ source,
240
+ language: options.language,
241
+ comments: [],
242
+ tags,
243
+ markup,
244
+ output,
245
+ runtime: true,
246
+ task: {
247
+ outputFile: output,
248
+ sourcecode,
249
+ sourceFile: filename,
250
+ parent
251
+ }
252
+ };
253
+ }
254
+ function generateStaticExample(options) {
255
+ const { filename, source, language, tags } = options;
256
+ const slug = getExampleName(filename);
257
+ const fingerprint = getFingerprint(source);
258
+ const asset = `${slug}-${fingerprint}.${language}`;
259
+ const runtimeLanguages = ["html"];
260
+ const runtime = runtimeLanguages.includes(language);
261
+ return {
262
+ source,
263
+ language: options.language,
264
+ comments: [],
265
+ tags,
266
+ markup: source,
267
+ output: runtime ? asset : null,
268
+ runtime,
269
+ task: null
270
+ };
271
+ }
272
+
273
+ function normalizeLanguage(lang) {
274
+ switch (lang) {
275
+ case "ts":
276
+ return "typescript";
277
+ case "js":
278
+ return "javascript";
279
+ default:
280
+ return lang;
281
+ }
282
+ }
283
+ function parseInfostring(infostring) {
284
+ const [rawLanguage, ...rawTags] = infostring.trim().split(/\s+/);
285
+ return {
286
+ language: normalizeLanguage(rawLanguage),
287
+ tags: rawTags
288
+ };
289
+ }
290
+
68
291
  class SoftError extends Error {
69
292
  code;
70
293
  details;
@@ -274,16 +497,17 @@ function codePreview(options) {
274
497
  };
275
498
  function fence(_md, tokens, idx, _options, env) {
276
499
  const { fileInfo } = env;
277
- const { content: source, info, map } = tokens[idx];
278
- const [language, ...rawTags] = info.split(/\s+/);
500
+ const { content: rawSource, info, map } = tokens[idx];
501
+ const { language, tags: rawTags } = parseInfostring(info);
279
502
  if (language === "mermaid") {
280
503
  return (
281
504
  /* HTML */
282
505
  `
283
- <pre class="mermaid">${htmlencode(source)}</pre>
506
+ <pre class="mermaid">${htmlencode(rawSource)}</pre>
284
507
  `
285
508
  );
286
509
  }
510
+ const source = transformCode(rawSource, language);
287
511
  const example = generateExample({
288
512
  source,
289
513
  language,
@@ -774,7 +998,9 @@ function createMarkdownRenderer(options) {
774
998
 
775
999
  exports.SoftError = SoftError;
776
1000
  exports.createMarkdownRenderer = createMarkdownRenderer;
1001
+ exports.generateExample = generateExample;
777
1002
  exports.generateId = generateId;
778
1003
  exports.getFingerprint = getFingerprint;
779
1004
  exports.getOutputFilePath = getOutputFilePath;
780
1005
  exports.normalizePath = normalizePath;
1006
+ exports.parseInfostring = parseInfostring;
package/dist/index.d.ts CHANGED
@@ -77,7 +77,7 @@ declare interface Document_2 {
77
77
  /** Document outline (i.e. the heading structure) */
78
78
  outline: DocumentOutline;
79
79
  /** format of body */
80
- format: "markdown" | "html" | "json";
80
+ format: "markdown" | "html" | "json" | "redirect";
81
81
  tags: string[];
82
82
  template: string;
83
83
  fileInfo: FileInfo;
@@ -112,6 +112,23 @@ export declare interface DocumentOutlineEntry {
112
112
  subheadings: DocumentOutline;
113
113
  }
114
114
 
115
+ /**
116
+ * Options for `extractExamplesProcessor`.
117
+ *
118
+ * @public
119
+ */
120
+ export declare interface ExtractExamplesOptions extends ProcessorOptions {
121
+ outputFolder: string;
122
+ languages?: string[];
123
+ }
124
+
125
+ /**
126
+ * Processor to extract and write examples to separate files.
127
+ *
128
+ * @public
129
+ */
130
+ export declare function extractExamplesProcessor(options: ExtractExamplesOptions): Processor;
131
+
115
132
  /**
116
133
  * @public
117
134
  */
@@ -199,10 +216,12 @@ export { Generator_2 as Generator }
199
216
  export declare interface GeneratorOptions {
200
217
  /** Site options */
201
218
  site: GeneratorSiteOptions;
202
- outputFolder: string;
203
- cacheFolder: string;
204
- /** List of folders to search when locating examples (searched recursively) */
205
- exampleFolders: string[];
219
+ /** Output folder. Default: `public` */
220
+ outputFolder?: string;
221
+ /** Where cached content is stored. Default: `temp/docs` */
222
+ cacheFolder?: string;
223
+ /** List of folders to search when locating examples (searched recursively). Default: `[]` */
224
+ exampleFolders?: string[];
206
225
  /** List of folders to search when locating templates. */
207
226
  templateFolders?: string[];
208
227
  /** List of extra processors to run */
@@ -216,11 +235,11 @@ export declare interface GeneratorOptions {
216
235
  /**
217
236
  * Options for markdown renderer.
218
237
  */
219
- markdown: {
238
+ markdown?: {
220
239
  /**
221
240
  * Options for markdown messagebox container.
222
241
  */
223
- messagebox: {
242
+ messagebox?: {
224
243
  /**
225
244
  * Default titles for messageboxes.
226
245
  *
@@ -254,6 +273,13 @@ export declare interface GeneratorSiteOptions {
254
273
  lang?: string;
255
274
  }
256
275
 
276
+ /**
277
+ * Creates redirects using html-files with meta redirects.
278
+ *
279
+ * @public
280
+ */
281
+ export declare function htmlRedirectProcessor(): Processor;
282
+
257
283
  /**
258
284
  * @public
259
285
  */
@@ -266,6 +292,7 @@ export declare interface Manifest {
266
292
  pages: Array<{
267
293
  path: string;
268
294
  title: string;
295
+ redirect: null | string;
269
296
  outline: Array<{
270
297
  heading: string;
271
298
  anchor: string;
@@ -337,7 +364,7 @@ export declare interface MOTDOptions {
337
364
  *
338
365
  * @public
339
366
  */
340
- export declare function motdProcessor(options: MOTDOptions): Processor;
367
+ export declare function motdProcessor(options?: MOTDOptions): Processor;
341
368
 
342
369
  /**
343
370
  * Read a JSON file with arbitrary navigation entries.
@@ -387,6 +414,7 @@ export declare interface NormalizedDocumentAttributes {
387
414
  href?: string;
388
415
  /** normalized sortorder (defaults to Infinity) */
389
416
  sortorder: number;
417
+ redirectFrom: string[];
390
418
  }
391
419
 
392
420
  /**
@@ -416,6 +444,7 @@ export declare interface ProcessorContext {
416
444
  readonly topnav: NavigationSection;
417
445
  readonly sidenav: NavigationSection;
418
446
  readonly resources: ResourceTask[];
447
+ readonly outputFolder: string;
419
448
  addDocument(document: Document_2 | Document_2[]): void;
420
449
  addVendorAsset(asset: VendorAsset | VendorAsset[]): void;
421
450
  /**
@@ -503,6 +532,15 @@ export declare interface ProcessorRuntime {
503
532
  */
504
533
  export declare type ProcessorStage = "generate-docs" | "generate-nav" | "assets" | "render";
505
534
 
535
+ /**
536
+ * Generate a netlify-style `_redirects` file.
537
+ *
538
+ * @public
539
+ * @param prefix - Prefix to append to all urls, set to subdirectory if you're deploying to a subdirectory.
540
+ * @param filename - Output filename (relative to output folder).
541
+ */
542
+ export declare function redirectFileProcessor(prefix?: string, filename?: string): Processor;
543
+
506
544
  /**
507
545
  * Represents a resource to be copied from `from` to `to`.
508
546
  *