Skip to content

Commit

Permalink
feat: custom configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
gearonix committed Mar 13, 2024
1 parent c1e5487 commit 4436f5b
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 20 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
},
"devDependencies": {
"@antfu/ni": "^0.21.12",
"@types/ini": "^4.1.0",
"@types/inquirer": "^9.0.7",
"@types/node": "^20.10.7",
"@types/which": "^3.0.3",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

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

6 changes: 6 additions & 0 deletions shims.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
declare module 'inquirer-tree-prompt'
declare module 'inquirer-search-list'
declare module 'envfile'

declare namespace NodeJS {
interface Process {
GX_CONFIG_PATH: string
}
}
3 changes: 2 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common'
import { Commands } from '@/commands'
import { ConfigModule } from '@/config'
import { LoggerModule } from '@/logger'
import { PackageManagerModule } from '@/pkg-manager'

@Module({
imports: [PackageManagerModule, LoggerModule],
imports: [PackageManagerModule, LoggerModule, ConfigModule],
providers: [...Commands]
})
export class AppModule {}
4 changes: 2 additions & 2 deletions src/commands/run/run.command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ensureDir } from '@neodx/fs'
import { hasOwn, isEmpty, isObject } from '@neodx/std'
import { Inject } from '@nestjs/common'
import { execaCommand } from 'execa'
import { execaCommand as $ } from 'execa'
import { Command, CommandRunner, Option } from 'nest-commander'
import { dirname } from 'node:path'
import { buildTargetInfoPrompt } from '@/commands/run/run.prompts'
Expand Down Expand Up @@ -102,7 +102,7 @@ export class RunCommand extends CommandRunner {
{ defaultArgs: options.args, projectCwd }
)

await execaCommand(`${command} ${args}`, {
await $(`${command} ${args}`, {
cwd,
env
})
Expand Down
10 changes: 10 additions & 0 deletions src/config/config.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Global, Module } from '@nestjs/common'
import { ConfigService } from './config.service'

@Global()
@Module({
controllers: [],
providers: [ConfigService],
exports: [ConfigService]
})
export class ConfigModule {}
16 changes: 16 additions & 0 deletions src/config/config.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { z } from 'zod'

export const ConfigSchema = z.object({
commandsFile: z.string().default('targets.json'),
preferredResolvingOrder: z
.array(z.union([z.literal('package-scripts'), z.literal('targets')]))
.default(['targets', 'package-scripts'])
})

function createConfigValues() {
return class ConfigValues {} as {
new (): z.infer<typeof ConfigSchema>
}
}

export class ConfigValues extends createConfigValues() {}
42 changes: 42 additions & 0 deletions src/config/config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Inject, Injectable } from '@nestjs/common'
import ini from 'ini'
import { existsSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import process from 'node:process'
import { ConfigSchema, ConfigValues } from '@/config/config.schema'
import { LoggerService } from '@/logger'

@Injectable()
export class ConfigService extends ConfigValues {
constructor(@Inject(LoggerService) private readonly logger: LoggerService) {
super()
Object.assign(this, this.config)
}

private get config() {
const userConfig = existsSync(this.configPath)
? ini.parse(readFileSync(this.configPath).toString())
: {}

try {
return ConfigSchema.parse(userConfig)
} catch (error) {
this.logger.error(error)
process.exit(1)
}
}

private get configPath() {
const customRcPath = process.env.GX_CONFIG_PATH

const isWindows = process.platform === 'win32'
const home = isWindows ? process.env.USERPROFILE : process.env.HOME

const defaultRcPath = resolve(home ?? '~/', '.gxrc')
const projectRcPath = resolve(process.cwd(), '.gxrc')

const baseRcPath = customRcPath ?? defaultRcPath

return existsSync(projectRcPath) ? projectRcPath : baseRcPath
}
}
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ConfigModule } from './config.module'
export { ConfigService } from './config.service'
43 changes: 32 additions & 11 deletions src/resolver/resolver.service.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
import { isNull } from '@neodx/std'
import { Injectable } from '@nestjs/common'
import { entries, sortObjectByOrder } from '@neodx/std'
import { Inject, Injectable } from '@nestjs/common'
import { ConfigService } from '@/config'
import { LoggerService } from '@/logger'
import type { PackageJsonResolverService } from '@/resolver/package/package.resolver.service'
import type { ResolvedTargets } from '@/resolver/resolver.types'
import type { ResolvedTargets, TargetType } from '@/resolver/resolver.types'
import type { TargetOptions } from '@/resolver/targets/targets-resolver.schema'
import type { TargetsResolverService } from '@/resolver/targets/targets-resolver.service'

@Injectable()
export class ResolverService {
constructor(
private readonly targetsResolver: TargetsResolverService,
private readonly pkgResolver: PackageJsonResolverService
private readonly pkgResolver: PackageJsonResolverService,
@Inject(ConfigService) private readonly cfg: ConfigService,
@Inject(LoggerService) private readonly logger: LoggerService
) {}

public async resolveProjectTargets(cwd: string): Promise<ResolvedTargets> {
const targets = await this.targetsResolver.resolveProjectTargets(cwd)
const sortedResolvers = sortObjectByOrder(
this.resolverMethods,
this.cfg.preferredResolvingOrder
)

if (isNull(targets)) {
return {
type: 'package-scripts',
targets: await this.pkgResolver.resolvePackageJsonScripts(cwd)
for (const [type, resolveTargets] of entries(sortedResolvers)) {
const targets = await resolveTargets.call(this, cwd)

if (targets) {
return { type, targets }
}
}

this.logger.error(
`Error occurred while resolving project targets.
Please check if preferredResolvingOrder is set correctly`
)
process.exit(1)
}

private get resolverMethods(): Record<
TargetType,
(cwd: string) => Promise<TargetOptions | null>
> {
return {
type: 'targets',
targets
'package-scripts': this.pkgResolver.resolvePackageJsonScripts,
// eslint-disable-next-line prettier/prettier
'targets': this.targetsResolver.resolveProjectTargets
}
}
}
6 changes: 5 additions & 1 deletion src/resolver/resolver.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { ConfigValues } from '@/config/config.schema'
import type { TargetOptions } from '@/resolver/targets/targets-resolver.schema'
import type { InferArrayItem } from '@/shared/types'

export type TargetType = InferArrayItem<ConfigValues['preferredResolvingOrder']>

export interface ResolvedTargets {
type: 'package-scripts' | 'targets'
type: TargetType
targets: TargetOptions
}
12 changes: 7 additions & 5 deletions src/resolver/targets/targets-resolver.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AnyRecord } from '@neodx/std'
import { Inject, Injectable } from '@nestjs/common'
import { resolve } from 'node:path'
import { z } from 'zod'
import { ConfigService } from '@/config'
import { LoggerService } from '@/logger'
import { readJson } from '@/shared/json'
import type { TargetOptions } from './targets-resolver.schema'
Expand All @@ -11,13 +12,14 @@ import { TargetsSchema } from './targets-resolver.schema'
@Injectable()
export class TargetsResolverService {
constructor(
@Inject(LoggerService) private readonly loggerService: LoggerService
@Inject(LoggerService) private readonly logger: LoggerService,
@Inject(ConfigService) private readonly cfg: ConfigService
) {}

public async resolveProjectTargets(
projectCwd: string
): Promise<TargetOptions | null> {
const targetJsonPath = resolve(projectCwd, 'commands.json')
const targetJsonPath = resolve(projectCwd, this.cfg.commandsFile)

if (!exists(targetJsonPath)) return null

Expand All @@ -36,14 +38,14 @@ export class TargetsResolverService {
const isZodError = error_ instanceof z.ZodError

if (!isZodError) {
this.loggerService.error('Unknown error parsing commands.json')
this.logger.error('Unknown error parsing commands.json')
throw error_
}

this.loggerService.error(
this.logger.error(
`Invalid commands.json file. (${projectPath}) See below for detailed info. \n`
)
this.loggerService.error(error_.format())
this.logger.error(error_.format())

process.exit(1)
}
Expand Down
3 changes: 3 additions & 0 deletions src/shared/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type InferArrayItem<T extends unknown[]> = T extends (infer S)[]
? S
: never

0 comments on commit 4436f5b

Please sign in to comment.