Skip to content

Use commander package to replace current implementation of CLI options #16

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 70 additions & 10 deletions package-lock.json

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

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"postversion": "npm install --package-lock-only --ignore-scripts --silent",
"tsx": "tsx",
"lint": "test -f ./dist/bin/lint.js || npm run build && ./dist/bin/lint.js",
"lintfix": "sh -c 'test -f ./dist/bin/lint.js || npm run build && ./dist/bin/lint.js --fix'",
"lintfix": "sh -c 'test -f ./dist/bin/lint.js || npm run build && ./dist/bin/lint.js --fix --eslint \"{src,scripts}/**/*.{js,mjs,ts,mts,jsx,tsx}\"'",
"lint-shell": "find ./src ./tests ./scripts -type f -regextype posix-extended -regex '.*\\.(sh)' -exec shellcheck {} +",
"docs": "shx rm -rf ./docs && typedoc --entryPointStrategy expand --gitRevision master --tsconfig ./tsconfig.build.json --out ./docs src",
"test": "node ./scripts/test.mjs"
Expand All @@ -38,11 +38,11 @@
"@typescript-eslint/eslint-plugin": "^8.27.0",
"@typescript-eslint/parser": "^8.27.0",
"@typescript-eslint/utils": "^8.26.1",
"eslint": "^9.18.0",
"eslint": ">=9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-tailwindcss": "^3.18.0"
Expand All @@ -52,9 +52,11 @@
"@swc/jest": "^0.2.29",
"@types/jest": "^29.5.2",
"@types/node": "^20.5.7",
"commander": "^13.1.0",
"jest": "^29.6.2",
"jest-extended": "^4.0.2",
"jest-junit": "^16.0.0",
"minimatch": "^10.0.1",
"prettier": "^3.0.0",
"shx": "^0.3.4",
"tsconfig-paths": "^3.9.0",
Expand Down
55 changes: 32 additions & 23 deletions src/bin/lint.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
#!/usr/bin/env node
import type { CLIOptions } from '../types.js';
import os from 'node:os';
import path from 'node:path';
import process from 'node:process';
import childProcess from 'node:child_process';
import fs from 'node:fs';
import { Command } from 'commander';
import * as utils from '../utils.js';

const platform = os.platform();
const program = new Command();

program
.name('matrixai-lint')
.description(
'Lint source files, scripts, and markdown with configured rules.',
)
.option('-f, --fix', 'Automatically fix problems')
.option(
'--user-config',
'Use user-provided ESLint config instead of built-in one',
)
.option('--config <path>', 'Path to explicit ESLint config file')
.option('--eslint <pat...>', 'Glob(s) to pass to ESLint')
.option('--shell <pat...>', 'Glob(s) to pass to shell-check')
.allowUnknownOption(true); // Optional: force rejection of unknown flags

/* eslint-disable no-console */
async function main(argv = process.argv) {
argv = argv.slice(2);
await program.parseAsync(argv);
const options = program.opts<CLIOptions>();

const fix = Boolean(options.fix);
const useUserConfig = Boolean(options.userConfig);
const explicitConfigPath: string | undefined = options.config;

const eslintPatterns: string[] | undefined = options.eslint;
const shellPatterns: string[] | undefined = options.shell;

let hadFailure = false;
let fix = false;
let useUserConfig = false;
let explicitConfigPath: string | undefined;
const restArgs: string[] = [];

while (argv.length > 0) {
const option = argv.shift()!;
switch (option) {
case '--fix':
fix = true;
break;
case '--user-config':
useUserConfig = true;
break;
case '--config':
explicitConfigPath = argv.shift(); // Grab the next token
break;
default:
restArgs.push(option);
}
}

// Resolve which config file to use
let chosenConfig: string | undefined;
Expand All @@ -59,7 +64,11 @@ async function main(argv = process.argv) {
}

try {
await utils.runESLint({ fix, configPath: chosenConfig });
await utils.runESLint({
fix,
configPath: chosenConfig,
explicitGlobs: eslintPatterns,
});
} catch (err) {
console.error(`ESLint failed: \n${err}`);
hadFailure = true;
Expand Down
10 changes: 9 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@ type RawMatrixCfg = Partial<{
forceInclude: unknown;
}>; // “might have these two keys, values are unknown”

export type { MatrixAILintCfg, RawMatrixCfg };
type CLIOptions = {
fix: boolean;
userConfig: boolean;
config?: string;
eslint?: string[];
shell?: string[];
};

export type { MatrixAILintCfg, RawMatrixCfg, CLIOptions };
44 changes: 32 additions & 12 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,72 @@ import { ESLint } from 'eslint';
async function runESLint({
fix,
configPath,
explicitGlobs,
}: {
fix: boolean;
configPath?: string;
explicitGlobs?: string[];
}) {
const dirname = path.dirname(url.fileURLToPath(import.meta.url));
const defaultConfigPath = path.resolve(dirname, './configs/js.js');

const matrixaiLintConfig = resolveMatrixConfig();
const forceInclude = matrixaiLintConfig.forceInclude;
const tsconfigPaths = matrixaiLintConfig.tsconfigPaths;
// PATH A – user supplied explicit globs
if (explicitGlobs?.length) {
console.log('Linting with explicit patterns:');
explicitGlobs.forEach((g) => console.log(' ' + g));

const eslint = new ESLint({
overrideConfigFile: configPath || defaultConfigPath,
fix,
errorOnUnmatchedPattern: false,
warnIgnored: false,
ignorePatterns: [], // Trust caller entirely
});

await lintAndReport(eslint, explicitGlobs, fix);
return;
}

// PATH B – default behaviour (tsconfig + matrix config)
const { forceInclude, tsconfigPaths } = resolveMatrixConfig();

if (tsconfigPaths.length === 0) {
console.error('[matrixai-lint] ⚠ No tsconfig.json files found.');
}

console.log(`Found ${tsconfigPaths.length} tsconfig.json files:`);
tsconfigPaths.forEach((tsconfigPath) => console.log(' ' + tsconfigPath));
tsconfigPaths.forEach((p) => console.log(' ' + p));

const { files: lintFiles, ignore } = buildPatterns(
const { files: patterns, ignore: ignorePats } = buildPatterns(
tsconfigPaths[0],
forceInclude,
);

console.log('Linting files:');
lintFiles.forEach((file) => console.log(' ' + file));
patterns.forEach((p) => console.log(' ' + p));

const eslint = new ESLint({
overrideConfigFile: configPath || defaultConfigPath,
fix,
errorOnUnmatchedPattern: false,
warnIgnored: false,
ignorePatterns: ignore,
ignorePatterns: ignorePats,
});

const results = await eslint.lintFiles(lintFiles);
await lintAndReport(eslint, patterns, fix);
}

async function lintAndReport(eslint: ESLint, patterns: string[], fix: boolean) {
const results = await eslint.lintFiles(patterns);

if (fix) {
await ESLint.outputFixes(results);
}

const formatter = await eslint.loadFormatter('stylish');
const resultText = formatter.format(results);
console.log(resultText);

/* eslint-enable no-console */
console.log(formatter.format(results));
}
/* eslint-enable no-console */

/**
* Find the user's ESLint config file in the current working directory.
Expand Down