remy / nodemon

Monitor for any changes in your node.js application and automatically restart the server - perfect for development
http://nodemon.io/
MIT License
26.35k stars 1.74k forks source link

Restart not firing beyond deletion of directory under watch, when this directory structure is recreated (on Ubuntu 22.04) #2192

Closed morgaan closed 5 months ago

morgaan commented 8 months ago

Expected behaviour

On Linux, when nodemon is configured to watch for an assets/ directory, and that assets/ exists when the nodemon command is executed; if assets/ is deleted and then later on recreated and/or has changes happening inside it; nodemon should restart and the restart event hook should be executed.

Actual behaviour

Upon assets/ deletion, nodemon does restart, but that is the end of it. Indeed when the assets/ directory gets recreated and moreover has changes happening inside it, nodemon does ignore it all. Just as if assets/ has been dropped from the watch list.

It is worth noting, that under MacOs, it works as described in the Expected behaviour.

Steps to reproduce

  1. In a fresh new directory: run npm init -y
  2. Install nodemon: run npm install -D nodemon@3.1.0
  3. Create the assets/ directory and file: run mkdir assets && echo "I'm an asset" > file.txt
  4. Create the app.js file: echo "console.log('Hello World!');" > app.js
  5. Amend package.json file to contain following nodemonConfig root property:
    "nodemonConfig": {
    "watch": [
      "assets/",
      "app.js"
    ],
      "ext": "*.*",
      "delay": 1,
      "events": {
        "restart": "echo 'restarting'"
      }
    }
  6. Start app.js with nodemon: Run npx nodemon app.js
  7. In an another Terminal window/tab, and in the directory that host the application, delete the assets/ directory: Run rm -rf assets
  8. Coming back to the Terminal window/tab that runs nodemon, you should see nodemon restarting due to changes and logging restarting followed by Hello World!, so far, all good!
  9. Now again in the other Terminal window/tab, recreate the asset assets/file.txt: Run mkdir assets && echo "I'm an asset" > file.txt
  10. Coming back to the Terminal window/tab that runs nodemon, you won't see any new nodemon restart. That is the issue I'm raising
  11. Bonus: Again in the other Terminal window/tab, make a change to app.js: Run echo "console.log('Still running!');" >> app.js
  12. Coming back to the Terminal window/tab that runs nodemon, you should see nodemon restarting due to app.js changes and logging restarting followed by Hello World! followed by Still running!. So the watch is still working but only for resources that have not been deleted.

Here is the `--dump`
--------------
node: v18.17.1
nodemon: 3.1.0
command: /home/unicorn/.nvm/versions/node/v18.17.1/bin/node /home/unicorn/nodemon-issue-sandbox/node_modules/.bin/nodemon --dump
cwd: /home/unicorn/nodemon-issue-sandbox
OS: linux x64
--------------
{
  run: false,
  system: { cwd: '/home/unicorn/nodemon-issue-sandbox' },
  required: false,
  dirs: [
    '/home/unicorn/nodemon-issue-sandbox/assets',
    '/home/unicorn/nodemon-issue-sandbox/app.js'
  ],
  timeout: 1000,
  options: {
    dump: true,
    watch: [ 'assets/', 'app.js', re: /assets\/|app\.js/ ],
    delay: 1,
    events: { restart: "echo 'restarting'" },
    ignore: [
      '**/.git/**',
      '**/.nyc_output/**',
      '**/.sass-cache/**',
      '**/bower_components/**',
      '**/coverage/**',
      '**/node_modules/**',
      re: /.*.*\/\.git\/.*.*|.*.*\/\.nyc_output\/.*.*|.*.*\/\.sass\-cache\/.*.*|.*.*\/bower_components\/.*.*|.*.*\/coverage\/.*.*|.*.*\/node_modules\/.*.*/
    ],
    monitor: [
      '/home/unicorn/nodemon-issue-sandbox/assets/**/*',
      'app.js',
      '!**/.git/**',
      '!**/.nyc_output/**',
      '!**/.sass-cache/**',
      '!**/bower_components/**',
      '!**/coverage/**',
      '!**/node_modules/**'
    ],
    ignoreRoot: [
      '**/.git/**',
      '**/.nyc_output/**',
      '**/.sass-cache/**',
      '**/bower_components/**',
      '**/coverage/**',
      '**/node_modules/**'
    ],
    restartable: 'rs',
    colours: true,
    execMap: { py: 'python', rb: 'ruby', ts: 'ts-node' },
    stdin: true,
    runOnChangeOnly: false,
    verbose: false,
    signal: 'SIGUSR2',
    stdout: true,
    watchOptions: {},
    execOptions: {
      script: 'index.js',
      exec: 'node',
      args: [],
      scriptPosition: 0,
      nodeArgs: undefined,
      execArgs: [],
      ext: '',
      env: {}
    }
  },
  load: [Function (anonymous)],
  reset: [Function: reset],
  lastStarted: 0,
  loaded: [ '/home/unicorn/nodemon-issue-sandbox/package.json' ],
  watchInterval: null,
  signal: 'SIGUSR2',
  command: {
    raw: { executable: 'node', args: [ 'index.js' ] },
    string: 'node index.js'
  }
}
 
github-actions[bot] commented 7 months ago

This issue has been automatically marked as idle and stale because it hasn't had any recent activity. It will be automtically closed if no further activity occurs. If you think this is wrong, or the problem still persists, just pop a reply in the comments and @remy will (try!) to follow up. Thank you for contributing <3

morgaan commented 7 months ago

I'm convinced this requires attention! I would be very thankful if you could give it a look 🙏

ryanto commented 7 months ago

Just want to confirm I'm seeing this behavior on Windows 11 as well. After a watched directory is deleted (and recreated) nodemon stops picking up changes. Tested on nodemon 3.1.0.

Fwiw I'm only seeing this issue on Windows, on macOS everything works as expected.

PS: Thanks so much for nodemon, it's a real time saver while developing!

brainthinks commented 7 months ago

I am also running into this. My use case is that my build process (for typescript) runs rm -rf ./dist, which is the dir that nodemon is watching. I ended up rolling my own file watcher...

Posting here in case anyone finds it useful. Note that this is specific to my use case, so YMMV.

#!/usr/bin/env node

/**
 * @see https://github.com/remy/nodemon?tab=readme-ov-file#using-nodemon-as-a-module
 * @see https://github.com/remy/nodemon/blob/main/doc/requireable.md
 */

import fs from 'node:fs';

import debounce from 'lodash-es/debounce.js';

import utils from 'nodemon/lib/utils/index.js';
import nodemonLogger from 'nodemon/lib/utils/log.js';
import nodemon from 'nodemon';

// @see https://github.com/remy/nodemon/issues/2195
utils.log = nodemonLogger(false);

// @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/nodemon/index.d.ts
const config = {
  verbose: true,
  watch: [ './dist' ],
  delay: 2000,
  exec: 'yarn run serve:dev',
  runOnChangeOnly: true,
};

nodemon(config);

// Don't accidentally spam nodemon.restart
const nodemonRestart = debounce(() => { nodemon.restart(); }, 2_000);

for (let i = 0; i < config.watch.length; i++) {
  const watchEntry = config.watch[i];

  // NOTE - `nodemon`, `chokidar`, `onchange`, `fs.watch` etc. don't work for
  // at least one of the following 2 reasons:
  //
  //   1. inability to respond to directory deletetions
  //   2. no options for polling, needed for Docker volumes
  //
  // Therefore, we roll our own file watcher with `fs.watchFile`, which works
  // in all tested scenarios.
  fs.watchFile(
    watchEntry,
    { persistent: true, interval: 500 },
    (curr, prev) => {
      if (curr.mtimeMs > prev.mtimeMs) {
        utils.log.info(`WATCHER: ${watchEntry} was deleted then recreated...`);
        nodemonRestart();
      }
    },
  );
}

utils.log.status('Watching for changes...');
remy commented 7 months ago

Just adding a thought but if you're directly watching "assets" and it's deleted then later recreated, I'm not sure why it would work.

This is because the watcher is explicitly registering the directory, so once it's gone it has nothing to hook.

This is because nodemon isn't arbitrarily watching for all changes.

If you were watching the parent directory, then I'd expect there to be a way to register deletion and recreation.

ryanto commented 7 months ago

Makes sense... surprisingly it works on macOS which I think is what threw me off.

In my situation the parent directory has a lot of files that change often, but I'm only interested a single sub directory (the one that's being deleted and re-created). In that case do you think it's best to watch the parent directory and then ignore everything in the parent that I'm not interested in?

Thanks in advance!

morgaan commented 7 months ago

Thank you @remy, I can only double what @ryanto wrote. Looking forward to your next comment :popcorn:

brainthinks commented 7 months ago

@remy to address your comment, under "normal" circumstances, you are correct in that watching a deleted folder will not work, for the reasons you mention. I think the sentiment in this issue is that this is a desirable feature, though it isn't something that is supported by the underlying file-watching libraries / logic.

I tried messing around with the watch and ignore values, and seemed to get close, since the changes triggered nodemon's watch monitor, but ultimately I think this is a limitation of chokidar. I'm not sure that nodemon could support this use case without adding an alternative watch implementation based on node's fs.watchFile, or something that was similarly tolerant to deletions and recreations. This is definitely non-trivial, as there is also a lot of supporting code around the chokidar watcher, and would likely require a different strategy for managing the watchers...

As such, I don't think nodemon can support this use case without significant effort, hence why I wrote my own watch for my dist directory.

My intention with this post is to hopefully answer the question of "why doesn't this work" and assert that it can't work without significant effort, which would involve either rewriting the watcher logic or writing an entire alternative based on something other than chokidar.

remy commented 7 months ago

@brainthinks to be honest, I think raising it with the chokidar repo might be the sensible direction. I'm really not sure where it lands in terms of desire to support, but the fact you've got inconsistent behaviour between windows and macros suggests there's favour for fixing/adding it to chokidar.

morgaan commented 7 months ago

@brainthinks Sorry I had missed your initial message with the code snippet :blush:.
I believe it can be super handy for folks, so thank you so much for sharing.

On my end, as we actually revisited our approach to avoid getting in the situation of having the directory deleted while nodemon is watching it, we seem to be out of trouble and therefore do not need the snippet nor this fix. But if in some other constellation I find myself stuck in this situation, and this has not been addressed, most likely at chokidar level, I'll know where to look for so thank you again.

Thank you all

github-actions[bot] commented 6 months ago

This issue has been automatically marked as idle and stale because it hasn't had any recent activity. It will be automtically closed if no further activity occurs. If you think this is wrong, or the problem still persists, just pop a reply in the comments and @remy will (try!) to follow up. Thank you for contributing <3

github-actions[bot] commented 5 months ago

Automatically closing this issue due to lack of activity