diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 67bb2aaf..4a2a7e4f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -50,7 +50,7 @@ jobs:
e2e:
needs: [changes, lint_unit_tests_and_builds]
- if: needs.changes.outputs.renderer == 'true' && (github.event_name == 'push' || github.event.pull_request.draft == false)
+ if: needs.changes.outputs.renderer == 'true'
runs-on: ubuntu-latest
defaults:
run:
@@ -78,6 +78,8 @@ jobs:
retention-days: 30
changes:
+ # nothing runs on a draft; marking it ready is a trigger of its own
+ if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
outputs:
renderer: ${{ steps.filter.outputs.renderer }}
@@ -90,8 +92,10 @@ jobs:
filters: |
renderer:
- '!integrations/**'
+ - '!examples/integrations/**'
integrations:
- 'integrations/**'
+ - 'examples/integrations/**'
- '.github/workflows/ci.yml'
- '.github/workflows/integrations-matrix.yml'
diff --git a/.github/workflows/integrations-matrix.yml b/.github/workflows/integrations-matrix.yml
index 75aa221e..a3fa40b6 100644
--- a/.github/workflows/integrations-matrix.yml
+++ b/.github/workflows/integrations-matrix.yml
@@ -6,7 +6,7 @@ on:
- cron: '0 6 * * 1'
pull_request:
branches: [main, 'release/**']
- types: [labeled, synchronize]
+ types: [labeled, synchronize, ready_for_review]
permissions:
contents: read
@@ -22,7 +22,7 @@ env:
jobs:
cells:
- if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-integrations-matrix-tests')
+ if: github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'run-integrations-matrix-tests'))
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -53,7 +53,7 @@ jobs:
path: integrations/results
node-20:
- if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-integrations-matrix-tests')
+ if: github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'run-integrations-matrix-tests'))
runs-on: ubuntu-latest
continue-on-error: true
name: node 20 (end of life, courtesy)
diff --git a/README.md b/README.md
index 99589f03..6fa886f7 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ See [Auto-generate documentation](https://docs.usebruno.com/api-docs/auto-genera
| Path | What it is |
|---|---|
| [`packages/bruno-api-docs`](./packages/bruno-api-docs) | The renderer: a React app built as a library and as the **standalone bundle** (`dist-standalone/`), the artifact the Bruno app's generated HTML loads from the CDN. |
-| [`examples/`](./examples) | Consumption patterns: `standalone-html` (how a generated HTML file uses the bundle), `react` and `express-server` (design sketches of the intended embedding API; not shipped packages yet, see Roadmap). |
+| [`examples/`](./examples) | `standalone-html`, how a generated HTML page uses the bundle. `integrations/nodejs`, runnable examples for the framework packages, one folder per framework. |
## Roadmap
diff --git a/examples/express-server/package-lock.json b/examples/express-server/package-lock.json
deleted file mode 100644
index 02c94549..00000000
--- a/examples/express-server/package-lock.json
+++ /dev/null
@@ -1,951 +0,0 @@
-{
- "name": "opencollection-express-example",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "opencollection-express-example",
- "version": "1.0.0",
- "license": "MIT",
- "dependencies": {
- "express": "^4.18.2"
- },
- "devDependencies": {
- "@types/express": "^4.17.21"
- }
- },
- "node_modules/@types/body-parser": {
- "version": "1.19.6",
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
- "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/connect": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/connect": {
- "version": "3.4.38",
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
- "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/express": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
- "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/body-parser": "*",
- "@types/express-serve-static-core": "^4.17.33",
- "@types/qs": "*",
- "@types/serve-static": "*"
- }
- },
- "node_modules/@types/express-serve-static-core": {
- "version": "4.19.6",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
- "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@types/qs": "*",
- "@types/range-parser": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/http-errors": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
- "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/mime": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
- "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "24.5.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz",
- "integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.12.0"
- }
- },
- "node_modules/@types/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/range-parser": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
- "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/send": {
- "version": "0.17.5",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
- "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/mime": "^1",
- "@types/node": "*"
- }
- },
- "node_modules/@types/serve-static": {
- "version": "1.15.8",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
- "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/http-errors": "*",
- "@types/node": "*",
- "@types/send": "*"
- }
- },
- "node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
- "license": "MIT",
- "dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "license": "MIT"
- },
- "node_modules/body-parser": {
- "version": "1.20.3",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
- "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
- "license": "MIT",
- "dependencies": {
- "bytes": "3.1.2",
- "content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "on-finished": "2.4.1",
- "qs": "6.13.0",
- "raw-body": "2.5.2",
- "type-is": "~1.6.18",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "license": "MIT"
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/express": {
- "version": "4.21.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
- "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
- "license": "MIT",
- "dependencies": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "1.20.3",
- "content-disposition": "0.5.4",
- "content-type": "~1.0.4",
- "cookie": "0.7.1",
- "cookie-signature": "1.0.6",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "1.3.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "merge-descriptors": "1.0.3",
- "methods": "~1.1.2",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "path-to-regexp": "0.1.12",
- "proxy-addr": "~2.0.7",
- "qs": "6.13.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "0.19.0",
- "serve-static": "1.16.2",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- },
- "engines": {
- "node": ">= 0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/finalhandler": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
- "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "2.0.1",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "license": "MIT",
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "license": "MIT"
- },
- "node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
- "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
- "license": "MIT"
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.0.6"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
- "license": "MIT",
- "dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "license": "MIT"
- },
- "node_modules/send": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
- "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "2.0.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/send/node_modules/encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/serve-static": {
- "version": "1.16.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
- "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
- "license": "MIT",
- "dependencies": {
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.19.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "license": "ISC"
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "license": "MIT",
- "dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/undici-types": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
- "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- }
- }
-}
diff --git a/examples/express-server/package.json b/examples/express-server/package.json
deleted file mode 100644
index 93077f6e..00000000
--- a/examples/express-server/package.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "name": "opencollection-express-example",
- "version": "1.0.0",
- "description": "Express server example using OpenCollection playground",
- "main": "server.js",
- "type": "module",
- "scripts": {
- "start": "node server.js",
- "start:cjs": "node server-cjs.cjs",
- "dev": "node --watch server.js",
- "dev:cjs": "node --watch server-cjs.cjs",
- "test": "node test.js"
- },
- "dependencies": {
- "express": "^4.18.2"
- },
- "devDependencies": {
- "@types/express": "^4.17.21"
- },
- "keywords": [
- "opencollection",
- "express",
- "api-docs",
- "playground"
- ],
- "author": "OpenCollection",
- "license": "MIT"
-}
\ No newline at end of file
diff --git a/examples/express-server/server-cjs.cjs b/examples/express-server/server-cjs.cjs
deleted file mode 100644
index 1df77472..00000000
--- a/examples/express-server/server-cjs.cjs
+++ /dev/null
@@ -1,117 +0,0 @@
-const express = require('express');
-const { playgroundHandler, setupPlaygroundAssets } = require('../../packages/oc-playground/dist/express.cjs');
-
-const app = express();
-const PORT = process.env.PORT || 3001;
-
-setupPlaygroundAssets(app, express.static, {
- playgroundDistPath: '../../packages/oc-playground/dist-standalone'
-});
-
-const apiCollection = {
- name: "My Express API (CommonJS)",
- description: "Example API documentation using OpenCollection playground with Express and CommonJS",
- docs: "This collection demonstrates different request types, bodies, auth, variables, and assertions for an Express-based API using CommonJS imports.",
- environments: [
- {
- name: "Development",
- description: "Local development environment",
- variables: [
- { name: "baseUrl", value: `http://localhost:${PORT}` },
- { name: "authToken", value: "dev-token-123" }
- ]
- }
- ],
- base: {
- headers: [
- { name: "Content-Type", value: "application/json" }
- ],
- auth: {
- type: "bearer",
- token: "{{authToken}}"
- }
- },
- items: [
- {
- name: "Get Users",
- type: "http",
- url: "{{baseUrl}}/api/users",
- method: "GET",
- docs: "Retrieve a list of all users",
- params: [
- { name: "limit", value: "10", type: "query" }
- ],
- assertions: [
- { expression: "response.status", operator: "==", value: "200" }
- ]
- },
- {
- name: "Create User",
- type: "http",
- url: "{{baseUrl}}/api/users",
- method: "POST",
- docs: "Create a new user",
- body: {
- type: "json",
- data: JSON.stringify({
- name: "Bob Wilson",
- email: "bob@example.com"
- }, null, 2)
- }
- }
- ]
-};
-
-app.get('/api-docs', playgroundHandler({
- collection: apiCollection,
- theme: 'dark',
- title: 'My Express API Documentation (CommonJS)'
-}));
-
-app.get('/simple-docs', playgroundHandler({
- name: "Simple API (CommonJS)",
- description: "My awesome API documentation using CommonJS",
- items: [{
- type: "http",
- method: "GET",
- url: "https://api.example.com/users",
- docs: "Get all users"
- }]
-}));
-
-app.get('/api/users', (req, res) => {
- res.json({
- users: [
- { id: 1, name: 'Bob Wilson', email: 'bob@example.com' },
- { id: 2, name: 'Carol Davis', email: 'carol@example.com' }
- ]
- });
-});
-
-app.post('/api/users', express.json(), (req, res) => {
- const { name, email } = req.body;
- res.status(201).json({
- id: Date.now(),
- name,
- email,
- createdAt: new Date().toISOString()
- });
-});
-
-app.get('/', (req, res) => {
- res.send(`
-
OpenCollection Express Example (CommonJS)
- Visit the API documentation:
-
- `);
-});
-
-app.listen(PORT, () => {
- console.log(`🚀 CommonJS Server running on http://localhost:${PORT}`);
- console.log(`📚 API docs available at:`);
- console.log(` - http://localhost:${PORT}/api-docs`);
- console.log(` - http://localhost:${PORT}/simple-docs`);
-});
\ No newline at end of file
diff --git a/examples/express-server/server.js b/examples/express-server/server.js
deleted file mode 100644
index c421bca5..00000000
--- a/examples/express-server/server.js
+++ /dev/null
@@ -1,247 +0,0 @@
-import express from 'express';
-import { playgroundHandler, setupPlaygroundAssets } from '../../packages/oc-playground/dist/express.js';
-
-const app = express();
-const PORT = process.env.PORT || 3000;
-
-setupPlaygroundAssets(app, express.static, {
- playgroundDistPath: '../../packages/oc-playground/dist-standalone'
-});
-
-const apiCollection = {
- name: "My Express API",
- description: "Example API documentation using OpenCollection playground with Express",
- docs: "This collection demonstrates different request types, bodies, auth, variables, and assertions for an Express-based API.",
- environments: [
- {
- name: "Development",
- description: "Local development environment",
- variables: [
- { name: "baseUrl", value: `http://localhost:${PORT}` },
- { name: "authToken", value: "dev-token-123" }
- ]
- },
- {
- name: "Production",
- variables: [
- { name: "baseUrl", value: "https://api.example.com" },
- { name: "authToken", value: "prod-token-456" }
- ]
- }
- ],
- base: {
- headers: [
- { name: "Content-Type", value: "application/json" }
- ],
- auth: {
- type: "bearer",
- token: "{{authToken}}"
- },
- variables: [
- { name: "globalVar", value: "global-value" }
- ]
- },
- items: [
- {
- name: "Get Users",
- type: "http",
- url: "{{baseUrl}}/api/users",
- method: "GET",
- docs: "Retrieve a list of all users",
- params: [
- { name: "limit", value: "10", type: "query" },
- { name: "offset", value: "0", type: "query" }
- ],
- assertions: [
- { expression: "response.status", operator: "==", value: "200" }
- ]
- },
- {
- name: "Get User by ID",
- type: "http",
- url: "{{baseUrl}}/api/users/:id",
- method: "GET",
- docs: "Get a specific user by their ID",
- params: [
- { name: "id", value: "123", type: "path" }
- ],
- headers: [
- { name: "X-Custom-Header", value: "CustomValue" }
- ]
- },
- {
- name: "Create User",
- type: "http",
- url: "{{baseUrl}}/api/users",
- method: "POST",
- docs: "Create a new user",
- body: {
- type: "json",
- data: JSON.stringify({
- name: "Alice Johnson",
- email: "alice@example.com",
- role: "user"
- }, null, 2)
- },
- assertions: [
- { expression: "response.body.name", operator: "==", value: "Alice Johnson" },
- { expression: "response.status", operator: "==", value: "201" }
- ]
- },
- {
- name: "Update User",
- type: "http",
- url: "{{baseUrl}}/api/users/:id",
- method: "PUT",
- docs: "Update an existing user",
- params: [
- { name: "id", value: "123", type: "path" }
- ],
- body: [
- { name: "name", value: "Alice Smith" },
- { name: "email", value: "alice.smith@example.com" }
- ]
- },
- {
- name: "Delete User",
- type: "http",
- url: "{{baseUrl}}/api/users/:id",
- method: "DELETE",
- docs: "Delete a user by ID",
- params: [
- { name: "id", value: "123", type: "path" }
- ],
- auth: {
- type: "apikey",
- key: "X-API-Key",
- value: "secret-key-999",
- placement: "header"
- }
- },
- {
- name: "User Management",
- type: "folder",
- docs: "Folder containing advanced user management endpoints",
- variables: [
- { name: "adminUserId", value: "admin-123" }
- ],
- headers: [
- { name: "X-Admin-Header", value: "AdminHeaderValue" }
- ],
- auth: {
- type: "basic",
- username: "admin",
- password: "password"
- }
- },
- {
- name: "Get Admin Dashboard",
- type: "http",
- url: "{{baseUrl}}/api/admin/dashboard",
- method: "GET",
- docs: "Access the admin dashboard (requires admin auth)"
- }
- ]
-};
-
-app.get('/api-docs', playgroundHandler({
- collection: apiCollection,
- theme: 'light',
- title: 'My Express API Documentation'
-}));
-
-app.get('/simple-docs', playgroundHandler({
- name: "Simple API",
- description: "My awesome API documentation",
- items: [{
- type: "http",
- method: "GET",
- url: "https://api.example.com/users",
- docs: "Get all users"
- }],
- theme: 'dark'
-}));
-
-app.get('/api/users', (req, res) => {
- const { limit = 10, offset = 0 } = req.query;
- res.json({
- users: [
- { id: 1, name: 'John Doe', email: 'john@example.com' },
- { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
- ].slice(offset, offset + parseInt(limit)),
- total: 2,
- limit: parseInt(limit),
- offset: parseInt(offset)
- });
-});
-
-app.get('/api/users/:id', (req, res) => {
- const { id } = req.params;
- res.json({
- id: parseInt(id),
- name: 'John Doe',
- email: 'john@example.com',
- role: 'user'
- });
-});
-
-app.post('/api/users', express.json(), (req, res) => {
- const { name, email, role = 'user' } = req.body;
- res.status(201).json({
- id: Date.now(),
- name,
- email,
- role,
- createdAt: new Date().toISOString()
- });
-});
-
-app.put('/api/users/:id', express.json(), (req, res) => {
- const { id } = req.params;
- const { name, email } = req.body;
- res.json({
- id: parseInt(id),
- name,
- email,
- updatedAt: new Date().toISOString()
- });
-});
-
-app.delete('/api/users/:id', (req, res) => {
- const { id } = req.params;
- res.status(204).send();
-});
-
-app.get('/api/admin/dashboard', (req, res) => {
- res.json({
- stats: {
- totalUsers: 42,
- activeUsers: 38,
- newUsersToday: 5
- },
- message: 'Admin dashboard data'
- });
-});
-
-app.get('/', (req, res) => {
- res.send(`
- OpenCollection Express Example
- Visit the API documentation:
-
- Try the API endpoints:
-
- `);
-});
-
-app.listen(PORT, () => {
- console.log(`🚀 Server running on http://localhost:${PORT}`);
- console.log(`📚 API docs available at:`);
- console.log(` - http://localhost:${PORT}/api-docs`);
- console.log(` - http://localhost:${PORT}/simple-docs`);
-});
\ No newline at end of file
diff --git a/examples/integrations/eslint.config.mjs b/examples/integrations/eslint.config.mjs
new file mode 100644
index 00000000..dd7c78f5
--- /dev/null
+++ b/examples/integrations/eslint.config.mjs
@@ -0,0 +1,7 @@
+// eslint lints nothing above its config, so the workspace rules are applied from here
+import { ignores, workspace } from '../../integrations/eslint.config.mjs';
+
+export default [
+ ignores,
+ { ...workspace, files: ['**/*.{js,ts}'], rules: { ...workspace.rules, 'no-console': 'off' } }
+];
diff --git a/integrations/nodejs/express/examples/api-collection/catalog/folder.yml b/examples/integrations/nodejs/express/api-collection/catalog/folder.yml
similarity index 100%
rename from integrations/nodejs/express/examples/api-collection/catalog/folder.yml
rename to examples/integrations/nodejs/express/api-collection/catalog/folder.yml
diff --git a/integrations/nodejs/express/examples/api-collection/catalog/get product.yml b/examples/integrations/nodejs/express/api-collection/catalog/get product.yml
similarity index 100%
rename from integrations/nodejs/express/examples/api-collection/catalog/get product.yml
rename to examples/integrations/nodejs/express/api-collection/catalog/get product.yml
diff --git a/integrations/nodejs/express/examples/api-collection/catalog/list products.yml b/examples/integrations/nodejs/express/api-collection/catalog/list products.yml
similarity index 100%
rename from integrations/nodejs/express/examples/api-collection/catalog/list products.yml
rename to examples/integrations/nodejs/express/api-collection/catalog/list products.yml
diff --git a/integrations/nodejs/express/examples/api-collection/opencollection.yml b/examples/integrations/nodejs/express/api-collection/opencollection.yml
similarity index 100%
rename from integrations/nodejs/express/examples/api-collection/opencollection.yml
rename to examples/integrations/nodejs/express/api-collection/opencollection.yml
diff --git a/integrations/nodejs/express/examples/auth.js b/examples/integrations/nodejs/express/auth.js
similarity index 90%
rename from integrations/nodejs/express/examples/auth.js
rename to examples/integrations/nodejs/express/auth.js
index 75bba061..0df73d4d 100644
--- a/integrations/nodejs/express/examples/auth.js
+++ b/examples/integrations/nodejs/express/auth.js
@@ -20,6 +20,6 @@ app.get('/health', (req, res) => res.json({ ok: true }));
// the guard runs before the docs on this mount and nowhere else. It covers collection.yml too,
// which is the file that carries the collection's own auth and variables.
-app.use('/docs', requireToken, apiDocs({ collectionUrl: './api-collection' }));
+app.use('/docs', requireToken, apiDocs({ collectionPath: './api-collection' }));
app.listen(port, () => console.log(`docs at http://localhost:${port}/docs/ (Authorization: Bearer let-me-in)`));
diff --git a/integrations/nodejs/express/examples/csp.js b/examples/integrations/nodejs/express/csp.js
similarity index 94%
rename from integrations/nodejs/express/examples/csp.js
rename to examples/integrations/nodejs/express/csp.js
index 369bdbec..c96aa24e 100644
--- a/integrations/nodejs/express/examples/csp.js
+++ b/examples/integrations/nodejs/express/csp.js
@@ -25,6 +25,6 @@ app.use(helmet({
app.get('/health', (req, res) => res.json({ ok: true }));
-app.use('/docs', apiDocs({ collectionUrl: './api-collection' }));
+app.use('/docs', apiDocs({ collectionPath: './api-collection' }));
app.listen(port, () => console.log(`docs at http://localhost:${port}/docs/`));
diff --git a/integrations/nodejs/express/examples/embed.js b/examples/integrations/nodejs/express/embed.js
similarity index 90%
rename from integrations/nodejs/express/examples/embed.js
rename to examples/integrations/nodejs/express/embed.js
index f521f8e4..9b8e40a2 100644
--- a/integrations/nodejs/express/examples/embed.js
+++ b/examples/integrations/nodejs/express/embed.js
@@ -8,7 +8,7 @@ const port = process.env.PORT ?? 3000;
// the docs inside a page of your own. The mount serves the collection and shell.js; the block
// only has to know where the mount is.
-app.use('/docs', apiDocs({ collectionUrl: './api-collection' }));
+app.use('/docs', apiDocs({ collectionPath: './api-collection' }));
app.get('/', (req, res) => {
res.type('html').send(`
diff --git a/integrations/nodejs/express/examples/quickstart.js b/examples/integrations/nodejs/express/quickstart.js
similarity index 85%
rename from integrations/nodejs/express/examples/quickstart.js
rename to examples/integrations/nodejs/express/quickstart.js
index 58f41f80..cd3f82fd 100644
--- a/integrations/nodejs/express/examples/quickstart.js
+++ b/examples/integrations/nodejs/express/quickstart.js
@@ -9,6 +9,6 @@ const port = process.env.PORT ?? 3000;
app.get('/health', (req, res) => res.json({ ok: true }));
// the path is relative to this file, not to where node was started
-app.use('/docs', apiDocs({ collectionUrl: './api-collection' }));
+app.use('/docs', apiDocs({ collectionPath: './api-collection' }));
app.listen(port, () => console.log(`docs at http://localhost:${port}/docs/`));
diff --git a/integrations/nodejs/fastify/examples/api-collection/catalog/folder.yml b/examples/integrations/nodejs/fastify/api-collection/catalog/folder.yml
similarity index 100%
rename from integrations/nodejs/fastify/examples/api-collection/catalog/folder.yml
rename to examples/integrations/nodejs/fastify/api-collection/catalog/folder.yml
diff --git a/integrations/nodejs/fastify/examples/api-collection/catalog/get product.yml b/examples/integrations/nodejs/fastify/api-collection/catalog/get product.yml
similarity index 100%
rename from integrations/nodejs/fastify/examples/api-collection/catalog/get product.yml
rename to examples/integrations/nodejs/fastify/api-collection/catalog/get product.yml
diff --git a/integrations/nodejs/fastify/examples/api-collection/catalog/list products.yml b/examples/integrations/nodejs/fastify/api-collection/catalog/list products.yml
similarity index 100%
rename from integrations/nodejs/fastify/examples/api-collection/catalog/list products.yml
rename to examples/integrations/nodejs/fastify/api-collection/catalog/list products.yml
diff --git a/integrations/nodejs/fastify/examples/api-collection/opencollection.yml b/examples/integrations/nodejs/fastify/api-collection/opencollection.yml
similarity index 100%
rename from integrations/nodejs/fastify/examples/api-collection/opencollection.yml
rename to examples/integrations/nodejs/fastify/api-collection/opencollection.yml
diff --git a/integrations/nodejs/fastify/examples/auth.js b/examples/integrations/nodejs/fastify/auth.js
similarity index 91%
rename from integrations/nodejs/fastify/examples/auth.js
rename to examples/integrations/nodejs/fastify/auth.js
index aec69de3..e4331656 100644
--- a/integrations/nodejs/fastify/examples/auth.js
+++ b/examples/integrations/nodejs/fastify/auth.js
@@ -19,7 +19,7 @@ app.register(async (guarded) => {
}
});
- await guarded.register(apiDocs, { prefix: '/docs', collectionUrl: './api-collection' });
+ await guarded.register(apiDocs, { prefix: '/docs', collectionPath: './api-collection' });
});
app.listen({ port }).then(() => console.log(`docs at http://localhost:${port}/docs/ (Authorization: Bearer let-me-in)`));
diff --git a/integrations/nodejs/fastify/examples/csp.js b/examples/integrations/nodejs/fastify/csp.js
similarity index 93%
rename from integrations/nodejs/fastify/examples/csp.js
rename to examples/integrations/nodejs/fastify/csp.js
index 3f28a623..766bd293 100644
--- a/integrations/nodejs/fastify/examples/csp.js
+++ b/examples/integrations/nodejs/fastify/csp.js
@@ -24,6 +24,6 @@ app.addHook('onSend', async (request, reply) => {
app.get('/health', async () => ({ ok: true }));
-app.register(apiDocs, { prefix: '/docs', collectionUrl: './api-collection' });
+app.register(apiDocs, { prefix: '/docs', collectionPath: './api-collection' });
app.listen({ port }).then(() => console.log(`docs at http://localhost:${port}/docs/`));
diff --git a/integrations/nodejs/fastify/examples/embed.js b/examples/integrations/nodejs/fastify/embed.js
similarity index 89%
rename from integrations/nodejs/fastify/examples/embed.js
rename to examples/integrations/nodejs/fastify/embed.js
index c9c72156..3d76a10d 100644
--- a/integrations/nodejs/fastify/examples/embed.js
+++ b/examples/integrations/nodejs/fastify/embed.js
@@ -8,7 +8,7 @@ const port = process.env.PORT ?? 3000;
// the docs inside a page of your own. The mount serves the collection and shell.js; the block
// only has to know where the mount is.
-app.register(apiDocs, { prefix: '/docs', collectionUrl: './api-collection' });
+app.register(apiDocs, { prefix: '/docs', collectionPath: './api-collection' });
app.get('/', async (request, reply) => {
return reply.type('text/html').send(`
diff --git a/integrations/nodejs/fastify/examples/quickstart.js b/examples/integrations/nodejs/fastify/quickstart.js
similarity index 83%
rename from integrations/nodejs/fastify/examples/quickstart.js
rename to examples/integrations/nodejs/fastify/quickstart.js
index f7f94a0e..e1077558 100644
--- a/integrations/nodejs/fastify/examples/quickstart.js
+++ b/examples/integrations/nodejs/fastify/quickstart.js
@@ -9,6 +9,6 @@ const port = process.env.PORT ?? 3000;
app.get('/health', async () => ({ ok: true }));
// prefix is the mount. The collection path is relative to this file, not to where node was started.
-app.register(apiDocs, { prefix: '/docs', collectionUrl: './api-collection' });
+app.register(apiDocs, { prefix: '/docs', collectionPath: './api-collection' });
app.listen({ port }).then(() => console.log(`docs at http://localhost:${port}/docs/`));
diff --git a/integrations/nodejs/nestjs/examples/01-quickstart/app.module.ts b/examples/integrations/nodejs/nestjs/01-quickstart/app.module.ts
similarity index 82%
rename from integrations/nodejs/nestjs/examples/01-quickstart/app.module.ts
rename to examples/integrations/nodejs/nestjs/01-quickstart/app.module.ts
index 57777936..9c4a09f7 100644
--- a/integrations/nodejs/nestjs/examples/01-quickstart/app.module.ts
+++ b/examples/integrations/nodejs/nestjs/01-quickstart/app.module.ts
@@ -5,7 +5,7 @@ import { ApiDocsModule } from '@usebruno/api-docs-nestjs';
imports: [
// relative to the compiled entry file, dist/01-quickstart/main.js, not to this source file
// and not to the cwd. From there, examples/api-collection is two levels up.
- ApiDocsModule.forRoot({ collectionUrl: '../../api-collection' })
+ ApiDocsModule.forRoot({ collectionPath: '../../api-collection' })
]
})
export class AppModule {}
diff --git a/integrations/nodejs/nestjs/examples/01-quickstart/main.ts b/examples/integrations/nodejs/nestjs/01-quickstart/main.ts
similarity index 100%
rename from integrations/nodejs/nestjs/examples/01-quickstart/main.ts
rename to examples/integrations/nodejs/nestjs/01-quickstart/main.ts
diff --git a/integrations/nodejs/nestjs/examples/02-auth/app.module.ts b/examples/integrations/nodejs/nestjs/02-auth/app.module.ts
similarity index 90%
rename from integrations/nodejs/nestjs/examples/02-auth/app.module.ts
rename to examples/integrations/nodejs/nestjs/02-auth/app.module.ts
index 46171f0d..57ea8f5a 100644
--- a/integrations/nodejs/nestjs/examples/02-auth/app.module.ts
+++ b/examples/integrations/nodejs/nestjs/02-auth/app.module.ts
@@ -14,8 +14,8 @@ function requireApiKey(req: Request, res: Response, next: NextFunction): void {
@Module({
imports: [
- ApiDocsModule.forRoot({ collectionUrl: '../../api-collection' }),
- ApiDocsModule.forRoot({ mountPath: '/internal/docs', collectionUrl: '../../api-collection' })
+ ApiDocsModule.forRoot({ collectionPath: '../../api-collection' }),
+ ApiDocsModule.forRoot({ mountPath: '/internal/docs', collectionPath: '../../api-collection' })
]
})
export class AppModule implements NestModule {
diff --git a/integrations/nodejs/nestjs/examples/02-auth/main.ts b/examples/integrations/nodejs/nestjs/02-auth/main.ts
similarity index 100%
rename from integrations/nodejs/nestjs/examples/02-auth/main.ts
rename to examples/integrations/nodejs/nestjs/02-auth/main.ts
diff --git a/integrations/nodejs/nestjs/examples/04-fastify-adapter/app.module.ts b/examples/integrations/nodejs/nestjs/03-global-prefix/app.module.ts
similarity index 63%
rename from integrations/nodejs/nestjs/examples/04-fastify-adapter/app.module.ts
rename to examples/integrations/nodejs/nestjs/03-global-prefix/app.module.ts
index 1878b959..bb0f9295 100644
--- a/integrations/nodejs/nestjs/examples/04-fastify-adapter/app.module.ts
+++ b/examples/integrations/nodejs/nestjs/03-global-prefix/app.module.ts
@@ -2,6 +2,6 @@ import { Module } from '@nestjs/common';
import { ApiDocsModule } from '@usebruno/api-docs-nestjs';
@Module({
- imports: [ApiDocsModule.forRoot({ collectionUrl: '../../api-collection' })]
+ imports: [ApiDocsModule.forRoot({ collectionPath: '../../api-collection' })]
})
export class AppModule {}
diff --git a/integrations/nodejs/nestjs/examples/03-global-prefix/main.ts b/examples/integrations/nodejs/nestjs/03-global-prefix/main.ts
similarity index 100%
rename from integrations/nodejs/nestjs/examples/03-global-prefix/main.ts
rename to examples/integrations/nodejs/nestjs/03-global-prefix/main.ts
diff --git a/integrations/nodejs/nestjs/examples/05-csp/app.module.ts b/examples/integrations/nodejs/nestjs/04-fastify-adapter/app.module.ts
similarity index 63%
rename from integrations/nodejs/nestjs/examples/05-csp/app.module.ts
rename to examples/integrations/nodejs/nestjs/04-fastify-adapter/app.module.ts
index 1878b959..bb0f9295 100644
--- a/integrations/nodejs/nestjs/examples/05-csp/app.module.ts
+++ b/examples/integrations/nodejs/nestjs/04-fastify-adapter/app.module.ts
@@ -2,6 +2,6 @@ import { Module } from '@nestjs/common';
import { ApiDocsModule } from '@usebruno/api-docs-nestjs';
@Module({
- imports: [ApiDocsModule.forRoot({ collectionUrl: '../../api-collection' })]
+ imports: [ApiDocsModule.forRoot({ collectionPath: '../../api-collection' })]
})
export class AppModule {}
diff --git a/integrations/nodejs/nestjs/examples/04-fastify-adapter/main.ts b/examples/integrations/nodejs/nestjs/04-fastify-adapter/main.ts
similarity index 100%
rename from integrations/nodejs/nestjs/examples/04-fastify-adapter/main.ts
rename to examples/integrations/nodejs/nestjs/04-fastify-adapter/main.ts
diff --git a/integrations/nodejs/nestjs/examples/03-global-prefix/app.module.ts b/examples/integrations/nodejs/nestjs/05-csp/app.module.ts
similarity index 63%
rename from integrations/nodejs/nestjs/examples/03-global-prefix/app.module.ts
rename to examples/integrations/nodejs/nestjs/05-csp/app.module.ts
index 1878b959..bb0f9295 100644
--- a/integrations/nodejs/nestjs/examples/03-global-prefix/app.module.ts
+++ b/examples/integrations/nodejs/nestjs/05-csp/app.module.ts
@@ -2,6 +2,6 @@ import { Module } from '@nestjs/common';
import { ApiDocsModule } from '@usebruno/api-docs-nestjs';
@Module({
- imports: [ApiDocsModule.forRoot({ collectionUrl: '../../api-collection' })]
+ imports: [ApiDocsModule.forRoot({ collectionPath: '../../api-collection' })]
})
export class AppModule {}
diff --git a/integrations/nodejs/nestjs/examples/05-csp/main.ts b/examples/integrations/nodejs/nestjs/05-csp/main.ts
similarity index 100%
rename from integrations/nodejs/nestjs/examples/05-csp/main.ts
rename to examples/integrations/nodejs/nestjs/05-csp/main.ts
diff --git a/integrations/nodejs/nestjs/examples/06-embed/app.module.ts b/examples/integrations/nodejs/nestjs/06-embed/app.module.ts
similarity index 74%
rename from integrations/nodejs/nestjs/examples/06-embed/app.module.ts
rename to examples/integrations/nodejs/nestjs/06-embed/app.module.ts
index c48d0e8f..088a766b 100644
--- a/integrations/nodejs/nestjs/examples/06-embed/app.module.ts
+++ b/examples/integrations/nodejs/nestjs/06-embed/app.module.ts
@@ -4,6 +4,6 @@ import { PortalController } from './portal.controller';
@Module({
controllers: [PortalController],
- imports: [ApiDocsModule.forRoot({ collectionUrl: '../../api-collection' })]
+ imports: [ApiDocsModule.forRoot({ collectionPath: '../../api-collection' })]
})
export class AppModule {}
diff --git a/integrations/nodejs/nestjs/examples/06-embed/main.ts b/examples/integrations/nodejs/nestjs/06-embed/main.ts
similarity index 100%
rename from integrations/nodejs/nestjs/examples/06-embed/main.ts
rename to examples/integrations/nodejs/nestjs/06-embed/main.ts
diff --git a/integrations/nodejs/nestjs/examples/06-embed/portal.controller.ts b/examples/integrations/nodejs/nestjs/06-embed/portal.controller.ts
similarity index 100%
rename from integrations/nodejs/nestjs/examples/06-embed/portal.controller.ts
rename to examples/integrations/nodejs/nestjs/06-embed/portal.controller.ts
diff --git a/integrations/nodejs/nestjs/examples/api-collection/catalog/folder.yml b/examples/integrations/nodejs/nestjs/api-collection/catalog/folder.yml
similarity index 100%
rename from integrations/nodejs/nestjs/examples/api-collection/catalog/folder.yml
rename to examples/integrations/nodejs/nestjs/api-collection/catalog/folder.yml
diff --git a/integrations/nodejs/nestjs/examples/api-collection/catalog/get product.yml b/examples/integrations/nodejs/nestjs/api-collection/catalog/get product.yml
similarity index 100%
rename from integrations/nodejs/nestjs/examples/api-collection/catalog/get product.yml
rename to examples/integrations/nodejs/nestjs/api-collection/catalog/get product.yml
diff --git a/integrations/nodejs/nestjs/examples/api-collection/catalog/list products.yml b/examples/integrations/nodejs/nestjs/api-collection/catalog/list products.yml
similarity index 100%
rename from integrations/nodejs/nestjs/examples/api-collection/catalog/list products.yml
rename to examples/integrations/nodejs/nestjs/api-collection/catalog/list products.yml
diff --git a/integrations/nodejs/nestjs/examples/api-collection/opencollection.yml b/examples/integrations/nodejs/nestjs/api-collection/opencollection.yml
similarity index 100%
rename from integrations/nodejs/nestjs/examples/api-collection/opencollection.yml
rename to examples/integrations/nodejs/nestjs/api-collection/opencollection.yml
diff --git a/examples/integrations/nodejs/nestjs/tsconfig.json b/examples/integrations/nodejs/nestjs/tsconfig.json
new file mode 100644
index 00000000..2803a415
--- /dev/null
+++ b/examples/integrations/nodejs/nestjs/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../../../integrations/tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "dist",
+ "baseUrl": ".",
+ "paths": { "*": ["../../../../integrations/node_modules/*"] },
+ "typeRoots": ["../../../../integrations/node_modules/@types"],
+ "declaration": false,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
+ },
+ "include": ["0*/*.ts"]
+}
diff --git a/examples/react/index.html b/examples/react/index.html
deleted file mode 100644
index 87d1bf83..00000000
--- a/examples/react/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- OpenCollection Playground - React Example
-
-
-
-
-
-
\ No newline at end of file
diff --git a/examples/react/package-lock.json b/examples/react/package-lock.json
deleted file mode 100644
index 591843c4..00000000
--- a/examples/react/package-lock.json
+++ /dev/null
@@ -1,1304 +0,0 @@
-{
- "name": "opencollection-react-example",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "opencollection-react-example",
- "version": "1.0.0",
- "dependencies": {
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@types/react": "^18.2.0",
- "@types/react-dom": "^18.2.0",
- "@vitejs/plugin-react": "^4.0.0",
- "vite": "^4.4.0"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
- "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
- "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.3",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.28.3",
- "@babel/helpers": "^7.28.4",
- "@babel/parser": "^7.28.4",
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.4",
- "@babel/types": "^7.28.4",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
- "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.28.3",
- "@babel/types": "^7.28.2",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.27.2",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.28.3"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
- "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
- "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.4"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
- "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
- "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
- "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.3",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.4",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.4",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
- "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
- "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
- "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
- "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
- "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
- "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
- "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
- "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
- "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
- "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
- "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
- "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
- "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
- "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
- "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
- "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
- "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
- "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
- "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
- "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
- "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
- "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
- "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.27",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
- "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
- "node_modules/@types/prop-types": {
- "version": "15.7.15",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
- "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/react": {
- "version": "18.3.24",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
- "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/prop-types": "*",
- "csstype": "^3.0.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "18.3.7",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
- "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^18.0.0"
- }
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
- "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.0",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.27",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.17.0"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
- }
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.8.4",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz",
- "integrity": "sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.js"
- }
- },
- "node_modules/browserslist": {
- "version": "4.26.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz",
- "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.8.3",
- "caniuse-lite": "^1.0.30001741",
- "electron-to-chromium": "^1.5.218",
- "node-releases": "^2.0.21",
- "update-browserslist-db": "^1.1.3"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001743",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
- "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.218",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz",
- "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/esbuild": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
- "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/android-arm": "0.18.20",
- "@esbuild/android-arm64": "0.18.20",
- "@esbuild/android-x64": "0.18.20",
- "@esbuild/darwin-arm64": "0.18.20",
- "@esbuild/darwin-x64": "0.18.20",
- "@esbuild/freebsd-arm64": "0.18.20",
- "@esbuild/freebsd-x64": "0.18.20",
- "@esbuild/linux-arm": "0.18.20",
- "@esbuild/linux-arm64": "0.18.20",
- "@esbuild/linux-ia32": "0.18.20",
- "@esbuild/linux-loong64": "0.18.20",
- "@esbuild/linux-mips64el": "0.18.20",
- "@esbuild/linux-ppc64": "0.18.20",
- "@esbuild/linux-riscv64": "0.18.20",
- "@esbuild/linux-s390x": "0.18.20",
- "@esbuild/linux-x64": "0.18.20",
- "@esbuild/netbsd-x64": "0.18.20",
- "@esbuild/openbsd-x64": "0.18.20",
- "@esbuild/sunos-x64": "0.18.20",
- "@esbuild/win32-arm64": "0.18.20",
- "@esbuild/win32-ia32": "0.18.20",
- "@esbuild/win32-x64": "0.18.20"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/node-releases": {
- "version": "2.0.21",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
- "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.11",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/react": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
- },
- "peerDependencies": {
- "react": "^18.3.1"
- }
- },
- "node_modules/react-refresh": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
- "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup": {
- "version": "3.29.5",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
- "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=14.18.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/vite": {
- "version": "4.5.14",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
- "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.18.10",
- "postcss": "^8.4.27",
- "rollup": "^3.27.1"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- },
- "peerDependencies": {
- "@types/node": ">= 14",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- }
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- }
- }
-}
diff --git a/examples/react/package.json b/examples/react/package.json
deleted file mode 100644
index cce434ae..00000000
--- a/examples/react/package.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "opencollection-react-example",
- "version": "1.0.0",
- "private": true,
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview"
- },
- "dependencies": {
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@types/react": "^18.2.0",
- "@types/react-dom": "^18.2.0",
- "@vitejs/plugin-react": "^4.0.0",
- "vite": "^4.4.0"
- }
-}
\ No newline at end of file
diff --git a/examples/react/src/App.jsx b/examples/react/src/App.jsx
deleted file mode 100644
index e337e055..00000000
--- a/examples/react/src/App.jsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import React from 'react';
-import { OpenCollectionPlayground } from '../../../packages/oc-playground/dist/oc-playground.es.js';
-import '../../../packages/oc-playground/dist/playground.css';
-
-const sampleCollection = {
- name: "Sample API Collection",
- description: "A comprehensive OpenCollection demonstration showcasing various API endpoints and configurations",
- items: [
- {
- type: "http",
- name: "Get Users",
- method: "GET",
- url: "https://jsonplaceholder.typicode.com/users",
- docs: "Retrieves a list of all users from the JSONPlaceholder API",
- headers: [
- {
- name: "Accept",
- value: "application/json"
- }
- ]
- },
- {
- type: "http",
- name: "Create User",
- method: "POST",
- url: "https://jsonplaceholder.typicode.com/users",
- docs: "Creates a new user in the system",
- headers: [
- {
- name: "Content-Type",
- value: "application/json"
- }
- ],
- body: {
- type: "json",
- data: JSON.stringify({
- name: "John Doe",
- email: "john@example.com"
- }, null, 2)
- }
- },
- {
- type: "http",
- name: "Get All Posts",
- method: "GET",
- url: "https://jsonplaceholder.typicode.com/posts",
- docs: "Retrieves all blog posts"
- },
- {
- type: "http",
- name: "Get Post by ID",
- method: "GET",
- url: "https://jsonplaceholder.typicode.com/posts/1",
- docs: "Retrieves a specific post by its ID"
- },
- {
- type: "script",
- script: "// Utility functions for API testing\nfunction generateRandomId() {\n return Math.floor(Math.random() * 1000) + 1;\n}\n\nconsole.log('Utility functions loaded');"
- }
- ],
- environments: [
- {
- name: "Production",
- description: "Production environment configuration",
- variables: [
- {
- name: "baseUrl",
- value: "https://api.example.com"
- }
- ]
- },
- {
- name: "Development",
- description: "Development environment for testing",
- variables: [
- {
- name: "baseUrl",
- value: "http://localhost:3000"
- }
- ]
- }
- ]
-};
-
-function App() {
- return (
-
- 🚀
-
- }
- />
- );
-}
-
-export default App;
\ No newline at end of file
diff --git a/examples/react/src/main.jsx b/examples/react/src/main.jsx
deleted file mode 100644
index 7c3fd8c2..00000000
--- a/examples/react/src/main.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-import App from './App.jsx';
-
-const container = document.getElementById('root');
-const root = createRoot(container);
-
-root.render(
-
-
-
-);
\ No newline at end of file
diff --git a/examples/react/vite.config.js b/examples/react/vite.config.js
deleted file mode 100644
index 4568ffba..00000000
--- a/examples/react/vite.config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-
-export default defineConfig({
- plugins: [react()],
- server: {
- port: 3001
- }
-});
\ No newline at end of file
diff --git a/examples/standalone-html/index.html b/examples/standalone-html/basic.html
similarity index 100%
rename from examples/standalone-html/index.html
rename to examples/standalone-html/basic.html
diff --git a/examples/standalone-html/bruno.json b/examples/standalone-html/bruno.json
deleted file mode 100644
index 50a80eba..00000000
--- a/examples/standalone-html/bruno.json
+++ /dev/null
@@ -1,647 +0,0 @@
-{
- "name": "E-commerce API",
- "version": "1",
- "items": [
- {
- "type": "folder",
- "name": "Cart",
- "filename": "Cart",
- "seq": 4,
- "root": {
- "docs": "Shopping cart endpoints",
- "meta": {
- "name": "Cart",
- "seq": 4
- }
- },
- "items": [
- {
- "type": "http",
- "name": "Add Item to Cart",
- "filename": "Add Item to Cart.bru",
- "seq": 2,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/cart/add/item/:itemId",
- "method": "POST",
- "headers": [
- {
- "name": "Content-Type",
- "value": "application/json",
- "enabled": true
- },
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [
- {
- "name": "itemId",
- "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9",
- "type": "path",
- "enabled": true
- }
- ],
- "body": {
- "mode": "json",
- "json": "{ \n \"quantity\": 2\n}",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Add a product to the user's shopping cart",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Clear Cart",
- "filename": "Clear Cart.bru",
- "seq": 5,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/cart",
- "method": "DELETE",
- "headers": [
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Remove all items from the user's cart",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Get User Cart",
- "filename": "Get User Cart.bru",
- "seq": 1,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/cart",
- "method": "GET",
- "headers": [
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Retrieve the current user's shopping cart",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Remove Item from Cart",
- "filename": "Remove Item from Cart.bru",
- "seq": 4,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/cart/item/:itemId",
- "method": "DELETE",
- "headers": [
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [
- {
- "name": "itemId",
- "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9",
- "type": "path",
- "enabled": true
- }
- ],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Remove a specific item from the cart",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Update Cart Item",
- "filename": "Update Cart Item.bru",
- "seq": 3,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/cart/item/:itemId",
- "method": "PUT",
- "headers": [
- {
- "name": "Content-Type",
- "value": "application/json",
- "enabled": true
- },
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [
- {
- "name": "itemId",
- "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9",
- "type": "path",
- "enabled": true
- }
- ],
- "body": {
- "mode": "json",
- "json": "{\n \"quantity\": 5\n}",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Update the quantity of an item in the cart",
- "auth": {
- "mode": "none"
- }
- }
- }
- ]
- },
- {
- "type": "folder",
- "name": "Authentication",
- "filename": "Authentication",
- "seq": 2,
- "root": {
- "docs": "User authentication endpoints",
- "meta": {
- "name": "Authentication",
- "seq": 2
- }
- },
- "items": [
- {
- "type": "http",
- "name": "Register User",
- "filename": "Register User.bru",
- "seq": 1,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/auth/register",
- "method": "POST",
- "headers": [
- {
- "name": "Content-Type",
- "value": "application/json",
- "enabled": true
- }
- ],
- "params": [],
- "body": {
- "mode": "json",
- "json": "{\n \"name\": \"Anoop\",\n \"email\": \"test@example.com\",\n \"password\": \"password123\"\n}",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Register a new user account",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Whoami",
- "filename": "Whoami.bru",
- "seq": 3,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/auth/whoami",
- "method": "GET",
- "headers": [
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Retrieve authenticated user's profile information",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Login",
- "filename": "Login.bru",
- "seq": 2,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/auth/login",
- "method": "POST",
- "headers": [],
- "params": [],
- "body": {
- "mode": "json",
- "json": "{\n \"email\": \"test@example.com\",\n \"password\": \"password123\"\n}",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {
- "res": "bru.setEnvVar(\"accessToken\", res.body.token)"
- },
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Authenticate a user and receive a token",
- "auth": {
- "mode": "none"
- }
- }
- }
- ]
- },
- {
- "type": "folder",
- "name": "Orders",
- "filename": "Orders",
- "seq": 5,
- "root": {
- "docs": "Order management endpoints",
- "meta": {
- "name": "Orders",
- "seq": 5
- }
- },
- "items": [
- {
- "type": "http",
- "name": "Create Order",
- "filename": "Create Order.bru",
- "seq": 1,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/orders",
- "method": "POST",
- "headers": [
- {
- "name": "Content-Type",
- "value": "application/json",
- "enabled": true
- },
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [],
- "body": {
- "mode": "json",
- "json": "{\n \"shippingAddress\": \"Mars\"\n}",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "// Check if the request was successful\nif (res.getStatus() === 200 || res.getStatus() === 201) {\n // Parse the response body\n var jsonData = res.getBody();\n \n // Check if the order has an ID\n if (jsonData && jsonData.id) {\n // Store the order ID in the environment\n bru.setEnvVar('order_id', jsonData.id);\n console.log('Order ID saved to environment: ' + jsonData.id);\n }\n}",
- "docs": "Create a new order using the items in the user's cart",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Get User Orders",
- "filename": "Get User Orders.bru",
- "seq": 2,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/orders",
- "method": "GET",
- "headers": [
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Retrieve a list of all orders placed by the current user",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Get Order by ID",
- "filename": "Get Order by ID.bru",
- "seq": 3,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/orders/:orderId",
- "method": "GET",
- "headers": [
- {
- "name": "Authorization",
- "value": "Bearer {{accessToken}}",
- "enabled": true
- }
- ],
- "params": [
- {
- "name": "orderId",
- "value": "9c4d1e4f-64dd-465a-b52a-5471caab828b",
- "type": "path",
- "enabled": true
- }
- ],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Retrieve detailed information about a specific order",
- "auth": {
- "mode": "none"
- }
- }
- }
- ]
- },
- {
- "type": "folder",
- "name": "Products",
- "filename": "Products",
- "seq": 3,
- "root": {
- "docs": "Product-related endpoints. The server automatically seeds 5 sample products on startup if no products exist.",
- "meta": {
- "name": "Products",
- "seq": 3
- }
- },
- "items": [
- {
- "type": "http",
- "name": "Get Products by Category",
- "filename": "Get Products by Category.bru",
- "seq": 2,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/products?category=Electronics",
- "method": "GET",
- "headers": [],
- "params": [
- {
- "name": "category",
- "value": "Electronics",
- "type": "query",
- "enabled": true
- }
- ],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Retrieve products filtered by category. Seeded categories include: Electronics, Sports, and Home.",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Get Product by ID",
- "filename": "Get Product by ID.bru",
- "seq": 3,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/products/:productId",
- "method": "GET",
- "headers": [],
- "params": [
- {
- "name": "productId",
- "value": "cd3ad475-0784-4a0d-9fe4-a9a17e5391d9",
- "type": "path",
- "enabled": true
- }
- ],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Retrieve detailed information about a specific product",
- "auth": {
- "mode": "none"
- }
- }
- },
- {
- "type": "http",
- "name": "Get All Products",
- "filename": "Get All Products.bru",
- "seq": 1,
- "settings": {},
- "tags": [],
- "request": {
- "url": "{{baseUrl}}/products",
- "method": "GET",
- "headers": [],
- "params": [
- {
- "name": "category",
- "value": "electronics",
- "type": "query",
- "enabled": false
- }
- ],
- "body": {
- "mode": "none",
- "formUrlEncoded": [],
- "multipartForm": [],
- "file": []
- },
- "script": {},
- "vars": {},
- "assertions": [],
- "tests": "",
- "docs": "Retrieve a list of all products, optionally filtered by category. Default seeded categories include: Electronics, Sports, and Home.",
- "auth": {
- "mode": "none"
- }
- }
- }
- ]
- }
- ],
- "activeEnvironmentUid": "RcGeNRbYHRYrqqtGIxs7V",
- "environments": [
- {
- "variables": [
- {
- "name": "baseUrl",
- "value": "http://localhost:3001/api",
- "enabled": true,
- "secret": false,
- "type": "text"
- },
- {
- "name": "userToken",
- "value": "ZjFhZDJhMDItNmQ3Yi00OTg2LWFhZjYtODJkZGE1M2UyYTJh",
- "secret": false,
- "enabled": true,
- "type": "text"
- },
- {
- "name": "accessToken",
- "value": "ZjFhZDJhMDItNmQ3Yi00OTg2LWFhZjYtODJkZGE1M2UyYTJh",
- "secret": false,
- "enabled": true,
- "type": "text"
- },
- {
- "name": "order_id",
- "value": "9c4d1e4f-64dd-465a-b52a-5471caab828b",
- "secret": false,
- "enabled": true,
- "type": "text"
- }
- ],
- "name": "dev"
- }
- ],
- "root": {
- "request": {
- "auth": {
- "mode": "none"
- }
- },
- "docs": "Collection for testing the E-commerce API endpoints. The server automatically seeds 5 sample products (Smartphone X, Running Shoes, Coffee Maker, Laptop Pro, Wireless Earbuds) on startup if no products exist in the database.\n\n[
](https://fetch.usebruno.com?url=git%40bitbucket.org%3Ausebruno%2Fdemo-api-collection.git \"target=_blank rel=noopener noreferrer\")",
- "meta": {
- "name": "E-commerce API"
- }
- },
- "brunoConfig": {
- "version": "1",
- "name": "E-commerce API",
- "type": "collection",
- "ignore": [
- "node_modules",
- ".git"
- ],
- "size": 0.005391120910644531,
- "filesCount": 23
- }
-}
\ No newline at end of file
diff --git a/examples/standalone-html/index2.html b/examples/standalone-html/nested-folders.html
similarity index 100%
rename from examples/standalone-html/index2.html
rename to examples/standalone-html/nested-folders.html
diff --git a/integrations/.dockerignore b/integrations/.dockerignore
index b1b516e8..ebd0b95b 100644
--- a/integrations/.dockerignore
+++ b/integrations/.dockerignore
@@ -5,6 +5,5 @@ nodejs/*/dist
shell/shell.js
nodejs/*/shell
test-results
-nodejs/nestjs/examples/dist
dist-tarballs
shell/bru.js
diff --git a/integrations/contract-tests/apps/express.js b/integrations/contract-tests/apps/express.js
index e01d3f51..196c6e07 100644
--- a/integrations/contract-tests/apps/express.js
+++ b/integrations/contract-tests/apps/express.js
@@ -1,5 +1,7 @@
'use strict';
+const fs = require('node:fs');
+const path = require('node:path');
const express = require('express');
const helmet = require('helmet');
const { apiDocs, embed } = require('@usebruno/api-docs-express');
@@ -10,6 +12,8 @@ const PORT = Number(process.env.PORT || 5456);
const COLLECTION = '../fixtures/api-collection';
const BRU = '../fixtures/api-collection-bru';
const BUNDLED = '../fixtures/bundled.yml';
+const CONTENT = fs.readFileSync(path.join(__dirname, BUNDLED), 'utf8');
+const URL_FIXTURE = 'https://raw.githubusercontent.com/usebruno/bruno-api-docs/1aa76e1ad567b6e05e03e46ee53fb6bc1a66e7d5/integrations/contract-tests/fixtures/bundled.yml';
const app = express();
@@ -32,31 +36,39 @@ app.use(
app.get('/control', (req, res) => res.json({ ok: true, from: 'the app itself' }));
app.use('/docs', apiDocs({
- collectionUrl: COLLECTION,
+ collectionPath: COLLECTION,
environments: { include: ['Local'] },
tags: { exclude: ['internal'] },
pageTitle: 'Acme API',
logo: 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 10 10%22%3E%3Ccircle cx=%225%22 cy=%225%22 r=%225%22/%3E%3C/svg%3E',
- gitCollectionUrl: 'https://token:secret@github.com/acme/api-collection'
+ repositoryUrl: 'https://token:secret@github.com/acme/api-collection'
}));
-app.use('/api/v2/docs', apiDocs({ collectionUrl: COLLECTION, environments: { include: '*', exclude: ['Prod'] } }));
+app.use('/api/v2/docs', apiDocs({ collectionPath: COLLECTION, environments: { include: '*', exclude: ['Prod'] } }));
-app.use('/internal/docs', apiDocs({ collectionUrl: COLLECTION }));
+app.use('/internal/docs', apiDocs({ collectionPath: COLLECTION }));
-app.use('/bundled/docs', apiDocs({ collectionUrl: BUNDLED }));
+app.use('/bundled/docs', apiDocs({ collectionPath: BUNDLED }));
-app.use('/bru/docs', apiDocs({ collectionUrl: BRU, environments: { include: ['Local'] }, tags: { exclude: ['internal'] } }));
+app.use('/content/docs', apiDocs({ content: CONTENT }));
-app.use('/broken/docs', apiDocs({ collectionUrl: './there-is-no-collection-here' }));
+app.use('/priority/docs', apiDocs({ content: CONTENT, collectionPath: COLLECTION }));
-app.use('/oversize/docs', apiDocs({ collectionUrl: '../fixtures/walk-oversize' }));
+app.use('/url/docs', apiDocs({ url: URL_FIXTURE }));
+
+app.use('/url-missing/docs', apiDocs({ url: URL_FIXTURE.replace('bundled.yml', 'missing.yml') }));
+
+app.use('/bru/docs', apiDocs({ collectionPath: BRU, environments: { include: ['Local'] }, tags: { exclude: ['internal'] } }));
+
+app.use('/broken/docs', apiDocs({ collectionPath: './there-is-no-collection-here' }));
+
+app.use('/oversize/docs', apiDocs({ collectionPath: '../fixtures/walk-oversize' }));
// theme is what the renderer will take next; until it does, passing it is a mistake we report
-app.use('/misconfigured/docs', apiDocs({ collectionUrl: COLLECTION, theme: 'dark' }));
+app.use('/misconfigured/docs', apiDocs({ collectionPath: COLLECTION, theme: 'dark' }));
// the docs inside the host's own page: the mount serves, the block only points at it
-app.use('/portal/docs', apiDocs({ collectionUrl: COLLECTION, environments: { include: ['Local'] } }));
+app.use('/portal/docs', apiDocs({ collectionPath: COLLECTION, environments: { include: ['Local'] } }));
app.get('/portal', (req, res) => {
res.type('html').send(`
Acme Developer Portal
diff --git a/integrations/contract-tests/apps/fastify.js b/integrations/contract-tests/apps/fastify.js
index 62f2ca30..92dd0cba 100644
--- a/integrations/contract-tests/apps/fastify.js
+++ b/integrations/contract-tests/apps/fastify.js
@@ -1,5 +1,7 @@
'use strict';
+const fs = require('node:fs');
+const path = require('node:path');
const Fastify = require('fastify');
const { apiDocs } = require('@usebruno/api-docs-fastify');
@@ -8,6 +10,8 @@ const PORT = Number(process.env.PORT || 5457);
const COLLECTION = '../fixtures/api-collection';
const BRU = '../fixtures/api-collection-bru';
const BUNDLED = '../fixtures/bundled.yml';
+const CONTENT = fs.readFileSync(path.join(__dirname, BUNDLED), 'utf8');
+const URL_FIXTURE = 'https://raw.githubusercontent.com/usebruno/bruno-api-docs/1aa76e1ad567b6e05e03e46ee53fb6bc1a66e7d5/integrations/contract-tests/fixtures/bundled.yml';
const app = Fastify({ logger: false });
@@ -37,26 +41,30 @@ async function main() {
await app.register(apiDocs, {
prefix: '/docs',
- collectionUrl: COLLECTION,
+ collectionPath: COLLECTION,
environments: { include: ['Local'] },
tags: { exclude: ['internal'] },
pageTitle: 'Acme API',
logo: 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 10 10%22%3E%3Ccircle cx=%225%22 cy=%225%22 r=%225%22/%3E%3C/svg%3E',
- gitCollectionUrl: 'https://token:secret@github.com/acme/api-collection'
+ repositoryUrl: 'https://token:secret@github.com/acme/api-collection'
});
await app.register(apiDocs, {
prefix: '/api/v2/docs',
- collectionUrl: COLLECTION,
+ collectionPath: COLLECTION,
environments: { include: '*', exclude: ['Prod'] }
});
- await app.register(apiDocs, { prefix: '/internal/docs', collectionUrl: COLLECTION });
- await app.register(apiDocs, { prefix: '/bundled/docs', collectionUrl: BUNDLED });
- await app.register(apiDocs, { prefix: '/bru/docs', collectionUrl: BRU, environments: { include: ['Local'] }, tags: { exclude: ['internal'] } });
- await app.register(apiDocs, { prefix: '/broken/docs', collectionUrl: './there-is-no-collection-here' });
- await app.register(apiDocs, { prefix: '/oversize/docs', collectionUrl: '../fixtures/walk-oversize' });
- await app.register(apiDocs, { prefix: '/misconfigured/docs', collectionUrl: COLLECTION, theme: 'dark' });
+ await app.register(apiDocs, { prefix: '/internal/docs', collectionPath: COLLECTION });
+ await app.register(apiDocs, { prefix: '/bundled/docs', collectionPath: BUNDLED });
+ await app.register(apiDocs, { prefix: '/content/docs', content: CONTENT });
+ await app.register(apiDocs, { prefix: '/priority/docs', content: CONTENT, collectionPath: COLLECTION });
+ await app.register(apiDocs, { prefix: '/url/docs', url: URL_FIXTURE });
+ await app.register(apiDocs, { prefix: '/url-missing/docs', url: URL_FIXTURE.replace('bundled.yml', 'missing.yml') });
+ await app.register(apiDocs, { prefix: '/bru/docs', collectionPath: BRU, environments: { include: ['Local'] }, tags: { exclude: ['internal'] } });
+ await app.register(apiDocs, { prefix: '/broken/docs', collectionPath: './there-is-no-collection-here' });
+ await app.register(apiDocs, { prefix: '/oversize/docs', collectionPath: '../fixtures/walk-oversize' });
+ await app.register(apiDocs, { prefix: '/misconfigured/docs', collectionPath: COLLECTION, theme: 'dark' });
await app.listen({ port: PORT, host: '127.0.0.1' });
console.log(`fastify rig on http://localhost:${PORT}`);
diff --git a/integrations/contract-tests/apps/nestjs.js b/integrations/contract-tests/apps/nestjs.js
index 1a13a7a5..d8c660e3 100644
--- a/integrations/contract-tests/apps/nestjs.js
+++ b/integrations/contract-tests/apps/nestjs.js
@@ -1,6 +1,8 @@
'use strict';
require('reflect-metadata');
+const fs = require('node:fs');
+const path = require('node:path');
const { Module } = require('@nestjs/common');
const { NestFactory } = require('@nestjs/core');
const helmet = require('helmet');
@@ -14,32 +16,38 @@ const ADAPTER = process.env.ADAPTER || 'express';
const COLLECTION = '../fixtures/api-collection';
const BRU = '../fixtures/api-collection-bru';
const BUNDLED = '../fixtures/bundled.yml';
+const CONTENT = fs.readFileSync(path.join(__dirname, BUNDLED), 'utf8');
+const URL_FIXTURE = 'https://raw.githubusercontent.com/usebruno/bruno-api-docs/1aa76e1ad567b6e05e03e46ee53fb6bc1a66e7d5/integrations/contract-tests/fixtures/bundled.yml';
// plain JS, so the decorator is applied as the function it is. The real Nest examples live in
-// nodejs/nestjs/examples and are TypeScript.
+// examples/integrations/nodejs/nestjs and are TypeScript.
class AppModule {}
Module({
imports: [
ApiDocsModule.forRoot({
mountPath: '/docs',
- collectionUrl: COLLECTION,
+ collectionPath: COLLECTION,
environments: { include: ['Local'] },
tags: { exclude: ['internal'] },
pageTitle: 'Acme API',
logo: 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 10 10%22%3E%3Ccircle cx=%225%22 cy=%225%22 r=%225%22/%3E%3C/svg%3E',
- gitCollectionUrl: 'https://token:secret@github.com/acme/api-collection'
+ repositoryUrl: 'https://token:secret@github.com/acme/api-collection'
}),
ApiDocsModule.forRoot({
mountPath: '/api/v2/docs',
- collectionUrl: COLLECTION,
+ collectionPath: COLLECTION,
environments: { include: '*', exclude: ['Prod'] }
}),
- ApiDocsModule.forRoot({ mountPath: '/internal/docs', collectionUrl: COLLECTION }),
- ApiDocsModule.forRoot({ mountPath: '/bundled/docs', collectionUrl: BUNDLED }),
- ApiDocsModule.forRoot({ mountPath: '/bru/docs', collectionUrl: BRU, environments: { include: ['Local'] }, tags: { exclude: ['internal'] } }),
- ApiDocsModule.forRoot({ mountPath: '/broken/docs', collectionUrl: './there-is-no-collection-here' }),
- ApiDocsModule.forRoot({ mountPath: '/oversize/docs', collectionUrl: '../fixtures/walk-oversize' }),
- ApiDocsModule.forRoot({ mountPath: '/misconfigured/docs', collectionUrl: COLLECTION, theme: 'dark' })
+ ApiDocsModule.forRoot({ mountPath: '/internal/docs', collectionPath: COLLECTION }),
+ ApiDocsModule.forRoot({ mountPath: '/bundled/docs', collectionPath: BUNDLED }),
+ ApiDocsModule.forRoot({ mountPath: '/content/docs', content: CONTENT }),
+ ApiDocsModule.forRoot({ mountPath: '/priority/docs', content: CONTENT, collectionPath: COLLECTION }),
+ ApiDocsModule.forRoot({ mountPath: '/url/docs', url: URL_FIXTURE }),
+ ApiDocsModule.forRoot({ mountPath: '/url-missing/docs', url: URL_FIXTURE.replace('bundled.yml', 'missing.yml') }),
+ ApiDocsModule.forRoot({ mountPath: '/bru/docs', collectionPath: BRU, environments: { include: ['Local'] }, tags: { exclude: ['internal'] } }),
+ ApiDocsModule.forRoot({ mountPath: '/broken/docs', collectionPath: './there-is-no-collection-here' }),
+ ApiDocsModule.forRoot({ mountPath: '/oversize/docs', collectionPath: '../fixtures/walk-oversize' }),
+ ApiDocsModule.forRoot({ mountPath: '/misconfigured/docs', collectionPath: COLLECTION, theme: 'dark' })
]
})(AppModule);
diff --git a/integrations/contract-tests/browser/contract.spec.mjs b/integrations/contract-tests/browser/contract.spec.mjs
index 6b4c8e13..cac86d92 100644
--- a/integrations/contract-tests/browser/contract.spec.mjs
+++ b/integrations/contract-tests/browser/contract.spec.mjs
@@ -37,6 +37,13 @@ test('the renderer booted, from the CDN, with our shell from our own origin', as
expect(await page.evaluate(() => typeof window.OpenCollection), 'the entry the deployed bundle still carries').toBe('function');
});
+test('the repository link reaches the page, with its credentials stripped', async ({ page }) => {
+ await page.goto('/docs/', { waitUntil: 'networkidle' });
+ const href = await page.locator('a[href^="https://fetch.usebruno.com"]').first().getAttribute('href');
+ expect(href).toContain(encodeURIComponent('https://github.com/acme/api-collection'));
+ expect(href).not.toContain('secret');
+});
+
test('the filters hold all the way to the page', async ({ page }) => {
await page.goto('/docs/', { waitUntil: 'networkidle' });
const text = await page.evaluate(() => document.body.innerText);
@@ -108,11 +115,13 @@ test('a .bru collection renders, and its filters hold', async ({ page }) => {
expect(errors).toEqual([]);
});
-test('a bundled single file renders the same way', async ({ page }) => {
- await page.goto('/bundled/docs/', { waitUntil: 'networkidle' });
- await expect(page).toHaveTitle('API Documentation');
- await expect(page.getByText('Catalog', { exact: true }).first()).toBeVisible();
-});
+for (const mount of ['/bundled/docs/', '/content/docs/', '/url/docs/']) {
+ test(`${mount} a bundled document renders the same way`, async ({ page }) => {
+ await page.goto(mount, { waitUntil: 'networkidle' });
+ await expect(page).toHaveTitle('API Documentation');
+ await expect(page.getByText('Catalog', { exact: true }).first()).toBeVisible();
+ });
+}
test('a broken mount explains itself in the page, and the app is still up', async ({ page, request }) => {
const response = await page.goto('/broken/docs/', { waitUntil: 'networkidle' });
diff --git a/integrations/contract-tests/check.sh b/integrations/contract-tests/check.sh
index d00be945..14c3fb34 100755
--- a/integrations/contract-tests/check.sh
+++ b/integrations/contract-tests/check.sh
@@ -9,10 +9,14 @@
# Every rig serves the same five mounts over the same fixture. That convention is what makes one
# script cover the whole matrix: a cell is an invocation, not an edit.
#
-# /docs environments include Local, tags exclude internal, pageTitle, gitCollectionUrl
+# /docs environments include Local, tags exclude internal, pageTitle, repositoryUrl
# /api/v2/docs environments all minus Prod
# /internal/docs no options
# /bundled/docs a single bundled yml
+# /content/docs the same document given inline as `content`
+# /priority/docs `content` and `collectionPath` both set: content wins
+# /url/docs the bundled yml fetched from its public URL, this repo on GitHub at a pinned commit
+# /url-missing/docs a URL that answers 404
# /bru/docs the same collection in Bruno's own format, same filters
# /broken/docs a collection that is not there
# /oversize/docs a collection over the caps
@@ -126,6 +130,20 @@ check "/bundled/docs/collection.yml -> yaml" header_has Content-Type yaml "$B
check "/bundled/docs/collection.yml -> ETag" revalidates "$BASE/bundled/docs/collection.yml"
check "/bundled/docs -> served as written" body_has "$BASE/bundled/docs/collection.yml" 'Acme API (bundled)'
+describe "content: the document given inline, served as a bundled file"
+check "/content/docs/ -> the page" status_is 200 "$BASE/content/docs/"
+check "/content/docs/collection.yml -> yaml" header_has Content-Type yaml "$BASE/content/docs/collection.yml"
+check "/content/docs/collection.yml -> ETag" revalidates "$BASE/content/docs/collection.yml"
+check "/content/docs -> the same bytes as the file" same_body "$BASE/content/docs/collection.yml" "$BASE/bundled/docs/collection.yml"
+check "/priority/docs -> content wins over the path" same_body "$BASE/priority/docs/collection.yml" "$BASE/bundled/docs/collection.yml"
+
+describe "url: a bundled document fetched once at start"
+check "/url/docs/ -> the page" status_is 200 "$BASE/url/docs/"
+check "/url/docs/collection.yml -> yaml" header_has Content-Type yaml "$BASE/url/docs/collection.yml"
+check "/url/docs -> the same bytes as the file" same_body "$BASE/url/docs/collection.yml" "$BASE/bundled/docs/collection.yml"
+check "/url-missing/docs/ -> 404" status_is 404 "$BASE/url-missing/docs/"
+check "naming the fetch" body_has "$BASE/url-missing/docs/" 'could not fetch'
+
describe "a broken setup does not stop the app"
check "the app's own route still answers" body_has "$BASE/control" 'the app itself'
check "/broken/docs/ -> 404, not a crash" status_is 404 "$BASE/broken/docs/"
diff --git a/integrations/contract-tests/examples.sh b/integrations/contract-tests/examples.sh
index ef92f969..1898cb52 100644
--- a/integrations/contract-tests/examples.sh
+++ b/integrations/contract-tests/examples.sh
@@ -1,14 +1,18 @@
#!/usr/bin/env bash
-# Every example under nodejs/*/examples boots and does what its own file says it does. One at a
-# time on one port, the way a reader runs them. The Nest ones run from their compiled dist, which
-# is the point of writing them in TypeScript.
+# Every example under examples/integrations/nodejs boots and does what its own file says it does.
+# One at a time on one port, the way a reader runs them. The Nest ones run from their compiled
+# dist, which is the point of writing them in TypeScript.
#
# bash contract-tests/examples.sh [--port 5480]
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+EXAMPLES="$(cd "$ROOT/../examples/integrations/nodejs" && pwd)"
source "$ROOT/contract-tests/lib.sh"
+# the examples sit outside the workspace, so this is how they find the packages and the frameworks
+export NODE_PATH="$ROOT/node_modules"
+
PORT=5480
while [ $# -gt 0 ]; do
case "$1" in
@@ -17,9 +21,10 @@ while [ $# -gt 0 ]; do
esac
done
+( cd "$ROOT" && npm run build:examples --silent )
covered "$0" \
- $(cd "$ROOT" && ls nodejs/*/examples/*.js) \
- $(cd "$ROOT" && ls -d nodejs/nestjs/examples/0*/ | sed 's|examples/\(.*\)/$|examples/dist/\1/main.js|')
+ $(cd "$EXAMPLES" && ls */*.js) \
+ $(cd "$EXAMPLES" && ls -d nestjs/0*/ | sed 's|nestjs/\(.*\)/$|nestjs/dist/\1/main.js|')
BASE="http://localhost:$PORT"
JUNIT_NAME="${JUNIT_NAME:-examples}"
@@ -30,8 +35,8 @@ port_is_free "$PORT"
# example FILE: stops the one before, boots this one
example() {
stop
- describe "${1#nodejs/}"
- boot "$ROOT/$1" "$PORT"
+ describe "$1"
+ boot "$EXAMPLES/$1" "$PORT"
}
CSP="Content-Security-Policy"
@@ -40,14 +45,14 @@ API_KEY=(-H 'x-api-key: let-me-in')
# ---- express -------------------------------------------------------------------
-example nodejs/express/examples/quickstart.js
+example express/quickstart.js
check "/docs -> 301 docs/" redirects_to "$BASE/docs" "docs/"
check "/docs/ -> the page" header_has Content-Type text/html "$BASE/docs/"
check "/docs/shell.js -> 200" status_is 200 "$BASE/docs/shell.js"
check "the collection is the one beside it" body_has "$BASE/docs/collection.yml" 'Acme API'
check "/health is the app's own" status_is 200 "$BASE/health"
-example nodejs/express/examples/auth.js
+example express/auth.js
check "/health stays open" status_is 200 "$BASE/health"
check "/docs/ -> 401" status_is 401 "$BASE/docs/"
check "/docs/ says how to authenticate" header_has WWW-Authenticate Bearer "$BASE/docs/"
@@ -57,13 +62,13 @@ check "/docs -> 401, the guard is before the redirect" status_is 401 "$BASE/docs
check "/docs/ with the token -> 200" status_is 200 "${BEARER[@]}" "$BASE/docs/"
check "/docs/collection.yml with the token -> 200" status_is 200 "${BEARER[@]}" "$BASE/docs/collection.yml"
-example nodejs/express/examples/csp.js
+example express/csp.js
check "/docs/ -> 200" status_is 200 "$BASE/docs/"
check "script-src allows the CDN, Monaco and wasm" header_has "$CSP" "script-src 'self' https://cdn.usebruno.com https://cdn.jsdelivr.net 'wasm-unsafe-eval'" "$BASE/docs/"
check "connect-src allows data:" header_has "$CSP" "connect-src 'self' data:" "$BASE/docs/"
check "style-src allows the fonts" header_has "$CSP" "fonts.googleapis.com" "$BASE/docs/"
-example nodejs/express/examples/embed.js
+example express/embed.js
check "/ is the host's own page" body_has "$BASE/" 'Acme Developer Portal'
check "with the docs block in it" body_has "$BASE/" 'id="bruno-docs"'
check "loading from the mount" body_has "$BASE/" 'src="/docs/shell.js"'
@@ -72,14 +77,14 @@ check "the mount serves the collection" body_has "$BASE/docs/collection
# ---- fastify -------------------------------------------------------------------
-example nodejs/fastify/examples/quickstart.js
+example fastify/quickstart.js
check "/docs -> 301 docs/" redirects_to "$BASE/docs" "docs/"
check "/docs/ -> the page" header_has Content-Type text/html "$BASE/docs/"
check "/docs/shell.js -> 200" status_is 200 "$BASE/docs/shell.js"
check "the collection is the one beside it" body_has "$BASE/docs/collection.yml" 'Acme API'
check "/health is the app's own" status_is 200 "$BASE/health"
-example nodejs/fastify/examples/auth.js
+example fastify/auth.js
check "/health stays open" status_is 200 "$BASE/health"
check "/docs/ -> 401" status_is 401 "$BASE/docs/"
check "/docs/ says how to authenticate" header_has WWW-Authenticate Bearer "$BASE/docs/"
@@ -90,13 +95,13 @@ check "POST /docs/ -> 401, the guard is before the 405" status_is 401 -X POST "$
check "/docs/ with the token -> 200" status_is 200 "${BEARER[@]}" "$BASE/docs/"
check "/docs/collection.yml with the token -> 200" status_is 200 "${BEARER[@]}" "$BASE/docs/collection.yml"
-example nodejs/fastify/examples/csp.js
+example fastify/csp.js
check "/docs/ -> 200" status_is 200 "$BASE/docs/"
check "script-src allows the CDN, Monaco and wasm" header_has "$CSP" "script-src 'self' https://cdn.usebruno.com https://cdn.jsdelivr.net 'wasm-unsafe-eval'" "$BASE/docs/"
check "connect-src allows data:" header_has "$CSP" "connect-src 'self' data:" "$BASE/docs/"
check "style-src allows the fonts" header_has "$CSP" "fonts.googleapis.com" "$BASE/docs/"
-example nodejs/fastify/examples/embed.js
+example fastify/embed.js
check "/ is the host's own page" body_has "$BASE/" 'Acme Developer Portal'
check "with the docs block in it" body_has "$BASE/" 'id="bruno-docs"'
check "loading from the mount" body_has "$BASE/" 'src="/docs/shell.js"'
@@ -105,13 +110,13 @@ check "the mount serves the collection" body_has "$BASE/docs/collection
# ---- nestjs, from the compiled dist -------------------------------------------------
-example nodejs/nestjs/examples/dist/01-quickstart/main.js
+example nestjs/dist/01-quickstart/main.js
check "/docs -> 301 docs/" redirects_to "$BASE/docs" "docs/"
check "/docs/ -> the page" header_has Content-Type text/html "$BASE/docs/"
check "/docs/shell.js -> 200" status_is 200 "$BASE/docs/shell.js"
check "the collection resolved from dist/" body_has "$BASE/docs/collection.yml" 'Acme API'
-example nodejs/nestjs/examples/dist/02-auth/main.js
+example nestjs/dist/02-auth/main.js
check "/docs/ stays open" status_is 200 "$BASE/docs/"
check "/internal/docs/ -> 401" status_is 401 "$BASE/internal/docs/"
check "/internal/docs/collection.yml -> 401" status_is 401 "$BASE/internal/docs/collection.yml"
@@ -120,27 +125,27 @@ check "/internal/docs -> 401, the guard is before the redirect" status_is 401 "$
check "/internal/docs/ with the key -> 200" status_is 200 "${API_KEY[@]}" "$BASE/internal/docs/"
check "/internal/docs/collection.yml with the key -> 200" status_is 200 "${API_KEY[@]}" "$BASE/internal/docs/collection.yml"
-example nodejs/nestjs/examples/dist/03-global-prefix/main.js
+example nestjs/dist/03-global-prefix/main.js
check "/docs/ -> 404, it moved" status_is 404 "$BASE/docs/"
check "/api/docs -> 301 docs/" redirects_to "$BASE/api/docs" "docs/"
check "/api/docs/ -> the page" header_has Content-Type text/html "$BASE/api/docs/"
check "/api/docs/shell.js -> 200" status_is 200 "$BASE/api/docs/shell.js"
check "/api/docs/collection.yml -> the collection" body_has "$BASE/api/docs/collection.yml" 'Acme API'
-example nodejs/nestjs/examples/dist/04-fastify-adapter/main.js
+example nestjs/dist/04-fastify-adapter/main.js
check "/docs -> 301 docs/" redirects_to "$BASE/docs" "docs/"
check "/docs/ -> the page" header_has Content-Type text/html "$BASE/docs/"
check "/docs/shell.js -> 200, not the page" header_has Content-Type javascript "$BASE/docs/shell.js"
check "/docs/collection.yml -> the collection" body_has "$BASE/docs/collection.yml" 'Acme API'
check "/docs// -> the page" header_has Content-Type text/html "$BASE/docs/catalog/list-products/"
-example nodejs/nestjs/examples/dist/05-csp/main.js
+example nestjs/dist/05-csp/main.js
check "/docs/ -> 200" status_is 200 "$BASE/docs/"
check "script-src allows the CDN, Monaco and wasm" header_has "$CSP" "script-src 'self' https://cdn.usebruno.com https://cdn.jsdelivr.net 'wasm-unsafe-eval'" "$BASE/docs/"
check "connect-src allows data:" header_has "$CSP" "connect-src 'self' data:" "$BASE/docs/"
check "style-src allows the fonts" header_has "$CSP" "fonts.googleapis.com" "$BASE/docs/"
-example nodejs/nestjs/examples/dist/06-embed/main.js
+example nestjs/dist/06-embed/main.js
check "/ is the host's own page" body_has "$BASE/" 'Acme Developer Portal'
check "with the docs block in it" body_has "$BASE/" 'id="bruno-docs"'
check "loading from the mount" body_has "$BASE/" 'src="/docs/shell.js"'
diff --git a/integrations/contract-tests/lib.sh b/integrations/contract-tests/lib.sh
index d167531b..a2ff4441 100644
--- a/integrations/contract-tests/lib.sh
+++ b/integrations/contract-tests/lib.sh
@@ -141,6 +141,7 @@ header_has() { local name=$1 pattern=$2; shift 2; want="$name containing '$patte
header_absent() { local name=$1; shift; want="no $name header"; got="$name: $(header_of "$name" "$@")"; [ "$got" = "$name: " ]; }
body_has() { local body; body="$(curl -s "$1")"; want="'$2' in the body"; got="not there, in ${#body} bytes"; grep -q "$2" <<<"$body"; }
body_lacks() { want="no '$2' in the body"; got="it is there"; ! grep -Eq "$2" <<<"$(curl -s "$1")"; }
+same_body() { want="the same bytes as $(basename "$2")"; got="they differ"; [ "$(curl -s "$1")" = "$(curl -s "$2")" ]; }
file_has() { want="'$2' in $(basename "$1")"; got="not there"; grep -q "$2" "$1"; }
file_lacks() { want="no '$2' in $(basename "$1")"; got="it is there"; ! grep -q "$2" "$1"; }
revalidates() { status_is 304 -H "If-None-Match: $(header_of ETag "$1")" "$1"; }
diff --git a/integrations/eslint.config.mjs b/integrations/eslint.config.mjs
index 8ecb44a5..4097a255 100644
--- a/integrations/eslint.config.mjs
+++ b/integrations/eslint.config.mjs
@@ -5,71 +5,76 @@ import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
// The repo's stylistic rules, minus everything browser-shaped.
-export default [
- {
- ignores: ['**/node_modules/**', '**/dist/**', '**/shell/shell.js', '**/shell/bru.js', 'shell/vendor/**']
+export const ignores = {
+ ignores: ['**/node_modules/**', '**/dist/**', '**/shell/shell.js', '**/shell/bru.js', 'shell/vendor/**']
+};
+
+// the rules for everything that runs in Node; the examples apply them from their own config
+export const workspace = {
+ plugins: {
+ '@stylistic': stylistic,
+ '@typescript-eslint': tsPlugin
},
- {
- plugins: {
- '@stylistic': stylistic,
- '@typescript-eslint': tsPlugin
- },
- languageOptions: {
- parser: tsParser,
- parserOptions: {
- ecmaVersion: 'latest',
- sourceType: 'module'
- },
- globals: {
- ...globals.node
- }
+ languageOptions: {
+ parser: tsParser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module'
},
- files: ['eslint.config.mjs', '**/src/**/*.ts', '**/test/**/*.{ts,mjs}', 'contract-tests/**/*.{js,mjs}', '**/examples/**/*.{js,ts}'],
- rules: {
- ...js.configs.recommended.rules,
- ...stylistic.configs.customize({
- indent: 2,
- quotes: 'single',
- semi: true,
- jsx: false
- }).rules,
- '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
- '@stylistic/comma-dangle': ['error', 'never'],
- '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
- '@stylistic/arrow-parens': ['error', 'always'],
- '@stylistic/array-bracket-spacing': ['error', 'never'],
- '@stylistic/arrow-spacing': ['error', { before: true, after: true }],
- '@stylistic/function-call-spacing': ['error', 'never'],
- '@stylistic/semi-style': ['error', 'last'],
- '@stylistic/max-len': ['error', {
- code: 120,
- ignoreUrls: true,
- ignoreStrings: true,
- ignoreTemplateLiterals: true,
- ignoreRegExpLiterals: true
- }],
+ globals: {
+ ...globals.node
+ }
+ },
+ files: ['eslint.config.mjs', '**/src/**/*.ts', '**/test/**/*.{ts,mjs}', 'contract-tests/**/*.{js,mjs}'],
+ rules: {
+ ...js.configs.recommended.rules,
+ ...stylistic.configs.customize({
+ indent: 2,
+ quotes: 'single',
+ semi: true,
+ jsx: false
+ }).rules,
+ '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
+ '@stylistic/comma-dangle': ['error', 'never'],
+ '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
+ '@stylistic/arrow-parens': ['error', 'always'],
+ '@stylistic/array-bracket-spacing': ['error', 'never'],
+ '@stylistic/arrow-spacing': ['error', { before: true, after: true }],
+ '@stylistic/function-call-spacing': ['error', 'never'],
+ '@stylistic/semi-style': ['error', 'last'],
+ '@stylistic/max-len': ['error', {
+ code: 120,
+ ignoreUrls: true,
+ ignoreStrings: true,
+ ignoreTemplateLiterals: true,
+ ignoreRegExpLiterals: true
+ }],
- 'curly': ['error', 'multi-line'],
+ 'curly': ['error', 'multi-line'],
- 'no-undef': 'off',
- 'no-unused-vars': 'off',
- '@typescript-eslint/no-unused-vars': ['error', {
- argsIgnorePattern: '^_',
- varsIgnorePattern: '^_',
- caughtErrorsIgnorePattern: '^_'
- }],
+ 'no-undef': 'off',
+ 'no-unused-vars': 'off',
+ '@typescript-eslint/no-unused-vars': ['error', {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ caughtErrorsIgnorePattern: '^_'
+ }],
- '@typescript-eslint/no-explicit-any': 'warn',
- '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
+ '@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
- 'eqeqeq': ['error', 'always', { null: 'ignore' }],
- 'no-debugger': 'error',
- 'no-var': 'error',
- 'prefer-const': 'error',
+ 'eqeqeq': ['error', 'always', { null: 'ignore' }],
+ 'no-debugger': 'error',
+ 'no-var': 'error',
+ 'prefer-const': 'error',
- 'no-console': ['error', { allow: ['log', 'warn', 'error'] }]
- }
- },
+ 'no-console': ['error', { allow: ['log', 'warn', 'error'] }]
+ }
+};
+
+export default [
+ ignores,
+ workspace,
{
files: ['shell/*.mjs'],
plugins: { '@stylistic': stylistic },
@@ -91,7 +96,7 @@ export default [
}
},
{
- files: ['contract-tests/apps/*.js', '**/examples/**/*.{js,ts}'],
+ files: ['contract-tests/apps/*.js'],
rules: {
'no-console': 'off'
}
diff --git a/integrations/nodejs/core/README.md b/integrations/nodejs/core/README.md
index 36bfaa8b..856ad101 100644
--- a/integrations/nodejs/core/README.md
+++ b/integrations/nodejs/core/README.md
@@ -20,7 +20,7 @@ npm install @usebruno/api-docs-core
const http = require('node:http');
const { createDocs } = require('@usebruno/api-docs-core');
-const handler = createDocs({ collectionUrl: './api-collection' }).handler();
+const handler = createDocs({ collectionPath: './api-collection' }).handler();
http.createServer((req, res) => {
if (req.url.startsWith('/docs')) {
@@ -38,16 +38,20 @@ http.createServer((req, res) => {
```js
createDocs({
- collectionUrl: './api-collection',
+ collectionPath: './api-collection',
environments: { include: ['Local'] },
tags: { exclude: ['internal'] },
pageTitle: 'Acme API',
logo: 'https://acme.dev/logo.svg',
- gitCollectionUrl: 'https://github.com/acme/api-collection'
+ repositoryUrl: 'https://github.com/acme/api-collection'
})
```
-`collectionUrl` is the path to a Bruno folder (`bruno.json` and `.bru` files), an OpenCollection yml folder or one bundled `.yml` file. The path is relative to your app's entry file.
+`collectionPath` is the path to a Bruno folder (`bruno.json` and `.bru` files), an OpenCollection yml folder or one bundled `.yml` file. The path is relative to your app's entry file.
+
+`url` is the public address of a bundled collection file, `https://example.com/api/opencollection.yml`, fetched once at start.
+
+`content` is the document itself as a string. When more than one source is set the first of `content`, `url`, `collectionPath` wins.
`environments` and `tags` take `{ include, exclude }`, where `include` is a list of names or `'*'`. Leave `environments` out and none are published. Excluded tags drop their requests from the served collection.
diff --git a/integrations/nodejs/core/src/collection/index.ts b/integrations/nodejs/core/src/collection/index.ts
index c7c8e520..86bcc680 100644
--- a/integrations/nodejs/core/src/collection/index.ts
+++ b/integrations/nodejs/core/src/collection/index.ts
@@ -1,23 +1,60 @@
// The collection layer's one entry point: where the document comes from is decided here, at startup.
-import { ConfigError, type CollectionOptions } from '../options';
+import { ConfigError, SOURCE_KEYS, type SourceKey, type CollectionOptions, type CollectionFilters } from '../options';
import { log } from '../log';
import type { SkippedFile } from './walk';
import { resolveCollectionSource } from './source';
-import { fileProvider, dirProvider, type Provider, type BuiltProvider } from './providers';
+import { bundledProvider, fileProvider, dirProvider, type Provider, type BuiltProvider } from './providers';
+import { parseCollectionUrl, fetchBundled } from './remote';
+
+interface Built {
+ provider: BuiltProvider;
+ bundled: boolean;
+ label: string;
+}
+
+const BUILD: Record Built> = {
+ content: fromContent,
+ url: fromUrl,
+ collectionPath: fromPath
+};
export function providerFor(options: CollectionOptions): Provider {
- const source = resolveCollectionSource(options.collectionUrl);
- const filters = { environments: options.environments, tags: options.tags };
+ const set = SOURCE_KEYS.filter((key) => options[key]);
+ if (set.length === 0) {
+ throw new ConfigError(`apiDocs: one of ${SOURCE_KEYS.map((key) => `\`${key}\``).join(', ')} is required`);
+ }
+ const [chosen, ...ignored] = set;
+ for (const key of ignored) {
+ log.warn(`${chosen} is set, ignoring ${key}`);
+ }
- // built first: a path that is not there is reported as missing, not as a filter mistake
- const built = source.mode === 'file' ? fileProvider(source.path) : dirProvider(source.path, filters);
- if (source.mode === 'file' && (filters.environments || filters.tags)) {
+ const filters = { environments: options.environments, tags: options.tags };
+ const built = BUILD[chosen](options[chosen] as string, filters);
+ if (built.bundled && (filters.environments || filters.tags)) {
throw new ConfigError('apiDocs: `environments` / `tags` filtering needs a collection directory');
}
- report(source.path, built);
+ report(built.label, built.provider);
+
+ return built.provider.serve;
+}
+
+function fromContent(text: string): Built {
+ return { provider: bundledProvider(text, 'content'), bundled: true, label: 'content' };
+}
+
+function fromUrl(value: string): Built {
+ const url = parseCollectionUrl(value);
+
+ return { provider: bundledProvider(fetchBundled(url), url), bundled: true, label: url };
+}
+
+function fromPath(value: string, filters: CollectionFilters): Built {
+ const source = resolveCollectionSource(value);
+ // built first: a path that is not there is reported as missing, not as a filter mistake
+ const provider = source.mode === 'file' ? fileProvider(source.path) : dirProvider(source.path, filters);
- return built.serve;
+ return { provider, bundled: source.mode === 'file', label: source.path };
}
/**
diff --git a/integrations/nodejs/core/src/collection/providers.ts b/integrations/nodejs/core/src/collection/providers.ts
index 5cdddabb..862e5a03 100644
--- a/integrations/nodejs/core/src/collection/providers.ts
+++ b/integrations/nodejs/core/src/collection/providers.ts
@@ -5,6 +5,7 @@ import { etagOf, type HttpResponse, type Conditional } from '../http';
import type { SkippedFile } from './walk';
import { walkCollection, CAPS, CapError, type CollectionFormat } from './walk';
import { applyFilters, isManifest } from './filter';
+import { RemoteError } from './remote';
export type Provider = (conditional: Conditional) => HttpResponse;
@@ -41,15 +42,21 @@ function serveFromMemory(body: string | Buffer, contentType: string): Provider {
};
}
+export function bundledProvider(body: string | Buffer, label: string): BuiltProvider {
+ if (Buffer.byteLength(body) > CAPS.totalBytes) {
+ throw new CapError('total size cap (5MB)', label);
+ }
+ const contentType = /^\s*[[{]/.test(String(body)) ? 'application/json; charset=utf-8' : 'text/yaml; charset=utf-8';
+
+ return { serve: serveFromMemory(body, contentType), fileCount: 1, skipped: [], unknownEnvironments: [] };
+}
+
export function fileProvider(filePath: string): BuiltProvider {
- const size = fs.statSync(filePath).size;
- if (size > CAPS.totalBytes) {
+ if (fs.statSync(filePath).size > CAPS.totalBytes) {
throw new CapError('total size cap (5MB)', filePath);
}
- const body = fs.readFileSync(filePath);
-
- return { serve: serveFromMemory(body, 'text/yaml; charset=utf-8'), fileCount: 1, skipped: [], unknownEnvironments: [] };
+ return bundledProvider(fs.readFileSync(filePath), filePath);
}
const formatOf = (rootDir: string): CollectionFormat =>
@@ -81,7 +88,7 @@ export function errorProvider(err: unknown): Provider {
if (err instanceof CapError) {
return () => errorResponse(413, err.message);
}
- if (err instanceof ManifestError) {
+ if (err instanceof ManifestError || err instanceof RemoteError) {
return () => errorResponse(404, err.message);
}
if (err instanceof ConfigError) {
diff --git a/integrations/nodejs/core/src/collection/remote.ts b/integrations/nodejs/core/src/collection/remote.ts
new file mode 100644
index 00000000..49c16d83
--- /dev/null
+++ b/integrations/nodejs/core/src/collection/remote.ts
@@ -0,0 +1,70 @@
+import { execFileSync } from 'node:child_process';
+import { ConfigError } from '../options';
+import { log } from '../log';
+import { stripGitCredentials } from '../url';
+import { CAPS, CapError } from './walk';
+import { safeLoad } from './yaml';
+
+export class RemoteError extends Error {}
+
+const FETCH_TIMEOUT_MS = 30_000;
+
+export function parseCollectionUrl(value: string): string {
+ let parsed: URL;
+ try {
+ parsed = new URL(value);
+ } catch {
+ throw new ConfigError(`apiDocs: \`url\` is not a URL: ${value}`);
+ }
+ if (parsed.protocol !== 'https:') {
+ throw new ConfigError('apiDocs: `url` must be https');
+ }
+ if (parsed.username || parsed.password) {
+ log.warn('credentials in `url` were ignored, the file has to be public');
+ }
+ if (!/\.(ya?ml|json)$/i.test(parsed.pathname)) {
+ throw new ConfigError('apiDocs: `url` takes the address of a bundled collection file, .yml, .yaml or .json');
+ }
+
+ return stripGitCredentials(value);
+}
+
+// the boot is synchronous and fetch is not, so the one request runs in a child of this same node
+const FETCH = `
+try {
+ const res = await fetch(process.argv[1], { redirect: 'follow', signal: AbortSignal.timeout(${FETCH_TIMEOUT_MS}) });
+ if (!res.ok) {
+ throw new Error('HTTP ' + res.status);
+ }
+ process.stdout.write(await res.text());
+} catch (err) {
+ console.error(err.name === 'TimeoutError' ? 'no answer in ${FETCH_TIMEOUT_MS / 1000}s' : (err.cause?.code ?? err.cause?.errors?.[0]?.code ?? err.cause?.message ?? err.message));
+ process.exit(2);
+}
+`;
+
+/** The document at a public address, fetched once at boot, under the same size cap as a file on disk. */
+export function fetchBundled(url: string): string {
+ let text: string;
+ try {
+ text = execFileSync(process.execPath, ['--input-type=module', '-e', FETCH, url], {
+ timeout: FETCH_TIMEOUT_MS + 5_000,
+ maxBuffer: CAPS.totalBytes,
+ stdio: ['ignore', 'pipe', 'pipe']
+ }).toString();
+ } catch (err) {
+ const failed = err as NodeJS.ErrnoException & { stderr?: Buffer };
+ if (failed.code === 'ENOBUFS') {
+ throw new CapError('total size cap (5MB)', url);
+ }
+ const reason = failed.stderr?.toString().trim().split('\n').pop() || failed.message;
+
+ throw new RemoteError(`could not fetch ${url}: ${reason}`);
+ }
+
+ if (safeLoad(text)?.bundled === false) {
+ throw new ConfigError('apiDocs: the file at `url` is the manifest of a directory collection, `url` takes a bundled collection');
+ }
+
+ return text;
+}
diff --git a/integrations/nodejs/core/src/collection/source.ts b/integrations/nodejs/core/src/collection/source.ts
index d117dc84..6174d56a 100644
--- a/integrations/nodejs/core/src/collection/source.ts
+++ b/integrations/nodejs/core/src/collection/source.ts
@@ -18,7 +18,7 @@ export function resolveCollectionPath(collectionOption: string): string {
// guarded here rather than in validateOptions: an empty path resolves to the app's own
// directory, and walking that would serve whatever yml the adopter happens to keep there
if (!collectionOption) {
- throw new ConfigError('apiDocs: `collectionUrl` is required');
+ throw new ConfigError('apiDocs: `collectionPath` is required');
}
const entryDir = require.main?.path
diff --git a/integrations/nodejs/core/src/options.ts b/integrations/nodejs/core/src/options.ts
index 0608ee82..1c110b44 100644
--- a/integrations/nodejs/core/src/options.ts
+++ b/integrations/nodejs/core/src/options.ts
@@ -8,13 +8,15 @@ export interface CollectionFilters {
tags?: Filter;
}
-export interface CollectionOptions extends CollectionFilters {
- collectionUrl: string;
-}
+/** Where the collection comes from, in priority order: the first one set is used. */
+export const SOURCE_KEYS = ['content', 'url', 'collectionPath'] as const;
+export type SourceKey = typeof SOURCE_KEYS[number];
+
+export interface CollectionOptions extends CollectionFilters, Partial> {}
export interface RendererOptions {
logo?: string;
- gitCollectionUrl?: string;
+ repositoryUrl?: string;
}
export interface ApiDocsOptions extends CollectionOptions, RendererOptions {
@@ -23,7 +25,7 @@ export interface ApiDocsOptions extends CollectionOptions, RendererOptions {
export class ConfigError extends Error {}
-const KNOWN_OPTIONS = new Set(['collectionUrl', 'environments', 'tags', 'logo', 'gitCollectionUrl', 'pageTitle']);
+const KNOWN_OPTIONS = new Set([...SOURCE_KEYS, 'environments', 'tags', 'logo', 'repositoryUrl', 'pageTitle']);
export function validateOptions(options: ApiDocsOptions): void {
const unknown = Object.keys(options).filter((key) => !KNOWN_OPTIONS.has(key));
@@ -31,6 +33,12 @@ export function validateOptions(options: ApiDocsOptions): void {
throw new ConfigError(`apiDocs: unknown option ${unknown.join(', ')}`);
}
+ for (const key of SOURCE_KEYS) {
+ if (options[key] !== undefined && typeof options[key] !== 'string') {
+ throw new ConfigError(`apiDocs: \`${key}\` takes a string`);
+ }
+ }
+
for (const name of ['environments', 'tags'] as const) {
const filter = options[name];
if (typeof filter?.include === 'string' && filter.include !== '*') {
diff --git a/integrations/nodejs/core/src/routes/shell.ts b/integrations/nodejs/core/src/routes/shell.ts
index d31628cf..2c89d262 100644
--- a/integrations/nodejs/core/src/routes/shell.ts
+++ b/integrations/nodejs/core/src/routes/shell.ts
@@ -1,6 +1,7 @@
// The browser half as the core serves it: shell.html stamped per mount, shell.js static, embed() body-only.
import fs from 'node:fs';
+import { stripGitCredentials } from '../url';
import path from 'node:path';
import { ConfigError, type RendererOptions, type ApiDocsOptions } from '../options';
import { etagOf, type HttpResponse, type Conditional } from '../http';
@@ -16,19 +17,7 @@ const DEFAULT_TITLE = 'API Documentation';
const escapeHtml = (s: string): string =>
s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
-export const stripGitCredentials = (url: string): string => {
- try {
- const parsed = new URL(url);
- if ((parsed.protocol !== 'http:' && parsed.protocol !== 'https:') || (!parsed.username && !parsed.password)) {
- return url;
- }
- parsed.username = '';
- parsed.password = '';
- return parsed.toString();
- } catch {
- return url;
- }
-};
+export { stripGitCredentials };
/** What the browser is allowed to see. Built from named fields, never copied from the input. */
export function rendererConfig(options: RendererOptions): RendererOptions {
@@ -36,8 +25,8 @@ export function rendererConfig(options: RendererOptions): RendererOptions {
if (options.logo) {
config.logo = options.logo;
}
- if (options.gitCollectionUrl) {
- config.gitCollectionUrl = stripGitCredentials(options.gitCollectionUrl);
+ if (options.repositoryUrl) {
+ config.repositoryUrl = stripGitCredentials(options.repositoryUrl);
}
return config;
diff --git a/integrations/nodejs/core/src/url.ts b/integrations/nodejs/core/src/url.ts
new file mode 100644
index 00000000..74a1fa7b
--- /dev/null
+++ b/integrations/nodejs/core/src/url.ts
@@ -0,0 +1,13 @@
+export const stripGitCredentials = (url: string): string => {
+ try {
+ const parsed = new URL(url);
+ if ((parsed.protocol !== 'http:' && parsed.protocol !== 'https:') || (!parsed.username && !parsed.password)) {
+ return url;
+ }
+ parsed.username = '';
+ parsed.password = '';
+ return parsed.toString();
+ } catch {
+ return url;
+ }
+};
diff --git a/integrations/nodejs/core/test/collection.test.mjs b/integrations/nodejs/core/test/collection.test.mjs
index e5379d19..2b057b4f 100644
--- a/integrations/nodejs/core/test/collection.test.mjs
+++ b/integrations/nodejs/core/test/collection.test.mjs
@@ -105,16 +105,16 @@ const quiet = (fn) => {
{
const gone = path.join(tmp, 'not-here.yml');
assert.equal(resolveCollectionSource(gone).mode, 'file', 'an unstattable path falls through to file mode');
- assert.throws(() => quiet(() => providerFor({ collectionUrl: gone })), { code: 'ENOENT' });
- assert.equal(errorProvider(thrownBy(() => providerFor({ collectionUrl: gone })))({}).status, 404);
- assert.throws(() => quiet(() => providerFor({ collectionUrl: gone, tags: { exclude: ['x'] } })), { code: 'ENOENT' },
+ assert.throws(() => quiet(() => providerFor({ collectionPath: gone })), { code: 'ENOENT' });
+ assert.equal(errorProvider(thrownBy(() => providerFor({ collectionPath: gone })))({}).status, 404);
+ assert.throws(() => quiet(() => providerFor({ collectionPath: gone, tags: { exclude: ['x'] } })), { code: 'ENOENT' },
'with filters set, a missing path is still reported as missing, not as a filter mistake');
if (process.getuid?.() !== 0) {
const locked = write('locked/opencollection.yml', 'opencollection: 1.0.0\nbundled: false\n');
fs.chmodSync(locked, 0o000);
assert.equal(resolveCollectionSource(locked).mode, 'file', 'an unreadable manifest falls through to file mode');
- assert.equal(errorProvider(thrownBy(() => providerFor({ collectionUrl: locked })))({}).status, 500);
+ assert.equal(errorProvider(thrownBy(() => providerFor({ collectionPath: locked })))({}).status, 500);
fs.chmodSync(locked, 0o644);
}
}
@@ -122,9 +122,35 @@ const quiet = (fn) => {
// --- filters need a directory, and that is a startup failure
{
const single = write('solo/opencollection.yml', 'opencollection: 1.0.0\nbundled: true\n');
- assert.throws(() => quiet(() => providerFor({ collectionUrl: single, tags: { exclude: ['internal'] } })), ConfigError);
- assert.throws(() => quiet(() => providerFor({ collectionUrl: single, environments: { include: '*' } })), ConfigError);
- assert.equal(quiet(() => providerFor({ collectionUrl: single }))({}).status, 200, 'without filters it serves');
+ assert.throws(() => quiet(() => providerFor({ collectionPath: single, tags: { exclude: ['internal'] } })), ConfigError);
+ assert.throws(() => quiet(() => providerFor({ collectionPath: single, environments: { include: '*' } })), ConfigError);
+ assert.equal(quiet(() => providerFor({ collectionPath: single }))({}).status, 200, 'without filters it serves');
+}
+
+// --- content: the document itself, served as a bundled file, and it wins over collectionPath
+{
+ const text = 'opencollection: 1.0.0\ninfo:\n name: Inline\n';
+ const served = quiet(() => providerFor({ content: text }))({});
+ assert.equal(served.status, 200);
+ assert.equal(served.body, text, 'served as given, byte for byte');
+ assert.match(served.headers['Content-Type'], /yaml/);
+ assert.match(quiet(() => providerFor({ content: '{"opencollection":"1.0.0"}' }))({}).headers['Content-Type'], /json/, 'a JSON document is served as JSON');
+
+ const dir = path.dirname(write('prio/opencollection.yml', 'opencollection: 1.0.0\nbundled: false\n'));
+ const warned = [];
+ const { log, warn } = console;
+ console.log = () => {};
+ console.warn = (line) => warned.push(line);
+ try {
+ assert.equal(providerFor({ content: text, collectionPath: dir })({}).body, text, 'content wins');
+ } finally {
+ Object.assign(console, { log, warn });
+ }
+ assert.ok(warned.some((line) => line.includes('content is set, ignoring collectionPath')), 'and says what it ignored');
+
+ assert.throws(() => quiet(() => providerFor({ content: text, tags: { exclude: ['x'] } })), ConfigError, 'filters need a directory, as for a bundled file');
+ assert.throws(() => quiet(() => providerFor({})), /one of `content`, `url`, `collectionPath` is required/);
+ assert.throws(() => quiet(() => providerFor({ content: '' })), /is required/, 'an empty document is not a source');
}
// --- pointing at the manifest of an unbundled collection means the directory around it
@@ -144,7 +170,7 @@ const quiet = (fn) => {
// so any caller that skipped the validator would walk it and serve whatever yml lives there
for (const empty of ['', undefined, null]) {
assert.throws(() => resolveCollectionPath(empty), ConfigError, `${JSON.stringify(empty)} is refused`);
- assert.throws(() => quiet(() => providerFor({ collectionUrl: empty })), ConfigError, 'and refused through providerFor');
+ assert.throws(() => quiet(() => providerFor({ collectionPath: empty })), ConfigError, 'and refused through providerFor');
}
}
diff --git a/integrations/nodejs/core/test/docs.test.mjs b/integrations/nodejs/core/test/docs.test.mjs
index 14ec2141..9215a121 100644
--- a/integrations/nodejs/core/test/docs.test.mjs
+++ b/integrations/nodejs/core/test/docs.test.mjs
@@ -42,7 +42,7 @@ const call = (handler, method, mount, sub, headers = {}) => {
return res;
};
-const docs = quiet(() => createDocs({ collectionUrl: collection, pageTitle: 'Fixture API' }));
+const docs = quiet(() => createDocs({ collectionPath: collection, pageTitle: 'Fixture API' }));
const handler = docs.handler();
// --- the routes, as the contract states them
@@ -123,7 +123,7 @@ const handler = docs.handler();
const missing = path.join(os.tmpdir(), 'bruno-docs-absent-' + Date.now());
let broken;
assert.doesNotThrow(() => {
- broken = quiet(() => createDocs({ collectionUrl: missing }));
+ broken = quiet(() => createDocs({ collectionPath: missing }));
}, 'createDocs never throws: a docs page cannot take down the API it documents');
const brokenHandler = broken.handler();
@@ -134,14 +134,14 @@ const handler = docs.handler();
}
const overCap = quiet(() => createDocs({
- collectionUrl: path.join(here, '..', '..', '..', 'contract-tests', 'fixtures', 'walk-oversize')
+ collectionPath: path.join(here, '..', '..', '..', 'contract-tests', 'fixtures', 'walk-oversize')
}));
assert.equal(overCap.collection().status, 413, 'a collection over the caps is a 413 at the mount');
- const badOptions = quiet(() => createDocs({ collectionUrl: collection, environments: { exclude: ['Prod'] } }));
+ const badOptions = quiet(() => createDocs({ collectionPath: collection, environments: { exclude: ['Prod'] } }));
assert.equal(badOptions.collection().status, 500, 'a config error reaches the mount too, not the boot');
- const typo = quiet(() => createDocs({ collectionUrl: collection, theme: 'dark' }));
+ const typo = quiet(() => createDocs({ collectionPath: collection, theme: 'dark' }));
assert.equal(typo.collection().status, 500, 'an option we do not know is an error, not silently forwarded');
assert.match(String(typo.collection().body), /unknown option theme/);
}
diff --git a/integrations/nodejs/core/test/filter.test.mjs b/integrations/nodejs/core/test/filter.test.mjs
index 3bbf314a..3dc1bf90 100644
--- a/integrations/nodejs/core/test/filter.test.mjs
+++ b/integrations/nodejs/core/test/filter.test.mjs
@@ -120,7 +120,10 @@ assert.throws(() => validateOptions({ environments: { include: 'Local' } }), /`e
'a string that is not the wildcard is named, not silently read as no names');
assert.throws(() => validateOptions({ tags: { include: 'internal' } }), /`tags.include` takes a list/);
validateOptions({ environments: { include: '*' }, tags: { include: '*' } });
-assert.throws(() => validateOptions({ collectionUrl: './c', theme: 'dark', favicon: 'x' }), /unknown option theme, favicon/, 'every unknown key, named');
+assert.throws(() => validateOptions({ collectionPath: './c', theme: 'dark', favicon: 'x' }), /unknown option theme, favicon/, 'every unknown key, named');
+assert.throws(() => validateOptions({ content: { info: {} } }), /`content` takes a string/);
+assert.throws(() => validateOptions({ url: 42 }), /`url` takes a string/);
+assert.doesNotThrow(() => validateOptions({ content: 'opencollection: 1.0.0\n' }));
validateOptions({ environments: { include: '*', exclude: ['Prod'] } });
validateOptions({});
diff --git a/integrations/nodejs/core/test/remote.test.mjs b/integrations/nodejs/core/test/remote.test.mjs
new file mode 100644
index 00000000..0a79a963
--- /dev/null
+++ b/integrations/nodejs/core/test/remote.test.mjs
@@ -0,0 +1,68 @@
+// `url`: a bundled collection file at a public address, fetched once at boot. The fixture is this
+// repo's own, on GitHub, pinned to a commit so it cannot drift. Needs the network, like the browser run.
+// Run: npm run test:unit
+import { fileURLToPath } from 'node:url';
+import path from 'node:path';
+import fs from 'node:fs';
+import assert from 'node:assert/strict';
+import { createRequire } from 'node:module';
+
+const here = path.dirname(fileURLToPath(import.meta.url));
+const require = createRequire(import.meta.url);
+const dist = path.join(here, '..', 'dist');
+const { parseCollectionUrl, fetchBundled, RemoteError } = require(path.join(dist, 'collection', 'remote.js'));
+const { errorProvider } = require(path.join(dist, 'collection', 'providers.js'));
+const { ConfigError } = require(path.join(dist, 'options.js'));
+const thrownBy = (fn) => {
+ try {
+ fn();
+ return null;
+ } catch (err) {
+ return err;
+ }
+};
+
+const FIXTURES = 'https://raw.githubusercontent.com/usebruno/bruno-api-docs/1aa76e1ad567b6e05e03e46ee53fb6bc1a66e7d5/integrations/contract-tests/fixtures';
+const bundled = fs.readFileSync(path.join(here, '..', '..', '..', 'contract-tests', 'fixtures', 'bundled.yml'), 'utf8');
+
+// --- what the option accepts
+{
+ assert.equal(parseCollectionUrl('https://example.com/api/opencollection.yml'), 'https://example.com/api/opencollection.yml');
+ assert.equal(parseCollectionUrl('https://cdn.example.com/v1/collection.yaml?x=1'), 'https://cdn.example.com/v1/collection.yaml?x=1');
+ assert.equal(parseCollectionUrl('https://example.com/api/collection.JSON'), 'https://example.com/api/collection.JSON');
+
+ const warned = [];
+ const { warn } = console;
+ console.warn = (line) => warned.push(line);
+ try {
+ assert.equal(parseCollectionUrl('https://user:tok@example.com/c.yml'), 'https://example.com/c.yml', 'credentials are dropped, the file is public by contract');
+ } finally {
+ console.warn = warn;
+ }
+ assert.ok(warned.some((line) => line.includes('credentials in `url` were ignored')), 'and it says so');
+
+ assert.throws(() => parseCollectionUrl('http://example.com/c.yml'), /must be https/);
+ assert.throws(() => parseCollectionUrl('not a url'), /is not a URL/);
+ assert.throws(() => parseCollectionUrl('https://github.com/acme/api'), /bundled collection file/, 'a repository is not a file');
+ assert.throws(() => parseCollectionUrl('https://github.com/acme/api/tree/main/collection'), /bundled collection file/, 'nor is a folder in it');
+}
+
+// --- the fetch
+{
+ assert.equal(fetchBundled(`${FIXTURES}/bundled.yml`), bundled, 'the document, byte for byte');
+ assert.equal(fetchBundled('https://github.com/usebruno/bruno-api-docs/raw/1aa76e1ad567b6e05e03e46ee53fb6bc1a66e7d5/integrations/contract-tests/fixtures/bundled.yml'), bundled, 'a redirect is followed');
+
+ const manifest = thrownBy(() => fetchBundled(`${FIXTURES}/api-collection/opencollection.yml`));
+ assert.ok(manifest instanceof ConfigError, 'the manifest of a directory collection is refused, not served empty');
+ assert.match(manifest.message, /bundled collection/);
+
+ const missing = thrownBy(() => fetchBundled(`${FIXTURES}/missing.yml`));
+ assert.ok(missing instanceof RemoteError);
+ assert.match(missing.message, /HTTP 404/);
+ assert.equal(errorProvider(missing)({}).status, 404, 'a fetch that fails is a 404 with the reason');
+
+ const down = thrownBy(() => fetchBundled('https://127.0.0.1:9/c.yml'));
+ assert.ok(down instanceof RemoteError, 'a host that does not answer');
+}
+
+console.log('remote-test: all assertions passed');
diff --git a/integrations/nodejs/core/test/shell.test.mjs b/integrations/nodejs/core/test/shell.test.mjs
index 0af9ce5e..3ffbd06c 100644
--- a/integrations/nodejs/core/test/shell.test.mjs
+++ b/integrations/nodejs/core/test/shell.test.mjs
@@ -13,7 +13,7 @@ const { loadShell, embed, rendererConfig, stripGitCredentials, CDN } = require(p
// --- stamping: every placeholder is filled, at any mount
{
- const shell = loadShell({ collectionUrl: './c', pageTitle: 'Acme API' });
+ const shell = loadShell({ collectionPath: './c', pageTitle: 'Acme API' });
const html = shell.html('/docs/').body;
assert.ok(!html.includes('{{'), 'no placeholder survives stamping');
@@ -23,13 +23,13 @@ const { loadShell, embed, rendererConfig, stripGitCredentials, CDN } = require(p
assert.ok(html.includes('src="/docs/shell.js"'), 'shell.js is loaded from the mount, same origin');
assert.ok(html.includes(`src="${CDN}/api-docs.js"`), 'the renderer comes from the CDN');
- assert.equal(loadShell({ collectionUrl: './c' }).html('/x/').body.match(/(.*)<\/title>/)[1], 'API Documentation');
- assert.ok(loadShell({ collectionUrl: './c' }).html('/a/b/c/').body.includes('data-base="/a/b/c/"'), 'any depth');
+ assert.equal(loadShell({ collectionPath: './c' }).html('/x/').body.match(/(.*)<\/title>/)[1], 'API Documentation');
+ assert.ok(loadShell({ collectionPath: './c' }).html('/a/b/c/').body.includes('data-base="/a/b/c/"'), 'any depth');
}
// --- the CSP promise: nothing inline
{
- const html = loadShell({ collectionUrl: './c' }).html('/docs/').body;
+ const html = loadShell({ collectionPath: './c' }).html('/docs/').body;
assert.ok(!/