diff --git a/readme.md b/readme.md index 6df952d..97047a5 100644 --- a/readme.md +++ b/readme.md @@ -17,14 +17,14 @@ Install it globally for quick access Optionally install it locally, to pin down versions if required. - $ npm i --save @crazyfactory/docker-project-cli + $ npm i --save @crazyfactory/docker-project-cli ## Configuration - + Configuration of DOPR can be done either via `package.json` under the `dopr` key or with a provided file defaulting to `docker-project.json`. -Default configuration +**Default configuration:** ```json { "file": ["./docker/docker-compose.yml"], @@ -39,6 +39,10 @@ Default configuration "command": "%action% %args%", "exec": false }, + "pull": { + "command": "%action% %args%", + "exec": false + }, "start": { "command": "%action% %args%", "exec": false @@ -53,20 +57,50 @@ Default configuration "command": "%action% %args%", "user": "node" }, - "npm": "%action% %args%", + "npm": { + "command": "%action% %args%", + "user": "node" + }, "git": "%action% %args%", "yarn": "%action% %args%" } } ``` -Notes: +*Notes:* - This will relay `up`, `down`, `start` and `stop` to `docker-compose -f $params$` - This will add custom commands like `dopr bash ...`, `dopr composer ...` and `dopr optimize` - The `node` will be launched with the user `node` by default. - This will use a different config if NODE_ENV is set to *production* or if dopr is with `--env production`. - The `"file"` value can be array or string. +**Sample configuration with all usecases:** + +```json +{ + "actions": { + "multiple-cmd": { + "command": ["echo multiple command as array", "@nested-cmd arg1 arg2"] + }, + "nested-cmd": { + "command": ["echo nested command %args%", "@deepnested-cmd --opt1 val1 --opt2 val2"] + }, + "deepnested-cmd": { + "command": ["echo deep nested command %args%"] + }, + "host-cmd": { + "service": "@host", + "command": "docker-compose version" + } + } +} +``` + +*Notes:* +- The `"actions".[$key]."command"` can be either array or string. +- The command can be reused or recalled by prefixing it with `@` (see sample above). +- The command that should run in host context will need `"service"` value of `"@host"` (see sample above). + ## Usage ### docker-compose shortcuts @@ -76,8 +110,8 @@ To start you project in deamon mode run $ dopr up -d -You can similarly use `down` and `stop`, just like you would with `docker-compose` directly. - +You can similarly use `down` and `stop`, just like you would with `docker-compose` directly. + ### custom commands You can add simple custom commands, but we add some by default. They are passed through to the service specified in your dopr configuration. @@ -86,7 +120,7 @@ For instance to open a bash session just run $ dopr bash -You can similarly access `node`, `npm`, `git` and `composer` like so +You can similarly access `node`, `npm`, `git` and `composer` like so $ dopr npm run my-script diff --git a/src/cli.js b/src/cli.js index 7104933..39a13fe 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const path = require('path'); -const {spawn} = require('child_process'); +const {execSync, spawnSync} = require('child_process'); const program = require('commander'); const chalk = require('chalk'); const deepAssign = require('deep-assign'); @@ -68,7 +68,7 @@ if (!packageConfig && !doprConfig) { if (typeof packageConfig.file === 'string') { packageConfig.file = [packageConfig.file]; } -if (typeof doprConfig.file === 'string') { +if (doprConfig && typeof doprConfig.file === 'string') { doprConfig.file = [doprConfig.file]; } @@ -114,6 +114,7 @@ if (cliAction.exec && !cliAction.service) { const dockerComposeFiles = []; +// Validate/sanitize all docker-compose files! cliAction.file.forEach((file, pos) => { file = path.resolve(file); @@ -130,41 +131,72 @@ cliAction.file.forEach((file, pos) => { dockerComposeFiles.push('--file', file); }); -// Parse command -const cliCommand = cliAction.command - .replace('%action%', action || '') - .replace('%args%', args.join(' ')) - .split(' '); - -// Args -const user = cliAction.user ? ['--user', cliAction.user] : []; - -const cliArgs = dockerComposeFiles - .concat(cliAction.exec ? ['exec', ...user, cliAction.service] : []) - .concat(cliAction.detached ? ['-d'] : []) - .concat(cliAction.privileged ? ['--privileged'] : []) - .concat(cliAction.index ? ['--index', cliAction.index] : []) - .concat(cliCommand); - -if (program.verbose) { - console.log(chalk.gray('ENV: ' + env)); - console.log(chalk.gray('CWD: ' + basePath)); - console.log(chalk.gray('CMD: docker-compose ' + cliArgs.join(' '))); +// Supporting array command so treat anything else as array as well. +if (typeof cliAction.command === 'string') { + cliAction.command = [cliAction.command]; } -// Fire! -const childProcess = spawn('docker-compose', cliArgs, { +const cliOptions = { cwd: basePath, - env, stdio: 'inherit', shell: true -}); +}; -childProcess.on('close', code => { +const exitHandler = code => { if (program.verbose) { console.log((code > 0 ? chalk.red : chalk.gray)(`command exited with code ${code}`)); } // Pass through exit code - process.exit(code); + if (code !== 0) { + process.exit(code); + } +}; + +// Run commands synchronously one after another! +cliAction.command.forEach(command => { + if (program.verbose) { + console.log(chalk.gray('ENV: ' + env)); + console.log(chalk.gray('CWD: ' + basePath)); + } + + // Is it a reference to another action? + if (command[0] === '@') { + const refArgs = dockerProjectArgs.slice(2).concat(command.substr(1)); + + if (program.verbose) { + console.log(chalk.gray('CMD: dopr ' + refArgs.join(' '))); + } + + // Fire! + return exitHandler(spawnSync('dopr ', refArgs, cliOptions).status); + } + + // Command is expected to run in host context! + if (cliAction.service === '@host') { + return execSync(command, cliOptions); + } + + // Parse command + const cliCommand = command + .replace('%action%', action || '') + .replace('%args%', args.join(' ')) + .split(' '); + + // Args + const user = cliAction.user ? ['--user', cliAction.user] : []; + + const cliArgs = dockerComposeFiles + .concat(cliAction.exec ? ['exec', ...user, cliAction.service] : []) + .concat(cliAction.detached ? ['-d'] : []) + .concat(cliAction.privileged ? ['--privileged'] : []) + .concat(cliAction.index ? ['--index', cliAction.index] : []) + .concat(cliCommand); + + if (program.verbose) { + console.log(chalk.gray('CMD: docker-compose ' + cliArgs.join(' '))); + } + + // Fire! + exitHandler(spawnSync('docker-compose', cliArgs, cliOptions).status); }); diff --git a/src/internals.spec.js b/src/internals.spec.js index 66513d6..7daa797 100644 --- a/src/internals.spec.js +++ b/src/internals.spec.js @@ -1,6 +1,6 @@ import test from 'ava'; -import {preprocessArgs, parseConfigActions, parseConfig} from './internals'; +import {preprocessArgs, parseConfigActions, parseConfig, collect} from './internals'; test('preprocessArgs() with empty array', t => { t.deepEqual(preprocessArgs(['node.exe', 'cli.js']), { @@ -145,3 +145,10 @@ test('parseConfig()', t => { t.deepEqual(parseConfig(config), expected); }); + +test('collect()', t => { + const memo = []; + + t.deepEqual(collect(1, memo), [1]); + t.deepEqual(collect('a', memo), [1, 'a']); +});