Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 85efd19

Browse files
committed
feat(cli): create initial, proof-of-concept CLI
1 parent 8d710a0 commit 85efd19

34 files changed

+1581
-25
lines changed

.eslintrc.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ module.exports = {
301301
{
302302
files: ['rollup.config.ts'],
303303
rules: {
304+
// rollup config must be default exported
304305
'import/no-default-export': 'off',
305306
},
306307
},
@@ -311,5 +312,42 @@ module.exports = {
311312
'no-console': 'off',
312313
},
313314
},
315+
{
316+
files: ['*.{js,jsx}'],
317+
rules: {
318+
'@typescript-eslint/explicit-function-return-type': 'off',
319+
},
320+
},
321+
// CLI lint configs
322+
{
323+
files: [
324+
'packages/cli/bin/**/*.{ts,js}',
325+
'packages/cli/src/reporters/Reporter.ts',
326+
],
327+
rules: {
328+
'no-console': 'off',
329+
},
330+
},
331+
{
332+
files: ['packages/cli/bin/**/*.js'],
333+
rules: {
334+
'@typescript-eslint/no-unsafe-assignment': 'off',
335+
'@typescript-eslint/ban-ts-comment': 'off',
336+
},
337+
},
338+
{
339+
files: ['packages/cli/**/*.{ts,tsx,js}'],
340+
rules: {
341+
'@typescript-eslint/consistent-type-imports': [
342+
'error',
343+
{ prefer: 'type-imports', disallowTypeAnnotations: true },
344+
],
345+
'@typescript-eslint/consistent-type-exports': 'error',
346+
'import/first': 'error',
347+
'import/newline-after-import': 'error',
348+
'import/no-duplicates': 'error',
349+
'simple-import-sort/imports': 'error',
350+
},
351+
},
314352
],
315353
};

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
"lint-fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
3838
"lint-markdown-fix": "yarn lint-markdown --fix",
3939
"lint-markdown": "markdownlint \"**/*.md\" --config=.markdownlint.json --ignore-path=.markdownlintignore",
40-
"lint": "cross-env NODE_OPTIONS=\"--max-old-space-size=16384\" eslint . --ext .js,.jsx,.ts,.tsx",
41-
"postinstall": "yarn husky install && yarn build",
40+
"lint": "ts-eslint -p ./tsconfig.eslint.json -p \"packages/*/tsconfig.json\"",
41+
"postinstall": "yarn patch-package && yarn husky install && yarn build",
4242
"pre-commit": "yarn lint-staged",
4343
"pre-push": "yarn check-format",
4444
"start": "nx run website:start",
@@ -75,10 +75,12 @@
7575
"@types/marked": "^3.0.2",
7676
"@types/ncp": "^2.0.5",
7777
"@types/node": "^16.11.4",
78+
"@types/node-fetch": "^3.0.3",
7879
"@types/prettier": "^2.4.2",
7980
"@types/rimraf": "^3.0.2",
8081
"@types/semver": "^7.3.9",
8182
"@types/tmp": "^0.2.2",
83+
"@types/yargs": "^17.0.8",
8284
"all-contributors-cli": "^6.20.0",
8385
"cross-env": "^7.0.3",
8486
"cspell": "^5.12.3",
@@ -101,6 +103,7 @@
101103
"markdownlint-cli": "^0.29.0",
102104
"ncp": "^2.0.0",
103105
"node-fetch": "^3.0.0",
106+
"patch-package": "^6.4.7",
104107
"prettier": "^2.5.0",
105108
"pretty-format": "^27.3.1",
106109
"rimraf": "^3.0.2",

packages/cli/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 TypeScript ESLint and other contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/cli/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<h1 align="center">typescript-eslint CLI</h1>
2+
3+
<p align="center">
4+
<img src="https://github.com/typescript-eslint/typescript-eslint/workflows/CI/badge.svg" alt="CI" />
5+
<a href="https://www.npmjs.com/package/@typescript-eslint/cli"><img src="https://img.shields.io/npm/v/@typescript-eslint/cli.svg?style=flat-square" alt="NPM Version" /></a>
6+
<a href="https://www.npmjs.com/package/@typescript-eslint/cli"><img src="https://img.shields.io/npm/dm/@typescript-eslint/cli.svg?style=flat-square" alt="NPM Downloads" /></a>
7+
</p>
8+
9+
CLI for coordinating lint runs that use typescript-eslint.
10+
11+
TODO: docs on CLI args
12+
13+
## Contributing
14+
15+
[See the contributing guide here](../../CONTRIBUTING.md)

packages/cli/bin/ts-eslint.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env node
2+
3+
// @ts-check
4+
5+
if (process.env.USE_TS_ESLINT_SRC == null) {
6+
// to use V8's code cache to speed up instantiation time
7+
require('v8-compile-cache');
8+
}
9+
10+
/**
11+
* @param {unknown} thing
12+
* @returns {thing is Record<string, unknown>}
13+
*/
14+
function isObject(thing) {
15+
return typeof thing === 'object' && thing != null;
16+
}
17+
18+
/**
19+
* Get the error message of a given value.
20+
* @param {unknown} error The value to get.
21+
* @returns {string} The error message.
22+
*/
23+
function getErrorMessage(error) {
24+
// Lazy loading because this is used only if an error happened.
25+
const util = require('util');
26+
27+
if (!isObject(error)) {
28+
return String(error);
29+
}
30+
31+
// Use the stacktrace if it's an error object.
32+
if (typeof error.stack === 'string') {
33+
return error.stack;
34+
}
35+
36+
// Otherwise, dump the object.
37+
return util.format('%o', error);
38+
}
39+
40+
/**
41+
* Catch and report unexpected error.
42+
* @param {unknown} error The thrown error object.
43+
* @returns {void}
44+
*/
45+
function onFatalError(error) {
46+
process.exitCode = 2;
47+
const message = getErrorMessage(error);
48+
console.error(`
49+
An unhandled exception occurred!
50+
${message}`);
51+
}
52+
53+
(async function main() {
54+
process.on('uncaughtException', onFatalError);
55+
process.on('unhandledRejection', onFatalError);
56+
57+
/** @type {import('../src/index')} */
58+
const cli = (() => {
59+
if (process.env.USE_TS_ESLINT_SRC == null) {
60+
// using an ignore because after a build a ts-expect-error will no longer error because TS will follow the
61+
// build maps to the source files...
62+
// @ts-ignore - have to reference the built file, not the src file
63+
return require('../dist/index');
64+
}
65+
66+
// ensure ts-node is registered correctly
67+
// eslint-disable-next-line import/no-extraneous-dependencies
68+
require('ts-node').register({
69+
transpileOnly: true,
70+
project: require('path').resolve(__dirname, '..', 'tsconfig.json'),
71+
});
72+
return require('../src/index');
73+
})();
74+
75+
await cli.execute();
76+
})().catch(onFatalError);

packages/cli/jest.config.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
// @ts-check
4+
/** @type {import('@jest/types').Config.InitialOptions} */
5+
module.exports = {
6+
globals: {
7+
'ts-jest': {
8+
isolatedModules: true,
9+
},
10+
},
11+
testEnvironment: 'node',
12+
transform: {
13+
['^.+\\.tsx?$']: 'ts-jest',
14+
},
15+
testRegex: ['./tests/.+\\.test\\.ts$'],
16+
collectCoverage: false,
17+
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
18+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
19+
coverageReporters: ['text-summary', 'lcov'],
20+
};

packages/cli/package.json

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"name": "@typescript-eslint/cli",
3+
"version": "5.9.0",
4+
"description": "TypeScript-ESLint CLI",
5+
"keywords": [
6+
"eslint",
7+
"typescript",
8+
"estree",
9+
"cli"
10+
],
11+
"engines": {
12+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
13+
},
14+
"files": [
15+
"dist",
16+
"package.json",
17+
"README.md",
18+
"LICENSE"
19+
],
20+
"repository": {
21+
"type": "git",
22+
"url": "https://github.com/typescript-eslint/typescript-eslint.git",
23+
"directory": "packages/cli"
24+
},
25+
"bugs": {
26+
"url": "https://github.com/typescript-eslint/typescript-eslint/issues"
27+
},
28+
"license": "MIT",
29+
"main": "dist/index.js",
30+
"bin": {
31+
"ts-eslint": "./bin/ts-eslint.js"
32+
},
33+
"types": "dist/index.d.ts",
34+
"scripts": {
35+
"build": "tsc -b tsconfig.build.json",
36+
"postbuild": "downlevel-dts dist _ts3.4/dist",
37+
"clean": "tsc -b tsconfig.build.json --clean",
38+
"postclean": "rimraf dist && rimraf _ts3.4 && rimraf coverage",
39+
"format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore",
40+
"lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'",
41+
"typecheck": "tsc -p tsconfig.json --noEmit",
42+
"ts-eslint": "USE_TS_ESLINT_SRC=1 ts-node --transpile-only ./bin/ts-eslint.js"
43+
},
44+
"funding": {
45+
"type": "opencollective",
46+
"url": "https://opencollective.com/typescript-eslint"
47+
},
48+
"typesVersions": {
49+
"<3.8": {
50+
"*": [
51+
"_ts3.4/*"
52+
]
53+
}
54+
},
55+
"dependencies": {
56+
"@typescript-eslint/experimental-utils": "5.9.0",
57+
"debug": "^4.3.2",
58+
"globby": "^11.0.4",
59+
"ink": "^3.2.0",
60+
"ink-use-stdout-dimensions": "^1.0.5",
61+
"is-glob": "^4.0.3",
62+
"jest-worker": "^27.4.5",
63+
"react": "^17.0.2",
64+
"semver": "^7.3.5",
65+
"v8-compile-cache": "^2.3.0",
66+
"yargs": "^17.3.1"
67+
},
68+
"peerDependencies": {
69+
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0",
70+
"typescript": "*"
71+
},
72+
"devDependencies": {
73+
"@types/is-glob": "*",
74+
"@types/semver": "*",
75+
"@types/yargs": "*"
76+
}
77+
}

packages/cli/project.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"root": "packages/cli",
3+
"type": "library",
4+
"implicitDependencies": []
5+
}

packages/cli/src/FileEnumerator.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { version } from 'eslint/package.json';
2+
import * as semver from 'semver';
3+
4+
const isESLintV8 = semver.major(version) >= 8;
5+
6+
declare class _FileEnumerator {
7+
constructor(options?: {
8+
readonly cwd?: string;
9+
readonly extensions?: string | null;
10+
readonly globInputPaths?: boolean;
11+
readonly errorOnUnmatchedPattern?: boolean;
12+
readonly ignore?: boolean;
13+
});
14+
15+
iterateFiles(
16+
patternOrPatterns: string | readonly string[],
17+
): IterableIterator<{
18+
readonly filePath: string;
19+
readonly config: unknown;
20+
readonly ignored: boolean;
21+
}>;
22+
}
23+
24+
const FileEnumerator = (
25+
isESLintV8
26+
? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
27+
require('eslint/use-at-your-own-risk').FileEnumerator
28+
: require('eslint/lib/cli-engine/file-enumerator')
29+
) as typeof _FileEnumerator;
30+
31+
export { FileEnumerator };

packages/cli/src/commands/Command.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { ArgumentsCamelCase, CommandBuilder, CommandModule } from 'yargs';
2+
3+
import type { Reporter } from '../reporters/Reporter';
4+
5+
// eslint-disable-next-line @typescript-eslint/ban-types
6+
export interface Command<TRawOpts = {}, TProcessedOpts = {}>
7+
extends CommandModule<
8+
TRawOpts & GlobalOptions,
9+
TProcessedOpts & GlobalOptions
10+
> {
11+
/** object declaring the options the command accepts, or a function accepting and returning a yargs instance */
12+
builder: CommandBuilder<
13+
TRawOpts & GlobalOptions,
14+
TProcessedOpts & GlobalOptions
15+
>;
16+
/** string used as the description for the command in help text, use `false` for a hidden command */
17+
describe: string;
18+
/** a function which will be passed the parsed argv. */
19+
handler: (
20+
args: ArgumentsCamelCase<TProcessedOpts & GlobalOptions>,
21+
) => Promise<void>;
22+
}
23+
24+
export interface CommandNoOpts extends CommandModule {
25+
/** string (or array of strings) that executes this command when given on the command line, first string may contain positional args */
26+
command: ReadonlyArray<string> | string;
27+
/** string used as the description for the command in help text, use `false` for a hidden command */
28+
describe: string;
29+
/** a function which will be passed the parsed argv. */
30+
handler: () => void | Promise<void>;
31+
}
32+
33+
export enum ReporterConfig {
34+
ink = 'ink',
35+
plain = 'plain',
36+
}
37+
38+
// numbering is important as each level includes the prior levels
39+
// eg level >= LogLevel.error should include both info and debug
40+
export enum LogLevel {
41+
error = 0,
42+
info = 1,
43+
debug = 2,
44+
}
45+
46+
export interface GlobalOptionsRaw {
47+
logLevel: keyof typeof LogLevel;
48+
reporter: ReporterConfig;
49+
}
50+
51+
export interface GlobalOptions {
52+
logLevel: LogLevel;
53+
reporter: Reporter;
54+
}

0 commit comments

Comments
 (0)