Skip to content

Commit

Permalink
feat: convert to typescript and esm (#77)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `read` is now written in TypeScript and types are now
shipped as part of this package.

BREAKING CHANGE: `read` now only exports a named export `{ read }`
and does not have a default export.
  • Loading branch information
lukekarrys committed Nov 16, 2023
1 parent d7887a4 commit 18cb7bd
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 227 deletions.
File renamed without changes.
7 changes: 7 additions & 0 deletions .eslintrc.js → .eslintrc.cjs
Expand Up @@ -12,7 +12,14 @@ module.exports = {
root: true,
ignorePatterns: [
'tap-testdir*/',
'dist/',
],
parser: '@typescript-eslint/parser',
settings: {
'import/resolver': {
typescript: {},
},
},
extends: [
'@npmcli',
...localConfigs,
Expand Down
7 changes: 3 additions & 4 deletions .gitignore
Expand Up @@ -7,26 +7,25 @@ tap-testdir*/

# keep these
!**/.gitignore
!/.commitlintrc.js
!/.eslintrc.js
!/.commitlintrc.cjs
!/.eslintrc.cjs
!/.eslintrc.local.*
!/.github/
!/.gitignore
!/.npmrc
!/.release-please-manifest.json
!/bin/
!/CHANGELOG*
!/CODE_OF_CONDUCT.md
!/CONTRIBUTING.md
!/docs/
!/lib/
!/LICENSE*
!/map.js
!/package.json
!/README*
!/release-please-config.json
!/scripts/
!/SECURITY.md
!/src/
!/tap-snapshots/
!/test/
!/tsconfig.json
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -8,7 +8,8 @@ few more features.
## Usage

```javascript
var read = require("read")
const { read } = require('read')
// or with ESM: import { read } from 'read'
try {
const result = await read(options)
} catch (er) {
Expand Down
82 changes: 0 additions & 82 deletions lib/read.js

This file was deleted.

64 changes: 48 additions & 16 deletions package.json
@@ -1,14 +1,41 @@
{
"name": "read",
"version": "2.1.0",
"main": "lib/read.js",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/read.d.ts",
"default": "./dist/esm/read.js"
},
"require": {
"types": "./dist/commonjs/read.d.ts",
"default": "./dist/commonjs/read.js"
}
}
},
"type": "module",
"tshy": {
"exports": {
"./package.json": "./package.json",
".": "./src/read.ts"
}
},
"dependencies": {
"mute-stream": "~1.0.0"
"mute-stream": "^1.0.0"
},
"devDependencies": {
"@npmcli/eslint-config": "^4.0.0",
"@npmcli/eslint-config": "^4.0.2",
"@npmcli/template-oss": "4.20.0",
"tap": "^16.3.0"
"@types/mute-stream": "^0.0.4",
"@types/tap": "^15.0.11",
"@typescript-eslint/parser": "^6.11.0",
"c8": "^8.0.1",
"eslint-import-resolver-typescript": "^3.6.1",
"tap": "^16.3.9",
"ts-node": "^10.9.1",
"tshy": "^1.8.0",
"typescript": "^5.2.2"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
Expand All @@ -21,29 +48,34 @@
},
"license": "ISC",
"scripts": {
"test": "tap",
"prepare": "tshy",
"pretest": "npm run prepare",
"presnap": "npm run prepare",
"test": "c8 tap",
"lint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"",
"postlint": "template-oss-check",
"template-oss-apply": "template-oss-apply --force",
"lintfix": "npm run lint -- --fix",
"snap": "tap",
"snap": "c8 tap",
"posttest": "npm run lint"
},
"files": [
"bin/",
"lib/"
],
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.20.0",
"publish": "true"
"publish": true,
"typescript": true,
"content": "./scripts/template-oss"
},
"main": "./dist/commonjs/read.js",
"types": "./dist/commonjs/read.d.ts",
"tap": {
"statements": 77,
"branches": 75,
"functions": 57,
"lines": 78,
"test-ignore": "fixtures/",
"coverage": false,
"node-arg": [
"--no-warnings",
"--loader",
"ts-node/esm"
],
"ts": false,
"nyc-arg": [
"--exclude",
"tap-snapshots/**"
Expand Down
10 changes: 10 additions & 0 deletions scripts/template-oss/index.js
@@ -0,0 +1,10 @@
export default {
rootModule: {
add: {
'package.json': {
file: 'pkg.json',
overwrite: false,
},
},
},
}
6 changes: 6 additions & 0 deletions scripts/template-oss/pkg.json
@@ -0,0 +1,6 @@
{
"scripts": {
"test": "c8 tap",
"snap": "c8 tap"
}
}
129 changes: 129 additions & 0 deletions src/read.ts
@@ -0,0 +1,129 @@
import Mute from 'mute-stream'
import { Completer, AsyncCompleter, createInterface, ReadLineOptions } from 'readline'

export interface Options<T extends string | number = string> {
default?: T
input?: ReadLineOptions['input'] & {
isTTY?: boolean
}
output?: ReadLineOptions['output'] & {
isTTY?: boolean
}
prompt?: string
silent?: boolean
timeout?: number
edit?: boolean
terminal?: boolean
replace?: string,
completer?: Completer | AsyncCompleter,
}

export async function read<T extends string | number = string> ({
default: def,
input = process.stdin,
output = process.stdout,
completer,
prompt = '',
silent,
timeout,
edit,
terminal,
replace,
}: Options<T>): Promise<T | string> {
if (
typeof def !== 'undefined' &&
typeof def !== 'string' &&
typeof def !== 'number'
) {
throw new Error('default value must be string or number')
}

let editDef = false
const defString = def?.toString()
prompt = prompt.trim() + ' '
terminal = !!(terminal || output.isTTY)

if (defString) {
if (silent) {
prompt += '(<default hidden>) '
// TODO: add tests for edit
/* c8 ignore start */
} else if (edit) {
editDef = true
/* c8 ignore stop */
} else {
prompt += '(' + defString + ') '
}
}

const m = new Mute({ replace, prompt })
m.pipe(output, { end: false })
output = m

return new Promise<string | T>((resolve, reject) => {
const rl = createInterface({ input, output, terminal, completer })
// TODO: add tests for timeout
/* c8 ignore start */
const timer = timeout && setTimeout(() => onError(new Error('timed out')), timeout)
/* c8 ignore stop */

m.unmute()
rl.setPrompt(prompt)
rl.prompt()

if (silent) {
m.mute()
// TODO: add tests for edit + default
/* c8 ignore start */
} else if (editDef && defString) {
const rlEdit = rl as typeof rl & {
line: string,
cursor: number,
_refreshLine: () => void,
}
rlEdit.line = defString
rlEdit.cursor = defString.length
rlEdit._refreshLine()
}
/* c8 ignore stop */

const done = () => {
rl.close()
clearTimeout(timer)
m.mute()
m.end()
}

// TODO: add tests for rejecting
/* c8 ignore start */
const onError = (er: Error) => {
done()
reject(er)
}
/* c8 ignore stop */

rl.on('error', onError)
rl.on('line', line => {
// TODO: add tests for silent
/* c8 ignore start */
if (silent && terminal) {
m.unmute()
}
/* c8 ignore stop */
done()
// TODO: add tests for default
/* c8 ignore start */
// truncate the \n at the end.
return resolve(line.replace(/\r?\n?$/, '') || defString || '')
/* c8 ignore stop */
})

// TODO: add tests for sigint
/* c8 ignore start */
rl.on('SIGINT', () => {
rl.close()
onError(new Error('canceled'))
})
/* c8 ignore stop */
})
}

0 comments on commit 18cb7bd

Please sign in to comment.