@shlroland/lint-cli
Version:
Setup Wizard for lint tools
382 lines (363 loc) โข 11.7 kB
JavaScript
import {
AnswerContext,
cjsConfigFactory,
deleteFile,
ensureConfig,
esmConfigFactory,
initGit,
initHusky,
install,
isGitRepository,
isHuskyInstalled,
onCancel,
shouldInitGitPrompt,
shouldOverridePrompt,
sleep,
willInstallList,
writeFile
} from "./chunk-7K4SGG5N.js";
// src/cli.ts
import process2 from "node:process";
import restoreCursor from "restore-cursor";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
// package.json
var version = "0.5.1";
// src/prompts.ts
import * as p4 from "@clack/prompts";
import c3 from "picocolors";
// src/answer/index.ts
import * as p from "@clack/prompts";
// src/answer/abstract/answer.ts
var AbstractAnswer = class {
static toolName;
async configGuard() {
const configs = this.config.pendingConfigs;
for (const config2 of configs) {
if (await config2.checkConfigFileExisted()) {
const shouldOverride = await shouldOverridePrompt(this.answerName);
if (shouldOverride) {
config2.overrideFile = async () => {
await deleteFile(config2.configFilePath);
};
this.config.addPendingConfig(config2);
} else {
this.config.removePendingConfig(config2);
}
}
}
}
get pendingPackages() {
return this.installer.pendingPackages;
}
get pendingConfigs() {
const result = [];
for (const config2 of this.config.pendingConfigs) {
result.push({
configFilePath: config2.configFilePath,
configFileContent: config2.configFileContent,
overrideFile: config2.overrideFile
});
}
return result;
}
};
// src/answer/abstract/config.ts
var Config = class {
#pendingConfigs = /* @__PURE__ */ new Map();
constructor(defaultConfigs) {
for (const config2 of defaultConfigs) {
this.#pendingConfigs.set(config2.configFileName, config2);
}
}
addPendingConfig(config2) {
this.#pendingConfigs.set(config2.configFileName, config2);
}
removePendingConfig(config2) {
this.#pendingConfigs.delete(config2.configFileName);
}
clearPendingConfigs() {
this.#pendingConfigs.clear();
}
get pendingConfigs() {
return this.#pendingConfigs.values();
}
};
// src/answer/abstract/config-option.ts
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
var ConfigOption = class {
context = AnswerContext.instance;
async overrideFile() {
return Promise.resolve();
}
};
var CommonConfigOption = class extends ConfigOption {
configFileName;
configFilePath;
configFileContent;
constructor(options) {
super();
this.configFileName = options.configFileName;
this.configFilePath = path.join(this.context.cwd, this.configFileName);
this.configFileContent = options.content;
}
async checkConfigFileExisted() {
const filePath = path.resolve(process.cwd(), this.configFilePath);
const exists = await fs.promises.access(filePath).then(() => true).catch(() => false);
return exists;
}
};
var CosmiConfigOption = class extends ConfigOption {
configFileName;
configFilePath;
configFileContent;
esmImportConfigContent;
esmExportConfigContent;
cjsImportConfigContent;
cjsExportConfigContent;
checkConfigNames;
constructor(options) {
super();
this.configFileName = options.configFileName;
this.esmImportConfigContent = options.esmImportConfigContent;
this.esmExportConfigContent = options.esmExportConfigContent;
this.cjsImportConfigContent = options.cjsImportConfigContent;
this.cjsExportConfigContent = options.cjsExportConfigContent;
this.checkConfigNames = options.checkConfigNames;
const { content, path: path2 } = this.configFileContentFactory();
this.configFileContent = content;
this.configFilePath = path2;
}
configFileContentFactory() {
const content = this.context.moduleType === "module" ? esmConfigFactory(this.esmExportConfigContent, this.esmImportConfigContent) : cjsConfigFactory(this.cjsExportConfigContent, this.cjsImportConfigContent);
return {
content,
path: path.join(this.context.cwd, this.configFileName)
};
}
async checkConfigFileExisted() {
const checkConfigNames = this.checkConfigNames;
for (const configName of checkConfigNames) {
const config2 = await ensureConfig(configName);
if (config2) {
return true;
}
}
return false;
}
};
// src/answer/abstract/install.ts
var Installer = class extends Set {
get pendingPackages() {
return Array.from(this.values());
}
};
// src/answer/commitlint.ts
var CommitlintAnswer = class extends AbstractAnswer {
static toolName = "commitlint";
answerName = "commitlint";
installer = new Installer(["czg", "@commitlint/cli", "@shlroland/cz-config"]);
config = new Config([
new CosmiConfigOption({
configFileName: "commitlint.config.js",
esmImportConfigContent: ``,
esmExportConfigContent: `{ extends: ['@shlroland/cz-config/commitlint'] }`,
cjsImportConfigContent: ``,
cjsExportConfigContent: `{ extends: ['@shlroland/cz-config/commitlint'] }`,
checkConfigNames: ["commitlint"]
})
]);
};
// src/answer/eslint.ts
var EslintAnswer = class extends AbstractAnswer {
static toolName = "eslint";
answerName = "eslint";
installer = new Installer(["eslint", "@shlroland/eslint-config", "eslint-plugin-format"]);
config = new Config(
[
new CosmiConfigOption({
configFileName: "eslint.config.js",
esmImportConfigContent: 'import { shlroland } from "@shlroland/eslint-config"',
esmExportConfigContent: "shlroland()",
cjsImportConfigContent: 'const { shlroland } = require("@shlroland/eslint-config")',
cjsExportConfigContent: "shlroland()",
checkConfigNames: ["eslint"]
})
]
);
};
// ../husky/lib/index.js
var config = {
hooks: {
"commit-msg": "npx --no -- commitlint --edit $1",
"pre-commit": "lint-staged \n tsc --noEmit"
}
};
var husky_default = config;
// src/answer/husky.ts
var HuskyAnswer = class extends AbstractAnswer {
static toolName = "husky";
context = AnswerContext.instance;
answerName = "husky";
installer = new Installer(["husky"]);
config = new Config([
new CommonConfigOption({
configFileName: ".husky/pre-commit",
content: husky_default.hooks["pre-commit"]
}),
new CommonConfigOption({
configFileName: ".husky/commit-msg",
content: husky_default.hooks["commit-msg"]
})
]);
async configGuard() {
const hasGit = await isGitRepository(this.context.cwd);
if (!hasGit) {
const shouldInitGit = await shouldInitGitPrompt();
if (!shouldInitGit) {
this.config.clearPendingConfigs();
return;
}
await initGit();
}
const hasHusky = await isHuskyInstalled(this.context.cwd);
if (!hasHusky) {
await initHusky();
}
const configs = this.config.pendingConfigs;
for (const config2 of configs) {
if (await config2.checkConfigFileExisted()) {
const shouldOverride = await shouldOverridePrompt(config2.configFileName);
if (shouldOverride) {
config2.overrideFile = async () => {
await deleteFile(config2.configFilePath);
};
this.config.addPendingConfig(config2);
} else {
this.config.removePendingConfig(config2);
}
}
}
}
};
// src/answer/lint-staged.ts
var LintStagedAnswer = class extends AbstractAnswer {
static toolName = "lint-staged";
answerName = "lint-staged";
installer = new Installer(["lint-staged", "@shlroland/lint-staged"]);
config = new Config([
new CosmiConfigOption({
configFileName: "lint-staged.config.js",
esmImportConfigContent: 'import lintStaged from "@shlroland/lint-staged"',
esmExportConfigContent: "lintStaged",
cjsImportConfigContent: 'const lintStaged = require("@shlroland/lint-staged")',
cjsExportConfigContent: "lintStaged",
checkConfigNames: ["lint-staged", "lintstaged"]
})
]);
};
// src/answer/index.ts
var answers = [
new EslintAnswer(),
new LintStagedAnswer(),
new CommitlintAnswer(),
new HuskyAnswer()
];
var toSelectAnswers = /* @__PURE__ */ new Map([
[EslintAnswer.toolName, EslintAnswer],
[LintStagedAnswer.toolName, LintStagedAnswer],
[CommitlintAnswer.toolName, CommitlintAnswer],
[HuskyAnswer.toolName, HuskyAnswer]
]);
async function getAnswers() {
const answerKeys = Array.from(toSelectAnswers.keys());
const options = answerKeys.map((answer) => ({
value: answer
}));
const selectedAnswers = await p.multiselect({
message: "Please select lint tools to install:",
options,
initialValues: answerKeys
});
if (p.isCancel(selectedAnswers)) {
onCancel();
return [];
}
return selectedAnswers.map((answer) => {
const AnswerCls = toSelectAnswers.get(answer);
if (!AnswerCls) {
throw new Error(`Answer ${answer} not found`);
}
return new AnswerCls();
});
}
// src/tasks/config.ts
import * as p2 from "@clack/prompts";
import c from "picocolors";
async function configTask(selectedAnswers) {
p2.log.step(c.cyan("๐ ๏ธ Config lint tools step"));
for (const answer of selectedAnswers) {
await answer.configGuard();
}
const pendingConfigs = selectedAnswers.map((answer) => answer.pendingConfigs).flat();
for (const { configFilePath, configFileContent, overrideFile } of pendingConfigs) {
await overrideFile();
await writeFile(configFilePath, configFileContent);
}
p2.log.step(c.green("๐ฅณ Config lint tools successfully."));
}
// src/tasks/install.ts
import * as p3 from "@clack/prompts";
import c2 from "picocolors";
async function installTask(selectedAnswers) {
p3.log.step(c2.cyan("๐ฆ Install lint tools step"));
const pendingPkgs = selectedAnswers.map((answer) => answer.pendingPackages).flat();
willInstallList(pendingPkgs);
await install(pendingPkgs);
p3.log.step(c2.green("๐ Install lint tools successfully."));
}
// src/prompts.ts
async function commandInstallPrompt() {
console.clear();
await sleep(500);
p4.intro(c3.bgGreen(c3.greenBright(`๐งบ This command will install lint tools only but not config them.`)));
await sleep(500);
const selectedAnswers = await getAnswers();
await installTask(selectedAnswers);
p4.outro(c3.green("๐ All done!"));
}
async function commandConfigPrompt() {
console.clear();
await sleep(500);
p4.intro(c3.bgBlack(c3.bgBlackBright(`๐งฐ This command will config lint tools only but not install them.`)));
await sleep(500);
const selectedAnswers = await getAnswers();
await configTask(selectedAnswers);
p4.outro(c3.green("๐ All done!"));
}
async function commandInitPrompt() {
await sleep(500);
p4.intro(c3.bgMagenta(c3.magentaBright(`๐ This command will install lint tools and config them2.`)));
await sleep(500);
const selectedAnswers = await getAnswers();
await installTask(selectedAnswers);
await configTask(selectedAnswers);
await sleep(500);
p4.outro(c3.magentaBright("๐ All done!"));
}
// src/cli.ts
yargs(hideBin(process2.argv)).scriptName("shlroland-lint").command("install", "install lint tools", (yargs2) => yargs2, () => commandInstallPrompt()).command("config", "config lint tool", (yargs2) => yargs2, () => commandConfigPrompt()).command(
["$0", "$0 init"],
"cli tool to setup lint tool",
(yargs2) => yargs2,
async () => {
await commandInitPrompt();
}
).alias("v", "version").version(version).help("h", "help info").showHelpOnFail(false).argv;
process2.stdin.on("keypress", (str, key) => {
if (key.ctrl && key.name === "c")
process2.exit();
});
restoreCursor();