From f0b9d11223f4b50624fcadc061e0e4a3db968813 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Tue, 15 Feb 2022 13:58:58 -0600 Subject: [PATCH] feat: add wrapper for GitHub Actions (#437) * feat: add initial github action config * ci: add gh action test * test: refactor test to check for exact UA match * feat: dynamically set user agent based on env * chore: temporarily skip test suite Our test bed is a bit broken right now and I should fix this up, but I just want to see how the GitHub Action test runs for the time being :grimacing: * chore: silence npm i logs * ci: restore tests, move action test to separate workflow file * Update action.yml * chore: update param names, job names * fix: attempt to get npm install working properly * chore: update loglevel * chore: ok updating log level AGAIN Once we add a debug option, I'll also update this line to reflect that! * chore: set working directory instead Docs: https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runsstepsworking-directory * test: attempt to get CI working again * test: how about this? * refactor: consolidate user-agent logic * refactor: move path to `working_directory` This way our logs look a little cleaner! * chore: typo * chore: remove unnecessary quotes * chore: oops * chore: does this look any cleaner? * fix: try this * revert: eh nvm * chore: removing quotes in one last place * revert: move this back So interesting thing I discovered in this test run: https://github.com/kanadgupta/metrotransit-nextrip-oas/runs/5163683403?check_suite_focus=true#step:3:18 Even though this will look a bit messier in the user's action logs, this is the only way to ensure that the command runs in the user's GitHub repo :sad: * ci: update test workflow to test it with another repo * ci: will this work? * ci: add a token that hopefully works * chore: consolidate CI * refactor: consolidate nock logic * chore: small formatting tweaks * ci: add upload step * fix: see if chalk prints colors nicely with this env var * chore: update workflow docs a bit --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ __tests__/cmds/docs.test.js | 9 ++------- __tests__/get-api-nock.js | 13 +++++-------- __tests__/lib/fetch.test.js | 34 ++++++++++++++++++++++++++++++++-- action.yml | 28 ++++++++++++++++++++++++++++ src/lib/fetch.js | 16 ++++++++++++---- 6 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 action.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b71df7b78..4e81dae27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: [push] jobs: build: + name: Test Suite runs-on: ubuntu-latest strategy: matrix: @@ -22,3 +23,30 @@ jobs: - name: Run tests run: npm test + + action: + name: GitHub Action Dry Run + runs-on: ubuntu-latest + steps: + - name: Checkout GitHub Action + uses: actions/checkout@v2.4.0 + with: + path: rdme-repo + + - name: Checkout external repo containing OpenAPI file + uses: actions/checkout@v2.4.0 + with: + path: oas-examples-repo + repository: readmeio/oas-examples + token: ${{ secrets.OAS_EXAMPLES_READ }} + + - name: Run `validate` command + uses: ./rdme-repo/ + with: + rdme: validate oas-examples-repo/3.1/json/petstore.json + + # Docs: https://rdme-test.readme.io + - name: Run `openapi` command + uses: ./rdme-repo/ + with: + rdme: openapi oas-examples-repo/3.1/json/petstore.json --key=${{ secrets.RDME_TEST_PROJECT_API_KEY }} --id=${{ secrets.RDME_TEST_PROJECT_API_SETTING }} diff --git a/__tests__/cmds/docs.test.js b/__tests__/cmds/docs.test.js index ef8e4561a..c3334f548 100644 --- a/__tests__/cmds/docs.test.js +++ b/__tests__/cmds/docs.test.js @@ -1,6 +1,5 @@ const nock = require('nock'); const chalk = require('chalk'); -const config = require('config'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); @@ -8,7 +7,6 @@ const frontMatter = require('gray-matter'); const APIError = require('../../src/lib/apiError'); const getApiNock = require('../get-api-nock'); -const { userAgent } = require('../get-api-nock'); const DocsCommand = require('../../src/cmds/docs'); const DocsEditCommand = require('../../src/cmds/docs/edit'); @@ -23,11 +21,8 @@ const category = 'CATEGORY_ID'; const apiSetting = 'API_SETTING_ID'; function getNockWithVersionHeader(v) { - return nock(config.get('host'), { - reqheaders: { - 'x-readme-version': v, - 'User-Agent': userAgent, - }, + return getApiNock({ + 'x-readme-version': v, }); } diff --git a/__tests__/get-api-nock.js b/__tests__/get-api-nock.js index 451b63a07..c3f4bbf1a 100644 --- a/__tests__/get-api-nock.js +++ b/__tests__/get-api-nock.js @@ -1,15 +1,12 @@ -const nock = require('nock'); const config = require('config'); -const pkg = require('../package.json'); - -const userAgent = `rdme/${pkg.version}`; +const nock = require('nock'); +const { getUserAgent } = require('../src/lib/fetch'); -module.exports = function () { +module.exports = function (reqHeaders = {}) { return nock(config.get('host'), { reqheaders: { - 'User-Agent': userAgent, + 'User-Agent': getUserAgent(), + ...reqHeaders, }, }); }; - -module.exports.userAgent = userAgent; diff --git a/__tests__/lib/fetch.test.js b/__tests__/lib/fetch.test.js index 3df480fbe..bd6386089 100644 --- a/__tests__/lib/fetch.test.js +++ b/__tests__/lib/fetch.test.js @@ -2,8 +2,38 @@ const config = require('config'); const fetch = require('../../src/lib/fetch'); const { cleanHeaders, handleRes } = require('../../src/lib/fetch'); const getApiNock = require('../get-api-nock'); +const pkg = require('../../package.json'); describe('#fetch()', () => { + describe('GitHub Actions environment', () => { + beforeEach(() => { + process.env.GITHUB_ACTIONS = 'true'; + }); + + afterEach(() => { + delete process.env.GITHUB_ACTIONS; + }); + + it('should use correct user-agent for requests in GitHub Action env', async () => { + const key = 'API_KEY'; + + const mock = getApiNock() + .get('/api/v1') + .basicAuth({ user: key }) + .reply(200, function () { + return this.req.headers['user-agent']; + }); + + const userAgent = await fetch(`${config.get('host')}/api/v1`, { + method: 'get', + headers: cleanHeaders(key), + }).then(handleRes); + + expect(userAgent.shift()).toBe(`rdme-github/${pkg.version}`); + mock.done(); + }); + }); + it('should wrap all requests with a rdme User-Agent', async () => { const key = 'API_KEY'; @@ -19,7 +49,7 @@ describe('#fetch()', () => { headers: cleanHeaders(key), }).then(handleRes); - expect(userAgent.shift()).toMatch(/rdme\/\d+.\d+.\d+/); + expect(userAgent.shift()).toBe(`rdme/${pkg.version}`); mock.done(); }); @@ -32,7 +62,7 @@ describe('#fetch()', () => { const userAgent = await fetch(`${config.get('host')}/api/v1/doesnt-need-auth`).then(handleRes); - expect(userAgent.shift()).toMatch(/rdme\/\d+.\d+.\d+/); + expect(userAgent.shift()).toBe(`rdme/${pkg.version}`); mock.done(); }); }); diff --git a/action.yml b/action.yml new file mode 100644 index 000000000..44d867d1a --- /dev/null +++ b/action.yml @@ -0,0 +1,28 @@ +name: rdme +author: ReadMe +branding: + color: blue + icon: book-open +description: Update your ReadMe developer hubs in a CI environment. +inputs: + rdme: + description: Command to pass into rdme + required: true +runs: + using: composite + steps: + # This is so we can guarantee a version of npm that supports lockfile@2 + - name: setup node + uses: actions/setup-node@v2 + with: + node-version: 16 + # When we add debug support, we should make this a conditional step, see RM-3545 + - name: Install rdme deps + run: npm install --production --silent + shell: bash + working-directory: ${{ github.action_path }} + - name: Execute rdme command + run: ${{ github.action_path }}/bin/rdme ${{ inputs.rdme }} + shell: bash + env: + FORCE_COLOR: '1' diff --git a/src/lib/fetch.js b/src/lib/fetch.js index 332cce133..45a0a0d6b 100644 --- a/src/lib/fetch.js +++ b/src/lib/fetch.js @@ -8,19 +8,27 @@ const APIError = require('./apiError'); * */ module.exports = (url, options = {}) => { - const userAgent = `rdme/${pkg.version}`; - if (!options.headers) { options.headers = { - 'User-Agent': userAgent, + 'User-Agent': module.exports.getUserAgent(), }; } else { - options.headers['User-Agent'] = userAgent; + options.headers['User-Agent'] = module.exports.getUserAgent(); } return fetch(url, options); }; +/** + * Getter function for a string to be used in the user-agent header + * based on the current environment. + * + */ +module.exports.getUserAgent = function getUserAgent() { + const gh = process.env.GITHUB_ACTIONS === 'true' ? '-github' : ''; + return `rdme${gh}/${pkg.version}`; +}; + /** * Small handler for transforming responses from our API into JSON and if there's errors, throwing * an APIError exception.