@cparra/apexdocs 3.0.0-alpha.2 → 3.0.0-alpha.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.
Files changed (33) hide show
  1. package/dist/cli/generate.js +88 -26
  2. package/dist/defaults-DGKfeZq-.js +13 -0
  3. package/dist/index.d.ts +13 -2
  4. package/dist/index.js +1 -1
  5. package/examples/markdown/docs/miscellaneous/MultiInheritanceClass.md +1 -1
  6. package/examples/markdown/docs/miscellaneous/SampleInterface.md +12 -8
  7. package/examples/markdown/docs/miscellaneous/Url.md +3 -3
  8. package/examples/markdown/force-app/classes/SampleInterface.cls +4 -0
  9. package/examples/vitepress/docs/index.md +10 -10
  10. package/examples/vitepress/docs/miscellaneous/BaseClass.md +1 -1
  11. package/examples/vitepress/docs/miscellaneous/MultiInheritanceClass.md +2 -2
  12. package/examples/vitepress/docs/miscellaneous/SampleException.md +1 -1
  13. package/examples/vitepress/docs/miscellaneous/SampleInterface.md +6 -6
  14. package/examples/vitepress/docs/miscellaneous/Url.md +3 -3
  15. package/examples/vitepress/docs/sample-enums/SampleEnum.md +3 -3
  16. package/examples/vitepress/docs/samplegroup/SampleClass.md +4 -4
  17. package/package.json +1 -1
  18. package/src/application/Apexdocs.ts +13 -8
  19. package/src/cli/commands/markdown.ts +4 -3
  20. package/src/core/markdown/__test__/generating-class-docs.spec.ts +2 -4
  21. package/src/core/markdown/__test__/generating-enum-docs.spec.ts +2 -2
  22. package/src/core/markdown/__test__/generating-interface-docs.spec.ts +2 -4
  23. package/src/core/markdown/__test__/generating-reference-guide.spec.ts +3 -3
  24. package/src/core/markdown/__test__/test-helpers.ts +1 -1
  25. package/src/core/markdown/adapters/__tests__/interface-adapter.spec.ts +1 -1
  26. package/src/core/markdown/adapters/__tests__/link-generator.spec.ts +130 -0
  27. package/src/core/markdown/adapters/generate-link.ts +82 -0
  28. package/src/core/markdown/adapters/renderable-bundle.ts +5 -22
  29. package/src/core/markdown/generate-docs.ts +1 -1
  30. package/src/core/shared/types.d.ts +12 -1
  31. package/src/defaults.ts +1 -1
  32. package/src/index.ts +1 -6
  33. package/src/util/logger.ts +18 -0
@@ -10,7 +10,7 @@ var apexReflection = require('@cparra/apex-reflection');
10
10
  var O = require('fp-ts/Option');
11
11
  var fastXmlParser = require('fast-xml-parser');
12
12
  var Handlebars = require('handlebars');
13
- var defaults = require('../defaults-jLXD2y8-.js');
13
+ var defaults = require('../defaults-DGKfeZq-.js');
14
14
  var fs = require('fs');
15
15
  var chalk = require('chalk');
16
16
  var logUpdate = require('log-update');
@@ -569,17 +569,68 @@ function singleGroup(headingLevel, groupName, adapter, members, linkGenerator) {
569
569
  };
570
570
  }
571
571
 
572
+ const generateLink = (strategy) => {
573
+ switch (strategy) {
574
+ case "relative":
575
+ return generateRelativeLink;
576
+ case "no-link":
577
+ return generateNoLink;
578
+ case "none":
579
+ return returnReferenceAsIs;
580
+ }
581
+ };
582
+ const generateRelativeLink = (references, from, referenceName) => {
583
+ function getRelativePath(fromPath, toPath) {
584
+ return path.relative(path.parse(path.join("/", fromPath)).dir, path.join("/", toPath));
585
+ }
586
+ const referenceTo = references[referenceName];
587
+ if (!referenceTo) {
588
+ return referenceName;
589
+ }
590
+ if (referenceTo && from === "__base__") {
591
+ return {
592
+ __type: "link",
593
+ title: referenceTo.displayName,
594
+ url: getRelativePath("", referenceTo.referencePath)
595
+ };
596
+ }
597
+ const referenceFrom = references[from];
598
+ if (!referenceFrom) {
599
+ return referenceTo.displayName;
600
+ }
601
+ return {
602
+ __type: "link",
603
+ title: referenceTo.displayName,
604
+ url: getRelativePath(referenceFrom.referencePath, referenceTo.referencePath)
605
+ };
606
+ };
607
+ const generateNoLink = (references, _from, referenceName) => {
608
+ const referenceTo = references[referenceName];
609
+ return referenceTo ? referenceTo.displayName : referenceName;
610
+ };
611
+ const returnReferenceAsIs = (references, _from, referenceName) => {
612
+ const referenceTo = references[referenceName];
613
+ if (!referenceTo) {
614
+ return referenceName;
615
+ }
616
+ return {
617
+ __type: "link",
618
+ title: referenceTo.displayName,
619
+ url: referenceTo.referencePath
620
+ };
621
+ };
622
+
572
623
  function parsedFilesToRenderableBundle(config, parsedFiles, references) {
573
- const referenceFinder = apply(linkGenerator, references, config.documentationRootDir);
624
+ const referenceFinder = apply(generateLink(config.linkingStrategy), references);
574
625
  function toReferenceGuide(parsedFiles2) {
575
626
  return parsedFiles2.reduce(
576
- addToReferenceGuide(referenceFinder, config, references),
627
+ addToReferenceGuide(apply(referenceFinder, "__base__"), config, references),
577
628
  {}
578
629
  );
579
630
  }
580
631
  function toRenderables(parsedFiles2) {
581
632
  return parsedFiles2.reduce((acc, parsedFile) => {
582
- const renderable = typeToRenderable(parsedFile, referenceFinder, config);
633
+ const renderable = typeToRenderable(parsedFile, apply(referenceFinder, parsedFile.source.name), config);
583
634
  acc.push(renderable);
584
635
  return acc;
585
636
  }, []);
@@ -604,17 +655,6 @@ function addToReferenceGuide(findLinkFromHome, config, references) {
604
655
  return acc;
605
656
  };
606
657
  }
607
- const linkGenerator = (references, documentationRootDir, referenceName) => {
608
- const reference = references[referenceName];
609
- return reference ? (
610
- // Starting the path with a "/" will ensure the link will always be relative to the root of the site.
611
- {
612
- __type: "link",
613
- title: reference.displayName,
614
- url: path__namespace.join("/", documentationRootDir, reference.referencePath)
615
- }
616
- ) : referenceName;
617
- };
618
658
  function getTypeGroup$1(type, config) {
619
659
  var _a, _b;
620
660
  const groupAnnotation = (_a = type.docComment) == null ? void 0 : _a.annotations.find((annotation) => annotation.name.toLowerCase() === "group");
@@ -1705,9 +1745,25 @@ class Logger {
1705
1745
  static clear() {
1706
1746
  logUpdate.clear();
1707
1747
  }
1748
+ static startSpinner() {
1749
+ if (this.spinnerInterval) {
1750
+ clearInterval(this.spinnerInterval);
1751
+ }
1752
+ this.spinnerInterval = setInterval(() => {
1753
+ this.logSingle("Processing...", true, "green", true);
1754
+ }, 80);
1755
+ }
1756
+ static stopSpinner() {
1757
+ if (this.spinnerInterval) {
1758
+ clearInterval(this.spinnerInterval);
1759
+ this.spinnerInterval = null;
1760
+ this.clear();
1761
+ }
1762
+ }
1708
1763
  }
1709
1764
  Logger.currentFrame = 0;
1710
1765
  Logger.frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1766
+ Logger.spinnerInterval = null;
1711
1767
 
1712
1768
  const referenceGuideTemplate = `
1713
1769
  # Apex Reference Guide
@@ -2834,14 +2890,19 @@ class Apexdocs {
2834
2890
  static generate(config) {
2835
2891
  return __async$1(this, null, function* () {
2836
2892
  Logger.logSingle("Initializing...", false);
2837
- const fileBodies = ApexFileReader.processFiles(new DefaultFileSystem(), config.sourceDir, config.includeMetadata);
2838
- switch (config.targetGenerator) {
2839
- case "markdown":
2840
- yield generate(fileBodies, config);
2841
- break;
2842
- case "openapi":
2843
- openApi(fileBodies, config);
2844
- break;
2893
+ Logger.startSpinner();
2894
+ try {
2895
+ const fileBodies = ApexFileReader.processFiles(new DefaultFileSystem(), config.sourceDir, config.includeMetadata);
2896
+ switch (config.targetGenerator) {
2897
+ case "markdown":
2898
+ yield generate(fileBodies, config);
2899
+ break;
2900
+ case "openapi":
2901
+ openApi(fileBodies, config);
2902
+ break;
2903
+ }
2904
+ } finally {
2905
+ Logger.stopSpinner();
2845
2906
  }
2846
2907
  });
2847
2908
  }
@@ -2886,10 +2947,11 @@ const markdownOptions = {
2886
2947
  describe: "Whether to include the file's meta.xml information: Whether it is active and and the API version",
2887
2948
  default: defaults.defaults.includeMetadata
2888
2949
  },
2889
- documentationRootDir: {
2950
+ linkingStrategy: {
2890
2951
  type: "string",
2891
- describe: "The root directory of the documentation. This is used to generate the correct relative paths.",
2892
- default: defaults.defaults.documentationRootDir
2952
+ describe: "The strategy to use when linking to other documentation pages.",
2953
+ choices: ["relative", "no-link", "none"],
2954
+ default: defaults.defaults.linkingStrategy
2893
2955
  }
2894
2956
  };
2895
2957
 
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const defaults = {
4
+ targetGenerator: "markdown",
5
+ targetDir: "./docs/",
6
+ scope: ["global"],
7
+ defaultGroupName: "Miscellaneous",
8
+ includeMetadata: false,
9
+ sortMembersAlphabetically: false,
10
+ linkingStrategy: "relative"
11
+ };
12
+
13
+ exports.defaults = defaults;
package/dist/index.d.ts CHANGED
@@ -10,6 +10,17 @@ type ConfigurableHooks = {
10
10
  transformReference: TransformReference;
11
11
  };
12
12
 
13
+ type LinkingStrategy =
14
+ // Links will be generated using relative paths.
15
+ | 'relative'
16
+ // No links will be generated.
17
+ // If the reference is found, the display name will be used.
18
+ // Otherwise, the name
19
+ // of the reference itself will be used.
20
+ | 'no-link'
21
+ // No logic will be applied, the reference path will be used as is.
22
+ | 'none';
23
+
13
24
  type UserDefinedMarkdownConfig = {
14
25
  targetGenerator: 'markdown';
15
26
  sourceDir: string;
@@ -19,7 +30,7 @@ type UserDefinedMarkdownConfig = {
19
30
  namespace?: string;
20
31
  sortMembersAlphabetically: boolean;
21
32
  includeMetadata: boolean;
22
- documentationRootDir: string;
33
+ linkingStrategy: LinkingStrategy;
23
34
  } & Partial<ConfigurableHooks>;
24
35
 
25
36
  type SourceFileMetadata = {
@@ -99,7 +110,7 @@ type TransformDocPage = (
99
110
  doc: DocPageData,
100
111
  ) => Partial<ConfigurableDocPageData> | Promise<Partial<ConfigurableDocPageData>>;
101
112
 
102
- type ConfigurableMarkdownConfig = Omit<SetOptional<UserDefinedMarkdownConfig, 'targetDir' | 'scope' | 'defaultGroupName' | 'includeMetadata' | 'sortMembersAlphabetically' | 'documentationRootDir'>, 'targetGenerator'>;
113
+ type ConfigurableMarkdownConfig = Omit<SetOptional<UserDefinedMarkdownConfig, 'targetDir' | 'scope' | 'defaultGroupName' | 'includeMetadata' | 'sortMembersAlphabetically' | 'linkingStrategy'>, 'targetGenerator'>;
103
114
  declare function defineMarkdownConfig(config: ConfigurableMarkdownConfig): UserDefinedMarkdownConfig;
104
115
  declare function skip(): Skip;
105
116
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var defaults = require('./defaults-jLXD2y8-.js');
3
+ var defaults = require('./defaults-DGKfeZq-.js');
4
4
 
5
5
  var __defProp = Object.defineProperty;
6
6
  var __defProps = Object.defineProperties;
@@ -5,7 +5,7 @@ ns
5
5
 
6
6
  **Inheritance**
7
7
 
8
- [SampleClass](../samplegroup/SampleClass.md) < [BaseClass](../miscellaneous/BaseClass.md)
8
+ [SampleClass](../samplegroup/SampleClass.md) < [BaseClass](BaseClass.md)
9
9
 
10
10
  ## Fields
11
11
  ### `sampleEnumFromBase`
@@ -6,10 +6,12 @@ This is a sample interface
6
6
 
7
7
  **Mermaid**
8
8
 
9
- graph TD
10
- A[SampleInterface] --&gt;|extends| B[ParentInterface]
11
- B --&gt;|extends| C[GrandParentInterface]
12
- C --&gt;|extends| D[GreatGrandParentInterface]
9
+ ```mermaid
10
+ graph TD
11
+ A[SampleInterface] -->|extends| B[ParentInterface]
12
+ B -->|extends| C[GrandParentInterface]
13
+ C -->|extends| D[GreatGrandParentInterface]
14
+ ```
13
15
 
14
16
  **Author** John Doe
15
17
 
@@ -17,17 +19,19 @@ C --&gt;|extends| D[GreatGrandParentInterface]
17
19
 
18
20
  **See** [SampleEnum](../sample-enums/SampleEnum.md)
19
21
 
20
- **See** [ReferencedEnum](../miscellaneous/ReferencedEnum.md)
22
+ **See** [ReferencedEnum](ReferencedEnum.md)
21
23
 
22
24
  ## Namespace
23
25
  ns
24
26
 
25
27
  ## Example
26
- SampleInterface sampleInterface &#x3D; new SampleInterface();
28
+ ```apex
29
+ SampleInterface sampleInterface = new SampleInterface();
27
30
  sampleInterface.sampleMethod();
31
+ ```
28
32
 
29
33
  **Extends**
30
- [ParentInterface](../miscellaneous/ParentInterface.md)
34
+ [ParentInterface](ParentInterface.md)
31
35
 
32
36
  ## Methods
33
37
  ### `sampleMethod()`
@@ -62,7 +66,7 @@ public String sampleMethod()
62
66
  Some return value
63
67
 
64
68
  #### Throws
65
- [SampleException](../miscellaneous/SampleException.md): This is a sample exception
69
+ [SampleException](SampleException.md): This is a sample exception
66
70
 
67
71
  AnotherSampleException: This is another sample exception
68
72
 
@@ -115,7 +115,7 @@ global Url(Url context, String spec)
115
115
  #### Parameters
116
116
  | Name | Type | Description |
117
117
  |------|------|-------------|
118
- | context | [Url](../miscellaneous/Url.md) | The context in which to parse the specification. |
118
+ | context | [Url](Url.md) | The context in which to parse the specification. |
119
119
  | spec | String | The string to parse as a URL. |
120
120
 
121
121
  ---
@@ -186,7 +186,7 @@ global static Url getCurrentRequestUrl()
186
186
  ```
187
187
 
188
188
  #### Return Type
189
- **[Url](../miscellaneous/Url.md)**
189
+ **[Url](Url.md)**
190
190
 
191
191
  The URL of the entire request.
192
192
 
@@ -294,7 +294,7 @@ global static Url getOrgDomainUrl()
294
294
  ```
295
295
 
296
296
  #### Return Type
297
- **[Url](../miscellaneous/Url.md)**
297
+ **[Url](Url.md)**
298
298
 
299
299
  getOrgDomainUrl() always returns the login URL for your org, regardless of context. Use that URL when making API calls to your org.
300
300
 
@@ -5,13 +5,17 @@
5
5
  * @see SampleEnum
6
6
  * @see ReferencedEnum
7
7
  * @mermaid
8
+ * ```mermaid
8
9
  * graph TD
9
10
  * A[SampleInterface] -->|extends| B[ParentInterface]
10
11
  * B -->|extends| C[GrandParentInterface]
11
12
  * C -->|extends| D[GreatGrandParentInterface]
13
+ * ```
12
14
  * @example
15
+ * ```apex
13
16
  * SampleInterface sampleInterface = new SampleInterface();
14
17
  * sampleInterface.sampleMethod();
18
+ * ```
15
19
  */
16
20
  @NamespaceAccessible
17
21
  public interface SampleInterface extends ParentInterface {
@@ -19,38 +19,38 @@ hero:
19
19
 
20
20
  ## Miscellaneous
21
21
 
22
- ### [BaseClass](/miscellaneous/BaseClass.md)
22
+ ### [BaseClass](miscellaneous/BaseClass.md)
23
23
 
24
- ### [MultiInheritanceClass](/miscellaneous/MultiInheritanceClass.md)
24
+ ### [MultiInheritanceClass](miscellaneous/MultiInheritanceClass.md)
25
25
 
26
- ### [ParentInterface](/miscellaneous/ParentInterface.md)
26
+ ### [ParentInterface](miscellaneous/ParentInterface.md)
27
27
 
28
- ### [ReferencedEnum](/miscellaneous/ReferencedEnum.md)
28
+ ### [ReferencedEnum](miscellaneous/ReferencedEnum.md)
29
29
 
30
- ### [SampleException](/miscellaneous/SampleException.md)
30
+ ### [SampleException](miscellaneous/SampleException.md)
31
31
 
32
32
  This is a sample exception.
33
33
 
34
- ### [SampleInterface](/miscellaneous/SampleInterface.md)
34
+ ### [SampleInterface](miscellaneous/SampleInterface.md)
35
35
 
36
36
  This is a sample interface
37
37
 
38
- ### [Url](/miscellaneous/Url.md)
38
+ ### [Url](miscellaneous/Url.md)
39
39
 
40
40
  Represents a uniform resource locator (URL) and provides access to parts of the URL.
41
41
  Enables access to the base URL used to access your Salesforce org.
42
42
 
43
43
  ## Sample Enums
44
44
 
45
- ### [SampleEnum](/sample-enums/SampleEnum.md)
45
+ ### [SampleEnum](sample-enums/SampleEnum.md)
46
46
 
47
- This is a sample enum. This references [ReferencedEnum](/miscellaneous/ReferencedEnum.md) .
47
+ This is a sample enum. This references [ReferencedEnum](miscellaneous/ReferencedEnum.md) .
48
48
 
49
49
  This description has several lines
50
50
 
51
51
  ## SampleGroup
52
52
 
53
- ### [SampleClass](/samplegroup/SampleClass.md)
53
+ ### [SampleClass](samplegroup/SampleClass.md)
54
54
 
55
55
  aliquip ex sunt officia ullamco anim deserunt magna aliquip nisi eiusmod in sit officia veniam ex
56
56
  deserunt ea officia exercitation laboris enim in duis quis enim eiusmod eu amet cupidatat.
@@ -17,4 +17,4 @@ public sampleEnumFromBase
17
17
  ```
18
18
 
19
19
  #### Type
20
- [SampleEnum](/sample-enums/SampleEnum.md)
20
+ [SampleEnum](../sample-enums/SampleEnum.md)
@@ -9,7 +9,7 @@ apexdocs
9
9
 
10
10
  **Inheritance**
11
11
 
12
- [SampleClass](/samplegroup/SampleClass.md) < [BaseClass](/miscellaneous/BaseClass.md)
12
+ [SampleClass](../samplegroup/SampleClass.md) < [BaseClass](BaseClass.md)
13
13
 
14
14
  ## Fields
15
15
  ### `sampleEnumFromBase`
@@ -22,7 +22,7 @@ public sampleEnumFromBase
22
22
  ```
23
23
 
24
24
  #### Type
25
- [SampleEnum](/sample-enums/SampleEnum.md)
25
+ [SampleEnum](../sample-enums/SampleEnum.md)
26
26
 
27
27
  ## Properties
28
28
  ### Group Name
@@ -9,7 +9,7 @@ This is a sample exception.
9
9
  **Usage**
10
10
 
11
11
  You can use the exception the following way.
12
- You can also take a look at [SampleClass](/samplegroup/SampleClass.md) to see how it is used.
12
+ You can also take a look at [SampleClass](../samplegroup/SampleClass.md) to see how it is used.
13
13
  This is a dangerous HTML tag: &lt;script&gt;alert(&#x27;Hello&#x27;);&lt;/script&gt;
14
14
 
15
15
  ```apex
@@ -19,9 +19,9 @@ C --&gt;|extends| D[GreatGrandParentInterface]
19
19
 
20
20
  **Date** 2020-01-01
21
21
 
22
- **See** [SampleEnum](/sample-enums/SampleEnum.md)
22
+ **See** [SampleEnum](../sample-enums/SampleEnum.md)
23
23
 
24
- **See** [ReferencedEnum](/miscellaneous/ReferencedEnum.md)
24
+ **See** [ReferencedEnum](ReferencedEnum.md)
25
25
 
26
26
  ## Namespace
27
27
  apexdocs
@@ -31,7 +31,7 @@ SampleInterface sampleInterface &#x3D; new SampleInterface();
31
31
  sampleInterface.sampleMethod();
32
32
 
33
33
  **Extends**
34
- [ParentInterface](/miscellaneous/ParentInterface.md)
34
+ [ParentInterface](ParentInterface.md)
35
35
 
36
36
  ## Methods
37
37
  ### `sampleMethod()`
@@ -66,7 +66,7 @@ public String sampleMethod()
66
66
  Some return value
67
67
 
68
68
  #### Throws
69
- [SampleException](/miscellaneous/SampleException.md): This is a sample exception
69
+ [SampleException](SampleException.md): This is a sample exception
70
70
 
71
71
  AnotherSampleException: This is another sample exception
72
72
 
@@ -94,10 +94,10 @@ public SampleEnum sampleMethodWithParams(String param1, Integer param2, SampleEn
94
94
  |------|------|-------------|
95
95
  | param1 | String | This is the first parameter |
96
96
  | param2 | Integer | This is the second parameter |
97
- | theEnum | [SampleEnum](/sample-enums/SampleEnum.md) | This is an enum parameter |
97
+ | theEnum | [SampleEnum](../sample-enums/SampleEnum.md) | This is an enum parameter |
98
98
 
99
99
  #### Return Type
100
- **[SampleEnum](/sample-enums/SampleEnum.md)**
100
+ **[SampleEnum](../sample-enums/SampleEnum.md)**
101
101
 
102
102
  Some return value
103
103
 
@@ -120,7 +120,7 @@ global Url(Url context, String spec)
120
120
  #### Parameters
121
121
  | Name | Type | Description |
122
122
  |------|------|-------------|
123
- | context | [Url](/miscellaneous/Url.md) | The context in which to parse the specification. |
123
+ | context | [Url](Url.md) | The context in which to parse the specification. |
124
124
  | spec | String | The string to parse as a URL. |
125
125
 
126
126
  ---
@@ -191,7 +191,7 @@ global static Url getCurrentRequestUrl()
191
191
  ```
192
192
 
193
193
  #### Return Type
194
- **[Url](/miscellaneous/Url.md)**
194
+ **[Url](Url.md)**
195
195
 
196
196
  The URL of the entire request.
197
197
 
@@ -299,7 +299,7 @@ global static Url getOrgDomainUrl()
299
299
  ```
300
300
 
301
301
  #### Return Type
302
- **[Url](/miscellaneous/Url.md)**
302
+ **[Url](Url.md)**
303
303
 
304
304
  getOrgDomainUrl() always returns the login URL for your org, regardless of context. Use that URL when making API calls to your org.
305
305
 
@@ -6,13 +6,13 @@ title: SampleEnum
6
6
 
7
7
  `NAMESPACEACCESSIBLE`
8
8
 
9
- This is a sample enum. This references [ReferencedEnum](/miscellaneous/ReferencedEnum.md) .
9
+ This is a sample enum. This references [ReferencedEnum](../miscellaneous/ReferencedEnum.md) .
10
10
 
11
11
  This description has several lines
12
12
 
13
13
  **Some Custom**
14
14
 
15
- Test. I can also have a [ReferencedEnum](/miscellaneous/ReferencedEnum.md) here.
15
+ Test. I can also have a [ReferencedEnum](../miscellaneous/ReferencedEnum.md) here.
16
16
  And it can be multiline.
17
17
 
18
18
  **Mermaid**
@@ -27,7 +27,7 @@ B --&gt;|referenced by| A
27
27
 
28
28
  **Date** 2022-01-01
29
29
 
30
- **See** [ReferencedEnum](/miscellaneous/ReferencedEnum.md)
30
+ **See** [ReferencedEnum](../miscellaneous/ReferencedEnum.md)
31
31
 
32
32
  ## Namespace
33
33
  apexdocs
@@ -19,12 +19,12 @@ sample.doSomething();
19
19
 
20
20
  **Inheritance**
21
21
 
22
- [BaseClass](/miscellaneous/BaseClass.md)
22
+ [BaseClass](../miscellaneous/BaseClass.md)
23
23
 
24
24
  **Implements**
25
25
 
26
- [SampleInterface](/miscellaneous/SampleInterface.md),
27
- [ParentInterface](/miscellaneous/ParentInterface.md)
26
+ [SampleInterface](../miscellaneous/SampleInterface.md),
27
+ [ParentInterface](../miscellaneous/ParentInterface.md)
28
28
 
29
29
  ## Fields
30
30
  ### Group Name
@@ -51,7 +51,7 @@ public sampleEnumFromBase
51
51
  ```
52
52
 
53
53
  ##### Type
54
- [SampleEnum](/sample-enums/SampleEnum.md)
54
+ [SampleEnum](../sample-enums/SampleEnum.md)
55
55
 
56
56
  ## Properties
57
57
  ### Group Name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cparra/apexdocs",
3
- "version": "3.0.0-alpha.2",
3
+ "version": "3.0.0-alpha.4",
4
4
  "description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.",
5
5
  "keywords": [
6
6
  "apex",
@@ -15,16 +15,21 @@ export class Apexdocs {
15
15
  */
16
16
  static async generate(config: UserDefinedConfig): Promise<void> {
17
17
  Logger.logSingle('Initializing...', false);
18
+ Logger.startSpinner();
18
19
 
19
- const fileBodies = ApexFileReader.processFiles(new DefaultFileSystem(), config.sourceDir, config.includeMetadata);
20
+ try {
21
+ const fileBodies = ApexFileReader.processFiles(new DefaultFileSystem(), config.sourceDir, config.includeMetadata);
20
22
 
21
- switch (config.targetGenerator) {
22
- case 'markdown':
23
- await markdown(fileBodies, config);
24
- break;
25
- case 'openapi':
26
- openApi(fileBodies, config);
27
- break;
23
+ switch (config.targetGenerator) {
24
+ case 'markdown':
25
+ await markdown(fileBodies, config);
26
+ break;
27
+ case 'openapi':
28
+ openApi(fileBodies, config);
29
+ break;
30
+ }
31
+ } finally {
32
+ Logger.stopSpinner();
28
33
  }
29
34
  }
30
35
  }
@@ -44,9 +44,10 @@ export const markdownOptions: { [key: string]: Options } = {
44
44
  describe: "Whether to include the file's meta.xml information: Whether it is active and and the API version",
45
45
  default: defaults.includeMetadata,
46
46
  },
47
- documentationRootDir: {
47
+ linkingStrategy: {
48
48
  type: 'string',
49
- describe: 'The root directory of the documentation. This is used to generate the correct relative paths.',
50
- default: defaults.documentationRootDir,
49
+ describe: 'The strategy to use when linking to other documentation pages.',
50
+ choices: ['relative', 'no-link', 'none'],
51
+ default: defaults.linkingStrategy,
51
52
  },
52
53
  };
@@ -268,9 +268,7 @@ describe('Generates interface documentation', () => {
268
268
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
269
269
  expect(result).documentationBundleHasLength(2);
270
270
  assertEither(result, (data) =>
271
- expect(data).firstDocContains(
272
- 'This is a description with a [ClassRef](/miscellaneous/ClassRef.md) reference',
273
- ),
271
+ expect(data).firstDocContains('This is a description with a [ClassRef](ClassRef.md) reference'),
274
272
  );
275
273
  });
276
274
 
@@ -304,7 +302,7 @@ describe('Generates interface documentation', () => {
304
302
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
305
303
  expect(result).documentationBundleHasLength(2);
306
304
  assertEither(result, (data) => expect(data).firstDocContains('See'));
307
- assertEither(result, (data) => expect(data).firstDocContains('[ClassRef](/miscellaneous/ClassRef.md)'));
305
+ assertEither(result, (data) => expect(data).firstDocContains('[ClassRef](ClassRef.md)'));
308
306
  });
309
307
 
310
308
  it('displays sees without links when the reference is not found', async () => {
@@ -211,7 +211,7 @@ describe('Generates enum documentation', () => {
211
211
  expect(result).documentationBundleHasLength(2);
212
212
  assertEither(result, (data) => expect(data).firstDocContains('Description'));
213
213
  assertEither(result, (data) =>
214
- expect(data).firstDocContains('This is a description with a [EnumRef](/miscellaneous/EnumRef.md) reference'),
214
+ expect(data).firstDocContains('This is a description with a [EnumRef](EnumRef.md) reference'),
215
215
  );
216
216
  });
217
217
 
@@ -245,7 +245,7 @@ describe('Generates enum documentation', () => {
245
245
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
246
246
  expect(result).documentationBundleHasLength(2);
247
247
  assertEither(result, (data) => expect(data).firstDocContains('See'));
248
- assertEither(result, (data) => expect(data).firstDocContains('[EnumRef](/miscellaneous/EnumRef.md)'));
248
+ assertEither(result, (data) => expect(data).firstDocContains('[EnumRef](EnumRef.md)'));
249
249
  });
250
250
 
251
251
  it('displays sees without links when the reference is not found', async () => {
@@ -189,9 +189,7 @@ describe('Generates interface documentation', () => {
189
189
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
190
190
  expect(result).documentationBundleHasLength(2);
191
191
  assertEither(result, (data) =>
192
- expect(data).firstDocContains(
193
- 'This is a description with a [InterfaceRef](/miscellaneous/InterfaceRef.md) reference',
194
- ),
192
+ expect(data).firstDocContains('This is a description with a [InterfaceRef](InterfaceRef.md) reference'),
195
193
  );
196
194
  });
197
195
 
@@ -225,7 +223,7 @@ describe('Generates interface documentation', () => {
225
223
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
226
224
  expect(result).documentationBundleHasLength(2);
227
225
  assertEither(result, (data) => expect(data).firstDocContains('See'));
228
- assertEither(result, (data) => expect(data).firstDocContains('[InterfaceRef](/miscellaneous/InterfaceRef.md)'));
226
+ assertEither(result, (data) => expect(data).firstDocContains('[InterfaceRef](InterfaceRef.md)'));
229
227
  });
230
228
 
231
229
  it('displays sees without links when the reference is not found', async () => {
@@ -25,10 +25,10 @@ describe('Generates a Reference Guide', () => {
25
25
  expect(result).documentationBundleHasLength(2);
26
26
 
27
27
  assertEither(result, (data) =>
28
- expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyEnum](/miscellaneous/MyEnum.md)'),
28
+ expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyEnum](miscellaneous/MyEnum.md)'),
29
29
  );
30
30
  assertEither(result, (data) =>
31
- expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyClass](/miscellaneous/MyClass.md)'),
31
+ expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('[MyClass](miscellaneous/MyClass.md)'),
32
32
  );
33
33
  });
34
34
 
@@ -174,7 +174,7 @@ describe('Generates a Reference Guide', () => {
174
174
  const result = await generateDocs([apexBundleFromRawString(input1), apexBundleFromRawString(input2)])();
175
175
  expect(result).documentationBundleHasLength(2);
176
176
  assertEither(result, (data) =>
177
- expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('with a [MyClass](/group2/MyClass.md)'),
177
+ expect((data.referenceGuide as ReferenceGuidePageData).content).toContain('with a [MyClass](group2/MyClass.md)'),
178
178
  );
179
179
  });
180
180
  });
@@ -17,7 +17,7 @@ export function generateDocs(apexBundles: UnparsedSourceFile[], config?: Partial
17
17
  defaultGroupName: 'Miscellaneous',
18
18
  sortMembersAlphabetically: true,
19
19
  referenceGuideTemplate: referenceGuideTemplate,
20
+ linkingStrategy: 'relative',
20
21
  ...config,
21
- documentationRootDir: '',
22
22
  });
23
23
  }
@@ -15,7 +15,7 @@ const defaultMarkdownGeneratorConfig: MarkdownGeneratorConfig = {
15
15
  defaultGroupName: 'Miscellaneous',
16
16
  referenceGuideTemplate: '',
17
17
  sortMembersAlphabetically: false,
18
- documentationRootDir: '',
18
+ linkingStrategy: 'relative',
19
19
  };
20
20
 
21
21
  describe('Conversion from InterfaceMirror to InterfaceSource understandable by the templating engine', () => {
@@ -0,0 +1,130 @@
1
+ import { generateLink } from '../generate-link';
2
+
3
+ describe('Generates links', () => {
4
+ describe('relative', () => {
5
+ it('generates relative links from the base when found', () => {
6
+ const references = {
7
+ referenceName: {
8
+ referencePath: 'referencePath',
9
+ displayName: 'displayName',
10
+ },
11
+ };
12
+ const from = '__base__';
13
+ const referenceName = 'referenceName';
14
+
15
+ const result = generateLink('relative')(references, from, referenceName);
16
+
17
+ expect(result).toEqual({
18
+ __type: 'link',
19
+ title: 'displayName',
20
+ url: 'referencePath',
21
+ });
22
+ });
23
+
24
+ it('returns the name of the reference when not found', () => {
25
+ const references = {};
26
+ const from = '__base__';
27
+ const referenceName = 'referenceName';
28
+
29
+ const result = generateLink('relative')(references, from, referenceName);
30
+
31
+ expect(result).toEqual('referenceName');
32
+ });
33
+
34
+ it('returns a relative path when linking from a file', () => {
35
+ const references = {
36
+ referenceName: {
37
+ referencePath: 'a/referencePath',
38
+ displayName: 'displayName',
39
+ },
40
+ from: {
41
+ referencePath: 'b/fromPath',
42
+ displayName: 'fromName',
43
+ },
44
+ };
45
+ const from = 'from';
46
+ const referenceName = 'referenceName';
47
+
48
+ const result = generateLink('relative')(references, from, referenceName);
49
+
50
+ expect(result).toEqual({
51
+ __type: 'link',
52
+ title: 'displayName',
53
+ url: '../a/referencePath',
54
+ });
55
+ });
56
+
57
+ it('returns the display name when the from reference is not found', () => {
58
+ const references = {
59
+ referenceName: {
60
+ referencePath: 'a/referencePath',
61
+ displayName: 'displayName',
62
+ },
63
+ };
64
+ const from = 'from';
65
+ const referenceName = 'referenceName';
66
+
67
+ const result = generateLink('relative')(references, from, referenceName);
68
+
69
+ expect(result).toEqual('displayName');
70
+ });
71
+ });
72
+
73
+ describe('no-link', () => {
74
+ it('returns the name of the reference when the reference is not found', () => {
75
+ const references = {};
76
+ const from = '__base__';
77
+ const referenceName = 'referenceName';
78
+
79
+ const result = generateLink('no-link')(references, from, referenceName);
80
+
81
+ expect(result).toEqual('referenceName');
82
+ });
83
+
84
+ it('returns the display name of the reference when the reference is found', () => {
85
+ const references = {
86
+ referenceName: {
87
+ referencePath: 'referencePath',
88
+ displayName: 'displayName',
89
+ },
90
+ };
91
+ const from = '__base__';
92
+ const referenceName = 'referenceName';
93
+
94
+ const result = generateLink('no-link')(references, from, referenceName);
95
+
96
+ expect(result).toEqual('displayName');
97
+ });
98
+ });
99
+
100
+ describe('none', () => {
101
+ it('returns the path as is when the reference is found', () => {
102
+ const references = {
103
+ referenceName: {
104
+ referencePath: 'referencePath',
105
+ displayName: 'displayName',
106
+ },
107
+ };
108
+ const from = '__base__';
109
+ const referenceName = 'referenceName';
110
+
111
+ const result = generateLink('none')(references, from, referenceName);
112
+
113
+ expect(result).toEqual({
114
+ __type: 'link',
115
+ title: 'displayName',
116
+ url: 'referencePath',
117
+ });
118
+ });
119
+
120
+ it('returns the name of the reference when the reference is not found', () => {
121
+ const references = {};
122
+ const from = '__base__';
123
+ const referenceName = 'referenceName';
124
+
125
+ const result = generateLink('none')(references, from, referenceName);
126
+
127
+ expect(result).toEqual('referenceName');
128
+ });
129
+ });
130
+ });
@@ -0,0 +1,82 @@
1
+ import { StringOrLink } from './types';
2
+ import path from 'path';
3
+ import { LinkingStrategy } from '../../shared/types';
4
+
5
+ export type LinkingStrategyFn = (
6
+ references: Record<string, { referencePath: string; displayName: string } | undefined>,
7
+ from: string,
8
+ referenceName: string,
9
+ ) => StringOrLink;
10
+
11
+ export const generateLink = (strategy: LinkingStrategy): LinkingStrategyFn => {
12
+ switch (strategy) {
13
+ case 'relative':
14
+ return generateRelativeLink;
15
+ case 'no-link':
16
+ return generateNoLink;
17
+ case 'none':
18
+ return returnReferenceAsIs;
19
+ }
20
+ };
21
+
22
+ const generateRelativeLink = (
23
+ references: Record<string, { referencePath: string; displayName: string } | undefined>,
24
+ from: string, // The name of the file for which the reference is being generated
25
+ referenceName: string,
26
+ ): StringOrLink => {
27
+ function getRelativePath(fromPath: string, toPath: string) {
28
+ return path.relative(path.parse(path.join('/', fromPath)).dir, path.join('/', toPath));
29
+ }
30
+
31
+ const referenceTo = references[referenceName];
32
+ if (!referenceTo) {
33
+ return referenceName;
34
+ }
35
+ // When linking from the base path (e.g. the reference guide/index page), the reference path is the same as the output
36
+ // path.
37
+ if (referenceTo && from === '__base__') {
38
+ return {
39
+ __type: 'link',
40
+ title: referenceTo.displayName,
41
+ url: getRelativePath('', referenceTo.referencePath),
42
+ };
43
+ }
44
+
45
+ const referenceFrom = references[from];
46
+
47
+ if (!referenceFrom) {
48
+ return referenceTo.displayName;
49
+ }
50
+
51
+ return {
52
+ __type: 'link',
53
+ title: referenceTo.displayName,
54
+ url: getRelativePath(referenceFrom.referencePath, referenceTo.referencePath),
55
+ };
56
+ };
57
+
58
+ const generateNoLink = (
59
+ references: Record<string, { referencePath: string; displayName: string } | undefined>,
60
+ _from: string,
61
+ referenceName: string,
62
+ ): StringOrLink => {
63
+ const referenceTo = references[referenceName];
64
+ return referenceTo ? referenceTo.displayName : referenceName;
65
+ };
66
+
67
+ const returnReferenceAsIs = (
68
+ references: Record<string, { referencePath: string; displayName: string } | undefined>,
69
+ _from: string,
70
+ referenceName: string,
71
+ ): StringOrLink => {
72
+ const referenceTo = references[referenceName];
73
+ if (!referenceTo) {
74
+ return referenceName;
75
+ }
76
+
77
+ return {
78
+ __type: 'link',
79
+ title: referenceTo.displayName,
80
+ url: referenceTo.referencePath,
81
+ };
82
+ };
@@ -1,29 +1,29 @@
1
1
  import { DocPageReference, ParsedFile } from '../../shared/types';
2
- import { Link, ReferenceGuideReference, Renderable, RenderableBundle, StringOrLink } from './types';
2
+ import { Link, ReferenceGuideReference, Renderable, RenderableBundle } from './types';
3
3
  import { typeToRenderable } from './apex-types';
4
4
  import { adaptDescribable } from './documentables';
5
5
  import { MarkdownGeneratorConfig } from '../generate-docs';
6
6
  import { apply } from '#utils/fp';
7
7
  import { Type } from '@cparra/apex-reflection';
8
- import * as path from 'path';
8
+ import { generateLink } from './generate-link';
9
9
 
10
10
  export function parsedFilesToRenderableBundle(
11
11
  config: MarkdownGeneratorConfig,
12
12
  parsedFiles: ParsedFile[],
13
13
  references: Record<string, DocPageReference>,
14
14
  ): RenderableBundle {
15
- const referenceFinder = apply(linkGenerator, references, config.documentationRootDir);
15
+ const referenceFinder = apply(generateLink(config.linkingStrategy), references);
16
16
 
17
17
  function toReferenceGuide(parsedFiles: ParsedFile[]): Record<string, ReferenceGuideReference[]> {
18
18
  return parsedFiles.reduce<Record<string, ReferenceGuideReference[]>>(
19
- addToReferenceGuide(referenceFinder, config, references),
19
+ addToReferenceGuide(apply(referenceFinder, '__base__'), config, references),
20
20
  {},
21
21
  );
22
22
  }
23
23
 
24
24
  function toRenderables(parsedFiles: ParsedFile[]): Renderable[] {
25
25
  return parsedFiles.reduce<Renderable[]>((acc, parsedFile) => {
26
- const renderable = typeToRenderable(parsedFile, referenceFinder, config);
26
+ const renderable = typeToRenderable(parsedFile, apply(referenceFinder, parsedFile.source.name), config);
27
27
  acc.push(renderable);
28
28
  return acc;
29
29
  }, []);
@@ -55,23 +55,6 @@ function addToReferenceGuide(
55
55
  };
56
56
  }
57
57
 
58
- const linkGenerator = (
59
- references: Record<string, DocPageReference>,
60
- documentationRootDir: string,
61
- referenceName: string,
62
- ): StringOrLink => {
63
- const reference: DocPageReference | undefined = references[referenceName];
64
-
65
- return reference
66
- ? // Starting the path with a "/" will ensure the link will always be relative to the root of the site.
67
- {
68
- __type: 'link',
69
- title: reference.displayName,
70
- url: path.join('/', documentationRootDir, reference.referencePath),
71
- }
72
- : referenceName;
73
- };
74
-
75
58
  function getTypeGroup(type: Type, config: MarkdownGeneratorConfig): string {
76
59
  const groupAnnotation = type.docComment?.annotations.find((annotation) => annotation.name.toLowerCase() === 'group');
77
60
  return groupAnnotation?.body ?? config.defaultGroupName;
@@ -42,7 +42,7 @@ export type MarkdownGeneratorConfig = Pick<
42
42
  | 'transformDocs'
43
43
  | 'transformDocPage'
44
44
  | 'transformReference'
45
- | 'documentationRootDir'
45
+ | 'linkingStrategy'
46
46
  > & {
47
47
  referenceGuideTemplate: string;
48
48
  sortMembersAlphabetically: boolean;
@@ -12,6 +12,17 @@ export type ConfigurableHooks = {
12
12
  transformReference: TransformReference;
13
13
  };
14
14
 
15
+ type LinkingStrategy =
16
+ // Links will be generated using relative paths.
17
+ | 'relative'
18
+ // No links will be generated.
19
+ // If the reference is found, the display name will be used.
20
+ // Otherwise, the name
21
+ // of the reference itself will be used.
22
+ | 'no-link'
23
+ // No logic will be applied, the reference path will be used as is.
24
+ | 'none';
25
+
15
26
  export type UserDefinedMarkdownConfig = {
16
27
  targetGenerator: 'markdown';
17
28
  sourceDir: string;
@@ -21,7 +32,7 @@ export type UserDefinedMarkdownConfig = {
21
32
  namespace?: string;
22
33
  sortMembersAlphabetically: boolean;
23
34
  includeMetadata: boolean;
24
- documentationRootDir: string;
35
+ linkingStrategy: LinkingStrategy;
25
36
  } & Partial<ConfigurableHooks>;
26
37
 
27
38
  export type UserDefinedOpenApiConfig = {
package/src/defaults.ts CHANGED
@@ -5,5 +5,5 @@ export const defaults = {
5
5
  defaultGroupName: 'Miscellaneous',
6
6
  includeMetadata: false,
7
7
  sortMembersAlphabetically: false,
8
- documentationRootDir: '',
8
+ linkingStrategy: 'relative' as const,
9
9
  };
package/src/index.ts CHANGED
@@ -12,12 +12,7 @@ import { defaults } from './defaults';
12
12
  type ConfigurableMarkdownConfig = Omit<
13
13
  SetOptional<
14
14
  UserDefinedMarkdownConfig,
15
- | 'targetDir'
16
- | 'scope'
17
- | 'defaultGroupName'
18
- | 'includeMetadata'
19
- | 'sortMembersAlphabetically'
20
- | 'documentationRootDir'
15
+ 'targetDir' | 'scope' | 'defaultGroupName' | 'includeMetadata' | 'sortMembersAlphabetically' | 'linkingStrategy'
21
16
  >,
22
17
  'targetGenerator'
23
18
  >;
@@ -8,6 +8,7 @@ export class Logger {
8
8
  static currentFrame = 0;
9
9
 
10
10
  static frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
11
+ static spinnerInterval: NodeJS.Timeout | null = null;
11
12
 
12
13
  /**
13
14
  * Logs a message with optional arguments.
@@ -54,4 +55,21 @@ export class Logger {
54
55
  public static clear() {
55
56
  logUpdate.clear();
56
57
  }
58
+
59
+ public static startSpinner() {
60
+ if (this.spinnerInterval) {
61
+ clearInterval(this.spinnerInterval);
62
+ }
63
+ this.spinnerInterval = setInterval(() => {
64
+ this.logSingle('Processing...', true, 'green', true);
65
+ }, 80);
66
+ }
67
+
68
+ public static stopSpinner() {
69
+ if (this.spinnerInterval) {
70
+ clearInterval(this.spinnerInterval);
71
+ this.spinnerInterval = null;
72
+ this.clear();
73
+ }
74
+ }
57
75
  }