Skip to content

Commit

Permalink
Add support for metalsmith builder hooks (vuejs#428)
Browse files Browse the repository at this point in the history
* Add support for metalsmith builder hooks

Close vuejs#427

* Fix style

* Fix documentation sentence

* Add e2e test for metalsmith custom plugin

* Enhance e2e tests for metalsmith custom plugin
  • Loading branch information
Toilal authored and zigomir committed Apr 9, 2017
1 parent 182feab commit 9b8100c
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 6 deletions.
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ vue init ~/fs/path/to-custom-template my-project
- `prompts`: used to collect user options data;

- `filters`: used to conditional filter files to render.

- `metalsmith`: used to add custom metalsmith plugins in the chain.

- `completeMessage`: the message to be displayed to the user when the template has been generated. You can include custom instruction here.

Expand Down Expand Up @@ -179,6 +181,36 @@ The `skipInterpolation` field in the metadata file should be a [minimatch glob p
}
```

#### Metalsmith

`vue-cli` uses [metalsmith](https://github.com/segmentio/metalsmith) to generate the project.

You may customize the metalsmith builder created by vue-cli to register custom plugins.

```js
{
"metalsmith": function (metalsmith, opts, helpers) {
function customMetalsmithPlugin (files, metalsmith, done) {
// Implement something really custom here.
done(null, files)
}

metalsmith.use(customMetalsmithPlugin)
}
}
```

If you need to hook metalsmith before questions are asked, you may use an object with `before` key.

```js
{
"metalsmith": {
before: function (metalsmith, opts, helpers) {},
after: function (metalsmith, opts, helpers) {}
}
}
```

#### Additional data available in meta.{js,json}

- `destDirName` - destination directory name
Expand All @@ -204,7 +236,7 @@ Arguments:
- `data`: the same data you can access in `completeMessage`:
```js
{
complete(data) {
complete (data) {
if (!data.inPlace) {
console.log(`cd ${data.destDirName}`)
}
Expand All @@ -218,7 +250,7 @@ Arguments:
- `files`: An array of generated files
```js
{
complete(data, {logger, chalk}) {
complete (data, {logger, chalk}) {
if (!data.inPlace) {
logger.log(`cd ${chalk.yellow(data.destDirName)}`)
}
Expand Down
19 changes: 16 additions & 3 deletions lib/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,24 @@ module.exports = function generate (name, src, dest, done) {
opts.helpers && Object.keys(opts.helpers).map(function (key) {
Handlebars.registerHelper(key, opts.helpers[key])
})
metalsmith
.use(askQuestions(opts.prompts))

var helpers = {chalk, logger}

if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}

metalsmith.use(askQuestions(opts.prompts))
.use(filterFiles(opts.filters))
.use(renderTemplateFiles(opts.skipInterpolation))
.clean(false)

if (typeof opts.metalsmith === 'function') {
opts.metalsmith(metalsmith, opts, helpers)
} else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
opts.metalsmith.after(metalsmith, opts, helpers)
}

metalsmith.clean(false)
.source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
.destination(dest)
.build(function (err, files) {
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/mock-metalsmith-custom-before-after/meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
"metalsmith": {
before: function (metalsmith, opts, helpers) {
metalsmith.metadata().before = "Before";
},
after: function (metalsmith, opts, helpers) {
metalsmith.metadata().after = "After";
function customMetalsmithPlugin (files, metalsmith, done) {
// Implement something really custom here.

var readme = files['readme.md']
delete files['readme.md']
files['custom-before-after/readme.md'] = readme

done(null, files)
}

metalsmith.use(customMetalsmithPlugin)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Metalsmith {{after}} and {{before}} hooks
16 changes: 16 additions & 0 deletions test/e2e/mock-metalsmith-custom/meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
"metalsmith": function (metalsmith, opts, helpers) {
metalsmith.metadata().custom = "Custom";
function customMetalsmithPlugin (files, metalsmith, done) {
// Implement something really custom here.

var readme = files['readme.md']
delete files['readme.md']
files['custom/readme.md'] = readme

done(null, files)
}

metalsmith.use(customMetalsmithPlugin)
}
}
1 change: 1 addition & 0 deletions test/e2e/mock-metalsmith-custom/template/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Metalsmith {{custom}}
44 changes: 43 additions & 1 deletion test/e2e/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const metadata = require('../../lib/options')
const { isLocalPath, getTemplatePath } = require('../../lib/local-path')

const MOCK_META_JSON_PATH = path.resolve('./test/e2e/mock-meta-json')
const MOCK_METALSMITH_CUSTOM_PATH = path.resolve('./test/e2e/mock-metalsmith-custom')
const MOCK_METALSMITH_CUSTOM_BEFORE_AFTER_PATH = path.resolve('./test/e2e/mock-metalsmith-custom-before-after')
const MOCK_TEMPLATE_REPO_PATH = path.resolve('./test/e2e/mock-template-repo')
const MOCK_TEMPLATE_BUILD_PATH = path.resolve('./test/e2e/mock-template-build')
const MOCK_METADATA_REPO_JS_PATH = path.resolve('./test/e2e/mock-metadata-repo-js')
Expand Down Expand Up @@ -120,6 +122,46 @@ describe('vue-cli', () => {
})
})

it('supports custom metalsmith plugins', done => {
generate('test', MOCK_METALSMITH_CUSTOM_PATH, MOCK_TEMPLATE_BUILD_PATH, err => {
if (err) done(err)

expect(exists(`${MOCK_TEMPLATE_BUILD_PATH}/custom/readme.md`)).to.equal(true)

async.eachSeries([
'readme.md'
], function (file, next) {
const template = fs.readFileSync(`${MOCK_METALSMITH_CUSTOM_PATH}/template/${file}`, 'utf8')
const generated = fs.readFileSync(`${MOCK_TEMPLATE_BUILD_PATH}/custom/${file}`, 'utf8')
render(template, {custom: 'Custom'}, (err, res) => {
if (err) return next(err)
expect(res).to.equal(generated)
next()
})
}, done)
})
})

it('supports custom metalsmith plugins with after/before object keys', done => {
generate('test', MOCK_METALSMITH_CUSTOM_BEFORE_AFTER_PATH, MOCK_TEMPLATE_BUILD_PATH, err => {
if (err) done(err)

expect(exists(`${MOCK_TEMPLATE_BUILD_PATH}/custom-before-after/readme.md`)).to.equal(true)

async.eachSeries([
'readme.md'
], function (file, next) {
const template = fs.readFileSync(`${MOCK_METALSMITH_CUSTOM_BEFORE_AFTER_PATH}/template/${file}`, 'utf8')
const generated = fs.readFileSync(`${MOCK_TEMPLATE_BUILD_PATH}/custom-before-after/${file}`, 'utf8')
render(template, {before: 'Before', after: 'After'}, (err, res) => {
if (err) return next(err)
expect(res).to.equal(generated)
next()
})
}, done)
})
})

it('generate a vaild package.json with escaped author', done => {
monkeyPatchInquirer(escapedAnswers)
generate('test', MOCK_TEMPLATE_REPO_PATH, MOCK_TEMPLATE_BUILD_PATH, err => {
Expand Down Expand Up @@ -254,7 +296,7 @@ describe('vue-cli', () => {
expect(getTemplatePath('../template')).to.equal(path.join(__dirname, '/../../../template'))
})

it.only('points out the file in the error', done => {
it('points out the file in the error', done => {
monkeyPatchInquirer(answers)
generate('test', MOCK_ERROR, MOCK_TEMPLATE_BUILD_PATH, err => {
expect(err.message).to.match(/^\[readme\.md\] Parse error/)
Expand Down

0 comments on commit 9b8100c

Please sign in to comment.