diff --git a/__tests__/lib/checkFile.test.ts b/__tests__/lib/checkFile.test.ts deleted file mode 100644 index 3188af19f..000000000 --- a/__tests__/lib/checkFile.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import fs from 'fs'; - -import { checkFilePath } from '../../src/lib/checkFile'; - -describe('#checkFilePath', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should return error for empty path value', () => { - return expect(checkFilePath('')).toBe('An output path must be supplied.'); - }); - - it('should return error if path already exists', () => { - expect.assertions(2); - const testPath = 'path-that-already-exists'; - - fs.existsSync = jest.fn(() => true); - - expect(checkFilePath(testPath)).toBe('Specified output path already exists.'); - expect(fs.existsSync).toHaveBeenCalledWith(testPath); - }); - - it("should return true if the path doesn't exist", () => { - expect.assertions(2); - const testPath = 'path-that-does-not-exist'; - - fs.existsSync = jest.fn(() => false); - - expect(checkFilePath(testPath)).toBe(true); - expect(fs.existsSync).toHaveBeenCalledWith(testPath); - }); -}); diff --git a/__tests__/lib/validatePromptInput.test.ts b/__tests__/lib/validatePromptInput.test.ts new file mode 100644 index 000000000..30c7ce2d5 --- /dev/null +++ b/__tests__/lib/validatePromptInput.test.ts @@ -0,0 +1,55 @@ +import fs from 'fs'; + +import { validateFilePath, validateSubdomain } from '../../src/lib/validatePromptInput'; + +describe('#validateFilePath', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return error for empty path value', () => { + return expect(validateFilePath('')).toBe('An output path must be supplied.'); + }); + + it('should return error if path already exists', () => { + expect.assertions(2); + const testPath = 'path-that-already-exists'; + + fs.existsSync = jest.fn(() => true); + + expect(validateFilePath(testPath)).toBe('Specified output path already exists.'); + expect(fs.existsSync).toHaveBeenCalledWith(testPath); + }); + + it("should return true if the path doesn't exist", () => { + expect.assertions(2); + const testPath = 'path-that-does-not-exist'; + + fs.existsSync = jest.fn(() => false); + + expect(validateFilePath(testPath)).toBe(true); + expect(fs.existsSync).toHaveBeenCalledWith(testPath); + }); +}); + +describe('#validateSubdomain', () => { + it('should validate basic subdomain', () => { + expect(validateSubdomain('subdomain')).toBe(true); + }); + + it('should validate subdomain with other characters', () => { + expect(validateSubdomain('test-Subdomain123')).toBe(true); + }); + + it('should reject subdomain with spaces', () => { + expect(validateSubdomain('test subdomain')).toBe( + 'Project subdomain must contain only letters, numbers and dashes.' + ); + }); + + it('should reject subdomain with special characters', () => { + expect(validateSubdomain('test-subdomain!')).toBe( + 'Project subdomain must contain only letters, numbers and dashes.' + ); + }); +}); diff --git a/src/cmds/openapi/convert.ts b/src/cmds/openapi/convert.ts index c1d4ba14d..011dda2b6 100644 --- a/src/cmds/openapi/convert.ts +++ b/src/cmds/openapi/convert.ts @@ -8,9 +8,9 @@ import chalk from 'chalk'; import prompts from 'prompts'; import Command, { CommandCategories } from '../../lib/baseCommand'; -import { checkFilePath } from '../../lib/checkFile'; import prepareOas from '../../lib/prepareOas'; import promptTerminal from '../../lib/promptWrapper'; +import { validateFilePath } from '../../lib/validatePromptInput'; export interface Options { spec?: string; @@ -72,7 +72,7 @@ export default class OpenAPIConvertCommand extends Command { const extension = path.extname(specPath); return `${path.basename(specPath).split(extension)[0]}.openapi${extension}`; }, - validate: value => checkFilePath(value), + validate: value => validateFilePath(value), }, ]); diff --git a/src/cmds/openapi/reduce.ts b/src/cmds/openapi/reduce.ts index c53f1e6fe..7e18dbbc3 100644 --- a/src/cmds/openapi/reduce.ts +++ b/src/cmds/openapi/reduce.ts @@ -11,10 +11,10 @@ import ora from 'ora'; import prompts from 'prompts'; import Command, { CommandCategories } from '../../lib/baseCommand'; -import { checkFilePath } from '../../lib/checkFile'; import { oraOptions } from '../../lib/logger'; import prepareOas from '../../lib/prepareOas'; import promptTerminal from '../../lib/promptWrapper'; +import { validateFilePath } from '../../lib/validatePromptInput'; export interface Options { spec?: string; @@ -170,7 +170,7 @@ export default class OpenAPIReduceCommand extends Command { const extension = path.extname(specPath); return `${path.basename(specPath).split(extension)[0]}.reduced${extension}`; }, - validate: value => checkFilePath(value), + validate: value => validateFilePath(value), }, ]); diff --git a/src/lib/createGHA/index.ts b/src/lib/createGHA/index.ts index e9472eaa8..2f7a3c01c 100644 --- a/src/lib/createGHA/index.ts +++ b/src/lib/createGHA/index.ts @@ -9,12 +9,12 @@ import chalk from 'chalk'; import prompts from 'prompts'; import simpleGit from 'simple-git'; -import { checkFilePath, cleanFileName } from '../checkFile'; import configstore from '../configstore'; import { getMajorPkgVersion } from '../getPkgVersion'; import isCI, { isNpmScript, isTest } from '../isCI'; import { debug, info } from '../logger'; import promptTerminal from '../promptWrapper'; +import { cleanFileName, validateFilePath } from '../validatePromptInput'; import yamlBase from './baseFile'; @@ -234,7 +234,7 @@ export default async function createGHA( type: 'text', initial: cleanFileName(`rdme-${command}`), format: prev => getGHAFileName(prev), - validate: value => checkFilePath(value, getGHAFileName), + validate: value => validateFilePath(value, getGHAFileName), }, ], { diff --git a/src/lib/loginFlow.ts b/src/lib/loginFlow.ts index 3626e3003..d4e7b7511 100644 --- a/src/lib/loginFlow.ts +++ b/src/lib/loginFlow.ts @@ -7,6 +7,7 @@ import fetch, { handleRes } from './fetch'; import getCurrentConfig from './getCurrentConfig'; import { debug } from './logger'; import promptTerminal from './promptWrapper'; +import { validateSubdomain } from './validatePromptInput'; interface LoginBody { email?: string; @@ -51,8 +52,9 @@ export default async function loginFlow(otp?: string) { { type: 'text', name: 'project', - message: 'What project are you logging into?', + message: 'What project subdomain are you logging into?', initial: storedConfig.project, + validate: validateSubdomain, }, ]); diff --git a/src/lib/checkFile.ts b/src/lib/validatePromptInput.ts similarity index 61% rename from src/lib/checkFile.ts rename to src/lib/validatePromptInput.ts index aec4e5f73..9f32f20e6 100644 --- a/src/lib/checkFile.ts +++ b/src/lib/validatePromptInput.ts @@ -16,7 +16,7 @@ export const cleanFileName = (input: string) => input.replace(/[^a-z0-9]/gi, '-' * @returns true if path is valid (i.e. is non-empty and doesn't already exist), * otherwise a string containing the error message */ -export function checkFilePath(value: string, getFullPath: (file: string) => string = file => file) { +export function validateFilePath(value: string, getFullPath: (file: string) => string = file => file) { if (value.length) { const fullPath = getFullPath(value); if (!fs.existsSync(fullPath)) { @@ -28,3 +28,16 @@ export function checkFilePath(value: string, getFullPath: (file: string) => stri return 'An output path must be supplied.'; } + +/** + * Validates that a project subdomain value is valid. + * + * @param value the terminal input + * @returns true if the subdomain value is valid, else an error message + */ +export function validateSubdomain(value: string) { + return ( + // eslint-disable-next-line unicorn/no-unsafe-regex + /^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/.test(value) || 'Project subdomain must contain only letters, numbers and dashes.' + ); +}