diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml
new file mode 100644
index 0000000..bdd47aa
--- /dev/null
+++ b/.github/workflows/bump-version.yml
@@ -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 "ci-bot@tellihealth.com"
+ git config --global user.name "CI Bot"
+ npm version patch -m "ci: bump version to %s"
+ git push origin main --follow-tags
diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml
index 0fcd3be..da609b6 100644
--- a/.github/workflows/node.yml
+++ b/.github/workflows/node.yml
@@ -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"
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index e739a1d..d416e80 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -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}}
diff --git a/README.md b/README.md
index 5a64520..96f4418 100644
--- a/README.md
+++ b/README.md
@@ -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.
@@ -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 |
@@ -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.
diff --git a/package-lock.json b/package-lock.json
index cfeb455..cb704e6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -484,20 +484,22 @@
}
},
"node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -1045,10 +1047,11 @@
}
},
"node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1445,6 +1448,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -2655,6 +2659,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@@ -3369,20 +3374,20 @@
"dev": true
},
"brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"requires": {
"balanced-match": "^1.0.0"
}
},
"braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"requires": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
}
},
"browser-stdout": {
@@ -3771,9 +3776,9 @@
"integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA=="
},
"fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
diff --git a/package.json b/package.json
index 4ec37dd..f4183d6 100644
--- a/package.json
+++ b/package.json
@@ -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",
@@ -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": {
@@ -62,4 +62,4 @@
"nock": "^14.0.2",
"sinon": "^20.0.0"
}
-}
+}
\ No newline at end of file
diff --git a/src/definitionGenerator.js b/src/definitionGenerator.js
index 9bfb223..980fc51 100644
--- a/src/definitionGenerator.js
+++ b/src/definitionGenerator.js
@@ -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();
@@ -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 {
@@ -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 = [];
diff --git a/src/owasp.js b/src/owasp.js
index 5189e5c..a1eb611 100644
--- a/src/owasp.js
+++ b/src/owasp.js
@@ -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 [](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame), [