grunt-contrib-clean: Negation not working?

The following deletes all files in the folder, including main.min.css - shouldn’t that file have been negated and thus left behind?

clean: [
  "dist/css/**",
  "!dist/css/main.min.css"
]

About this issue

  • Original URL
  • State: closed
  • Created 11 years ago
  • Comments: 23 (14 by maintainers)

Most upvoted comments

It would be really helpful if cowboy’s explanation could be added to the npm readme for grunt-contrib-clean. As it is now, most every person that uses clean will waste ~20 minutes figuring out it doesn’t work as expected. Even a small note in the readme would go a long way.

@abierbaum glob patterns get expanded into a big list of all files and directories that are matched. that resulting lists is passed into the task, which in this case, goes through them one at a time and deletes them.

For example, if you have this directory structure:

.
└── deep
     ├── deep.txt
     └── deeper
         ├── deeper.txt
         └── deepest
             └── deepest.txt

And you tell the clean task to delete these files:

clean: {
  all: ['deep/**']
}

Grunt will first expand that glob pattern to this list of files and directories:

deep
deep/deep.txt
deep/deeper
deep/deeper/deeper.txt
deep/deeper/deepest
deep/deeper/deepest/deepest.txt

And then try to delete them. Because the rimraf lib used in the clean task deletes both files and directories, this is what happens:

  1. delete deep - ok, delete directory and all of it’s contents.
  2. delete deep/deep.txt - whoops, this file no longer exists, skip.
  3. delete deep/deeper - whoops, this file no longer exists, skip.
  4. …etc…

One thing you could do is tell Grunt to only match files per the filter option. That requires you to specify files in a more verbose format, but it gives you a lot more power and flexibility:

clean: {
  all: {
    src: ['deep/**'],
    filter: 'isFile'
  }
}

Now when the task is run, Grunt matches these files but no directories:

deep/deep.txt
deep/deeper/deeper.txt
deep/deeper/deepest/deepest.txt

Which rimraf will then delete. But then again, it won’t delete any directories at all, which you might want to do.

This isn’t a problem, though. Because you can specify multiple files objects per task target, you can probably get a bit creative and specify multiple sets of src files using the most verbose format, like so:

clean: {
  all: {
    files: [
      // Delete all files except for *.foo in subdir1 and *.bar in subdir2
      {
        src: ['deep/**', '!deep/subdir1/*.foo', '!deep/subdir2/*.bar'],
        filter: 'isFile'
      },
      // Delete some specific directories (and all their contents)
      {
        src: ['deep/subdir1/subdir3', 'deep/subdir4', ...]
      },
    ]
  }
}

@dalgard Try this solution. Since /** will also match dist/css/ and delete it, try using /**/* instead:

clean: [
  "dist/css/**/*",
  "!dist/css/main.min.css"
]

Otherwise specifically exclude the folder from being deleted, as @cowboy suggested, with:

clean: [
  "dist/css/**",
  "!dist/css/",
  "!dist/css/main.min.css"
]