Skip to content

Commit

Permalink
fix: use esm build when using import (#392)
Browse files Browse the repository at this point in the history
* fix: use esm build in when using import

* fix: cjs warning

* fix: examples

* chore: upgrade deps

* chore: update node versions

* fix: types

* chore: ci

* chore: upgrade deps

* chore: ignore smoke test yarn.lock

* chore: upgrade deps

* chore: debug

* chore: fix smoke tests
  • Loading branch information
hongaar committed May 17, 2022
1 parent eecc2ca commit 03e5cdb
Show file tree
Hide file tree
Showing 22 changed files with 1,400 additions and 2,251 deletions.
14 changes: 10 additions & 4 deletions .github/workflows/ci.yaml
Expand Up @@ -2,19 +2,25 @@ name: ci

on:
push:
branches:
- main
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [12, 14, 16]
experimental: [false]
include:
- node: 14
lts: true
- node: 16
lts: true
- node: 17
experimental: true
continue-on-error: ${{ matrix.experimental }}
lts: false
- node: 18
lts: false
continue-on-error: ${{ ! matrix.lts }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -9,3 +9,4 @@
node_modules/
lib/
coverage/
tests/smoke/*/yarn.lock
8 changes: 5 additions & 3 deletions examples/calculator.ts
@@ -1,11 +1,13 @@
import { program, command, argument } from '../src'
import { argument, command, program } from '../src/index.js'

type Args = { number: number[] }

const number = argument('number', { type: 'number', variadic: true })

const makeOutput = (op: string, initial = (args: Args) => 0) => (args: Args) =>
args.number.reduce((sum, arg) => eval(`${sum} ${op} ${arg}`), initial(args))
const makeOutput =
(op: string, initial = (args: Args) => 0) =>
(args: Args) =>
args.number.reduce((sum, arg) => eval(`${sum} ${op} ${arg}`), initial(args))

program()
.description('calculator')
Expand Down
2 changes: 1 addition & 1 deletion examples/cat.ts
@@ -1,5 +1,5 @@
import { readFileSync } from 'fs'
import { command, program } from '../src'
import { command, program } from '../src/index.js'

const cat = command('cat')
.description('Concatenate files')
Expand Down
4 changes: 2 additions & 2 deletions examples/dice.ts
@@ -1,4 +1,4 @@
import { program, command } from '../src'
import { command, program } from '../src/index.js'

async function rng(bounds: [number, number]) {
const [min, max] = bounds
Expand All @@ -14,4 +14,4 @@ const dice = program().add(
})
)

dice.repl()
dice.runOrRepl()
26 changes: 9 additions & 17 deletions examples/errors.ts
@@ -1,55 +1,47 @@
import { command, program } from '../src'
import { command, program } from '../src/index.js'

const app = program()

const syncOk = command('sync')
.description('Print sync message')
.action(function () {
console.log('ok/sync')
})
.action(() => 'ok/sync')

const asyncOk = command('async')
.description('Print async message')
.action(async function () {
console.log('ok/async')
})
.action(async () => 'ok/async')

const syncNok = command('sync')
.description('Throw sync error')
.action(function () {
.action(() => {
throw new Error('nok/sync')
})

const asyncNok = command('async')
.description('Throw async error')
.action(async function () {
.action(async () => {
throw new Error('nok/async')
})

const syncValidation = command('sync')
.description('Test validation error with sync handler')
.argument('required')
.action(function () {
console.log('call without arguments')
})
.action(() => 'call without arguments')

const asyncValidation = command('async')
.description('Test validation error with async handler')
.argument('required')
.action(async function () {
console.log('call without arguments')
})
.action(async () => 'call without arguments')

const noHandler = command('no_handler').description(
'Test missing command handler'
)

// Say bye
const success = (resolved: unknown) => console.log('[success]', resolved)
const success = (resolved: unknown) => console.log('resolved:', resolved)

// Print error message only (omit stack trace) and exit with a meaningful status
const fail = (error: any) => {
console.error('[failed]', String(error))
console.error('rejected:', String(error))

if (!app.isRepl()) {
process.exit(42)
Expand Down
2 changes: 1 addition & 1 deletion examples/foo.ts
@@ -1,4 +1,4 @@
import { program, command } from '../src'
import { command, program } from '../src/index.js'

const foo = command('foo')
.description('Outputs "bar".')
Expand Down
2 changes: 1 addition & 1 deletion examples/pizza.ts
@@ -1,4 +1,4 @@
import { program, command } from '../src'
import { command, program } from '../src/index.js'

const cmd = command()
.argument('address', {
Expand Down
2 changes: 1 addition & 1 deletion examples/pretty.ts
@@ -1,4 +1,4 @@
import { command, program } from '../src'
import { command, program } from '../src/index.js'

const app = program()
.description('JSON pretty printer')
Expand Down
2 changes: 1 addition & 1 deletion examples/repl.ts
@@ -1,4 +1,4 @@
import { command, program } from '../src'
import { command, program } from '../src/index.js'

let url: string | null = null

Expand Down
2 changes: 1 addition & 1 deletion examples/simple.ts
@@ -1,4 +1,4 @@
import { program, command } from '../src'
import { command, program } from '../src/index.js'

const echo = command('concat')
.description('Concatenate input')
Expand Down
2 changes: 1 addition & 1 deletion examples/types.ts
@@ -1,4 +1,4 @@
import { command, program } from '../src'
import { command, program } from '../src/index.js'

/**
* Keep in mind that argument/option types are not validated at runtime.
Expand Down
22 changes: 11 additions & 11 deletions package.json
Expand Up @@ -30,7 +30,7 @@
"main": "./lib/cjs/index.cjs",
"exports": {
"types": "./lib/types/index.d.ts",
"import": "./lib/cjs/index.cjs",
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.cjs"
},
"type": "module",
Expand All @@ -50,33 +50,33 @@
"test:unit": "jest",
"test:types": "tsd",
"test:smoke": "./tests/smoke/run.sh",
"start": "ts-node",
"start": "node --loader ts-node/esm",
"release": "semantic-release"
},
"dependencies": {
"@types/yargs": "17.0.2",
"@types/yargs": "17.0.10",
"enquirer": "^2.3.6",
"string-argv": "^0.3.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.4.0"
"yargs": "^17.5.1"
},
"devDependencies": {
"@semantic-release/changelog": "6.0.1",
"@semantic-release/git": "10.0.1",
"@types/jest": "27.4.1",
"@types/node": "17.0.23",
"@types/jest": "27.5.1",
"@types/node": "17.0.33",
"convert-extension": "0.3.0",
"doctoc": "2.1.0",
"husky": "7.0.4",
"jest": "27.5.1",
"doctoc": "2.2.0",
"husky": "8.0.1",
"jest": "28.1.0",
"leasot": "13.1.0",
"mock-argv": "2.0.8",
"prettier": "2.6.2",
"semantic-release": "19.0.2",
"ts-jest": "27.1.4",
"ts-jest": "28.0.2",
"ts-node": "10.7.0",
"tsd": "0.20.0",
"typescript": "4.6.3"
"typescript": "4.6.4"
},
"tsd": {
"directory": "tests/types"
Expand Down
22 changes: 9 additions & 13 deletions src/command.ts
@@ -1,13 +1,10 @@
import { Arguments as BaseArguments, Argv, CommandModule } from 'yargs'
import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'
import { Argument, ArgumentOptions } from './argument.js'
import { InferArgType } from './baseArg.js'
import { Option, OptionOptions } from './option.js'
import { prompter } from './prompter.js'

export type Arguments<T = {}> = T &
BaseArguments<T> & {
__promise?: Promise<any>
}
export type YargsArguments<T = {}> = ArgumentsCamelCase<T>

type CommandOptions = {
/**
Expand All @@ -30,7 +27,7 @@ type CommandOptions = {
type CommandRunner = (command: string) => Promise<unknown>

export interface HandlerFn<T> {
(args: Omit<T, '_' | '$0'>, commandRunner: CommandRunner): Promise<any> | any
(args: T, commandRunner: CommandRunner): Promise<any> | any
}

function isArgument(obj: Argument | Option | Command): obj is Argument {
Expand Down Expand Up @@ -213,6 +210,7 @@ export class Command<T = {}> {
aliases: [],
describe: this.options.hidden ? false : this.options.description || '',
builder: this.getBuilder(commandRunner),
// @ts-ignore Our handler returns a different type than void
handler: this.getHandler(commandRunner),
}
return module
Expand Down Expand Up @@ -261,23 +259,21 @@ export class Command<T = {}> {
}

/**
* Wraps the actual command handler to insert prompt and async handler logic.
* Takes command runner.
* Wraps the actual command handler to insert prompt and handler logic.
*/
private getHandler(commandRunner: CommandRunner) {
return async (argv: Arguments<T>) => {
const { _, $0, ...rest } = argv
return async (argv: YargsArguments<T> & { __promise?: Promise<any> }) => {
const prompterInstance = prompter(
[...this.getArguments(), ...this.getOptions()],
rest
argv
)

let promise = prompterInstance.prompt()

promise = promise.then((args) => {
promise = promise.then(({ _, $0, __promise, ...args }) => {
// @todo coerce all types and remove coerce option from baseArg
if (this.handler) {
return this.handler(args, commandRunner)
return this.handler(args as unknown as T, commandRunner)
}

// Display help if this command contains sub-commands
Expand Down
4 changes: 2 additions & 2 deletions src/program.ts
Expand Up @@ -4,7 +4,7 @@ import path from 'path'
import TypedEventEmitter from 'typed-emitter'
import { Argv, ParserConfigurationOptions } from 'yargs'
import createYargs from 'yargs/yargs'
import { Arguments, Command, command } from './command.js'
import { command, Command } from './command.js'
import { history, History } from './history.js'
import { Repl, repl } from './repl.js'
import { isPromise } from './utils.js'
Expand Down Expand Up @@ -215,7 +215,7 @@ export class Program extends (EventEmitter as new () => TypedEventEmitter<Events
return new Promise((resolve, reject) => {
// @ts-ignore Not sure why this is needed?
this.createYargsInstance()
.parse(cmd, {}, (err, argv: Arguments | Promise<Arguments>, output) => {
.parse(cmd, {}, (err, argv, output) => {
// We don't use yargs 17 promise style argv
if (isPromise(argv)) {
throw new Error('argv is of unexpected type')
Expand Down
8 changes: 7 additions & 1 deletion src/prompter.ts
@@ -1,7 +1,13 @@
import { prompt } from 'enquirer'
import Enquirer from 'enquirer'
import { Argument } from './argument.js'
import { Option } from './option.js'

/**
* Workaround for "The requested module 'enquirer' is a CommonJS module, which
* may not support all module.exports as named exports."
*/
const prompt = Enquirer.prompt

type PromptType =
| 'input'
| 'number'
Expand Down
7 changes: 3 additions & 4 deletions src/repl.ts
@@ -1,4 +1,3 @@
import { Prompt } from 'enquirer'
import { CompleterResult } from 'readline'
import nodeRepl, { REPLServer } from 'repl'
import { parseArgsStringToArgv } from 'string-argv'
Expand Down Expand Up @@ -53,9 +52,9 @@ export class Repl {
// Setup history
this.history?.hydrateReplServer(this.server)

// Fixes bug with hidden cursor after enquirer prompt
// @ts-ignore
new Prompt().cursorShow()
// Fixes bug with hidden cursor after enquirer prompt, this is identical to
// the enquirer method Prompt.cursorShow()
process.stdout.write(`\u001b[?25h`)
}

public stop() {
Expand Down

0 comments on commit 03e5cdb

Please sign in to comment.