Skip to content

Commit

Permalink
chore: configurable exit codes via CLI pjson
Browse files Browse the repository at this point in the history
  • Loading branch information
WillieRuemmele committed Nov 9, 2023
1 parent 8990432 commit f6e4834
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 15 deletions.
4 changes: 3 additions & 1 deletion src/config/cache.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {Plugin} from '../interfaces'
import {PJSON, Plugin} from '../interfaces'

type CacheContents = {
rootPlugin: Plugin
rootCli: PJSON.CLI
}

type ValueOf<T> = T[keyof T]
Expand All @@ -20,6 +21,7 @@ export default class Cache extends Map<keyof CacheContents, ValueOf<CacheContent
}

public get(key: 'rootPlugin'): Plugin | undefined
public get(key: 'rootCli'): PJSON.CLI | undefined
public get(key: keyof CacheContents): ValueOf<CacheContents> | undefined {
return super.get(key)
}
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/pjson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ export namespace PJSON {
jitPlugins?: Record<string, string>
npmRegistry?: string
nsisCustomization?: string
exitCodes?: {
requiredArgs?: number
failedFlagValidation?: number
nonExistentFlag?: number
unexpectedArgs?: number
invalidArgsSpec?: number
}
schema?: number
scope?: string
pluginPrefix?: string
Expand Down
36 changes: 28 additions & 8 deletions src/parser/validate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Cache from '../config/cache'
import {Arg, Flag, FlagRelationship, ParserInput, ParserOutput} from '../interfaces/parser'
import {uniq} from '../util/util'
import {
Expand All @@ -14,14 +15,21 @@ export async function validate(parse: {input: ParserInput; output: ParserOutput}

function validateArgs() {
if (parse.output.nonExistentFlags?.length > 0) {
// this is the first error that could be thrown, exit codes 1 and 2 have been used, so we'll start with 3
throw new NonExistentFlagsError({exit: 3, flags: parse.output.nonExistentFlags, parse})
throw new NonExistentFlagsError({
exit: Cache.getInstance().get('rootCli')?.pjson.oclif.exitCodes.nonExistentFlag,
flags: parse.output.nonExistentFlags,
parse,
})
}

const maxArgs = Object.keys(parse.input.args).length
if (parse.input.strict && parse.output.argv.length > maxArgs) {
const extras = parse.output.argv.slice(maxArgs)
throw new UnexpectedArgsError({args: extras, exit: 4, parse})
throw new UnexpectedArgsError({
args: extras,
exit: Cache.getInstance().get('rootCli')?.pjson.oclif.exitCodes.unexpectedArgs,
parse,
})
}

const missingRequiredArgs: Arg<any>[] = []
Expand All @@ -33,7 +41,11 @@ export async function validate(parse: {input: ParserInput; output: ParserOutput}
} else if (hasOptional) {
// (required arg) check whether an optional has occurred before
// optionals should follow required, not before
throw new InvalidArgsSpecError({args: parse.input.args, exit: 5, parse})
throw new InvalidArgsSpecError({
args: parse.input.args,
exit: Cache.getInstance().get('rootCli')?.pjson.oclif.exitCodes.invalidArgsSpec,
parse,
})
}

if (arg.required && !parse.output.args[name] && parse.output.args[name] !== 0) {
Expand All @@ -46,8 +58,12 @@ export async function validate(parse: {input: ParserInput; output: ParserOutput}
.filter(([_, flagDef]) => flagDef.type === 'option' && Boolean(flagDef.multiple))
.map(([name]) => name)

// if a command is missing args -> exit 6
throw new RequiredArgsError({args: missingRequiredArgs, exit: 6, flagsWithMultiple, parse})
throw new RequiredArgsError({
args: missingRequiredArgs,
exit: Cache.getInstance().get('rootCli')?.pjson.oclif.exitCodes.requiredArgs,
flagsWithMultiple,
parse,
})
}
}

Expand Down Expand Up @@ -78,8 +94,12 @@ export async function validate(parse: {input: ParserInput; output: ParserOutput}
const results = await Promise.all(promises)

const failed = results.filter((r) => r.status === 'failed')
// if a command is missing flags -> exit 7
if (failed.length > 0) throw new FailedFlagValidationError({exit: 7, failed, parse})
if (failed.length > 0)
throw new FailedFlagValidationError({
exit: Cache.getInstance().get('rootCli')?.pjson.oclif.exitCodes.failedFlagValidation,
failed,
parse,
})
}

async function resolveFlags(flags: FlagRelationship[]): Promise<Record<string, unknown>> {
Expand Down
47 changes: 41 additions & 6 deletions test/parser/validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,42 @@
import {expect} from 'chai'
import {fail} from 'node:assert'
import {SinonSandbox, SinonStub, createSandbox} from 'sinon'

import Cache from '../../src/config/cache'
import {CLIError} from '../../src/errors'
import {validate} from '../../src/parser/validate'

let sandbox: SinonSandbox
let cacheStub: SinonStub

const cache = Cache.getInstance()
before(() => {
sandbox = createSandbox()
})

beforeEach(() => {
// don't stub the entire rootCli object
// @ts-ignore
cacheStub = sandbox
.stub(cache, 'get')
.withArgs('rootCli')
.returns({
pjson: {
oclif: {
exitCodes: {
requiredArgs: 3,
failedFlagValidation: 7,
nonExistentFlag: 5,
unexpectedArgs: 6,
invalidArgsSpec: 4,
},
},
},
})
})

afterEach(() => sandbox.restore())

describe('validate', () => {
const input = {
argv: [],
Expand Down Expand Up @@ -41,7 +74,7 @@ describe('validate', () => {
fail('should have thrown')
} catch (error) {
const err = error as CLIError
expect(err.oclif.exit).to.equal(3)
expect(err.oclif.exit).to.equal(5)
expect(err.message).to.include('Nonexistent flag: foobar')
}
})
Expand All @@ -59,7 +92,7 @@ describe('validate', () => {
fail('should have thrown')
} catch (error) {
const err = error as CLIError
expect(err.oclif.exit).to.equal(4)
expect(err.oclif.exit).to.equal(6)
expect(err.message).to.include('Unexpected arguments: found, me')
}
})
Expand Down Expand Up @@ -103,7 +136,7 @@ describe('validate', () => {
} catch (error) {
const err = error as CLIError
expect(err.message).to.include('Invalid argument spec')
expect(err.oclif.exit).to.equal(5)
expect(err.oclif.exit).to.equal(4)
}
})

Expand Down Expand Up @@ -276,7 +309,7 @@ describe('validate', () => {
} catch (error) {
const err = error as CLIError
expect(err.message).to.include('Missing 1 required arg')
expect(err.oclif.exit).to.equal(6)
expect(err.oclif.exit).to.equal(3)
}
})

Expand Down Expand Up @@ -932,6 +965,9 @@ describe('validate', () => {
})

it('should fail if the specified flags whose when property resolves to true in exclusive, flag has a false value', async () => {
// no values set for error overrides, will default to 2
cacheStub.reset()

const input = {
argv: [],
flags: {
Expand Down Expand Up @@ -967,8 +1003,7 @@ describe('validate', () => {
fail('should have thrown')
} catch (error) {
const err = error as CLIError
expect(err.oclif.exit).to.equal(7)

expect(err.oclif.exit).to.equal(2)
expect(err.message).to.include('--cookies=false cannot also be provided when using --dessert')
}
})
Expand Down

0 comments on commit f6e4834

Please sign in to comment.