├── .husky ├── .gitignore ├── pre-commit └── commit-msg ├── .npmrc ├── .eslintignore ├── .czrc ├── src ├── interface │ ├── path.ts │ ├── syncable.ts │ ├── file.ts │ ├── answers.ts │ ├── resource.ts │ ├── milirc.ts │ ├── loader.ts │ ├── handler.ts │ └── repository.ts ├── render │ ├── interface │ │ ├── loader.ts │ │ ├── hook.ts │ │ ├── template.ts │ │ ├── question.ts │ │ └── config.ts │ ├── inquire.ts │ ├── schema │ │ ├── loader.json │ │ ├── handler.json │ │ ├── hooks.json │ │ ├── templates.json │ │ ├── config.json │ │ └── questions.json │ ├── download │ │ ├── install-deps.ts │ │ └── index.ts │ ├── exec-loader.ts │ ├── exec-handler.ts │ ├── compile.ts │ ├── index.ts │ └── load-config.ts ├── index.ts ├── util │ ├── exec.ts │ ├── gen-eval-content.ts │ ├── pretty-error.ts │ ├── logger.ts │ ├── create-tmp-dir.ts │ ├── readdeepdir.ts │ ├── sync-dir.ts │ ├── copy.ts │ ├── check.ts │ └── parse-template.ts ├── handler │ ├── init.ts │ ├── exist.ts │ ├── overwrite.ts │ ├── rename.ts │ ├── merge-json.ts │ ├── ignore.ts │ ├── merge-yaml.ts │ ├── handlebars.ts │ └── delete.ts ├── const.ts ├── schema │ ├── hander.json │ ├── milirc.json │ ├── index.ts │ ├── question.json │ ├── template.json │ └── rule.json ├── clean.ts ├── diff │ ├── infer-encoding.ts │ ├── index.ts │ └── show-diff.ts ├── loader │ ├── markdown-section │ │ ├── schema.json │ │ └── index.ts │ ├── npm.ts │ └── git.ts ├── migrate.ts ├── init.ts ├── update.ts ├── upgrade.ts ├── check.ts └── cli.ts ├── .lintstagedrc.yml ├── .commitlintrc.yml ├── tests ├── index.ts ├── tsconfig.json └── util │ ├── gen-eval-content.ts │ └── parse-template.ts ├── docs ├── images │ ├── check.png │ └── logo.svg ├── en │ ├── handler │ │ ├── rename.md │ │ ├── exist.md │ │ ├── ignore-when.md │ │ ├── merge.md │ │ ├── mustache.md │ │ ├── extract-area.md │ │ └── index.md │ ├── node-interface.md │ ├── template.md │ └── cli.md └── zh-cn │ ├── handler │ ├── exist.md │ ├── init.md │ ├── merge-yaml.md │ ├── handlebars.md │ ├── merge-json.md │ ├── index.md │ ├── ignore.md │ └── delete.md │ ├── loader │ ├── npm.md │ ├── git.md │ ├── index.md │ └── markdown-section.md │ ├── cli.md │ ├── readme.md │ └── template.md ├── .travis.yml ├── .milirc.yml ├── .vscode └── settings.json ├── .editorconfig ├── ava.config.js ├── .c8rc.json ├── .npmignore ├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── bug_report.md │ └── docs_improve.md ├── workflows │ ├── release.yml │ └── codeql-analysis.yml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── backers.md ├── tsconfig.json ├── LICENSE ├── .gitignore ├── README.md ├── package.json ├── .eslintrc.yml └── CHANGELOG.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /es 3 | /dist 4 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "cz-conventional-changelog" 3 | } 4 | -------------------------------------------------------------------------------- /src/interface/path.ts: -------------------------------------------------------------------------------- 1 | export type Path = Readonly 2 | -------------------------------------------------------------------------------- /.lintstagedrc.yml: -------------------------------------------------------------------------------- 1 | '**/*.{ts,js,tsx,jsx}': 2 | - 'eslint --fix' 3 | -------------------------------------------------------------------------------- /.commitlintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - '@commitlint/config-conventional' 3 | -------------------------------------------------------------------------------- /src/interface/syncable.ts: -------------------------------------------------------------------------------- 1 | export type Syncable = Promise | T 2 | -------------------------------------------------------------------------------- /tests/index.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | test.todo('write your test here...') 4 | -------------------------------------------------------------------------------- /docs/images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mili-project-manager/mili/HEAD/docs/images/check.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | after_success: 5 | - npm run coveralls 6 | dist: focal 7 | -------------------------------------------------------------------------------- /src/render/interface/loader.ts: -------------------------------------------------------------------------------- 1 | export interface Loader { 2 | name: string 3 | options: Record 4 | } 5 | -------------------------------------------------------------------------------- /src/interface/file.ts: -------------------------------------------------------------------------------- 1 | export interface File { 2 | path: string 3 | encoding: 'utf8' | 'binary' | 'hex' | 'ascii' 4 | } 5 | -------------------------------------------------------------------------------- /src/interface/answers.ts: -------------------------------------------------------------------------------- 1 | export interface Answers { 2 | [name: string]: string | number | boolean | (string | number)[] 3 | } 4 | -------------------------------------------------------------------------------- /.milirc.yml: -------------------------------------------------------------------------------- 1 | template: npm:@mtpl/component 2 | version: 4.5.5 3 | answers: 4 | travis: true 5 | lock: false 6 | module: commonjs 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './init' 2 | export * from './upgrade' 3 | export * from './update' 4 | export * from './clean' 5 | export * from './check' 6 | -------------------------------------------------------------------------------- /src/interface/resource.ts: -------------------------------------------------------------------------------- 1 | import { Answers } from './answers' 2 | 3 | 4 | export type Resource = { 5 | answers: Answers 6 | [key: string]: any 7 | } 8 | -------------------------------------------------------------------------------- /src/util/exec.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util' 2 | import * as childProcess from 'child_process' 3 | 4 | export const exec = promisify(childProcess.exec) 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 3 | "eslint.validate": [ 4 | "typescript", 5 | "typescriptreact" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/util/gen-eval-content.ts: -------------------------------------------------------------------------------- 1 | export const genEvalContent = (resource: string, content: string): string => ` 2 | const resource = ${resource}; 3 | module.exports = ${content}; 4 | ` 5 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["./**/*.ts"], 4 | "compilerOptions": { 5 | "sourceRoot": "../src", 6 | "sourceMap": true, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [{**,[!node_modules/**]}] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/interface/milirc.ts: -------------------------------------------------------------------------------- 1 | import { Answers } from '@/interface/answers' 2 | 3 | 4 | export interface Milirc { 5 | template: string 6 | version: string 7 | registry?: string 8 | 9 | answers?: Answers 10 | } 11 | -------------------------------------------------------------------------------- /docs/en/handler/rename.md: -------------------------------------------------------------------------------- 1 | # `rename` Header 2 | 3 | Rename the project file name to another 4 | 5 | ## Example 6 | 7 | ``` 8 | { 9 | handler: buildInHandler => buildInHandler.rename('new_file_name') 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/exist.md: -------------------------------------------------------------------------------- 1 | # `exist` Handler 2 | 3 | 4 | 如果文件不存在则创建文件,存在则不渲染 5 | 6 | 7 | ## 示例 8 | 9 | ``` 10 | [ 11 | { 12 | "path": "src/index.js" 13 | "handlers": ["exist"] 14 | } 15 | ] 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/init.md: -------------------------------------------------------------------------------- 1 | # `init` Handler 2 | 3 | 文件仅在`mili init `命令运行时渲染 4 | 5 | ## 示例 6 | 7 | ```json 8 | [ 9 | { 10 | "path": "src/**", 11 | "handlers": ["init"] 12 | } 13 | ] 14 | ``` 15 | -------------------------------------------------------------------------------- /ava.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: ['tests/**/*.ts', '!tests/**/*.before-each.ts'], 3 | typescript: { 4 | rewritePaths: { 5 | 'tests/': 'lib/tests/', 6 | }, 7 | compile: false, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /src/interface/loader.ts: -------------------------------------------------------------------------------- 1 | import { Syncable } from './syncable' 2 | 3 | interface LoaderOptions { 4 | [key: string]: any 5 | } 6 | 7 | export type Exec> = (cwd: string, options: T) => Syncable 8 | -------------------------------------------------------------------------------- /src/util/pretty-error.ts: -------------------------------------------------------------------------------- 1 | import * as PrettyError from 'pretty-error' 2 | 3 | export const pe = new PrettyError() 4 | export function prettyError(e: unknown): string { 5 | return pe.render(e instanceof Error ? e : new Error(String(e))) 6 | } 7 | -------------------------------------------------------------------------------- /src/render/interface/hook.ts: -------------------------------------------------------------------------------- 1 | export interface Hook { 2 | name: 'after-init' | 'after-upgrade' | 'after-update' | 'after-checked' | 'after-render' | 'before-init' | 'before-upgrade' | 'before-update' | 'before-checked' | 'before-render' 3 | exec: string 4 | } 5 | -------------------------------------------------------------------------------- /src/handler/init.ts: -------------------------------------------------------------------------------- 1 | import { Compile } from '@/interface/handler' 2 | 3 | 4 | export const compile: Compile = async function(dist, src, filepath, resource) { 5 | const mili = resource.get('mili') 6 | if (mili && mili.operation !== 'init') { 7 | return '/dev/null' 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/merge-yaml.md: -------------------------------------------------------------------------------- 1 | # `merge-yaml` Handler 2 | 3 | 将模板的文件和使用者项目目录下的同名文件进行合并, 如果文件存在字段冲突,则以模板文件的定义为准。 4 | 5 | 这个`handler`限制使用者不可以修改模板字段,但是允许其进行自定义额外的字段。 6 | 7 | 8 | ## 示例 9 | 10 | ```json 11 | [ 12 | { 13 | "path": ".eslintrc.yml", 14 | "handlers": ["merge-yaml"] 15 | }, 16 | ] 17 | ``` 18 | -------------------------------------------------------------------------------- /src/render/inquire.ts: -------------------------------------------------------------------------------- 1 | import { Answers } from '@/interface/answers' 2 | import { Question } from './interface/question' 3 | import * as inquirer from 'inquirer' 4 | 5 | 6 | export async function inquire(questions: Question[], answers: Answers = {}): Promise { 7 | return await inquirer.prompt(questions, answers) 8 | } 9 | -------------------------------------------------------------------------------- /.c8rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "check-coverage": false, 4 | "include": [ 5 | "lib/src", 6 | "src" 7 | ], 8 | "exclude": [ 9 | "node_modules", 10 | "**/*.d.ts" 11 | ], 12 | "sourceMap": true, 13 | "extension": [ 14 | ".ts", 15 | ".js", 16 | ".jsx", 17 | ".tsx" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/handlebars.md: -------------------------------------------------------------------------------- 1 | # `handlebars` Handler 2 | 3 | 4 | 使用[handlebars](https://handlebarsjs.com/)渲染模板文件, 5 | `Resource`会作为渲染使用的数据视图。 6 | 并且会自动删除文件的`.hbs`文件扩展名。 7 | 8 | 9 | ## 示例 10 | 11 | ```json 12 | [ 13 | { 14 | "path": ".milirc.yml.hbs", 15 | "handlers": ["handlebars"] 16 | }, 17 | ] 18 | ``` 19 | -------------------------------------------------------------------------------- /src/render/schema/loader.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "exec" 5 | ], 6 | "properties": { 7 | "engines": { 8 | "type": "array", 9 | "items": { 10 | "type": "string" 11 | } 12 | }, 13 | "exec": { 14 | "instanceof": "Function" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /demo 2 | /docs 3 | /templates 4 | /tests 5 | /src 6 | node_modules 7 | .github 8 | .nyc_output 9 | .vscode 10 | 11 | # Dev Config File 12 | .commitlintrc.yml 13 | .czrc 14 | .eslintrc.yml 15 | .gitignore 16 | .huskyrc.yml 17 | .lintstagedrc.yml 18 | .travis.yml 19 | ava.config.js 20 | rollup.config.js 21 | tsconfig.json 22 | -------------------------------------------------------------------------------- /src/render/schema/handler.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "compile" 5 | ], 6 | "properties": { 7 | "engines": { 8 | "type": "array", 9 | "items": { 10 | "type": "string" 11 | } 12 | }, 13 | "compile": { 14 | "instanceof": "Function" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as tempDir from 'temp-dir' 3 | 4 | 5 | export const TEMPLATE_CACHE_FILEPATH = path.join(tempDir, 'mili', 'template_cache') 6 | export const TEMPLATE_STORAGE_FILEPATH = path.join(tempDir, 'mili', 'template_storage') 7 | export const TEMPORARY_FILEPATH = path.join(tempDir, 'mili', 'temporary') 8 | -------------------------------------------------------------------------------- /src/schema/hander.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$id": "handler.json", 4 | "anyOf": [ 5 | { "type": "string" }, 6 | { "instanceof": "Function" }, 7 | { 8 | "type": "object", 9 | "properties": { 10 | "genFile": { "instanceof": "Function" }, 11 | "genPath": { "instanceof": "Function" } 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/render/interface/template.ts: -------------------------------------------------------------------------------- 1 | export interface Handler { 2 | name: string 3 | options: Record 4 | } 5 | 6 | export interface Template { 7 | path: string 8 | /** 9 | * @default 'utf8' 10 | */ 11 | encoding: 'utf8' | 'binary' | 'hex' | 'ascii' 12 | /** 13 | * @default 'cover' 14 | */ 15 | handlers: Handler[] 16 | } 17 | -------------------------------------------------------------------------------- /src/handler/exist.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import * as path from 'path' 3 | import { Compile } from '@/interface/handler' 4 | 5 | 6 | export const compile: Compile = async function(dist, src, filepath) { 7 | const distfilepath = path.join(dist, filepath) 8 | 9 | if (await fs.pathExists(distfilepath)) { 10 | return '/dev/null' 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/clean.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import { TEMPLATE_CACHE_FILEPATH, TEMPLATE_STORAGE_FILEPATH, TEMPORARY_FILEPATH } from './const' 3 | 4 | 5 | export async function clean(): Promise { 6 | await Promise.all([ 7 | fs.remove(TEMPLATE_STORAGE_FILEPATH), 8 | fs.remove(TEMPORARY_FILEPATH), 9 | fs.remove(TEMPLATE_CACHE_FILEPATH), 10 | ]) 11 | } 12 | -------------------------------------------------------------------------------- /docs/zh-cn/loader/npm.md: -------------------------------------------------------------------------------- 1 | # `npm` Loader 2 | 3 | 加载使用者项目的`npm`包信息 4 | 5 | ## Resource Example 6 | 7 | ```json5 8 | { 9 | "npm": { 10 | // package name read from package.json 11 | "name": "mili", 12 | // package version read from package.json 13 | "version": "0.0.1", 14 | "description": "package description read from package.json", 15 | } 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /src/render/download/install-deps.ts: -------------------------------------------------------------------------------- 1 | import { Path } from '@/interface/path' 2 | import { exec } from '@/util/exec' 3 | import * as fs from 'fs-extra' 4 | import * as path from 'path' 5 | 6 | 7 | export async function installDeps(cwd: Path): Promise { 8 | if (await fs.pathExists(path.join(cwd, 'package.json'))) { 9 | await exec('npm install', { cwd }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/interface/handler.ts: -------------------------------------------------------------------------------- 1 | import { Path } from './path' 2 | import { Syncable } from './syncable' 3 | 4 | 5 | export interface CompielOptions { 6 | encoding: 'utf8' | 'binary' | 'hex' | 'ascii' 7 | [key: string]: any 8 | } 9 | 10 | export type Compile = (dist: Path, src: Path, filepath: Path, resource: Map, options: T) => Syncable 11 | -------------------------------------------------------------------------------- /tests/util/gen-eval-content.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { genEvalContent } from '@/util/gen-eval-content' 3 | import * as evalstr from 'eval' 4 | 5 | 6 | test('NPM Template', t => { 7 | const str = genEvalContent(JSON.stringify({ 8 | answers: { 9 | test: true, 10 | }, 11 | }), 'resource.answers.test === true') 12 | 13 | t.true(evalstr(str)) 14 | }) 15 | 16 | -------------------------------------------------------------------------------- /src/util/logger.ts: -------------------------------------------------------------------------------- 1 | import * as chalk from 'chalk' 2 | 3 | 4 | export function info(message: string): void { 5 | console.info(chalk.green(`[MILI] ${message}`)) 6 | } 7 | 8 | export function warn(message: string): void { 9 | console.warn(chalk.yellow(`[MILI] ${message}`)) 10 | } 11 | 12 | export function error(message: string): void { 13 | console.error(chalk.red(`[MILI] ${message}`)) 14 | } 15 | -------------------------------------------------------------------------------- /src/schema/milirc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "milirc.json", 3 | "type": "object", 4 | "properties": { 5 | "templates": { 6 | "type": "array", 7 | "items": { 8 | "type": "string" 9 | } 10 | }, 11 | "answers": { 12 | "type": "object", 13 | "nullable": true, 14 | "properties": {} 15 | } 16 | }, 17 | "required": [ 18 | "template" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /docs/en/handler/exist.md: -------------------------------------------------------------------------------- 1 | # `exist` Handler 2 | 3 | If the project file is existed, template file won't rendered. Otherwise render the template file to project. 4 | 5 | It will be auto added at the last of handlers, when `rule.upgrade` is `exist`. 6 | 7 | ## Example 8 | 9 | ```javascript 10 | { 11 | upgrade: `exist` 12 | } 13 | ``` 14 | 15 | or 16 | 17 | ```javascript 18 | { 19 | handler: 'exist' 20 | } 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /docs/en/handler/ignore-when.md: -------------------------------------------------------------------------------- 1 | # `ignore-when` Handler 2 | 3 | No render the file when meet certain conditions. 4 | 5 | `ignoreWhen(resource => resource.operation !== 'init')` will be auto added at the last of handlers, when `rule.upgrade` is `exist`. 6 | 7 | ## Example 8 | 9 | ```javascript 10 | { 11 | handlers: [ 12 | buildInHandler => buildInHandler.ignoreWhen(resource => resource.operation !== 'init') 13 | ] 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /src/handler/overwrite.ts: -------------------------------------------------------------------------------- 1 | import { Compile } from '@/interface/handler' 2 | import * as fs from 'fs-extra' 3 | import * as path from 'path' 4 | 5 | 6 | export const compile: Compile = async function(dist, src, filepath) { 7 | const distfilepath = path.join(dist, filepath) 8 | const srcfilepath = path.join(src, filepath) 9 | 10 | await fs.ensureDir(path.dirname(distfilepath)) 11 | await fs.copyFile(srcfilepath, distfilepath) 12 | } 13 | -------------------------------------------------------------------------------- /src/diff/infer-encoding.ts: -------------------------------------------------------------------------------- 1 | import { File } from '@/interface/file' 2 | import { Path } from '@/interface/path' 3 | import * as path from 'path' 4 | 5 | 6 | const binaryFileExtensitions = ['.jpeg', '.jpg', '.png', '.ico', '.webp'] 7 | 8 | export function inferEncoding(filepath: Path): File['encoding'] { 9 | const ext = path.extname(filepath) 10 | 11 | if (binaryFileExtensitions.includes(ext)) return 'binary' 12 | 13 | return 'utf8' 14 | } 15 | -------------------------------------------------------------------------------- /src/schema/index.ts: -------------------------------------------------------------------------------- 1 | import * as Template from './template.json' 2 | export const TemplateSchema = Template 3 | 4 | import * as Rule from './rule.json' 5 | export const RuleSchema = Rule 6 | 7 | import * as Handler from './hander.json' 8 | export const HandlerSchema = Handler 9 | 10 | import * as Question from './question.json' 11 | export const QuestionSchema = Question 12 | 13 | import * as Milirc from './milirc.json' 14 | export const MilircSchema = Milirc 15 | -------------------------------------------------------------------------------- /src/util/create-tmp-dir.ts: -------------------------------------------------------------------------------- 1 | import { TEMPORARY_FILEPATH } from '@/const' 2 | import * as fs from 'fs-extra' 3 | import * as path from 'path' 4 | import { nanoid } from 'nanoid' 5 | 6 | 7 | export async function createTmpDir(): Promise { 8 | const tmpPath = path.join(TEMPORARY_FILEPATH , nanoid()) 9 | 10 | if (await fs.pathExists(tmpPath)) throw new Error('Folder is used by another process.') 11 | await fs.ensureDir(tmpPath) 12 | 13 | return tmpPath 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/loader/markdown-section/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "key", 5 | "filepath", 6 | "sections" 7 | ], 8 | "properties": { 9 | "key": { 10 | "type": "string" 11 | }, 12 | "filepath": { 13 | "type": "string" 14 | }, 15 | "encoding": { 16 | "type": "string" 17 | }, 18 | "sections": { 19 | "type": "array", 20 | "items": { 21 | "type": "string" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/render/interface/question.ts: -------------------------------------------------------------------------------- 1 | interface Choice { 2 | name: string 3 | short: string 4 | value: string | number 5 | } 6 | 7 | export interface Question { 8 | type: 'input'| 'number'| 'confirm'| 'list'| 'rawlist'| 'expand'| 'checkbox'| 'password'| 'editor' 9 | name: string 10 | message?: string 11 | default?: string | number | boolean | Array 12 | choices?: (Choice | string | number)[] 13 | loop?: boolean 14 | when?: any 15 | schema?: any 16 | } 17 | -------------------------------------------------------------------------------- /src/schema/question.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "question.json", 3 | "type": "object", 4 | "properties": { 5 | "type": { 6 | "type": "string", 7 | "enum": [ 8 | "input", 9 | "list", 10 | "checkbox", 11 | "confirm", 12 | "number", 13 | "rawlist", 14 | "expand", 15 | "password", 16 | "editor" 17 | ] 18 | }, 19 | "name": { 20 | "type": "string" 21 | } 22 | }, 23 | "required": [ 24 | "name" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /docs/en/handler/merge.md: -------------------------------------------------------------------------------- 1 | # `merge` Handler 2 | 3 | This handler merge the `file.content` and the content of project file to produce new `file.content`. 4 | It will be auto added at the last of handlers, when `rule.upgrade` is `merge`. 5 | 6 | The supported file formats are `.json`, `.yaml`, `.yml`, `.gitignore`, `.npmignore` and `.babelrc`. 7 | 8 | 9 | ## Example 10 | 11 | ```javascript 12 | { 13 | upgrade: `merge` 14 | } 15 | ``` 16 | 17 | or 18 | 19 | ```javascript 20 | { 21 | handler: 'merge' 22 | } 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/merge-json.md: -------------------------------------------------------------------------------- 1 | # `merge-json` Handler 2 | 3 | 将模板的文件和使用者项目目录下的同名文件进行合并, 如果文件存在字段冲突,则以模板文件的定义为准。 4 | 5 | 这个`handler`限制使用者不可以修改模板字段,但是允许其进行自定义额外的字段。 6 | 7 | ## options 8 | 9 | options | required | description 10 | :------------------|:---------|:------------------------------------------------ 11 | spaces | 否 | 要缩进的空格数或用于缩进的字符串。默认值:2 12 | 13 | ## 示例 14 | 15 | ```json 16 | [ 17 | { 18 | "path": ".eslintrc.json", 19 | "handlers": ["merge-json"] 20 | }, 21 | ] 22 | ``` 23 | -------------------------------------------------------------------------------- /src/render/schema/hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "propertyNames": { 5 | "type": "string", 6 | "enum": [ 7 | "after-init", 8 | "after-update", 9 | "after-upgrade", 10 | "after-check", 11 | "after-render", 12 | "before-init", 13 | "before-update", 14 | "before-upgrade", 15 | "before-check", 16 | "before-render" 17 | ] 18 | }, 19 | "additionalProperties": { 20 | "type": "string" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/zh-cn/loader/git.md: -------------------------------------------------------------------------------- 1 | # `git` Loader 2 | 3 | 加载使用者项目`git`信息 4 | 5 | ## 参数 6 | 7 | option | required | description 8 | :----------|:---------|:------------------------ 9 | `remote` | 否 | 读取项目的哪个`remote`作为仓库地址,如果不穿则会取`git remote`命令列出的第一个`remote`地址 10 | 11 | ## Resource Example 12 | 13 | ```json 14 | { 15 | "repository": { 16 | "isRepo": true, 17 | "isGitHubRepo": true, 18 | "url": "https://github.com/mili-project-manager/mili.git", 19 | "gitHubOwner": "mili-project-manager", 20 | "gitHubRepoName": "mili" 21 | } 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/en/handler/mustache.md: -------------------------------------------------------------------------------- 1 | # `mustache` Handler 2 | 3 | This handler use the `file.content` as the template, and create new view that combines `file.addition` and `resource`, render the new `file.content` with the [mustache](https://github.com/janl/mustache.js). 4 | 5 | The view is: 6 | 7 | ```javascript 8 | { 9 | mili: resource.mili, 10 | project: resource.project, 11 | template: resource.template, 12 | answers: resource.answers, 13 | addition: file.addition, 14 | } 15 | ``` 16 | 17 | 18 | ## Example 19 | 20 | ```javascript 21 | { 22 | handler: 'mustache', 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/en/handler/extract-area.md: -------------------------------------------------------------------------------- 1 | # `extract-area` Handler 2 | 3 | Extract the text between two `tags` from the content of project, 4 | and set text to `file.content`. 5 | 6 | ## Example 7 | 8 | 9 | ```javascript 10 | { 11 | handlers: [ 12 | buildInHandlers => buildInHandlers.extractArea('description', '') 13 | ] 14 | } 15 | ``` 16 | 17 | If the content of project is: 18 | 19 | ```markdown 20 | // content of project 21 | # Example 22 | 23 | The text area 24 | ``` 25 | 26 | You will get `'The text area'` from `file.addition.description` 27 | -------------------------------------------------------------------------------- /src/handler/rename.ts: -------------------------------------------------------------------------------- 1 | import { CompielOptions, Compile } from '@/interface/handler' 2 | import * as fs from 'fs-extra' 3 | import * as path from 'path' 4 | 5 | 6 | interface Options extends CompielOptions { 7 | filename: string 8 | } 9 | 10 | export const compile: Compile = async function(dist, src, filepath, resource, options) { 11 | const srcfilepath = path.join(src, filepath) 12 | const distfilepath = path.join(path.dirname(srcfilepath), options.filename) 13 | 14 | await fs.move(srcfilepath, distfilepath, { overwrite: true }) 15 | 16 | return path.relative(src, distfilepath) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/interface/repository.ts: -------------------------------------------------------------------------------- 1 | type RepositoryType = 'npm' | 'git' | 'fs' 2 | 3 | interface BaseRepository { 4 | type: Readonly 5 | name: Readonly 6 | version: Readonly 7 | storage: Readonly 8 | } 9 | 10 | export interface NpmRepository extends BaseRepository{ 11 | type: 'npm' 12 | registry?: Readonly 13 | } 14 | export interface GitRepository extends BaseRepository { 15 | type: 'git' 16 | } 17 | export interface FsRepository extends BaseRepository { 18 | type: 'fs' 19 | cwd: Readonly 20 | } 21 | 22 | export type Repository = NpmRepository | GitRepository | FsRepository 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: 4 | patreon: val_istar_guo 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/migrate.ts: -------------------------------------------------------------------------------- 1 | import { Repository } from './interface/repository' 2 | import * as semver from 'semver' 3 | import * as fs from 'fs-extra' 4 | import * as path from 'path' 5 | import { execFileSync } from 'child_process' 6 | 7 | 8 | export function migrate(cwd: string, repository: Repository, fromVersion: string): void { 9 | if (fromVersion === repository.version) return 10 | if (!semver.valid(fromVersion)) throw new Error(`Invalid version ${fromVersion}`) 11 | 12 | 13 | if (!fs.pathExistsSync(path.join(repository.storage, 'migration'))) return 14 | 15 | const files = fs.readdirSync(path.join(repository.storage, 'migration')) 16 | for (const file of files) { 17 | execFileSync(file, [], { cwd }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | **Close issues** 6 | List the issues solved.(e.g. 'close #xxx, close #xxx and close #xxx') 7 | 8 | 9 | **Please check if the PR fulfills these requirements** 10 | 11 | - [ ] Have you followed the guidelines in our [Contributing document](https://github.com/mili-project-manager/mili/blob/master/.github/CODE_OF_CONDUCT.md)? 12 | - [ ] Have you written new tests for your core changes, as applicable? 13 | - [ ] Have you written or modified docs for your core changes, as applicable? 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Feature: ' 5 | labels: enhancement 6 | 7 | --- 8 | 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tests/util/parse-template.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { parseTemplate } from '@/util/parse-template' 3 | 4 | test('NPM Template', t => { 5 | const repository = parseTemplate('npm:@mtpl/code-style', 'latest', {}) 6 | 7 | t.is(repository.name, '@mtpl/code-style') 8 | }) 9 | 10 | test('Git Http Template', t => { 11 | const repository = parseTemplate('https://github.com/mili-project-manager/mili.git', 'latest', {}) 12 | 13 | t.is(repository.name, 'https://github.com/mili-project-manager/mili.git') 14 | }) 15 | 16 | 17 | test('Git SSH Template', t => { 18 | const repository = parseTemplate('git@github.com:mili-project-manager/mili.git', 'latest', {}) 19 | 20 | t.is(repository.name, 'git@github.com:mili-project-manager/mili.git') 21 | }) 22 | -------------------------------------------------------------------------------- /docs/zh-cn/loader/index.md: -------------------------------------------------------------------------------- 1 | # Loader 2 | 3 | 4 | 执行`echo "export function exec() {}" >> your_loader.js`即可快速创建一个Loader。 5 | 虽然他什么功能也没有。 6 | 7 | 只需要在文件中`export`一个命名为`compile`的函数,就可以作为`mili`的Handler。 8 | `compile`函数的具体出入参数如下: 9 | 10 | ```typescript 11 | interface LoaderOptions { 12 | [key: string]: any 13 | } 14 | 15 | export type Exec> = (cwd: string, options: T) => Syncable 16 | ``` 17 | 18 | ## 函数的参数说明 19 | 20 | key | description 21 | :-------------------|:-------------- 22 | cwd | 存放项目的文件目录 23 | options | 用户配置的参数 24 | 25 | ## 返回值 26 | 27 | 返回`resource`对象 28 | 29 | 30 | ## 内置`hadnler` 31 | 32 | - [`npm`](./npm.md) 33 | - [`git`](./git.md) 34 | - [`markdown-section`](./markdown-section.md) 35 | -------------------------------------------------------------------------------- /src/render/exec-loader.ts: -------------------------------------------------------------------------------- 1 | import AJV from 'ajv8' 2 | import ajvKeywords from 'ajv-keywords' 3 | import { Loader } from '@/render/interface/loader' 4 | import * as loaderSchema from './schema/loader.json' 5 | 6 | 7 | const ajv = new AJV() 8 | ajvKeywords(ajv) 9 | const validate = ajv.compile(loaderSchema) 10 | export async function execLoader(cwd: string, loader: Loader): Promise> { 11 | const pkg = await import(loader.name) 12 | 13 | const valid = validate(pkg) 14 | if (!valid) throw new Error(ajv.errorsText(validate.errors, { dataVar: 'loader' })) 15 | 16 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 17 | // @ts-ignore 18 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 19 | return pkg.exec(cwd, loader.options) 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'Bug: ' 5 | labels: bug 6 | 7 | --- 8 | 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Platform (please complete the following information):** 24 | 25 | - Environment: NodeJS 26 | + Version: 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /backers.md: -------------------------------------------------------------------------------- 1 |

Sponsors & Backers

2 | 3 | mili is an MIT-licensed open source project. 4 | It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/Val-istar-Guo/mili/blob/master/backer.md). 5 | If you'd like to join them, please consider [become a backer or sponsor on Patreon](https://www.patreon.com/val_istar_guo). 6 | 7 |

Gold Sponsors

8 | 9 | 10 | 11 |

Bronze Sponsors

12 | 13 | 14 | 15 | 16 |

Generous Backers

17 | 18 | 19 | 20 | 21 |

Backers

22 | 23 | 24 | -------------------------------------------------------------------------------- /src/handler/merge-json.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import { CompielOptions, Compile } from '@/interface/handler' 3 | import * as R from 'ramda' 4 | import * as path from 'path' 5 | 6 | 7 | interface Options extends CompielOptions{ 8 | spaces: number | string 9 | } 10 | 11 | export const compile: Compile = async function(dist, src, filepath, resource, options) { 12 | const encoding = options.encoding 13 | 14 | const distfilepath = path.join(dist, filepath) 15 | const srcfilepath = path.join(src, filepath) 16 | const jsonInProject = await fs.pathExists(distfilepath) ? await fs.readJSON(distfilepath, { encoding }) : null 17 | const jsonInTemplate = await fs.readJSON(srcfilepath, { encoding }) 18 | 19 | const json = R.mergeDeepRight(jsonInProject, jsonInTemplate) 20 | await fs.writeJSON(srcfilepath, json, { spaces: options.spaces || 2 }) 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs_improve.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Document improve 3 | about: Improve document quality or improve document content 4 | title: 'Document: ' 5 | labels: documentation 6 | --- 7 | 8 | 9 | **Is a part of the document confusing? Please describe.** 10 | Document path or hyperlink or github permanent link. 11 | > if use path or hyperlink, plase copy fragment that makes you confused into blockquote 12 | A clear and concise description of why are you confused about this document. 13 | 14 | 15 | **Is the document missing? Please describe.** 16 | A clear and concise description of what do you want to add to the document. 17 | 18 | 19 | **Describe your suggestions for improving the documentation** 20 | A clear and concise description of what you suggest. 21 | 22 | 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /src/util/readdeepdir.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as fs from 'fs-extra' 3 | import * as R from 'ramda' 4 | import { Syncable } from '@/interface/syncable' 5 | 6 | 7 | interface Options { 8 | filter?: (filepath: string) => Syncable 9 | } 10 | 11 | export async function readdeepdir(dir: string, options: Options = {}): Promise { 12 | const files = await fs.readdir(dir) 13 | 14 | const promises = files.map(async(filename: string) => { 15 | const filepath = path.join(dir, filename) 16 | 17 | if (options.filter && !(await options.filter(filepath))) return [] 18 | 19 | const stat = await fs.lstat(filepath) 20 | 21 | if (stat.isDirectory()) { 22 | const subfiles = await readdeepdir(filepath, options) 23 | return subfiles.map(sub => path.join(filename, sub)) 24 | } 25 | 26 | 27 | return filename 28 | }) 29 | 30 | return R.unnest(await Promise.all(promises)) 31 | } 32 | -------------------------------------------------------------------------------- /src/render/exec-handler.ts: -------------------------------------------------------------------------------- 1 | import AJV from 'ajv8' 2 | import ajvKeywords from 'ajv-keywords' 3 | import { Handler } from './interface/template' 4 | import { Path } from '@/interface/path' 5 | import * as handlerSchema from './schema/handler.json' 6 | 7 | 8 | const ajv = new AJV() 9 | ajvKeywords(ajv) 10 | const validate = ajv.compile(handlerSchema) 11 | export async function execHandler(dist: Path, src: Path, filepath: Path, resource: Map, handler: Handler): Promise { 12 | const pkg = await import(handler.name) 13 | 14 | const valid = validate(pkg) 15 | if (!valid) throw new Error(ajv.errorsText(validate.errors, { dataVar: 'loader' })) 16 | 17 | // TODO: Check Hanlder engines 18 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 19 | // @ts-ignore 20 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 21 | return await pkg.compile(dist, src, filepath, resource, handler.options) 22 | } 23 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/index.md: -------------------------------------------------------------------------------- 1 | # Handler 2 | 3 | 执行`echo "export function compile() {}" >> your_handler.js`即可快速创建一个Handler。 4 | 虽然他什么功能也没有。 5 | 6 | 只需要在文件中`export`一个命名为`compile`的函数,就可以作为`mili`的Handler。 7 | `compile`函数的具体出入参数如下: 8 | 9 | ```typescript 10 | export type Compile = (dist: Path, src: Path, filepath: Path, resource: Map, options: Record) => Syncable 11 | ``` 12 | 13 | ## 函数的参数说明 14 | 15 | key | description 16 | :-------------------|:-------------- 17 | dist | 存放项目的文件目录 18 | src | 存放模板的文件夹目录 19 | filepath | 模板文件的相对地址 20 | resource | 资源数据 21 | options | 配置参数 22 | 23 | 24 | ## 内置`hadnler` 25 | 26 | - [`handlebars`](./handlebars.md) 27 | - [`merge-json`](./merge-json.md) 28 | - [`merge-yaml`](./merge-yaml.md) 29 | - [`ignore`](./ignore) 30 | - [`delete`]( ./delete.md ) 31 | - [`init`](./init.md) 32 | - [`exist`](./exist.md) 33 | -------------------------------------------------------------------------------- /src/render/interface/config.ts: -------------------------------------------------------------------------------- 1 | import { Answers } from '@/interface/answers' 2 | import { Loader } from './loader' 3 | import { Hook } from './hook' 4 | import { Question } from './question' 5 | import { Template } from './template' 6 | 7 | 8 | export interface Config { 9 | /** 10 | * The template version 11 | */ 12 | version: string 13 | 14 | /** 15 | * The range of mili version 16 | * @example ['>=2.0.0 <3.0.0'] 17 | */ 18 | engines: string[] 19 | 20 | /** 21 | * Extend from other templates 22 | * 23 | * @example ['npm:@mtpl/mili-template'] 24 | */ 25 | extends: { 26 | template: string 27 | version: string 28 | answers?: Answers 29 | when?: any 30 | }[] 31 | 32 | /** 33 | * Load the data need for files rendering 34 | * 35 | * @example ['mili-loader-npm'] 36 | */ 37 | loaders: Loader[] 38 | 39 | questions: Question[] 40 | hooks: Hook[] 41 | templates: Template[] 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | name: Release 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: GoogleCloudPlatform/release-please-action@v2 12 | id: release 13 | with: 14 | token: ${{secrets.RELEASE_TOKEN}} 15 | release-type: node 16 | package-name: "mili" 17 | - uses: actions/checkout@v2 18 | if: ${{ steps.release.outputs.release_created }} 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: 14 22 | registry-url: 'https://registry.npmjs.org' 23 | if: ${{ steps.release.outputs.release_created }} 24 | - run: npm i 25 | if: ${{ steps.release.outputs.release_created }} 26 | - run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 29 | if: ${{ steps.release.outputs.release_created }} 30 | -------------------------------------------------------------------------------- /src/handler/ignore.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda' 2 | import Ajv from 'ajv8' 3 | import { CompielOptions, Compile } from '@/interface/handler' 4 | import * as evalstr from 'eval' 5 | import { genEvalContent } from '@/util/gen-eval-content' 6 | 7 | 8 | interface Options extends CompielOptions { 9 | schema?: any 10 | eval?: string 11 | } 12 | 13 | const ajv = new Ajv() 14 | 15 | 16 | export const compile: Compile = function(dist, src, filepath, resource, options) { 17 | if (('schema' in options)) { 18 | const validate = ajv.compile(options.schema) 19 | const valid = validate(R.fromPairs(Array.from(resource.entries()))) 20 | if (valid) return '/dev/null' 21 | } else if (options.eval) { 22 | const content = genEvalContent( 23 | JSON.stringify(R.fromPairs(Array.from(resource.entries()))), 24 | options.eval, 25 | ) 26 | 27 | if (evalstr(content)) return '/dev/null' 28 | } else { 29 | return '/dev/null' 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. 4 | 5 | Please note we have a [code of conduct](https://github.com/mili-project-manager/mili/blob/master/.github/CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 6 | 7 | 8 | # Issue considerations 9 | 10 | * Use templates whenever possible 11 | * Please confirm if there are duplicate problems 12 | 13 | 14 | # PullRequest considerations 15 | 16 | * Update the README.md with details of changes to the interface 17 | * Complete testing of the modified code 18 | * Each commit message must follows the [Conventional Commits Specification](https://conventionalcommits.org/). This will be used for automatic generation of changelog. 19 | * No need to bump the version number, there will be someone responsible for the release. 20 | -------------------------------------------------------------------------------- /src/handler/merge-yaml.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import { Compile } from '@/interface/handler' 3 | import * as R from 'ramda' 4 | import * as path from 'path' 5 | import * as yaml from 'js-yaml' 6 | 7 | 8 | export const compile: Compile = async function(dist, src, filepath, resource, options) { 9 | const encoding = options.encoding 10 | 11 | const distfilepath = path.join(dist, filepath) 12 | const srcfilepath = path.join(src, filepath) 13 | const yamlInProject = await fs.pathExists(distfilepath) ? yaml.load(await fs.readFile(distfilepath, { encoding })) : null 14 | const yamlInTemplate = yaml.load(await fs.readFile(srcfilepath, { encoding })) 15 | 16 | let json = yamlInTemplate 17 | 18 | if (typeof yamlInTemplate === 'object' && yamlInTemplate && typeof yamlInProject === 'object' && yamlInProject) { 19 | json = R.mergeDeepRight(yamlInProject, yamlInTemplate) 20 | } 21 | 22 | 23 | await fs.writeFile(srcfilepath, yaml.dump(json)) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL analysis" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | schedule: 13 | - cron: '0 4 * * 1' 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: 28 | - javascript 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v2 33 | 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v1 36 | with: 37 | languages: ${{matrix.language}} 38 | 39 | - name: Autobuild 40 | uses: github/codeql-action/autobuild@v1 41 | 42 | - name: Perform CodeQL Analysis 43 | uses: github/codeql-action/analyze@v1 44 | -------------------------------------------------------------------------------- /src/init.ts: -------------------------------------------------------------------------------- 1 | import { render } from './render' 2 | import { check } from './util/check' 3 | import { parseTemplate } from './util/parse-template' 4 | 5 | 6 | interface Options { 7 | template: string 8 | /** 9 | * @default 'latest' 10 | */ 11 | version?: string 12 | /** 13 | * The npm Registry 14 | */ 15 | registry?: string 16 | /** 17 | * @default process.cwd() 18 | */ 19 | cwd?: string 20 | /** 21 | * @default false 22 | */ 23 | force?: boolean 24 | } 25 | 26 | export async function init(options: Options): Promise { 27 | const { 28 | template, 29 | version = 'latest', 30 | registry, 31 | cwd = process.cwd(), 32 | force = false, 33 | } = options 34 | 35 | if (!force) await check(cwd) 36 | const tmpDir = cwd 37 | 38 | const repository = parseTemplate(template, version, { registry, cwd }) 39 | const resource = { mili: { operation: 'init', registry } } 40 | await render(tmpDir, repository, {}, resource) 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "outDir": "./lib", 5 | "moduleResolution": "node", 6 | "module": "commonjs", 7 | "target": "esnext", 8 | "strict": true, 9 | "noImplicitAny": false, 10 | "declaration": true, 11 | "resolveJsonModule": true, 12 | "baseUrl": "./", 13 | "rootDir": "./", 14 | "noEmitOnError": true, 15 | "paths": { 16 | "@/*": [ 17 | "src/*" 18 | ], 19 | "@root/*": [ 20 | "./*" 21 | ] 22 | }, 23 | "plugins": [ 24 | { 25 | "transform": "typescript-transform-paths" 26 | }, 27 | { 28 | "transform": "typescript-transform-paths", 29 | "afterDeclarations": true 30 | } 31 | ], 32 | "skipLibCheck": true 33 | }, 34 | "exclude": [ 35 | "node_modules" 36 | ], 37 | "include": [ 38 | "src/**/*.ts" 39 | ], 40 | "ts-node": { 41 | "compiler": "ttypescript" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/schema/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "template.json", 3 | "type": "object", 4 | "properties": { 5 | "path": { 6 | "type": "string", 7 | "default": "./" 8 | }, 9 | "engines": { 10 | "type": "string" 11 | }, 12 | "rules": { 13 | "type": "array", 14 | "items": { 15 | "$ref": "rule.json" 16 | }, 17 | "default": [] 18 | }, 19 | "hooks": { 20 | "type": "object", 21 | "propertyNames": { 22 | "enum": [ 23 | "initialized", 24 | "updated", 25 | "upgraded", 26 | "checked", 27 | "rendered" 28 | ] 29 | }, 30 | "patternProperties": { 31 | ".*": { 32 | "oneOf": [ 33 | { 34 | "type": "string" 35 | }, 36 | { 37 | "instanceof": "Function" 38 | } 39 | ] 40 | } 41 | } 42 | }, 43 | "questions": { 44 | "type": "array", 45 | "items": { 46 | "$ref": "question.json" 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/util/sync-dir.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import * as path from 'path' 3 | import { readdeepdir } from './readdeepdir' 4 | import simpleGit from 'simple-git' 5 | import { exec } from 'child_process' 6 | 7 | 8 | /** 9 | * Copy file from `src` to `dist` 10 | * Delete file in `dist` which `src` not existed 11 | */ 12 | export async function syncDir(src: string, dist: string): Promise { 13 | exec(`rsync --exclude ".git" \ 14 | -avh --no-perms ${src}/ ${dist}`) 15 | 16 | const git = simpleGit(dist) 17 | 18 | const files = await readdeepdir(dist, { 19 | filter: async filepath => { 20 | const relativePath = path.relative(dist, filepath) 21 | if (relativePath === '.git') return false 22 | const result = await git.checkIgnore(filepath) 23 | 24 | return !result.length 25 | }, 26 | }) 27 | const promises = files.map(async filename => { 28 | const filepath = path.join(src, filename) 29 | if (await fs.pathExists(filepath)) return 30 | await fs.remove(path.join(dist, filename)) 31 | }) 32 | 33 | await Promise.all(promises) 34 | } 35 | -------------------------------------------------------------------------------- /src/loader/npm.ts: -------------------------------------------------------------------------------- 1 | import { Exec } from '@/interface/loader' 2 | import * as fs from 'fs-extra' 3 | import * as path from 'path' 4 | 5 | 6 | interface NpmLoaderOptions { 7 | /** 8 | * The default package name 9 | */ 10 | name: string 11 | 12 | /** 13 | * The default package version 14 | */ 15 | version: string 16 | 17 | /** 18 | * The default package description 19 | */ 20 | description: string 21 | } 22 | 23 | export const exec: Exec = async function(cwd, options) { 24 | const packageJsonFilepath = path.join(cwd, 'package.json') 25 | 26 | if (!await fs.pathExists(packageJsonFilepath)) { 27 | return { 28 | npm: { 29 | name: options.name || path.basename(path.dirname(cwd)), 30 | version: options.version || '0.0.1', 31 | description: options.description || '', 32 | }, 33 | } 34 | } 35 | 36 | const config = await fs.readJSON(packageJsonFilepath) 37 | 38 | return { 39 | npm: { 40 | name: config.name, 41 | version: config.version, 42 | description: config.description, 43 | }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Val.istar.Guo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/util/copy.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import simpleGit from 'simple-git' 3 | import * as ora from 'ora' 4 | import * as path from 'path' 5 | import { exec } from './exec' 6 | 7 | 8 | export async function copy(src: string, dist: string, showProgress = false): Promise { 9 | const git = simpleGit(src) 10 | 11 | const spinner = ora({ 12 | text: 'Load Files', 13 | discardStdin: true, 14 | }) 15 | 16 | if (showProgress) spinner.start() 17 | 18 | const files = await fs.readdir(src) 19 | const filepaths: string[] = [] 20 | for (const filepath of files) { 21 | const srcfilepath = path.join(src, filepath) 22 | const result = await git.checkIgnore(srcfilepath) 23 | 24 | if (result.length) continue 25 | 26 | const stat = await fs.lstat(srcfilepath) 27 | 28 | if (stat.isDirectory()) filepaths.push(`${filepath}/.`) 29 | else filepaths.push(filepath) 30 | } 31 | 32 | 33 | const tarOutput = path.relative(src, dist) 34 | await exec(`tar -cf ${tarOutput}.tar ${filepaths.join(' ')} && tar -xf ${dist}.tar -C ${dist}`, { cwd: src }) 35 | 36 | if (showProgress) spinner.succeed('Load All Files') 37 | } 38 | -------------------------------------------------------------------------------- /src/handler/handlebars.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import { Compile } from '@/interface/handler' 3 | import * as path from 'path' 4 | import * as Handlebars from 'handlebars' 5 | import * as R from 'ramda' 6 | import * as HandlebarsRamdaHelpers from 'handlebars-ramda-helpers' 7 | 8 | 9 | HandlebarsRamdaHelpers.register(Handlebars) 10 | Handlebars.registerHelper('isEmpty', function(value) { 11 | return R.isEmpty(value) 12 | }) 13 | 14 | Handlebars.registerHelper('equals', function(lvalue, rvalue) { 15 | return R.equals(lvalue, rvalue) 16 | }) 17 | 18 | Handlebars.registerHelper('head', function(arr) { 19 | return R.head(arr) 20 | }) 21 | 22 | 23 | export const compile: Compile = async function(dist, src, filepath, resource, options) { 24 | const encoding = options.encoding 25 | const srcfilepath = path.join(src, filepath) 26 | filepath = filepath.replace(/\.(hbs|handlebars)$/, '') 27 | 28 | const source = await fs.readFile(srcfilepath, { encoding }) 29 | 30 | const template = Handlebars.compile(source) 31 | 32 | 33 | const view = R.fromPairs(Array.from(resource.entries())) 34 | const result = template(view) 35 | 36 | await fs.writeFile(path.join(src, filepath), result) 37 | 38 | return filepath 39 | } 40 | -------------------------------------------------------------------------------- /src/render/schema/templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "required": [ 6 | "path" 7 | ], 8 | "properties": { 9 | "path": { 10 | "oneOf": [ 11 | { 12 | "type": "string", 13 | "format": "uri-reference" 14 | }, 15 | { 16 | "type": "array", 17 | "items": { 18 | "type": "string", 19 | "format": "uri-reference" 20 | } 21 | } 22 | ] 23 | }, 24 | "encoding": { 25 | "type": "string", 26 | "enum": [ 27 | "utf8", 28 | "binary", 29 | "hex", 30 | "ascii" 31 | ] 32 | }, 33 | "handlers": { 34 | "type": "array", 35 | "items": { 36 | "oneOf": [ 37 | { 38 | "type": "string" 39 | }, 40 | { 41 | "type": "object", 42 | "properties": { 43 | "name": { 44 | "type": "string" 45 | }, 46 | "options": {} 47 | } 48 | } 49 | ] 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/zh-cn/loader/markdown-section.md: -------------------------------------------------------------------------------- 1 | # `markdown-section` Loader 2 | 3 | 从Markdown文件中提取两个``注释之间的内容 4 | 5 | 使用Loader配合`handlebars` Handler进行模板渲染,可以保证`readme.md`一类的markdown文件符合模板的格式规范。您可以用于: 6 | 7 | * 限制Markdown文件有一致的页头和页脚 8 | * 生成一致的shields标签美化README.md文件 9 | 10 | ## 参数 11 | 12 | option | required | description 13 | :-----------|:---------|:------------------------ 14 | `key` | 是 | 指定提取的数据存放入`resource`的键值 15 | `sections` | 是 | 需要地区的`section`名称 16 | `filepath` | 是 | 文件路径 17 | `encoding` | 否 | 文件编码,默认`'utf8'` 18 | 19 | 20 | ## Resource Example 21 | 22 | 假设我们项目中有如下`readme.md`文件 23 | 24 | ```markdown 25 | # Title 26 | 27 | The Description 28 | 29 | # Footer 30 | 31 | The Footer 32 | ``` 33 | 34 | Loader 配置如下: 35 | 36 | ```json 37 | { 38 | "loaders": [ 39 | { 40 | "name": "markdown-section", 41 | "options": { 42 | "key": "readme", 43 | "sections": ["description"], 44 | "filepath": "readme.md", 45 | "encoding": "utf8" 46 | } 47 | } 48 | ] 49 | } 50 | ``` 51 | 52 | 将得到`resource`: 53 | 54 | ``` 55 | { 56 | "readme": { 57 | "description": "The Description" 58 | } 59 | } 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /src/schema/rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "rule.json", 3 | "type": "object", 4 | "properties": { 5 | "path": { 6 | "type": "string" 7 | }, 8 | "encoding": { 9 | "oneOf": [ 10 | { 11 | "type": "string", 12 | "enum": [ 13 | "utf8", 14 | "binary" 15 | ] 16 | }, 17 | { 18 | "type": "object", 19 | "propertyNames": { 20 | "enum": [ 21 | "utf8", 22 | "binary" 23 | ] 24 | }, 25 | "patternProperties": { 26 | ".*": { 27 | "type": "string" 28 | } 29 | } 30 | } 31 | ] 32 | }, 33 | "upgrade": { 34 | "type": "string", 35 | "enum": [ 36 | "cover", 37 | "keep", 38 | "exist", 39 | "merge" 40 | ], 41 | "default": "cover" 42 | }, 43 | "glob": { 44 | "type": "boolean", 45 | "default": true 46 | }, 47 | "handler": { 48 | "$ref": "handler.json" 49 | }, 50 | "handlers": { 51 | "type": "array", 52 | "items": { 53 | "$ref": "handler.json" 54 | } 55 | } 56 | }, 57 | "required": [ 58 | "path" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /src/handler/delete.ts: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda' 2 | import Ajv from 'ajv8' 3 | import { CompielOptions, Compile } from '@/interface/handler' 4 | import * as fs from 'fs-extra' 5 | import * as path from 'path' 6 | import * as evalstr from 'eval' 7 | import { genEvalContent } from '@/util/gen-eval-content' 8 | 9 | 10 | interface Options extends CompielOptions { 11 | schema?: any 12 | eval?: string 13 | } 14 | 15 | const del: Compile = async function(dist, src, filepath) { 16 | const distfilepath = path.join(dist, filepath) 17 | await fs.remove(distfilepath) 18 | return '/dev/null' 19 | } 20 | 21 | const ajv = new Ajv() 22 | 23 | export const compile: Compile = async function(dist, src, filepath, resource, options) { 24 | if (('schema' in options)) { 25 | const validate = ajv.compile(options.schema) 26 | const valid = validate(R.fromPairs(Array.from(resource.entries()))) 27 | if (valid) return del(dist, src, filepath, resource, options) 28 | } else if (options.eval) { 29 | const content = genEvalContent( 30 | JSON.stringify(R.fromPairs(Array.from(resource.entries()))), 31 | options.eval, 32 | ) 33 | 34 | if (evalstr(content)) return del(dist, src, filepath, resource, options) 35 | } else { 36 | return del(dist, src, filepath, resource, options) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/ignore.md: -------------------------------------------------------------------------------- 1 | # `ignore` Handler 2 | 3 | 不渲染模板文件,如果使用者项目中存在同名文件,也不会删除此文件。 4 | 5 | 如果不传任何参数,默认会不渲染文件 6 | 7 | ## 参数 8 | 9 | options | required | description 10 | :------------------|:---------|:-------------------- 11 | schema | 否 | 使用[ajv](https://ajv.js.org/guide/getting-started.html)校验`resource`是否符合`json schema`,若符合则不渲染。 12 | eval | 否 | 执行一段返回Boolean的NodeJS代码,可从上下文中获取`resource` 13 | 14 | ## 示例 15 | 16 | 如果`resource.answers.confirm_question`的值为`true`,则不渲染`index.js`文件 17 | 18 | ```json 19 | [ 20 | { 21 | "path": "index.js", 22 | "handlers": [ 23 | { 24 | "name": "ignore", 25 | "options": { 26 | "schema": { 27 | "type": "object", 28 | "property": { 29 | "answers": { 30 | "type": "object", 31 | "properties": { 32 | "confirm_question": { 33 | "type": "boolean", 34 | "const": "true" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | ] 43 | } 44 | ] 45 | ``` 46 | 47 | ### eval 48 | 49 | ```json 50 | [ 51 | { 52 | "path": "index.js", 53 | "handlers": [ 54 | { 55 | "name": "ignore", 56 | "options": { 57 | "eval": "resource.answers.confirm_question === true" 58 | } 59 | } 60 | ] 61 | } 62 | ] 63 | ``` 64 | -------------------------------------------------------------------------------- /src/render/compile.ts: -------------------------------------------------------------------------------- 1 | import { Template } from './interface/template' 2 | import * as R from 'ramda' 3 | import * as micromatch from 'micromatch' 4 | import { execHandler } from './exec-handler' 5 | import { readdeepdir } from '@/util/readdeepdir' 6 | import { TEMPLATE_CACHE_FILEPATH } from '@/const' 7 | import { nanoid } from 'nanoid' 8 | import * as fs from 'fs-extra' 9 | import * as path from 'path' 10 | 11 | 12 | export async function compile(cwd: string, templatePath: string, templates: Template[], base: Map): Promise { 13 | const resource = new Map(base) 14 | 15 | const cacheDir = path.join(TEMPLATE_CACHE_FILEPATH, nanoid()) 16 | await fs.ensureDir(cacheDir) 17 | await fs.emptyDir(cacheDir) 18 | await fs.copy(templatePath, cacheDir) 19 | 20 | for (const filename of await readdeepdir(cacheDir)) { 21 | for (const template of templates) { 22 | if (!micromatch.isMatch(filename, template.path, { dot: true })) continue 23 | 24 | let filepath = filename 25 | const encoding = template.encoding 26 | 27 | for (const handler of template.handlers) { 28 | const subpath = await execHandler( 29 | cwd, 30 | cacheDir, 31 | filepath, 32 | resource, 33 | R.mergeDeepLeft(handler, { options: { encoding } }), 34 | ) 35 | 36 | if (subpath) filepath = subpath 37 | 38 | if (filepath === '/dev/null') break 39 | } 40 | 41 | break 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/update.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import { Answers } from './interface/answers' 3 | import { migrate } from './migrate' 4 | import { render } from './render' 5 | import { check } from './util/check' 6 | import { copy } from './util/copy' 7 | import { createTmpDir } from './util/create-tmp-dir' 8 | import { parseTemplate } from './util/parse-template' 9 | import { syncDir } from './util/sync-dir' 10 | 11 | 12 | interface Options { 13 | template: string 14 | /** 15 | * @default 'latest' 16 | */ 17 | version?: string 18 | /** 19 | * The npm Registry 20 | */ 21 | registry?: string 22 | /** 23 | * @default process.cwd() 24 | */ 25 | cwd?: string 26 | /** 27 | * @default false 28 | */ 29 | force?: boolean 30 | 31 | /** 32 | * @default {} 33 | */ 34 | answers?: Answers 35 | } 36 | 37 | 38 | export async function update(options: Options): Promise { 39 | const { 40 | template, 41 | version = 'latest', 42 | registry, 43 | cwd = process.cwd(), 44 | force = false, 45 | answers = {}, 46 | } = options 47 | 48 | if (!force) await check(cwd) 49 | const tmpDir = await createTmpDir() 50 | await copy(cwd, tmpDir, true) 51 | 52 | const repository = parseTemplate(template, version, { registry, cwd }) 53 | const resource = { mili: { operation: 'update', registry } } 54 | await render(tmpDir, repository, answers, resource) 55 | 56 | migrate(tmpDir, repository, version) 57 | await syncDir(tmpDir, cwd) 58 | await fs.remove(tmpDir) 59 | } 60 | -------------------------------------------------------------------------------- /src/upgrade.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import { Answers } from './interface/answers' 3 | import { migrate } from './migrate' 4 | import { render } from './render' 5 | import { check } from './util/check' 6 | import { copy } from './util/copy' 7 | import { createTmpDir } from './util/create-tmp-dir' 8 | import { parseTemplate } from './util/parse-template' 9 | import { syncDir } from './util/sync-dir' 10 | 11 | 12 | interface Options { 13 | template: string 14 | /** 15 | * @default 'latest' 16 | */ 17 | version?: string 18 | /** 19 | * The npm Registry 20 | */ 21 | registry?: string 22 | /** 23 | * @default process.cwd() 24 | */ 25 | cwd?: string 26 | /** 27 | * @default false 28 | */ 29 | force?: boolean 30 | 31 | /** 32 | * @default {} 33 | */ 34 | answers?: Answers 35 | } 36 | 37 | export async function upgrade(options: Options, version = 'latest'): Promise { 38 | const { 39 | template, 40 | version: fromVersion = 'latest', 41 | registry, 42 | cwd = process.cwd(), 43 | force = false, 44 | answers = {}, 45 | } = options 46 | 47 | if (!force) await check(cwd) 48 | const tmpDir = await createTmpDir() 49 | await copy(cwd, tmpDir, true) 50 | 51 | const repository = parseTemplate(template, version, { registry, cwd }) 52 | const resource = { mili: { operation: 'upgrade', registry } } 53 | await render(tmpDir, repository, answers, resource) 54 | 55 | migrate(tmpDir, repository, fromVersion) 56 | await syncDir(tmpDir, cwd) 57 | await fs.remove(tmpDir) 58 | } 59 | -------------------------------------------------------------------------------- /src/check.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra' 2 | import { Answers } from './interface/answers' 3 | import { render } from './render' 4 | import { createTmpDir } from './util/create-tmp-dir' 5 | import { parseTemplate } from './util/parse-template' 6 | import { diff } from './diff' 7 | import { copy } from './util/copy' 8 | 9 | 10 | interface Options { 11 | template: string 12 | 13 | /** 14 | * @default 'latest' 15 | */ 16 | version?: string 17 | 18 | /** 19 | * The npm Registry 20 | */ 21 | registry?: string 22 | 23 | /** 24 | * @default process.cwd() 25 | */ 26 | cwd?: string 27 | 28 | /** 29 | * @default false 30 | */ 31 | showDiff?: boolean 32 | 33 | /** 34 | * @default false 35 | */ 36 | fold?: boolean 37 | 38 | /** 39 | * @default {} 40 | */ 41 | answers?: Answers 42 | } 43 | 44 | 45 | export async function check(options: Options): Promise { 46 | const { 47 | template, 48 | version = 'latest', 49 | registry, 50 | cwd = process.cwd(), 51 | showDiff = false, 52 | fold = false, 53 | answers = {}, 54 | } = options 55 | 56 | const tmpDir = await createTmpDir() 57 | await copy(cwd, tmpDir, true) 58 | 59 | const repository = parseTemplate(template, version, { registry, cwd }) 60 | const resource = { mili: { operation: 'check', registry } } 61 | await render(tmpDir, repository, answers, resource) 62 | 63 | const errs = await diff(cwd, tmpDir, { fold, showDiff }) 64 | 65 | await fs.remove(tmpDir) 66 | 67 | if (errs.length) throw new Error('Check Failed') 68 | } 69 | -------------------------------------------------------------------------------- /src/render/schema/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "required": [ 4 | "engines" 5 | ], 6 | "properties": { 7 | "version": { 8 | "type": "string" 9 | }, 10 | "engines": { 11 | "type": "array", 12 | "items": { 13 | "type": "string" 14 | } 15 | }, 16 | "extends": { 17 | "type": "array", 18 | "items": { 19 | "oneOf": [ 20 | { 21 | "type": "string" 22 | }, 23 | { 24 | "type": "object", 25 | "required": [ 26 | "template" 27 | ], 28 | "properties": { 29 | "template": { 30 | "type": "string" 31 | }, 32 | "version": { 33 | "type": "string" 34 | }, 35 | "answers": {}, 36 | "when": {} 37 | } 38 | } 39 | ] 40 | } 41 | }, 42 | "loaders": { 43 | "type": "array", 44 | "items": { 45 | "oneOf": [ 46 | { 47 | "type": "string" 48 | }, 49 | { 50 | "type": "object", 51 | "required": [ 52 | "name" 53 | ], 54 | "properties": { 55 | "name": { 56 | "type": "string" 57 | }, 58 | "options": { 59 | "type": "object", 60 | "additionalProperties": true 61 | } 62 | } 63 | } 64 | ] 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/zh-cn/handler/delete.md: -------------------------------------------------------------------------------- 1 | # `delete` Handler 2 | 3 | 删除使用者项目目录中的此文件。 4 | 5 | 如果不穿任何参数,默认会删除文件 6 | 7 | ## options 8 | 9 | options | required | description 10 | :------------------|:---------|:------------------------------------------------ 11 | schema | 否 | 使用[ajv](https://ajv.js.org/guide/getting-started.html)校验`resource`是否符合`json schema`,若符合则删除。 12 | eval | 否 | 执行一段返回Boolean的NodeJS代码,可从上下文中获取`resource` 13 | 14 | ## 示例 15 | 16 | 如果`resource.answers.confirm_question`的值为`true`,则删除使用者项目目录的index.js文件 17 | 18 | 19 | ### schema 20 | 21 | ```json 22 | [ 23 | { 24 | "path": "index.js", 25 | "handlers": [ 26 | { 27 | "name": "delete", 28 | "options": { 29 | "schema": { 30 | "type": "object", 31 | "property": { 32 | "answers": { 33 | "type": "object", 34 | "properties": { 35 | "confirm_question": { 36 | "type": "boolean", 37 | "const": "true" 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | ] 46 | } 47 | ] 48 | ``` 49 | 50 | ### eval 51 | 52 | ```json 53 | [ 54 | { 55 | "path": "index.js", 56 | "handlers": [ 57 | { 58 | "name": "delete", 59 | "options": { 60 | "eval": "resource.answers.confirm_question === true" 61 | } 62 | } 63 | ] 64 | } 65 | ] 66 | ``` 67 | 68 | ###### **与`ignore`的区别**:如果用户目录下存在此文件,`ignore`不会删除文件,也不会覆盖文件内容。但是`delete`会删除此文件。 69 | -------------------------------------------------------------------------------- /src/loader/markdown-section/index.ts: -------------------------------------------------------------------------------- 1 | import { Exec } from '@/interface/loader' 2 | import * as fs from 'fs-extra' 3 | import * as path from 'path' 4 | import * as schema from './schema.json' 5 | import Ajv from 'ajv8' 6 | 7 | 8 | const ajv = new Ajv() 9 | const validate = ajv.compile(schema) 10 | 11 | interface MarkdownSectionLoaderOptions { 12 | /** 13 | * The resource key 14 | */ 15 | key: string 16 | 17 | /** 18 | * The filepath 19 | */ 20 | filepath: string 21 | 22 | /** 23 | * The file encoding 24 | * 25 | * @default 'utf8' 26 | */ 27 | encoding: string 28 | 29 | /** 30 | * The findind sections 31 | */ 32 | sections: string 33 | } 34 | 35 | export const exec: Exec = async function(cwd, options) { 36 | if (!validate(options)) throw new Error(ajv.errorsText(validate.errors)) 37 | 38 | const filepath = path.join(cwd, options.filepath) 39 | 40 | if (!await fs.pathExists(filepath)) { 41 | return { 42 | [options.key]: {}, 43 | } 44 | } 45 | 46 | const content = await fs.readFile(filepath, options.encoding || 'utf8') 47 | const resource = { 48 | [options.key]: {}, 49 | } 50 | 51 | for (const section of options.sections) { 52 | const tag = `` 53 | let beginIndex = content.indexOf(tag) 54 | if (beginIndex === -1) continue 55 | 56 | beginIndex += tag.length 57 | const endIndex = content.indexOf(tag, beginIndex) 58 | 59 | if (endIndex === -1) continue 60 | 61 | resource[options.key][section] = content.substring(beginIndex, endIndex) 62 | } 63 | 64 | return resource 65 | } 66 | -------------------------------------------------------------------------------- /src/util/check.ts: -------------------------------------------------------------------------------- 1 | import simpleGit from 'simple-git' 2 | import * as fs from 'fs-extra' 3 | import * as path from 'path' 4 | 5 | 6 | const reminder = [ 7 | 'This command may overwrite some files', 8 | "If you're sure to run the command, rerun it with --force.", 9 | ].join('\n') 10 | 11 | 12 | function isEmptyDir(dir: string): boolean { 13 | const files = fs.readdirSync(dir) 14 | return !files.length 15 | } 16 | 17 | export function isChildPathOf(parent: string) { 18 | return (child: string): boolean => { 19 | if (child === parent) return false 20 | const parentTokens = parent.split('/').filter(i => i.length) 21 | const childTokens = child.split('/').filter(i => i.length) 22 | return parentTokens.every((t, i) => childTokens[i] === t) 23 | } 24 | } 25 | 26 | 27 | async function isWorkDirClean(dir: string): Promise { 28 | const git = simpleGit(dir) 29 | 30 | const stdout = await git.raw(['ls-files', '--exclude-standard', '--others', '-m']) 31 | const files = stdout ? stdout.split('\n') : [] 32 | 33 | let toplevel = await git.revparse(['--show-toplevel']) 34 | toplevel = toplevel.replace(/\n$/, '') 35 | 36 | return !files 37 | .map(file => path.join(toplevel, file)) 38 | .filter(isChildPathOf(dir)) 39 | .length 40 | } 41 | 42 | export async function check(dir: string): Promise { 43 | if (!await fs.pathExists(dir)) throw new Error(`Not Existed Dir: ${dir}`) 44 | const git = simpleGit(dir) 45 | 46 | const isRepo = await git.checkIsRepo() 47 | if (isRepo) { 48 | if (!(await isWorkDirClean(dir))) { 49 | throw new Error([ 50 | 'Git working directory not clean', 51 | reminder, 52 | ].join('\n')) 53 | } 54 | } else if (!isEmptyDir(dir)) { 55 | throw new Error([ 56 | 'The project directory is not empty.', 57 | reminder, 58 | ].join('\n')) 59 | } 60 | 61 | return true 62 | } 63 | -------------------------------------------------------------------------------- /src/render/schema/questions.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "items": { 4 | "type": "object", 5 | "properties": { 6 | "type": { 7 | "type": "string", 8 | "enum": [ 9 | "input", 10 | "number", 11 | "confirm", 12 | "list", 13 | "rawlist", 14 | "expand", 15 | "checkbox", 16 | "password", 17 | "editor" 18 | ] 19 | }, 20 | "name": { 21 | "type": "string" 22 | }, 23 | "message": { 24 | "type": "string" 25 | }, 26 | "default": { 27 | "oneOf": [ 28 | { 29 | "type": "string" 30 | }, 31 | { 32 | "type": "boolean" 33 | }, 34 | { 35 | "type": "number" 36 | }, 37 | { 38 | "type": "array" 39 | } 40 | ] 41 | }, 42 | "choices": { 43 | "type": "array", 44 | "items": { 45 | "oneOf": [ 46 | { 47 | "type": "string" 48 | }, 49 | { 50 | "type": "number" 51 | }, 52 | { 53 | "type": "object", 54 | "properties": { 55 | "name": { 56 | "type": "string" 57 | }, 58 | "short": { 59 | "type": "string" 60 | }, 61 | "value": { 62 | "oneOf": [ 63 | { 64 | "type": "string" 65 | }, 66 | { 67 | "type": "number" 68 | } 69 | ] 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | }, 76 | "loop": { 77 | "type": "boolean" 78 | }, 79 | "schema": {}, 80 | "when": {} 81 | }, 82 | "required": [ 83 | "name", 84 | "type" 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/util/parse-template.ts: -------------------------------------------------------------------------------- 1 | import { NpmRepository, Repository } from '@/interface/repository' 2 | import * as isSSH from 'is-ssh' 3 | import * as fs from 'fs-extra' 4 | import * as path from 'path' 5 | import * as isUrl from 'is-url' 6 | import * as validateNpmPackageName from 'validate-npm-package-name' 7 | import { TEMPLATE_STORAGE_FILEPATH } from '@/const' 8 | 9 | function calcStorage(name, version): string { 10 | return path.join(TEMPLATE_STORAGE_FILEPATH, encodeURIComponent(name), version || 'noversion') 11 | } 12 | 13 | 14 | interface Options { 15 | cwd?: string 16 | registry?: string 17 | } 18 | 19 | export function parseTemplate(template: string, version = 'latest', options: Options): Repository { 20 | if (template.startsWith('npm:')) { 21 | const name = template.substr('npm:'.length) 22 | if (!validateNpmPackageName(name)) throw new Error(`Invalid npm package name ${name}`) 23 | 24 | const result: NpmRepository = { 25 | type: 'npm', 26 | name, 27 | version, 28 | storage: calcStorage(template, version), 29 | } 30 | 31 | if (options.registry) result.registry = options.registry 32 | 33 | return result 34 | } else if (isUrl(template)) { 35 | return { 36 | type: 'git', 37 | name: template, 38 | version, 39 | storage: calcStorage(template, version), 40 | } 41 | } else if (template.startsWith('github:')) { 42 | const name = `https://github.com/${template.substr('github:'.length)}.git` 43 | return { 44 | type: 'git', 45 | name, 46 | version, 47 | storage: calcStorage(name, version), 48 | } 49 | } else if (isSSH(template)) { 50 | return { 51 | type: 'git', 52 | name: template, 53 | version, 54 | storage: calcStorage(template, version), 55 | } 56 | } else if (fs.pathExistsSync(template)) { 57 | return { 58 | type: 'fs', 59 | name: template, 60 | version: 'latest', 61 | cwd: options.cwd || process.cwd(), 62 | storage: calcStorage(template, 'latest'), 63 | } 64 | } 65 | 66 | throw new Error(`Invalid template: ${template}`) 67 | } 68 | -------------------------------------------------------------------------------- /docs/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/render/index.ts: -------------------------------------------------------------------------------- 1 | import AJV from 'ajv8' 2 | import * as R from 'ramda' 3 | import * as path from 'path' 4 | import { Answers } from '@/interface/answers' 5 | import { Repository } from '@/interface/repository' 6 | import { parseTemplate } from '@/util/parse-template' 7 | import * as logger from '@/util/logger' 8 | import { download } from './download' 9 | import { loadConfig } from './load-config' 10 | import { inquire } from './inquire' 11 | import { execLoader } from './exec-loader' 12 | import { compile } from './compile' 13 | 14 | 15 | const ajv = new AJV() 16 | 17 | 18 | export async function render(cwd: string, repository: Repository, answers: Answers, initResource: Record): Promise { 19 | const templatePath = await download(repository, cwd) 20 | const config = await loadConfig(templatePath) 21 | 22 | if (!initResource.mili) { 23 | initResource.mili = { 24 | stack: [], 25 | } 26 | } 27 | 28 | if (!initResource.mili.stack) { 29 | initResource.mili.stack = [] 30 | } 31 | 32 | initResource.mili.stack.push({ ...repository, version: config.version }) 33 | 34 | answers = await inquire(config.questions, answers) 35 | 36 | for (const extension of config.extends) { 37 | if (extension.when) { 38 | const validate = ajv.compile(extension.when) 39 | const valid = validate(answers) 40 | if (!valid) continue 41 | } 42 | 43 | const subRepository = parseTemplate(extension.template, extension.version, { 44 | registry: initResource.mili.registry, 45 | cwd, 46 | }) 47 | 48 | let subAnwsers = R.clone(answers) 49 | if (extension.answers) subAnwsers = { ...subAnwsers, ...extension.answers } 50 | 51 | await render( 52 | cwd, 53 | subRepository, 54 | subAnwsers, 55 | R.clone(initResource), 56 | ) 57 | } 58 | 59 | 60 | logger.info(`compile template ${repository.name}...`) 61 | const resource = new Map(Object.entries(initResource)) 62 | 63 | for (const loader of config.loaders) { 64 | const result = await execLoader(cwd, loader) 65 | for (const [key, value] of Object.entries(result)) { 66 | if (resource.has(key)) throw new Error(`Key Confilct - Loader:${loader.name}, key:${key}`) 67 | 68 | resource.set(key, value) 69 | } 70 | } 71 | 72 | resource.set('answers', answers) 73 | 74 | await compile(cwd, path.join(templatePath, 'templates'), config.templates, resource) 75 | } 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | lib 87 | es 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | 120 | # Lock File 121 | package-lock.json 122 | yarn.lock 123 | -------------------------------------------------------------------------------- /src/render/download/index.ts: -------------------------------------------------------------------------------- 1 | import simpleGit from 'simple-git' 2 | import * as semver from 'semver' 3 | import * as fs from 'fs-extra' 4 | import * as path from 'path' 5 | import { Path } from '@/interface/path' 6 | import { Repository } from '@/interface/repository' 7 | import { copy } from '@/util/copy' 8 | import * as logger from '@/util/logger' 9 | import { exec } from '@/util/exec' 10 | import { installDeps } from './install-deps' 11 | 12 | 13 | export async function hasCache(repository: Repository): Promise { 14 | if (semver.valid(repository.version)) { 15 | const existed = await fs.pathExists(repository.storage) 16 | if (existed) { 17 | logger.info(`use the ${repository.name} template cache`) 18 | return existed 19 | } 20 | } 21 | return false 22 | } 23 | 24 | export async function download(repository: Repository, cwd: string): Promise { 25 | if (repository.type === 'git') { 26 | if (await hasCache(repository)) return repository.storage 27 | await fs.ensureDir(repository.storage) 28 | logger.info(`clone ${repository.name} template from git...`) 29 | 30 | const git = simpleGit(repository.storage) 31 | 32 | const version = semver.valid(repository.version) ? `v${repository.version}` : repository.version 33 | 34 | await fs.emptyDir(repository.storage) 35 | await git.clone(repository.name, repository.storage, ['--branch', version, '--single-branch']) 36 | 37 | await installDeps(repository.storage) 38 | return repository.storage 39 | } else if (repository.type === 'npm') { 40 | logger.info(`install ${repository.name} template from npm...`) 41 | const templatePath = path.join(repository.storage, 'node_modules', repository.name) 42 | if (await hasCache(repository)) templatePath 43 | await fs.ensureDir(repository.storage) 44 | 45 | await fs.emptyDir(repository.storage) 46 | await fs.writeJSON(path.join(repository.storage, 'package.json'), { 47 | main: 'index.js', 48 | description: '', 49 | license: 'MIT', 50 | }) 51 | 52 | let command = `npm install --production ${repository.name}@${repository.version}` 53 | if (repository.registry) command += ` --registry ${repository.registry}` 54 | await exec(command, { cwd: repository.storage }) 55 | 56 | return templatePath 57 | } else if (repository.type === 'fs') { 58 | logger.info(`copy ${repository.name} template from file system...`) 59 | await fs.emptyDir(repository.storage) 60 | await copy(path.join(cwd, repository.name), repository.storage) 61 | return repository.storage 62 | } 63 | 64 | throw new Error('Unable download the repository') 65 | } 66 | -------------------------------------------------------------------------------- /src/diff/index.ts: -------------------------------------------------------------------------------- 1 | import { showDiff, ShowDiffOptions, showRemoved } from './show-diff' 2 | import * as fs from 'fs-extra' 3 | import * as path from 'path' 4 | import * as chalk from 'chalk' 5 | import simpleGit from 'simple-git' 6 | import * as logger from '@/util/logger' 7 | import { compare, Difference } from 'dir-compare' 8 | 9 | 10 | interface DiffOptions extends ShowDiffOptions { 11 | showDiff: boolean 12 | } 13 | 14 | 15 | async function dircmp(cwd: string, tmpDir: string): Promise { 16 | const git = simpleGit(cwd) 17 | 18 | const result = await compare(cwd, tmpDir, { 19 | compareContent: true, 20 | skipSymlinks: true, 21 | skipSubdirs: true, 22 | excludeFilter: '.git', 23 | }) 24 | 25 | const diff: Difference[] = [] 26 | 27 | for (const item of result.diffSet || []) { 28 | if (item.state === 'equal' && item.type1 !== 'directory' && item.type2 !== 'directory') continue 29 | if (item.state !== 'equal' && item.path1 && item.name1) { 30 | const list = await git.checkIgnore(path.join(item.path1, item.name1)) 31 | if (list.length) continue 32 | } 33 | 34 | if (item.type1 === 'directory' && item.type2 === 'directory') { 35 | if (item.path1 && item.name1 && item.path2 && item.name2) { 36 | const subdiff = await dircmp(path.join(item.path1, item.name1), path.join(item.path2, item.name2)) 37 | 38 | diff.push(...subdiff) 39 | } 40 | continue 41 | } 42 | 43 | diff.push(item) 44 | } 45 | 46 | return diff 47 | } 48 | 49 | export async function diff(cwd: string, tmpDir: string, options: DiffOptions): Promise { 50 | const errors: string[] = [] 51 | const diff = await dircmp(cwd, tmpDir) 52 | 53 | for (const item of diff) { 54 | if (item.type2 === 'missing') { 55 | if (options.showDiff && item.name1) { 56 | errors.push(showRemoved(item.name1)) 57 | } else { 58 | const filepath = path.relative(cwd, path.join(item.path1 || '', item.name1 || '')) 59 | errors.push(chalk.red(`${filepath}: Should be remove`)) 60 | } 61 | } else { 62 | const oldFilepath = path.join(item.path1 || '', item.name1 || '') 63 | const newFilepath = path.join(item.path2 || '', item.name2 || '') 64 | 65 | const oldBuffer = item.type1 === 'missing' ? Buffer.from('') : await fs.readFile(oldFilepath) 66 | const newBuffer = await fs.readFile(newFilepath) 67 | 68 | const filepath = path.relative(tmpDir, newFilepath) 69 | if (showDiff) { 70 | errors.push(showDiff(filepath, oldBuffer, newBuffer, options)) 71 | } else { 72 | errors.push(chalk.yellow(`${filepath}: Not Match Template`)) 73 | } 74 | } 75 | } 76 | 77 | if (errors.length) logger.error(errors.join('\n')) 78 | 79 | return errors 80 | } 81 | -------------------------------------------------------------------------------- /src/loader/git.ts: -------------------------------------------------------------------------------- 1 | import { Exec } from '@/interface/loader' 2 | import simpleGit from 'simple-git' 3 | import * as isUrl from 'is-url' 4 | 5 | 6 | interface GitLoaderOptions { 7 | /** 8 | * The git remote 9 | * 10 | * @default 'origin' 11 | */ 12 | remote: string 13 | } 14 | 15 | interface GitHubResource { 16 | repository: { 17 | isGitHubRepo: boolean 18 | gitHubOwner?: string 19 | gitHubRepoName?: string 20 | } 21 | } 22 | 23 | interface GitResource extends GitHubResource { 24 | repository: { 25 | isRepo: boolean 26 | isGitHubRepo: boolean 27 | url?: string 28 | } 29 | } 30 | 31 | 32 | function parseRepo(url: string): GitHubResource['repository'] { 33 | const githubSSHRegExp = /^git@github.com:([-a-zA-Z0-9@:%._+~#=]+)\/([-a-zA-Z0-9@:%._+~#=]+)\.git$/ 34 | const githubHttpRegExp = /https:\/\/github.com\/([-a-zA-Z0-9@:%._+~#=]+)\/([-a-zA-Z0-9@:%._+~#=]+)\.git$/ 35 | 36 | let isGitHubRepo = false 37 | let gitHubOwner: string | undefined 38 | let gitHubRepoName: string | undefined 39 | 40 | if (isUrl(url)) { 41 | const matched = githubHttpRegExp.exec(url) 42 | if (matched) { 43 | isGitHubRepo = true 44 | gitHubOwner = matched[1] 45 | gitHubRepoName = matched[2] 46 | } 47 | } else { 48 | const matched = githubSSHRegExp.exec(url) 49 | if (matched) { 50 | isGitHubRepo = true 51 | gitHubOwner = matched[1] 52 | gitHubRepoName = matched[2] 53 | } 54 | } 55 | 56 | 57 | return { 58 | isGitHubRepo, 59 | gitHubOwner, 60 | gitHubRepoName, 61 | } 62 | } 63 | 64 | 65 | export const exec: Exec = async function(cwd, options) { 66 | const git = simpleGit(cwd) 67 | 68 | if (!await git.checkIsRepo()) { 69 | return { 70 | repository: { 71 | isRepo: false, 72 | isGitHubRepo: false, 73 | }, 74 | } 75 | } 76 | 77 | const remotes = await git.getRemotes(true) 78 | 79 | 80 | if (options.remote) { 81 | const remote = remotes.find(remote => remote.name === options.remote) 82 | if (!remote) throw new Error(`The remote ${options.remote} not existed.`) 83 | const url = remote.refs.push 84 | 85 | return { 86 | repository: { 87 | isRepo: true, 88 | url, 89 | ...parseRepo(url), 90 | }, 91 | } 92 | } else if (remotes[0]) { 93 | return { 94 | repository: { 95 | isRepo: true, 96 | url: remotes[0].refs.push , 97 | ...parseRepo(remotes[0].refs.push), 98 | }, 99 | } 100 | } else { 101 | return { 102 | repository: { 103 | isRepo: true, 104 | url: '', 105 | isGitHubRepo: false, 106 | }, 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /docs/zh-cn/cli.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | 使用 `npx mili -h`查看最新介绍 4 | 5 | ## `mili init [options] ` 6 | 7 | 创建项目 8 | 9 | ### options 10 | 11 | option | default | description 12 | :---------------------------|:---------------------------------------|:-------------- 13 | `-v --version ` | `latest` | 设置模版版本 14 | `--cwd ` | `progress.cwd()` | 设置当前的工作目录 15 | `--no-deps` | - | 是否安装模版依赖,不安装可以节约时间 16 | `--force` | - | 跳过安全检测 17 | 18 | ### repository 19 | 20 | 模版项目的存储地址,支持的类型有: 21 | 22 | repository | description | example 23 | :--------------------|:--------------------------------------|:-------- 24 | `github:owner/name` | github仓库clone地址的缩写 | `npx mili init github:Val-istar-Guo/mili-template` 25 | `npm:package-name` | 模版项目可以是npm包。| `npx mili init npm:mili-template` 26 | clone with HTTPS | 模版项目的存储地址是一个远程git仓库 | `npx mili init https://github.com/Val-istar-Guo/mili-template.git` 27 | clone with SSH | 模版项目的存储地址是一个远程git仓库 | `npx mili init git@github.com:Val-istar-Guo/mili-template.git` 28 | relative path | 从指定的相对路径获取模版项目. 当在一个仓库中,管理多个具有相同模版项目时非常有用。例如使用learn工具时。 | `npx mili init ./template/module-template` 29 | absolute path | 从制定的绝对路径获取模版项目,往往用于测试 | `npx mili ini /template/test-template` 30 | 31 | ## `mili upgrade [options]` 32 | 33 | 升级模版 34 | 35 | ### options 36 | 37 | option | default | description 38 | :---------------------------|:---------------------------------------|:-------------- 39 | `--cwd [cwd]` | `progress.cwd()` | 设置工作目录 40 | `--force` | - | 跳过安全检测 41 | 42 | ## `mili update [options]` 43 | 44 | 使用指定版本(默认为当前版本)的模版更新项目。 45 | 46 | ### options 47 | 48 | option | default | description 49 | :---------------------------|:---------------------|:-------------- 50 | `-v --version [version]` | 当前模版版本 | 设置模版版本 51 | `--cwd [cwd]` | `progress.cwd()` | 设置工作目录 52 | 53 | ## `mili clean` 54 | 55 | 清理mili缓存,例如缓存的已下载的模版项目 56 | 57 | 58 | ## `mili check [options]` 59 | 60 | 检查项目文件是否符合模版的规范,`mili check`会以`.milirc`中的模版配置编译模版,并对比生成的文件内容与当前项目文件内容做对比。 61 | 如果存在差异,则说明当前项目文件内容不符合模版要求。 62 | `mili check`并不会自动修复错误内容。 63 | 因为文件内容的错误很可能并非简单的样式错误,有可能是项目开发人员错误的修改了配置规范或者项目模块造成的。 64 | 您可以运行`mili update`来修正文件错误, 65 | 在运行`mili update`前,推荐先提交代码或者保存到暂存区,这样可以方便的review项目的更新差异,确保项目能够正常运行。 66 | 67 | 另外,`mili check`可以配合`husky`工具使用,在用户提交代码时进行校验。避免用户提交不符合模版规范的代码。 68 | 也可以配合`lint-stage`, 从而实现只对改变的文件进行`check`,提高`check`速度。 69 | 70 | ### options 71 | 72 | option | default | description 73 | :---------------------------|:---------------------|:-------------- 74 | `--cwd [cwd]` | `progress.cwd()` | 设置工作目录 75 | `-d --diff` | `false` | 展示文件内容的差异,类似`git diff` 76 | `--fold` | `false` | 展示内容差异时,折叠未改动的代码。配合`--diff`使用 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | logo 4 |

5 | 6 |

Mili

7 | 8 | 9 | [![version](https://img.shields.io/npm/v/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 10 | [![downloads](https://img.shields.io/npm/dm/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 11 | [![license](https://img.shields.io/npm/l/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 12 | [![dependencies](https://img.shields.io/librariesio/github/mili-project-manager/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 13 | [![coveralls](https://img.shields.io/coveralls/github/mili-project-manager/mili.svg?style=flat-square)](https://coveralls.io/github/mili-project-manager/mili) 14 | 15 | 16 | 17 | 18 | [简体中文](./docs/zh-cn/readme.md) 19 | 20 | **Projects that derived from the same scaffolding, have evolved over time and become different.** 21 | Scaffolding lost control of the subsequent development of the project. 22 | When we need to improve some of the basic functions of scaffolding(e.g. eslint rules), we need to modify each project, and even have to design a customized solution for some old projects. 23 | 24 | Therefore, in order to improve the control ability of scaffolding for the subsequent development of the project,template can modified some files and release new template version, then project can upgrade the template version. 25 | 26 | It is useful for team project management. 27 | 28 | 29 | ## Usage 30 | 31 | 32 | ### Init Project 33 | 34 | ```shell 35 | mkdir my_project 36 | cd my_project 37 | 38 | # template in npm 39 | npx mili init npm:@mtpl/code-style 40 | # template in github 41 | npx mili init github:mili-project-manager/mtpl-code-style 42 | # template in private git repository 43 | npx mili init https://github.com/mili-project-manager/mtpl-code-style 44 | # ssh is also support 45 | npx mili init git@github.com:mili-project-manager/mtpl-code-style.git 46 | ``` 47 | 48 | ### Upgrade 49 | 50 | The upgrade operation is very simple to use. 51 | It will upgrade the template to the latest version. 52 | 53 | ```shell 54 | npx run upgrade 55 | ``` 56 | 57 | ###### This command maybe overwrite your files. 58 | 59 | 60 | ### Check Before Commit 61 | 62 | With [husky](https://www.npmjs.com/package/husky), 63 | it is easy to verify whether the project file meets the template before commit. 64 | Thereby ensuring the specification of the project code. 65 | 66 | Run in terminal: 67 | ```shell 68 | npx mili check --diff --fold 69 | ``` 70 | 71 | The example stdout: 72 | 73 | ![mili check](./docs/images/check.png) 74 | 75 | Run `npx mili upgrade` command will auto modify code according to the diff. 76 | 77 | 78 | 79 | 80 | 81 | 82 | ## Contributing & Development 83 | 84 | If there is any doubt, it is very welcome to discuss the issue together. 85 | Please read [Contributor Covenant Code of Conduct](.github/CODE_OF_CONDUCT.md) and [CONTRIBUTING](.github/CONTRIBUTING.md). 86 | -------------------------------------------------------------------------------- /docs/en/node-interface.md: -------------------------------------------------------------------------------- 1 | # Node Interface 2 | 3 | Mili provides a Node.js API which can be used directly in Node.js runtime. 4 | 5 | ```javascript 6 | import mili from 'mili' 7 | 8 | mili.init({ repository }) 9 | mili.upgrade() 10 | mili.update() 11 | mili.outdated() 12 | mili.clean() 13 | ``` 14 | 15 | ## `mili.init(options)` 16 | 17 | option | default | description 18 | :------------|:--------------------------------------|:-------------- 19 | *repository | - | The repository path. [see more](./cli.md#repository) 20 | name | `basename of cwd` or `progress.cwd()` | Set application name 21 | version | `latest version` | Set the template version 22 | cwd | `progress.cwd()` | Set the current work directory 23 | noDeps | `false` | Don't install template dependencies. You can save some time, if don't install dependencies. 24 | force | `false` | Enforce command. Ignore security check. 25 | 26 | ## `mili.upgrade(options)` 27 | 28 | option | default | description 29 | :-----------|:-----------------|:-------------- 30 | cwd | `progress.cwd()` | Set the current work directory 31 | noDeps | `false` | Don't install template dependencies. You can save some time, if don't install dependencies. 32 | force | `false` | Enforce command. Ignore security check. 33 | recursive | `false` | Upgrade recursive all subfolder. 34 | ignore | `[]` | Folders that do not need to be searched when recursively upgrading 35 | 36 | ## `mili.update(options)` 37 | 38 | option | default | description 39 | :-----------|:-------------------------|:-------------- 40 | version | current template version | Set the template version 41 | cwd | `progress.cwd()` | Set the current work directory 42 | noDeps | `false` | Don't install template dependencies. You can save some time, if don't install dependencies. 43 | force | `false` | Enforce command. Ignore security check. 44 | recursive | `false` | Upgrade recursive all subfolder. 45 | ignore | `[]` | Folders that do not need to be searched when recursively upgrading 46 | 47 | ## `mili.outdated(options)` 48 | 49 | option | default | description 50 | :--------|:-------------------------|:-------------- 51 | cwd | `progress.cwd()` | Set the current work directory 52 | 53 | ## `mili.clean()` 54 | 55 | No options. 56 | 57 | ## `mili.check(options)` 58 | 59 | option | default | description 60 | :---------------------------|:---------------------|:-------------- 61 | `--cwd [cwd]` | `progress.cwd()` | Set the current work directory 62 | `--no-deps` | - | Don't install template dependencies. You can save some time, if don't install dependencies. 63 | `-r --recursive` | - | Checking will recursive all subfolder. 64 | `--ignore [file]` | - | Folders that do not need to be searched when recursively checking. 65 | `-d --diff` | `false` | Show file difference, like `git diff` 66 | `--fold` | `false` | Fold unchanged code, when show file difference. Used with `--diff`. 67 | -------------------------------------------------------------------------------- /docs/zh-cn/readme.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 |

Mili

6 | 7 | [![version](https://img.shields.io/npm/v/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 8 | [![downloads](https://img.shields.io/npm/dm/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 9 | [![license](https://img.shields.io/npm/l/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 10 | [![dependencies](https://img.shields.io/david/mili-project-manager/mili.svg?style=flat-square)](https://www.npmjs.com/package/mili) 11 | [![coveralls](https://img.shields.io/coveralls/github/mili-project-manager/mili.svg?style=flat-square)](https://coveralls.io/github/mili-project-manager/mili) 12 | 13 | > 米粒之光,可与皓月争辉 14 | 15 | **从同一个脚手架衍生出来的项目,很多细节会渐渐变得不一致** 16 | 也就是说:脚手架对于项目的后续发展失去了控制力。 17 | 当我们想要升级一些脚手架的基础功能(例:eslint规则)时,我们不得不去升级每一个项目. 18 | 甚至不得不对一些改动过大的项目设计定制化的升级方案。 19 | 20 | Mili 提供使用模板来控制项目结构和文件内容的能力,所有使用模板的项目,都必须通过升级模板来实现被控制的文件的变更升级。 21 | 22 | 如果你们团队管理着多个项目,并希望这些项目具有统一的、可持续迭代的代码规范、基础功能、框架结构,mili非常适合作为你们的脚手架工具。 23 | 24 | ## 目录 25 | 26 | - [命令行使用](./cli.md) 27 | - [模板开发指南](./template.md) 28 | - [Loaders](./loader/index.md) 29 | - [Handlers](./handler/index.md) 30 | 31 | ## Usage 32 | ### 创建项目 33 | 34 | ```shell 35 | mkdir my_project 36 | cd my_project 37 | 38 | # 来自于NPM仓库的模板 39 | npx mili init npm:@mtpl/code-style 40 | # 来自于GitHub仓库的模板 41 | npx mili init github:mili-project-manager/mtpl-code-style 42 | # 来自于私有仓库的模板 43 | npx mili init https://github.com/mili-project-manager/mtpl-code-style 44 | # 支持SSH 45 | npx mili init git@github.com:mili-project-manager/mtpl-code-style.git 46 | ``` 47 | 48 | ### 升级项目 49 | 50 | 将`模板`升级到`latest`版本 51 | 52 | ```shell 53 | # 在项目目录下执行 54 | mili upgrade 55 | ``` 56 | 57 | 58 | ### 校验项目文件 59 | 60 | 配合[husky](https://www.npmjs.com/package/husky),可以很容易的实现提交前校验项目文件是否符合模版规范。 61 | 从而保障项目代码的规范。 62 | 63 | 在终端运行命令: 64 | ```shell 65 | npx mili check 66 | ``` 67 | 68 | 输出结果: 69 | 70 | ![mili check](../images/check.png) 71 | 72 | 运行`npx mili update`命令,mili将自动按照`diff`的提示进行增删,从而符合模版的规范。 73 | 74 | 75 | ## 正反馈 76 | 77 | ### 模版 => 项目 78 | 79 | 模版的更新都会按照设定的文件升级方案应用到每个使用这个模版的项目中去,减轻每个项目进行升级的工作量。 80 | 81 | ### 项目 => 模版 82 | 83 | 当存在一下情况时: 84 | 85 | 1. 被模版的基础功能/结构无法满足项目的业务需求 86 | 2. 模版的文件存在bug 87 | 88 | 这时候,项目的开发者需要对模版进行更新。因为只有更新到模版后,才能‘安全的’将这些改动应用到项目中来(通过升级模版)。 89 | 与此同时,模版 => 项目的反馈也会为所有的项目带来bug升级、新特性、或结构性调整,减少其他项目升级工作量。 90 | 91 | ## Q&A 92 | 93 | ### template的模版非常像是一个依赖包,为什么不将模版提成一个npm包呢? 94 | 95 | 1. 其实,模版的一部分功能确实应该抽象成一个库,将打包、发布、公司内部服务甚至server等全部按照统一规范包装成一个库(npm包)。 96 | 97 | 但是,这只是理想状况。通常来说,团队始于一个模版,一个项目,逐步发展到多个项目。 98 | 经过很长一段时间,总结经验,最终才会形成一个规范的库。 99 | 而在这段时间内,将会面临每个项目中实现细节分化的问题。 100 | 101 | 最终,在将有一致规范的功能抽象为库的过程中,需要大量改造项目,带来很大的项目改造开发量。 102 | 103 | mili可以在这个过程中起到缓冲作用和辅助升级作用,保证所有项目具有一致的模版。 104 | 105 | 2. 有些文件无法抽象到库中,但是还需要在所有项目里统一。例如:issue模版、readme.md的内容结构等东西。 106 | 必须要放在项目根目录下才能被识别,但是又需要跟随模版升级、多项目统一。 107 | 108 | 因此,即使已经将具有统一规范的功能完全的抽象为一个库,依旧有很大的可能,在每个项目下面,存在一些需要多项目统一的文件。 109 | 110 | 另外,必须要说明的一点是**将具有统一规范的部分提取成库与mili并不冲突**。往往你提取完成后,得到的依旧是一个简化的轻量级的模版。 111 | 112 | ### 对于不同小团队之间的不同开发方式如何做统一 113 | 114 | 1. 如果是类似于“代码是否应该使用分号”这类的差异,是佛说佛有理,公说公有理。 115 | 但是这类差异“统一”往往比“不统一”更有团队价值。 116 | 还有争议更大的如`react` vs `vue`。 117 | 2. 再一个面临的问题是,当前的*web模版*并不适合一些团队的*小程序*类型的应用开发。 118 | 这时候可能出现两种办法: 119 | * 扩展web模版,使其能支持多端开发,可以利用一些`write once run everywhere`的框架。 120 | * 开发一个新的模版专门用于规范小程序开发,使得web与小程序分离。保持两方面开发的灵活性。 121 | 这就类似于制定法律,往往不止有一个宪法,还会有商品法、房地产管理法等等。 122 | 123 | 是用一个模版,还是管理多个模版。需要从灵活性、简单、统一等方面考量出最适合团队的一个综合方案。 124 | 但是,保持多个模版中相似规范和框架的一致性是必要的。可以通过开发一个“模版的模版”来实现。 125 | 126 | 127 | ## Contributing & Development 128 | 129 | 如果存在如何疑问,非常欢迎提issue一起讨论。 130 | 请在讨论时,遵守[Contributor Covenant Code of Conduct](../../.github/CODE_OF_CONDUCT.md)与[CONTRIBUTING](../../.github/CONTRIBUTING.md)。 131 | 让我们共同维护良好的社区环境。 132 | -------------------------------------------------------------------------------- /docs/en/template.md: -------------------------------------------------------------------------------- 1 | # Template 2 | 3 | The template's entry file exposes the configuration of the template. 4 | Mili will try to find the entry file in these places in the template repository. 5 | 6 | 1. the main field defined in package.json 7 | 2. the entry.js in project root. 8 | 9 | The entry file should export the template config: 10 | 11 | field | type | descripton | example 12 | :------------|:-----------|:-----------|:-------- 13 | path | `string` | The folder path of template files that relative to the entry file | `'./template'` 14 | engines | `string` | The range of mili version | `'>=2.0.0 <3.0.0'` 15 | rules | `object[]` | The configuration of each file | Details later 16 | hooks | `object` | The hook will run on the lifecycle of the mili | Details later 17 | interaction | `object[]` | Relying on [inquirer](https://github.com/SBoudrias/Inquirer.js/) to implement user-defined parameters | Details later 18 | 19 | ## Version Management 20 | 21 | Version Management is usefully when project upgrade the template. Because: 22 | 23 | 1. Version checking of mili will give user a warning when MAJOR template version upgrade, 24 | 2. Users can upgrade to a specific version of the template. 25 | 3. If you run `npx mili upgrade` when the template version is up to date, mili can give a warning. 26 | 3. Changelog is usefully when upgrade template. 27 | 3. `npx mili outdated` command will available. 28 | 29 | Mili fllows [Semantic Versioning](https://semver.org/). 30 | For template published to npm, npm is already version management. 31 | However, for templates hosted in the git repository, you need to manually tag the version. 32 | And tag must conform to the format like `v0.0.1`. 33 | 34 | ## Template Files Rule 35 | 36 | The rules is an array of object, that describe how each file is rendered: 37 | 38 | field | type | description | example 39 | :---------|:---------------------------|:-----------|:-------- 40 | path | `string` | The file path that relative to the folder path of template files 41 | handlers | `Function[]` or `string[]` | The array of handler used to handle file one by one. It determines the behavior of the file when it is initialized and upgraded. This is also the design philosophy of the mili template. [See more abolut handler](./handler/index.md) 42 | upgrade | `'cover'` or `'keep'` or `'merge'` or `'exist'` | This is the syntactic sugar of the handlers, and can be used simultaneously with the handlers. Used to automatically add some handlers based on different modes. 43 | 44 | An rules example: 45 | 46 | ```javascript 47 | exports.rules = [{ 48 | path: 'README.md.mustache', 49 | handlers: [ 50 | core => core.extractArea('content', ''), 51 | core => core.extractArea('description', ''), 52 | 'mustache', 53 | ], 54 | }] 55 | ``` 56 | 57 | ## Template Hooks 58 | 59 | The hook will run on the lifecycle of the mili. 60 | And it can be a shell command(string) or a javascript function(support async). 61 | 62 | The hooks currently available are: 63 | 64 | field | description | example 65 | :-------------|:---------------------------------------|:-------- 66 | initialized | Run after `npx mili init` completed | `{ initialized: 'npm install' }` 67 | upgraded | Run after `npx mili upgrade` completed | `{ upgraded: 'npm install' }` 68 | updated | Run after `npx mili update` completed | `{ updated: 'npm install' }` 69 | checked | Run after `npx mili check` completed | `{ checked: "echo 'hello world'" }` 70 | rendered | Run after all file rendered. Before `initialized`, `upgraded` and `updated` | `{ rendered: 'npm install' }` 71 | 72 | ## Template Interaction 73 | 74 | Relying on [inquirer](https://github.com/SBoudrias/Inquirer.js/) to implement user-defined parameters. 75 | And you can find the answsers. 76 | 77 | example: 78 | 79 | ```javascript 80 | exports.interaction = [ 81 | { type: 'input', name: 'key', message: 'question' } 82 | ] 83 | ``` 84 | 85 | Then, you can get the answer from `resource.answsers.key`. 86 | -------------------------------------------------------------------------------- /docs/en/handler/index.md: -------------------------------------------------------------------------------- 1 | # Handler 2 | 3 | 'handler' is an' object 'composed of two' function '. The structure is: 4 | 5 | ```typescript 6 | interface Handler { 7 | genFile: (file: CompiledFile, resource: Readonly): Promise 8 | genPath: (path: string, resource: Readonly): Promise 9 | } 10 | ``` 11 | 12 | `mili` run all `genPath` of handlers first, then the `genFile`. 13 | It is beacuse `genFile` may be need to read the project file. 14 | 15 | There are two important parameters, `file` and `resource`.Let's to show you how to use them. 16 | 17 | ## file 18 | 19 | By modifying the `file.content`, you can control the file content that is rendered to the project file. 20 | The initial `file.content` is the content of the template file. 21 | And the behavior of rendering 'file' is controlled by `file.delete` and `file.renderable`. 22 | 23 | key | type | description 24 | :-------------------|:--------------------------------------|:-------------- 25 | content | `string` | The contents will be write to project. 26 | projectFileExisted | `boolean` | Whether the project file exists. 27 | getProjectContent | `() => Promise` | Get the content of project file. Never read file manually, use `getProjectContent` is more secure. 28 | deleted | `boolean` | Whether to delete file,defaulted `false`. 29 | renderable | `boolean` | Whether to render file,defaulted`true`. 30 | addition | `object` | The additional information of file. 31 | 32 | 33 | ## resource 34 | 35 | Resource is the read-only data, contains all runtime data. 36 | 37 | key | type | description 38 | :----------|:--------------------------------------|:-------------- 39 | operation | `'init'` or `'upgrade'` or `'update'` | The operation of cli 40 | mili | `object` | The information about mili used 41 | template | `object` | The information about template 42 | project | `object` | The information about project 43 | addition | `object` | The addition key-value set by handler(e.g. `extractArea` handler) 44 | 45 | ### resource.mili 46 | 47 | key | type | description 48 | :----------|:-----------------------|:-------------- 49 | version | 版本号 | The version of mili. 50 | 51 | ### resource.template 52 | 53 | key | type | description 54 | :------------|:---------------|:-------------- 55 | path | `string` | The folder path of template files that relative to the entry file 56 | engines | `string` | The range of mili version 57 | files | `object[]` | The rule of each file 58 | hooks | `object` | The hook will run on the lifecycle of the mili 59 | question | `object[]` | Relying on [inquirer](https://github.com/SBoudrias/Inquirer.js/) to implement user-defined parameters 60 | 61 | ### resource.project 62 | 63 | key | type | description 64 | :----------|:----------------------------|:-------------- 65 | path | `string` | The project path 66 | answers | `object` | The answers of interaction setted in template config 67 | 68 | ## The build-in `hadnler` 69 | 70 | - exist[./exist.md] 71 | - merge[./merge.md] 72 | - mustache[./mustache.md] 73 | - ignoreWhen[./ignore-when.md] 74 | - extractArea[./extract-area.md] 75 | 76 | ## More `handler` 77 | 78 | - [mili-handler-additional-property](https://github.com/Val-istar-Guo/mili-handler-additional-property): The mili handler used to add property to file.addition. 79 | - [mili-handler-prettier](https://github.com/Val-istar-Guo/mili-handler-prettier): The mili handler used to format code by prettier. 80 | - [mili-handler-ejs](https://github.com/Val-istar-Guo/mili-handler-ejs): The mili handler used to parse ejs template. 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mili", 3 | "version": "4.8.0", 4 | "description": "Scaffolding with continuous control over the development of the project.", 5 | "main": "lib/src/index.js", 6 | "module": "es/src/index.js", 7 | "bin": "./lib/src/cli.js", 8 | "types": "lib/src/index.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mili-project-manager/mili.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/mili-project-manager/mili/issues" 15 | }, 16 | "homepage": "https://github.com/mili-project-manager/mili#readme", 17 | "keywords": [ 18 | "mili", 19 | "scaffold", 20 | "template", 21 | "templates", 22 | "continuous" 23 | ], 24 | "author": "Val.istar.Guo ", 25 | "license": "MIT", 26 | "engines": { 27 | "node": ">=12.13.0" 28 | }, 29 | "scripts": { 30 | "test": "npm run clean && npm run build:test && c8 ava", 31 | "test:debug": "ava --verbose", 32 | "test:update": "ava -u", 33 | "report": "npm run build:test && c8 --reporter=html --reporter=text-summary --skip-full ava", 34 | "coveralls": "c8 report --reporter=text-lcov | coveralls", 35 | "release": "standard-version", 36 | "release:alpha": "standard-version --prerelease alpha", 37 | "release:first": "standard-version --first-release", 38 | "prepublishOnly": "npm run build", 39 | "clean": "rm -rf ./lib/* && rm -rf ./es/*", 40 | "prebuild": "npm run clean", 41 | "build:es": "ttsc -p build/tsconfig.es.json && npm run copyfile:es", 42 | "build:lib": "ttsc -p build/tsconfig.lib.json && npm run copyfile:lib", 43 | "build:test": "ttsc -p build/tsconfig.test.json && npm run copyfile:lib", 44 | "build": "npm run build:lib && npm run build:es", 45 | "prewatch": "npm run copyfile:lib", 46 | "watch": "NODE_ENV=development ttsc -p build/tsconfig.lib.json -w", 47 | "copyfile:es": "copyfiles -u 1 \"src/**/*.!(ts)\" es/src", 48 | "copyfile:lib": "copyfiles -u 1 \"src/**/*.!(ts)\" lib/src", 49 | "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" --fix", 50 | "prepare": "is-ci || husky install" 51 | }, 52 | "devDependencies": { 53 | "@ava/typescript": "^3.0.1", 54 | "@commitlint/cli": "^17.4.4", 55 | "@commitlint/config-conventional": "^17.4.4", 56 | "@typescript-eslint/eslint-plugin": "^4.15.2", 57 | "@typescript-eslint/parser": "^4.15.2", 58 | "ava": "^4.3.0", 59 | "c8": "^7.12.0", 60 | "commitizen": "^4.2.4", 61 | "copyfiles": "^2.4.1", 62 | "coveralls": "^3.1.0", 63 | "cz-conventional-changelog": "^3.3.0", 64 | "eslint": "^7.20.0", 65 | "husky": "^5.2.0", 66 | "is-ci": "^3.0.0", 67 | "lint-staged": "^11.0.0", 68 | "mili": "^4.7.5", 69 | "sinon": "^15.0.1", 70 | "standard-version": "^9.3.0", 71 | "ts-node": "^10.8.1", 72 | "ttypescript": "^1.5.13", 73 | "typescript": "^4.7.4", 74 | "typescript-transform-paths": "^3.3.1" 75 | }, 76 | "dependencies": { 77 | "@types/diff": "^5.0.2", 78 | "@types/fs-extra": "^9.0.13", 79 | "@types/inquirer": "^7.3.3", 80 | "@types/js-yaml": "^4.0.5", 81 | "@types/micromatch": "^4.0.2", 82 | "@types/node": "^14.18.21", 83 | "@types/ramda": "^0.28.14", 84 | "@types/semver": "^7.3.10", 85 | "ajv": "^8.11.0", 86 | "ajv-formats": "^2.1.1", 87 | "ajv-keywords": "^5.1.0", 88 | "ajv8": "npm:ajv@^8.11.0", 89 | "chalk": "^4.1.2", 90 | "commander": "^7.2.0", 91 | "cosmiconfig": "^7.0.1", 92 | "diff": "^5.1.0", 93 | "dir-compare": "^3.3.0", 94 | "eval": "^0.1.8", 95 | "fs-extra": "^10.1.0", 96 | "handlebars": "^4.7.7", 97 | "handlebars-ramda-helpers": "^1.6.0", 98 | "inquirer": "^8.2.4", 99 | "is-ssh": "^1.4.0", 100 | "is-url": "^1.2.4", 101 | "js-yaml": "^4.1.0", 102 | "json5": "^2.2.1", 103 | "micromatch": "^4.0.5", 104 | "nanoid": "^3.3.4", 105 | "ora": "^5.4.1", 106 | "pretty-error": "^4.0.0", 107 | "ramda": "^0.28.0", 108 | "semver": "^7.3.7", 109 | "simple-git": "^3.10.0", 110 | "temp-dir": "^2.0.0", 111 | "validate-npm-package-name": "^3.0.0" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import Ajv from 'ajv8' 3 | import { program } from 'commander' 4 | import * as path from 'path' 5 | import * as fs from 'fs-extra' 6 | import * as milircSchema from './schema/milirc.json' 7 | import { cosmiconfig } from 'cosmiconfig' 8 | import { version } from '../package.json' 9 | import { init } from './init' 10 | import { upgrade } from './upgrade' 11 | import { update } from './update' 12 | import { Milirc } from './interface/milirc' 13 | import { clean } from './clean' 14 | import { check } from './check' 15 | import * as logger from '@/util/logger' 16 | import { prettyError } from '@/util/pretty-error' 17 | 18 | 19 | const ajv = new Ajv() 20 | const validate = ajv.compile(milircSchema) 21 | const explorer = cosmiconfig('mili') 22 | 23 | 24 | async function getConfig(cwd: string = process.cwd()): Promise { 25 | const result = await explorer.search(cwd) 26 | if (!result) throw new Error('Cannot find .milirc.yml or .milirc.json') 27 | const config = result.config 28 | const valid = validate(config) 29 | if (!valid) throw new Error(ajv.errorsText(validate.errors, { dataVar: 'milirc' })) 30 | return config as unknown as Milirc 31 | } 32 | 33 | async function main(): Promise { 34 | program 35 | .version(version) 36 | 37 | const absolutize = (val): string => path.resolve(val) 38 | 39 | program 40 | .command('init ') 41 | .usage('[options] ') 42 | .description('initialize the project') 43 | .option('-f --force') 44 | .option('-v --version ', 'Set the template version') 45 | .option('--registry ', 'Set the npm registry') 46 | .option('--cwd ', 'Set the current work directory', absolutize) 47 | .action(async(repository, option) => { 48 | const { force = false, cwd, version, registry } = option 49 | 50 | if (cwd) await fs.ensureDir(cwd) 51 | 52 | await init({ 53 | cwd, 54 | template: repository, 55 | registry, 56 | force, 57 | version, 58 | }) 59 | }) 60 | 61 | 62 | program 63 | .command('upgrade') 64 | .description('upgrade the template') 65 | .option('-f --force') 66 | .option('--cwd [cwd]', 'Set the current work directory', absolutize) 67 | .option('-v --version ', 'Set the template version') 68 | .action(async options => { 69 | const config = await getConfig(options.cwd) 70 | 71 | await upgrade({ 72 | template: config.template, 73 | version: config.version, 74 | registry: config.registry, 75 | cwd: options.cwd, 76 | force: options.force, 77 | answers: config.answers, 78 | }, options.version) 79 | }) 80 | 81 | program 82 | .command('update') 83 | .description('Update the project with the current version of the template') 84 | .option('-f --force') 85 | .option('--cwd [cwd]', 'Set the current work directory', absolutize) 86 | .action(async option => { 87 | const { force = false } = option 88 | const cwd: string | undefined = option.cwd 89 | 90 | if (cwd && !fs.pathExistsSync(cwd)) throw new Error(`No such directory: ${cwd}`) 91 | 92 | const config = await getConfig(cwd) 93 | 94 | await update({ 95 | template: config.template, 96 | version: config.version, 97 | registry: config.registry, 98 | cwd, 99 | force, 100 | answers: config.answers, 101 | }) 102 | }) 103 | 104 | program 105 | .command('clean') 106 | .description('Clean the cache of mili') 107 | .action(async() => { 108 | await clean() 109 | }) 110 | 111 | 112 | program 113 | .command('check [files...]') 114 | .description('Check if the project file meets the template requirements') 115 | .option('--cwd [cwd]', 'Set the current work directory', absolutize) 116 | .option('-d --diff', 'Show file difference') 117 | .option('--fold', 'fold undifferentiated code') 118 | .action(async(files, options) => { 119 | const { cwd, diff, fold } = options 120 | if (cwd && !fs.pathExistsSync(cwd)) { 121 | throw new Error(`No such directory: ${cwd as string}`) 122 | } 123 | 124 | const config = await getConfig(options.cwd) 125 | 126 | await check({ 127 | template: config.template, 128 | version: config.version, 129 | registry: config.registry, 130 | answers: config.answers, 131 | cwd, 132 | showDiff: diff, 133 | fold, 134 | }) 135 | }) 136 | 137 | program.on('command:*', function(operands) { 138 | logger.error(`error: unknown command '${operands[0] as string}'`) 139 | process.exitCode = 1 140 | }) 141 | 142 | 143 | try { 144 | await program.parseAsync(process.argv) 145 | } catch (e) { 146 | logger.error(prettyError(e)) 147 | process.exitCode = 1 148 | } 149 | } 150 | 151 | void main() 152 | -------------------------------------------------------------------------------- /docs/zh-cn/template.md: -------------------------------------------------------------------------------- 1 | # Template 2 | 3 | ## 目录结构 4 | 5 | mili对模板的项目结构有及其严格的约束条件 6 | 7 | 8 | ``` 9 | | 10 | ├─ mili.json # 模板的基础信息 11 | ├─ hooks.json # 安装/升级/更新模板时触发的生命周期钩子 12 | ├─ questions.json # 依赖inquirer实现的命令行交互工具。使得用户可以自定义模版参数 13 | ├─ templates.json # 定义模板文件的文件各种配置属性 14 | ├─ migration # 升级模板时,若模板版本低于迁移脚本的版本号,则脚本会在升级时执行 15 | | ├─ 1.0.0.sh 16 | | ├─ 2.0.0.sh 17 | | └─ ... 18 | | 19 | └─ templates # 存放模板文件的目录 20 | ├─ package.json.hbs 21 | ├─ eslintrc.json 22 | └─ ... 23 | ``` 24 | 25 | 文件/文件夹 | 是否必须 | 详细说明 26 | :-----------------|:--------|:-------- 27 | `mili.json` | 是 | 模板的基础信息 28 | `hooks.json` | 否 | 安装/升级/更新模板时触发的生命周期钩子 29 | `questions.json` | 否 | 依赖inquirer实现的命令行交互工具。使得用户可以自定义模版参数 30 | `migration` | 否 | 升级模板时,若模板版本低于迁移脚本的版本号,则脚本会在升级时执行。版本号必须符合[semver](https://semver.org/)规范。 31 | `templates.json` | 是 | 定义模板文件的文件各种配置属性 32 | `templates` | 是 | 存放模板文件的目录 33 | 34 | ###### 文件/文件夹的名称不可修改 35 | 36 | 37 | ## `mili.json` 38 | 39 | 属性名 | 是否必须 | 格式 | 详细说明 40 | :----------|:-------|:------------------------|:--------- 41 | `version` | 否 | `string` | 模板的版本号,如果不填写,则使用package.json的版本号 42 | `engines` | 否 | `string[]` | 需要使用的`mili`版本 43 | `extends` | 否 | `(string \| object)[]` | 此模板需要依赖其他模板 44 | `loaders` | 否 | `(string \| object)[]` | 需要使用的数据加载器,加载的数据将用于模板渲染 45 | 46 | Example: 47 | 48 | ```json5 49 | { 50 | // 需要mili版本大于4.0.0才可以使用此模板 51 | "engines": [">4.0.0"], 52 | // mili渲染此模板时,会先渲染"npm:@mtpl/base"和"npm:@mtpl/code-style"模板,然后再执行此模板的渲染 53 | "extends": [ 54 | "npm:@mtpl/base", 55 | { 56 | "template": "npm:@mtpl/code-style", 57 | "answers": { 58 | "framework": "node" 59 | } 60 | }, 61 | ], 62 | // 将会读取使用者项目的npm信息和git信息 63 | "loaders": ["npm", "git"] 64 | } 65 | ``` 66 | 67 | ### `extends` 字段结构 68 | 69 | 70 | 属性名 | 是否必须 | 格式 | 详细说明 71 | :-----------|:-------|:------------------------|:--------- 72 | `template` | 是 | `string` | 模板地址 73 | `version` | 否 | `string` | 模板版本。不设置此选项时,默认使用`latest`版本 74 | `answers` | 否 | `string` | 模板的问题答案。设定后将不会再提示用户回答问题。 75 | 76 | 77 | 78 | ## `template.json` 与 `templates` 79 | 80 | `templates`文件夹下定义了一系列的模板文件,这些文件将会依据`templates.json`中配置的规则,渲染到使用者的项目中去。 81 | 82 | 假设我们的模板有如下目录: 83 | 84 | ``` 85 | ├─ templates.json 86 | └─ templates 87 | ├─ package.json.hbs 88 | └─ eslintrc.json 89 | ``` 90 | 91 | `templates.json`的中可以这样进行配置: 92 | 93 | ```json5 94 | [ 95 | { 96 | // 相对于./templates目录的文件路径,支持glob 97 | "path": ".eslintrc.yml", 98 | "handlers": [ 99 | /** 100 | * 将模板的文件和使用者项目目录下的同名文件进行合并 101 | * 如果文件存在字段冲突,则以模板文件的定义为准 102 | * 103 | * 这个`handler`限制使用者不可以修改模板的eslint规则 104 | * 但是允许其进行自定义额外的eslint规则 105 | */ 106 | "merge-yaml" 107 | ] 108 | }, 109 | { 110 | "path": "package.json.hbs", 111 | "handlers": [ 112 | /** 113 | * 使用`handlebars`引擎进行文件渲染 114 | * 此`handler`会自动移除文件的扩展名`.hbs` 115 | * 渲染使用的视图数据在`mili`中称之为`resource` 116 | * `resource`包含`loader`加载的数据、`questions.json`产生的`answers`以及其他`handler`产生的数据 117 | * 后续会详细讲解`resource`及如何开发一个自定义的`loader`或`handler` 118 | */ 119 | "handlebars", 120 | /** 121 | * 与`merge-yaml`类似 122 | * `handler`的先后执行顺序会影响渲染结果 123 | */ 124 | "merge-json" 125 | ] 126 | } 127 | ] 128 | 129 | ``` 130 | 131 | ### Resource 132 | 133 | `resource`是一个`Map()`对象,功能是存储渲染模板过程中产生的各种数据。其用途主要有以下几点: 134 | 135 | * 作为`handlebars`、`ejs`等模板渲染类的`handler`的视图数据 136 | * `handler`可以向`resource`中添加数据,影响后续`handler`的行为,实现数据通信 137 | 138 | `mili`默认会在`resource`上挂载一部分数据: 139 | 140 | - `resource.answers`: `questions.json`中定义问题的回答结果 141 | - `resource.mili`: mili的运行时参数 142 | - `resource.mili.registry`: 使用的npm registry地址(undfined表示使用官方源)。 143 | - `resource.mili.operation`: mili的运行模式,以下字符串之一:'init', 'update', 'upgrade', 'check' 144 | - `resource.mili.stack`: milirc模板调用栈 145 | - `resource.mili.stack[0].template`: 根模板名称 146 | - `resource.mili.stack[0].version`: 根模板的版本号 147 | 148 | 在`mili.json`中配置`"loaders": ["npm", "git"]`将会从项目中加载的数据放入`resource`。 149 | 150 | 以`npm loader`为例: 151 | 152 | - `resource.npm.name`: 使用者项目目录下`package.json`文件的`name` 153 | - `resource.npm.version`: 使用者项目目录下`package.json`文件的`version` 154 | - `resource.npm.description`: 使用者项目目录下`package.json`文件的`description` 155 | 156 | ## `questions.json` 157 | 158 | 有些模板需要询问使用者一些问题,才能确定如何进行渲染,此文件用于定义问题。 159 | `mili`使用[inquirer](https://www.npmjs.com/package/inquirer)实现功能。 160 | `questions.json`的配置也请参考[inquirer](https://www.npmjs.com/package/inquirer)文档。 161 | 162 | Example: 163 | 164 | ```json 165 | [ 166 | { 167 | "type": "confirm", 168 | "name": "isUseConventionalCommits", 169 | "message": "Conventional Commits", 170 | "default": true 171 | } 172 | ] 173 | ``` 174 | 175 | ## `hooks.json` 176 | 177 | `hooks.json`可以定义在mili的某个运行阶段执行shell命令。 178 | 支持的钩子列表: 179 | 180 | field | description 181 | :---------------|:----------------------------- 182 | before-init | `npx mili init`运行前触发 183 | before-update | `npx mili update`运行前触发 184 | before-upgrade | `npx mili upgrade` 运行前触发 185 | before-check | `npx mili check` 运行前触发 186 | before-render | 任意命令前触发 187 | after-init | `npx mili init`运行后触发 188 | after-update | `npx mili update`运行后触发 189 | after-upgrade | `npx mili upgrade`运行后触发 190 | after-check | `npx mili check`运行后触发 191 | after-render | 任意命令运行后触发 192 | -------------------------------------------------------------------------------- /src/diff/show-diff.ts: -------------------------------------------------------------------------------- 1 | import { diffLines, Change } from 'diff' 2 | import * as chalk from 'chalk' 3 | import * as R from 'ramda' 4 | import { inferEncoding } from './infer-encoding' 5 | 6 | 7 | const createLineNumberFormater = (maxOldLineNumberLength: number, maxNewLineNumberLength: number) => (oldNumber: number | null, newNumber: number | null) => { 8 | const oldNumberStr = R.isNil(oldNumber) ? ' ' : String(oldNumber) 9 | const newNumberStr = R.isNil(newNumber) ? ' ' : String(newNumber) 10 | 11 | const oldN = oldNumberStr.padEnd(maxOldLineNumberLength, ' ') 12 | const newN = newNumberStr.padEnd(maxNewLineNumberLength, ' ') 13 | 14 | return `${oldN}|${newN} ` 15 | } 16 | 17 | type LineFormater = (oldNumber: number | null, newNumber: number | null, tag: string, str: string, fold?: boolean) => string 18 | const createLineFormater = (maxOldLineNumber: number, maxNewLineNumber: number): LineFormater => { 19 | const maxOldLineNumberLength = String(maxOldLineNumber).length 20 | const maxNewLineNumberLength = String(maxNewLineNumber).length 21 | const formatLineNumber = createLineNumberFormater(maxOldLineNumberLength, maxNewLineNumberLength) 22 | 23 | return (oldNumber, newNumber, tag, str, fold) => { 24 | let lines: string[] = str.match(/((.*\n)|(.+$))/g) || [] 25 | 26 | lines = lines 27 | .map((line, i) => { 28 | const oldNumberWithOffset = oldNumber && oldNumber + i 29 | const newNumberWithOffset = newNumber && newNumber + i 30 | const lineNumber = formatLineNumber(oldNumberWithOffset, newNumberWithOffset) 31 | return `${lineNumber} ${tag} ${line.replace(/(\n$)/, '')}\n` 32 | }) 33 | 34 | if (fold && lines.length > 2) { 35 | const dot = '...\n'.padStart(maxOldLineNumberLength + 3, ' ') 36 | lines.splice(1, lines.length - 2, dot) 37 | } 38 | 39 | return lines.join('') 40 | } 41 | } 42 | 43 | type EndLineValidater = (i: number) => boolean 44 | const createEndLineValider = (diffPairs: Change[]): EndLineValidater => { 45 | const index = [...diffPairs].reverse().findIndex(item => !item.added && !item.removed) 46 | const count = diffPairs.length - 1 47 | const lastSamePairIndex = index >= 0 ? count - index : index 48 | 49 | return i => { 50 | if (lastSamePairIndex < i) return true 51 | else if (lastSamePairIndex === diffPairs.length - 1 && lastSamePairIndex === i) return true 52 | return false 53 | } 54 | } 55 | 56 | function diffBody(oldContent: string, newContent: string, options: ShowDiffOptions): string { 57 | let str = '' 58 | let oldLineNumber = 1 59 | let newLineNumber = 1 60 | const maxOldLineNumber = oldContent.split('\n').length 61 | const maxNewLineNumber = oldContent.split('\n').length 62 | const formatLine = createLineFormater(maxOldLineNumber, maxNewLineNumber) 63 | 64 | const diffPairs = diffLines(oldContent, newContent) 65 | const isEndLine = createEndLineValider(diffPairs) 66 | 67 | 68 | diffPairs.forEach(({ added, removed, value }, i) => { 69 | const needFillEndLine = isEndLine(i) 70 | 71 | const inc = value.split('\n').length - 1 72 | 73 | if (added) { 74 | const strWithoutColor = formatLine(null, newLineNumber, '+', value) 75 | 76 | str += chalk.green(strWithoutColor) 77 | newLineNumber += inc 78 | } else if (removed) { 79 | const strWithoutColor = formatLine(oldLineNumber, null, '-', value) 80 | 81 | str += chalk.red(strWithoutColor) 82 | oldLineNumber += inc 83 | } else { 84 | const strWithoutColor = formatLine(oldLineNumber, newLineNumber, ' ', value, options.fold) 85 | str += chalk.grey(strWithoutColor) 86 | 87 | newLineNumber += inc 88 | oldLineNumber += inc 89 | } 90 | 91 | /** 92 | * Add an empty line, 93 | * if '\n' at the end of file. 94 | * So, It's easy to tell if the last line end with '\n' 95 | */ 96 | if (needFillEndLine && /\n$/.test(value)) { 97 | if (added) { 98 | const strWithoutColor = formatLine(null, newLineNumber, '+', '\n') 99 | str += chalk.green(strWithoutColor) 100 | newLineNumber += 1 101 | } else if (removed) { 102 | const strWithoutColor = formatLine(oldLineNumber, null, '-', '\n') 103 | str += chalk.red(strWithoutColor) 104 | oldLineNumber += 1 105 | } else { 106 | const strWithoutColor = formatLine(oldLineNumber, newLineNumber, ' ', '\n') 107 | str += chalk.grey(strWithoutColor) 108 | newLineNumber += 1 109 | oldLineNumber += 1 110 | } 111 | } 112 | }) 113 | 114 | return str 115 | } 116 | 117 | 118 | export interface ShowDiffOptions { 119 | fold?: boolean 120 | } 121 | 122 | export function showDiff( 123 | filename: string, 124 | oldBuffer: Buffer, 125 | newBuffer: Buffer, 126 | options: ShowDiffOptions = {}, 127 | ): string { 128 | let str = '' 129 | 130 | if (inferEncoding(filename) !== 'utf8') { 131 | str = 'The file format is not supported' 132 | } else { 133 | const oldContent = oldBuffer.toString('utf-8') 134 | const newContent = newBuffer.toString('utf-8') 135 | str = diffBody(oldContent, newContent, options) 136 | } 137 | 138 | 139 | const headerLength = filename.length + 4 140 | 141 | const header = chalk.yellow([ 142 | Array(headerLength).fill('=').join(''), 143 | ` ${filename} `, 144 | Array(headerLength).fill('-').join(''), 145 | ].join('\n')) 146 | const footer = chalk.yellow(Array(headerLength).fill('=').join('')) 147 | 148 | return ['\n', header, str, footer].join('\n') 149 | } 150 | 151 | export function showRemoved(filename: string): string { 152 | const str = 'The file should be remove' 153 | 154 | const headerLength = filename.length + 4 155 | 156 | const header = chalk.red([ 157 | Array(headerLength).fill('=').join(''), 158 | ` ${filename} `, 159 | Array(headerLength).fill('-').join(''), 160 | ].join('\n')) 161 | const footer = chalk.red(Array(headerLength).fill('=').join('')) 162 | 163 | return ['\n', header, str, footer].join('\n') 164 | } 165 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - eslint:recommended 3 | plugins: 4 | - '@typescript-eslint' 5 | parserOptions: 6 | project: 7 | - ./tsconfig.json 8 | - ./tests/tsconfig.json 9 | ecmaVersion: 2017 10 | sourceType: module 11 | env: 12 | es6: true 13 | node: true 14 | rules: 15 | func-call-spacing: 16 | - error 17 | - never 18 | no-unused-vars: error 19 | require-atomic-updates: 'off' 20 | eqeqeq: error 21 | no-floating-decimal: error 22 | no-var: error 23 | no-implicit-coercion: 24 | - error 25 | - allow: 26 | - '!!' 27 | no-multi-spaces: error 28 | no-useless-return: error 29 | no-undef-init: error 30 | no-trailing-spaces: error 31 | no-unneeded-ternary: error 32 | no-whitespace-before-property: error 33 | nonblock-statement-body-position: error 34 | space-infix-ops: error 35 | space-unary-ops: error 36 | switch-colon-spacing: error 37 | arrow-body-style: error 38 | arrow-spacing: error 39 | generator-star-spacing: error 40 | no-useless-computed-key: error 41 | no-useless-rename: error 42 | new-cap: error 43 | no-lonely-if: error 44 | no-console: 'off' 45 | multiline-comment-style: 46 | - error 47 | - separate-lines 48 | object-shorthand: error 49 | prefer-const: error 50 | prefer-spread: error 51 | prefer-template: error 52 | rest-spread-spacing: error 53 | sort-imports: 'off' 54 | template-curly-spacing: error 55 | yield-star-spacing: error 56 | yoda: 57 | - error 58 | - never 59 | curly: 60 | - error 61 | - multi-line 62 | - consistent 63 | semi: 64 | - error 65 | - never 66 | quotes: 67 | - error 68 | - single 69 | - avoidEscape: true 70 | dot-location: 71 | - error 72 | - property 73 | array-bracket-spacing: 74 | - error 75 | - never 76 | array-bracket-newline: 77 | - error 78 | - consistent 79 | block-spacing: 80 | - error 81 | - always 82 | brace-style: 83 | - error 84 | - 1tbs 85 | comma-dangle: 86 | - error 87 | - always-multiline 88 | comma-style: 89 | - error 90 | - last 91 | computed-property-spacing: 92 | - error 93 | - never 94 | function-paren-newline: 95 | - error 96 | - multiline 97 | implicit-arrow-linebreak: 98 | - error 99 | - beside 100 | indent: 101 | - error 102 | - 2 103 | key-spacing: 104 | - error 105 | - afterColon: true 106 | beforeColon: false 107 | mode: strict 108 | keyword-spacing: 109 | - error 110 | - before: true 111 | after: true 112 | line-comment-position: 113 | - error 114 | - above 115 | linebreak-style: 116 | - error 117 | - unix 118 | lines-between-class-members: 119 | - error 120 | - always 121 | - exceptAfterSingleLine: true 122 | no-multiple-empty-lines: 123 | - error 124 | - max: 2 125 | no-empty: 126 | - error 127 | - allowEmptyCatch: true 128 | object-curly-newline: 129 | - error 130 | - multiline: true 131 | consistent: true 132 | object-curly-spacing: 133 | - error 134 | - always 135 | one-var-declaration-per-line: 136 | - error 137 | - initializations 138 | padded-blocks: 139 | - error 140 | - never 141 | quote-props: 142 | - error 143 | - as-needed 144 | space-before-blocks: 145 | - error 146 | - always 147 | space-before-function-paren: 148 | - error 149 | - never 150 | space-in-parens: 151 | - error 152 | - never 153 | spaced-comment: 154 | - error 155 | - always 156 | arrow-parens: 157 | - error 158 | - as-needed 159 | '@typescript-eslint/semi': 160 | - error 161 | - never 162 | '@typescript-eslint/indent': 163 | - error 164 | - 2 165 | '@typescript-eslint/explicit-function-return-type': 166 | - error 167 | - allowHigherOrderFunctions: true 168 | allowExpressions: true 169 | allowTypedFunctionExpressions: true 170 | '@typescript-eslint/member-delimiter-style': 171 | - error 172 | - multiline: 173 | delimiter: none 174 | '@typescript-eslint/no-unused-vars': error 175 | '@typescript-eslint/func-call-spacing': 176 | - error 177 | - never 178 | '@typescript-eslint/ban-ts-comment': 'off' 179 | '@typescript-eslint/explicit-member-accessibility': 180 | - error 181 | - overrides: 182 | accessors: 'off' 183 | constructors: 'off' 184 | properties: no-public 185 | overrides: 186 | - files: 187 | - '**/*.ts' 188 | - '**/*.tsx' 189 | extends: 190 | - plugin:@typescript-eslint/recommended 191 | - plugin:@typescript-eslint/recommended-requiring-type-checking 192 | parser: '@typescript-eslint/parser' 193 | parserOptions: 194 | project: 195 | - ./tsconfig.json 196 | - ./tests/tsconfig.json 197 | plugins: 198 | - '@typescript-eslint' 199 | rules: 200 | '@typescript-eslint/no-unsafe-call': 'off' 201 | '@typescript-eslint/no-unsafe-member-access': 'off' 202 | '@typescript-eslint/no-unsafe-assignment': 'off' 203 | new-cap: 204 | - error 205 | - capIsNew: false 206 | '@typescript-eslint/no-floating-promises': error 207 | semi: 'off' 208 | '@typescript-eslint/semi': 209 | - error 210 | - never 211 | indent: 'off' 212 | '@typescript-eslint/indent': 213 | - error 214 | - 2 215 | '@typescript-eslint/explicit-member-accessibility': 216 | - error 217 | - overrides: 218 | accessors: 'off' 219 | constructors: 'off' 220 | properties: no-public 221 | '@typescript-eslint/explicit-function-return-type': 222 | - error 223 | - allowHigherOrderFunctions: true 224 | allowExpressions: true 225 | allowTypedFunctionExpressions: true 226 | '@typescript-eslint/member-delimiter-style': 227 | - error 228 | - multiline: 229 | delimiter: none 230 | no-unused-vars: 'off' 231 | '@typescript-eslint/no-unused-vars': error 232 | func-call-spacing: 'off' 233 | '@typescript-eslint/func-call-spacing': 234 | - error 235 | - never 236 | -------------------------------------------------------------------------------- /src/render/load-config.ts: -------------------------------------------------------------------------------- 1 | import AJV from 'ajv8' 2 | import ajvFormats from 'ajv-formats' 3 | import * as fs from 'fs-extra' 4 | import * as path from 'path' 5 | import * as JSON5 from 'json5' 6 | import * as YAML from 'js-yaml' 7 | import * as configSchema from './schema/config.json' 8 | import * as hooksSchema from './schema/hooks.json' 9 | import * as questionsSchema from './schema/questions.json' 10 | import * as templatesSchema from './schema/templates.json' 11 | import * as R from 'ramda' 12 | import { Config } from './interface/config' 13 | import { Hook } from './interface/hook' 14 | import { Question } from './interface/question' 15 | import { Template } from './interface/template' 16 | 17 | 18 | const ajv = new AJV() 19 | ajvFormats(ajv) 20 | 21 | function validate(schema: any, data: any, dataVar: string): true { 22 | const validator = ajv.compile(schema) 23 | const valid = validator(data) 24 | if (!valid) throw new Error(ajv.errorsText(validator.errors, { dataVar })) 25 | 26 | return true 27 | } 28 | 29 | async function exist(filepath): Promise { 30 | try { 31 | await fs.access(filepath) 32 | return true 33 | } catch (error) { 34 | return false 35 | } 36 | } 37 | 38 | async function readJson(filepath: string): Promise { 39 | const content = await fs.readFile(filepath, { encoding: 'utf8' }) 40 | return JSON5.parse(content) 41 | } 42 | async function readYaml(filepath: string): Promise { 43 | const content = await fs.readFile(filepath, { encoding: 'utf8' }) 44 | return YAML.load(content) as any 45 | } 46 | 47 | async function readConfig(filepath: string): Promise { 48 | const ymlFilepath = `${filepath}.yml` 49 | const jsonFilepath = `${filepath}.json` 50 | 51 | if (await exist(ymlFilepath)) { 52 | return readYaml(ymlFilepath) 53 | } else if (await exist(jsonFilepath)) { 54 | return readJson(jsonFilepath) 55 | } 56 | } 57 | 58 | const buildInLoaders = fs.readdirSync(path.join(__dirname, '../loader')) 59 | const buildInHanlders = fs.readdirSync(path.join(__dirname, '../handler')) 60 | 61 | export async function loadMiliConfig(dir: string): Promise> { 62 | const config = await readConfig(path.join(dir, 'mili')) 63 | validate(configSchema, config, 'config') 64 | 65 | if (!config.version) { 66 | const packageJson = await readJson(path.join(dir, 'package.json')) 67 | config.version = packageJson.version 68 | } 69 | 70 | if (!config.extends) config.extends = [] 71 | if (!config.loaders) config.loaders = [] 72 | 73 | config.loaders = config.loaders.map(loader => { 74 | if (typeof loader === 'string') { 75 | return { 76 | name: loader, 77 | options: {}, 78 | } 79 | } 80 | 81 | return loader 82 | }) 83 | 84 | config.loaders = config.loaders.map(loader => { 85 | if (buildInLoaders.includes(`${loader.name}.js`) || buildInLoaders.includes(loader.name)) { 86 | return { 87 | name: path.join(__dirname, '../loader', loader.name), 88 | options: loader.options, 89 | } 90 | } else { 91 | return { 92 | name: path.join(dir, 'node_modules', loader.name), 93 | options: loader.options, 94 | } 95 | } 96 | }) 97 | 98 | config.extends = config.extends.map(ext => { 99 | if (typeof ext === 'string') { 100 | return { 101 | template: ext, 102 | version: 'latest', 103 | } 104 | } 105 | 106 | return ext 107 | }) 108 | 109 | return config 110 | } 111 | 112 | export async function loadHooksConfig(dir: string): Promise { 113 | const config: Record = await readConfig(path.join(dir, 'hooks')) 114 | if (!config) return [] 115 | 116 | validate(hooksSchema, config, 'hooks') 117 | const pair = Object.entries(config) as [Hook['name'], string][] 118 | 119 | return pair.map(([name, exec]) => ({ name, exec })) 120 | } 121 | 122 | export async function loadQuestionsConfig(dir: string): Promise { 123 | const config = await readConfig(path.join(dir, 'questions')) 124 | if (!config) return [] 125 | 126 | validate(questionsSchema, config, 'questions') 127 | return config 128 | } 129 | 130 | export async function loadTemplateConfig(dir: string): Promise { 131 | const config = await readConfig(path.join(dir, 'templates')) 132 | validate(templatesSchema, config, 'templates') 133 | 134 | 135 | config.push({ 136 | path: '**', 137 | encoding: 'utf8', 138 | handlers: ['overwrite'], 139 | }) 140 | 141 | return R.unnest(config.map(item => { 142 | if (!item.encoding) item.encoding = 'utf8' 143 | if (!item.handlers || !item.handlers.length) item.handlers = ['overwrite'] 144 | 145 | item.handlers = item.handlers.map(handler => { 146 | if (typeof handler === 'string') { 147 | return { 148 | name: handler, 149 | options: {}, 150 | } 151 | } 152 | 153 | return handler 154 | }) 155 | 156 | if (item.handlers[item.handlers.length - 1].name !== 'overwrite') { 157 | item.handlers.push({ 158 | name: 'overwrite', 159 | options: {}, 160 | }) 161 | } 162 | 163 | 164 | item.handlers = item.handlers.map(handler => { 165 | if (buildInHanlders.includes(`${handler.name}.js`) || buildInHanlders.includes(handler.name)) { 166 | return { 167 | name: path.join(__dirname, '../handler', handler.name), 168 | options: handler.options, 169 | } 170 | } else { 171 | return { 172 | name: path.join(dir, 'node_modules', handler.name), 173 | options: handler.options, 174 | } 175 | } 176 | }) 177 | 178 | if (Array.isArray(item.path)) { 179 | return item.path.map(subpath => { 180 | const handler = R.clone(item) 181 | handler.path = subpath 182 | return handler 183 | }) 184 | } 185 | 186 | return item 187 | })) 188 | } 189 | 190 | export async function loadConfig(dir: string): Promise { 191 | const config = await loadMiliConfig(dir) 192 | 193 | return { 194 | ...config, 195 | hooks: await loadHooksConfig(dir), 196 | questions: await loadQuestionsConfig(dir), 197 | templates: await loadTemplateConfig(dir), 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /docs/en/cli.md: -------------------------------------------------------------------------------- 1 | # Command Line Interface 2 | 3 | Use `npx mili -h` to see more detials of cli. 4 | 5 | ## `mili init [options] [repository]` 6 | 7 | init project 8 | 9 | ### options 10 | 11 | option | default | description 12 | :---------------------------|:---------------------------------------|:-------------- 13 | `-n --app-name [app_name]` | `basename of cwd` or `progress.cwd()` | Set application name 14 | `-v --version [version]` | latest version | Set the template version. 15 | `--cwd [cwd]` | `progress.cwd()` | Set the current work directory 16 | `--no-deps` | - | Don't install template dependencies. You can save some time, if don't install dependencies. 17 | `--force` | - | Enforce command. Ignore security check. 18 | 19 | ### repository 20 | 21 | The repository of template, The supported repository types are: 22 | 23 | repository | description | example 24 | :--------------------|:--------------------------------------|:-------- 25 | `github:owner/name` | Shorthand of github repository. It is equal to https url used to clone | `npx mili init github:Val-istar-Guo/mili-template` 26 | `npm:package-name` | The template could be an npm package. Note that some file names will be ignored at the time of publish(e.g. `.gitignore`). | `npx mili init npm:mili-template` 27 | clone with HTTPS | Clone template from repository. | `npx mili init https://github.com/Val-istar-Guo/mili-template.git` 28 | clone with SSH | Clone template from repository. | `npx mili init git@github.com:Val-istar-Guo/mili-template.git` 29 | relative path | Get template from the relative path. It is useful when managing a lot of packages that use a unified template in a repository. (e.g. lerna) The path must begin with `./` or `../`. | `npx mili init ./template/module-template` 30 | absolute path | Get template from the absolute path.It is often used for testing | `npx mili ini /template/test-template` 31 | 32 | ## `mili upgrade [options]` 33 | 34 | upgrade your project if then template depended outdated. 35 | 36 | ### options 37 | 38 | option | default | description 39 | :---------------------------|:---------------------------------------|:-------------- 40 | `--cwd [cwd]` | `progress.cwd()` | Set the current work directory 41 | `--no-deps` | - | Don't install template dependencies. You can save some time, if don't install dependencies. 42 | `--force` | - | Enforce command. Ignore security check. 43 | `-r --recursive` | - | Upgrade recursive all subfolder. 44 | `--ignore [file]` | - | Folders that do not need to be searched when recursively upgrading 45 | 46 | ## `mili update [options]` 47 | 48 | Update your project file with the current version of the template 49 | 50 | ### options 51 | 52 | option | default | description 53 | :---------------------------|:---------------------------------------|:-------------- 54 | `-v --version [version]` | current template version | Set the template version 55 | `--cwd [cwd]` | `progress.cwd()` | Set the current work directory 56 | `--no-deps` | - | Don't install template dependencies. You can save some time, if don't install dependencies. 57 | `--force` | - | Enforce command. Ignore security check. 58 | `-r --recursive` | - | Upgrade recursive all subfolder. 59 | `--ignore [file]` | - | Folders that do not need to be searched when recursively upgrading 60 | 61 | ## `mili clean` 62 | 63 | Clean the cache of mili(e.g. cloned template) 64 | 65 | ## `mili outdated` 66 | 67 | Check if the file is out of date 68 | 69 | ## `mili check [options] ` 70 | 71 | Verify whether the project file meets the template,`mili check` will compile template according to the `.milirc`. 72 | Then compare the difference between the compilation result and the project file. 73 | If there is a difference, indicates that the current project file content does not meet the template requirements. 74 | 75 | `mili check` will not auto fix file. 76 | Because the error of the file content may be caused by the project developer incorrectly modifying the configuration specification or project module. 77 | 78 | You can run `mili update` to fix file errors. 79 | Before running, it is recommended to submit the code first or add it to the staging area, so that you can easily review the update differences of the project to ensure the project can working properly. 80 | 81 | You can use `mili check` with `husky` and `lint-stage`. 82 | 83 | ### 84 | 85 | Only check the files at list. If not set ``, all files of project will be checked. 86 | 87 | ### options 88 | 89 | option | default | description 90 | :---------------------------|:---------------------|:-------------- 91 | `--cwd [cwd]` | `progress.cwd()` | Set the current work directory 92 | `--no-deps` | - | Don't install template dependencies. You can save some time, if don't install dependencies. 93 | `-r --recursive` | - | Checking will recursive all subfolder. 94 | `--ignore [file]` | - | Folders that do not need to be searched when recursively checking. 95 | `-d --diff` | `false` | Show file difference, like `git diff` 96 | `--fold` | `false` | Fold unchanged code, when show file difference. Used with `--diff`. 97 | 98 | ## Template version 99 | 100 | Template version should be one of: 101 | 102 | * semantic version number: Specific template versions 103 | * `'latest'`: The latest template version 104 | * `'default'`: The default files of template or default branch of repository. Used for template development. 105 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [4.8.0](https://www.github.com/mili-project-manager/mili/compare/v4.7.5...v4.8.0) (2023-02-22) 6 | 7 | 8 | ### Features 9 | 10 | * add ramda helper for handlebars ([eff77fd](https://www.github.com/mili-project-manager/mili/commit/eff77fda49acb32b6d4f937c393260dff969fd95)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * cannot require nanoid@4 ([9d7c643](https://www.github.com/mili-project-manager/mili/commit/9d7c643ccb30a7c1e4a10690d3d7aae0b1cd35a2)) 16 | 17 | ### [4.7.5](https://www.github.com/mili-project-manager/mili/compare/v4.7.4...v4.7.5) (2022-03-23) 18 | 19 | 20 | ### Performance Improvements 21 | 22 | * upgrade dependencies and fix build error ([d72cdc6](https://www.github.com/mili-project-manager/mili/commit/d72cdc645fffd889042d7e0503fac62db3b3a502)) 23 | 24 | ### [4.7.4](https://www.github.com/mili-project-manager/mili/compare/v4.7.3...v4.7.4) (2021-11-29) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * cannot find module 'ajv/dist/compile/codegen' ([7d22e84](https://www.github.com/mili-project-manager/mili/commit/7d22e8489a1db228bda9eebfcc219319e61f031e)) 30 | 31 | ### [4.7.4](https://github.com/mili-project-manager/mili/compare/v4.7.3...v4.7.4) (2021-07-14) 32 | 33 | ### [4.7.3](https://www.github.com/mili-project-manager/mili/compare/v4.7.2...v4.7.3) (2021-07-14) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * relative path repository not work ([815f857](https://www.github.com/mili-project-manager/mili/commit/815f857544b19e9a77d6cafb365bc1d01c414822)) 39 | 40 | ### [4.7.2](https://www.github.com/mili-project-manager/mili/compare/v4.7.1...v4.7.2) (2021-07-13) 41 | 42 | 43 | ### Performance Improvements 44 | 45 | * improve the running speed of the check command ([29cb82c](https://www.github.com/mili-project-manager/mili/commit/29cb82c16a4727eb798472c90f19ac5b72431d9b)) 46 | 47 | ### [4.7.1](https://www.github.com/mili-project-manager/mili/compare/v4.7.0...v4.7.1) (2021-07-12) 48 | 49 | 50 | ### Performance Improvements 51 | 52 | * improve the running speed of the check command ([e9b5340](https://www.github.com/mili-project-manager/mili/commit/e9b53404be03eb5a86a583816de99205c69ad5be)) 53 | 54 | ## [4.7.0](https://www.github.com/mili-project-manager/mili/compare/v4.6.0...v4.7.0) (2021-06-19) 55 | 56 | 57 | ### Features 58 | 59 | * cli add -f option ([77972f4](https://www.github.com/mili-project-manager/mili/commit/77972f4750bc4f238665d087a2a1e69e3be58192)) 60 | 61 | ## [4.6.0](https://www.github.com/mili-project-manager/mili/compare/v4.5.2...v4.6.0) (2021-05-31) 62 | 63 | 64 | ### Features 65 | 66 | * add rename handler ([c3f45bc](https://www.github.com/mili-project-manager/mili/commit/c3f45bcab2c1adbecf4bfc5e6d5c0b48e9994bd6)) 67 | 68 | ### [4.5.2](https://www.github.com/mili-project-manager/mili/compare/v4.5.1...v4.5.2) (2021-05-31) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * yaml handler invalid inconsistency prompt ([d7df2c0](https://www.github.com/mili-project-manager/mili/commit/d7df2c0ddde54ad21c7fb48a4391475072fc0665)) 74 | 75 | ### [4.5.1](https://www.github.com/mili-project-manager/mili/compare/v4.5.0...v4.5.1) (2021-05-31) 76 | 77 | 78 | ### Performance Improvements 79 | 80 | * increase copy speed ([fa8054e](https://www.github.com/mili-project-manager/mili/commit/fa8054ef6ca2e49a525a73f7e6692bb292740a6c)) 81 | 82 | ## [4.5.0](https://www.github.com/mili-project-manager/mili/compare/v4.4.2...v4.5.0) (2021-05-31) 83 | 84 | 85 | ### Features 86 | 87 | * enable set up npm registry ([3ef7a04](https://www.github.com/mili-project-manager/mili/commit/3ef7a040556100abf72e9d9d8834db5ececc36f6)) 88 | 89 | ### [4.4.2](https://www.github.com/mili-project-manager/mili/compare/v4.4.1...v4.4.2) (2021-05-30) 90 | 91 | 92 | ### Bug Fixes 93 | 94 | * incorrect deletion of ignored issues ([5ad25bf](https://www.github.com/mili-project-manager/mili/commit/5ad25bf76ddad6601d97f8d7d4a66506ed29c806)) 95 | 96 | ### [4.4.1](https://www.github.com/mili-project-manager/mili/compare/v4.4.0...v4.4.1) (2021-05-30) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * cannot recognize ssh repo ([8bbfc14](https://www.github.com/mili-project-manager/mili/commit/8bbfc149e7753d42d09224a5f1ae98820df53914)) 102 | * migration not ignored ([a6ccd77](https://www.github.com/mili-project-manager/mili/commit/a6ccd77fe84b8db10d0128bce7e1896a0575f948)) 103 | * mili update unable delete files ([8c007c1](https://www.github.com/mili-project-manager/mili/commit/8c007c15f6c47dd3db32c5e1cf3aac5851bd152b)) 104 | * relative path not available ([58bdf92](https://www.github.com/mili-project-manager/mili/commit/58bdf92f5e2bf7ec08710b9e608da5161cb1d1d0)) 105 | 106 | 107 | ### Performance Improvements 108 | 109 | * not install deps for local repo ([8aa9c46](https://www.github.com/mili-project-manager/mili/commit/8aa9c4658c9234f7dfd57da0269c00311582db2a)) 110 | 111 | ## [4.4.0](https://www.github.com/mili-project-manager/mili/compare/v4.3.0...v4.4.0) (2021-05-30) 112 | 113 | 114 | ### Features 115 | 116 | * add init handler ([ed0e41d](https://www.github.com/mili-project-manager/mili/commit/ed0e41d4b2830ddc04e3e81a515a52a62d07c6e3)) 117 | 118 | 119 | ### Bug Fixes 120 | 121 | * cached files are not deleted ([cb998fb](https://www.github.com/mili-project-manager/mili/commit/cb998fbe55b4cf128d9c013616b7f8e0a6902baf)) 122 | * glob cannot match the dotfiles ([f704102](https://www.github.com/mili-project-manager/mili/commit/f704102690763144f415177fe474070a91b1785d)) 123 | 124 | ## [4.3.0](https://www.github.com/mili-project-manager/mili/compare/v4.2.5...v4.3.0) (2021-05-30) 125 | 126 | 127 | ### Features 128 | 129 | * add `eval` option to delete handler and ignore handler ([c4c062c](https://www.github.com/mili-project-manager/mili/commit/c4c062cd576db1a7bb9fb0cc76978c67d1a08bc9)) 130 | * add exist handler ([8223c25](https://www.github.com/mili-project-manager/mili/commit/8223c256d6c625c8d9f11efadf78205f5d14b54d)) 131 | * add markdown-section loader ([b07df94](https://www.github.com/mili-project-manager/mili/commit/b07df9412d4ff5b9d5c782b9b99be26286bb11de)) 132 | * add repository stack to resource ([d85e58c](https://www.github.com/mili-project-manager/mili/commit/d85e58c3260dc29f87e3a6cc076c145dc2d9f509)) 133 | * **handlebars:** add equals helper ([44efaa8](https://www.github.com/mili-project-manager/mili/commit/44efaa85682eea39e0681463c4949b04c0b12903)) 134 | * **handlebars:** add head helper ([c7910f1](https://www.github.com/mili-project-manager/mili/commit/c7910f18250ff5bc8430d413a1d1e571df6763e7)) 135 | * template.path can be an array ([89e4f1e](https://www.github.com/mili-project-manager/mili/commit/89e4f1e34a40a452a7609fc5307ed34cd6852da2)) 136 | 137 | 138 | ### Bug Fixes 139 | 140 | * unable auto create folder ([cc53099](https://www.github.com/mili-project-manager/mili/commit/cc53099429b6898aa91669f2cad27991b84c20a4)) 141 | 142 | ### [4.2.5](https://www.github.com/mili-project-manager/mili/compare/v4.2.4...v4.2.5) (2021-05-28) 143 | 144 | 145 | ### Bug Fixes 146 | 147 | * loop copy ([eb34bd7](https://www.github.com/mili-project-manager/mili/commit/eb34bd7e96caf3ea18fac2eabf360867b6ee4335)) 148 | 149 | ### [4.2.4](https://www.github.com/mili-project-manager/mili/compare/v4.2.3...v4.2.4) (2021-05-28) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * loop copy ([a624773](https://www.github.com/mili-project-manager/mili/commit/a62477321e5edf552f0e984ea5403a40fe0260ee)) 155 | 156 | ### [4.2.3](https://www.github.com/mili-project-manager/mili/compare/v4.2.2...v4.2.3) (2021-05-28) 157 | 158 | 159 | ### Bug Fixes 160 | 161 | * errors is not iterable ([06cd235](https://www.github.com/mili-project-manager/mili/commit/06cd235ce056cc3128e1ab37dfc9f0b2f3baf730)) 162 | 163 | ### [4.2.2](https://www.github.com/mili-project-manager/mili/compare/v4.2.1...v4.2.2) (2021-05-28) 164 | 165 | 166 | ### Bug Fixes 167 | 168 | * cannot copy to a subdirectory of itself ([1f931b9](https://www.github.com/mili-project-manager/mili/commit/1f931b9055e3608fbbf9d1f780a4b2cdccec4313)) 169 | * cannot download specify version template ([eaf6551](https://www.github.com/mili-project-manager/mili/commit/eaf65517a0402c0aa85920e55d495712185f2112)) 170 | 171 | ### [4.2.1](https://www.github.com/mili-project-manager/mili/compare/v4.2.0...v4.2.1) (2021-05-28) 172 | 173 | 174 | ### Bug Fixes 175 | 176 | * wrong render order ([fdefa2d](https://www.github.com/mili-project-manager/mili/commit/fdefa2dede4decf339dfb6a9203008dbead4d227)) 177 | 178 | ## [4.2.0](https://www.github.com/mili-project-manager/mili/compare/v4.1.0...v4.2.0) (2021-05-28) 179 | 180 | 181 | ### Features 182 | 183 | * enable progress bar for upgrade, update and check ([0e2311d](https://www.github.com/mili-project-manager/mili/commit/0e2311d77930b92f1dc30d415f350f81b12b7c55)) 184 | 185 | 186 | ### Bug Fixes 187 | 188 | * milirc.template missing 'npm:' prefix ([eb65842](https://www.github.com/mili-project-manager/mili/commit/eb658429180ff688c0abf53964aeee47283ea485)) 189 | * missing extends.when() arguments ([2b9bd20](https://www.github.com/mili-project-manager/mili/commit/2b9bd20724f37a93f7ecd6c19f632224bec04146)) 190 | 191 | ## [4.1.0](https://www.github.com/mili-project-manager/mili/compare/v4.0.1...v4.1.0) (2021-05-28) 192 | 193 | 194 | ### Features 195 | 196 | * merge-json add spaces option ([4291ec0](https://www.github.com/mili-project-manager/mili/commit/4291ec0bd5ac5794b1e8d411b539dab90107b5a4)) 197 | 198 | ### [4.0.1](https://www.github.com/mili-project-manager/mili/compare/v4.0.0...v4.0.1) (2021-05-28) 199 | 200 | 201 | ### Bug Fixes 202 | 203 | * bad temporary directory path ([3d7d9f1](https://www.github.com/mili-project-manager/mili/commit/3d7d9f159fc3dd08c5e660cc7d68f9200a31926f)) 204 | * cannot run loader when run mili init ([1b4bda9](https://www.github.com/mili-project-manager/mili/commit/1b4bda9a304a498b8d92a4b27f30db9796a04d36)) 205 | * cannot run without hooks config ([240e59b](https://www.github.com/mili-project-manager/mili/commit/240e59b6f197ef9e68a71f70a39e6549660240d5)) 206 | * mili.extends cannot set as string ([58da479](https://www.github.com/mili-project-manager/mili/commit/58da4797e1befb17c15b168bdbbe55aa171e52e4)) 207 | 208 | 209 | ### Performance Improvements 210 | 211 | * increase the running speed and add the progress bar ([f6c9281](https://www.github.com/mili-project-manager/mili/commit/f6c928148265a49934b4cbf87665f05450ee1f63)) 212 | 213 | ## [4.0.0](https://www.github.com/mili-project-manager/mili/compare/v3.10.2...v4.0.0) (2021-05-27) 214 | 215 | 216 | ### ⚠ BREAKING CHANGES 217 | 218 | * The template and handler of mili@3 is not compatible. 219 | * Node.js 12 LTS is now the minimum required version. 220 | 221 | ### Features 222 | 223 | * bump Node.js version requirement to 12.13.0 ([33f2271](https://www.github.com/mili-project-manager/mili/commit/33f2271b0c8e21031bcec4b085ea8a64b07e0b7a)) 224 | 225 | 226 | ### Code Refactoring 227 | 228 | * redesign the project ([dc4b72d](https://www.github.com/mili-project-manager/mili/commit/dc4b72d13aecebae7d4cba5ae175def83f1a0002)), closes [#90](https://www.github.com/mili-project-manager/mili/issues/90) 229 | 230 | ### [3.10.2](https://github.com/Val-istar-Guo/mili/compare/v3.10.1...v3.10.2) (2021-04-26) 231 | 232 | 233 | ### Bug Fixes 234 | 235 | * cannot find inquirer ([278e074](https://github.com/Val-istar-Guo/mili/commit/278e074a4c3239f309d47120fd062b5b89327970)) 236 | 237 | ### [3.10.1](https://github.com/Val-istar-Guo/mili/compare/v3.10.0...v3.10.1) (2021-04-25) 238 | 239 | 240 | ### Bug Fixes 241 | 242 | * cannot find validate-npm-package-name ([0213f7f](https://github.com/Val-istar-Guo/mili/commit/0213f7fdeee624256fa3f517dd285289ee40339c)) 243 | * schema warning ([404c218](https://github.com/Val-istar-Guo/mili/commit/404c218f7bb57b0f2dece468590d9f27bcaf240d)) 244 | 245 | ## [3.10.0](https://github.com/Val-istar-Guo/mili/compare/v3.9.2...v3.10.0) (2021-04-24) 246 | 247 | 248 | ### Bug Fixes 249 | 250 | * async commander support ([619fe10](https://github.com/Val-istar-Guo/mili/commit/619fe1070034819b638304f70e1e1cdca7eb7c5b)) 251 | * upgrade template ([cb3cb73](https://github.com/Val-istar-Guo/mili/commit/cb3cb7320f8febbc3f61362792f2a5c5f343c462)) 252 | 253 | ### [3.9.2](https://github.com/Val-istar-Guo/mili/compare/v3.9.1...v3.9.2) (2020-10-22) 254 | 255 | 256 | ### Bug Fixes 257 | 258 | * .git isn't required for git url ([c5421af](https://github.com/Val-istar-Guo/mili/commit/c5421af66f8bb750bc7bea7c29e67039d3bbf433)), closes [#89](https://github.com/Val-istar-Guo/mili/issues/89) 259 | 260 | ### [3.9.1](https://github.com/Val-istar-Guo/mili/compare/v3.9.0...v3.9.1) (2020-07-18) 261 | 262 | ## [3.9.0](https://github.com/Val-istar-Guo/mili/compare/v3.8.2...v3.9.0) (2020-04-27) 263 | 264 | 265 | ### Features 266 | 267 | * cache the templates in users home dir ([59efb0a](https://github.com/Val-istar-Guo/mili/commit/59efb0aa46c9a881e9b504f3ea50a1d6588c7551)) 268 | 269 | 270 | ### Bug Fixes 271 | 272 | * update version not support latest and default ([8d1c9b1](https://github.com/Val-istar-Guo/mili/commit/8d1c9b1d224d6e2b74c550b6001102a9648f72a1)) 273 | 274 | ### [3.8.2](https://github.com/Val-istar-Guo/mili/compare/v3.8.1...v3.8.2) (2020-04-24) 275 | 276 | 277 | ### Bug Fixes 278 | 279 | * git on a directory that does not exist ([3008816](https://github.com/Val-istar-Guo/mili/commit/300881690138b1d8c764f223c7ccef094f2cae23)) 280 | 281 | ### [3.8.1](https://github.com/Val-istar-Guo/mili/compare/v3.8.0...v3.8.1) (2020-03-07) 282 | 283 | 284 | ### Bug Fixes 285 | 286 | * git error when run mili check in pre-commit hook ([d527377](https://github.com/Val-istar-Guo/mili/commit/d5273773806737e6b7fef67a03a3627842296a3a)), closes [#87](https://github.com/Val-istar-Guo/mili/issues/87) 287 | 288 | ## [3.8.0](https://github.com/Val-istar-Guo/mili/compare/v3.6.0...v3.8.0) (2020-03-01) 289 | 290 | 291 | ### Features 292 | 293 | * support merge eslintignore ([ace86ba](https://github.com/Val-istar-Guo/mili/commit/ace86ba3a41481c1dd373b8fbeb99d9491f4f3af)) 294 | * warn user when template upgrade a major version ([f8062a6](https://github.com/Val-istar-Guo/mili/commit/f8062a6054d086f141100bfc46aa8359595ede69)) 295 | 296 | ## [3.7.0](https://github.com/Val-istar-Guo/mili/compare/v3.6.0...v3.7.0) (2019-07-25) 297 | 298 | 299 | ### Features 300 | 301 | * warn user when template upgrade a major version ([f8062a6](https://github.com/Val-istar-Guo/mili/commit/f8062a6)) 302 | 303 | 304 | 305 | ## [3.6.0](https://github.com/Val-istar-Guo/mili/compare/v3.5.0...v3.6.0) (2019-07-19) 306 | 307 | 308 | ### Bug Fixes 309 | 310 | * unable get ref from project repository ([721017e](https://github.com/Val-istar-Guo/mili/commit/721017e)) 311 | 312 | 313 | ### Features 314 | 315 | * **handler:** support merge .babelrc ([3dabcca](https://github.com/Val-istar-Guo/mili/commit/3dabcca)) 316 | 317 | 318 | ### Tests 319 | 320 | * extract area missing tag test ([96b236a](https://github.com/Val-istar-Guo/mili/commit/96b236a)) 321 | * test for ejs handler ([79c471a](https://github.com/Val-istar-Guo/mili/commit/79c471a)) 322 | * test for merge comment in ignore file ([5349c4c](https://github.com/Val-istar-Guo/mili/commit/5349c4c)) 323 | 324 | 325 | 326 | ## [3.5.0](https://github.com/Val-istar-Guo/mili/compare/v3.4.0...v3.5.0) (2019-07-08) 327 | 328 | 329 | ### Features 330 | 331 | * sigle file check ([c026c2f](https://github.com/Val-istar-Guo/mili/commit/c026c2f)) 332 | 333 | 334 | ### Tests 335 | 336 | * add test from execte func in subproject ([097e3f0](https://github.com/Val-istar-Guo/mili/commit/097e3f0)) 337 | 338 | 339 | 340 | ## [3.4.0](https://github.com/Val-istar-Guo/mili/compare/v3.2.0...v3.4.0) (2019-07-05) 341 | 342 | 343 | ### Bug Fixes 344 | 345 | * mili@1 error should before config valiator ([56edb92](https://github.com/Val-istar-Guo/mili/commit/56edb92)) 346 | * missing validation of the handler field ([2a23e53](https://github.com/Val-istar-Guo/mili/commit/2a23e53)) 347 | * **handler:** misleading error message ([5ffb22f](https://github.com/Val-istar-Guo/mili/commit/5ffb22f)) 348 | * **question:** old fields lost when incremental question ([a391316](https://github.com/Val-istar-Guo/mili/commit/a391316)) 349 | * npm outdated not work ([c717cf5](https://github.com/Val-istar-Guo/mili/commit/c717cf5)) 350 | 351 | 352 | ### Features 353 | 354 | * **handler:** new handler used for rename file ([12cb12b](https://github.com/Val-istar-Guo/mili/commit/12cb12b)) 355 | * **handler:** support merge .npmrc ([2af02f1](https://github.com/Val-istar-Guo/mili/commit/2af02f1)) 356 | 357 | 358 | ### Tests 359 | 360 | * rename test file name ([2ea0d1d](https://github.com/Val-istar-Guo/mili/commit/2ea0d1d)) 361 | 362 | 363 | 364 | ## [3.3.0](https://github.com/Val-istar-Guo/mili/compare/v3.2.0...v3.3.0) (2019-07-05) 365 | 366 | 367 | ### Bug Fixes 368 | 369 | * mili@1 error should before config valiator ([56edb92](https://github.com/Val-istar-Guo/mili/commit/56edb92)) 370 | * missing validation of the handler field ([2a23e53](https://github.com/Val-istar-Guo/mili/commit/2a23e53)) 371 | * **handler:** misleading error message ([5ffb22f](https://github.com/Val-istar-Guo/mili/commit/5ffb22f)) 372 | * **question:** old fields lost when incremental question ([a391316](https://github.com/Val-istar-Guo/mili/commit/a391316)) 373 | * npm outdated not work ([c717cf5](https://github.com/Val-istar-Guo/mili/commit/c717cf5)) 374 | 375 | 376 | ### Features 377 | 378 | * **handler:** new handler used for rename file ([12cb12b](https://github.com/Val-istar-Guo/mili/commit/12cb12b)) 379 | * **handler:** support merge .npmrc ([2af02f1](https://github.com/Val-istar-Guo/mili/commit/2af02f1)) 380 | 381 | 382 | ### Tests 383 | 384 | * rename test file name ([a8f38e1](https://github.com/Val-istar-Guo/mili/commit/a8f38e1)) 385 | 386 | 387 | 388 | ## [3.2.0](https://github.com/Val-istar-Guo/mili/compare/v3.1.0...v3.2.0) (2019-07-04) 389 | 390 | 391 | ### Bug Fixes 392 | 393 | * log of recursive execte only used for check ([0a20d88](https://github.com/Val-istar-Guo/mili/commit/0a20d88)) 394 | * template.path error when reload npm template config ([cd7ed47](https://github.com/Val-istar-Guo/mili/commit/cd7ed47)) 395 | * unable recursive upgrade ([79bd644](https://github.com/Val-istar-Guo/mili/commit/79bd644)) 396 | 397 | 398 | ### Features 399 | 400 | * enable cache npm template ([d9708e9](https://github.com/Val-istar-Guo/mili/commit/d9708e9)) 401 | 402 | 403 | 404 | ## [3.1.0](https://github.com/Val-istar-Guo/mili/compare/v3.0.0...v3.1.0) (2019-07-04) 405 | 406 | 407 | ### Bug Fixes 408 | 409 | * missing type when validate question.type ([394af6b](https://github.com/Val-istar-Guo/mili/commit/394af6b)) 410 | * should exit when checkout an unexist version ([08cb107](https://github.com/Val-istar-Guo/mili/commit/08cb107)) 411 | * the path error in error message of merge handler ([7610ac9](https://github.com/Val-istar-Guo/mili/commit/7610ac9)) 412 | * **handler:** difference between the merge results of init and upgrade ([209a7ee](https://github.com/Val-istar-Guo/mili/commit/209a7ee)) 413 | * **handler:** ignoreWhen running is reversed ([59682a7](https://github.com/Val-istar-Guo/mili/commit/59682a7)) 414 | 415 | 416 | ### Features 417 | 418 | * add default version used for template development ([41c9614](https://github.com/Val-istar-Guo/mili/commit/41c9614)) 419 | 420 | 421 | 422 | ## [3.0.0](https://github.com/Val-istar-Guo/mili/compare/v2.9.0...v3.0.0) (2019-07-03) 423 | 424 | 425 | ### Bug Fixes 426 | 427 | * different meanings of github repository shorthand ([775356d](https://github.com/Val-istar-Guo/mili/commit/775356d)), closes [#66](https://github.com/Val-istar-Guo/mili/issues/66) 428 | 429 | 430 | ### Features 431 | 432 | * support for delete file ([634c808](https://github.com/Val-istar-Guo/mili/commit/634c808)), closes [#29](https://github.com/Val-istar-Guo/mili/issues/29) 433 | * support replace effect function to another ([c66fe03](https://github.com/Val-istar-Guo/mili/commit/c66fe03)), closes [#71](https://github.com/Val-istar-Guo/mili/issues/71) 434 | 435 | 436 | ### refactor 437 | 438 | * rewrite code with typescript ([c7d7cff](https://github.com/Val-istar-Guo/mili/commit/c7d7cff)), closes [#70](https://github.com/Val-istar-Guo/mili/issues/70) 439 | 440 | 441 | ### BREAKING CHANGES 442 | 443 | * Never support github path like `xxx/xxx`.Please use `github:xxx/xxx` instead. 444 | * Rename milirc.interaction to milirc.question; Rename `afterInit`, `afterUpgrade` 445 | and `afterUpdate` to `initialized`, `updated` and `upgraded`; Change parameters of handler. 446 | 447 | 448 | 449 | 450 | # [2.9.0](https://github.com/Val-istar-Guo/mili/compare/v2.8.0...v2.9.0) (2019-06-13) 451 | 452 | 453 | ### Bug Fixes 454 | 455 | * different output when project file is same ([94e6827](https://github.com/Val-istar-Guo/mili/commit/94e6827)) 456 | 457 | 458 | ### Features 459 | 460 | * check for unsafe file changes ([83753fa](https://github.com/Val-istar-Guo/mili/commit/83753fa)), closes [#72](https://github.com/Val-istar-Guo/mili/issues/72) 461 | 462 | 463 | 464 | 465 | # [2.8.0](https://github.com/Val-istar-Guo/mili/compare/v2.7.3...v2.8.0) (2019-06-10) 466 | 467 | 468 | ### Bug Fixes 469 | 470 | * --ignore is not work ([fec6d0c](https://github.com/Val-istar-Guo/mili/commit/fec6d0c)), closes [#76](https://github.com/Val-istar-Guo/mili/issues/76) 471 | 472 | 473 | ### Features 474 | 475 | * **handler:** merge handler support yaml/yml file ([57c9dd9](https://github.com/Val-istar-Guo/mili/commit/57c9dd9)) 476 | * **handlers:** merge handler support .npmignore file ([b673770](https://github.com/Val-istar-Guo/mili/commit/b673770)), closes [#75](https://github.com/Val-istar-Guo/mili/issues/75) 477 | 478 | 479 | 480 | 481 | ## [2.7.3](https://github.com/Val-istar-Guo/mili/compare/v2.7.2...v2.7.3) (2019-06-10) 482 | 483 | 484 | ### Bug Fixes 485 | 486 | * the npm template of organization cannot download ([5342f63](https://github.com/Val-istar-Guo/mili/commit/5342f63)), closes [#73](https://github.com/Val-istar-Guo/mili/issues/73) 487 | 488 | 489 | 490 | 491 | ## [2.7.2](https://github.com/Val-istar-Guo/mili/compare/v2.7.1...v2.7.2) (2019-05-29) 492 | 493 | 494 | 495 | 496 | ## [2.7.1](https://github.com/Val-istar-Guo/mili/compare/v2.7.0...v2.7.1) (2019-05-26) 497 | 498 | 499 | 500 | 501 | # [2.7.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.6.1...v2.7.0) (2019-05-16) 502 | 503 | 504 | ### Features 505 | 506 | * create dir or throw error when cwd unexisted ([89db505](https://github.com/Val-istar-Guo/vue-boilerplate/commit/89db505)), closes [#67](https://github.com/Val-istar-Guo/vue-boilerplate/issues/67) 507 | 508 | 509 | 510 | 511 | ## [2.6.1](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.6.0...v2.6.1) (2019-05-15) 512 | 513 | 514 | ### Bug Fixes 515 | 516 | * ./ is missing in relative path ([806f14b](https://github.com/Val-istar-Guo/vue-boilerplate/commit/806f14b)), closes [#65](https://github.com/Val-istar-Guo/vue-boilerplate/issues/65) 517 | * cosmiconfig result should not be changed directly ([99aa1f9](https://github.com/Val-istar-Guo/vue-boilerplate/commit/99aa1f9)), closes [#65](https://github.com/Val-istar-Guo/vue-boilerplate/issues/65) 518 | 519 | 520 | 521 | 522 | # [2.6.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.5.0...v2.6.0) (2019-05-13) 523 | 524 | 525 | ### Features 526 | 527 | * add cwd setting in cli ([ff1898d](https://github.com/Val-istar-Guo/vue-boilerplate/commit/ff1898d)), closes [#63](https://github.com/Val-istar-Guo/vue-boilerplate/issues/63) 528 | * handler can remove file ([82f9dc6](https://github.com/Val-istar-Guo/vue-boilerplate/commit/82f9dc6)), closes [#61](https://github.com/Val-istar-Guo/vue-boilerplate/issues/61) 529 | 530 | 531 | 532 | 533 | # [2.5.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.4.0...v2.5.0) (2019-05-05) 534 | 535 | 536 | ### Bug Fixes 537 | 538 | * missing require('utils/log') ([18ff879](https://github.com/Val-istar-Guo/vue-boilerplate/commit/18ff879)) 539 | 540 | 541 | ### Features 542 | 543 | * --force needed only on the woking dir is not clean ([d56225b](https://github.com/Val-istar-Guo/vue-boilerplate/commit/d56225b)), closes [#46](https://github.com/Val-istar-Guo/vue-boilerplate/issues/46) 544 | * **hooks:** use function to define hook ([3ae83e7](https://github.com/Val-istar-Guo/vue-boilerplate/commit/3ae83e7)), closes [#57](https://github.com/Val-istar-Guo/vue-boilerplate/issues/57) 545 | 546 | 547 | 548 | 549 | # [2.4.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.3.0...v2.4.0) (2019-04-29) 550 | 551 | 552 | ### Bug Fixes 553 | 554 | * **npm:** the path error when upgrade template from npm ([c5d8882](https://github.com/Val-istar-Guo/vue-boilerplate/commit/c5d8882)) 555 | 556 | 557 | ### Features 558 | 559 | * add option to prevent install dependencies ([69c3685](https://github.com/Val-istar-Guo/vue-boilerplate/commit/69c3685)), closes [#43](https://github.com/Val-istar-Guo/vue-boilerplate/issues/43) 560 | * new option to upgrade recursive ([cd7321a](https://github.com/Val-istar-Guo/vue-boilerplate/commit/cd7321a)), closes [#47](https://github.com/Val-istar-Guo/vue-boilerplate/issues/47) 561 | 562 | 563 | 564 | 565 | # [2.3.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.2.0...v2.3.0) (2019-04-25) 566 | 567 | 568 | ### Bug Fixes 569 | 570 | * log is undefined ([94d8afd](https://github.com/Val-istar-Guo/vue-boilerplate/commit/94d8afd)), closes [#45](https://github.com/Val-istar-Guo/vue-boilerplate/issues/45) 571 | * relative path will be saved as absolute path in .milirc ([e15c844](https://github.com/Val-istar-Guo/vue-boilerplate/commit/e15c844)), closes [#48](https://github.com/Val-istar-Guo/vue-boilerplate/issues/48) 572 | * throw error when main is unset at package.json ([033abb4](https://github.com/Val-istar-Guo/vue-boilerplate/commit/033abb4)), closes [#44](https://github.com/Val-istar-Guo/vue-boilerplate/issues/44) 573 | 574 | 575 | ### Features 576 | 577 | * could load template from npm ([10d06f9](https://github.com/Val-istar-Guo/vue-boilerplate/commit/10d06f9)), closes [#37](https://github.com/Val-istar-Guo/vue-boilerplate/issues/37) 578 | 579 | 580 | 581 | 582 | # [2.2.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.1.1...v2.2.0) (2019-04-14) 583 | 584 | 585 | ### Bug Fixes 586 | 587 | * **handler:** field missing when merging .gitignore ([93d1adc](https://github.com/Val-istar-Guo/vue-boilerplate/commit/93d1adc)) 588 | 589 | 590 | ### Features 591 | 592 | * **handlers:** add ejs handler ([c6fb9ac](https://github.com/Val-istar-Guo/vue-boilerplate/commit/c6fb9ac)) 593 | 594 | 595 | 596 | 597 | ## [2.1.1](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.1.0...v2.1.1) (2019-03-03) 598 | 599 | 600 | ### Bug Fixes 601 | 602 | * no require check-params ([a821814](https://github.com/Val-istar-Guo/vue-boilerplate/commit/a821814)) 603 | 604 | 605 | 606 | 607 | # [2.1.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v2.0.0...v2.1.0) (2019-03-03) 608 | 609 | 610 | ### Bug Fixes 611 | 612 | * files that upgrade type is keep was covered when upgrade and update ([c1987da](https://github.com/Val-istar-Guo/vue-boilerplate/commit/c1987da)) 613 | 614 | 615 | ### Features 616 | 617 | * auto upgrade milirc file form v1 to v2 ([a1e18e0](https://github.com/Val-istar-Guo/vue-boilerplate/commit/a1e18e0)) 618 | * check template engines and throw error ([33bd1df](https://github.com/Val-istar-Guo/vue-boilerplate/commit/33bd1df)) 619 | 620 | 621 | 622 | 623 | # [2.0.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.10.0...v2.0.0) (2019-03-03) 624 | 625 | 626 | ### Bug Fixes 627 | 628 | * **copy:** reset repository after cpoyed ([10d5118](https://github.com/Val-istar-Guo/vue-boilerplate/commit/10d5118)) 629 | * **update:** the log is printed as 'upgrade' ([49d1a35](https://github.com/Val-istar-Guo/vue-boilerplate/commit/49d1a35)) 630 | 631 | 632 | ### Code Refactoring 633 | 634 | * split module functionality to reduce duplicate code ([d56137b](https://github.com/Val-istar-Guo/vue-boilerplate/commit/d56137b)) 635 | 636 | 637 | ### Features 638 | 639 | * **clone:** try clone if cannot pull repository ([22ccbfb](https://github.com/Val-istar-Guo/vue-boilerplate/commit/22ccbfb)) 640 | * **command:** new command used to check template is outdated ([e707f67](https://github.com/Val-istar-Guo/vue-boilerplate/commit/e707f67)), closes [#38](https://github.com/Val-istar-Guo/vue-boilerplate/issues/38) 641 | * **merge-handler:** support merge gitignore file ([650ec83](https://github.com/Val-istar-Guo/vue-boilerplate/commit/650ec83)) 642 | * get information by interaction ([5f3057c](https://github.com/Val-istar-Guo/vue-boilerplate/commit/5f3057c)), closes [#17](https://github.com/Val-istar-Guo/vue-boilerplate/issues/17) 643 | 644 | 645 | ### BREAKING CHANGES 646 | 647 | * View structure and milirc config structure was changed.Template need to be upgraded 648 | to support new view.And project need to change milirc manually. 649 | 650 | 651 | 652 | 653 | # [1.10.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.9.0...v1.10.0) (2018-12-11) 654 | 655 | 656 | ### Features 657 | 658 | * install template dependencies ([8ae909e](https://github.com/Val-istar-Guo/vue-boilerplate/commit/8ae909e)) 659 | 660 | 661 | 662 | 663 | # [1.9.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.8.0...v1.9.0) (2018-12-03) 664 | 665 | 666 | ### Features 667 | 668 | * use glob to match rule.path ([bf52e6d](https://github.com/Val-istar-Guo/vue-boilerplate/commit/bf52e6d)), closes [#23](https://github.com/Val-istar-Guo/vue-boilerplate/issues/23) 669 | * relative path for template ([f62fd86](https://github.com/Val-istar-Guo/vue-boilerplate/commit/f62fd86)) 670 | 671 | 672 | 673 | 674 | # [1.8.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.7.0...v1.8.0) (2018-11-30) 675 | 676 | 677 | ### Features 678 | 679 | * **init:** shorthand for the github repository uri ([35cb74b](https://github.com/Val-istar-Guo/vue-boilerplate/commit/35cb74b)) 680 | 681 | 682 | 683 | 684 | # [1.7.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.6.0...v1.7.0) (2018-11-29) 685 | 686 | 687 | ### Bug Fixes 688 | 689 | * unable init empty folder ([4242bed](https://github.com/Val-istar-Guo/vue-boilerplate/commit/4242bed)) 690 | 691 | 692 | ### Features 693 | 694 | * access mili commands programmatically from javascript ([0366505](https://github.com/Val-istar-Guo/vue-boilerplate/commit/0366505)), closes [#33](https://github.com/Val-istar-Guo/vue-boilerplate/issues/33) 695 | 696 | 697 | 698 | 699 | # [1.6.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.5.0...v1.6.0) (2018-11-06) 700 | 701 | 702 | ### Features 703 | 704 | * add project status check before init, upgrade, update ([b42b1cb](https://github.com/Val-istar-Guo/vue-boilerplate/commit/b42b1cb)), closes [#26](https://github.com/Val-istar-Guo/vue-boilerplate/issues/26) 705 | * add template control option ([de87d2d](https://github.com/Val-istar-Guo/vue-boilerplate/commit/de87d2d)), closes [#21](https://github.com/Val-istar-Guo/vue-boilerplate/issues/21) 706 | 707 | 708 | 709 | 710 | # [1.5.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.4.0...v1.5.0) (2018-11-05) 711 | 712 | 713 | ### Features 714 | 715 | * add hooks ([012f90e](https://github.com/Val-istar-Guo/vue-boilerplate/commit/012f90e)), closes [#31](https://github.com/Val-istar-Guo/vue-boilerplate/issues/31) 716 | 717 | 718 | 719 | 720 | # [1.4.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.3.3...v1.4.0) (2018-11-04) 721 | 722 | 723 | ### Bug Fixes 724 | 725 | * get latest version of template when run update ([5d74d50](https://github.com/Val-istar-Guo/vue-boilerplate/commit/5d74d50)) 726 | 727 | 728 | ### Features 729 | 730 | * add the command clean cache ([4d7c6df](https://github.com/Val-istar-Guo/vue-boilerplate/commit/4d7c6df)), closes [#25](https://github.com/Val-istar-Guo/vue-boilerplate/issues/25) 731 | * check the mili version of template ([b94cd59](https://github.com/Val-istar-Guo/vue-boilerplate/commit/b94cd59)), closes [#30](https://github.com/Val-istar-Guo/vue-boilerplate/issues/30) 732 | 733 | 734 | 735 | 736 | ## [1.3.3](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.3.2...v1.3.3) (2018-11-03) 737 | 738 | 739 | ### Bug Fixes 740 | 741 | * extract unexpect string when cannot find tag ([2c16b3c](https://github.com/Val-istar-Guo/vue-boilerplate/commit/2c16b3c)) 742 | 743 | 744 | 745 | 746 | ## [1.3.2](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.3.1...v1.3.2) (2018-11-02) 747 | 748 | 749 | ### Bug Fixes 750 | 751 | * throwError is undefined ([77b6ec2](https://github.com/Val-istar-Guo/vue-boilerplate/commit/77b6ec2)) 752 | 753 | 754 | 755 | 756 | ## [1.3.1](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.3.0...v1.3.1) (2018-10-30) 757 | 758 | 759 | ### Bug Fixes 760 | 761 | * add 'exist' to enum of valid upgrade type ([37385f4](https://github.com/Val-istar-Guo/vue-boilerplate/commit/37385f4)) 762 | 763 | 764 | 765 | 766 | # [1.3.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.2.1...v1.3.0) (2018-10-29) 767 | 768 | 769 | ### Bug Fixes 770 | 771 | * **encoding:** encoding for binary file ([a50482c](https://github.com/Val-istar-Guo/vue-boilerplate/commit/a50482c)) 772 | 773 | 774 | ### Features 775 | 776 | * add new upgrade type 'exist' ([4df7db6](https://github.com/Val-istar-Guo/vue-boilerplate/commit/4df7db6)), closes [#27](https://github.com/Val-istar-Guo/vue-boilerplate/issues/27) 777 | 778 | 779 | 780 | 781 | ## [1.2.1](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.2.0...v1.2.1) (2018-10-26) 782 | 783 | 784 | ### Bug Fixes 785 | 786 | * **clone:** cannot get includes of undefined ([ab72b32](https://github.com/Val-istar-Guo/vue-boilerplate/commit/ab72b32)) 787 | * **merge:** missing a comma ([b12d955](https://github.com/Val-istar-Guo/vue-boilerplate/commit/b12d955)) 788 | 789 | 790 | 791 | 792 | # [1.2.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.1.4...v1.2.0) (2018-10-25) 793 | 794 | 795 | ### Bug Fixes 796 | 797 | * **clone:** version order error ([c9ee866](https://github.com/Val-istar-Guo/vue-boilerplate/commit/c9ee866)) 798 | * **handler:** unable to recognize functional handler ([6b0000a](https://github.com/Val-istar-Guo/vue-boilerplate/commit/6b0000a)) 799 | 800 | 801 | ### Features 802 | 803 | * **handler:** new handler to get content area from project file ([217267a](https://github.com/Val-istar-Guo/vue-boilerplate/commit/217267a)), closes [#22](https://github.com/Val-istar-Guo/vue-boilerplate/issues/22) 804 | 805 | 806 | 807 | 808 | ## [1.1.4](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.1.3...v1.1.4) (2018-10-25) 809 | 810 | 811 | ### Bug Fixes 812 | 813 | * **copy:** commentator.extnames is undefined ([28570d1](https://github.com/Val-istar-Guo/vue-boilerplate/commit/28570d1)), closes [#24](https://github.com/Val-istar-Guo/vue-boilerplate/issues/24) 814 | 815 | 816 | 817 | 818 | ## [1.1.3](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.1.2...v1.1.3) (2018-10-23) 819 | 820 | 821 | ### Bug Fixes 822 | 823 | * **copy:** comments affect the mrkdown file function ([2e14ec6](https://github.com/Val-istar-Guo/vue-boilerplate/commit/2e14ec6)) 824 | 825 | 826 | 827 | 828 | ## [1.1.2](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.1.1...v1.1.2) (2018-10-21) 829 | 830 | 831 | ### Bug Fixes 832 | 833 | * template config sanitization is not effective ([fcc0e79](https://github.com/Val-istar-Guo/vue-boilerplate/commit/fcc0e79)) 834 | 835 | 836 | 837 | 838 | ## [1.1.1](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.1.0...v1.1.1) (2018-10-21) 839 | 840 | 841 | ### Bug Fixes 842 | 843 | * compatible with the case where the repository field is a string ([d28a3e4](https://github.com/Val-istar-Guo/vue-boilerplate/commit/d28a3e4)) 844 | * git link parse error ([2528e96](https://github.com/Val-istar-Guo/vue-boilerplate/commit/2528e96)) 845 | 846 | 847 | 848 | 849 | # [1.1.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.0.1...v1.1.0) (2018-10-21) 850 | 851 | 852 | ### Bug Fixes 853 | 854 | * **merge:** file merge handler unusable ([a24b479](https://github.com/Val-istar-Guo/vue-boilerplate/commit/a24b479)), closes [#19](https://github.com/Val-istar-Guo/vue-boilerplate/issues/19) 855 | 856 | 857 | ### Features 858 | 859 | * add a comment indicating the upgrade type of file ([18d2cbd](https://github.com/Val-istar-Guo/vue-boilerplate/commit/18d2cbd)), closes [#15](https://github.com/Val-istar-Guo/vue-boilerplate/issues/15) 860 | * add update command ([01b8fed](https://github.com/Val-istar-Guo/vue-boilerplate/commit/01b8fed)), closes [#16](https://github.com/Val-istar-Guo/vue-boilerplate/issues/16) 861 | 862 | 863 | 864 | 865 | ## [1.0.1](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v1.0.0...v1.0.1) (2018-10-18) 866 | 867 | 868 | ### Bug Fixes 869 | 870 | * missing mustache module ([e7a0d9d](https://github.com/Val-istar-Guo/vue-boilerplate/commit/e7a0d9d)), closes [#14](https://github.com/Val-istar-Guo/vue-boilerplate/issues/14) 871 | 872 | 873 | 874 | 875 | # [1.0.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v0.1.0...v1.0.0) (2018-10-17) 876 | 877 | 878 | ### Code Refactoring 879 | 880 | * separate the template from the scaffold ([6de2212](https://github.com/Val-istar-Guo/vue-boilerplate/commit/6de2212)), closes [#7](https://github.com/Val-istar-Guo/vue-boilerplate/issues/7) 881 | 882 | 883 | ### Features 884 | 885 | * **template:** constraint version number and change log ([5500f8c](https://github.com/Val-istar-Guo/vue-boilerplate/commit/5500f8c)) 886 | 887 | 888 | ### BREAKING CHANGES 889 | 890 | * The option `-t` of `mili init` command is no long supported. And each template will 891 | become a independent project. If you want to upgrade the template, you need to manually configure 892 | .milirc, then run mili upgrade. 893 | 894 | 895 | 896 | 897 | # [0.1.0](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v0.0.9...v0.1.0) (2018-10-03) 898 | 899 | 900 | ### Features 901 | 902 | * **template:** push after publish ([031347d](https://github.com/Val-istar-Guo/vue-boilerplate/commit/031347d)) 903 | 904 | 905 | 906 | 907 | ## [0.0.9](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v0.0.8...v0.0.9) (2018-10-03) 908 | 909 | 910 | 911 | 912 | ## [0.0.8](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v0.0.7...v0.0.8) (2018-10-03) 913 | 914 | 915 | ### Bug Fixes 916 | 917 | * **command:** project upgrade did not get the community profile feature ([06e3cf3](https://github.com/Val-istar-Guo/vue-boilerplate/commit/06e3cf3)) 918 | 919 | 920 | 921 | 922 | ## [0.0.7](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v0.0.5...v0.0.7) (2018-10-03) 923 | 924 | 925 | ### Features 926 | 927 | * **commit:** add commit message specification ([fbea6d2](https://github.com/Val-istar-Guo/vue-boilerplate/commit/fbea6d2)) 928 | * **template:** add commit message specification ([dcdacd4](https://github.com/Val-istar-Guo/vue-boilerplate/commit/dcdacd4)), closes [#6](https://github.com/Val-istar-Guo/vue-boilerplate/issues/6) 929 | * **template:** husky upgrade to ^1.0.1 ([fc447c0](https://github.com/Val-istar-Guo/vue-boilerplate/commit/fc447c0)) 930 | * **template:** support github community profile ([96e6f08](https://github.com/Val-istar-Guo/vue-boilerplate/commit/96e6f08)), closes [#11](https://github.com/Val-istar-Guo/vue-boilerplate/issues/11) 931 | * **upgrade:** upgrade will overwrite .czrc and .commitlintrc.yml ([e179ba6](https://github.com/Val-istar-Guo/vue-boilerplate/commit/e179ba6)) 932 | * **upgrade:** upgrade will overwrite .huskyrc.yml ([17db781](https://github.com/Val-istar-Guo/vue-boilerplate/commit/17db781)) 933 | 934 | 935 | ### BREAKING CHANGES 936 | 937 | * **upgrade:** .czrc and .commitlintrc.yml will be override when upgrade mili 938 | * **upgrade:** .huskyrc.yml will be override when upgrade mili 939 | * **template:** husky hooks in "scripts" will no longer work 940 | 941 | 942 | 943 | 944 | ## [0.0.6](https://github.com/Val-istar-Guo/vue-boilerplate/compare/v0.0.5...v0.0.6) (2018-10-02) 945 | 946 | 947 | ### Features 948 | 949 | * **commit:** add commit message specification ([fbea6d2](https://github.com/Val-istar-Guo/vue-boilerplate/commit/fbea6d2)) 950 | * **template:** add commit message specification ([dcdacd4](https://github.com/Val-istar-Guo/vue-boilerplate/commit/dcdacd4)), closes [#6](https://github.com/Val-istar-Guo/vue-boilerplate/issues/6) 951 | * **template:** husky upgrade to ^1.0.1 ([fc447c0](https://github.com/Val-istar-Guo/vue-boilerplate/commit/fc447c0)) 952 | 953 | 954 | ### BREAKING CHANGES 955 | 956 | * **template:** husky hooks in "scripts" will no longer work 957 | --------------------------------------------------------------------------------