What's the recommended way to copy multiple dotfiles with yeoman?
Asked Answered
A

4

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);
Amie answered 19/7, 2014 at 21:4 Comment(0)
S
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()
);
Soapbark answered 9/12, 2014 at 18:33 Comment(5)
Also, worth noting that if you're using a glod in from the destination needs to be a directory.Westphal
This was the right answer, and deserves to be upvoted. See my latest commit for a usage example: github.com/srsgores/generator-stylus-boilerplate/commit/…Barehanded
Thanks! Also, this.destinationRoot() can be replaced with this.destinationPath("path/to/folder") for relative destinations (as indicated in srsgores' commit example).Amie
If you have nested dot files as well, then you can use: this.templatePath('static/**/.*')Heinz
I'm sorry but why so much code? The answer below is better.Gaelic
B
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.

Bailiwick answered 8/2, 2016 at 13:39 Comment(6)
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".Upali
@julmot I think I've eventually found it following the File Utilities section on the File System Docs. 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)Bailiwick
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).Frosting
Erratum - the whole template path must be specified: this.fs.copyTpl(glob.sync(this.templatePath('files/**'), {dot: true}), 'client', context)Frosting
@NicolasForney For copyTpl it seems the {globOptions} needs to be in 5th argument position (eg. here), as the syntax says #copyTpl(from, to, context[, templateOptions [, copyOptions ]]).Roxieroxine
I don't know why, but this only seems to be working for me if the globOptions are in the 4th argument of copyTpl().Dogs
D
23

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 } }
);
Dogs answered 11/11, 2017 at 13:52 Comment(5)
explain the downvote? This is the current working version.Dogs
This is better. Prefer this.Gaelic
For copyTpl you need to use the fifth argument with globOptions - I just updated the answerDemagoguery
This should be accepted. Works for me and the accepted one does not. Plus it's in the mem-fs docsNunci
Preferable because it changes the behavior of the copyTpl function through an explicit option, instead of obfuscating the problem with multiple copy statements.Mentalism
K
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)

Kassandrakassaraba answered 26/4, 2016 at 9:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.