-
-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
watch
Command - watch files/dirs and rerun task on change
#33
Closed
Closed
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
74740bc
add node-chokidar for watching files
57c6491
add FileWatcher class for watch command
ee1bdeb
adapt command parsing to new command regex
47db52e
outsource MaidRunner
e8aae67
on file change, get the task that is watching that file and run it
40c09de
pass the tasks name to watch()
3707763
use the regexGroups array for better readability
845c907
merge master
b923dea
only init watcher on first file, only log&add file if not already wat…
68db54a
turn FileWatcher into Singleton
d7ed3c6
adapt unwatch and close to singleton
e943ca6
regenerate snapshots
9994219
update to loosened restrictions
6f4b301
run all tasks subsequently in case a task array gets passed
b784d7c
auto-fix commands without 'when' by defaulting to 'before this'
710d83f
include edge cases in fixCommand, don't append anything if it's just …
a5d3f74
trying to fix the tests
7858fb2
make tasks execution on watch action asynchronous
0604f20
use custom asyncForEach
be6af5d
merge
af47299
CRC
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
const chokidar = require('chokidar') | ||
const logger = require('./logger') | ||
|
||
class FileWatcher { | ||
constructor() { | ||
this.runner = [] | ||
this.watcher = [] | ||
this.watchedTasks = {} | ||
} | ||
|
||
init(runner) { | ||
this.watcher.push( | ||
chokidar.watch(['file, dir, glob'], { | ||
ignored: /(^|[/\\])\../, | ||
persistent: true, | ||
ignoreInitial: true | ||
}) | ||
) | ||
|
||
this.watcher[0] | ||
.on('add', path => | ||
this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) | ||
) | ||
.on('change', path => | ||
this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) | ||
) | ||
.on('unlink', path => | ||
this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) | ||
) | ||
.on('error', error => logger.log(`Watcher error: ${error}`)) | ||
} | ||
|
||
getTaskNames(path) { | ||
let foundTask = null | ||
Object.keys(this.watchedTasks).forEach(taskName => { | ||
if (this.watchedTasks[taskName] === path) { | ||
foundTask = taskName | ||
} else { | ||
let pathRegex = this.watchedTasks[taskName].replace('*', '(.*?)') | ||
pathRegex = | ||
pathRegex.charAt(pathRegex.length - 1) === '/' | ||
? pathRegex.concat('(.*?)') | ||
: pathRegex | ||
if (new RegExp(pathRegex).test(path)) { | ||
foundTask = taskName | ||
} | ||
} | ||
}) | ||
|
||
return foundTask.split(',') | ||
} | ||
|
||
watch(file, taskName) { | ||
this.watcher[0].add(file) | ||
this.watchedTasks[taskName] = file | ||
} | ||
|
||
unwatch(file) { | ||
this.watcher[0].unwatch(file) | ||
} | ||
|
||
close() { | ||
this.watcher[0].close() | ||
} | ||
} | ||
|
||
// Singleton | ||
const instance = new FileWatcher() | ||
Object.freeze(instance) | ||
|
||
module.exports = instance |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
const path = require('path') | ||
const chalk = require('chalk') | ||
const mm = require('micromatch') | ||
const requireFromString = require('require-from-string') | ||
const logger = require('./logger') | ||
const readMaidFile = require('./readMaidFile') | ||
const MaidError = require('./MaidError') | ||
const runCLICommand = require('./runCLICommand') | ||
const FileWatcher = require('./FileWatcher') | ||
|
||
class MaidRunner { | ||
constructor(opts = {}) { | ||
this.maidfile = readMaidFile(opts.section) | ||
logger.setOptions({ quiet: opts.quiet }) | ||
|
||
if (!this.maidfile) { | ||
throw new MaidError('No maidfile was found. Stop.') | ||
} | ||
this.firstTask = true | ||
this.alreadyWatching = {} | ||
} | ||
|
||
async runTasks(taskNames, inParallel) { | ||
if (!taskNames || taskNames.length === 0) return | ||
|
||
if (inParallel) { | ||
await Promise.all( | ||
taskNames.map(taskName => { | ||
return this.runTask(taskName) | ||
}) | ||
) | ||
} else { | ||
for (const taskName of taskNames) { | ||
await this.runTask(taskName) | ||
} | ||
} | ||
} | ||
|
||
async runFile(taskName) { | ||
await this.runTask('beforeAll', false) | ||
await this.runTask(taskName) | ||
await this.runTask('afterAll', false) | ||
} | ||
|
||
async runTask(taskName, throwWhenNoMatchedTask = true) { | ||
const task = | ||
taskName && | ||
this.maidfile && | ||
this.maidfile.tasks.find(task => task.name === taskName) | ||
|
||
if (!task) { | ||
if (throwWhenNoMatchedTask) { | ||
throw new MaidError(`No task called "${taskName}" was found. Stop.`) | ||
} else { | ||
return | ||
} | ||
} | ||
|
||
await this.runTaskHooks(task, 'before') | ||
|
||
const start = Date.now() | ||
|
||
logger.log(`Starting '${chalk.cyan(task.name)}'...`) | ||
await new Promise((resolve, reject) => { | ||
const handleError = err => { | ||
throw new MaidError(`Task '${task.name}' failed.\n${err.stack}`) | ||
} | ||
if (checkTypes(task, ['sh', 'bash'])) { | ||
return runCLICommand({ task, resolve, reject }) | ||
} | ||
if (checkTypes(task, ['py', 'python'])) { | ||
return runCLICommand({ type: 'python', task, resolve, reject }) | ||
} | ||
if (checkTypes(task, ['js', 'javascript'])) { | ||
let res | ||
try { | ||
res = requireFromString(task.script, this.maidfile.filepath) | ||
} catch (err) { | ||
return handleError(err) | ||
} | ||
res = res.default || res | ||
return resolve( | ||
typeof res === 'function' | ||
? Promise.resolve(res()).catch(handleError) | ||
: res | ||
) | ||
} | ||
|
||
return resolve() | ||
}) | ||
|
||
logger.log( | ||
`Finished '${chalk.cyan(task.name)}' ${chalk.magenta( | ||
`after ${Date.now() - start} ms` | ||
)}...` | ||
) | ||
await this.runTaskHooks(task, 'after') | ||
} | ||
|
||
async runTaskHooks(task, when) { | ||
const prefix = when === 'before' ? 'pre' : 'post' | ||
const tasks = this.maidfile.tasks.filter(({ name }) => { | ||
return name === `${prefix}${task.name}` | ||
}) | ||
await this.runTasks(tasks.map(task => task.name)) | ||
for (const item of task[when]) { | ||
const { taskNames, inParallel, watchTargets } = item | ||
// if this is the overall first task, init the watcher | ||
if (watchTargets) { | ||
if (this.firstTask) { | ||
FileWatcher.init(this) | ||
this.firstTask = false | ||
} | ||
// log watching on first run of each task | ||
if (!this.alreadyWatching[taskNames]) { | ||
logger.log( | ||
`'${chalk.cyan(taskNames)}' is watching ${chalk.magenta( | ||
watchTargets | ||
)}...` | ||
) | ||
FileWatcher.watch(watchTargets, taskNames) | ||
this.alreadyWatching[taskNames] = true | ||
} | ||
} | ||
|
||
await this.runTasks(taskNames, inParallel) | ||
} | ||
} | ||
|
||
getHelp(patterns) { | ||
patterns = [].concat(patterns) | ||
const tasks = | ||
patterns.length > 0 | ||
? this.maidfile.tasks.filter(task => { | ||
return mm.some(task.name, patterns) | ||
}) | ||
: this.maidfile.tasks | ||
|
||
if (tasks.length === 0) { | ||
throw new MaidError( | ||
`No tasks for pattern "${patterns.join(' ')}" was found. Stop.` | ||
) | ||
} | ||
|
||
console.log( | ||
`\n ${chalk.magenta.bold( | ||
`Task${tasks.length > 1 ? 's' : ''} in ${path.relative( | ||
process.cwd(), | ||
this.maidfile.filepath | ||
)}:` | ||
)}\n\n` + | ||
tasks | ||
.map( | ||
task => | ||
` ${chalk.bold(task.name)}\n${chalk.dim( | ||
task.description | ||
? task.description | ||
.split('\n') | ||
.map(v => ` ${v.trim()}`) | ||
.join('\n') | ||
: ' No description' | ||
)}` | ||
) | ||
.join('\n\n') + | ||
'\n' | ||
) | ||
} | ||
} | ||
|
||
function checkTypes(task, types) { | ||
return types.some(type => type === task.type) | ||
} | ||
|
||
module.exports = MaidRunner |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
synchronous
forEach
which executes asyncrunFile
? Ouch.