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

fix: args typed with undefined #389

Merged
merged 10 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 25 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,40 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install
- run: yarn test
- run: yarn build
- run: yarn test
- uses: actions/upload-artifact@v3
with:
name: lib
path: lib

coverage:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install
- uses: actions/download-artifact@v3
with:
name: lib
path: lib
- uses: paambaati/[email protected]
env:
CC_TEST_REPORTER_ID: e1a2f8ecd90c13810c302d9cdfb4a26a5b79666e899c4f353e558416c168da0d
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- uses: actions/cache@v3
with:
node-version: 14
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install
- run: npx semantic-release
env:
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib"
"typescript.tsdk": "node_modules/typescript/lib"
}
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -737,9 +737,7 @@ Optionally deploy to GitHub, S3, etc. using your preferred CD method if needed.

## Todo

- [ ] Better code coverage
- [ ] Consider resolving ambiguity in _prompt_ param/method
- [ ] Async autocomplete method for arg values
See [TODO.md](TODO.md)

## Contributing

Expand Down
12 changes: 8 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
### TODOs
| Filename | line # | TODO
|:------|:------:|:------
| [src/prompter.ts](src/prompter.ts#L16) | 16 | Wait for upstream change to import types from enquirer
| [src/prompter.ts](src/prompter.ts#L77) | 77 | ignoring type error here, probably need another type
| Filename | line # | TODO |
|:------|:------:|:------|
| [src/baseArg.ts](src/baseArg.ts#L88) | 88 | See if we can add this to autocompleter |
| [src/command.ts](src/command.ts#L278) | 278 | coerce all types and remove coerce option from baseArg |
| [src/command.ts](src/command.ts#L293) | 293 | Upgrade to native async handlers in yarn 17 |
| [src/prompter.ts](src/prompter.ts#L16) | 16 | Wait for upstream change to import types from enquirer |
| [src/prompter.ts](src/prompter.ts#L77) | 77 | ignoring type error here, probably need another type |
| [test-d/command.test-d.ts](test-d/command.test-d.ts#L89) | 89 | option types |
2 changes: 1 addition & 1 deletion examples/cat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { command, program } from '../src'

const cat = command('cat')
.description('Concatenate files')
.argument('files', { variadic: true })
.argument('files', { variadic: true, default: [] })
.action(({ files }) =>
console.log(
files.reduce((str, file) => str + readFileSync(file, 'utf8'), '')
Expand Down
57 changes: 54 additions & 3 deletions examples/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { program, command } from '../src'
import { command, program } from '../src'

/**
* Keep in mind that argument/option types are not validated at runtime.
Expand All @@ -7,8 +7,57 @@ import { program, command } from '../src'
*/

const string = command('string')
.argument('arg', { description: 'Required string argument' })
.option('opt', { description: 'Optional string option', type: 'string' })
.argument('arg1', { description: 'Required string argument' })
.argument('arg2', { optional: true, description: 'Optional string argument' })
.argument('arg3', { variadic: true, description: 'Variadic string argument' })
.option('opt1', { type: 'string', description: 'String option' })
.option('opt2', {
default: 'foo',
description: 'String option with default',
})
.action((args) => {
console.log('Args are', args)
})

const number = command('number')
.argument('arg1', { type: 'number', description: 'Required number argument' })
.argument('arg2', {
type: 'number',
optional: true,
description: 'Optional number argument',
})
.argument('arg3', {
type: 'number',
variadic: true,
description: 'Variadic number argument',
})
.option('opt1', { type: 'number', description: 'number option' })
.option('opt2', { default: 100, description: 'Number option with default' })
.action((args) => {
console.log('Args are', args)
})

const boolean = command('boolean')
.argument('arg1', {
type: 'boolean',
description: 'Required boolean argument',
})
.argument('arg2', {
type: 'boolean',
optional: true,
description: 'Optional boolean argument',
})
.argument('arg3', {
type: 'boolean',
variadic: true,
description: 'Variadic boolean argument',
})
.option('opt1', { type: 'boolean', description: 'number option' })
.option('opt2', {
type: 'boolean',
default: false,
description: 'Number option with default',
})
.action((args) => {
console.log('Args are', args)
})
Expand Down Expand Up @@ -41,6 +90,8 @@ const defaultValues = command('default')
const app = program()
.description('All argument and option types')
.add(string)
.add(number)
.add(boolean)
.add(choices)
.add(defaultValues)

Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
"files": [
"lib"
],
"types": "lib/index.d.ts",
"scripts": {
"prepublishOnly": "yarn build",
"build": "tsc",
"build": "tsc --project tsconfig.build.json",
"watch": "tsc --watch",
"lint": "prettier --write \"src/**/*\"",
"test": "jest",
"test": "tsd && jest",
"start": "ts-node",
"doc:toc": "doctoc README.md",
"doc:todos": "leasot --exit-nicely --reporter markdown \"src/**/*.ts\" > TODO.md"
"doc:todos": "leasot --exit-nicely --reporter markdown \"{src,test-d,tests}/**/*.ts\" > TODO.md"
},
"author": "",
"license": "MIT",
Expand All @@ -57,6 +58,7 @@
"prettier": "2.6.0",
"ts-jest": "27.1.3",
"ts-node": "10.7.0",
"tsd": "^0.19.1",
"typescript": "4.6.2"
}
}
}
70 changes: 34 additions & 36 deletions src/baseArg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,40 @@ export interface BaseArgOptions {
prompt?: true | string
}

// prettier-ignore
export type InferArgType<O extends Options | PositionalOptions, F = unknown> =
/**
* Add support for numeric variadic arguments
*/
O extends {
variadic: true
type: 'number'
}
? Array<number>
: /**
* Add support for string variadic arguments
*/
O extends {
variadic: true
}
? Array<string>
: /**
* Choices with array type
*/ O extends { choices: ReadonlyArray<infer C>; type: 'array' }
? C[]
: /**
* Choices with array default
*/ O extends {
choices: ReadonlyArray<infer C>
default: ReadonlyArray<string>
}
? C[]
: /**
* Prefer choices over default
*/ O extends { choices: ReadonlyArray<infer C> }
? C
: /**
* Allow fallback type
*/
unknown extends InferredOptionType<O>
? F
: InferredOptionType<O>
// Default number
O extends { default: number } ? number :
// Optional number
O extends { type: 'number', optional: true } ? number | undefined :
// Variadic number
O extends { type: 'number', variadic: true } ? Array<number> :
// Number
O extends { type: 'number' } ? number :
// Default boolean
O extends { default: boolean } ? boolean :
// Optional boolean
O extends { type: 'boolean', optional: true } ? boolean | undefined :
// Variadic boolean
O extends { type: 'boolean', variadic: true } ? Array<boolean> :
// Boolean
O extends { type: 'boolean' } ? boolean :
// Default string
O extends { default: string } ? string :
// Optional string
O extends { optional: true } ? string | undefined :
// Variadic string
O extends { variadic: true } ? Array<string> :
// Choices with array type
O extends { choices: ReadonlyArray<infer C>; type: 'array' } ? C[] :
// Choices with array default
O extends { choices: ReadonlyArray<infer C>, default: ReadonlyArray<string> } ? C[] :
// Prefer choices over default
O extends { choices: ReadonlyArray<infer C> } ? C :
// Allow fallback type
unknown extends InferredOptionType<O> ? F :
// yargs type
InferredOptionType<O>

export class BaseArg {
protected name: string
Expand Down Expand Up @@ -87,6 +84,7 @@ export class BaseArg {

/**
* Get possible values, is specified.
* @todo See if we can add this to autocompleter
*/
getChoices() {
return this.options.choices
Expand Down
6 changes: 4 additions & 2 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class Command<T = {}> {
) {
this.add(new Argument(name, options))

return (this as unknown) as Command<
return this as unknown as Command<
T & { [key in K]: InferArgType<O, string> }
>
}
Expand All @@ -107,7 +107,7 @@ export class Command<T = {}> {
) {
this.add(new Option(name, options))

return (this as unknown) as Command<T & { [key in K]: InferArgType<O> }>
return this as unknown as Command<T & { [key in K]: InferArgType<O> }>
}

/**
Expand Down Expand Up @@ -275,6 +275,7 @@ export class Command<T = {}> {
let promise = prompterInstance.prompt()

promise = promise.then((args) => {
// @todo coerce all types and remove coerce option from baseArg
if (this.handler) {
return this.handler(args, commandRunner)
}
Expand All @@ -289,6 +290,7 @@ export class Command<T = {}> {

// Save promise chain on argv instance, so we can access it in parse
// callback.
// @todo Upgrade to native async handlers in yarn 17
argv.__promise = promise

return promise
Expand Down
7 changes: 4 additions & 3 deletions src/option.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Options as BaseOptions, Argv } from 'yargs'
import { BaseArgOptions, BaseArg } from './baseArg'
import { Argv, Options as BaseOptions } from 'yargs'
import { BaseArg, BaseArgOptions } from './baseArg'

// We ignore some not-so-common use cases from the type to make using this
// library easier. They could still be used at runtime but won't be documented
Expand All @@ -14,6 +14,7 @@ type IgnoreOptions =
| 'defaultDescription'
| 'demand'
| 'demandOption'
| 'deprecate'
| 'desc'
| 'describe'
| 'global'
Expand All @@ -28,7 +29,7 @@ type IgnoreOptions =
| 'requiresArg'
| 'skipValidation'
| 'string'
// | 'implies'
| 'implies'

export interface OptionOptions
extends Omit<BaseOptions, IgnoreOptions>,
Expand Down
Loading