mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
FTP support
This commit is contained in:
parent
ea432684d7
commit
21cf9b2773
@ -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))
|
||||
|
@ -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 }
|
||||
}
|
@ -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'
|
||||
|
@ -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 (
|
||||
|
@ -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>(
|
||||
|
42
packages/common/src/safeCatch.ts
Normal file
42
packages/common/src/safeCatch.ts
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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')
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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)
|
||||
})()
|
||||
|
312
packages/daemon/src/services/FtpService.ts
Normal file
312
packages/daemon/src/services/FtpService.ts
Normal 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 }
|
||||
})
|
@ -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))
|
||||
|
@ -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 }
|
||||
|
@ -1,4 +1 @@
|
||||
import { createPromiseHelper } from '@pockethost/common'
|
||||
|
||||
export const promiseHelper = createPromiseHelper({})
|
||||
export const { safeCatch } = promiseHelper
|
||||
export { safeCatch } from '@pockethost/common'
|
||||
|
@ -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
|
@ -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',
|
||||
|
@ -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>(
|
||||
|
@ -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
|
||||
}
|
||||
})()
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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 = ''
|
||||
|
46
patches/ftp-srv+4.6.2.patch
Normal file
46
patches/ftp-srv+4.6.2.patch
Normal 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
170
yarn.lock
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user