Skip to content

Commit

Permalink
feat: migrate command
Browse files Browse the repository at this point in the history
  • Loading branch information
gearonix committed Mar 22, 2024
1 parent b65808e commit fdc29fc
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 18 deletions.
7 changes: 4 additions & 3 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RunCommand } from './run/run.command'
import { ShowCommand } from './show/show.command'
import { MigrateCommand } from '@/commands/migrate/migrate.command'
import { RunCommand } from '@/commands/run/run.command'
import { ShowCommand } from '@/commands/show/show.command'

export const Commands = [RunCommand, ShowCommand]
export const Commands = [MigrateCommand, RunCommand, ShowCommand]
111 changes: 111 additions & 0 deletions src/commands/migrate/migrate.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { assertDir, ensureFile } from '@neodx/fs'
import { mapValues } from '@neodx/std'
import { Inject } from '@nestjs/common'
import {
CliUtilityService,
Command,
CommandRunner,
Option
} from 'nest-commander'
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 { 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'
import type { PackageJson, PackageScripts } from '@/shared/json'
import { writeJson } from '@/shared/json'
import { invariant } from '@/shared/misc'

export interface MigrateCommandOptions {
all?: boolean
}

@Command({
name: 'migrate',
// TODO: add descriptions
description: ''
})
export class MigrateCommand extends CommandRunner {
constructor(
@InjectPackageManager() private readonly manager: AbstractPackageManager,
@Inject(LoggerService) private readonly logger: LoggerService,
@Inject(ConfigService) private readonly cfg: ConfigService,
private readonly utilityService: CliUtilityService
) {
super()
}

public async run(params: string[], options: MigrateCommandOptions) {
await this.manager.computeWorkspaceProjects()

const { projects: workspaceProjects } = this.manager

if (options.all) {
await Promise.all(
workspaceProjects.map(async (projectMeta) =>
this.migrateProjectCommands(projectMeta, options)
)
)
return
}

const [projectName = ROOT_PROJECT] = params

const projectMeta = workspaceProjects.find(
({ name }) => name === projectName
)

invariant(projectMeta, `Project '${projectName}' not found.`)

await this.migrateProjectCommands(projectMeta)
}

private async migrateProjectCommands(
projectMeta: WorkspaceProject,
options?: MigrateCommandOptions
): Promise<void> {
const { location: projectCwd, type: targetType } = projectMeta

await assertDir(projectMeta.location)

if (targetType === 'package-scripts') {
const commandsFilePath = resolve(
projectMeta.location,
this.cfg.commandsFile
)
const pkgScripts = projectMeta.targets as NonNullable<
PackageJson['scripts']
>

await ensureFile(commandsFilePath)

const serializedTargets = mapValues<PackageScripts, TargetOptions>(
pkgScripts,
(command) => ({ command })
)

await writeJson(commandsFilePath, serializedTargets)

this.logger.info(`Generated ${this.cfg.commandsFile} in ${projectCwd}`)
return
}

if (!options?.all) {
this.logger.error(
`The specified target type '${targetType}' is not allowed.
It seems you may already have a '${this.cfg.commandsFile}' file in your project.`
)
}
}

@Option({
flags: '--all [boolean]',
name: 'all'
})
public parseAll(all: string) {
return this.utilityService.parseBoolean(all)
}
}
2 changes: 1 addition & 1 deletion src/commands/run/run.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
} from 'nest-commander'
import { dirname } from 'node:path'
import { buildTargetInfoPrompt } from '@/commands/run/run.prompts'
import { createIndependentTargetCommand } from '@/commands/run/utils/independent-target-command'
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 { ResolverService } from '@/resolver/resolver.service'
import { invariant } from '@/shared/misc'
import { createIndependentTargetCommand } from './utils/independent-target-command'

export interface RunCommandOptions {
args?: string
Expand Down
5 changes: 3 additions & 2 deletions src/pkg-manager/managers/abstract.pkg-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ export abstract class AbstractPackageManager {
workspaces: WorkspaceProject[] = []
): Promise<void> {
const cwd = process.cwd()
const { targets } = await this.resolver.resolveProjectTargets(cwd)
const { targets, type } = await this.resolver.resolveProjectTargets(cwd)

const root = {
name: ROOT_PROJECT,
location: cwd,
targets
targets,
type
} satisfies WorkspaceProject

this.projects = [root, ...workspaces]
Expand Down
4 changes: 2 additions & 2 deletions src/pkg-manager/managers/bun.pkg-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ export class BunPackageManager extends AbstractPackageManager {

const workspaceName = scopedPkgJson.name ?? null
const workspaceDir = dirname(pattern)
const { targets } =
const { targets, type } =
await this.resolver.resolveProjectTargets(workspaceDir)

return { name: workspaceName, location: workspaceDir, targets }
return { name: workspaceName, location: workspaceDir, targets, type }
})
)

Expand Down
4 changes: 2 additions & 2 deletions src/pkg-manager/managers/npm.pkg-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ export class NpmPackageManager extends AbstractPackageManager {
const normalizedPath = dependency.resolved.replace(/^file:..\//, '')
const absolutePath = resolve(cwd, normalizedPath)

const { targets } =
const { targets, type } =
await this.resolver.resolveProjectTargets(absolutePath)

return { name, location: absolutePath, targets }
return { name, location: absolutePath, targets, type }
})
)

Expand Down
5 changes: 3 additions & 2 deletions src/pkg-manager/managers/pnpm.pkg-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export class PnpmPackageManager extends AbstractPackageManager {
const isRoot = pathEqual(path, process.cwd())
if (isRoot) return null

const { targets } = await this.resolver.resolveProjectTargets(path)
const { targets, type } =
await this.resolver.resolveProjectTargets(path)

return { name, location: path, targets }
return { name, location: path, targets, type }
})
)

Expand Down
4 changes: 2 additions & 2 deletions src/pkg-manager/managers/yarn-berry.pkg-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export class YarnBerryPackageManager extends AbstractPackageManager {
const workspaces = await Promise.all(
serializedLines.map(async (serializedMeta) => {
const project = parseJson<WorkspaceProject>(serializedMeta)
const { targets } = await this.resolver.resolveProjectTargets(
const { targets, type } = await this.resolver.resolveProjectTargets(
project.location
)

return { ...project, targets }
return { ...project, targets, type }
})
)

Expand Down
5 changes: 3 additions & 2 deletions src/pkg-manager/managers/yarn.pkg-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ export class YarnPackageManager extends AbstractPackageManager {
const yarnWorkspaces = await Promise.all(
workspacesEntries.map(async ([name, metadata]) => {
const location = toAbsolutePath(metadata.location)
const { targets } = await this.resolver.resolveProjectTargets(location)
const { targets, type } =
await this.resolver.resolveProjectTargets(location)

return { name, location, targets }
return { name, location, targets, type }
})
)

Expand Down
2 changes: 2 additions & 0 deletions src/pkg-manager/pkg-manager.types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { TargetType } from '@/resolver/resolver.types'
import type { TargetOptions } from '@/resolver/targets/targets-resolver.schema'

export interface WorkspaceProject {
name: string | null
location: string
targets: TargetOptions
type: TargetType
}

export interface RunCommandOptions {
Expand Down
21 changes: 19 additions & 2 deletions src/shared/json.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { parseJson } from '@neodx/fs'
import { parseJson, serializeJson } from '@neodx/fs'
import type { AnyRecord } from '@neodx/std'
import chalk from 'chalk'
import { readFile } from 'node:fs/promises'
import { readFile, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import process from 'process'
import { ERROR_PREFIX } from '@/logger'
import { toAbsolutePath } from '@/shared/misc'

export interface PackageJson {
name?: string
Expand All @@ -18,6 +19,8 @@ export interface PackageJson {
workspaces?: string[] | (Record<string, string[]> & { packages: string[] })
}

export type PackageScripts = NonNullable<PackageJson['scripts']>

export async function readJson<Result extends AnyRecord = AnyRecord>(
path: string,
error = 'Unable to parse JSON string.'
Expand All @@ -32,3 +35,17 @@ export async function readJson<Result extends AnyRecord = AnyRecord>(
process.exit(1)
}
}

export async function writeJson<Result extends AnyRecord = AnyRecord>(
path: string,
content: Result,
error = 'Unable to write JSON.'
) {
try {
await writeFile(toAbsolutePath(path), serializeJson(content))
} catch {
console.error(`\n ${ERROR_PREFIX} ${chalk.bold(chalk.red(error))}\n`)

process.exit(1)
}
}

0 comments on commit fdc29fc

Please sign in to comment.