29

I am building a yeoman generator for a fairly typical node app:

/
|--package.json
|--.gitignore
|--.travis.yml
|--README.md
|--app/
    |--index.js
    |--models
    |--views
    |--controllers

In the templates folder of my yeoman generator, I have to rename the dotfiles (and the package.json) to prevent them from being processed as part of the generator:

templates/
 |--_package.json
 |--_gitignore
 |--_travis.yml
 |--README.md
 |--app/
     |--index.js
     |--models
     |--views
     |--controllers

I see a lot of generators that copy dotfiles individually manually:

this.copy('_package.json', 'package.json')
this.copy('_gitignore', '.gitignore')
this.copy('_gitattributes', '.gitattributes')

I think it's a pain to manually change my generator code when I add new template files. I would like to automatically copy all files in the /templates folder, and rename the ones that are prefixed with _.

What's the best way to do this?

If I were to describe my intention in imaginary regex, this is what it would look like:

this.copy(/^_(.*)/, '.$1')
ths.copy(/^[^_]/)

EDIT This is the best I can manage:

this.expandFiles('**', { cwd: this.sourceRoot() }).map(function() {
    this.copy file, file.replace(/^_/, '.')
}, this);
Raine Revere
  • 30,985
  • 5
  • 40
  • 52

4 Answers4

35

I found this question through Google as I was looking for the solution, and then I figured it out myself.

Using the new fs API, you can use globs!

// Copy all non-dotfiles
this.fs.copy(
  this.templatePath('static/**/*'),
  this.destinationRoot()
);

// Copy all dotfiles
this.fs.copy(
  this.templatePath('static/.*'),
  this.destinationRoot()
);
Emobe
  • 660
  • 11
  • 32
callumacrae
  • 8,185
  • 8
  • 32
  • 49
  • Also, worth noting that if you're using a glod in from the destination needs to be a directory. – Scott Sword Mar 16 '15 at 20:20
  • This was the right answer, and deserves to be upvoted. See my latest commit for a usage example: https://github.com/srsgores/generator-stylus-boilerplate/commit/d785cfe31e3a519edb3b75edb22a5dbc776ecdfd – user1429980 May 10 '15 at 23:58
  • Thanks! Also, this.destinationRoot() can be replaced with this.destinationPath("path/to/folder") for relative destinations (as indicated in srsgores' commit example). – Raine Revere Jun 04 '15 at 14:42
  • If you have nested dot files as well, then you can use: `this.templatePath('static/**/.*')` – duhseekoh Jan 28 '16 at 23:44
  • 1
    I'm sorry but why so much code? The answer below is better. – kgpdeveloper Jan 04 '18 at 04:20
31

Adding to @callumacrae 's answer: you can also define dot: true in the globOptions of copy(). That way a /** glob will include dotfiles. Example:

this.fs.copy(
  this.templatePath('files/**'),
  this.destinationPath('client'),
  { globOptions: { dot: true } }
);

A list of available Glob options can be found in the README of node-glob.

sthzg
  • 5,514
  • 2
  • 29
  • 50
  • 4
    Yeoman documentation is really bad. Where did you found the "globOptions" information? I know the list of options, however there is no hint about the base object name "globOptions". – dude Mar 23 '16 at 14:37
  • @julmot I _think_ I've eventually found it following the **File Utilities** section on the [File System Docs](http://yeoman.io/authoring/file-system.html). They link to the `mem-fs-editor` npm package which provides the underlying implementation for the file system operations. In that README there are several mentions of `globOptions` (which then again lead to the `node-glob` package :-), which is also linked from the [API Docs for `actions/file`](http://yeoman.io/generator/actions_file.html)) – sthzg Mar 23 '16 at 14:52
  • 1
    I was also struggling to handle the dot file but with fs.copyTpl which internally use fs.copy. But apparently fs.copyTpl dosen't accept globOptions like copy do. To overcome this situation here is a solution : this.fs.copyTpl(glob.sync('files/**', {dot: true}), 'client', context). – Nicolas Forney Apr 12 '16 at 12:43
  • Erratum - the whole template path must be specified: this.fs.copyTpl(glob.sync(this.templatePath('files/**'), {dot: true}), 'client', context) – Nicolas Forney Apr 12 '16 at 13:11
  • 3
    @NicolasForney For `copyTpl` it seems the `{globOptions}` needs to be in **5th** argument position ([eg. here](https://stackoverflow.com/a/46083777/1266650)), as the [syntax](https://github.com/SBoudrias/mem-fs-editor#copytplfrom-to-context-templateoptions--copyoptions) says `#copyTpl(from, to, context[, templateOptions [, ` **`copyOptions`** `]])`. – laggingreflex Sep 06 '17 at 20:28
  • I don't know why, but this only seems to be working for me if the globOptions are in the 4th argument of copyTpl(). – Daniel Patrick Nov 11 '17 at 13:50
22

Just got this working for me: the globOptions needs to be in the fifth argument:

this.fs.copyTpl(
  this.templatePath('sometemplate/**/*'),
  this.destinationPath(this.destinationRoot()),
  null,
  null,
  { globOptions: { dot: true } }
);
Emobe
  • 660
  • 11
  • 32
Daniel Patrick
  • 3,980
  • 6
  • 29
  • 49
0

If you don't want to use templates that starts with a dot, you can use the dive module to achieve something identical:

var templatePath = this.templatePath('static-dotfiles');
var destinationRoot = this.destinationRoot();
dive(templatePath, {all: true}, function (err, file, stat) {
    if (err) throw err;
    this.fs.copy(
            file,
            (destinationRoot + path.sep + path.relative(templatePath, file))
                    .replace(path.sep + '_', path.sep + '.')
    );
}.bind(this));

where static-dotfiles is the name of your template folder for dotfiles where _ replaces . in filenames (ex: _gitignore).

Don't forget to add a requirement to dive at the top of your generator with

var dive = require('dive');

Of course, this also works for copyTpl.

Note that all subparts of paths that starts with a _ will be replaced by a . (ex: static-dotfiles/_config/_gitignore will be generated as .config/.gitignore)

mperrin
  • 994
  • 1
  • 10
  • 19