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

feat: CLI #37

Merged
merged 7 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
53 changes: 46 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,51 @@ A collection of optionated in-house linting rules.

ESLint configuration is provided in the `eslint.config.js`, aka. "Flat Config" format.

```js
// eslint.config.js
module.exports = require('@atmina/linting/eslint/recommended');
## Quickstart

1. Install
```sh
yarn add @atmina/linting
# or
pnpm add @atmina/linting
reiv marked this conversation as resolved.
Show resolved Hide resolved
```
2. Run the CLI tool
```sh
yarn linting
# or
pnpm linting
```
This will set up the necessary dependencies and configurations for you.

## IDE Integration
In VS Code, use these workspace settings:

```json5
{
"eslint.experimental.useFlatConfig": true,
"eslint.workingDirectories": [
// In a monorepo, specify linted packages here
"frontend"
],
// Optional
"editor.codeActionsOnSave": {
"source.fixAll": true
}
}
```

```js
// .prettierrc.js
module.exports = require('@atmina/linting/prettier');
```
In WebStorm, go to Settings and enable ESLint (Select "Automatic ESLint Configuration"). If desired, enable
"Run eslint --fix on Save".

## Development
When working on `linting`, it may be useful to test its effects in a different project. To do so, link your local copy
of `linting` in the other project's package.json (works with pnpm and yarn). This may require restarting your IDE once
after setting up the link.

```JSON
{
"devDependencies": {
"@atmina/linting": "link:local/path/to/linting"
}
}
```
3 changes: 3 additions & 0 deletions bin/linting.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off
reiv marked this conversation as resolved.
Show resolved Hide resolved

node "%~dp0\linting.mjs" %*
135 changes: 135 additions & 0 deletions bin/linting.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { dirname, resolve } from "node:path";
import { readFile, writeFile } from "node:fs/promises";
import { pkgUp } from "pkg-up";
import { parseNi, run as runNi } from "@antfu/ni";
// ni uses this so we do too
import prompts from "@posva/prompts";

const TEMPLATE_HEADER = '/* eslint-disable */';

const TEMPLATE_CJS = `${TEMPLATE_HEADER}

/**
* @type {import('eslint').Linter.FlatConfig[]}
*/
module.exports = [
{{configs}}
];
`;

const TEMPLATE_ESM = `${TEMPLATE_HEADER}

{{imports}}

/**
* @type {import('eslint').Linter.FlatConfig[]}
*/
const config = [
{{configs}}
];

export default config;
`;



const ni = (args) => {
return runNi(parseNi, args);
}

const confirm = async (message) => {
return (await prompts({type: 'confirm', name: 'confirm', message: message, initial: true })).confirm;
}

const createConfig = (pkg) => {
const isEsm = pkg.type === 'module';
console.log('Format (based on package.json -> type):', isEsm ? 'ES Module' : 'CommonJS');
const dependencies = pkg.dependencies ?? {};
const template = isEsm ? TEMPLATE_ESM : TEMPLATE_CJS;
const configs = isEsm
? ['...recommended']
: [`...require('@atmina/linting/eslint/recommended')`];
const imports = isEsm
? [`import recommended from '@atmina/linting/eslint/recommended.js'`]
: [];

if (dependencies['tailwindcss']) {
console.log('+ Tailwind CSS');
if (isEsm) {
imports.push(`import tailwind from '@atmina/linting/eslint/tailwind.js'`);
configs.push('tailwind');
} else {
configs.push(`require('@atmina/linting/eslint/tailwind')`);
}
}

if (dependencies['react'] || dependencies['next']) {
reiv marked this conversation as resolved.
Show resolved Hide resolved
console.log('+ React');
if (isEsm) {
imports.push(`import react from '@atmina/linting/eslint/react.js'`);
configs.push('react');
} else {
configs.push(`require('@atmina/linting/eslint/react')`);
}
}

if (dependencies['next']) {
console.log('+ Next.js');
if (isEsm) {
imports.push(`import next from '@atmina/linting/eslint/next.js'`);
imports.push(`import nextPlugin from '@next/eslint-plugin-next'`);
configs.push('next(nextPlugin)');
} else {
configs.push(`require('@atmina/linting/eslint/next')(require('@next/eslint-plugin-next'))`);
}
}

return template
.replace('{{configs}}', configs.map(config => ` ${config},`).join('\n'))
.replace('{{imports}}', imports.map(imp => `${imp};`).join('\n'));
}

const main = async () => {
const packagePath = await pkgUp();
if (!packagePath) {
console.error('No package.json found');
return;
}
let pkg = JSON.parse(await readFile(packagePath, 'utf-8'));
if (!await confirm(`This will set up linting in the "${pkg.name ?? ''}" package. Continue?`)) {
return;
}
await ni([
// Install as devDependencies
'-D',
'eslint',
'prettier',
// Enables autocomplete in eslint.config.js
'@types/eslint',
'@atmina/linting'
]);
// Read again after package update
pkg = JSON.parse(await readFile(packagePath, 'utf-8'));
const configPath = resolve(dirname(packagePath), 'eslint.config.js');
let hasConfig = false;
try {
hasConfig = !!(await readFile(configPath, 'utf-8'));
} catch (e) {
if (e.code !== 'ENOENT') {
console.error(e);
return;
}
}
if (!hasConfig || await confirm('Overwrite existing eslint.config.js?')) {
const config = createConfig(pkg);
await writeFile(configPath, config, 'utf-8');
console.log('Created eslint.config.js');
}

pkg['prettier'] = '@atmina/linting/prettier';
await writeFile(packagePath, JSON.stringify(pkg, null, 2), 'utf-8');
console.log('Configured Prettier');
console.log('ATMINA Score increased! 📈');
}

void main();
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"description": "A collection of opinionated in-house linting rules.",
"main": "index.js",
"scripts": {},
"bin": {
"linting": "bin/linting.mjs"
},
"keywords": [
"eslint",
"prettier",
Expand All @@ -26,7 +29,9 @@
"typescript": "5.1.6"
},
"dependencies": {
"@antfu/ni": "^0.21.4",
"@eslint/js": "^8.35.0",
"@posva/prompts": "^2.4.4",
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"eslint-config-prettier": "^8.7.0",
Expand All @@ -35,7 +40,8 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-tailwindcss": "^3.10.1",
"globals": "^13.20.0"
"globals": "^13.20.0",
"pkg-up": "^4.0.0"
},
"peerDependencies": {
"eslint": "^8.35.0",
Expand Down
77 changes: 77 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.