From de7d7e8cdd73c81203a998830314f279581a7aae Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Mon, 23 Sep 2024 12:45:46 +0100 Subject: [PATCH 01/16] =?UTF-8?q?Rollback=20log-symbols=20version=20?= =?UTF-8?q?=F0=9F=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The icons lost their colour. --- package.json | 2 +- yarn.lock | 27 +++++++++++---------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 65a4254..fad64a7 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "eslint-plugin-sort-destructure-keys": "2.0.0", "eslint-plugin-sort-exports": "0.9.1", "glob": "10.4.5", - "log-symbols": "7.0.0", + "log-symbols": "6.0.0", "markdownlint": "0.35.0", "markdownlint-rule-helpers": "0.26.0", "node-notifier": "10.0.1", diff --git a/yarn.lock b/yarn.lock index 47432e3..54b58fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2424,7 +2424,7 @@ chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@5.3.0: +chalk@5.3.0, chalk@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -3454,10 +3454,10 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-unicode-supported@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz#fdf32df9ae98ff6ab2cedc155a5a6e895701c451" - integrity sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q== +is-unicode-supported@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" + integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== is-wsl@^2.2.0: version "2.2.0" @@ -4065,13 +4065,13 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -log-symbols@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-7.0.0.tgz#953999bb9cec27a09049c8f45e1154ec81163061" - integrity sha512-zrc91EDk2M+2AXo/9BTvK91pqb7qrPg2nX/Hy+u8a5qQlbaOflCKO+6SqgZ+M+xUFxGdKTgwnGiL96b1W3ikRA== +log-symbols@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-6.0.0.tgz#bb95e5f05322651cac30c0feb6404f9f2a8a9439" + integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== dependencies: - is-unicode-supported "^2.0.0" - yoctocolors "^2.1.1" + chalk "^5.3.0" + is-unicode-supported "^1.3.0" lru-cache@^10.2.0: version "10.2.2" @@ -5389,8 +5389,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -yoctocolors@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/yoctocolors/-/yoctocolors-2.1.1.tgz#e0167474e9fbb9e8b3ecca738deaa61dd12e56fc" - integrity sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ== From 8c6722389a6eb257ae03cacac2bebb8e50a690c3 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Mon, 23 Sep 2024 12:48:13 +0100 Subject: [PATCH 02/16] =?UTF-8?q?Log=20commander=20errors=20in=20red=20?= =?UTF-8?q?=E2=9D=A3=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 7ac4aaf..1d489b1 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ #!/usr/bin/env node +import chalk from 'chalk' import { Command } from 'commander' +import logSymbols from 'log-symbols' import { Events, Linter } from '@Types' import { clearCacheDirectory } from '@Utils/cache' @@ -20,8 +22,12 @@ program .name('lint-pilot') .description('Lint Pilot: Your co-pilot for maintaining high code quality with seamless ESLint, Stylelint, and MarkdownLint integration.') .version('0.0.1') + .addHelpText('beforeAll', '\nāœˆļø Lint Pilot āœˆļø\n') - .showHelpAfterError('\nšŸ’” Run `lint-pilot --help` for more information.\n') + .configureOutput({ + outputError: (str, write) => write(`\n${logSymbols.error} ${chalk.red(str)}`), + }) + .showHelpAfterError('\nšŸ’” Run `lint-pilot --help` for more information') const runLinter = async ({ cache, eslintUseLegacyConfig, filePattern, fix, linter, ignore }: RunLinter) => { const startTime = new Date().getTime() From 06af57c88ba53d82cfa05c09611b682edcf811c1 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Mon, 23 Sep 2024 12:54:26 +0100 Subject: [PATCH 03/16] =?UTF-8?q?Moved=20linting=20code=20to=20an=20action?= =?UTF-8?q?=20to=20better=20test=20=F0=9F=9B=8B=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/index.spec.ts | 28 ++++++++ src/index.ts | 138 +----------------------------------- src/lint-action/index.ts | 26 +++++++ 3 files changed, 56 insertions(+), 136 deletions(-) create mode 100644 src/__tests__/index.spec.ts create mode 100644 src/lint-action/index.ts diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts new file mode 100644 index 0000000..1b95004 --- /dev/null +++ b/src/__tests__/index.spec.ts @@ -0,0 +1,28 @@ +import { spawnSync } from 'child_process' +import path from 'path' + +describe('lint-pilot', () => { + + const cliPath = path.resolve(__dirname, '../index.ts') + + it('displays the help text', () => { + const result = spawnSync('yarn', ['run', 'tsx', cliPath, '--help'], { encoding: 'utf-8' }) + + expect(result.stdout).toContain('āœˆļø Lint Pilot āœˆļø\n') + expect(result.stdout).toContain('Lint Pilot: Your co-pilot for maintaining high code quality') + }) + + it('displays the version', () => { + const result = spawnSync('yarn', ['run', 'tsx', cliPath, '--version'], { encoding: 'utf-8' }) + + expect(result.stdout).toContain('0.0.1') + }) + + it('handles unknown options', () => { + const result = spawnSync('yarn', ['run', 'tsx', cliPath, '--unknown'], { encoding: 'utf-8' }) + + expect(result.stderr).toContain('error: unknown option') + expect(result.stderr).toContain('šŸ’” Run `lint-pilot --help` for more information') + }) + +}) diff --git a/src/index.ts b/src/index.ts index 1d489b1..6774606 100755 --- a/src/index.ts +++ b/src/index.ts @@ -3,18 +3,7 @@ import chalk from 'chalk' import { Command } from 'commander' import logSymbols from 'log-symbols' -import { Events, Linter } from '@Types' -import { clearCacheDirectory } from '@Utils/cache' -import colourLog from '@Utils/colourLog' -import { notifyResults } from '@Utils/notifier' -import { clearTerminal } from '@Utils/terminal' - -import type { LintReport, RunLinter, RunLintPilot } from '@Types' - -import getFilePatterns from './filePatterns' -import linters from './linters/index' -import sourceFiles from './sourceFiles' -import { fileChangeEvent, watchFiles } from './watchFiles' +import lintAction from './lint-action' const program = new Command() @@ -29,130 +18,7 @@ program }) .showHelpAfterError('\nšŸ’” Run `lint-pilot --help` for more information') -const runLinter = async ({ cache, eslintUseLegacyConfig, filePattern, fix, linter, ignore }: RunLinter) => { - const startTime = new Date().getTime() - colourLog.info(`Running ${linter.toLowerCase()}...`) - - const files = await sourceFiles({ - filePattern, - ignore, - linter, - }) - - const report: LintReport = await linters[linter].lintFiles({ - cache, - eslintUseLegacyConfig, - files, - fix, - }) - - colourLog.summary(report.summary, startTime) - - return report -} - -const runLintPilot = ({ cache, eslintUseLegacyConfig, filePatterns, fix, title, watch }: RunLintPilot) => { - const commonArgs = { - cache, - fix, - ignore: filePatterns.ignorePatterns, - } - - Promise.all([ - runLinter({ - ...commonArgs, - eslintUseLegacyConfig, - filePattern: filePatterns.includePatterns[Linter.ESLint], - linter: Linter.ESLint, - }), - runLinter({ - ...commonArgs, - filePattern: filePatterns.includePatterns[Linter.Markdownlint], - linter: Linter.Markdownlint, - }), - runLinter({ - ...commonArgs, - filePattern: filePatterns.includePatterns[Linter.Stylelint], - linter: Linter.Stylelint, - }), - ]).then((reports) => { - reports.forEach(report => { - colourLog.results(report) - }) - - reports.forEach(({ summary }) => { - colourLog.summaryBlock(summary) - }) - - const exitCode = notifyResults(reports, title) - - if (watch) { - colourLog.info('Watching for changes...') - } else { - process.exit(exitCode) - } - }) -} - -program - .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', 'āœˆļø') - .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') - - .option('--fix', 'automatically fix problems', false) - .option('-w, --watch', 'watch for file changes and re-run the linters', false) - - .option('--cache', 'cache linting results', false) - .option('--clearCache', 'clear the cache', false) - - .option('--ignore-dirs ', 'directories to ignore globally') - .option('--ignore-patterns ', 'file patterns to ignore globally') - .option('--eslint-include ', 'file patterns to include for ESLint') - - .option('--debug', 'output additional debug information including the list of files being linted', false) - .option('--eslint-use-legacy-config', 'set to true to use the legacy ESLint configuration', false) - - .action(({ cache, clearCache, debug, emoji, eslintInclude, eslintUseLegacyConfig, fix, ignoreDirs, ignorePatterns, title, watch }) => { - clearTerminal() - colourLog.title(`${emoji} ${title} ${emoji}`) - console.log() - - if (clearCache) { - clearCacheDirectory() - } - - global.debug = debug - - const filePatterns = getFilePatterns({ - eslintInclude, - ignoreDirs, - ignorePatterns, - }) - - const lintPilotOptions = { - cache, - eslintUseLegacyConfig, - filePatterns, - fix, - title, - watch, - } - - runLintPilot(lintPilotOptions) - - if (watch) { - watchFiles({ - filePatterns: Object.values(filePatterns.includePatterns).flat(), - ignorePatterns: filePatterns.ignorePatterns, - }) - - fileChangeEvent.on(Events.FILE_CHANGED, ({ message }) => { - clearTerminal() - colourLog.info(message) - console.log() - runLintPilot(lintPilotOptions) - }) - } - }) +lintAction(program) process.on('exit', () => { console.log() diff --git a/src/lint-action/index.ts b/src/lint-action/index.ts new file mode 100644 index 0000000..cdc5266 --- /dev/null +++ b/src/lint-action/index.ts @@ -0,0 +1,26 @@ +import { Command } from 'commander' + +const lintAction = (program: Command) => { + program + .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', 'āœˆļø') + .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') + + .option('--fix', 'automatically fix problems', false) + .option('-w, --watch', 'watch for file changes and re-run the linters', false) + + .option('--cache', 'cache linting results', false) + .option('--clearCache', 'clear the cache', false) + + .option('--ignore-dirs ', 'define directories to ignore') + .option('--ignore-patterns ', 'define file patterns to ignore') + .option('--eslint-include ', 'define additional file patterns for ESLint') + + .option('--debug', 'output additional debug information', false) + .option('--eslint-use-legacy-config', 'use legacy ESLint config', false) + + .action(({ cache, clearCache, debug, emoji, eslintInclude, eslintUseLegacyConfig, fix, ignoreDirs, ignorePatterns, title, watch }) => { + console.log({ cache, clearCache, debug, emoji, eslintInclude, eslintUseLegacyConfig, fix, ignoreDirs, ignorePatterns, title, watch }) + }) +} + +export default lintAction From 8e53bfb2686941131476a842becd5cdd8ebe2669 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sat, 26 Oct 2024 22:50:20 +0100 Subject: [PATCH 04/16] =?UTF-8?q?Updated=20yarn=20install=20command=20in?= =?UTF-8?q?=20GitHub=20action=20=F0=9F=90=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6cb1bbc..fb45696 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,15 +25,15 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install Dependencies - run: yarn install --immutable + run: yarn install --frozen-lockfile --ignore-scripts --no-progress --prefer-offline - name: Run Tests run: yarn test --coverage - - name: Run Build + - name: Build Lint Pilot run: yarn build - - name: Lint + - name: Run Lint Pilot run: | yarn lint --ignore-patterns '**/*.ts' yarn lint --eslint-use-legacy-config --ignore-patterns '**/*.ts' From 7261d166d4d3de8ff2cf6c55ae12bfa14ff73d4d Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sat, 26 Oct 2024 22:57:20 +0100 Subject: [PATCH 05/16] =?UTF-8?q?Support=20JSON=20=F0=9F=99=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.ts | 4 +--- package.json | 1 + rollup.config.js | 2 ++ tsconfig.json | 1 + yarn.lock | 7 +++++++ 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 274046d..04ede1a 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -6,9 +6,7 @@ const config: JestConfigWithTsJest = { clearMocks: true, collectCoverageFrom: [ 'src/**/*', - // TODO: Write tests for these files when they are less likely to change '!src/index.ts', - '!src/linters/index.ts', ], coverageDirectory: 'coverage', coverageThreshold: { @@ -36,7 +34,7 @@ const config: JestConfigWithTsJest = { }, transformIgnorePatterns: [ './node_modules/(?!(chalk)/)', - ] + ], } export default config diff --git a/package.json b/package.json index fad64a7..9d117ec 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@babel/core": "7.25.2", "@babel/preset-env": "7.25.4", "@babel/preset-typescript": "7.24.7", + "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "15.2.3", "@rollup/plugin-typescript": "11.1.6", "@types/eslint": "9.6.1", diff --git a/rollup.config.js b/rollup.config.js index f18a0d7..532b67c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs' import { resolve } from 'node:path' +import json from '@rollup/plugin-json' import { nodeResolve } from '@rollup/plugin-node-resolve' import typescript from '@rollup/plugin-typescript' import copy from 'rollup-plugin-copy' @@ -37,6 +38,7 @@ export default [{ format: 'es', }, plugins: [ + json(), nodeResolve({ preferBuiltins: true, }), diff --git a/tsconfig.json b/tsconfig.json index b93a333..943869b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "@Types": ["./src/types/index.ts"], "@Utils/*": ["./src/utils/*"], }, + "resolveJsonModule": true, "rootDir": "./", "strict": true, "target": "ESNext" diff --git a/yarn.lock b/yarn.lock index 54b58fb..8066fbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1722,6 +1722,13 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@rollup/plugin-json@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-6.1.0.tgz#fbe784e29682e9bb6dee28ea75a1a83702e7b805" + integrity sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA== + dependencies: + "@rollup/pluginutils" "^5.1.0" + "@rollup/plugin-node-resolve@15.2.3": version "15.2.3" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz#e5e0b059bd85ca57489492f295ce88c2d4b0daf9" From 570e293c039f215d135370dff1dfba2d15128856 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sat, 26 Oct 2024 23:26:22 +0100 Subject: [PATCH 06/16] =?UTF-8?q?Created=20our=20own=20log-symbols=20?= =?UTF-8?q?=E2=9D=87=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This means we don't have two modules for setting colours. - Ability to customise symbols. --- jest-config/setup.ts | 5 ---- jest.config.ts | 2 +- package.json | 2 +- src/utils/__tests__/log-symbols.spec.ts | 38 +++++++++++++++++++++++++ src/utils/log-symbols.ts | 17 +++++++++++ yarn.lock | 18 ++++-------- 6 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 src/utils/__tests__/log-symbols.spec.ts create mode 100644 src/utils/log-symbols.ts diff --git a/jest-config/setup.ts b/jest-config/setup.ts index c5d16f6..d681567 100644 --- a/jest-config/setup.ts +++ b/jest-config/setup.ts @@ -2,11 +2,6 @@ * MOCKS AND SPIES */ -jest.mock('log-symbols', () => ({ - warning: '!', - error: 'X', -})) - jest.spyOn(process, 'exit').mockImplementation(code => { throw new Error(`process.exit(${code})`) }) diff --git a/jest.config.ts b/jest.config.ts index 04ede1a..ff603eb 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -33,7 +33,7 @@ const config: JestConfigWithTsJest = { '^.+\\.(js|ts)$': 'babel-jest', }, transformIgnorePatterns: [ - './node_modules/(?!(chalk)/)', + './node_modules/(?!(chalk|is-unicode-supported)/)', ], } diff --git a/package.json b/package.json index 9d117ec..c6fb8a2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "eslint-plugin-sort-destructure-keys": "2.0.0", "eslint-plugin-sort-exports": "0.9.1", "glob": "10.4.5", - "log-symbols": "6.0.0", + "is-unicode-supported": "2.1.0", "markdownlint": "0.35.0", "markdownlint-rule-helpers": "0.26.0", "node-notifier": "10.0.1", diff --git a/src/utils/__tests__/log-symbols.spec.ts b/src/utils/__tests__/log-symbols.spec.ts new file mode 100644 index 0000000..d46072a --- /dev/null +++ b/src/utils/__tests__/log-symbols.spec.ts @@ -0,0 +1,38 @@ +import isUnicodeSupported from 'is-unicode-supported' + +jest.mock('is-unicode-supported') + +describe('logSymbols', () => { + + afterEach(() => { + jest.resetModules() + }) + + it('returns Unicode symbols when supported', () => { + (isUnicodeSupported as jest.Mock).mockReturnValue(true) + + const logSymbols = require('../log-symbols').default + + expect(logSymbols).toEqual({ + error: 'āœ—', + info: 'ā„¹', + success: 'āœ“', + tipEmoji: 'šŸ’”', + warning: 'āš ', + }) + }) + + it('returns fallback symbols when Unicode is not supported', () => { + (isUnicodeSupported as jest.Mock).mockReturnValue(false) + + const logSymbols = require('../log-symbols').default + + expect(logSymbols).toEqual({ + error: 'Ɨ', + info: '[i]', + success: 'āˆš', + tipEmoji: 'TIP:', + warning: 'ā€¼', + }) + }) +}) diff --git a/src/utils/log-symbols.ts b/src/utils/log-symbols.ts new file mode 100644 index 0000000..98ed4c5 --- /dev/null +++ b/src/utils/log-symbols.ts @@ -0,0 +1,17 @@ +import isUnicodeSupported from 'is-unicode-supported' + +const _isUnicodeSupported = isUnicodeSupported() + +const error = _isUnicodeSupported ? 'āœ—' : 'Ɨ' +const info = _isUnicodeSupported ? 'ā„¹' : '[i]' +const success = _isUnicodeSupported ? 'āœ“' : 'āˆš' +const tipEmoji = _isUnicodeSupported ? 'šŸ’”' : 'TIP:' +const warning = _isUnicodeSupported ? 'āš ' : 'ā€¼' + +export default { + error, + info, + success, + tipEmoji, + warning, +} diff --git a/yarn.lock b/yarn.lock index 8066fbe..ed0a136 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2431,7 +2431,7 @@ chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@5.3.0, chalk@^5.3.0: +chalk@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -3461,10 +3461,10 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-unicode-supported@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" - integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-unicode-supported@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a" + integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== is-wsl@^2.2.0: version "2.2.0" @@ -4072,14 +4072,6 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -log-symbols@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-6.0.0.tgz#bb95e5f05322651cac30c0feb6404f9f2a8a9439" - integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== - dependencies: - chalk "^5.3.0" - is-unicode-supported "^1.3.0" - lru-cache@^10.2.0: version "10.2.2" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" From 97edfe81108fcbd70f326e620daa2e228fd16037 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 19:19:33 +0000 Subject: [PATCH 07/16] =?UTF-8?q?Prepare=20update=20to=20run=20individual?= =?UTF-8?q?=20linters=20=F0=9F=98=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.ts | 3 +- package.json | 1 - src/__tests__/index.spec.ts | 64 +++++++++++++++++----- src/index.ts | 71 +++++++++++++++++++++---- src/utils/__tests__/log-symbols.spec.ts | 38 ------------- src/utils/log-symbols.ts | 17 ------ yarn.lock | 5 -- 7 files changed, 114 insertions(+), 85 deletions(-) delete mode 100644 src/utils/__tests__/log-symbols.spec.ts delete mode 100644 src/utils/log-symbols.ts diff --git a/jest.config.ts b/jest.config.ts index ff603eb..456c362 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -6,7 +6,6 @@ const config: JestConfigWithTsJest = { clearMocks: true, collectCoverageFrom: [ 'src/**/*', - '!src/index.ts', ], coverageDirectory: 'coverage', coverageThreshold: { @@ -33,7 +32,7 @@ const config: JestConfigWithTsJest = { '^.+\\.(js|ts)$': 'babel-jest', }, transformIgnorePatterns: [ - './node_modules/(?!(chalk|is-unicode-supported)/)', + './node_modules/(?!(chalk)/)', ], } diff --git a/package.json b/package.json index c6fb8a2..3045259 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "eslint-plugin-sort-destructure-keys": "2.0.0", "eslint-plugin-sort-exports": "0.9.1", "glob": "10.4.5", - "is-unicode-supported": "2.1.0", "markdownlint": "0.35.0", "markdownlint-rule-helpers": "0.26.0", "node-notifier": "10.0.1", diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 1b95004..c5a7fd7 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -1,28 +1,68 @@ -import { spawnSync } from 'child_process' -import path from 'path' +import chalk from 'chalk' -describe('lint-pilot', () => { +import { description, name, version } from '../../package.json' + +import program from '../index' - const cliPath = path.resolve(__dirname, '../index.ts') +jest.mock('chalk', () => ({ + red: jest.fn(text => text), +})) + +describe('lint-pilot', () => { it('displays the help text', () => { - const result = spawnSync('yarn', ['run', 'tsx', cliPath, '--help'], { encoding: 'utf-8' }) + const helpInformation = program + .configureHelp({ + helpWidth: -1, + }) + .helpInformation() + + expect(helpInformation).toStrictEqual(`Usage: ${name} [options] [command] - expect(result.stdout).toContain('āœˆļø Lint Pilot āœˆļø\n') - expect(result.stdout).toContain('Lint Pilot: Your co-pilot for maintaining high code quality') +${description} + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + lint run all linters: ESLint, Stylelint, and MarkdownLint (default) + lint:es run ESLint only + lint:markdown run MarkdownLint only + lint:style run Stylelint only + help [command] display help for command +`) }) it('displays the version', () => { - const result = spawnSync('yarn', ['run', 'tsx', cliPath, '--version'], { encoding: 'utf-8' }) + program + .configureOutput({ writeOut: jest.fn() }) + .exitOverride() - expect(result.stdout).toContain('0.0.1') + expect(() => { + program.parse(['node', './index.ts', '--version']); + }).toThrow(version) }) it('handles unknown options', () => { - const result = spawnSync('yarn', ['run', 'tsx', cliPath, '--unknown'], { encoding: 'utf-8' }) + expect.assertions(4) + + const writeErrMock = jest.fn() + + program + .configureOutput({ + writeErr: writeErrMock, + }) + + try { + program.parse(['node', './index.ts', '--unknown']); + } catch (e: any) { + expect(e.message).toMatch('process.exit(1)') + } - expect(result.stderr).toContain('error: unknown option') - expect(result.stderr).toContain('šŸ’” Run `lint-pilot --help` for more information') + expect(chalk.red).toHaveBeenCalledOnceWith(expect.stringContaining('āœ— unknown option \'--unknown\'')) + expect(writeErrMock).toHaveBeenNthCalledWith(1, expect.stringContaining('āœ— unknown option \'--unknown\'')) + expect(writeErrMock).toHaveBeenNthCalledWith(2, expect.stringContaining('šŸ’” Run `lint-pilot --help` for more information.')) }) }) diff --git a/src/index.ts b/src/index.ts index 6774606..dc8ad58 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,27 +1,78 @@ #!/usr/bin/env node import chalk from 'chalk' import { Command } from 'commander' -import logSymbols from 'log-symbols' -import lintAction from './lint-action' +import { description, name, version } from '../package.json' const program = new Command() program - .name('lint-pilot') - .description('Lint Pilot: Your co-pilot for maintaining high code quality with seamless ESLint, Stylelint, and MarkdownLint integration.') - .version('0.0.1') + .name(name) + .description(description) + .version(version) - .addHelpText('beforeAll', '\nāœˆļø Lint Pilot āœˆļø\n') + .addHelpText('beforeAll', '\nāœˆļø Lint Pilot\n') .configureOutput({ - outputError: (str, write) => write(`\n${logSymbols.error} ${chalk.red(str)}`), + outputError: (str, write) => write(chalk.red(`\nāœ— ${str.replace(/^error: /iu, '')}`)), }) - .showHelpAfterError('\nšŸ’” Run `lint-pilot --help` for more information') + .showHelpAfterError('\nšŸ’” Run `lint-pilot --help` for more information.') -lintAction(program) +/* + * Default + */ +program + .command('lint', { isDefault: true }) + .description('run all linters: ESLint, Stylelint, and MarkdownLint (default)') + .action(() => { + // TODO: Run Linters + console.log('Run Lint') + }) + +/* +* ESLint +*/ + +program + .command('lint:es') + .description('run ESLint only') + .action(() => { + // TODO: Run ESLint + console.log('Run ESLint') + }) + +/* +* MarkdownLint +*/ + +program + .command('lint:markdown') + .description('run MarkdownLint only') + .action(() => { + // TODO: Run MarkdownLint + console.log('Run MarkdownLint') + }) + +/* +* Stylelint +*/ + +program + .command('lint:style') + .description('run Stylelint only') + .action(() => { + // TODO: Run Stylelint + console.log('Run Stylelint') + }) + +/* istanbul ignore next */ process.on('exit', () => { console.log() }) -program.parseAsync(process.argv) +/* istanbul ignore next */ +if (process.env.NODE_ENV !== 'test') { + program.parseAsync(process.argv) +} + +export default program diff --git a/src/utils/__tests__/log-symbols.spec.ts b/src/utils/__tests__/log-symbols.spec.ts deleted file mode 100644 index d46072a..0000000 --- a/src/utils/__tests__/log-symbols.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import isUnicodeSupported from 'is-unicode-supported' - -jest.mock('is-unicode-supported') - -describe('logSymbols', () => { - - afterEach(() => { - jest.resetModules() - }) - - it('returns Unicode symbols when supported', () => { - (isUnicodeSupported as jest.Mock).mockReturnValue(true) - - const logSymbols = require('../log-symbols').default - - expect(logSymbols).toEqual({ - error: 'āœ—', - info: 'ā„¹', - success: 'āœ“', - tipEmoji: 'šŸ’”', - warning: 'āš ', - }) - }) - - it('returns fallback symbols when Unicode is not supported', () => { - (isUnicodeSupported as jest.Mock).mockReturnValue(false) - - const logSymbols = require('../log-symbols').default - - expect(logSymbols).toEqual({ - error: 'Ɨ', - info: '[i]', - success: 'āˆš', - tipEmoji: 'TIP:', - warning: 'ā€¼', - }) - }) -}) diff --git a/src/utils/log-symbols.ts b/src/utils/log-symbols.ts deleted file mode 100644 index 98ed4c5..0000000 --- a/src/utils/log-symbols.ts +++ /dev/null @@ -1,17 +0,0 @@ -import isUnicodeSupported from 'is-unicode-supported' - -const _isUnicodeSupported = isUnicodeSupported() - -const error = _isUnicodeSupported ? 'āœ—' : 'Ɨ' -const info = _isUnicodeSupported ? 'ā„¹' : '[i]' -const success = _isUnicodeSupported ? 'āœ“' : 'āˆš' -const tipEmoji = _isUnicodeSupported ? 'šŸ’”' : 'TIP:' -const warning = _isUnicodeSupported ? 'āš ' : 'ā€¼' - -export default { - error, - info, - success, - tipEmoji, - warning, -} diff --git a/yarn.lock b/yarn.lock index ed0a136..15913f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3461,11 +3461,6 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-unicode-supported@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a" - integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== - is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" From e92be7b15c0f0174f77b2c06d9232054ed7b3981 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 19:27:42 +0000 Subject: [PATCH 08/16] =?UTF-8?q?Removed=20additional=20lint=20commands=20?= =?UTF-8?q?=F0=9F=9A=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have proved it works and can add them without it being a breaking change. --- src/__tests__/index.spec.ts | 3 --- src/index.ts | 36 ------------------------------------ src/lint-action/index.ts | 26 -------------------------- 3 files changed, 65 deletions(-) delete mode 100644 src/lint-action/index.ts diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index c5a7fd7..45b5ea5 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -27,9 +27,6 @@ Options: Commands: lint run all linters: ESLint, Stylelint, and MarkdownLint (default) - lint:es run ESLint only - lint:markdown run MarkdownLint only - lint:style run Stylelint only help [command] display help for command `) }) diff --git a/src/index.ts b/src/index.ts index dc8ad58..55737ab 100755 --- a/src/index.ts +++ b/src/index.ts @@ -29,42 +29,6 @@ program console.log('Run Lint') }) -/* -* ESLint -*/ - -program - .command('lint:es') - .description('run ESLint only') - .action(() => { - // TODO: Run ESLint - console.log('Run ESLint') - }) - -/* -* MarkdownLint -*/ - -program - .command('lint:markdown') - .description('run MarkdownLint only') - .action(() => { - // TODO: Run MarkdownLint - console.log('Run MarkdownLint') - }) - -/* -* Stylelint -*/ - -program - .command('lint:style') - .description('run Stylelint only') - .action(() => { - // TODO: Run Stylelint - console.log('Run Stylelint') - }) - /* istanbul ignore next */ process.on('exit', () => { console.log() diff --git a/src/lint-action/index.ts b/src/lint-action/index.ts deleted file mode 100644 index cdc5266..0000000 --- a/src/lint-action/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Command } from 'commander' - -const lintAction = (program: Command) => { - program - .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', 'āœˆļø') - .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') - - .option('--fix', 'automatically fix problems', false) - .option('-w, --watch', 'watch for file changes and re-run the linters', false) - - .option('--cache', 'cache linting results', false) - .option('--clearCache', 'clear the cache', false) - - .option('--ignore-dirs ', 'define directories to ignore') - .option('--ignore-patterns ', 'define file patterns to ignore') - .option('--eslint-include ', 'define additional file patterns for ESLint') - - .option('--debug', 'output additional debug information', false) - .option('--eslint-use-legacy-config', 'use legacy ESLint config', false) - - .action(({ cache, clearCache, debug, emoji, eslintInclude, eslintUseLegacyConfig, fix, ignoreDirs, ignorePatterns, title, watch }) => { - console.log({ cache, clearCache, debug, emoji, eslintInclude, eslintUseLegacyConfig, fix, ignoreDirs, ignorePatterns, title, watch }) - }) -} - -export default lintAction From 22f93674473e59750eb3e5c7d238a60b75bcba68 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 20:26:04 +0000 Subject: [PATCH 09/16] =?UTF-8?q?Added=20the=20options=20for=20the=20lint?= =?UTF-8?q?=20action=20=F0=9F=92=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/index.ts b/src/index.ts index 55737ab..5368d1e 100755 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,23 @@ program program .command('lint', { isDefault: true }) .description('run all linters: ESLint, Stylelint, and MarkdownLint (default)') + + .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', 'āœˆļø') + .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') + + .option('--fix', 'automatically fix problems', false) + .option('-w, --watch', 'watch for file changes and re-run the linters', false) + + .option('--cache', 'cache linting results', false) + .option('--clearCache', 'clear the cache', false) + + .option('--ignore-dirs ', 'directories to ignore globally') + .option('--ignore-patterns ', 'file patterns to ignore globally') + .option('--eslint-include ', 'file patterns to include for ESLint') + + .option('--debug', 'output additional debug information including the list of files being linted', false) + .option('--eslint-use-legacy-config', 'set to true to use the legacy ESLint configuration', false) + .action(() => { // TODO: Run Linters console.log('Run Lint') From 79e160671a5c930959f0a04bd86a3c621ae94b6c Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 20:59:14 +0000 Subject: [PATCH 10/16] =?UTF-8?q?Prepare=20the=20lint=20command=20?= =?UTF-8?q?=F0=9F=93=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc | 6 ++-- eslint.config.js | 4 +-- src/__tests__/index.spec.ts | 58 ++++++++++++++++++++++++++++--- src/commands/index.ts | 5 +++ src/commands/lint/index.ts | 5 +++ src/index.ts | 68 ++++++++++++++++++++----------------- 6 files changed, 104 insertions(+), 42 deletions(-) create mode 100644 src/commands/index.ts create mode 100644 src/commands/lint/index.ts diff --git a/.eslintrc b/.eslintrc index 7b25f5a..a02e7e7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,11 +2,11 @@ "extends": [ "./lib/all-legacy" ], - "rules": { - // "semi": [1, "always"] - }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" + }, + "rules": { + // "semi": [1, "always"] } } diff --git a/eslint.config.js b/eslint.config.js index 90cf469..0f6f226 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,6 @@ export default [ { rules: { // semi: [1, 'always'], - } - } + }, + }, ] diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 45b5ea5..c300504 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -1,16 +1,21 @@ import chalk from 'chalk' import { description, name, version } from '../../package.json' - -import program from '../index' +import { lint } from '../commands' +import createProgram from '../index' jest.mock('chalk', () => ({ red: jest.fn(text => text), })) +jest.mock('../commands', () => ({ + lint: jest.fn(), +})) + describe('lint-pilot', () => { it('displays the help text', () => { + const program = createProgram() const helpInformation = program .configureHelp({ helpWidth: -1, @@ -26,18 +31,19 @@ Options: -h, --help display help for command Commands: - lint run all linters: ESLint, Stylelint, and MarkdownLint (default) + lint [options] run all linters: ESLint, Stylelint, and MarkdownLint (default) help [command] display help for command `) }) it('displays the version', () => { + const program = createProgram() program .configureOutput({ writeOut: jest.fn() }) .exitOverride() expect(() => { - program.parse(['node', './index.ts', '--version']); + program.parse(['node', './index.ts', '--version']) }).toThrow(version) }) @@ -46,13 +52,14 @@ Commands: const writeErrMock = jest.fn() + const program = createProgram() program .configureOutput({ writeErr: writeErrMock, }) try { - program.parse(['node', './index.ts', '--unknown']); + program.parse(['node', './index.ts', '--unknown']) } catch (e: any) { expect(e.message).toMatch('process.exit(1)') } @@ -62,4 +69,45 @@ Commands: expect(writeErrMock).toHaveBeenNthCalledWith(2, expect.stringContaining('šŸ’” Run `lint-pilot --help` for more information.')) }) + it('calls the lint command by default', () => { + const program = createProgram() + program.parse(['node', './index.ts']) + + expect(lint).toHaveBeenCalledTimes(1) + }) + + it('calls the lint command with default options', () => { + const program = createProgram() + program.parse(['node', './index.ts', 'lint']) + + expect(lint).toHaveBeenCalledOnceWith({ + cache: false, + clearCache: false, + debug: false, + emoji: 'āœˆļø', + eslintUseLegacyConfig: false, + fix: false, + title: 'Lint Pilot', + watch: false, + }) + }) + + it('calls the lint command with configured options', () => { + const program = createProgram() + program.parse(['node', './index.ts', 'lint', '--emoji', 'šŸš€', '--title', 'Rocket Lint', '--fix', '--watch', '--cache', '--clearCache', '--ignore-dirs', 'node_modules', '--ignore-patterns', 'dist', '--eslint-include', '*.mdx', '--debug', '--eslint-use-legacy-config']) + + expect(lint).toHaveBeenCalledOnceWith({ + cache: true, + clearCache: true, + debug: true, + emoji: 'šŸš€', + eslintInclude: ['*.mdx'], + eslintUseLegacyConfig: true, + fix: true, + ignoreDirs: ['node_modules'], + ignorePatterns: ['dist'], + title: 'Rocket Lint', + watch: true, + }) + }) }) diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..fc557e3 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,5 @@ +import lint from './lint' + +export { + lint, +} diff --git a/src/commands/lint/index.ts b/src/commands/lint/index.ts new file mode 100644 index 0000000..dd17ae9 --- /dev/null +++ b/src/commands/lint/index.ts @@ -0,0 +1,5 @@ +const lint = (options: any) => { + console.log('Run Lint', options) +} + +export default lint diff --git a/src/index.ts b/src/index.ts index 5368d1e..bdcbb16 100755 --- a/src/index.ts +++ b/src/index.ts @@ -4,47 +4,50 @@ import { Command } from 'commander' import { description, name, version } from '../package.json' -const program = new Command() +import { lint } from './commands' -program - .name(name) - .description(description) - .version(version) +const createProgram = () => { + const program = new Command() - .addHelpText('beforeAll', '\nāœˆļø Lint Pilot\n') - .configureOutput({ - outputError: (str, write) => write(chalk.red(`\nāœ— ${str.replace(/^error: /iu, '')}`)), - }) - .showHelpAfterError('\nšŸ’” Run `lint-pilot --help` for more information.') + program + .name(name) + .description(description) + .version(version) -/* - * Default - */ + .addHelpText('beforeAll', '\nāœˆļø Lint Pilot\n') + .configureOutput({ + outputError: (str, write) => write(chalk.red(`\nāœ— ${str.replace(/^error: /iu, '')}`)), + }) + .showHelpAfterError('\nšŸ’” Run `lint-pilot --help` for more information.') -program - .command('lint', { isDefault: true }) - .description('run all linters: ESLint, Stylelint, and MarkdownLint (default)') + /* + * Lint Command + */ - .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', 'āœˆļø') - .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') + program + .command('lint', { isDefault: true }) + .description('run all linters: ESLint, Stylelint, and MarkdownLint (default)') - .option('--fix', 'automatically fix problems', false) - .option('-w, --watch', 'watch for file changes and re-run the linters', false) + .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', 'āœˆļø') + .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') - .option('--cache', 'cache linting results', false) - .option('--clearCache', 'clear the cache', false) + .option('--fix', 'automatically fix problems', false) + .option('-w, --watch', 'watch for file changes and re-run the linters', false) - .option('--ignore-dirs ', 'directories to ignore globally') - .option('--ignore-patterns ', 'file patterns to ignore globally') - .option('--eslint-include ', 'file patterns to include for ESLint') + .option('--cache', 'cache linting results', false) + .option('--clearCache', 'clear the cache', false) - .option('--debug', 'output additional debug information including the list of files being linted', false) - .option('--eslint-use-legacy-config', 'set to true to use the legacy ESLint configuration', false) + .option('--ignore-dirs ', 'directories to ignore globally') + .option('--ignore-patterns ', 'file patterns to ignore globally') + .option('--eslint-include ', 'file patterns to include for ESLint') - .action(() => { - // TODO: Run Linters - console.log('Run Lint') - }) + .option('--debug', 'output additional debug information including the list of files being linted', false) + .option('--eslint-use-legacy-config', 'set to true to use the legacy ESLint configuration', false) + + .action(options => lint(options)) + + return program +} /* istanbul ignore next */ process.on('exit', () => { @@ -53,7 +56,8 @@ process.on('exit', () => { /* istanbul ignore next */ if (process.env.NODE_ENV !== 'test') { + const program = createProgram() program.parseAsync(process.argv) } -export default program +export default createProgram From 1435fe1c81f545f97592fc2899f9771219acd4fb Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 21:48:02 +0000 Subject: [PATCH 11/16] =?UTF-8?q?Added=20types=20to=20the=20lint=20options?= =?UTF-8?q?=20=F0=9F=86=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/index.spec.ts | 14 ++++++++++---- src/commands/lint/index.ts | 4 +++- src/types/commands.ts | 17 +++++++++++++++++ tsconfig.json | 2 +- 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 src/types/commands.ts diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index c300504..699020a 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -4,6 +4,8 @@ import { description, name, version } from '../../package.json' import { lint } from '../commands' import createProgram from '../index' +import type { LintOptions } from '@Types/commands' + jest.mock('chalk', () => ({ red: jest.fn(text => text), })) @@ -80,7 +82,7 @@ Commands: const program = createProgram() program.parse(['node', './index.ts', 'lint']) - expect(lint).toHaveBeenCalledOnceWith({ + const expectedOptions: LintOptions = { cache: false, clearCache: false, debug: false, @@ -89,14 +91,16 @@ Commands: fix: false, title: 'Lint Pilot', watch: false, - }) + } + + expect(lint).toHaveBeenCalledOnceWith(expectedOptions) }) it('calls the lint command with configured options', () => { const program = createProgram() program.parse(['node', './index.ts', 'lint', '--emoji', 'šŸš€', '--title', 'Rocket Lint', '--fix', '--watch', '--cache', '--clearCache', '--ignore-dirs', 'node_modules', '--ignore-patterns', 'dist', '--eslint-include', '*.mdx', '--debug', '--eslint-use-legacy-config']) - expect(lint).toHaveBeenCalledOnceWith({ + const expectedOptions: LintOptions = { cache: true, clearCache: true, debug: true, @@ -108,6 +112,8 @@ Commands: ignorePatterns: ['dist'], title: 'Rocket Lint', watch: true, - }) + } + + expect(lint).toHaveBeenCalledOnceWith(expectedOptions) }) }) diff --git a/src/commands/lint/index.ts b/src/commands/lint/index.ts index dd17ae9..73abc0c 100644 --- a/src/commands/lint/index.ts +++ b/src/commands/lint/index.ts @@ -1,4 +1,6 @@ -const lint = (options: any) => { +import type { LintOptions } from '@Types/commands' + +const lint = (options: LintOptions) => { console.log('Run Lint', options) } diff --git a/src/types/commands.ts b/src/types/commands.ts new file mode 100644 index 0000000..9c548a3 --- /dev/null +++ b/src/types/commands.ts @@ -0,0 +1,17 @@ +interface LintOptions { + cache: boolean + clearCache: boolean + debug: boolean + emoji: string + eslintInclude?: string | Array + eslintUseLegacyConfig: boolean + fix: boolean + ignoreDirs?: string | Array + ignorePatterns?: string | Array + title: string + watch: boolean +} + +export type { + LintOptions, +} diff --git a/tsconfig.json b/tsconfig.json index 943869b..a53939d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "./lib", "paths": { "@Jest/*": ["./jest-config/*"], - "@Types": ["./src/types/index.ts"], + "@Types/*": ["./src/types/*"], "@Utils/*": ["./src/utils/*"], }, "resolveJsonModule": true, From b73c402243fd7fb689e7504d08b462cde26032b6 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 21:52:03 +0000 Subject: [PATCH 12/16] =?UTF-8?q?Clear=20terminal=20when=20running=20lint?= =?UTF-8?q?=20command=20=F0=9F=86=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/lint/index.ts | 3 +++ src/utils/__tests__/terminal.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/commands/lint/index.ts b/src/commands/lint/index.ts index 73abc0c..cb393cf 100644 --- a/src/commands/lint/index.ts +++ b/src/commands/lint/index.ts @@ -1,6 +1,9 @@ import type { LintOptions } from '@Types/commands' +import { clearTerminal } from '@Utils/terminal' const lint = (options: LintOptions) => { + clearTerminal() + console.log('Run Lint', options) } diff --git a/src/utils/__tests__/terminal.spec.ts b/src/utils/__tests__/terminal.spec.ts index 427db89..6adeb57 100644 --- a/src/utils/__tests__/terminal.spec.ts +++ b/src/utils/__tests__/terminal.spec.ts @@ -3,11 +3,11 @@ import { clearTerminal } from '../terminal' describe('clearTerminal', () => { it('calls process.stdout.write to clear the terminal', () => { - const mockWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => true) + const stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true) clearTerminal() - expect(mockWrite).toHaveBeenCalledWith('\x1Bc\x1B[3J\x1B[2J\x1B[H') + expect(stdoutSpy).toHaveBeenCalledWith('\x1Bc\x1B[3J\x1B[2J\x1B[H') }) }) From de3a91d2c731e461842561d9a6b5ddbec2a48993 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 21:58:58 +0000 Subject: [PATCH 13/16] =?UTF-8?q?Log=20the=20title=20when=20running=20the?= =?UTF-8?q?=20lint=20command=20=F0=9F=93=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/lint/index.ts | 4 +++- src/utils/__tests__/colour-log.spec.ts | 24 ++++++++++++++++++++++++ src/utils/colour-log.ts | 7 +++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/utils/__tests__/colour-log.spec.ts create mode 100644 src/utils/colour-log.ts diff --git a/src/commands/lint/index.ts b/src/commands/lint/index.ts index cb393cf..d057a38 100644 --- a/src/commands/lint/index.ts +++ b/src/commands/lint/index.ts @@ -1,8 +1,10 @@ import type { LintOptions } from '@Types/commands' +import colourLog from '@Utils/colour-log' import { clearTerminal } from '@Utils/terminal' -const lint = (options: LintOptions) => { +const lint = ({ emoji, title, ...options }: LintOptions) => { clearTerminal() + colourLog.title(`${emoji} ${title}\n`) console.log('Run Lint', options) } diff --git a/src/utils/__tests__/colour-log.spec.ts b/src/utils/__tests__/colour-log.spec.ts new file mode 100644 index 0000000..8b9ce28 --- /dev/null +++ b/src/utils/__tests__/colour-log.spec.ts @@ -0,0 +1,24 @@ +import chalk from 'chalk' + +import colourLog from '../colour-log' + +jest.mock('chalk', () => ({ + cyan: jest.fn().mockImplementation(text => text), +})) + +describe('colourLog', () => { + + const mockedConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}) + + describe('title', () => { + + it('logs the title in cyan', () => { + colourLog.title('āœˆļø Lint Pilot') + + expect(chalk.cyan).toHaveBeenCalledOnceWith('āœˆļø Lint Pilot') + expect(mockedConsoleLog).toHaveBeenCalledOnceWith('āœˆļø Lint Pilot') + }) + + }) + +}) diff --git a/src/utils/colour-log.ts b/src/utils/colour-log.ts new file mode 100644 index 0000000..169c1dd --- /dev/null +++ b/src/utils/colour-log.ts @@ -0,0 +1,7 @@ +import chalk from 'chalk' + +const colourLog = { + title: (title: string) => console.log(chalk.cyan(title)), +} + +export default colourLog From 2a8bcc4a541a4d1bad584f49ce870395e9bb3261 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 22:07:51 +0000 Subject: [PATCH 14/16] =?UTF-8?q?Clear=20the=20cache=20when=20requested=20?= =?UTF-8?q?in=20the=20lint=20command=20=F0=9F=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/lint/index.ts | 9 ++++++++- src/utils/__tests__/cache.spec.ts | 11 ++++++----- src/utils/__tests__/colour-log.spec.ts | 12 ++++++++++++ src/utils/cache.ts | 2 +- src/utils/colour-log.ts | 1 + 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/commands/lint/index.ts b/src/commands/lint/index.ts index d057a38..e0828f6 100644 --- a/src/commands/lint/index.ts +++ b/src/commands/lint/index.ts @@ -1,11 +1,18 @@ import type { LintOptions } from '@Types/commands' +import { clearCacheDirectory } from '@Utils/cache' import colourLog from '@Utils/colour-log' import { clearTerminal } from '@Utils/terminal' -const lint = ({ emoji, title, ...options }: LintOptions) => { +const lint = ({ clearCache, debug, emoji, title, ...options }: LintOptions) => { + global.debug = debug + clearTerminal() colourLog.title(`${emoji} ${title}\n`) + if (clearCache) { + clearCacheDirectory() + } + console.log('Run Lint', options) } diff --git a/src/utils/__tests__/cache.spec.ts b/src/utils/__tests__/cache.spec.ts index 2582a61..6f9f442 100644 --- a/src/utils/__tests__/cache.spec.ts +++ b/src/utils/__tests__/cache.spec.ts @@ -1,19 +1,20 @@ import fs from 'node:fs' import path from 'node:path' -import colourLog from '@Utils/colourLog' +import colourLog from '@Utils/colour-log' import { clearCacheDirectory, getCacheDirectory } from '../cache' jest.mock('node:fs') -jest.mock('@Utils/colourLog', () => ({ - info: jest.fn(), -})) describe('clearCacheDirectory', () => { const expectedCacheDirectory = `${process.cwd()}/.lintpilotcache` + beforeEach(() => { + jest.spyOn(colourLog, 'info').mockImplementation(() => true) + }) + it('clears the cache directory if it exists', () => { jest.mocked(fs.existsSync).mockReturnValueOnce(true) @@ -41,7 +42,7 @@ describe('clearCacheDirectory', () => { describe('getCacheDirectory', () => { - it('returns the correct cache directory path for a given file', () => { + it('returns the cache directory for a given file', () => { jest.spyOn(path, 'resolve') const result = getCacheDirectory('.eslintcache') diff --git a/src/utils/__tests__/colour-log.spec.ts b/src/utils/__tests__/colour-log.spec.ts index 8b9ce28..9f3f4bc 100644 --- a/src/utils/__tests__/colour-log.spec.ts +++ b/src/utils/__tests__/colour-log.spec.ts @@ -3,6 +3,7 @@ import chalk from 'chalk' import colourLog from '../colour-log' jest.mock('chalk', () => ({ + blue: jest.fn().mockImplementation(text => text), cyan: jest.fn().mockImplementation(text => text), })) @@ -10,6 +11,17 @@ describe('colourLog', () => { const mockedConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}) + describe('info', () => { + + it('logs the text in blue', () => { + colourLog.info('Starting lint...') + + expect(chalk.blue).toHaveBeenCalledOnceWith('Starting lint...') + expect(mockedConsoleLog).toHaveBeenCalledOnceWith('Starting lint...') + }) + + }) + describe('title', () => { it('logs the title in cyan', () => { diff --git a/src/utils/cache.ts b/src/utils/cache.ts index c52823a..2b9106c 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -1,7 +1,7 @@ import fs from 'node:fs' import path from 'node:path' -import colourLog from './colourLog' +import colourLog from './colour-log' const CACHE_FOLDER = '.lintpilotcache' diff --git a/src/utils/colour-log.ts b/src/utils/colour-log.ts index 169c1dd..ac40376 100644 --- a/src/utils/colour-log.ts +++ b/src/utils/colour-log.ts @@ -1,6 +1,7 @@ import chalk from 'chalk' const colourLog = { + info: (text: string) => console.log(chalk.blue(text)), title: (title: string) => console.log(chalk.cyan(title)), } From 06f4c026754ad7585afb37b74344ee429e2303e1 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Sun, 27 Oct 2024 22:45:24 +0000 Subject: [PATCH 15/16] =?UTF-8?q?Get=20the=20file=20patterns=20to=20lint/i?= =?UTF-8?q?gnore=20=F0=9F=97=84=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/__tests__/index.spec.ts | 2 +- .../lint/__tests__/file-patterns.spec.ts} | 34 ++++++++++--------- .../lint/file-patterns.ts} | 27 ++++++--------- src/commands/lint/index.ts | 12 +++++-- src/index.ts | 14 ++++---- src/types/lint.ts | 20 +++++++++++ src/utils/__tests__/cache.spec.ts | 12 +++---- src/utils/__tests__/colour-log.spec.ts | 22 ++++++++++++ src/utils/colour-log.ts | 9 +++++ 10 files changed, 105 insertions(+), 49 deletions(-) rename src/{__tests__/filePatterns.spec.ts => commands/lint/__tests__/file-patterns.spec.ts} (76%) rename src/{filePatterns.ts => commands/lint/file-patterns.ts} (61%) create mode 100644 src/types/lint.ts diff --git a/package.json b/package.json index 3045259..1e04d5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lint-pilot", - "description": "Lint Pilot: Your co-pilot for maintaining high code quality with seamless ESLint, Stylelint, and MarkdownLint integration.", + "description": "Lint Pilot: Your co-pilot for maintaining high code quality with seamless ESLint, Stylelint, and Markdownlint integration.", "repository": { "type": "git", "url": "git+https://github.com/01taylop/lint-pilot.git" diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 699020a..fc53253 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -33,7 +33,7 @@ Options: -h, --help display help for command Commands: - lint [options] run all linters: ESLint, Stylelint, and MarkdownLint (default) + lint [options] run all linters: ESLint, Stylelint, and Markdownlint (default) help [command] display help for command `) }) diff --git a/src/__tests__/filePatterns.spec.ts b/src/commands/lint/__tests__/file-patterns.spec.ts similarity index 76% rename from src/__tests__/filePatterns.spec.ts rename to src/commands/lint/__tests__/file-patterns.spec.ts index c41639f..8a851ea 100644 --- a/src/__tests__/filePatterns.spec.ts +++ b/src/commands/lint/__tests__/file-patterns.spec.ts @@ -1,14 +1,16 @@ -import colourLog from '@Utils/colourLog' -import { Linter } from '@Types' +import { Linter } from '@Types/lint' +import colourLog from '@Utils/colour-log' -import getFilePatterns from '../filePatterns' +import { getFilePatterns } from '../file-patterns' -describe('filePatterns', () => { +describe('getFilePatterns', () => { - jest.spyOn(colourLog, 'config').mockImplementation(() => {}) - jest.spyOn(console, 'log').mockImplementation(() => {}) + beforeEach(() => { + jest.spyOn(colourLog, 'config').mockImplementation(() => true) + jest.spyOn(console, 'log').mockImplementation(() => true) + }) - it('returns the correct file patterns for eslint', () => { + it('returns the default file patterns for ESLint', () => { const filePatterns = getFilePatterns({}) const expectedPatterns = [ @@ -19,7 +21,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('ESLint Patterns', expectedPatterns) }) - it('returns the correct file patterns for eslint with an additional include pattern', () => { + it('returns the file patterns for ESLint with an additional include pattern', () => { const filePatterns = getFilePatterns({ eslintInclude: 'foo' }) @@ -33,7 +35,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('ESLint Patterns', expectedPatterns) }) - it('returns the correct file patterns for eslint with several additional include patterns', () => { + it('returns the file patterns for ESLint with several additional include patterns', () => { const filePatterns = getFilePatterns({ eslintInclude: ['foo', 'bar'], }) @@ -48,7 +50,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('ESLint Patterns', expectedPatterns) }) - it('returns the correct file patterns for markdownlint', () => { + it('returns the default file patterns for Markdownlint', () => { const filePatterns = getFilePatterns({}) const expectedPatterns = [ @@ -59,7 +61,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('Markdownlint Patterns', expectedPatterns) }) - it('returns the correct file patterns for stylelint', () => { + it('returns the default file patterns for Stylelint', () => { const filePatterns = getFilePatterns({}) const expectedPatterns = [ @@ -70,7 +72,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('Stylelint Patterns', expectedPatterns) }) - it('returns the correct ignore file patterns', () => { + it('returns the default ignore file patterns', () => { const filePatterns = getFilePatterns({}) const expectedPatterns = [ @@ -82,7 +84,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) }) - it('returns the correct ignore file patterns with an additional directory ignored', () => { + it('returns the ignore file patterns with an additional directory ignored', () => { const filePatterns = getFilePatterns({ ignoreDirs: 'foo' }) @@ -96,7 +98,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) }) - it('returns the correct ignore file patterns with several additional directories ignored', () => { + it('returns the ignore file patterns with several additional directories ignored', () => { const filePatterns = getFilePatterns({ ignoreDirs: ['foo', 'bar'], }) @@ -110,7 +112,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) }) - it('returns the correct ignore file patterns with an additional pattern ignored', () => { + it('returns the ignore file patterns with an additional pattern ignored', () => { const filePatterns = getFilePatterns({ ignorePatterns: 'foo' }) @@ -125,7 +127,7 @@ describe('filePatterns', () => { expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) }) - it('returns the correct ignore file patterns with several additional patterns ignored', () => { + it('returns the ignore file patterns with several additional patterns ignored', () => { const filePatterns = getFilePatterns({ ignorePatterns: ['foo', 'bar'], }) diff --git a/src/filePatterns.ts b/src/commands/lint/file-patterns.ts similarity index 61% rename from src/filePatterns.ts rename to src/commands/lint/file-patterns.ts index 43f3c1b..8df8d64 100644 --- a/src/filePatterns.ts +++ b/src/commands/lint/file-patterns.ts @@ -1,20 +1,13 @@ -import { type FilePatterns, Linter } from '@Types' -import colourLog from '@Utils/colourLog' +import type { LintOptions } from '@Types/commands' +import { type FilePatterns, Linter } from '@Types/lint' +import colourLog from '@Utils/colour-log' -type StringOrArray = string | Array +type GetFilePatterns = Pick -interface GetFilePatterns { - eslintInclude?: StringOrArray - ignoreDirs?: StringOrArray - ignorePatterns?: StringOrArray -} - -const enforceArray = (value: StringOrArray = []): Array => Array.of(value).flat() - -const getFilePatterns = ({ eslintInclude, ignoreDirs, ignorePatterns }: GetFilePatterns): FilePatterns => { +const getFilePatterns = ({ eslintInclude = [], ignoreDirs = [], ignorePatterns = [] }: GetFilePatterns): FilePatterns => { const eslintIncludePatterns = [ '**/*.{cjs,js,jsx,mjs,ts,tsx}', - ...enforceArray(eslintInclude), + ...Array.of(eslintInclude).flat(), ] const ignoreDirectories = [ @@ -24,12 +17,12 @@ const getFilePatterns = ({ eslintInclude, ignoreDirs, ignorePatterns }: GetFileP 'tmp', 'tscOutput', 'vendor', - ...enforceArray(ignoreDirs), + ...Array.of(ignoreDirs).flat(), ].sort() const ignoreGlobs = [ '**/*.+(map|min).*', - ...enforceArray(ignorePatterns), + ...Array.of(ignorePatterns).flat(), ].sort() const filePatterns = { @@ -53,4 +46,6 @@ const getFilePatterns = ({ eslintInclude, ignoreDirs, ignorePatterns }: GetFileP return filePatterns } -export default getFilePatterns +export { + getFilePatterns, +} diff --git a/src/commands/lint/index.ts b/src/commands/lint/index.ts index e0828f6..47851e0 100644 --- a/src/commands/lint/index.ts +++ b/src/commands/lint/index.ts @@ -3,7 +3,9 @@ import { clearCacheDirectory } from '@Utils/cache' import colourLog from '@Utils/colour-log' import { clearTerminal } from '@Utils/terminal' -const lint = ({ clearCache, debug, emoji, title, ...options }: LintOptions) => { +import { getFilePatterns } from './file-patterns' + +const lint = ({ clearCache, debug, emoji, eslintInclude, ignoreDirs, ignorePatterns, title, ...options }: LintOptions) => { global.debug = debug clearTerminal() @@ -13,7 +15,13 @@ const lint = ({ clearCache, debug, emoji, title, ...options }: LintOptions) => { clearCacheDirectory() } - console.log('Run Lint', options) + const filePatterns = getFilePatterns({ + eslintInclude, + ignoreDirs, + ignorePatterns, + }) + + console.log('Run Lint', options, filePatterns) } export default lint diff --git a/src/index.ts b/src/index.ts index bdcbb16..3d2d4d8 100755 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,7 @@ const createProgram = () => { program .command('lint', { isDefault: true }) - .description('run all linters: ESLint, Stylelint, and MarkdownLint (default)') + .description('run all linters: ESLint, Stylelint, and Markdownlint (default)') .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', 'āœˆļø') .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') @@ -37,14 +37,14 @@ const createProgram = () => { .option('--cache', 'cache linting results', false) .option('--clearCache', 'clear the cache', false) - .option('--ignore-dirs ', 'directories to ignore globally') - .option('--ignore-patterns ', 'file patterns to ignore globally') - .option('--eslint-include ', 'file patterns to include for ESLint') + .option('--ignore-dirs ', 'define directories to ignore') + .option('--ignore-patterns ', 'define file patterns to ignore') + .option('--eslint-include ', 'define additional file patterns for ESLint') - .option('--debug', 'output additional debug information including the list of files being linted', false) - .option('--eslint-use-legacy-config', 'set to true to use the legacy ESLint configuration', false) + .option('--debug', 'output additional debug information', false) + .option('--eslint-use-legacy-config', 'use legacy ESLint config', false) - .action(options => lint(options)) + .action(lint) return program } diff --git a/src/types/lint.ts b/src/types/lint.ts new file mode 100644 index 0000000..71ec183 --- /dev/null +++ b/src/types/lint.ts @@ -0,0 +1,20 @@ +enum Linter { + ESLint = 'ESLint', + Markdownlint = 'Markdownlint', + Stylelint = 'Stylelint', +} + +interface FilePatterns { + includePatterns: { + [key in Linter]: Array + } + ignorePatterns: Array +} + +export type { + FilePatterns, +} + +export { + Linter, +} diff --git a/src/utils/__tests__/cache.spec.ts b/src/utils/__tests__/cache.spec.ts index 6f9f442..38de2b6 100644 --- a/src/utils/__tests__/cache.spec.ts +++ b/src/utils/__tests__/cache.spec.ts @@ -20,12 +20,12 @@ describe('clearCacheDirectory', () => { clearCacheDirectory() - expect(fs.existsSync).toHaveBeenCalledWith(expectedCacheDirectory) - expect(fs.rmSync).toHaveBeenCalledWith(expectedCacheDirectory, { + expect(fs.existsSync).toHaveBeenCalledOnceWith(expectedCacheDirectory) + expect(fs.rmSync).toHaveBeenCalledOnceWith(expectedCacheDirectory, { force: true, recursive: true, }) - expect(colourLog.info).toHaveBeenCalledWith('Cache cleared.\n') + expect(colourLog.info).toHaveBeenCalledOnceWith('Cache cleared.\n') }) it('does not attempt to clear the cache if the directory does not exist', () => { @@ -33,9 +33,9 @@ describe('clearCacheDirectory', () => { clearCacheDirectory() - expect(fs.existsSync).toHaveBeenCalledWith(expectedCacheDirectory) + expect(fs.existsSync).toHaveBeenCalledOnceWith(expectedCacheDirectory) expect(fs.rmSync).not.toHaveBeenCalled() - expect(colourLog.info).toHaveBeenCalledWith('No cache to clear.\n') + expect(colourLog.info).toHaveBeenCalledOnceWith('No cache to clear.\n') }) }) @@ -47,7 +47,7 @@ describe('getCacheDirectory', () => { const result = getCacheDirectory('.eslintcache') - expect(path.resolve).toHaveBeenCalledWith(process.cwd(), '.lintpilotcache', '.eslintcache') + expect(path.resolve).toHaveBeenCalledOnceWith(process.cwd(), '.lintpilotcache', '.eslintcache') expect(result).toBe(`${process.cwd()}/.lintpilotcache/.eslintcache`) }) diff --git a/src/utils/__tests__/colour-log.spec.ts b/src/utils/__tests__/colour-log.spec.ts index 9f3f4bc..d321f0b 100644 --- a/src/utils/__tests__/colour-log.spec.ts +++ b/src/utils/__tests__/colour-log.spec.ts @@ -5,12 +5,34 @@ import colourLog from '../colour-log' jest.mock('chalk', () => ({ blue: jest.fn().mockImplementation(text => text), cyan: jest.fn().mockImplementation(text => text), + dim: jest.fn().mockImplementation(text => text), + magenta: jest.fn().mockImplementation(text => text), })) describe('colourLog', () => { const mockedConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}) + describe('config', () => { + + it('logs the key in magenta and a single config item in dim', () => { + colourLog.config('setting', ['foo']) + + expect(chalk.magenta).toHaveBeenCalledOnceWith('setting: ') + expect(chalk.dim).toHaveBeenCalledOnceWith('foo') + expect(mockedConsoleLog).toHaveBeenCalledOnceWith('setting: ', 'foo') + }) + + it('logs the key in magenta and a config array in dim', () => { + colourLog.config('setting', ['foo', 'bar', 'baz']) + + expect(chalk.magenta).toHaveBeenCalledOnceWith('setting: ') + expect(chalk.dim).toHaveBeenCalledOnceWith('[foo, bar, baz]') + expect(mockedConsoleLog).toHaveBeenCalledOnceWith('setting: ', '[foo, bar, baz]') + }) + + }) + describe('info', () => { it('logs the text in blue', () => { diff --git a/src/utils/colour-log.ts b/src/utils/colour-log.ts index ac40376..e9eea55 100644 --- a/src/utils/colour-log.ts +++ b/src/utils/colour-log.ts @@ -1,7 +1,16 @@ import chalk from 'chalk' const colourLog = { + config: (key: string, config: Array) => { + const configString = config.length > 1 + ? `[${config.join(', ')}]` + : config[0] + + console.log(chalk.magenta(`${key}: `), chalk.dim(configString)) + }, + info: (text: string) => console.log(chalk.blue(text)), + title: (title: string) => console.log(chalk.cyan(title)), } From c226e8a84c9fa789142c1d4b83197e95523d5584 Mon Sep 17 00:00:00 2001 From: Patrick Taylor Date: Mon, 28 Oct 2024 11:04:04 +0000 Subject: [PATCH 16/16] =?UTF-8?q?Watch=20for=20file=20changes=20?= =?UTF-8?q?=F0=9F=91=81=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lint/__tests__/watch-files.spec.ts} | 138 +++++++++--------- src/commands/lint/index.ts | 27 +++- .../lint/watch-files.ts} | 21 +-- 3 files changed, 109 insertions(+), 77 deletions(-) rename src/{__tests__/watchFiles.spec.ts => commands/lint/__tests__/watch-files.spec.ts} (51%) rename src/{watchFiles.ts => commands/lint/watch-files.ts} (70%) diff --git a/src/__tests__/watchFiles.spec.ts b/src/commands/lint/__tests__/watch-files.spec.ts similarity index 51% rename from src/__tests__/watchFiles.spec.ts rename to src/commands/lint/__tests__/watch-files.spec.ts index bc842ff..9d9bbfe 100644 --- a/src/__tests__/watchFiles.spec.ts +++ b/src/commands/lint/__tests__/watch-files.spec.ts @@ -1,17 +1,22 @@ -import { readFile } from 'fs' +import { readFile } from 'node:fs' import chokidar from 'chokidar' -import { Events } from '@Types' +import { EVENTS, fileChangeEvent, watchFiles } from '../watch-files' -import { fileChangeEvent, watchFiles } from '../watchFiles' - -jest.mock('fs') +jest.mock('node:fs') jest.mock('chokidar') describe('watchFiles', () => { let mockWatcher: chokidar.FSWatcher + const getEventPromise = (eventHandler: jest.Mock): Promise => new Promise((resolve) => { + fileChangeEvent.on(EVENTS.FILE_CHANGED, (params: Record) => { + eventHandler(params) + resolve() + }) + }) + const saveFile = (path: string, content: string, event: 'add' | 'change' | 'unlink') => { jest.mocked(readFile).mockImplementationOnce((_path: any, _encoding: any, callback?: (error: any, data: any) => void): void => { if (callback) { @@ -40,7 +45,7 @@ describe('watchFiles', () => { fileChangeEvent.removeAllListeners() }) - it('initialises chokidar with the correct file and ignore patterns', () => { + it('initialises chokidar with the file patterns and ignore patterns', () => { const filePatterns = ['**/*.ts'] const ignorePatterns = ['node_modules'] @@ -53,103 +58,106 @@ describe('watchFiles', () => { }) }) - it('emits a "FILE_CHANGED" event when saving an existing file (because there is no hash map yet)', done => { - expect.assertions(1) + it('emits a "FILE_CHANGED" event when saving an existing file (because there is no hash map yet)', async () => { + expect.assertions(2) + const eventHandler = jest.fn() + const eventPromise = getEventPromise(eventHandler) const mockPath = 'mock/existing-file.ts' - fileChangeEvent.on(Events.FILE_CHANGED, params => { - expect(params).toStrictEqual({ - message: `File \`${mockPath}\` has been changed.`, - path: mockPath, - }) - done() - }) - watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) - saveFile(mockPath, 'hello-world', 'change') + + await eventPromise + + expect(eventHandler).toHaveBeenCalledTimes(1) + expect(eventHandler).toHaveBeenNthCalledWith(1, { + message: `File \`${mockPath}\` has been changed.`, + path: mockPath, + }) }) - it('emits a "FILE_CHANGED" event when saving a file if the file content changes', done => { - expect.assertions(2) + it('emits a "FILE_CHANGED" event when saving a file if the file content changes', async () => { + expect.assertions(3) + const eventHandler = jest.fn() + const eventPromise = getEventPromise(eventHandler) const mockPath = 'mock/update-file.ts' - fileChangeEvent.on(Events.FILE_CHANGED, params => { - expect(params).toStrictEqual({ - message: `File \`${mockPath}\` has been changed.`, - path: mockPath, - }) - }) - watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) - saveFile(mockPath, 'old-content', 'change') // First save - no hash map yet saveFile(mockPath, 'new-content', 'change') // Second save - content hash is different - setTimeout(() => { - done() - }, 100) + await eventPromise + + expect(eventHandler).toHaveBeenCalledTimes(2) + expect(eventHandler).toHaveBeenNthCalledWith(1, { + message: `File \`${mockPath}\` has been changed.`, + path: mockPath, + }) + expect(eventHandler).toHaveBeenNthCalledWith(2, { + message: `File \`${mockPath}\` has been changed.`, + path: mockPath, + }) }) - it('does not emit a "FILE_CHANGED" event when saving a file if the file content did not change', done => { - expect.assertions(1) + it('does not emit a "FILE_CHANGED" event when saving a file if the file content did not change', async () => { + expect.assertions(2) + const eventHandler = jest.fn() + const eventPromise = getEventPromise(eventHandler) const mockPath = 'mock/unchanged-file.ts' - fileChangeEvent.on(Events.FILE_CHANGED, params => { - expect(params).toStrictEqual({ - message: `File \`${mockPath}\` has been changed.`, - path: mockPath, - }) - }) - watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) - saveFile(mockPath, 'old-content', 'change') // First save - no hash map yet saveFile(mockPath, 'old-content', 'change') // Second save - content hash is the same saveFile(mockPath, 'old-content', 'change') // Third save - content hash is still the same - setTimeout(() => { - done() - }, 100) + await eventPromise + + expect(eventHandler).toHaveBeenCalledTimes(1) + expect(eventHandler).toHaveBeenNthCalledWith(1, { + message: `File \`${mockPath}\` has been changed.`, + path: mockPath, + }) }) - it('emits a "FILE_CHANGED" event when a new file is added', done => { - expect.assertions(1) + it('emits a "FILE_CHANGED" event when a new file is added', async () => { + expect.assertions(2) + const eventHandler = jest.fn() + const eventPromise = getEventPromise(eventHandler) const mockPath = 'mock/new-file.ts' - fileChangeEvent.on(Events.FILE_CHANGED, params => { - expect(params).toStrictEqual({ - message: `File \`${mockPath}\` has been added.`, - path: mockPath, - }) - done() - }) - watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) - saveFile(mockPath, 'new-content', 'add') + + await eventPromise + + expect(eventHandler).toHaveBeenCalledTimes(1) + expect(eventHandler).toHaveBeenNthCalledWith(1, { + message: `File \`${mockPath}\` has been added.`, + path: mockPath, + }) }) - it('emits a "FILE_CHANGED" event when a file is removed', done => { - expect.assertions(1) + it('emits a "FILE_CHANGED" event when a file is removed', async () => { + expect.assertions(2) + const eventHandler = jest.fn() + const eventPromise = getEventPromise(eventHandler) const mockPath = 'mock/legacy-file.ts' - fileChangeEvent.on(Events.FILE_CHANGED, params => { - expect(params).toStrictEqual({ - message: `File \`${mockPath}\` has been removed.`, - path: mockPath, - }) - done() - }) - watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) - saveFile(mockPath, 'legacy-content', 'unlink') + + await eventPromise + + expect(eventHandler).toHaveBeenCalledTimes(1) + expect(eventHandler).toHaveBeenNthCalledWith(1, { + message: `File \`${mockPath}\` has been removed.`, + path: mockPath, + }) }) }) diff --git a/src/commands/lint/index.ts b/src/commands/lint/index.ts index 47851e0..e2c3f6d 100644 --- a/src/commands/lint/index.ts +++ b/src/commands/lint/index.ts @@ -4,8 +4,9 @@ import colourLog from '@Utils/colour-log' import { clearTerminal } from '@Utils/terminal' import { getFilePatterns } from './file-patterns' +import { EVENTS, fileChangeEvent, watchFiles } from './watch-files' -const lint = ({ clearCache, debug, emoji, eslintInclude, ignoreDirs, ignorePatterns, title, ...options }: LintOptions) => { +const lint = ({ cache, clearCache, debug, emoji, eslintInclude, eslintUseLegacyConfig, fix, ignoreDirs, ignorePatterns, title, watch }: LintOptions) => { global.debug = debug clearTerminal() @@ -21,7 +22,29 @@ const lint = ({ clearCache, debug, emoji, eslintInclude, ignoreDirs, ignorePatte ignorePatterns, }) - console.log('Run Lint', options, filePatterns) + const lintOptions = { + cache, + eslintUseLegacyConfig, + filePatterns, + fix, + title, + watch, + } + + console.log('Run Lint', lintOptions) // TODO: Run lint + + if (watch) { + watchFiles({ + filePatterns: Object.values(filePatterns.includePatterns).flat(), + ignorePatterns: filePatterns.ignorePatterns, + }) + + fileChangeEvent.on(EVENTS.FILE_CHANGED, ({ message }) => { + clearTerminal() + colourLog.info(`${message}\n`) + console.log('Run Lint', lintOptions) // TODO: Run lint + }) + } } export default lint diff --git a/src/watchFiles.ts b/src/commands/lint/watch-files.ts similarity index 70% rename from src/watchFiles.ts rename to src/commands/lint/watch-files.ts index dd001c9..9ff6afe 100644 --- a/src/watchFiles.ts +++ b/src/commands/lint/watch-files.ts @@ -1,10 +1,12 @@ -import { createHash } from 'crypto' -import { EventEmitter } from 'events' -import { readFile } from 'fs' +import { createHash } from 'node:crypto' +import { EventEmitter } from 'node:events' +import { readFile } from 'node:fs' import chokidar from 'chokidar' -import { Events } from '@Types' +enum EVENTS { + FILE_CHANGED = 'FILE_CHANGED', +} interface WatchFiles { filePatterns: Array @@ -15,8 +17,6 @@ const fileChangeEvent = new EventEmitter() const fileHashes = new Map() -const getHash = (data: string) => createHash('md5').update(data).digest('hex') - const watchFiles = ({ filePatterns, ignorePatterns }: WatchFiles) => { const watcher = chokidar.watch(filePatterns, { ignored: ignorePatterns, @@ -25,7 +25,7 @@ const watchFiles = ({ filePatterns, ignorePatterns }: WatchFiles) => { }) watcher.on('add', (path, _stats) => { - fileChangeEvent.emit(Events.FILE_CHANGED, { + fileChangeEvent.emit(EVENTS.FILE_CHANGED, { message: `File \`${path}\` has been added.`, path, }) @@ -33,10 +33,10 @@ const watchFiles = ({ filePatterns, ignorePatterns }: WatchFiles) => { watcher.on('change', (path, _stats) => { readFile(path, 'utf8', (_error, data) => { - const newHash = getHash(data) + const newHash = createHash('md5').update(data).digest('hex') if (fileHashes.get(path) !== newHash) { fileHashes.set(path, newHash) - fileChangeEvent.emit(Events.FILE_CHANGED, { + fileChangeEvent.emit(EVENTS.FILE_CHANGED, { message: `File \`${path}\` has been changed.`, path, }) @@ -45,7 +45,7 @@ const watchFiles = ({ filePatterns, ignorePatterns }: WatchFiles) => { }) watcher.on('unlink', path => { - fileChangeEvent.emit(Events.FILE_CHANGED, { + fileChangeEvent.emit(EVENTS.FILE_CHANGED, { message: `File \`${path}\` has been removed.`, path, }) @@ -53,6 +53,7 @@ const watchFiles = ({ filePatterns, ignorePatterns }: WatchFiles) => { } export { + EVENTS, fileChangeEvent, watchFiles, }