Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/bump-version.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Bump Version on Merge to Main

on:
push:
branches: [main]

jobs:
bump-version:
runs-on: ubuntu-latest

if: "!contains(github.event.head_commit.message, 'ci: bump version')" # skip version bump commits

steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Bump version (patch)
run: |
git config --global user.email "[email protected]"
git config --global user.name "CI Bot"
npm version patch -m "ci: bump version to %s"
git push origin main --follow-tags
8 changes: 5 additions & 3 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ jobs:

strategy:
matrix:
node-version: [20.x, 22.x]
node-version: [20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

if: "!contains(github.event.head_commit.message, 'ci: bump version')" # skip version bump commits

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
Expand Down
24 changes: 8 additions & 16 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,18 @@ on:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm ci
# - run: npm test

publish-npm:
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org/
node-version: ${{ matrix.node-version }}
registry-url: https://npm.pkg.github.com/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ To add this plugin to your package.json:
**Using npm:**

```bash
npm install --save-dev serverless-openapi-documenter
npm install --save-dev @telllihealth/serverless-openapi-documenter
```

Next you need to add the plugin to the `plugins` section of your `serverless.yml` file.

```yml
plugins:
- serverless-openapi-documenter
- "@telllihealth/serverless-openapi-documenter"
```

> Note: Add this plugin _after_ `serverless-offline` to prevent issues with `String.replaceAll` being overridden incorrectly.
Expand Down Expand Up @@ -103,6 +103,8 @@ Options:
| tags[].externalDocs.url | `custom.documentation.tags.externalDocumentation.url` |
| tags[].externalDocs.description | `custom.documentation.tags.externalDocumentation.description` |
| tags[].externalDocs.x- | `custom.documentation.tags.externalDocumentation.x-` if extended specifications provided |
| basePath | `custom.documentation.basePath`Specifies the base path to prepend to all Lambda HTTP function paths in the generated OpenAPI specification |
| paths | `custom.documentation.paths` OpenAPI paths that are not backed by Lambda functions |
| path[path] | functions.functions.events.[http OR httpApi].path |
| path[path].servers[].description | functions.functions.servers.description |
| path[path].servers[].url | functions.functions.servers.url |
Expand Down Expand Up @@ -545,6 +547,53 @@ functions:
application/json: ${file(create_request.json)}
```

#### Adding non-Lambda endpoints with `paths`

You can add OpenAPI paths that are not backed by Lambda functions using the `paths` field under `custom.documentation`. This is useful for documenting endpoints handled outside of Serverless functions, such as static assets, third-party integrations, or legacy APIs.

The `paths` field should follow the [OpenAPI Paths Object](https://spec.openapis.org/oas/v3.0.4#paths-object) structure. Any paths defined here will be merged into the generated OpenAPI document alongside Lambda-backed endpoints.

**Example:**

```yml
custom:
documentation:
title: My API
version: "1"
paths:
/static/health:
get:
summary: Health check for static endpoint
description: Returns status of the static service
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
/external/webhook:
post:
summary: Webhook endpoint
description: Receives webhook calls from external service
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
event:
type: string
responses:
"204":
description: No Content
```

#### Functions

To define the documentation for a given function event, you need to create a `documentation` attribute for your `http` or `httpApi` event in your `serverless.yml` file.
Expand Down
45 changes: 25 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "serverless-openapi-documenter",
"name": "@tellihealth/serverless-openapi-documenter",
"version": "0.0.113",
"description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
"main": "index.js",
Expand Down Expand Up @@ -38,10 +38,10 @@
},
"repository": {
"type": "git",
"url": "https://github.com/JaredCE/serverless-openapi-documenter.git"
"url": "https://github.com/tellihealth/serverless-openapi-documenter.git"
},
"bugs": {
"url": "https://github.com/JaredCE/serverless-openapi-documenter/issues"
"url": "https://github.com/tellihealth/serverless-openapi-documenter/issues"
},
"license": "MIT",
"dependencies": {
Expand All @@ -62,4 +62,4 @@
"nock": "^14.0.2",
"sinon": "^20.0.0"
}
}
}
49 changes: 46 additions & 3 deletions src/definitionGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,13 @@ class DefinitionGenerator {
}
}

await this.createPaths().catch((err) => {
throw err;
});
await this.createPaths()
.then(() => {
this.mergeExistingPaths();
})
.catch((err) => {
throw err;
});

this.cleanupLinks();

Expand Down Expand Up @@ -255,6 +259,13 @@ class DefinitionGenerator {
slashPath = `/${(event?.http?.path || event.httpApi?.path) ?? ""}`;
}

const basePath = this.getBasePath();

if (basePath) {
// append the base path so server is just the pure domain
slashPath = `/${basePath}${slashPath}`;
}

if (paths[slashPath]) {
Object.assign(paths[slashPath], path);
} else {
Expand All @@ -266,6 +277,38 @@ class DefinitionGenerator {
Object.assign(this.openAPI, { paths });
}

mergeExistingPaths() {
const paths = this.serverless.service.custom.documentation.paths;

if (paths) {
const basePath = this.getBasePath();

if (basePath) {
// check is paths's keys are not starting with `basePath` prefix and if set append it
for (const key of Object.keys(paths)) {
if (!key.startsWith(`/${basePath}`)) {
paths[`/${basePath}${key}`] = paths[key];
delete paths[key];
}
}
}

const origPaths = this.openAPI.paths || {};

Object.assign(this.openAPI, { paths: { ...origPaths, ...paths } });
}
}

/**
* @description Retrieves the basePath value if set, allowing the server URL to be just the plain domain. The `basePath` will be prepended to each Lambda HTTP path. If `basePath` starts with a slash (/) returns the basePath without the leading slash.
* @returns {string}
*/
getBasePath() {
const basePath =
this.serverless.service.custom.documentation.basePath || "";
return basePath.replace(/^\//, "");
}

createServers(servers) {
const serverDoc = servers;
const newServers = [];
Expand Down
4 changes: 4 additions & 0 deletions src/owasp.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class OWASP {
description:
"The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) advertised in the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) headers should be followed and not be changed. The header allows you to avoid [MIME type sniffing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#mime_sniffing) by saying that the MIME types are deliberately configured. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)",
},
"X-DNS-Prefetch-Control": {
description: "Controls DNS prefetching.",
},
"X-Frame-Options": {
description:
"The X-Frame-Options [HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) response header can be used to indicate whether or not a browser should be allowed to render a page in a [<frame>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame), [<iframe>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), [<embed>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed) or [<object>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object). Sites can use this to avoid [click-jacking](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#click-jacking) attacks, by ensuring that their content is not embedded into other sites. - [MDN Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)",
Expand All @@ -81,6 +84,7 @@ class OWASP {
referrerPolicy: "Referrer-Policy",
strictTransportSecurity: "Strict-Transport-Security",
xContentTypeOptions: "X-Content-Type-Options",
xDnsPrefetchControl: "X-DNS-Prefetch-Control",
xFrameOptions: "X-Frame-Options",
xPermittedCrossDomainPolicies: "X-Permitted-Cross-Domain-Policies",
};
Expand Down
Loading