diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..ac6ca88
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,21 @@
+{
+ "overrides": [
+ {
+ "files": "*.ts",
+ "parser": "@typescript-eslint/parser",
+ "rules": {
+ "no-unused-vars": "off",
+ "no-useless-constructor": "off",
+ "@typescript-eslint/no-unused-vars": "error",
+ "@typescript-eslint/no-useless-constructor": "error"
+ }
+ }
+ ],
+ "parserOptions": {
+ "ecmaVersion": 2018,
+ "sourceType": "module"
+ },
+ "plugins": [
+ "@typescript-eslint/eslint-plugin"
+ ]
+}
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 9c14fc9..fba38a3 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -43,3 +43,27 @@ jobs:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
- run: npm test
- run: npm run coverage
+ esm:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - run: npm install
+ - run: npm run test:esm
+ deno:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 14
+ - run: npm install
+ - run: npm run compile
+ - uses: denolib/setup-deno@v2
+ with:
+ deno-version: v1.x
+ - run: |
+ deno --version
+ deno test test/deno/cliui-test.ts
diff --git a/.gitignore b/.gitignore
index eef5570..0028f33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,4 @@ node_modules
.nyc_output
package-lock.json
coverage
-
+build
diff --git a/.nycrc b/.nycrc
index 827f9a7..73cd631 100644
--- a/.nycrc
+++ b/.nycrc
@@ -7,7 +7,7 @@
"html",
"text"
],
- "lines": 98.0,
- "branches": "98",
- "statements": "98.0"
+ "lines": 99.0,
+ "branches": "95",
+ "statements": "99.0"
}
diff --git a/README.md b/README.md
index b9a847f..65b5672 100644
--- a/README.md
+++ b/README.md
@@ -10,13 +10,13 @@ easily create complex multi-column command-line-interfaces.
## Example
```js
-var ui = require('cliui')()
+const ui = require('cliui')()
ui.div('Usage: $0 [command] [options]')
ui.div({
text: 'Options:',
- padding: [2, 0, 2, 0]
+ padding: [2, 0, 1, 0]
})
ui.div(
@@ -40,6 +40,32 @@ ui.div(
console.log(ui.toString())
```
+## Deno/ESM Support
+
+As of `v7` `cliui` supports [Deno](https://github.com/denoland/deno) and
+[ESM](https://nodejs.org/api/esm.html#esm_ecmascript_modules):
+
+```typescript
+import cliui from "https://deno.land/x/cliui/deno.ts";
+
+const ui = cliui({})
+
+ui.div('Usage: $0 [command] [options]')
+
+ui.div({
+ text: 'Options:',
+ padding: [2, 0, 1, 0]
+})
+
+ui.div({
+ text: "-f, --file",
+ width: 20,
+ padding: [0, 4, 0, 4]
+})
+
+console.log(ui.toString())
+```
+
## Layout DSL
diff --git a/deno.ts b/deno.ts
new file mode 100644
index 0000000..8adbc03
--- /dev/null
+++ b/deno.ts
@@ -0,0 +1,13 @@
+// Bootstrap cliui with CommonJS dependencies:
+import { cliui, UIOptions, UI } from './build/lib/index.js'
+import { wrap, stripAnsi } from './build/lib/string-utils.js'
+
+export default function ui (opts: UIOptions): UI {
+ return cliui(opts, {
+ stringWidth: (str: string) => {
+ return [...str].length
+ },
+ stripAnsi,
+ wrap
+ })
+}
diff --git a/index.mjs b/index.mjs
new file mode 100644
index 0000000..bc7a022
--- /dev/null
+++ b/index.mjs
@@ -0,0 +1,13 @@
+// Bootstrap cliui with CommonJS dependencies:
+import { cliui } from './build/lib/index.js'
+import { wrap, stripAnsi } from './build/lib/string-utils.js'
+
+export default function ui (opts) {
+ return cliui(opts, {
+ stringWidth: (str) => {
+ return [...str].length
+ },
+ stripAnsi,
+ wrap
+ })
+}
diff --git a/lib/cjs.ts b/lib/cjs.ts
new file mode 100644
index 0000000..bda4241
--- /dev/null
+++ b/lib/cjs.ts
@@ -0,0 +1,12 @@
+// Bootstrap cliui with CommonJS dependencies:
+import { cliui, UIOptions } from './index.js'
+const stringWidth = require('string-width')
+const stripAnsi = require('strip-ansi')
+const wrap = require('wrap-ansi')
+export default function ui (opts: UIOptions) {
+ return cliui(opts, {
+ stringWidth,
+ stripAnsi,
+ wrap
+ })
+}
diff --git a/index.js b/lib/index.ts
similarity index 64%
rename from index.js
rename to lib/index.ts
index e917b00..db5052e 100644
--- a/index.js
+++ b/lib/index.ts
@@ -1,26 +1,57 @@
'use strict'
-const stringWidth = require('string-width')
-const stripAnsi = require('strip-ansi')
-const wrap = require('wrap-ansi')
-
const align = {
right: alignRight,
center: alignCenter
}
+
const top = 0
const right = 1
const bottom = 2
const left = 3
-class UI {
- constructor (opts) {
+export interface UIOptions {
+ width: number;
+ wrap: boolean;
+ rows?: string[];
+}
+
+interface Column {
+ text: string;
+ width?: number;
+ align?: 'right'|'left'|'center',
+ padding: number[],
+ border?: boolean;
+}
+
+interface ColumnArray extends Array {
+ span: boolean;
+}
+
+interface Line {
+ hidden?: boolean;
+ text: string;
+ span?: boolean;
+}
+
+interface Mixin {
+ stringWidth: Function;
+ stripAnsi: Function;
+ wrap: Function;
+}
+
+export class UI {
+ width: number;
+ wrap: boolean;
+ rows: ColumnArray[];
+
+ constructor (opts: UIOptions) {
this.width = opts.width
this.wrap = opts.wrap
this.rows = []
}
- span (...args) {
+ span (...args: ColumnArray) {
const cols = this.div(...args)
cols.span = true
}
@@ -29,33 +60,32 @@ class UI {
this.rows = []
}
- div (...args) {
+ div (...args: (Column|string)[]): ColumnArray {
if (args.length === 0) {
this.div('')
}
- if (this.wrap && this._shouldApplyLayoutDSL(...args)) {
- return this._applyLayoutDSL(args[0])
+ if (this.wrap && this.shouldApplyLayoutDSL(...args) && typeof args[0] === 'string') {
+ return this.applyLayoutDSL(args[0])
}
const cols = args.map(arg => {
if (typeof arg === 'string') {
- return this._colFromString(arg)
+ return this.colFromString(arg)
}
-
return arg
- })
+ }) as ColumnArray
this.rows.push(cols)
return cols
}
- _shouldApplyLayoutDSL (...args) {
+ private shouldApplyLayoutDSL (...args: (Column|string)[]): boolean {
return args.length === 1 && typeof args[0] === 'string' &&
/[\t\n]/.test(args[0])
}
- _applyLayoutDSL (str) {
+ private applyLayoutDSL (str: string): ColumnArray {
const rows = str.split('\n').map(row => row.split('\t'))
let leftColumnWidth = 0
@@ -64,10 +94,10 @@ class UI {
// don't allow the first column to take up more
// than 50% of the screen.
rows.forEach(columns => {
- if (columns.length > 1 && stringWidth(columns[0]) > leftColumnWidth) {
+ if (columns.length > 1 && mixin.stringWidth(columns[0]) > leftColumnWidth) {
leftColumnWidth = Math.min(
Math.floor(this.width * 0.5),
- stringWidth(columns[0])
+ mixin.stringWidth(columns[0])
)
}
})
@@ -79,30 +109,30 @@ class UI {
this.div(...columns.map((r, i) => {
return {
text: r.trim(),
- padding: this._measurePadding(r),
+ padding: this.measurePadding(r),
width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined
- }
+ } as Column
}))
})
return this.rows[this.rows.length - 1]
}
- _colFromString (text) {
+ private colFromString (text: string): Column {
return {
text,
- padding: this._measurePadding(text)
+ padding: this.measurePadding(text)
}
}
- _measurePadding (str) {
+ private measurePadding (str: string): number[] {
// measure padding without ansi escape codes
- const noAnsi = stripAnsi(str)
+ const noAnsi = mixin.stripAnsi(str)
return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length]
}
- toString () {
- const lines = []
+ toString (): string {
+ const lines: Line[] = []
this.rows.forEach(row => {
this.rowToString(row, lines)
@@ -116,24 +146,25 @@ class UI {
.join('\n')
}
- rowToString (row, lines) {
- this._rasterize(row).forEach((rrow, r) => {
+ rowToString (row: ColumnArray, lines: Line[]) {
+ this.rasterize(row).forEach((rrow, r) => {
let str = ''
- rrow.forEach((col, c) => {
+ rrow.forEach((col: string, c: number) => {
const { width } = row[c] // the width with padding.
- const wrapWidth = this._negatePadding(row[c]) // the width without padding.
+ const wrapWidth = this.negatePadding(row[c]) // the width without padding.
let ts = col // temporary string used during alignment/padding.
- if (wrapWidth > stringWidth(col)) {
- ts += ' '.repeat(wrapWidth - stringWidth(col))
+ if (wrapWidth > mixin.stringWidth(col)) {
+ ts += ' '.repeat(wrapWidth - mixin.stringWidth(col))
}
// align the string within its column.
if (row[c].align && row[c].align !== 'left' && this.wrap) {
- ts = align[row[c].align](ts, wrapWidth)
- if (stringWidth(ts) < wrapWidth) {
- ts += ' '.repeat(width - stringWidth(ts) - 1)
+ const fn = align[(row[c].align as 'right'|'center')]
+ ts = fn(ts, wrapWidth)
+ if (mixin.stringWidth(ts) < wrapWidth) {
+ ts += ' '.repeat((width || 0) - mixin.stringWidth(ts) - 1)
}
}
@@ -153,7 +184,7 @@ class UI {
// if prior row is span, try to render the
// current row on the prior line.
if (r === 0 && lines.length > 0) {
- str = this._renderInline(str, lines[lines.length - 1])
+ str = this.renderInline(str, lines[lines.length - 1])
}
})
@@ -169,10 +200,11 @@ class UI {
// if the full 'source' can render in
// the target line, do so.
- _renderInline (source, previousLine) {
- const leadingWhitespace = source.match(/^ */)[0].length
+ private renderInline (source: string, previousLine: Line) {
+ const match = source.match(/^ */)
+ const leadingWhitespace = match ? match[0].length : 0
const target = previousLine.text
- const targetTextWidth = stringWidth(target.trimRight())
+ const targetTextWidth = mixin.stringWidth(target.trimRight())
if (!previousLine.span) {
return source
@@ -194,9 +226,9 @@ class UI {
return target.trimRight() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft()
}
- _rasterize (row) {
- const rrows = []
- const widths = this._columnWidths(row)
+ private rasterize (row: ColumnArray) {
+ const rrows: string[][] = []
+ const widths = this.columnWidths(row)
let wrapped
// word wrap all columns, and create
@@ -205,14 +237,14 @@ class UI {
// leave room for left and right padding.
col.width = widths[c]
if (this.wrap) {
- wrapped = wrap(col.text, this._negatePadding(col), { hard: true }).split('\n')
+ wrapped = mixin.wrap(col.text, this.negatePadding(col), { hard: true }).split('\n')
} else {
wrapped = col.text.split('\n')
}
if (col.border) {
- wrapped.unshift('.' + '-'.repeat(this._negatePadding(col) + 2) + '.')
- wrapped.push("'" + '-'.repeat(this._negatePadding(col) + 2) + "'")
+ wrapped.unshift('.' + '-'.repeat(this.negatePadding(col) + 2) + '.')
+ wrapped.push("'" + '-'.repeat(this.negatePadding(col) + 2) + "'")
}
// add top and bottom padding.
@@ -221,7 +253,7 @@ class UI {
wrapped.push(...new Array(col.padding[bottom] || 0).fill(''))
}
- wrapped.forEach((str, r) => {
+ wrapped.forEach((str: string, r: number) => {
if (!rrows[r]) {
rrows.push([])
}
@@ -241,8 +273,8 @@ class UI {
return rrows
}
- _negatePadding (col) {
- let wrapWidth = col.width
+ private negatePadding (col: Column) {
+ let wrapWidth = col.width || 0
if (col.padding) {
wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0)
}
@@ -254,10 +286,10 @@ class UI {
return wrapWidth
}
- _columnWidths (row) {
+ private columnWidths (row: ColumnArray) {
if (!this.wrap) {
return row.map(col => {
- return col.width || stringWidth(col.text)
+ return col.width || mixin.stringWidth(col.text)
})
}
@@ -288,7 +320,7 @@ class UI {
}
}
-function addBorder (col, ts, style) {
+function addBorder (col: Column, ts: string, style: string) {
if (col.border) {
if (/[.']-+[.']/.test(ts)) {
return ''
@@ -306,7 +338,7 @@ function addBorder (col, ts, style) {
// calculates the minimum width of
// a column, based on padding preferences.
-function _minWidth (col) {
+function _minWidth (col: Column) {
const padding = col.padding || []
const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0)
if (col.border) {
@@ -316,16 +348,17 @@ function _minWidth (col) {
return minWidth
}
-function getWindowWidth () {
+function getWindowWidth (): number {
/* istanbul ignore next: depends on terminal */
if (typeof process === 'object' && process.stdout && process.stdout.columns) {
return process.stdout.columns
}
+ return 80
}
-function alignRight (str, width) {
+function alignRight (str: string, width: number): string {
str = str.trim()
- const strWidth = stringWidth(str)
+ const strWidth = mixin.stringWidth(str)
if (strWidth < width) {
return ' '.repeat(width - strWidth) + str
@@ -334,9 +367,9 @@ function alignRight (str, width) {
return str
}
-function alignCenter (str, width) {
+function alignCenter (str: string, width: number): string {
str = str.trim()
- const strWidth = stringWidth(str)
+ const strWidth = mixin.stringWidth(str)
/* istanbul ignore next */
if (strWidth >= width) {
@@ -346,9 +379,11 @@ function alignCenter (str, width) {
return ' '.repeat((width - strWidth) >> 1) + str
}
-module.exports = function (opts = {}) {
+let mixin: Mixin
+export function cliui (opts: Partial = {}, _mixin: Mixin) {
+ mixin = _mixin
return new UI({
- width: opts.width || getWindowWidth() || /* istanbul ignore next */ 80,
+ width: opts.width || getWindowWidth(),
wrap: opts.wrap !== false
})
}
diff --git a/lib/string-utils.ts b/lib/string-utils.ts
new file mode 100644
index 0000000..23d78fd
--- /dev/null
+++ b/lib/string-utils.ts
@@ -0,0 +1,30 @@
+// Minimal replacement for ansi string helpers "wrap-ansi" and "strip-ansi".
+// to facilitate ESM and Deno modules.
+// TODO: look at porting https://www.npmjs.com/package/wrap-ansi to ESM.
+
+// The npm application
+// Copyright (c) npm, Inc. and Contributors
+// Licensed on the terms of The Artistic License 2.0
+// See: https://github.com/npm/cli/blob/4c65cd952bc8627811735bea76b9b110cc4fc80e/lib/utils/ansi-trim.js
+const ansi = new RegExp('\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|' +
+'\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)', 'g')
+
+export function stripAnsi (str: string) {
+ return str.replace(ansi, '')
+}
+
+export function wrap (str: string, width: number) {
+ const [start, end] = str.match(ansi) || ['', '']
+ str = stripAnsi(str)
+ let wrapped = ''
+ for (let i = 0; i < str.length; i++) {
+ if (i !== 0 && (i % width) === 0) {
+ wrapped += '\n'
+ }
+ wrapped += str.charAt(i)
+ }
+ if (start && end) {
+ wrapped = `${start}${wrapped}${end}`
+ }
+ return wrapped
+}
diff --git a/package.json b/package.json
index 90bfd23..1f4bab7 100644
--- a/package.json
+++ b/package.json
@@ -3,11 +3,26 @@
"version": "6.0.0",
"description": "easily create complex multi-column command-line-interfaces",
"main": "index.js",
+ "exports": {
+ "import": "./index.mjs",
+ "require": "./build/index.cjs"
+ },
+ "type": "module",
+ "module": "./index.mjs",
"scripts": {
- "fix": "standard --fix",
- "test": "c8 mocha",
- "posttest": "standard",
- "coverage": "c8 report --check-coverage"
+ "check": "standardx '**/*.ts' && standardx '**/*.js' && standardx '**/*.cjs'",
+ "fix": "standardx --fix '**/*.ts' && standardx --fix '**/*.js' && standardx --fix '**/*.cjs'",
+ "pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs",
+ "test": "c8 mocha ./test/*.cjs",
+ "test:esm": "c8 mocha ./test/esm/cliui-test.mjs",
+ "postest": "check",
+ "posttest": "npm run check",
+ "coverage": "c8 report --check-coverage",
+ "precompile": "rimraf build",
+ "compile": "tsc",
+ "postcompile": "npm run build:cjs",
+ "build:cjs": "rollup -c",
+ "prepare": "npm run compile"
},
"repository": {
"type": "git",
@@ -38,14 +53,27 @@
"wrap-ansi": "^7.0.0"
},
"devDependencies": {
+ "@typescript-eslint/eslint-plugin": "^3.8.0",
+ "@typescript-eslint/parser": "^3.8.0",
+ "@wessberg/rollup-plugin-ts": "^1.3.2",
"c8": "^7.3.0",
"chai": "^4.2.0",
"chalk": "^4.1.0",
+ "cross-env": "^7.0.2",
+ "eslint": "^7.6.0",
+ "eslint-plugin-import": "^2.22.0",
+ "eslint-plugin-node": "^11.1.0",
+ "gts": "^2.0.2",
"mocha": "^8.1.1",
- "standard": "^14.3.4"
+ "rimraf": "^3.0.2",
+ "rollup": "^2.23.1",
+ "standardx": "^5.0.0",
+ "typescript": "^3.9.7"
},
"files": [
- "index.js"
+ "build",
+ "index.mjs",
+ "!*.d.ts"
],
"engine": {
"node": ">=10"
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..ec8a2ac
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,17 @@
+import ts from '@wessberg/rollup-plugin-ts'
+
+const output = {
+ format: 'cjs',
+ file: './build/index.cjs',
+ exports: 'default'
+}
+
+if (process.env.NODE_ENV === 'test') output.sourcemap = true
+
+export default {
+ input: './lib/cjs.ts',
+ output,
+ plugins: [
+ ts({ /* options */ })
+ ]
+}
diff --git a/test/cliui.js b/test/cliui.cjs
similarity index 97%
rename from test/cliui.js
rename to test/cliui.cjs
index e9752a8..6dd86e7 100644
--- a/test/cliui.js
+++ b/test/cliui.cjs
@@ -8,7 +8,7 @@ require('chai').should()
process.env.FORCE_COLOR = 1
const chalk = require('chalk')
-const cliui = require('../')
+const cliui = require('../build/index.cjs')
const stripAnsi = require('strip-ansi')
describe('cliui', () => {
@@ -82,7 +82,7 @@ describe('cliui', () => {
const ui = cliui({
width: 40
})
- const widths = ui._columnWidths([{}, {}, {}])
+ const widths = ui.columnWidths([{}, {}, {}])
widths[0].should.equal(13)
widths[1].should.equal(13)
@@ -93,7 +93,7 @@ describe('cliui', () => {
const ui = cliui({
width: 40
})
- const widths = ui._columnWidths([{ width: 20 }, {}, {}])
+ const widths = ui.columnWidths([{ width: 20 }, {}, {}])
widths[0].should.equal(20)
widths[1].should.equal(10)
@@ -104,7 +104,7 @@ describe('cliui', () => {
const ui = cliui({
width: 40
})
- const widths = ui._columnWidths([{}, { width: 10 }, {}])
+ const widths = ui.columnWidths([{}, { width: 10 }, {}])
widths[0].should.equal(15)
widths[1].should.equal(10)
@@ -115,7 +115,7 @@ describe('cliui', () => {
const ui = cliui({
width: 40
})
- const widths = ui._columnWidths([{ width: 20 }, { width: 12 }, {}])
+ const widths = ui.columnWidths([{ width: 20 }, { width: 12 }, {}])
widths[0].should.equal(20)
widths[1].should.equal(12)
@@ -126,7 +126,7 @@ describe('cliui', () => {
const ui = cliui({
width: 40
})
- const widths = ui._columnWidths([{ width: 30 }, { width: 30 }, { padding: [0, 2, 0, 1] }])
+ const widths = ui.columnWidths([{ width: 30 }, { width: 30 }, { padding: [0, 2, 0, 1] }])
widths[0].should.equal(30)
widths[1].should.equal(30)
diff --git a/test/deno/cliui-test.ts b/test/deno/cliui-test.ts
new file mode 100644
index 0000000..ce24068
--- /dev/null
+++ b/test/deno/cliui-test.ts
@@ -0,0 +1,51 @@
+/* global Deno */
+
+import {
+ assert,
+ assertEquals
+} from 'https://deno.land/std/testing/asserts.ts'
+import cliui from '../../deno.ts'
+
+Deno.test("wraps text at 'width' if a single column is given", () => {
+ const ui = cliui({
+ width: 10
+ })
+
+ ui.div('i am a string that should be wrapped')
+
+ ui.toString().split('\n').forEach((row: string) => {
+ assert(row.length <= 10)
+ })
+})
+
+Deno.test('evenly divides text across columns if multiple columns are given', () => {
+ const ui = cliui({
+ width: 40
+ })
+
+ ui.div(
+ { text: 'i am a string that should be wrapped', width: 15 },
+ 'i am a second string that should be wrapped',
+ 'i am a third string that should be wrapped'
+ )
+
+ // total width of all columns is <=
+ // the width cliui is initialized with.
+ ui.toString().split('\n').forEach((row: string) => {
+ assert(row.length <= 40)
+ })
+
+ // it should wrap each column appropriately.
+ // TODO: we should flesh out the Deno and ESM implementation
+ // such that it spreads words out over multiple columns appropriately:
+ const expected = [
+ 'i am a string ti am a seconi am a third',
+ 'hat should be wd string tha string that',
+ 'rapped t should be should be w',
+ ' wrapped rapped'
+ ]
+
+ ui.toString().split('\n').forEach((line: string, i: number) => {
+ assertEquals(line, expected[i])
+ })
+})
diff --git a/test/esm/cliui-test.mjs b/test/esm/cliui-test.mjs
new file mode 100644
index 0000000..f57d77d
--- /dev/null
+++ b/test/esm/cliui-test.mjs
@@ -0,0 +1,47 @@
+import {ok as assert, strictEqual} from 'assert'
+import cliui from '../../index.mjs'
+
+describe('ESM', () => {
+ it("wraps text at 'width' if a single column is given", () => {
+ const ui = cliui({
+ width: 10
+ })
+
+ ui.div('i am a string that should be wrapped')
+
+ ui.toString().split('\n').forEach((row) => {
+ assert(row.length <= 10)
+ })
+ })
+
+ it('evenly divides text across columns if multiple columns are given', () => {
+ const ui = cliui({
+ width: 40
+ })
+
+ ui.div(
+ { text: 'i am a string that should be wrapped', width: 15 },
+ 'i am a second string that should be wrapped',
+ 'i am a third string that should be wrapped'
+ )
+
+ // total width of all columns is <=
+ // the width cliui is initialized with.
+ ui.toString().split('\n').forEach((row) => {
+ assert(row.length <= 40)
+ })
+
+ // it should wrap each column appropriately.
+ // TODO: we should flesh out the Deno and ESM implementation
+ // such that it spreads words out over multiple columns appropriately:
+ const expected = [
+ 'i am a string ti am a seconi am a third',
+ 'hat should be wd string tha string that',
+ 'rapped t should be should be w',
+ ' wrapped rapped'
+ ]
+ ui.toString().split('\n').forEach((line, i) => {
+ strictEqual(line, expected[i])
+ })
+ })
+})
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..d5d8c21
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "./node_modules/gts/tsconfig-google.json",
+ "compilerOptions": {
+ "outDir": "build",
+ "rootDir": ".",
+ "sourceMap": false,
+ "target": "es2017",
+ "moduleResolution": "node",
+ "module": "es2015"
+ },
+ "include": [
+ "lib/**/*.ts"
+ ],
+ "exclude": [
+ "lib/cjs.ts"
+ ]
+}
\ No newline at end of file
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
index 0000000..bc078f4
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,6 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "sourceMap": true
+ }
+}
\ No newline at end of file