@iyowei/cli-fast-gitignore
Version:
命令行工具,用来生成、更新 .gitignore 文件。 ’github/gitignore‘ 模板库已内嵌。
402 lines (332 loc) • 11.6 kB
JavaScript
import { existsSync, writeFile } from 'fs';
import { resolve, join } from 'path';
import { homedir } from 'os';
import { callsiteHomeSync } from '@iyowei/callsite-home';
import { fastGitignoreSync } from '@iyowei/fast-gitignore';
import { writeJsonFile } from 'write-json-file';
import { loadJsonFileSync } from 'load-json-file';
import { cosmiconfigSync } from 'cosmiconfig';
import updateNotifier from 'update-notifier';
import terminalLink from 'terminal-link';
import redent from 'redent';
import chalk from 'chalk'; // eslint-disable-line
import meow from 'meow';
import shell from 'shelljs';
class CliFastGitignore {
CLI = undefined;
NO_INPUT = false;
WP = undefined;
USER_DEFINED_CONFIG = undefined;
USER_DEFINED_CONFIG_READ_PATH = undefined;
LAST_CACHE = undefined;
LAST_SAVING_PATH = join(homedir(), `cli-fast-gitignore-last.json`);
EMPTY_CONFIG = { topics: undefined, custom: undefined };
CONFIRMED_TOPICS = undefined;
DEST = undefined;
constructor() {
this.CLI = meow(
`
命令行工具,用来生成、更新 .gitignore 文件。 "${terminalLink(
'github/gitignore',
'https://github.com/github/gitignore',
)}" 模板库已内嵌。
${chalk.bold('使用方式')}
$ fast-gitignore|fgi [主题] [...] [选项] [...]
${chalk.bold('选项')}
--out, -o, '.gitignore' 文件存储位置,默认:'process.cwd()'
--config-from-cwd 使用从当前工作路径处读取到的预设,跨项目操作时如有需要可使用
--version, -V, 查看版本号
--help, -h 查看帮助
${chalk.bold('示例')}
$ fgi macOS Windows Linux Node 在当前工作路径读取预设并生成 .gitignore 文件
`,
{
description: false,
importMeta: import.meta,
flags: {
out: {
type: 'string',
alias: 'o',
},
configFromCwd: {
type: 'boolean',
default: false,
},
help: {
type: 'boolean',
alias: 'h',
},
version: {
type: 'boolean',
alias: 'V',
},
},
},
);
this.WP = CliFastGitignore.getWorkingDirectory(this.CLI.flags.out);
shell.echo('');
this.getLast();
shell.echo(
chalk.grey(
' 提示:读取上次预设有 2 个用途,读取上次预设主题,判定是否覆写上次预设',
),
);
this.collectTopics();
this.DEST = this.getDest();
}
async main() {
// 慎始 ↓
this.prerequisites();
shell.echo(' 决策:通过');
const PAYLOAD = {
topic: this.CONFIRMED_TOPICS,
templatesDir: CliFastGitignore.resolve(
'templates',
callsiteHomeSync('cli-fast-gitignore'),
),
};
// 只可能在用户未输入主题的情况下,使用预设文件或者上次操作预设时才可能会有 custom
if (this.NO_INPUT) {
// 1
// 判断读取到的预设是否有效
if (this.USER_DEFINED_CONFIG && this.USER_DEFINED_CONFIG.topics) {
// 2
if (!CliFastGitignore.isEmpty(this.USER_DEFINED_CONFIG.custom)) {
// 合并定制的规则,输入:1. `this.USER_DEFINED_CONFIG` 有效;2. `this.USER_DEFINED_CONFIG.custom` 非空
PAYLOAD.custom = this.USER_DEFINED_CONFIG.custom;
}
} else if (!CliFastGitignore.isEmpty(this.LAST_CACHE.custom)) {
// 没有预设,则读取上次操作的 custom,
PAYLOAD.custom = this.LAST_CACHE.custom;
}
}
shell.echo(' 构造:模板获取参数');
const TMP_PRESET = {
topics: PAYLOAD.topic,
};
if (PAYLOAD.custom) {
TMP_PRESET.custom = PAYLOAD.custom;
}
shell.echo(' 构造:预设文件、上次预设内容');
const gotObjIgnore = fastGitignoreSync(PAYLOAD);
const tplData = Object.values(gotObjIgnore).join('\n\n\n');
shell.echo(' 构造:.gitignore 内容');
// 善终 ↓
const TASKS = [
new Promise((solve, reject) => {
const OUTPUT = join(this.DEST, '.gitignore');
writeFile(OUTPUT, tplData, (err) => {
if (err) {
reject(new Error(`输出:创建/更新 "${OUTPUT}" [${err.message}]`));
return;
}
shell.echo(` 输出:创建/更新 "${OUTPUT}"`);
solve(true);
});
}),
];
if (
!Object.is(JSON.stringify(this.LAST_CACHE), JSON.stringify(TMP_PRESET))
) {
shell.echo(' 决策:准备更新 "上次预设"');
TASKS.push(
new Promise((solve, reject) => {
writeJsonFile(this.LAST_SAVING_PATH, TMP_PRESET, {
indent: 2,
}).then(
() => {
// 不需要更新 this.LAST_CACHE,因为任务到这里没有任何地方再需要使用 this.LAST_CACHE 了
shell.echo(' 输出:储备当前预设为 "上次预设",下次使用');
solve(true);
},
(err) => {
reject(new Error(`输出:更新 "上次预设" [${err.message}]`));
},
);
}),
);
} else {
shell.echo(
` ${chalk.grey(
'决策:仅在 "运行时预设" 与 "上次预设" 内容不一致时覆写',
)}`,
);
}
const CWD_CONFIGS = CliFastGitignore.getUserDefinedConfig(this.WP.cwd);
const TWD_CONFIGS = CliFastGitignore.getUserDefinedConfig(this.WP.twd);
/**
* 创建 .gitignorerc.json 的场景,
* - 使用了 --config-form-cwd 参数,且两个位置读取到的配置不同
* - 输出位置没有 .gitignorerc.json
*/
if (
(this.CLI.flags.configFromCwd &&
!Object.is(JSON.stringify(CWD_CONFIGS), JSON.stringify(TWD_CONFIGS))) ||
CliFastGitignore.isEmpty(this.USER_DEFINED_CONFIG)
) {
shell.echo(' 决策:准备创建/更新预设文件');
TASKS.push(
new Promise((solve, reject) => {
const OUTPUT = join(this.WP.twd, '.gitignorerc.json');
writeJsonFile(OUTPUT, TMP_PRESET, {
indent: 2,
}).then(
() => {
shell.echo(` 输出:创建/更新 "${OUTPUT}"`);
solve(true);
},
(err) => {
reject(new Error(`输出:创建/更新 "${OUTPUT}" [${err.message}]`));
},
);
}),
);
} else {
shell.echo(
` ${chalk.grey(
`决策:已存在预设文件 ${join(this.WP.twd, '.gitignorerc.json')}`,
)} `,
);
}
await Promise.all(TASKS).catch((err) => {
CliFastGitignore.terminateCli(err.message);
});
this.success();
updateNotifier({ pkg: this.CLI.pkg }).notify();
}
collectTopics() {
shell.echo(' 输入:收集主题');
if (this.CLI.input.length === 0) {
shell.echo(` ${chalk.grey('输入:未手动输入主题')}`);
this.NO_INPUT = true;
if (this.CLI.flags.configFromCwd) {
this.USER_DEFINED_CONFIG_READ_PATH = this.WP.cwd;
} else {
this.USER_DEFINED_CONFIG_READ_PATH = this.WP.twd;
}
shell.echo(
` 输入:选择读取 "${this.USER_DEFINED_CONFIG_READ_PATH}" 预设`,
);
// 从 "工作路径" 或 "指定路径" 读取预设,取决于是否指定了 `--config-from-cwd` 参数
this.USER_DEFINED_CONFIG = CliFastGitignore.getUserDefinedConfig(
this.USER_DEFINED_CONFIG_READ_PATH,
);
if (
CliFastGitignore.isEmpty(this.USER_DEFINED_CONFIG) ||
CliFastGitignore.isEmpty(this.USER_DEFINED_CONFIG.topics)
) {
this.CONFIRMED_TOPICS = this.LAST_CACHE.topics;
shell.echo(
CliFastGitignore.isEmpty(this.CONFIRMED_TOPICS)
? ` 输入:${chalk.grey(
`"${this.USER_DEFINED_CONFIG_READ_PATH}" 无预设,尝试读取上次预设,但上次预设也没有`,
)}`
: ` 输入:${chalk.grey(
`"${this.USER_DEFINED_CONFIG_READ_PATH}" 无预设,`,
)}尝试读取上次预设 ${this.CONFIRMED_TOPICS.join(', ')}`,
);
} else {
this.CONFIRMED_TOPICS = this.USER_DEFINED_CONFIG.topics;
shell.echo(
` 输入:选择预设中的主题 ${this.USER_DEFINED_CONFIG.topics.join(
', ',
)}`,
);
}
} else {
this.CONFIRMED_TOPICS = this.CLI.input;
shell.echo(` 输入:选择手动输入的主题 ${this.CONFIRMED_TOPICS}`);
}
}
getDest() {
const { flags } = this.CLI;
const { out } = flags;
if (CliFastGitignore.isEmpty(out)) {
shell.echo(` 输入:提供的输出位置为当前工作路径 ${this.WP.twd}`);
return this.WP.twd;
}
shell.echo(` 输入:提供的输出位置为指定路径 ${out}`);
return out;
}
getLast() {
if (existsSync(this.LAST_SAVING_PATH)) {
shell.echo(' 输入:读取上次预设');
this.LAST_CACHE = loadJsonFileSync(this.LAST_SAVING_PATH);
shell.echo(
CliFastGitignore.isEmpty(this.LAST_CACHE)
? ` ${chalk.grey('输入:上次预设为空')}`
: ' 输入:已读取上次预设',
);
return this.LAST_CACHE;
}
shell.echo(' 输入:使用空预设');
this.LAST_CACHE = this.EMPTY_CONFIG;
return this.EMPTY_CONFIG;
}
prerequisites() {
shell.echo(` 输入:检查输出路径 '${this.DEST}' 是否真实`);
if (!existsSync(this.DEST)) {
CliFastGitignore.terminateCli(`'${this.DEST}' 位置无效`);
}
// TODO: 待定,指定了主题,或者有自定义,2 者皆空则退出程序
shell.echo(' 输入:检查主题是否有效');
if (!this.CONFIRMED_TOPICS) {
CliFastGitignore.terminateCli('未读取到主题预设');
}
}
static getUserDefinedConfig(searchPath) {
const explorer = cosmiconfigSync('gitignore');
const foundConfig = explorer.search(searchPath);
return CliFastGitignore.isEmpty(foundConfig)
? this.EMPTY_CONFIG
: CliFastGitignore.get(foundConfig, 'config');
}
// 正常终止
success() {
shell.echo(
redent(
`
${chalk.greenBright.bold('完成!')}
${chalk.grey(CliFastGitignore.resolve('.gitignore', this.DEST))}
`,
2,
),
);
}
// 异常终止
static terminateCli(msg) {
shell.echo(`\n ${chalk.bold.redBright(msg)}\n`);
shell.exit(1);
}
// twd 永远指向生成目录
// --config-from-cwd 参数只影响从 twd 还是 cwd 读取预设
static getWorkingDirectory(twd) {
return {
twd: CliFastGitignore.isEmpty(twd) ? process.cwd() : twd,
cwd: process.cwd(),
};
}
static resolve(relativePath, base) {
return resolve(base, relativePath);
}
static isEmpty(obj) {
return (
[Object, Array].includes((obj || {}).constructor) &&
!Object.entries(obj || {}).length
);
}
static get(obj, objPath, defaultValue = undefined) {
return String.prototype.split
.call(objPath, /[,[\].]+?/)
.filter(Boolean)
.reduce(
(a, c) => (Object.hasOwnProperty.call(a, c) ? a[c] : defaultValue),
obj,
);
}
}
(async () => {
const ins = new CliFastGitignore();
await ins.main();
})();