UNPKG

epic-cli

Version:

Commands useful for everyday web development with node.

100 lines (83 loc) 3.67 kB
#!/usr/bin/env bun import { execSync } from 'node:child_process' import { styleText } from 'node:util' import Bun from 'bun' import { diff, neq, valid } from 'semver' const skipUpdate = process.argv.includes('--no-install') const updatedDependencies: { name: string; previous: string; latest: string; breaking: boolean }[] = [] let processedDependencies = 0 const versionRangeRegex = /(\^|~|>=|>|<=|<)?\d+(\.\d+){0,2}(\.\*)?/ const versionRangeReplaceRegex = /^(\^|~|>=|>|<=|<)/ const packageJson = await Bun.file('./package.json').json() const dependencyTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies', 'bundleDependencies'] const exactDependencies = [] as { name: string; version: string }[] const dependencyCount = dependencyTypes.reduce((total, type) => total + (packageJson[type] ? Object.keys(packageJson[type]).length : 0), 0) if (dependencyCount === 0) { console.log('No dependencies found.') process.exit(0) } process.stdout.write(`Updating ${dependencyCount} dependencies...\r`) await Promise.all( dependencyTypes.map(async (type) => { const dependencies = packageJson[type] as { [key: string]: string } if (!dependencies) { return } const fetchPromises = Object.keys(dependencies).map(async (name) => { const previousVersionRange = dependencies[name] if (!previousVersionRange) { return } const previousVersion = previousVersionRange.replace(versionRangeReplaceRegex, '') if (!valid(previousVersion)) { return // Ignore invalid versions } const versionRange = (previousVersionRange.match(versionRangeRegex) ?? [])[1] if (!versionRange) { exactDependencies.push({ name, version: previousVersionRange }) return // Keep exact versions as they are } const { version } = (await (await fetch(`https://registry.npmjs.org/${name}/latest`)).json()) as { version?: string } if (version && neq(version, previousVersion)) { dependencies[name] = `${versionRange || ''}${version}` updatedDependencies.push({ name, previous: previousVersionRange, latest: `${versionRange || ''}${version}`, breaking: diff(previousVersion, version) === 'major', }) } processedDependencies += 1 process.stdout.write(`Updating ${processedDependencies}/${dependencyCount} dependencies...\r`) }) await Promise.all(fetchPromises) }), ) process.stdout.write('\r\x1b[K') // Clear progress output. if (updatedDependencies.length > 0) { console.log(`${updatedDependencies.length} dependencies updated.`) for (const update of updatedDependencies) { console.log( `${styleText('bold', update.name)} ${update.previous}${update.latest}${ update.breaking ? styleText('bold', styleText('red', ' BREAKING CHANGE!')) : '' }`, ) } Bun.write('./package.json', `${JSON.stringify(packageJson, null, 2)}\n`) if (!skipUpdate) { console.log('Installing new dependencies with Bun.') // Using bun shell here will not exit in tests... execSync('bun update', { stdio: 'inherit', }) // NOTE write package again after "bun update" as it will turn ~ ranges into ^ ranges. Bun.write('./package.json', `${JSON.stringify(packageJson, null, 2)}\n`) } } else { console.log('All dependencies are up to date.') } if (exactDependencies.length > 0) { console.log( `Warning: Dependencies ${exactDependencies.map(({ name, version }) => `${styleText('bold', name)} (${version})`).join(',')} won't be updated as they are exact. Add a version range to ensure automatic updates.`, ) }