kopy
![Build Status](https://img.shields.io/circleci/project/saojs/kopy/master.svg?style=flat)
Gracefully copy a directory and render templates.
Why is this useful?
This could be used to build a scaffolding tool like yeoman or vue-cli, and it's actually used by SAO.
Install
$ npm install --save kopy
Usage
const copy = require('kopy')
copy('./template', './dest', {
data: {
foo: 'bar'
}
}).then(({files}) => {
console.log(files)
}).catch(err => {
console.log(err.stack)
})
Template Syntax
Templates could use ejs syntax or any template engine supported by jstransformer
API
copy(src, dest, options)
Returns a Promise resolving the majo
instance we use.
copy(...args)
.then(stream => {
stream.meta.answers
stream.meta.data
stream.meta.merged
})
src
Type: string
Required: true
Source directory. Could be a relative or absolute path.
dest
Type: string
Required: true
Destination directory.
options
glob
Type: Array
string
Default: ['**', '!**/node_modules/**']
Use the glob pattern(s) to find files in src
directory.
template
Type: object
Default: require('jstransformer-ejs')
You can use a custom template engine, like [handlebars]:
copy(src, dest, {
template: require('jstransformer-handlebars')
})
templateOptions
Type: object
function
The template engine options.
If it's a function we use the return value as templateOptions
, and the first argument is { answers, data, merged }
.
clean
Type: boolean
Default: false
Whether to clean destination directory before writing to it.
cwd
Type: string
Default: process.cwd()
Current working directory.
data
Type: object
function
Default: undefined
The data to be used in rendering templates in source directory, filter files etc.
If it's a function, we use its return value as data
, and the first arguments is answers
.
prompts
Type: Array<InquirerPrompt>
Default: undefined
inquirer prompts, the answers of prompts will be assigned to data
mockPrompts
Type: Object
An object of mocked prompt values, eg:
{
prompts: [
{ name: 'foo', message: 'type foo', validate: v => v === 'foo' },
{ name: 'hey', message: 'type hey' }
],
mockPrompts: {
foo: 'bar'
}
}
In the above case, we will not run prompts to get answers from users, instead we use set foo
's value to bar
and validate it. And in this case it will throw since 'bar' !== 'foo'
. The value of hey
would be undefined
.
skipInterpolation
Type: string | Array<string|function> | function
Default: undefined
(we skip all binary files by default)
Patterns(minimatch) used to skip interpolation, eg: ./foo*/bar-*.js
It could also be a function, whose first arg is file path and second arg is file content, eg. we want to exclude all .js
files:
copy(src, dest, {
skipInterpolation(file, stream) {
return /\.js$/.test(file)
}
})
disableInterpolation
Type: boolean
Default: false
Similar to skipInterpolation
, but disableInterpolation
disables all template interpolation, template markup will remain the way it is.
filters
Type: object
function
Default: undefined
An object containing file filter rules, the key of each entry is a minimatch pattern, and its value is a JavaScript expression evaluated in the context of (prompt answers) data:
copy(src, dest, {
filters: {
'**/*.js': 'useJavaScript',
'**/*.ts': '!useJavaScript'
}
})
If it's a function, the first argument of it would be the result of data
merging with prompt answers.
move
Type: object
function
Default: undefined
Similar to filters
, but instead of filtering files, it just renames the file:
copy(src, dest, {
move: {
'gitignore': '.gitignore',
'folder/file.js': 'another/file.ts'
}
})
If it's a function, the first argument of it would be the result of data
merging with prompt answers.
The value of each entry should be a file path or a function will returns a file path:
copy(src, dest, {
move: {
'foo.*': 'foo.js',
'bar-*.js': filepath => filepath.replace(/^bar-/, 'bar/')
}
})
transforms
Type: object
Default: undefined
Transform files in the way you like instead of rendering with specific template engine.
copy(src, dest, {
transforms: {
'**/*.js'(relativePath, stream) {
const contents = stream.fileContents(relativePath)
const { code } = babel.transform(contents)
stream.writeContents(relativePath, code)
}
}
})
skipExisting
Type: function
boolean
Default: undefined
Whether to skip existing file, it could be function that takes the path to existing file as argument.
copy(src, dest, {
skipExisting(file) {
console.log(`${file} exists, skipped!`)
}
})
write
Type: boolean
Default: true
Process files and write to disk.
stream
The majo instance.
stream.meta
stream.meta.answers
Prompts answers.
stream.meta.data
The data
you passed from options
.
stream.meta.merged
Merged answers
and data
.
kopy © EGOIST, Released under the MIT License.
Authored and maintained by EGOIST with help from contributors (list).
egoistian.com · GitHub @egoist · Twitter @_egoistlily