FTP support

This commit is contained in:
Ben Allfree 2022-12-30 16:20:43 -08:00
parent ea432684d7
commit 21cf9b2773
27 changed files with 684 additions and 142 deletions

View File

@ -3,6 +3,7 @@ import { mkSingleton } from './mkSingleton'
export type Config = {
raw?: boolean
debug: boolean
errorTrace?: boolean
pfx?: string[]
}
@ -16,10 +17,11 @@ export const createLogger = (config: Partial<Config>) => {
const _config: Required<Config> = {
raw: false,
debug: true,
errorTrace: true,
pfx: [''],
...config,
}
const { pfx } = _config
const { pfx, errorTrace } = _config
const _pfx = (s: string) =>
[s, ...pfx]
@ -54,8 +56,13 @@ export const createLogger = (config: Partial<Config>) => {
_log(true, _pfx(`INFO`), ...args)
}
const trace = (...args: any[]) => {
_log(true, _pfx(`TRACE`), ...args)
}
const error = (...args: any[]) => {
_log(true, _pfx(`ERROR`), ...args)
if (!errorTrace) return
console.error(`========================`)
;[..._buf.slice(_curIdx, MAX_BUF), ..._buf.slice(0, _curIdx)].forEach(
(args) => {
@ -65,13 +72,16 @@ export const createLogger = (config: Partial<Config>) => {
console.error(`========================`)
}
const create = (s: string) =>
const create = (s: string, configOverride?: Partial<Config>) =>
createLogger({
..._config,
...configOverride,
pfx: [..._config.pfx, s],
})
return { raw, dbg, warn, info, error, create }
const child = (extra: any) => create(JSON.stringify(extra))
return { raw, dbg, warn, info, error, create, child, trace, debug: dbg }
}
export const logger = mkSingleton((config: Config) => createLogger(config))

View File

@ -1,50 +0,0 @@
import { ClientResponseError } from 'pocketbase'
import { logger } from './Logger'
export type PromiseHelperConfig = {}
export type PromiseHelper = ReturnType<typeof createPromiseHelper>
export const createPromiseHelper = (config: PromiseHelperConfig) => {
let c = 0
const safeCatch = <TIn extends any[], TOut>(
name: string,
cb: (...args: TIn) => Promise<TOut>
) => {
return (...args: TIn) => {
const _c = c++
const uuid = `${name}:${_c}`
const pfx = `safeCatch:${uuid}`
const { raw, error, warn } = logger().create(pfx)
raw(`args`, args)
const tid = setTimeout(() => {
warn(`timeout waiting for ${pfx}`)
}, 100)
return cb(...args)
.then((res) => {
raw(`finished`)
clearTimeout(tid)
return res
})
.catch((e: any) => {
if (e instanceof ClientResponseError) {
if (e.status === 400) {
error(
`PocketBase API error: It looks like you don't have permission to make this request.`
)
} else if (e.status === 0) {
warn(`Client request aborted (duplicate)`)
} else {
error(`Unknown PocketBase API error`, JSON.stringify(e))
}
} else {
error(JSON.stringify(e, null, 2))
}
throw e
})
}
}
return { safeCatch }
}

View File

@ -3,6 +3,6 @@ export * from './CleanupManager'
export * from './Logger'
export * from './mkSingleton'
export * from './pocketbase-client-helpers'
export * from './PromiseHelper'
export * from './safeCatch'
export * from './schema'
export * from './TimerManager'

View File

@ -6,8 +6,8 @@ import {
UnsubscribeFunc,
} from 'pocketbase'
import type { JsonObject } from 'type-fest'
import { Logger } from '../Logger'
import { PromiseHelper } from '../PromiseHelper'
import { logger } from '../Logger'
import { safeCatch } from '../safeCatch'
import { BaseFields, RpcCommands, UserId } from '../schema'
import type { WatchHelper } from './WatchHelper'
@ -17,8 +17,6 @@ export const newId = () => nanoid(15)
export type RpcHelperConfig = {
client: pocketbaseEs
watchHelper: WatchHelper
promiseHelper: PromiseHelper
logger: Logger
}
export type RpcHelper = ReturnType<typeof createRpcHelper>
@ -57,8 +55,6 @@ export const createRpcHelper = (config: RpcHelperConfig) => {
const {
client,
watchHelper: { watchById },
promiseHelper: { safeCatch },
logger: { dbg },
} = config
const mkRpc = <TPayload extends JsonObject, TResult extends JsonObject>(
@ -66,6 +62,8 @@ export const createRpcHelper = (config: RpcHelperConfig) => {
) => {
type ConcreteRpcRecord = RpcFields<TPayload, TResult>
const { dbg } = logger().create('mkRpc')
return safeCatch(
cmd,
async (

View File

@ -1,24 +1,18 @@
import type { BaseFields, RecordId } from '@pockethost/common'
import { logger, safeCatch } from '@pockethost/common'
import type pocketbaseEs from 'pocketbase'
import type { RecordSubscription, UnsubscribeFunc } from 'pocketbase'
import { Logger } from '../Logger'
import { PromiseHelper } from '../PromiseHelper'
export type WatchHelperConfig = {
client: pocketbaseEs
promiseHelper: PromiseHelper
logger: Logger
}
export type WatchHelper = ReturnType<typeof createWatchHelper>
export const createWatchHelper = (config: WatchHelperConfig) => {
const {
client,
promiseHelper: { safeCatch },
logger: { dbg },
} = config
const { client } = config
const { dbg } = logger().create(`watchHelper`)
const watchById = safeCatch(
`watchById`,
async <TRec>(

View File

@ -0,0 +1,42 @@
import { ClientResponseError } from 'pocketbase'
import { logger } from './Logger'
let c = 0
export const safeCatch = <TIn extends any[], TOut>(
name: string,
cb: (...args: TIn) => Promise<TOut>
) => {
return (...args: TIn) => {
const _c = c++
const uuid = `${name}:${_c}`
const pfx = `safeCatch:${uuid}`
const { raw, error, warn } = logger().create(pfx)
raw(`args`, args)
const tid = setTimeout(() => {
warn(`timeout waiting for ${pfx}`)
}, 100)
return cb(...args)
.then((res) => {
raw(`finished`)
clearTimeout(tid)
return res
})
.catch((e: any) => {
if (e instanceof ClientResponseError) {
if (e.status === 400) {
warn(
`PocketBase API error: It looks like you don't have permission to make this request.`
)
} else if (e.status === 0) {
warn(`Client request aborted (duplicate)`)
} else {
warn(`Unknown PocketBase API error`, JSON.stringify(e))
}
} else {
warn(JSON.stringify(e, null, 2))
}
throw e
})
}
}

View File

@ -23,6 +23,7 @@
"date-fns": "^2.29.3",
"event-source-polyfill": "^1.0.31",
"eventsource": "^2.0.2",
"ftp-srv": "^4.6.2",
"get-port": "^6.1.2",
"http-proxy": "^1.18.1",
"knex": "^2.3.0",

View File

@ -51,3 +51,8 @@ export const PH_BIN_CACHE = env(
`PH_BIN_CACHE`,
join(__dirname, `../../../.pbincache`)
)
export const PH_FTP_PORT = envi('PH_FTP_PORT', 21)
export const SSL_KEY = env('SSL_KEY')
export const SSL_CERT = env('SSL_CERT')

View File

@ -65,6 +65,10 @@ export const createInstanceMixin = (context: MixinContext) => {
}
)
const getInstances = safeCatch(`getInstances`, async () => {
return client.collection('instances').getFullList<InstanceFields>()
})
const updateInstances = safeCatch(
'updateInstances',
async (cb: (rec: InstanceFields) => Partial<InstanceFields>) => {
@ -110,6 +114,7 @@ export const createInstanceMixin = (context: MixinContext) => {
)
return {
getInstances,
updateInstance,
updateInstanceStatus,
getInstanceBySubdomain,

View File

@ -1,11 +1,18 @@
import { logger } from '@pockethost/common'
import { logger, mkSingleton } from '@pockethost/common'
import { Knex } from 'knex'
import {
Collection,
default as PocketBase,
default as pocketbaseEs,
} from 'pocketbase'
import { DAEMON_PB_DATA_DIR, PUBLIC_PB_SUBDOMAIN } from '../constants'
import {
DAEMON_PB_DATA_DIR,
DAEMON_PB_PASSWORD,
DAEMON_PB_USERNAME,
PUBLIC_PB_DOMAIN,
PUBLIC_PB_PROTOCOL,
PUBLIC_PB_SUBDOMAIN,
} from '../constants'
import { Collection_Serialized } from '../migrate/schema'
import { safeCatch } from '../util/promiseHelper'
import { createBackupMixin } from './BackupMixin'
@ -49,6 +56,7 @@ export const createPbClient = (url: string) => {
const api = {
client,
url,
knex: rawDb,
adminAuthViaEmail,
applySchema,
@ -60,3 +68,18 @@ export const createPbClient = (url: string) => {
return api
}
export const clientService = mkSingleton(async (url: string) => {
const { dbg, error } = logger().create(`client singleton`)
const client = createPbClient(url)
try {
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
dbg(`Logged in`)
} catch (e) {
error(
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
)
error(`***WARNING*** LOG IN MANUALLY, ADJUST .env, AND RESTART DOCKER`)
}
return client
})

View File

@ -1,15 +1,8 @@
import { logger } from '@pockethost/common'
import {
DAEMON_PB_PASSWORD,
DAEMON_PB_USERNAME,
DEBUG,
PH_BIN_CACHE,
PUBLIC_PB_DOMAIN,
PUBLIC_PB_PROTOCOL,
PUBLIC_PB_SUBDOMAIN,
} from './constants'
import { createPbClient } from './db/PbClient'
import { DEBUG, PH_BIN_CACHE, PUBLIC_PB_SUBDOMAIN } from './constants'
import { clientService } from './db/PbClient'
import { createBackupService } from './services/BackupService'
import { ftpService } from './services/FtpService'
import { createInstanceService } from './services/InstanceService'
import { pocketbase } from './services/PocketBaseService'
import { createProxyService } from './services/ProxyService'
@ -21,6 +14,8 @@ logger({ debug: DEBUG })
global.EventSource = require('eventsource')
;(async () => {
const { dbg, error, info } = logger().create(`server.ts`)
info(`Starting`)
const pbService = await pocketbase({
cachePath: PH_BIN_CACHE,
checkIntervalMs: 1000 * 5 * 60,
@ -37,17 +32,9 @@ global.EventSource = require('eventsource')
/**
* Launch services
*/
const client = createPbClient(url)
try {
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
dbg(`Logged in`)
} catch (e) {
error(
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
)
error(`***WARNING*** LOG IN MANUALLY, ADJUST .env, AND RESTART DOCKER`)
}
const client = await clientService(url)
ftpService({})
const rpcService = await createRpcService({ client })
const instanceService = await createInstanceService({ client, rpcService })
const proxyService = await createProxyService({
@ -56,12 +43,20 @@ global.EventSource = require('eventsource')
})
const backupService = await createBackupService(client, rpcService)
process.once('SIGUSR2', async () => {
info(`SIGUSR2 detected`)
info(`Hooking into process exit event`)
const shutdown = (signal: NodeJS.Signals) => {
info(`Got signal ${signal}`)
info(`Shutting down`)
ftpService().shutdown()
proxyService.shutdown()
instanceService.shutdown()
rpcService.shutdown()
backupService.shutdown()
pbService.shutdown()
})
}
process.on('SIGTERM', shutdown)
process.on('SIGINT', shutdown)
process.on('SIGHUP', shutdown)
})()

View File

@ -0,0 +1,312 @@
import { Logger, logger, mkSingleton } from '@pockethost/common'
import { existsSync, mkdirSync, readFileSync } from 'fs'
import { FileStat, FileSystem, FtpConnection, FtpSrv } from 'ftp-srv'
import { join } from 'path'
import { Readable } from 'stream'
import {
DAEMON_PB_DATA_DIR,
PH_FTP_PORT,
SSL_CERT,
SSL_KEY,
} from '../constants'
import {
clientService,
createPbClient,
PocketbaseClientApi,
} from '../db/PbClient'
export type FtpConfig = {}
export enum FolderNames {
PbData = 'pb_data',
PbStatic = 'pb_static',
PbWorkers = 'workers',
PbBackup = 'backup',
}
const README_CONTENTS: { [_ in FolderNames]: string } = {
[FolderNames.PbBackup]: `This directory contains tgz backups of your data. PocketHost creates these automatically, or you can create them manually. For more information, see https://pockethost.io/docs/backups`,
[FolderNames.PbData]: `This directory contains your PocketBase data. For more information, see https://pockethost.io/docs/data`,
[FolderNames.PbStatic]: `This directory contains static files such as your web frontend. PocketHost will serve these when your instance URL receives a request. For more information, see https://pockethost.io/docs/static `,
[FolderNames.PbWorkers]: `This directory contains your Deno workers. For more information, see https://pockethost.io/docs/workers`,
}
const README_NAME = 'readme.md'
const FOLDER_NAMES: FolderNames[] = [
FolderNames.PbBackup,
FolderNames.PbData,
FolderNames.PbStatic,
FolderNames.PbWorkers,
]
function isFolder(name: string): name is FolderNames {
return FOLDER_NAMES.includes(name as FolderNames)
}
class PhFs extends FileSystem {
private log: Logger
private client: PocketbaseClientApi
constructor(connection: FtpConnection, client: PocketbaseClientApi) {
super(connection, { root: '/', cwd: '/' })
this.client = client
this.log = logger().create(`PhFs`)
}
async chdir(path?: string | undefined): Promise<string> {
this.log.dbg(`chdir`, path)
if (!path) {
throw new Error(`Expected path`)
}
const _path = path.startsWith('/') ? path : join(this.cwd, path)
const [empty, subdomain] = _path.split('/')
this.log.dbg({ _path, subdomain })
if (subdomain) {
const instance = await this.client.getInstanceBySubdomain(subdomain)
if (!instance) {
throw new Error(`Subdomain not found`)
}
}
this.cwd = _path
return path
}
async list(path?: string | undefined): Promise<FileStat[]> {
this.log.dbg(`list ${path}`, this.cwd)
if (!path) {
throw new Error(`Expected path`)
}
const _path = path.startsWith('/') ? path : join(this.cwd, path)
const [empty, subdomain, folderName] = _path.split('/')
this.log.dbg({ _path, subdomain, folderName })
if (subdomain === '') {
const instances = await this.client.getInstances()
return instances.map((i) => {
return {
isDirectory: () => true,
mode: 0o755,
size: 0,
mtime: Date.parse(i.updated),
name: i.subdomain,
}
})
}
if (subdomain) {
const [instance, user] = await this.client.getInstanceBySubdomain(
subdomain
)
if (!instance) {
throw new Error(`Expected instance here`)
}
if (!folderName) {
return FOLDER_NAMES.map((name) => ({
isDirectory: () => true,
mode: 0o755,
size: 0,
mtime: Date.parse(instance.updated),
name: name,
}))
}
if (isFolder(folderName)) {
const dir = join(DAEMON_PB_DATA_DIR, instance.id, folderName)
this.log.dbg({ dir, exists: existsSync(dir) })
return [
{
isDirectory: () => false,
mode: 0o444,
size: README_CONTENTS[folderName].length,
mtime: Date.parse(instance.updated),
name: README_NAME,
},
...(existsSync(dir)
? await super.list(
join(DAEMON_PB_DATA_DIR, instance.id, folderName)
)
: []),
]
}
}
throw new Error(`Error parsing ${_path}`)
}
async get(fileName: string): Promise<FileStat> {
const _path = fileName.startsWith('/') ? fileName : join(this.cwd, fileName)
const [empty, subdomain, folderName, ...fNames] = _path.split('/')
const fName = fNames.join('/')
this.log.dbg(`get`, { _path, subdomain, folderName, fName, fileName })
if (!subdomain) {
return {
isDirectory: () => true,
mode: 0o755,
size: 0,
mtime: +new Date(),
name: '/',
}
}
const [instance, user] = await this.client.getInstanceBySubdomain(subdomain)
if (!instance) {
throw new Error(`Expected instance here`)
}
if (!folderName) {
return {
isDirectory: () => true,
mode: 0o755,
size: 0,
mtime: Date.parse(instance.updated),
name: subdomain,
}
}
if (!isFolder(folderName)) {
throw new Error(`Invalid folder name`)
}
if (!fName) {
return {
isDirectory: () => true,
mode: 0o755,
size: 0,
mtime: Date.parse(instance.updated),
name: folderName,
}
}
return super.get(_path)
}
async write(
fileName: string,
options?: { append?: boolean | undefined; start?: any } | undefined
) {
const _path = fileName.startsWith('/') ? fileName : join(this.cwd, fileName)
const [empty, subdomain, folderName, ...fNames] = _path.split('/')
const fName = fNames.join('/')
this.log.dbg(`read`, { subdomain, folderName, fName })
if (!subdomain) {
throw new Error(`Subdomain not found`)
}
if (!folderName) {
throw new Error(`Folder name expected here`)
}
const [instance, user] = await this.client.getInstanceBySubdomain(subdomain)
if (!instance) {
throw new Error(`Expected instance here`)
}
if (!isFolder(folderName)) {
throw new Error(`Invalid folder name ${folderName}`)
}
if (fName === README_NAME) {
throw new Error(`Can't overwrite ${fName}`)
}
const dir = join(DAEMON_PB_DATA_DIR, instance.id, folderName)
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true })
}
return super.write(join(dir, fName), options)
}
async read(
fileName: string,
options: { start?: any } | undefined
): Promise<any> {
const _path = fileName.startsWith('/') ? fileName : join(this.cwd, fileName)
const [empty, subdomain, folderName, ...fNames] = _path.split('/')
const fName = fNames.join('/')
this.log.dbg(`read`, { subdomain, folderName, fName })
if (!subdomain) {
throw new Error(`Subdomain not found`)
}
if (!folderName) {
throw new Error(`Folder name expected here`)
}
const [instance, user] = await this.client.getInstanceBySubdomain(subdomain)
if (!instance) {
throw new Error(`Expected instance here`)
}
if (!isFolder(folderName)) {
throw new Error(`Invalid folder name ${folderName}`)
}
if (fName === README_NAME) {
return Readable.from([README_CONTENTS[folderName]])
}
const realPath = join(
`/`,
DAEMON_PB_DATA_DIR,
instance.id,
folderName,
fName
)
this.log.dbg({ realPath })
return super.read(realPath, options)
}
async delete(path: string): Promise<any> {
throw new Error(`delete ${path}`)
}
async mkdir(path: string): Promise<any> {
throw new Error(`mkdir ${path}`)
}
async rename(from: string, to: string): Promise<any> {
throw new Error(`rename ${from} ${to}`)
}
async chmod(path: string, mode: string): Promise<any> {
throw new Error(`chmod ${path} ${mode}`)
}
getUniqueName(fileName: string): string {
throw new Error(`getUniqueName ${fileName}`)
}
}
const tls = {
key: readFileSync(SSL_KEY || ''),
cert: readFileSync(SSL_CERT || ''),
}
export const ftpService = mkSingleton((config: FtpConfig) => {
const log = logger().create('FtpService')
const { dbg, info } = log
const ftpServer = new FtpSrv({
url: 'ftp://0.0.0.0:' + PH_FTP_PORT,
anonymous: false,
log: log.create(`ftpServer`, { errorTrace: false }),
tls,
})
ftpServer.on(
'login',
async ({ connection, username, password }, resolve, reject) => {
const url = (await clientService()).url
const client = createPbClient(url)
try {
await client.client
.collection('users')
.authWithPassword(username, password)
dbg(`Logged in`)
const fs = new PhFs(connection, client)
resolve({ fs })
} catch (e) {
reject(new Error(`Invalid username or password`))
}
}
)
ftpServer.listen().then(() => {
info('Ftp server is starting...')
})
const shutdown = () => {
info(`Closing FTP server`)
ftpServer.close()
}
return { shutdown }
})

View File

@ -143,7 +143,13 @@ export const createPocketbaseService = async (
)
}
const args = [command, `--dir`, `${DAEMON_PB_DATA_DIR}/${slug}/pb_data`]
const args = [
command,
`--dir`,
`${DAEMON_PB_DATA_DIR}/${slug}/pb_data`,
`--publicDir`,
`${DAEMON_PB_DATA_DIR}/${slug}/pb_static`,
]
if (command === 'serve') {
args.push(`--http`)
args.push(mkInternalAddress(port))

View File

@ -75,10 +75,16 @@ export const createProxyService = async (config: ProxyServiceConfig) => {
info('daemon on port 3000')
server.listen(3000)
const shutdown = () => {
const shutdown = async () => {
info(`Shutting down proxy server`)
server.close()
instanceManager.shutdown()
return new Promise<void>((resolve) => {
server.close((err) => {
if (err) error(err)
resolve()
})
server.closeAllConnections()
instanceManager.shutdown()
})
}
return { shutdown }

View File

@ -1,4 +1 @@
import { createPromiseHelper } from '@pockethost/common'
export const promiseHelper = createPromiseHelper({})
export const { safeCatch } = promiseHelper
export { safeCatch } from '@pockethost/common'

View File

@ -1,6 +0,0 @@
PUBLIC_APP_PROTOCOL = http
PUBLIC_APP_DOMAIN = localhost
PUBLIC_PB_PROTOCOL=https
PUBLIC_PB_SUBDOMAIN = pockethost-central
PUBLIC_PB_DOMAIN = pockethost.io
PUBLIC_POCKETHOST_VERSION=0.5.0

View File

@ -13,9 +13,8 @@ export const envi = (name: string, _default: number) => parseInt(env(name, _defa
export const envb = (name: string, _default: boolean) => boolean(env(name, _default.toString()))
export const PUBLIC_PB_DOMAIN = env('PUBLIC_PB_DOMAIN', 'pockethost.io')
export const PUBLIC_PB_SUBDOMAIN = env('PUBLIC_PB_SUBDOMAIN', 'pockethost-central')
export const PUBLIC_APP_DOMAIN = env('PUBLIC_PB_SUBDOMAIN', 'localhost')
export const PUBLIC_APP_DB = env('PUBLIC_APP_DB', 'pockethost-central')
export const PUBLIC_APP_DOMAIN = env('PUBLIC_APP_DOMAIN', 'pockethost.io')
export const PUBLIC_APP_PROTOCOL = env('PUBLIC_APP_PROTOCOL', 'https')
export const PUBLIC_PB_PROTOCOL = env(
'PUBLIC_PB_PROTOCOL',

View File

@ -3,7 +3,9 @@ import {
assertExists,
createRpcHelper,
createWatchHelper,
logger,
RpcCommands,
safeCatch,
type BackupFields,
type BackupInstancePayload,
type BackupInstanceResult,
@ -11,8 +13,6 @@ import {
type CreateInstanceResult,
type InstanceFields,
type InstanceId,
type Logger,
type PromiseHelper,
type RestoreInstancePayload,
type RestoreInstanceResult,
type UserFields
@ -38,15 +38,12 @@ export type AuthStoreProps = {
export type PocketbaseClientConfig = {
url: string
logger: Logger
promiseHelper: PromiseHelper
}
export type PocketbaseClient = ReturnType<typeof createPocketbaseClient>
export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
const { url, logger, promiseHelper } = config
const { dbg, error } = logger
const { safeCatch } = promiseHelper
const { url } = config
const { dbg, error } = logger()
const client = new PocketBase(url)
@ -109,9 +106,9 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
client.collection('users').authRefresh()
)
const watchHelper = createWatchHelper({ client, promiseHelper, logger })
const watchHelper = createWatchHelper({ client })
const { watchById, watchAllById } = watchHelper
const rpcMixin = createRpcHelper({ client, watchHelper, promiseHelper, logger })
const rpcMixin = createRpcHelper({ client, watchHelper })
const { mkRpc } = rpcMixin
const createInstance = mkRpc<CreateInstancePayload, CreateInstanceResult>(

View File

@ -1,6 +1,6 @@
import { browser, dev } from '$app/environment'
import { PUBLIC_PB_DOMAIN, PUBLIC_PB_SUBDOMAIN } from '$src/env'
import { createLogger, createPromiseHelper } from '@pockethost/common'
import { PUBLIC_APP_DB, PUBLIC_APP_DOMAIN } from '$src/env'
import { logger } from '@pockethost/common'
import { createPocketbaseClient, type PocketbaseClient } from './PocketbaseClient'
export const client = (() => {
@ -8,11 +8,10 @@ export const client = (() => {
return () => {
if (!browser) throw new Error(`PocketBase client not supported in SSR`)
if (clientInstance) return clientInstance
const logger = createLogger({ debug: dev })
logger.info(`Initializing pocketbase client`)
const url = `https://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}`
const promiseHelper = createPromiseHelper({ logger })
clientInstance = createPocketbaseClient({ url, logger, promiseHelper })
const { info } = logger({ debug: dev })
info(`Initializing pocketbase client`)
const url = `https://${PUBLIC_APP_DB}.${PUBLIC_APP_DOMAIN}`
clientInstance = createPocketbaseClient({ url })
return clientInstance
}
})()

View File

@ -1,5 +1,6 @@
<script lang="ts">
import { PUBLIC_PB_DOMAIN, PUBLIC_PB_PROTOCOL } from '$src/env'
import { PUBLIC_APP_DOMAIN } from '$env/static/public'
import { PUBLIC_APP_PROTOCOL } from '$src/env'
import { assertExists } from '@pockethost/common'
import Backup from './Backup.svelte'
import Code from './Code.svelte'
@ -9,7 +10,7 @@
assertExists($instance, `Expected instance here`)
const { subdomain } = $instance
const url = `${PUBLIC_PB_PROTOCOL}://${subdomain}.${PUBLIC_PB_DOMAIN}`
const url = `${PUBLIC_APP_PROTOCOL}://${subdomain}.${PUBLIC_APP_DOMAIN}`
const code = `const url = '${url}'\nconst client = new PocketBase(url)`
</script>

View File

@ -1,12 +1,12 @@
<script lang="ts">
import CodeSample from '$components/CodeSample.svelte'
import { PUBLIC_PB_DOMAIN, PUBLIC_PB_PROTOCOL } from '$src/env'
import { PUBLIC_APP_DOMAIN, PUBLIC_APP_PROTOCOL } from '$src/env'
import type { InstanceFields } from '@pockethost/common'
export let instance: InstanceFields
const { subdomain } = instance
const url = `${PUBLIC_PB_PROTOCOL}://${subdomain}.${PUBLIC_PB_DOMAIN}`
const url = `${PUBLIC_APP_PROTOCOL}://${subdomain}.${PUBLIC_APP_DOMAIN}`
const code = `const url = '${url}'\nconst client = new PocketBase(url)`
</script>

View File

@ -1,12 +1,12 @@
<script lang="ts">
import ProvisioningStatus from '$components/ProvisioningStatus.svelte'
import { PUBLIC_PB_DOMAIN, PUBLIC_PB_PROTOCOL } from '$src/env'
import { PUBLIC_APP_DOMAIN, PUBLIC_APP_PROTOCOL } from '$src/env'
import type { InstanceFields } from '@pockethost/common'
export let instance: InstanceFields
const { subdomain, status, version } = instance
const url = `${PUBLIC_PB_PROTOCOL}://${subdomain}.${PUBLIC_PB_DOMAIN}`
const url = `${PUBLIC_APP_PROTOCOL}://${subdomain}.${PUBLIC_APP_DOMAIN}`
</script>
<div class="py-4">

View File

@ -1,6 +1,6 @@
<script lang="ts">
import AlertBar from '$components/AlertBar.svelte'
import { PUBLIC_PB_DOMAIN } from '$src/env'
import { PUBLIC_APP_DOMAIN } from '$src/env'
import { handleCreateNewInstance } from '$util/database'
import { generateSlug } from 'random-word-slugs'
@ -61,7 +61,7 @@
</div>
<div class="col-auto ps-0">
<span class="form-text">.{PUBLIC_PB_DOMAIN}</span>
<span class="form-text">.{PUBLIC_APP_DOMAIN}</span>
</div>
</div>

View File

@ -3,7 +3,7 @@
import AuthStateGuard from '$components/helpers/AuthStateGuard.svelte'
import ProvisioningStatus from '$components/ProvisioningStatus.svelte'
import RetroBoxContainer from '$components/RetroBoxContainer.svelte'
import { PUBLIC_PB_DOMAIN } from '$src/env'
import { PUBLIC_APP_DOMAIN } from '$src/env'
import { client } from '$src/pocketbase'
import { error } from '$util/logger'
import {
@ -103,7 +103,7 @@
<a
class="btn btn-light pocketbase-button"
href={`https://${app.subdomain}.${PUBLIC_PB_DOMAIN}/_`}
href={`https://${app.subdomain}.${PUBLIC_APP_DOMAIN}/_`}
target="_blank"
>
<img src="/images/pocketbase-logo.svg" alt="PocketBase Logo" class="img-fluid" />

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { handleRegistration, handleLogin, handleFormError } from '$util/database'
import AlertBar from '$components/AlertBar.svelte'
import { handleFormError, handleLogin, handleRegistration } from '$util/database'
let email: string = ''
let password: string = ''

View File

@ -0,0 +1,46 @@
diff --git a/node_modules/ftp-srv/ftp-srv.d.ts b/node_modules/ftp-srv/ftp-srv.d.ts
index b14b113..15134cb 100644
--- a/node_modules/ftp-srv/ftp-srv.d.ts
+++ b/node_modules/ftp-srv/ftp-srv.d.ts
@@ -2,11 +2,19 @@ import * as tls from 'tls'
import { Stats } from 'fs'
import { EventEmitter } from 'events';
+export type FileStat= {
+ isDirectory(): boolean
+ mode: number
+ size: number
+ mtime: number
+ name: string
+}
+
export class FileSystem {
readonly connection: FtpConnection;
readonly root: string;
- readonly cwd: string;
+ cwd: string;
constructor(connection: FtpConnection, {root, cwd}?: {
root: any;
@@ -15,9 +23,9 @@ export class FileSystem {
currentDirectory(): string;
- get(fileName: string): Promise<any>;
+ get(fileName: string): Promise<FileStat>;
- list(path?: string): Promise<any>;
+ list(path?: string): Promise<FileStat[]>;
chdir(path?: string): Promise<string>;
@@ -28,7 +36,7 @@ export class FileSystem {
read(fileName: string, {start}?: {
start?: any;
- }): Promise<any>;
+ }): Promise<ReadableStream>;
delete(path: string): Promise<any>;

170
yarn.lock
View File

@ -1131,6 +1131,11 @@ binary@~0.3.0:
buffers "~0.1.1"
chainsaw "~0.1.0"
bluebird@^3.5.1:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bluebird@~3.4.1:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
@ -1208,6 +1213,16 @@ builtin-modules@^3.3.0:
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
bunyan@^1.8.12:
version "1.8.15"
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46"
integrity sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==
optionalDependencies:
dtrace-provider "~0.8"
moment "^2.19.3"
mv "~2"
safe-json-stringify "~1"
busboy@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
@ -1332,6 +1347,15 @@ cliui@^5.0.0:
strip-ansi "^5.2.0"
wrap-ansi "^5.1.0"
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
@ -1592,6 +1616,13 @@ dotenv@^7.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c"
integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==
dtrace-provider@~0.8:
version "0.8.8"
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e"
integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==
dependencies:
nan "^2.14.0"
duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
@ -1861,6 +1892,14 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
find-up@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790"
@ -1924,6 +1963,19 @@ fstream@^1.0.12:
mkdirp ">=0.5 0"
rimraf "2"
ftp-srv@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/ftp-srv/-/ftp-srv-4.6.2.tgz#20d94cf3c9ec14cfd16e5d3648ecf852660a017e"
integrity sha512-BaBE2PV5tfWNuBxGTMlswnbaUGbWJYX3POOv4Li5HOnqPS/+DLbCQBDM7WMc4Ll6BVPO8t27ziXOLGfPJeJYpg==
dependencies:
bluebird "^3.5.1"
bunyan "^1.8.12"
ip "^1.1.5"
lodash "^4.17.15"
moment "^2.22.1"
uuid "^3.2.1"
yargs "^15.4.1"
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -1995,6 +2047,17 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
dependencies:
is-glob "^4.0.1"
glob@^6.0.1:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
integrity sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.3, glob@^7.1.4:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@ -2177,6 +2240,11 @@ interpret@^2.2.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
ip@^1.1.5:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48"
integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==
ip@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
@ -2447,6 +2515,13 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
locate-path@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.1.1.tgz#8e1e5a75c7343770cef02ff93c4bf1f0aa666374"
@ -2464,7 +2539,7 @@ lodash.throttle@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
lodash@^4.17.21:
lodash@^4.17.15, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -2547,7 +2622,7 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
minimatch@^3.1.1:
"minimatch@2 || 3", minimatch@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@ -2620,7 +2695,7 @@ minizlib@^2.0.0, minizlib@^2.1.1:
minipass "^3.0.0"
yallist "^4.0.0"
"mkdirp@>=0.5 0", mkdirp@^0.5.1:
"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.1:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
@ -2632,6 +2707,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment@^2.19.3, moment@^2.22.1:
version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@ -2673,6 +2753,20 @@ msgpackr@^1.5.4:
optionalDependencies:
msgpackr-extract "^2.2.0"
mv@~2:
version "2.1.1"
resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2"
integrity sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==
dependencies:
mkdirp "~0.5.1"
ncp "~2.0.0"
rimraf "~2.4.0"
nan@^2.14.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
@ -2683,6 +2777,11 @@ nanoid@^4.0.0:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.0.tgz#6e144dee117609232c3f415c34b0e550e64999a5"
integrity sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==
ncp@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==
negotiator@^0.6.2:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
@ -2829,7 +2928,7 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
p-limit@^2.0.0:
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
@ -2850,6 +2949,13 @@ p-locate@^3.0.0:
dependencies:
p-limit "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-locate@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f"
@ -2931,6 +3037,11 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-exists@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7"
@ -3199,6 +3310,13 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rimraf@~2.4.0:
version "2.4.5"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da"
integrity sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==
dependencies:
glob "^6.0.1"
rollup@^2.79.1:
version "2.79.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
@ -3237,6 +3355,11 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-json-stringify@~1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd"
integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@ -3746,6 +3869,11 @@ utility-types@^3.10.0:
resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b"
integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
uuid@^3.2.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
v8-compile-cache@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
@ -3821,6 +3949,15 @@ wrap-ansi@^5.1.0:
string-width "^3.0.0"
strip-ansi "^5.0.0"
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
@ -3868,6 +4005,14 @@ yargs-parser@^13.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
@ -3889,6 +4034,23 @@ yargs@^13.3.0:
y18n "^4.0.0"
yargs-parser "^13.1.2"
yargs@^15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
yargs@^17.3.1:
version "17.6.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"