Ctrl K
ComunidadGuías…

scripts/run-script.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { spawn } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'

import * as p from '@clack/prompts'
import { getPackages } from '@manypkg/get-packages'

const __dirname = import.meta.dirname
const rootDir = path.join(__dirname, '..')
const cacheFile = path.join(
  rootDir,
  'node_modules',
  '.cache',
  'tamery-run.json'
)

const ROOT_ID = '<root>'

interface WorkspacePackage {
  id: string
  name: string
  dir: string
  scripts: string[]
}

interface Selection {
  packages: string[]
  script: string
}

const readTurboTasks = () => {
  const turboJson = JSON.parse(
    fs.readFileSync(path.join(rootDir, 'turbo.json'), 'utf-8')
  )

  return new Set(
    Object.keys(turboJson.tasks ?? {}).map(
      (task) => task.split('#').at(-1) as string
    )
  )
}

const runnerFile = path.basename(import.meta.filename)

const discoverPackages = async (): Promise<WorkspacePackage[]> => {
  const { packages, rootPackage } = await getPackages(rootDir)

  const toWorkspacePackage = (
    pkg: (typeof packages)[number],
    isRoot: boolean
  ) => ({
    dir: pkg.relativeDir,
    id: isRoot ? ROOT_ID : pkg.packageJson.name,
    name: pkg.packageJson.name,
    scripts: Object.entries(
      ('scripts' in pkg.packageJson ? pkg.packageJson.scripts : undefined) ?? {}
    )
      .filter(([, command]) => !command.includes(runnerFile))
      .map(([name]) => name),
  })

  return [
    ...(rootPackage ? [toWorkspacePackage(rootPackage, true)] : []),
    ...packages
      .filter((pkg) => pkg.dir !== rootPackage?.dir)
      .map((pkg) => toWorkspacePackage(pkg, false)),
  ].filter((pkg) => pkg.scripts.length > 0)
}

const readCache = (): Selection | null => {
  try {
    return JSON.parse(fs.readFileSync(cacheFile, 'utf-8'))
  } catch {
    return null
  }
}

const writeCache = (selection: Selection) => {
  fs.mkdirSync(path.dirname(cacheFile), { recursive: true })
  fs.writeFileSync(cacheFile, JSON.stringify(selection, null, 2))
}

const parseArgs = (argv: string[]) => {
  const separator = argv.indexOf('--')
  const own = separator === -1 ? argv : argv.slice(0, separator)
  const forwarded = separator === -1 ? [] : argv.slice(separator + 1)
  const flags = new Set(own.filter((arg) => arg.startsWith('-')))

  return {
    all: flags.has('--all') || flags.has('-a'),
    dryRun: flags.has('--dry-run') || flags.has('-d'),
    forwarded,
    last: flags.has('--last') || flags.has('-l'),
    noTurbo: flags.has('--no-turbo'),
    script: own.find((arg) => !arg.startsWith('-')),
  }
}

const run = (command: string, args: string[]) => {
  p.log.step(`${command} ${args.join(' ')}`)

  const child = spawn(command, args, {
    cwd: rootDir,
    shell: process.platform === 'win32',
    stdio: 'inherit',
  })

  child.on('exit', (code, signal) => {
    process.exit(signal ? 1 : (code ?? 0))
  })
  child.on('error', (error) => {
    p.log.error(error.message)
    process.exit(1)
  })
}

const buildCommand = (
  selected: WorkspacePackage[],
  script: string,
  options: { turbo: boolean; forwarded: string[] }
) => {
  const onlyRoot = selected.length === 1 && selected[0]?.id === ROOT_ID

  if (onlyRoot) {
    return { args: ['run', script, ...options.forwarded], command: 'pnpm' }
  }

  const filters = selected.filter((pkg) => pkg.id !== ROOT_ID)

  if (options.turbo) {
    return {
      args: [
        'exec',
        'turbo',
        'run',
        script,
        ...filters.map((pkg) => `--filter=${pkg.name}`),
        ...(options.forwarded.length > 0 ? ['--', ...options.forwarded] : []),
      ],
      command: 'pnpm',
    }
  }

  return {
    args: [
      ...filters.flatMap((pkg) => ['--filter', pkg.name]),
      ...(filters.length > 1 ? ['--parallel'] : []),
      'run',
      script,
      ...options.forwarded,
    ],
    command: 'pnpm',
  }
}

const rememberedPackageIds = (packages: WorkspacePackage[]) =>
  readCache()?.packages.filter((id) => packages.some((pkg) => pkg.id === id)) ??
  []

const defaultMultiselectValues = (
  packages: WorkspacePackage[],
  script: string | undefined,
  remembered: string[]
) => {
  if (remembered.length > 0) {
    return remembered
  }
  if (script) {
    return packages.filter((pkg) => pkg.id !== ROOT_ID).map((pkg) => pkg.id)
  }
  return []
}

const selectPackageIds = async (
  packages: WorkspacePackage[],
  args: ReturnType<typeof parseArgs>,
  cached: Selection | null
): Promise<string[]> => {
  if (args.all || !process.stdin.isTTY) {
    if (!args.all) {
      p.log.info('Non-interactive shell — selecting all packages')
    }
    return packages.filter((pkg) => pkg.id !== ROOT_ID).map((pkg) => pkg.id)
  }

  if (cached) {
    return cached.packages.filter((id) => packages.some((pkg) => pkg.id === id))
  }

  const remembered = rememberedPackageIds(packages)
  const answer = await p.multiselect({
    initialValues: defaultMultiselectValues(packages, args.script, remembered),
    message: args.script
      ? `Select packages to run "${args.script}" in`
      : 'Select packages',
    options: packages.map((pkg) => ({
      hint: pkg.dir,
      label: pkg.id === ROOT_ID ? 'root' : pkg.name,
      value: pkg.id,
    })),
    required: true,
  })

  if (p.isCancel(answer)) {
    p.cancel('Cancelled')
    process.exit(0)
  }

  return answer
}

const resolveScript = async (
  selected: WorkspacePackage[],
  args: ReturnType<typeof parseArgs>,
  cached: Selection | null
): Promise<string> => {
  const scriptCounts = new Map<string, number>()

  for (const pkg of selected) {
    for (const name of pkg.scripts) {
      scriptCounts.set(name, (scriptCounts.get(name) ?? 0) + 1)
    }
  }

  const preferredScript =
    args.script ??
    (cached?.script && scriptCounts.has(cached.script)
      ? cached.script
      : undefined)

  if (preferredScript && !scriptCounts.has(preferredScript)) {
    p.cancel(`No selected package has a script named "${preferredScript}"`)
    process.exit(1)
  }

  if (preferredScript) {
    return preferredScript
  }

  const answer = await p.select({
    initialValue: cached?.script,
    message: 'Select script',
    options: [...scriptCounts.keys()].toSorted().map((name) => ({
      hint: `${scriptCounts.get(name)}/${selected.length} packages`,
      label: name,
      value: name,
    })),
  })

  if (p.isCancel(answer)) {
    p.cancel('Cancelled')
    process.exit(0)
  }

  return answer
}

const main = async () => {
  const args = parseArgs(process.argv.slice(2))
  const discovered = await discoverPackages()
  const scriptFilter = args.script
  const packages = scriptFilter
    ? discovered.filter((pkg) => pkg.scripts.includes(scriptFilter))
    : discovered
  const cached = args.last ? readCache() : null

  p.intro('tamery · run workspace scripts')

  if (packages.length === 0) {
    p.cancel(`No package has a script named "${args.script}"`)
    return process.exit(1)
  }

  const selectedIds = await selectPackageIds(packages, args, cached)
  const selected = packages.filter((pkg) => selectedIds.includes(pkg.id))

  if (selected.length === 0) {
    p.cancel('No packages selected')
    return process.exit(1)
  }

  const script = await resolveScript(selected, args, cached)
  const targets = selected.filter((pkg) => pkg.scripts.includes(script))
  const skipped = selected.filter((pkg) => !pkg.scripts.includes(script))

  if (skipped.length > 0) {
    p.log.warn(
      `Skipping (no "${script}" script): ${skipped.map((pkg) => pkg.name).join(', ')}`
    )
  }

  if (targets.length > 1 && targets.some((pkg) => pkg.id === ROOT_ID)) {
    p.log.warn(
      `Root "${script}" runs only when root is selected alone — skipping root`
    )
  }

  writeCache({ packages: targets.map((pkg) => pkg.id), script })

  const isTurboTask = readTurboTasks().has(script)

  if (!isTurboTask && !args.noTurbo) {
    p.log.warn(
      `"${script}" is not a turbo.json task — running through pnpm instead`
    )
  }

  const { command, args: commandArgs } = buildCommand(targets, script, {
    forwarded: args.forwarded,
    turbo: isTurboTask && !args.noTurbo,
  })

  p.outro(
    `Running "${script}" in ${targets.length} package${targets.length === 1 ? '' : 's'}`
  )

  if (args.dryRun) {
    console.log(`${command} ${commandArgs.join(' ')}`)
    return
  }

  run(command, commandArgs)
}

main()