Skip to content

Commit

Permalink
feat: run-many command
Browse files Browse the repository at this point in the history
  • Loading branch information
gearonix committed Mar 22, 2024
1 parent fdc29fc commit ed6ad0b
Show file tree
Hide file tree
Showing 15 changed files with 329 additions and 27 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ module.exports = configure({
rules: {
'import/extensions': 'warn',
'import/no-unresolved': 'warn'
}
},
extends: [
'plugin:oxlint/recommended'
]
}
})
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
"scripts": {
"build": "unbuild",
"dev": "unbuild --stub",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"lint": "oxlint . && eslint .",
"lint:fix": "oxlint . && eslint . --fix",
"prepublishOnly": "nr build",
"release": "bumpp && npm publish",
"test": "vitest",
Expand All @@ -61,8 +61,10 @@
"@types/which": "^3.0.3",
"bumpp": "^9.2.1",
"eslint": "^8.56.0",
"eslint-plugin-oxlint": "^0.2.7",
"esno": "^4.0.0",
"lint-staged": "^15.2.0",
"oxlint": "^0.2.14",
"pnpm": "^8.14.0",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
Expand All @@ -77,7 +79,7 @@
"pre-commit": "pnpm install && pnpm lint-staged && pnpm typecheck"
},
"lint-staged": {
"*": "eslint . --fix"
"*": "oxlint . && eslint . --fix"
},
"dependencies": {
"@neodx/fs": "^0.0.11",
Expand Down
89 changes: 89 additions & 0 deletions pnpm-lock.yaml

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

8 changes: 7 additions & 1 deletion src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { MigrateCommand } from '@/commands/migrate/migrate.command'
import { RunCommand } from '@/commands/run/run.command'
import { RunManyCommand } from '@/commands/run-many/run-many.command'
import { ShowCommand } from '@/commands/show/show.command'

export const Commands = [MigrateCommand, RunCommand, ShowCommand]
export const Commands = [
RunCommand,
MigrateCommand,
ShowCommand,
RunManyCommand
]
2 changes: 1 addition & 1 deletion src/commands/migrate/migrate.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { resolve } from 'node:path'
import { ConfigService } from '@/config'
import { LoggerService } from '@/logger'
import type { AbstractPackageManager } from '@/pkg-manager'
import { InjectPackageManager } from '@/pkg-manager'
import { InjectPackageManager } from '@/pkg-manager/pkg-manager.decorator'
import { ROOT_PROJECT } from '@/pkg-manager/pkg-manager.consts'
import type { WorkspaceProject } from '@/pkg-manager/pkg-manager.types'
import type { TargetOptions } from '@/resolver/targets/targets-resolver.schema'
Expand Down
154 changes: 154 additions & 0 deletions src/commands/run-many/run-many.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import {
compact,
concurrent,
concurrently,
isEmpty,
isTypeOfBoolean
} from '@neodx/std'
import { Inject } from '@nestjs/common'
import {
CliUtilityService,
Command,
CommandRunner,
Option
} from 'nest-commander'
import { cpus } from 'node:os'
import { RunCommand } from '@/commands/run/run.command'
import { LoggerService } from '@/logger'
import type { AbstractPackageManager } from '@/pkg-manager'
import { InjectPackageManager } from '@/pkg-manager/pkg-manager.decorator'
import type { WorkspaceProject } from '@/pkg-manager/pkg-manager.types'
import { invariant } from '@/shared/misc'

export interface RunManyCommandOptions {
all?: boolean
exclude?: string[]
parallel?: number
projects?: string[]
targets?: string[]
}

@Command({
name: 'run-many',
// TODO: add descriptions
description: ''
})
export class RunManyCommand extends CommandRunner {
constructor(
@InjectPackageManager() private readonly manager: AbstractPackageManager,
@Inject(LoggerService) private readonly logger: LoggerService,
@Inject(RunCommand) private readonly runner: RunCommand,
private readonly utilityService: CliUtilityService
) {
super()
}

private readonly DEFAULT_CONCURRENT_THREADS = 1 as const

public async run(_: string[], opts: RunManyCommandOptions) {
await this.manager.computeWorkspaceProjects()

const timeEnd = this.logger.time()
const { projects } = this.manager

const computeProjectsToRun = (): WorkspaceProject[] => {
if (opts.all) return projects

const isExcluded = ({ name }: WorkspaceProject) =>
!opts.exclude?.includes(name)
const isIncluded = ({ name }: WorkspaceProject) =>
opts.projects?.includes(name)

if (opts.exclude) return projects.filter(isExcluded)

if (opts.projects) return projects.filter(isIncluded)

this.logger.error(
'Invalid options provided: Either "all", "exclude", or "projects" should be specified.'
)
process.exit(1)
}
const projectsToRun = computeProjectsToRun()

invariant(opts.targets && !isEmpty(opts.targets), 'Targets are required.')

const targetsToRun = opts.targets as string[]
const threadsConcurrency = isTypeOfBoolean(opts.parallel)
? cpus().length
: opts.parallel ?? this.DEFAULT_CONCURRENT_THREADS

await concurrently(
projectsToRun,
async (projectMeta) => {
const targetsConcurrency = Math.min(targetsToRun.length, cpus().length)

const splitTargets = concurrent<string, void>(async (target) => {
const project = projectMeta.name
this.logger.log(
`${this.logger.greaterSignPrefix} gx run ${project}:${target}\n`
)

this.logger.mute()
try {
await this.runner.run([target, project], {})
} catch {}

this.logger.unmute()
}, targetsConcurrency)

await splitTargets(targetsToRun)
},
threadsConcurrency
)

const projectNames = projectsToRun.map(({ name }) => name)

const joinCommas = (arr: string[]) => arr.join(', ')

timeEnd(
`Successfully ran targets ${joinCommas(targetsToRun)} for projects ${joinCommas(projectNames)}`
)
}

@Option({
flags: '--all [boolean]',
name: 'all'
})
public parseAll(all: string) {
return this.utilityService.parseBoolean(all)
}

@Option({
flags: '--parallel [boolean]',
name: 'parallel'
})
public parseParallel(parallel: string) {
return this.utilityService.parseInt(parallel)
}

@Option({
flags: '-e --exclude [string]',
name: 'exclude'
})
public parseExclude(exclude: string) {
return serializeCommas(exclude)
}

@Option({
flags: '-p --projects [string]',
name: 'projects'
})
public parseProjects(projects: string) {
return serializeCommas(projects)
}

@Option({
flags: '-t --targets [string]',
name: 'targets'
})
public parseTargets(targets: string) {
return serializeCommas(targets)
}
}

const serializeCommas = (str: string) => compact(str.split(', '))
16 changes: 10 additions & 6 deletions src/commands/run/run.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import { dirname } from 'node:path'
import { buildTargetInfoPrompt } from '@/commands/run/run.prompts'
import { LoggerService } from '@/logger'
import type { AbstractPackageManager } from '@/pkg-manager'
import { InjectPackageManager } from '@/pkg-manager'
import { PackageManager, ROOT_PROJECT } from '@/pkg-manager/pkg-manager.consts'
import { InjectPackageManager } from '@/pkg-manager/pkg-manager.decorator'
import { ResolverService } from '@/resolver/resolver.service'
import { invariant } from '@/shared/misc'
import { createIndependentTargetCommand } from './utils/independent-target-command'

// TODO: comment everything

export interface RunCommandOptions {
args?: string
parallel?: boolean
Expand Down Expand Up @@ -54,7 +56,7 @@ export class RunCommand extends CommandRunner {
invariant(target, 'Please specify a target. It cannot be empty.')

const timeEnd = this.logger.time()
let projectCwd = process.cwd()
let projectCwd = options.cwd ?? process.cwd()

if (project) {
const projectMeta = this.manager.projects.find(
Expand All @@ -73,11 +75,13 @@ export class RunCommand extends CommandRunner {

const { targets, type: targetType } =
await this.resolver.resolveProjectTargets(projectCwd)
const hasTarget = isObject(targets) && hasOwn(targets, target)

invariant(
isObject(targets) && hasOwn(targets, target),
`Could not find target ${target} in project ${project}.`
)
if (!hasTarget) {
return this.logger.error(
`Could not find target ${target} in project ${project}.`
)
}

if (targetType === 'package-scripts') {
const command = this.manager.createRunCommand({
Expand Down
2 changes: 1 addition & 1 deletion src/commands/show/show.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from 'nest-commander'
import { LoggerService } from '@/logger'
import type { AbstractPackageManager } from '@/pkg-manager'
import { InjectPackageManager } from '@/pkg-manager'
import { InjectPackageManager } from '@/pkg-manager/pkg-manager.decorator'

export interface ShowCommandOptions {
json?: boolean
Expand Down
Loading

0 comments on commit ed6ad0b

Please sign in to comment.