diff --git a/README.md b/README.md index e979272891..dc41983b05 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. + +```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 @@ -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}`) } @@ -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)}`) } 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) { 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 new file mode 100644 index 0000000000..fb9c88ed94 --- /dev/null +++ b/test/e2e/mock-metalsmith-custom/meta.js @@ -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) + } +} 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..2871dc1a64 --- /dev/null +++ b/test/e2e/mock-metalsmith-custom/template/readme.md @@ -0,0 +1 @@ +Metalsmith {{custom}} diff --git a/test/e2e/test.js b/test/e2e/test.js index 8bab537bcd..1b2b1c0399 100644 --- a/test/e2e/test.js +++ b/test/e2e/test.js @@ -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') @@ -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 => { @@ -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/)