diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7f4219788..399ce165e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -368,3 +368,86 @@ jobs: git diff exit 1 fi + + audit-api-spec: + runs-on: ubuntu-latest + + steps: + - name: Checkout PR + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Node.js 18 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22 + + - name: Restore lerna dependencies + id: lerna-cache + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: | + node_modules + modules/*/node_modules + key: ${{ runner.os }}-node18-${{ hashFiles('yarn.lock') }}-${{ hashFiles('tsconfig.packages.json') }}-${{ hashFiles('**/package.json') }} + + - name: Install Packages + if: steps.lerna-cache.outputs.cache-hit != 'true' + run: yarn install --with-frozen-lockfile --ignore-scripts + + - name: Build packages + env: + DISABLE_V8_COMPILE_CACHE: '1' + run: yarn run postinstall + + - name: Install OpenAPI Generator at root + run: yarn add -W @api-ts/openapi-generator@v5 + + - name: Download and install vacuum v0.18.1 + run: | + curl -L \ + --output vacuum.tar.gz \ + --silent \ + --show-error \ + --fail \ + https://github.com/daveshanley/vacuum/releases/download/v0.18.1/vacuum_0.18.1_linux_x86_64.tar.gz + tar -xzf vacuum.tar.gz + chmod u+x vacuum + sudo mv vacuum /usr/local/bin/ + vacuum version + + - name: Generate API spec + working-directory: modules/express + run: | + ../../node_modules/.bin/openapi-generator \ + --codec-file openapi-generator.rc.js \ + src/typedRoutes/api/index.ts \ + > api-generated.json + + - name: Audit with Vacuum + working-directory: modules/express + run: | + + vacuum report \ + --no-style \ + --stdout \ + --ruleset ruleset.yaml \ + api-generated.json > vacuum-report.json + + jq '.resultSet.results // []' vacuum-report.json > vacuum-results.json + + ERROR_COUNT=$(jq '[.[] | select(.ruleSeverity == "error")] | length' vacuum-results.json) + WARNING_COUNT=$(jq '[.[] | select(.ruleSeverity == "warn")] | length' vacuum-results.json) + + echo "Found $ERROR_COUNT error(s) and $WARNING_COUNT warning(s)" + + if [ "$ERROR_COUNT" -gt 0 ]; then + echo "API specification audit failed with $ERROR_COUNT error(s)" + echo "" + echo "Errors:" + jq -r '.[] | select(.ruleSeverity == "error") | " - [\(.ruleId)] \(.message) at \(.path)"' vacuum-results.json + exit 1 + else + echo "API specification audit passed!" + fi diff --git a/modules/express/openapi-generator.rc.js b/modules/express/openapi-generator.rc.js index 03d1f8cfa3..9d3a1cfb73 100644 --- a/modules/express/openapi-generator.rc.js +++ b/modules/express/openapi-generator.rc.js @@ -14,7 +14,7 @@ module.exports = (E) => { NonEmptyString: () => E.right({ type: 'string', minLength: 1 }), DateFromISOString: () => E.right({ type: 'string', format: 'date-time' }), BigIntFromString: () => E.right({ type: 'string' }), - BooleanFromString: () => E.right({ type: 'string', enum: ['true', 'false'] }), + BooleanFromString: () => E.right({ type: 'boolean' }), }, 'io-ts-bigint': { BigIntFromString: () => E.right({ type: 'string' }), diff --git a/modules/express/ruleset.yaml b/modules/express/ruleset.yaml new file mode 100644 index 0000000000..e3cde7d955 --- /dev/null +++ b/modules/express/ruleset.yaml @@ -0,0 +1,410 @@ +description: Recommended rules for a high quality specification. +documentationUrl: https://quobix.com/vacuum/rulesets/recommended +rules: + duplicated-entry-in-enum: + category: + description: Schemas are how request bodies and response payloads are defined. They define the data going in and the data flowing out of an operation. These rules check for structural validity, checking types, checking required fields and validating correct use of structures. + id: schemas + name: Schemas + description: Enum values can't be the same. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Make each enum value unique. + id: duplicated-entry-in-enum + recommended: true + severity: error + then: + function: duplicatedEnum + type: validation + no-$ref-siblings: + category: + description: Schemas are how request bodies and response payloads are defined. They define the data going in and the data flowing out of an operation. These rules check for structural validity, checking types, checking required fields and validating correct use of structures. + id: schemas + name: Schemas + description: $ref values can't be next to other properties. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Remove all sibling nodes, including descriptions. + id: no-$ref-siblings + recommended: true + severity: error + then: + function: refSiblings + type: validation + no-ambiguous-paths: + category: + description: Operations are the core of the contract, they define paths and HTTP methods. These rules check operations have been well constructed, looks for operationId, parameter, schema and return types in depth. + id: operations + name: Operations + description: Paths must resolve unambiguously from one another. For example, /{id}/ambiguous and /ambiguous/{id} are the same thing. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Make the path, and the variables used, unique. Check the ordering of variables and the naming of path segments. + id: no-ambiguous-paths + recommended: true + resolved: true + severity: error + then: + function: noAmbiguousPaths + type: validation + no-eval-in-markdown: + category: + description: Validation rules make sure that certain characters or patterns have not been used that may cause issues when rendering in different types of applications. + id: validation + name: Validation + description: Markdown descriptions can't have `eval()` statements'. Malicious actors can use these to embed code in contracts, executing when a browser reads it. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Remove all references to 'eval()' from the description. + id: no-eval-in-markdown + recommended: true + resolved: true + severity: error + then: + function: noEvalDescription + functionOptions: + pattern: eval\( + type: validation + no-script-tags-in-markdown: + category: + description: Validation rules make sure that certain characters or patterns have not been used that may cause issues when rendering in different types of applications. + id: validation + name: Validation + description: Markdown descriptions can't have `