common-boilerplate
base class for boilerplate
Write your boilerplate
use create-common-boilerplate for quick start.
$ npm init common-boilerplate
Lifecycle
- ask question
- list all file from boilerplate paths
- render files to target dir
- do post jobs
Directory
├── bin
│ └── cli.js
│
├── boilerplate
│ ├── lib
│ ├── test
│ ├── README.md
│ ├── _.eslintrc
│ ├── _.gitignore
│ ├── _package.json
│ └── index.js
│
├── test
│ └── index.test.js
├── index.js
├── README.md
└── package.json
-
index.js
is your Boilerplate Logic, the main entry. -
boilerplate/**
is your template dir, will be copy to dest.
Boilerplate Entry
// index.js
const Boilerplate = require('common-boilerplate');
class MainBoilerplate extends Boilerplate {
// must provide your directory
get [Symbol.for('boilerplate#root')]() {
return __dirname;
}
};
module.exports = MainBoilerplate;
Ask questions
Inquirer is built-in to provide prompt
helper.
Add your questions:
class MainBoilerplate extends Boilerplate {
async askQuestions() {
const answers = await this.prompt([
{
name: 'name',
type: 'input',
message: 'Project Name: ',
default: () => this.locals.name, // set default from locals
},
{
type: 'list',
name: 'type',
message: 'choose your type:',
choices: [ 'simple', 'plugin', 'framework' ],
},
]);
this.setLocals(answers);
// use built-in questions
await this.askGit();
}
};
Built-in Questions:
-
askNpm()
: ask forname
/scope
/description
, andpkgName
getter. -
askGit()
: ask forrepository
Locals
this.locals
is used to fill the template, it's merge from built-in -> argv -> user's prompt answer
;
Built-in:
-
name
- project name, by default togit repository name
-
user
- user info-
name
-git config user.name
-
email
-git config user.email
-
author
-${user} <${email}>
-
-
gitInfo
- git url info- extract from
git config remote.origin.url
- see git-url-parse for more details.
- extract from
-
npm
- npm global cli name, will guest by order:tnpm -> cnpm -> npm
-
registry
- npm registry url, not set by default
Template Render
Built-in render is nunjucks.
And use micromatch to match this.templateRules
to treat as template.
this.templateRules = [ '!res/**' ];
File Name Convert
- also use template render, so
{{name}}.test.js
is supported. - some file is special, so you can't use it's origin name
- such as
boilerplate/package.json
, npm will readfiles
and ignore your files. - use
_
as prefix, such as_package.json
/_.gitignore
/_.eslintrc
- add your mapping by
this.fileMapping
- such as
Default mappings:
this.fileMapping = {
gitignore: '.gitignore',
_gitignore: '.gitignore',
'_.gitignore': '.gitignore',
'_package.json': 'package.json',
'_.eslintrc': '.eslintrc',
'_.eslintignore': '.eslintignore',
'_.npmignore': '.npmignore',
};
Logger
Provide powerful cli logger for developer, see consola for more details.
debug
is disabled by default, use --verbose
or DEBUG=
to print all logs.
this.logger.info('this is info log');
this.logger.level = 'DEBUG';
HttpClient
Provide httpclient for developer, see urllib for more details.
await this.request(url, opts);
Use this.requestOpts
as default request options.
RunScript
Provide runscript for developer, see runscript for more details.
cwd
is set to target dir, and will use this.local.npm
as cli.
await this.runScript('ci', { grep: 'home.test.js' }, {});
await this.installDeps({ optional : false });
await this.runTest({});
CommandLine argv
Also support custom argv:
-
argv
will convert to camelCase, such as--page-size=1 -> pageSize
- dot prop will convert to nested object, such as
--page.size=1 -> { page: { size: '1' } }
- see yargs#optionskey for more details
class MainBoilerplate extends Boilerplate {
// use as `--test=123 --str=456`
initOptions() {
const options = Object.assign({}, super.initOptions());
options.test = {
type: 'string',
description: 'just a test',
};
options.str = {
type: 'string',
description: 'just a str',
};
return options;
}
};
Built-in:
-
--baseDir=
- directory of application, default toprocess.cwd()
-
--npm=
- npm cli, tnpm/cnpm/npm, will auto guess -
--registry=
- npm registry url, also support alias-r=china
, will auto guest from npm cli. -
--force
- force to override directory if it's not empty
Boilerplate Chain
Support mutli-level boilerplate, so you can share logic between boilerplates.
class ShareBoilerplate extends Boilerplate {
// must provide your directory
get [Symbol.for('boilerplate#root')]() {
return __dirname;
}
};
module.exports = ShareBoilerplate;
// child
class MainBoilerplate extends ShareBoilerplate {
// must provide your directory
get [Symbol.for('boilerplate#root')]() {
return __dirname;
}
// example for ignore some files from parent
async listFiles(...args) {
const files = await super.listFiles(...args);
files['github.png'] = undefined;
return files;
}
};
module.exports = MainBoilerplate;
- must provide getter
Symbol.for('boilerplate#root')
to announce your root, andboilerplate
directory is required to exists at your root directory. - will auto load all files from boilerplate, same key will be override.
- you could custom by
async listFiles()
, such as ignore some files from parent.
Unit Testing
Use Coffee and assert-file.
const coffee = require('coffee');
const assertFile = require('assert-file');
const { rimraf, mkdirp } = require('mz-modules');
describe('test/index.test.js', () => {
const fixtures = path.join(__dirname, 'fixtures');
const tmpDir = path.join(__dirname, '.tmp');
beforeEach(async () => {
await rimraf(tmpDir);
await mkdirp(tmpDir);
});
it('should work', async () => {
// run cli
await coffee.fork(path.join(fixtures, 'simple/bin/cli.js'), [], { cwd: tmpDir })
// .debug()
// tell coffee to listen prompt event then auto answer
.waitForPrompt()
// answer to the questions
.writeKey('example\n')
.writeKey('ENTER')
// emit `DOWN` key to select the second choise
.writeKey('DOWN', 'ENTER')
.expect('stdout', /npm install --no-package-lock/)
.expect('stdout', /1 passing/)
.expect('code', 0)
.end();
// expect to be exists
assertFile(`${tmpDir}/.gitignore`);
// check with `includes`
assertFile(`${tmpDir}/README.md`, 'name = example');
// check with regex
assertFile(`${tmpDir}/README.md`, /name = example/);
// check whether contains json
assertFile(`${tmpDir}/package.json`, {
name: 'example',
boilerplate: {
name: 'common-boilerplate-test-project',
version: '1.0.0',
}
});
});
});