mrkeyoor.com_
Sun 09 Aug 06:56 UTC
npmUtilsupdated 09 Aug 2026

@cnakazawa/watch

@cnakazawa/watch is a CommonJS utility for monitoring a directory tree from Node.js. It walks the tree, polls each discovered path with fs.watchFile, and reports creation, change, and removal either through one callback or an EventEmitter-style monitor. The package also installs a watch command that reruns a shell command after file changes. This scoped release is a lightly packaged form of mikeal/watch, not a modern event-driven watcher, so its simple API comes with old implementation choices.

Verdict

Keep @cnakazawa/watch when compatibility with its callback behavior matters or polling is specifically useful. For a new watcher, its per-path polling, missing types, dated source, and weak error handling make chokidar or @parcel/watcher the more defensible install.

API stability4/5The public surface is only watchTree, unwatchTree, createMonitor, walk, and the CLI, and the default-branch history shows no recent API churn. That makes existing integrations unlikely to break. The score stops short of five because stability here mostly comes from inactivity, the README documents the unscoped package name, and the source behavior does not fully honor its claim that watchTree options pass through to fs.watchFile.
Docs2/5The README explains the initial callback sentinel, monitor events, filtering, polling interval, and CLI flags with usable examples. It is still incomplete for current users: installation and require examples name watch instead of @cnakazawa/watch, there is no TypeScript guidance, error behavior is not documented, stop timing is barely covered, and the option pass-through description conflicts with the 1.0.4 source.
Maintenance1/5The npm release is 1.0.4 from February 2020. GitHub reports the repository as unarchived, but its latest default-branch commits returned by the API are from 2017, the repo's last push was in January 2023, and a true issue-only search finds 43 open issues. A package can remain operational without releases, but this evidence offers little reason to expect fixes for modern Node behavior or platform edge cases.
Ecosystem2/5The package recorded 2,989,932 downloads in the measured week, largely reflecting its place in established dependency trees, and the original repository has 1,279 stars. Its direct ecosystem is thin: there are no bundled declarations, plugin hooks, framework adapters, or documented extension packages. Modern watcher tooling and examples overwhelmingly center on chokidar, native fs.watch, or specialized native watchers instead.

Use it if

  • You maintain code that already depends on @cnakazawa/watch and need to preserve its watchTree callback contract
  • You need polling because native filesystem notifications are unreliable on the mounted or networked filesystem you use
  • You want a tiny CommonJS API that produces current and previous fs.Stats objects for changed files
  • You need a basic command-line rerunner with an adjustable quiet period after each command starts
Skip it if

Setup reality

Installation is one npm command and there are no peer dependencies or native builds, but the package's age shows immediately. Import it with require('@cnakazawa/watch'); the upstream README says require('watch') because it documents the unscoped original. The module is CommonJS and ships no TypeScript declarations, so typed projects need a local declaration or an untyped boundary. Startup is asynchronous: watchTree first walks the complete tree and then calls your callback with a files object plus null current and previous stats. Treat that special call as readiness, not as a file change. The implementation uses fs.watchFile on every path and accepts interval in seconds, converting it to milliseconds internally. The README says options pass through to fs.watchFile, but the published source constructs a fresh object containing only interval, so do not expect persistent or bigint to work. Use ignoreDirectoryPattern for node_modules and build outputs before watching a big tree. Permissions are split between ignoreUnreadableDir for EACCES during directory reads and ignoreNotPermitted for EPERM during stat calls. Stopping is manual through unwatchTree(root) or monitor.stop(). The CLI installs the generic executable name watch, runs the supplied command through a shell helper, and needs platform-specific quoting, so never interpolate untrusted filenames or arguments into that command string.

Patterns

Watch a directory tree and classify callbackswatch-tree

const watch = require('@cnakazawa/watch')

watch.watchTree('./src', (file, curr, prev) => {
  if (typeof file === 'object' && curr === null && prev === null) {
    console.log('ready', Object.keys(file).length)
  } else if (prev === null) {
    console.log('created', file)
  } else if (curr.nlink === 0) {
    console.log('removed', file)
  } else {
    console.log('changed', file)
  }
})

The first callback is a readiness sentinel containing the initial file map; failing to branch on it makes startup look like a file event.

Use named create, change, and remove eventscreate-monitor

const watch = require('@cnakazawa/watch')

watch.createMonitor('./src', (monitor) => {
  monitor.on('created', (file, stat) => console.log('created', file, stat.size))
  monitor.on('changed', (file, curr, prev) => console.log('changed', file, prev.mtime, curr.mtime))
  monitor.on('removed', (file, stat) => console.log('removed', file, stat.mtime))
})

Listeners are attached after the initial walk completes, so this API gives you a ready monitor but no documented error event.

Inspect the initial file mapread-initial-files

const watch = require('@cnakazawa/watch')

watch.createMonitor('./content', (monitor) => {
  for (const [file, stat] of Object.entries(monitor.files)) {
    if (!stat.isDirectory()) console.log(file, stat.size)
  }
})

monitor.files includes both directories and files, keyed by the joined paths produced during the initial walk.

Watch only JavaScript and JSON filesfilter-extensions

const path = require('node:path')
const watch = require('@cnakazawa/watch')

watch.createMonitor('./src', {
  filter(file, stat) {
    return stat.isDirectory() || ['.js', '.cjs', '.json'].includes(path.extname(file))
  },
}, (monitor) => {
  monitor.on('changed', (file) => console.log(file))
})

Return true for directories or the initial walk will exclude them before it can discover matching descendants.

Skip dependency and build directoriesignore-generated-directories

const watch = require('@cnakazawa/watch')

watch.createMonitor('.', {
  ignoreDirectoryPattern: /(?:^|[\/])(?:node_modules|dist|coverage)(?:[\/]|$)/,
}, (monitor) => {
  monitor.on('changed', (file) => console.log(file))
})

The regular expression receives full joined paths. Anchoring only to node_modules would miss nested dependency directories.

Exclude dotfiles during the walkignore-dotfiles

const watch = require('@cnakazawa/watch')

watch.watchTree('.', { ignoreDotFiles: true }, (file, curr, prev) => {
  if (typeof file === 'string' && curr && prev) console.log(file)
})

This checks whether each basename starts with a dot; it is not a general hidden-file test on every operating system.

Reduce polling frequencyset-poll-interval

const watch = require('@cnakazawa/watch')

watch.createMonitor('./logs', { interval: 2 }, (monitor) => {
  monitor.on('changed', (file) => console.log(file))
})

interval is documented in seconds and the source multiplies it by 1,000 before passing it to fs.watchFile.

Skip unreadable and forbidden pathstolerate-permissions

const watch = require('@cnakazawa/watch')

watch.createMonitor('/srv/shared', {
  ignoreUnreadableDir: true,
  ignoreNotPermitted: true,
}, (monitor) => {
  console.log('watching', Object.keys(monitor.files).length, 'paths')
})

The two flags cover different source branches: EACCES from readdir and EPERM from stat. Other errors can still be thrown asynchronously.

Stop a monitor during shutdownstop-monitor

const watch = require('@cnakazawa/watch')

watch.createMonitor('./src', (monitor) => {
  const shutdown = () => {
    monitor.stop()
    process.exit(0)
  }
  process.once('SIGINT', shutdown)
  process.once('SIGTERM', shutdown)
})

monitor.stop() delegates to unwatchTree for the original root and removes the fs.watchFile pollers known for that tree.

Stop a callback-based tree watcherunwatch-tree

const watch = require('@cnakazawa/watch')
const root = './src'

watch.watchTree(root, () => {})
setTimeout(() => watch.unwatchTree(root), 30_000)

Pass the same root string used to start watching; the internal watcher registry is keyed by that value.

Walk a tree once without keeping watcherswalk-once

const watch = require('@cnakazawa/watch')

watch.walk('./src', { ignoreDotFiles: true }, (err, files) => {
  if (err) throw err
  const regularFiles = Object.entries(files)
    .filter(([, stat]) => stat.isFile())
    .map(([file]) => file)
  console.log(regularFiles)
})

walk is exported by the package source but not described in the README API, so treat it as a weakly supported convenience rather than a firm contract.

Rerun tests from the command linerun-cli-command

npx --package @cnakazawa/watch watch 'npm test' ./src --wait=1 --ignoreDotFiles --ignoreDirectoryPattern='/node_modules|dist/'

The binary is named watch and executes the command through a shell helper. Quote for your shell and never construct the command from untrusted input.

Alternatives

PackageRegistryPick it when
chokidarnpmUse for maintained cross-platform watching with glob ignores, readiness, atomic-write handling, and a familiar event API
node-watchnpmUse when you want a smaller fs.watch-based API and do not need chokidar's broader behavior controls
@parcel/watchernpmUse when performance on large trees matters enough to accept native binaries and platform-specific packages