mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Closes #16391 Like the title suggest this PR adds error reporting when the `npm install` or `npm remove` commands fail. ## Test plan Tested by swapping out the command for `echo "bla"; exit 1` and capturing the output from the integration tests: <img width="792" alt="Screenshot 2025-02-13 at 14 33 02" src="https://github.com/user-attachments/assets/d1288114-106a-4ac6-a54b-d02b74c98f35" /> <img width="761" alt="Screenshot 2025-02-13 at 14 31 05" src="https://github.com/user-attachments/assets/6d5b9427-457f-4e67-9723-4e340da61749" /> Decided not to add a new test for this since it's unlikely we'll do big changes here and the upgrade integration tests are already quite slow.
112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
import { exec as execCb } from 'node:child_process'
|
|
import fs from 'node:fs/promises'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
import { DefaultMap } from '../../../tailwindcss/src/utils/default-map'
|
|
import { error, warn } from './renderer'
|
|
|
|
const exec = promisify(execCb)
|
|
|
|
const SAVE_DEV: Record<string, string> = {
|
|
default: '-D',
|
|
bun: '-d',
|
|
}
|
|
|
|
export function pkg(base: string) {
|
|
return {
|
|
async add(packages: string[], location: 'dependencies' | 'devDependencies' = 'dependencies') {
|
|
let packageManager = await packageManagerForBase.get(base)
|
|
let args = packages.slice()
|
|
if (location === 'devDependencies') {
|
|
args.push(SAVE_DEV[packageManager] || SAVE_DEV.default)
|
|
}
|
|
|
|
let command = `${packageManager} add ${args.join(' ')}`
|
|
try {
|
|
return await exec(command, { cwd: base })
|
|
} catch (e: any) {
|
|
error(`An error occurred while running \`${command}\`\n\n${e.stdout}\n${e.stderr}`, {
|
|
prefix: '↳ ',
|
|
})
|
|
throw e
|
|
}
|
|
},
|
|
async remove(packages: string[]) {
|
|
let packageManager = await packageManagerForBase.get(base)
|
|
let command = `${packageManager} remove ${packages.join(' ')}`
|
|
try {
|
|
return await exec(command, { cwd: base })
|
|
} catch (e: any) {
|
|
error(`An error occurred while running \`${command}\`\n\n${e.stdout}\n${e.stderr}`, {
|
|
prefix: '↳ ',
|
|
})
|
|
throw e
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
let didWarnAboutPackageManager = false
|
|
let packageManagerForBase = new DefaultMap(async (base) => {
|
|
do {
|
|
// 1. Check package.json for a `packageManager` field
|
|
let packageJsonPath = resolve(base, 'package.json')
|
|
try {
|
|
let packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8')
|
|
let packageJson = JSON.parse(packageJsonContent)
|
|
if (packageJson.packageManager) {
|
|
if (packageJson.packageManager.includes('bun')) {
|
|
return 'bun'
|
|
}
|
|
if (packageJson.packageManager.includes('yarn')) {
|
|
return 'yarn'
|
|
}
|
|
if (packageJson.packageManager.includes('pnpm')) {
|
|
return 'pnpm'
|
|
}
|
|
if (packageJson.packageManager.includes('npm')) {
|
|
return 'npm'
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
// 2. Check for common lockfiles
|
|
try {
|
|
await fs.access(resolve(base, 'bun.lockb'))
|
|
return 'bun'
|
|
} catch {}
|
|
try {
|
|
await fs.access(resolve(base, 'bun.lock'))
|
|
return 'bun'
|
|
} catch {}
|
|
try {
|
|
await fs.access(resolve(base, 'pnpm-lock.yaml'))
|
|
return 'pnpm'
|
|
} catch {}
|
|
|
|
try {
|
|
await fs.access(resolve(base, 'yarn.lock'))
|
|
return 'yarn'
|
|
} catch {}
|
|
|
|
try {
|
|
await fs.access(resolve(base, 'package-lock.json'))
|
|
return 'npm'
|
|
} catch {}
|
|
|
|
// 3. If no lockfile is found, we might be in a monorepo
|
|
let previousBase = base
|
|
base = dirname(base)
|
|
|
|
// Already at the root
|
|
if (previousBase === base) {
|
|
if (!didWarnAboutPackageManager) {
|
|
didWarnAboutPackageManager = true
|
|
warn('Could not detect a package manager. Please manually update `tailwindcss` to v4.')
|
|
}
|
|
|
|
return Promise.reject('No package manager detected')
|
|
}
|
|
} while (true)
|
|
})
|