rewrite with commander

This commit is contained in:
cupcakearmy 2020-11-06 23:51:23 +01:00
parent e9a7e03af7
commit 60d7e0b561
No known key found for this signature in database
GPG Key ID: D28129AE5654D9D9
27 changed files with 702 additions and 766 deletions

4
.gitignore vendored
View File

@ -12,11 +12,9 @@ data
restore restore
docker docker
Dockerfile Dockerfile
build
# Config # Config
.autorestic.yml .autorestic.yml
.autorestic.lock .autorestic.lock
.docker.yml .docker.yml
# Docs
.codedoc

View File

@ -2,10 +2,9 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"build:watch": "tsc -w", "dev": "tsc -w",
"dev": "tsnd --no-notify --respawn ./src/autorestic.ts", "move": "mv bin/index-linux bin/autorestic_linux_x64 && mv bin/index-macos bin/autorestic_macos_x64",
"move": "mv bin/autorestic-linux bin/autorestic_linux_x64 && mv bin/autorestic-macos bin/autorestic_macos_x64", "bin": "yarn run build && pkg lib/index.js --targets latest-macos-x64,latest-linux-x64 --out-path bin && yarn run move",
"bin": "yarn run build && pkg lib/autorestic.js --targets latest-macos-x64,latest-linux-x64 --out-path bin && yarn run move",
"docs:build": "codedoc install && codedoc build", "docs:build": "codedoc install && codedoc build",
"docs:dev": "codedoc serve" "docs:dev": "codedoc serve"
}, },
@ -25,7 +24,6 @@
"commander": "^6.2.0", "commander": "^6.2.0",
"cron-parser": "2.x.x", "cron-parser": "2.x.x",
"js-yaml": "3.x.x", "js-yaml": "3.x.x",
"minimist": "1.x.x",
"uhrwerk": "1.x.x" "uhrwerk": "1.x.x"
} }
} }

View File

@ -1,63 +0,0 @@
import 'colors'
import minimist from 'minimist'
import { init } from './config'
import handlers, { error, help } from './handlers'
import { Config } from './types'
import { readLock, writeLock, unlock } from './lock'
process.on('uncaughtException', (err) => {
console.log(err.message)
unlock()
process.exit(1)
})
export const { _: commands, ...flags } = minimist(process.argv.slice(2), {
alias: {
c: 'config',
v: 'version',
h: 'help',
a: 'all',
l: 'location',
b: 'backend',
d: 'dry-run',
},
boolean: ['a', 'd'],
string: ['l', 'b'],
})
export const VERSION = '0.20'
export const INSTALL_DIR = '/usr/local/bin'
export const VERBOSE = flags.verbose
export let config: Config
async function main() {
config = init()
// Don't let 2 instances run on the same config
const lock = readLock()
if (lock.running) {
console.log('An instance of autorestic is already running for this config file'.red)
return
}
writeLock({
...lock,
running: true,
})
// For dev
// return await handlers['cron']([], { ...flags, all: true })
if (commands.length < 1 || commands[0] === 'help') return help()
const command: string = commands[0]
const args: string[] = commands.slice(1)
const fn = handlers[command] || error
await fn(args, flags)
}
main()
.catch((e: Error) => console.error(e.message))
.finally(unlock)

View File

@ -1,11 +1,9 @@
import { Writer } from 'clitastic' import { Writer } from 'clitastic'
import { config, VERBOSE } from './autorestic' import { config, VERBOSE } from './'
import { Backend, Backends, Locations } from './types' import { Backend, Backends, Locations } from './types'
import { exec, pathRelativeToConfigFile, filterObjectByKey } from './utils' import { exec, pathRelativeToConfigFile, filterObjectByKey } from './utils'
const ALREADY_EXISTS = /(?=.*already)(?=.*config).*/ const ALREADY_EXISTS = /(?=.*already)(?=.*config).*/
export const getPathFromBackend = (backend: Backend): string => { export const getPathFromBackend = (backend: Backend): string => {
@ -35,8 +33,7 @@ export const getEnvFromBackend = (backend: Backend) => {
export const getBackendsFromLocations = (locations: Locations): string[] => { export const getBackendsFromLocations = (locations: Locations): string[] => {
const backends = new Set<string>() const backends = new Set<string>()
for (const to of Object.values(locations).map(location => location.to)) for (const to of Object.values(locations).map((location) => location.to)) Array.isArray(to) ? to.forEach((t) => backends.add(t)) : backends.add(to)
Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to)
return Array.from(backends) return Array.from(backends)
} }
@ -47,8 +44,7 @@ export const checkAndConfigureBackend = (name: string, backend: Backend) => {
const { out, err } = exec('restic', ['init'], { env }) const { out, err } = exec('restic', ['init'], { env })
if (err.length > 0 && !ALREADY_EXISTS.test(err)) if (err.length > 0 && !ALREADY_EXISTS.test(err)) throw new Error(`Could not load the backend "${name}": ${err}`)
throw new Error(`Could not load the backend "${name}": ${err}`)
if (VERBOSE && out.length > 0) console.log(out) if (VERBOSE && out.length > 0) console.log(out)
@ -59,16 +55,12 @@ export const checkAndConfigureBackend = (name: string, backend: Backend) => {
} }
export const checkAndConfigureBackends = (backends?: Backends) => { export const checkAndConfigureBackends = (backends?: Backends) => {
if (!backends) if (!backends) backends = config.backends
backends = config.backends
console.log('\nConfiguring Backends'.grey.underline) console.log('\nConfiguring Backends'.grey.underline)
for (const [name, backend] of Object.entries(backends)) for (const [name, backend] of Object.entries(backends)) checkAndConfigureBackend(name, backend)
checkAndConfigureBackend(name, backend)
} }
export const checkAndConfigureBackendsForLocations = (locations: Locations) => { export const checkAndConfigureBackendsForLocations = (locations: Locations) => {
checkAndConfigureBackends( checkAndConfigureBackends(filterObjectByKey(config.backends, getBackendsFromLocations(locations)))
filterObjectByKey(config.backends, getBackendsFromLocations(locations)),
)
} }

View File

@ -1,7 +1,7 @@
import { Writer } from 'clitastic' import { Writer } from 'clitastic'
import { mkdirSync } from 'fs' import { mkdirSync } from 'fs'
import { config, VERBOSE } from './autorestic' import { config, VERBOSE } from './'
import { getEnvFromBackend } from './backend' import { getEnvFromBackend } from './backend'
import { LocationFromPrefixes } from './config' import { LocationFromPrefixes } from './config'
import { Locations, Location, Backend } from './types' import { Locations, Location, Backend } from './types'
@ -18,20 +18,16 @@ import {
getPathFromVolume, getPathFromVolume,
} from './utils' } from './utils'
export const backupFromFilesystem = (from: string, location: Location, backend: Backend, tags?: string[]) => { export const backupFromFilesystem = (from: string, location: Location, backend: Backend, tags?: string[]) => {
const path = pathRelativeToConfigFile(from) const path = pathRelativeToConfigFile(from)
const { out, err, status } = exec( const { out, err, status } = exec('restic', ['backup', '.', ...getFlagsFromLocation(location, 'backup')], {
'restic', env: getEnvFromBackend(backend),
['backup', '.', ...getFlagsFromLocation(location, 'backup')], cwd: path,
{ env: getEnvFromBackend(backend), cwd: path }, })
)
if (VERBOSE) console.log(out, err) if (VERBOSE) console.log(out, err)
if (status != 0 || err.length > 0) if (status != 0 || err.length > 0) throw new Error(err)
throw new Error(err)
} }
export const backupFromVolume = (volume: string, location: Location, backend: Backend) => { export const backupFromVolume = (volume: string, location: Location, backend: Backend) => {
@ -61,7 +57,6 @@ export const backupSingle = (name: string, to: string, location: Location) => {
const [type, value] = decodeLocationFromPrefix(location.from) const [type, value] = decodeLocationFromPrefix(location.from)
switch (type) { switch (type) {
case LocationFromPrefixes.Filesystem: case LocationFromPrefixes.Filesystem:
backupFromFilesystem(value, location, backend) backupFromFilesystem(value, location, backend)
break break
@ -69,7 +64,6 @@ export const backupSingle = (name: string, to: string, location: Location) => {
case LocationFromPrefixes.DockerVolume: case LocationFromPrefixes.DockerVolume:
backupFromVolume(value, location, backend) backupFromVolume(value, location, backend)
break break
} }
writer.done(`${name}${to.blue} : ${'Done ✓'.green} (${delta.finished(true)})`) writer.done(`${name}${to.blue} : ${'Done ✓'.green} (${delta.finished(true)})`)
@ -102,10 +96,8 @@ export const backupLocation = (name: string, location: Location) => {
} }
export const backupAll = (locations?: Locations) => { export const backupAll = (locations?: Locations) => {
if (!locations) if (!locations) locations = config.locations
locations = config.locations
console.log('\nBacking Up'.underline.grey) console.log('\nBacking Up'.underline.grey)
for (const [name, location] of Object.entries(locations)) for (const [name, location] of Object.entries(locations)) backupLocation(name, location)
backupLocation(name, location)
} }

View File

@ -5,7 +5,6 @@ import { homedir } from 'os'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import CronParser from 'cron-parser' import CronParser from 'cron-parser'
import { flags } from './autorestic'
import { Backend, Config } from './types' import { Backend, Config } from './types'
import { makeArrayIfIsNot, makeObjectKeysLowercase, rand } from './utils' import { makeArrayIfIsNot, makeObjectKeysLowercase, rand } from './utils'
@ -56,9 +55,9 @@ export const normalizeAndCheckLocations = (config: Config) => {
} }
} }
const findConfigFile = (): string => { const findConfigFile = (custom: string): string => {
const config = '.autorestic.yml' const config = '.autorestic.yml'
const paths = [resolve(flags.config || ''), resolve('./' + config), homedir() + '/' + config] const paths = [resolve(custom || ''), resolve('./' + config), homedir() + '/' + config]
for (const path of paths) { for (const path of paths) {
try { try {
const file = statSync(path) const file = statSync(path)
@ -70,8 +69,8 @@ const findConfigFile = (): string => {
export let CONFIG_FILE: string = '' export let CONFIG_FILE: string = ''
export const init = (): Config => { export const init = (custom: string): Config => {
const file = findConfigFile() const file = findConfigFile(custom)
CONFIG_FILE = file CONFIG_FILE = file
const parsed = yaml.safeLoad(readFileSync(CONFIG_FILE).toString()) const parsed = yaml.safeLoad(readFileSync(CONFIG_FILE).toString())

View File

@ -1,12 +1,11 @@
import CronParser from 'cron-parser' import CronParser from 'cron-parser'
import { config } from './autorestic' import { config } from './'
import { checkAndConfigureBackendsForLocations } from './backend' import { checkAndConfigureBackendsForLocations } from './backend'
import { Location } from './types' import { Location } from './types'
import { backupLocation } from './backup' import { backupLocation } from './backup'
import { readLock, writeLock } from './lock' import { readLock, writeLock } from './lock'
const runCronForLocation = (name: string, location: Location) => { const runCronForLocation = (name: string, location: Location) => {
const lock = readLock() const lock = readLock()
const parsed = CronParser.parseExpression(location.cron || '') const parsed = CronParser.parseExpression(location.cron || '')
@ -26,8 +25,7 @@ export const runCron = () => {
checkAndConfigureBackendsForLocations(Object.fromEntries(locationsWithCron)) checkAndConfigureBackendsForLocations(Object.fromEntries(locationsWithCron))
console.log('\nRunning cron jobs'.underline.gray) console.log('\nRunning cron jobs'.underline.gray)
for (const [name, location] of locationsWithCron) for (const [name, location] of locationsWithCron) runCronForLocation(name, location)
runCronForLocation(name, location)
console.log('\nFinished!'.underline + ' 🎉') console.log('\nFinished!'.underline + ' 🎉')
} }

View File

@ -1,18 +1,10 @@
import { Writer } from 'clitastic' import { Writer } from 'clitastic'
import { config, VERBOSE } from './autorestic' import { config, VERBOSE } from './'
import { getEnvFromBackend } from './backend' import { getEnvFromBackend } from './backend'
import { LocationFromPrefixes } from './config' import { LocationFromPrefixes } from './config'
import { Locations, Location, Flags } from './types' import { Locations, Location, Flags } from './types'
import { import { exec, pathRelativeToConfigFile, getFlagsFromLocation, makeArrayIfIsNot, fill, decodeLocationFromPrefix, getPathFromVolume } from './utils'
exec,
pathRelativeToConfigFile,
getFlagsFromLocation,
makeArrayIfIsNot,
fill, decodeLocationFromPrefix, getPathFromVolume,
} from './utils'
export const forgetSingle = (name: string, to: string, location: Location, dryRun: boolean) => { export const forgetSingle = (name: string, to: string, location: Location, dryRun: boolean) => {
const base = name + to.blue + ' : ' const base = name + to.blue + ' : '
@ -39,11 +31,7 @@ export const forgetSingle = (name: string, to: string, location: Location, dryRu
if (dryRun) flags.push('--dry-run') if (dryRun) flags.push('--dry-run')
writer.replaceLn(base + 'Forgetting old snapshots… ⏳') writer.replaceLn(base + 'Forgetting old snapshots… ⏳')
const cmd = exec( const cmd = exec('restic', ['forget', '--path', path, '--prune', ...flags], { env: getEnvFromBackend(backend) })
'restic',
['forget', '--path', path, '--prune', ...flags],
{ env: getEnvFromBackend(backend) },
)
if (VERBOSE) console.log(cmd.out, cmd.err) if (VERBOSE) console.log(cmd.out, cmd.err)
writer.done(base + 'Done ✓'.green) writer.done(base + 'Done ✓'.green)
@ -61,15 +49,13 @@ export const forgetLocation = (name: string, backup: Location, dryRun: boolean)
} }
} }
export const forgetAll = (backups?: Locations, flags?: Flags) => { export const forgetAll = (backups?: Locations, dryRun = false) => {
if (!backups) { if (!backups) {
backups = config.locations backups = config.locations
} }
console.log('\nRemoving old snapshots according to policy'.underline.grey) console.log('\nRemoving old snapshots according to policy'.underline.grey)
const dryRun = flags ? flags['dry-run'] : false
if (dryRun) console.log('Running in dry-run mode, not touching data\n'.yellow) if (dryRun) console.log('Running in dry-run mode, not touching data\n'.yellow)
for (const [name, backup] of Object.entries(backups)) for (const [name, backup] of Object.entries(backups)) forgetLocation(name, backup, dryRun)
forgetLocation(name, backup, dryRun)
} }

View File

@ -1,244 +0,0 @@
import { chmodSync, renameSync, unlinkSync } from 'fs'
import { tmpdir } from 'os'
import { join, resolve } from 'path'
import axios from 'axios'
import { Writer } from 'clitastic'
import { config, INSTALL_DIR, VERSION } from './autorestic'
import { checkAndConfigureBackends, getEnvFromBackend, checkAndConfigureBackendsForLocations } from './backend'
import { backupAll } from './backup'
import { runCron } from './cron'
import { forgetAll } from './forget'
import showAll from './info'
import { restoreSingle } from './restore'
import { Backends, Flags, Locations } from './types'
import {
checkIfCommandIsAvailable,
checkIfResticIsAvailable,
downloadFile,
exec,
filterObjectByKey,
makeArrayIfIsNot,
} from './utils'
export type Handlers = {
[command: string]: (args: string[], flags: Flags) => void
}
const parseBackend = (flags: Flags): Backends => {
if (!flags.all && !flags.backend)
throw new Error(
'No backends specified.'.red +
'\n--all [-a]\t\t\t\tCheck all.' +
'\n--backend [-b] myBackend\t\tSpecify one or more backend',
)
if (flags.all) return config.backends
else {
const backends = makeArrayIfIsNot<string>(flags.backend)
for (const backend of backends)
if (!config.backends[backend])
throw new Error('Invalid backend: '.red + backend)
return filterObjectByKey(config.backends, backends)
}
}
const parseLocations = (flags: Flags): Locations => {
if (!flags.all && !flags.location)
throw new Error(
'No locations specified.'.red +
'\n--all [-a]\t\t\t\tBackup all.' +
'\n--location [-l] site1\t\t\tSpecify one or more locations',
)
if (flags.all) {
return config.locations
} else {
const locations = makeArrayIfIsNot<string>(flags.location)
for (const location of locations)
if (!config.locations[location])
throw new Error('Invalid location: '.red + location)
return filterObjectByKey(config.locations, locations)
}
}
const handlers: Handlers = {
check(args, flags) {
checkIfResticIsAvailable()
const backends = parseBackend(flags)
checkAndConfigureBackends(backends)
},
backup(args, flags) {
checkIfResticIsAvailable()
const locations: Locations = parseLocations(flags)
checkAndConfigureBackendsForLocations(locations)
backupAll(locations)
console.log('\nFinished!'.underline + ' 🎉')
},
cron(args, flags) {
checkIfResticIsAvailable()
runCron()
},
restore(args, flags) {
checkIfResticIsAvailable()
const locations = parseLocations(flags)
const keys = Object.keys(locations)
if (keys.length < 1) throw new Error(`You need to specify the location to restore with --location`.red)
if (keys.length > 2) throw new Error(`Only one location is supported at a time when restoring`.red)
restoreSingle(keys[0], flags.from, flags.to)
},
forget(args, flags) {
checkIfResticIsAvailable()
const locations: Locations = parseLocations(flags)
checkAndConfigureBackendsForLocations(locations)
forgetAll(locations, flags)
console.log('\nFinished!'.underline + ' 🎉')
},
exec(args, flags) {
checkIfResticIsAvailable()
const backends = parseBackend(flags)
for (const [name, backend] of Object.entries(backends)) {
console.log(`\n${name}:\n`.grey.underline)
const env = getEnvFromBackend(backend)
const { out, err } = exec('restic', args, { env })
console.log(out, err)
}
},
info() {
showAll()
},
async install() {
try {
checkIfResticIsAvailable()
console.log('Restic is already installed')
return
} catch {
}
const w = new Writer('Checking latest version... ⏳')
checkIfCommandIsAvailable('bzip2')
const { data: json } = await axios({
method: 'get',
url: 'https://api.github.com/repos/restic/restic/releases/latest',
responseType: 'json',
})
const archMap: { [a: string]: string } = {
x32: '386',
x64: 'amd64',
}
w.replaceLn('Downloading binary... 🌎')
const name = `${json.name.replace(' ', '_')}_${process.platform}_${archMap[process.arch]}.bz2`
const dl = json.assets.find((asset: any) => asset.name === name)
if (!dl)
return console.log(
'Cannot get the right binary.'.red,
'Please see https://bit.ly/2Y1Rzai',
)
const tmp = join(tmpdir(), name)
const extracted = tmp.slice(0, -4) //without the .bz2
await downloadFile(dl.browser_download_url, tmp)
w.replaceLn('Decompressing binary... 📦')
exec('bzip2', ['-dk', tmp])
unlinkSync(tmp)
w.replaceLn(`Moving to ${INSTALL_DIR} 🚙`)
chmodSync(extracted, 0o755)
renameSync(extracted, INSTALL_DIR + '/restic')
w.done(
`\nFinished! restic is installed under: ${INSTALL_DIR}`.underline + ' 🎉',
)
},
uninstall() {
for (const bin of ['restic', 'autorestic'])
try {
unlinkSync(INSTALL_DIR + '/' + bin)
console.log(`Finished! ${bin} was uninstalled`)
} catch (e) {
console.log(`${bin} is already uninstalled`.red)
}
},
async update() {
checkIfResticIsAvailable()
const w = new Writer('Checking for latest restic version... ⏳')
exec('restic', ['self-update'])
w.replaceLn('Checking for latest autorestic version... ⏳')
const { data: json } = await axios({
method: 'get',
url:
'https://api.github.com/repos/cupcakearmy/autorestic/releases/latest',
responseType: 'json',
})
if (json.tag_name != VERSION) {
const platformMap: { [key: string]: string } = {
darwin: 'macos',
}
const name = `autorestic_${platformMap[process.platform] || process.platform}_${process.arch}`
const dl = json.assets.find((asset: any) => asset.name === name)
const to = INSTALL_DIR + '/autorestic'
w.replaceLn('Downloading binary... 🌎')
await downloadFile(dl.browser_download_url, to)
chmodSync(to, 0o755)
}
w.done('All up to date! 🚀')
},
version() {
console.log('version'.grey, VERSION)
},
}
export const help = () => {
console.log(
'\nAutorestic'.blue +
` - ${VERSION} - Easy Restic CLI Utility` +
'\n' +
'\nOptions:'.yellow +
`\n -c, --config Specify config file. Default: .autorestic.yml` +
'\n' +
'\nCommands:'.yellow +
'\n info Show all locations and backends' +
'\n check [-b, --backend] [-a, --all] Check backends' +
'\n backup [-l, --location] [-a, --all] Backup all or specified locations' +
'\n forget [-l, --location] [-a, --all] [--dry-run] Forget old snapshots according to declared policies' +
'\n restore [-l, --location] [--from backend] [--to <out dir>] Restore all or specified locations' +
'\n' +
'\n exec [-b, --backend] [-a, --all] <command> -- [native options] Execute native restic command' +
'\n' +
'\n install install restic' +
'\n uninstall uninstall restic' +
'\n update update restic' +
'\n help Show help' +
'\n' +
'\nExamples: '.yellow +
'https://git.io/Jf0x6' +
'\n',
)
}
export const error = () => {
help()
console.log(
`Invalid Command:`.red.underline,
`${process.argv.slice(2).join(' ')}`,
)
}
export default handlers

13
src/handlers/backup.ts Normal file
View File

@ -0,0 +1,13 @@
import { checkAndConfigureBackendsForLocations } from '../backend'
import { backupAll } from '../backup'
import { Flags, Locations } from '../types'
import { checkIfResticIsAvailable, parseLocations } from '../utils'
export default function backup({ location, all }: Flags) {
checkIfResticIsAvailable()
const locations: Locations = parseLocations(location, all)
checkAndConfigureBackendsForLocations(locations)
backupAll(locations)
console.log('\nFinished!'.underline + ' 🎉')
}

9
src/handlers/check.ts Normal file
View File

@ -0,0 +1,9 @@
import { checkAndConfigureBackends } from '../backend'
import { Flags } from '../types'
import { checkIfResticIsAvailable, parseBackend } from '../utils'
export default function check({ backend, all }: Flags) {
checkIfResticIsAvailable()
const backends = parseBackend(backend, all)
checkAndConfigureBackends(backends)
}

7
src/handlers/cron.ts Normal file
View File

@ -0,0 +1,7 @@
import { runCron } from '../cron'
import { checkIfResticIsAvailable } from '../utils'
export function cron() {
checkIfResticIsAvailable()
runCron()
}

14
src/handlers/exec.ts Normal file
View File

@ -0,0 +1,14 @@
import { getEnvFromBackend } from '../backend'
import { Flags } from '../types'
import { checkIfResticIsAvailable, exec as execCLI, parseBackend } from '../utils'
export default function exec({ backend, all }: Flags, args: string[]) {
checkIfResticIsAvailable()
const backends = parseBackend(backend, all)
for (const [name, backend] of Object.entries(backends)) {
console.log(`\n${name}:\n`.grey.underline)
const env = getEnvFromBackend(backend)
const { out, err } = execCLI('restic', args, { env })
console.log(out, err)
}
}

13
src/handlers/forget.ts Normal file
View File

@ -0,0 +1,13 @@
import { checkAndConfigureBackendsForLocations } from '../backend'
import { forgetAll } from '../forget'
import { Flags, Locations } from '../types'
import { checkIfResticIsAvailable, parseLocations } from '../utils'
export default function forget({ location, all, dryRun }: Flags) {
checkIfResticIsAvailable()
const locations: Locations = parseLocations(location, all)
checkAndConfigureBackendsForLocations(locations)
forgetAll(locations, dryRun)
console.log('\nFinished!'.underline + ' 🎉')
}

18
src/handlers/info.ts Normal file
View File

@ -0,0 +1,18 @@
import { config } from '../'
import { fill, treeToString } from '../utils'
const showAll = () => {
console.log('\n\n' + fill(32, '_') + 'LOCATIONS:'.underline)
for (const [key, data] of Object.entries(config.locations)) {
console.log(`\n${key.blue.underline}:`)
console.log(treeToString(data, ['to:', 'from:', 'hooks:', 'options:', 'cron:']))
}
console.log('\n\n' + fill(32, '_') + 'BACKENDS:'.underline)
for (const [key, data] of Object.entries(config.backends)) {
console.log(`\n${key.blue.underline}:`)
console.log(treeToString(data, ['type:', 'path:', 'key:']))
}
}
export default showAll

50
src/handlers/install.ts Normal file
View File

@ -0,0 +1,50 @@
import { join } from 'path'
import { chmodSync, renameSync, unlinkSync } from 'fs'
import { tmpdir } from 'os'
import axios from 'axios'
import { Writer } from 'clitastic'
import { INSTALL_DIR } from '..'
import { checkIfCommandIsAvailable, checkIfResticIsAvailable, downloadFile, exec } from '../utils'
export default async function install() {
try {
checkIfResticIsAvailable()
console.log('Restic is already installed')
return
} catch {}
const w = new Writer('Checking latest version... ⏳')
checkIfCommandIsAvailable('bzip2')
const { data: json } = await axios({
method: 'get',
url: 'https://api.github.com/repos/restic/restic/releases/latest',
responseType: 'json',
})
const archMap: { [a: string]: string } = {
x32: '386',
x64: 'amd64',
}
w.replaceLn('Downloading binary... 🌎')
const name = `${json.name.replace(' ', '_')}_${process.platform}_${archMap[process.arch]}.bz2`
const dl = json.assets.find((asset: any) => asset.name === name)
if (!dl) return console.log('Cannot get the right binary.'.red, 'Please see https://bit.ly/2Y1Rzai')
const tmp = join(tmpdir(), name)
const extracted = tmp.slice(0, -4) //without the .bz2
await downloadFile(dl.browser_download_url, tmp)
w.replaceLn('Decompressing binary... 📦')
exec('bzip2', ['-dk', tmp])
unlinkSync(tmp)
w.replaceLn(`Moving to ${INSTALL_DIR} 🚙`)
chmodSync(extracted, 0o755)
renameSync(extracted, INSTALL_DIR + '/restic')
w.done(`\nFinished! restic is installed under: ${INSTALL_DIR}`.underline + ' 🎉')
}

9
src/handlers/restore.ts Normal file
View File

@ -0,0 +1,9 @@
import { restoreSingle } from '../restore'
import { Flags } from '../types'
import { checkIfResticIsAvailable, checkIfValidLocation } from '../utils'
export default function restore({ location, to, from }: Flags) {
checkIfResticIsAvailable()
checkIfValidLocation(location)
restoreSingle(location, from, to)
}

13
src/handlers/uninstall.ts Normal file
View File

@ -0,0 +1,13 @@
import { unlinkSync } from 'fs'
import { INSTALL_DIR } from '..'
export function uninstall() {
for (const bin of ['restic', 'autorestic'])
try {
unlinkSync(INSTALL_DIR + '/' + bin)
console.log(`Finished! ${bin} was uninstalled`)
} catch (e) {
console.log(`${bin} is already uninstalled`.red)
}
}

37
src/handlers/upgrade.ts Normal file
View File

@ -0,0 +1,37 @@
import { chmodSync } from 'fs'
import axios from 'axios'
import { Writer } from 'clitastic'
import { INSTALL_DIR, VERSION } from '..'
import { checkIfResticIsAvailable, downloadFile, exec } from '../utils'
export async function upgrade() {
checkIfResticIsAvailable()
const w = new Writer('Checking for latest restic version... ⏳')
exec('restic', ['self-update'])
w.replaceLn('Checking for latest autorestic version... ⏳')
const { data: json } = await axios({
method: 'get',
url: 'https://api.github.com/repos/cupcakearmy/autorestic/releases/latest',
responseType: 'json',
})
if (json.tag_name != VERSION) {
const platformMap: { [key: string]: string } = {
darwin: 'macos',
}
const name = `autorestic_${platformMap[process.platform] || process.platform}_${process.arch}`
const dl = json.assets.find((asset: any) => asset.name === name)
const to = INSTALL_DIR + '/autorestic'
w.replaceLn('Downloading binary... 🌎')
await downloadFile(dl.browser_download_url, to)
chmodSync(to, 0o755)
}
w.done('All up to date! 🚀')
}

104
src/index.ts Normal file
View File

@ -0,0 +1,104 @@
import 'colors'
import { program } from 'commander'
import { unlock, readLock, writeLock } from './lock'
import { Config } from './types'
import { init } from './config'
import info from './handlers/info'
import check from './handlers/check'
import backup from './handlers/backup'
import restore from './handlers/restore'
import forget from './handlers/forget'
import { cron } from './handlers/cron'
import exec from './handlers/exec'
import install from './handlers/install'
import { uninstall } from './handlers/uninstall'
import { upgrade } from './handlers/upgrade'
export const VERSION = '0.20'
export const INSTALL_DIR = '/usr/local/bin'
process.on('uncaughtException', (err) => {
console.log(err.message)
unlock()
process.exit(1)
})
let queue: Function = () => {}
const enqueue = (fn: Function) => (cmd: any) => {
queue = () => fn(cmd.opts())
}
program.storeOptionsAsProperties()
program.name('autorestic').version(VERSION)
program.option('-c, --config <path>', 'Config file').option('-v, --verbose', 'Verbosity', false)
program.command('info').action(enqueue(info))
program
.command('check')
.description('Checks and initializes backend as needed')
.option('-b, --backend <backends...>')
.option('-a, --all')
.action(enqueue(check))
program.command('backup').description('Performs a backup').option('-b, --backend <backends...>').option('-a, --all').action(enqueue(backup))
program
.command('restore')
.description('Restores data to a specified folder from a location')
.requiredOption('-l, --location <location>')
.option('--from <backend>')
.requiredOption('--to <path>', 'Path to save the restored data to')
.action(enqueue(restore))
program
.command('forget')
.description('This will prune and remove data according to your policies')
.option('-l, --location <locations...>')
.option('-a, --all')
.option('--dry-run')
.action(enqueue(forget))
program
.command('cron')
.description('Intended to be triggered by an automated system like systemd or crontab.')
.option('-a, --all')
.action(enqueue(cron))
program
.command('exec')
.description('Run any native restic command on desired backends')
.option('-b, --backend <backends...>')
.option('-a, --all')
.action(({ args, all, backend }) => {
queue = () => exec({ all, backend }, args)
})
program.command('install').description('Installs both restic and autorestic to /usr/local/bin').action(enqueue(install))
program.command('uninstall').description('Uninstalls autorestic from the system').action(enqueue(uninstall))
program.command('upgrade').alias('update').description('Checks and installs new autorestic versions').action(enqueue(upgrade))
const { verbose, config: configFile } = program.parse(process.argv)
export const VERBOSE = verbose
export let config: Config = init(configFile)
try {
const lock = readLock()
if (lock.running) throw new Error('An instance of autorestic is already running for this config file'.red)
writeLock({
...lock,
running: true,
})
queue()
} catch (e) {
console.error(e.message)
} finally {
unlock()
}

View File

@ -1,25 +1,17 @@
import { config } from './autorestic' import { config } from './'
import { fill, treeToString } from './utils' import { fill, treeToString } from './utils'
const showAll = () => { const showAll = () => {
console.log('\n\n' + fill(32, '_') + 'LOCATIONS:'.underline) console.log('\n\n' + fill(32, '_') + 'LOCATIONS:'.underline)
for (const [key, data] of Object.entries(config.locations)) { for (const [key, data] of Object.entries(config.locations)) {
console.log(`\n${key.blue.underline}:`) console.log(`\n${key.blue.underline}:`)
console.log(treeToString( console.log(treeToString(data, ['to:', 'from:', 'hooks:', 'options:', 'cron:']))
data,
['to:', 'from:', 'hooks:', 'options:', 'cron:'],
))
} }
console.log('\n\n' + fill(32, '_') + 'BACKENDS:'.underline) console.log('\n\n' + fill(32, '_') + 'BACKENDS:'.underline)
for (const [key, data] of Object.entries(config.backends)) { for (const [key, data] of Object.entries(config.backends)) {
console.log(`\n${key.blue.underline}:`) console.log(`\n${key.blue.underline}:`)
console.log(treeToString( console.log(treeToString(data, ['type:', 'path:', 'key:']))
data,
['type:', 'path:', 'key:'],
))
} }
} }

View File

@ -1,7 +1,7 @@
import fs from 'fs' import fs from 'fs'
import { pathRelativeToConfigFile } from "./utils" import { pathRelativeToConfigFile } from './utils'
import { Lockfile } from "./types" import { Lockfile } from './types'
export const getLockFileName = () => { export const getLockFileName = () => {
const LOCK_FILE = '.autorestic.lock' const LOCK_FILE = '.autorestic.lock'
@ -12,11 +12,11 @@ export const readLock = (): Lockfile => {
const name = getLockFileName() const name = getLockFileName()
let lock = { let lock = {
running: false, running: false,
crons: {} crons: {},
} }
try { try {
lock = JSON.parse(fs.readFileSync(name, { encoding: 'utf-8' })) lock = JSON.parse(fs.readFileSync(name, { encoding: 'utf-8' }))
} catch { } } catch {}
return lock return lock
} }
export const writeLock = (lock: Lockfile) => { export const writeLock = (lock: Lockfile) => {

View File

@ -1,26 +1,14 @@
import { Writer } from 'clitastic' import { Writer } from 'clitastic'
import { resolve } from 'path' import { resolve } from 'path'
import { config } from './autorestic' import { config } from './'
import { getEnvFromBackend } from './backend' import { getEnvFromBackend } from './backend'
import { LocationFromPrefixes } from './config' import { LocationFromPrefixes } from './config'
import { Backend } from './types' import { Backend } from './types'
import { import { checkIfDockerVolumeExistsOrFail, decodeLocationFromPrefix, exec, execPlain, getPathFromVolume } from './utils'
checkIfDockerVolumeExistsOrFail,
decodeLocationFromPrefix,
exec,
execPlain,
getPathFromVolume,
} from './utils'
export const restoreToFilesystem = (from: string, to: string, backend: Backend) => { export const restoreToFilesystem = (from: string, to: string, backend: Backend) => {
exec( exec('restic', ['restore', 'latest', '--path', resolve(from), '--target', to], { env: getEnvFromBackend(backend) })
'restic',
['restore', 'latest', '--path', resolve(from), '--target', to],
{ env: getEnvFromBackend(backend) },
)
} }
export const restoreToVolume = (volume: string, backend: Backend) => { export const restoreToVolume = (volume: string, backend: Backend) => {
@ -62,7 +50,6 @@ export const restoreSingle = (locationName: string, from: string, to?: string) =
const [type, value] = decodeLocationFromPrefix(location.from) const [type, value] = decodeLocationFromPrefix(location.from)
switch (type) { switch (type) {
case LocationFromPrefixes.Filesystem: case LocationFromPrefixes.Filesystem:
if (!to) throw new Error(`You need to specify the restore path with --to`.red) if (!to) throw new Error(`You need to specify the restore path with --to`.red)
restoreToFilesystem(value, to, backend) restoreToFilesystem(value, to, backend)
@ -71,8 +58,6 @@ export const restoreSingle = (locationName: string, from: string, to?: string) =
case LocationFromPrefixes.DockerVolume: case LocationFromPrefixes.DockerVolume:
restoreToVolume(value, backend) restoreToVolume(value, backend)
break break
} }
w.done(locationName.green + '\t\tDone 🎉') w.done(locationName.green + '\t\tDone 🎉')
} }

View File

@ -55,14 +55,7 @@ type BackendGS = {
google_application_credentials: string google_application_credentials: string
} }
export type Backend = export type Backend = BackendAzure | BackendB2 | BackendGS | BackendLocal | BackendREST | BackendS3 | BackendSFTP
| BackendAzure
| BackendB2
| BackendGS
| BackendLocal
| BackendREST
| BackendS3
| BackendSFTP
export type Backends = { [name: string]: Backend } export type Backends = { [name: string]: Backend }

View File

@ -1,13 +1,15 @@
import axios from 'axios'
import { spawnSync, SpawnSyncOptions } from 'child_process' import { spawnSync, SpawnSyncOptions } from 'child_process'
import { createHash, randomBytes } from 'crypto' import { createHash, randomBytes } from 'crypto'
import { createWriteStream, renameSync, unlinkSync } from 'fs' import { createWriteStream, renameSync, unlinkSync } from 'fs'
import { homedir, tmpdir } from 'os' import { homedir, tmpdir } from 'os'
import { dirname, isAbsolute, join, resolve } from 'path' import { dirname, isAbsolute, join, resolve } from 'path'
import axios from 'axios'
import { Duration, Humanizer } from 'uhrwerk' import { Duration, Humanizer } from 'uhrwerk'
import { CONFIG_FILE, LocationFromPrefixes } from './config' import { CONFIG_FILE, LocationFromPrefixes } from './config'
import { Location } from './types' import { Backends, Location, Locations } from './types'
import { config } from '.'
export const exec = (command: string, args: string[], { env, ...rest }: SpawnSyncOptions = {}) => { export const exec = (command: string, args: string[], { env, ...rest }: SpawnSyncOptions = {}) => {
const { stdout, stderr, status } = spawnSync(command, args, { const { stdout, stderr, status } = spawnSync(command, args, {
@ -106,6 +108,35 @@ export const getFlagsFromLocation = (location: Location, command?: string): stri
return flags return flags
} }
export function parseBackend(backends: string[] = [], all: boolean = false): Backends {
if (all) return config.backends
if (backends.length) {
for (const backend of backends) if (!config.backends[backend]) throw new Error('Invalid backend: '.red + backend)
return filterObjectByKey(config.backends, backends)
} else {
throw new Error(
'No backends specified.'.red + '\n-a, --all, -a\t\t\tSelect all.' + '\n-b, --backend <backends...>\t\tSpecify one or more backend'
)
}
}
export function checkIfValidLocation(location: string) {
if (!config.locations[location]) throw new Error('Invalid location: '.red + location)
}
export function parseLocations(locations: string[] = [], all: boolean = false): Locations {
if (all) {
return config.locations
}
if (locations.length) {
for (const location of locations) checkIfValidLocation(location)
return filterObjectByKey(config.locations, locations)
}
throw new Error(
'No locations specified.'.red + '\n-a, --all\t\t\tSelect all.' + '\n-l, --location <locations...>\t\t\tSpecify one or more location'
)
}
export const makeArrayIfIsNot = <T>(maybeArray: T | T[]): T[] => (Array.isArray(maybeArray) ? maybeArray : [maybeArray]) export const makeArrayIfIsNot = <T>(maybeArray: T | T[]): T[] => (Array.isArray(maybeArray) ? maybeArray : [maybeArray])
export const fill = (length: number, filler = ' '): string => new Array(length).fill(filler).join('') export const fill = (length: number, filler = ' '): string => new Array(length).fill(filler).join('')

View File

@ -3,6 +3,7 @@
"target": "esnext", "target": "esnext",
"module": "commonjs", "module": "commonjs",
"outDir": "./lib", "outDir": "./lib",
"rootDir": "./src",
"strict": true, "strict": true,
"esModuleInterop": true "esModuleInterop": true
} }

233
yarn.lock
View File

@ -3,21 +3,21 @@
"@babel/parser@^7.9.4": "@babel/parser@^7.9.4":
version "7.10.3" version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0"
integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA== integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==
"@babel/runtime@^7.9.2": "@babel/runtime@^7.9.2":
version "7.10.3" version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@codedoc/cli@0.2.x": "@codedoc/cli@0.2.x":
version "0.2.1" version "0.2.6"
resolved "https://registry.yarnpkg.com/@codedoc/cli/-/cli-0.2.1.tgz#03daa1b1e0cedff25c0b5dc29ab8c823b2dd28ef" resolved "https://registry.yarnpkg.com/@codedoc/cli/-/cli-0.2.6.tgz#5e6e981f8eafefcab2b8c81ee6c14815baf2732c"
integrity sha512-kdxbcKaxM3AUEoD70trMrUFitdbJ8mTS7u5ojJfzZ2uQtTIjO1D0XJLydsPyPXqLQf/TyOZJPEm03Q3d9Smt0A== integrity sha512-zn92PvMamXCteZudz1i606qa4bPXcXveNgb6oQTAiW7Lq+bRMZHUyKvA3Q3jVwlQtV7SidJg3jA4RwkMllktog==
dependencies: dependencies:
chalk "^4.0.0" chalk "^4.0.0"
shelljs "^0.8.3" shelljs "^0.8.3"
@ -46,11 +46,6 @@
"@nodelib/fs.scandir" "2.1.3" "@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0" fastq "^1.6.0"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/js-yaml@3.x.x": "@types/js-yaml@3.x.x":
version "3.12.5" version "3.12.5"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb"
@ -62,9 +57,9 @@
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
"@types/node@14.x.x": "@types/node@14.x.x":
version "14.0.14" version "14.14.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.6.tgz#146d3da57b3c636cc0d1769396ce1cfa8991147f"
integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== integrity sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==
"@types/strip-bom@^3.0.0": "@types/strip-bom@^3.0.0":
version "3.0.0" version "3.0.0"
@ -76,10 +71,10 @@
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
ajv@^6.5.5: ajv@^6.12.3:
version "6.12.2" version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies: dependencies:
fast-deep-equal "^3.1.1" fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0" fast-json-stable-stringify "^2.0.0"
@ -87,11 +82,10 @@ ajv@^6.5.5:
uri-js "^4.2.2" uri-js "^4.2.2"
ansi-styles@^4.1.0: ansi-styles@^4.1.0:
version "4.2.1" version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies: dependencies:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1" color-convert "^2.0.1"
anymatch@~3.1.1: anymatch@~3.1.1:
@ -147,9 +141,9 @@ aws-sign2@~0.7.0:
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0: aws4@^1.8.0:
version "1.10.0" version "1.11.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
axios@0.19.x: axios@0.19.x:
version "0.19.2" version "0.19.2"
@ -235,9 +229,9 @@ chalk@^4.0.0:
supports-color "^7.1.0" supports-color "^7.1.0"
chokidar@^3.4.0: chokidar@^3.4.0:
version "3.4.0" version "3.4.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
dependencies: dependencies:
anymatch "~3.1.1" anymatch "~3.1.1"
braces "~3.0.2" braces "~3.0.2"
@ -245,7 +239,7 @@ chokidar@^3.4.0:
is-binary-path "~2.1.0" is-binary-path "~2.1.0"
is-glob "~4.0.1" is-glob "~4.0.1"
normalize-path "~3.0.0" normalize-path "~3.0.0"
readdirp "~3.4.0" readdirp "~3.5.0"
optionalDependencies: optionalDependencies:
fsevents "~2.1.2" fsevents "~2.1.2"
@ -280,6 +274,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
commander@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==
concat-map@0.0.1: concat-map@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -291,9 +290,9 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cron-parser@2.x.x: cron-parser@2.x.x:
version "2.15.0" version "2.17.0"
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.15.0.tgz#04803cd51d8efcfcc6f83ac08e60f3f8c40c7ec5" resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.17.0.tgz#5707421a7e0a73ee74675d1c032a2f14123f2cf8"
integrity sha512-rMFkrQw8+oG5OuwjiXesup4KeIlEG/IU82YtG4xyAHbO5jhKmYaHPp/ZNhq9+7TjSJ65E3zV3kQPUbmXSff2/g== integrity sha512-oTmzVEwlurRe51HqTm4afshVr8Rkxy9kFiWxh5e6SmrY2o9NDYU4S6SduanBZYXLgkLy0skA98y7/tztW/DmjQ==
dependencies: dependencies:
is-nan "^1.3.0" is-nan "^1.3.0"
moment-timezone "^0.5.31" moment-timezone "^0.5.31"
@ -458,9 +457,9 @@ fast-levenshtein@~2.0.6:
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fastq@^1.6.0: fastq@^1.6.0:
version "1.8.0" version "1.9.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947"
integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== integrity sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==
dependencies: dependencies:
reusify "^1.0.4" reusify "^1.0.4"
@ -527,6 +526,11 @@ fsevents@~2.1.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
get-stdin@^4.0.1: get-stdin@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
@ -575,22 +579,17 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
har-schema@^2.0.0: har-schema@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.3: har-validator@~5.1.3:
version "5.1.3" version "5.1.5"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
dependencies: dependencies:
ajv "^6.5.5" ajv "^6.12.3"
har-schema "^2.0.0" har-schema "^2.0.0"
has-flag@^4.0.0: has-flag@^4.0.0:
@ -598,6 +597,13 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
hosted-git-info@^2.1.4: hosted-git-info@^2.1.4:
version "2.8.8" version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
@ -662,6 +668,13 @@ is-binary-path@~2.1.0:
dependencies: dependencies:
binary-extensions "^2.0.0" binary-extensions "^2.0.0"
is-core-module@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946"
integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1: is-extglob@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@ -701,21 +714,11 @@ is-utf8@^0.2.0:
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
isarray@~1.0.0: isarray@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
isstream@~0.1.2: isstream@~0.1.2:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@ -851,7 +854,7 @@ minimatch@^3.0.4:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimist@1.x.x, minimist@^1.1.3, minimist@^1.2.5: minimist@^1.1.3, minimist@^1.2.5:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@ -876,9 +879,9 @@ moment-timezone@^0.5.31:
moment ">= 2.9.0" moment ">= 2.9.0"
"moment@>= 2.9.0": "moment@>= 2.9.0":
version "2.27.0" version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
ms@2.0.0: ms@2.0.0:
version "2.0.0" version "2.0.0"
@ -893,17 +896,6 @@ multistream@^2.1.1:
inherits "^2.0.1" inherits "^2.0.1"
readable-stream "^2.0.5" readable-stream "^2.0.5"
node-notifier@^5.4.0:
version "5.4.3"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50"
integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==
dependencies:
growly "^1.3.0"
is-wsl "^1.1.0"
semver "^5.5.0"
shellwords "^0.1.1"
which "^1.3.0"
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@ -1028,7 +1020,7 @@ pinkie@^2.0.0:
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
pkg-fetch@^2.6.7: pkg-fetch@^2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/pkg-fetch/-/pkg-fetch-2.6.9.tgz#c18c5fa9604c57a3df3d9630afb64e176bc8732d" resolved "https://registry.yarnpkg.com/pkg-fetch/-/pkg-fetch-2.6.9.tgz#c18c5fa9604c57a3df3d9630afb64e176bc8732d"
integrity sha512-EnVR8LRILXBvaNP+wJOSY02c3+qDDfyEyR+aqAHLhcc9PBnbxFT9UZ1+If49goPQzQPn26TzF//fc6KXZ0aXEg== integrity sha512-EnVR8LRILXBvaNP+wJOSY02c3+qDDfyEyR+aqAHLhcc9PBnbxFT9UZ1+If49goPQzQPn26TzF//fc6KXZ0aXEg==
@ -1046,9 +1038,9 @@ pkg-fetch@^2.6.7:
unique-temp-dir "^1.0.0" unique-temp-dir "^1.0.0"
pkg@4.4.x: pkg@4.4.x:
version "4.4.8" version "4.4.9"
resolved "https://registry.yarnpkg.com/pkg/-/pkg-4.4.8.tgz#145fb81f31eebfb90d2010dd2c4b663ca0db4009" resolved "https://registry.yarnpkg.com/pkg/-/pkg-4.4.9.tgz#be04f8d03795772b7c4394724ae7252d7c2a4519"
integrity sha512-Fqqv0iaX48U3CFZxd6Dq6JKe7BrAWbgRAqMJkz/m8W3H5cqJ6suvsUWe5AJPRlN/AhbBYXBJ0XG9QlYPTXcVFA== integrity sha512-FK4GqHtcCY2PPPVaKViU0NyRzpo6gCS7tPKN5b7AkElqjAOCH1bsRKgohEnxThr6DWfTGByGqba2YHGR/BqbmA==
dependencies: dependencies:
"@babel/parser" "^7.9.4" "@babel/parser" "^7.9.4"
"@babel/runtime" "^7.9.2" "@babel/runtime" "^7.9.2"
@ -1059,7 +1051,7 @@ pkg@4.4.x:
into-stream "^5.1.1" into-stream "^5.1.1"
minimist "^1.2.5" minimist "^1.2.5"
multistream "^2.1.1" multistream "^2.1.1"
pkg-fetch "^2.6.7" pkg-fetch "^2.6.9"
progress "^2.0.3" progress "^2.0.3"
resolve "^1.15.1" resolve "^1.15.1"
stream-meter "^1.0.4" stream-meter "^1.0.4"
@ -1124,10 +1116,10 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.1.4:
string_decoder "~1.1.1" string_decoder "~1.1.1"
util-deprecate "~1.0.1" util-deprecate "~1.0.1"
readdirp@~3.4.0: readdirp@~3.5.0:
version "3.4.0" version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
@ -1147,9 +1139,9 @@ redent@^1.0.0:
strip-indent "^1.0.1" strip-indent "^1.0.1"
regenerator-runtime@^0.13.4: regenerator-runtime@^0.13.4:
version "0.13.5" version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
repeating@^2.0.0: repeating@^2.0.0:
version "2.0.1" version "2.0.1"
@ -1192,10 +1184,11 @@ request@^2.88.0:
uuid "^3.3.2" uuid "^3.3.2"
resolve@^1.0.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.15.1: resolve@^1.0.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.15.1:
version "1.17.0" version "1.18.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130"
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==
dependencies: dependencies:
is-core-module "^2.0.0"
path-parse "^1.0.6" path-parse "^1.0.6"
reusify@^1.0.4: reusify@^1.0.4:
@ -1211,9 +1204,9 @@ rimraf@^2.6.1:
glob "^7.1.3" glob "^7.1.3"
run-parallel@^1.1.9: run-parallel@^1.1.9:
version "1.1.9" version "1.1.10"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==
safe-buffer@^5.0.1, safe-buffer@^5.1.2: safe-buffer@^5.0.1, safe-buffer@^5.1.2:
version "5.2.1" version "5.2.1"
@ -1230,7 +1223,7 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
"semver@2 || 3 || 4 || 5", semver@^5.5.0: "semver@2 || 3 || 4 || 5":
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@ -1249,11 +1242,6 @@ shelljs@^0.8.3:
interpret "^1.0.0" interpret "^1.0.0"
rechoir "^0.6.2" rechoir "^0.6.2"
shellwords@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
signal-exit@^3.0.0: signal-exit@^3.0.0:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@ -1299,9 +1287,9 @@ spdx-expression-parse@^3.0.0:
spdx-license-ids "^3.0.0" spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0: spdx-license-ids@^3.0.0:
version "3.0.5" version "3.0.6"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce"
integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==
sprintf-js@~1.0.2: sprintf-js@~1.0.2:
version "1.0.3" version "1.0.3"
@ -1362,9 +1350,9 @@ strip-json-comments@^2.0.0:
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@^7.1.0: supports-color@^7.1.0:
version "7.1.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies: dependencies:
has-flag "^4.0.0" has-flag "^4.0.0"
@ -1399,24 +1387,23 @@ trim-newlines@^1.0.0:
integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
ts-node-dev@^1.0.0-pre.40, ts-node-dev@^1.0.0-pre.44: ts-node-dev@^1.0.0-pre.40, ts-node-dev@^1.0.0-pre.44:
version "1.0.0-pre.49" version "1.0.0"
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0-pre.49.tgz#28836f73fe9513f339ccb1c0bebdb2e2e25c52f0" resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0.tgz#24a2270d225c29ce269de2a31f88b1b259fc84cb"
integrity sha512-iJd4QPPOaCAByl/WuEdmDX8xDR2GmoWYu6aKvGudGUcfP1sJRjpaHb7oFDuvBspQF1xhxUdbjfHuvEtZPwKZFQ== integrity sha512-leA/3TgGtnVU77fGngBwVZztqyDRXirytR7dMtMWZS5b2hGpLl+VDnB0F/gf3A+HEPSzS/KwxgXFP7/LtgX4MQ==
dependencies: dependencies:
chokidar "^3.4.0" chokidar "^3.4.0"
dateformat "~1.0.4-1.2.3" dateformat "~1.0.4-1.2.3"
dynamic-dedupe "^0.3.0" dynamic-dedupe "^0.3.0"
minimist "^1.2.5" minimist "^1.2.5"
mkdirp "^1.0.4" mkdirp "^1.0.4"
node-notifier "^5.4.0"
resolve "^1.0.0" resolve "^1.0.0"
rimraf "^2.6.1" rimraf "^2.6.1"
source-map-support "^0.5.12" source-map-support "^0.5.12"
tree-kill "^1.2.2" tree-kill "^1.2.2"
ts-node "^8.10.2" ts-node "^9.0.0"
tsconfig "^7.0.0" tsconfig "^7.0.0"
ts-node@^8.10.2, ts-node@^8.8.2: ts-node@^8.8.2:
version "8.10.2" version "8.10.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
@ -1427,6 +1414,17 @@ ts-node@^8.10.2, ts-node@^8.8.2:
source-map-support "^0.5.17" source-map-support "^0.5.17"
yn "3.1.1" yn "3.1.1"
ts-node@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3"
integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
tsconfig@^7.0.0: tsconfig@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7"
@ -1457,9 +1455,9 @@ type-check@~0.3.2:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
typescript@3.9.x, typescript@^3.8.3: typescript@3.9.x, typescript@^3.8.3:
version "3.9.5" version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
uhrwerk@1.x.x: uhrwerk@1.x.x:
version "1.0.2" version "1.0.2"
@ -1486,9 +1484,9 @@ universalify@^0.1.0:
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
uri-js@^4.2.2: uri-js@^4.2.2:
version "4.2.2" version "4.4.0"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602"
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==
dependencies: dependencies:
punycode "^2.1.0" punycode "^2.1.0"
@ -1519,13 +1517,6 @@ verror@1.10.0:
core-util-is "1.0.2" core-util-is "1.0.2"
extsprintf "^1.2.0" extsprintf "^1.2.0"
which@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
word-wrap@~1.2.3: word-wrap@~1.2.3:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"