mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 15:11:29 -07:00
Initial commit
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import { cliError } from '@jahands/cli-tools'
|
||||
import Table from 'cli-table3'
|
||||
import * as toml from 'smol-toml'
|
||||
import { match } from 'ts-pattern'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { getRepoRoot } from './path'
|
||||
import { PackageJson } from './pkg'
|
||||
|
||||
type UpdateResult = {
|
||||
type: string
|
||||
updated: number
|
||||
failed: number
|
||||
files: string[]
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
export async function updatePnpm() {
|
||||
const $$ = $({
|
||||
verbose: false,
|
||||
stdio: 'pipe',
|
||||
})
|
||||
|
||||
const repoRoot = getRepoRoot()
|
||||
cd(repoRoot)
|
||||
const miseAvailable = await which('mise')
|
||||
echo(chalk.white(`Checking for pnpm updates...`))
|
||||
const res = await fetch('https://registry.npmjs.org/pnpm')
|
||||
if (!res.ok) {
|
||||
throw cliError(`Failed to fetch pnpm registry: ${res.status}`)
|
||||
}
|
||||
const body = await res.json()
|
||||
const pnpm = NpmRegistryPnpmResponse.parse(body)
|
||||
const latest = pnpm['dist-tags'].latest
|
||||
echo(chalk.blue(`Latest pnpm version: ${latest}`))
|
||||
|
||||
const results: UpdateResult[] = []
|
||||
|
||||
if (miseAvailable) {
|
||||
const miseResult = await updateMiseTomlFiles(repoRoot, latest)
|
||||
results.push(miseResult)
|
||||
}
|
||||
|
||||
const packageJsonResult = await updatePackageJsonFiles(repoRoot, latest)
|
||||
results.push(packageJsonResult)
|
||||
|
||||
if (results.some((r) => r.updated > 0)) {
|
||||
echo(chalk.blue(`Fixing formatting...`))
|
||||
await $$`runx fix --format`
|
||||
}
|
||||
|
||||
// Display summary table
|
||||
const table = new Table({
|
||||
head: [
|
||||
chalk.whiteBright('File Type'),
|
||||
chalk.whiteBright('Updated'),
|
||||
chalk.whiteBright('Failed'),
|
||||
chalk.whiteBright('Status'),
|
||||
],
|
||||
})
|
||||
|
||||
let totalUpdated = 0
|
||||
let totalFailed = 0
|
||||
for (const result of results) {
|
||||
totalUpdated += result.updated
|
||||
totalFailed += result.failed
|
||||
|
||||
const status = match(result)
|
||||
.when(
|
||||
(r) => r.failed > 0,
|
||||
() => chalk.red('Failed')
|
||||
)
|
||||
.when(
|
||||
(r) => r.updated > 0,
|
||||
() => chalk.green('Updated')
|
||||
)
|
||||
.otherwise(() => chalk.gray('No changes'))
|
||||
|
||||
table.push([result.type, result.updated.toString(), result.failed.toString(), status])
|
||||
}
|
||||
|
||||
// Show detailed errors if any
|
||||
for (const result of results) {
|
||||
if (result.errors.length > 0) {
|
||||
echo(chalk.red(`\nErrors in ${result.type}:`))
|
||||
for (const error of result.errors) {
|
||||
echo(chalk.red(` ${error}`))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalUpdated > 0) {
|
||||
if (miseAvailable) {
|
||||
echo(chalk.blue('\nRunning mise up...'))
|
||||
await $$`mise up`
|
||||
}
|
||||
|
||||
echo(chalk.blue(`Fixing formatting...`))
|
||||
await $$`runx fix --format`
|
||||
|
||||
echo(chalk.greenBright(`\nSuccessfully updated pnpm to ${latest}`))
|
||||
} else if (totalFailed > 0) {
|
||||
echo(chalk.red(`\nFailed to update some files. See errors above.`))
|
||||
process.exit(1)
|
||||
} else {
|
||||
echo(chalk.green(`\nNo pnpm updates needed, already on ${latest}`))
|
||||
}
|
||||
|
||||
echo('\n' + table.toString())
|
||||
}
|
||||
|
||||
const Semver = z.string().regex(/^\d+\.\d+\.\d+$/)
|
||||
const NpmRegistryPnpmResponse = z.object({
|
||||
_id: z.literal('pnpm'),
|
||||
name: z.literal('pnpm'),
|
||||
'dist-tags': z.object({
|
||||
latest: Semver.describe('pnpm latest version, e.g. 9.5.0'),
|
||||
}),
|
||||
})
|
||||
|
||||
type MiseToml = z.infer<typeof MiseToml>
|
||||
const MiseToml = z
|
||||
.object({
|
||||
tools: z
|
||||
.object({
|
||||
pnpm: Semver.describe('pnpm version, e.g. 9.5.0').optional(),
|
||||
})
|
||||
.catchall(z.string()),
|
||||
alias: z.record(z.string(), z.string()).optional(),
|
||||
})
|
||||
.loose()
|
||||
|
||||
/**
|
||||
* Update package.json files with the new pnpm version
|
||||
* @param repoRoot - The root directory of the repository
|
||||
* @param newVersion - The new pnpm version
|
||||
* @returns UpdateResult with details about the operation
|
||||
*/
|
||||
async function updatePackageJsonFiles(repoRoot: string, newVersion: string): Promise<UpdateResult> {
|
||||
const result: UpdateResult = {
|
||||
type: 'package.json',
|
||||
updated: 0,
|
||||
failed: 0,
|
||||
files: [],
|
||||
errors: [],
|
||||
}
|
||||
|
||||
const packageJsonFiles = await glob('**/package.json', {
|
||||
cwd: repoRoot,
|
||||
gitignore: true,
|
||||
ignore: ['**/node_modules/**', 'turbo/generators/**'],
|
||||
})
|
||||
|
||||
for (const file of packageJsonFiles) {
|
||||
const filePath = `${repoRoot}/${file}`
|
||||
|
||||
try {
|
||||
const packageJson = PackageJson.loose().parse(await Bun.file(filePath).json())
|
||||
|
||||
if (packageJson.packageManager?.startsWith('pnpm@')) {
|
||||
const currentVersion = packageJson.packageManager.replace('pnpm@', '')
|
||||
if (currentVersion !== newVersion) {
|
||||
echo(chalk.blue(`Updating ${file} packageManager to pnpm@${newVersion}`))
|
||||
packageJson.packageManager = `pnpm@${newVersion}`
|
||||
await Bun.file(filePath).write(JSON.stringify(packageJson, null, 2) + '\n')
|
||||
result.updated++
|
||||
result.files.push(file)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
result.failed++
|
||||
result.errors.push(`Failed to update ${file}: ${e instanceof Error ? e.message : String(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Update mise.toml and .mise.toml files with the new pnpm version
|
||||
* @param repoRoot - The root directory of the repository
|
||||
* @param newVersion - The new pnpm version
|
||||
* @returns UpdateResult with details about the operation
|
||||
*/
|
||||
async function updateMiseTomlFiles(repoRoot: string, newVersion: string): Promise<UpdateResult> {
|
||||
const result: UpdateResult = {
|
||||
type: 'mise.toml',
|
||||
updated: 0,
|
||||
failed: 0,
|
||||
files: [],
|
||||
errors: [],
|
||||
}
|
||||
|
||||
const miseTomlFiles = await glob('**/{.mise.toml,mise.toml}', {
|
||||
cwd: repoRoot,
|
||||
gitignore: true,
|
||||
ignore: ['**/node_modules/**'],
|
||||
})
|
||||
|
||||
for (const file of miseTomlFiles) {
|
||||
const filePath = `${repoRoot}/${file}`
|
||||
|
||||
try {
|
||||
const miseToml = MiseToml.parse(toml.parse(await Bun.file(filePath).text()))
|
||||
|
||||
if (miseToml.tools.pnpm && miseToml.tools.pnpm !== newVersion) {
|
||||
echo(chalk.blue(`Updating ${file} to pnpm@${newVersion}`))
|
||||
miseToml.tools.pnpm = newVersion
|
||||
const miseString = toml.stringify(miseToml) + '\n'
|
||||
await Bun.file(filePath).write(miseString.replaceAll('"', "'"))
|
||||
result.updated++
|
||||
result.files.push(file)
|
||||
}
|
||||
} catch (e) {
|
||||
result.failed++
|
||||
result.errors.push(`Failed to update ${file}: ${e instanceof Error ? e.message : String(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user