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

6
.gitignore vendored
View File

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

View File

@ -2,10 +2,9 @@
"private": true,
"scripts": {
"build": "tsc",
"build:watch": "tsc -w",
"dev": "tsnd --no-notify --respawn ./src/autorestic.ts",
"move": "mv bin/autorestic-linux bin/autorestic_linux_x64 && mv bin/autorestic-macos bin/autorestic_macos_x64",
"bin": "yarn run build && pkg lib/autorestic.js --targets latest-macos-x64,latest-linux-x64 --out-path bin && yarn run move",
"dev": "tsc -w",
"move": "mv bin/index-linux bin/autorestic_linux_x64 && mv bin/index-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",
"docs:build": "codedoc install && codedoc build",
"docs:dev": "codedoc serve"
},
@ -25,7 +24,6 @@
"commander": "^6.2.0",
"cron-parser": "2.x.x",
"js-yaml": "3.x.x",
"minimist": "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,74 +1,66 @@
import { Writer } from 'clitastic'
import { config, VERBOSE } from './autorestic'
import { config, VERBOSE } from './'
import { Backend, Backends, Locations } from './types'
import { exec, pathRelativeToConfigFile, filterObjectByKey } from './utils'
const ALREADY_EXISTS = /(?=.*already)(?=.*config).*/
export const getPathFromBackend = (backend: Backend): string => {
switch (backend.type) {
case 'local':
return pathRelativeToConfigFile(backend.path)
case 'b2':
case 'azure':
case 'gs':
case 's3':
case 'sftp':
case 'rest':
return `${backend.type}:${backend.path}`
default:
throw new Error(`Unknown backend type.`)
}
switch (backend.type) {
case 'local':
return pathRelativeToConfigFile(backend.path)
case 'b2':
case 'azure':
case 'gs':
case 's3':
case 'sftp':
case 'rest':
return `${backend.type}:${backend.path}`
default:
throw new Error(`Unknown backend type.`)
}
}
export const getEnvFromBackend = (backend: Backend) => {
const { type, path, key, ...rest } = backend
return {
RESTIC_PASSWORD: key,
RESTIC_REPOSITORY: getPathFromBackend(backend),
...rest,
}
const { type, path, key, ...rest } = backend
return {
RESTIC_PASSWORD: key,
RESTIC_REPOSITORY: getPathFromBackend(backend),
...rest,
}
}
export const getBackendsFromLocations = (locations: Locations): string[] => {
const backends = new Set<string>()
for (const to of Object.values(locations).map(location => location.to))
Array.isArray(to) ? to.forEach(t => backends.add(t)) : backends.add(to)
return Array.from(backends)
const backends = new Set<string>()
for (const to of Object.values(locations).map((location) => location.to)) Array.isArray(to) ? to.forEach((t) => backends.add(t)) : backends.add(to)
return Array.from(backends)
}
export const checkAndConfigureBackend = (name: string, backend: Backend) => {
const writer = new Writer(name.blue + ' : ' + 'Configuring... ⏳')
try {
const env = getEnvFromBackend(backend)
const writer = new Writer(name.blue + ' : ' + 'Configuring... ⏳')
try {
const env = getEnvFromBackend(backend)
const { out, err } = exec('restic', ['init'], { env })
const { out, err } = exec('restic', ['init'], { env })
if (err.length > 0 && !ALREADY_EXISTS.test(err))
throw new Error(`Could not load the backend "${name}": ${err}`)
if (err.length > 0 && !ALREADY_EXISTS.test(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)
writer.done(name.blue + ' : ' + 'Done ✓'.green)
} catch (e) {
writer.done(name.blue + ' : ' + 'Error ⚠️ ' + e.message.red)
}
writer.done(name.blue + ' : ' + 'Done ✓'.green)
} catch (e) {
writer.done(name.blue + ' : ' + 'Error ⚠️ ' + e.message.red)
}
}
export const checkAndConfigureBackends = (backends?: Backends) => {
if (!backends)
backends = config.backends
if (!backends) backends = config.backends
console.log('\nConfiguring Backends'.grey.underline)
for (const [name, backend] of Object.entries(backends))
checkAndConfigureBackend(name, backend)
console.log('\nConfiguring Backends'.grey.underline)
for (const [name, backend] of Object.entries(backends)) checkAndConfigureBackend(name, backend)
}
export const checkAndConfigureBackendsForLocations = (locations: Locations) => {
checkAndConfigureBackends(
filterObjectByKey(config.backends, getBackendsFromLocations(locations)),
)
checkAndConfigureBackends(filterObjectByKey(config.backends, getBackendsFromLocations(locations)))
}

View File

@ -1,111 +1,103 @@
import { Writer } from 'clitastic'
import { mkdirSync } from 'fs'
import { config, VERBOSE } from './autorestic'
import { config, VERBOSE } from './'
import { getEnvFromBackend } from './backend'
import { LocationFromPrefixes } from './config'
import { Locations, Location, Backend } from './types'
import {
exec,
pathRelativeToConfigFile,
getFlagsFromLocation,
makeArrayIfIsNot,
execPlain,
MeasureDuration,
fill,
decodeLocationFromPrefix,
checkIfDockerVolumeExistsOrFail,
getPathFromVolume,
exec,
pathRelativeToConfigFile,
getFlagsFromLocation,
makeArrayIfIsNot,
execPlain,
MeasureDuration,
fill,
decodeLocationFromPrefix,
checkIfDockerVolumeExistsOrFail,
getPathFromVolume,
} from './utils'
export const backupFromFilesystem = (from: string, location: Location, backend: Backend, tags?: string[]) => {
const path = pathRelativeToConfigFile(from)
const path = pathRelativeToConfigFile(from)
const { out, err, status } = exec(
'restic',
['backup', '.', ...getFlagsFromLocation(location, 'backup')],
{ env: getEnvFromBackend(backend), cwd: path },
)
const { out, err, status } = exec('restic', ['backup', '.', ...getFlagsFromLocation(location, 'backup')], {
env: getEnvFromBackend(backend),
cwd: path,
})
if (VERBOSE) console.log(out, err)
if (status != 0 || err.length > 0)
throw new Error(err)
if (VERBOSE) console.log(out, err)
if (status != 0 || err.length > 0) throw new Error(err)
}
export const backupFromVolume = (volume: string, location: Location, backend: Backend) => {
const tmp = getPathFromVolume(volume)
try {
mkdirSync(tmp)
checkIfDockerVolumeExistsOrFail(volume)
const tmp = getPathFromVolume(volume)
try {
mkdirSync(tmp)
checkIfDockerVolumeExistsOrFail(volume)
// For incremental backups. Unfortunately due to how the docker mounts work the permissions get lost.
// execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine cp -aT /data /backup`)
execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine tar cf /backup/archive.tar -C /data .`)
// For incremental backups. Unfortunately due to how the docker mounts work the permissions get lost.
// execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine cp -aT /data /backup`)
execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine tar cf /backup/archive.tar -C /data .`)
backupFromFilesystem(tmp, location, backend)
} catch (e) {
throw e
} finally {
execPlain(`rm -rf ${tmp}`)
}
backupFromFilesystem(tmp, location, backend)
} catch (e) {
throw e
} finally {
execPlain(`rm -rf ${tmp}`)
}
}
export const backupSingle = (name: string, to: string, location: Location) => {
const delta = new MeasureDuration()
const writer = new Writer(name + to.blue + ' : ' + 'Backing up... ⏳')
const delta = new MeasureDuration()
const writer = new Writer(name + to.blue + ' : ' + 'Backing up... ⏳')
try {
const backend = config.backends[to]
const [type, value] = decodeLocationFromPrefix(location.from)
try {
const backend = config.backends[to]
const [type, value] = decodeLocationFromPrefix(location.from)
switch (type) {
switch (type) {
case LocationFromPrefixes.Filesystem:
backupFromFilesystem(value, location, backend)
break
case LocationFromPrefixes.Filesystem:
backupFromFilesystem(value, location, backend)
break
case LocationFromPrefixes.DockerVolume:
backupFromVolume(value, location, backend)
break
}
case LocationFromPrefixes.DockerVolume:
backupFromVolume(value, location, backend)
break
}
writer.done(`${name}${to.blue} : ${'Done ✓'.green} (${delta.finished(true)})`)
} catch (e) {
writer.done(`${name}${to.blue} : ${'Failed!'.red} (${delta.finished(true)}) ${e.message}`)
}
writer.done(`${name}${to.blue} : ${'Done ✓'.green} (${delta.finished(true)})`)
} catch (e) {
writer.done(`${name}${to.blue} : ${'Failed!'.red} (${delta.finished(true)}) ${e.message}`)
}
}
export const backupLocation = (name: string, location: Location) => {
const display = name.yellow + ' ▶ '
const filler = fill(name.length + 3)
let first = true
const display = name.yellow + ' ▶ '
const filler = fill(name.length + 3)
let first = true
if (location.hooks && location.hooks.before)
for (const command of makeArrayIfIsNot(location.hooks.before)) {
const cmd = execPlain(command, {})
console.log(cmd.out, cmd.err)
}
if (location.hooks && location.hooks.before)
for (const command of makeArrayIfIsNot(location.hooks.before)) {
const cmd = execPlain(command, {})
console.log(cmd.out, cmd.err)
}
for (const t of makeArrayIfIsNot(location.to)) {
backupSingle(first ? display : filler, t, location)
if (first) first = false
}
for (const t of makeArrayIfIsNot(location.to)) {
backupSingle(first ? display : filler, t, location)
if (first) first = false
}
if (location.hooks && location.hooks.after)
for (const command of makeArrayIfIsNot(location.hooks.after)) {
const cmd = execPlain(command)
console.log(cmd.out, cmd.err)
}
if (location.hooks && location.hooks.after)
for (const command of makeArrayIfIsNot(location.hooks.after)) {
const cmd = execPlain(command)
console.log(cmd.out, cmd.err)
}
}
export const backupAll = (locations?: Locations) => {
if (!locations)
locations = config.locations
if (!locations) locations = config.locations
console.log('\nBacking Up'.underline.grey)
for (const [name, location] of Object.entries(locations))
backupLocation(name, location)
console.log('\nBacking Up'.underline.grey)
for (const [name, location] of Object.entries(locations)) backupLocation(name, location)
}

View File

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

View File

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

View File

@ -1,75 +1,61 @@
import { Writer } from 'clitastic'
import { config, VERBOSE } from './autorestic'
import { config, VERBOSE } from './'
import { getEnvFromBackend } from './backend'
import { LocationFromPrefixes } from './config'
import { Locations, Location, Flags } from './types'
import {
exec,
pathRelativeToConfigFile,
getFlagsFromLocation,
makeArrayIfIsNot,
fill, decodeLocationFromPrefix, getPathFromVolume,
} from './utils'
import { exec, pathRelativeToConfigFile, getFlagsFromLocation, makeArrayIfIsNot, fill, decodeLocationFromPrefix, getPathFromVolume } from './utils'
export const forgetSingle = (name: string, to: string, location: Location, dryRun: boolean) => {
const base = name + to.blue + ' : '
const writer = new Writer(base + 'Removing old snapshots… ⏳')
const base = name + to.blue + ' : '
const writer = new Writer(base + 'Removing old snapshots… ⏳')
const backend = config.backends[to]
const flags = getFlagsFromLocation(location, 'forget')
const backend = config.backends[to]
const flags = getFlagsFromLocation(location, 'forget')
const [type, value] = decodeLocationFromPrefix(location.from)
let path: string
switch (type) {
case LocationFromPrefixes.Filesystem:
path = pathRelativeToConfigFile(value)
break
case LocationFromPrefixes.DockerVolume:
path = getPathFromVolume(value)
break
}
const [type, value] = decodeLocationFromPrefix(location.from)
let path: string
switch (type) {
case LocationFromPrefixes.Filesystem:
path = pathRelativeToConfigFile(value)
break
case LocationFromPrefixes.DockerVolume:
path = getPathFromVolume(value)
break
}
if (flags.length == 0) {
writer.done(base + 'Skipping, no policy declared')
return
}
if (dryRun) flags.push('--dry-run')
if (flags.length == 0) {
writer.done(base + 'Skipping, no policy declared')
return
}
if (dryRun) flags.push('--dry-run')
writer.replaceLn(base + 'Forgetting old snapshots… ⏳')
const cmd = exec(
'restic',
['forget', '--path', path, '--prune', ...flags],
{ env: getEnvFromBackend(backend) },
)
writer.replaceLn(base + 'Forgetting old snapshots… ⏳')
const cmd = exec('restic', ['forget', '--path', path, '--prune', ...flags], { env: getEnvFromBackend(backend) })
if (VERBOSE) console.log(cmd.out, cmd.err)
writer.done(base + 'Done ✓'.green)
if (VERBOSE) console.log(cmd.out, cmd.err)
writer.done(base + 'Done ✓'.green)
}
export const forgetLocation = (name: string, backup: Location, dryRun: boolean) => {
const display = name.yellow + ' ▶ '
const filler = fill(name.length + 3)
let first = true
const display = name.yellow + ' ▶ '
const filler = fill(name.length + 3)
let first = true
for (const t of makeArrayIfIsNot(backup.to)) {
const nameOrBlankSpaces: string = first ? display : filler
forgetSingle(nameOrBlankSpaces, t, backup, dryRun)
if (first) first = false
}
for (const t of makeArrayIfIsNot(backup.to)) {
const nameOrBlankSpaces: string = first ? display : filler
forgetSingle(nameOrBlankSpaces, t, backup, dryRun)
if (first) first = false
}
}
export const forgetAll = (backups?: Locations, flags?: Flags) => {
if (!backups) {
backups = config.locations
}
export const forgetAll = (backups?: Locations, dryRun = false) => {
if (!backups) {
backups = config.locations
}
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)
console.log('\nRemoving old snapshots according to policy'.underline.grey)
if (dryRun) console.log('Running in dry-run mode, not touching data\n'.yellow)
for (const [name, backup] of Object.entries(backups))
forgetLocation(name, backup, dryRun)
for (const [name, backup] of Object.entries(backups)) 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,26 +1,18 @@
import { config } from './autorestic'
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, '_') + '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:'],
))
}
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
export default showAll

View File

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

View File

@ -1,78 +1,63 @@
import { Writer } from 'clitastic'
import { resolve } from 'path'
import { config } from './autorestic'
import { config } from './'
import { getEnvFromBackend } from './backend'
import { LocationFromPrefixes } from './config'
import { Backend } from './types'
import {
checkIfDockerVolumeExistsOrFail,
decodeLocationFromPrefix,
exec,
execPlain,
getPathFromVolume,
} from './utils'
import { checkIfDockerVolumeExistsOrFail, decodeLocationFromPrefix, exec, execPlain, getPathFromVolume } from './utils'
export const restoreToFilesystem = (from: string, to: string, backend: Backend) => {
exec(
'restic',
['restore', 'latest', '--path', resolve(from), '--target', to],
{ env: getEnvFromBackend(backend) },
)
exec('restic', ['restore', 'latest', '--path', resolve(from), '--target', to], { env: getEnvFromBackend(backend) })
}
export const restoreToVolume = (volume: string, backend: Backend) => {
const tmp = getPathFromVolume(volume)
try {
restoreToFilesystem(tmp, tmp, backend)
try {
checkIfDockerVolumeExistsOrFail(volume)
} catch {
execPlain(`docker volume create ${volume}`)
}
const tmp = getPathFromVolume(volume)
try {
restoreToFilesystem(tmp, tmp, backend)
try {
checkIfDockerVolumeExistsOrFail(volume)
} catch {
execPlain(`docker volume create ${volume}`)
}
// For incremental backups. Unfortunately due to how the docker mounts work the permissions get lost.
// execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine cp -aT /backup /data`)
execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine tar xf /backup/archive.tar -C /data`)
} finally {
execPlain(`rm -rf ${tmp}`)
}
// For incremental backups. Unfortunately due to how the docker mounts work the permissions get lost.
// execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine cp -aT /backup /data`)
execPlain(`docker run --rm -v ${volume}:/data -v ${tmp}:/backup alpine tar xf /backup/archive.tar -C /data`)
} finally {
execPlain(`rm -rf ${tmp}`)
}
}
export const restoreSingle = (locationName: string, from: string, to?: string) => {
const location = config.locations[locationName]
const location = config.locations[locationName]
const baseText = locationName.green + '\t\t'
const w = new Writer(baseText + `Restoring...`)
const baseText = locationName.green + '\t\t'
const w = new Writer(baseText + `Restoring...`)
let backendName: string = Array.isArray(location.to) ? location.to[0] : location.to
if (from) {
if (!location.to.includes(from)) {
w.done(baseText + `Backend ${from} is not a valid location for ${locationName}`.red)
return
}
backendName = from
w.replaceLn(baseText + `Restoring from ${backendName.blue}...`)
} else if (Array.isArray(location.to) && location.to.length > 1) {
w.replaceLn(baseText + `Restoring from ${backendName.blue}...\tTo select a specific backend pass the ${'--from'.blue} flag`)
}
const backend = config.backends[backendName]
let backendName: string = Array.isArray(location.to) ? location.to[0] : location.to
if (from) {
if (!location.to.includes(from)) {
w.done(baseText + `Backend ${from} is not a valid location for ${locationName}`.red)
return
}
backendName = from
w.replaceLn(baseText + `Restoring from ${backendName.blue}...`)
} else if (Array.isArray(location.to) && location.to.length > 1) {
w.replaceLn(baseText + `Restoring from ${backendName.blue}...\tTo select a specific backend pass the ${'--from'.blue} flag`)
}
const backend = config.backends[backendName]
const [type, value] = decodeLocationFromPrefix(location.from)
switch (type) {
const [type, value] = decodeLocationFromPrefix(location.from)
switch (type) {
case LocationFromPrefixes.Filesystem:
if (!to) throw new Error(`You need to specify the restore path with --to`.red)
restoreToFilesystem(value, to, backend)
break
case LocationFromPrefixes.Filesystem:
if (!to) throw new Error(`You need to specify the restore path with --to`.red)
restoreToFilesystem(value, to, backend)
break
case LocationFromPrefixes.DockerVolume:
restoreToVolume(value, backend)
break
}
w.done(locationName.green + '\t\tDone 🎉')
case LocationFromPrefixes.DockerVolume:
restoreToVolume(value, backend)
break
}
w.done(locationName.green + '\t\tDone 🎉')
}

View File

@ -3,84 +3,77 @@ export type StringOrArray = string | string[]
// BACKENDS
type BackendLocal = {
type: 'local'
key: string
path: string
type: 'local'
key: string
path: string
}
type BackendSFTP = {
type: 'sftp'
key: string
path: string
password?: string
type: 'sftp'
key: string
path: string
password?: string
}
type BackendREST = {
type: 'rest'
key: string
path: string
user?: string
password?: string
type: 'rest'
key: string
path: string
user?: string
password?: string
}
type BackendS3 = {
type: 's3'
key: string
path: string
aws_access_key_id: string
aws_secret_access_key: string
type: 's3'
key: string
path: string
aws_access_key_id: string
aws_secret_access_key: string
}
type BackendB2 = {
type: 'b2'
key: string
path: string
b2_account_id: string
b2_account_key: string
type: 'b2'
key: string
path: string
b2_account_id: string
b2_account_key: string
}
type BackendAzure = {
type: 'azure'
key: string
path: string
azure_account_name: string
azure_account_key: string
type: 'azure'
key: string
path: string
azure_account_name: string
azure_account_key: string
}
type BackendGS = {
type: 'gs'
key: string
path: string
google_project_id: string
google_application_credentials: string
type: 'gs'
key: string
path: string
google_project_id: string
google_application_credentials: string
}
export type Backend =
| BackendAzure
| BackendB2
| BackendGS
| BackendLocal
| BackendREST
| BackendS3
| BackendSFTP
export type Backend = BackendAzure | BackendB2 | BackendGS | BackendLocal | BackendREST | BackendS3 | BackendSFTP
export type Backends = { [name: string]: Backend }
// LOCATIONS
export type Location = {
from: string
to: StringOrArray
cron?: string
hooks?: {
before?: StringOrArray
after?: StringOrArray
}
options?: {
[key: string]: {
[key: string]: StringOrArray
}
}
from: string
to: StringOrArray
cron?: string
hooks?: {
before?: StringOrArray
after?: StringOrArray
}
options?: {
[key: string]: {
[key: string]: StringOrArray
}
}
}
export type Locations = { [name: string]: Location }
@ -88,17 +81,17 @@ export type Locations = { [name: string]: Location }
// OTHER
export type Config = {
locations: Locations
backends: Backends
locations: Locations
backends: Backends
}
export type Lockfile = {
running: boolean
crons: {
[name: string]: {
lastRun: number
}
}
running: boolean
crons: {
[name: string]: {
lastRun: number
}
}
}
export type Flags = { [arg: string]: any }

View File

@ -1,13 +1,15 @@
import axios from 'axios'
import { spawnSync, SpawnSyncOptions } from 'child_process'
import { createHash, randomBytes } from 'crypto'
import { createWriteStream, renameSync, unlinkSync } from 'fs'
import { homedir, tmpdir } from 'os'
import { dirname, isAbsolute, join, resolve } from 'path'
import axios from 'axios'
import { Duration, Humanizer } from 'uhrwerk'
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 = {}) => {
const { stdout, stderr, status } = spawnSync(command, args, {
@ -106,6 +108,35 @@ export const getFlagsFromLocation = (location: Location, command?: string): stri
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 fill = (length: number, filler = ' '): string => new Array(length).fill(filler).join('')

View File

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

233
yarn.lock
View File

@ -3,21 +3,21 @@
"@babel/parser@^7.9.4":
version "7.10.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315"
integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA==
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0"
integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==
"@babel/runtime@^7.9.2":
version "7.10.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364"
integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies:
regenerator-runtime "^0.13.4"
"@codedoc/cli@0.2.x":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@codedoc/cli/-/cli-0.2.1.tgz#03daa1b1e0cedff25c0b5dc29ab8c823b2dd28ef"
integrity sha512-kdxbcKaxM3AUEoD70trMrUFitdbJ8mTS7u5ojJfzZ2uQtTIjO1D0XJLydsPyPXqLQf/TyOZJPEm03Q3d9Smt0A==
version "0.2.6"
resolved "https://registry.yarnpkg.com/@codedoc/cli/-/cli-0.2.6.tgz#5e6e981f8eafefcab2b8c81ee6c14815baf2732c"
integrity sha512-zn92PvMamXCteZudz1i606qa4bPXcXveNgb6oQTAiW7Lq+bRMZHUyKvA3Q3jVwlQtV7SidJg3jA4RwkMllktog==
dependencies:
chalk "^4.0.0"
shelljs "^0.8.3"
@ -46,11 +46,6 @@
"@nodelib/fs.scandir" "2.1.3"
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":
version "3.12.5"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb"
@ -62,9 +57,9 @@
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
"@types/node@14.x.x":
version "14.0.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==
version "14.14.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.6.tgz#146d3da57b3c636cc0d1769396ce1cfa8991147f"
integrity sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==
"@types/strip-bom@^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"
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
ajv@^6.5.5:
version "6.12.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
ajv@^6.12.3:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
@ -87,11 +82,10 @@ ajv@^6.5.5:
uri-js "^4.2.2"
ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
anymatch@~3.1.1:
@ -147,9 +141,9 @@ aws-sign2@~0.7.0:
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
version "1.11.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
axios@0.19.x:
version "0.19.2"
@ -235,9 +229,9 @@ chalk@^4.0.0:
supports-color "^7.1.0"
chokidar@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8"
integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==
version "3.4.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
@ -245,7 +239,7 @@ chokidar@^3.4.0:
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.4.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.1.2"
@ -280,6 +274,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
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:
version "0.0.1"
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=
cron-parser@2.x.x:
version "2.15.0"
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.15.0.tgz#04803cd51d8efcfcc6f83ac08e60f3f8c40c7ec5"
integrity sha512-rMFkrQw8+oG5OuwjiXesup4KeIlEG/IU82YtG4xyAHbO5jhKmYaHPp/ZNhq9+7TjSJ65E3zV3kQPUbmXSff2/g==
version "2.17.0"
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.17.0.tgz#5707421a7e0a73ee74675d1c032a2f14123f2cf8"
integrity sha512-oTmzVEwlurRe51HqTm4afshVr8Rkxy9kFiWxh5e6SmrY2o9NDYU4S6SduanBZYXLgkLy0skA98y7/tztW/DmjQ==
dependencies:
is-nan "^1.3.0"
moment-timezone "^0.5.31"
@ -458,9 +457,9 @@ fast-levenshtein@~2.0.6:
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fastq@^1.6.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481"
integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==
version "1.9.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947"
integrity sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==
dependencies:
reusify "^1.0.4"
@ -527,6 +526,11 @@ fsevents@~2.1.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
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:
version "4.0.1"
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"
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:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
version "5.1.5"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
dependencies:
ajv "^6.5.5"
ajv "^6.12.3"
har-schema "^2.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"
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:
version "2.8.8"
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:
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:
version "2.1.1"
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"
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
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:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@ -851,7 +854,7 @@ minimatch@^3.0.4:
dependencies:
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"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@ -876,9 +879,9 @@ moment-timezone@^0.5.31:
moment ">= 2.9.0"
"moment@>= 2.9.0":
version "2.27.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
ms@2.0.0:
version "2.0.0"
@ -893,17 +896,6 @@ multistream@^2.1.1:
inherits "^2.0.1"
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:
version "2.5.0"
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"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
pkg-fetch@^2.6.7:
pkg-fetch@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/pkg-fetch/-/pkg-fetch-2.6.9.tgz#c18c5fa9604c57a3df3d9630afb64e176bc8732d"
integrity sha512-EnVR8LRILXBvaNP+wJOSY02c3+qDDfyEyR+aqAHLhcc9PBnbxFT9UZ1+If49goPQzQPn26TzF//fc6KXZ0aXEg==
@ -1046,9 +1038,9 @@ pkg-fetch@^2.6.7:
unique-temp-dir "^1.0.0"
pkg@4.4.x:
version "4.4.8"
resolved "https://registry.yarnpkg.com/pkg/-/pkg-4.4.8.tgz#145fb81f31eebfb90d2010dd2c4b663ca0db4009"
integrity sha512-Fqqv0iaX48U3CFZxd6Dq6JKe7BrAWbgRAqMJkz/m8W3H5cqJ6suvsUWe5AJPRlN/AhbBYXBJ0XG9QlYPTXcVFA==
version "4.4.9"
resolved "https://registry.yarnpkg.com/pkg/-/pkg-4.4.9.tgz#be04f8d03795772b7c4394724ae7252d7c2a4519"
integrity sha512-FK4GqHtcCY2PPPVaKViU0NyRzpo6gCS7tPKN5b7AkElqjAOCH1bsRKgohEnxThr6DWfTGByGqba2YHGR/BqbmA==
dependencies:
"@babel/parser" "^7.9.4"
"@babel/runtime" "^7.9.2"
@ -1059,7 +1051,7 @@ pkg@4.4.x:
into-stream "^5.1.1"
minimist "^1.2.5"
multistream "^2.1.1"
pkg-fetch "^2.6.7"
pkg-fetch "^2.6.9"
progress "^2.0.3"
resolve "^1.15.1"
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"
util-deprecate "~1.0.1"
readdirp@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada"
integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
@ -1147,9 +1139,9 @@ redent@^1.0.0:
strip-indent "^1.0.1"
regenerator-runtime@^0.13.4:
version "0.13.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
repeating@^2.0.0:
version "2.0.1"
@ -1192,10 +1184,11 @@ request@^2.88.0:
uuid "^3.3.2"
resolve@^1.0.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.15.1:
version "1.17.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
version "1.18.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130"
integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==
dependencies:
is-core-module "^2.0.0"
path-parse "^1.0.6"
reusify@^1.0.4:
@ -1211,9 +1204,9 @@ rimraf@^2.6.1:
glob "^7.1.3"
run-parallel@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
version "1.1.10"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
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"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
"semver@2 || 3 || 4 || 5", semver@^5.5.0:
"semver@2 || 3 || 4 || 5":
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@ -1249,11 +1242,6 @@ shelljs@^0.8.3:
interpret "^1.0.0"
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:
version "3.0.3"
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:
version "3.0.5"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
version "3.0.6"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce"
integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==
sprintf-js@~1.0.2:
version "1.0.3"
@ -1362,9 +1350,9 @@ strip-json-comments@^2.0.0:
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
@ -1399,24 +1387,23 @@ trim-newlines@^1.0.0:
integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
ts-node-dev@^1.0.0-pre.40, ts-node-dev@^1.0.0-pre.44:
version "1.0.0-pre.49"
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0-pre.49.tgz#28836f73fe9513f339ccb1c0bebdb2e2e25c52f0"
integrity sha512-iJd4QPPOaCAByl/WuEdmDX8xDR2GmoWYu6aKvGudGUcfP1sJRjpaHb7oFDuvBspQF1xhxUdbjfHuvEtZPwKZFQ==
version "1.0.0"
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0.tgz#24a2270d225c29ce269de2a31f88b1b259fc84cb"
integrity sha512-leA/3TgGtnVU77fGngBwVZztqyDRXirytR7dMtMWZS5b2hGpLl+VDnB0F/gf3A+HEPSzS/KwxgXFP7/LtgX4MQ==
dependencies:
chokidar "^3.4.0"
dateformat "~1.0.4-1.2.3"
dynamic-dedupe "^0.3.0"
minimist "^1.2.5"
mkdirp "^1.0.4"
node-notifier "^5.4.0"
resolve "^1.0.0"
rimraf "^2.6.1"
source-map-support "^0.5.12"
tree-kill "^1.2.2"
ts-node "^8.10.2"
ts-node "^9.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"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
@ -1427,6 +1414,17 @@ ts-node@^8.10.2, ts-node@^8.8.2:
source-map-support "^0.5.17"
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:
version "7.0.0"
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"
typescript@3.9.x, typescript@^3.8.3:
version "3.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
uhrwerk@1.x.x:
version "1.0.2"
@ -1486,9 +1484,9 @@ universalify@^0.1.0:
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
version "4.4.0"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602"
integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==
dependencies:
punycode "^2.1.0"
@ -1519,13 +1517,6 @@ verror@1.10.0:
core-util-is "1.0.2"
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:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"