Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: typescript rewrite #560

Merged
merged 21 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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", {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hell yeah

"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
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