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
const Boilerplate = require('common-boilerplate');
class MainBoilerplate extends Boilerplate {
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,
},
{
type: 'list',
name: 'type',
message: 'choose your type:',
choices: [ 'simple', 'plugin', 'framework' ],
},
]);
this.setLocals(answers);
await this.askGit();
}
};
Built-in Questions:
askNpm()
: ask for name
/ scope
/ description
, and pkgName
getter.askGit()
: ask for repository
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 to git 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.
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 read files
and ignore your files. - use
_
as prefix, such as _package.json
/ _.gitignore
/ _.eslintrc
- add your mapping by
this.fileMapping
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 {
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 to process.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 {
get [Symbol.for('boilerplate#root')]() {
return __dirname;
}
};
module.exports = ShareBoilerplate;
class MainBoilerplate extends ShareBoilerplate {
get [Symbol.for('boilerplate#root')]() {
return __dirname;
}
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, and boilerplate
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 () => {
await coffee.fork(path.join(fixtures, 'simple/bin/cli.js'), [], { cwd: tmpDir })
.waitForPrompt()
.writeKey('example\n')
.writeKey('ENTER')
.writeKey('DOWN', 'ENTER')
.expect('stdout', /npm install --no-package-lock/)
.expect('stdout', /1 passing/)
.expect('code', 0)
.end();
assertFile(`${tmpDir}/.gitignore`);
assertFile(`${tmpDir}/README.md`, 'name = example');
assertFile(`${tmpDir}/README.md`, /name = example/);
assertFile(`${tmpDir}/package.json`, {
name: 'example',
boilerplate: {
name: 'common-boilerplate-test-project',
version: '1.0.0',
}
});
});
});