Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LINT-86(Feedback): Refine bootstraping of linting #98

Merged
merged 18 commits into from
Feb 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# @feature-sliced/
azinit marked this conversation as resolved.
Show resolved Hide resolved

### Как работает?
CLI - Хранит в себе зависимости, необходимые для работы @feature-sliced/eslint-config.
При запуске, проверяется является ли проект Typescript-проектом:
- парсится файл package.json пользователя, и в случае обнаружения в нём пакетов @types/* или пакета typescript проект признаётся TS-проектом.

Далее запускается ui-prompt, который подтверждает у пользователя установку TS-пакетов, и установку в общем.
При согласии пользователя - запускается процесс установки, проверяется наличе устанавливаемых пакетов у пользователя, в случаю присутствия - пакет пропускается (фильтруется), если запущенно с force - все пакеты принудительно будут установлены.
27 changes: 27 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
Krakazybik marked this conversation as resolved.
Show resolved Hide resolved
"name": "@feature-sliced/eslint-cli",
"version": "0.0.0",
"description": "",
"main": "dist/cli.js",
"scripts": {
"build": "rollup -c rollup.conf.js",
"cli:run": "node src/index.js",
"cli:dev": "nodemon src/index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"meow": "8.1.2"
},
"devDependencies": {
"picocolors": "^1.0.0",
"prompts": "^2.4.2",
"clear": "^0.1.0",
"lodash": "^4.17.21",
azinit marked this conversation as resolved.
Show resolved Hide resolved
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.1.3",
"rollup-plugin-terser": "^7.0.2",
"rollup": "^2.67.3"
}
}
14 changes: 14 additions & 0 deletions packages/cli/rollup.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import { terser } from "rollup-plugin-terser";

export default {
input: "src/index.js",
output: {
file: "dist/cli.js",
format: "cjs",
},
plugins: [nodeResolve({ include: ["node_modules/**"] }), commonjs(), json(), terser()],
external: ["meow"],
};
43 changes: 43 additions & 0 deletions packages/cli/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const meow = require("meow");
azinit marked this conversation as resolved.
Show resolved Hide resolved
const _ = require("lodash");
const { installCmdBuilder, installDependencies } = require("./run");
const {
getPkgManger,
depsPackages,
basicPackages,
typescriptPackages,
filterInstalledDeps,
getUserDeps,
} = require("./packages");
const { ui } = require("./ui");
const { log } = require("./log");

const cli = meow(null, {});

function bootstrap({ withTs = true, force = false }) {
log.info("@feature-sliced/eslint-config/cli");

const userDeps = getUserDeps(cli);

const userPkgManager = getPkgManger();
if (!userPkgManager) {
return;
}
log.info(`Found ${userPkgManager}. Start install missing dependencies.`);

const runInstall = installCmdBuilder(userPkgManager);
const installDeps = force ? depsPackages : filterInstalledDeps(depsPackages, userDeps);

installDependencies(runInstall, _.merge(installDeps, basicPackages));

if (withTs) {
installDependencies(
runInstall,
force ? typescriptPackages : filterInstalledDeps(typescriptPackages, userDeps),
);
}

log.info(`Done.`);
}

ui(bootstrap, isTypeScriptProject(getUserDeps(cli)));
azinit marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 9 additions & 0 deletions packages/cli/src/log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const pc = require("picocolors");

const log = {
error: (text) => console.error(pc.red(text)),
warn: (text) => console.warn(pc.yellow(text)),
info: (text) => console.info(pc.green(text)),
};

azinit marked this conversation as resolved.
Show resolved Hide resolved
module.exports = { log };
91 changes: 91 additions & 0 deletions packages/cli/src/packages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const fs = require("fs");
const path = require("path");
const { log } = require("./log");
const _ = require("lodash");

const basicPackages = {
"@feature-sliced/eslint-config": "latest",
};

const depsPackages = {
"eslint-plugin-boundaries": "^2.8.0",
"eslint-plugin-import": "^2.25.4",
};

const typescriptPackages = {
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"eslint-import-resolver-typescript": "latest",
};

const PkgMangers = {
npm: { lock: "package-lock.json", install: "install" },
yarn: { lock: "yarn.lock", install: "add" },
pnpm: { lock: "pnpm-lock.yaml", install: "install" },
};

function isTypeScriptProject(userDeps) {
for (const dep of userDeps) {
if (dep.includes("@types/") || dep.includes("typescript")) {
return true;
}
}
return false;
}

function getUserDeps(cli) {
return _.merge(cli.pkg.dependencies, cli.pkg.devDependencies);
}

function filterInstalledDeps(installDeps, existDeps) {
const exist = Object.keys(existDeps);
return Object.keys(installDeps).reduce(
(result, dep) => (exist.includes(dep) ? result : { ...result, [dep]: installDeps[dep] }),
{},
);
}

function getPkgManger() {
const pkgManagersNames = Object.keys(PkgMangers);

const selectedPkgManagers = pkgManagersNames.reduce((result, pkgManager) => {
const pkgManagerPath = path.resolve(PkgMangers[pkgManager].lock);

try {
const exist = fs.existsSync(pkgManagerPath);
if (exist) return [...result, pkgManager];
} catch (error) {}

return result;
}, []);

if (selectedPkgManagers.length === 0) {
log.error("Something wrong! No one package manager found in project! Stopped!");
return null;
}

if (selectedPkgManagers.length > 1) {
log.error("Something wrong! Find more then one package manager in project! Stopped!");
return null;
}

return selectedPkgManagers[0];
}

function withPkgManager(cmdExecutor, pkgManager) {
return function () {
cmdExecutor.call(null, ...arguments, pkgManager);
};
}

module.exports = {
withPkgManager,
getPkgManger,
PkgMangers,
basicPackages,
depsPackages,
typescriptPackages,
getUserDeps,
filterInstalledDeps,
isTypeScriptProject,
};
45 changes: 45 additions & 0 deletions packages/cli/src/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { log } = require("./log");
const { spawnSync } = require("child_process");
const { PkgMangers, withPkgManager } = require("./packages");

function runCmdFactory(cmd, executor) {
return function (cmdArgs) {
executor.call(null, [cmd, cmdArgs]);
};
}

function exec(cmd, pkgManager = null) {
if (!pkgManager) {
log.error("No one package manager found in cmd scope!");
return;
}

log.info(`Install ${cmd.slice(-1)}`);

try {
const spawnResultBuffer = spawnSync(pkgManager, [...cmd], {
shell: true,
});
} catch (error) {
console.error(error);
}
}

function installCmdBuilder(userPkgManager) {
const installCmd = PkgMangers[userPkgManager].install;
const userExec = withPkgManager(exec, userPkgManager);
return runCmdFactory(installCmd, userExec);
}

function installDependencies(installFn, dependencies, dev = true) {
const depsString = Object.keys(dependencies).reduce((result, dep) => {
const version = dependencies[dep] && `@${dependencies[dep]}`;
return `${result} "${dep + version}"`;
}, "");

const installArgs = `${dev && "-D"}${depsString}`;

installFn(installArgs);
}

module.exports = { exec, installDependencies, installCmdBuilder };
36 changes: 36 additions & 0 deletions packages/cli/src/ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const prompts = require("prompts");
const { log } = require("./log");

const HELLO_MESSAGE = "Welcome to @feature-sliced/eslint-config installer.";
const INSTALL_MESSAGE = "Run installation?";
const TYPESCRIPT_MESSAGE =
"Typescript detected in your project. Install additionally typescript dependencies?";

const questions = [
{
type: "confirm",
name: "install",
message: INSTALL_MESSAGE,
},
];

const tsQuestions = [
{
type: "confirm",
name: "typescript",
message: TYPESCRIPT_MESSAGE,
},
];

async function ui(install, typescript) {
const usedQuestions = typescript ? [...tsQuestions, ...questions] : questions;

log.info(HELLO_MESSAGE);

const answers = await prompts(usedQuestions);
if (answers.install) {
install({ withTs: answers.typescript });
}
}

module.exports = { ui };
Loading