Skip to content

Commit

Permalink
feat: typescript rewrite (#560)
Browse files Browse the repository at this point in the history
* feat: converting most of the library over to TS

* feat: moving all unit tests over to TS, fixing issues

* fix: updating the ci workflow to build the dist

* fix: various problems

* fix: refactoring the fetch wrapper to use the Headers API

* fix: broken tests

* fix: better excluding the dist from jest

* fix: refactoring away the dynamic command loading system for something more strict

* fix: update GHA workflow

* test(node12): point to right file path

* chore: small jest config fix

Saw the following error:

```
jest-haste-map: Haste module naming collision: rdme
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/dist/package.json
    * <rootDir>/package.json
```

this is a fix for that, stolen from here: jestjs/jest#8114 (comment)

* chore: fix @actions/core import

* chore: fix core import in another spot

* chore: simplify debug command

* chore: updating contributing guidelines

* revert: use key variable rather than opts

this was bugging me in the diff lol

* feat: stricter return types for commands

* fix: another strict return type

* fix: another stricter return type

* fix: use command-line-usage types

* fix: more getSpecs types fixes

Co-authored-by: Kanad Gupta <[email protected]>
  • Loading branch information
erunion and kanadgupta authored Aug 9, 2022
1 parent a1d6b4f commit 5ed97cc
Show file tree
Hide file tree
Showing 102 changed files with 4,943 additions and 3,069 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
coverage/
dist/
!.alexrc.js
34 changes: 32 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
{
"extends": ["@readme/eslint-config"],
"extends": [
"@readme/eslint-config",
"@readme/eslint-config/typescript"
],
"root": true,
"parserOptions": {
"ecmaVersion": 2020
},
"overrides": [
{
"files": ["bin/set-version-output", "config/*.js"],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}
],
"rules": {
"@typescript-eslint/ban-types": ["error", {
"types": {
// We specify `{}` in `CommandOptions` generics when those commands don't have their own
// options and it's cleaner for us to do that than `Record<string, unknown>`.
"{}": false
}
}],

/**
* Because our command classes have a `run` method that might not always call `this` we need to
* explicitly exclude `run` from this rule.
*/
"class-methods-use-this": ["error", { "exceptMethods": ["run"] }],

"import/order": ["error", {
"alphabetize": {
"order": "asc",
"caseInsensitive": true
},
"groups": ["type", "builtin", "external", "internal", "parent", "sibling", "index", "object"],
"newlines-between": "always"
}],

/**
* This is a small rule to prevent us from using console.log() statements in our commands.
*
Expand All @@ -24,6 +52,8 @@
* Furthermore, we should also be using our custom loggers (see src/lib/logger.js)
* instead of using console.info() or console.warn() statements.
*/
"no-console": ["warn"]
"no-console": "warn",

"no-restricted-syntax": "off"
}
}
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
- name: Install deps
run: npm ci

- name: Build dist
run: npm run build

- name: Run tests
run: npm test

Expand All @@ -42,10 +45,13 @@ jobs:
- name: Install deps
run: npm ci

- name: Build dist
run: npm run build

# rdme doesn't work on Node 12 but we just want to run this single test to make sure that
# our "we don't support node 12" error is shown.
- name: Run tests
run: npx jest __tests__/bin.test.js
run: npx jest __tests__/bin.test.ts

action:
name: GitHub Action Dry Run
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
coverage/
dist/
node_modules/
swagger.json
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
coverage/
dist/
9 changes: 8 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@

## Running Shell Commands Locally 🐚

To run test commands from within the repository, run your commands from the root of the repository and use `./bin/rdme` instead of `rdme` so it properly points to the command executable, like so:
To run test commands from within the repository, run the build and then run your commands from the root of the repository and use `./bin/rdme` instead of `rdme` so it properly points to the command executable, like so:

```sh
npm run build
./bin/rdme validate __tests__/__fixtures__/ref-oas/petstore.json
```

If you need to debug commands quicker and re-building TS everytime is becoming cumbersome, you can use the debug command, like so:

```sh
npm run debug:bin -- validate __tests__/__fixtures__/ref-oas/petstore.json
```

## Running GitHub Actions Locally 🐳

To run GitHub Actions locally, we'll be using [`act`](https://github.com/nektos/act) (make sure to read their [prerequisites list](https://github.com/nektos/act#necessary-prerequisites-for-running-act) and have that ready to go before installing `act`)!
Expand Down
File renamed without changes.
15 changes: 8 additions & 7 deletions __tests__/bin.test.js → __tests__/bin.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
const { exec } = require('child_process');
const isSupportedNodeVersion = require('../src/lib/isSupportedNodeVersion');
const pkg = require('../package.json');
import { exec } from 'child_process';

import pkg from '../package.json';
import isSupportedNodeVersion from '../src/lib/isSupportedNodeVersion';

describe('bin', () => {
if (isSupportedNodeVersion(process.version)) {
it('should show our help screen', async () => {
expect.assertions(1);

await new Promise(done => {
await new Promise(resolve => {
exec(`node ${__dirname}/../bin/rdme`, (error, stdout) => {
expect(stdout).toContain('a utility for interacting with ReadMe');
done();
resolve(true);
});
});
});
} else {
it('should fail with a message', async () => {
expect.assertions(1);

await new Promise(done => {
await new Promise(resolve => {
exec(`node ${__dirname}/../bin/rdme`, (error, stdout, stderr) => {
expect(stderr).toContain(
`We're sorry, this release of rdme does not support Node.js ${process.version}. We support the following versions: ${pkg.engines.node}`
);
done();
resolve(true);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,84 +1,13 @@
const nock = require('nock');
import nock from 'nock';

const getApiNock = require('../get-api-nock');
import CategoriesCreateCommand from '../../../src/cmds/categories/create';
import getAPIMock, { getAPIMockWithVersionHeader } from '../../helpers/get-api-mock';

const CategoriesCommand = require('../../src/cmds/categories');
const CategoriesCreateCommand = require('../../src/cmds/categories/create');

const categories = new CategoriesCommand();
const categoriesCreate = new CategoriesCreateCommand();

const key = 'API_KEY';
const version = '1.0.0';

function getNockWithVersionHeader(v) {
return getApiNock({
'x-readme-version': v,
});
}

describe('rdme categories', () => {
beforeAll(() => nock.disableNetConnect());

afterEach(() => nock.cleanAll());

it('should error if no api key provided', () => {
return expect(categories.run({})).rejects.toStrictEqual(
new Error('No project API key provided. Please use `--key`.')
);
});

it('should return all categories for a single page', async () => {
const getMock = getNockWithVersionHeader(version)
.persist()
.get('/api/v1/categories?perPage=20&page=1')
.basicAuth({ user: key })
.reply(200, [{ title: 'One Category', slug: 'one-category', type: 'guide' }], {
'x-total-count': '1',
});

const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });

await expect(categories.run({ key, version: '1.0.0' })).resolves.toBe(
JSON.stringify([{ title: 'One Category', slug: 'one-category', type: 'guide' }], null, 2)
);

getMock.done();
versionMock.done();
});

it('should return all categories for multiple pages', async () => {
const getMock = getNockWithVersionHeader(version)
.persist()
.get('/api/v1/categories?perPage=20&page=1')
.basicAuth({ user: key })
.reply(200, [{ title: 'One Category', slug: 'one-category', type: 'guide' }], {
'x-total-count': '21',
})
.get('/api/v1/categories?perPage=20&page=2')
.basicAuth({ user: key })
.reply(200, [{ title: 'Another Category', slug: 'another-category', type: 'guide' }], {
'x-total-count': '21',
});

const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });

await expect(categories.run({ key, version: '1.0.0' })).resolves.toBe(
JSON.stringify(
[
{ title: 'One Category', slug: 'one-category', type: 'guide' },
{ title: 'Another Category', slug: 'another-category', type: 'guide' },
],
null,
2
)
);

getMock.done();
versionMock.done();
});
});

describe('rdme categories:create', () => {
beforeAll(() => nock.disableNetConnect());

Expand All @@ -104,25 +33,26 @@ describe('rdme categories:create', () => {

it('should error if categoryType is not `guide` or `reference`', () => {
return expect(
// @ts-expect-error Testing a CLI arg failure case.
categoriesCreate.run({ key: '123', title: 'Test Title', categoryType: 'test' })
).rejects.toStrictEqual(new Error('`categoryType` must be `guide` or `reference`.'));
});

it('should create a new category if the title and type do not match and preventDuplicates=true', async () => {
const getMock = getNockWithVersionHeader(version)
const getMock = getAPIMockWithVersionHeader(version)
.persist()
.get('/api/v1/categories?perPage=20&page=1')
.basicAuth({ user: key })
.reply(200, [{ title: 'Existing Category', slug: 'existing-category', type: 'guide' }], {
'x-total-count': '1',
});

const postMock = getNockWithVersionHeader(version)
const postMock = getAPIMockWithVersionHeader(version)
.post('/api/v1/categories')
.basicAuth({ user: key })
.reply(201, { title: 'New Category', slug: 'new-category', type: 'guide', id: '123' });

const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
const versionMock = getAPIMock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });

await expect(
categoriesCreate.run({
Expand All @@ -140,20 +70,20 @@ describe('rdme categories:create', () => {
});

it('should create a new category if the title matches but the type does not match and preventDuplicates=true', async () => {
const getMock = getNockWithVersionHeader(version)
const getMock = getAPIMockWithVersionHeader(version)
.persist()
.get('/api/v1/categories?perPage=20&page=1')
.basicAuth({ user: key })
.reply(200, [{ title: 'Category', slug: 'category', type: 'guide' }], {
'x-total-count': '1',
});

const postMock = getNockWithVersionHeader(version)
const postMock = getAPIMockWithVersionHeader(version)
.post('/api/v1/categories')
.basicAuth({ user: key })
.reply(201, { title: 'Category', slug: 'category', type: 'reference', id: '123' });

const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
const versionMock = getAPIMock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });

await expect(
categoriesCreate.run({
Expand All @@ -171,12 +101,12 @@ describe('rdme categories:create', () => {
});

it('should create a new category if the title and type match and preventDuplicates=false', async () => {
const postMock = getNockWithVersionHeader(version)
const postMock = getAPIMockWithVersionHeader(version)
.post('/api/v1/categories')
.basicAuth({ user: key })
.reply(201, { title: 'Category', slug: 'category', type: 'reference', id: '123' });

const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
const versionMock = getAPIMock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });

await expect(
categoriesCreate.run({
Expand All @@ -192,15 +122,15 @@ describe('rdme categories:create', () => {
});

it('should not create a new category if the title and type match and preventDuplicates=true', async () => {
const getMock = getNockWithVersionHeader(version)
const getMock = getAPIMockWithVersionHeader(version)
.persist()
.get('/api/v1/categories?perPage=20&page=1')
.basicAuth({ user: key })
.reply(200, [{ title: 'Category', slug: 'category', type: 'guide', id: '123' }], {
'x-total-count': '1',
});

const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
const versionMock = getAPIMock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });

await expect(
categoriesCreate.run({
Expand All @@ -221,15 +151,15 @@ describe('rdme categories:create', () => {
});

it('should not create a new category if the non case sensitive title and type match and preventDuplicates=true', async () => {
const getMock = getNockWithVersionHeader(version)
const getMock = getAPIMockWithVersionHeader(version)
.persist()
.get('/api/v1/categories?perPage=20&page=1')
.basicAuth({ user: key })
.reply(200, [{ title: 'Category', slug: 'category', type: 'guide', id: '123' }], {
'x-total-count': '1',
});

const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
const versionMock = getAPIMock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });

await expect(
categoriesCreate.run({
Expand Down
Loading

0 comments on commit 5ed97cc

Please sign in to comment.