Skip to content

Commit

Permalink
Merge pull request #37 from atmina/cli
Browse files Browse the repository at this point in the history
feat: CLI
  • Loading branch information
reiv committed Jul 4, 2023
2 parents 1b329d6 + 5b9859f commit 344dfda
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 8 deletions.
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 -D @atmina/linting
# or
pnpm add -D @atmina/linting
```
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"
}
}
```
136 changes: 136 additions & 0 deletions bin/linting.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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')`);
}
}

// React config is included in the Next.js config
if (dependencies['react'] && !dependencies['next']) {
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.

0 comments on commit 344dfda

Please sign in to comment.