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

80
pnpm-lock.yaml generated
View File

@ -1,38 +1,38 @@
lockfileVersion: 5.3 lockfileVersion: 5.4
specifiers: specifiers:
'@types/inquirer': ^8.2.0 '@types/inquirer': ^8.2.1
'@types/node': ^16.11.26 '@types/node': ^16.11.47
chalk: ^5.0.1 chalk: ^5.0.1
inquirer: ^8.2.1 inquirer: ^8.2.4
typescript: ^4.6.2 typescript: ^4.7.4
dependencies: dependencies:
chalk: 5.0.1 chalk: 5.0.1
inquirer: 8.2.1 inquirer: 8.2.4
devDependencies: devDependencies:
'@types/inquirer': 8.2.0 '@types/inquirer': 8.2.1
'@types/node': 16.11.26 '@types/node': 16.11.47
typescript: 4.6.2 typescript: 4.7.4
packages: packages:
/@types/inquirer/8.2.0: /@types/inquirer/8.2.1:
resolution: {integrity: sha512-BNoMetRf3gmkpAlV5we+kxyZTle7YibdOntIZbU5pyIfMdcwy784KfeZDAcuyMznkh5OLa17RVXZOGA5LTlkgQ==} resolution: {integrity: sha512-wKW3SKIUMmltbykg4I5JzCVzUhkuD9trD6efAmYgN2MrSntY0SMRQzEnD3mkyJ/rv9NLbTC7g3hKKE86YwEDLw==}
dependencies: dependencies:
'@types/through': 0.0.30 '@types/through': 0.0.30
rxjs: 7.5.5 rxjs: 7.5.6
dev: true dev: true
/@types/node/16.11.26: /@types/node/16.11.47:
resolution: {integrity: sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==} resolution: {integrity: sha512-fpP+jk2zJ4VW66+wAMFoBJlx1bxmBKx4DUFf68UHgdGCOuyUTDlLWqsaNPJh7xhNDykyJ9eIzAygilP/4WoN8g==}
dev: true dev: true
/@types/through/0.0.30: /@types/through/0.0.30:
resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==} resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==}
dependencies: dependencies:
'@types/node': 16.11.26 '@types/node': 16.11.47
dev: true dev: true
/ansi-escapes/4.3.2: /ansi-escapes/4.3.2:
@ -97,8 +97,8 @@ packages:
restore-cursor: 3.1.0 restore-cursor: 3.1.0
dev: false dev: false
/cli-spinners/2.6.1: /cli-spinners/2.7.0:
resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==} resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
@ -108,7 +108,7 @@ packages:
dev: false dev: false
/clone/1.0.4: /clone/1.0.4:
resolution: {integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4=} resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
dev: false dev: false
@ -124,7 +124,7 @@ packages:
dev: false dev: false
/defaults/1.0.3: /defaults/1.0.3:
resolution: {integrity: sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=} resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==}
dependencies: dependencies:
clone: 1.0.4 clone: 1.0.4
dev: false dev: false
@ -134,7 +134,7 @@ packages:
dev: false dev: false
/escape-string-regexp/1.0.5: /escape-string-regexp/1.0.5:
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
dev: false dev: false
@ -174,8 +174,8 @@ packages:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: false dev: false
/inquirer/8.2.1: /inquirer/8.2.4:
resolution: {integrity: sha512-pxhBaw9cyTFMjwKtkjePWDhvwzvrNGAw7En4hottzlPvz80GZaMZthdDU35aA6/f5FRZf3uhE057q8w1DE3V2g==} resolution: {integrity: sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
dependencies: dependencies:
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
@ -188,10 +188,11 @@ packages:
mute-stream: 0.0.8 mute-stream: 0.0.8
ora: 5.4.1 ora: 5.4.1
run-async: 2.4.1 run-async: 2.4.1
rxjs: 7.5.5 rxjs: 7.5.6
string-width: 4.2.3 string-width: 4.2.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
through: 2.3.8 through: 2.3.8
wrap-ansi: 7.0.0
dev: false dev: false
/is-fullwidth-code-point/3.0.0: /is-fullwidth-code-point/3.0.0:
@ -244,7 +245,7 @@ packages:
bl: 4.1.0 bl: 4.1.0
chalk: 4.1.2 chalk: 4.1.2
cli-cursor: 3.1.0 cli-cursor: 3.1.0
cli-spinners: 2.6.1 cli-spinners: 2.7.0
is-interactive: 1.0.0 is-interactive: 1.0.0
is-unicode-supported: 0.1.0 is-unicode-supported: 0.1.0
log-symbols: 4.1.0 log-symbols: 4.1.0
@ -253,7 +254,7 @@ packages:
dev: false dev: false
/os-tmpdir/1.0.2: /os-tmpdir/1.0.2:
resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=} resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: false dev: false
@ -279,10 +280,10 @@ packages:
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
dev: false dev: false
/rxjs/7.5.5: /rxjs/7.5.6:
resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==} resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==}
dependencies: dependencies:
tslib: 2.3.1 tslib: 2.4.0
/safe-buffer/5.2.1: /safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@ -326,7 +327,7 @@ packages:
dev: false dev: false
/through/2.3.8: /through/2.3.8:
resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: false dev: false
/tmp/0.0.33: /tmp/0.0.33:
@ -336,26 +337,35 @@ packages:
os-tmpdir: 1.0.2 os-tmpdir: 1.0.2
dev: false dev: false
/tslib/2.3.1: /tslib/2.4.0:
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
/type-fest/0.21.3: /type-fest/0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: false dev: false
/typescript/4.6.2: /typescript/4.7.4:
resolution: {integrity: sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==} resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'} engines: {node: '>=4.2.0'}
hasBin: true hasBin: true
dev: true dev: true
/util-deprecate/1.0.2: /util-deprecate/1.0.2:
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false dev: false
/wcwidth/1.0.1: /wcwidth/1.0.1:
resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=} resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies: dependencies:
defaults: 1.0.3 defaults: 1.0.3
dev: false 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 #!/usr/bin/env node
import cp from 'child_process'
import chalk from 'chalk' import chalk from 'chalk'
import cp from 'child_process'
import inquirer from 'inquirer' 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() { function checkIfBrewIsInstalled() {
try { try {
@ -15,13 +36,30 @@ function checkIfBrewIsInstalled() {
} }
} }
function getListOfLeaves(): string[] { function getListOfPackages(): ListEntry[] {
const list = cp.execSync('brew leaves', { encoding: 'utf-8' }) 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[] { function getListOfCasks(): ListEntry[] {
return leaves.filter((leave) => !keepers.includes(leave)) 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() { async function main() {
@ -31,49 +69,64 @@ async function main() {
} }
console.log(`${chalk.bold.blue('UnBrew')} - Brew cleanup utility\nVersion: ${VERSION}\n`) console.log(`${chalk.bold.blue('UnBrew')} - Brew cleanup utility\nVersion: ${VERSION}\n`)
let leaves: string[] const { cask } = await inquirer.prompt([
let loosers: string[] {
type: 'confirm',
name: 'cask',
message: `Also consider casks?`,
},
])
let initialState = getList(cask)
leaves = getListOfLeaves()
const { keepers } = await inquirer.prompt([ const { keepers } = await inquirer.prompt([
{ {
type: 'checkbox', type: 'checkbox',
message: 'Select packages to keep (all by default)', message: 'Select packages to keep (all by default)',
name: 'keepers', name: 'keepers',
choices: leaves.map((leave) => ({ choices: initialState.map((entry) => ({
name: leave, name: entry,
checked: true, checked: true,
})), })),
}, },
]) ])
loosers = getLoosers(keepers, leaves) // Uninstalling
if (loosers.length === 0) { let first = true
console.log(chalk.bold('No package/s selected for deletion.')) let allUninstalled: ListEntry[] = []
return while (true) {
} // Get all to be uninstalled
const { confirmed } = await inquirer.prompt([ const loosers = getLoosers(keepers, first ? initialState : getList(cask))
{
type: 'confirm',
name: 'confirmed',
message: `Delelte: ${chalk.bold.blue(loosers.join(' '))}`,
},
])
if (!confirmed) { // First time prompt
console.log(chalk.bold.red('Aborted')) if (first) {
return 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') console.log('✅ Uninstalled: ' + allUninstalled.join(', '))
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('🧽 Cleaning up') console.log('🧽 Cleaning up')
cp.execSync(`brew cleanup`) cp.execSync(`brew cleanup`)
@ -81,5 +134,5 @@ async function main() {
console.log(chalk.bold.green('🚀 Done')) console.log(chalk.bold.green('🚀 Done'))
} }
main().finally(() => { main().finally(() => {
console.log(chalk.blue('Bye Bye 👋')) console.log(chalk.blue('👋 Bye Bye'))
}) })