-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(cli): ♻️ reorganize the entire structure of the code to impr…
…ove its organization
- Loading branch information
Showing
37 changed files
with
601 additions
and
582 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@shlroland/lint-cli": minor | ||
--- | ||
|
||
Reorganize the entire structure of the code to improve its organization |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
#!/usr/bin/env node | ||
'use strict' | ||
|
||
import '../dist/cli.js' | ||
import '../dist/main.js' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import type { Config } from './config' | ||
import type { Installer } from './install' | ||
import { deleteFile, shouldOverridePrompt } from '../../utils' | ||
|
||
export abstract class AbstractAnswer { | ||
static toolName: string | ||
|
||
abstract answerName: string | ||
|
||
abstract installer: Installer | ||
|
||
abstract config: Config | ||
|
||
async configGuard(): Promise<void> { | ||
const configs = this.config.pendingConfigs | ||
for (const config of configs) { | ||
if (await config.checkConfigFileExisted()) { | ||
const shouldOverride = await shouldOverridePrompt(this.answerName) | ||
if (shouldOverride) { | ||
config.overrideFile = async () => { | ||
await deleteFile(config.configFilePath) | ||
} | ||
this.config.addPendingConfig(config) | ||
} | ||
else { | ||
this.config.removePendingConfig(config) | ||
} | ||
} | ||
} | ||
} | ||
|
||
get pendingPackages(): string[] { | ||
return this.installer.pendingPackages | ||
} | ||
|
||
get pendingConfigs(): { configFilePath: string, configFileContent: string, overrideFile: () => Promise<void> }[] { | ||
const result: { configFilePath: string, configFileContent: string, overrideFile: () => Promise<void> }[] = [] | ||
|
||
for (const config of this.config.pendingConfigs) { | ||
result.push({ | ||
configFilePath: config.configFilePath, | ||
configFileContent: config.configFileContent, | ||
overrideFile: config.overrideFile, | ||
}) | ||
} | ||
|
||
return result | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import fs from 'node:fs' | ||
import path from 'node:path' | ||
import process from 'node:process' | ||
import { cjsConfigFactory, ensureConfig, esmConfigFactory } from '../../utils' | ||
import { AnswerContext } from './context' | ||
|
||
export abstract class ConfigOption { | ||
protected context: AnswerContext = AnswerContext.instance | ||
|
||
abstract configFileName: string | ||
|
||
abstract configFilePath: string | ||
|
||
abstract configFileContent: string | ||
|
||
abstract checkConfigFileExisted(): Promise<boolean> | ||
|
||
async overrideFile(): Promise<void> { | ||
return Promise.resolve() | ||
} | ||
} | ||
|
||
export class CommonConfigOption extends ConfigOption { | ||
configFileName: string | ||
|
||
configFilePath: string | ||
|
||
configFileContent: string | ||
|
||
constructor(options: { configFileName: string, content: string }) { | ||
super() | ||
this.configFileName = options.configFileName | ||
this.configFilePath = path.join(this.context.cwd, this.configFileName) | ||
this.configFileContent = options.content | ||
} | ||
|
||
async checkConfigFileExisted(): Promise<boolean> { | ||
const filePath = path.resolve(process.cwd(), this.configFilePath) | ||
const exists = await fs.promises.access(filePath).then(() => true).catch(() => false) | ||
return exists | ||
} | ||
} | ||
|
||
export class CosmiConfigOption extends ConfigOption { | ||
configFileName: string | ||
|
||
configFilePath: string | ||
|
||
configFileContent: string | ||
|
||
private esmImportConfigContent: string | ||
|
||
private esmExportConfigContent: string | ||
|
||
private cjsImportConfigContent: string | ||
|
||
private cjsExportConfigContent: string | ||
|
||
private checkConfigNames: string[] | ||
|
||
constructor( | ||
options: { | ||
configFileName: string | ||
esmImportConfigContent: string | ||
esmExportConfigContent: string | ||
cjsImportConfigContent: string | ||
cjsExportConfigContent: string | ||
checkConfigNames: string[] | ||
}, | ||
) { | ||
super() | ||
this.configFileName = options.configFileName | ||
this.esmImportConfigContent = options.esmImportConfigContent | ||
this.esmExportConfigContent = options.esmExportConfigContent | ||
this.cjsImportConfigContent = options.cjsImportConfigContent | ||
this.cjsExportConfigContent = options.cjsExportConfigContent | ||
this.checkConfigNames = options.checkConfigNames | ||
const { content, path } = this.configFileContentFactory() | ||
this.configFileContent = content | ||
this.configFilePath = path | ||
} | ||
|
||
private configFileContentFactory(): { content: string, path: string } { | ||
const content = this.context.moduleType === 'module' | ||
? esmConfigFactory(this.esmExportConfigContent, this.esmImportConfigContent) | ||
: cjsConfigFactory(this.cjsExportConfigContent, this.cjsImportConfigContent) | ||
|
||
return { | ||
content, | ||
path: path.join(this.context.cwd, this.configFileName), | ||
} | ||
} | ||
|
||
async checkConfigFileExisted(): Promise<boolean> { | ||
const checkConfigNames = this.checkConfigNames | ||
for (const configName of checkConfigNames) { | ||
const config = await ensureConfig(configName) | ||
if (config) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import type { ConfigOption } from './config-option' | ||
|
||
export class Config { | ||
#pendingConfigs: Map<string, ConfigOption> = new Map() | ||
|
||
constructor(defaultConfigs: ConfigOption[]) { | ||
for (const config of defaultConfigs) { | ||
this.#pendingConfigs.set(config.configFileName, config) | ||
} | ||
} | ||
|
||
addPendingConfig(config: ConfigOption) { | ||
this.#pendingConfigs.set(config.configFileName, config) | ||
} | ||
|
||
removePendingConfig(config: ConfigOption) { | ||
this.#pendingConfigs.delete(config.configFileName) | ||
} | ||
|
||
clearPendingConfigs() { | ||
this.#pendingConfigs.clear() | ||
} | ||
|
||
get pendingConfigs() { | ||
return this.#pendingConfigs.values() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import process from 'node:process' | ||
import { getModuleType } from '../../utils' | ||
|
||
export interface Context { | ||
moduleType: 'module' | 'commonjs' | ||
cwd: string | ||
} | ||
|
||
export class AnswerContext { | ||
moduleType: 'module' | 'commonjs' | ||
|
||
cwd: string | ||
|
||
constructor(context: Context) { | ||
this.cwd = context.cwd | ||
|
||
this.moduleType = context.moduleType | ||
} | ||
|
||
static instance: AnswerContext | ||
} | ||
|
||
export async function initAnswerContext() { | ||
const cwd = process.cwd() | ||
const moduleType = await getModuleType(cwd) | ||
const context = { | ||
moduleType, | ||
cwd, | ||
} | ||
|
||
AnswerContext.instance = new AnswerContext(context) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class Installer extends Set<string> { | ||
get pendingPackages(): string[] { | ||
return Array.from(this.values()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { AbstractAnswer } from './abstract/answer' | ||
import { Config } from './abstract/config' | ||
import { CosmiConfigOption } from './abstract/config-option' | ||
import { Installer } from './abstract/install' | ||
|
||
export class CommitlintAnswer extends AbstractAnswer { | ||
static toolName = 'commitlint' | ||
|
||
answerName = 'commitlint' | ||
|
||
installer = new Installer(['czg', '@commitlint/cli', '@shlroland/cz-config']) | ||
|
||
config = new Config([ | ||
new CosmiConfigOption({ | ||
configFileName: 'commitlint.config.js', | ||
esmImportConfigContent: ``, | ||
esmExportConfigContent: `{ extends: ['@shlroland/cz-config/commitlint'] }`, | ||
cjsImportConfigContent: ``, | ||
cjsExportConfigContent: `{ extends: ['@shlroland/cz-config/commitlint'] }`, | ||
checkConfigNames: ['commitlint'], | ||
}), | ||
]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { AbstractAnswer } from './abstract/answer' | ||
import { Config } from './abstract/config' | ||
import { CosmiConfigOption } from './abstract/config-option' | ||
import { Installer } from './abstract/install' | ||
|
||
export class EslintAnswer extends AbstractAnswer { | ||
static toolName = 'eslint' | ||
|
||
answerName = 'eslint' | ||
|
||
installer = new Installer(['eslint', '@shlroland/eslint-config', 'eslint-plugin-format']) | ||
|
||
config = new Config( | ||
[ | ||
new CosmiConfigOption({ | ||
configFileName: 'eslint.config.js', | ||
esmImportConfigContent: 'import { shlroland } from "@shlroland/eslint-config"', | ||
esmExportConfigContent: 'shlroland()', | ||
cjsImportConfigContent: 'const { shlroland } = require("@shlroland/eslint-config")', | ||
cjsExportConfigContent: 'shlroland()', | ||
checkConfigNames: ['eslint'], | ||
}), | ||
], | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import huskyConfig from '@shlroland/husky-config' | ||
import { deleteFile, initGit, isGitRepository, shouldInitGitPrompt, shouldOverridePrompt } from '../utils' | ||
import { AbstractAnswer } from './abstract/answer' | ||
import { Config } from './abstract/config' | ||
import { CommonConfigOption } from './abstract/config-option' | ||
import { AnswerContext } from './abstract/context' | ||
import { Installer } from './abstract/install' | ||
|
||
export class HuskyAnswer extends AbstractAnswer { | ||
static toolName = 'husky' | ||
|
||
context = AnswerContext.instance | ||
|
||
answerName = 'husky' | ||
|
||
installer = new Installer(['husky']) | ||
|
||
config = new Config([ | ||
new CommonConfigOption({ | ||
configFileName: '.husky/pre-commit', | ||
content: huskyConfig.hooks['pre-commit'], | ||
}), | ||
new CommonConfigOption({ | ||
configFileName: '.husky/commit-msg', | ||
content: huskyConfig.hooks['commit-msg'], | ||
}), | ||
]) | ||
|
||
async configGuard(): Promise<void> { | ||
const hasGit = await isGitRepository(this.context.cwd) | ||
if (!hasGit) { | ||
const shouldInitGit = await shouldInitGitPrompt() | ||
if (!shouldInitGit) { | ||
this.config.clearPendingConfigs() | ||
return | ||
} | ||
await initGit() | ||
} | ||
|
||
const configs = this.config.pendingConfigs | ||
for (const config of configs) { | ||
if (await config.checkConfigFileExisted()) { | ||
const shouldOverride = await shouldOverridePrompt(config.configFileName) | ||
if (shouldOverride) { | ||
config.overrideFile = async () => { | ||
await deleteFile(config.configFilePath) | ||
} | ||
this.config.addPendingConfig(config) | ||
} | ||
else { | ||
this.config.removePendingConfig(config) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { AbstractAnswer } from './abstract/answer' | ||
import * as p from '@clack/prompts' | ||
import { onCancel } from '../utils' | ||
import { CommitlintAnswer } from './commitlint' | ||
import { EslintAnswer } from './eslint' | ||
import { HuskyAnswer } from './husky' | ||
import { LintStagedAnswer } from './lint-staged' | ||
|
||
export const answers = [ | ||
new EslintAnswer(), | ||
new LintStagedAnswer(), | ||
new CommitlintAnswer(), | ||
new HuskyAnswer(), | ||
] | ||
|
||
export const toSelectAnswers = new Map([ | ||
[EslintAnswer.toolName, EslintAnswer], | ||
[LintStagedAnswer.toolName, LintStagedAnswer], | ||
[CommitlintAnswer.toolName, CommitlintAnswer], | ||
[HuskyAnswer.toolName, HuskyAnswer], | ||
]) | ||
|
||
export async function getAnswers(): Promise<AbstractAnswer[]> { | ||
const answerKeys = Array.from(toSelectAnswers.keys()) | ||
const options = answerKeys.map(answer => ({ | ||
value: answer, | ||
})) | ||
|
||
const selectedAnswers = await p.multiselect({ | ||
message: 'Please select lint tools to install:', | ||
options, | ||
initialValues: answerKeys, | ||
}) | ||
|
||
if (p.isCancel(selectedAnswers)) { | ||
onCancel() | ||
return [] | ||
} | ||
|
||
return selectedAnswers.map((answer) => { | ||
const AnswerCls = toSelectAnswers.get(answer) | ||
if (!AnswerCls) { | ||
throw new Error(`Answer ${answer} not found`) | ||
} | ||
return new AnswerCls() | ||
}) | ||
} |
Oops, something went wrong.