diff --git a/README.md b/README.md index fa84fe0..10e7edf 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ - [Asynchronous task](#asynchronous-task) - [py/python](#pypython) - [Use a custom maidfile](#use-a-custom-maidfile) + - [Synchronizing tasks with package.json scripts](#synchronizing-tasks-with-packagejson-scripts) - [Development](#development) - [lint](#lint) - [test](#test) @@ -266,6 +267,33 @@ Unlike a `maidfile.md` which uses all `h2` headers as tasks, in `README.md` only Alternatively, if you're not using `maidfile.md`, you can also use `--section h2_header` and `--path foo.md` flags to customize it. +### Synchronizing tasks with package.json scripts + +You're team may currently use `yarn` or `npm run` to execute tasks. In order to reduce workflow churn maid provides a way of automatically updating the scripts section of your package.json with the relavent maid tasks. + +```bash +maid update-scripts +``` + +It's recommended to use this technique along side [lint-staged](https://github.com/okonet/lint-staged) and [husky](https://github.com/typicode/husky) to automatically update your package.json in a precommit hook. + +```json +{ + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "README.md": ["maid update-scripts --git-add", "git add"] + } +} +``` + +**Note:** The `--git-add` flag will automatically stage the `package.json`. If you have other changes to the package.json when that happens you could end up commiting those changes without intending to. + +`--no-write` allows you to test the command without actually updating the package.json. Useful for both debugging and checking to ensure there are no command conflicts + ## Development @@ -286,6 +314,8 @@ If you want to automatically fix lint errors, try adding `--fix` plugin to the c Use [AVA](https://github.com/avajs/ava) to run unit tests. +Run task `lint` before this. + ```bash yarn ava "${@:1}" ``` diff --git a/bin/cli.js b/bin/cli.js index dac8aa1..ceee1b6 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -11,6 +11,27 @@ cli.command('*', 'Run a task in current working directory', (input, flags) => { return runner.runFile(taskName) }) +cli + .command( + 'update-scripts', + 'Write maid tasks to package.json scripts', + (input, flags) => { + const runner = require('..')(flags) + const updateScripts = require('../lib/updateScripts') + updateScripts(runner, flags) + } + ) + .option('git-add', { + desc: 'Runs git-add on the changed package.json file', + type: 'boolean', + default: false + }) + .option('write', { + type: 'boolean', + default: true, + desc: 'Write output to the package.json' + }) + cli.command('help', 'Display task description', (input, flags) => { const runner = require('..')(flags) return runner.getHelp(input) diff --git a/lib/index.js b/lib/index.js index 0c6b358..5f525a2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,6 +17,14 @@ class Maid { } } + listTasks() { + return this.maidfile.tasks.map(task => task.name) + } + + getFilepath() { + return this.maidfile.filepath + } + async runTasks(taskNames, inParallel) { if (!taskNames || taskNames.length === 0) return diff --git a/lib/runCLICommand.js b/lib/runCLICommand.js index 0648784..42300cf 100644 --- a/lib/runCLICommand.js +++ b/lib/runCLICommand.js @@ -2,14 +2,18 @@ const path = require('path') const spawn = require('cross-spawn') const MaidError = require('./MaidError') -module.exports = ({ task, type = task.type, resolve, reject }) => { - const cmd = spawn(type, ['-c', task.script, ...process.argv.slice(2)], { +const exec = (cmd, args) => + spawn(cmd, args, { stdio: 'inherit', - env: Object.assign({}, process.env, { + env: { + ...process.env, PATH: `${path.resolve('node_modules/.bin')}:${process.env.PATH}` - }) + } }) +module.exports = ({ task, type = task.type, resolve, reject }) => { + const cmd = exec(type, ['-c', task.script, ...process.argv.slice(2)]) + cmd.on('close', code => { if (code === 0) { resolve() @@ -20,3 +24,5 @@ module.exports = ({ task, type = task.type, resolve, reject }) => { return cmd } + +module.exports.exec = exec diff --git a/lib/updateScripts.js b/lib/updateScripts.js new file mode 100644 index 0000000..17b25a9 --- /dev/null +++ b/lib/updateScripts.js @@ -0,0 +1,88 @@ +const loadFile = require('./loadFile') +const MaidError = require('./MaidError') +const fs = require('fs') +const path = require('path') +const { exec } = require('./runCLICommand') +const logger = require('./logger') + +const flattenObj = (a, b) => ({ + ...a, + ...b +}) + +const getPassThroughArgs = flags => { + const passThroughArgs = [] + + if (flags.quiet) { + passThroughArgs.push('--quiet') + } + + if (flags.section) { + passThroughArgs.push('-s', flags.section) + } + + const pathIndex = process.argv.findIndex(arg => arg.match(/-p|--path/)) + 1 + if (pathIndex) { + passThroughArgs.push('-p', process.argv[pathIndex]) + } + + return passThroughArgs.join(' ') +} + +const checkForTaskConflicts = (maidTasks, customScripts) => { + const conflictingTasks = maidTasks.filter(task => + Object.keys(customScripts).includes(task) + ) + + if (conflictingTasks.length) { + throw new MaidError( + `Conflicts between maidfile and package.json. Please check these scripts: \n\t + ${conflictingTasks.join(', ')}` + ) + } +} + +module.exports = (maid, flags) => { + const { path: pkgPath, data: pkg } = loadFile.loadSync(['package.json']) + if (!pkgPath) return null + + const maidExec = + path.basename(process.argv[0]) === 'node' + ? `node ${path.relative(process.cwd(), process.argv[1])}` + : 'maid' + + const { scripts = {} } = pkg + const tasks = maid + .listTasks() + .filter( + (task, index, tasks) => + !tasks.includes((task.match(/(?:pre|post)(.*)/) || [])[1]) + ) + const passThroughArgs = getPassThroughArgs(flags) + + const baseScripts = Object.keys(scripts) + .filter(task => !scripts[task].startsWith(maidExec)) + .map(task => ({ [task]: scripts[task] })) + .reduce(flattenObj, {}) + + checkForTaskConflicts(tasks, baseScripts) + + const finalScripts = tasks + .map(task => ({ + [task]: `${maidExec} ${passThroughArgs} ${task}`.replace(/\s+/g, ' ') + })) + .reduce(flattenObj, baseScripts) + + if (flags.write) { + fs.writeFileSync( + pkgPath, + JSON.stringify({ ...pkg, ...{ scripts: finalScripts } }, null, 2) + ) + } else { + logger.log('\n', finalScripts) + } + + if (flags.gitAdd) { + exec('git', ['add', pkgPath]) + } +} diff --git a/package.json b/package.json index db03732..bfeac86 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,9 @@ "lib" ], "scripts": { - "maid": "node bin/cli", - "test": "yarn maid lint && yarn maid test" + "lint": "node bin/cli lint", + "test": "node bin/cli test", + "toc": "node bin/cli toc" }, "author": "egoist <0x142857@gmail.com>", "license": "MIT", @@ -59,12 +60,16 @@ }, "lint-staged": { "*.js": [ - "yarn maid lint --fix", + "node bin/cli lint --fix", "git add" ], + "package.json": [ + "node bin/cli update-scripts --no-write" + ], "README.md": [ - "yarn maid toc", + "node bin/cli toc", + "node bin/cli update-scripts --git-add", "git add" ] } -} +} \ No newline at end of file