Skip to content

Commit

Permalink
feat: initialize
Browse files Browse the repository at this point in the history
  • Loading branch information
chunhao committed Jun 25, 2023
0 parents commit 1ac3876
Show file tree
Hide file tree
Showing 25 changed files with 17,036 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true


[*.md]
trim_trailing_whitespace = false
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/dist/
/node_modules/
/test/
16 changes: 16 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es6: true
},
extends: ['standard', 'plugin:prettier/recommended'],
globals: {},
parserOptions: {
ecmaVersion: 2018
},
plugins: ['prettier'],
rules: {
'no-useless-return': 0
}
};
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.DS_Store
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
dist/
.idea/

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
test/e2e/mock-template-build
3 changes: 3 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh
5 changes: 5 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
semi: true,
singleQuote: true,
trailingComma: 'none'
};
Empty file added CHANGELOG.md
Empty file.
62 changes: 62 additions & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env node
const program = require('commander');
const chalk = require('chalk');
const figlet = require('figlet');

const pk = require('../package.json');
const { THRESHOLD } = require('../lib/constant');
const enhanceErrorMessage = require('../lib/utils/enhanceErrorMessage');
const log = require('../lib/utils/log');
const { suggestCommands } = require('../lib/utils/index');
const create = require('../lib/create/create');

program
.name('chbuild')
.description('一个脚手架')
.version(pk.version, '-v, --version');

program
.command('create')
.description('创建一个模版项目')
.argument('<templateName>', '模版类型')
.argument('<projectName>', '项目名称')
.option('-f, --force', '存在同名文件夹强制覆盖')
.action((templateName, projectName, option) => {
create(templateName, projectName, option);
});

// 非法命令处理
program.arguments('<command>').action((cmd) => {
program.outputHelp();
console.log();
log.error(chalk.red(`未知命令 ${chalk.yellow(cmd)}`));
console.log();
suggestCommands(cmd, program.commands, THRESHOLD);
});

// 增加说明
program.on('--help', () => {
// 使用 figlet 绘制 Logo
console.log(
'\r\n' +
figlet.textSync(pk.name, {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
})
);
console.log(
`\r\nRun ${chalk.cyan(
`chcli <command> --help`
)} for detailed usage of given command\r\n`
);
});

// 重写参数缺失错误信息
enhanceErrorMessage('missingArgument', (argsName) => {
return `缺少参数 ${chalk.yellow(`<${argsName}>`)}`;
});

program.parse(process.argv);
3 changes: 3 additions & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['@commitlint/config-conventional']
};
10 changes: 10 additions & 0 deletions lib/config/templateGitRepo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"react": {
"github": "https://github.com/chun1hao/react-template.git",
"download": "github:chun1hao/react-template"
},
"vue": {
"github": "https://github.com/chun1hao/vuejs-admin-dashboard-template.git",
"download": "github:chun1hao/vuejs-admin-dashboard-template"
}
}
7 changes: 7 additions & 0 deletions lib/constant/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const THRESHOLD = 0.6;
const MIN_NODE_VERSION = '9.0.0';

module.exports = {
THRESHOLD,
MIN_NODE_VERSION
};
76 changes: 76 additions & 0 deletions lib/create/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const path = require('path');
const fs = require('fs-extra');
const chalk = require('chalk');
const validateProjectName = require('validate-npm-package-name');
const inquirer = require('inquirer');

const Creator = require('./creator');
const log = require('../utils/log');
const { pauseSpinner } = require('../utils/spinner');
const checkNodeVersion = require('../utils/checkNodeVesion');
const getCliVersion = require('../utils/getCliVersion');
const tempalteGit = require('../config/templateGitRepo.json');
const { MIN_NODE_VERSION } = require('../constant');

async function create(templateName, projectName, options) {
if (!checkNodeVersion(MIN_NODE_VERSION)) {
log.error(
`node版本过低,更新版本 ${chalk.yellow('>=' + MIN_NODE_VERSION)} `
);
return;
}

if (!tempalteGit[templateName]) {
log.error(`输入的模板名 ${chalk.yellow(templateName)} 不存在`);
return;
}
// 校验项目名(包名)是否合法
const validateResult = validateProjectName(projectName);
if (!validateResult.validForOldPackages) {
log.error(`无效的项目名称:${chalk.red(projectName)}`);
validateResult.errors &&
validateResult.errors.forEach((err) => {
log.error(err);
});
process.exit(1);
}
if (!validateResult.validForNewPackages) {
validateResult.warnings &&
validateResult.warnings.forEach((warn) => {
log.warn(warn);
});
}

await getCliVersion(true);
const targetDir = path.resolve(process.cwd(), projectName);
if (fs.existsSync(targetDir)) {
if (options.force) {
fs.removeSync(targetDir);
} else {
const { overwrite } = await inquirer.prompt([
{
name: 'overwrite',
type: 'list',
message: `目录 ${chalk.red(targetDir)} 已经存在,是否覆盖?`,
choices: [
{ name: '覆盖', value: true },
{ name: '取消创建', value: false }
]
}
]);
if (!overwrite) return;
fs.removeSync(targetDir);
}
}

const creator = new Creator(templateName, projectName, targetDir);
await creator.create(options);
}

module.exports = (templateName, projectName, ...args) => {
return create(templateName, projectName, args).catch((err) => {
pauseSpinner();
log.error(err);
process.exit(1);
});
};
48 changes: 48 additions & 0 deletions lib/create/creator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const fs = require('fs-extra');
const EventEmitter = require('events');
const chalk = require('chalk');

const getCliVersion = require('../utils/getCliVersion');
const templateGitRepo = require('../config/templateGitRepo.json');
const log = require('../utils/log');
const { logWithSpinner, stopSpinner } = require('../utils/spinner');
const loadRemote = require('../utils/loadRemote');
const clearConsole = require('../utils/clearConsole');

module.exports = class Creator extends EventEmitter {
constructor(templateName, projectName, context) {
super();
this.templateName = templateName;
this.projectName = projectName;
this.context = context;
}

create = async (cliOptions = {}) => {
const { templateName, projectName, context } = this;
const gitRepoUrl = templateGitRepo[templateName];
let tmpdir;
await getCliVersion();
log.info(`✨ 创建项目到: ${chalk.yellow(context)}.`);
try {
stopSpinner();
logWithSpinner(`⠋`, `正在下载,可能需要一些时间。。。`);
tmpdir = await loadRemote(gitRepoUrl['download'], true);
} catch (e) {
stopSpinner();
log.error(`操作失败 ${chalk.cyan(gitRepoUrl['download'])}: ${e}`);
process.exit(1);
}

try {
fs.copySync(tmpdir, context);
} catch (error) {
return log.error(chalk.red.dim(`Error: ${error}`));
}

stopSpinner();
clearConsole();
console.log(`🎉 创建项目成功 ${chalk.yellow(projectName)}.`);
console.log(`\r\n cd ${chalk.green(projectName)}`);
console.log(`\r\n ${chalk.green('npm i')}`);
};
};
6 changes: 6 additions & 0 deletions lib/utils/checkNodeVesion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const semver = require('semver');

module.exports = function (minVersion) {
const curVersion = semver.valid(semver.coerce(process.version));
return semver.satisfies(curVersion, '>=' + minVersion);
};
11 changes: 11 additions & 0 deletions lib/utils/clearConsole.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const readline = require('readline');

module.exports = async (title) => {
if (process.stdout.isTTY) {
// 是否在终端环境
readline.cursorTo(process.stdout, 0, 0);
readline.clearScreenDown(process.stdout);

if (title) console.log(title);
}
};
15 changes: 15 additions & 0 deletions lib/utils/enhanceErrorMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 重写Command上的一些报错信息
const { Command } = require('commander');
const chalk = require('chalk');

const npmlog = require('./log');

module.exports = (methodName, log) => {
Command.prototype[methodName] = function (...args) {
if (methodName === 'unknownOption' && this._allowUnknowOption) return false;
this.outputHelp();
console.log('');
npmlog.error(chalk.red(log(...args)));
process.exit(1);
};
};
35 changes: 35 additions & 0 deletions lib/utils/getCliVersion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { exec } = require('child_process');
const semver = require('semver');
const chalk = require('chalk');

const pk = require('../../package.json');
const clearConsole = require('./clearConsole');
const log = require('../utils/log');

module.exports = async (checkUpdate) => {
let local = pk.version;
let name = pk.name;

return new Promise((resolve) => {
exec(`npm view ${name} version`, (error, stdout, stderr) => {
if (error) {
log.error(error.stack);
log.error('Error code: ' + error.code);
log.error('Signal received: ' + error.signal);
process.exit(1);
} else {
let title = chalk.bold.blue(`CHCLI v${local}`);
let latestVersion = semver.valid(semver.coerce(stdout));
if (checkUpdate && semver.satisfies(latestVersion, '>' + local)) {
// 提示升级
title += chalk.green(`
┌────────────────────${`─`.repeat(latestVersion.length)}──┐
│ Update available: ${latestVersion}
└────────────────────${`─`.repeat(latestVersion.length)}──┘`);
}
clearConsole(title);
resolve(true);
}
});
});
};
15 changes: 15 additions & 0 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const chalk = require('chalk');
const didYouMean = require('didyoumean');

// 简易匹配用户命令
function suggestCommands(cmd, commands, threshold) {
const avaliableCommands = commands.map((cmd) => cmd._name);
const suggestion = didYouMean(cmd, avaliableCommands);
if (suggestion) {
console.log(chalk.green(`你是否想输入 ${chalk.red(suggestion)} ?`));
}
}

module.exports = {
suggestCommands
};
20 changes: 20 additions & 0 deletions lib/utils/loadRemote.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const download = require('download-git-repo');

const pk = require('../../package.json');

module.exports = async (name, clone = false) => {
const tempdir = path.resolve(os.tmpdir(), pk.name);
if (clone) {
await fs.remove(tempdir);
}

return new Promise((resolve, reject) => {
download(name, tempdir, { clone: false }, (err) => {
if (err) return reject(err);
resolve(tempdir);
});
});
};
Loading

0 comments on commit 1ac3876

Please sign in to comment.