mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2024-12-22 08:16:25 +00:00
rewrite with commander
This commit is contained in:
parent
e9a7e03af7
commit
60d7e0b561
6
.gitignore
vendored
6
.gitignore
vendored
@ -12,11 +12,9 @@ data
|
||||
restore
|
||||
docker
|
||||
Dockerfile
|
||||
build
|
||||
|
||||
# Config
|
||||
.autorestic.yml
|
||||
.autorestic.lock
|
||||
.docker.yml
|
||||
|
||||
# Docs
|
||||
.codedoc
|
||||
.docker.yml
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
@ -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)))
|
||||
}
|
||||
|
144
src/backup.ts
144
src/backup.ts
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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 + ' 🎉')
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
244
src/handlers.ts
244
src/handlers.ts
@ -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
13
src/handlers/backup.ts
Normal 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
9
src/handlers/check.ts
Normal 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
7
src/handlers/cron.ts
Normal 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
14
src/handlers/exec.ts
Normal 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
13
src/handlers/forget.ts
Normal 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
18
src/handlers/info.ts
Normal 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
50
src/handlers/install.ts
Normal 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
9
src/handlers/restore.ts
Normal 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
13
src/handlers/uninstall.ts
Normal 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
37
src/handlers/upgrade.ts
Normal 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
104
src/index.ts
Normal 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()
|
||||
}
|
32
src/info.ts
32
src/info.ts
@ -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
|
||||
|
10
src/lock.ts
10
src/lock.ts
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
101
src/restore.ts
101
src/restore.ts
@ -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 🎉')
|
||||
}
|
||||
|
||||
|
113
src/types.ts
113
src/types.ts
@ -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 }
|
||||
|
35
src/utils.ts
35
src/utils.ts
@ -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('')
|
||||
|
@ -3,6 +3,7 @@
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
|
233
yarn.lock
233
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user