Is there a way to respond to bash prompting in a workflow?

I’ve created a CLI tool using oclif and I have a PR gate GitHub Actions workflow to validate the functionality of my CLI. FWIW this question is applicable to a non-CLI codebase that has a workflow that actions a CLI tool with prompts.

The CLI is called like this: npx @my-company/my-cli-tool my-command. When this is run, I’ve programmed the CLI to elicit user input to make a selection from a list of checkboxes before proceeding. When executing “manually” in a terminal, clicking the ENTER key when presented with the prompt will allow the rest of my command to execute (by submitting all the options that are selected by default). When I run this command in a workflow step, it of course, just dies because no user input is given (Error: Process completed with exit code 130.).

Is there a way to define a workflow step to effectively respond to CLI prompts? I was thinking something like this might work:

      - run: |
          npx @my-company/my-cli-tool my-command
          sleep 10s
          echo '\n'

Worst case: I can update my CLI command to bypass the prompt if a certain flag is passed.

Under Linux, this can be as easy as piping the output of a command to another:

printf '\n' | npx @my-company/my-cli-tool my-command

I tested this with a very basic readline script:

const readline = require("readline")

function getInput(req) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    })
    return new Promise(resolve => rl.question(req, res => {
        rl.close()
        resolve(res)
    }))
}

(async () => {
    console.log("Sleeping...")
    await new Promise(resolve => {
        setTimeout(resolve, 5000)
    })
    console.log(`Got input: ${ await getInput("Ready? ") }`)
})()
> printf 'Go!\n' | node testscript.js
Sleeping...
Ready? Go!
Got input: Go!

It stops working if I sleep and ask for input twice, however. The second prompt doesn’t receive anything and the script appears to exit early. So I tried something else: There is a tool called expect that is made specifically for this purpose. The modified Node.js script:

const readline = require("readline")

function getInput(req) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    })
    return new Promise(resolve => rl.question(req, res => {
        rl.close()
        resolve(res)
    }))
}

async function sleep(time) {
    return new Promise(resolve => {
        console.log(`Sleeping for ${time} ms...`)
        setTimeout(resolve, time)
    })
}

(async () => {
    await sleep(3000)
    console.log(`Got input: ${ await getInput("Continue? ") }`)
    await sleep(2000)
    console.log(`Got input: ${ await getInput("Are you sure? ") }`)
})()

The expect script:

spawn node .
expect "Continue?"
send "Yes.\n"
expect "Are you sure?"
send "Absolutely!\n"
expect EOF

And finally, running expect:

> expect expect_script
spawn node .
Sleeping for 3000 ms...
Continue? Yes.
Got input: Yes.
Sleeping for 2000 ms...
Are you sure? Absolutely!
Got input: Absolutely!
1 Like