Skip to content
Merged
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
5 changes: 3 additions & 2 deletions lib/GitBranch.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const ShellCommand = require ('./ShellCommand')
const ShellCommandLogged = require ('./ShellCommandLogged')

module.exports = class GitBranch {

Expand All @@ -23,7 +24,7 @@ module.exports = class GitBranch {

async push () {
for (let i of await this.pushTodo ()) {
await ShellCommand.withText(i).run ()
await ShellCommandLogged.withText(i).run ()
}
}

Expand Down Expand Up @@ -109,6 +110,6 @@ module.exports = class GitBranch {
}

async run (cmd) {
return ShellCommand.withText(cmd).runSilent ()
return ShellCommand.withText(cmd).run ()
}
}
2 changes: 1 addition & 1 deletion lib/GitOld.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = class OldGit {

async translate (o) {
if (!o.version) {
let v = await ShellCommand.withText (`git --version`).runSilent ()
let v = await ShellCommand.withText (`git --version`).run ()
o.version = v.split ('git version')[1]
}

Expand Down
2 changes: 1 addition & 1 deletion lib/GitRepo.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ module.exports = class {
}

async run (cmd) {
return ShellCommand.withText (cmd).runSilent ()
return ShellCommand.withText (cmd).run ()
}
}
9 changes: 4 additions & 5 deletions lib/RunCommand.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

const ShellCommand = require ('./ShellCommand')
const ShellCommandLogged = require ('./ShellCommandLogged')
const UserInput = require ('./UserInput')

module.exports = class {
Expand Down Expand Up @@ -32,7 +32,7 @@ module.exports = class {
for (let idx = 0; idx < this.todo.length; idx++) {
let i = this.todo [idx]

let cmd = ShellCommand.withText (i)
let cmd = ShellCommandLogged.withText (i)

try {
await cmd.run ()
Expand All @@ -44,11 +44,10 @@ module.exports = class {

async fallback (idx, x) {
let tail = await this.print (idx)
let xx = x.message
if (tail) {
xx = xx + `\nFix above and then run rest of the plan:\n${tail}\n`
x.message = x.message + `\nFix above and then run rest of the plan:\n${tail}\n`
}
throw new Error (xx)
throw x
}

async print (idx) {
Expand Down
41 changes: 7 additions & 34 deletions lib/ShellCommand.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,39 @@
const { spawn } = require('child_process')

// @todo #0:30m decompose
// runSilent go to ShellCommand
// run go to ShellCommandLogged
module.exports = class ShellCommand {

static withText (cmd) {
return new ShellCommand ({ cmd, spawn })
}

constructor(o) {
constructor (o) {
this.cwd = o.cwd
this.cmd = o.cmd
this.spawn = o.spawn
}

async run () {
let cwdLabel = this.cwd ? ` (${this.cwd})` : ''
this.log(`${cwdLabel} > ${await this.print ()}`)
return this.runSilent({log_flow: true})
}

async runSilent (options = {}) {
const {log_flow} = options
if (global.FUZZ) {
// @todo #120:1h move to injected ShellCommandFuzz
return global.FUZZ_SHELL_REPLY
}

return new Promise((ok, fail) => {
const cmd = 'sh'
const cmd = 'sh'
const args = ['-c', this.cmd]

const env = { ...process.env,
TERM: 'xterm-256color'
}
const o = {env}
const subprocess = this.spawn(cmd, args, o)
const env = { ...process.env, TERM: 'xterm-256color' }
const subprocess = this.spawn(cmd, args, { env })

let stdout = '', stderr = ''

subprocess.stdout.on('data', (data) => {
if (log_flow) process.stdout.write(data)
stdout = stdout + data
})

subprocess.stderr.on('data', (data) => {
if (log_flow) process.stdout.write(data)
stderr = stderr + data
})

subprocess.on('exit', (code) => {
subprocess.on('close', (code) => {
if (code === 0) {
ok(stdout.toString().trim())
} else {
Expand All @@ -62,16 +47,4 @@ module.exports = class ShellCommand {
})
}


async print () {
return this.cmd
}

log (label) {
if (global.FUZZ) {
return ''
}
console.log (label)
}

}
68 changes: 68 additions & 0 deletions lib/ShellCommandLogged.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const { spawn } = require('child_process')

module.exports = class ShellCommandLogged {

static withText (cmd) {
return new ShellCommandLogged ({ cmd, spawn })
}

constructor (o) {
this.cwd = o.cwd
this.cmd = o.cmd
this.spawn = o.spawn
}

async run () {
let cwdLabel = this.cwd ? ` (${this.cwd})` : ''
this.log(`${cwdLabel} > ${await this.print()}`)

if (global.FUZZ) {
return global.FUZZ_SHELL_REPLY
}

return new Promise((ok, fail) => {
const cmd = 'sh'
const args = ['-c', this.cmd]
const env = { ...process.env, TERM: 'xterm-256color' }
const subprocess = this.spawn(cmd, args, { env })

let stdout = '', stderr = ''

subprocess.stdout.on('data', (data) => {
process.stdout.write(data)
stdout = stdout + data
})

subprocess.stderr.on('data', (data) => {
process.stdout.write(data)
stderr = stderr + data
})

subprocess.on('close', (code) => {
if (code === 0) {
ok(stdout.toString().trim())
} else {
const err = new Error(stderr.toString().trim())
err.toString = function () { return '' }
fail(err)
}
})

subprocess.on('error', (error) => {
fail(new Error(error.toString().trim()))
})
})
}

async print () {
return this.cmd
}

log (label) {
if (global.FUZZ) {
return ''
}
console.log(label)
}

}
4 changes: 4 additions & 0 deletions mr.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ if (!nodeVersion.isValid()) {
try {
await main (process.argv.slice (2))
} catch (x) {
const msg = x.toString().trim()
if (msg) {
console.error(msg)
}
process.exit(1)
}

Expand Down
2 changes: 1 addition & 1 deletion tests/GitBranch.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('git branch equals', () => {
})


mock.method(ShellCommand.prototype, 'runSilent', function () {
mock.method(ShellCommand.prototype, 'run', function () {
switch (this.cmd) {
case 'git log --reverse --pretty=format:%s origin/master..TASK-42':
return 'TASK-42 commit msg'
Expand Down
2 changes: 1 addition & 1 deletion tests/GitRepo.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const GitRepo = require ('../lib/GitRepo')


describe('random input', () => {
mock.method(ShellCommand.prototype, 'runSilent', function () {
mock.method(ShellCommand.prototype, 'run', function () {
switch (this.cmd) {
case 'git log --oneline origin/main..origin/main':
return ''
Expand Down
3 changes: 2 additions & 1 deletion tests/MergeCommand.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const {describe, it, mock} = require ('node:test')
const assert = require ('assert')
const ShellCommand = require ('../lib/ShellCommand')
const ShellCommandLogged = require ('../lib/ShellCommandLogged')
const GitRepo = require ('../lib/GitRepo')
const GitBranch = require ('../lib/GitBranch')
const MergeCommand = require ('../lib/MergeCommand')
Expand All @@ -26,7 +27,7 @@ describe('MergeCommand', async () => {

}
mock.method(ShellCommand.prototype, 'run', f)
mock.method(ShellCommand.prototype, 'runSilent', f)
mock.method(ShellCommandLogged.prototype, 'run', f)

mock.method(GitBranch.prototype, 'isOriginGitlab', function () {
return false
Expand Down
3 changes: 2 additions & 1 deletion tests/MrCommand.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const {describe, it, mock} = require ('node:test')
const assert = require ('assert')
const ShellCommand = require ('../lib/ShellCommand')
const ShellCommandLogged = require ('../lib/ShellCommandLogged')
const GitRepo = require ('../lib/GitRepo')
const GitBranch = require ('../lib/GitBranch')
const GitOld = require ('../lib/GitOld')
Expand Down Expand Up @@ -28,7 +29,7 @@ describe('random input', () => {

}
mock.method(ShellCommand.prototype, 'run', f)
mock.method(ShellCommand.prototype, 'runSilent', f)
mock.method(ShellCommandLogged.prototype, 'run', f)

mock.method(GitBranch.prototype, 'isOriginGitlab', function () {
return false
Expand Down
23 changes: 8 additions & 15 deletions tests/ShellCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const assert = require ('assert')
const ShellCommand = require ('../lib/ShellCommand')


describe('random input', () => {
describe('ShellCommand', () => {

const makeSpawnMock = (o = {}) => {

Expand All @@ -25,8 +25,8 @@ describe('random input', () => {
},
},
on: (event, callback) => {
if (event === 'exit') {
return callback(o.exit)
if (event === 'close') {
return callback(o.close)
}
},
})
Expand All @@ -43,26 +43,19 @@ describe('random input', () => {
})

it ('exec ok', async (t) => {
const spawn = makeSpawnMock ({stdout: '', stderr: '', exit: 0})
assert.strictEqual(await (new ShellCommand ({cmd: 'git fetch', spawn}).runSilent ()), '')
const spawn = makeSpawnMock ({stdout: '', stderr: '', close: 0})
assert.strictEqual(await (new ShellCommand ({cmd: 'git fetch', spawn}).run ()), '')
})

it ('exec fail', async (t) => {
const spawn = makeSpawnMock ({stdout: '', stderr: 'not a repo', exit: -127})
assert.rejects(new ShellCommand ({cmd: 'git fetch', spawn}).runSilent (), {message: 'not a repo'})
const spawn = makeSpawnMock ({stdout: '', stderr: 'not a repo', close: -127})
assert.rejects(new ShellCommand ({cmd: 'git fetch', spawn}).run (), {message: 'not a repo'})
})

it ('git status fuzz', async (t) => {
it ('fuzz', async (t) => {
global.FUZZ = 1
global.FUZZ_SHELL_REPLY = 'fake ok'
assert.strictEqual(await (new ShellCommand ({cmd: 'git status'}).run ()), 'fake ok')
global.FUZZ = 0
})

it ('git status silent fuzz', async (t) => {
global.FUZZ = 1
global.FUZZ_SHELL_REPLY = 'fake ok'
assert.strictEqual(await (new ShellCommand ({cmd: 'git status'}).runSilent ()), 'fake ok')
global.FUZZ = 0
})
})
50 changes: 50 additions & 0 deletions tests/ShellCommandLogged.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const {describe, it} = require ('node:test')
const assert = require ('assert')
const ShellCommandLogged = require ('../lib/ShellCommandLogged')

describe('ShellCommandLogged', () => {

const makeSpawnMock = (o = {}) => {
const {stdout, stderr, close} = o
return (command, args, options) => ({
stdout: {
on: (event, callback) => {
if (event === 'data') {
return callback(`${stdout}\n`)
}
},
},
stderr: {
on: (event, callback) => {
if (event === 'data') {
return callback(`${stderr}\n`)
}
},
},
on: (event, callback) => {
if (event === 'close') {
return callback(o.close)
}
},
})
}

it ('fuzz', async (t) => {
global.FUZZ = 1
global.FUZZ_SHELL_REPLY = 'fake ok'
assert.strictEqual(await (new ShellCommandLogged ({cmd: 'git status'}).run ()), 'fake ok')
global.FUZZ = 0
})

it ('#113 streamed jest output not duplicated on failure', async (t) => {
const longOutput = Array(10).fill('PASS tests/GitBranch.js').join('\n') + '\nTests: 10 passed, 10 total'
const spawn = makeSpawnMock ({stdout: '', stderr: longOutput, close: 1})
try {
await new ShellCommandLogged ({cmd: 'npm test', spawn}).run ()
assert.fail('should throw')
} catch (err) {
assert.ok(err.message.includes('PASS'))
assert.strictEqual(err.toString(), '')
}
})
})