Skip to content

Commit 0d5ba64

Browse files
committed
feat: Add function and command-line option for clearing cache
BREAKING CHANGE: The package exports only named exports from now on. If you imported the function `grab` as a default export, import it by the name `grab` as a named export from now on. The command-line tool works as it did with no breaking change.
1 parent 6c3bf05 commit 0d5ba64

8 files changed

Lines changed: 139 additions & 37 deletions

File tree

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ If used to install a binary executable to a NPM package, [link-bin-executable] c
1212
## Synopsis
1313

1414
```js
15-
import grab from 'grab-github-release'
15+
import { grab } from 'grab-github-release'
1616

1717
try {
1818
const repository = 'prantlf/v-jsonlint'
@@ -57,6 +57,7 @@ Make sure, that you use [Node.js] version 18 or newer.
5757
Usage: [options]
5858

5959
Options:
60+
--clear-cache clears the cache, optionally for a "name"
6061
-r|--repository <repository> GitHub repository formatted "owner/name"
6162
-i|--version-spec <semver> semantic version specifier or "latest"
6263
-n|--name <file-name> archive name without the platform suffix
@@ -108,6 +109,14 @@ interface GrabOptions {
108109
targetDirectory?: string
109110
// unpack the executable and remove the archive
110111
unpackExecutable?: boolean
112+
// store the downloaded archives from GitHub releases to the cache
113+
// in ~/.cache/grabghr; `true` is the default
114+
cache?: boolean
115+
// GitHub authentication token, overrides the environment variables
116+
// GITHUB_TOKEN or GH_TOKEN
117+
token?: string
118+
// print details about the program execution
119+
verbose?: boolean
111120
}
112121

113122
interface GrabResult {
@@ -119,9 +128,20 @@ interface GrabResult {
119128
executable?: string
120129
}
121130

131+
interface ClearCacheOptions {
132+
// archive name without the platform and architecture suffix
133+
// and without the ".zip" extension as well, used as a cache directory name
134+
name?: string
135+
// print details about the program execution
136+
verbose?: boolean
137+
}
138+
122139
// downloads and optionally unpacks an archive from GitHub release assets
123140
// for the current platform
124-
export default function grab(options: GrabOptions): GrabResult
141+
export function grab(options: GrabOptions): Promise<GrabResult>
142+
143+
// clears the cache used for downloading archives from GitHub releases
144+
export function clearCache(options?: ClearCacheOptions): Promise<void>
125145
```
126146

127147
## Contributing

bin/grab-github-release.js

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
#!/usr/bin/env node
22

3-
import grab from '../dist/index.mjs'
3+
import { grab, clearCache as clear } from '../dist/index.mjs'
44

55
function help() {
66
console.log(`Generates a unique build number with a human-readable build time.
77
88
Usage: [options]
99
1010
Options:
11+
--clear-cache clears the cache, optionally for a "name"
1112
-r|--repository <repository> GitHub repository formatted "owner/name"
1213
-i|--version-spec <semver> semantic version specifier or "latest"
1314
-n|--name <file-name> archive name without the platform suffix
@@ -37,7 +38,7 @@ function fail(message) {
3738
}
3839

3940
const { argv } = process
40-
let repository, version, name, platformSuffixes, archSuffixes,
41+
let clearCache, repository, version, name, platformSuffixes, archSuffixes,
4142
targetDirectory, unpackExecutable, cache, token, verbose
4243

4344
for (let i = 2, l = argv.length; i < l; ++i) {
@@ -47,6 +48,9 @@ for (let i = 2, l = argv.length; i < l; ++i) {
4748
const parseArg = async (arg, flag) => {
4849
let entries
4950
switch (arg) {
51+
case 'clear-cache':
52+
clearCache = flag
53+
return
5054
case 'r': case 'repository':
5155
repository = match[4] || argv[++i]
5256
return
@@ -116,26 +120,35 @@ for (let i = 2, l = argv.length; i < l; ++i) {
116120
fail(`unrecognized argument: "${arg}"`)
117121
}
118122

119-
if (!repository) {
120-
if (argv.length > 2) fail('missing repository')
121-
help()
122-
process.exit(0)
123-
}
123+
if (clearCache) {
124+
try {
125+
await clear({ name, verbose })
126+
} catch(err) {
127+
console.error(err.message)
128+
process.exitCode = 1
129+
}
130+
} else {
131+
if (!repository) {
132+
if (argv.length > 2) fail('missing repository')
133+
help()
134+
process.exit(0)
135+
}
124136

125-
try {
126-
await grab({
127-
repository,
128-
version,
129-
name,
130-
platformSuffixes,
131-
archSuffixes,
132-
targetDirectory,
133-
unpackExecutable,
134-
cache,
135-
token,
136-
verbose
137-
})
138-
} catch(err) {
139-
console.error(err.message)
140-
process.exitCode = 1
141-
}
137+
try {
138+
await grab({
139+
repository,
140+
version,
141+
name,
142+
platformSuffixes,
143+
archSuffixes,
144+
targetDirectory,
145+
unpackExecutable,
146+
cache,
147+
token,
148+
verbose
149+
})
150+
} catch(err) {
151+
console.error(err.message)
152+
process.exitCode = 1
153+
}
154+
}

src/index.d.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ interface GrabOptions {
4545
* in ~/.cache/grabghr; `true` is the default
4646
*/
4747
cache?: boolean
48+
/**
49+
* GitHub authentication token, overrides the environment variables
50+
* GITHUB_TOKEN or GH_TOKEN
51+
*/
52+
token?: string
53+
/**
54+
* print details about the program execution
55+
*/
56+
verbose?: boolean
4857
}
4958

5059
interface GrabResult {
@@ -62,10 +71,29 @@ interface GrabResult {
6271
executable?: string
6372
}
6473

74+
interface ClearCacheOptions {
75+
/**
76+
* archive name without the platform and architecture suffix
77+
* and without the ".zip" extension as well, used as a cache directory name
78+
*/
79+
name?: string
80+
/**
81+
* print details about the program execution
82+
*/
83+
verbose?: boolean
84+
}
85+
6586
/**
6687
* downloads and optionally unpacks an archive from GitHub release assets
6788
* for the current platform
6889
*
6990
* @param options see properties of `GrabOptions` for more information
7091
*/
71-
export default function grab(options: GrabOptions): GrabResult
92+
export function grab(options: GrabOptions): Promise<GrabResult>
93+
94+
/**
95+
* clears the cache used for downloading archives from GitHub releases
96+
*
97+
* @param options see properties of `ClearCacheOptions` for more information
98+
*/
99+
export function clearCache(options?: ClearCacheOptions): Promise<void>

src/index.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Readable } from 'stream'
77
import { open as openArchive } from 'yauzl'
88

99
let log = debug('grabghr')
10-
const { access, chmod, copyFile, mkdir, rename, unlink } = promises
10+
const { access, chmod, copyFile, mkdir, rm, rename, unlink } = promises
1111
const { arch, platform } = process
1212

1313
async function exists(file) {
@@ -192,6 +192,13 @@ async function storeCache(cacheDir, cachePath, archivePath, copy) {
192192
}
193193
}
194194

195+
async function removeCache(name) {
196+
const cacheDir = name ? join(homedir(), '.cache/grabghr', name)
197+
: join(homedir(), '.cache/grabghr')
198+
log('remove "%s"', cacheDir)
199+
await rm(cacheDir, { recursive: true, force: true })
200+
}
201+
195202
async function download(url, archive, token) {
196203
const res = await fetchSafely(url, token)
197204
await new Promise((resolve, reject) => {
@@ -268,7 +275,7 @@ async function makeExecutable(executable) {
268275
}
269276
}
270277

271-
export default async function grab({ name, repository, version, platformSuffixes, archSuffixes, targetDirectory, unpackExecutable, cache, token, verbose }) {
278+
export async function grab({ name, repository, version, platformSuffixes, archSuffixes, targetDirectory, unpackExecutable, cache, token, verbose }) {
272279
if (verbose) log = console.log.bind(console)
273280
if (!version) version = 'latest'
274281
const verspec = clean(version) || version
@@ -286,3 +293,8 @@ export default async function grab({ name, repository, version, platformSuffixes
286293
}
287294
return { archive: archivePath, version }
288295
}
296+
297+
export async function clearCache({ name, verbose } = {}) {
298+
if (verbose) log = console.log.bind(console)
299+
await removeCache(name)
300+
}

test/cjs.cjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const { ok } = require('assert')
2-
const grab = require('../dist/index.cjs')
2+
const exported = require('../dist/index.cjs')
33

4-
ok(typeof grab === 'function')
4+
ok(exported)
5+
ok(typeof exported === 'object')
6+
ok(typeof exported.grab === 'function')
7+
ok(typeof exported.clearCache === 'function')

test/mocked.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { after, before, beforeEach, test, mock } from 'node:test'
44
import { homedir } from 'os'
55
import { dirname, join } from 'path'
66
import { fileURLToPath } from 'url'
7-
import grab from 'grab-github-release'
7+
import { grab, clearCache } from 'grab-github-release'
88
import releases from './data/releases.json' assert { type: 'json' }
99

1010
const exists = file => access(file).then(() => true, () => false)
@@ -30,7 +30,8 @@ const content = new Blob(
3030
{ type: 'application.zip' }
3131
)
3232
const targetDirectory = join(__dirname, 'tmp')
33-
const cacheDirectory = join(homedir(), '.cache/grabghr/', name)
33+
const wholeCacheDirectory = join(homedir(), '.cache/grabghr')
34+
const cacheDirectory = join(wholeCacheDirectory, name)
3435

3536
async function cleanup() {
3637
// failed on Windows on GitHub
@@ -174,3 +175,18 @@ test('copy archive of the latest implicit version from cache and unpack executab
174175
strictEqual(actualVersion, version)
175176
ok(actualExecutable.endsWith(executable))
176177
})
178+
179+
test('clear cache for a name', async () => {
180+
await grab({ repository, version })
181+
ok(await exists(cacheDirectory), 'cache not found')
182+
await clearCache({ name, verbose: true })
183+
ok(!await exists(cacheDirectory), 'cache found')
184+
ok(await exists(wholeCacheDirectory), 'whole cache not found')
185+
})
186+
187+
test('clear the whole cache', async () => {
188+
await grab({ repository, version })
189+
ok(await exists(cacheDirectory), 'cache not found')
190+
await clearCache()
191+
ok(!await exists(wholeCacheDirectory), 'whole cache found')
192+
})

test/real.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { access, rm } from 'fs/promises'
33
import { after, before, test } from 'node:test'
44
import { homedir } from 'os'
55
import { join } from 'path'
6-
import grab from 'grab-github-release'
6+
import { grab } from 'grab-github-release'
77

88
const exists = file => access(file).then(() => true, () => false)
99
const { platform, arch } = process
@@ -17,7 +17,7 @@ const platformSuffixes = {
1717
win32: 'windows'
1818
}
1919
const archive = `${name}-${platformSuffixes[platform]}-${arch}.zip`
20-
const cacheDir = join(homedir(), '.cache/grabghr/', name)
20+
const cacheDir = join(homedir(), '.cache/grabghr', name)
2121

2222
function cleanup() {
2323
return Promise.all([

test/types.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import grab from 'grab-github-release'
1+
import { grab, clearCache } from 'grab-github-release'
22

33
declare type testCallback = () => void
44
declare function test (label: string, callback: testCallback)
@@ -22,6 +22,16 @@ test('Type declarations for TypeScript', () => {
2222
x64: ['']
2323
},
2424
targetDirectory: '',
25-
unpackExecutable: true
25+
unpackExecutable: true,
26+
cache: true,
27+
token: '',
28+
verbose: true
29+
})
30+
31+
clearCache()
32+
33+
clearCache({
34+
name: '',
35+
verbose: true
2636
})
2737
})

0 commit comments

Comments
 (0)