Skip to content
Open
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
14 changes: 11 additions & 3 deletions terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class Interact {
output: opts.masked ? new Writable({ write: mask }) : stdio.out
})

this._rl.input?.setMode(tty.constants.MODE_RAW)
if ('setMode' in this._rl.input) this._rl.input.setMode(tty.constants.MODE_RAW)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted this optional chaining to an explicit check for setMode() because the intent seems to set the mode if the stdin is a TTY. With it possibly being a Pipe instead, the optional chaining doesn't protect against calling setMode.

Copy link

Copilot AI Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Guarding with in will throw if this._rl.input is null or undefined. Consider checking existence first, e.g. if (this._rl.input?.setMode) or if (this._rl.input && typeof this._rl.input.setMode === 'function') to avoid possible runtime errors.

Suggested change
if ('setMode' in this._rl.input) this._rl.input.setMode(tty.constants.MODE_RAW)
if (this._rl.input?.setMode) this._rl.input.setMode(tty.constants.MODE_RAW)

Copilot uses AI. Check for mistakes.
this._rl.on('close', () => {
console.log() // new line
Bare.exit()
Expand Down Expand Up @@ -147,12 +147,20 @@ class Interact {
return fields
}

#autosubmit () {
async #autosubmit () {
const fields = {}
const defaults = this._defaults
while (this._params.length) {
const param = this._params.shift()
fields[param.name] = defaults[param.name] ?? param.default
let answer = defaults[param.name] ?? param.default
const valid = !param.validation || await param.validation(answer)
if (valid) {
if (typeof answer === 'string') answer = answer.replace(this.constructor.rx, '')
fields[param.name] = answer
} else if (param.validation) {
stdio.err.write(`Validating '${param.name}' parameter default failed: ${param.msg}\n`)
throw Error(`Validating '${param.name}' parameter default failed: ${param.msg}\n`)
}
}
return fields
}
Expand Down
72 changes: 72 additions & 0 deletions test/terminal.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,75 @@ test('permit function with encrypted key', async function (t) {
const exitedRes = await exited
t.is(exitedRes, true, 'Pear.exit ok')
})

test('interact - run - autosubmit validation fails', async function (t) {
t.plan(1)

const { teardown } = rig()
t.teardown(teardown)

const { Interact, stdio } = require('../terminal')

let output = ''
const originalWrite = stdio.err.write
stdio.err.write = (str) => { output += str }
t.teardown(() => { stdio.err.write = originalWrite })

const prompt = new Interact('', [{
name: 'foo',
validation: () => false, // always fail
default: 'biz',
msg: 'must be a square circle'
}])

try {
await prompt.run({ autosubmit: true })
} catch (e) {
t.is(output, "Validating 'foo' parameter default failed: must be a square circle\n", 'got error for param')
}

t.end()
})

test('interact - run - autosubmit validation passes', async function (t) {
t.plan(2)

const { teardown } = rig()
t.teardown(teardown)

const { Interact, stdio } = require('../terminal')

let output = ''
const originalWrite = stdio.err.write
stdio.err.write = (str) => { output += str }
t.teardown(() => { stdio.err.write = originalWrite })

const prompt = new Interact('', [
{
name: 'foo',
validation: () => true, // always passes
default: 'bar',
msg: 'anything or nothing is good'
},
{
name: 'biz',
default: 'baz',
msg: 'vibing'
}
])

let locals
try {
locals = await prompt.run({ autosubmit: true })
} catch (e) {
t.fail('Shouldnt fail validation')
}

t.is(output, '', 'output stdio is silent')
t.alike(locals, {
foo: 'bar',
biz: 'baz'
}, 'has defaults as output')

t.end()
})