diff --git a/.github/workflows/close_stale.yml b/.github/workflows/close_stale.yml deleted file mode 100644 index d5cd3cf1..00000000 --- a/.github/workflows/close_stale.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Close inactive issues -on: - schedule: - - cron: "30 1 * * *" - -jobs: - close-issues: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/stale@v5 - with: - days-before-issue-stale: 30 - days-before-issue-close: 14 - stale-issue-label: "stale" - stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." - close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." - days-before-pr-stale: -1 - days-before-pr-close: -1 - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index e5902fdd..9ab183d5 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,64 @@ ApexDocs is a non-opinionated documentation generator for Salesforce Apex classes. -It can output documentation in Markdown -format, -which allows you to use the Static Site Generator of your choice to create a documentation site that fits your needs, -hosted in any static web hosting service. +It can output documentation in Markdown format, which allows you to use the Static Site Generator of your choice to +create a documentation site that fits your needs, hosted in any static web hosting service. + +## 👀 Examples + +ApexDocs generates Markdown files, which can be integrated into any Static Site Generation (SSG) engine, +(e.g. Jekyll, Vitepress, Hugo, Docosaurus, etc.) to create a documentation site that fits your needs. + +This gives you the flexibility to create beautiful leveraging your preferred SSG engine, which +usually provides a wide range of themes, dark mode support, and other features out of the box. + +
+ Vitepress Light + Vitepress Dark +
+ +*These are examples of documentation sites generated using Vitepress. +Head over to the `examples/vitepress` folder to see the code.* + +The extra flexibility also lets you integrate the output documentation with your existing documentation site, +allowing you to match the look and feel of your existing site. + +
+ Integration +
+ +OpenApi REST definitions can be visualized using a tool like ReDoc, Swagger UI, or any other OpenApi viewer. + +
+ OpenApi +
+ +This repo contains several other example implementations in the `examples` directory, showcasing how to integrate +with different tools. + +* [Examples](./examples) + +### In the wild + +Here are some live projects using ApexDocs: + +- [Trailhead Apex Recipes](https://github.com/trailheadapps/apex-recipes) +- [Salesforce Commerce Apex Reference](https://developer.salesforce.com/docs/commerce/salesforce-commerce/references/comm-apex-reference/cart-reference.html) +- [Expression (API)](https://cesarparra.github.io/expression/) +- [Nimble AMS Docs](https://nimbleuser.github.io/nams-api-docs/#/api-reference/) + +## 🚀 Features + +* Generate documentation for Salesforce Apex classes as Markdown files +* Generate an OpenApi REST specification based on `@RestResource` classes +* Generate a changelog based on the differences between two versions of your Salesforce Apex classes +* Support for grouping blocks of related code within a class +* Support for ignoring files and members from being documented +* Namespace support +* Configuration file support +* Single line ApexDoc Blocks +* Custom tag support +* And much, much more! ## 💿 Installation @@ -49,38 +103,6 @@ Run the following command to generate a changelog for your Salesforce Apex class apexdocs changelog --previousVersionDir force-app-previous --currentVersionDir force-app ``` -## 🚀 Features - -* Generate documentation for Salesforce Apex classes as Markdown files -* Generate an OpenApi REST specification based on `@RestResource` classes -* Generate a changelog based on the differences between two versions of your Salesforce Apex classes -* Support for grouping blocks of related code within a class -* Support for ignoring files and members from being documented -* Namespace support -* Configuration file support -* Single line ApexDoc Blocks -* Custom tag support -* And much, much more! - -## 👀 Demo - -ApexDocs generates Markdown files, which can be integrated into any Static Site Generation engine, -(e.g. Jekyll, Vitepress, Hugo, Docosaurus, etc.) to create a documentation site that fits your needs. - -This repo contains several example implementations in the `examples` directory, showcasing how to integrate -with some of these tools. - -* [Examples](./examples) - -### In the wild - -Here are some live projects using ApexDocs: - -- [Trailhead Apex Recipes](https://github.com/trailheadapps/apex-recipes) -- [Salesforce Commerce Apex Reference](https://developer.salesforce.com/docs/commerce/salesforce-commerce/references/comm-apex-reference/cart-reference.html) -- [Expression (API)](https://cesarparra.github.io/expression/) -- [Nimble AMS Docs](https://nimbleuser.github.io/nams-api-docs/#/api-reference/) - ## ▶️ Available Commands ### Markdown diff --git a/examples/open-api/docs/openapi.json b/examples/open-api/docs/openapi.json index 43c4b59f..f2e60a56 100644 --- a/examples/open-api/docs/openapi.json +++ b/examples/open-api/docs/openapi.json @@ -10,7 +10,7 @@ } ], "paths": { - "AccountService/": { + "/AccountService/": { "description": "Account related operations", "get": { "tags": [ @@ -338,7 +338,7 @@ } } }, - "Contact/": { + "/Contact/": { "description": "Contact related operations", "get": { "tags": [ @@ -359,7 +359,7 @@ } } }, - "Order/": { + "/Order/": { "description": "Order related operations", "get": { "tags": [ diff --git a/examples/open-api/force-app/main/default/classes/dto/ChildClass.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/ChildClass.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/ChildClass.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/Reference1.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/Reference1.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/Reference1.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/Reference2.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/Reference2.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/Reference2.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/Reference3.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/Reference3.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/Reference3.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/Reference4.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/Reference4.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/Reference4.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/Reference5.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/Reference5.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/Reference5.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/Reference6.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/Reference6.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/Reference6.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/Reference7.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/Reference7.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/Reference7.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/dto/SampleClass.cls-meta.xml b/examples/open-api/force-app/main/default/classes/dto/SampleClass.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/dto/SampleClass.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls-meta.xml b/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceToSkip.cls-meta.xml b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceToSkip.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceToSkip.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls index f1847a67..c3a46488 100644 --- a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls @@ -1,7 +1,7 @@ /** * @description Contact related operations */ -@RestResource(urlMapping='/Contact/*') +@RestResource(UrlMapping='/Contact/*') global with sharing class SampleRestResourceWithInnerClass { /** * @description This is a sample HTTP Get method diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls-meta.xml b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls index c0981306..0742b893 100644 --- a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls @@ -1,7 +1,7 @@ /** * @description Order related operations */ -@RestResource(urlMapping='/Order/*') +@RestResource(UrlMapping='/Order/*') global with sharing class SampleRestResourceWithoutApexDocs { @HttpGet global static String doGet(String param1, Reference1 param2) { diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls-meta.xml b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/imgs/integration.png b/imgs/integration.png new file mode 100644 index 00000000..b6558d32 Binary files /dev/null and b/imgs/integration.png differ diff --git a/imgs/redocly.png b/imgs/redocly.png new file mode 100644 index 00000000..d0a27ae1 Binary files /dev/null and b/imgs/redocly.png differ diff --git a/imgs/vitepress-dark.png b/imgs/vitepress-dark.png new file mode 100644 index 00000000..ec473a32 Binary files /dev/null and b/imgs/vitepress-dark.png differ diff --git a/imgs/vitepress-light.png b/imgs/vitepress-light.png new file mode 100644 index 00000000..a69b5e54 Binary files /dev/null and b/imgs/vitepress-light.png differ diff --git a/package-lock.json b/package-lock.json index 4cb39f91..73c8a990 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cparra/apexdocs", - "version": "3.3.1", + "version": "3.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cparra/apexdocs", - "version": "3.3.1", + "version": "3.3.2", "license": "MIT", "dependencies": { "@cparra/apex-reflection": "2.15.0", diff --git a/package.json b/package.json index 01e49836..fb7e56ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cparra/apexdocs", - "version": "3.3.1", + "version": "3.3.2", "description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.", "keywords": [ "apex", diff --git a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts index 3951b896..22ad5fec 100644 --- a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +++ b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts @@ -24,13 +24,13 @@ it('should add a path based on the @UrlResource annotation on the class', functi const processor = new OpenApiDocsProcessor(noLogger); processor.onProcess(classMirror); - expect(processor.openApiModel.paths).toHaveProperty('Account/'); + expect(processor.openApiModel.paths).toHaveProperty('/Account/'); }); it('should respect slashes', function () { const annotationElementValue = { key: 'urlMapping', - value: "'v1/Account/*'", + value: "'/v1/Account/*'", }; const classMirror = new ClassMirrorBuilder() .addAnnotation(new AnnotationBuilder().addElementValue(annotationElementValue).build()) @@ -39,7 +39,7 @@ it('should respect slashes', function () { const processor = new OpenApiDocsProcessor(noLogger); processor.onProcess(classMirror); - expect(processor.openApiModel.paths).toHaveProperty('v1/Account/'); + expect(processor.openApiModel.paths).toHaveProperty('/v1/Account/'); }); it('should contain a path with a description when the class has an ApexDoc comment', function () { @@ -55,5 +55,5 @@ it('should contain a path with a description when the class has an ApexDoc comme const processor = new OpenApiDocsProcessor(noLogger); processor.onProcess(classMirror); - expect(processor.openApiModel.paths['Account/'].description).toBe('My Description'); + expect(processor.openApiModel.paths['/Account/'].description).toBe('My Description'); }); diff --git a/src/core/openapi/open-api-docs-processor.ts b/src/core/openapi/open-api-docs-processor.ts index 5f076348..4dca32f9 100644 --- a/src/core/openapi/open-api-docs-processor.ts +++ b/src/core/openapi/open-api-docs-processor.ts @@ -84,10 +84,10 @@ export class OpenApiDocsProcessor { return null; } - let endpointPath = urlMapping.value.replaceAll('"', '').replaceAll("'", '').replaceAll('/*', '/'); - if (endpointPath.startsWith('/')) { - endpointPath = endpointPath.substring(1); - } - return endpointPath; + // The OpenApi path needs to start with a leading slash, but + // Salesforce @RestResource annotations already require a leading slash, + // so no need to check for it. + // See URL Guidelines: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_rest_resource.htm + return urlMapping.value.replaceAll('"', '').replaceAll("'", '').replaceAll('/*', '/'); } }