Watching for File Changes

Updated: 03 September 2023

Essentially I’m trying to do something like nodemon and forever but just a lot simpler and that will suit my needs better

Running Dev

Clone the GitHub Repo, and then install the app dependencies and install the application globally

Terminal window
1
npm i
2
npm i -g

Next we can link NPM to our actual package instead of the global version with

Terminal window
1
npm link

What’s Happening Here

Getting Started

We have a simple node app that is running globally, but linked to our application’s index.js file with the following

Terminal window
1
npm i -g
2
npm link

Define Run Command

Next we the startover command linked in our package.json as follows

1
{
2
"name": "startover",
3
"version": "0.0.0",
4
"description": "Rerun scripts on file change",
5
"main": "index.js",
6
"scripts": { ... },
7
"bin": {
8
"startover": "./index.js"
9
},
10
...
11
}

This will allow us to run th index.js file which is done by simply running the following command

Terminal window
1
startover

Parsing Command Line Arguments

We make use of the commander package to parse CLI arguments, we do this from the index.js file and parses the various parameters

1
const app = require('commander')
2
3
const split = (val) => val.split(',')
4
5
app
6
.version('0.1.0')
7
.option('-d, --dirs [directories]', 'List of directories to watch', split)
8
.option(
9
'-c, --commands [directory]',
10
'List of Commands to run when files change',
11
split
12
)
13
.option(
14
'-f, --exclude-files [files to exclude]',
15
'List of Files to Exclude from Watch',
16
split
17
)
18
.option(
19
'-e, --exclude-extensions [extensions to exclude]',
20
'List of Extensions to Exclude from Watch',
21
split
22
)
23
.option(
24
'-D, --exclude-directories [directories to exclude]',
25
'List of Directories to Exclude from Watch',
26
split
27
)
28
.parse(process.argv)
29
30
console.log('Called with the following Options')
31
32
if (app.dirs) console.log('dirs:', app.dirs)
33
if (app.commands) console.log('commands:', app.commands)
34
if (app.excludeFiles) console.log('excluded files:', app.excludeFiles)
35
if (app.excludeExtensions)
36
console.log('excluded extensions:', app.excludeExtensions)
37
if (app.excludeDirectories)
38
console.log('excluded directories:', app.excludeDirectories)

We can also get help and information on the commands with

Terminal window
1
startover -h

Watcher Events

I then defined the events for the file watcher as well as the configuration

1
// Initialize watcher.
2
var watcher = chokidar.watch('.', {
3
ignored: new Array().concat(
4
app.excludeDirectories,
5
app.excludeFiles,
6
new RegExp(app.excludeExtensions.map((el) => '.' + el).join('|'))
7
),
8
persistent: true,
9
})
10
11
// Something to use when events are received.
12
var log = console.log.bind(console)
13
14
// Add event listeners.
15
var ready = false
16
17
watcher
18
.on('add', (path) => console.log(`File ${path} has been added`))
19
.on('change', (path) => {
20
if (ready) execute()
21
log(`File ${path} has been changed`)
22
})
23
.on('unlink', (path) => {
24
if (ready) execute()
25
log(`File ${path} has been removed`)
26
})
27
.on('addDir', (path) => {
28
if (ready) execute()
29
log(`Directory ${path} has been added`)
30
})
31
.on('unlinkDir', (path) => {
32
if (ready) execute()
33
log(`Directory ${path} has been removed`)
34
})
35
.on('error', (error) => log(`Watcher error: ${error}`))
36
.on('ready', () => {
37
ready = true
38
log('Initial scan complete. Ready for changes')
39
})

And finally added a function to carry out the custom command that I need to be run

1
const child_process = require('child_process')
2
3
// Utility functions
4
const split = (val) => val.split(',')
5
6
const execute = () => {
7
app.commands.forEach((command) => {
8
child_process.exec(command, function (error, stdout, stderr) {
9
if (stdout) console.log('command out:\n ' + stdout)
10
if (stderr) console.log('stderr:\n' + stderr)
11
if (error !== null) console.log('exec error: ' + error)
12
})
13
})
14
}

Run Startover

We can run startover once it is installed with the following command

Terminal window
1
startover -d myapp -f hello.js,"bye world.html" -e css,md -c "npm run build" -D test,

It is important to remember that the command/commands we are running from the -c option must be compatible with the system/shell we are running startover in and they will run one after the other

Resources

I’ve made use of a few different resources for the application as follows

Articles

Libraries