Skip to content

Commit

Permalink
Merge pull request #50 from zambezi/interactive-exec
Browse files Browse the repository at this point in the history
Execute arbitrary command on successful interactive build
  • Loading branch information
mstade authored Mar 1, 2018
2 parents 4e72767 + e97439d commit d7f110b
Show file tree
Hide file tree
Showing 15 changed files with 156 additions and 33 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ $ ez-build --help
--no-copy disable copying of non-code files to lib
--no-debug disable source map generation
--log <normal|json> log output format [normal]
--interactive watch for and recompile on changes (implies -O 0)
--interactive [cmd] watch for and recompile on changes, optionally executing cmd on successful builds (implies -O 0)
--production enable production options (implies -O 1)
--flags <flags> toggle build flags
@<path> read options from the file at <path> (relative to cwd)
Expand Down Expand Up @@ -126,10 +126,12 @@ By default ez-build will generate source maps and other debugging information fo

Determines the output format of ez-build's log. This is generally not used, but setting it to `json` provides additional detail and can sometimes help in debugging issues.

### `--interactive`
### `--interactive [cmd]`

Runs ez-build in interactive mode, meaning it will run continuously and watch for changes to input files and directories. This is very useful for rapid development, since it's much faster to rebuild only what changed than the entire project. Setting this flag implies `-O 0` which disables all optimizations.

Optionally, this mode can execute a command on successful builds. For example, this might be used to run a test suite whenever a build passes. Commands that include spaces *must* be properly quoted with double quotes.

This flag is ignored entirely if combined with `--production`.

If `NODE_ENV=production` is set when invoking ez-build with `--interactive`, it will still enter interactive mode, but will *not* change the value of `NODE_ENV`.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"bin": {
"ez-build": "bin/ez-build.js"
},
"version": "0.7.4",
"version": "0.8.0",
"description": "The Zambezi build process",
"devDependencies": {
"babel-cli": "6.26.0",
Expand Down
2 changes: 1 addition & 1 deletion src/cli/opts.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default async function parse(pkg, argv) {
.option('--no-copy', `disable copying of non-code files to ${defaults.lib}`, Boolean, !defaults.copy)
.option('--no-debug', 'disable source map generation', Boolean, !defaults.debug)
.option('--log <normal|json>', `log output format [${defaults.log}]`, /^(json|normal)$/i, defaults.log)
.option('--interactive', `watch for and recompile on changes (implies -O 0)`)
.option('--interactive [cmd]', `watch for and recompile on changes (implies -O 0)`)
.option('--production', `enable production options (implies -O 1)`)
.option('--target-browsers <spec|false>', `define target browser environments: [${defaults.targetBrowsers}]`, concatFlags, [])
.option('--target-node <current|number|false>', `define target node environment: [${defaults.targetNode}]`, defaults.targetNode)
Expand Down
26 changes: 23 additions & 3 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { watch } from 'chokidar'
import { timed } from './util/performance'
import './util/cli'
import { default as parseOpts } from './cli/opts'
import { execSync } from 'child_process'

const keys = Object.keys
const all = Promise.all.bind(Promise)
Expand Down Expand Up @@ -76,9 +77,28 @@ async function main() {
.on('add', build)
.on('change', build)

let cmd = typeof opts.interactive === 'string'? opts.interactive : false

async function build(file) {
let result = await execute(type, pipeline[type], file)
await status(type, ...result)
let results = await execute(type, pipeline[type], file)
await status(type, ...results)

if (cmd) {
let success = true

for (let result of results) {
let { input, messages, files, error } = await result

if (error) {
success = false
break
}
}

if (success) {
execSync(cmd, { stdio: 'inherit' })
}
}
}
})
console.info('Watching source files for changes...')
Expand Down Expand Up @@ -235,4 +255,4 @@ Comments on the detected babel configuration:
}

return !!babelrc
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Watching source files for changes...
js – src/a.js -> lib/a.js,lib/a.js.map
js – src/added.js -> lib/added.js,lib/added.js.map
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Watching source files for changes...
js – src/a.js -> lib/a.js,lib/a.js.map
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Watching source files for changes...
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Watching source files for changes...
js – src/a.js -> lib/a.js,lib/a.js.map
success!
js – src/added.js -> lib/added.js,lib/added.js.map
success!
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Watching source files for changes...
js – src/a.js -> lib/a.js,lib/a.js.map
success!
js – src/added.js -> lib/added.js,lib/added.js.map
success!
js – src/added.js -> lib/added.js,lib/added.js.map
success!
js – src/b.js: Unexpected token, expected ; (8:18)
6 | }
7 |
> 8 | export default 'b'export all from foo
| ^
9 | 
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Watching source files for changes...
js – src/a.js -> lib/a.js,lib/a.js.map
success!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Watching source files for changes...
51 changes: 51 additions & 0 deletions test/cli/interactive-exec.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bats
load test-util

setup() {
load_fixture typical-project

if [[ "${BATS_TEST_NUMBER}" -eq 1 ]]; then
{
kill $(cat ez-build.pid) && wait $(cat ez-build.pid)
} 2>/dev/null

${EZ_BUILD_BIN} --interactive "echo success!" > build.log 2>&1 &
EZPID=$!
disown ${EZPID}
echo ${EZPID} > ez-build.pid
fi
}

teardown() {
if [[ "$BATS_TEST_NUMBER" -eq "${#BATS_TEST_NAMES[@]}" ]]; then
{
kill $(cat ez-build.pid) && wait $(cat ez-build.pid)
} 2>/dev/null
rm *.{pid,log}
fi

git checkout .
unload_fixture typical-project
}

@test "'ez-build --interactive \"echo success!\"' should wait for changes" {
eventually 'assert_expected "$(cat build.log)"'
}

@test "'ez-build --interactive \"echo success!\"' should rebuild files when they are modified" {
touch src/a.js
eventually 'assert_expected "$(cat build.log)"'
}

@test "'ez-build --interactive \"echo success!\"' should build files when they are added" {
refute_exists src/added.js
touch src/added.js
eventually 'assert_exists lib/added.js'
eventually 'assert_exists lib/added.js.map'
eventually 'assert_expected "$(cat build.log)"'
}

@test "'ez-build --interactive \"echo success!\"' should only execute command on successful builds" {
echo "export all from foo" >> src/b.js
eventually 'assert_expected "$(cat build.log)"'
}
26 changes: 5 additions & 21 deletions test/cli/interactive.bats
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
#!/usr/bin/env bats
load test-util

eventually() {
for try in $(seq 1 30); do
set +e
output="$(eval ${@})"
status=$?
set -e

if [[ ${status} == 0 ]]; then
return 0
fi
sleep 1
done

echo "${output}"
return ${status}
}

setup() {
load_fixture typical-project

Expand All @@ -26,7 +9,7 @@ setup() {
kill $(cat ez-build.pid) && wait $(cat ez-build.pid)
} 2>/dev/null

${EZ_BUILD_BIN} --interactive 2>&1 > build.log &
${EZ_BUILD_BIN} --interactive > build.log 2>&1 &
EZPID=$!
disown ${EZPID}
echo ${EZPID} > ez-build.pid
Expand All @@ -45,17 +28,18 @@ teardown() {
}

@test "'ez-build --interactive' should wait for changes" {
eventually 'assert_equal "Watching source files for changes..." "$(tail -1 build.log)"'
eventually 'assert_expected "$(cat build.log)"'
}

@test "'ez-build --interactive' should rebuild files when they are modified" {
touch src/a.js
eventually 'assert_equal "js – src/a.js -> lib/a.js,lib/a.js.map" "$(tail -1 build.log)"'
eventually 'assert_expected "$(cat build.log)"'
}

@test "'ez-build --interactive' should build files when they are added" {
refute_exists src/added.js
touch src/added.js
eventually 'assert_exists lib/added.js'
eventually 'assert_exists lib/added.js.map'
}
eventually 'assert_expected "$(cat build.log)"'
}
21 changes: 19 additions & 2 deletions test/cli/test-util.bash
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ assert_equal() {
expected="${1}"
actual="${2}"
fi

diff=$(echo ${expected[@]} ${actual[@]} | tr ' ' '\n' | sort | uniq -u)

if [[ -z "${diff}" ]]; then
Expand Down Expand Up @@ -188,4 +188,21 @@ unload_fixture() {
echo "unknown fixture: ${fixture}"
return 1
fi
}
}

eventually() {
for try in $(seq 1 30); do
set +e
output="$(eval ${@})"
status=$?
set -e

if [[ ${status} == 0 ]]; then
return 0
fi
sleep 1
done

echo "${output}"
return ${status}
}
27 changes: 24 additions & 3 deletions test/unit/cli/opts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import
} from 'js-combinatorics'

test('Options', async t => {
t.plan(57)
t.plan(69)

const barePkg = await readFixture('bare-project')
, typicalPkg = await readFixture('typical-project')
Expand Down Expand Up @@ -72,7 +72,7 @@ test('Options', async t => {
}, {})
).map(async ({ input, expected }) => {
let actual = await parseOpts(barePkg, argv('--flags', `${input}`))

if (!deepEqual(actual.flags, defaultFlags)) {
t.deepEqual(actual.flags, defaultFlags, `--flags ${input}`)
}
Expand Down Expand Up @@ -149,6 +149,27 @@ test('Options', async t => {
t.ok(opts.interactive, 'enables interactive mode')
t.notOk(opts.production, 'leaves production mode disabled')

t.comment('Options > --interactive foo')
opts = await parseOpts(barePkg, argv('--interactive', 'foo'))
t.equal(opts.optimize, 0, 'implies -O 0')
t.ok(opts.interactive, 'enables interactive mode')
t.equal(opts.interactive, 'foo', 'Command equals `foo`')
t.notOk(opts.production, 'leaves production mode disabled')

t.comment('Options > --interactive foo bar')
opts = await parseOpts(barePkg, argv('--interactive', 'foo', 'bar'))
t.equal(opts.optimize, 0, 'implies -O 0')
t.ok(opts.interactive, 'enables interactive mode')
t.equal(opts.interactive, 'foo', 'Command equals `foo`')
t.notOk(opts.production, 'leaves production mode disabled')

t.comment('Options > --interactive "foo bar"')
opts = await parseOpts(barePkg, argv('--interactive', 'foo bar'))
t.equal(opts.optimize, 0, 'implies -O 0')
t.ok(opts.interactive, 'enables interactive mode')
t.equal(opts.interactive, 'foo bar', 'Command equals `foo bar`')
t.notOk(opts.production, 'leaves production mode disabled')

t.comment('Options > --src and --lib directories')
opts = await parseOpts(typicalPkg, argv())
t.equal(opts.src, typicalPkg.relative(typicalPkg.directories.src), 'should pick up src path from package.directories.src if --src is not specified')
Expand Down Expand Up @@ -226,4 +247,4 @@ function generateCombinations(flags) {
}, { input: [], expected: {} })
))
}, [])
}
}

0 comments on commit d7f110b

Please sign in to comment.