From 8932af166860e809c86e98ed2c3142f0969fec3b Mon Sep 17 00:00:00 2001 From: Toilal Date: Sat, 1 Apr 2017 17:43:09 +0200 Subject: [PATCH 1/5] Add support for metalsmith builder hooks Close #427 --- README.md | 32 ++++++++++++++++++++++++++++++++ lib/generate.js | 19 ++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e979272891..07997406f1 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. + +```javascript +{ + "metalsmith": function(metalsmith, opts, helpers) { + function customMetalsmithPlugin(files, metalsmith, done) { + // Implement something really custom here. + done(null, files); + } + + metalsmith.use(customMetalsmithPlugin); + } +} +``` + +If you need your to hook metalsmith before questions are asked, you may use an object with `before`/`after` keys + +```javascript +{ + "metalsmith": { + before: function(metalsmith, opts, helpers) {}, + after: function(metalsmith, opts, helpers) {} + } +} +``` + #### Additional data available in meta.{js,json} - `destDirName` - destination directory name diff --git a/lib/generate.js b/lib/generate.js index a98a40fc2c..6404b3940c 100644 --- a/lib/generate.js +++ b/lib/generate.js @@ -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) { From f39241ace0a1b098056d1cc932c3036fd0e34288 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Sat, 1 Apr 2017 19:41:03 +0200 Subject: [PATCH 2/5] Fix style --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 07997406f1..bf4d2136cb 100644 --- a/README.md +++ b/README.md @@ -187,26 +187,26 @@ The `skipInterpolation` field in the metadata file should be a [minimatch glob p You may customize the metalsmith builder created by vue-cli to register custom plugins. -```javascript +```js { - "metalsmith": function(metalsmith, opts, helpers) { - function customMetalsmithPlugin(files, metalsmith, done) { + "metalsmith": function (metalsmith, opts, helpers) { + function customMetalsmithPlugin (files, metalsmith, done) { // Implement something really custom here. - done(null, files); + done(null, files) } - metalsmith.use(customMetalsmithPlugin); + metalsmith.use(customMetalsmithPlugin) } } ``` If you need your to hook metalsmith before questions are asked, you may use an object with `before`/`after` keys -```javascript +```js { "metalsmith": { - before: function(metalsmith, opts, helpers) {}, - after: function(metalsmith, opts, helpers) {} + before: function (metalsmith, opts, helpers) {}, + after: function (metalsmith, opts, helpers) {} } } ``` @@ -236,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}`) } @@ -250,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)}`) } From e5a1c78aa687c8fb4e34a6d9d8bea07d270a423e Mon Sep 17 00:00:00 2001 From: Toilal Date: Tue, 4 Apr 2017 23:05:24 +0200 Subject: [PATCH 3/5] Fix documentation sentence --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf4d2136cb..dc41983b05 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ You may customize the metalsmith builder created by vue-cli to register custom p } ``` -If you need your to hook metalsmith before questions are asked, you may use an object with `before`/`after` keys +If you need to hook metalsmith before questions are asked, you may use an object with `before` key. ```js { From 0125ac798cd1a77b54da73de7141d3469f860ae7 Mon Sep 17 00:00:00 2001 From: Toilal Date: Thu, 6 Apr 2017 23:12:33 +0200 Subject: [PATCH 4/5] Add e2e test for metalsmith custom plugin --- test/e2e/mock-metalsmith-custom/meta.js | 15 +++++++++++++++ .../e2e/mock-metalsmith-custom/template/readme.md | 1 + test/e2e/test.js | 13 ++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/e2e/mock-metalsmith-custom/meta.js create mode 100644 test/e2e/mock-metalsmith-custom/template/readme.md diff --git a/test/e2e/mock-metalsmith-custom/meta.js b/test/e2e/mock-metalsmith-custom/meta.js new file mode 100644 index 0000000000..f18e7224df --- /dev/null +++ b/test/e2e/mock-metalsmith-custom/meta.js @@ -0,0 +1,15 @@ +module.exports = { + "metalsmith": function (metalsmith, opts, helpers) { + 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) + } +} diff --git a/test/e2e/mock-metalsmith-custom/template/readme.md b/test/e2e/mock-metalsmith-custom/template/readme.md new file mode 100644 index 0000000000..1626bd952c --- /dev/null +++ b/test/e2e/mock-metalsmith-custom/template/readme.md @@ -0,0 +1 @@ +Metalsmith diff --git a/test/e2e/test.js b/test/e2e/test.js index 8bab537bcd..ce2ce805c0 100644 --- a/test/e2e/test.js +++ b/test/e2e/test.js @@ -13,6 +13,7 @@ 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.resolve('./test/e2e/mock-metalsmith-custom') 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') @@ -120,6 +121,16 @@ describe('vue-cli', () => { }) }) + it('supports custom metalsmith plugins', done => { + generate('test', MOCK_METALSMITH_CUSTOM, MOCK_TEMPLATE_BUILD_PATH, err => { + if (err) done(err) + + expect(exists(`${MOCK_TEMPLATE_BUILD_PATH}/custom/readme.md`)).to.equal(true) + + done() + }) + }) + it('generate a vaild package.json with escaped author', done => { monkeyPatchInquirer(escapedAnswers) generate('test', MOCK_TEMPLATE_REPO_PATH, MOCK_TEMPLATE_BUILD_PATH, err => { @@ -254,7 +265,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/) From 7605ff26439be76a3ceffd141b65106da1942523 Mon Sep 17 00:00:00 2001 From: Toilal Date: Sat, 8 Apr 2017 14:07:46 +0200 Subject: [PATCH 5/5] Enhance e2e tests for metalsmith custom plugin --- .../meta.js | 21 +++++++++++ .../template/readme.md | 1 + test/e2e/mock-metalsmith-custom/meta.js | 1 + .../mock-metalsmith-custom/template/readme.md | 2 +- test/e2e/test.js | 37 +++++++++++++++++-- 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 test/e2e/mock-metalsmith-custom-before-after/meta.js create mode 100644 test/e2e/mock-metalsmith-custom-before-after/template/readme.md diff --git a/test/e2e/mock-metalsmith-custom-before-after/meta.js b/test/e2e/mock-metalsmith-custom-before-after/meta.js new file mode 100644 index 0000000000..30faf32a76 --- /dev/null +++ b/test/e2e/mock-metalsmith-custom-before-after/meta.js @@ -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) + } + } +} diff --git a/test/e2e/mock-metalsmith-custom-before-after/template/readme.md b/test/e2e/mock-metalsmith-custom-before-after/template/readme.md new file mode 100644 index 0000000000..ceac78bd5c --- /dev/null +++ b/test/e2e/mock-metalsmith-custom-before-after/template/readme.md @@ -0,0 +1 @@ +Metalsmith {{after}} and {{before}} hooks diff --git a/test/e2e/mock-metalsmith-custom/meta.js b/test/e2e/mock-metalsmith-custom/meta.js index f18e7224df..fb9c88ed94 100644 --- a/test/e2e/mock-metalsmith-custom/meta.js +++ b/test/e2e/mock-metalsmith-custom/meta.js @@ -1,5 +1,6 @@ module.exports = { "metalsmith": function (metalsmith, opts, helpers) { + metalsmith.metadata().custom = "Custom"; function customMetalsmithPlugin (files, metalsmith, done) { // Implement something really custom here. diff --git a/test/e2e/mock-metalsmith-custom/template/readme.md b/test/e2e/mock-metalsmith-custom/template/readme.md index 1626bd952c..2871dc1a64 100644 --- a/test/e2e/mock-metalsmith-custom/template/readme.md +++ b/test/e2e/mock-metalsmith-custom/template/readme.md @@ -1 +1 @@ -Metalsmith +Metalsmith {{custom}} diff --git a/test/e2e/test.js b/test/e2e/test.js index ce2ce805c0..1b2b1c0399 100644 --- a/test/e2e/test.js +++ b/test/e2e/test.js @@ -13,7 +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.resolve('./test/e2e/mock-metalsmith-custom') +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') @@ -122,12 +123,42 @@ describe('vue-cli', () => { }) it('supports custom metalsmith plugins', done => { - generate('test', MOCK_METALSMITH_CUSTOM, MOCK_TEMPLATE_BUILD_PATH, err => { + 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) - done() + 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) }) })