Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/cli-kit/src/public/node/base-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {inTemporaryDirectory, mkdir, writeFile} from './fs.js'
import {joinPath, resolvePath, cwd} from './path.js'
import {mockAndCaptureOutput} from './testing/output.js'
import {unstyled} from './output.js'
import {_resetTerminalSupportsPromptingCache} from './system.js'
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest'
import {Flags} from '@oclif/core'

Expand All @@ -20,6 +21,7 @@ beforeEach(() => {
Object.defineProperty(process.stdin, 'isTTY', {value: true, configurable: true, writable: true})
Object.defineProperty(process.stdout, 'isTTY', {value: true, configurable: true, writable: true})
vi.stubEnv('CI', '')
_resetTerminalSupportsPromptingCache()
})

afterEach(() => {
Expand Down Expand Up @@ -441,6 +443,7 @@ describe('applying environments', async () => {
runTestInTmpDir('throws in non-TTY mode when a non-TTY required argument is missing', async (tmpDir: string) => {
// Given — simulate non-interactive (CI) environment
vi.stubEnv('CI', 'true')
_resetTerminalSupportsPromptingCache()

// When
await MockCommandWithRequiredFlagInNonTTY.run(['--path', tmpDir])
Expand Down
77 changes: 76 additions & 1 deletion packages/cli-kit/src/public/node/system.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as system from './system.js'
import {execa} from 'execa'
import {describe, expect, test, vi} from 'vitest'
import {describe, expect, test, vi, beforeEach} from 'vitest'
import which from 'which'
import {Readable} from 'stream'

Expand Down Expand Up @@ -353,6 +353,81 @@ describe('isStdinPiped', () => {
})
})

describe('terminalSupportsPrompting', () => {
beforeEach(() => {
system._resetTerminalSupportsPromptingCache()
})

test('returns true when not in CI and both stdin/stdout are TTY', () => {
// Given
vi.stubEnv('CI', '')
Object.defineProperty(process.stdin, 'isTTY', {value: true, configurable: true})
Object.defineProperty(process.stdout, 'isTTY', {value: true, configurable: true})

// When
const got = system.terminalSupportsPrompting()

// Then
expect(got).toBe(true)
})

test('returns false when in CI', () => {
// Given
vi.stubEnv('CI', 'true')
Object.defineProperty(process.stdin, 'isTTY', {value: true, configurable: true})
Object.defineProperty(process.stdout, 'isTTY', {value: true, configurable: true})

// When
const got = system.terminalSupportsPrompting()

// Then
expect(got).toBe(false)
})

test('memoizes the result', () => {
// Given
vi.stubEnv('CI', '')
let calls = 0
Object.defineProperty(process.stdin, 'isTTY', {
get: () => {
calls++
return true
},
configurable: true,
})
Object.defineProperty(process.stdout, 'isTTY', {value: true, configurable: true})

// When
system.terminalSupportsPrompting()
system.terminalSupportsPrompting()

// Then
expect(calls).toBe(1)
})

test('reset function clears the cache', () => {
// Given
vi.stubEnv('CI', '')
let calls = 0
Object.defineProperty(process.stdin, 'isTTY', {
get: () => {
calls++
return true
},
configurable: true,
})
Object.defineProperty(process.stdout, 'isTTY', {value: true, configurable: true})

// When
system.terminalSupportsPrompting()
system._resetTerminalSupportsPromptingCache()
system.terminalSupportsPrompting()

// Then
expect(calls).toBe(2)
})
})

describe('readStdinString', () => {
test('returns undefined when stdin is not piped', async () => {
// Given
Expand Down
22 changes: 19 additions & 3 deletions packages/cli-kit/src/public/node/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,16 +333,32 @@ export function terminalSupportsHyperlinks(): boolean {
return supportsHyperlinks.stdout
}

/**
* Memoized value for the terminal prompting support check.
*/
let memoizedTerminalSupportsPrompting: boolean | undefined

/**
* Check if the standard input and output streams support prompting.
*
* @returns True if the standard input and output streams support prompting.
*/
export function terminalSupportsPrompting(): boolean {
if (isTruthy(process.env.CI)) {
return false
if (memoizedTerminalSupportsPrompting === undefined) {
const isCi = isTruthy(process.env.CI)
const isStdinTTY = Boolean(process.stdin.isTTY)
const isStdoutTTY = Boolean(process.stdout.isTTY)
memoizedTerminalSupportsPrompting = !isCi && isStdinTTY && isStdoutTTY
}
return Boolean(process.stdin.isTTY && process.stdout.isTTY)
return memoizedTerminalSupportsPrompting
}

/**
* Resets the memoized value for the terminal prompting support check.
* This is used for testing purposes.
*/
export function _resetTerminalSupportsPromptingCache(): void {
memoizedTerminalSupportsPrompting = undefined
}

/**
Expand Down
Loading