diff --git a/apps/api/package.json b/apps/api/package.json index b5dcb5823..6b48e4b78 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -22,12 +22,13 @@ "@fastify/jwt": "6.3.2", "@fastify/static": "6.5.0", "@iarna/toml": "2.2.5", + "@ladjs/graceful": "3.0.2", "@prisma/client": "3.15.2", "axios": "0.27.2", "bcryptjs": "2.4.3", "bree": "9.1.2", "cabin": "9.1.2", - "compare-versions": "4.1.3", + "compare-versions": "4.1.4", "cuid": "2.1.8", "dayjs": "1.11.5", "dockerode": "3.3.4", @@ -43,17 +44,18 @@ "jsonwebtoken": "8.5.1", "node-forge": "1.3.1", "node-os-utils": "1.3.7", - "p-queue": "7.3.0", + "p-all": "4.0.0", + "p-throttle": "5.0.0", "public-ip": "6.0.1", "ssh-config": "4.1.6", "strip-ansi": "7.0.1", "unique-names-generator": "4.7.1" }, "devDependencies": { - "@types/node": "18.7.11", + "@types/node": "18.7.13", "@types/node-os-utils": "1.3.0", - "@typescript-eslint/eslint-plugin": "5.34.0", - "@typescript-eslint/parser": "5.34.0", + "@typescript-eslint/eslint-plugin": "5.35.1", + "@typescript-eslint/parser": "5.35.1", "esbuild": "0.15.5", "eslint": "8.22.0", "eslint-config-prettier": "8.5.0", diff --git a/apps/api/prisma/migrations/20220825064811_concurrent_build_settings/migration.sql b/apps/api/prisma/migrations/20220825064811_concurrent_build_settings/migration.sql new file mode 100644 index 000000000..1535c2bf7 --- /dev/null +++ b/apps/api/prisma/migrations/20220825064811_concurrent_build_settings/migration.sql @@ -0,0 +1,29 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Setting" ( + "id" TEXT NOT NULL PRIMARY KEY, + "fqdn" TEXT, + "isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "minPort" INTEGER NOT NULL DEFAULT 9000, + "maxPort" INTEGER NOT NULL DEFAULT 9100, + "proxyPassword" TEXT NOT NULL, + "proxyUser" TEXT NOT NULL, + "proxyHash" TEXT, + "isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false, + "isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true, + "DNSServers" TEXT, + "isTraefikUsed" BOOLEAN NOT NULL DEFAULT true, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "ipv4" TEXT, + "ipv6" TEXT, + "arch" TEXT, + "concurrentBuilds" INTEGER NOT NULL DEFAULT 1 +); +INSERT INTO "new_Setting" ("DNSServers", "arch", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "DNSServers", "arch", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting"; +DROP TABLE "Setting"; +ALTER TABLE "new_Setting" RENAME TO "Setting"; +CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/apps/api/prisma/migrations/20220825072007_build_queue_improvements/migration.sql b/apps/api/prisma/migrations/20220825072007_build_queue_improvements/migration.sql new file mode 100644 index 000000000..78c51fc15 --- /dev/null +++ b/apps/api/prisma/migrations/20220825072007_build_queue_improvements/migration.sql @@ -0,0 +1,24 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Build" ( + "id" TEXT NOT NULL PRIMARY KEY, + "type" TEXT NOT NULL, + "applicationId" TEXT, + "destinationDockerId" TEXT, + "gitSourceId" TEXT, + "githubAppId" TEXT, + "gitlabAppId" TEXT, + "commit" TEXT, + "pullmergeRequestId" TEXT, + "forceRebuild" BOOLEAN NOT NULL DEFAULT false, + "sourceBranch" TEXT, + "branch" TEXT, + "status" TEXT DEFAULT 'queued', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_Build" ("applicationId", "branch", "commit", "createdAt", "destinationDockerId", "gitSourceId", "githubAppId", "gitlabAppId", "id", "status", "type", "updatedAt") SELECT "applicationId", "branch", "commit", "createdAt", "destinationDockerId", "gitSourceId", "githubAppId", "gitlabAppId", "id", "status", "type", "updatedAt" FROM "Build"; +DROP TABLE "Build"; +ALTER TABLE "new_Build" RENAME TO "Build"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 02440300b..dfa7ae26b 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -27,6 +27,7 @@ model Setting { ipv4 String? ipv6 String? arch String? + concurrentBuilds Int @default(1) } model User { @@ -197,6 +198,9 @@ model Build { githubAppId String? gitlabAppId String? commit String? + pullmergeRequestId String? + forceRebuild Boolean @default(false) + sourceBranch String? branch String? status String? @default("queued") createdAt DateTime @default(now()) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 18864dd6e..dac8d34ef 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -5,11 +5,10 @@ import env from '@fastify/env'; import cookie from '@fastify/cookie'; import path, { join } from 'path'; import autoLoad from '@fastify/autoload'; -import { asyncExecShell, isDev, listSettings, prisma, version } from './lib/common'; +import { asyncExecShell, asyncSleep, isDev, listSettings, prisma, version } from './lib/common'; import { scheduler } from './lib/scheduler'; -import axios from 'axios'; import compareVersions from 'compare-versions'; - +import Graceful from '@ladjs/graceful' declare module 'fastify' { interface FastifyInstance { config: { @@ -104,49 +103,39 @@ fastify.listen({ port, host }, async (err: any, address: any) => { } console.log(`Coolify's API is listening on ${host}:${port}`); await initServer(); - await scheduler.start('deployApplication'); - await scheduler.start('cleanupStorage'); - await scheduler.start('cleanupPrismaEngines'); - await scheduler.start('checkProxies'); - // Check if no build is running - - // Check for update + const graceful = new Graceful({ brees: [scheduler] }); + graceful.listen(); + setInterval(async () => { - const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); - if (isAutoUpdateEnabled) { - const currentVersion = version; - const { data: versions } = await axios - .get( - `https://get.coollabs.io/versions.json` - , { - params: { - appId: process.env['COOLIFY_APP_ID'] || undefined, - version: currentVersion - } - }) - const latestVersion = versions['coolify'].main.version; - const isUpdateAvailable = compareVersions(latestVersion, currentVersion); - if (isUpdateAvailable === 1) { - if (scheduler.workers.has('deployApplication')) { - scheduler.workers.get('deployApplication').postMessage("status:autoUpdater"); - } - } + if (!scheduler.workers.has('deployApplication')) { + scheduler.run('deployApplication'); } + if (!scheduler.workers.has('infrastructure')) { + scheduler.run('infrastructure'); + } + }, 2000) + + // autoUpdater + setInterval(async () => { + scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:autoUpdater") }, isDev ? 5000 : 60000 * 15) - // Cleanup storage + // cleanupStorage setInterval(async () => { - if (scheduler.workers.has('deployApplication')) { - scheduler.workers.get('deployApplication').postMessage("status:cleanupStorage"); - } - }, isDev ? 5000 : 60000 * 10) + scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupStorage") + }, isDev ? 6000 : 60000 * 10) + + // checkProxies + setInterval(async () => { + scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkProxies") + }, 10000) + + // cleanupPrismaEngines + // setInterval(async () => { + // scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupPrismaEngines") + // }, 60000) - scheduler.on('worker deleted', async (name) => { - if (name === 'autoUpdater' || name === 'cleanupStorage') { - if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication'); - } - }); await getArch(); await getIPAddress(); }); @@ -170,6 +159,12 @@ async function initServer() { try { await asyncExecShell(`docker network create --attachable coolify`); } catch (error) { } + try { + const isOlder = compareVersions('3.8.1', version); + if (isOlder === -1) { + await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } }); + } + } catch (error) { } } async function getArch() { try { diff --git a/apps/api/src/jobs/autoUpdater.ts b/apps/api/src/jobs/autoUpdater.ts deleted file mode 100644 index 566ffea29..000000000 --- a/apps/api/src/jobs/autoUpdater.ts +++ /dev/null @@ -1,43 +0,0 @@ -import axios from 'axios'; -import compareVersions from 'compare-versions'; -import { parentPort } from 'node:worker_threads'; -import { asyncExecShell, asyncSleep, isDev, prisma, version } from '../lib/common'; - -(async () => { - if (parentPort) { - try { - const currentVersion = version; - const { data: versions } = await axios - .get( - `https://get.coollabs.io/versions.json` - , { - params: { - appId: process.env['COOLIFY_APP_ID'] || undefined, - version: currentVersion - } - }) - const latestVersion = versions['coolify'].main.version; - const isUpdateAvailable = compareVersions(latestVersion, currentVersion); - if (isUpdateAvailable === 1) { - const activeCount = 0 - if (activeCount === 0) { - if (!isDev) { - console.log(`Updating Coolify to ${latestVersion}.`); - await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); - await asyncExecShell(`env | grep COOLIFY > .env`); - await asyncExecShell( - `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"` - ); - } else { - console.log('Updating (not really in dev mode).'); - } - } - } - } catch (error) { - console.log(error); - } finally { - await prisma.$disconnect(); - } - - } else process.exit(0); -})(); diff --git a/apps/api/src/jobs/checkProxies.ts b/apps/api/src/jobs/checkProxies.ts deleted file mode 100644 index 992281c3a..000000000 --- a/apps/api/src/jobs/checkProxies.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { parentPort } from 'node:worker_threads'; -import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd, listSettings } from '../lib/common'; -import { checkContainer } from '../lib/docker'; - -(async () => { - if (parentPort) { - try { - const { arch } = await listSettings(); - // Coolify Proxy local - const engine = '/var/run/docker.sock'; - const localDocker = await prisma.destinationDocker.findFirst({ - where: { engine, network: 'coolify' } - }); - if (localDocker && localDocker.isCoolifyProxyUsed) { - // Remove HAProxy - const found = await checkContainer({ dockerId: localDocker.id, container: 'coolify-haproxy' }); - if (found) { - await executeDockerCmd({ - dockerId: localDocker.id, - command: `docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy` - }) - } - await startTraefikProxy(localDocker.id); - } - - // TCP Proxies - const databasesWithPublicPort = await prisma.database.findMany({ - where: { publicPort: { not: null } }, - include: { settings: true, destinationDocker: true } - }); - for (const database of databasesWithPublicPort) { - const { destinationDockerId, destinationDocker, publicPort, id } = database; - if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { - const { privatePort } = generateDatabaseConfiguration(database, arch); - // Remove HAProxy - const found = await checkContainer({ - dockerId: localDocker.id, container: `haproxy-for-${publicPort}` - }); - if (found) { - await executeDockerCmd({ - dockerId: localDocker.id, - command: `docker stop -t 0 haproxy-for-${publicPort} && docker rm haproxy-for-${publicPort}` - }) - } - await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); - } - } - const wordpressWithFtp = await prisma.wordpress.findMany({ - where: { ftpPublicPort: { not: null } }, - include: { service: { include: { destinationDocker: true } } } - }); - for (const ftp of wordpressWithFtp) { - const { service, ftpPublicPort } = ftp; - const { destinationDockerId, destinationDocker, id } = service; - if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { - // Remove HAProxy - const found = await checkContainer({ dockerId: localDocker.id, container: `haproxy-for-${ftpPublicPort}` }); - if (found) { - await executeDockerCmd({ - dockerId: localDocker.id, - command: `docker stop -t 0 haproxy -for-${ftpPublicPort} && docker rm haproxy-for-${ftpPublicPort}` - }) - } - await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp'); - } - } - - // HTTP Proxies - const minioInstances = await prisma.minio.findMany({ - where: { publicPort: { not: null } }, - include: { service: { include: { destinationDocker: true } } } - }); - for (const minio of minioInstances) { - const { service, publicPort } = minio; - const { destinationDockerId, destinationDocker, id } = service; - if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { - // Remove HAProxy - const found = await checkContainer({ dockerId: localDocker.id, container: `${id}-${publicPort}` }); - if (found) { - await executeDockerCmd({ - dockerId: localDocker.id, - command: `docker stop -t 0 ${id}-${publicPort} && docker rm ${id}-${publicPort} ` - }) - } - await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000); - } - } - - } catch (error) { - - } finally { - await prisma.$disconnect(); - } - - } else process.exit(0); -})(); diff --git a/apps/api/src/jobs/cleanupPrismaEngines.ts b/apps/api/src/jobs/cleanupPrismaEngines.ts deleted file mode 100644 index 335bdce7d..000000000 --- a/apps/api/src/jobs/cleanupPrismaEngines.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { parentPort } from 'node:worker_threads'; -import { asyncExecShell, isDev, prisma } from '../lib/common'; - -(async () => { - if (parentPort) { - if (!isDev) { - try { - const { stdout } = await asyncExecShell(`ps -ef | grep /app/prisma-engines/query-engine | grep -v grep | wc -l | xargs`) - if (stdout.trim() != null && stdout.trim() != '' && Number(stdout.trim()) > 1) { - await asyncExecShell(`killall -q -e /app/prisma-engines/query-engine -o 10m`) - } - } catch (error) { - console.log(error); - } finally { - await prisma.$disconnect(); - } - } - } else process.exit(0); -})(); diff --git a/apps/api/src/jobs/cleanupStorage.ts b/apps/api/src/jobs/cleanupStorage.ts deleted file mode 100644 index 97683ac2d..000000000 --- a/apps/api/src/jobs/cleanupStorage.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { parentPort } from 'node:worker_threads'; -import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, version } from '../lib/common'; - -(async () => { - if (parentPort) { - const destinationDockers = await prisma.destinationDocker.findMany(); - let enginesDone = new Set() - for (const destination of destinationDockers) { - if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return - if (destination.engine) enginesDone.add(destination.engine) - if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress) - - let lowDiskSpace = false; - try { - let stdout = null - if (!isDev) { - const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` }) - stdout = output.stdout; - } else { - const output = await asyncExecShell( - `df -kPT /` - ); - stdout = output.stdout; - } - let lines = stdout.trim().split('\n'); - let header = lines[0]; - let regex = - /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g; - const boundaries = []; - let match; - - while ((match = regex.exec(header))) { - boundaries.push(match[0].length); - } - - boundaries[boundaries.length - 1] = -1; - const data = lines.slice(1).map((line) => { - const cl = boundaries.map((boundary) => { - const column = boundary > 0 ? line.slice(0, boundary) : line; - line = line.slice(boundary); - return column.trim(); - }); - return { - capacity: Number.parseInt(cl[5], 10) / 100 - }; - }); - if (data.length > 0) { - const { capacity } = data[0]; - if (capacity > 0.8) { - lowDiskSpace = true; - } - } - } catch (error) { - console.log(error); - } - await cleanupDockerStorage(destination.id, lowDiskSpace, false) - } - await prisma.$disconnect(); - } else process.exit(0); -})(); diff --git a/apps/api/src/jobs/deployApplication.ts b/apps/api/src/jobs/deployApplication.ts index 93d358fe3..ca5a5e230 100644 --- a/apps/api/src/jobs/deployApplication.ts +++ b/apps/api/src/jobs/deployApplication.ts @@ -10,208 +10,269 @@ import * as buildpacks from '../lib/buildPacks'; (async () => { if (parentPort) { - const concurrency = 1 - const PQueue = await import('p-queue'); - const queue = new PQueue.default({ concurrency }); parentPort.on('message', async (message) => { - if (parentPort) { - if (message === 'error') throw new Error('oops'); - if (message === 'cancel') { - parentPort.postMessage('cancelled'); - return; - } - if (message === 'status:autoUpdater') { - parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'autoUpdater' }); - return; - } - if (message === 'status:cleanupStorage') { - parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'cleanupStorage' }); - return; - } - if (message === 'action:flushQueue') { - queue.clear() - return; - } + if (message === 'error') throw new Error('oops'); + if (message === 'cancel') { + parentPort.postMessage('cancelled'); + await prisma.$disconnect() + process.exit(0); + } + }); + const pThrottle = await import('p-throttle') + const throttle = pThrottle.default({ + limit: 1, + interval: 2000 + }); - await queue.add(async () => { - const { - id: applicationId, - repository, - name, - destinationDocker, - destinationDockerId, - gitSource, - build_id: buildId, - configHash, - fqdn, - projectId, - secrets, - phpModules, - type, - pullmergeRequestId = null, - sourceBranch = null, - settings, - persistentStorage, - pythonWSGI, - pythonModule, - pythonVariable, - denoOptions, - exposePort, - baseImage, - baseBuildImage, - deploymentType, - forceRebuild - } = message - let { - branch, - buildPack, - port, - installCommand, - buildCommand, - startCommand, - baseDirectory, - publishDirectory, - dockerFileLocation, - denoMainFile - } = message - const currentHash = crypto - .createHash('sha256') - .update( - JSON.stringify({ + + const th = throttle(async () => { + try { + const queuedBuilds = await prisma.build.findMany({ where: { status: 'queued' }, orderBy: { createdAt: 'asc' } }); + const { concurrentBuilds } = await prisma.setting.findFirst({}) + if (queuedBuilds.length > 0) { + parentPort.postMessage({ deploying: true }); + const concurrency = concurrentBuilds; + const pAll = await import('p-all'); + const actions = [] + + for (const queueBuild of queuedBuilds) { + actions.push(async () => { + const application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } }) + const { id: buildId, type, sourceBranch = null, pullmergeRequestId = null, forceRebuild } = queueBuild + const { + id: applicationId, + repository, + name, + destinationDocker, + destinationDockerId, + gitSource, + configHash, + fqdn, + projectId, + secrets, + phpModules, + settings, + persistentStorage, pythonWSGI, pythonModule, pythonVariable, - deploymentType, denoOptions, + exposePort, baseImage, baseBuildImage, + deploymentType, + } = application + let { + branch, buildPack, port, - exposePort, installCommand, buildCommand, startCommand, - secrets, - branch, - repository, - fqdn - }) - ) - .digest('hex'); - try { - const { debug } = settings; - if (concurrency === 1) { - await prisma.build.updateMany({ - where: { - status: { in: ['queued', 'running'] }, - id: { not: buildId }, - applicationId, - createdAt: { lt: new Date(new Date().getTime() - 10 * 1000) } - }, - data: { status: 'failed' } - }); - } - let imageId = applicationId; - let domain = getDomain(fqdn); - const volumes = - persistentStorage?.map((storage) => { - return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : '' - }${storage.path}`; - }) || []; - // Previews, we need to get the source branch and set subdomain - if (pullmergeRequestId) { - branch = sourceBranch; - domain = `${pullmergeRequestId}.${domain}`; - imageId = `${applicationId}-${pullmergeRequestId}`; - } - - let deployNeeded = true; - let destinationType; - - if (destinationDockerId) { - destinationType = 'docker'; - } - if (destinationType === 'docker') { - await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } }); - const { workdir, repodir } = await createDirectories({ repository, buildId }); - const configuration = await setDefaultConfiguration(message); - - buildPack = configuration.buildPack; - port = configuration.port; - installCommand = configuration.installCommand; - startCommand = configuration.startCommand; - buildCommand = configuration.buildCommand; - publishDirectory = configuration.publishDirectory; - baseDirectory = configuration.baseDirectory; - dockerFileLocation = configuration.dockerFileLocation; - denoMainFile = configuration.denoMainFile; - const commit = await importers[gitSource.type]({ - applicationId, - debug, - workdir, - repodir, - githubAppId: gitSource.githubApp?.id, - gitlabAppId: gitSource.gitlabApp?.id, - customPort: gitSource.customPort, - repository, - branch, - buildId, - apiUrl: gitSource.apiUrl, - htmlUrl: gitSource.htmlUrl, - projectId, - deployKeyId: gitSource.gitlabApp?.deployKeyId || null, - privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null, - forPublic: gitSource.forPublic - }); - if (!commit) { - throw new Error('No commit found?'); - } - let tag = commit.slice(0, 7); - if (pullmergeRequestId) { - tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`; - } - + baseDirectory, + publishDirectory, + dockerFileLocation, + denoMainFile + } = application + const currentHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + pythonWSGI, + pythonModule, + pythonVariable, + deploymentType, + denoOptions, + baseImage, + baseBuildImage, + buildPack, + port, + exposePort, + installCommand, + buildCommand, + startCommand, + secrets, + branch, + repository, + fqdn + }) + ) + .digest('hex'); try { - await prisma.build.update({ where: { id: buildId }, data: { commit } }); - } catch (err) { - console.log(err); - } - - if (!pullmergeRequestId) { - - if (configHash !== currentHash) { - deployNeeded = true; - if (configHash) { - await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId }); - } - } else { - deployNeeded = false; + const { debug } = settings; + if (concurrency === 1) { + await prisma.build.updateMany({ + where: { + status: { in: ['queued', 'running'] }, + id: { not: buildId }, + applicationId, + createdAt: { lt: new Date(new Date().getTime() - 10 * 1000) } + }, + data: { status: 'failed' } + }); + } + let imageId = applicationId; + let domain = getDomain(fqdn); + const volumes = + persistentStorage?.map((storage) => { + return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : '' + }${storage.path}`; + }) || []; + // Previews, we need to get the source branch and set subdomain + if (pullmergeRequestId) { + branch = sourceBranch; + domain = `${pullmergeRequestId}.${domain}`; + imageId = `${applicationId}-${pullmergeRequestId}`; } - } else { - deployNeeded = true; - } - let imageFound = false; - try { - await executeDockerCmd({ - dockerId: destinationDocker.id, - command: `docker image inspect ${applicationId}:${tag}` - }) - imageFound = true; - } catch (error) { - // - } - await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage); + let deployNeeded = true; + let destinationType; - if (forceRebuild) deployNeeded = true - if (!imageFound || deployNeeded) { - // if (true) { - if (buildpacks[buildPack]) - await buildpacks[buildPack]({ - dockerId: destinationDocker.id, - buildId, + if (destinationDockerId) { + destinationType = 'docker'; + } + if (destinationType === 'docker') { + await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } }); + const { workdir, repodir } = await createDirectories({ repository, buildId }); + const configuration = await setDefaultConfiguration(application); + + buildPack = configuration.buildPack; + port = configuration.port; + installCommand = configuration.installCommand; + startCommand = configuration.startCommand; + buildCommand = configuration.buildCommand; + publishDirectory = configuration.publishDirectory; + baseDirectory = configuration.baseDirectory; + dockerFileLocation = configuration.dockerFileLocation; + denoMainFile = configuration.denoMainFile; + const commit = await importers[gitSource.type]({ applicationId, - domain, + debug, + workdir, + repodir, + githubAppId: gitSource.githubApp?.id, + gitlabAppId: gitSource.gitlabApp?.id, + customPort: gitSource.customPort, + repository, + branch, + buildId, + apiUrl: gitSource.apiUrl, + htmlUrl: gitSource.htmlUrl, + projectId, + deployKeyId: gitSource.gitlabApp?.deployKeyId || null, + privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null, + forPublic: gitSource.forPublic + }); + if (!commit) { + throw new Error('No commit found?'); + } + let tag = commit.slice(0, 7); + if (pullmergeRequestId) { + tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`; + } + + try { + await prisma.build.update({ where: { id: buildId }, data: { commit } }); + } catch (err) { + console.log(err); + } + + if (!pullmergeRequestId) { + if (configHash !== currentHash) { + deployNeeded = true; + if (configHash) { + await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId }); + } + } else { + deployNeeded = false; + } + } else { + deployNeeded = true; + } + + let imageFound = false; + try { + await executeDockerCmd({ + dockerId: destinationDocker.id, + command: `docker image inspect ${applicationId}:${tag}` + }) + imageFound = true; + } catch (error) { + // + } + await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage); + + if (forceRebuild) deployNeeded = true + if (!imageFound || deployNeeded) { + // if (true) { + if (buildpacks[buildPack]) + await buildpacks[buildPack]({ + dockerId: destinationDocker.id, + buildId, + applicationId, + domain, + name, + type, + pullmergeRequestId, + buildPack, + repository, + branch, + projectId, + publishDirectory, + debug, + commit, + tag, + workdir, + port: exposePort ? `${exposePort}:${port}` : port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + phpModules, + pythonWSGI, + pythonModule, + pythonVariable, + dockerFileLocation, + denoMainFile, + denoOptions, + baseImage, + baseBuildImage, + deploymentType + }); + else { + await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId }); + throw new Error(`Build pack ${buildPack} not found.`); + } + } else { + await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId }); + } + try { + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` }) + } catch (error) { + // + } + const envs = [ + `PORT=${port}` + ]; + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + envs.push(`${secret.name}=${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + envs.push(`${secret.name}=${secret.value}`); + } + } + }); + } + await fs.writeFile(`${workdir}/.env`, envs.join('\n')); + const labels = makeLabelForStandaloneApplication({ + applicationId, + fqdn, name, type, pullmergeRequestId, @@ -219,148 +280,93 @@ import * as buildpacks from '../lib/buildPacks'; repository, branch, projectId, - publishDirectory, - debug, - commit, - tag, - workdir, port: exposePort ? `${exposePort}:${port}` : port, + commit, installCommand, buildCommand, startCommand, baseDirectory, - secrets, - phpModules, - pythonWSGI, - pythonModule, - pythonVariable, - dockerFileLocation, - denoMainFile, - denoOptions, - baseImage, - baseBuildImage, - deploymentType + publishDirectory }); - else { - await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId }); - throw new Error(`Build pack ${buildPack} not found.`); - } - } else { - await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId }); - } - try { - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` }) - } catch (error) { - // - } - const envs = [ - `PORT=${port}` - ]; - if (secrets.length > 0) { - secrets.forEach((secret) => { - if (pullmergeRequestId) { - if (secret.isPRMRSecret) { - envs.push(`${secret.name}=${secret.value}`); - } - } else { - if (!secret.isPRMRSecret) { - envs.push(`${secret.name}=${secret.value}`); - } + let envFound = false; + try { + envFound = !!(await fs.stat(`${workdir}/.env`)); + } catch (error) { + // } - }); + try { + await saveBuildLog({ line: 'Deployment started.', buildId, applicationId }); + const composeVolumes = volumes.map((volume) => { + return { + [`${volume.split(':')[0]}`]: { + name: volume.split(':')[0] + } + }; + }); + const composeFile = { + version: '3.8', + services: { + [imageId]: { + image: `${applicationId}:${tag}`, + container_name: imageId, + volumes, + env_file: envFound ? [`${workdir}/.env`] : [], + labels, + depends_on: [], + expose: [port], + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + // logging: { + // driver: 'fluentd', + // }, + ...defaultComposeConfiguration(destinationDocker.network), + } + }, + networks: { + [destinationDocker.network]: { + external: true + } + }, + volumes: Object.assign({}, ...composeVolumes) + }; + await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` }) + await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); + } catch (error) { + await saveBuildLog({ line: error, buildId, applicationId }); + await prisma.build.updateMany({ + where: { id: buildId, status: { in: ['queued', 'running'] } }, + data: { status: 'failed' } + }); + throw new Error(error); + } + await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId }); + await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } }); + if (!pullmergeRequestId) await prisma.application.update({ + where: { id: applicationId }, + data: { configHash: currentHash } + }); + } } - await fs.writeFile(`${workdir}/.env`, envs.join('\n')); - const labels = makeLabelForStandaloneApplication({ - applicationId, - fqdn, - name, - type, - pullmergeRequestId, - buildPack, - repository, - branch, - projectId, - port: exposePort ? `${exposePort}:${port}` : port, - commit, - installCommand, - buildCommand, - startCommand, - baseDirectory, - publishDirectory - }); - let envFound = false; - try { - envFound = !!(await fs.stat(`${workdir}/.env`)); - } catch (error) { - // - } - try { - await saveBuildLog({ line: 'Deployment started.', buildId, applicationId }); - const composeVolumes = volumes.map((volume) => { - return { - [`${volume.split(':')[0]}`]: { - name: volume.split(':')[0] - } - }; - }); - const composeFile = { - version: '3.8', - services: { - [imageId]: { - image: `${applicationId}:${tag}`, - container_name: imageId, - volumes, - env_file: envFound ? [`${workdir}/.env`] : [], - labels, - depends_on: [], - expose: [port], - ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), - // logging: { - // driver: 'fluentd', - // }, - ...defaultComposeConfiguration(destinationDocker.network), - } - }, - networks: { - [destinationDocker.network]: { - external: true - } - }, - volumes: Object.assign({}, ...composeVolumes) - }; - await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` }) - await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); - } catch (error) { - await saveBuildLog({ line: error, buildId, applicationId }); + catch (error) { await prisma.build.updateMany({ - where: { id: message.build_id, status: { in: ['queued', 'running'] } }, + where: { id: buildId, status: { in: ['queued', 'running'] } }, data: { status: 'failed' } }); - throw new Error(error); + await saveBuildLog({ line: error, buildId, applicationId }); } - await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId }); - await prisma.build.update({ where: { id: message.build_id }, data: { status: 'success' } }); - if (!pullmergeRequestId) await prisma.application.update({ - where: { id: applicationId }, - data: { configHash: currentHash } - }); - } - - } - catch (error) { - await prisma.build.updateMany({ - where: { id: message.build_id, status: { in: ['queued', 'running'] } }, - data: { status: 'failed' } }); - await saveBuildLog({ line: error, buildId, applicationId }); - } finally { - await prisma.$disconnect(); } - }); - await prisma.$disconnect(); + await pAll.default(actions, { concurrency }) + } + } catch (error) { + } finally { } - }); + }) + + while (true) { + await th() + } + + } else process.exit(0); })(); diff --git a/apps/api/src/jobs/infrastructure.ts b/apps/api/src/jobs/infrastructure.ts new file mode 100644 index 000000000..d19163686 --- /dev/null +++ b/apps/api/src/jobs/infrastructure.ts @@ -0,0 +1,216 @@ +import { parentPort } from 'node:worker_threads'; +import axios from 'axios'; +import compareVersions from 'compare-versions'; +import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version } from '../lib/common'; + +async function disconnect() { + await prisma.$disconnect(); +} +async function autoUpdater() { + try { + const currentVersion = version; + const { data: versions } = await axios + .get( + `https://get.coollabs.io/versions.json` + , { + params: { + appId: process.env['COOLIFY_APP_ID'] || undefined, + version: currentVersion + } + }) + const latestVersion = versions['coolify'].main.version; + const isUpdateAvailable = compareVersions(latestVersion, currentVersion); + if (isUpdateAvailable === 1) { + const activeCount = 0 + if (activeCount === 0) { + if (!isDev) { + console.log(`Updating Coolify to ${latestVersion}.`); + await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); + await asyncExecShell(`env | grep COOLIFY > .env`); + await asyncExecShell( + `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"` + ); + } else { + console.log('Updating (not really in dev mode).'); + } + } + } + } catch (error) { + console.log(error); + } +} +async function checkProxies() { + try { + const { default: isReachable } = await import('is-port-reachable'); + let portReachable; + + const { arch, ipv4, ipv6 } = await listSettings(); + // Coolify Proxy local + const engine = '/var/run/docker.sock'; + const localDocker = await prisma.destinationDocker.findFirst({ + where: { engine, network: 'coolify' } + }); + if (localDocker && localDocker.isCoolifyProxyUsed) { + portReachable = await isReachable(80, { host: ipv4 || ipv6 }) + if (!portReachable) { + await startTraefikProxy(localDocker.id); + } + } + + // TCP Proxies + const databasesWithPublicPort = await prisma.database.findMany({ + where: { publicPort: { not: null } }, + include: { settings: true, destinationDocker: true } + }); + for (const database of databasesWithPublicPort) { + const { destinationDockerId, destinationDocker, publicPort, id } = database; + if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { + const { privatePort } = generateDatabaseConfiguration(database, arch); + portReachable = await isReachable(publicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 }) + if (!portReachable) { + await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); + } + } + } + const wordpressWithFtp = await prisma.wordpress.findMany({ + where: { ftpPublicPort: { not: null } }, + include: { service: { include: { destinationDocker: true } } } + }); + for (const ftp of wordpressWithFtp) { + const { service, ftpPublicPort } = ftp; + const { destinationDockerId, destinationDocker, id } = service; + if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { + portReachable = await isReachable(ftpPublicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 }) + if (!portReachable) { + await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp'); + } + } + } + + // HTTP Proxies + const minioInstances = await prisma.minio.findMany({ + where: { publicPort: { not: null } }, + include: { service: { include: { destinationDocker: true } } } + }); + for (const minio of minioInstances) { + const { service, publicPort } = minio; + const { destinationDockerId, destinationDocker, id } = service; + if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { + portReachable = await isReachable(publicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 }) + if (!portReachable) { + await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000); + } + } + } + } catch (error) { + + } +} +async function cleanupPrismaEngines() { + if (!isDev) { + try { + const { stdout } = await asyncExecShell(`ps -ef | grep /app/prisma-engines/query-engine | grep -v grep | wc -l | xargs`) + if (stdout.trim() != null && stdout.trim() != '' && Number(stdout.trim()) > 1) { + await asyncExecShell(`killall -q -e /app/prisma-engines/query-engine -o 1m`) + } + } catch (error) { + console.log(error); + } + } +} +async function cleanupStorage() { + const destinationDockers = await prisma.destinationDocker.findMany(); + let enginesDone = new Set() + for (const destination of destinationDockers) { + if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return + if (destination.engine) enginesDone.add(destination.engine) + if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress) + + let lowDiskSpace = false; + try { + let stdout = null + if (!isDev) { + const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` }) + stdout = output.stdout; + } else { + const output = await asyncExecShell( + `df -kPT /` + ); + stdout = output.stdout; + } + let lines = stdout.trim().split('\n'); + let header = lines[0]; + let regex = + /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g; + const boundaries = []; + let match; + + while ((match = regex.exec(header))) { + boundaries.push(match[0].length); + } + + boundaries[boundaries.length - 1] = -1; + const data = lines.slice(1).map((line) => { + const cl = boundaries.map((boundary) => { + const column = boundary > 0 ? line.slice(0, boundary) : line; + line = line.slice(boundary); + return column.trim(); + }); + return { + capacity: Number.parseInt(cl[5], 10) / 100 + }; + }); + if (data.length > 0) { + const { capacity } = data[0]; + if (capacity > 0.8) { + lowDiskSpace = true; + } + } + } catch (error) { + console.log(error); + } + await cleanupDockerStorage(destination.id, lowDiskSpace, false) + } +} + +(async () => { + let status = { + cleanupStorage: false, + autoUpdater: false + } + if (parentPort) { + parentPort.on('message', async (message) => { + if (parentPort) { + if (message === 'error') throw new Error('oops'); + if (message === 'cancel') { + parentPort.postMessage('cancelled'); + process.exit(1); + } + if (message === 'action:cleanupStorage') { + if (!status.autoUpdater) { + status.cleanupStorage = true + await cleanupStorage(); + status.cleanupStorage = false + } + return; + } + if (message === 'action:cleanupPrismaEngines') { + await cleanupPrismaEngines(); + return; + } + if (message === 'action:checkProxies') { + await checkProxies(); + return; + } + if (message === 'action:autoUpdater') { + if (!status.cleanupStorage) { + status.autoUpdater = true + await autoUpdater(); + status.autoUpdater = false + } + return; + } + } + }); + } else process.exit(0); +})(); diff --git a/apps/api/src/lib/buildPacks/common.ts b/apps/api/src/lib/buildPacks/common.ts index b463a91db..c66477a0b 100644 --- a/apps/api/src/lib/buildPacks/common.ts +++ b/apps/api/src/lib/buildPacks/common.ts @@ -553,7 +553,7 @@ export async function buildImage({ await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` }) const { status } = await prisma.build.findUnique({ where: { id: buildId } }) if (status === 'canceled') { - throw new Error('Build canceled.') + throw new Error('Deployment canceled.') } if (isCache) { await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId }); diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index e77dc995c..148bacfff 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -19,7 +19,7 @@ import * as serviceFields from './serviceFields' import { saveBuildLog } from './buildPacks/common'; import { scheduler } from './scheduler'; -export const version = '3.8.1'; +export const version = '3.8.2'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; @@ -93,7 +93,6 @@ export const asyncExecShellStream = async ({ debug, buildId, applicationId, comm const { execaCommand } = await import('execa') const subprocess = execaCommand(command, { env: { DOCKER_BUILDKIT: "1", DOCKER_HOST: engine } }) if (debug) { - await saveBuildLog({ line: `=========================`, buildId, applicationId }); subprocess.stdout.on('data', async (data) => { const stdout = data.toString(); const array = stdout.split('\n') @@ -123,7 +122,6 @@ export const asyncExecShellStream = async ({ debug, buildId, applicationId, comm } subprocess.on('exit', async (code) => { await asyncSleep(1000); - await saveBuildLog({ line: `=========================`, buildId, applicationId }); if (code === 0) { resolve(code) } else { @@ -136,9 +134,33 @@ export const asyncExecShellStream = async ({ debug, buildId, applicationId, comm export const asyncSleep = (delay: number): Promise => new Promise((resolve) => setTimeout(resolve, delay)); export const prisma = new PrismaClient({ - errorFormat: 'minimal' + errorFormat: 'minimal', + // log: [ + // { + // emit: 'event', + // level: 'query', + // }, + // { + // emit: 'stdout', + // level: 'error', + // }, + // { + // emit: 'stdout', + // level: 'info', + // }, + // { + // emit: 'stdout', + // level: 'warn', + // }, + // ], }); +// prisma.$on('query', (e) => { + // console.log({e}) + // console.log('Query: ' + e.query) + // console.log('Params: ' + e.params) + // console.log('Duration: ' + e.duration + 'ms') +// }) export const base64Encode = (text: string): string => { return Buffer.from(text).toString('base64'); }; @@ -1871,7 +1893,7 @@ export async function stopBuild(buildId, applicationId) { let count = 0; await new Promise(async (resolve, reject) => { const { destinationDockerId, status } = await prisma.build.findFirst({ where: { id: buildId } }); - const { engine, id: dockerId } = await prisma.destinationDocker.findFirst({ where: { id: destinationDockerId } }); + const { id: dockerId } = await prisma.destinationDocker.findFirst({ where: { id: destinationDockerId } }); const interval = setInterval(async () => { try { if (status === 'failed' || status === 'canceled') { @@ -1881,10 +1903,10 @@ export async function stopBuild(buildId, applicationId) { if (count > 15) { clearInterval(interval); if (scheduler.workers.has('deployApplication')) { - scheduler.workers.get('deployApplication').postMessage("action:flushQueue") + scheduler.workers.get('deployApplication').postMessage('cancel') } - await cleanupDB(buildId); - return reject(new Error('Build canceled')); + await cleanupDB(buildId, applicationId); + return reject(new Error('Deployment canceled.')); } const { stdout: buildContainers } = await executeDockerCmd({ dockerId, command: `docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` }) if (buildContainers) { @@ -1896,9 +1918,9 @@ export async function stopBuild(buildId, applicationId) { await removeContainer({ id, dockerId }); clearInterval(interval); if (scheduler.workers.has('deployApplication')) { - scheduler.workers.get('deployApplication').postMessage("action:flushQueue") + scheduler.workers.get('deployApplication').postMessage('cancel') } - await cleanupDB(buildId); + await cleanupDB(buildId, applicationId); return resolve(); } } @@ -1909,11 +1931,12 @@ export async function stopBuild(buildId, applicationId) { }); } -async function cleanupDB(buildId: string) { +async function cleanupDB(buildId: string, applicationId: string) { const data = await prisma.build.findUnique({ where: { id: buildId } }); if (data?.status === 'queued' || data?.status === 'running') { await prisma.build.update({ where: { id: buildId }, data: { status: 'canceled' } }); } + await saveBuildLog({ line: 'Deployment canceled.', buildId, applicationId }); } export function convertTolOldVolumeNames(type) { diff --git a/apps/api/src/lib/scheduler.ts b/apps/api/src/lib/scheduler.ts index 5d8a57844..ebff53e12 100644 --- a/apps/api/src/lib/scheduler.ts +++ b/apps/api/src/lib/scheduler.ts @@ -9,48 +9,22 @@ Bree.extend(TSBree); const options: any = { defaultExtension: 'js', + // logger: new Cabin(), logger: false, workerMessageHandler: async ({ name, message }) => { - if (name === 'deployApplication') { - if (message.pending === 0 && message.size === 0) { - if (message.caller === 'autoUpdater') { - if (!scheduler.workers.has('autoUpdater')) { - await scheduler.stop('deployApplication'); - await scheduler.run('autoUpdater') - } - } - if (message.caller === 'cleanupStorage') { - if (!scheduler.workers.has('cleanupStorage')) { - await scheduler.run('cleanupStorage') - } - } - + if (name === 'deployApplication' && message?.deploying) { + if (scheduler.workers.has('autoUpdater') || scheduler.workers.has('cleanupStorage')) { + scheduler.workers.get('deployApplication').postMessage('cancel') } } }, jobs: [ - { - name: 'deployApplication' - }, - { - name: 'cleanupStorage', - }, - { - name: 'cleanupPrismaEngines', - interval: '1m' - }, - { - name: 'checkProxies', - interval: '10s' - }, - { - name: 'autoUpdater', - } + { name: 'infrastructure' }, + { name: 'deployApplication' }, ], }; if (isDev) options.root = path.join(__dirname, '../jobs'); - export const scheduler = new Bree(options); diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 2abf30b3a..26b0d2bb9 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -75,7 +75,6 @@ export async function getApplicationStatus(request: FastifyRequest) { isExited = await isContainerExited(application.destinationDocker.id, id); } return { - isQueueActive: scheduler.workers.has('deployApplication'), isRunning, isExited, }; @@ -453,6 +452,8 @@ export async function deployApplication(request: FastifyRequest) { return errorHandler({ status, message }) } } +export async function restartCoolify(request: FastifyRequest) { + try { + const teamId = request.user.teamId; + if (teamId === '0') { + if (!isDev) { + await asyncExecShell(`docker restart coolify`); + return {}; + } else { + console.log('Restarting Coolify') + return {}; + } + } + throw { status: 500, message: 'You are not authorized to restart Coolify.' }; + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} export async function showUsage() { try { return { @@ -101,7 +118,8 @@ export async function showDashboard(request: FastifyRequest) { include: { settings: true } }); const databases = await prisma.database.findMany({ - where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { settings: true } }); const services = await prisma.service.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } } diff --git a/apps/api/src/routes/api/v1/index.ts b/apps/api/src/routes/api/v1/index.ts index 0d145312d..8f9f821f8 100644 --- a/apps/api/src/routes/api/v1/index.ts +++ b/apps/api/src/routes/api/v1/index.ts @@ -1,5 +1,5 @@ import { FastifyPluginAsync } from 'fastify'; -import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually } from './handlers'; +import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually, restartCoolify } from './handlers'; import { GetCurrentUser } from './types'; export interface Update { @@ -47,6 +47,10 @@ const root: FastifyPluginAsync = async (fastify): Promise => { onRequest: [fastify.authenticate] }, async () => await showUsage()); + fastify.post('/internal/restart', { + onRequest: [fastify.authenticate] + }, async (request) => await restartCoolify(request)); + fastify.post('/internal/cleanup', { onRequest: [fastify.authenticate] }, async () => await cleanupManually()); diff --git a/apps/api/src/routes/webhooks/github/handlers.ts b/apps/api/src/routes/webhooks/github/handlers.ts index 968305a97..275a72979 100644 --- a/apps/api/src/routes/webhooks/github/handlers.ts +++ b/apps/api/src/routes/webhooks/github/handlers.ts @@ -142,12 +142,6 @@ export async function gitHubEvents(request: FastifyRequest): Promi type: 'webhook_commit' } }); - scheduler.workers.get('deployApplication').postMessage({ - build_id: buildId, - type: 'webhook_commit', - ...applicationFound - }); - return { message: 'Queued. Thank you!' }; @@ -183,6 +177,8 @@ export async function gitHubEvents(request: FastifyRequest): Promi await prisma.build.create({ data: { id: buildId, + pullmergeRequestId, + sourceBranch, applicationId: applicationFound.id, destinationDockerId: applicationFound.destinationDocker.id, gitSourceId: applicationFound.gitSource.id, @@ -192,14 +188,7 @@ export async function gitHubEvents(request: FastifyRequest): Promi type: 'webhook_pr' } }); - scheduler.workers.get('deployApplication').postMessage({ - build_id: buildId, - type: 'webhook_pr', - ...applicationFound, - sourceBranch, - pullmergeRequestId - }); - + return { message: 'Queued. Thank you!' }; diff --git a/apps/api/src/routes/webhooks/gitlab/handlers.ts b/apps/api/src/routes/webhooks/gitlab/handlers.ts index 0e7f8ec5d..df848e45c 100644 --- a/apps/api/src/routes/webhooks/gitlab/handlers.ts +++ b/apps/api/src/routes/webhooks/gitlab/handlers.ts @@ -89,12 +89,6 @@ export async function gitLabEvents(request: FastifyRequest) { } }); - scheduler.workers.get('deployApplication').postMessage({ - build_id: buildId, - type: 'webhook_commit', - ...applicationFound - }); - return { message: 'Queued. Thank you!' }; @@ -141,6 +135,8 @@ export async function gitLabEvents(request: FastifyRequest) { await prisma.build.create({ data: { id: buildId, + pullmergeRequestId, + sourceBranch, applicationId: applicationFound.id, destinationDockerId: applicationFound.destinationDocker.id, gitSourceId: applicationFound.gitSource.id, @@ -150,14 +146,6 @@ export async function gitLabEvents(request: FastifyRequest) { type: 'webhook_mr' } }); - scheduler.workers.get('deployApplication').postMessage({ - build_id: buildId, - type: 'webhook_mr', - ...applicationFound, - sourceBranch, - pullmergeRequestId - }); - return { message: 'Queued. Thank you!' }; diff --git a/apps/ui/package.json b/apps/ui/package.json index 0a64124f0..cc5fe9009 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -14,20 +14,20 @@ "format": "prettier --write --plugin-search-dir=. ." }, "devDependencies": { - "@playwright/test": "1.24.2", + "@playwright/test": "1.25.1", "@sveltejs/kit": "1.0.0-next.405", "@types/js-cookie": "3.0.2", - "@typescript-eslint/eslint-plugin": "5.33.0", - "@typescript-eslint/parser": "5.33.0", + "@typescript-eslint/eslint-plugin": "5.35.1", + "@typescript-eslint/parser": "5.35.1", "autoprefixer": "10.4.8", - "eslint": "8.21.0", + "eslint": "8.22.0", "eslint-config-prettier": "8.5.0", "eslint-plugin-svelte3": "4.0.0", "postcss": "8.4.16", "prettier": "2.7.1", "prettier-plugin-svelte": "2.7.0", "svelte": "3.49.0", - "svelte-check": "2.8.0", + "svelte-check": "2.8.1", "svelte-preprocess": "4.10.7", "tailwindcss": "3.1.8", "tailwindcss-scrollbar": "0.1.0", @@ -38,8 +38,9 @@ "type": "module", "dependencies": { "@sveltejs/adapter-static": "1.0.0-next.39", + "@tailwindcss/typography": "^0.5.4", "cuid": "2.1.8", - "daisyui": "2.22.0", + "daisyui": "2.24.0", "js-cookie": "3.0.1", "p-limit": "4.0.0", "svelte-select": "4.4.7", diff --git a/apps/ui/src/lib/components/Usage.svelte b/apps/ui/src/lib/components/Usage.svelte index 6868322ad..a461f8419 100644 --- a/apps/ui/src/lib/components/Usage.svelte +++ b/apps/ui/src/lib/components/Usage.svelte @@ -20,11 +20,12 @@ let usageInterval: any; let loading = { usage: false, - cleanup: false + cleanup: false, + restart: false }; - import { appSession } from '$lib/store'; + import { addToast, appSession } from '$lib/store'; import { onDestroy, onMount } from 'svelte'; - import { get } from '$lib/api'; + import { get, post } from '$lib/api'; import { errorNotification } from '$lib/common'; async function getStatus() { if (loading.usage) return; @@ -33,6 +34,25 @@ usage = data.usage; loading.usage = false; } + async function restartCoolify() { + const sure = confirm( + 'Are you sure you would like to restart Coolify? Currently running deployments will be stopped and restarted.' + ); + if (sure) { + loading.restart = true; + try { + await post(`/internal/restart`, {}); + addToast({ + type: 'success', + message: 'Coolify restarted successfully. It will take a moment.' + }); + } catch (error) { + return errorNotification(error); + } finally { + loading.restart = false; + } + } + } onDestroy(() => { clearInterval(usageInterval); }); @@ -48,65 +68,106 @@ return errorNotification(error); } }); + async function manuallyCleanupStorage() { + try { + loading.cleanup = true; + await post('/internal/cleanup', {}); + return addToast({ + message: 'Cleanup done.', + type: 'success' + }); + } catch (error) { + return errorNotification(error); + } finally { + loading.cleanup = false; + } + } -
-
Hardware Details
-
-
-
-
Total Memory
-
+
+
+

Hardware Details

+
+ {#if $appSession.teamId === '0'} + + + {/if} +
+
+
+
+
+
Memory
+
+
Total
+
{(usage?.memory.totalMemMb).toFixed(0)}MB
-
-
Used Memory
-
+ +
+
Used
+
{(usage?.memory.usedMemMb).toFixed(0)}MB
-
-
Free Memory
-
+ +
+
Free
+
{usage?.memory.freeMemPercentage}%
-
-
-
Total CPUs
-
+ +
+
CPU
+
+
Total
+
{usage?.cpu.count}
-
-
CPU Usage
-
+ +
+
Usage
+
{usage?.cpu.usage}%
-
+ +
Load Average (5,10,30mins)
-
{usage?.cpu.load}
+
{usage?.cpu.load}
-
-
-
Total Disk
-
+
+
Disk
+
+
Total
+
{usage?.disk.totalGb}GB
-
-
Used Disk
-
+ +
+
Used
+
{usage?.disk.usedGb}GB
-
-
Free Disk
-
{usage?.disk.freePercentage}%
+ +
+
Free
+
+ {usage?.disk.freePercentage}% +
diff --git a/apps/ui/src/lib/components/svg/applications/Docker.svelte b/apps/ui/src/lib/components/svg/applications/Docker.svelte index 981d4d225..ea1787bd9 100644 --- a/apps/ui/src/lib/components/svg/applications/Docker.svelte +++ b/apps/ui/src/lib/components/svg/applications/Docker.svelte @@ -2,79 +2,8 @@ export let isAbsolute = true; - - - + + + + + diff --git a/apps/ui/src/lib/components/svg/services/Appwrite.svelte b/apps/ui/src/lib/components/svg/services/Appwrite.svelte index fbabd5168..5523a6f09 100644 --- a/apps/ui/src/lib/components/svg/services/Appwrite.svelte +++ b/apps/ui/src/lib/components/svg/services/Appwrite.svelte @@ -4,7 +4,7 @@ diff --git a/apps/ui/src/lib/components/svg/services/Fider.svelte b/apps/ui/src/lib/components/svg/services/Fider.svelte index 6d7ccaba7..e94c94591 100644 --- a/apps/ui/src/lib/components/svg/services/Fider.svelte +++ b/apps/ui/src/lib/components/svg/services/Fider.svelte @@ -5,7 +5,7 @@ diff --git a/apps/ui/src/lib/components/svg/services/Searxng.svelte b/apps/ui/src/lib/components/svg/services/Searxng.svelte index 52cbaed81..bb775d2bf 100644 --- a/apps/ui/src/lib/components/svg/services/Searxng.svelte +++ b/apps/ui/src/lib/components/svg/services/Searxng.svelte @@ -3,7 +3,6 @@ diff --git a/apps/ui/src/routes/applications/[id]/__layout.svelte b/apps/ui/src/routes/applications/[id]/__layout.svelte index c40f66439..83f66dc6f 100644 --- a/apps/ui/src/routes/applications/[id]/__layout.svelte +++ b/apps/ui/src/routes/applications/[id]/__layout.svelte @@ -66,7 +66,6 @@ let loading = false; let statusInterval: any; - let isQueueActive = false; $disabledButton = !$appSession.isAdmin || (!application.fqdn && !application.settings.isBot) || @@ -121,7 +120,6 @@ if ($status.application.loading) return; $status.application.loading = true; const data = await get(`/applications/${id}/status`); - isQueueActive = data.isQueueActive; $status.application.isRunning = data.isRunning; $status.application.isExited = data.isExited; $status.application.loading = false; @@ -259,13 +257,10 @@
handleDeploySubmit(true)}>
{:else} -
+
handleDeploySubmit(false)}>
-
{#if build.status === 'running'} @@ -178,7 +177,7 @@ {#if !noMoreBuilds} {#if buildCount > 5}
-
diff --git a/apps/ui/src/routes/index.svelte b/apps/ui/src/routes/index.svelte index d6cecacfd..1a8734f9b 100644 --- a/apps/ui/src/routes/index.svelte +++ b/apps/ui/src/routes/index.svelte @@ -36,10 +36,6 @@ import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte'; import { dev } from '$app/env'; - let loading = { - cleanup: false - }; - let numberOfGetStatus = 0; function getRndInteger(min: number, max: number) { @@ -75,280 +71,217 @@ numberOfGetStatus--; } } - async function manuallyCleanupStorage() { - try { - loading.cleanup = true; - await post('/internal/cleanup', {}); - return addToast({ - message: 'Cleanup done.', - type: 'success' - }); - } catch (error) { - return errorNotification(error); - } finally { - loading.cleanup = false; - } - }
{$t('index.dashboard')}
- {#if $appSession.teamId === '0'} - - {/if}
-
-
-
- {#if applications.length > 0} -
-
Resources
-
- - - {#each applications as application} - - - - + + + + + + {/if} + {#if application.settings.isBot && application.exposePort} + - - - {/each} - - {#each services as service} - - - - - - - - {/each} - {#each databases as database} - - - - - - - {/each} - -
- {#await getStatus(application)} -
- {:then status} - {#if status === 'Running'} -
- {:else} -
- {/if} - {/await} -
{application.name}
-
- - - - {#if application.fqdn} - - - - - - - {/if} - {#if application.settings.isBot && application.exposePort} - - - - - - - {/if} - - - - - - - - - - - - - - -
- {#await getStatus(service)} -
- {:then status} - {#if status === 'Running'} -
- {:else} -
- {/if} - {/await} -
{service.name}
-
- -
- Service -
-
- {#if service.fqdn} - - - - - - - {/if} - - - - - - - - - - - - - - -
- {#await getStatus(database)} -
- {:then status} - {#if status === 'Running'} -
- {:else} -
- {/if} - {/await} -
{database.name}
-
- - -
- Database -
-
- - - - - - - - - - - - - - -
+ + + + + + + {/if} +
+
+
-
- {:else if $appSession.teamId !== '0'} -
Nothing is configured yet.
- {/if} - {#if $appSession.teamId === '0'} - - {/if} -
+ + {/each} + {:else} +

Nothing is configured yet.

+ {/if} +
+

Services

+
+ + +

Databases

+ diff --git a/apps/ui/tailwind.config.cjs b/apps/ui/tailwind.config.cjs index 91cab4bf1..cc36c7115 100644 --- a/apps/ui/tailwind.config.cjs +++ b/apps/ui/tailwind.config.cjs @@ -62,5 +62,5 @@ module.exports = { scrollbar: ['dark'], extend: {} }, - plugins: [require('tailwindcss-scrollbar'), require('daisyui')] + plugins: [require('tailwindcss-scrollbar'), require('daisyui'), require("@tailwindcss/typography")] }; diff --git a/package.json b/package.json index 9d6fd9250..d3f808b2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "3.8.1", + "version": "3.8.2", "license": "Apache-2.0", "repository": "github:coollabsio/coolify", "scripts": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45e590081..f99ba1f6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,16 +22,17 @@ importers: '@fastify/jwt': 6.3.2 '@fastify/static': 6.5.0 '@iarna/toml': 2.2.5 + '@ladjs/graceful': 3.0.2 '@prisma/client': 3.15.2 - '@types/node': 18.7.11 + '@types/node': 18.7.13 '@types/node-os-utils': 1.3.0 - '@typescript-eslint/eslint-plugin': 5.34.0 - '@typescript-eslint/parser': 5.34.0 + '@typescript-eslint/eslint-plugin': 5.35.1 + '@typescript-eslint/parser': 5.35.1 axios: 0.27.2 bcryptjs: 2.4.3 bree: 9.1.2 cabin: 9.1.2 - compare-versions: 4.1.3 + compare-versions: 4.1.4 cuid: 2.1.8 dayjs: 1.11.5 dockerode: 3.3.4 @@ -52,7 +53,8 @@ importers: node-forge: 1.3.1 node-os-utils: 1.3.7 nodemon: 2.0.19 - p-queue: 7.3.0 + p-all: 4.0.0 + p-throttle: 5.0.0 prettier: 2.7.1 prisma: 3.15.2 public-ip: 6.0.1 @@ -63,7 +65,7 @@ importers: typescript: 4.7.4 unique-names-generator: 4.7.1 dependencies: - '@breejs/ts-worker': 2.0.0_vko2uubnomvm6mw63pxqdtopyq + '@breejs/ts-worker': 2.0.0_rzqxabipis2a5sxrpk4obdh4zu '@fastify/autoload': 5.2.0 '@fastify/cookie': 8.0.0 '@fastify/cors': 8.1.0 @@ -71,12 +73,13 @@ importers: '@fastify/jwt': 6.3.2 '@fastify/static': 6.5.0 '@iarna/toml': 2.2.5 + '@ladjs/graceful': 3.0.2 '@prisma/client': 3.15.2_prisma@3.15.2 axios: 0.27.2 bcryptjs: 2.4.3 bree: 9.1.2 cabin: 9.1.2 - compare-versions: 4.1.3 + compare-versions: 4.1.4 cuid: 2.1.8 dayjs: 1.11.5 dockerode: 3.3.4 @@ -92,16 +95,17 @@ importers: jsonwebtoken: 8.5.1 node-forge: 1.3.1 node-os-utils: 1.3.7 - p-queue: 7.3.0 + p-all: 4.0.0 + p-throttle: 5.0.0 public-ip: 6.0.1 ssh-config: 4.1.6 strip-ansi: 7.0.1 unique-names-generator: 4.7.1 devDependencies: - '@types/node': 18.7.11 + '@types/node': 18.7.13 '@types/node-os-utils': 1.3.0 - '@typescript-eslint/eslint-plugin': 5.34.0_euudt5oqhhodkyae5tf6wjmsda - '@typescript-eslint/parser': 5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq + '@typescript-eslint/eslint-plugin': 5.35.1_ktjxjibzrfqejavile4bhmzhjq + '@typescript-eslint/parser': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq esbuild: 0.15.5 eslint: 8.22.0 eslint-config-prettier: 8.5.0_eslint@8.22.0 @@ -115,16 +119,17 @@ importers: apps/ui: specifiers: - '@playwright/test': 1.24.2 + '@playwright/test': 1.25.1 '@sveltejs/adapter-static': 1.0.0-next.39 '@sveltejs/kit': 1.0.0-next.405 + '@tailwindcss/typography': ^0.5.4 '@types/js-cookie': 3.0.2 - '@typescript-eslint/eslint-plugin': 5.33.0 - '@typescript-eslint/parser': 5.33.0 + '@typescript-eslint/eslint-plugin': 5.35.1 + '@typescript-eslint/parser': 5.35.1 autoprefixer: 10.4.8 cuid: 2.1.8 - daisyui: 2.22.0 - eslint: 8.21.0 + daisyui: 2.24.0 + eslint: 8.22.0 eslint-config-prettier: 8.5.0 eslint-plugin-svelte3: 4.0.0 js-cookie: 3.0.1 @@ -133,7 +138,7 @@ importers: prettier: 2.7.1 prettier-plugin-svelte: 2.7.0 svelte: 3.49.0 - svelte-check: 2.8.0 + svelte-check: 2.8.1 svelte-preprocess: 4.10.7 svelte-select: 4.4.7 sveltekit-i18n: 2.2.2 @@ -144,27 +149,28 @@ importers: vite: 3.0.5 dependencies: '@sveltejs/adapter-static': 1.0.0-next.39 + '@tailwindcss/typography': 0.5.4_tailwindcss@3.1.8 cuid: 2.1.8 - daisyui: 2.22.0_25hquoklqeoqwmt7fwvvcyxm5e + daisyui: 2.24.0_25hquoklqeoqwmt7fwvvcyxm5e js-cookie: 3.0.1 p-limit: 4.0.0 svelte-select: 4.4.7 sveltekit-i18n: 2.2.2_svelte@3.49.0 devDependencies: - '@playwright/test': 1.24.2 + '@playwright/test': 1.25.1 '@sveltejs/kit': 1.0.0-next.405_svelte@3.49.0+vite@3.0.5 '@types/js-cookie': 3.0.2 - '@typescript-eslint/eslint-plugin': 5.33.0_njno5y7ry2l2lcmiu4tywxkwnq - '@typescript-eslint/parser': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq + '@typescript-eslint/eslint-plugin': 5.35.1_ktjxjibzrfqejavile4bhmzhjq + '@typescript-eslint/parser': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq autoprefixer: 10.4.8_postcss@8.4.16 - eslint: 8.21.0 - eslint-config-prettier: 8.5.0_eslint@8.21.0 - eslint-plugin-svelte3: 4.0.0_a7wk4ghvg4hia4trwaglu7p6cq + eslint: 8.22.0 + eslint-config-prettier: 8.5.0_eslint@8.22.0 + eslint-plugin-svelte3: 4.0.0_laaqauvsmoyypsiqkozwyi2fn4 postcss: 8.4.16 prettier: 2.7.1 prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi svelte: 3.49.0 - svelte-check: 2.8.0_vylzxgme5yisu3bsyvcau4hjtq + svelte-check: 2.8.1_vylzxgme5yisu3bsyvcau4hjtq svelte-preprocess: 4.10.7_fje22ktja5v2dh6nbkissncqme tailwindcss: 3.1.8 tailwindcss-scrollbar: 0.1.0_tailwindcss@3.1.8 @@ -211,14 +217,14 @@ packages: engines: {node: '>= 10'} dev: false - /@breejs/ts-worker/2.0.0_vko2uubnomvm6mw63pxqdtopyq: + /@breejs/ts-worker/2.0.0_rzqxabipis2a5sxrpk4obdh4zu: resolution: {integrity: sha512-6anHRcmgYlF7mrm/YVRn6rx2cegLuiY3VBxkkimOTWC/dVQeH336imVSuIKEGKTwiuNTPr2hswVdDSneNuXg3A==} engines: {node: '>= 12.11'} peerDependencies: bree: '>=9.0.0' dependencies: bree: 9.1.2 - ts-node: 10.8.2_yuzmdm4dwbtqx7oi4ad7vmtwja + ts-node: 10.8.2_57uwcby55h6tzvkj3v5sfcgxoe tsconfig-paths: 4.1.0 transitivePeerDependencies: - '@swc/core' @@ -374,6 +380,14 @@ packages: resolution: {integrity: sha512-hZere0rUga8kTzSTFbHREXpD9E/jwi94+B5RyLAmMIzl/w/EK1z7rFEnMHzPkU4AZkL42JWSsGXoV8LXMihybg==} dev: false + /@ladjs/graceful/3.0.2: + resolution: {integrity: sha512-T4Z+0R0zgZfR32KIs3FEuH7oFSnhj3c+00wVtp07aeIl8PDfQGcXGB3C8SfOZ2EzPMRuIpIul5kHkVBdSTULXw==} + engines: {node: '>=14'} + dependencies: + lil-http-terminator: 1.2.2 + p-is-promise: 3.0.0 + dev: false + /@leichtgewicht/ip-codec/2.0.4: resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} dev: false @@ -401,13 +415,13 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.13.0 - /@playwright/test/1.24.2: - resolution: {integrity: sha512-Q4X224pRHw4Dtkk5PoNJplZCokLNvVbXD9wDQEMrHcEuvWpJWEQDeJ9gEwkZ3iCWSFSWBshIX177B231XW4wOQ==} + /@playwright/test/1.25.1: + resolution: {integrity: sha512-IJ4X0yOakXtwkhbnNzKkaIgXe6df7u3H3FnuhI9Jqh+CdO0e/lYQlDLYiyI9cnXK8E7UAppAWP+VqAv6VX7HQg==} engines: {node: '>=14'} hasBin: true dependencies: - '@types/node': 18.6.5 - playwright-core: 1.24.2 + '@types/node': 18.7.13 + playwright-core: 1.25.1 dev: true /@prisma/client/3.15.2_prisma@3.15.2: @@ -513,6 +527,17 @@ packages: defer-to-connect: 2.0.1 dev: false + /@tailwindcss/typography/0.5.4_tailwindcss@3.1.8: + resolution: {integrity: sha512-QEdg40EmGvE7kKoDei8zr5sf4D1pIayHj4R31bH3lX8x2BtTiR+jNejYPOkhbmy3DXgkMF9jC8xqNiGFAuL9Sg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + tailwindcss: 3.1.8 + dev: false + /@tsconfig/node10/1.0.8: resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==} dev: false @@ -534,7 +559,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 18.7.11 + '@types/node': 18.7.13 '@types/responselike': 1.0.0 dev: false @@ -557,7 +582,7 @@ packages: /@types/keyv/3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.7.11 + '@types/node': 18.7.13 dev: false /@types/lodash/4.14.182: @@ -568,16 +593,8 @@ packages: resolution: {integrity: sha512-XwVteWQx/XkfRPyaGkw8dEbrCAkoRZ73pI3XznUYIpzbCfpQB3UnDlR5TnmdhetlT889tUJGF8QWo9xrgTpsiA==} dev: true - /@types/node/18.0.3: - resolution: {integrity: sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==} - dev: true - - /@types/node/18.6.5: - resolution: {integrity: sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==} - dev: true - - /@types/node/18.7.11: - resolution: {integrity: sha512-KZhFpSLlmK/sdocfSAjqPETTMd0ug6HIMIAwkwUpU79olnZdQtMxpQP+G1wDzCH7na+FltSIhbaZuKdwZ8RDrw==} + /@types/node/18.7.13: + resolution: {integrity: sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -590,17 +607,17 @@ packages: /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 18.7.11 + '@types/node': 18.7.13 dev: false /@types/sass/1.43.1: resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} dependencies: - '@types/node': 18.0.3 + '@types/node': 18.7.13 dev: true - /@typescript-eslint/eslint-plugin/5.33.0_njno5y7ry2l2lcmiu4tywxkwnq: - resolution: {integrity: sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==} + /@typescript-eslint/eslint-plugin/5.35.1_ktjxjibzrfqejavile4bhmzhjq: + resolution: {integrity: sha512-RBZZXZlI4XCY4Wzgy64vB+0slT9+yAPQRjj/HSaRwUot33xbDjF1oN9BLwOLTewoOI0jothIltZRe9uJCHf8gg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -610,37 +627,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq - '@typescript-eslint/scope-manager': 5.33.0 - '@typescript-eslint/type-utils': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq - '@typescript-eslint/utils': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq - debug: 4.3.4 - eslint: 8.21.0 - functional-red-black-tree: 1.0.1 - ignore: 5.2.0 - regexpp: 3.2.0 - semver: 7.3.7 - tsutils: 3.21.0_typescript@4.7.4 - typescript: 4.7.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/eslint-plugin/5.34.0_euudt5oqhhodkyae5tf6wjmsda: - resolution: {integrity: sha512-eRfPPcasO39iwjlUAMtjeueRGuIrW3TQ9WseIDl7i5UWuFbf83yYaU7YPs4j8+4CxUMIsj1k+4kV+E+G+6ypDQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/parser': 5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq - '@typescript-eslint/scope-manager': 5.34.0 - '@typescript-eslint/type-utils': 5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq - '@typescript-eslint/utils': 5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq + '@typescript-eslint/parser': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq + '@typescript-eslint/scope-manager': 5.35.1 + '@typescript-eslint/type-utils': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq + '@typescript-eslint/utils': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq debug: 4.3.4 eslint: 8.22.0 functional-red-black-tree: 1.0.1 @@ -653,8 +643,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.33.0_qugx7qdu5zevzvxaiqyxfiwquq: - resolution: {integrity: sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==} + /@typescript-eslint/parser/5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq: + resolution: {integrity: sha512-XL2TBTSrh3yWAsMYpKseBYTVpvudNf69rPOWXWVBI08My2JVT5jR66eTt4IgQFHA/giiKJW5dUD4x/ZviCKyGg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -663,29 +653,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.33.0 - '@typescript-eslint/types': 5.33.0 - '@typescript-eslint/typescript-estree': 5.33.0_typescript@4.7.4 - debug: 4.3.4 - eslint: 8.21.0 - typescript: 4.7.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/parser/5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq: - resolution: {integrity: sha512-SZ3NEnK4usd2CXkoV3jPa/vo1mWX1fqRyIVUQZR4As1vyp4fneknBNJj+OFtV8WAVgGf+rOHMSqQbs2Qn3nFZQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 5.34.0 - '@typescript-eslint/types': 5.34.0 - '@typescript-eslint/typescript-estree': 5.34.0_typescript@4.7.4 + '@typescript-eslint/scope-manager': 5.35.1 + '@typescript-eslint/types': 5.35.1 + '@typescript-eslint/typescript-estree': 5.35.1_typescript@4.7.4 debug: 4.3.4 eslint: 8.22.0 typescript: 4.7.4 @@ -693,24 +663,16 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager/5.33.0: - resolution: {integrity: sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==} + /@typescript-eslint/scope-manager/5.35.1: + resolution: {integrity: sha512-kCYRSAzIW9ByEIzmzGHE50NGAvAP3wFTaZevgWva7GpquDyFPFcmvVkFJGWJJktg/hLwmys/FZwqM9EKr2u24Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.33.0 - '@typescript-eslint/visitor-keys': 5.33.0 + '@typescript-eslint/types': 5.35.1 + '@typescript-eslint/visitor-keys': 5.35.1 dev: true - /@typescript-eslint/scope-manager/5.34.0: - resolution: {integrity: sha512-HNvASMQlah5RsBW6L6c7IJ0vsm+8Sope/wu5sEAf7joJYWNb1LDbJipzmdhdUOnfrDFE6LR1j57x1EYVxrY4ow==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.34.0 - '@typescript-eslint/visitor-keys': 5.34.0 - dev: true - - /@typescript-eslint/type-utils/5.33.0_qugx7qdu5zevzvxaiqyxfiwquq: - resolution: {integrity: sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==} + /@typescript-eslint/type-utils/5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq: + resolution: {integrity: sha512-8xT8ljvo43Mp7BiTn1vxLXkjpw8wS4oAc00hMSB4L1/jIiYbjjnc3Qp2GAUOG/v8zsNCd1qwcqfCQ0BuishHkw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -719,26 +681,7 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/utils': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq - debug: 4.3.4 - eslint: 8.21.0 - tsutils: 3.21.0_typescript@4.7.4 - typescript: 4.7.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/type-utils/5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq: - resolution: {integrity: sha512-Pxlno9bjsQ7hs1pdWRUv9aJijGYPYsHpwMeCQ/Inavhym3/XaKt1ZKAA8FIw4odTBfowBdZJDMxf2aavyMDkLg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '*' - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/utils': 5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq + '@typescript-eslint/utils': 5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq debug: 4.3.4 eslint: 8.22.0 tsutils: 3.21.0_typescript@4.7.4 @@ -747,18 +690,13 @@ packages: - supports-color dev: true - /@typescript-eslint/types/5.33.0: - resolution: {integrity: sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==} + /@typescript-eslint/types/5.35.1: + resolution: {integrity: sha512-FDaujtsH07VHzG0gQ6NDkVVhi1+rhq0qEvzHdJAQjysN+LHDCKDKCBRlZFFE0ec0jKxiv0hN63SNfExy0KrbQQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/types/5.34.0: - resolution: {integrity: sha512-49fm3xbbUPuzBIOcy2CDpYWqy/X7VBkxVN+DC21e0zIm3+61Z0NZi6J9mqPmSW1BDVk9FIOvuCFyUPjXz93sjA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@typescript-eslint/typescript-estree/5.33.0_typescript@4.7.4: - resolution: {integrity: sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==} + /@typescript-eslint/typescript-estree/5.35.1_typescript@4.7.4: + resolution: {integrity: sha512-JUqE1+VRTGyoXlDWWjm6MdfpBYVq+hixytrv1oyjYIBEOZhBCwtpp5ZSvBt4wIA1MKWlnaC2UXl2XmYGC3BoQA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -766,8 +704,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.33.0 - '@typescript-eslint/visitor-keys': 5.33.0 + '@typescript-eslint/types': 5.35.1 + '@typescript-eslint/visitor-keys': 5.35.1 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -778,55 +716,16 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.34.0_typescript@4.7.4: - resolution: {integrity: sha512-mXHAqapJJDVzxauEkfJI96j3D10sd567LlqroyCeJaHnu42sDbjxotGb3XFtGPYKPD9IyLjhsoULML1oI3M86A==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.34.0 - '@typescript-eslint/visitor-keys': 5.34.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.3.7 - tsutils: 3.21.0_typescript@4.7.4 - typescript: 4.7.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/utils/5.33.0_qugx7qdu5zevzvxaiqyxfiwquq: - resolution: {integrity: sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==} + /@typescript-eslint/utils/5.35.1_4rv7y5c6xz3vfxwhbrcxxi73bq: + resolution: {integrity: sha512-v6F8JNXgeBWI4pzZn36hT2HXXzoBBBJuOYvoQiaQaEEjdi5STzux3Yj8v7ODIpx36i/5s8TdzuQ54TPc5AITQQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 5.33.0 - '@typescript-eslint/types': 5.33.0 - '@typescript-eslint/typescript-estree': 5.33.0_typescript@4.7.4 - eslint: 8.21.0 - eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.21.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@typescript-eslint/utils/5.34.0_4rv7y5c6xz3vfxwhbrcxxi73bq: - resolution: {integrity: sha512-kWRYybU4Rn++7lm9yu8pbuydRyQsHRoBDIo11k7eqBWTldN4xUdVUMCsHBiE7aoEkFzrUEaZy3iH477vr4xHAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 5.34.0 - '@typescript-eslint/types': 5.34.0 - '@typescript-eslint/typescript-estree': 5.34.0_typescript@4.7.4 + '@typescript-eslint/scope-manager': 5.35.1 + '@typescript-eslint/types': 5.35.1 + '@typescript-eslint/typescript-estree': 5.35.1_typescript@4.7.4 eslint: 8.22.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.22.0 @@ -835,19 +734,11 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys/5.33.0: - resolution: {integrity: sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==} + /@typescript-eslint/visitor-keys/5.35.1: + resolution: {integrity: sha512-cEB1DvBVo1bxbW/S5axbGPE6b7FIMAbo3w+AGq6zNDA7+NYJOIkKj/sInfTv4edxd4PxJSgdN4t6/pbvgA+n5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.33.0 - eslint-visitor-keys: 3.3.0 - dev: true - - /@typescript-eslint/visitor-keys/5.34.0: - resolution: {integrity: sha512-O1moYjOSrab0a2fUvFpsJe0QHtvTC+cR+ovYpgKrAVXzqQyc74mv76TgY6z+aEtjQE2vgZux3CQVtGryqdcOAw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.34.0 + '@typescript-eslint/types': 5.35.1 eslint-visitor-keys: 3.3.0 dev: true @@ -2073,8 +1964,8 @@ packages: delayed-stream: 1.0.0 dev: false - /compare-versions/4.1.3: - resolution: {integrity: sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg==} + /compare-versions/4.1.4: + resolution: {integrity: sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==} dev: false /component-emitter/1.3.0: @@ -2224,8 +2115,8 @@ packages: resolution: {integrity: sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==} dev: false - /daisyui/2.22.0_25hquoklqeoqwmt7fwvvcyxm5e: - resolution: {integrity: sha512-H08aQP+Mfl2fQOmyaxd1NP54gQbr2xRUj2MmFFvfJ5EDGbthVBICDMNFKyia/zBfR58G8eTJo3CmydglM81/7Q==} + /daisyui/2.24.0_25hquoklqeoqwmt7fwvvcyxm5e: + resolution: {integrity: sha512-Fdu/4LCdTfWLWAbCuPxvnaRotEfJ+hVPgZ2kv/aUk9RZ00Yk8fGdJtIf0kXJ3IgUKOr8rCXUpfQY6DQU9usPCQ==} peerDependencies: autoprefixer: ^10.0.2 postcss: ^8.1.6 @@ -2984,15 +2875,6 @@ packages: engines: {node: '>=12'} dev: false - /eslint-config-prettier/8.5.0_eslint@8.21.0: - resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - dependencies: - eslint: 8.21.0 - dev: true - /eslint-config-prettier/8.5.0_eslint@8.22.0: resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} hasBin: true @@ -3019,13 +2901,13 @@ packages: prettier-linter-helpers: 1.0.0 dev: true - /eslint-plugin-svelte3/4.0.0_a7wk4ghvg4hia4trwaglu7p6cq: + /eslint-plugin-svelte3/4.0.0_laaqauvsmoyypsiqkozwyi2fn4: resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==} peerDependencies: eslint: '>=8.0.0' svelte: ^3.2.0 dependencies: - eslint: 8.21.0 + eslint: 8.22.0 svelte: 3.49.0 dev: true @@ -3045,16 +2927,6 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.21.0: - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 8.21.0 - eslint-visitor-keys: 2.1.0 - dev: true - /eslint-utils/3.0.0_eslint@8.22.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} @@ -3075,54 +2947,6 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.21.0: - resolution: {integrity: sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint/eslintrc': 1.3.0 - '@humanwhocodes/config-array': 0.10.4 - '@humanwhocodes/gitignore-to-minimatch': 1.0.2 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.21.0 - eslint-visitor-keys: 3.3.0 - espree: 9.3.3 - esquery: 1.4.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - functional-red-black-tree: 1.0.1 - glob-parent: 6.0.2 - globals: 13.15.0 - globby: 11.1.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.0 - import-fresh: 3.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.1 - regexpp: 3.2.0 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - text-table: 0.2.0 - v8-compile-cache: 2.3.0 - transitivePeerDependencies: - - supports-color - dev: true - /eslint/8.22.0: resolution: {integrity: sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3223,10 +3047,6 @@ packages: engines: {node: '>=6'} dev: false - /eventemitter3/4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: false - /execa/6.1.0: resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4179,6 +3999,11 @@ packages: set-cookie-parser: 2.4.8 dev: false + /lil-http-terminator/1.2.2: + resolution: {integrity: sha512-2n6gKJIKgPjy4JfSlwsQnAA7wK4SEA1cegqdsYwr7qObVfIHdELjDGjEcYWJanfF/u/mRzIT2WPqhpzC6R9pZw==} + engines: {node: '>=8'} + dev: false + /lilconfig/2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} engines: {node: '>=10'} @@ -4254,6 +4079,10 @@ packages: lodash._basetostring: 4.12.0 dev: false + /lodash.castarray/4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + dev: false + /lodash.includes/4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} dev: false @@ -4280,7 +4109,6 @@ packages: /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true /lodash.omit/4.5.0: resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==} @@ -4703,6 +4531,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /p-all/4.0.0: + resolution: {integrity: sha512-QXqMc8PpYu0gmNM6VcKP0uYqeI+dtvSNeaDb8ktnNjposr+nftHHCSYbj/S/oUceF6R868jw1XOxkJKUSiHgEQ==} + engines: {node: '>=12.20'} + dependencies: + p-map: 5.5.0 + dev: false + /p-cancelable/3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -4713,6 +4548,11 @@ packages: engines: {node: '>=4'} dev: false + /p-is-promise/3.0.0: + resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==} + engines: {node: '>=8'} + dev: false + /p-limit/2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4754,12 +4594,16 @@ packages: p-limit: 3.1.0 dev: true - /p-queue/7.3.0: - resolution: {integrity: sha512-5fP+yVQ0qp0rEfZoDTlP2c3RYBgxvRsw30qO+VtPPc95lyvSG+x6USSh1TuLB4n96IO6I8/oXQGsTgtna4q2nQ==} + /p-map/5.5.0: + resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} engines: {node: '>=12'} dependencies: - eventemitter3: 4.0.7 - p-timeout: 5.0.2 + aggregate-error: 4.0.1 + dev: false + + /p-throttle/5.0.0: + resolution: {integrity: sha512-iXBFjW4kP/5Ivw7uC9EDnj+/xo3pNn4Rws3zgMGPwXnWTv1M3P0LVdZxLrqRUI5JK0Fp3Du0bt6lCaVrI3WF7g==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false /p-timeout/3.2.0: @@ -4769,11 +4613,6 @@ packages: p-finally: 1.0.0 dev: false - /p-timeout/5.0.2: - resolution: {integrity: sha512-sEmji9Yaq+Tw+STwsGAE56hf7gMy9p0tQfJojIAamB7WHJYJKf1qlsg9jqBWG8q9VCxKPhZaP/AcXwEoBcYQhQ==} - engines: {node: '>=12'} - dev: false - /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -4954,8 +4793,8 @@ packages: find-up: 3.0.0 dev: false - /playwright-core/1.24.2: - resolution: {integrity: sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA==} + /playwright-core/1.25.1: + resolution: {integrity: sha512-lSvPCmA2n7LawD2Hw7gSCLScZ+vYRkhU8xH0AapMyzwN+ojoDqhkH/KIEUxwNu2PjPoE/fcE0wLAksdOhJ2O5g==} engines: {node: '>=14'} hasBin: true dev: true @@ -5751,8 +5590,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /svelte-check/2.8.0_vylzxgme5yisu3bsyvcau4hjtq: - resolution: {integrity: sha512-HRL66BxffMAZusqe5I5k26mRWQ+BobGd9Rxm3onh7ZVu0nTk8YTKJ9vu3LVPjUGLU9IX7zS+jmwPVhJYdXJ8vg==} + /svelte-check/2.8.1_vylzxgme5yisu3bsyvcau4hjtq: + resolution: {integrity: sha512-cibyY1sgt3ONIDnQbSgV2X9AJFhwEslRHNo95lijrYfPzVEvTvbmL2ohsUyqB5L7j1GhLXtQbjCJ4lZZ/fwbeQ==} hasBin: true peerDependencies: svelte: ^3.24.0 @@ -5988,7 +5827,7 @@ packages: engines: {node: '>=0.10.0'} dev: true - /ts-node/10.8.2_yuzmdm4dwbtqx7oi4ad7vmtwja: + /ts-node/10.8.2_57uwcby55h6tzvkj3v5sfcgxoe: resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} hasBin: true peerDependencies: @@ -6007,7 +5846,7 @@ packages: '@tsconfig/node12': 1.0.9 '@tsconfig/node14': 1.0.1 '@tsconfig/node16': 1.0.2 - '@types/node': 18.7.11 + '@types/node': 18.7.13 acorn: 8.8.0 acorn-walk: 8.2.0 arg: 4.1.3