scripts/run-script.ts
Rama main · réplica de lectura del repositorio original.
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()