2023-09-09 21:40:53 -03:00
/* eslint-disable no-console */
/* eslint-disable import/max-dependencies */
import process from 'node:process' ;
2023-08-03 18:33:58 -03:00
import path from 'node:path' ;
2023-09-09 21:40:53 -03:00
2023-08-04 11:12:32 -03:00
import { createSpinner } from 'nanospinner' ;
2023-09-09 21:40:53 -03:00
import { Command } from 'commander' ;
2023-08-25 17:31:56 -03:00
import { erase } from 'sisteransi' ;
2023-09-09 21:40:53 -03:00
import cardinal from 'cardinal' ;
import prompts from 'prompts' ;
import c from 'picocolors' ;
import PackageInstaller from './package-installer.js' ;
import ConfigsProcessor from './configs-processor.js' ;
import ConfigsFile from './configs-file.js' ;
import notNull from './lib/not-null.js' ;
import Workspace from './workspace.js' ;
import count from './lib/count.js' ;
2023-08-14 14:33:44 -03:00
const stdout = process . stdout ;
2023-08-01 16:20:17 -03:00
export default class Cli {
2023-08-03 18:33:58 -03:00
# program = new Command ( ) ;
2023-08-01 16:20:17 -03:00
2023-08-03 18:33:58 -03:00
/** @type {import('./types').CliArgs} */
args = {
2023-08-29 15:08:29 -03:00
configs : [ ] ,
2023-09-09 21:40:53 -03:00
dir : process . cwd ( ) ,
2023-08-03 18:33:58 -03:00
} ;
2023-08-01 16:20:17 -03:00
2023-08-04 10:07:59 -03:00
/ * *
2023-09-09 21:40:53 -03:00
* @ param { import ( './types' ) . CliArgs } [ args ] - Cli arguments object .
2023-08-04 10:07:59 -03:00
* /
constructor ( args ) {
2023-08-03 18:33:58 -03:00
this . # program
2023-08-25 17:31:56 -03:00
. argument ( '[url-to-config]' )
2023-08-03 18:33:58 -03:00
. option ( '--packages <string...>' )
2023-08-04 10:07:59 -03:00
. option ( '--dir <path>' , undefined )
2023-08-23 10:54:15 -03:00
. option ( '--merge-to-root' )
. option ( '--install-pkgs' )
2023-08-04 10:07:59 -03:00
. parse ( ) ;
2023-08-03 16:53:26 -03:00
2023-08-03 18:33:58 -03:00
this . args = {
... this . args ,
... this . # program . opts ( ) ,
2023-08-04 10:07:59 -03:00
... args ,
2023-08-03 18:33:58 -03:00
} ;
2023-08-03 16:53:26 -03:00
2023-09-09 21:40:53 -03:00
this . args . dir = this . args . dir . startsWith ( '/' )
? this . args . dir
: path . join ( process . cwd ( ) , this . args . dir ) ;
2023-08-03 18:33:58 -03:00
}
2023-08-03 16:53:26 -03:00
2023-09-09 21:40:53 -03:00
// eslint-disable-next-line max-lines-per-function, max-statements, complexity
2023-08-03 18:33:58 -03:00
async run ( ) {
2023-08-23 10:54:15 -03:00
process . chdir ( this . args . dir ) ;
2023-08-29 15:08:29 -03:00
const configs = this . args . configs ;
2023-08-04 11:12:32 -03:00
const spinner = createSpinner ( 'Detecting workspace configuration' ) ;
2023-08-03 18:33:58 -03:00
const processor = new ConfigsProcessor ( { configs } ) ;
2023-09-09 21:40:53 -03:00
const workspace = new Workspace ( this . args . dir , this . args . packages ) ;
2023-08-04 11:12:32 -03:00
2023-09-09 21:40:53 -03:00
let packages = await workspace . getPackages ( ) ;
packages = packages . map ( ( pkg ) => {
spinner . update ( {
text : ` Detecting configuration for package ${ c . bold ( c . blue ( pkg . name ) ) } ` ,
} ) ;
2023-08-04 11:12:32 -03:00
2023-09-09 21:40:53 -03:00
pkg . config = processor . detectConfig ( pkg ) ;
2023-08-04 11:12:32 -03:00
2023-09-09 21:40:53 -03:00
return pkg ;
} ) ;
2023-08-04 11:12:32 -03:00
2023-08-04 16:10:24 -03:00
spinner . success ( {
text :
2023-09-09 21:40:53 -03:00
` Detecting workspace configuration ${
c . dim ( ` ${ count . packagesWithConfigs ( packages ) } configs founded \n ` ) } ` ,
2023-08-04 11:12:32 -03:00
} ) ;
2023-09-09 21:40:53 -03:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const merge = this . args . mergeToRoot ? ? ( packages . length > 1
2023-08-14 14:33:44 -03:00
/** @type {{merge: boolean}} */
2023-09-09 21:40:53 -03:00
? ( await prompts ( {
2023-08-04 16:10:24 -03:00
initial : true ,
2023-09-09 21:40:53 -03:00
message :
` Would you like to merge all configuration files into one root ${ c . blue ( 'eslint.config.js?' ) } ${
c . italic ( c . dim ( '\nAll configurations will be applied to the entire workspace and packages' ) ) } ` ,
name : 'merge' ,
2023-08-04 16:10:24 -03:00
type : 'confirm' ,
2023-09-09 21:40:53 -03:00
// eslint-disable-next-line unicorn/no-await-expression-member
} ) ) . merge : true ) ;
2023-08-04 16:10:24 -03:00
console . log ( c . dim ( '\nPlease select which options you prefer\n' ) ) ;
packages = await processor . questionConfig (
2023-09-09 21:40:53 -03:00
merge ? Workspace . mergePackages ( packages ) : packages ,
configs . filter ( config => config . manual ) ,
2023-08-04 16:10:24 -03:00
) ;
2023-08-01 16:20:17 -03:00
2023-09-09 21:40:53 -03:00
const fileHandler = new ConfigsFile (
configs ,
packages . find ( config => config . root ) ? . path ,
) ;
2023-08-01 16:20:17 -03:00
2023-08-14 14:33:44 -03:00
for ( const pkg of packages ) {
pkg . configFile = fileHandler . generateObj ( pkg ) ;
2023-09-09 21:40:53 -03:00
// eslint-disable-next-line no-await-in-loop
pkg . configFile . content = await ConfigsFile . generate ( pkg . configFile ) ;
2023-08-14 14:58:41 -03:00
/** @type {boolean} */
const shouldWrite =
/** @type {{write: boolean}} */
2023-09-09 21:40:53 -03:00
// eslint-disable-next-line no-await-in-loop
2023-08-14 14:58:41 -03:00
( await prompts ( {
2023-09-09 21:40:53 -03:00
initial : true ,
2023-08-14 14:58:41 -03:00
message : ` Do you want to write this config file for ${ pkg . root
? c . blue ( 'the root directory' )
: c . blue ( pkg . name )
2023-08-14 14:33:44 -03:00
} ? \ n \ n$ { cardinal . highlight ( pkg . configFile . content ) } ` ,
2023-09-09 21:40:53 -03:00
name : 'write' ,
type : 'confirm' ,
// eslint-disable-next-line unicorn/no-await-expression-member
2023-08-14 14:58:41 -03:00
} ) ) . write ;
2023-08-14 14:33:44 -03:00
2023-09-09 21:40:53 -03:00
stdout . write (
erase . lines ( pkg . configFile . content . split ( '\n' ) . length + 2 ) ,
) ;
if ( shouldWrite ) {
// eslint-disable-next-line no-await-in-loop
await ConfigsFile . write (
pkg . configFile . path ,
pkg . configFile . content ,
) ;
}
2023-08-14 14:33:44 -03:00
}
2023-08-01 16:20:17 -03:00
2023-09-09 21:40:53 -03:00
const packagesMap = new Map ( packages . map ( p =>
[ p . path , [ ... notNull ( p . configFile ) . imports . keys ( ) ] ] ,
) ) ;
const installer = new PackageInstaller (
packagesMap ,
packages . find ( p => p . root === true ) ? . path ? ? this . args . dir ,
) ;
2023-08-23 10:54:15 -03:00
/** @type {boolean | 'changePackage'} */
2023-09-09 21:40:53 -03:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
let installPkgs = this . args . installPkgs ? ? ( await prompts ( {
choices : [
{
description : installer . packageManager . description ,
title : 'Yes, install all packages' ,
value : true ,
} ,
{ title : 'No, I will install them manually' , value : false } ,
{ title : 'Change package manager' , value : 'changePackage' } ,
] ,
message :
` Would you like to ESLit to install the npm packages with ${ c . green ( installer . packageManager . name ) } ? \n ${ c . reset ( c . dim ( ` Packages to install: ${ [ ... new Set ( packagesMap . values ( ) ) ] . join ( ' ' ) } \n ` ) ) } ` ,
name : 'install' ,
type : 'select' ,
// eslint-disable-next-line unicorn/no-await-expression-member
} ) ) . install ;
2023-08-23 10:54:15 -03:00
if ( installPkgs === 'changePackage' ) {
/** @type {{manager: import('./types').PackageManagerName}} */
const prompt = await prompts ( {
2023-09-09 21:40:53 -03:00
choices : Object . values ( installer . packageManagers ) . map ( m => ( {
description : m . description ,
title : m . name ,
value : m . name ,
} ) ) ,
2023-08-23 10:54:15 -03:00
message : 'What package manager do you want ESLit to use?' ,
2023-09-09 21:40:53 -03:00
name : 'manager' ,
2023-08-23 10:54:15 -03:00
type : 'select' ,
} ) ;
2023-09-09 21:40:53 -03:00
installer . packageManager =
installer . packageManagers [ prompt . manager ] ;
2023-08-23 11:40:16 -03:00
2023-09-09 21:40:53 -03:00
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if ( ! installer . packageManager )
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-throw-literal, @typescript-eslint/no-confusing-void-expression
throw console . log ( c . red ( 'You must select a package manager' ) ) ;
2023-08-23 11:40:16 -03:00
2023-08-23 10:54:15 -03:00
installPkgs = true ;
}
if ( installPkgs ) await installer . install ( ) ;
2023-08-01 16:20:17 -03:00
}
}