diff --git a/.eslintrc b/.eslintrc index ac6ca880..d74b2e83 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,6 +11,9 @@ } } ], + "globals": { + "Deno": true + }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" diff --git a/browser.mjs b/browser.mjs new file mode 100644 index 00000000..5b950f8c --- /dev/null +++ b/browser.mjs @@ -0,0 +1,13 @@ +// Bootstrap cliui with esm dependencies: +import { cliui } from './build/lib/index.js' +import stringWidth from 'https://esm.sh/string-width@^6' +import stripAnsi from 'https://esm.sh/strip-ansi@^7' +import wrap from 'https://esm.sh/wrap-ansi@^8' + +export default function ui (opts) { + return cliui(opts, { + stringWidth, + stripAnsi, + wrap + }) +} diff --git a/deno.ts b/deno.ts index a94e49f3..fa9d4630 100644 --- a/deno.ts +++ b/deno.ts @@ -1,13 +1,22 @@ -// Bootstrap cliui with CommonJS dependencies: +// Bootstrap cliui with esm dependencies: import { cliui, UI } from './build/lib/index.js' import type { UIOptions } from './build/lib/index.d.ts' -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 - }, +import stringWidth from 'https://esm.sh/string-width@6' +import stripAnsi from 'https://esm.sh/strip-ansi@7' +import wrap from 'https://esm.sh/wrap-ansi@8' + +export default function ui (opts?: UIOptions): UI { + let optsWithWidth = opts ?? {} + if (!optsWithWidth.width) { + const { columns } = Deno.consoleSize() + if (columns) { + optsWithWidth = Object.assign(optsWithWidth, { width: columns }) + } + } + + return cliui(optsWithWidth, { + stringWidth, stripAnsi, wrap }) diff --git a/index.mjs b/index.mjs index bc7a022b..c1e7aba0 100644 --- a/index.mjs +++ b/index.mjs @@ -1,12 +1,12 @@ // Bootstrap cliui with CommonJS dependencies: import { cliui } from './build/lib/index.js' -import { wrap, stripAnsi } from './build/lib/string-utils.js' +import stringWidth from 'string-width' +import stripAnsi from 'strip-ansi' +import wrap from 'wrap-ansi' export default function ui (opts) { return cliui(opts, { - stringWidth: (str) => { - return [...str].length - }, + stringWidth, stripAnsi, wrap }) diff --git a/lib/cjs.ts b/lib/cjs.ts index bda4241a..dbe2ef39 100644 --- a/lib/cjs.ts +++ b/lib/cjs.ts @@ -3,7 +3,7 @@ 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) { +export default function ui (opts?: UIOptions) { return cliui(opts, { stringWidth, stripAnsi, diff --git a/lib/index.ts b/lib/index.ts index 0673c3a5..0b1fc774 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -11,10 +11,11 @@ const bottom = 2 const left = 3 export interface UIOptions { - width: number; + width?: number; wrap?: boolean; rows?: string[]; } +type UIConstructorOptions = UIOptions & {width:number}; interface Column { text: string; @@ -45,7 +46,7 @@ export class UI { wrap: boolean; rows: ColumnArray[]; - constructor (opts: UIOptions) { + constructor (opts: UIConstructorOptions) { this.width = opts.width this.wrap = opts.wrap ?? true this.rows = [] @@ -380,7 +381,7 @@ function alignCenter (str: string, width: number): string { } let mixin: Mixin -export function cliui (opts: Partial, _mixin: Mixin) { +export function cliui (opts: UIOptions | undefined, _mixin: Mixin) { mixin = _mixin return new UI({ width: opts?.width || getWindowWidth(), diff --git a/lib/string-utils.ts b/lib/string-utils.ts index 23d78fdb..6bbba71c 100644 --- a/lib/string-utils.ts +++ b/lib/string-utils.ts @@ -2,6 +2,10 @@ // to facilitate ESM and Deno modules. // TODO: look at porting https://www.npmjs.com/package/wrap-ansi to ESM. +// The current approach is to use the cjs version of the modules for node, and the esm version +// for contexts which can import from URLs. This file could be deleted if/once we are happy with +// the new approach. + // The npm application // Copyright (c) npm, Inc. and Contributors // Licensed on the terms of The Artistic License 2.0 diff --git a/package.json b/package.json index eab6bf47..194ced2f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "require": "./build/index.cjs" }, "./build/index.cjs" - ] + ], + "./browser.mjs": { + "import": "./browser.mjs" + } }, "type": "module", "module": "./index.mjs", @@ -20,6 +23,7 @@ "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", + "test:deno": "deno test ./test/deno/cliui-test.ts", "postest": "check", "coverage": "c8 report --check-coverage", "precompile": "rimraf build", @@ -75,6 +79,7 @@ "files": [ "build", "index.mjs", + "browser.mjs", "!*.d.ts" ], "engines": { diff --git a/test/deno/cliui-test.ts b/test/deno/cliui-test.ts index ce24068d..de7b6c45 100644 --- a/test/deno/cliui-test.ts +++ b/test/deno/cliui-test.ts @@ -39,10 +39,11 @@ Deno.test('evenly divides text across columns if multiple columns are given', () // 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' + 'i am a string i am a i am a third', + 'that should be second string that', + 'wrapped string that should be', + ' should be wrapped', + ' wrapped' ] ui.toString().split('\n').forEach((line: string, i: number) => { diff --git a/test/esm/cliui-test.mjs b/test/esm/cliui-test.mjs index f57d77d1..1e33e23e 100644 --- a/test/esm/cliui-test.mjs +++ b/test/esm/cliui-test.mjs @@ -35,11 +35,12 @@ describe('ESM', () => { // 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' - ] + 'i am a string i am a i am a third', + 'that should be second string that', + 'wrapped string that should be', + ' should be wrapped', + ' wrapped' + ] ui.toString().split('\n').forEach((line, i) => { strictEqual(line, expected[i]) })