Skip to content

Commit

Permalink
Merge branch 'main' into sm/perf-gains
Browse files Browse the repository at this point in the history
  • Loading branch information
WillieRuemmele committed Jul 11, 2023
2 parents 2778406 + 544f115 commit 489aac9
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 13 deletions.
29 changes: 28 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
exclude:
- os: windows-latest
node_version: lts/*
- os: windows-latest
- os: windows-latest
node_version: lts/-1
fail-fast: false
runs-on: ${{ matrix.os }}
Expand All @@ -35,3 +35,30 @@ jobs:
- run: yarn install --network-timeout 600000
- run: yarn build
- run: yarn test:e2e
nuts:
needs: linux-unit-tests
uses: salesforcecli/github-workflows/.github/workflows/externalNut.yml@main
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-latest"]
externalProjectGitUrl:
- https://github.com/salesforcecli/plugin-auth
- https://github.com/salesforcecli/plugin-data
- https://github.com/salesforcecli/plugin-org
- https://github.com/salesforcecli/plugin-schema
- https://github.com/salesforcecli/plugin-user
with:
packageName: "@oclif/core"
externalProjectGitUrl: ${{ matrix.externalProjectGitUrl }}
command: "yarn test:nuts"
os: ${{ matrix.os }}
useCache: false
preSwapCommands: "npx yarn-deduplicate; yarn install"
preExternalBuildCommands: "shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@oclif/core"
secrets:
TESTKIT_AUTH_URL: ${{ secrets.TESTKIT_AUTH_URL }}
TESTKIT_HUB_USERNAME: ${{ secrets.TESTKIT_HUB_USERNAME }}
TESTKIT_JWT_CLIENT_ID: ${{ secrets.TESTKIT_JWT_CLIENT_ID }}
TESTKIT_JWT_KEY: ${{ secrets.TESTKIT_JWT_KEY }}
TESTKIT_HUB_INSTANCE: ${{ secrets.TESTKIT_HUB_INSTANCE }}
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## [2.8.12](https://github.com/oclif/core/compare/2.8.11...2.8.12) (2023-07-10)


### Bug Fixes

* properly truncate table cells that contain fullwidth characters or ANSI escape sequences ([db51bf2](https://github.com/oclif/core/commit/db51bf216b4b37d6c6e1d054e64b38dff0856d6d))



## [2.8.11](https://github.com/oclif/core/compare/2.8.10...2.8.11) (2023-07-01)


Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@oclif/core",
"description": "base library for oclif CLIs",
"version": "2.8.11",
"version": "2.8.12",
"author": "Salesforce",
"bugs": "https://github.com/oclif/core/issues",
"dependencies": {
Expand All @@ -25,6 +25,7 @@
"object-treeify": "^1.1.33",
"password-prompt": "^1.1.2",
"semver": "^7.5.3",
"slice-ansi": "^4.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"supports-color": "^8.1.1",
Expand Down Expand Up @@ -56,6 +57,7 @@
"@types/proxyquire": "^1.3.28",
"@types/semver": "^7.5.0",
"@types/shelljs": "^0.8.11",
"@types/slice-ansi": "^4.0.0",
"@types/strip-ansi": "^5.2.1",
"@types/supports-color": "^8.1.1",
"@types/wordwrap": "^1.0.1",
Expand Down Expand Up @@ -110,6 +112,7 @@
"commitlint": "commitlint",
"lint": "eslint . --ext .ts --config .eslintrc",
"posttest": "yarn lint",
"compile": "tsc",
"prepack": "yarn run build",
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
"test:e2e": "mocha --forbid-only \"test/**/*.e2e.ts\" --timeout 1200000",
Expand Down
8 changes: 7 additions & 1 deletion src/cli-ux/styled/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {stdout} from '../stream'

const sw = require('string-width')
const {orderBy} = require('natural-orderby')
const sliceAnsi = require('slice-ansi')

class Table<T extends Record<string, unknown>> {
options: table.Options & { printLine(s: any): any }
Expand Down Expand Up @@ -284,7 +285,12 @@ class Table<T extends Record<string, unknown>> {
const colorWidth = (d.length - visualWidth)
let cell = d.padEnd(width + colorWidth)
if ((cell.length - colorWidth) > width || visualWidth === width) {
cell = cell.slice(0, width - 2) + '… '
// truncate the cell, preserving ANSI escape sequences, and keeping
// into account the width of fullwidth unicode characters
cell = sliceAnsi(cell, 0, width - 2) + '… '
// pad with spaces; this is necessary in case the original string
// contained fullwidth characters which cannot be split
cell += ' '.repeat(width - sw(cell))
}

l += cell
Expand Down
25 changes: 16 additions & 9 deletions src/parser/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
public async parse(): Promise<ParserOutput<TFlags, BFlags, TArgs>> {
this._debugInput()

const findLongFlag = (arg: string) => {
const findLongFlag = (arg: string):string | undefined => {
const name = arg.slice(2)
if (this.input.flags[name]) {
return name
Expand All @@ -98,17 +98,23 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
}
}

const findShortFlag = ([_, char]: string) => {
const findShortFlag = ([_, char]: string):string | undefined => {
if (this.flagAliases[char]) {
return this.flagAliases[char].name
}

return Object.keys(this.input.flags).find(k => this.input.flags[k].char === char)
return Object.keys(this.input.flags).find(k => (this.input.flags[k].char === char && char !== undefined && this.input.flags[k].char !== undefined))
}

const findFlag = (arg: string): { name?: string, isLong: boolean } => {
const isLong = arg.startsWith('--')
const short = isLong ? false : arg.startsWith('-')
const name = isLong ? findLongFlag(arg) : (short ? findShortFlag(arg) : undefined)
return {name, isLong}
}

const parseFlag = (arg: string): boolean => {
const long = arg.startsWith('--')
const name = long ? findLongFlag(arg) : findShortFlag(arg)
const {name, isLong} = findFlag(arg)
if (!name) {
const i = arg.indexOf('=')
if (i !== -1) {
Expand All @@ -129,16 +135,17 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
const flag = this.input.flags[name]
if (flag.type === 'option') {
this.currentFlag = flag
const input = long || arg.length < 3 ? this.argv.shift() : arg.slice(arg[2] === '=' ? 3 : 2)
if (typeof input !== 'string') {
const input = isLong || arg.length < 3 ? this.argv.shift() : arg.slice(arg[2] === '=' ? 3 : 2)
// if the value ends up being one of the command's flags, the user didn't provide an input
if ((typeof input !== 'string') || findFlag(input).name) {
throw new CLIError(`Flag --${name} expects a value`)
}

this.raw.push({type: 'flag', flag: flag.name, input})
this.raw.push({type: 'flag', flag: flag.name, input: input})
} else {
this.raw.push({type: 'flag', flag: flag.name, input: arg})
// push the rest of the short characters back on the stack
if (!long && arg.length > 2) {
if (!isLong && arg.length > 2) {
this.argv.unshift(`-${arg.slice(2)}`)
}
}
Expand Down
35 changes: 34 additions & 1 deletion test/cli-ux/styled/table.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {expect, fancy} from 'fancy-test'

import {ux} from '../../../src'
import * as screen from '../../../src/screen'

/* eslint-disable camelcase */
const apps = [
Expand Down Expand Up @@ -317,6 +318,38 @@ describe('styled/table', () => {
321 supertable-test-2${ws}
123 supertable-test-1${ws}\n`)
})

const orig = {
stdtermwidth: screen.stdtermwidth,
CLI_UX_SKIP_TTY_CHECK: process.env.CLI_UX_SKIP_TTY_CHECK,
}

fancy
.do(() => {
Object.assign(screen, {stdtermwidth: 9})
process.env.CLI_UX_SKIP_TTY_CHECK = 'true'
})
.finally(() => {
Object.assign(screen, {stdtermwidth: orig.stdtermwidth})
process.env.CLI_UX_SKIP_TTY_CHECK = orig.CLI_UX_SKIP_TTY_CHECK
})
.stdout({stripColor: false})
.end('correctly truncates columns with fullwidth characters or ansi escape sequences', output => {
/* eslint-disable camelcase */
const app4 = {
build_stack: {
name: 'heroku-16',
},
id: '456',
name: '\u001B[31m超级表格—测试\u001B[0m',
web_url: 'https://supertable-test-1.herokuapp.com/',
}
/* eslint-enable camelcase */

ux.table([...apps, app4 as any], {name: {}}, {'no-header': true})
expect(output.stdout).to.equal(` super…${ws}
super…${ws}
\u001B[31m超级\u001B[39m…${ws}${ws}\n`)
})
})
})

70 changes: 70 additions & 0 deletions test/parser/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,76 @@ describe('parse', () => {
expect(Boolean(out.flags.myflag2)).to.equal(true)
})

it('doesn\'t throw when 2nd char in value matches a flag char', async () => {
const out = await parse(['--myflag', 'Ishikawa', '-s', 'value'], {
flags: {myflag: Flags.string(), second: Flags.string({char: 's'})},
})
expect(out.flags.myflag).to.equal('Ishikawa')
expect(out.flags.second).to.equal('value')
})

it('doesn\'t throw when an unprefixed flag value contains a flag name', async () => {
const out = await parse(['--myflag', 'a-second-place-finish', '-s', 'value'], {
flags: {myflag: Flags.string(), second: Flags.string({char: 's'})},
})
expect(out.flags.myflag).to.equal('a-second-place-finish')
expect(out.flags.second).to.equal('value')
})

it('throws error when no value provided to required flag', async () => {
try {
await parse(['--myflag', '--second', 'value'], {
flags: {myflag: Flags.string({required: true}), second: Flags.string()},
})
assert.fail('should have thrown')
} catch (error) {
expect((error as CLIError).message).to.include('Flag --myflag expects a value')
}
})

it('throws error when no value provided to required flag before a short char flag', async () => {
try {
await parse(['--myflag', '-s', 'value'], {
flags: {myflag: Flags.string({required: true}), second: Flags.string({char: 's'})},
})
assert.fail('should have thrown')
} catch (error) {
expect((error as CLIError).message).to.include('Flag --myflag expects a value')
}
})

it('doesn\'t throw when boolean flag passed', async () => {
const out = await parse(['--myflag', '--second', 'value'], {
flags: {myflag: Flags.boolean(), second: Flags.string()},
})
expect(out.flags.myflag).to.be.true
expect(out.flags.second).to.equal('value')
})

it('doesn\'t throw when negative number passed', async () => {
const out = await parse(['--myflag', '-s', '-9'], {
flags: {myflag: Flags.boolean(), second: Flags.integer({char: 's'})},
})
expect(out.flags.myflag).to.be.true
expect(out.flags.second).to.equal(-9)
})

it('doesn\'t throw when boolean short char is passed', async () => {
const out = await parse(['--myflag', '-s', 'value'], {
flags: {myflag: Flags.boolean(), second: Flags.string({char: 's'})},
})
expect(out.flags.myflag).to.be.true
expect(out.flags.second).to.equal('value')
})

it('doesn\'t throw when short char is passed as a string value', async () => {
const out = await parse(['--myflag', '\'-s\'', '-s', 'value'], {
flags: {myflag: Flags.string(), second: Flags.string({char: 's'})},
})
expect(out.flags.myflag).to.equal('\'-s\'')
expect(out.flags.second).to.equal('value')
})

it('parses short flags', async () => {
const out = await parse(['-mf'], {
flags: {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,11 @@
dependencies:
"@sinonjs/fake-timers" "^7.1.0"

"@types/slice-ansi@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/slice-ansi/-/slice-ansi-4.0.0.tgz#eb40dfbe3ac5c1de61f6bcb9ed471f54baa989d6"
integrity sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==

"@types/strip-ansi@^5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-5.2.1.tgz#acd97f1f091e332bb7ce697c4609eb2370fa2a92"
Expand Down

0 comments on commit 489aac9

Please sign in to comment.