add support for cask

This commit is contained in:
cupcakearmy 2022-08-01 17:16:35 +02:00
parent 689776d975
commit 9e892cb06e
No known key found for this signature in database
GPG Key ID: 3235314B4D31232F
4 changed files with 155 additions and 76 deletions

16
CHANGELOG.md Normal file
View File

@ -0,0 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.3.0] - 2022-08-01
### Added
- Support for casks.
### Security
- Update dependencies.

View File

@ -1,7 +1,7 @@
{
"name": "unbrew",
"description": "brew cleanup utility",
"version": "1.2.4",
"version": "1.3.0",
"author": {
"name": "Niccolo Borgioli",
"email": "hi@nicco.io",
@ -32,11 +32,11 @@
"type": "module",
"dependencies": {
"chalk": "^5.0.1",
"inquirer": "^8.2.1"
"inquirer": "^8.2.4"
},
"devDependencies": {
"@types/inquirer": "^8.2.0",
"@types/node": "^16.11.26",
"typescript": "^4.6.2"
"@types/inquirer": "^8.2.1",
"@types/node": "^16.11.47",
"typescript": "^4.7.4"
}
}

View File

@ -1,38 +1,38 @@
lockfileVersion: 5.3
lockfileVersion: 5.4
specifiers:
'@types/inquirer': ^8.2.0
'@types/node': ^16.11.26
'@types/inquirer': ^8.2.1
'@types/node': ^16.11.47
chalk: ^5.0.1
inquirer: ^8.2.1
typescript: ^4.6.2
inquirer: ^8.2.4
typescript: ^4.7.4
dependencies:
chalk: 5.0.1
inquirer: 8.2.1
inquirer: 8.2.4
devDependencies:
'@types/inquirer': 8.2.0
'@types/node': 16.11.26
typescript: 4.6.2
'@types/inquirer': 8.2.1
'@types/node': 16.11.47
typescript: 4.7.4
packages:
/@types/inquirer/8.2.0:
resolution: {integrity: sha512-BNoMetRf3gmkpAlV5we+kxyZTle7YibdOntIZbU5pyIfMdcwy784KfeZDAcuyMznkh5OLa17RVXZOGA5LTlkgQ==}
/@types/inquirer/8.2.1:
resolution: {integrity: sha512-wKW3SKIUMmltbykg4I5JzCVzUhkuD9trD6efAmYgN2MrSntY0SMRQzEnD3mkyJ/rv9NLbTC7g3hKKE86YwEDLw==}
dependencies:
'@types/through': 0.0.30
rxjs: 7.5.5
rxjs: 7.5.6
dev: true
/@types/node/16.11.26:
resolution: {integrity: sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==}
/@types/node/16.11.47:
resolution: {integrity: sha512-fpP+jk2zJ4VW66+wAMFoBJlx1bxmBKx4DUFf68UHgdGCOuyUTDlLWqsaNPJh7xhNDykyJ9eIzAygilP/4WoN8g==}
dev: true
/@types/through/0.0.30:
resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==}
dependencies:
'@types/node': 16.11.26
'@types/node': 16.11.47
dev: true
/ansi-escapes/4.3.2:
@ -97,8 +97,8 @@ packages:
restore-cursor: 3.1.0
dev: false
/cli-spinners/2.6.1:
resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==}
/cli-spinners/2.7.0:
resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==}
engines: {node: '>=6'}
dev: false
@ -108,7 +108,7 @@ packages:
dev: false
/clone/1.0.4:
resolution: {integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4=}
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
dev: false
@ -124,7 +124,7 @@ packages:
dev: false
/defaults/1.0.3:
resolution: {integrity: sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=}
resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==}
dependencies:
clone: 1.0.4
dev: false
@ -134,7 +134,7 @@ packages:
dev: false
/escape-string-regexp/1.0.5:
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
dev: false
@ -174,8 +174,8 @@ packages:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: false
/inquirer/8.2.1:
resolution: {integrity: sha512-pxhBaw9cyTFMjwKtkjePWDhvwzvrNGAw7En4hottzlPvz80GZaMZthdDU35aA6/f5FRZf3uhE057q8w1DE3V2g==}
/inquirer/8.2.4:
resolution: {integrity: sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==}
engines: {node: '>=12.0.0'}
dependencies:
ansi-escapes: 4.3.2
@ -188,10 +188,11 @@ packages:
mute-stream: 0.0.8
ora: 5.4.1
run-async: 2.4.1
rxjs: 7.5.5
rxjs: 7.5.6
string-width: 4.2.3
strip-ansi: 6.0.1
through: 2.3.8
wrap-ansi: 7.0.0
dev: false
/is-fullwidth-code-point/3.0.0:
@ -244,7 +245,7 @@ packages:
bl: 4.1.0
chalk: 4.1.2
cli-cursor: 3.1.0
cli-spinners: 2.6.1
cli-spinners: 2.7.0
is-interactive: 1.0.0
is-unicode-supported: 0.1.0
log-symbols: 4.1.0
@ -253,7 +254,7 @@ packages:
dev: false
/os-tmpdir/1.0.2:
resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=}
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
dev: false
@ -279,10 +280,10 @@ packages:
engines: {node: '>=0.12.0'}
dev: false
/rxjs/7.5.5:
resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==}
/rxjs/7.5.6:
resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==}
dependencies:
tslib: 2.3.1
tslib: 2.4.0
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@ -326,7 +327,7 @@ packages:
dev: false
/through/2.3.8:
resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=}
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: false
/tmp/0.0.33:
@ -336,26 +337,35 @@ packages:
os-tmpdir: 1.0.2
dev: false
/tslib/2.3.1:
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
/tslib/2.4.0:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
/type-fest/0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
dev: false
/typescript/4.6.2:
resolution: {integrity: sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==}
/typescript/4.7.4:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/util-deprecate/1.0.2:
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false
/wcwidth/1.0.1:
resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=}
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies:
defaults: 1.0.3
dev: false
/wrap-ansi/7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false

View File

@ -1,10 +1,31 @@
#!/usr/bin/env node
import cp from 'child_process'
import chalk from 'chalk'
import cp from 'child_process'
import inquirer from 'inquirer'
const VERSION = '1.2.3'
const VERSION = '1.3.0'
class ListEntry {
name!: string
type!: 'package' | 'cask'
constructor(options: ListEntry) {
Object.assign(this, options)
}
toString() {
return `${this.type === 'package' ? '📦' : '🍾'} ${this.name}`
}
static parse(line: string): ListEntry {
const [type, name] = line.split(' ')
if (!name) throw new Error('Could not parse line')
if (type === '📦') return new ListEntry({ name, type: 'package' })
if (type === '🍾') return new ListEntry({ name, type: 'cask' })
throw new Error('Could not parse type')
}
}
function checkIfBrewIsInstalled() {
try {
@ -15,13 +36,30 @@ function checkIfBrewIsInstalled() {
}
}
function getListOfLeaves(): string[] {
function getListOfPackages(): ListEntry[] {
const list = cp.execSync('brew leaves', { encoding: 'utf-8' })
return list.trim().split('\n')
return list
.trim()
.split('\n')
.map((line) => new ListEntry({ name: line, type: 'package' }))
}
function getLoosers(keepers: string[], leaves = getListOfLeaves()): string[] {
return leaves.filter((leave) => !keepers.includes(leave))
function getListOfCasks(): ListEntry[] {
const list = cp.execSync('brew list --cask -1', { encoding: 'utf-8' })
return list
.trim()
.split('\n')
.map((line) => new ListEntry({ name: line, type: 'cask' }))
}
function getList(cask: boolean): ListEntry[] {
const list = getListOfPackages()
if (cask) list.push(...getListOfCasks())
return list
}
function getLoosers(keepers: ListEntry[], leaves: ListEntry[]): ListEntry[] {
return leaves.filter((leave) => !keepers.some((keeper) => keeper.name === leave.name))
}
async function main() {
@ -31,49 +69,64 @@ async function main() {
}
console.log(`${chalk.bold.blue('UnBrew')} - Brew cleanup utility\nVersion: ${VERSION}\n`)
let leaves: string[]
let loosers: string[]
const { cask } = await inquirer.prompt([
{
type: 'confirm',
name: 'cask',
message: `Also consider casks?`,
},
])
let initialState = getList(cask)
leaves = getListOfLeaves()
const { keepers } = await inquirer.prompt([
{
type: 'checkbox',
message: 'Select packages to keep (all by default)',
name: 'keepers',
choices: leaves.map((leave) => ({
name: leave,
choices: initialState.map((entry) => ({
name: entry,
checked: true,
})),
},
])
loosers = getLoosers(keepers, leaves)
if (loosers.length === 0) {
console.log(chalk.bold('No package/s selected for deletion.'))
return
}
const { confirmed } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmed',
message: `Delelte: ${chalk.bold.blue(loosers.join(' '))}`,
},
])
// Uninstalling
let first = true
let allUninstalled: ListEntry[] = []
while (true) {
// Get all to be uninstalled
const loosers = getLoosers(keepers, first ? initialState : getList(cask))
if (!confirmed) {
console.log(chalk.bold.red('Aborted'))
return
// First time prompt
if (first) {
if (loosers.length === 0) {
console.log(chalk.bold('No package/s selected for deletion.'))
return
}
const { confirmed } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmed',
message: `Delelte: ${chalk.bold.blue(loosers.map((l) => l.name).join(', '))} and their dependencies?`,
},
])
if (!confirmed) {
console.log(chalk.bold.red('Aborted'))
return
}
console.log('🗑 Uninstalling')
first = false
}
// Actual uninstalling
if (loosers.length === 0) break
allUninstalled.push(...loosers)
cp.execSync(`brew uninstall ${loosers.map((l) => l.name).join(' ')}`)
}
console.log('🗑 Uninstalling')
const allLoosers: string[] = []
while (loosers.length) {
allLoosers.push(...loosers)
const joinedLoosers = loosers.join(' ')
cp.execSync(`brew uninstall ${joinedLoosers}`)
loosers = getLoosers(keepers)
}
console.log('✅ Uninstalled: ' + allLoosers.join(', '))
console.log('✅ Uninstalled: ' + allUninstalled.join(', '))
console.log('🧽 Cleaning up')
cp.execSync(`brew cleanup`)
@ -81,5 +134,5 @@ async function main() {
console.log(chalk.bold.green('🚀 Done'))
}
main().finally(() => {
console.log(chalk.blue('Bye Bye 👋'))
console.log(chalk.blue('👋 Bye Bye'))
})