diff --git a/__tests__/cmds/versions/update.test.ts b/__tests__/cmds/versions/update.test.ts index e24174df0..893c7b38e 100644 --- a/__tests__/cmds/versions/update.test.ts +++ b/__tests__/cmds/versions/update.test.ts @@ -1,37 +1,41 @@ +import type { Config } from '@oclif/core'; + import nock from 'nock'; import prompts from 'prompts'; -import { describe, beforeAll, afterEach, it, expect, vi } from 'vitest'; +import { describe, beforeAll, beforeEach, afterEach, it, expect, vi } from 'vitest'; -import UpdateVersionCommand from '../../../src/cmds/versions/update.js'; import APIError from '../../../src/lib/apiError.js'; import getAPIMock from '../../helpers/get-api-mock.js'; +import setupOclifConfig from '../../helpers/setup-oclif-config.js'; const key = 'API_KEY'; const version = '1.0.0'; -const updateVersion = new UpdateVersionCommand(); - describe('rdme versions:update', () => { + let oclifConfig: Config; + let run: (args?: string[]) => Promise; + beforeAll(() => { nock.disableNetConnect(); }); + beforeEach(async () => { + oclifConfig = await setupOclifConfig(); + run = (args?: string[]) => oclifConfig.runCommand('versions:update', args); + }); + afterEach(() => nock.cleanAll()); it('should prompt for login if no API key provided', async () => { const consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {}); prompts.inject(['this-is-not-an-email', 'password', 'subdomain']); - // @ts-expect-error deliberately passing in bad data - await expect(updateVersion.run({})).rejects.toStrictEqual(new Error('You must provide a valid email address.')); + await expect(run()).rejects.toStrictEqual(new Error('You must provide a valid email address.')); consoleInfoSpy.mockRestore(); }); it('should error in CI if no API key provided', async () => { process.env.TEST_RDME_CI = 'true'; - // @ts-expect-error deliberately passing in bad data - await expect(updateVersion.run({})).rejects.toStrictEqual( - new Error('No project API key provided. Please use `--key`.'), - ); + await expect(run()).rejects.toStrictEqual(new Error('No project API key provided. Please use `--key`.')); delete process.env.TEST_RDME_CI; }); @@ -59,7 +63,7 @@ describe('rdme versions:update', () => { .basicAuth({ user: key }) .reply(201, updatedVersionObject); - await expect(updateVersion.run({ key })).resolves.toBe(`Version ${versionToChange} updated successfully.`); + await expect(run(['--key', key])).resolves.toBe(`Version ${versionToChange} updated successfully.`); mockRequest.done(); }); @@ -88,16 +92,23 @@ describe('rdme versions:update', () => { .reply(201, updatedVersionObject); await expect( - updateVersion.run({ + run([ + '--key', key, - version: versionToChange, - newVersion: renamedVersion, - deprecated: 'true', - beta: 'true', - main: 'false', - codename: 'updated-test', - hidden: 'false', - }), + versionToChange, + '--newVersion', + renamedVersion, + '--deprecated', + 'true', + '--beta', + 'true', + '--main', + 'false', + '--codename', + 'updated-test', + '--hidden', + 'false', + ]), ).resolves.toBe(`Version ${versionToChange} updated successfully.`); mockRequest.done(); }); @@ -127,16 +138,23 @@ describe('rdme versions:update', () => { .reply(201, updatedVersionObject); await expect( - updateVersion.run({ + run([ + '--key', key, - version: versionToChange, - newVersion: renamedVersion, - beta: 'false', - deprecated: 'false', - main: 'false', - codename: 'updated-test', - hidden: 'true', - }), + versionToChange, + '--newVersion', + renamedVersion, + '--beta', + 'false', + '--deprecated', + 'false', + '--main', + 'false', + '--codename', + 'updated-test', + '--hidden', + 'true', + ]), ).resolves.toBe(`Version ${versionToChange} updated successfully.`); mockRequest.done(); }); @@ -167,14 +185,19 @@ describe('rdme versions:update', () => { .reply(201, updatedVersionObject); await expect( - updateVersion.run({ + run([ + '--key', key, - version: versionToChange, - newVersion: renamedVersion, - main: 'false', - codename: 'updated-test', - hidden: 'false', - }), + versionToChange, + '--newVersion', + renamedVersion, + '--main', + 'false', + '--codename', + 'updated-test', + '--hidden', + 'false', + ]), ).resolves.toBe(`Version ${versionToChange} updated successfully.`); mockRequest.done(); }); @@ -204,14 +227,19 @@ describe('rdme versions:update', () => { .reply(201, updatedVersionObject); await expect( - updateVersion.run({ + run([ + '--key', key, - version: versionToChange, - beta: 'false', - main: 'false', - codename: 'updated-test', - hidden: 'false', - }), + versionToChange, + '--beta', + 'false', + '--main', + 'false', + '--codename', + 'updated-test', + '--hidden', + 'false', + ]), ).resolves.toBe(`Version ${versionToChange} updated successfully.`); mockRequest.done(); }); @@ -238,15 +266,21 @@ describe('rdme versions:update', () => { .reply(201, updatedVersionObject); await expect( - updateVersion.run({ + run([ + '--key', key, - version: versionToChange, - newVersion: renamedVersion, - deprecated: 'true', - beta: 'false', - main: 'true', - hidden: 'true', - }), + versionToChange, + '--newVersion', + renamedVersion, + '--deprecated', + 'true', + '--beta', + 'false', + '--main', + 'true', + '--hidden', + 'true', + ]), ).resolves.toBe(`Version ${versionToChange} updated successfully.`); mockRequest.done(); }); @@ -282,97 +316,41 @@ describe('rdme versions:update', () => { .basicAuth({ user: key }) .reply(400, errorResponse); - await expect(updateVersion.run({ key, version })).rejects.toStrictEqual(new APIError(errorResponse)); + await expect(run(['--key', key, version])).rejects.toThrow(new APIError(errorResponse)); mockRequest.done(); }); describe('bad flag values', () => { - it('should throw if non-boolean `beta` flag is passed', async () => { + it('should throw if non-boolean `beta` flag is passed', () => { const versionToChange = '1.1.0'; - const mockRequest = getAPIMock() - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }) - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }); - - await expect( - updateVersion.run({ - key, - version: versionToChange, - // @ts-expect-error deliberately passing a bad value here - beta: 'hi', - }), - ).rejects.toStrictEqual(new Error("Invalid option passed for 'beta'. Must be 'true' or 'false'.")); - mockRequest.done(); + return expect(run(['--key', key, versionToChange, '--beta', 'hi'])).rejects.toThrow( + 'Expected --beta=hi to be one of: true, false', + ); }); - it('should throw if non-boolean `deprecated` flag is passed', async () => { + it('should throw if non-boolean `deprecated` flag is passed', () => { const versionToChange = '1.1.0'; - const mockRequest = getAPIMock() - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }) - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }); - - await expect( - updateVersion.run({ - key, - version: versionToChange, - // @ts-expect-error deliberately passing a bad value here - deprecated: 'hi', - }), - ).rejects.toStrictEqual(new Error("Invalid option passed for 'deprecated'. Must be 'true' or 'false'.")); - mockRequest.done(); + return expect(run(['--key', key, versionToChange, '--deprecated', 'hi'])).rejects.toThrow( + 'Expected --deprecated=hi to be one of: true, false', + ); }); - it('should throw if non-boolean `hidden` flag is passed', async () => { + it('should throw if non-boolean `hidden` flag is passed', () => { const versionToChange = '1.1.0'; - const mockRequest = getAPIMock() - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }) - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }); - - await expect( - updateVersion.run({ - key, - version: versionToChange, - // @ts-expect-error deliberately passing a bad value here - hidden: 'hi', - }), - ).rejects.toStrictEqual(new Error("Invalid option passed for 'hidden'. Must be 'true' or 'false'.")); - mockRequest.done(); + return expect(run(['--key', key, versionToChange, '--hidden', 'hi'])).rejects.toThrow( + 'Expected --hidden=hi to be one of: true, false', + ); }); - it('should throw if non-boolean `main` flag is passed', async () => { + it('should throw if non-boolean `main` flag is passed', () => { const versionToChange = '1.1.0'; - const mockRequest = getAPIMock() - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }) - .get(`/api/v1/version/${versionToChange}`) - .basicAuth({ user: key }) - .reply(200, { version: versionToChange }); - - await expect( - updateVersion.run({ - key, - version: versionToChange, - // @ts-expect-error deliberately passing a bad value here - main: 'hi', - }), - ).rejects.toStrictEqual(new Error("Invalid option passed for 'main'. Must be 'true' or 'false'.")); - mockRequest.done(); + return expect(run(['--key', key, versionToChange, '--main', 'hi'])).rejects.toThrow( + 'Expected --main=hi to be one of: true, false', + ); }); }); }); diff --git a/src/cmds/versions/create.ts b/src/cmds/versions/create.ts index c27ea1610..f9dc28f79 100644 --- a/src/cmds/versions/create.ts +++ b/src/cmds/versions/create.ts @@ -12,18 +12,6 @@ import * as promptHandler from '../../lib/prompts.js'; import promptTerminal from '../../lib/promptWrapper.js'; import readmeAPIFetch, { cleanHeaders, handleRes } from '../../lib/readmeAPIFetch.js'; -export interface Options extends CommonOptions { - fork?: string; -} - -export interface CommonOptions { - beta?: 'true' | 'false'; - codename?: string; - deprecated?: 'true' | 'false'; - hidden?: 'true' | 'false'; - main?: 'true' | 'false'; -} - export default class CreateVersionCommand extends BaseCommand { static description = 'Create a new version for your project.'; diff --git a/src/cmds/versions/update.ts b/src/cmds/versions/update.ts index 8203579d9..58f032276 100644 --- a/src/cmds/versions/update.ts +++ b/src/cmds/versions/update.ts @@ -1,50 +1,37 @@ -import type { CommonOptions } from './create.js'; import type { Version } from './index.js'; -import type { AuthenticatedCommandOptions } from '../../lib/baseCommand.js'; +import { Args, Flags } from '@oclif/core'; import { Headers } from 'node-fetch'; import prompts from 'prompts'; -import Command, { CommandCategories } from '../../lib/baseCommand.js'; +import BaseCommand from '../../lib/baseCommandNew.js'; import castStringOptToBool from '../../lib/castStringOptToBool.js'; +import { baseVersionFlags, keyFlag } from '../../lib/flags.js'; import * as promptHandler from '../../lib/prompts.js'; import promptTerminal from '../../lib/promptWrapper.js'; import readmeAPIFetch, { cleanHeaders, handleRes } from '../../lib/readmeAPIFetch.js'; import { getProjectVersion } from '../../lib/versionSelect.js'; -export interface Options extends CommonOptions { - newVersion?: string; -} - -export default class UpdateVersionCommand extends Command { - constructor() { - super(); +export default class UpdateVersionCommand extends BaseCommand { + static description = 'Update an existing version for your project.'; - this.command = 'versions:update'; - this.usage = 'versions:update [options]'; - this.description = 'Update an existing version for your project.'; - this.cmdCategory = CommandCategories.VERSIONS; - - this.hiddenArgs = ['version']; - this.args = [ - this.getKeyArg(), - { - name: 'newVersion', - type: String, - description: 'What should the version be renamed to?', - }, - ...this.getVersionOpts(), - ]; - } + static args = { + version: Args.string({ description: "The existing version you'd like to update." }), + }; - async run(opts: AuthenticatedCommandOptions) { - await super.run(opts); + static flags = { + newVersion: Flags.string({ description: 'What should the version be renamed to?' }), + key: keyFlag, + ...baseVersionFlags, + }; - const { key, version, newVersion, codename, main, beta, hidden, deprecated } = opts; + async run(): Promise { + const { version } = this.args; + const { key, newVersion, codename, main, beta, hidden, deprecated } = this.flags; const selectedVersion = await getProjectVersion(version, key); - Command.debug(`selectedVersion: ${selectedVersion}`); + this.debug(`selectedVersion: ${selectedVersion}`); // TODO: I think this fetch here is unnecessary but // it will require a bigger refactor of getProjectVersion diff --git a/src/lib/castStringOptToBool.ts b/src/lib/castStringOptToBool.ts index 1b4425767..f7e8fcb00 100644 --- a/src/lib/castStringOptToBool.ts +++ b/src/lib/castStringOptToBool.ts @@ -1,14 +1,15 @@ -import type { Options as CreateOptions } from '../cmds/versions/create.js'; -import type { Options as UpdateOptions } from '../cmds/versions/update.js'; +import type { baseVersionFlags } from './flags.js'; + +/** + * All the boolean flags from the `versions:create` and `versions:update` commands + */ +type VersionBooleanOpts = Exclude; /** * Takes a CLI flag that is expected to be a 'true' or 'false' string * and casts it to a boolean. */ -export default function castStringOptToBool( - opt: string | undefined, - optName: keyof CreateOptions | keyof UpdateOptions, -) { +export default function castStringOptToBool(opt: string | undefined, optName: VersionBooleanOpts) { if (!opt) { return undefined; }