Skip to content

Add support for metalsmith builder hooks #428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides this one you can also test before and after, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just push a new commit with an additional test, and better assertions

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure. What you're doing here in custom metalsmith plugin is you're moving file from root template directory into custom directory, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's it.


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 => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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