diff --git a/.dockerignore b/.dockerignore index 3fd3b242a..ad52155e3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,11 @@ .DS_Store node_modules -/.svelte /build -/functions -.pnpm-store -.pnpm-debug.log +/.svelte-kit +/package + +.env +.env.stag +/db/*.db +/db/*.db-journal +/data/haproxy/haproxy.cfg \ No newline at end of file diff --git a/.env.template b/.env.template index 589fe1380..30bd7cf21 100644 --- a/.env.template +++ b/.env.template @@ -1,35 +1,5 @@ -#################################### -# Domain where your Coolify instance will be available and reachable. -# It's the same as you set in Github OAuth App and Github App as . -DOMAIN= -## Let's Encrypt contact email required -EMAIL= - -# JWT Token Sign Key for logging you in to Coolify's frontend -JWT_SIGN_KEY= -# Encryption key for SECRETS - do NOT share it with others! -SECRETS_ENCRYPTION_KEY= - -# Docker Engine -DOCKER_ENGINE=/var/run/docker.sock -# Docker network to use internally between the proxy and your apps -DOCKER_NETWORK=coollabs - -# Mongodb -# Values in case if you are using our Mongodb installation - CHANGE user and password fields! -MONGODB_HOST=coollabs-mongodb -MONGODB_PORT=27017 -MONGODB_USER=supercooldbuser -MONGODB_PASSWORD=developmentPassword4db -MONGODB_DB=coolLabs-prod - -# Frontend only variables -VITE_GITHUB_APP_CLIENTID= -VITE_GITHUB_APP_NAME= - -# Github OAuth & App secrets and private key - you can get it from Github. -GITHUB_APP_CLIENT_SECRET= -GITHUP_APP_WEBHOOK_SECRET= - -# It should look like this. Newlines breaks with \n -GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA7Y+Uwkd8FINSwFktWGdtwCaOAazTDYR8ucEzGyR9r+ooJZhF\nOc32qgDSps6Q5DsqPOzvfhiviqU+et9VF+bJhfdzwJ+Le86QZH1RgsDMoY049XvI\nKSwP........" +COOLIFY_APP_ID= +COOLIFY_SECRET_KEY= +COOLIFY_DATABASE_URL=file:../db/prod.db +COOLIFY_SENTRY_DSN= +COOLIFY_IS_ON="docker" \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index fba386194..3ccf435f0 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -10,7 +10,7 @@ module.exports = { }, parserOptions: { sourceType: 'module', - ecmaVersion: 2019 + ecmaVersion: 2020 }, env: { browser: true, diff --git a/.gitignore b/.gitignore index 82beb8504..38eeaa479 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,13 @@ -/node_modules -/.svelte -/.svelte-kit -/.pnpm-store -/build -/functions -.env .DS_Store -.pnpm-debug.log \ No newline at end of file +node_modules +/build +/.svelte-kit +/package + +.env +.env.prod +.env.stag +/db/*.db +/db/*.db-journal +/data/haproxy/haproxy.cfg +/data/haproxy/haproxy.cfg.lkg diff --git a/.husky/_/.gitignore b/.husky/_/.gitignore new file mode 100644 index 000000000..f59ec20aa --- /dev/null +++ b/.husky/_/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..dc0378c34 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged \ No newline at end of file diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000..f56aabfec --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "**/*.{js,jsx,ts,tsx,cjs,svelte,json,css,scss,md,yaml}": [ + "prettier --ignore-path .gitignore --write --plugin-search-dir=." + ] +} diff --git a/.npmrc b/.npmrc deleted file mode 100644 index b6f27f135..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 82a5e3e12..000000000 --- a/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -.svelte/** -static/** -build/** -node_modules/** -.svelte-kit/** \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..83d513cec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM node:16.14.0-alpine +WORKDIR /app +COPY package*.json . +RUN yarn install +COPY . . +RUN yarn build + +FROM node:16.14.0-alpine +WORKDIR /app + +RUN apk add --no-cache git openssh-client curl jq cmake sqlite + +RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6 +RUN pnpm add -g pnpm + +RUN curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz" | tar -xzvf - docker/docker -C . --strip-components 1 && mv docker /usr/bin/docker +RUN mkdir -p ~/.docker/cli-plugins/ +RUN curl -SL https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose +RUN chmod +x ~/.docker/cli-plugins/docker-compose + +COPY --from=0 /app/docker-compose.yaml . +COPY --from=0 /app/build . +COPY --from=0 /app/package.json . +COPY --from=0 /app/node_modules ./node_modules +COPY --from=0 /app/prisma ./prisma + +EXPOSE 3000 +CMD ["pnpm", "start"] \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 29ebfa545..000000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. \ No newline at end of file diff --git a/README.md b/README.md index 6295f42a6..d0bdd2563 100644 --- a/README.md +++ b/README.md @@ -2,57 +2,11 @@ An open-source, hassle-free, self-hostable Heroku & Netlify alternative. -## Demo +## Status -[Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm) +This version is not usable yet. But it will be: -## Installation - -Installation is automated with the following command: - -```bash -/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)" -``` - -## Features - -- Deploy Pull Request automatically, so you can review them quickly! -- You can deploy any of the following applications, databases and services easily. - -(constantly growing lists) - -### Applications - -With Github integration - -- Static sites -- NodeJS -- VueJS -- NuxtJS -- NextJS -- React/Preact -- NextJS -- Gatsby -- Svelte -- PHP -- Rust -- or any custom dockerfile - -### Databases - -- MongoDB -- MySQL -- PostgreSQL -- CouchDB -- Redis - -### Services - -- [WordPress](https://wordpress.org) -- [Plausible Analytics](https://plausible.io) -- [NocoDB](https://nocodb.com) -- [VSCode Server](https://github.com/cdr/code-server) -- [MinIO](https://min.io) +![](https://media.tenor.com/images/4554562afdb531e5fe62e301afd22ea2/tenor.gif) ## Support diff --git a/data/docker/daemon.json b/data/docker/daemon.json new file mode 100644 index 000000000..d1a836def --- /dev/null +++ b/data/docker/daemon.json @@ -0,0 +1,11 @@ +{ + "log-driver": "json-file", + "log-opts": { + "max-size": "100m", + "max-file": "5" + }, + "features": { + "buildkit": true + }, + "live-restore": true +} diff --git a/data/haproxy/dataplaneapi.hcl b/data/haproxy/dataplaneapi.hcl new file mode 100644 index 000000000..e914c1399 --- /dev/null +++ b/data/haproxy/dataplaneapi.hcl @@ -0,0 +1,29 @@ +config_version = 2 +name = "easy_gar" +mode = "single" +status = "null" + +dataplaneapi { + host = "0.0.0.0" + port = 5555 + + transaction { + transaction_dir = "/tmp/haproxy" + } + + advertised { + api_address = "" + api_port = 0 + } +} + +haproxy { + config_file = "/usr/local/etc/haproxy/haproxy.cfg" + haproxy_bin = "/usr/local/sbin/haproxy" + + reload { + reload_delay = 2 + reload_cmd = "kill -HUP 1" + restart_cmd = "kill -SIGUSR2 1" + } +} diff --git a/data/haproxy/haproxy.cfg-http.template b/data/haproxy/haproxy.cfg-http.template new file mode 100644 index 000000000..72fdece7a --- /dev/null +++ b/data/haproxy/haproxy.cfg-http.template @@ -0,0 +1,19 @@ +global + log stdout format raw local0 debug + +defaults + mode http + log global + timeout http-request 60s + timeout connect 10s + timeout client 60s + timeout server 60s + +frontend "${APP}" + mode http + bind *:"${PORT}" name "${APP}" + default_backend "${APP}" + +backend "${APP}" + mode http + server "${APP}" "${APP}":"${PRIVATE_PORT}" check diff --git a/data/haproxy/haproxy.cfg-tcp.template b/data/haproxy/haproxy.cfg-tcp.template new file mode 100644 index 000000000..b45644d03 --- /dev/null +++ b/data/haproxy/haproxy.cfg-tcp.template @@ -0,0 +1,15 @@ +global + log stdout format raw local0 debug + +defaults + mode tcp + log global + +frontend "${APP}" + mode tcp + bind *:"${PORT}" name "${APP}" + default_backend "${APP}" + +backend "${APP}" + mode tcp + server "${APP}" "${APP}":"${PRIVATE_PORT}" check diff --git a/data/haproxy/haproxy.cfg.template b/data/haproxy/haproxy.cfg.template new file mode 100644 index 000000000..c39cb7878 --- /dev/null +++ b/data/haproxy/haproxy.cfg.template @@ -0,0 +1,38 @@ +global + stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners + log stdout format raw local0 debug + +defaults + mode http + log global + timeout http-request 60s + timeout connect 10s + timeout client 60s + timeout server 60s + +userlist haproxy-dataplaneapi + user admin insecure-password "${HAPROXY_PASSWORD}" + +frontend http + mode http + bind :80 + bind :443 ssl crt /usr/local/etc/haproxy/ssl/ alpn h2,http/1.1 + acl is_certbot path_beg /.well-known/acme-challenge/ + use_backend backend-certbot if is_certbot + use_backend %[req.hdr(host),lower] + +frontend stats + bind *:8404 + stats enable + stats uri / + stats refresh 5s + stats admin if TRUE + stats auth "${HAPROXY_USERNAME}:${HAPROXY_PASSWORD}" + +backend backend-certbot + mode http + server certbot host.docker.internal:9080 + +program api + command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi + no option start-on-reload diff --git a/data/haproxy/ssl/default.pem b/data/haproxy/ssl/default.pem new file mode 100644 index 000000000..77e0746ce --- /dev/null +++ b/data/haproxy/ssl/default.pem @@ -0,0 +1,81 @@ +-----BEGIN CERTIFICATE----- +MIIFETCCAvkCFE/5JtU5geT5hOjFuQPiLgCYHwsOMA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjExMDExMDkwNzQ1WhcNMzExMDA5MDkw +NzQ1WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEArEZDhvc3ew2Gb8pvJlUrh5x+L5iqNxDYU3cOcOgeELCmOyAS +cH+/1xrsHQI05xWPpz6VAja2NKl4OP90getPPkiQV1xAg5/gsOsRL8Pi/MwvQKfZ +ObyW3t+sfFb1K3sVnm8bgk5F9OIVyAtzAx+Y53muEJsHOHpaEidnwbY2VE0zQB/G +DBQovrMefAwmH4RPqFor6NzFMKVRi33pQjYmcfCVFZylrDeCn8T7llV0lrnWqv6z +sGKfL3E4nHvyh/RsGNOXy+XQMxB9SA3j6hFTNtgQIPO/lxptz/+BLZoUt48nHZtr +sc5j+3sn8c1O9e6MjI/1q8lvZsk7ZsWCGSwCOvJ9LnxCWOEQUUfqIvGLsk7NJQgf +IkodZH9sW5Sjlro21+WBf3nvqlZ8g7r6K1RJOA8AtUiCaN/+o65t86WkwCSwQXcm ++nArcwddOx2HN9sFrjJ59N1eYEDGmyK3BdppYuVXay705PmxotR1hCBvnXOb34dn +gZxsxFTohr97JvEdNtGSNz4USyZPjgIMF/Gu8ruh0gQ1byhmayRqMGEqMAh58Lvb +3HYsd3Bf+LB9PpaXLAdKzsTZ8a28zyDYo8a70h7iBRxhmFwa+Df+pSmUEdzhejfx +7jEslhBQSQDmllaHrHc1G6H/w/u+04vi1joaLeLEGQclinKLeU88s9j3zzUCAwEA +ATANBgkqhkiG9w0BAQsFAAOCAgEAGQED96wBGzbMUlk9mIvZeLerzEAB3YfgfAYa +EAi79QHxM8UX06xmA2xtGvJSvlU8Xods9vxpBmIUnbDRTIAHNDApT19+vPg/iSfQ +1J9Fo4b5kjmWL6SalEdYcxqH9V/QndHta4MXP91u/ZsJ/exwDTZFatXsfGkPjUmN +Xp+Ip6iQg7+kV3JpRnMSbevj2Oujs7qTAdQedH38ZTNS0AaM5gvZyQkccCTKNBQ4 +3O8MhCau7U0EUirndqsQXa0D3o78FpKztLNXSM7919jU2y36kMrWXfArfrBKHJ9b +nZeO7nkbHgvmVS8NTg9pR7L7u+YXTa2p1H2ZnpMQvruV7iL/Pb1H2N68UdvnQScL +sgacGSzM6b6PVdWRbECiuzC0UyWLZo/LoU3DQFGoiDQ4e/B3+TMrvgFI0CnpAQ4w +qiaVFJlRQeF4GaS4qHsN28OBliFATB3TXONFnz1aVkQlEHuh2+JbuL1b1lxvlX5t +gBbu/GgAcP4Uy2z4PoDmempAvNi2kCcLB98m+jbFSMSB3nkrdj6MzyN7kW9bhk3T +ClimxDmc23seprwLcxJUPP5q+HRB1VLKXLwIYxu+Up3g29d4k1Iy9nUUP9lITLTk +blJxZ2BPuQqTLzyqmAEWa1HxljFC1b7oMp9a98PbxC3MxUggM7zx/rgXWxM8osib +uwSZmw0= +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEArEZDhvc3ew2Gb8pvJlUrh5x+L5iqNxDYU3cOcOgeELCmOyAS +cH+/1xrsHQI05xWPpz6VAja2NKl4OP90getPPkiQV1xAg5/gsOsRL8Pi/MwvQKfZ +ObyW3t+sfFb1K3sVnm8bgk5F9OIVyAtzAx+Y53muEJsHOHpaEidnwbY2VE0zQB/G +DBQovrMefAwmH4RPqFor6NzFMKVRi33pQjYmcfCVFZylrDeCn8T7llV0lrnWqv6z +sGKfL3E4nHvyh/RsGNOXy+XQMxB9SA3j6hFTNtgQIPO/lxptz/+BLZoUt48nHZtr +sc5j+3sn8c1O9e6MjI/1q8lvZsk7ZsWCGSwCOvJ9LnxCWOEQUUfqIvGLsk7NJQgf +IkodZH9sW5Sjlro21+WBf3nvqlZ8g7r6K1RJOA8AtUiCaN/+o65t86WkwCSwQXcm ++nArcwddOx2HN9sFrjJ59N1eYEDGmyK3BdppYuVXay705PmxotR1hCBvnXOb34dn +gZxsxFTohr97JvEdNtGSNz4USyZPjgIMF/Gu8ruh0gQ1byhmayRqMGEqMAh58Lvb +3HYsd3Bf+LB9PpaXLAdKzsTZ8a28zyDYo8a70h7iBRxhmFwa+Df+pSmUEdzhejfx +7jEslhBQSQDmllaHrHc1G6H/w/u+04vi1joaLeLEGQclinKLeU88s9j3zzUCAwEA +AQKCAgEAm1/z33Jwk4crTQAjJ0uBqxm1pW/ndSq4MO8cEzEGjL8F7iWK+/P8LiGV ++sPWuuRzX7/N3OVDiFOgnqeniNWV7vK7XE9T0GMN4ALiyVW/D4mIxKOeA7jXycOq +aap0DPdCFFbZVLkL10Vhp77LyHFjEsJn/4oTBRk0y1LG/as9bOMD6j29/X7hEL20 +LOU4LQzEW26YU7lqD+nKlijFjHYSTolRrOBPe/fE1BxxXLFOKfMKbcaygc8xCzTu +fhQ8Nep45BtSuQ9Yq/WfSLFecemWR8yvH0k37yxjBknHVD23maZ+/PEEPKWM/2+g +IzGsmZrBILVmOb2/v9CWxqY0JEfQ6aU/nLW1ZiXSOIPmKEooK/hPVxFIyQ1yET4G +kQZ5RroY/QDrI14ms8P0iDzZ8K3EFKUyjiBbc83Mb0YIZ4hKd76gioOUIPeEQB+y +QLZ8Cb9YS3V8uIOJg+F4xlpJSePAZphfSxRLojSiKUeCs8gNUxGz0zwiMNf1p76F +8CaLgvSwT/cgQjWitMeeE1Ha+8lY8VzESmd10gPk2uES/qdrmMhwFwovPqqrtMqj +kMrFKNy7Crka6me3dhKEtryRTk5ho2IS/VCy/eXQ7lUW8Cl4uFxmjpHYSJMqDWvC +vu1p3/B1psSZIy2V2M9QqwZCysHqvGJMOCvYmnc6T62+kDRKQ7ECggEBAOS/1ptA +75OBAsHLovkspiCvn3gb/VTvH6LOvxYTohjr5iBeX137vg0aR1rg0jwcdf8EEJYw +4YxOid7KmV7O25ujzduQgwpVgujnJAeBLeLDC5dVbq3PQah61AvR2O/7t+Ls3oxi +cWh/OHC6SeZ/n406cxSCCUpVwtgHTaNFzaSmpDdEOSbjvXjQQjiRsG7j/1u64riq +RlJ/hIUlcys0g94yeN/5lPaNfsq0+vTSAYuTVVXVbEntwWcZVZxnQJviZVgJ99zM +RzE3sprvvr+I5QQ4FRMn0W9U7gblSJd5FGEL8gye4SRd+LxoUL4DR6pfuwd0vlXA +g+dgiOKoHm2Bb8cCggEBAMDMHMNR6uipdMivPjBTlklnaYd9SY3c5x65yNtx4CNh +rXyvy/6YvME7PPnKQZ8TQ4DkbVDUCAF7wnyAJJ7eWMav3bNlqWWjzaBvQz4Fn0XG +/1W5R1CoJ9DW5FY3f9efJzQTmfn6dIlCx1gW7XfVBZQqI1LORMWUYenk0KAvjlg2 +UHYYl/BT1RhtYyzOJHto1PaUvCNDiOiDWAkTpLigYm7hGgVmcSwbo4F54SNUHdV7 +yz3CorCM4VsEYYSL80WHYxf/Zc+mcIDoWOdog0iEeK/Zu++yG5lPRxC1862GmsZA +J06BMqX+NVGOfiGcVaGH+SZJXeFcrr7F8ZWp6y38QSMCggEAJwzo4hAv1gqMIfFV +nRwWMDZLDwIYOUupJu4MiQRJA+AhpRz3QuAbDbmSvNzshv6E1kgnXLxzhLRTrQkB +LcI6k1NfbUA6XqVCd+gdqnpPDwslC2y2PE3Jc62kTXBBjJZ4SfEN/QFBQwmU5Qmo +XAUlg8KaqsGYPGxvmtmEU38zIAyitByddRoj2mATLf0RFZ0ulsZMtiG7Z5IFWYWP +J60LZf9Py0ycNYrqPkivHuRLBzzbsI+CsQw5nBQjHVQzH2mCy4jIG5V0Ad70Sqbq +9V+1WQcJ8f82Lb9d8ydpQRKWfArCA42L+d1g/SkBv65nqZo2H4u6goEfA3zjYW45 +44/ZOQKCAQB440MhwYqe6ioc76z51l+ElUAZQZjOR/XvUSS9XHDjHosOhJhPgmvQ +aZl5MrXkzcpk1lYo+Vovu+8d66eKqfZWVs2XgCYwYf48G6e5CwNsWDOgB7XMwDN/ +Ak9YNCKIC/Yj9Cp3EPDjZCjkdjPeEIcX+Tf+4vFCRiEC7INX/ZmufBgFhLQ4cAhM +8cHexT8g1oG6P1acces1h626u0NstLwjtCeBvVM3CfmC5O4jHco7Iw00I4epVhyz +2lJfLvWR4itjT7QB+OXQHmAocWLoJJAcC1WJHU+q2IfB1aT+aElCB9XdpqsgY/4A +rm0uG/2hdEXoGNaxyVCUtD8fzdR2GBarAoIBACCYXXREMYb6i1TbR5Q2LvVQUPsO +Hgnbr+PLmx93rfUzDcr5r+cJgryjYQDKJTRleDJhg80M3RYOq+IOdl6yxOmRATmJ +ZDgwRVD1F6VFxBJePcAW30FI5CoBogsHaZQDKGsopEaDRLK5E3QHUVG5qj323RdI +Unf1++wI4nw+qwsVf1gSTcAdzq29v3NIWUyvvrmTNO4MxFTt0/lqkCsdT/2EFQDB +/yQ1HCtQQjXE1xlYh0BnMZp9+4FmrlMC9Oj5H0dDSWmInPION0ft8/SjBj4TQ5Qi +2DUo1WOWQnVR8Bxz0B8McXS+dOmgLe8ws4/ez7DoEVqHTgirKqBg5qRFQKw= +-----END RSA PRIVATE KEY----- diff --git a/db/.gitkeep b/db/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml new file mode 100644 index 000000000..41f287f2b --- /dev/null +++ b/docker-compose-dev.yaml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + redis: + image: 'bitnami/redis:6.2' + container_name: coolify-redis + environment: + - ALLOW_EMPTY_PASSWORD=yes + networks: + - coolify-infra + ports: + - target: 6379 + published: 6379 + protocol: tcp + mode: host + +networks: + coolify-infra: + attachable: true + name: coolify-infra diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml deleted file mode 100644 index 57cc0f1fb..000000000 --- a/docker-compose-dev.yml +++ /dev/null @@ -1,84 +0,0 @@ -version: '3.8' - -services: - proxy: - image: traefik:v2.4 - hostname: coollabs-proxy - ports: - - target: 80 - published: 80 - protocol: tcp - mode: host - - target: 443 - published: 443 - protocol: tcp - mode: host - - target: 8080 - published: 8080 - protocol: tcp - mode: host - command: - - --api.insecure=true - - --api.dashboard=true - - --api.debug=true - - --log.level=ERROR - - --providers.docker=true - - --providers.docker.swarmMode=true - - --providers.docker.exposedbydefault=false - - --providers.docker.network=coollabs - - --providers.docker.swarmModeRefreshSeconds=1s - - --entrypoints.web.address=:80 - - --entrypoints.websecure.address=:443 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - networks: - - coollabs - deploy: - update_config: - parallelism: 1 - delay: 10s - order: start-first - replicas: 1 - placement: - constraints: - - node.role == manager - labels: - - 'traefik.enable=true' - - 'traefik.http.routers.api.entrypoints=websecure' - - 'traefik.http.routers.api.service=api@internal' - - 'traefik.http.routers.api.middlewares=auth' - - 'traefik.http.services.traefik.loadbalancer.server.port=80' - - # Global redirect www to non-www - - 'traefik.http.routers.www-catchall.rule=hostregexp(`{host:www.(.+)}`)' - - 'traefik.http.routers.www-catchall.entrypoints=web' - - 'traefik.http.routers.www-catchall.middlewares=redirect-www-to-nonwww' - - "traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.regex=^http://(?:www\\.)?(.+)" - - 'traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.replacement=http://$${1}' - mongodb: - image: bitnami/mongodb:4.4 - hostname: coollabs-mongodb - ports: - - target: 27017 - published: 27017 - protocol: tcp - mode: host - environment: - - MONGODB_DISABLE_SYSTEM_LOG=true - - MONGODB_ROOT_PASSWORD=developmentPassword4db - - MONGODB_USERNAME=supercooldbuser - - MONGODB_PASSWORD=developmentPassword4db - - MONGODB_DATABASE=coolify - volumes: - - coollabs-mongodb-data:/bitnami/mongodb - networks: - - coollabs - -volumes: - coollabs-mongodb-data: {} - -networks: - coollabs: - driver: overlay - name: coollabs - external: true diff --git a/docker-compose-haproxy.yaml b/docker-compose-haproxy.yaml new file mode 100644 index 000000000..66b35acd6 --- /dev/null +++ b/docker-compose-haproxy.yaml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + haproxy: + image: coollabsio/coolify-haproxy-alpine:latest + container_name: coolify-haproxy + extra_hosts: + - 'host.docker.internal:host-gateway' + networks: + - coolify + volumes: + - './data/haproxy/:/usr/local/etc/haproxy/' + ports: + - '80:80' + - '443:443' + - '8404:8404' + - '5555:5555' + - '3306:3306' + +networks: + coolify: + attachable: true + name: coolify diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..66160f0e6 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,43 @@ +version: '3.8' + +services: + coolify: + image: coollabsio/coolify:latest + restart: always + container_name: coolify + ports: + - target: 3000 + published: 3000 + protocol: tcp + mode: host + volumes: + - 'coolify-db:/app/db' + - 'coolify-ssl-certs:/app/ssl' + - 'coolify-letsencrypt:/etc/letsencrypt' + - '/var/run/docker.sock:/var/run/docker.sock' + env_file: + - '.env' + networks: + - coolify-infra + depends_on: ['redis'] + redis: + image: bitnami/redis:6.2 + restart: always + container_name: coolify-redis + environment: + - ALLOW_EMPTY_PASSWORD=yes + networks: + - coolify-infra + +networks: + coolify-infra: + attachable: true + name: coolify-infra + +volumes: + coolify-db: + name: coolify-db + coolify-ssl-certs: + name: coolify-ssl-certs + coolify-letsencrypt: + name: coolify-letsencrypt diff --git a/haproxy-http.Dockerfile b/haproxy-http.Dockerfile new file mode 100644 index 000000000..089ae0f96 --- /dev/null +++ b/haproxy-http.Dockerfile @@ -0,0 +1,6 @@ +FROM haproxytech/haproxy-alpine:2.5 +RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe + +COPY data/haproxy/haproxy.cfg-http.template /usr/local/etc/haproxy/haproxy.cfg +COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl +COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem \ No newline at end of file diff --git a/haproxy-tcp.Dockerfile b/haproxy-tcp.Dockerfile new file mode 100644 index 000000000..bddcbe541 --- /dev/null +++ b/haproxy-tcp.Dockerfile @@ -0,0 +1,6 @@ +FROM haproxytech/haproxy-alpine:2.5 +RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe + +COPY data/haproxy/haproxy.cfg-tcp.template /usr/local/etc/haproxy/haproxy.cfg +COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl +COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem \ No newline at end of file diff --git a/haproxy.Dockerfile b/haproxy.Dockerfile new file mode 100644 index 000000000..45e2d9d71 --- /dev/null +++ b/haproxy.Dockerfile @@ -0,0 +1,6 @@ +FROM haproxytech/haproxy-alpine:2.5 +RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe + +COPY data/haproxy/haproxy.cfg.template /usr/local/etc/haproxy/haproxy.cfg +COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl +COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem \ No newline at end of file diff --git a/install.sh b/install.sh deleted file mode 100644 index 33889e714..000000000 --- a/install.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -preTasks() { -echo ' -############################## -#### Pulling Git Updates ##### -##############################' -GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git pull - -if [ $? -ne 0 ]; then - echo ' -#################################### -#### Ooops something not okay! ##### -####################################' - exit 1 -fi - -echo ' -############################## -#### Building Base Image ##### -##############################' -docker build --label coolify-reserve=true -t coolify-base -f install/Dockerfile-base . - -if [ $? -ne 0 ]; then - echo ' -#################################### -#### Ooops something not okay! ##### -####################################' - exit 1 -fi - -echo ' -################################## -#### Checking configuration. ##### -##################################' -docker run --rm -w /usr/src/app coolify-base node install/install.js --check -if [ $? -ne 0 ]; then - echo ' -################################## -#### Missing configuration ! ##### -##################################' - exit 1 -fi -} -case "$1" in - "all") - preTasks - echo ' -################################# -#### Rebuilding everything. ##### -#################################' - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type all - ;; - "coolify") - preTasks - echo ' -############################## -#### Rebuilding Coolify. ##### -##############################' - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type coolify - ;; - "proxy") - preTasks - echo ' -############################ -#### Rebuilding Proxy. ##### -############################' - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type proxy - ;; - "upgrade-phase-1") - preTasks - echo ' -################################ -#### Upgrading Coolify P1. ##### -################################' - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type upgrade - ;; - "upgrade-phase-2") - echo ' -################################ -#### Upgrading Coolify P2. ##### -################################' - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/update.js --type upgrade - ;; - *) - exit 1 - ;; -esac \ No newline at end of file diff --git a/install/Dockerfile b/install/Dockerfile deleted file mode 100644 index a9b3a30b3..000000000 --- a/install/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM coolify-base -WORKDIR /usr/src/app -RUN pnpm build -CMD ["pnpm", "start"] -EXPOSE 3000 \ No newline at end of file diff --git a/install/Dockerfile-base b/install/Dockerfile-base deleted file mode 100644 index 04778a30e..000000000 --- a/install/Dockerfile-base +++ /dev/null @@ -1,19 +0,0 @@ -FROM ubuntu:20.04 as binaries -RUN apt update && apt install -y curl gnupg2 ca-certificates -RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - -RUN echo 'deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable' >> /etc/apt/sources.list -RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o /usr/bin/envsubst -RUN chmod +x /usr/bin/envsubst -RUN apt update && apt install -y docker-ce-cli && apt clean all - -FROM node:14 as modules -COPY --from=binaries /usr/bin/docker /usr/bin/docker -COPY --from=binaries /usr/bin/envsubst /usr/bin/envsubst -RUN curl -L https://pnpm.js.org/pnpm.js | node - add --global pnpm -WORKDIR /usr/src/app -COPY ./package*.json . -RUN pnpm install - -FROM modules -WORKDIR /usr/src/app -COPY . . \ No newline at end of file diff --git a/install/Dockerfile-dev b/install/Dockerfile-dev deleted file mode 100644 index a5a63844c..000000000 --- a/install/Dockerfile-dev +++ /dev/null @@ -1,10 +0,0 @@ -FROM node:latest -LABEL coolify-preserve=true -WORKDIR /usr/src/app -RUN curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz | tar -xzvf - docker/docker -C . --strip-components 1 -RUN mv /usr/src/app/docker /usr/bin/docker -RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o /usr/bin/envsubst -RUN curl -L https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -o /usr/bin/jq -RUN chmod +x /usr/bin/envsubst /usr/bin/jq /usr/bin/docker -ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache -RUN curl -f https://get.pnpm.io/v6.js | node - add --global pnpm \ No newline at end of file diff --git a/install/Dockerfile-new b/install/Dockerfile-new deleted file mode 100644 index c0c46b08e..000000000 --- a/install/Dockerfile-new +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:lts -LABEL coolify-preserve=true -WORKDIR /usr/src/app -RUN curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz | tar -xzvf - docker/docker -C . --strip-components 1 -RUN mv /usr/src/app/docker /usr/bin/docker -RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o /usr/bin/envsubst -RUN curl -L https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -o /usr/bin/jq -RUN chmod +x /usr/bin/envsubst /usr/bin/jq /usr/bin/docker -RUN curl -f https://get.pnpm.io/v6.js | node - add --global pnpm -COPY ./*package.json . -RUN pnpm install -COPY . . -RUN pnpm build -CMD ["pnpm", "start"] -EXPOSE 3000 \ No newline at end of file diff --git a/install/README.md b/install/README.md deleted file mode 100644 index 9b9f89f5a..000000000 --- a/install/README.md +++ /dev/null @@ -1,10 +0,0 @@ -Some of the files are here for backwards compatibility. - -I will do things after 2 months: - -- rm ./install.js and ./update.js -- rm ../install.sh -- rm ./Dockerfile-base -- rm ./obs -- rm ./check.js "No need to check env file. During installation, it is checked by the installer. If you change it between to upgrades: 🤷‍♂️" -- Rename Dockerfile-new to Dockerfile diff --git a/install/coolify-template.yml b/install/coolify-template.yml deleted file mode 100644 index 3ced0cadf..000000000 --- a/install/coolify-template.yml +++ /dev/null @@ -1,97 +0,0 @@ -version: '3.8' - -services: - proxy: - image: traefik:v2.4 - hostname: coollabs-proxy - ports: - - target: 80 - published: 80 - protocol: tcp - mode: host - - target: 443 - published: 443 - protocol: tcp - mode: host - command: - - --api.insecure=false - - --api.dashboard=false - - --api.debug=false - - --log.level=ERROR - - --providers.docker=true - - --providers.docker.swarmMode=true - - --providers.docker.exposedbydefault=false - - --providers.docker.network=${DOCKER_NETWORK} - - --providers.docker.swarmModeRefreshSeconds=1s - - --entrypoints.web.address=:80 - - --entrypoints.websecure.address=:443 - - --certificatesresolvers.letsencrypt.acme.httpchallenge=true - - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web - - --certificatesresolvers.letsencrypt.acme.email=${EMAIL} - - --certificatesresolvers.letsencrypt.acme.storage=/data/coolify/acme.json - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /data/coolify:/data/coolify - networks: - - ${DOCKER_NETWORK} - deploy: - update_config: - parallelism: 1 - delay: 10s - order: start-first - replicas: 1 - placement: - constraints: - - node.role == manager - labels: - - 'traefik.enable=true' - - 'traefik.http.routers.api.entrypoints=websecure' - - 'traefik.http.routers.api.service=api@internal' - - 'traefik.http.routers.api.middlewares=auth' - - 'traefik.http.services.traefik.loadbalancer.server.port=80' - - 'traefik.http.services.traefik.loadbalancer.server.port=443' - - # Global redirect www to non-www - - 'traefik.http.routers.www-catchall.rule=hostregexp(`{host:www.(.+)}`)' - - 'traefik.http.routers.www-catchall.entrypoints=web' - - 'traefik.http.routers.www-catchall.middlewares=redirect-www-to-nonwww' - - "traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.regex=^http://(?:www\\.)?(.+)" - - 'traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.replacement=http://$$$${1}' - - # Global redirect http to https - - 'traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)' - - 'traefik.http.routers.http-catchall.entrypoints=web' - - 'traefik.http.routers.http-catchall.middlewares=redirect-to-https' - - - 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https' - - 'traefik.http.middlewares.global-compress.compress=true' - - coolify: - image: coolify - hostname: coollabs-coolify - env_file: - - .env - networks: - - ${DOCKER_NETWORK} - command: 'yarn start' - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - deploy: - update_config: - parallelism: 1 - delay: 10s - order: start-first - replicas: 1 - labels: - - 'traefik.enable=true' - - 'traefik.http.routers.coolify.entrypoints=websecure' - - 'traefik.http.routers.coolify.tls.certresolver=letsencrypt' - - 'traefik.http.routers.coolify.rule=Host(`${DOMAIN}`) && PathPrefix(`/`)' - - 'traefik.http.services.coolify.loadbalancer.server.port=3000' - - 'traefik.http.routers.coolify.middlewares=global-compress' - -networks: - ${DOCKER_NETWORK}: - driver: overlay - name: ${DOCKER_NETWORK} - external: true diff --git a/install/install.js b/install/install.js deleted file mode 100644 index 0366bc3b7..000000000 --- a/install/install.js +++ /dev/null @@ -1,36 +0,0 @@ -require('dotenv').config(); -const { program } = require('commander'); -const shell = require('shelljs'); -const user = shell.exec('whoami', { silent: true }).stdout.replace('\n', ''); - -program.version('0.0.1'); -program - .option('-d, --debug', 'Debug outputs.') - .option('-c, --check', 'Only checks configuration.') - .option('-t, --type ', 'Deploy type.'); - -program.parse(process.argv); - -const options = program.opts(); - -if (user !== 'root') { - console.error(`Please run as root! Current user: ${user}`); - process.exit(1); -} -shell.exec(`docker network create ${process.env.DOCKER_NETWORK} --driver overlay`, { - silent: !options.debug -}); -shell.exec('docker build -t coolify -f install/Dockerfile .'); -if (options.type === 'all') { - shell.exec('docker stack rm coollabs-coolify', { silent: !options.debug }); -} else if (options.type === 'coolify') { - shell.exec('docker service rm coollabs-coolify_coolify'); -} else if (options.type === 'proxy') { - shell.exec('docker service rm coollabs-coolify_proxy'); -} -if (options.type !== 'upgrade') { - shell.exec( - 'set -a && source .env && set +a && envsubst < install/coolify-template.yml | docker stack deploy -c - coollabs-coolify', - { silent: !options.debug, shell: '/bin/bash' } - ); -} diff --git a/install/update.js b/install/update.js deleted file mode 100644 index 1137937ba..000000000 --- a/install/update.js +++ /dev/null @@ -1,24 +0,0 @@ -require('dotenv').config(); -const { program } = require('commander'); -const shell = require('shelljs'); -const user = shell.exec('whoami', { silent: true }).stdout.replace('\n', ''); -program.version('0.0.1'); -program - .option('-d, --debug', 'Debug outputs.') - .option('-c, --check', 'Only checks configuration.') - .option('-t, --type ', 'Deploy type.'); - -program.parse(process.argv); -const options = program.opts(); -if (user !== 'root') { - console.error(`Please run as root! Current user: ${user}`); - process.exit(1); -} - -if (options.type === 'upgrade') { - shell.exec('docker service rm coollabs-coolify_coolify'); - shell.exec( - 'set -a && source .env && set +a && envsubst < install/coolify-template.yml | docker stack deploy -c - coollabs-coolify', - { silent: !options.debug, shell: '/bin/bash' } - ); -} diff --git a/package.json b/package.json index 6a31f3533..3f6d3f6c8 100644 --- a/package.json +++ b/package.json @@ -1,61 +1,83 @@ { "name": "coolify", - "description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.", - "version": "1.0.24", + "description": "An open-source & self-hostable Heroku / Netlify alternative.", + "version": "2.0.0", "license": "AGPL-3.0", "scripts": { - "dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d", - "dev:docker:stop": "docker-compose -f docker-compose-dev.yml down", - "dev": "TAILWIND_MODE=watch NODE_ENV=development svelte-kit dev --host 0.0.0.0", - "build": "NODE_ENV=production svelte-kit build", + "dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", + "dev:stop": "docker-compose -f docker-compose-dev.yaml down", + "dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10", + "studio": "npx prisma studio", + "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js", + "build": "svelte-kit build", "preview": "svelte-kit preview", - "start": "node build", - "lint": "prettier --check . && eslint --ignore-path .gitignore .", - "format": "prettier --write ." + "check": "svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", + "db:generate": "prisma generate", + "db:push": "prisma db push && prisma generate", + "db:seed": "prisma db seed", + "prerelease": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .", + "release:coolify": "cross-var yarn prerelease && docker push coollabsio/coolify:$npm_package_version && docker image push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest", + "release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine", + "release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine", + "release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine", + "prepare": "husky install" }, "devDependencies": { - "@sveltejs/adapter-node": "1.0.0-next.39", - "@sveltejs/kit": "1.0.0-next.142", - "@types/dockerode": "^3.2.3", - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", - "autoprefixer": "^10.2.6", - "cssnano": "^5.0.5", - "eslint": "^7.28.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-svelte3": "^3.2.0", - "postcss": "^8.3.0", - "postcss-load-config": "^3.0.1", - "prettier": "~2.3.1", - "prettier-plugin-svelte": "^2.3.0", - "svelte": "^3.38.2", - "svelte-preprocess": "^4.7.3", - "tailwindcss": "2.2.4", - "tslib": "^2.2.0", - "typescript": "^4.3.2", - "vite": "^2.3.6" + "@sveltejs/adapter-node": "1.0.0-next.67", + "@sveltejs/adapter-static": "1.0.0-next.27", + "@sveltejs/kit": "1.0.0-next.259", + "@types/bcrypt": "5.0.0", + "@types/js-cookie": "3.0.1", + "@types/node": "17.0.16", + "@types/node-forge": "1.0.0", + "@typescript-eslint/eslint-plugin": "4.31.1", + "@typescript-eslint/parser": "4.31.1", + "@zerodevx/svelte-toast": "0.6.3", + "autoprefixer": "10.4.2", + "cross-var": "1.1.0", + "eslint": "7.32.0", + "eslint-config-prettier": "8.3.0", + "eslint-plugin-svelte3": "3.2.1", + "husky": "7.0.4", + "lint-staged": "12.3.3", + "postcss": "8.4.6", + "prettier": "2.5.1", + "prettier-plugin-svelte": "2.6.0", + "prettier-plugin-tailwindcss": "0.1.7", + "prisma": "3.9.1", + "svelte": "3.46.4", + "svelte-check": "2.4.3", + "svelte-preprocess": "4.10.3", + "tailwindcss": "3.0.19", + "ts-node": "10.5.0", + "tslib": "2.3.1", + "typescript": "4.5.5" }, "type": "module", "dependencies": { - "@iarna/toml": "^2.2.5", - "@zerodevx/svelte-toast": "^0.3.0", - "bcrypt": "^5.0.1", - "commander": "^7.2.0", - "compare-versions": "^3.6.0", - "cookie": "^0.4.1", - "cuid": "^2.1.8", - "dayjs": "^1.10.5", - "dockerode": "^3.3.0", - "dotenv-extended": "^2.9.0", - "generate-password": "^1.6.0", - "js-yaml": "^4.1.0", - "jsonwebtoken": "^8.5.1", - "microtip": "^0.2.2", - "mongoose": "^5.12.13", - "shelljs": "^0.8.4", - "svelte-kit-cookie-session": "^1.3.1", - "svelte-select": "^3.17.0", - "systeminformation": "^5.7.7", - "unique-names-generator": "^4.5.0" + "@iarna/toml": "2.2.5", + "@prisma/client": "3.9.1", + "@sentry/node": "6.17.6", + "bcrypt": "5.0.1", + "bullmq": "1.69.0", + "compare-versions": "4.1.3", + "cookie": "0.4.2", + "cuid": "2.1.8", + "dayjs": "1.10.7", + "dockerode": "3.3.1", + "dotenv-extended": "2.9.0", + "generate-password": "1.7.0", + "get-port": "6.0.0", + "got": "12.0.1", + "js-cookie": "3.0.1", + "js-yaml": "4.1.0", + "jsonwebtoken": "8.5.1", + "node-forge": "1.2.1", + "svelte-kit-cookie-session": "2.0.3", + "unique-names-generator": "4.6.0" + }, + "prisma": { + "seed": "node prisma/seed.cjs" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6d4a9d79..76d4ab997 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,140 +1,168 @@ lockfileVersion: 5.3 specifiers: - '@iarna/toml': ^2.2.5 - '@sveltejs/adapter-node': 1.0.0-next.33 - '@sveltejs/kit': 1.0.0-next.125 - '@types/dockerode': ^3.2.3 - '@typescript-eslint/eslint-plugin': ^4.26.1 - '@typescript-eslint/parser': ^4.26.1 - '@zerodevx/svelte-toast': ^0.3.0 - autoprefixer: ^10.2.6 - bcrypt: ^5.0.1 - commander: ^7.2.0 - compare-versions: ^3.6.0 - cookie: ^0.4.1 - cssnano: ^5.0.5 - cuid: ^2.1.8 - dayjs: ^1.10.5 - dockerode: ^3.3.0 - dotenv-extended: ^2.9.0 - eslint: ^7.28.0 - eslint-config-prettier: ^8.3.0 - eslint-plugin-svelte3: ^3.2.0 - generate-password: ^1.6.0 - js-yaml: ^4.1.0 - jsonwebtoken: ^8.5.1 - microtip: ^0.2.2 - mongoose: ^5.12.13 - postcss: ^8.3.0 - postcss-load-config: ^3.0.1 - prettier: ~2.3.1 - prettier-plugin-svelte: ^2.3.0 - shelljs: ^0.8.4 - svelte: ^3.38.2 - svelte-kit-cookie-session: ^1.0.6 - svelte-preprocess: ^4.7.3 - svelte-select: ^3.17.0 - systeminformation: ^5.7.7 - tailwindcss: 2.2.4 - tslib: ^2.2.0 - typescript: ^4.3.2 - unique-names-generator: ^4.5.0 - vite: ^2.3.6 + '@iarna/toml': 2.2.5 + '@prisma/client': 3.9.1 + '@sentry/node': 6.17.6 + '@sveltejs/adapter-node': 1.0.0-next.67 + '@sveltejs/adapter-static': 1.0.0-next.27 + '@sveltejs/kit': 1.0.0-next.259 + '@types/bcrypt': 5.0.0 + '@types/js-cookie': 3.0.1 + '@types/node': 17.0.16 + '@types/node-forge': 1.0.0 + '@typescript-eslint/eslint-plugin': 4.31.1 + '@typescript-eslint/parser': 4.31.1 + '@zerodevx/svelte-toast': 0.6.3 + autoprefixer: 10.4.2 + bcrypt: 5.0.1 + bullmq: 1.69.0 + compare-versions: 4.1.3 + cookie: 0.4.2 + cross-var: 1.1.0 + cuid: 2.1.8 + dayjs: 1.10.7 + dockerode: 3.3.1 + dotenv-extended: 2.9.0 + eslint: 7.32.0 + eslint-config-prettier: 8.3.0 + eslint-plugin-svelte3: 3.2.1 + generate-password: 1.7.0 + get-port: 6.0.0 + got: 12.0.1 + husky: 7.0.4 + js-cookie: 3.0.1 + js-yaml: 4.1.0 + jsonwebtoken: 8.5.1 + lint-staged: 12.3.3 + node-forge: 1.2.1 + postcss: 8.4.6 + prettier: 2.5.1 + prettier-plugin-svelte: 2.6.0 + prettier-plugin-tailwindcss: 0.1.7 + prisma: 3.9.1 + svelte: 3.46.4 + svelte-check: 2.4.3 + svelte-kit-cookie-session: 2.0.3 + svelte-preprocess: 4.10.3 + tailwindcss: 3.0.19 + ts-node: 10.5.0 + tslib: 2.3.1 + typescript: 4.5.5 + unique-names-generator: 4.6.0 dependencies: '@iarna/toml': 2.2.5 - '@zerodevx/svelte-toast': 0.3.0 + '@prisma/client': 3.9.1_prisma@3.9.1 + '@sentry/node': 6.17.6 bcrypt: 5.0.1 - commander: 7.2.0 - compare-versions: 3.6.0 - cookie: 0.4.1 + bullmq: 1.69.0 + compare-versions: 4.1.3 + cookie: 0.4.2 cuid: 2.1.8 - dayjs: 1.10.5 - dockerode: 3.3.0 + dayjs: 1.10.7 + dockerode: 3.3.1 dotenv-extended: 2.9.0 - generate-password: 1.6.0 + generate-password: 1.7.0 + get-port: 6.0.0 + got: 12.0.1 + js-cookie: 3.0.1 js-yaml: 4.1.0 jsonwebtoken: 8.5.1 - microtip: 0.2.2 - mongoose: 5.12.13 - shelljs: 0.8.4 - svelte-kit-cookie-session: 1.0.6 - svelte-select: 3.17.0 - systeminformation: 5.7.7 - unique-names-generator: 4.5.0 + node-forge: 1.2.1 + svelte-kit-cookie-session: 2.0.3 + unique-names-generator: 4.6.0 devDependencies: - '@sveltejs/adapter-node': 1.0.0-next.33 - '@sveltejs/kit': 1.0.0-next.125_svelte@3.38.2 - '@types/dockerode': 3.2.3 - '@typescript-eslint/eslint-plugin': 4.26.1_c8cbd5e7f5f92609ec78d991aced454b - '@typescript-eslint/parser': 4.26.1_eslint@7.28.0+typescript@4.3.2 - autoprefixer: 10.2.6_postcss@8.3.0 - cssnano: 5.0.5_postcss@8.3.0 - eslint: 7.28.0 - eslint-config-prettier: 8.3.0_eslint@7.28.0 - eslint-plugin-svelte3: 3.2.0_eslint@7.28.0+svelte@3.38.2 - postcss: 8.3.0 - postcss-load-config: 3.0.1 - prettier: 2.3.1 - prettier-plugin-svelte: 2.3.0_prettier@2.3.1+svelte@3.38.2 - svelte: 3.38.2 - svelte-preprocess: 4.7.3_ddfd8490a44ef0de606159ff3aef985a - tailwindcss: 2.2.4_6daa0ece57b4377652e73c9c66c3b94c - tslib: 2.2.0 - typescript: 4.3.2 - vite: 2.3.6 + '@sveltejs/adapter-node': 1.0.0-next.67 + '@sveltejs/adapter-static': 1.0.0-next.27 + '@sveltejs/kit': 1.0.0-next.259_svelte@3.46.4 + '@types/bcrypt': 5.0.0 + '@types/js-cookie': 3.0.1 + '@types/node': 17.0.16 + '@types/node-forge': 1.0.0 + '@typescript-eslint/eslint-plugin': 4.31.1_5d7752337e5ea49772097d8af1823bf9 + '@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.5.5 + '@zerodevx/svelte-toast': 0.6.3 + autoprefixer: 10.4.2_postcss@8.4.6 + cross-var: 1.1.0 + eslint: 7.32.0 + eslint-config-prettier: 8.3.0_eslint@7.32.0 + eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.46.4 + husky: 7.0.4 + lint-staged: 12.3.3 + postcss: 8.4.6 + prettier: 2.5.1 + prettier-plugin-svelte: 2.6.0_prettier@2.5.1+svelte@3.46.4 + prettier-plugin-tailwindcss: 0.1.7_prettier@2.5.1 + prisma: 3.9.1 + svelte: 3.46.4 + svelte-check: 2.4.3_postcss@8.4.6+svelte@3.46.4 + svelte-preprocess: 4.10.3_88b359da5cac6d8f6ee1bbb7080a3fa9 + tailwindcss: 3.0.19_27d966e3a2f4b84fbc8a2f9653dbb362 + ts-node: 10.5.0_99ae9436e134a034c8d45fdd98ebbf22 + tslib: 2.3.1 + typescript: 4.5.5 packages: - /@babel/code-frame/7.12.11: - resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} - dependencies: - '@babel/highlight': 7.14.0 - dev: true - - /@babel/code-frame/7.14.5: - resolution: {integrity: sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==} - engines: {node: '>=6.9.0'} + resolution: + { + integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + } dependencies: '@babel/highlight': 7.14.5 dev: true - /@babel/helper-validator-identifier/7.14.0: - resolution: {integrity: sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==} - dev: true - - /@babel/helper-validator-identifier/7.14.5: - resolution: {integrity: sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/highlight/7.14.0: - resolution: {integrity: sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==} - dependencies: - '@babel/helper-validator-identifier': 7.14.0 - chalk: 2.4.2 - js-tokens: 4.0.0 + /@babel/helper-validator-identifier/7.15.7: + resolution: + { + integrity: sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== + } + engines: { node: '>=6.9.0' } dev: true /@babel/highlight/7.14.5: - resolution: {integrity: sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==} - engines: {node: '>=6.9.0'} + resolution: + { + integrity: sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + } + engines: { node: '>=6.9.0' } dependencies: - '@babel/helper-validator-identifier': 7.14.5 + '@babel/helper-validator-identifier': 7.15.7 chalk: 2.4.2 js-tokens: 4.0.0 dev: true - /@eslint/eslintrc/0.4.2: - resolution: {integrity: sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==} - engines: {node: ^10.12.0 || >=12.0.0} + /@cspotcode/source-map-consumer/0.8.0: + resolution: + { + integrity: sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + } + engines: { node: '>= 12' } + dev: true + + /@cspotcode/source-map-support/0.7.0: + resolution: + { + integrity: sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + } + engines: { node: '>=12' } + dependencies: + '@cspotcode/source-map-consumer': 0.8.0 + dev: true + + /@eslint/eslintrc/0.4.3: + resolution: + { + integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + } + engines: { node: ^10.12.0 || >=12.0.0 } dependencies: ajv: 6.12.6 - debug: 4.3.1 + debug: 4.3.3 espree: 7.3.1 - globals: 13.9.0 + globals: 13.12.0 ignore: 4.0.6 import-fresh: 3.3.0 js-yaml: 3.14.1 @@ -144,159 +172,434 @@ packages: - supports-color dev: true - /@fullhuman/postcss-purgecss/4.0.3_postcss@8.3.0: - resolution: {integrity: sha512-/EnQ9UDWGGqHkn1UKAwSgh+gJHPKmD+Z+5dQ4gWT4qq2NUyez3zqAfZNwFH3eSgmgO+wjTXfhlLchx2M9/K+7Q==} - peerDependencies: - postcss: ^8.0.0 + /@humanwhocodes/config-array/0.5.0: + resolution: + { + integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + } + engines: { node: '>=10.10.0' } dependencies: - postcss: 8.3.0 - purgecss: 4.0.3 + '@humanwhocodes/object-schema': 1.2.0 + debug: 4.3.3 + minimatch: 3.0.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/object-schema/1.2.0: + resolution: + { + integrity: sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + } dev: true /@iarna/toml/2.2.5: - resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + resolution: + { + integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== + } dev: false - /@mapbox/node-pre-gyp/1.0.5: - resolution: {integrity: sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==} + /@mapbox/node-pre-gyp/1.0.6: + resolution: + { + integrity: sha512-qK1ECws8UxuPqOA8F5LFD90vyVU33W7N3hGfgsOVfrJaRVc8McC3JClTDHpeSbL9CBrOHly/4GsNPAvIgNZE+g== + } hasBin: true dependencies: detect-libc: 1.0.3 https-proxy-agent: 5.0.0 make-dir: 3.1.0 - node-fetch: 2.6.1 + node-fetch: 2.6.6 nopt: 5.0.0 - npmlog: 4.1.2 + npmlog: 5.0.1 rimraf: 3.0.2 semver: 7.3.5 - tar: 6.1.0 + tar: 6.1.11 transitivePeerDependencies: - supports-color dev: false /@nodelib/fs.scandir/2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + } + engines: { node: '>= 8' } dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 dev: true /@nodelib/fs.stat/2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + } + engines: { node: '>= 8' } dev: true - /@nodelib/fs.walk/1.2.7: - resolution: {integrity: sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==} - engines: {node: '>= 8'} + /@nodelib/fs.walk/1.2.8: + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + } + engines: { node: '>= 8' } dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.11.0 + fastq: 1.13.0 dev: true - /@rollup/pluginutils/4.1.0: - resolution: {integrity: sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ==} - engines: {node: '>= 8.0.0'} + /@prisma/client/3.9.1_prisma@3.9.1: + resolution: + { + integrity: sha512-aLwfXKLvL+loQ0IuPPCXkcq8cXBg1IeoHHa5lqQu3dJHdj45wnislA/Ny4UxRQjD5FXqrfAb8sWtF+jhdmjFTg== + } + engines: { node: '>=12.6' } + requiresBuild: true peerDependencies: - rollup: ^1.20.0||^2.0.0 + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + '@prisma/engines-version': 3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009 + prisma: 3.9.1 + dev: false + + /@prisma/engines-version/3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009: + resolution: + { + integrity: sha512-5Dh+qTDhpPR66w6NNAnPs+/W/Qt4r1DSd+qhfPFcDThUK4uxoZKGlPb2IYQn5LL+18aIGnmteDf7BnVMmvBNSQ== + } + dev: false + + /@prisma/engines/3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009: + resolution: + { + integrity: sha512-qM+uJbkelB21bnK44gYE049YTHIjHysOuj0mj5U2gDGyNLfmiazlggzFPCgEjgme4U5YB2tYs6Z5Hq08Kl8pjA== + } + requiresBuild: true + dev: true + + /@rollup/pluginutils/4.1.2: + resolution: + { + integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ== + } + engines: { node: '>= 8.0.0' } dependencies: estree-walker: 2.0.2 picomatch: 2.3.0 dev: true - /@sveltejs/adapter-node/1.0.0-next.33: - resolution: {integrity: sha512-0mABbAWQ5Dg7eSptvpX+fmZ0tgifscgNwgtg4iX6GRhWGizTYYBgp4yAHl4IXPqlAQkW7Lh0gw3+G2tQcs2qcQ==} + /@sentry/core/6.17.6: + resolution: + { + integrity: sha512-wSNsQSqsW8vQ2HEvUEXYOJnzTyVDSWbyH4RHrWV1pQM8zqGx/qfz0sKFM5XFnE9ZeaXKL8LXV3v5i73v+z8lew== + } + engines: { node: '>=6' } + dependencies: + '@sentry/hub': 6.17.6 + '@sentry/minimal': 6.17.6 + '@sentry/types': 6.17.6 + '@sentry/utils': 6.17.6 + tslib: 1.14.1 + dev: false + + /@sentry/hub/6.17.6: + resolution: + { + integrity: sha512-Ps9nk+DoFia8jhZ1lucdRE0vDx8hqXOsKXJE8a3hK/Ndki0J9jedYqBeLqSgiFG4qRjXpNFcD6TEM6tnQrv5lw== + } + engines: { node: '>=6' } + dependencies: + '@sentry/types': 6.17.6 + '@sentry/utils': 6.17.6 + tslib: 1.14.1 + dev: false + + /@sentry/minimal/6.17.6: + resolution: + { + integrity: sha512-PLGf8WlhtdHuY6ofwYR3nyClr/TYHHAW6i0r62OZCOXTqnFPJorZpAz3VCCP2jMJmbgVbo03wN+u/xAA/zwObA== + } + engines: { node: '>=6' } + dependencies: + '@sentry/hub': 6.17.6 + '@sentry/types': 6.17.6 + tslib: 1.14.1 + dev: false + + /@sentry/node/6.17.6: + resolution: + { + integrity: sha512-T1s0yPbGvYpoh9pJgLvpy7s+jVwCyf0ieEoN9rSbnPwbi2vm6MfoV5wtGrE0cBHTPgnyOMv+zq4Q3ww6dfr7Pw== + } + engines: { node: '>=6' } + dependencies: + '@sentry/core': 6.17.6 + '@sentry/hub': 6.17.6 + '@sentry/tracing': 6.17.6 + '@sentry/types': 6.17.6 + '@sentry/utils': 6.17.6 + cookie: 0.4.2 + https-proxy-agent: 5.0.0 + lru_map: 0.3.3 + tslib: 1.14.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@sentry/tracing/6.17.6: + resolution: + { + integrity: sha512-+h5ov+zEm5WH9+vmFfdT4EIqBOW7Tggzh0BDz8QRStRc2JbvEiSZDs+HlsycBwWMQi/ucJs93FPtNnWjW+xvBw== + } + engines: { node: '>=6' } + dependencies: + '@sentry/hub': 6.17.6 + '@sentry/minimal': 6.17.6 + '@sentry/types': 6.17.6 + '@sentry/utils': 6.17.6 + tslib: 1.14.1 + dev: false + + /@sentry/types/6.17.6: + resolution: + { + integrity: sha512-peGM873lDJtHd/jwW9Egr/hhxLuF0bcPIf2kMZlvEvW/G5GCbuaCR4ArQJlh7vQyma+NLn/XdojpJkC0TomKrw== + } + engines: { node: '>=6' } + dev: false + + /@sentry/utils/6.17.6: + resolution: + { + integrity: sha512-RI797N8Ax5yuKUftVX6dc0XmXqo5CN7XqJYPFzYC8udutQ4L8ZYadtUcqNsdz1ZQxl+rp0XK9Q6wjoWmsI2RXA== + } + engines: { node: '>=6' } + dependencies: + '@sentry/types': 6.17.6 + tslib: 1.14.1 + dev: false + + /@sindresorhus/is/4.2.0: + resolution: + { + integrity: sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw== + } + engines: { node: '>=10' } + dev: false + + /@sveltejs/adapter-node/1.0.0-next.67: + resolution: + { + integrity: sha512-+LuLn91xARZsRANiQNIIDpMMncUTnP2pJc8tyL+FdpVvs5UtlvkYJpeCBPFqjjseRpIIbi8Slu89GCdrRXBDUg== + } dependencies: - esbuild: 0.12.6 tiny-glob: 0.2.9 dev: true - /@sveltejs/kit/1.0.0-next.125_svelte@3.38.2: - resolution: {integrity: sha512-msYbZak97rW2Cgk08NiJW5Sz4S6XnVcjwTXrMo1G4MkYn8cZeL/eKWhuYrjGFvv4U3bYOZhTj/WfAzXNVEFX0A==} - engines: {node: ^12.20 || >=14.13} + /@sveltejs/adapter-static/1.0.0-next.27: + resolution: + { + integrity: sha512-dcN1p1D7ZY/a9SClfN14mgm9pyWbLxdwM9gzPMZG6xXOoqMtwI03aZOFgGGumHPdv+XcGRZM96vUSRoDm6vBJQ== + } + dependencies: + tiny-glob: 0.2.9 + dev: true + + /@sveltejs/kit/1.0.0-next.259_svelte@3.46.4: + resolution: + { + integrity: sha512-+Tss6cQXmpi4Jno/ZP0zJ3INBLMED+WeW4UI81tmexheC76Y2p+cbInneKO/REx/8QFo1iroYrWAUkZPsOg8Ew== + } + engines: { node: '>=14.13' } hasBin: true peerDependencies: - svelte: ^3.34.0 + svelte: ^3.44.0 dependencies: - '@sveltejs/vite-plugin-svelte': 1.0.0-next.12_svelte@3.38.2+vite@2.4.2 - cheap-watch: 1.0.3 + '@sveltejs/vite-plugin-svelte': 1.0.0-next.33_svelte@3.46.4+vite@2.8.0 sade: 1.7.4 - svelte: 3.38.2 - vite: 2.4.2 + svelte: 3.46.4 + vite: 2.8.0 transitivePeerDependencies: - - rollup + - diff-match-patch + - less + - sass + - stylus - supports-color dev: true - /@sveltejs/vite-plugin-svelte/1.0.0-next.12_svelte@3.38.2+vite@2.4.2: - resolution: {integrity: sha512-cuyNkJ6leptfv+7qL/fWQ7EpGWdguosFOUI0z93oQUmFTcX7QxJ5h+QI3NQyktBzlKL/761L8BbG2hHNkVbLIQ==} - engines: {node: ^12.20 || ^14.13.1 || >= 16} + /@sveltejs/vite-plugin-svelte/1.0.0-next.33_svelte@3.46.4+vite@2.8.0: + resolution: + { + integrity: sha512-aj0h2+ZixgT+yoJFIs8dRRw/Cj9tgNu3+hY4CJikpa04mfhR61wXqJFfi2ZEFMUvFda5nCxKYIChFkc6wq5fJA== + } + engines: { node: ^14.13.1 || >= 16 } peerDependencies: - svelte: ^3.34.0 - vite: ^2.3.7 + diff-match-patch: ^1.0.5 + svelte: ^3.44.0 + vite: ^2.7.0 + peerDependenciesMeta: + diff-match-patch: + optional: true dependencies: - '@rollup/pluginutils': 4.1.0 - debug: 4.3.2 + '@rollup/pluginutils': 4.1.2 + debug: 4.3.3 kleur: 4.1.4 magic-string: 0.25.7 require-relative: 0.8.7 - svelte: 3.38.2 - svelte-hmr: 0.14.5_svelte@3.38.2 - vite: 2.4.2 + svelte: 3.46.4 + svelte-hmr: 0.14.9_svelte@3.46.4 + vite: 2.8.0 transitivePeerDependencies: - - rollup - supports-color dev: true - /@trysound/sax/0.1.1: - resolution: {integrity: sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow==} - engines: {node: '>=10.13.0'} - dev: true - - /@types/bson/4.0.3: - resolution: {integrity: sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==} + /@szmarczak/http-timer/5.0.1: + resolution: + { + integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + } + engines: { node: '>=14.16' } dependencies: - '@types/node': 15.0.1 + defer-to-connect: 2.0.1 dev: false - /@types/dockerode/3.2.3: - resolution: {integrity: sha512-nZRhpSxm3PYianRBcRExcHxDvEzYHUPfGCnRL5Fe4/fSEZbtxrRNJ7okzCans3lXxj2t298EynFHGTnTC2f1Iw==} - dependencies: - '@types/node': 15.0.1 + /@tsconfig/node10/1.0.8: + resolution: + { + integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + } dev: true - /@types/json-schema/7.0.7: - resolution: {integrity: sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==} + /@tsconfig/node12/1.0.9: + resolution: + { + integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + } dev: true - /@types/mongodb/3.6.12: - resolution: {integrity: sha512-49aEzQD5VdHPxyd5dRyQdqEveAg9LanwrH8RQipnMuulwzKmODXIZRp0umtxi1eBUfEusRkoy8AVOMr+kVuFog==} + /@tsconfig/node14/1.0.1: + resolution: + { + integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + } + dev: true + + /@tsconfig/node16/1.0.2: + resolution: + { + integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + } + dev: true + + /@types/bcrypt/5.0.0: + resolution: + { + integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw== + } dependencies: - '@types/bson': 4.0.3 - '@types/node': 15.0.1 + '@types/node': 17.0.16 + dev: true + + /@types/cacheable-request/6.0.2: + resolution: + { + integrity: sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== + } + dependencies: + '@types/http-cache-semantics': 4.0.1 + '@types/keyv': 3.1.3 + '@types/node': 17.0.16 + '@types/responselike': 1.0.0 dev: false - /@types/node/15.0.1: - resolution: {integrity: sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==} + /@types/http-cache-semantics/4.0.1: + resolution: + { + integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== + } + dev: false + + /@types/js-cookie/3.0.1: + resolution: + { + integrity: sha512-7wg/8gfHltklehP+oyJnZrz9XBuX5ZPP4zB6UsI84utdlkRYLnOm2HfpLXazTwZA+fpGn0ir8tGNgVnMEleBGQ== + } + dev: true + + /@types/json-schema/7.0.9: + resolution: + { + integrity: sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + } + dev: true + + /@types/keyv/3.1.3: + resolution: + { + integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== + } + dependencies: + '@types/node': 17.0.16 + dev: false + + /@types/node-forge/1.0.0: + resolution: + { + integrity: sha512-h0bgwPKq5u99T9Gor4qtV1lCZ41xNkai0pie1n/a2mh2/4+jENWOlo7AJ4YKxTZAnSZ8FRurUpdIN7ohaPPuHA== + } + dependencies: + '@types/node': 17.0.16 + dev: true + + /@types/node/17.0.16: + resolution: + { + integrity: sha512-ydLaGVfQOQ6hI1xK2A5nVh8bl0OGoIfYMxPWHqqYe9bTkWCfqiVvZoh2I/QF2sNSkZzZyROBoTefIEI+PB6iIA== + } /@types/parse-json/4.0.0: - resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + resolution: + { + integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + } dev: true - /@types/pug/2.0.4: - resolution: {integrity: sha1-h3L80EGOPNLMFxVV1zAHQVBR9LI=} + /@types/pug/2.0.5: + resolution: + { + integrity: sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA== + } dev: true - /@types/sass/1.16.0: - resolution: {integrity: sha512-2XZovu4NwcqmtZtsBR5XYLw18T8cBCnU2USFHTnYLLHz9fkhnoEMoDsqShJIOFsFhn5aJHjweiUUdTrDGujegA==} + /@types/responselike/1.0.0: + resolution: + { + integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + } dependencies: - '@types/node': 15.0.1 + '@types/node': 17.0.16 + dev: false + + /@types/sass/1.16.1: + resolution: + { + integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ== + } + dependencies: + '@types/node': 17.0.16 dev: true - /@typescript-eslint/eslint-plugin/4.26.1_c8cbd5e7f5f92609ec78d991aced454b: - resolution: {integrity: sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/eslint-plugin/4.31.1_5d7752337e5ea49772097d8af1823bf9: + resolution: + { + integrity: sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA== + } + engines: { node: ^10.12.0 || >=12.0.0 } peerDependencies: '@typescript-eslint/parser': ^4.0.0 eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -305,42 +608,47 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/experimental-utils': 4.26.1_eslint@7.28.0+typescript@4.3.2 - '@typescript-eslint/parser': 4.26.1_eslint@7.28.0+typescript@4.3.2 - '@typescript-eslint/scope-manager': 4.26.1 - debug: 4.3.1 - eslint: 7.28.0 + '@typescript-eslint/experimental-utils': 4.31.1_eslint@7.32.0+typescript@4.5.5 + '@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.5.5 + '@typescript-eslint/scope-manager': 4.31.1 + debug: 4.3.3 + eslint: 7.32.0 functional-red-black-tree: 1.0.1 - lodash: 4.17.21 - regexpp: 3.1.0 + regexpp: 3.2.0 semver: 7.3.5 - tsutils: 3.21.0_typescript@4.3.2 - typescript: 4.3.2 + tsutils: 3.21.0_typescript@4.5.5 + typescript: 4.5.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/experimental-utils/4.26.1_eslint@7.28.0+typescript@4.3.2: - resolution: {integrity: sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/experimental-utils/4.31.1_eslint@7.32.0+typescript@4.5.5: + resolution: + { + integrity: sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q== + } + engines: { node: ^10.12.0 || >=12.0.0 } peerDependencies: eslint: '*' dependencies: - '@types/json-schema': 7.0.7 - '@typescript-eslint/scope-manager': 4.26.1 - '@typescript-eslint/types': 4.26.1 - '@typescript-eslint/typescript-estree': 4.26.1_typescript@4.3.2 - eslint: 7.28.0 + '@types/json-schema': 7.0.9 + '@typescript-eslint/scope-manager': 4.31.1 + '@typescript-eslint/types': 4.31.1 + '@typescript-eslint/typescript-estree': 4.31.1_typescript@4.5.5 + eslint: 7.32.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@7.28.0 + eslint-utils: 3.0.0_eslint@7.32.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/parser/4.26.1_eslint@7.28.0+typescript@4.3.2: - resolution: {integrity: sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/parser/4.31.1_eslint@7.32.0+typescript@4.5.5: + resolution: + { + integrity: sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ== + } + engines: { node: ^10.12.0 || >=12.0.0 } peerDependencies: eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 typescript: '*' @@ -348,68 +656,89 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 4.26.1 - '@typescript-eslint/types': 4.26.1 - '@typescript-eslint/typescript-estree': 4.26.1_typescript@4.3.2 - debug: 4.3.1 - eslint: 7.28.0 - typescript: 4.3.2 + '@typescript-eslint/scope-manager': 4.31.1 + '@typescript-eslint/types': 4.31.1 + '@typescript-eslint/typescript-estree': 4.31.1_typescript@4.5.5 + debug: 4.3.3 + eslint: 7.32.0 + typescript: 4.5.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/4.26.1: - resolution: {integrity: sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} + /@typescript-eslint/scope-manager/4.31.1: + resolution: + { + integrity: sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ== + } + engines: { node: ^8.10.0 || ^10.13.0 || >=11.10.1 } dependencies: - '@typescript-eslint/types': 4.26.1 - '@typescript-eslint/visitor-keys': 4.26.1 + '@typescript-eslint/types': 4.31.1 + '@typescript-eslint/visitor-keys': 4.31.1 dev: true - /@typescript-eslint/types/4.26.1: - resolution: {integrity: sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} + /@typescript-eslint/types/4.31.1: + resolution: + { + integrity: sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ== + } + engines: { node: ^8.10.0 || ^10.13.0 || >=11.10.1 } dev: true - /@typescript-eslint/typescript-estree/4.26.1_typescript@4.3.2: - resolution: {integrity: sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/typescript-estree/4.31.1_typescript@4.5.5: + resolution: + { + integrity: sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg== + } + engines: { node: ^10.12.0 || >=12.0.0 } peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 4.26.1 - '@typescript-eslint/visitor-keys': 4.26.1 - debug: 4.3.1 - globby: 11.0.3 - is-glob: 4.0.1 + '@typescript-eslint/types': 4.31.1 + '@typescript-eslint/visitor-keys': 4.31.1 + debug: 4.3.3 + globby: 11.0.4 + is-glob: 4.0.3 semver: 7.3.5 - tsutils: 3.21.0_typescript@4.3.2 - typescript: 4.3.2 + tsutils: 3.21.0_typescript@4.5.5 + typescript: 4.5.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/visitor-keys/4.26.1: - resolution: {integrity: sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} + /@typescript-eslint/visitor-keys/4.31.1: + resolution: + { + integrity: sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ== + } + engines: { node: ^8.10.0 || ^10.13.0 || >=11.10.1 } dependencies: - '@typescript-eslint/types': 4.26.1 - eslint-visitor-keys: 2.0.0 + '@typescript-eslint/types': 4.31.1 + eslint-visitor-keys: 2.1.0 dev: true - /@zerodevx/svelte-toast/0.3.0: - resolution: {integrity: sha512-TY5dLB5DEpxuHu60M6gum+fDxzIGXg35R8500orz58JTFs9LiHWoEfeJ4n9t7Afix3iMmYLl2thjhnCHCiQIhQ==} - dev: false + /@zerodevx/svelte-toast/0.6.3: + resolution: + { + integrity: sha512-k0W1JFoqHIcIQaP9ij99+Rv0ugaQSSNwOuNwwmTGRjWtIqrQr+ExLDE8LQGXLlJIprqDyMWB4lJkUql/r0RAtA== + } + dev: true /abbrev/1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + resolution: + { + integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + } dev: false - /acorn-jsx/5.3.1_acorn@7.4.1: - resolution: {integrity: sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==} + /acorn-jsx/5.3.2_acorn@7.4.1: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + } peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: @@ -417,7 +746,10 @@ packages: dev: true /acorn-node/1.8.2: - resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} + resolution: + { + integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + } dependencies: acorn: 7.4.1 acorn-walk: 7.2.0 @@ -425,27 +757,67 @@ packages: dev: true /acorn-walk/7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} + resolution: + { + integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + } + engines: { node: '>=0.4.0' } + dev: true + + /acorn-walk/8.2.0: + resolution: + { + integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + } + engines: { node: '>=0.4.0' } dev: true /acorn/7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} + resolution: + { + integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + } + engines: { node: '>=0.4.0' } + hasBin: true + dev: true + + /acorn/8.5.0: + resolution: + { + integrity: sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + } + engines: { node: '>=0.4.0' } hasBin: true dev: true /agent-base/6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} + resolution: + { + integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + } + engines: { node: '>= 6.0.0' } dependencies: - debug: 4.3.1 + debug: 4.3.3 transitivePeerDependencies: - supports-color dev: false + /aggregate-error/3.1.0: + resolution: + { + integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + } + engines: { node: '>=8' } + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + /ajv/6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + } dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -453,8 +825,11 @@ packages: uri-js: 4.4.1 dev: true - /ajv/8.2.0: - resolution: {integrity: sha512-WSNGFuyWd//XO8n/m/EaOlNLtO0yL8EXT/74LqT4khdhpZjP7lkj/kT5uwRmGitKEVp/Oj7ZUHeGfPtgHhQ5CA==} + /ajv/8.6.3: + resolution: + { + integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw== + } dependencies: fast-deep-equal: 3.1.3 json-schema-traverse: 1.0.0 @@ -462,267 +837,1045 @@ packages: uri-js: 4.4.1 dev: true - /alphanum-sort/1.0.2: - resolution: {integrity: sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=} + /ansi-colors/4.1.1: + resolution: + { + integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + } + engines: { node: '>=6' } dev: true - /ansi-colors/4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} - engines: {node: '>=6'} + /ansi-escapes/4.3.2: + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + } + engines: { node: '>=8' } + dependencies: + type-fest: 0.21.3 dev: true /ansi-regex/2.1.1: - resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=} - engines: {node: '>=0.10.0'} + resolution: { integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8= } + engines: { node: '>=0.10.0' } + dev: true + + /ansi-regex/3.0.0: + resolution: { integrity: sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= } + engines: { node: '>=4' } dev: false - /ansi-regex/5.0.0: - resolution: {integrity: sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==} - engines: {node: '>=8'} + /ansi-regex/5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + } + engines: { node: '>=8' } + + /ansi-regex/6.0.1: + resolution: + { + integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + } + engines: { node: '>=12' } + dev: true + + /ansi-styles/2.2.1: + resolution: { integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= } + engines: { node: '>=0.10.0' } dev: true /ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + } + engines: { node: '>=4' } dependencies: color-convert: 1.9.3 dev: true /ansi-styles/4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + } + engines: { node: '>=8' } dependencies: color-convert: 2.0.1 dev: true + /ansi-styles/6.1.0: + resolution: + { + integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + } + engines: { node: '>=12' } + dev: true + /anymatch/3.1.2: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + } + engines: { node: '>= 8' } dependencies: normalize-path: 3.0.0 picomatch: 2.3.0 dev: true - /aproba/1.2.0: - resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==} + /aproba/2.0.0: + resolution: + { + integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + } dev: false - /are-we-there-yet/1.1.5: - resolution: {integrity: sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==} + /are-we-there-yet/2.0.0: + resolution: + { + integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + } + engines: { node: '>=10' } dependencies: delegates: 1.0.0 - readable-stream: 2.3.7 + readable-stream: 3.6.0 dev: false - /arg/5.0.0: - resolution: {integrity: sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==} + /arg/4.1.3: + resolution: + { + integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + } + dev: true + + /arg/5.0.1: + resolution: + { + integrity: sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== + } dev: true /argparse/1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + } dependencies: sprintf-js: 1.0.3 dev: true /argparse/2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + } dev: false /array-union/2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + } + engines: { node: '>=8' } dev: true /asn1/0.2.4: - resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==} + resolution: + { + integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + } dependencies: safer-buffer: 2.1.2 dev: false /astral-regex/2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + } + engines: { node: '>=8' } dev: true /auto-parse/1.8.0: - resolution: {integrity: sha512-Uri4uC+K5cSi5hjM4snFrqPrjqUpwxeSW5EMTPvN7Ju3PlDzmXXDr5tjdzxPvvwgT3J7bmMDJ3Rm625nbrc72A==} + resolution: + { + integrity: sha512-Uri4uC+K5cSi5hjM4snFrqPrjqUpwxeSW5EMTPvN7Ju3PlDzmXXDr5tjdzxPvvwgT3J7bmMDJ3Rm625nbrc72A== + } dependencies: typpy: 2.3.11 dev: false - /autoprefixer/10.2.6_postcss@8.3.0: - resolution: {integrity: sha512-8lChSmdU6dCNMCQopIf4Pe5kipkAGj/fvTMslCsih0uHpOrXOPUEVOmYMMqmw3cekQkSD7EhIeuYl5y0BLdKqg==} - engines: {node: ^10 || ^12 || >=14} + /autoprefixer/10.4.2_postcss@8.4.6: + resolution: + { + integrity: sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ== + } + engines: { node: ^10 || ^12 || >=14 } hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.16.6 - caniuse-lite: 1.0.30001235 - colorette: 1.2.2 - fraction.js: 4.1.1 + browserslist: 4.19.1 + caniuse-lite: 1.0.30001300 + fraction.js: 4.1.2 normalize-range: 0.1.2 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 + picocolors: 1.0.0 + postcss: 8.4.6 + postcss-value-parser: 4.2.0 + dev: true + + /babel-code-frame/6.26.0: + resolution: { integrity: sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= } + dependencies: + chalk: 1.1.3 + esutils: 2.0.3 + js-tokens: 3.0.2 + dev: true + + /babel-core/6.26.3: + resolution: + { + integrity: sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== + } + dependencies: + babel-code-frame: 6.26.0 + babel-generator: 6.26.1 + babel-helpers: 6.24.1 + babel-messages: 6.23.0 + babel-register: 6.26.0 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + babylon: 6.18.0 + convert-source-map: 1.8.0 + debug: 2.6.9 + json5: 0.5.1 + lodash: 4.17.21 + minimatch: 3.0.4 + path-is-absolute: 1.0.1 + private: 0.1.8 + slash: 1.0.0 + source-map: 0.5.7 + dev: true + + /babel-generator/6.26.1: + resolution: + { + integrity: sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== + } + dependencies: + babel-messages: 6.23.0 + babel-runtime: 6.26.0 + babel-types: 6.26.0 + detect-indent: 4.0.0 + jsesc: 1.3.0 + lodash: 4.17.21 + source-map: 0.5.7 + trim-right: 1.0.1 + dev: true + + /babel-helper-bindify-decorators/6.24.1: + resolution: { integrity: sha1-FMGeXxQte0fxmlJDHlKxzLxAozA= } + dependencies: + babel-runtime: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-builder-binary-assignment-operator-visitor/6.24.1: + resolution: { integrity: sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= } + dependencies: + babel-helper-explode-assignable-expression: 6.24.1 + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-call-delegate/6.24.1: + resolution: { integrity: sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= } + dependencies: + babel-helper-hoist-variables: 6.24.1 + babel-runtime: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-define-map/6.26.0: + resolution: { integrity: sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= } + dependencies: + babel-helper-function-name: 6.24.1 + babel-runtime: 6.26.0 + babel-types: 6.26.0 + lodash: 4.17.21 + dev: true + + /babel-helper-explode-assignable-expression/6.24.1: + resolution: { integrity: sha1-8luCz33BBDPFX3BZLVdGQArCLKo= } + dependencies: + babel-runtime: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-explode-class/6.24.1: + resolution: { integrity: sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes= } + dependencies: + babel-helper-bindify-decorators: 6.24.1 + babel-runtime: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-function-name/6.24.1: + resolution: { integrity: sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= } + dependencies: + babel-helper-get-function-arity: 6.24.1 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-get-function-arity/6.24.1: + resolution: { integrity: sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-hoist-variables/6.24.1: + resolution: { integrity: sha1-HssnaJydJVE+rbyZFKc/VAi+enY= } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-optimise-call-expression/6.24.1: + resolution: { integrity: sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-regex/6.26.0: + resolution: { integrity: sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + lodash: 4.17.21 + dev: true + + /babel-helper-remap-async-to-generator/6.24.1: + resolution: { integrity: sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= } + dependencies: + babel-helper-function-name: 6.24.1 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helper-replace-supers/6.24.1: + resolution: { integrity: sha1-v22/5Dk40XNpohPKiov3S2qQqxo= } + dependencies: + babel-helper-optimise-call-expression: 6.24.1 + babel-messages: 6.23.0 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-helpers/6.24.1: + resolution: { integrity: sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= } + dependencies: + babel-runtime: 6.26.0 + babel-template: 6.26.0 + dev: true + + /babel-messages/6.23.0: + resolution: { integrity: sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-check-es2015-constants/6.22.0: + resolution: { integrity: sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-syntax-async-functions/6.13.0: + resolution: { integrity: sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= } + dev: true + + /babel-plugin-syntax-async-generators/6.13.0: + resolution: { integrity: sha1-a8lj67FuzLrmuStZbrfzXDQqi5o= } + dev: true + + /babel-plugin-syntax-class-constructor-call/6.18.0: + resolution: { integrity: sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY= } + dev: true + + /babel-plugin-syntax-class-properties/6.13.0: + resolution: { integrity: sha1-1+sjt5oxf4VDlixQW4J8fWysJ94= } + dev: true + + /babel-plugin-syntax-decorators/6.13.0: + resolution: { integrity: sha1-MSVjtNvePMgGzuPkFszurd0RrAs= } + dev: true + + /babel-plugin-syntax-do-expressions/6.13.0: + resolution: { integrity: sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0= } + dev: true + + /babel-plugin-syntax-dynamic-import/6.18.0: + resolution: { integrity: sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo= } + dev: true + + /babel-plugin-syntax-exponentiation-operator/6.13.0: + resolution: { integrity: sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= } + dev: true + + /babel-plugin-syntax-export-extensions/6.13.0: + resolution: { integrity: sha1-cKFITw+QiaToStRLrDU8lbmxJyE= } + dev: true + + /babel-plugin-syntax-function-bind/6.13.0: + resolution: { integrity: sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y= } + dev: true + + /babel-plugin-syntax-object-rest-spread/6.13.0: + resolution: { integrity: sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= } + dev: true + + /babel-plugin-syntax-trailing-function-commas/6.22.0: + resolution: { integrity: sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= } + dev: true + + /babel-plugin-transform-async-generator-functions/6.24.1: + resolution: { integrity: sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds= } + dependencies: + babel-helper-remap-async-to-generator: 6.24.1 + babel-plugin-syntax-async-generators: 6.13.0 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-async-to-generator/6.24.1: + resolution: { integrity: sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= } + dependencies: + babel-helper-remap-async-to-generator: 6.24.1 + babel-plugin-syntax-async-functions: 6.13.0 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-class-constructor-call/6.24.1: + resolution: { integrity: sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk= } + dependencies: + babel-plugin-syntax-class-constructor-call: 6.18.0 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + dev: true + + /babel-plugin-transform-class-properties/6.24.1: + resolution: { integrity: sha1-anl2PqYdM9NvN7YRqp3vgagbRqw= } + dependencies: + babel-helper-function-name: 6.24.1 + babel-plugin-syntax-class-properties: 6.13.0 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + dev: true + + /babel-plugin-transform-decorators/6.24.1: + resolution: { integrity: sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0= } + dependencies: + babel-helper-explode-class: 6.24.1 + babel-plugin-syntax-decorators: 6.13.0 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-do-expressions/6.22.0: + resolution: { integrity: sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs= } + dependencies: + babel-plugin-syntax-do-expressions: 6.13.0 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-arrow-functions/6.22.0: + resolution: { integrity: sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-block-scoped-functions/6.22.0: + resolution: { integrity: sha1-u8UbSflk1wy42OC5ToICRs46YUE= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-block-scoping/6.26.0: + resolution: { integrity: sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= } + dependencies: + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + lodash: 4.17.21 + dev: true + + /babel-plugin-transform-es2015-classes/6.24.1: + resolution: { integrity: sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= } + dependencies: + babel-helper-define-map: 6.26.0 + babel-helper-function-name: 6.24.1 + babel-helper-optimise-call-expression: 6.24.1 + babel-helper-replace-supers: 6.24.1 + babel-messages: 6.23.0 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-computed-properties/6.24.1: + resolution: { integrity: sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= } + dependencies: + babel-runtime: 6.26.0 + babel-template: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-destructuring/6.23.0: + resolution: { integrity: sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-duplicate-keys/6.24.1: + resolution: { integrity: sha1-c+s9MQypaePvnskcU3QabxV2Qj4= } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-for-of/6.23.0: + resolution: { integrity: sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-function-name/6.24.1: + resolution: { integrity: sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= } + dependencies: + babel-helper-function-name: 6.24.1 + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-literals/6.22.0: + resolution: { integrity: sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-modules-amd/6.24.1: + resolution: { integrity: sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= } + dependencies: + babel-plugin-transform-es2015-modules-commonjs: 6.26.2 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-modules-commonjs/6.26.2: + resolution: + { + integrity: sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== + } + dependencies: + babel-plugin-transform-strict-mode: 6.24.1 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-modules-systemjs/6.24.1: + resolution: { integrity: sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= } + dependencies: + babel-helper-hoist-variables: 6.24.1 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-modules-umd/6.24.1: + resolution: { integrity: sha1-rJl+YoXNGO1hdq22B9YCNErThGg= } + dependencies: + babel-plugin-transform-es2015-modules-amd: 6.24.1 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-object-super/6.24.1: + resolution: { integrity: sha1-JM72muIcuDp/hgPa0CH1cusnj40= } + dependencies: + babel-helper-replace-supers: 6.24.1 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-parameters/6.24.1: + resolution: { integrity: sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= } + dependencies: + babel-helper-call-delegate: 6.24.1 + babel-helper-get-function-arity: 6.24.1 + babel-runtime: 6.26.0 + babel-template: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-shorthand-properties/6.24.1: + resolution: { integrity: sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-spread/6.22.0: + resolution: { integrity: sha1-1taKmfia7cRTbIGlQujdnxdG+NE= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-sticky-regex/6.24.1: + resolution: { integrity: sha1-AMHNsaynERLN8M9hJsLta0V8zbw= } + dependencies: + babel-helper-regex: 6.26.0 + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-template-literals/6.22.0: + resolution: { integrity: sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-typeof-symbol/6.23.0: + resolution: { integrity: sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= } + dependencies: + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-es2015-unicode-regex/6.24.1: + resolution: { integrity: sha1-04sS9C6nMj9yk4fxinxa4frrNek= } + dependencies: + babel-helper-regex: 6.26.0 + babel-runtime: 6.26.0 + regexpu-core: 2.0.0 + dev: true + + /babel-plugin-transform-exponentiation-operator/6.24.1: + resolution: { integrity: sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= } + dependencies: + babel-helper-builder-binary-assignment-operator-visitor: 6.24.1 + babel-plugin-syntax-exponentiation-operator: 6.13.0 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-export-extensions/6.22.0: + resolution: { integrity: sha1-U3OLR+deghhYnuqUbLvTkQm75lM= } + dependencies: + babel-plugin-syntax-export-extensions: 6.13.0 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-function-bind/6.22.0: + resolution: { integrity: sha1-xvuOlqwpajELjPjqQBRiQH3fapc= } + dependencies: + babel-plugin-syntax-function-bind: 6.13.0 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-object-rest-spread/6.26.0: + resolution: { integrity: sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= } + dependencies: + babel-plugin-syntax-object-rest-spread: 6.13.0 + babel-runtime: 6.26.0 + dev: true + + /babel-plugin-transform-regenerator/6.26.0: + resolution: { integrity: sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= } + dependencies: + regenerator-transform: 0.10.1 + dev: true + + /babel-plugin-transform-strict-mode/6.24.1: + resolution: { integrity: sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + dev: true + + /babel-preset-es2015/6.24.1: + resolution: { integrity: sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk= } + deprecated: '🙌 Thanks for using Babel: we recommend using babel-preset-env now: please read https://babeljs.io/env to update!' + dependencies: + babel-plugin-check-es2015-constants: 6.22.0 + babel-plugin-transform-es2015-arrow-functions: 6.22.0 + babel-plugin-transform-es2015-block-scoped-functions: 6.22.0 + babel-plugin-transform-es2015-block-scoping: 6.26.0 + babel-plugin-transform-es2015-classes: 6.24.1 + babel-plugin-transform-es2015-computed-properties: 6.24.1 + babel-plugin-transform-es2015-destructuring: 6.23.0 + babel-plugin-transform-es2015-duplicate-keys: 6.24.1 + babel-plugin-transform-es2015-for-of: 6.23.0 + babel-plugin-transform-es2015-function-name: 6.24.1 + babel-plugin-transform-es2015-literals: 6.22.0 + babel-plugin-transform-es2015-modules-amd: 6.24.1 + babel-plugin-transform-es2015-modules-commonjs: 6.26.2 + babel-plugin-transform-es2015-modules-systemjs: 6.24.1 + babel-plugin-transform-es2015-modules-umd: 6.24.1 + babel-plugin-transform-es2015-object-super: 6.24.1 + babel-plugin-transform-es2015-parameters: 6.24.1 + babel-plugin-transform-es2015-shorthand-properties: 6.24.1 + babel-plugin-transform-es2015-spread: 6.22.0 + babel-plugin-transform-es2015-sticky-regex: 6.24.1 + babel-plugin-transform-es2015-template-literals: 6.22.0 + babel-plugin-transform-es2015-typeof-symbol: 6.23.0 + babel-plugin-transform-es2015-unicode-regex: 6.24.1 + babel-plugin-transform-regenerator: 6.26.0 + dev: true + + /babel-preset-stage-0/6.24.1: + resolution: { integrity: sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo= } + dependencies: + babel-plugin-transform-do-expressions: 6.22.0 + babel-plugin-transform-function-bind: 6.22.0 + babel-preset-stage-1: 6.24.1 + dev: true + + /babel-preset-stage-1/6.24.1: + resolution: { integrity: sha1-dpLNfc1oSZB+auSgqFWJz7niv7A= } + dependencies: + babel-plugin-transform-class-constructor-call: 6.24.1 + babel-plugin-transform-export-extensions: 6.22.0 + babel-preset-stage-2: 6.24.1 + dev: true + + /babel-preset-stage-2/6.24.1: + resolution: { integrity: sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE= } + dependencies: + babel-plugin-syntax-dynamic-import: 6.18.0 + babel-plugin-transform-class-properties: 6.24.1 + babel-plugin-transform-decorators: 6.24.1 + babel-preset-stage-3: 6.24.1 + dev: true + + /babel-preset-stage-3/6.24.1: + resolution: { integrity: sha1-g2raCp56f6N8sTj7kyb4eTSkg5U= } + dependencies: + babel-plugin-syntax-trailing-function-commas: 6.22.0 + babel-plugin-transform-async-generator-functions: 6.24.1 + babel-plugin-transform-async-to-generator: 6.24.1 + babel-plugin-transform-exponentiation-operator: 6.24.1 + babel-plugin-transform-object-rest-spread: 6.26.0 + dev: true + + /babel-register/6.26.0: + resolution: { integrity: sha1-btAhFz4vy0htestFxgCahW9kcHE= } + dependencies: + babel-core: 6.26.3 + babel-runtime: 6.26.0 + core-js: 2.6.12 + home-or-tmp: 2.0.0 + lodash: 4.17.21 + mkdirp: 0.5.5 + source-map-support: 0.4.18 + dev: true + + /babel-runtime/6.26.0: + resolution: { integrity: sha1-llxwWGaOgrVde/4E/yM3vItWR/4= } + dependencies: + core-js: 2.6.12 + regenerator-runtime: 0.11.1 + dev: true + + /babel-template/6.26.0: + resolution: { integrity: sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= } + dependencies: + babel-runtime: 6.26.0 + babel-traverse: 6.26.0 + babel-types: 6.26.0 + babylon: 6.18.0 + lodash: 4.17.21 + dev: true + + /babel-traverse/6.26.0: + resolution: { integrity: sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= } + dependencies: + babel-code-frame: 6.26.0 + babel-messages: 6.23.0 + babel-runtime: 6.26.0 + babel-types: 6.26.0 + babylon: 6.18.0 + debug: 2.6.9 + globals: 9.18.0 + invariant: 2.2.4 + lodash: 4.17.21 + dev: true + + /babel-types/6.26.0: + resolution: { integrity: sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= } + dependencies: + babel-runtime: 6.26.0 + esutils: 2.0.3 + lodash: 4.17.21 + to-fast-properties: 1.0.3 + dev: true + + /babylon/6.18.0: + resolution: + { + integrity: sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + } + hasBin: true dev: true /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + } /base64-js/1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + } dev: false /bcrypt-pbkdf/1.0.2: - resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=} + resolution: { integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= } dependencies: tweetnacl: 0.14.5 dev: false /bcrypt/5.0.1: - resolution: {integrity: sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==} - engines: {node: '>= 10.0.0'} + resolution: + { + integrity: sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== + } + engines: { node: '>= 10.0.0' } requiresBuild: true dependencies: - '@mapbox/node-pre-gyp': 1.0.5 + '@mapbox/node-pre-gyp': 1.0.6 node-addon-api: 3.2.1 transitivePeerDependencies: - supports-color dev: false /binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + } + engines: { node: '>=8' } dev: true - /bl/2.2.1: - resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} - dependencies: - readable-stream: 2.3.7 - safe-buffer: 5.2.1 - dev: false - /bl/4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + } dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.0 dev: false - /bluebird/3.5.1: - resolution: {integrity: sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==} - dev: false - - /boolbase/1.0.0: - resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=} - dev: true - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + resolution: + { + integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + } dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + } + engines: { node: '>=8' } dependencies: fill-range: 7.0.1 dev: true - /browserslist/4.16.6: - resolution: {integrity: sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + /browserslist/4.19.1: + resolution: + { + integrity: sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true dependencies: - caniuse-lite: 1.0.30001235 - colorette: 1.2.2 - electron-to-chromium: 1.3.725 + caniuse-lite: 1.0.30001300 + electron-to-chromium: 1.4.48 escalade: 3.1.1 - node-releases: 1.1.71 + node-releases: 2.0.1 + picocolors: 1.0.0 dev: true - /bson/1.1.6: - resolution: {integrity: sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==} - engines: {node: '>=0.6.19'} - dev: false + /buffer-crc32/0.2.13: + resolution: { integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= } + dev: true /buffer-equal-constant-time/1.0.1: - resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=} + resolution: { integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= } dev: false /buffer/5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + resolution: + { + integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + } dependencies: base64-js: 1.5.1 ieee754: 1.2.1 dev: false - /bytes/3.1.0: - resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==} - engines: {node: '>= 0.8'} - dev: true + /bullmq/1.69.0: + resolution: + { + integrity: sha512-1aIO7bN0HQeADWoXa+I72GgofvoBFRs/kcoveB3KN8ytKv7QJbGhtks0pYNhHn/P9H3OWHWDccpNEfnv3VGfcw== + } + dependencies: + cron-parser: 2.18.0 + get-port: 5.1.1 + glob: 7.2.0 + ioredis: 4.28.3 + lodash: 4.17.21 + msgpackr: 1.4.7 + semver: 6.3.0 + tslib: 1.14.1 + uuid: 8.3.2 + transitivePeerDependencies: + - supports-color + dev: false + + /cacheable-lookup/6.0.4: + resolution: + { + integrity: sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A== + } + engines: { node: '>=10.6.0' } + dev: false + + /cacheable-request/7.0.2: + resolution: + { + integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== + } + engines: { node: '>=8' } + dependencies: + clone-response: 1.0.2 + get-stream: 5.2.0 + http-cache-semantics: 4.1.0 + keyv: 4.0.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.0 + dev: false + + /call-bind/1.0.2: + resolution: + { + integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + } + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.1 + dev: false /callsites/3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + } + engines: { node: '>=6' } dev: true /camelcase-css/2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + } + engines: { node: '>= 6' } dev: true /camelcase/5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + } + engines: { node: '>=6' } dev: false - /caniuse-api/3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - dependencies: - browserslist: 4.16.6 - caniuse-lite: 1.0.30001235 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 + /caniuse-lite/1.0.30001300: + resolution: + { + integrity: sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA== + } dev: true - /caniuse-lite/1.0.30001235: - resolution: {integrity: sha512-zWEwIVqnzPkSAXOUlQnPW2oKoYb2aLQ4Q5ejdjBcnH63rfypaW34CxaeBn1VMya2XaEU3P/R2qHpWyj+l0BT1A==} + /chalk/1.1.3: + resolution: { integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= } + engines: { node: '>=0.10.0' } + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 dev: true /chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + } + engines: { node: '>=4' } dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 dev: true - /chalk/4.1.1: - resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==} - engines: {node: '>=10'} + /chalk/4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + } + engines: { node: '>=10' } dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 dev: true - /cheap-watch/1.0.3: - resolution: {integrity: sha512-xC5CruMhLzjPwJ5ecUxGu1uGmwJQykUhqd2QrCrYbwvsFYdRyviu6jG9+pccwDXJR/OpmOTOJ9yLFunVgQu9wg==} - engines: {node: '>=8'} - dev: true - - /chokidar/3.5.2: - resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==} - engines: {node: '>= 8.10.0'} + /chokidar/3.5.3: + resolution: + { + integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + } + engines: { node: '>= 8.10.0' } dependencies: anymatch: 3.1.2 braces: 3.0.2 glob-parent: 5.1.2 is-binary-path: 2.1.0 - is-glob: 4.0.1 + is-glob: 4.0.3 normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: @@ -730,94 +1883,173 @@ packages: dev: true /chownr/1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + resolution: + { + integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + } dev: false /chownr/2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + } + engines: { node: '>=10' } dev: false - /code-point-at/1.1.0: - resolution: {integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=} - engines: {node: '>=0.10.0'} + /clean-stack/2.2.0: + resolution: + { + integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + } + engines: { node: '>=6' } + dev: true + + /cli-cursor/3.1.0: + resolution: + { + integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + } + engines: { node: '>=8' } + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-truncate/2.1.0: + resolution: + { + integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + } + engines: { node: '>=8' } + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + dev: true + + /cli-truncate/3.1.0: + resolution: + { + integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.0 + dev: true + + /clone-response/1.0.2: + resolution: { integrity: sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= } + dependencies: + mimic-response: 1.0.1 + dev: false + + /cluster-key-slot/1.1.0: + resolution: + { + integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + } + engines: { node: '>=0.10.0' } dev: false /color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + resolution: + { + integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + } dependencies: color-name: 1.1.3 dev: true /color-convert/2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + } + engines: { node: '>=7.0.0' } dependencies: color-name: 1.1.4 dev: true /color-name/1.1.3: - resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} + resolution: { integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= } dev: true /color-name/1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + } dev: true - /color-string/1.5.5: - resolution: {integrity: sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==} - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 + /color-support/1.1.3: + resolution: + { + integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + } + hasBin: true + dev: false + + /colorette/2.0.16: + resolution: + { + integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + } dev: true - /color/3.1.3: - resolution: {integrity: sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==} - dependencies: - color-convert: 1.9.3 - color-string: 1.5.5 + /commander/8.3.0: + resolution: + { + integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + } + engines: { node: '>= 12' } dev: true - /colord/2.0.1: - resolution: {integrity: sha512-vm5YpaWamD0Ov6TSG0GGmUIwstrWcfKQV/h2CmbR7PbNu41+qdB5PW9lpzhjedrpm08uuYvcXi0Oel1RLZIJuA==} - dev: true - - /colorette/1.2.2: - resolution: {integrity: sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==} - dev: true - - /commander/6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - dev: true - - /commander/7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - /compare-versions/3.6.0: - resolution: {integrity: sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==} + /compare-versions/4.1.3: + resolution: + { + integrity: sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== + } dev: false /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: { integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= } /console-control-strings/1.1.0: - resolution: {integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=} + resolution: { integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= } dev: false - /cookie/0.4.1: - resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} - engines: {node: '>= 0.6'} + /convert-source-map/1.8.0: + resolution: + { + integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + } + dependencies: + safe-buffer: 5.1.2 + dev: true + + /cookie/0.4.2: + resolution: + { + integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + } + engines: { node: '>= 0.6' } dev: false - /core-util-is/1.0.2: - resolution: {integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=} - dev: false + /core-js/2.6.12: + resolution: + { + integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + } + deprecated: core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js. + requiresBuild: true + dev: true - /cosmiconfig/7.0.0: - resolution: {integrity: sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==} - engines: {node: '>=10'} + /cosmiconfig/7.0.1: + resolution: + { + integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + } + engines: { node: '>=10' } dependencies: '@types/parse-json': 4.0.0 import-fresh: 3.3.0 @@ -826,199 +2058,206 @@ packages: yaml: 1.10.2 dev: true + /cpu-features/0.0.2: + resolution: + { + integrity: sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA== + } + engines: { node: '>=8.0.0' } + requiresBuild: true + dependencies: + nan: 2.15.0 + dev: false + optional: true + + /create-require/1.1.1: + resolution: + { + integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + } + dev: true + + /cron-parser/2.18.0: + resolution: + { + integrity: sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== + } + engines: { node: '>=0.8' } + dependencies: + is-nan: 1.3.2 + moment-timezone: 0.5.33 + dev: false + + /cross-spawn/5.1.0: + resolution: { integrity: sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= } + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + /cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + } + engines: { node: '>= 8' } dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - /css-color-names/0.0.4: - resolution: {integrity: sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=} - dev: true - - /css-color-names/1.0.1: - resolution: {integrity: sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==} - dev: true - - /css-declaration-sorter/6.0.3_postcss@8.3.0: - resolution: {integrity: sha512-52P95mvW1SMzuRZegvpluT6yEv0FqQusydKQPZsNN5Q7hh8EwQvN8E2nwuJ16BBvNN6LcoIZXu/Bk58DAhrrxw==} - engines: {node: '>= 10'} - peerDependencies: - postcss: ^8.0.9 + /cross-var/1.1.0: + resolution: { integrity: sha1-8PDUuyNdlRONGlOYQtKQ8A23HNY= } + hasBin: true dependencies: - postcss: 8.3.0 - timsort: 0.3.0 - dev: true - - /css-select/3.1.2: - resolution: {integrity: sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==} - dependencies: - boolbase: 1.0.0 - css-what: 4.0.0 - domhandler: 4.2.0 - domutils: 2.6.0 - nth-check: 2.0.0 - dev: true - - /css-tree/1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - dev: true - - /css-unit-converter/1.1.2: - resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==} - dev: true - - /css-what/4.0.0: - resolution: {integrity: sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==} - engines: {node: '>= 6'} + babel-preset-es2015: 6.24.1 + babel-preset-stage-0: 6.24.1 + babel-register: 6.26.0 + cross-spawn: 5.1.0 + exit: 0.1.2 dev: true /cssesc/3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + } + engines: { node: '>=4' } hasBin: true dev: true - /cssnano-preset-default/5.1.2_postcss@8.3.0: - resolution: {integrity: sha512-spilp8LRw0sacuxiN9A/dyyPr6G/WISKMBKcBD4NMoPV0ENx4DeuWvIIrSx9PII2nJIDCO3kywkqTPreECBVOg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - css-declaration-sorter: 6.0.3_postcss@8.3.0 - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-calc: 8.0.0_postcss@8.3.0 - postcss-colormin: 5.2.0_postcss@8.3.0 - postcss-convert-values: 5.0.1_postcss@8.3.0 - postcss-discard-comments: 5.0.1_postcss@8.3.0 - postcss-discard-duplicates: 5.0.1_postcss@8.3.0 - postcss-discard-empty: 5.0.1_postcss@8.3.0 - postcss-discard-overridden: 5.0.1_postcss@8.3.0 - postcss-merge-longhand: 5.0.2_postcss@8.3.0 - postcss-merge-rules: 5.0.2_postcss@8.3.0 - postcss-minify-font-values: 5.0.1_postcss@8.3.0 - postcss-minify-gradients: 5.0.1_postcss@8.3.0 - postcss-minify-params: 5.0.1_postcss@8.3.0 - postcss-minify-selectors: 5.1.0_postcss@8.3.0 - postcss-normalize-charset: 5.0.1_postcss@8.3.0 - postcss-normalize-display-values: 5.0.1_postcss@8.3.0 - postcss-normalize-positions: 5.0.1_postcss@8.3.0 - postcss-normalize-repeat-style: 5.0.1_postcss@8.3.0 - postcss-normalize-string: 5.0.1_postcss@8.3.0 - postcss-normalize-timing-functions: 5.0.1_postcss@8.3.0 - postcss-normalize-unicode: 5.0.1_postcss@8.3.0 - postcss-normalize-url: 5.0.1_postcss@8.3.0 - postcss-normalize-whitespace: 5.0.1_postcss@8.3.0 - postcss-ordered-values: 5.0.1_postcss@8.3.0 - postcss-reduce-initial: 5.0.1_postcss@8.3.0 - postcss-reduce-transforms: 5.0.1_postcss@8.3.0 - postcss-svgo: 5.0.2_postcss@8.3.0 - postcss-unique-selectors: 5.0.1_postcss@8.3.0 - dev: true - - /cssnano-utils/2.0.1_postcss@8.3.0: - resolution: {integrity: sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - dev: true - - /cssnano/5.0.5_postcss@8.3.0: - resolution: {integrity: sha512-L2VtPXnq6rmcMC9vkBOP131sZu3ccRQI27ejKZdmQiPDpUlFkUbpXHgKN+cibeO1U4PItxVZp1zTIn5dHsXoyg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cosmiconfig: 7.0.0 - cssnano-preset-default: 5.1.2_postcss@8.3.0 - is-resolvable: 1.1.0 - postcss: 8.3.0 - dev: true - - /csso/4.2.0: - resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} - engines: {node: '>=8.0.0'} - dependencies: - css-tree: 1.1.3 - dev: true - /cuid/2.1.8: - resolution: {integrity: sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==} + resolution: + { + integrity: sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg== + } dev: false - /dayjs/1.10.5: - resolution: {integrity: sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==} + /dayjs/1.10.7: + resolution: + { + integrity: sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig== + } dev: false - /debug/3.1.0: - resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + /debug/2.6.9: + resolution: + { + integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + } dependencies: ms: 2.0.0 + dev: true + + /debug/4.3.3: + resolution: + { + integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /debug/4.3.3_supports-color@9.2.1: + resolution: + { + integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 9.2.1 + dev: true + + /decompress-response/6.0.0: + resolution: + { + integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + } + engines: { node: '>=10' } + dependencies: + mimic-response: 3.1.0 dev: false - /debug/4.3.1: - resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - - /debug/4.3.2: - resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 + /deep-is/0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + } dev: true - /deep-is/0.1.3: - resolution: {integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=} - dev: true + /defer-to-connect/2.0.1: + resolution: + { + integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + } + engines: { node: '>=10' } + dev: false + + /define-properties/1.1.3: + resolution: + { + integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + } + engines: { node: '>= 0.4' } + dependencies: + object-keys: 1.1.1 + dev: false /defined/1.0.0: - resolution: {integrity: sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=} + resolution: { integrity: sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= } dev: true /delegates/1.0.0: - resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} + resolution: { integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= } dev: false - /denque/1.5.0: - resolution: {integrity: sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==} - engines: {node: '>=0.10'} + /denque/1.5.1: + resolution: + { + integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== + } + engines: { node: '>=0.10' } dev: false - /detect-indent/6.0.0: - resolution: {integrity: sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==} - engines: {node: '>=8'} + /detect-indent/4.0.0: + resolution: { integrity: sha1-920GQ1LN9Docts5hnE7jqUdd4gg= } + engines: { node: '>=0.10.0' } + dependencies: + repeating: 2.0.1 + dev: true + + /detect-indent/6.1.0: + resolution: + { + integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== + } + engines: { node: '>=8' } dev: true /detect-libc/1.0.3: - resolution: {integrity: sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=} - engines: {node: '>=0.10'} + resolution: { integrity: sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= } + engines: { node: '>=0.10' } hasBin: true dev: false /detective/5.2.0: - resolution: {integrity: sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==} - engines: {node: '>=0.8.0'} + resolution: + { + integrity: sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + } + engines: { node: '>=0.8.0' } hasBin: true dependencies: acorn-node: 1.8.2 @@ -1026,229 +2265,542 @@ packages: minimist: 1.2.5 dev: true - /didyoumean/1.2.1: - resolution: {integrity: sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=} + /didyoumean/1.2.2: + resolution: + { + integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + } + dev: true + + /diff/4.0.2: + resolution: + { + integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + } + engines: { node: '>=0.3.1' } dev: true /dir-glob/3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + } + engines: { node: '>=8' } dependencies: path-type: 4.0.0 dev: true /dlv/1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + resolution: + { + integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + } dev: true - /docker-modem/3.0.0: - resolution: {integrity: sha512-WwFajJ8I5geZ/dDZ5FDMDA6TBkWa76xWwGIGw8uzUjNUGCN0to83wJ8Oi1AxrJTC0JBn+7fvIxUctnawtlwXeg==} - engines: {node: '>= 8.0'} + /docker-modem/3.0.3: + resolution: + { + integrity: sha512-Tgkn2a+yiNP9FoZgMa/D9Wk+D2Db///0KOyKSYZRJa8w4+DzKyzQMkczKSdR/adQ0x46BOpeNkoyEOKjPhCzjw== + } + engines: { node: '>= 8.0' } dependencies: - debug: 4.3.1 + debug: 4.3.3 readable-stream: 3.6.0 split-ca: 1.0.1 - ssh2: 0.8.9 + ssh2: 1.5.0 transitivePeerDependencies: - supports-color dev: false - /dockerode/3.3.0: - resolution: {integrity: sha512-St08lfOjpYCOXEM8XA0VLu3B3hRjtddODphNW5GFoA0AS3JHgoPQKOz0Qmdzg3P+hUPxhb02g1o1Cu1G+U3lRg==} - engines: {node: '>= 8.0'} + /dockerode/3.3.1: + resolution: + { + integrity: sha512-AS2mr8Lp122aa5n6d99HkuTNdRV1wkkhHwBdcnY6V0+28D3DSYwhxAk85/mM9XwD3RMliTxyr63iuvn5ZblFYQ== + } + engines: { node: '>= 8.0' } dependencies: - docker-modem: 3.0.0 + docker-modem: 3.0.3 tar-fs: 2.0.1 transitivePeerDependencies: - supports-color dev: false /doctrine/3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + resolution: + { + integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + } + engines: { node: '>=6.0.0' } dependencies: esutils: 2.0.3 dev: true - /dom-serializer/1.3.1: - resolution: {integrity: sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q==} - dependencies: - domelementtype: 2.2.0 - domhandler: 4.2.0 - entities: 2.2.0 - dev: true - - /domelementtype/2.2.0: - resolution: {integrity: sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==} - dev: true - - /domhandler/4.2.0: - resolution: {integrity: sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==} - engines: {node: '>= 4'} - dependencies: - domelementtype: 2.2.0 - dev: true - - /domutils/2.6.0: - resolution: {integrity: sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA==} - dependencies: - dom-serializer: 1.3.1 - domelementtype: 2.2.0 - domhandler: 4.2.0 - dev: true - /dotenv-extended/2.9.0: - resolution: {integrity: sha512-MKc4WCqZj6Abx4rpDbQ9LsuBJldRLxLgFkY5qE+4JM7hXVYT/v8zyWGgnBeDjSOGzEecWOFPlosNpxfB9YnsCw==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-MKc4WCqZj6Abx4rpDbQ9LsuBJldRLxLgFkY5qE+4JM7hXVYT/v8zyWGgnBeDjSOGzEecWOFPlosNpxfB9YnsCw== + } + engines: { node: '>=6' } hasBin: true dependencies: auto-parse: 1.8.0 camelcase: 5.3.1 cross-spawn: 7.0.3 - dotenv: 8.2.0 + dotenv: 8.6.0 dev: false - /dotenv/8.2.0: - resolution: {integrity: sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==} - engines: {node: '>=8'} + /dotenv/8.6.0: + resolution: + { + integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + } + engines: { node: '>=10' } dev: false + /eastasianwidth/0.2.0: + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + } + dev: true + /ecdsa-sig-formatter/1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + resolution: + { + integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + } dependencies: safe-buffer: 5.2.1 dev: false - /electron-to-chromium/1.3.725: - resolution: {integrity: sha512-2BbeAESz7kc6KBzs7WVrMc1BY5waUphk4D4DX5dSQXJhsc3tP5ZFaiyuL0AB7vUKzDYpIeYwTYlEfxyjsGUrhw==} + /electron-to-chromium/1.4.48: + resolution: + { + integrity: sha512-RT3SEmpv7XUA+tKXrZGudAWLDpa7f8qmhjcLaM6OD/ERxjQ/zAojT8/Vvo0BSzbArkElFZ1WyZ9FuwAYbkdBNA== + } dev: true /emoji-regex/8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + } + + /emoji-regex/9.2.2: + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + } dev: true /end-of-stream/1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + resolution: + { + integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + } dependencies: once: 1.4.0 dev: false /enquirer/2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} + resolution: + { + integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + } + engines: { node: '>=8.6' } dependencies: ansi-colors: 4.1.1 dev: true - /entities/2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - dev: true - /error-ex/1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + resolution: + { + integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + } dependencies: is-arrayish: 0.2.1 dev: true - /esbuild/0.12.15: - resolution: {integrity: sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==} - hasBin: true - requiresBuild: true + /es6-promise/3.3.1: + resolution: { integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM= } dev: true - /esbuild/0.12.6: - resolution: {integrity: sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA==} + /esbuild-android-arm64/0.14.21: + resolution: + { + integrity: sha512-Bqgld1TY0wZv8TqiQmVxQFgYzz8ZmyzT7clXBDZFkOOdRybzsnj8AZuK1pwcLVA7Ya6XncHgJqIao7NFd3s0RQ== + } + engines: { node: '>=12' } + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64/0.14.21: + resolution: + { + integrity: sha512-j+Eg+e13djzyYINVvAbOo2/zvZ2DivuJJTaBrJnJHSD7kUNuGHRkHoSfFjbI80KHkn091w350wdmXDNSgRjfYQ== + } + engines: { node: '>=12' } + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64/0.14.21: + resolution: + { + integrity: sha512-nDNTKWDPI0RuoPj5BhcSB2z5EmZJJAyRtZLIjyXSqSpAyoB8eyAKXl4lB8U2P78Fnh4Lh1le/fmpewXE04JhBQ== + } + engines: { node: '>=12' } + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64/0.14.21: + resolution: + { + integrity: sha512-zIurkCHXhxELiDZtLGiexi8t8onQc2LtuE+S7457H/pP0g0MLRKMrsn/IN4LDkNe6lvBjuoZZi2OfelOHn831g== + } + engines: { node: '>=12' } + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64/0.14.21: + resolution: + { + integrity: sha512-wdxMmkJfbwcN+q85MpeUEamVZ40FNsBa9mPq8tAszDn8TRT2HoJvVRADPIIBa9SWWwlDChIMjkDKAnS3KS/sPA== + } + engines: { node: '>=12' } + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32/0.14.21: + resolution: + { + integrity: sha512-fmxvyzOPPh2xiEHojpCeIQP6pXcoKsWbz3ryDDIKLOsk4xp3GbpHIEAWP0xTeuhEbendmvBDVKbAVv3PnODXLg== + } + engines: { node: '>=12' } + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64/0.14.21: + resolution: + { + integrity: sha512-edZyNOv1ql+kpmlzdqzzDjRQYls+tSyi4QFi+PdBhATJFUqHsnNELWA9vMSzAaInPOEaVUTA5Ml28XFChcy4DA== + } + engines: { node: '>=12' } + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm/0.14.21: + resolution: + { + integrity: sha512-aSU5pUueK6afqmLQsbU+QcFBT62L+4G9hHMJDHWfxgid6hzhSmfRH9U/f+ymvxsSTr/HFRU4y7ox8ZyhlVl98w== + } + engines: { node: '>=12' } + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64/0.14.21: + resolution: + { + integrity: sha512-t5qxRkq4zdQC0zXpzSB2bTtfLgOvR0C6BXYaRE/6/k8/4SrkZcTZBeNu+xGvwCU4b5dU9ST9pwIWkK6T1grS8g== + } + engines: { node: '>=12' } + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le/0.14.21: + resolution: + { + integrity: sha512-jLZLQGCNlUsmIHtGqNvBs3zN+7a4D9ckf0JZ+jQTwHdZJ1SgV9mAjbB980OFo66LoY+WeM7t3WEnq3FjI1zw4A== + } + engines: { node: '>=12' } + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le/0.14.21: + resolution: + { + integrity: sha512-4TWxpK391en2UBUw6GSrukToTDu6lL9vkm3Ll40HrI08WG3qcnJu7bl8e1+GzelDsiw1QmfAY/nNvJ6iaHRpCQ== + } + engines: { node: '>=12' } + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64/0.14.21: + resolution: + { + integrity: sha512-fElngqOaOfTsF+u+oetDLHsPG74vB2ZaGZUqmGefAJn3a5z9Z2pNa4WpVbbKgHpaAAy5tWM1m1sbGohj6Ki6+Q== + } + engines: { node: '>=12' } + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x/0.14.21: + resolution: + { + integrity: sha512-brleZ6R5fYv0qQ7ZBwenQmP6i9TdvJCB092c/3D3pTLQHBGHJb5zWgKxOeS7bdHzmLy6a6W7GbFk6QKpjyD6QA== + } + engines: { node: '>=12' } + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64/0.14.21: + resolution: + { + integrity: sha512-nCEgsLCQ8RoFWVV8pVI+kX66ICwbPP/M9vEa0NJGIEB/Vs5sVGMqkf67oln90XNSkbc0bPBDuo4G6FxlF7PN8g== + } + engines: { node: '>=12' } + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-openbsd-64/0.14.21: + resolution: + { + integrity: sha512-h9zLMyVD0T73MDTVYIb/qUTokwI6EJH9O6wESuTNq6+XpMSr6C5aYZ4fvFKdNELW+Xsod+yDS2hV2JTUAbFrLA== + } + engines: { node: '>=12' } + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-sunos-64/0.14.21: + resolution: + { + integrity: sha512-Kl+7Cot32qd9oqpLdB1tEGXEkjBlijrIxMJ0+vlDFaqsODutif25on0IZlFxEBtL2Gosd4p5WCV1U7UskNQfXA== + } + engines: { node: '>=12' } + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32/0.14.21: + resolution: + { + integrity: sha512-V7vnTq67xPBUCk/9UtlolmQ798Ecjdr1ZoI1vcSgw7M82aSSt0eZdP6bh5KAFZU8pxDcx3qoHyWQfHYr11f22A== + } + engines: { node: '>=12' } + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64/0.14.21: + resolution: + { + integrity: sha512-kDgHjKOHwjfJDCyRGELzVxiP/RBJBTA+wyspf78MTTJQkyPuxH2vChReNdWc+dU2S4gIZFHMdP1Qrl/k22ZmaA== + } + engines: { node: '>=12' } + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64/0.14.21: + resolution: + { + integrity: sha512-8Sbo0zpzgwWrwjQYLmHF78f7E2xg5Ve63bjB2ng3V2aManilnnTGaliq2snYg+NOX60+hEvJHRdVnuIAHW0lVw== + } + engines: { node: '>=12' } + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild/0.14.21: + resolution: + { + integrity: sha512-7WEoNMBJdLN993dr9h0CpFHPRc3yFZD+EAVY9lg6syJJ12gc5fHq8d75QRExuhnMkT2DaRiIKFThRvDWP+fO+A== + } + engines: { node: '>=12' } hasBin: true requiresBuild: true + optionalDependencies: + esbuild-android-arm64: 0.14.21 + esbuild-darwin-64: 0.14.21 + esbuild-darwin-arm64: 0.14.21 + esbuild-freebsd-64: 0.14.21 + esbuild-freebsd-arm64: 0.14.21 + esbuild-linux-32: 0.14.21 + esbuild-linux-64: 0.14.21 + esbuild-linux-arm: 0.14.21 + esbuild-linux-arm64: 0.14.21 + esbuild-linux-mips64le: 0.14.21 + esbuild-linux-ppc64le: 0.14.21 + esbuild-linux-riscv64: 0.14.21 + esbuild-linux-s390x: 0.14.21 + esbuild-netbsd-64: 0.14.21 + esbuild-openbsd-64: 0.14.21 + esbuild-sunos-64: 0.14.21 + esbuild-windows-32: 0.14.21 + esbuild-windows-64: 0.14.21 + esbuild-windows-arm64: 0.14.21 dev: true /escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + } + engines: { node: '>=6' } dev: true /escape-string-regexp/1.0.5: - resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} - engines: {node: '>=0.8.0'} + resolution: { integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= } + engines: { node: '>=0.8.0' } dev: true /escape-string-regexp/4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + } + engines: { node: '>=10' } dev: true - /eslint-config-prettier/8.3.0_eslint@7.28.0: - resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==} + /eslint-config-prettier/8.3.0_eslint@7.32.0: + resolution: + { + integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + } hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 7.28.0 + eslint: 7.32.0 dev: true - /eslint-plugin-svelte3/3.2.0_eslint@7.28.0+svelte@3.38.2: - resolution: {integrity: sha512-qdWB1QN21dEozsJFdR8XlEhMnsS6aKHjsXWuNmchYwxoet5I6QdCr1Xcq62++IzRBMCNCeH4waXqSOAdqrZzgA==} - engines: {node: '>=10'} + /eslint-plugin-svelte3/3.2.1_eslint@7.32.0+svelte@3.46.4: + resolution: + { + integrity: sha512-YoBR9mLoKCjGghJ/gvpnFZKaMEu/VRcuxpSRS8KuozuEo7CdBH7bmBHa6FmMm0i4kJnOyx+PVsaptz96K6H/4Q== + } + engines: { node: '>=10' } peerDependencies: eslint: '>=6.0.0' svelte: ^3.2.0 dependencies: - eslint: 7.28.0 - svelte: 3.38.2 + eslint: 7.32.0 + svelte: 3.46.4 dev: true /eslint-scope/5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + resolution: + { + integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + } + engines: { node: '>=8.0.0' } dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 dev: true /eslint-utils/2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + } + engines: { node: '>=6' } dependencies: eslint-visitor-keys: 1.3.0 dev: true - /eslint-utils/3.0.0_eslint@7.28.0: - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + /eslint-utils/3.0.0_eslint@7.32.0: + resolution: + { + integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + } + engines: { node: ^10.0.0 || ^12.0.0 || >= 14.0.0 } peerDependencies: eslint: '>=5' dependencies: - eslint: 7.28.0 - eslint-visitor-keys: 2.0.0 + eslint: 7.32.0 + eslint-visitor-keys: 2.1.0 dev: true /eslint-visitor-keys/1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + } + engines: { node: '>=4' } dev: true - /eslint-visitor-keys/2.0.0: - resolution: {integrity: sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==} - engines: {node: '>=10'} + /eslint-visitor-keys/2.1.0: + resolution: + { + integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + } + engines: { node: '>=10' } dev: true - /eslint/7.28.0: - resolution: {integrity: sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==} - engines: {node: ^10.12.0 || >=12.0.0} + /eslint/7.32.0: + resolution: + { + integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + } + engines: { node: ^10.12.0 || >=12.0.0 } hasBin: true dependencies: '@babel/code-frame': 7.12.11 - '@eslint/eslintrc': 0.4.2 + '@eslint/eslintrc': 0.4.3 + '@humanwhocodes/config-array': 0.5.0 ajv: 6.12.6 - chalk: 4.1.1 + chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.1 + debug: 4.3.3 doctrine: 3.0.0 enquirer: 2.3.6 escape-string-regexp: 4.0.0 eslint-scope: 5.1.1 eslint-utils: 2.1.0 - eslint-visitor-keys: 2.0.0 + eslint-visitor-keys: 2.1.0 espree: 7.3.1 esquery: 1.4.0 esutils: 2.0.3 @@ -1256,11 +2808,11 @@ packages: file-entry-cache: 6.0.1 functional-red-black-tree: 1.0.1 glob-parent: 5.1.2 - globals: 13.8.0 + globals: 13.12.0 ignore: 4.0.6 import-fresh: 3.3.0 imurmurhash: 0.1.4 - is-glob: 4.0.1 + is-glob: 4.0.3 js-yaml: 3.14.1 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -1269,11 +2821,11 @@ packages: natural-compare: 1.4.0 optionator: 0.9.1 progress: 2.0.3 - regexpp: 3.1.0 + regexpp: 3.2.0 semver: 7.3.5 - strip-ansi: 6.0.0 + strip-ansi: 6.0.1 strip-json-comments: 3.1.1 - table: 6.6.0 + table: 6.7.2 text-table: 0.2.0 v8-compile-cache: 2.3.0 transitivePeerDependencies: @@ -1281,200 +2833,337 @@ packages: dev: true /espree/7.3.1: - resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} - engines: {node: ^10.12.0 || >=12.0.0} + resolution: + { + integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + } + engines: { node: ^10.12.0 || >=12.0.0 } dependencies: acorn: 7.4.1 - acorn-jsx: 5.3.1_acorn@7.4.1 + acorn-jsx: 5.3.2_acorn@7.4.1 eslint-visitor-keys: 1.3.0 dev: true /esprima/4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + } + engines: { node: '>=4' } hasBin: true dev: true /esquery/1.4.0: - resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} - engines: {node: '>=0.10'} + resolution: + { + integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + } + engines: { node: '>=0.10' } dependencies: - estraverse: 5.2.0 + estraverse: 5.3.0 dev: true /esrecurse/4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + } + engines: { node: '>=4.0' } dependencies: - estraverse: 5.2.0 + estraverse: 5.3.0 dev: true /estraverse/4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} + resolution: + { + integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + } + engines: { node: '>=4.0' } dev: true - /estraverse/5.2.0: - resolution: {integrity: sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==} - engines: {node: '>=4.0'} + /estraverse/5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + } + engines: { node: '>=4.0' } dev: true /estree-walker/2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + resolution: + { + integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + } dev: true /esutils/2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + } + engines: { node: '>=0.10.0' } + dev: true + + /execa/5.1.1: + resolution: + { + integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + } + engines: { node: '>=10' } + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.5 + strip-final-newline: 2.0.0 + dev: true + + /exit/0.1.2: + resolution: { integrity: sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= } + engines: { node: '>= 0.8.0' } dev: true /fast-deep-equal/3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + } dev: true - /fast-glob/3.2.5: - resolution: {integrity: sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==} - engines: {node: '>=8'} + /fast-glob/3.2.11: + resolution: + { + integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + } + engines: { node: '>=8.6.0' } dependencies: '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.7 + '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.4 - picomatch: 2.3.0 dev: true /fast-json-stable-stringify/2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + } dev: true /fast-levenshtein/2.0.6: - resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} + resolution: { integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= } dev: true - /fastq/1.11.0: - resolution: {integrity: sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==} + /fastq/1.13.0: + resolution: + { + integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + } dependencies: reusify: 1.0.4 dev: true /file-entry-cache/6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + resolution: + { + integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + } + engines: { node: ^10.12.0 || >=12.0.0 } dependencies: flat-cache: 3.0.4 dev: true /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + } + engines: { node: '>=8' } dependencies: to-regex-range: 5.0.1 dev: true /flat-cache/3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} + resolution: + { + integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + } + engines: { node: ^10.12.0 || >=12.0.0 } dependencies: - flatted: 3.1.1 + flatted: 3.2.2 rimraf: 3.0.2 dev: true - /flatted/3.1.1: - resolution: {integrity: sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==} + /flatted/3.2.2: + resolution: + { + integrity: sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== + } dev: true - /fraction.js/4.1.1: - resolution: {integrity: sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==} + /form-data-encoder/1.7.1: + resolution: + { + integrity: sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg== + } + dev: false + + /fraction.js/4.1.2: + resolution: + { + integrity: sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== + } dev: true /fs-constants/1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + resolution: + { + integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + } dev: false - /fs-extra/10.0.0: - resolution: {integrity: sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==} - engines: {node: '>=12'} - dependencies: - graceful-fs: 4.2.6 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: true - /fs-minipass/2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + } + engines: { node: '>= 8' } dependencies: - minipass: 3.1.3 + minipass: 3.1.5 dev: false /fs.realpath/1.0.0: - resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + resolution: { integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8= } /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + resolution: + { + integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] + requiresBuild: true dev: true optional: true /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + resolution: + { + integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + } /function.name/1.0.13: - resolution: {integrity: sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA==} + resolution: + { + integrity: sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA== + } dependencies: noop6: 1.0.9 dev: false /functional-red-black-tree/1.0.1: - resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} + resolution: { integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= } dev: true - /gauge/2.7.4: - resolution: {integrity: sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=} + /gauge/3.0.1: + resolution: + { + integrity: sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ== + } + engines: { node: '>=10' } dependencies: - aproba: 1.2.0 + aproba: 2.0.0 + color-support: 1.1.3 console-control-strings: 1.1.0 has-unicode: 2.0.1 object-assign: 4.1.1 - signal-exit: 3.0.3 - string-width: 1.0.2 - strip-ansi: 3.0.1 - wide-align: 1.1.3 + signal-exit: 3.0.5 + string-width: 2.1.1 + strip-ansi: 4.0.0 + wide-align: 1.1.5 dev: false - /generate-password/1.6.0: - resolution: {integrity: sha512-YUJTQkApkLT/fru0QdYWP0lVZdPKhF5kXCP24sgI4gR/vFMJFopCj5t1+9FAKIYcML/nxzx2PMkA1ymO1FC+tQ==} + /generate-password/1.7.0: + resolution: + { + integrity: sha512-WPCtlfy0jexf7W5IbwxGUgpIDvsZIohbI2DAq2Q6TSlKKis+G4GT9sxvPxrZUGL8kP6WUXMWNqYnxY6DDKAdFA== + } dev: false + /get-intrinsic/1.1.1: + resolution: + { + integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + } + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.2 + dev: false + + /get-port/5.1.1: + resolution: + { + integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + } + engines: { node: '>=8' } + dev: false + + /get-port/6.0.0: + resolution: + { + integrity: sha512-qSVkVF6Eq1GdL/cBNiFuP4nUHMF7OEMTqEjC6alR2N90u8BFOoO0PFhNTX2QtAUoGrz8NnrSWj85TZ8YXZ6LOA== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + dev: false + + /get-stream/5.2.0: + resolution: + { + integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + } + engines: { node: '>=8' } + dependencies: + pump: 3.0.0 + dev: false + + /get-stream/6.0.1: + resolution: + { + integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + } + engines: { node: '>=10' } + /glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + } + engines: { node: '>= 6' } dependencies: - is-glob: 4.0.1 + is-glob: 4.0.3 dev: true - /glob-parent/6.0.0: - resolution: {integrity: sha512-Hdd4287VEJcZXUwv1l8a+vXC1GjOQqXe+VS30w/ypihpcnu9M1n3xeYeJu5CBpeEQj2nAab2xxz28GuA3vp4Ww==} - engines: {node: '>=10.13.0'} + /glob-parent/6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + } + engines: { node: '>=10.13.0' } dependencies: - is-glob: 4.0.1 + is-glob: 4.0.3 dev: true - /glob/7.1.6: - resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.0.4 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: false - - /glob/7.1.7: - resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + /glob/7.2.0: + resolution: + { + integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + } dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -1483,228 +3172,409 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 - /globals/13.8.0: - resolution: {integrity: sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==} - engines: {node: '>=8'} + /globals/13.12.0: + resolution: + { + integrity: sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== + } + engines: { node: '>=8' } dependencies: type-fest: 0.20.2 dev: true - /globals/13.9.0: - resolution: {integrity: sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 + /globals/9.18.0: + resolution: + { + integrity: sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== + } + engines: { node: '>=0.10.0' } dev: true /globalyzer/0.1.0: - resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + resolution: + { + integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== + } dev: true - /globby/11.0.3: - resolution: {integrity: sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==} - engines: {node: '>=10'} + /globby/11.0.4: + resolution: + { + integrity: sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + } + engines: { node: '>=10' } dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.5 + fast-glob: 3.2.11 ignore: 5.1.8 merge2: 1.4.1 slash: 3.0.0 dev: true /globrex/0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + resolution: + { + integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + } dev: true - /graceful-fs/4.2.6: - resolution: {integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==} + /got/12.0.1: + resolution: + { + integrity: sha512-1Zhoh+lDej3t7Ks1BP/Jufn+rNqdiHQgUOcTxHzg2Dao1LQfp5S4Iq0T3iBxN4Zdo7QqCJL+WJUNzDX6rCP2Ew== + } + engines: { node: '>=14.16' } + dependencies: + '@sindresorhus/is': 4.2.0 + '@szmarczak/http-timer': 5.0.1 + '@types/cacheable-request': 6.0.2 + '@types/responselike': 1.0.0 + cacheable-lookup: 6.0.4 + cacheable-request: 7.0.2 + decompress-response: 6.0.0 + form-data-encoder: 1.7.1 + get-stream: 6.0.1 + http2-wrapper: 2.1.10 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 2.0.0 + dev: false + + /graceful-fs/4.2.8: + resolution: + { + integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + } + dev: true + + /has-ansi/2.0.0: + resolution: { integrity: sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= } + engines: { node: '>=0.10.0' } + dependencies: + ansi-regex: 2.1.1 dev: true /has-flag/3.0.0: - resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} - engines: {node: '>=4'} + resolution: { integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0= } + engines: { node: '>=4' } dev: true /has-flag/4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + } + engines: { node: '>=8' } dev: true + /has-symbols/1.0.2: + resolution: + { + integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + } + engines: { node: '>= 0.4' } + dev: false + /has-unicode/2.0.1: - resolution: {integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=} + resolution: { integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= } dev: false /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} + resolution: + { + integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + } + engines: { node: '>= 0.4.0' } dependencies: function-bind: 1.1.1 - /hex-color-regex/1.1.0: - resolution: {integrity: sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==} + /home-or-tmp/2.0.0: + resolution: { integrity: sha1-42w/LSyufXRqhX440Y1fMqeILbg= } + engines: { node: '>=0.10.0' } + dependencies: + os-homedir: 1.0.2 + os-tmpdir: 1.0.2 dev: true - /hsl-regex/1.0.0: - resolution: {integrity: sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=} - dev: true + /http-cache-semantics/4.1.0: + resolution: + { + integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + } + dev: false - /hsla-regex/1.0.0: - resolution: {integrity: sha1-wc56MWjIxmFAM6S194d/OyJfnDg=} - dev: true - - /html-tags/3.1.0: - resolution: {integrity: sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==} - engines: {node: '>=8'} - dev: true + /http2-wrapper/2.1.10: + resolution: + { + integrity: sha512-QHgsdYkieKp+6JbXP25P+tepqiHYd+FVnDwXpxi/BlUcoIB0nsmTOymTNvETuTO+pDuwcSklPE72VR3DqV+Haw== + } + engines: { node: '>=10.19.0' } + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + dev: false /https-proxy-agent/5.0.0: - resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + } + engines: { node: '>= 6' } dependencies: agent-base: 6.0.2 - debug: 4.3.1 + debug: 4.3.3 transitivePeerDependencies: - supports-color dev: false + /human-signals/2.1.0: + resolution: + { + integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + } + engines: { node: '>=10.17.0' } + dev: true + + /husky/7.0.4: + resolution: + { + integrity: sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== + } + engines: { node: '>=12' } + hasBin: true + dev: true + /ieee754/1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + } dev: false /ignore/4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} + resolution: + { + integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + } + engines: { node: '>= 4' } dev: true /ignore/5.1.8: - resolution: {integrity: sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==} - engines: {node: '>= 4'} + resolution: + { + integrity: sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + } + engines: { node: '>= 4' } dev: true /import-cwd/3.0.0: - resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg== + } + engines: { node: '>=8' } dependencies: import-from: 3.0.0 dev: true /import-fresh/3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + } + engines: { node: '>=6' } dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 dev: true /import-from/3.0.0: - resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== + } + engines: { node: '>=8' } dependencies: resolve-from: 5.0.0 dev: true /imurmurhash/0.1.4: - resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} - engines: {node: '>=0.8.19'} + resolution: { integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o= } + engines: { node: '>=0.8.19' } + dev: true + + /indent-string/4.0.0: + resolution: + { + integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + } + engines: { node: '>=8' } dev: true /inflight/1.0.6: - resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} + resolution: { integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= } dependencies: once: 1.4.0 wrappy: 1.0.2 /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + } - /interpret/1.4.0: - resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} - engines: {node: '>= 0.10'} + /invariant/2.2.4: + resolution: + { + integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + } + dependencies: + loose-envify: 1.4.0 + dev: true + + /ioredis/4.28.3: + resolution: + { + integrity: sha512-9JOWVgBnuSxpIgfpjc1OeY1OLmA4t2KOWWURTDRXky+eWO0LZhI33pQNT9gYxANUXfh5p/zYephYni6GPRsksQ== + } + engines: { node: '>=6' } + dependencies: + cluster-key-slot: 1.1.0 + debug: 4.3.3 + denque: 1.5.1 + lodash.defaults: 4.2.0 + lodash.flatten: 4.4.0 + lodash.isarguments: 3.1.0 + p-map: 2.1.0 + redis-commands: 1.7.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color dev: false - /is-absolute-url/3.0.3: - resolution: {integrity: sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==} - engines: {node: '>=8'} - dev: true - /is-arrayish/0.2.1: - resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} - dev: true - - /is-arrayish/0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + resolution: { integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= } dev: true /is-binary-path/2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + } + engines: { node: '>=8' } dependencies: binary-extensions: 2.2.0 dev: true - /is-color-stop/1.1.0: - resolution: {integrity: sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=} - dependencies: - css-color-names: 0.0.4 - hex-color-regex: 1.1.0 - hsl-regex: 1.0.0 - hsla-regex: 1.0.0 - rgb-regex: 1.0.1 - rgba-regex: 1.0.0 - dev: true - - /is-core-module/2.4.0: - resolution: {integrity: sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==} + /is-core-module/2.8.1: + resolution: + { + integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + } dependencies: has: 1.0.3 - - /is-extglob/2.1.1: - resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} - engines: {node: '>=0.10.0'} dev: true - /is-fullwidth-code-point/1.0.0: - resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=} - engines: {node: '>=0.10.0'} - dependencies: - number-is-nan: 1.0.1 + /is-extglob/2.1.1: + resolution: { integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= } + engines: { node: '>=0.10.0' } + dev: true + + /is-finite/1.1.0: + resolution: + { + integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + } + engines: { node: '>=0.10.0' } + dev: true + + /is-fullwidth-code-point/2.0.0: + resolution: { integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= } + engines: { node: '>=4' } dev: false /is-fullwidth-code-point/3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + } + engines: { node: '>=8' } + + /is-fullwidth-code-point/4.0.0: + resolution: + { + integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + } + engines: { node: '>=12' } dev: true - /is-glob/4.0.1: - resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==} - engines: {node: '>=0.10.0'} + /is-glob/4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + } + engines: { node: '>=0.10.0' } dependencies: is-extglob: 2.1.1 dev: true - /is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /is-resolvable/1.1.0: - resolution: {integrity: sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==} - dev: true - - /isarray/1.0.0: - resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} + /is-nan/1.3.2: + resolution: + { + integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + } + engines: { node: '>= 0.4' } + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 dev: false + /is-number/7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + } + engines: { node: '>=0.12.0' } + dev: true + + /is-stream/2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + } + engines: { node: '>=8' } + dev: true + /isexe/2.0.0: - resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} + resolution: { integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= } + + /js-cookie/3.0.1: + resolution: + { + integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw== + } + engines: { node: '>=12' } + dev: false + + /js-tokens/3.0.2: + resolution: { integrity: sha1-mGbfOVECEw449/mWvOtlRDIJwls= } + dev: true /js-tokens/4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + } dev: true /js-yaml/3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + resolution: + { + integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + } hasBin: true dependencies: argparse: 1.0.10 @@ -1712,39 +3582,68 @@ packages: dev: true /js-yaml/4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + } hasBin: true dependencies: argparse: 2.0.1 dev: false + /jsesc/0.5.0: + resolution: { integrity: sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= } + hasBin: true + dev: true + + /jsesc/1.3.0: + resolution: { integrity: sha1-RsP+yMGJKxKwgz25vHYiF226s0s= } + hasBin: true + dev: true + + /json-buffer/3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + } + dev: false + /json-parse-even-better-errors/2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + } dev: true /json-schema-traverse/0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + } dev: true /json-schema-traverse/1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + } dev: true /json-stable-stringify-without-jsonify/1.0.1: - resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} + resolution: { integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= } dev: true - /jsonfile/6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.6 + /json5/0.5.1: + resolution: { integrity: sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= } + hasBin: true dev: true /jsonwebtoken/8.5.1: - resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} - engines: {node: '>=4', npm: '>=1.4.28'} + resolution: + { + integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + } + engines: { node: '>=4', npm: '>=1.4.28' } dependencies: jws: 3.2.2 lodash.includes: 4.3.0 @@ -1754,12 +3653,15 @@ packages: lodash.isplainobject: 4.0.6 lodash.isstring: 4.0.1 lodash.once: 4.1.1 - ms: 2.1.3 + ms: 2.1.2 semver: 5.7.1 dev: false /jwa/1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + resolution: + { + integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + } dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 @@ -1767,390 +3669,600 @@ packages: dev: false /jws/3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + resolution: + { + integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + } dependencies: jwa: 1.4.1 safe-buffer: 5.2.1 dev: false - /kareem/2.3.2: - resolution: {integrity: sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==} + /keyv/4.0.4: + resolution: + { + integrity: sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg== + } + dependencies: + json-buffer: 3.0.1 dev: false /kleur/4.1.4: - resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== + } + engines: { node: '>=6' } dev: true /levn/0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + } + engines: { node: '>= 0.8.0' } dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 dev: true - /lilconfig/2.0.3: - resolution: {integrity: sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==} - engines: {node: '>=10'} + /lilconfig/2.0.4: + resolution: + { + integrity: sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + } + engines: { node: '>=10' } dev: true /lines-and-columns/1.1.6: - resolution: {integrity: sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=} + resolution: { integrity: sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= } + dev: true + + /lint-staged/12.3.3: + resolution: + { + integrity: sha512-OqcLsqcPOqzvsfkxjeBpZylgJ3SRG1RYqc9LxC6tkt6tNsq1bNVkAixBwX09f6CobcHswzqVOCBpFR1Fck0+ag== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + hasBin: true + dependencies: + cli-truncate: 3.1.0 + colorette: 2.0.16 + commander: 8.3.0 + debug: 4.3.3_supports-color@9.2.1 + execa: 5.1.1 + lilconfig: 2.0.4 + listr2: 4.0.1 + micromatch: 4.0.4 + normalize-path: 3.0.0 + object-inspect: 1.12.0 + string-argv: 0.3.1 + supports-color: 9.2.1 + yaml: 1.10.2 + transitivePeerDependencies: + - enquirer + dev: true + + /listr2/4.0.1: + resolution: + { + integrity: sha512-D65Nl+zyYHL2jQBGmxtH/pU8koPZo5C8iCNE8EoB04RwPgQG1wuaKwVbeZv9LJpiH4Nxs0FCp+nNcG8OqpniiA== + } + engines: { node: '>=12' } + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.16 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.5.2 + through: 2.3.8 + wrap-ansi: 7.0.0 dev: true /lodash.clonedeep/4.5.0: - resolution: {integrity: sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=} + resolution: { integrity: sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= } dev: true + /lodash.defaults/4.2.0: + resolution: { integrity: sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= } + dev: false + /lodash.flatten/4.4.0: - resolution: {integrity: sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=} - dev: true + resolution: { integrity: sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= } + dev: false /lodash.includes/4.3.0: - resolution: {integrity: sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=} + resolution: { integrity: sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= } + dev: false + + /lodash.isarguments/3.1.0: + resolution: { integrity: sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= } dev: false /lodash.isboolean/3.0.3: - resolution: {integrity: sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=} + resolution: { integrity: sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= } dev: false /lodash.isinteger/4.0.4: - resolution: {integrity: sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=} + resolution: { integrity: sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= } dev: false /lodash.isnumber/3.0.3: - resolution: {integrity: sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=} + resolution: { integrity: sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= } dev: false /lodash.isplainobject/4.0.6: - resolution: {integrity: sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=} + resolution: { integrity: sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= } dev: false /lodash.isstring/4.0.1: - resolution: {integrity: sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=} + resolution: { integrity: sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= } dev: false - /lodash.memoize/4.1.2: - resolution: {integrity: sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=} - dev: true - /lodash.merge/4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + } dev: true /lodash.once/4.1.1: - resolution: {integrity: sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=} + resolution: { integrity: sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= } dev: false - /lodash.toarray/4.4.0: - resolution: {integrity: sha1-JMS/zWsvuji/0FlNsRedjptlZWE=} - dev: true - - /lodash.topath/4.5.2: - resolution: {integrity: sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=} - dev: true - /lodash.truncate/4.4.2: - resolution: {integrity: sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=} - dev: true - - /lodash.uniq/4.5.0: - resolution: {integrity: sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=} + resolution: { integrity: sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= } dev: true /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + } + + /log-update/4.0.0: + resolution: + { + integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + } + engines: { node: '>=10' } + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + dev: true + + /loose-envify/1.4.0: + resolution: + { + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + } + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: true + + /lowercase-keys/2.0.0: + resolution: + { + integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + } + engines: { node: '>=8' } + dev: false + + /lowercase-keys/3.0.0: + resolution: + { + integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + dev: false + + /lru-cache/4.1.5: + resolution: + { + integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + } + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 dev: true /lru-cache/6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + } + engines: { node: '>=10' } dependencies: yallist: 4.0.0 + /lru_map/0.3.3: + resolution: { integrity: sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= } + dev: false + /magic-string/0.25.7: - resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==} + resolution: + { + integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + } dependencies: sourcemap-codec: 1.4.8 dev: true /make-dir/3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + } + engines: { node: '>=8' } dependencies: semver: 6.3.0 dev: false - /mdn-data/2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + /make-error/1.3.6: + resolution: + { + integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + } dev: true - /memory-pager/1.5.0: - resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} - dev: false - optional: true + /merge-stream/2.0.0: + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + } + dev: true /merge2/1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + } + engines: { node: '>= 8' } dev: true /micromatch/4.0.4: - resolution: {integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==} - engines: {node: '>=8.6'} + resolution: + { + integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + } + engines: { node: '>=8.6' } dependencies: braces: 3.0.2 picomatch: 2.3.0 dev: true - /microtip/0.2.2: - resolution: {integrity: sha512-oah38eH5vSHVFP6yXjbKWOYt92mav++0j3zh844h1vhOscqEg7Rf4agDEIwUTFCcAPPdlhYUQMe6eZfHgD+4oQ==} + /mimic-fn/2.1.0: + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + } + engines: { node: '>=6' } + dev: true + + /mimic-response/1.0.1: + resolution: + { + integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + } + engines: { node: '>=4' } + dev: false + + /mimic-response/3.1.0: + resolution: + { + integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + } + engines: { node: '>=10' } dev: false /min-indent/1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + } + engines: { node: '>=4' } dev: true /minimatch/3.0.4: - resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} + resolution: + { + integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + } dependencies: brace-expansion: 1.1.11 /minimist/1.2.5: - resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} + resolution: + { + integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + } dev: true - /minipass/3.1.3: - resolution: {integrity: sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==} - engines: {node: '>=8'} + /minipass/3.1.5: + resolution: + { + integrity: sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== + } + engines: { node: '>=8' } dependencies: yallist: 4.0.0 dev: false /minizlib/2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + } + engines: { node: '>= 8' } dependencies: - minipass: 3.1.3 + minipass: 3.1.5 yallist: 4.0.0 dev: false /mkdirp-classic/0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + resolution: + { + integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + } dev: false + /mkdirp/0.5.5: + resolution: + { + integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + } + hasBin: true + dependencies: + minimist: 1.2.5 + dev: true + /mkdirp/1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + } + engines: { node: '>=10' } hasBin: true dev: false - /modern-normalize/1.1.0: - resolution: {integrity: sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==} - engines: {node: '>=6'} - dev: true - - /mongodb/3.6.8: - resolution: {integrity: sha512-sDjJvI73WjON1vapcbyBD3Ao9/VN3TKYY8/QX9EPbs22KaCSrQ5rXo5ZZd44tWJ3wl3FlnrFZ+KyUtNH6+1ZPQ==} - engines: {node: '>=4'} - peerDependencies: - aws4: '*' - bson-ext: '*' - kerberos: '*' - mongodb-client-encryption: '*' - mongodb-extjson: '*' - snappy: '*' - peerDependenciesMeta: - aws4: - optional: true - bson-ext: - optional: true - kerberos: - optional: true - mongodb-client-encryption: - optional: true - mongodb-extjson: - optional: true - snappy: - optional: true + /moment-timezone/0.5.33: + resolution: + { + integrity: sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== + } dependencies: - bl: 2.2.1 - bson: 1.1.6 - denque: 1.5.0 - optional-require: 1.0.3 - safe-buffer: 5.2.1 - optionalDependencies: - saslprep: 1.0.3 + moment: 2.29.1 dev: false - /mongoose-legacy-pluralize/1.0.2_mongoose@5.12.13: - resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==} - peerDependencies: - mongoose: '*' - dependencies: - mongoose: 5.12.13 + /moment/2.29.1: + resolution: + { + integrity: sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + } dev: false - /mongoose/5.12.13: - resolution: {integrity: sha512-QGn1FCzZ8Z+mMGVg8oR2kQw4NmhLloCHsw1NqKWg3Yr7WfPzkE4pe7s9P6o5pkYGsku17n9mqMHowne7EFK/zQ==} - engines: {node: '>=4.0.0'} - dependencies: - '@types/mongodb': 3.6.12 - bson: 1.1.6 - kareem: 2.3.2 - mongodb: 3.6.8 - mongoose-legacy-pluralize: 1.0.2_mongoose@5.12.13 - mpath: 0.8.3 - mquery: 3.2.5 - ms: 2.1.2 - regexp-clone: 1.0.0 - safe-buffer: 5.2.1 - sift: 13.5.2 - sliced: 1.0.1 - transitivePeerDependencies: - - aws4 - - bson-ext - - kerberos - - mongodb-client-encryption - - mongodb-extjson - - snappy - dev: false - - /mpath/0.8.3: - resolution: {integrity: sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==} - engines: {node: '>=4.0.0'} - dev: false - - /mquery/3.2.5: - resolution: {integrity: sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==} - engines: {node: '>=4.0.0'} - dependencies: - bluebird: 3.5.1 - debug: 3.1.0 - regexp-clone: 1.0.0 - safe-buffer: 5.1.2 - sliced: 1.0.1 - dev: false - - /mri/1.1.6: - resolution: {integrity: sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==} - engines: {node: '>=4'} + /mri/1.2.0: + resolution: + { + integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + } + engines: { node: '>=4' } dev: true /ms/2.0.0: - resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} - dev: false + resolution: { integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= } + dev: true /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + resolution: + { + integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + } - /ms/2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /msgpackr-extract/1.0.15: + resolution: + { + integrity: sha512-vgJgzFva0/4/mt84wXf3CRCDPHKqiqk5t7/kVSjk/V2IvwSjoStHhxyq/b2+VrWcch3sxiNQOJEWXgI86Fm7AQ== + } + requiresBuild: true + dependencies: + nan: 2.15.0 + node-gyp-build: 4.3.0 + dev: false + optional: true + + /msgpackr/1.4.7: + resolution: + { + integrity: sha512-bhC8Ed1au3L3oHaR/fe4lk4w7PLGFcWQ5XY/Tk9N6tzDRz8YndjCG68TD8zcvYZoxNtw767eF/7VpaTpU9kf9w== + } + optionalDependencies: + msgpackr-extract: 1.0.15 dev: false - /nanoid/3.1.23: - resolution: {integrity: sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + /nan/2.15.0: + resolution: + { + integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== + } + dev: false + optional: true + + /nanoid/3.2.0: + resolution: + { + integrity: sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } hasBin: true dev: true /natural-compare/1.4.0: - resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} + resolution: { integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= } dev: true /node-addon-api/3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} + resolution: + { + integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + } dev: false - /node-emoji/1.10.0: - resolution: {integrity: sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==} + /node-fetch/2.6.6: + resolution: + { + integrity: sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + } + engines: { node: 4.x || >=6.0.0 } dependencies: - lodash.toarray: 4.4.0 - dev: true - - /node-fetch/2.6.1: - resolution: {integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==} - engines: {node: 4.x || >=6.0.0} + whatwg-url: 5.0.0 dev: false - /node-releases/1.1.71: - resolution: {integrity: sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==} + /node-forge/1.2.1: + resolution: + { + integrity: sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== + } + engines: { node: '>= 6.13.0' } + dev: false + + /node-gyp-build/4.3.0: + resolution: + { + integrity: sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + } + hasBin: true + dev: false + optional: true + + /node-releases/2.0.1: + resolution: + { + integrity: sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + } dev: true /noop6/1.0.9: - resolution: {integrity: sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA==} + resolution: + { + integrity: sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA== + } dev: false /nopt/5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + } + engines: { node: '>=6' } hasBin: true dependencies: abbrev: 1.1.1 dev: false /normalize-path/3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + } + engines: { node: '>=0.10.0' } dev: true /normalize-range/0.1.2: - resolution: {integrity: sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=} - engines: {node: '>=0.10.0'} + resolution: { integrity: sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= } + engines: { node: '>=0.10.0' } dev: true - /normalize-url/4.5.0: - resolution: {integrity: sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==} - engines: {node: '>=8'} - dev: true + /normalize-url/6.1.0: + resolution: + { + integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + } + engines: { node: '>=10' } + dev: false - /npmlog/4.1.2: - resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==} + /npm-run-path/4.0.1: + resolution: + { + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + } + engines: { node: '>=8' } dependencies: - are-we-there-yet: 1.1.5 + path-key: 3.1.1 + dev: true + + /npmlog/5.0.1: + resolution: + { + integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + } + dependencies: + are-we-there-yet: 2.0.0 console-control-strings: 1.1.0 - gauge: 2.7.4 + gauge: 3.0.1 set-blocking: 2.0.0 dev: false - /nth-check/2.0.0: - resolution: {integrity: sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==} - dependencies: - boolbase: 1.0.0 - dev: true - - /number-is-nan/1.0.1: - resolution: {integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=} - engines: {node: '>=0.10.0'} - dev: false - /object-assign/4.1.1: - resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} - engines: {node: '>=0.10.0'} + resolution: { integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= } + engines: { node: '>=0.10.0' } dev: false /object-hash/2.2.0: - resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + } + engines: { node: '>= 6' } dev: true + /object-inspect/1.12.0: + resolution: + { + integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + } + dev: true + + /object-keys/1.1.1: + resolution: + { + integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + } + engines: { node: '>= 0.4' } + dev: false + /once/1.4.0: - resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + resolution: { integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E= } dependencies: wrappy: 1.0.2 - /optional-require/1.0.3: - resolution: {integrity: sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==} - engines: {node: '>=4'} - dev: false + /onetime/5.1.2: + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + } + engines: { node: '>=6' } + dependencies: + mimic-fn: 2.1.0 + dev: true /optionator/0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + } + engines: { node: '>= 0.8.0' } dependencies: - deep-is: 0.1.3 + deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 @@ -2158,132 +4270,125 @@ packages: word-wrap: 1.2.3 dev: true + /os-homedir/1.0.2: + resolution: { integrity: sha1-/7xJiDNuDoM94MFox+8VISGqf7M= } + engines: { node: '>=0.10.0' } + dev: true + + /os-tmpdir/1.0.2: + resolution: { integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= } + engines: { node: '>=0.10.0' } + dev: true + + /p-cancelable/3.0.0: + resolution: + { + integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + } + engines: { node: '>=12.20' } + dev: false + + /p-map/2.1.0: + resolution: + { + integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + } + engines: { node: '>=6' } + dev: false + + /p-map/4.0.0: + resolution: + { + integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + } + engines: { node: '>=10' } + dependencies: + aggregate-error: 3.1.0 + dev: true + /parent-module/1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + } + engines: { node: '>=6' } dependencies: callsites: 3.1.0 dev: true /parse-json/5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + } + engines: { node: '>=8' } dependencies: - '@babel/code-frame': 7.14.5 + '@babel/code-frame': 7.12.11 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.1.6 dev: true /path-is-absolute/1.0.1: - resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} - engines: {node: '>=0.10.0'} + resolution: { integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= } + engines: { node: '>=0.10.0' } /path-key/3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + } + engines: { node: '>=8' } /path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + } + dev: true /path-type/4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + } + engines: { node: '>=8' } + dev: true + + /picocolors/1.0.0: + resolution: + { + integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + } dev: true /picomatch/2.3.0: - resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} - engines: {node: '>=8.6'} + resolution: + { + integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + } + engines: { node: '>=8.6' } dev: true - /postcss-calc/8.0.0_postcss@8.3.0: - resolution: {integrity: sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==} + /postcss-js/4.0.0_postcss@8.4.6: + resolution: + { + integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== + } + engines: { node: ^12 || ^14 || >= 16 } peerDependencies: - postcss: ^8.2.2 - dependencies: - postcss: 8.3.0 - postcss-selector-parser: 6.0.6 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-colormin/5.2.0_postcss@8.3.0: - resolution: {integrity: sha512-+HC6GfWU3upe5/mqmxuqYZ9B2Wl4lcoUUNkoaX59nEWV4EtADCMiBqui111Bu8R8IvaZTmqmxrqOAqjbHIwXPw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.16.6 - caniuse-api: 3.0.0 - colord: 2.0.1 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-convert-values/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-C3zR1Do2BkKkCgC0g3sF8TS0koF2G+mN8xxayZx3f10cIRmTaAnpgpRQZjNekTZxM2ciSPoh2IWJm0VZx8NoQg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-discard-comments/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - dev: true - - /postcss-discard-duplicates/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - dev: true - - /postcss-discard-empty/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - dev: true - - /postcss-discard-overridden/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - dev: true - - /postcss-js/3.0.3: - resolution: {integrity: sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==} - engines: {node: '>=10.0'} + postcss: ^8.3.3 dependencies: camelcase-css: 2.0.1 - postcss: 8.3.5 + postcss: 8.4.6 dev: true - /postcss-load-config/3.0.1: - resolution: {integrity: sha512-/pDHe30UYZUD11IeG8GWx9lNtu1ToyTsZHnyy45B4Mrwr/Kb6NgYl7k753+05CJNKnjbwh4975amoPJ+TEjHNQ==} - engines: {node: '>= 10'} - dependencies: - cosmiconfig: 7.0.0 - import-cwd: 3.0.0 - dev: true - - /postcss-load-config/3.1.0: - resolution: {integrity: sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==} - engines: {node: '>= 10'} + /postcss-load-config/3.1.0_ts-node@10.5.0: + resolution: + { + integrity: sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g== + } + engines: { node: '>= 10' } peerDependencies: ts-node: '>=9.0.0' peerDependenciesMeta: @@ -2291,359 +4396,166 @@ packages: optional: true dependencies: import-cwd: 3.0.0 - lilconfig: 2.0.3 + lilconfig: 2.0.4 + ts-node: 10.5.0_99ae9436e134a034c8d45fdd98ebbf22 yaml: 1.10.2 dev: true - /postcss-merge-longhand/5.0.2_postcss@8.3.0: - resolution: {integrity: sha512-BMlg9AXSI5G9TBT0Lo/H3PfUy63P84rVz3BjCFE9e9Y9RXQZD3+h3YO1kgTNsNJy7bBc1YQp8DmSnwLIW5VPcw==} - engines: {node: ^10 || ^12 || >=14.0} + /postcss-nested/5.0.6_postcss@8.4.6: + resolution: + { + integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== + } + engines: { node: '>=12.0' } peerDependencies: - postcss: ^8.2.15 + postcss: ^8.2.14 dependencies: - css-color-names: 1.0.1 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - stylehacks: 5.0.1_postcss@8.3.0 + postcss: 8.4.6 + postcss-selector-parser: 6.0.9 dev: true - /postcss-merge-rules/5.0.2_postcss@8.3.0: - resolution: {integrity: sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.16.6 - caniuse-api: 3.0.0 - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-selector-parser: 6.0.6 - vendors: 1.0.4 - dev: true - - /postcss-minify-font-values/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-minify-gradients/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-odOwBFAIn2wIv+XYRpoN2hUV3pPQlgbJ10XeXPq8UY2N+9ZG42xu45lTn/g9zZ+d70NKSQD6EOi6UiCMu3FN7g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-utils: 2.0.1_postcss@8.3.0 - is-color-stop: 1.1.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-minify-params/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - alphanum-sort: 1.0.2 - browserslist: 4.16.6 - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - uniqs: 2.0.0 - dev: true - - /postcss-minify-selectors/5.1.0_postcss@8.3.0: - resolution: {integrity: sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - alphanum-sort: 1.0.2 - postcss: 8.3.0 - postcss-selector-parser: 6.0.6 - dev: true - - /postcss-nested/5.0.5_postcss@8.3.0: - resolution: {integrity: sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==} - engines: {node: '>=10.0'} - peerDependencies: - postcss: ^8.1.13 - dependencies: - postcss: 8.3.0 - postcss-selector-parser: 6.0.6 - dev: true - - /postcss-normalize-charset/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - dev: true - - /postcss-normalize-display-values/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-normalize-positions/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-normalize-repeat-style/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-normalize-string/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-normalize-timing-functions/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-normalize-unicode/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.16.6 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-normalize-url/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-hkbG0j58Z1M830/CJ73VsP7gvlG1yF+4y7Fd1w4tD2c7CaA2Psll+pQ6eQhth9y9EaqZSLzamff/D0MZBMbYSg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - is-absolute-url: 3.0.3 - normalize-url: 4.5.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-normalize-whitespace/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-ordered-values/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-6mkCF5BQ25HvEcDfrMHCLLFHlraBSlOXFnQMHYhSpDO/5jSR1k8LdEXOkv+7+uzW6o6tBYea1Km0wQSRkPJkwA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-reduce-initial/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.16.6 - caniuse-api: 3.0.0 - postcss: 8.3.0 - dev: true - - /postcss-reduce-transforms/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - cssnano-utils: 2.0.1_postcss@8.3.0 - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - dev: true - - /postcss-selector-parser/6.0.6: - resolution: {integrity: sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==} - engines: {node: '>=4'} + /postcss-selector-parser/6.0.9: + resolution: + { + integrity: sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== + } + engines: { node: '>=4' } dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 dev: true - /postcss-svgo/5.0.2_postcss@8.3.0: - resolution: {integrity: sha512-YzQuFLZu3U3aheizD+B1joQ94vzPfE6BNUcSYuceNxlVnKKsOtdo6hL9/zyC168Q8EwfLSgaDSalsUGa9f2C0A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 + /postcss-value-parser/4.2.0: + resolution: + { + integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + } + dev: true + + /postcss/8.4.6: + resolution: + { + integrity: sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== + } + engines: { node: ^10 || ^12 || >=14 } dependencies: - postcss: 8.3.0 - postcss-value-parser: 4.1.0 - svgo: 2.3.0 - dev: true - - /postcss-unique-selectors/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - alphanum-sort: 1.0.2 - postcss: 8.3.0 - postcss-selector-parser: 6.0.6 - uniqs: 2.0.0 - dev: true - - /postcss-value-parser/3.3.1: - resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} - dev: true - - /postcss-value-parser/4.1.0: - resolution: {integrity: sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==} - dev: true - - /postcss/8.3.0: - resolution: {integrity: sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - colorette: 1.2.2 - nanoid: 3.1.23 - source-map-js: 0.6.2 - dev: true - - /postcss/8.3.5: - resolution: {integrity: sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - colorette: 1.2.2 - nanoid: 3.1.23 - source-map-js: 0.6.2 + nanoid: 3.2.0 + picocolors: 1.0.0 + source-map-js: 1.0.2 dev: true /prelude-ls/1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + } + engines: { node: '>= 0.8.0' } dev: true - /prettier-plugin-svelte/2.3.0_prettier@2.3.1+svelte@3.38.2: - resolution: {integrity: sha512-HTzXvSq7lWFuLsSaxYOUkGkVNCl3RrSjDCOgQjkBX5FQGmWjL8o3IFACSGhjPMMfWKADpapAr0zdbBWkND9mqw==} + /prettier-plugin-svelte/2.6.0_prettier@2.5.1+svelte@3.46.4: + resolution: + { + integrity: sha512-NPSRf6Y5rufRlBleok/pqg4+1FyGsL0zYhkYP6hnueeL1J/uCm3OfOZPsLX4zqD9VAdcXfyEL2PYqGv8ZoOSfA== + } peerDependencies: prettier: ^1.16.4 || ^2.0.0 svelte: ^3.2.0 dependencies: - prettier: 2.3.1 - svelte: 3.38.2 + prettier: 2.5.1 + svelte: 3.46.4 dev: true - /prettier/2.3.1: - resolution: {integrity: sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==} - engines: {node: '>=10.13.0'} + /prettier-plugin-tailwindcss/0.1.7_prettier@2.5.1: + resolution: + { + integrity: sha512-tmBr45hCLuit2Cz9Pwow0/Jl1bGivYGsfcF29O+3sKcE++ybjz9dfie565S3ZsvAeV8uYer9SRMBWDsHPly2Lg== + } + engines: { node: '>=12.17.0' } + peerDependencies: + prettier: '>=2.2.0' + dependencies: + prettier: 2.5.1 + dev: true + + /prettier/2.5.1: + resolution: + { + integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== + } + engines: { node: '>=10.13.0' } hasBin: true dev: true - /pretty-hrtime/1.0.3: - resolution: {integrity: sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=} - engines: {node: '>= 0.8'} + /prisma/3.9.1: + resolution: + { + integrity: sha512-IGcJAu5LzlFv+i+NNhOEh1J1xVVttsVdRBxmrMN7eIH+7mRN6L89Hz1npUAiz4jOpNlHC7n9QwaOYZGxTqlwQw== + } + engines: { node: '>=12.6' } + hasBin: true + requiresBuild: true + dependencies: + '@prisma/engines': 3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009 dev: true - /process-nextick-args/2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: false + /private/0.1.8: + resolution: + { + integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + } + engines: { node: '>= 0.6' } + dev: true /progress/2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} + resolution: + { + integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + } + engines: { node: '>=0.4.0' } + dev: true + + /pseudomap/1.0.2: + resolution: { integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM= } dev: true /pump/3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + resolution: + { + integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + } dependencies: end-of-stream: 1.4.4 once: 1.4.0 dev: false /punycode/2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} - engines: {node: '>=6'} - dev: true - - /purgecss/4.0.3: - resolution: {integrity: sha512-PYOIn5ibRIP34PBU9zohUcCI09c7drPJJtTDAc0Q6QlRz2/CHQ8ywGLdE7ZhxU2VTqB7p5wkvj5Qcm05Rz3Jmw==} - hasBin: true - dependencies: - commander: 6.2.1 - glob: 7.1.7 - postcss: 8.3.5 - postcss-selector-parser: 6.0.6 + resolution: + { + integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + } + engines: { node: '>=6' } dev: true /queue-microtask/1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + } dev: true /quick-lru/5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - dev: true - - /readable-stream/2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} - dependencies: - core-util-is: 1.0.2 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - dev: false + resolution: + { + integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + } + engines: { node: '>=10' } /readable-stream/3.6.0: - resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + } + engines: { node: '>= 6' } dependencies: inherits: 2.0.4 string_decoder: 1.3.0 @@ -2651,361 +4563,670 @@ packages: dev: false /readdirp/3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + resolution: + { + integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + } + engines: { node: '>=8.10.0' } dependencies: picomatch: 2.3.0 dev: true - /rechoir/0.6.2: - resolution: {integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=} - engines: {node: '>= 0.10'} - dependencies: - resolve: 1.20.0 + /redis-commands/1.7.0: + resolution: + { + integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + } dev: false - /reduce-css-calc/2.1.8: - resolution: {integrity: sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==} + /redis-errors/1.2.0: + resolution: { integrity: sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= } + engines: { node: '>=4' } + dev: false + + /redis-parser/3.0.0: + resolution: { integrity: sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= } + engines: { node: '>=4' } dependencies: - css-unit-converter: 1.1.2 - postcss-value-parser: 3.3.1 + redis-errors: 1.2.0 + dev: false + + /regenerate/1.4.2: + resolution: + { + integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + } dev: true - /regexp-clone/1.0.0: - resolution: {integrity: sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==} - dev: false + /regenerator-runtime/0.11.1: + resolution: + { + integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + } + dev: true - /regexpp/3.1.0: - resolution: {integrity: sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==} - engines: {node: '>=8'} + /regenerator-transform/0.10.1: + resolution: + { + integrity: sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== + } + dependencies: + babel-runtime: 6.26.0 + babel-types: 6.26.0 + private: 0.1.8 + dev: true + + /regexpp/3.2.0: + resolution: + { + integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + } + engines: { node: '>=8' } + dev: true + + /regexpu-core/2.0.0: + resolution: { integrity: sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= } + dependencies: + regenerate: 1.4.2 + regjsgen: 0.2.0 + regjsparser: 0.1.5 + dev: true + + /regjsgen/0.2.0: + resolution: { integrity: sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= } + dev: true + + /regjsparser/0.1.5: + resolution: { integrity: sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= } + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + + /repeating/2.0.1: + resolution: { integrity: sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= } + engines: { node: '>=0.10.0' } + dependencies: + is-finite: 1.1.0 dev: true /require-from-string/2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + } + engines: { node: '>=0.10.0' } dev: true /require-relative/0.8.7: - resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=} + resolution: { integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= } dev: true + /resolve-alpn/1.2.1: + resolution: + { + integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + } + dev: false + /resolve-from/4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + } + engines: { node: '>=4' } dev: true /resolve-from/5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + } + engines: { node: '>=8' } dev: true - /resolve/1.20.0: - resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==} + /resolve/1.22.0: + resolution: + { + integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + } + hasBin: true dependencies: - is-core-module: 2.4.0 + is-core-module: 2.8.1 path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /responselike/2.0.0: + resolution: + { + integrity: sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + } + dependencies: + lowercase-keys: 2.0.0 + dev: false + + /restore-cursor/3.1.0: + resolution: + { + integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + } + engines: { node: '>=8' } + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.5 + dev: true /reusify/1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + resolution: + { + integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } dev: true - /rgb-regex/1.0.1: - resolution: {integrity: sha1-wODWiC3w4jviVKR16O3UGRX+rrE=} + /rfdc/1.3.0: + resolution: + { + integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + } dev: true - /rgba-regex/1.0.0: - resolution: {integrity: sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=} + /rimraf/2.7.1: + resolution: + { + integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + } + hasBin: true + dependencies: + glob: 7.2.0 dev: true /rimraf/3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + resolution: + { + integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + } hasBin: true dependencies: - glob: 7.1.7 + glob: 7.2.0 - /rollup/2.46.0: - resolution: {integrity: sha512-qPGoUBNl+Z8uNu0z7pD3WPTABWRbcOwIrO/5ccDJzmrtzn0LVf6Lj91+L5CcWhXl6iWf23FQ6m8Jkl2CmN1O7Q==} - engines: {node: '>=10.0.0'} + /rollup/2.61.1: + resolution: + { + integrity: sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA== + } + engines: { node: '>=10.0.0' } hasBin: true optionalDependencies: fsevents: 2.3.2 dev: true /run-parallel/1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + } dependencies: queue-microtask: 1.2.3 dev: true - /sade/1.7.4: - resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==} - engines: {node: '>= 6'} + /rxjs/7.5.2: + resolution: + { + integrity: sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w== + } dependencies: - mri: 1.1.6 + tslib: 2.3.1 + dev: true + + /sade/1.7.4: + resolution: + { + integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA== + } + engines: { node: '>= 6' } + dependencies: + mri: 1.2.0 dev: true /safe-buffer/5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: false + resolution: + { + integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + } + dev: true /safe-buffer/5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + } dev: false /safer-buffer/2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + } dev: false - /salteen/1.0.0: - resolution: {integrity: sha512-hApiOPudRSzM5aD1dWIWlXL8dPUja8GloMzHMlmDtNiU5brFNyjvQ+SiLLxHVQNeZ2HgKBnzrRHp+BXQiA5vGg==} - engines: {node: '>=6'} - dev: false - - /saslprep/1.0.3: - resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} - engines: {node: '>=6'} + /sander/0.5.1: + resolution: { integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0= } dependencies: - sparse-bitfield: 3.0.3 - dev: false - optional: true + es6-promise: 3.3.1 + graceful-fs: 4.2.8 + mkdirp: 0.5.5 + rimraf: 2.7.1 + dev: true /semver/5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + resolution: + { + integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + } hasBin: true dev: false /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + resolution: + { + integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + } hasBin: true dev: false /semver/7.3.5: - resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + } + engines: { node: '>=10' } hasBin: true dependencies: lru-cache: 6.0.0 /set-blocking/2.0.0: - resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=} + resolution: { integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc= } dev: false + /shebang-command/1.2.0: + resolution: { integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= } + engines: { node: '>=0.10.0' } + dependencies: + shebang-regex: 1.0.0 + dev: true + /shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + } + engines: { node: '>=8' } dependencies: shebang-regex: 3.0.0 + /shebang-regex/1.0.0: + resolution: { integrity: sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= } + engines: { node: '>=0.10.0' } + dev: true + /shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + } + engines: { node: '>=8' } - /shelljs/0.8.4: - resolution: {integrity: sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==} - engines: {node: '>=4'} - hasBin: true - dependencies: - glob: 7.1.6 - interpret: 1.4.0 - rechoir: 0.6.2 - dev: false + /signal-exit/3.0.5: + resolution: + { + integrity: sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== + } - /sift/13.5.2: - resolution: {integrity: sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==} - dev: false - - /signal-exit/3.0.3: - resolution: {integrity: sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==} - dev: false - - /simple-swizzle/0.2.2: - resolution: {integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=} - dependencies: - is-arrayish: 0.3.2 + /slash/1.0.0: + resolution: { integrity: sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= } + engines: { node: '>=0.10.0' } dev: true /slash/3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + } + engines: { node: '>=8' } dev: true - /slice-ansi/4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + /slice-ansi/3.0.0: + resolution: + { + integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + } + engines: { node: '>=8' } dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 dev: true - /sliced/1.0.1: - resolution: {integrity: sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=} - dev: false - - /source-map-js/0.6.2: - resolution: {integrity: sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==} - engines: {node: '>=0.10.0'} + /slice-ansi/4.0.0: + resolution: + { + integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + } + engines: { node: '>=10' } + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 dev: true - /source-map/0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + /slice-ansi/5.0.0: + resolution: + { + integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + } + engines: { node: '>=12' } + dependencies: + ansi-styles: 6.1.0 + is-fullwidth-code-point: 4.0.0 + dev: true + + /sorcery/0.10.0: + resolution: { integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc= } + hasBin: true + dependencies: + buffer-crc32: 0.2.13 + minimist: 1.2.5 + sander: 0.5.1 + sourcemap-codec: 1.4.8 + dev: true + + /source-map-js/1.0.2: + resolution: + { + integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + } + engines: { node: '>=0.10.0' } + dev: true + + /source-map-support/0.4.18: + resolution: + { + integrity: sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== + } + dependencies: + source-map: 0.5.7 + dev: true + + /source-map/0.5.7: + resolution: { integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= } + engines: { node: '>=0.10.0' } + dev: true + + /source-map/0.7.3: + resolution: + { + integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + } + engines: { node: '>= 8' } dev: true /sourcemap-codec/1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + resolution: + { + integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + } dev: true - /sparse-bitfield/3.0.3: - resolution: {integrity: sha1-/0rm5oZWBWuks+eSqzM004JzyhE=} - dependencies: - memory-pager: 1.5.0 - dev: false - optional: true - /split-ca/1.0.1: - resolution: {integrity: sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=} + resolution: { integrity: sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= } dev: false /sprintf-js/1.0.3: - resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} + resolution: { integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= } dev: true - /ssh2-streams/0.4.10: - resolution: {integrity: sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==} - engines: {node: '>=5.2.0'} + /ssh2/1.5.0: + resolution: + { + integrity: sha512-iUmRkhH9KGeszQwDW7YyyqjsMTf4z+0o48Cp4xOwlY5LjtbIAvyd3fwnsoUZW/hXmTCRA3yt7S/Jb9uVjErVlA== + } + engines: { node: '>=10.16.0' } + requiresBuild: true dependencies: asn1: 0.2.4 bcrypt-pbkdf: 1.0.2 - streamsearch: 0.1.2 + optionalDependencies: + cpu-features: 0.0.2 + nan: 2.15.0 dev: false - /ssh2/0.8.9: - resolution: {integrity: sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==} - engines: {node: '>=5.2.0'} - dependencies: - ssh2-streams: 0.4.10 + /standard-as-callback/2.1.0: + resolution: + { + integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + } dev: false - /stable/0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + /string-argv/0.3.1: + resolution: + { + integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + } + engines: { node: '>=0.6.19' } dev: true - /streamsearch/0.1.2: - resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} - engines: {node: '>=0.8.0'} - dev: false - - /string-width/1.0.2: - resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=} - engines: {node: '>=0.10.0'} + /string-width/2.1.1: + resolution: + { + integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + } + engines: { node: '>=4' } dependencies: - code-point-at: 1.1.0 - is-fullwidth-code-point: 1.0.0 - strip-ansi: 3.0.1 + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 dev: false - /string-width/4.2.2: - resolution: {integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==} - engines: {node: '>=8'} + /string-width/4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + } + engines: { node: '>=8' } dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.0 + strip-ansi: 6.0.1 + + /string-width/5.1.0: + resolution: + { + integrity: sha512-7x54QnN21P+XL/v8SuNKvfgsUre6PXpN7mc77N3HlZv+f1SBRGmjxtOud2Z6FZ8DmdkD/IdjCaf9XXbnqmTZGQ== + } + engines: { node: '>=12' } + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 dev: true - /string_decoder/1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - dependencies: - safe-buffer: 5.1.2 - dev: false - /string_decoder/1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + } dependencies: safe-buffer: 5.2.1 dev: false /strip-ansi/3.0.1: - resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=} - engines: {node: '>=0.10.0'} + resolution: { integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= } + engines: { node: '>=0.10.0' } dependencies: ansi-regex: 2.1.1 + dev: true + + /strip-ansi/4.0.0: + resolution: { integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8= } + engines: { node: '>=4' } + dependencies: + ansi-regex: 3.0.0 dev: false - /strip-ansi/6.0.0: - resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==} - engines: {node: '>=8'} + /strip-ansi/6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + } + engines: { node: '>=8' } dependencies: - ansi-regex: 5.0.0 + ansi-regex: 5.0.1 + + /strip-ansi/7.0.1: + resolution: + { + integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + } + engines: { node: '>=12' } + dependencies: + ansi-regex: 6.0.1 + dev: true + + /strip-final-newline/2.0.0: + resolution: + { + integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + } + engines: { node: '>=6' } dev: true /strip-indent/3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + } + engines: { node: '>=8' } dependencies: min-indent: 1.0.1 dev: true /strip-json-comments/3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + } + engines: { node: '>=8' } dev: true - /stylehacks/5.0.1_postcss@8.3.0: - resolution: {integrity: sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - dependencies: - browserslist: 4.16.6 - postcss: 8.3.0 - postcss-selector-parser: 6.0.6 + /supports-color/2.0.0: + resolution: { integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= } + engines: { node: '>=0.8.0' } dev: true /supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + resolution: + { + integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + } + engines: { node: '>=4' } dependencies: has-flag: 3.0.0 dev: true /supports-color/7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + } + engines: { node: '>=8' } dependencies: has-flag: 4.0.0 dev: true - /svelte-hmr/0.14.5_svelte@3.38.2: - resolution: {integrity: sha512-3O+kkbT1XKAomKB0LRcdY8JUTzONoNZ8rSH4iEdG7piIYsw+KkXpTkbbU1Sc1yPY4onfXkmCrHElYsxr0V1Snw==} + /supports-color/9.2.1: + resolution: + { + integrity: sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ== + } + engines: { node: '>=12' } + dev: true + + /supports-preserve-symlinks-flag/1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + } + engines: { node: '>= 0.4' } + dev: true + + /svelte-check/2.4.3_postcss@8.4.6+svelte@3.46.4: + resolution: + { + integrity: sha512-0zJMMgqYHoP7QEG3tfc5DekpHAOqoy4QOL8scWMSdHIpVVDVC0MuYK57nFyj3XVTW8Zfm85FlgnAdQYsVmST2Q== + } + hasBin: true + peerDependencies: + svelte: ^3.24.0 + dependencies: + chokidar: 3.5.3 + fast-glob: 3.2.11 + import-fresh: 3.3.0 + minimist: 1.2.5 + picocolors: 1.0.0 + sade: 1.7.4 + source-map: 0.7.3 + svelte: 3.46.4 + svelte-preprocess: 4.10.3_88b359da5cac6d8f6ee1bbb7080a3fa9 + typescript: 4.5.5 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - node-sass + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + dev: true + + /svelte-hmr/0.14.9_svelte@3.46.4: + resolution: + { + integrity: sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg== + } peerDependencies: svelte: '>=3.19.0' dependencies: - svelte: 3.38.2 + svelte: 3.46.4 dev: true - /svelte-kit-cookie-session/1.0.6: - resolution: {integrity: sha512-vFf0eIyb6T3xaFQGzygLpkMEOvnxarmnobpZmkaC/Q2kh1Q2aFvwo3TEVrg5lD5mIT9irp4MqxRxIGWesF0XOQ==} - dependencies: - salteen: 1.0.0 + /svelte-kit-cookie-session/2.0.3: + resolution: + { + integrity: sha512-jOBUpvrkt/fI5zaqWsWHDDIGnfuPQt3/PC1FDJpEV/E/hA8DvGO52esFny1HvUAP1tkVZ5FU3k6Yd3HyQH5oUQ== + } dev: false - /svelte-preprocess/4.7.3_ddfd8490a44ef0de606159ff3aef985a: - resolution: {integrity: sha512-Zx1/xLeGOIBlZMGPRCaXtlMe4ZA0faato5Dc3CosEqwu75MIEPuOstdkH6cy+RYTUYynoxzNaDxkPX4DbrPwRA==} - engines: {node: '>= 9.11.2'} + /svelte-preprocess/4.10.3_88b359da5cac6d8f6ee1bbb7080a3fa9: + resolution: + { + integrity: sha512-ttw17lJfb/dx2ZJT9sesaXT5l7mPQ9Apx1H496Kli3Hkk7orIRGpOw6rCPkRNzr6ueVPqb4vzodS5x7sBFhKHw== + } + engines: { node: '>= 9.11.2' } requiresBuild: true peerDependencies: '@babel/core': ^7.10.2 coffeescript: ^2.5.1 - less: ^3.11.3 + less: ^3.11.3 || ^4.0.0 node-sass: '*' postcss: ^7 || ^8 postcss-load-config: ^2.1.0 || ^3.0.0 pug: ^3.0.0 sass: ^1.26.8 - stylus: ^0.54.7 + stylus: ^0.55.0 sugarss: ^2.0.0 svelte: ^3.23.0 typescript: ^3.9.5 || ^4.0.0 @@ -3033,106 +5254,82 @@ packages: typescript: optional: true dependencies: - '@types/pug': 2.0.4 - '@types/sass': 1.16.0 - detect-indent: 6.0.0 - postcss: 8.3.0 - postcss-load-config: 3.0.1 + '@types/pug': 2.0.5 + '@types/sass': 1.16.1 + detect-indent: 6.1.0 + magic-string: 0.25.7 + postcss: 8.4.6 + sorcery: 0.10.0 strip-indent: 3.0.0 - svelte: 3.38.2 - typescript: 4.3.2 + svelte: 3.46.4 + typescript: 4.5.5 dev: true - /svelte-select/3.17.0: - resolution: {integrity: sha512-ITmX/XUiSdkaILmsTviKRkZPaXckM5/FA7Y8BhiUPoamaZG/ZDyOo6ydjFu9fDVFTbwoAUGUi6HBjs+ZdK2AwA==} - dev: false - - /svelte/3.38.2: - resolution: {integrity: sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==} - engines: {node: '>= 8'} + /svelte/3.46.4: + resolution: + { + integrity: sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg== + } + engines: { node: '>= 8' } dev: true - /svgo/2.3.0: - resolution: {integrity: sha512-fz4IKjNO6HDPgIQxu4IxwtubtbSfGEAJUq/IXyTPIkGhWck/faiiwfkvsB8LnBkKLvSoyNNIY6d13lZprJMc9Q==} - engines: {node: '>=10.13.0'} - hasBin: true + /table/6.7.2: + resolution: + { + integrity: sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g== + } + engines: { node: '>=10.0.0' } dependencies: - '@trysound/sax': 0.1.1 - chalk: 4.1.1 - commander: 7.2.0 - css-select: 3.1.2 - css-tree: 1.1.3 - csso: 4.2.0 - stable: 0.1.8 - dev: true - - /systeminformation/5.7.7: - resolution: {integrity: sha512-aQ7MBeVI2MKPYOi3YJAoZ45JVlRkBA7IXoqGgtVBamvtE0I6JLOyJzD/VVc9pnMXDb3yqaMwssAjhwtJax4/Rw==} - engines: {node: '>=4.0.0'} - os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos] - hasBin: true - dev: false - - /table/6.6.0: - resolution: {integrity: sha512-iZMtp5tUvcnAdtHpZTWLPF0M7AgiQsURR2DwmxnJwSy8I3+cY+ozzVvYha3BOLG2TB+L0CqjIz+91htuj6yCXg==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.2.0 + ajv: 8.6.3 lodash.clonedeep: 4.5.0 - lodash.flatten: 4.4.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 - string-width: 4.2.2 - strip-ansi: 6.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 dev: true - /tailwindcss/2.2.4_6daa0ece57b4377652e73c9c66c3b94c: - resolution: {integrity: sha512-OdBCPgazNNsknSP+JfrPzkay9aqKjhKtFhbhgxHgvEFdHy/GuRPo2SCJ4w1SFTN8H6FPI4m6qD/Jj20NWY1GkA==} - engines: {node: '>=12.13.0'} + /tailwindcss/3.0.19_27d966e3a2f4b84fbc8a2f9653dbb362: + resolution: + { + integrity: sha512-rjsdfz/qZya5xQ0OVynEMETgWq1CacmftgMYeXXh6bRM5vxsNwRSbMJsCCIjq/w67om9VP/AFMolOwiE+5VKig== + } + engines: { node: '>=12.13.0' } hasBin: true peerDependencies: autoprefixer: ^10.0.2 postcss: ^8.0.9 dependencies: - '@fullhuman/postcss-purgecss': 4.0.3_postcss@8.3.0 - arg: 5.0.0 - autoprefixer: 10.2.6_postcss@8.3.0 - bytes: 3.1.0 - chalk: 4.1.1 - chokidar: 3.5.2 - color: 3.1.3 - cosmiconfig: 7.0.0 + arg: 5.0.1 + autoprefixer: 10.4.2_postcss@8.4.6 + chalk: 4.1.2 + chokidar: 3.5.3 + color-name: 1.1.4 + cosmiconfig: 7.0.1 detective: 5.2.0 - didyoumean: 1.2.1 + didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.2.5 - fs-extra: 10.0.0 - glob-parent: 6.0.0 - html-tags: 3.1.0 - is-glob: 4.0.1 - lodash: 4.17.21 - lodash.topath: 4.5.2 - modern-normalize: 1.1.0 - node-emoji: 1.10.0 + fast-glob: 3.2.11 + glob-parent: 6.0.2 + is-glob: 4.0.3 normalize-path: 3.0.0 object-hash: 2.2.0 - postcss: 8.3.0 - postcss-js: 3.0.3 - postcss-load-config: 3.1.0 - postcss-nested: 5.0.5_postcss@8.3.0 - postcss-selector-parser: 6.0.6 - postcss-value-parser: 4.1.0 - pretty-hrtime: 1.0.3 + postcss: 8.4.6 + postcss-js: 4.0.0_postcss@8.4.6 + postcss-load-config: 3.1.0_ts-node@10.5.0 + postcss-nested: 5.0.6_postcss@8.4.6 + postcss-selector-parser: 6.0.9 + postcss-value-parser: 4.2.0 quick-lru: 5.1.1 - reduce-css-calc: 2.1.8 - resolve: 1.20.0 - tmp: 0.2.1 + resolve: 1.22.0 transitivePeerDependencies: - ts-node dev: true /tar-fs/2.0.1: - resolution: {integrity: sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==} + resolution: + { + integrity: sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + } dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 @@ -3141,8 +5338,11 @@ packages: dev: false /tar-stream/2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} + resolution: + { + integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + } + engines: { node: '>=6' } dependencies: bl: 4.1.0 end-of-stream: 1.4.4 @@ -3151,180 +5351,345 @@ packages: readable-stream: 3.6.0 dev: false - /tar/6.1.0: - resolution: {integrity: sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==} - engines: {node: '>= 10'} + /tar/6.1.11: + resolution: + { + integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + } + engines: { node: '>= 10' } dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 - minipass: 3.1.3 + minipass: 3.1.5 minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 dev: false /text-table/0.2.0: - resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} + resolution: { integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= } dev: true - /timsort/0.3.0: - resolution: {integrity: sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=} + /through/2.3.8: + resolution: { integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= } dev: true /tiny-glob/0.2.9: - resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + resolution: + { + integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== + } dependencies: globalyzer: 0.1.0 globrex: 0.1.2 dev: true - /tmp/0.2.1: - resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} - engines: {node: '>=8.17.0'} - dependencies: - rimraf: 3.0.2 + /to-fast-properties/1.0.3: + resolution: { integrity: sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= } + engines: { node: '>=0.10.0' } dev: true /to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + } + engines: { node: '>=8.0' } dependencies: is-number: 7.0.0 dev: true + /tr46/0.0.3: + resolution: { integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= } + dev: false + + /trim-right/1.0.1: + resolution: { integrity: sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= } + engines: { node: '>=0.10.0' } + dev: true + + /ts-node/10.5.0_99ae9436e134a034c8d45fdd98ebbf22: + resolution: + { + integrity: sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw== + } + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.7.0 + '@tsconfig/node10': 1.0.8 + '@tsconfig/node12': 1.0.9 + '@tsconfig/node14': 1.0.1 + '@tsconfig/node16': 1.0.2 + '@types/node': 17.0.16 + acorn: 8.5.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.5.5 + v8-compile-cache-lib: 3.0.0 + yn: 3.1.1 + dev: true + /tslib/1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + resolution: + { + integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + } + + /tslib/2.3.1: + resolution: + { + integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + } dev: true - /tslib/2.2.0: - resolution: {integrity: sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==} - dev: true - - /tsutils/3.21.0_typescript@4.3.2: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} + /tsutils/3.21.0_typescript@4.5.5: + resolution: + { + integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + } + engines: { node: '>= 6' } peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 4.3.2 + typescript: 4.5.5 dev: true /tweetnacl/0.14.5: - resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=} + resolution: { integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= } dev: false /type-check/0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + } + engines: { node: '>= 0.8.0' } dependencies: prelude-ls: 1.2.1 dev: true /type-fest/0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} + resolution: + { + integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + } + engines: { node: '>=10' } dev: true - /typescript/4.3.2: - resolution: {integrity: sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==} - engines: {node: '>=4.2.0'} + /type-fest/0.21.3: + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + } + engines: { node: '>=10' } + dev: true + + /typescript/4.5.5: + resolution: + { + integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + } + engines: { node: '>=4.2.0' } hasBin: true dev: true /typpy/2.3.11: - resolution: {integrity: sha512-Jh/fykZSaxeKO0ceMAs6agki9T5TNA9kiIR6fzKbvafKpIw8UlNlHhzuqKyi5lfJJ5VojJOx9tooIbyy7vHV/g==} + resolution: + { + integrity: sha512-Jh/fykZSaxeKO0ceMAs6agki9T5TNA9kiIR6fzKbvafKpIw8UlNlHhzuqKyi5lfJJ5VojJOx9tooIbyy7vHV/g== + } dependencies: function.name: 1.0.13 dev: false - /uniqs/2.0.0: - resolution: {integrity: sha1-/+3ks2slKQaW5uFl1KWe25mOawI=} - dev: true - - /unique-names-generator/4.5.0: - resolution: {integrity: sha512-GaiWvo3rKIHi1SyYP8/9cDrPMPSwEiYDUo2p0NQHeCHDXzLn8P6p8bttSS3lX7HHS3Yl5vnw/ulybO4GN85CgQ==} - engines: {node: '>=8'} + /unique-names-generator/4.6.0: + resolution: + { + integrity: sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ== + } + engines: { node: '>=8' } dev: false - /universalify/2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} - engines: {node: '>= 10.0.0'} - dev: true - /uri-js/4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + } dependencies: punycode: 2.1.1 dev: true /util-deprecate/1.0.2: - resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} + resolution: { integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= } + + /uuid/8.3.2: + resolution: + { + integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + } + hasBin: true + dev: false + + /v8-compile-cache-lib/3.0.0: + resolution: + { + integrity: sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== + } + dev: true /v8-compile-cache/2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} + resolution: + { + integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + } dev: true - /vendors/1.0.4: - resolution: {integrity: sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==} - dev: true - - /vite/2.3.6: - resolution: {integrity: sha512-fsEpNKDHgh3Sn66JH06ZnUBnIgUVUtw6ucDhlOj1CEqxIkymU25yv1/kWDPlIjyYHnalr0cN6V+zzUJ+fmWHYw==} - engines: {node: '>=12.0.0'} + /vite/2.8.0: + resolution: + { + integrity: sha512-ed5rjyeysttuPJX/aKSA0gTB/8ZKLM5xF6FtEuKy1B9DiQbDNFMVMQxnb9JesgBPUMMIJxC8w5KZ/KNWLKFXoA== + } + engines: { node: '>=12.2.0' } hasBin: true + peerDependencies: + less: '*' + sass: '*' + stylus: '*' + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true dependencies: - esbuild: 0.12.6 - postcss: 8.3.0 - resolve: 1.20.0 - rollup: 2.46.0 + esbuild: 0.14.21 + postcss: 8.4.6 + resolve: 1.22.0 + rollup: 2.61.1 optionalDependencies: fsevents: 2.3.2 dev: true - /vite/2.4.2: - resolution: {integrity: sha512-2MifxD2I9fjyDmmEzbULOo3kOUoqX90A58cT6mECxoVQlMYFuijZsPQBuA14mqSwvV3ydUsqnq+BRWXyO9Qa+w==} - engines: {node: '>=12.0.0'} + /webidl-conversions/3.0.1: + resolution: { integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= } + dev: false + + /whatwg-url/5.0.0: + resolution: { integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0= } + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + + /which/1.3.1: + resolution: + { + integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + } hasBin: true dependencies: - esbuild: 0.12.15 - postcss: 8.3.5 - resolve: 1.20.0 - rollup: 2.46.0 - optionalDependencies: - fsevents: 2.3.2 + isexe: 2.0.0 dev: true /which/2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + } + engines: { node: '>= 8' } hasBin: true dependencies: isexe: 2.0.0 - /wide-align/1.1.3: - resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==} + /wide-align/1.1.5: + resolution: + { + integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + } dependencies: - string-width: 1.0.2 + string-width: 4.2.3 dev: false /word-wrap/1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} + resolution: + { + integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + } + engines: { node: '>=0.10.0' } + dev: true + + /wrap-ansi/6.2.0: + resolution: + { + integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + } + engines: { node: '>=8' } + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi/7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + } + engines: { node: '>=10' } + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 dev: true /wrappy/1.0.2: - resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + resolution: { integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= } /xtend/4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} + resolution: + { + integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + } + engines: { node: '>=0.4' } + dev: true + + /yallist/2.1.2: + resolution: { integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= } dev: true /yallist/4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + resolution: + { + integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + } /yaml/1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} + resolution: + { + integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + } + engines: { node: '>= 6' } + dev: true + + /yn/3.1.1: + resolution: + { + integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + } + engines: { node: '>=6' } dev: true diff --git a/postcss.config.cjs b/postcss.config.cjs index fba8830b2..054c147cb 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,18 +1,6 @@ -const tailwindcss = require('tailwindcss'); -const autoprefixer = require('autoprefixer'); -const cssnano = require('cssnano'); - -const mode = process.env.NODE_ENV; -const dev = mode === 'development'; - module.exports = { - plugins: [ - tailwindcss, - autoprefixer, - - !dev && - cssnano({ - preset: 'default' - }) - ] + plugins: { + tailwindcss: {}, + autoprefixer: {} + } }; diff --git a/prisma/migrations/20220131142425_init/migration.sql b/prisma/migrations/20220131142425_init/migration.sql new file mode 100644 index 000000000..6607a48b5 --- /dev/null +++ b/prisma/migrations/20220131142425_init/migration.sql @@ -0,0 +1,443 @@ +-- CreateTable +CREATE TABLE "Setting" ( + "id" TEXT NOT NULL PRIMARY KEY, + "fqdn" TEXT, + "isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false, + "proxyPassword" TEXT NOT NULL, + "proxyUser" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL PRIMARY KEY, + "email" TEXT NOT NULL, + "type" TEXT NOT NULL, + "password" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "Permission" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + "teamId" TEXT NOT NULL, + "permission" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Permission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Permission_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Team" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "databaseId" TEXT, + "serviceId" TEXT, + FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "TeamInvitation" ( + "id" TEXT NOT NULL PRIMARY KEY, + "uid" TEXT NOT NULL, + "email" TEXT NOT NULL, + "teamId" TEXT NOT NULL, + "teamName" TEXT NOT NULL, + "permission" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateTable +CREATE TABLE "Application" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "fqdn" TEXT, + "repository" TEXT, + "configHash" TEXT, + "branch" TEXT, + "buildPack" TEXT, + "projectId" INTEGER, + "port" INTEGER, + "installCommand" TEXT, + "buildCommand" TEXT, + "startCommand" TEXT, + "baseDirectory" TEXT, + "publishDirectory" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "destinationDockerId" TEXT, + "gitSourceId" TEXT, + CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "ApplicationSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "applicationId" TEXT NOT NULL, + "debug" BOOLEAN NOT NULL DEFAULT false, + "previews" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Secret" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "value" TEXT NOT NULL, + "isBuildSecret" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "applicationId" TEXT NOT NULL, + CONSTRAINT "Secret_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "BuildLog" ( + "id" TEXT NOT NULL PRIMARY KEY, + "applicationId" TEXT, + "buildId" TEXT NOT NULL, + "line" TEXT NOT NULL, + "time" INTEGER NOT NULL +); + +-- CreateTable +CREATE TABLE "Build" ( + "id" TEXT NOT NULL PRIMARY KEY, + "type" TEXT NOT NULL, + "applicationId" TEXT, + "destinationDockerId" TEXT, + "gitSourceId" TEXT, + "githubAppId" TEXT, + "gitlabAppId" TEXT, + "commit" TEXT, + "status" TEXT DEFAULT 'queued', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "DestinationDocker" ( + "id" TEXT NOT NULL PRIMARY KEY, + "network" TEXT NOT NULL, + "name" TEXT NOT NULL, + "engine" TEXT NOT NULL, + "remoteEngine" BOOLEAN NOT NULL DEFAULT false, + "isCoolifyProxyUsed" BOOLEAN DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "GitSource" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "type" TEXT, + "apiUrl" TEXT, + "htmlUrl" TEXT, + "organization" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "githubAppId" TEXT, + "gitlabAppId" TEXT, + CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "GithubApp" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT, + "appId" INTEGER, + "installationId" INTEGER, + "clientId" TEXT, + "clientSecret" TEXT, + "webhookSecret" TEXT, + "privateKey" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "GitlabApp" ( + "id" TEXT NOT NULL PRIMARY KEY, + "oauthId" INTEGER NOT NULL, + "groupName" TEXT, + "deployKeyId" INTEGER, + "privateSshKey" TEXT, + "publicSshKey" TEXT, + "webhookToken" TEXT, + "appId" TEXT, + "appSecret" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "Database" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "publicPort" INTEGER, + "defaultDatabase" TEXT, + "type" TEXT, + "version" TEXT, + "dbUser" TEXT, + "dbUserPassword" TEXT, + "rootUser" TEXT, + "rootUserPassword" TEXT, + "destinationDockerId" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Database_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "DatabaseSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "databaseId" TEXT NOT NULL, + "isPublic" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "DatabaseSettings_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Service" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "fqdn" TEXT, + "type" TEXT, + "version" TEXT, + "destinationDockerId" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Service_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "PlausibleAnalytics" ( + "id" TEXT NOT NULL PRIMARY KEY, + "email" TEXT, + "username" TEXT, + "password" TEXT NOT NULL, + "postgresqlUser" TEXT NOT NULL, + "postgresqlPassword" TEXT NOT NULL, + "postgresqlDatabase" TEXT NOT NULL, + "postgresqlPublicPort" INTEGER, + "secretKeyBase" TEXT, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "PlausibleAnalytics_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Minio" ( + "id" TEXT NOT NULL PRIMARY KEY, + "rootUser" TEXT NOT NULL, + "rootUserPassword" TEXT NOT NULL, + "publicPort" INTEGER, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Minio_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Vscodeserver" ( + "id" TEXT NOT NULL PRIMARY KEY, + "password" TEXT NOT NULL, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Vscodeserver_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Wordpress" ( + "id" TEXT NOT NULL PRIMARY KEY, + "extraConfig" TEXT, + "tablePrefix" TEXT, + "mysqlUser" TEXT NOT NULL, + "mysqlPassword" TEXT NOT NULL, + "mysqlRootUser" TEXT NOT NULL, + "mysqlRootUserPassword" TEXT NOT NULL, + "mysqlDatabase" TEXT, + "mysqlPublicPort" INTEGER, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_TeamToUser" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_ApplicationToTeam" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "Application" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_GitSourceToTeam" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "GitSource" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_GithubAppToTeam" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "GithubApp" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_GitlabAppToTeam" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "GitlabApp" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_DestinationDockerToTeam" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "DestinationDocker" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_DatabaseToTeam" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "Database" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_ServiceToTeam" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + FOREIGN KEY ("A") REFERENCES "Service" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_id_key" ON "User"("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Application_fqdn_key" ON "Application"("fqdn"); + +-- CreateIndex +CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Secret_name_key" ON "Secret"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network"); + +-- CreateIndex +CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GithubApp_name_key" ON "GithubApp"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "GitlabApp_oauthId_key" ON "GitlabApp"("oauthId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GitlabApp_groupName_key" ON "GitlabApp"("groupName"); + +-- CreateIndex +CREATE UNIQUE INDEX "DatabaseSettings_databaseId_key" ON "DatabaseSettings"("databaseId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PlausibleAnalytics_serviceId_key" ON "PlausibleAnalytics"("serviceId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Minio_serviceId_key" ON "Minio"("serviceId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Vscodeserver_serviceId_key" ON "Vscodeserver"("serviceId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId"); + +-- CreateIndex +CREATE UNIQUE INDEX "_TeamToUser_AB_unique" ON "_TeamToUser"("A", "B"); + +-- CreateIndex +CREATE INDEX "_TeamToUser_B_index" ON "_TeamToUser"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_ApplicationToTeam_AB_unique" ON "_ApplicationToTeam"("A", "B"); + +-- CreateIndex +CREATE INDEX "_ApplicationToTeam_B_index" ON "_ApplicationToTeam"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_GitSourceToTeam_AB_unique" ON "_GitSourceToTeam"("A", "B"); + +-- CreateIndex +CREATE INDEX "_GitSourceToTeam_B_index" ON "_GitSourceToTeam"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_GithubAppToTeam_AB_unique" ON "_GithubAppToTeam"("A", "B"); + +-- CreateIndex +CREATE INDEX "_GithubAppToTeam_B_index" ON "_GithubAppToTeam"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_GitlabAppToTeam_AB_unique" ON "_GitlabAppToTeam"("A", "B"); + +-- CreateIndex +CREATE INDEX "_GitlabAppToTeam_B_index" ON "_GitlabAppToTeam"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_DestinationDockerToTeam_AB_unique" ON "_DestinationDockerToTeam"("A", "B"); + +-- CreateIndex +CREATE INDEX "_DestinationDockerToTeam_B_index" ON "_DestinationDockerToTeam"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_DatabaseToTeam_AB_unique" ON "_DatabaseToTeam"("A", "B"); + +-- CreateIndex +CREATE INDEX "_DatabaseToTeam_B_index" ON "_DatabaseToTeam"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_ServiceToTeam_AB_unique" ON "_ServiceToTeam"("A", "B"); + +-- CreateIndex +CREATE INDEX "_ServiceToTeam_B_index" ON "_ServiceToTeam"("B"); diff --git a/prisma/migrations/20220210104005_redis_aol/migration.sql b/prisma/migrations/20220210104005_redis_aol/migration.sql new file mode 100644 index 000000000..8a7e41098 --- /dev/null +++ b/prisma/migrations/20220210104005_redis_aol/migration.sql @@ -0,0 +1,28 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Team" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "databaseId" TEXT, + "serviceId" TEXT +); +INSERT INTO "new_Team" ("createdAt", "databaseId", "id", "name", "serviceId", "updatedAt") SELECT "createdAt", "databaseId", "id", "name", "serviceId", "updatedAt" FROM "Team"; +DROP TABLE "Team"; +ALTER TABLE "new_Team" RENAME TO "Team"; +CREATE TABLE "new_DatabaseSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "databaseId" TEXT NOT NULL, + "isPublic" BOOLEAN NOT NULL DEFAULT false, + "appendOnly" BOOLEAN NOT NULL DEFAULT true, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "DatabaseSettings_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_DatabaseSettings" ("createdAt", "databaseId", "id", "isPublic", "updatedAt") SELECT "createdAt", "databaseId", "id", "isPublic", "updatedAt" FROM "DatabaseSettings"; +DROP TABLE "DatabaseSettings"; +ALTER TABLE "new_DatabaseSettings" RENAME TO "DatabaseSettings"; +CREATE UNIQUE INDEX "DatabaseSettings_databaseId_key" ON "DatabaseSettings"("databaseId"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000..e5e5c4705 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 000000000..b12e29fc5 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,298 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("COOLIFY_DATABASE_URL") +} + +model Setting { + id String @id @default(cuid()) + fqdn String? @unique + isRegistrationEnabled Boolean @default(false) + proxyPassword String + proxyUser String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model User { + id String @id @unique @default(cuid()) + email String @unique + type String + password String? + teams Team[] + permission Permission[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Permission { + id String @id @default(cuid()) + user User @relation(fields: [userId], references: [id]) + userId String + team Team @relation(fields: [teamId], references: [id]) + teamId String + permission String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Team { + id String @id @default(cuid()) + users User[] + name String? + applications Application[] + gitSources GitSource[] + gitHubApps GithubApp[] + gitLabApps GitlabApp[] + destinationDocker DestinationDocker[] + permissions Permission[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + database Database[] @relation(fields: [databaseId], references: [id]) + databaseId String? + service Service[] @relation(fields: [serviceId], references: [id]) + serviceId String? +} + +model TeamInvitation { + id String @id @default(cuid()) + uid String + email String + teamId String + teamName String + permission String + createdAt DateTime @default(now()) +} + +model Application { + id String @id @default(cuid()) + name String + fqdn String? @unique + repository String? + configHash String? + branch String? + buildPack String? + projectId Int? + port Int? + installCommand String? + buildCommand String? + startCommand String? + baseDirectory String? + publishDirectory String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + settings ApplicationSettings? + teams Team[] + destinationDockerId String? + destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) + gitSourceId String? + gitSource GitSource? @relation(fields: [gitSourceId], references: [id]) + secrets Secret[] +} + +model ApplicationSettings { + id String @id @default(cuid()) + application Application @relation(fields: [applicationId], references: [id]) + applicationId String @unique + debug Boolean @default(false) + previews Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Secret { + id String @id @default(cuid()) + name String @unique + value String + isBuildSecret Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + application Application @relation(fields: [applicationId], references: [id]) + applicationId String +} + +model BuildLog { + id String @id @default(cuid()) + applicationId String? + buildId String + line String + time Int +} + +model Build { + id String @id @default(cuid()) + type String + applicationId String? + destinationDockerId String? + gitSourceId String? + githubAppId String? + gitlabAppId String? + commit String? + status String? @default("queued") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model DestinationDocker { + id String @id @default(cuid()) + network String @unique + name String + engine String + remoteEngine Boolean @default(false) + isCoolifyProxyUsed Boolean? @default(false) + teams Team[] + application Application[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + database Database[] + service Service[] +} + +model GitSource { + id String @id @default(cuid()) + name String + teams Team[] + type String? + apiUrl String? + htmlUrl String? + organization String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + githubAppId String? @unique + githubApp GithubApp? @relation(fields: [githubAppId], references: [id]) + application Application[] + gitlabAppId String? @unique + gitlabApp GitlabApp? @relation(fields: [gitlabAppId], references: [id]) +} + +model GithubApp { + id String @id @default(cuid()) + name String? @unique + teams Team[] + appId Int? + installationId Int? + clientId String? + clientSecret String? + webhookSecret String? + privateKey String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + gitSource GitSource? +} + +model GitlabApp { + id String @id @default(cuid()) + oauthId Int @unique + groupName String? @unique + teams Team[] + deployKeyId Int? + privateSshKey String? + publicSshKey String? + webhookToken String? + appId String? + appSecret String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + gitSource GitSource? +} + +model Database { + id String @id @default(cuid()) + name String + publicPort Int? + defaultDatabase String? + type String? + version String? + dbUser String? + dbUserPassword String? + rootUser String? + rootUserPassword String? + settings DatabaseSettings? + destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) + destinationDockerId String? + teams Team[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model DatabaseSettings { + id String @id @default(cuid()) + database Database @relation(fields: [databaseId], references: [id]) + databaseId String @unique + isPublic Boolean @default(false) + appendOnly Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Service { + id String @id @default(cuid()) + name String + fqdn String? + type String? + version String? + teams Team[] + destinationDockerId String? + destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + plausibleAnalytics PlausibleAnalytics? + minio Minio? + vscodeserver Vscodeserver? + wordpress Wordpress? +} + +model PlausibleAnalytics { + id String @id @default(cuid()) + email String? + username String? + password String + postgresqlUser String + postgresqlPassword String + postgresqlDatabase String + postgresqlPublicPort Int? + secretKeyBase String? + serviceId String @unique + service Service @relation(fields: [serviceId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Minio { + id String @id @default(cuid()) + rootUser String + rootUserPassword String + publicPort Int? + serviceId String @unique + service Service @relation(fields: [serviceId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Vscodeserver { + id String @id @default(cuid()) + password String + serviceId String @unique + service Service @relation(fields: [serviceId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Wordpress { + id String @id @default(cuid()) + extraConfig String? + tablePrefix String? + mysqlUser String + mysqlPassword String + mysqlRootUser String + mysqlRootUserPassword String + mysqlDatabase String? + mysqlPublicPort Int? + serviceId String @unique + service Service @relation(fields: [serviceId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/prisma/seed.cjs b/prisma/seed.cjs new file mode 100644 index 000000000..2dacc9a72 --- /dev/null +++ b/prisma/seed.cjs @@ -0,0 +1,80 @@ +const dotEnvExtended = require('dotenv-extended'); +dotEnvExtended.load(); +const { PrismaClient } = require('@prisma/client'); +const prisma = new PrismaClient(); +const crypto = require('crypto'); +const generator = require('generate-password'); +const cuid = require('cuid'); + +function generatePassword(length = 24) { + return generator.generate({ + length, + numbers: true, + strict: true + }); +} +const algorithm = 'aes-256-ctr'; + +async function main() { + // Enable registration for the first user + // Set initial HAProxy password + const settingsFound = await prisma.setting.findFirst({}); + if (!settingsFound) { + await prisma.setting.create({ + data: { + isRegistrationEnabled: true, + proxyPassword: encrypt(generatePassword()), + proxyUser: cuid() + } + }); + } + const localDocker = await prisma.destinationDocker.findFirst({ + where: { engine: '/var/run/docker.sock' } + }); + if (!localDocker) { + await prisma.destinationDocker.create({ + data: { + engine: '/var/run/docker.sock', + name: 'Local Docker', + isCoolifyProxyUsed: true, + network: 'coolify' + } + }); + } +} +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); + +const encrypt = (text) => { + if (text) { + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv); + const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); + return JSON.stringify({ + iv: iv.toString('hex'), + content: encrypted.toString('hex') + }); + } +}; + +const decrypt = (hashString) => { + if (hashString) { + const hash = JSON.parse(hashString); + const decipher = crypto.createDecipheriv( + algorithm, + process.env['COOLIFY_SECRET_KEY'], + Buffer.from(hash.iv, 'hex') + ); + const decrpyted = Buffer.concat([ + decipher.update(Buffer.from(hash.content, 'hex')), + decipher.final() + ]); + return decrpyted.toString(); + } +}; diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 000000000..e728f8d0d --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +clear +ARG1=$1 +WHO=$(whoami) +APP_ID=$(cat /proc/sys/kernel/random/uuid) +RANDOM_SECRET=$(echo $(($(date +%s%N) / 1000000)) | sha256sum | base64 | head -c 32) +SENTRY_DSN="https://9e7a74326f29422584d2d0bebdc8b7d3@o1082494.ingest.sentry.io/6091062" +DOCKER_MAJOR=20 +DOCKER_MINOR=10 +DOCKER_VERSION_OK="nok" + +set -eou pipefail + +if [ $ARG1 ] && [ $ARG1 == "-d" ]; then + set -x +fi + +function errorchecker() { + exitCode=$? + if [ $exitCode -ne "0" ]; then + echo "$0 exited unexpectedly with status: $exitCode" + exit $exitCode + fi +} +trap 'errorchecker' EXIT + +echo -e "Welcome to Coolify installer! \n" +echo "This script will install all the required packages and services to run Coolify." +echo -e "If you want to install Coolify on a different OS, please open an issue on Github to get supported version.\n\n" + +echo -e "To see what I'm doing, please check:" +echo -e "https://github.com/coollabsio/get.coollabs.io/blob/main/static/coolify/install_v2.sh\n\n" + +if [ $WHO != 'root' ]; then + echo 'Run as root please: sudo sh -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"' + exit 1 +fi + +if [ ! -x "$(command -v docker)" ]; then + while true; do + read -p "Docker Engine not found, should I install it automatically? [Yy/Nn] " yn + case $yn in + [Yy]*) + sh -c "$(curl -fsSL https://get.docker.com)" + break + ;; + [Nn]*) + echo "Please install docker manually and update it to the latest, but at least to $DOCKER_MAJOR.$DOCKER_MINOR" + exit 0 + ;; + *) echo "Please answer Y or N." ;; + esac + done +fi + +SERVER_VERSION=$(docker version -f "{{.Server.Version}}") +SERVER_VERSION_MAJOR=$(echo "$SERVER_VERSION" | cut -d'.' -f 1) +SERVER_VERSION_MINOR=$(echo "$SERVER_VERSION" | cut -d'.' -f 2) + +if [ "$SERVER_VERSION_MAJOR" -ge "$DOCKER_MAJOR" ] && + [ "$SERVER_VERSION_MINOR" -ge "$DOCKER_MINOR" ]; then + DOCKER_VERSION_OK="ok" +fi + +if [ $DOCKER_VERSION_OK == 'nok' ]; then + echo "Docker version less than $DOCKER_MAJOR.$DOCKER_MINOR, please update it to at least to $DOCKER_MAJOR.$DOCKER_MINOR" + exit 1 +fi + +# Adding docker daemon configuration +cat </etc/docker/daemon.json +{ + "log-driver": "json-file", + "log-opts": { + "max-size": "100m", + "max-file": "5" + }, + "features": { + "buildkit": true + }, + "live-restore": true +} +EOF + +# Restarting docker daemon +sh -c "systemctl daemon-reload && systemctl restart docker" + +# Downloading docker compose cli plugin +mkdir -p ~/.docker/cli-plugins/ +curl -SL https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose +chmod +x ~/.docker/cli-plugins/docker-compose + +# Making base directory for coolify +if [ ! -d coolify ]; then + mkdir coolify +fi + +if [ -f coolify/.env ]; then + echo -e "Coolify is already installed, using some of the existing settings." +else + echo "COOLIFY_APP_ID=$APP_ID +COOLIFY_SECRET_KEY=$RANDOM_SECRET +COOLIFY_DATABASE_URL=file:../db/prod.db +COOLIFY_SENTRY_DSN=$SENTRY_DSN +COOLIFY_HOSTED_ON=docker" > coolify/.env +fi + +cd coolify && docker run -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db-sqlite coollabsio/coolify:latest /bin/sh -c "env | grep COOLIFY > .env && docker compose up -d --force-recreate" + +echo -e "Congratulations! Your coolify is ready to use.\n" +echo "Please visit http://:3000/ to get started." +echo "It will take a few minutes to start up, don't worry." \ No newline at end of file diff --git a/scripts/install_podman_experiment.sh b/scripts/install_podman_experiment.sh new file mode 100644 index 000000000..0edfb90d2 --- /dev/null +++ b/scripts/install_podman_experiment.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +clear +ARG1=$1 +WHO=$(whoami) +APP_ID=$(cat /proc/sys/kernel/random/uuid) +RANDOM_SECRET=$(echo $(($(date +%s%N) / 1000000)) | sha256sum | base64 | head -c 32) +SENTRY_DSN="https://9e7a74326f29422584d2d0bebdc8b7d3@o1082494.ingest.sentry.io/6091062" + +UBUNTU_MAJOR_MIN=20 +UBUNTU_MINOR_MIN=04 +OS_OK="nok" + +set -eou pipefail + +if [ $ARG1 ] && [ $ARG1 == "-d" ]; then + set -x +fi + +function errorchecker() { + exitCode=$? + if [ $exitCode -ne "0" ]; then + echo "$0 exited unexpectedly with status: $exitCode" + exit $exitCode + fi +} +trap 'errorchecker' EXIT + +if [ $WHO != 'root' ]; then + echo 'Run as root please: sudo sh -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"' + exit 1 +fi + +. /etc/lsb-release +if [ $DISTRIB_ID != 'Ubuntu' ]; then + echo 'Not supported OS, please open an issue on Github to get supported version.' + exit 1 +fi + +DISTRIB_RELEASE_MAJOR=$(echo "$DISTRIB_RELEASE" | cut -d'.' -f 1) +DISTRIB_RELEASE_MINOR=$(echo "$DISTRIB_RELEASE" | cut -d'.' -f 2) + +if [ "$DISTRIB_RELEASE_MAJOR" -ge "$UBUNTU_MAJOR_MIN" ] && + [ "$DISTRIB_RELEASE_MINOR" -ge "$UBUNTU_MINOR_MIN" ]; then + OS_OK="ok" +fi + +if [ $OS_OK == 'nok' ]; then + echo "Ubuntu version less than $UBUNTU_MAJOR_MIN.$UBUNTU_MINOR_MIN." + exit 1 +fi + +function installPodman() { + apt-get update -y + apt-get install curl wget gnupg2 -y + if [ "$DISTRIB_RELEASE_MAJOR" -eq "20" ] && [ "$DISTRIB_RELEASE_MINOR" -eq "04" ]; then + echo 'Installing on 20.04' + source /etc/os-release + sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list" + wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key -O- | apt-key add - + apt-get update -y + apt-get -y install podman + return 0 + elif [ "$DISTRIB_RELEASE_MAJOR" -eq "20" ] && [ "$DISTRIB_RELEASE_MINOR" -eq "10" ]; then + apt-get -y install podman + return 0 + elif [ "$DISTRIB_RELEASE_MAJOR" -gt "20" ]; then + apt-get -y install podman + return 0 + else + exit 1 + fi + +} + +if [ ! -x "$(command -v podman)" ]; then + while true; do + read -p "Podman not found, should I install it automatically? [Yy/Nn] " yn + case $yn in + [Yy]*) + installPodman + break + ;; + [Nn]*) + echo "Please install docker manually and update it to the latest, but at least to $DOCKER_MAJOR.$DOCKER_MINOR" + exit 0 + ;; + *) echo "Please answer Yy or Nn." ;; + esac + done +fi + +# Making base directory for coolify +if [ ! -d coolify ]; then + mkdir coolify +fi + +echo "COOLIFY_APP_ID=$APP_ID +COOLIFY_SECRET_KEY=$RANDOM_SECRET +COOLIFY_DATABASE_URL=file:../db/prod.db +COOLIFY_SENTRY_DSN=$SENTRY_DSN +COOLIFY_HOSTED_ON=docker" >coolify/.env + +systemctl start podman.socket +systemctl enable podman.socket + +podman volume create coolify-db +podman volume create coolify-ssl-certs +podman volume create coolify-letsencrypt + + +cd coolify && podman run --privileged -tid --env-file .env -v /var/run/podman/podman.sock:/var/run/podman/podman.sock -v coolify-db-sqlite:/app/db docker.io/coollabsio/coolify:latest /bin/sh -c "env | grep COOLIFY > .env && docker-compose up -d --force-recreate" +echo "Done" +exit 0 diff --git a/src/app.html b/src/app.html index 488c26e30..d5c431e11 100644 --- a/src/app.html +++ b/src/app.html @@ -4,10 +4,10 @@ - Coolify - + + Coolify %svelte.head% diff --git a/src/app.postcss b/src/app.postcss deleted file mode 100644 index 51f8daf3a..000000000 --- a/src/app.postcss +++ /dev/null @@ -1,149 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -html { - height: 100%; -} -body { - background-color: rgb(22, 22, 22); - min-height: 100vh; - overflow-x: hidden; - @apply text-white; -} - -:root { - --toastBackground: rgba(41, 37, 36, 0.8); - --toastProgressBackground: transparent; - --toastFont: 'Inter'; -} - -.border-gradient { - border-bottom: 2px solid transparent; - border-image: linear-gradient( - 0.25turn, - rgba(255, 249, 34), - rgba(255, 0, 128), - rgba(56, 2, 155, 0) - ); - border-image-slice: 1; -} -.border-gradient-full { - border: 4px solid transparent; - border-image: linear-gradient( - 0.25turn, - rgba(255, 249, 34), - rgba(255, 0, 128), - rgba(56, 2, 155, 0) - ); - border-image-slice: 1; -} - -[aria-label][role~='tooltip']::after { - background: rgba(41, 37, 36, 0.9); - color: white; - font-family: 'Inter'; - font-size: 16px; - font-weight: 600; - white-space: normal; -} - -[role~='tooltip'][data-microtip-position|='bottom']::before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%28180%2018%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') - no-repeat; -} - -[role~='tooltip'][data-microtip-position|='top']::before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%280%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') - no-repeat; -} -[role~='tooltip'][data-microtip-position='right']::before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%2890%206%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') - no-repeat; -} - -[role~='tooltip'][data-microtip-position='left']::before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%28-90%2018%2018%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') - no-repeat; -} - -.main { - width: calc(100% - 4rem); - margin-left: 4rem; -} -._toastMsg { - @apply text-sm font-bold !important; -} -._toastItem { - @apply w-full border-l-2 border-green-600 !important; -} -._toastBtn { - @apply text-xs !important; -} -._toastBtn:hover { - @apply bg-gray-500 !important; -} -.icon { - @apply text-white rounded p-2 transition duration-100; -} -.icon:hover { - @apply bg-warmGray-700; -} -input { - @apply text-sm rounded py-2 px-6 font-bold bg-warmGray-800 text-white transition duration-150 outline-none border border-transparent !important; -} -input:hover, -input:focus-visible, -input:focus { - @apply bg-warmGray-700 !important; -} -textarea { - @apply text-sm rounded py-2 px-6 font-bold bg-warmGray-800 text-white transition duration-150 outline-none resize-none !important; -} -textarea:hover { - @apply bg-warmGray-700 !important; -} -select { - @apply text-sm rounded py-2 px-6 font-bold bg-warmGray-800 text-white transition duration-150 outline-none !important; -} -select:hover { - @apply bg-warmGray-700 !important; -} - -label { - @apply text-left text-base font-bold text-warmGray-400 !important; -} -button { - @apply outline-none !important; -} -.button { - @apply rounded text-sm font-bold transition-all duration-100 !important; -} - -.button:focus-visible, -.button:focus { - @apply bg-warmGray-700 !important; -} -.h-271 { - min-height: 271px !important; -} -.repository-select-search .listItem .item, -.repository-select-search .empty { - @apply text-sm py-3 font-bold bg-warmGray-800 text-white cursor-pointer border-none hover:bg-warmGray-700 !important; -} - -.repository-select-search .listContainer { - @apply bg-transparent !important; -} - -.repository-select-search .clearSelect { - @apply text-white cursor-pointer !important; -} - -.repository-select-search .selectedItem { - @apply text-white relative cursor-pointer font-bold text-sm flex items-center !important; -} -.selectContainer { - background: transparent !important; - @apply border-0 !important; -} diff --git a/src/components/Application/ActiveTab/General.svelte b/src/components/Application/ActiveTab/General.svelte deleted file mode 100644 index fe3f5b79a..000000000 --- a/src/components/Application/ActiveTab/General.svelte +++ /dev/null @@ -1,610 +0,0 @@ - - -
-
-
Build Packs
-
-
- Static -
-
- NodeJS -
-
- VueJS -
-
- NuxtJS -
-
- React/Preact -
-
- NextJS -
-
- Gatsby -
-
- Svelte -
-
- PHP -
-
- Rust -
-
- NestJS -
-
- Docker -
-
- Python -
-
-
-
General settings
-
-
-
    -
  • -
    -

    Preview deployments

    -

    - PR's will be deployed so you could review them easily -

    -
    - - {#if $isPullRequestPermissionsGranted} -
    - - {#if loading.previewDeployment} -
    - {$application.general.isPreviewDeploymentEnabled ? 'Enabling...' : 'Disabling...'} -
    - {/if} -
    - {:else} -
    - {#if !howToActivate} - - {:else} - - {/if} - {#if howToActivate} -
    -
    -
    (howToActivate = false)} - > - X -
    -

    - You need to add two new permissions to your GitHub - App: -

    -
    -

    - 1. In Repository permissions, add - Read-only - access to Pull requests. -

    -
    -

    - 2. In Subscribe to events section, - check Pull request field. -

    -
    -

    - 3. You receive an email where you need to - accept the new permissions. -

    -
    -
    - {/if} -
    - {/if} -
  • -
-
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
Commands
-
-
-
- {#if $application.build.pack === 'python'} - - - - - - - {:else} - - - - - - - - {/if} -
-
-
-
- - diff --git a/src/components/Application/ActiveTab/PullRequests.svelte b/src/components/Application/ActiveTab/PullRequests.svelte deleted file mode 100644 index 1ee176133..000000000 --- a/src/components/Application/ActiveTab/PullRequests.svelte +++ /dev/null @@ -1,102 +0,0 @@ - - -
Pull Requests
-
- {#if $prApplication.length > 0} -
- {#each $prApplication as pr} -
-
- {pr.publish.domain} -
- - - - - - -
- {/each} -
- {:else} -
No PR deployments found
- {/if} -
diff --git a/src/components/Application/ActiveTab/Secrets.svelte b/src/components/Application/ActiveTab/Secrets.svelte deleted file mode 100644 index 541514454..000000000 --- a/src/components/Application/ActiveTab/Secrets.svelte +++ /dev/null @@ -1,128 +0,0 @@ - - -
Secrets
-
-
-
- - -
-
- - -
- -
- -
- -
-
-
- -
-
- - {#if $application.publish.secrets.length > 0} -
- {#each $application.publish.secrets as secret} -
- - -
- -
- -
- {/each} -
- {/if} -
diff --git a/src/components/Application/Branches.svelte b/src/components/Application/Branches.svelte deleted file mode 100644 index feb25d790..000000000 --- a/src/components/Application/Branches.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - -{#if loading} -
- - -
-{/if} diff --git a/src/components/Application/BuildEnv.svelte b/src/components/Application/BuildEnv.svelte deleted file mode 100644 index 121414070..000000000 --- a/src/components/Application/BuildEnv.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/src/components/Application/Configuration.svelte b/src/components/Application/Configuration.svelte deleted file mode 100644 index 60ecd6200..000000000 --- a/src/components/Application/Configuration.svelte +++ /dev/null @@ -1,198 +0,0 @@ - - -
- {#if relogin} - - {:else} - {#await loadGithubRepositories(false)} - - {:then} - {#if loading.github} - - {:else} -
- - {#if $application.repository.organization} - - {/if} - - {#if $application.repository.branch} - - {/if} -
- {/if} - {/await} - {/if} -
diff --git a/src/components/Application/Login.svelte b/src/components/Application/Login.svelte deleted file mode 100644 index c5089ffd0..000000000 --- a/src/components/Application/Login.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - -
-
Choose your Git provider
- -
diff --git a/src/components/Application/Navbar.svelte b/src/components/Application/Navbar.svelte deleted file mode 100644 index 5ddc6e196..000000000 --- a/src/components/Application/Navbar.svelte +++ /dev/null @@ -1,173 +0,0 @@ - - - diff --git a/src/components/Application/Repositories.svelte b/src/components/Application/Repositories.svelte deleted file mode 100644 index 4f8d4fd0a..000000000 --- a/src/components/Application/Repositories.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - -
- {#if $githubRepositories.length !== 0} - -
- - {/if} - {/if} -
-{/if} diff --git a/src/components/Database/SVGs/Clickhouse.svelte b/src/components/Database/SVGs/Clickhouse.svelte deleted file mode 100644 index b8cd28933..000000000 --- a/src/components/Database/SVGs/Clickhouse.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/components/Database/SVGs/CouchDb.svelte b/src/components/Database/SVGs/CouchDb.svelte deleted file mode 100644 index 393c1f3e6..000000000 --- a/src/components/Database/SVGs/CouchDb.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - \ No newline at end of file diff --git a/src/components/Database/SVGs/MongoDb.svelte b/src/components/Database/SVGs/MongoDb.svelte deleted file mode 100644 index bc2bf91d5..000000000 --- a/src/components/Database/SVGs/MongoDb.svelte +++ /dev/null @@ -1,32 +0,0 @@ - -MongoDB_Leaf_FullColor_RGB - diff --git a/src/components/Database/SVGs/Postgresql.svelte b/src/components/Database/SVGs/Postgresql.svelte deleted file mode 100644 index b2fe80c99..000000000 --- a/src/components/Database/SVGs/Postgresql.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/components/Loading.svelte b/src/components/Loading.svelte deleted file mode 100644 index 19e357793..000000000 --- a/src/components/Loading.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - -{#if fullscreen} - {#if github} -
-
-
- -
- {githubLoadingText} -
-
-
-
- {:else} -
- -
- {/if} -{:else} -
- -
-{/if} - - diff --git a/src/components/PasswordField.svelte b/src/components/PasswordField.svelte deleted file mode 100644 index 8f754c883..000000000 --- a/src/components/PasswordField.svelte +++ /dev/null @@ -1,56 +0,0 @@ - - -
- {#if showPassword} - - {:else} - - {/if} - -
(showPassword = !showPassword)} - > - {#if showPassword} - - - - {:else} - - - - - {/if} -
-
diff --git a/src/components/Service/CodeServer.svelte b/src/components/Service/CodeServer.svelte deleted file mode 100644 index d06679f34..000000000 --- a/src/components/Service/CodeServer.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -{#await getPassword()} - -{:then} -
-
-
-
Password
- -
-
-
-{/await} diff --git a/src/components/Service/MinIO.svelte b/src/components/Service/MinIO.svelte deleted file mode 100644 index 683e8967f..000000000 --- a/src/components/Service/MinIO.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - -
-
-
-
Root User
- -
- -
-
Root Password
- -
-
-
diff --git a/src/components/Service/Plausible.svelte b/src/components/Service/Plausible.svelte deleted file mode 100644 index 44b7036b7..000000000 --- a/src/components/Service/Plausible.svelte +++ /dev/null @@ -1,88 +0,0 @@ - - -{#if loading} - -{:else} -
-
-
-
General
-
- - - -
- -
-
Domain
- -
-
-
Email address
- -
-
-
Username
- -
-
-
Password
- -
-
PostgreSQL
-
-
Username
- -
-
-
Password
- -
-
-
Database
- -
-
-
-{/if} diff --git a/src/components/Tooltip.svelte b/src/components/Tooltip.svelte deleted file mode 100644 index f4942709d..000000000 --- a/src/components/Tooltip.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/components/TooltipInfo.svelte b/src/components/TooltipInfo.svelte deleted file mode 100644 index 9e00d0d60..000000000 --- a/src/components/TooltipInfo.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/src/global.d.ts b/src/global.d.ts index 716f94507..4a2702fbc 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,8 +1,75 @@ /// -/// -/// +interface Cookies { + teamId?: string; + gitlabToken?: string; + 'kit.session'?: string; +} +interface Locals { + gitlabToken?: string; + user: { + teamId: string; + permission: string; + isAdmin: boolean; + }; + session: { + data: { + uid?: string; + teams?: string[]; + expires?: string; + }; + }; +} -export type DateTimeFormatOptions = { +type Applications = { + name: string; + domain: string; +}; + +interface Hash { + iv: string; + content: string; +} + +interface BuildPack { + name: string; +} + +// TODO: Not used, not working what?! +enum GitSource { + Github = 'github', + Gitlab = 'gitlab', + Bitbucket = 'bitbucket' +} + +type RawHaproxyConfiguration = { + _version: number; + data: string; +}; + +type NewTransaction = { + _version: number; + id: string; + status: string; +}; + +type HttpRequestRuleForceSSL = { + return_hdrs: null; + cond: string; + cond_test: string; + index: number; + redir_code: number; + redir_type: string; + redir_value: string; + type: string; +}; + +// TODO: No any please +type HttpRequestRule = { + _version: number; + data: Array; +}; + +type DateTimeFormatOptions = { localeMatcher?: 'lookup' | 'best fit'; weekday?: 'long' | 'short' | 'narrow'; era?: 'long' | 'short' | 'narrow'; @@ -17,106 +84,3 @@ export type DateTimeFormatOptions = { hour12?: boolean; timeZone?: string; }; -export type Application = { - github: { - installation: { - id: number; - }; - app: { - id: number; - }; - }; - repository: { - id: number; - organization: string; - name: string; - branch: string; - }; - general: { - deployId: string; - nickname: string; - workdir: string; - isPreviewDeploymentEnabled: boolean; - pullRequest: number; - }; - build: { - pack: string; - directory: string; - command: { - build: string | null; - installation: string; - start: string; - python: { - module?: string; - instance?: string; - }; - }; - container: { - name: string; - tag: string; - baseSHA: string; - }; - }; - publish: { - directory: string; - domain: string; - path: string; - port: number; - secrets: Array>; - }; -}; -export type Database = { - config: - | { - general: { - deployId: string; - nickname: string; - workdir: string; - type: string; - }; - database: { - usernames: Array; - passwords: Array; - defaultDatabaseName: string; - }; - deploy: { - name: string; - }; - } - | Record; - envs: Array; -}; -export type Dashboard = { - databases: { - deployed: - | [ - { - configuration: Database; - } - ] - | []; - }; - services: { - deployed: - | [ - { - configuration: any; - } - ] - | []; - }; - applications: { - deployed: - | [ - { - configuration: Application; - UpdatedAt: any; - } - ] - | []; - }; -}; -export type GithubInstallations = { - id: number; - app_id: number; -}; diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 000000000..d4fea1baf --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,77 @@ +import dotEnvExtended from 'dotenv-extended'; +dotEnvExtended.load(); +import type { GetSession } from '@sveltejs/kit'; +import { handleSession } from 'svelte-kit-cookie-session'; +import { getUserDetails, isTeamIdTokenAvailable, sentry } from '$lib/common'; +import { version } from '$lib/common'; +import cookie from 'cookie'; +import { dev } from '$app/env'; + +export const handle = handleSession( + { + secret: process.env['COOLIFY_SECRET_KEY'], + expires: 30 + }, + async function ({ event, resolve }) { + let response; + try { + const cookies: Cookies = cookie.parse(event.request.headers.get('cookie') || ''); + if (cookies['kit.session']) { + const { permission, teamId } = await getUserDetails(event, false); + event.locals.user = { + teamId, + permission, + isAdmin: permission === 'admin' || permission === 'owner' + }; + } + if (cookies.gitlabToken) { + event.locals.gitlabToken = cookies.gitlabToken; + } + response = await resolve(event, { + ssr: !event.url.pathname.startsWith('/webhooks/success') + }); + } catch (error) { + response = await resolve(event, { + ssr: !event.url.pathname.startsWith('/webhooks/success') + }); + response.headers.append( + 'Set-Cookie', + cookie.serialize('kit.session', '', { + path: '/', + expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT') + }) + ); + response.headers.append( + 'Set-Cookie', + cookie.serialize('teamId', '', { + path: '/', + expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT') + }) + ); + response.headers.append( + 'Set-Cookie', + cookie.serialize('gitlabToken', '', { + path: '/', + expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT') + }) + ); + } finally { + return response; + } + } +); + +export const getSession: GetSession = function (request) { + return { + version, + gitlabToken: request.locals?.gitlabToken || null, + uid: request.locals.session.data?.uid || null, + teamId: request.locals.user?.teamId || null, + permission: request.locals.user?.permission, + isAdmin: request.locals.user?.isAdmin || false + }; +}; + +export async function handleError({ error, event }) { + if (!dev) sentry.captureException(error, { event }); +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts deleted file mode 100644 index 05738d97c..000000000 --- a/src/hooks/index.ts +++ /dev/null @@ -1,151 +0,0 @@ -import dotEnvExtended from 'dotenv-extended'; -dotEnvExtended.load(); -import { publicPages } from '$lib/consts'; -import mongoose from 'mongoose'; -import { verifyUserId } from '$lib/api/common'; -import { initializeSession } from 'svelte-kit-cookie-session'; -import { cleanupStuckedDeploymentsInDB } from '$lib/api/applications/cleanup'; -import { docker } from '$lib/api/docker'; -import Configuration from '$models/Configuration'; - -process.on('SIGINT', function () { - mongoose.connection.close(function () { - console.log('Mongoose default connection disconnected through app termination'); - process.exit(0); - }); -}); - -async function connectMongoDB() { - // TODO: Save configurations on start? - const { MONGODB_USER, MONGODB_PASSWORD, MONGODB_HOST, MONGODB_PORT, MONGODB_DB } = process.env; - try { - if (process.env.NODE_ENV === 'production') { - await mongoose.connect( - `mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DB}?authSource=${MONGODB_DB}&readPreference=primary&ssl=false`, - { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false } - ); - } else { - await mongoose.connect( - 'mongodb://supercooldbuser:developmentPassword4db@localhost:27017/coolify?&readPreference=primary&ssl=false', - { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false } - ); - } - console.log('Connected to mongodb.'); - } catch (error) { - console.log(error); - } -} - -(async () => { - if (mongoose.connection.readyState !== 1) await connectMongoDB(); - try { - await mongoose.connection.db.dropCollection('logs-servers'); - } catch (error) { - // - } - try { - await cleanupStuckedDeploymentsInDB(); - } catch (error) { - console.log(error); - } - try { - const dockerServices = await docker.engine.listServices(); - let applications: any = dockerServices.filter( - (r) => - r.Spec.Labels.managedBy === 'coolify' && - r.Spec.Labels.type === 'application' && - r.Spec.Labels.configuration - ); - applications = applications.map((r) => { - if (JSON.parse(r.Spec.Labels.configuration)) { - return { - configuration: JSON.parse(r.Spec.Labels.configuration), - UpdatedAt: r.UpdatedAt - }; - } - return {}; - }); - applications = [ - ...new Map( - applications.map((item) => [ - item.configuration.publish.domain + item.configuration.publish.path, - item - ]) - ).values() - ]; - for (const application of applications) { - await Configuration.findOneAndUpdate( - { - 'repository.name': application.configuration.repository.name, - 'repository.organization': application.configuration.repository.organization, - 'repository.branch': application.configuration.repository.branch, - 'publish.domain': application.configuration.publish.domain - }, - { - ...application.configuration - }, - { upsert: true, new: true } - ); - } - } catch (error) { - console.log(error); - } -})(); - -export async function handle({ request, resolve }) { - const { SECRETS_ENCRYPTION_KEY } = process.env; - let session; - try { - session = initializeSession(request.headers, { - secret: SECRETS_ENCRYPTION_KEY, - cookie: { path: '/', secure: true } - }); - } catch (error) { - console.log(error); - return { - status: 302, - headers: { - 'set-cookie': 'kit.session=deleted;path=/;expires=Wed, 21 Oct 2015 07:28:00 GMT', - location: '/' - } - }; - } - - request.locals.session = session; - if (session?.data?.coolToken) { - try { - await verifyUserId(session.data.coolToken); - request.locals.session = session; - } catch (error) { - request.locals.session.destroy = true; - } - } - const response = await resolve(request); - if (!session['set-cookie']) { - if (!session?.data?.coolToken && !publicPages.includes(request.path)) { - return { - status: 302, - headers: { - location: '/' - } - }; - } - return response; - } - return { - ...response, - headers: { - ...response.headers, - ...session - } - }; -} -export function getSession(request) { - const { data } = request.locals.session; - return { - isLoggedIn: data && Object.keys(data).length !== 0 ? true : false, - expires: data.expires, - coolToken: data.coolToken, - ghToken: data.ghToken || null - }; -} diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 000000000..42fe10a1e --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,60 @@ +async function send({ method, path, data = {}, headers, timeout = 30000 }) { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + const opts = { method, headers: {}, body: null, signal: controller.signal }; + if (Object.keys(data).length > 0) { + let parsedData = data; + for (const [key, value] of Object.entries(data)) { + if (value === '') { + parsedData[key] = null; + } + } + if (parsedData) { + opts.headers['Content-Type'] = 'application/json'; + opts.body = JSON.stringify(parsedData); + } + } + + if (headers) { + opts.headers = { + ...opts.headers, + ...headers + }; + } + const response = await fetch(`${path}`, opts); + + clearTimeout(id); + + const contentType = response.headers.get('content-type'); + + let responseData = {}; + if (contentType) { + if (contentType?.indexOf('application/json') !== -1) { + responseData = await response.json(); + } else if (contentType?.indexOf('text/plain') !== -1) { + responseData = await response.text(); + } else { + return {}; + } + } else { + return {}; + } + if (!response.ok) throw responseData; + return responseData; +} + +export function get(path, headers = {}): Promise { + return send({ method: 'GET', path, headers }); +} + +export function del(path, data = {}, headers = {}): Promise { + return send({ method: 'DELETE', path, data, headers }); +} + +export function post(path, data, headers = {}): Promise { + return send({ method: 'POST', path, data, headers }); +} + +export function put(path, data, headers = {}): Promise { + return send({ method: 'PUT', path, data, headers }); +} diff --git a/src/lib/api/applications/buildContainer.ts b/src/lib/api/applications/buildContainer.ts deleted file mode 100644 index e971747f6..000000000 --- a/src/lib/api/applications/buildContainer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Deployment from '$models/Deployment'; -import { saveAppLog } from './logging'; -import * as packs from './packs'; - -export default async function (configuration) { - const { id, organization, name, branch } = configuration.repository; - const { domain } = configuration.publish; - const deployId = configuration.general.deployId; - const execute = packs[configuration.build.pack]; - if (execute) { - await Deployment.findOneAndUpdate( - { repoId: id, branch, deployId, organization, name, domain }, - { repoId: id, branch, deployId, organization, name, domain, progress: 'inprogress' } - ); - await saveAppLog('### Building application.', configuration); - await execute(configuration); - await saveAppLog('### Building done.', configuration); - } else { - try { - await Deployment.findOneAndUpdate( - { repoId: id, branch, deployId, organization, name, domain }, - { repoId: id, branch, deployId, organization, name, domain, progress: 'failed' } - ); - } catch (error) { - // Hmm. - } - throw new Error('No buildpack found.'); - } -} diff --git a/src/lib/api/applications/cleanup.ts b/src/lib/api/applications/cleanup.ts deleted file mode 100644 index b30078702..000000000 --- a/src/lib/api/applications/cleanup.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { docker } from '$lib/api/docker'; -import Deployment from '$models/Deployment'; -import { execShellAsync } from '../common'; -import crypto from 'crypto'; -export async function deleteSameDeployments(configuration, originalDomain = null) { - await ( - await docker.engine.listServices() - ) - .filter((r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application') - .map(async (s) => { - const running = JSON.parse(s.Spec.Labels.configuration); - if ( - running.repository.id === configuration.repository.id && - running.repository.branch === configuration.repository.branch && - running.publish.domain === originalDomain || configuration.publish.domain - ) { - await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`); - } - }); -} - -export async function cleanupStuckedDeploymentsInDB() { - // Cleanup stucked deployments. - await Deployment.updateMany( - { progress: { $in: ['queued', 'inprogress'] } }, - { progress: 'failed' } - ); -} -export async function purgeImagesContainers(configuration, deleteAll = false) { - const { name, tag } = configuration.build.container; - try { - await execShellAsync('docker container prune -f'); - } catch (error) { - // - } - try { - if (deleteAll) { - const IDsToDelete = ( - await execShellAsync( - `docker images ls --filter=reference='${name}' --format '{{json .ID }}'` - ) - ) - .trim() - .replace(/"/g, '') - .split('\n'); - if (IDsToDelete.length > 0) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`); - } else { - const IDsToDelete = ( - await execShellAsync( - `docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'` - ) - ) - .trim() - .replace(/"/g, '') - .split('\n'); - if (IDsToDelete.length > 1) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`); - } - } catch (error) { - console.log(error); - } - try { - await execShellAsync('docker image prune -f'); - } catch (error) { - // - } -} diff --git a/src/lib/api/applications/cloneRepository.ts b/src/lib/api/applications/cloneRepository.ts deleted file mode 100644 index 63c4b8040..000000000 --- a/src/lib/api/applications/cloneRepository.ts +++ /dev/null @@ -1,54 +0,0 @@ -import jsonwebtoken from 'jsonwebtoken'; -import { execShellAsync } from '../common'; - -export default async function (configuration) { - try { - const { GITHUB_APP_PRIVATE_KEY } = process.env; - const { workdir, isPreviewDeploymentEnabled, pullRequest } = configuration.general; - const { organization, name, branch } = configuration.repository; - const github = configuration.github; - if (!github.installation.id || !github.app.id) { - throw new Error('Github installation ID is invalid.'); - } - const githubPrivateKey = GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n').replace(/"/g, ''); - - const payload = { - iat: Math.round(new Date().getTime() / 1000), - exp: Math.round(new Date().getTime() / 1000 + 60), - iss: parseInt(github.app.id) - }; - - const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, { - algorithm: 'RS256' - }); - - const { token } = await ( - await fetch( - `https://api.github.com/app/installations/${github.installation.id}/access_tokens`, - { - method: 'POST', - headers: { - Authorization: 'Bearer ' + jwtToken, - Accept: 'application/vnd.github.machine-man-preview+json' - } - } - ) - ).json(); - await execShellAsync( - `mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${token}@github.com/${organization}/${name}.git ${workdir}/` - ); - - if (isPreviewDeploymentEnabled && pullRequest && pullRequest !== 0) { - await execShellAsync( - `cd ${workdir} && git fetch origin pull/${pullRequest}/head:pull_${pullRequest} && git checkout pull_${pullRequest}` - ); - } - configuration.build.container.tag = ( - await execShellAsync(`cd ${workdir}/ && git rev-parse HEAD`) - ) - .replace('\n', '') - .slice(0, 7); - } catch (error) { - console.log(error); - } -} diff --git a/src/lib/api/applications/common.ts b/src/lib/api/applications/common.ts deleted file mode 100644 index 543e6c3a5..000000000 --- a/src/lib/api/applications/common.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const baseServiceConfiguration = { - replicas: 1, - restart_policy: { - condition: 'any', - max_attempts: 6 - }, - update_config: { - parallelism: 1, - delay: '10s', - order: 'start-first' - }, - rollback_config: { - parallelism: 1, - delay: '10s', - order: 'start-first', - failure_action: 'rollback' - } -}; diff --git a/src/lib/api/applications/configuration.ts b/src/lib/api/applications/configuration.ts deleted file mode 100644 index b4b2706bb..000000000 --- a/src/lib/api/applications/configuration.ts +++ /dev/null @@ -1,198 +0,0 @@ -import cuid from 'cuid'; -import crypto from 'crypto'; -import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator'; -import { docker } from '$lib/api/docker'; -import { baseServiceConfiguration } from './common'; -import { execShellAsync } from '../common'; -import { promises as fs } from 'fs'; -import Configuration from '$models/Configuration'; -function getUniq() { - return uniqueNamesGenerator({ dictionaries: [adjectives, animals, colors], length: 2 }); -} - -export function setDefaultConfiguration(configuration) { - const nickname = configuration.general.nickname || getUniq(); - const deployId = cuid(); - const shaBase = JSON.stringify({ path: configuration.publish.path, domain: configuration.publish.domain }); - const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex'); - - configuration.build.container.name = sha256.slice(0, 15); - - configuration.general.nickname = nickname; - configuration.general.deployId = deployId; - configuration.general.workdir = `/tmp/${deployId}`; - if (configuration.general.isPreviewDeploymentEnabled && configuration.general.pullRequest !== 0) { - configuration.build.container.name = `pr${configuration.general.pullRequest}-${sha256.slice( - 0, - 8 - )}`; - configuration.publish.domain = `pr${configuration.general.pullRequest}.${configuration.publish.domain}`; - } - if (!configuration.publish.path) configuration.publish.path = '/'; - if (!configuration.publish.port) { - if ( - configuration.build.pack === 'nodejs' || - configuration.build.pack === 'nuxtjs' || - configuration.build.pack === 'rust' || - configuration.build.pack === 'nextjs' || - configuration.build.pack === 'nestjs' - ) { - configuration.publish.port = 3000; - } else if (configuration.build.pack === 'python') { - configuration.publish.port = 4000; - } else { - configuration.publish.port = 80; - } - } - if (!configuration.build.directory) configuration.build.directory = ''; - if (configuration.build.directory.startsWith('/')) - configuration.build.directory = configuration.build.directory.replace('/', ''); - - if (!configuration.publish.directory) configuration.publish.directory = ''; - if (configuration.publish.directory.startsWith('/')) - configuration.publish.directory = configuration.publish.directory.replace('/', ''); - - if (configuration.build.pack === 'nodejs') { - if (!configuration.build.command.installation) - configuration.build.command.installation = 'yarn install'; - } - if ( - configuration.build.pack === 'nodejs' || - configuration.build.pack === 'vuejs' || - configuration.build.pack === 'nuxtjs' || - configuration.build.pack === 'nextjs' || - configuration.build.pack === 'nestjs' - ) { - if (!configuration.build.command.start) configuration.build.command.start = 'yarn start'; - } - if (configuration.build.pack === 'python') { - if (!configuration.build.command.python.module) - configuration.build.command.python.module = 'main'; - if (!configuration.build.command.python.instance) - configuration.build.command.python.instance = 'app'; - } - - configuration.build.container.baseSHA = crypto - .createHash('sha256') - .update(JSON.stringify(baseServiceConfiguration)) - .digest('hex'); - configuration.baseServiceConfiguration = baseServiceConfiguration; - - return configuration; -} - -export async function precheckDeployment(configuration) { - const services = await Configuration.find({ - 'publish.domain': configuration.publish.domain, - 'publish.path': configuration.publish.path - }) - // const services = (await docker.engine.listServices()).filter( - // (r) => - // r.Spec.Labels.managedBy === 'coolify' && - // r.Spec.Labels.type === 'application' && - // JSON.parse(r.Spec.Labels.configuration).publish.domain === configuration.publish.domain - // ); - let foundService = false; - let configChanged = false; - let imageChanged = false; - let forceUpdate = false; - for (const service of services) { - // const running = JSON.parse(service.Spec.Labels.configuration); - if ( - service.repository.id === configuration.repository.id && - service.repository.branch === configuration.repository.branch - ) { - foundService = true; - // Base service configuration changed - if ( - !service.build.container.baseSHA || - service.build.container.baseSHA !== configuration.build.container.baseSHA - ) { - forceUpdate = true; - } - // If the deployment is in error state, forceUpdate - const state = await execShellAsync( - `docker stack ps ${service.build.container.name} --format '{{ json . }}'` - ); - const isError = state - .split('\n') - .filter((n) => n) - .map((s) => JSON.parse(s)) - .filter( - (n) => - n.DesiredState !== 'Running' && n.Image.split(':')[1] === service.build.container.tag - ); - if (isError.length > 0) { - forceUpdate = true; - } - - const compareObjects = (a, b) => { - if (a === b) return true; - - if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false; - - const keysA = Object.keys(a), - keysB = Object.keys(b); - - if (keysA.length != keysB.length) return false; - - for (const key of keysA) { - if (!keysB.includes(key)) return false; - - if (typeof a[key] === 'function' || typeof b[key] === 'function') { - if (a[key].toString() != b[key].toString()) return false; - } else { - if (!compareObjects(a[key], b[key])) return false; - } - } - - return true; - }; - - const runningWithoutContainer = JSON.parse(JSON.stringify(service)); - delete runningWithoutContainer.build.container; - - const configurationWithoutContainer = JSON.parse(JSON.stringify(configuration)); - delete configurationWithoutContainer.build.container; - - // If only the configuration changed - if ( - !compareObjects(runningWithoutContainer.build, configurationWithoutContainer.build) || - !compareObjects(runningWithoutContainer.publish, configurationWithoutContainer.publish) || - runningWithoutContainer.general.isPreviewDeploymentEnabled !== - configurationWithoutContainer.general.isPreviewDeploymentEnabled - ) { - configChanged = true; - } - - // If only the image changed - if (service.build.container.tag !== configuration.build.container.tag) imageChanged = true; - // If build pack changed, forceUpdate the service - if (service.build.pack !== configuration.build.pack) forceUpdate = true; - if ( - configuration.general.isPreviewDeploymentEnabled && - configuration.general.pullRequest !== 0 - ) - forceUpdate = true; - } - - } - if (forceUpdate) { - imageChanged = false; - configChanged = false; - } - return { - foundService, - imageChanged, - configChanged, - forceUpdate - }; -} - -export async function updateServiceLabels(configuration) { - return await execShellAsync( - `docker service update --label-add configuration='${JSON.stringify(configuration)}' ${ - configuration.build.container.name - }_${configuration.build.container.name}` - ); -} diff --git a/src/lib/api/applications/copyFiles.ts b/src/lib/api/applications/copyFiles.ts deleted file mode 100644 index 7a6d02424..000000000 --- a/src/lib/api/applications/copyFiles.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { promises as fs } from 'fs'; -export default async function (configuration) { - const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby']; - try { - // TODO: Write full .dockerignore for all deployments!! - if (configuration.build.pack === 'php') { - await fs.writeFile( - `${configuration.general.workdir}/.htaccess`, - ` - RewriteEngine On - RewriteBase / - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^(.+)$ index.php [QSA,L] - ` - ); - } - // await fs.writeFile(`${configuration.general.workdir}/.dockerignore`, 'node_modules') - if (staticDeployments.includes(configuration.build.pack)) { - await fs.writeFile( - `${configuration.general.workdir}/nginx.conf`, - `user nginx; - worker_processes auto; - - error_log /var/log/nginx/error.log warn; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - } - - http { - include /etc/nginx/mime.types; - - access_log off; - sendfile on; - #tcp_nopush on; - keepalive_timeout 65; - - server { - listen 80; - server_name localhost; - - location / { - root /usr/share/nginx/html; - index index.html; - try_files $uri $uri/index.html $uri/ /index.html =404; - } - - error_page 404 /50x.html; - - # redirect server error pages to the static page /50x.html - # - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - - } - - } - ` - ); - } - } catch (error) { - console.log(error); - throw new Error(error); - } -} diff --git a/src/lib/api/applications/deploy.ts b/src/lib/api/applications/deploy.ts deleted file mode 100644 index b09a22e37..000000000 --- a/src/lib/api/applications/deploy.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { docker } from '$lib/api/docker'; -import { saveAppLog } from './logging'; -import { promises as fs } from 'fs'; -import { deleteSameDeployments, purgeImagesContainers } from './cleanup'; -import yaml from 'js-yaml'; -import { delay, execShellAsync } from '../common'; - -export default async function (configuration, nextStep) { - const generateEnvs = {}; - for (const secret of configuration.publish.secrets) { - generateEnvs[secret.name] = secret.value; - } - const containerName = configuration.build.container.name; - const containerTag = configuration.build.container.tag; - - // Only save SHA256 of it in the configuration label - const baseServiceConfiguration = configuration.baseServiceConfiguration; - delete configuration.baseServiceConfiguration; - - const stack = { - version: '3.8', - services: { - [containerName]: { - image: `${containerName}:${containerTag}`, - networks: [`${docker.network}`], - environment: generateEnvs, - deploy: { - ...baseServiceConfiguration, - labels: [ - 'managedBy=coolify', - 'type=application', - 'configuration=' + JSON.stringify(configuration), - 'traefik.enable=true', - 'traefik.http.services.' + - containerName + - `.loadbalancer.server.port=${configuration.publish.port}`, - 'traefik.http.routers.' + containerName + '.entrypoints=websecure', - 'traefik.http.routers.' + - containerName + - '.rule=Host(`' + - configuration.publish.domain + - '`) && PathPrefix(`' + - configuration.publish.path + - '`)', - 'traefik.http.routers.' + containerName + '.tls.certresolver=letsencrypt', - 'traefik.http.routers.' + containerName + '.middlewares=global-compress' - ] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - } - }; - await saveAppLog('### Publishing.', configuration); - await fs.writeFile(`${configuration.general.workdir}/stack.yml`, yaml.dump(stack)); - if (nextStep === 2) { - // console.log('image changed') - await execShellAsync( - `docker service update --image ${containerName}:${containerTag} ${containerName}_${containerName}` - ); - } else { - // console.log('new deployment or force deployment or config changed') - // if (originalDomain !== configuration.publish.domain) { - // await deleteSameDeployments(configuration, originalDomain); - // } else { - // await deleteSameDeployments(configuration); - // } - // await deleteSameDeployments(configuration); - await execShellAsync( - `cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}` - ); - } - - - - await saveAppLog('### Published done!', configuration); -} diff --git a/src/lib/api/applications/logging.ts b/src/lib/api/applications/logging.ts deleted file mode 100644 index 55cd648ee..000000000 --- a/src/lib/api/applications/logging.ts +++ /dev/null @@ -1,58 +0,0 @@ -import Settings from '$models/Settings'; -import ServerLog from '$models/ServerLog'; -import ApplicationLog from '$models/ApplicationLog'; -import dayjs from 'dayjs'; -import { version } from '../../../../package.json'; - -function generateTimestamp() { - return `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} `; -} -const patterns = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' -].join('|'); - -export async function saveAppLog(event, configuration, isError?: boolean) { - try { - const deployId = configuration.general.deployId; - const repoId = configuration.repository.id; - const branch = configuration.repository.branch; - if (isError) { - const clearedEvent = - '[ERROR 😱] ' + - generateTimestamp() + - event.replace(new RegExp(patterns, 'g'), '').replace(/(\r\n|\n|\r)/gm, ''); - await new ApplicationLog({ repoId, branch, deployId, event: clearedEvent }).save(); - } else { - if (event && event !== '\n') { - const clearedEvent = - '[INFO] ' + - generateTimestamp() + - event.replace(new RegExp(patterns, 'g'), '').replace(/(\r\n|\n|\r)/gm, ''); - await new ApplicationLog({ repoId, branch, deployId, event: clearedEvent }).save(); - } - } - } catch (error) { - console.log(error); - return error; - } -} - -export async function saveServerLog(error) { - const settings = await Settings.findOne({ applicationName: 'coolify' }); - const payload = { - message: error.message, - stack: error.stack, - type: error.type || 'spaghetticode', - version - }; - - const found = await ServerLog.find(payload); - if (found.length === 0 && error.message) await new ServerLog(payload).save(); - if (settings && settings.sendErrors && process.env.NODE_ENV === 'production') { - await fetch('https://errors.coollabs.io/api/error', { - method: 'POST', - body: JSON.stringify({ ...payload }) - }); - } -} diff --git a/src/lib/api/applications/packs/docker/index.ts b/src/lib/api/applications/packs/docker/index.ts deleted file mode 100644 index e3d9f01e3..000000000 --- a/src/lib/api/applications/packs/docker/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; - -export default async function (configuration) { - const path = `${configuration.general.workdir}/${ - configuration.build.directory ? configuration.build.directory : '' - }`; - if (fs.stat(`${path}/Dockerfile`)) { - const stream = await docker.engine.buildImage( - { src: ['.'], context: path }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); - } else { - throw new Error('No custom dockerfile found.'); - } -} diff --git a/src/lib/api/applications/packs/gatsby/index.ts b/src/lib/api/applications/packs/gatsby/index.ts deleted file mode 100644 index 9cb109979..000000000 --- a/src/lib/api/applications/packs/gatsby/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; - -// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', -const publishStaticDocker = (configuration) => { - return [ - 'FROM nginx:stable-alpine', - 'COPY nginx.conf /etc/nginx/nginx.conf', - 'WORKDIR /usr/share/nginx/html', - `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, - 'EXPOSE 80', - 'CMD ["nginx", "-g", "daemon off;"]' - ].join('\n'); -}; - -export default async function (configuration) { - await buildImage(configuration, true); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishStaticDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/helpers.ts b/src/lib/api/applications/packs/helpers.ts deleted file mode 100644 index 74bfabcd1..000000000 --- a/src/lib/api/applications/packs/helpers.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; - -const buildImageNodeDocker = (configuration, prodBuild, generateEnvs) => { - return [ - 'FROM node:lts', - ...generateEnvs, - 'WORKDIR /usr/src/app', - `COPY ${configuration.build.directory}/package*.json ./`, - configuration.build.command.installation && `RUN ${configuration.build.command.installation}`, - `COPY ./${configuration.build.directory} ./`, - `RUN ${configuration.build.command.build}`, - prodBuild && `RUN rm -fr node_modules && ${configuration.build.command.installation} --prod` - ].join('\n'); -}; -export async function buildImage(configuration, cacheBuild?: boolean, prodBuild?: boolean) { - // TODO: Edit secrets - // TODO: Add secret from .env file / json - const generateEnvs = []; - const dotEnv = [] - for (const secret of configuration.publish.secrets) { - dotEnv.push(`${secret.name}=${secret.value}`) - if (secret.isBuild) generateEnvs.push(`ENV ${secret.name}=${secret.value}`) - } - await fs.writeFile( - `${configuration.general.workdir}/.env`, - dotEnv.join('\n') - ) - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - buildImageNodeDocker(configuration, prodBuild, generateEnvs) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { - t: `${configuration.build.container.name}:${cacheBuild - ? `${configuration.build.container.tag}-cache` - : configuration.build.container.tag - }` - } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/nestjs/index.ts b/src/lib/api/applications/packs/nestjs/index.ts deleted file mode 100644 index 9443f3220..000000000 --- a/src/lib/api/applications/packs/nestjs/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; -// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`, -const publishNodejsDocker = (configuration) => { - return [ - 'FROM node:lts', - 'WORKDIR /usr/src/app', - configuration.build.command.build - ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./` - : ` - COPY ${configuration.build.directory}/package*.json ./ - RUN ${configuration.build.command.installation} - COPY ./${configuration.build.directory} ./`, - `EXPOSE ${configuration.publish.port}`, - `CMD ${configuration.build.command.start}` - ].join('\n'); -}; - -export default async function (configuration) { - if (configuration.build.command.build) await buildImage(configuration, false, true); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishNodejsDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/nextjs/index.ts b/src/lib/api/applications/packs/nextjs/index.ts deleted file mode 100644 index 0e550f78e..000000000 --- a/src/lib/api/applications/packs/nextjs/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; -// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`, -const publishNodejsDocker = (configuration) => { - return [ - 'FROM node:lts', - 'WORKDIR /usr/src/app', - configuration.build.command.build - ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./` - : ` - COPY ${configuration.build.directory}/package*.json ./ - RUN ${configuration.build.command.installation} - COPY ./${configuration.build.directory} ./`, - `EXPOSE ${configuration.publish.port}`, - `CMD ${configuration.build.command.start}` - ].join('\n'); -}; -export default async function (configuration) { - await buildImage(configuration); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishNodejsDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/nodejs/index.ts b/src/lib/api/applications/packs/nodejs/index.ts deleted file mode 100644 index eef7842ae..000000000 --- a/src/lib/api/applications/packs/nodejs/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; -// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`, -const publishNodejsDocker = (configuration) => { - return [ - 'FROM node:lts', - 'WORKDIR /usr/src/app', - configuration.build.command.build - ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./` - : ` - COPY ${configuration.build.directory}/package*.json ./ - RUN ${configuration.build.command.installation} - COPY ./${configuration.build.directory} ./`, - `EXPOSE ${configuration.publish.port}`, - `CMD ${configuration.build.command.start}` - ].join('\n'); -}; - -export default async function (configuration) { - if (configuration.build.command.build) await buildImage(configuration); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishNodejsDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/nuxtjs/index.ts b/src/lib/api/applications/packs/nuxtjs/index.ts deleted file mode 100644 index a444dcf39..000000000 --- a/src/lib/api/applications/packs/nuxtjs/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; -// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`, -const publishNodejsDocker = (configuration) => { - return [ - 'FROM node:lts', - 'WORKDIR /usr/src/app', - configuration.build.command.build - ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./` - : ` - COPY ${configuration.build.directory}/package*.json ./ - RUN ${configuration.build.command.installation} - COPY ./${configuration.build.directory} ./`, - `EXPOSE ${configuration.publish.port}`, - `CMD ${configuration.build.command.start}` - ].join('\n'); -}; - -export default async function (configuration) { - await buildImage(configuration); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishNodejsDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/php/index.ts b/src/lib/api/applications/packs/php/index.ts deleted file mode 100644 index b576018c6..000000000 --- a/src/lib/api/applications/packs/php/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', -const publishPHPDocker = (configuration) => { - return [ - 'FROM php:apache', - 'RUN a2enmod rewrite', - 'WORKDIR /usr/src/app', - `COPY ./${configuration.build.directory} /var/www/html`, - 'EXPOSE 80', - 'CMD ["apache2-foreground"]' - ].join('\n'); -}; - -export default async function (configuration) { - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishPHPDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/python/index.ts b/src/lib/api/applications/packs/python/index.ts deleted file mode 100644 index 9998564fb..000000000 --- a/src/lib/api/applications/packs/python/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`, -const publishPython = (configuration) => { - return [ - 'FROM python:3-alpine', - 'WORKDIR /usr/src/app', - 'RUN pip install gunicorn', - `COPY ./${configuration.build.directory}/requirements.txt ./`, - `RUN pip install --no-cache-dir -r ./${configuration.build.directory}/requirements.txt`, - `COPY ./${configuration.build.directory}/ .`, - `EXPOSE ${configuration.publish.port}`, - `CMD gunicorn -w=4 ${configuration.build.command.python.module}:${configuration.build.command.python.instance}` - ].join('\n'); -}; - -export default async function (configuration) { - await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishPython(configuration)); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/react/index.ts b/src/lib/api/applications/packs/react/index.ts deleted file mode 100644 index 9cb109979..000000000 --- a/src/lib/api/applications/packs/react/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; - -// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', -const publishStaticDocker = (configuration) => { - return [ - 'FROM nginx:stable-alpine', - 'COPY nginx.conf /etc/nginx/nginx.conf', - 'WORKDIR /usr/share/nginx/html', - `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, - 'EXPOSE 80', - 'CMD ["nginx", "-g", "daemon off;"]' - ].join('\n'); -}; - -export default async function (configuration) { - await buildImage(configuration, true); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishStaticDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/rust/index.ts b/src/lib/api/applications/packs/rust/index.ts deleted file mode 100644 index c11a9db32..000000000 --- a/src/lib/api/applications/packs/rust/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import TOML from '@iarna/toml'; -import { execShellAsync } from '$lib/api/common'; - -const publishRustDocker = (configuration, custom) => { - return [ - 'FROM rust:latest', - 'WORKDIR /app', - `COPY --from=${configuration.build.container.name}:cache /app/target target`, - `COPY --from=${configuration.build.container.name}:cache /usr/local/cargo /usr/local/cargo`, - 'COPY . .', - `RUN cargo build --release --bin ${custom.name}`, - 'FROM debian:buster-slim', - 'WORKDIR /app', - 'RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*', - 'RUN update-ca-certificates', - `COPY --from=${configuration.build.container.name}:cache /app/target/release/${custom.name} ${custom.name}`, - `EXPOSE ${configuration.publish.port}`, - `CMD ["/app/${custom.name}"]` - ].join('\n'); -}; - -const cacheRustDocker = (configuration, custom) => { - return [ - `FROM rust:latest AS planner-${configuration.build.container.name}`, - 'WORKDIR /app', - 'RUN cargo install cargo-chef', - 'COPY . .', - 'RUN cargo chef prepare --recipe-path recipe.json', - 'FROM rust:latest', - 'WORKDIR /app', - 'RUN cargo install cargo-chef', - `COPY --from=planner-${configuration.build.container.name} /app/recipe.json recipe.json`, - 'RUN cargo chef cook --release --recipe-path recipe.json' - ].join('\n'); -}; - -export default async function (configuration) { - const cargoToml = await execShellAsync(`cat ${configuration.general.workdir}/Cargo.toml`); - const parsedToml = TOML.parse(cargoToml); - const custom = { - name: parsedToml.package.name - }; - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - cacheRustDocker(configuration, custom) - ); - - let stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:cache` } - ); - await streamEvents(stream, configuration); - - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishRustDocker(configuration, custom) - ); - - stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/static/index.ts b/src/lib/api/applications/packs/static/index.ts deleted file mode 100644 index 5f4d5c302..000000000 --- a/src/lib/api/applications/packs/static/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; - -// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', -const publishStaticDocker = (configuration) => { - return [ - 'FROM nginx:stable-alpine', - 'COPY nginx.conf /etc/nginx/nginx.conf', - 'WORKDIR /usr/share/nginx/html', - configuration.build.command.build - ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./` - : `COPY ./${configuration.build.directory} ./`, - 'EXPOSE 80', - 'CMD ["nginx", "-g", "daemon off;"]' - ].join('\n'); -}; - -export default async function (configuration) { - if (configuration.build.command.build) await buildImage(configuration, true); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishStaticDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/svelte/index.ts b/src/lib/api/applications/packs/svelte/index.ts deleted file mode 100644 index 9cb109979..000000000 --- a/src/lib/api/applications/packs/svelte/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; - -// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', -const publishStaticDocker = (configuration) => { - return [ - 'FROM nginx:stable-alpine', - 'COPY nginx.conf /etc/nginx/nginx.conf', - 'WORKDIR /usr/share/nginx/html', - `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, - 'EXPOSE 80', - 'CMD ["nginx", "-g", "daemon off;"]' - ].join('\n'); -}; - -export default async function (configuration) { - await buildImage(configuration, true); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishStaticDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/packs/templates.ts b/src/lib/api/applications/packs/templates.ts deleted file mode 100644 index 07d0faaa6..000000000 --- a/src/lib/api/applications/packs/templates.ts +++ /dev/null @@ -1,66 +0,0 @@ -const defaultBuildAndDeploy = { - installation: 'yarn install', - build: 'yarn build', - start: 'yarn start' -}; - -const templates = { - svelte: { - pack: 'svelte', - ...defaultBuildAndDeploy, - directory: 'public', - name: 'Svelte' - }, - '@nestjs/core': { - pack: 'nestjs', - ...defaultBuildAndDeploy, - start: 'yarn start:prod', - port: 3000, - name: 'NestJS' - }, - next: { - pack: 'nextjs', - ...defaultBuildAndDeploy, - port: 3000, - name: 'NextJS' - }, - nuxt: { - pack: 'nuxtjs', - ...defaultBuildAndDeploy, - port: 3000, - name: 'NuxtJS' - }, - 'react-scripts': { - pack: 'react', - ...defaultBuildAndDeploy, - directory: 'build', - name: 'React' - }, - 'parcel-bundler': { - pack: 'static', - ...defaultBuildAndDeploy, - directory: 'dist', - name: 'Parcel' - }, - '@vue/cli-service': { - pack: 'vuejs', - ...defaultBuildAndDeploy, - directory: 'dist', - port: 80, - name: 'Vue' - }, - gatsby: { - pack: 'gatsby', - ...defaultBuildAndDeploy, - directory: 'public', - name: 'Gatsby' - }, - 'preact-cli': { - pack: 'react', - ...defaultBuildAndDeploy, - directory: 'build', - name: 'Preact' - } -}; - -export default templates; diff --git a/src/lib/api/applications/packs/vuejs/index.ts b/src/lib/api/applications/packs/vuejs/index.ts deleted file mode 100644 index d0f688a9a..000000000 --- a/src/lib/api/applications/packs/vuejs/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { docker, streamEvents } from '$lib/api/docker'; -import { promises as fs } from 'fs'; -import { buildImage } from '../helpers'; - -// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', -const publishStaticDocker = (configuration) => { - return [ - 'FROM nginx:stable-alpine', - 'COPY nginx.conf /etc/nginx/nginx.conf', - 'WORKDIR /usr/share/nginx/html', - `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, - 'EXPOSE 80', - 'CMD ["nginx", "-g", "daemon off;"]' - ].join('\n'); -}; - -export default async function (configuration) { - await buildImage(configuration, true); - await fs.writeFile( - `${configuration.general.workdir}/Dockerfile`, - publishStaticDocker(configuration) - ); - const stream = await docker.engine.buildImage( - { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } - ); - await streamEvents(stream, configuration); -} diff --git a/src/lib/api/applications/preChecks.ts b/src/lib/api/applications/preChecks.ts deleted file mode 100644 index 738e9353b..000000000 --- a/src/lib/api/applications/preChecks.ts +++ /dev/null @@ -1,76 +0,0 @@ -import Configuration from "$models/Configuration"; -import { compareObjects, execShellAsync } from "../common"; - -export default async function (configuration) { - /* - 0 => nothing changed, no need to redeploy - 1 => force update - 2 => configuration changed - 3 => continue normally - */ - const currentConfiguration = await Configuration.findOne({ - 'general.nickname': configuration.general.nickname - }) - if (currentConfiguration) { - // Base service configuration changed - if ( - !currentConfiguration.build.container.baseSHA || - currentConfiguration.build.container.baseSHA !== configuration.build.container.baseSHA - ) { - return 1 - } - - // If the deployment is in error state, forceUpdate - try { - const state = await execShellAsync( - `docker stack ps ${currentConfiguration.build.container.name} --format '{{ json . }}'` - ); - const isError = state - .split('\n') - .filter((n) => n) - .map((s) => JSON.parse(s)) - .filter( - (n) => - n.DesiredState !== 'Running' && n.Image.split(':')[1] === currentConfiguration.build.container.tag - ); - if (isError.length > 0) { - return 1 - } - } catch(error) { - console.log(error) - } - - - // If previewDeployments enabled - if ( - currentConfiguration.general.isPreviewDeploymentEnabled && - currentConfiguration.general.pullRequest !== 0 - ) { - return 1 - } - // If build pack changed, forceUpdate the service - if (currentConfiguration.build.pack !== configuration.build.pack) { - return 1 - } - - const currentConfigurationCompare = JSON.parse(JSON.stringify(currentConfiguration)); - const configurationCompare = JSON.parse(JSON.stringify(configuration)); - delete currentConfigurationCompare.build.container; - delete configurationCompare.build.container; - - if ( - !compareObjects(currentConfigurationCompare.build, configurationCompare.build) || - !compareObjects(currentConfigurationCompare.publish, configurationCompare.publish) || - currentConfigurationCompare.general.isPreviewDeploymentEnabled !== - configurationCompare.general.isPreviewDeploymentEnabled - ) { - return 1 - } - - if (currentConfiguration.build.container.tag !== configuration.build.container.tag) { - return 2 - } - return 0 - } - return 3 -} \ No newline at end of file diff --git a/src/lib/api/applications/preTasks.ts b/src/lib/api/applications/preTasks.ts deleted file mode 100644 index 0c2cb0e78..000000000 --- a/src/lib/api/applications/preTasks.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Configuration from "$models/Configuration"; -import Deployment from "$models/Deployment"; - -export default async function (configuration) { - // Check if deployment is already queued - const alreadyQueued = await Deployment.find({ - path: configuration.publish.path, - domain: configuration.publish.domain, - progress: { $in: ['queued', 'inprogress'] } - }); - if (alreadyQueued.length > 0) { - return { - status: 200, - body: { - success: false, - message: 'Deployment already queued.' - } - }; - } - const { id, organization, name, branch } = configuration.repository; - const { domain, path } = configuration.publish; - const { deployId, nickname } = configuration.general; - // Save new deployment - await new Deployment({ - repoId: id, - branch, - deployId, - domain, - organization, - name, - nickname - }).save(); - - await Configuration.findOneAndUpdate( - { - 'publish.domain': domain, - 'publish.path': path, - 'general.pullRequest': { $in: [null, 0] } - }, - { ...configuration }, - { upsert: true, new: true } - ); - return -} \ No newline at end of file diff --git a/src/lib/api/applications/queueAndBuild.ts b/src/lib/api/applications/queueAndBuild.ts deleted file mode 100644 index d3342c2c2..000000000 --- a/src/lib/api/applications/queueAndBuild.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Deployment from '$models/Deployment'; -import dayjs from 'dayjs'; -import buildContainer from './buildContainer'; -import { purgeImagesContainers } from './cleanup'; -import { updateServiceLabels } from './configuration'; -import copyFiles from './copyFiles'; -import deploy from './deploy'; -import { saveAppLog } from './logging'; - -export default async function (configuration, nextStep) { - const { id, organization, name, branch } = configuration.repository; - const { domain } = configuration.publish; - const { deployId } = configuration.general; - try { - await saveAppLog(`### Successfully queued.`, configuration); - await copyFiles(configuration); - await buildContainer(configuration); - await deploy(configuration, nextStep); - await Deployment.findOneAndUpdate( - { repoId: id, branch, deployId, organization, name, domain }, - { repoId: id, branch, deployId, organization, name, domain, progress: 'done' } - ); - - await updateServiceLabels(configuration); - await purgeImagesContainers(configuration); - } catch (error) { - await Deployment.findOneAndUpdate( - { repoId: id, branch, deployId, organization, name, domain }, - { repoId: id, branch, deployId, organization, name, domain, progress: 'failed' } - ); - } -} diff --git a/src/lib/api/common.ts b/src/lib/api/common.ts deleted file mode 100644 index 82575972d..000000000 --- a/src/lib/api/common.ts +++ /dev/null @@ -1,72 +0,0 @@ -import shell from 'shelljs'; -import User from '$models/User'; -import jsonwebtoken from 'jsonwebtoken'; -import { saveServerLog } from './applications/logging'; - -export function execShellAsync(cmd, opts = {}) { - try { - return new Promise(function (resolve, reject) { - shell.config.silent = true; - shell.exec(cmd, opts, async function (code, stdout, stderr) { - if (code !== 0) { - await saveServerLog({ message: JSON.stringify({ cmd, opts, code, stdout, stderr }) }); - return reject(new Error(stderr)); - } - return resolve(stdout); - }); - }); - } catch (error) { - return new Error('Oops'); - } -} -export function cleanupTmp(dir) { - if (dir !== '/') shell.rm('-fr', dir); -} - -export async function verifyUserId(token) { - const { JWT_SIGN_KEY } = process.env; - try { - const verify = jsonwebtoken.verify(token, JWT_SIGN_KEY); - const found = await User.findOne({ uid: verify.jti }); - if (found) { - return Promise.resolve(true); - } else { - return Promise.reject(false); - } - } catch (error) { - console.log(error); - return Promise.reject(false); - } -} - -export function delay(t) { - return new Promise(function (resolve) { - setTimeout(function () { - resolve('OK'); - }, t); - }); -} - - -export function compareObjects(a, b) { - if (a === b) return true; - - if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false; - - const keysA = Object.keys(a), - keysB = Object.keys(b); - - if (keysA.length != keysB.length) return false; - - for (const key of keysA) { - if (!keysB.includes(key)) return false; - - if (typeof a[key] === 'function' || typeof b[key] === 'function') { - if (a[key].toString() != b[key].toString()) return false; - } else { - if (!compareObjects(a[key], b[key])) return false; - } - } - - return true; -}; \ No newline at end of file diff --git a/src/lib/api/docker.ts b/src/lib/api/docker.ts deleted file mode 100644 index 0c623e590..000000000 --- a/src/lib/api/docker.ts +++ /dev/null @@ -1,27 +0,0 @@ -import Dockerode from 'dockerode'; -import { saveAppLog } from './applications/logging'; - -const { DOCKER_ENGINE, DOCKER_NETWORK } = process.env; -export const docker = { - engine: new Dockerode({ - socketPath: DOCKER_ENGINE - }), - network: DOCKER_NETWORK -}; -export async function streamEvents(stream, configuration) { - await new Promise((resolve, reject) => { - docker.engine.modem.followProgress(stream, onFinished, onProgress); - function onFinished(err, res) { - if (err) reject(err); - resolve(res); - } - function onProgress(event) { - if (event.error) { - saveAppLog(event.error, configuration, true); - reject(event.error); - } else if (event.stream) { - saveAppLog(event.stream, configuration); - } - } - }); -} diff --git a/src/lib/api/github.ts b/src/lib/api/github.ts deleted file mode 100644 index 7ffef6cc4..000000000 --- a/src/lib/api/github.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Request } from '@sveltejs/kit'; - -export async function githubAPI( - request: Request, - resource: string, - token?: string, - data?: Record -) { - const base = 'https://api.github.com'; - const res = await fetch(`${base}${resource}`, { - method: request.method, - headers: { - 'content-type': 'application/json', - accept: 'application/json', - authorization: token ? `token ${token}` : '' - }, - body: data && JSON.stringify(data) - }); - return { - status: res.status, - body: await res.json() - }; -} diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts new file mode 100644 index 000000000..a156864df --- /dev/null +++ b/src/lib/buildPacks/common.ts @@ -0,0 +1,191 @@ +import { base64Encode } from '$lib/crypto'; +import { getDomain, saveBuildLog, version } from '$lib/common'; +import * as db from '$lib/database'; +import { scanningTemplates } from '$lib/components/templates'; +import { promises as fs } from 'fs'; +import { staticDeployments } from '$lib/components/common'; + +export function makeLabelForStandaloneApplication({ + applicationId, + fqdn, + name, + type, + pullmergeRequestId = null, + buildPack, + repository, + branch, + projectId, + port, + commit, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory +}) { + if (pullmergeRequestId) { + const protocol = fqdn.startsWith('https://') ? 'https' : 'http'; + const domain = getDomain(fqdn); + fqdn = `${protocol}://${pullmergeRequestId}.${domain}`; + } + return [ + '--label coolify.managed=true', + `--label coolify.version=${version}`, + `--label coolify.type=standalone-application`, + `--label coolify.configuration=${base64Encode( + JSON.stringify({ + applicationId, + fqdn, + name, + type, + pullmergeRequestId, + buildPack, + repository, + branch, + projectId, + port, + commit, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory + }) + )}` + ]; +} +export async function makeLabelForStandaloneDatabase({ id, image, volume }) { + const database = await db.prisma.database.findFirst({ where: { id } }); + delete database.destinationDockerId; + delete database.createdAt; + delete database.updatedAt; + return [ + 'coolify.managed=true', + `coolify.version=${version}`, + `coolify.type=standalone-database`, + `coolify.configuration=${base64Encode( + JSON.stringify({ + version, + image, + volume, + ...database + }) + )}` + ]; +} + +export async function makeLabelForPlausibleAnalytics({ id, images, volume }) { + const service = await db.prisma.service.findFirst({ + where: { id }, + include: { plausibleAnalytics: true } + }); + delete service.destinationDockerId; + delete service.createdAt; + delete service.updatedAt; + return [ + 'coolify.managed=true', + `coolify.version=${version}`, + `coolify.type=service-plausibleanalytics`, + `coolify.configuration=${base64Encode( + JSON.stringify({ + version, + images, + volume, + ...service + }) + )}` + ]; +} + +export const setDefaultConfiguration = async (data) => { + let { buildPack, port, installCommand, startCommand, buildCommand, publishDirectory } = data; + const template = scanningTemplates[buildPack]; + if (!port) { + port = template?.port || 3000; + + if (buildPack === 'static') port = 80; + else if (buildPack === 'node') port = 3000; + else if (buildPack === 'php') port = 80; + } + if (!installCommand) installCommand = template?.installCommand || 'yarn install'; + if (!startCommand) startCommand = template?.startCommand || 'yarn start'; + if (!buildCommand) buildCommand = template?.buildCommand || null; + if (!publishDirectory) publishDirectory = template?.publishDirectory || null; + + return { + buildPack, + port, + installCommand, + startCommand, + buildCommand, + publishDirectory + }; +}; + +export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId) { + try { + // TODO: Write full .dockerignore for all deployments!! + if (buildPack === 'php') { + await fs.writeFile( + `${workdir}/.htaccess`, + ` + RewriteEngine On + RewriteBase / + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.+)$ index.php [QSA,L] + ` + ); + saveBuildLog({ line: 'Copied default configuration file for PHP.', buildId, applicationId }); + } else if (staticDeployments.includes(buildPack)) { + await fs.writeFile( + `${workdir}/nginx.conf`, + `user nginx; + worker_processes auto; + + error_log /var/log/nginx/error.log warn; + pid /var/run/nginx.pid; + + events { + worker_connections 1024; + } + + http { + include /etc/nginx/mime.types; + + access_log off; + sendfile on; + #tcp_nopush on; + keepalive_timeout 65; + + server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/index.html $uri/ /index.html =404; + } + + error_page 404 /50x.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + } + + } + ` + ); + saveBuildLog({ line: 'Copied default configuration file.', buildId, applicationId }); + } + } catch (error) { + console.log(error); + throw new Error(error); + } +} diff --git a/src/lib/buildPacks/docker.ts b/src/lib/buildPacks/docker.ts new file mode 100644 index 000000000..5bc6ce453 --- /dev/null +++ b/src/lib/buildPacks/docker.ts @@ -0,0 +1,36 @@ +import { buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +export default async function ({ + applicationId, + debug, + tag, + workdir, + docker, + buildId, + baseDirectory, + secrets +}) { + try { + let file = `${workdir}/Dockerfile`; + if (baseDirectory) { + file = `${workdir}/${baseDirectory}/Dockerfile`; + } + + const Dockerfile: Array = (await fs.readFile(`${file}`, 'utf8')) + .toString() + .trim() + .split('\n'); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (secret.isBuildSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + }); + } + await fs.writeFile(`${file}`, Dockerfile.join('\n')); + await buildImage({ applicationId, tag, workdir, docker, buildId, debug }); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/gatsby.ts b/src/lib/buildPacks/gatsby.ts new file mode 100644 index 000000000..fd1495995 --- /dev/null +++ b/src/lib/buildPacks/gatsby.ts @@ -0,0 +1,27 @@ +import { buildCacheImageWithNode, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, imageforBuild): Promise => { + const { applicationId, tag, workdir, publishDirectory } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${imageforBuild}`); + Dockerfile.push('WORKDIR /usr/share/nginx/html'); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`EXPOSE 80`); + Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'nginx:stable-alpine'; + const imageForBuild = 'node:lts'; + + await buildCacheImageWithNode(data, imageForBuild); + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/api/applications/packs/index.ts b/src/lib/buildPacks/index.ts similarity index 73% rename from src/lib/api/applications/packs/index.ts rename to src/lib/buildPacks/index.ts index 5f7c8c9c8..c8b133b5c 100644 --- a/src/lib/api/applications/packs/index.ts +++ b/src/lib/buildPacks/index.ts @@ -1,29 +1,27 @@ -import vuejs from './vuejs'; -import svelte from './svelte'; -import Static from './static'; -import rust from './rust'; -import react from './react'; -import php from './php'; -import nuxtjs from './nuxtjs'; -import nodejs from './nodejs'; -import nextjs from './nextjs'; -import nestjs from './nestjs'; -import gatsby from './gatsby'; +import node from './node'; +import staticApp from './static'; import docker from './docker'; -import python from './python'; +import gatsby from './gatsby'; +import svelte from './svelte'; +import react from './react'; +import nestjs from './nestjs'; +import nextjs from './nextjs'; +import nuxtjs from './nuxtjs'; +import vuejs from './vuejs'; +import php from './php'; +import rust from './rust'; export { - vuejs, - svelte, - Static as static, - rust, - react, - php, - nuxtjs, - nodejs, - nextjs, - nestjs, - gatsby, + node, + staticApp as static, docker, - python + gatsby, + svelte, + react, + nestjs, + nextjs, + nuxtjs, + vuejs, + php, + rust }; diff --git a/src/lib/buildPacks/nestjs.ts b/src/lib/buildPacks/nestjs.ts new file mode 100644 index 000000000..0a8bf9f38 --- /dev/null +++ b/src/lib/buildPacks/nestjs.ts @@ -0,0 +1,29 @@ +import { buildCacheImageWithNode, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push( + `COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./` + ); + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'node:lts'; + const imageForBuild = 'node:lts'; + + await buildCacheImageWithNode(data, imageForBuild); + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/nextjs.ts b/src/lib/buildPacks/nextjs.ts new file mode 100644 index 000000000..53a7f884e --- /dev/null +++ b/src/lib/buildPacks/nextjs.ts @@ -0,0 +1,45 @@ +import { buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = + data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/src/app'); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (secret.isBuildSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + }); + } + Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); + try { + await fs.stat(`${workdir}/yarn.lock`); + Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`); + } catch (error) {} + try { + await fs.stat(`${workdir}/pnpm-lock.yaml`); + Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`); + } catch (error) {} + Dockerfile.push(`RUN ${installCommand}`); + Dockerfile.push(`COPY ./${baseDirectory || ''} ./`); + if (buildCommand) { + Dockerfile.push(`RUN ${buildCommand}`); + } + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'node:lts'; + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/node.ts b/src/lib/buildPacks/node.ts new file mode 100644 index 000000000..53a7f884e --- /dev/null +++ b/src/lib/buildPacks/node.ts @@ -0,0 +1,45 @@ +import { buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = + data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/src/app'); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (secret.isBuildSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + }); + } + Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); + try { + await fs.stat(`${workdir}/yarn.lock`); + Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`); + } catch (error) {} + try { + await fs.stat(`${workdir}/pnpm-lock.yaml`); + Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`); + } catch (error) {} + Dockerfile.push(`RUN ${installCommand}`); + Dockerfile.push(`COPY ./${baseDirectory || ''} ./`); + if (buildCommand) { + Dockerfile.push(`RUN ${buildCommand}`); + } + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'node:lts'; + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/nuxtjs.ts b/src/lib/buildPacks/nuxtjs.ts new file mode 100644 index 000000000..53a7f884e --- /dev/null +++ b/src/lib/buildPacks/nuxtjs.ts @@ -0,0 +1,45 @@ +import { buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = + data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/src/app'); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (secret.isBuildSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + }); + } + Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); + try { + await fs.stat(`${workdir}/yarn.lock`); + Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`); + } catch (error) {} + try { + await fs.stat(`${workdir}/pnpm-lock.yaml`); + Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`); + } catch (error) {} + Dockerfile.push(`RUN ${installCommand}`); + Dockerfile.push(`COPY ./${baseDirectory || ''} ./`); + if (buildCommand) { + Dockerfile.push(`RUN ${buildCommand}`); + } + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'node:lts'; + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/php.ts b/src/lib/buildPacks/php.ts new file mode 100644 index 000000000..cf8e49f98 --- /dev/null +++ b/src/lib/buildPacks/php.ts @@ -0,0 +1,25 @@ +import { buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { workdir, baseDirectory } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('RUN a2enmod rewrite'); + Dockerfile.push('WORKDIR /var/www/html'); + Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`); + Dockerfile.push(`EXPOSE 80`); + Dockerfile.push('CMD ["apache2-foreground"]'); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'php:apache'; + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/react.ts b/src/lib/buildPacks/react.ts new file mode 100644 index 000000000..859544281 --- /dev/null +++ b/src/lib/buildPacks/react.ts @@ -0,0 +1,26 @@ +import { buildCacheImageWithNode, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, workdir, publishDirectory } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/share/nginx/html'); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`EXPOSE 80`); + Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'nginx:stable-alpine'; + const imageForBuild = 'node:lts'; + await buildCacheImageWithNode(data, imageForBuild); + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/rust.ts b/src/lib/buildPacks/rust.ts new file mode 100644 index 000000000..0af47c44c --- /dev/null +++ b/src/lib/buildPacks/rust.ts @@ -0,0 +1,43 @@ +import { asyncExecShell } from '$lib/common'; +import { buildCacheImageWithCargo, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; +import TOML from '@iarna/toml'; + +const createDockerfile = async (data, image, name): Promise => { + const { workdir, port, applicationId, tag } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target target`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`); + Dockerfile.push(`COPY . .`); + Dockerfile.push(`RUN cargo build --release --bin ${name}`); + Dockerfile.push('FROM debian:buster-slim'); + Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push( + `RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*` + ); + Dockerfile.push(`RUN update-ca-certificates`); + Dockerfile.push( + `COPY --from=${applicationId}:${tag}-cache /usr/src/app/target/release/${name} ${name}` + ); + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ["/usr/src/app/${name}"]`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { workdir } = data; + const image = 'rust:latest'; + const imageForBuild = 'rust:latest'; + const { stdout: cargoToml } = await asyncExecShell(`cat ${workdir}/Cargo.toml`); + const parsedToml: any = TOML.parse(cargoToml); + const name = parsedToml.package.name; + await buildCacheImageWithCargo(data, imageForBuild); + await createDockerfile(data, image, name); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/static.ts b/src/lib/buildPacks/static.ts new file mode 100644 index 000000000..0db1a0c83 --- /dev/null +++ b/src/lib/buildPacks/static.ts @@ -0,0 +1,40 @@ +import { buildCacheImageWithNode, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, workdir, buildCommand, baseDirectory, publishDirectory, secrets } = + data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/share/nginx/html'); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (secret.isBuildSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + }); + } + if (buildCommand) { + Dockerfile.push( + `COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./` + ); + } else { + Dockerfile.push(`COPY ./${baseDirectory || ''} ./`); + } + Dockerfile.push(`EXPOSE 80`); + Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'nginx:stable-alpine'; + const imageForBuild = 'node:lts'; + if (data.buildCommand) await buildCacheImageWithNode(data, imageForBuild); + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/svelte.ts b/src/lib/buildPacks/svelte.ts new file mode 100644 index 000000000..9a730eb63 --- /dev/null +++ b/src/lib/buildPacks/svelte.ts @@ -0,0 +1,27 @@ +import { buildCacheImageWithNode, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, workdir, publishDirectory } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/share/nginx/html'); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`EXPOSE 80`); + Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'nginx:stable-alpine'; + const imageForBuild = 'node:lts'; + + await buildCacheImageWithNode(data, imageForBuild); + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/buildPacks/vuejs.ts b/src/lib/buildPacks/vuejs.ts new file mode 100644 index 000000000..859544281 --- /dev/null +++ b/src/lib/buildPacks/vuejs.ts @@ -0,0 +1,26 @@ +import { buildCacheImageWithNode, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, workdir, publishDirectory } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /usr/share/nginx/html'); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`EXPOSE 80`); + Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'nginx:stable-alpine'; + const imageForBuild = 'node:lts'; + await buildCacheImageWithNode(data, imageForBuild); + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/common.ts b/src/lib/common.ts index f9d89679f..70fd7eeba 100644 --- a/src/lib/common.ts +++ b/src/lib/common.ts @@ -1,9 +1,150 @@ -export function dashify(str: string, options?: any) { - if (typeof str !== 'string') return str; - return str - .trim() - .replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-')) - .replace(/^-+|-+$/g, '') - .replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m)) - .toLowerCase(); -} \ No newline at end of file +import child from 'child_process'; +import util from 'util'; +import { dev } from '$app/env'; +import * as Sentry from '@sentry/node'; +import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator'; + +import * as db from '$lib/database'; +import { buildLogQueue } from './queues'; + +import { version as currentVersion } from '../../package.json'; +import { dockerInstance } from './docker'; +import dayjs from 'dayjs'; +import Cookie from 'cookie'; + +try { + if (!dev) { + Sentry.init({ + dsn: process.env['COOLIFY_SENTRY_DSN'], + tracesSampleRate: 0, + environment: 'production' + }); + } +} catch (err) { + console.log('Could not initialize Sentry, no worries.'); +} + +const customConfig: Config = { + dictionaries: [adjectives, colors, animals], + style: 'capital', + separator: ' ', + length: 3 +}; + +export const version = currentVersion; +export const asyncExecShell = util.promisify(child.exec); +export const asyncSleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay)); +export const sentry = Sentry; + +export const uniqueName = () => uniqueNamesGenerator(customConfig); + +export const saveBuildLog = async ({ line, buildId, applicationId }) => { + const addTimestamp = `${generateTimestamp()} ${line}`; + return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId }); +}; + +export const isTeamIdTokenAvailable = (request) => { + const cookie = request.headers.cookie + ?.split(';') + .map((s) => s.trim()) + .find((s) => s.startsWith('teamId=')) + ?.split('=')[1]; + if (!cookie) { + return getTeam(request); + } else { + return cookie; + } +}; + +export const getTeam = (event) => { + const cookies: Cookies = Cookie.parse(event.request.headers.get('cookie')); + if (cookies.teamId) { + return cookies.teamId; + } else if (event.locals.session.data.teamId) { + return event.locals.session.data.teamId; + } + return null; +}; + +export const getUserDetails = async (event, isAdminRequired = true) => { + // try { + const teamId = getTeam(event); + const userId = event.locals.session.data.uid || null; + const { permission = 'read' } = await db.prisma.permission.findFirst({ + where: { teamId, userId }, + select: { permission: true }, + rejectOnNotFound: true + }); + const payload = { + teamId, + userId, + permission, + status: 200, + body: { + message: 'OK' + } + }; + if (isAdminRequired && permission !== 'admin' && permission !== 'owner') { + payload.status = 401; + payload.body.message = + 'You do not have permission to do this. \nAsk an admin to modify your permissions.'; + } + + return payload; + // } catch (err) { + // console.log(err); + // return { + // teamId: null, + // userId: null, + // permission: 'read', + // status: 401, + // body: { + // message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.' + // } + // }; + // } +}; + +export function getEngine(engine) { + return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : `tcp://${engine}:2375`; +} + +export async function removeContainer(id, engine) { + const host = getEngine(engine); + try { + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}` + ); + if (JSON.parse(stdout).Running) { + await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${id}`); + await asyncExecShell(`DOCKER_HOST=${host} docker rm ${id}`); + } + } catch (error) { + console.log(error); + throw error; + } +} + +export const removeDestinationDocker = async ({ id, engine }) => { + return await removeContainer(id, engine); +}; + +export const createDirectories = async ({ repository, buildId }) => { + const repodir = `/tmp/build-sources/${repository}/`; + const workdir = `/tmp/build-sources/${repository}/${buildId}`; + + await asyncExecShell(`mkdir -p ${workdir}`); + + return { + workdir, + repodir + }; +}; + +export function generateTimestamp() { + return `${dayjs().format('HH:mm:ss.SSS')} `; +} + +export function getDomain(domain) { + return domain?.replace('https://', '').replace('http://', ''); +} diff --git a/src/lib/components/CopyPasswordField.svelte b/src/lib/components/CopyPasswordField.svelte new file mode 100644 index 000000000..4237f6e8e --- /dev/null +++ b/src/lib/components/CopyPasswordField.svelte @@ -0,0 +1,148 @@ + + + showActions(true)} + on:mouseleave={() => showActions(false)} +> + {#if !isPasswordField || showPassword} + {#if textarea} + + {:else} + + {/if} + {:else} + + {/if} + + {#if actionsShow} +
+
+ {#if isPasswordField} +
(showPassword = !showPassword)}> + {#if showPassword} + + + + {:else} + + + + + {/if} +
+ {/if} + {#if value && isHttps} +
+ + + + + +
+ {/if} +
+
+ {/if} +
diff --git a/src/lib/components/DeleteIcon.svelte b/src/lib/components/DeleteIcon.svelte new file mode 100644 index 000000000..f04b3952c --- /dev/null +++ b/src/lib/components/DeleteIcon.svelte @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/src/lib/components/Explainer.svelte b/src/lib/components/Explainer.svelte new file mode 100644 index 000000000..4eaf3a398 --- /dev/null +++ b/src/lib/components/Explainer.svelte @@ -0,0 +1,6 @@ + + +
{@html text}
diff --git a/src/lib/components/Loading.svelte b/src/lib/components/Loading.svelte new file mode 100644 index 000000000..de113e261 --- /dev/null +++ b/src/lib/components/Loading.svelte @@ -0,0 +1,67 @@ + + +{#if fullscreen} +
+ +
+{:else} +
+ +
+{/if} + + diff --git a/src/lib/components/Setting.svelte b/src/lib/components/Setting.svelte new file mode 100644 index 000000000..4a80e56ae --- /dev/null +++ b/src/lib/components/Setting.svelte @@ -0,0 +1,68 @@ + + +
  • +
    +

    {title}

    + +
    + +
    + Use setting + + + + +
    + +
  • diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts new file mode 100644 index 000000000..3ba845fb0 --- /dev/null +++ b/src/lib/components/common.ts @@ -0,0 +1,33 @@ +export const asyncSleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay)); +export const dateOptions: DateTimeFormatOptions = { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: false +}; + +export async function getGithubToken({ apiUrl, application, githubToken }): Promise { + const response = await fetch( + `${apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${githubToken}` + } + } + ); + if (!response.ok) { + throw new Error('Git Source not configured.'); + } + const data = await response.json(); + return data.token; +} +export const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby', 'php']; +export const notNodeDeployments = ['php', 'docker', 'rust']; + +export function getDomain(domain) { + return domain?.replace('https://', '').replace('http://', ''); +} diff --git a/src/lib/components/svg/applications/Docker.svelte b/src/lib/components/svg/applications/Docker.svelte new file mode 100644 index 000000000..684bc06d3 --- /dev/null +++ b/src/lib/components/svg/applications/Docker.svelte @@ -0,0 +1,73 @@ + + + diff --git a/src/lib/components/svg/applications/Gatsby.svelte b/src/lib/components/svg/applications/Gatsby.svelte new file mode 100644 index 000000000..af1d7b3b6 --- /dev/null +++ b/src/lib/components/svg/applications/Gatsby.svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/lib/components/svg/applications/Nestjs.svelte b/src/lib/components/svg/applications/Nestjs.svelte new file mode 100644 index 000000000..81755e25f --- /dev/null +++ b/src/lib/components/svg/applications/Nestjs.svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/lib/components/svg/applications/Nextjs.svelte b/src/lib/components/svg/applications/Nextjs.svelte new file mode 100644 index 000000000..2aec938f1 --- /dev/null +++ b/src/lib/components/svg/applications/Nextjs.svelte @@ -0,0 +1,5 @@ + + + diff --git a/src/lib/components/svg/applications/Nodejs.svelte b/src/lib/components/svg/applications/Nodejs.svelte new file mode 100644 index 000000000..3e7e84d28 --- /dev/null +++ b/src/lib/components/svg/applications/Nodejs.svelte @@ -0,0 +1,14 @@ + diff --git a/src/lib/components/svg/applications/Nuxtjs.svelte b/src/lib/components/svg/applications/Nuxtjs.svelte new file mode 100644 index 000000000..738cd715d --- /dev/null +++ b/src/lib/components/svg/applications/Nuxtjs.svelte @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/lib/components/svg/applications/PHP.svelte b/src/lib/components/svg/applications/PHP.svelte new file mode 100644 index 000000000..554680ad6 --- /dev/null +++ b/src/lib/components/svg/applications/PHP.svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/lib/components/svg/applications/Python.svelte b/src/lib/components/svg/applications/Python.svelte new file mode 100644 index 000000000..791811fce --- /dev/null +++ b/src/lib/components/svg/applications/Python.svelte @@ -0,0 +1,50 @@ + + + diff --git a/src/lib/components/svg/applications/React.svelte b/src/lib/components/svg/applications/React.svelte new file mode 100644 index 000000000..ed05e6550 --- /dev/null +++ b/src/lib/components/svg/applications/React.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/svg/applications/Rust.svelte b/src/lib/components/svg/applications/Rust.svelte new file mode 100644 index 000000000..19ce81f29 --- /dev/null +++ b/src/lib/components/svg/applications/Rust.svelte @@ -0,0 +1,8 @@ + + + diff --git a/src/lib/components/svg/applications/Static.svelte b/src/lib/components/svg/applications/Static.svelte new file mode 100644 index 000000000..d3732b34b --- /dev/null +++ b/src/lib/components/svg/applications/Static.svelte @@ -0,0 +1,28 @@ + diff --git a/src/lib/components/svg/applications/Svelte.svelte b/src/lib/components/svg/applications/Svelte.svelte new file mode 100644 index 000000000..ad3d1d82d --- /dev/null +++ b/src/lib/components/svg/applications/Svelte.svelte @@ -0,0 +1,21 @@ + + + + diff --git a/src/lib/components/svg/applications/Vuejs.svelte b/src/lib/components/svg/applications/Vuejs.svelte new file mode 100644 index 000000000..1017f24a3 --- /dev/null +++ b/src/lib/components/svg/applications/Vuejs.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/components/svg/databases/Clickhouse.svelte b/src/lib/components/svg/databases/Clickhouse.svelte new file mode 100644 index 000000000..dd237a48c --- /dev/null +++ b/src/lib/components/svg/databases/Clickhouse.svelte @@ -0,0 +1,13 @@ + + + diff --git a/src/lib/components/svg/databases/CouchDB.svelte b/src/lib/components/svg/databases/CouchDB.svelte new file mode 100644 index 000000000..411c4928d --- /dev/null +++ b/src/lib/components/svg/databases/CouchDB.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/lib/components/svg/databases/MongoDB.svelte b/src/lib/components/svg/databases/MongoDB.svelte new file mode 100644 index 000000000..b99ffe4da --- /dev/null +++ b/src/lib/components/svg/databases/MongoDB.svelte @@ -0,0 +1,33 @@ + + + + diff --git a/src/components/Database/SVGs/Mysql.svelte b/src/lib/components/svg/databases/MySQL.svelte similarity index 85% rename from src/components/Database/SVGs/Mysql.svelte rename to src/lib/components/svg/databases/MySQL.svelte index 341222f68..095093214 100644 --- a/src/components/Database/SVGs/Mysql.svelte +++ b/src/lib/components/svg/databases/MySQL.svelte @@ -1,16 +1,17 @@ - \ No newline at end of file + class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-10 w-10' : 'mx-auto w-8 h-8'} + xmlns="http://www.w3.org/2000/svg" + width="64" + height="64" + viewBox="0 0 25.6 25.6" + > diff --git a/src/lib/components/svg/databases/PostgreSQL.svelte b/src/lib/components/svg/databases/PostgreSQL.svelte new file mode 100644 index 000000000..3021508a6 --- /dev/null +++ b/src/lib/components/svg/databases/PostgreSQL.svelte @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Database/SVGs/Redis.svelte b/src/lib/components/svg/databases/Redis.svelte similarity index 93% rename from src/components/Database/SVGs/Redis.svelte rename to src/lib/components/svg/databases/Redis.svelte index d35b5f973..2571e601d 100644 --- a/src/components/Database/SVGs/Redis.svelte +++ b/src/lib/components/svg/databases/Redis.svelte @@ -1,10 +1,9 @@ - + export let isAbsolute = false; + + +minio logo diff --git a/src/lib/components/svg/services/NocoDB.svelte b/src/lib/components/svg/services/NocoDB.svelte new file mode 100644 index 000000000..3e61a2b8f --- /dev/null +++ b/src/lib/components/svg/services/NocoDB.svelte @@ -0,0 +1,9 @@ + + +nocodb logo diff --git a/src/lib/components/svg/services/PlausibleAnalytics.svelte b/src/lib/components/svg/services/PlausibleAnalytics.svelte new file mode 100644 index 000000000..920f2ecf1 --- /dev/null +++ b/src/lib/components/svg/services/PlausibleAnalytics.svelte @@ -0,0 +1,9 @@ + + +plausible logo diff --git a/src/lib/components/svg/services/VSCodeServer.svelte b/src/lib/components/svg/services/VSCodeServer.svelte new file mode 100644 index 000000000..3b100e470 --- /dev/null +++ b/src/lib/components/svg/services/VSCodeServer.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/src/lib/components/svg/services/Wordpress.svelte b/src/lib/components/svg/services/Wordpress.svelte new file mode 100644 index 000000000..aaccb931a --- /dev/null +++ b/src/lib/components/svg/services/Wordpress.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/src/lib/components/templates.ts b/src/lib/components/templates.ts new file mode 100644 index 000000000..fb52efddb --- /dev/null +++ b/src/lib/components/templates.ts @@ -0,0 +1,155 @@ +const defaultBuildAndDeploy = { + installCommand: 'yarn install', + buildCommand: 'yarn build', + startCommand: 'yarn start' +}; +export const buildPacks = [ + { + name: 'node', + installCommand: null, + buildCommand: null, + startCommand: null, + publishDirectory: null, + port: null, + fancyName: 'Node.js', + hoverColor: 'hover:bg-green-700', + color: 'bg-green-700' + }, + + { + name: 'static', + ...defaultBuildAndDeploy, + publishDirectory: 'dist', + port: 80, + fancyName: 'Static', + hoverColor: 'hover:bg-orange-700', + color: 'bg-orange-700' + }, + { + name: 'docker', + installCommand: null, + buildCommand: null, + startCommand: null, + publishDirectory: null, + port: null, + fancyName: 'Docker', + hoverColor: 'hover:bg-sky-700', + color: 'bg-sky-700' + }, + { + name: 'svelte', + ...defaultBuildAndDeploy, + publishDirectory: 'public', + port: 80, + fancyName: 'Svelte', + hoverColor: 'hover:bg-orange-700', + color: 'bg-orange-700' + }, + { + name: 'nestjs', + ...defaultBuildAndDeploy, + startCommand: 'yarn start:prod', + port: 3000, + fancyName: 'NestJS', + hoverColor: 'hover:bg-red-700', + color: 'bg-red-700' + }, + { + name: 'react', + ...defaultBuildAndDeploy, + publishDirectory: 'build', + port: 80, + fancyName: 'React', + hoverColor: 'hover:bg-blue-700', + color: 'bg-blue-700' + }, + { + name: 'nextjs', + ...defaultBuildAndDeploy, + port: 3000, + fancyName: 'NextJS', + hoverColor: 'hover:bg-blue-700', + color: 'bg-blue-700' + }, + { + name: 'gatsby', + ...defaultBuildAndDeploy, + publishDirectory: 'public', + port: 80, + fancyName: 'Gatsby', + hoverColor: 'hover:bg-blue-700', + color: 'bg-blue-700' + }, + { + name: 'vuejs', + ...defaultBuildAndDeploy, + publishDirectory: 'dist', + port: 80, + fancyName: 'VueJS', + hoverColor: 'hover:bg-green-700', + color: 'bg-green-700' + }, + { + name: 'nuxtjs', + ...defaultBuildAndDeploy, + port: 3000, + fancyName: 'NuxtJS', + hoverColor: 'hover:bg-green-700', + color: 'bg-green-700' + }, + { + name: 'preact', + ...defaultBuildAndDeploy, + publishDirectory: 'build', + port: 80, + fancyName: 'Preact', + hoverColor: 'hover:bg-blue-700', + color: 'bg-blue-700' + }, + { + name: 'php', + port: 80, + fancyName: 'PHP', + hoverColor: 'hover:bg-indigo-700', + color: 'bg-indigo-700' + }, + { + name: 'rust', + port: 3000, + fancyName: 'Rust', + hoverColor: 'hover:bg-pink-700', + color: 'bg-pink-700' + } +]; +export const scanningTemplates = { + svelte: { + buildPack: 'svelte' + }, + '@nestjs/core': { + buildPack: 'nestjs' + }, + next: { + buildPack: 'nextjs' + }, + nuxt: { + buildPack: 'nuxtjs' + }, + 'react-scripts': { + buildPack: 'react' + }, + 'parcel-bundler': { + buildPack: 'static' + }, + '@vue/cli-service': { + buildPack: 'vuejs' + }, + vuejs: { + buildPack: 'vuejs' + }, + gatsby: { + buildPack: 'gatsby' + }, + 'preact-cli': { + buildPack: 'react' + } +}; diff --git a/src/lib/consts.ts b/src/lib/consts.ts deleted file mode 100644 index 37e60e886..000000000 --- a/src/lib/consts.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const publicPages = [ - '/', - '/api/v1/login/github/app', - '/api/v1/webhooks/deploy', - '/success', - '/api/v1/login/email' -]; -export const VITE_GITHUB_APP_NAME = import.meta.env.VITE_GITHUB_APP_NAME; diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts new file mode 100644 index 000000000..861a25510 --- /dev/null +++ b/src/lib/crypto.ts @@ -0,0 +1,36 @@ +import crypto from 'crypto'; +const algorithm = 'aes-256-ctr'; + +export const base64Encode = (text: string) => { + return Buffer.from(text).toString('base64'); +}; +export const base64Decode = (text: string) => { + return Buffer.from(text, 'base64').toString('ascii'); +}; +export const encrypt = (text: string) => { + if (text) { + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv); + const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); + return JSON.stringify({ + iv: iv.toString('hex'), + content: encrypted.toString('hex') + }); + } +}; + +export const decrypt = (hashString: string) => { + if (hashString) { + const hash: Hash = JSON.parse(hashString); + const decipher = crypto.createDecipheriv( + algorithm, + process.env['COOLIFY_SECRET_KEY'], + Buffer.from(hash.iv, 'hex') + ); + const decrpyted = Buffer.concat([ + decipher.update(Buffer.from(hash.content, 'hex')), + decipher.final() + ]); + return decrpyted.toString(); + } +}; diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts new file mode 100644 index 000000000..037334873 --- /dev/null +++ b/src/lib/database/applications.ts @@ -0,0 +1,230 @@ +import { decrypt, encrypt } from '$lib/crypto'; +import { removeProxyConfiguration } from '$lib/haproxy'; +import { asyncExecShell, getEngine, removeContainer } from '$lib/common'; + +import { getDomain, removeDestinationDocker } from '$lib/common'; +import { prisma } from './common'; + +export async function listApplications(teamId) { + return await prisma.application.findMany({ where: { teams: { some: { id: teamId } } } }); +} + +export async function newApplication({ name, teamId }) { + return await prisma.application.create({ + data: { + name, + teams: { connect: { id: teamId } }, + settings: { create: { debug: false, previews: false } } + } + }); +} + +export async function importApplication({ + name, + teamId, + fqdn, + port, + buildCommand, + startCommand, + installCommand +}) { + return await prisma.application.create({ + data: { + name, + fqdn, + port, + buildCommand, + startCommand, + installCommand, + teams: { connect: { id: teamId } } + } + }); +} + +export async function removeApplication({ id, teamId }) { + const { fqdn, destinationDockerId, destinationDocker } = await prisma.application.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + const domain = getDomain(fqdn); + if (destinationDockerId) { + const host = getEngine(destinationDocker.engine); + const { stdout: containers } = await asyncExecShell( + `DOCKER_HOST=${host} docker ps -a --filter network=${destinationDocker.network} --filter name=${id} --format '{{json .}}'` + ); + if (containers) { + const containersArray = containers.trim().split('\n'); + for (const container of containersArray) { + const containerObj = JSON.parse(container); + const id = containerObj.ID; + const preview = containerObj.Image.split('-')[1]; + await removeDestinationDocker({ id, engine: destinationDocker.engine }); + if (preview) { + await removeProxyConfiguration({ domain: `${preview}.${domain}` }); + } else { + await removeProxyConfiguration({ domain }); + } + } + } + } + + await prisma.applicationSettings.deleteMany({ where: { application: { id } } }); + await prisma.buildLog.deleteMany({ where: { applicationId: id } }); + await prisma.secret.deleteMany({ where: { applicationId: id } }); + await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } }); +} + +export async function getApplicationWebhook({ projectId, branch }) { + try { + let body = await prisma.application.findFirst({ + where: { projectId, branch }, + include: { + destinationDocker: true, + settings: true, + gitSource: { include: { githubApp: true, gitlabApp: true } }, + secrets: true + } + }); + + if (body.gitSource?.githubApp?.clientSecret) { + body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret); + } + if (body.gitSource?.githubApp?.webhookSecret) { + body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret); + } + if (body.gitSource?.githubApp?.privateKey) { + body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey); + } + if (body?.gitSource?.gitlabApp?.appSecret) { + body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret); + } + if (body?.gitSource?.gitlabApp?.webhookToken) { + body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken); + } + if (body?.secrets.length > 0) { + body.secrets = body.secrets.map((s) => { + s.value = decrypt(s.value); + return s; + }); + } + + return { ...body }; + } catch (e) { + throw { status: 404, body: { message: e.message } }; + } +} +export async function getApplication({ id, teamId }) { + let body = await prisma.application.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include: { + destinationDocker: true, + settings: true, + gitSource: { include: { githubApp: true, gitlabApp: true } }, + secrets: true + } + }); + + if (body.gitSource?.githubApp?.clientSecret) { + body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret); + } + if (body.gitSource?.githubApp?.webhookSecret) { + body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret); + } + if (body.gitSource?.githubApp?.privateKey) { + body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey); + } + if (body?.gitSource?.gitlabApp?.appSecret) { + body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret); + } + if (body?.secrets.length > 0) { + body.secrets = body.secrets.map((s) => { + s.value = decrypt(s.value); + return s; + }); + } + + return { ...body }; +} + +export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) { + if (webhookToken) { + const encryptedWebhookToken = encrypt(webhookToken); + return await prisma.application.update({ + where: { id }, + data: { + repository, + branch, + projectId, + gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } } + } + }); + } else { + return await prisma.application.update({ + where: { id }, + data: { repository, branch, projectId } + }); + } +} + +export async function configureBuildPack({ id, buildPack }) { + return await prisma.application.update({ where: { id }, data: { buildPack } }); +} + +export async function configureApplication({ + id, + buildPack, + name, + fqdn, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory +}) { + return await prisma.application.update({ + where: { id }, + data: { + buildPack, + fqdn, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory, + name + } + }); +} + +export async function setApplicationSettings({ id, debug, previews }) { + return await prisma.application.update({ + where: { id }, + data: { settings: { update: { debug, previews } } }, + include: { destinationDocker: true } + }); +} + +export async function createBuild({ + id, + applicationId, + destinationDockerId, + gitSourceId, + githubAppId, + gitlabAppId, + type +}) { + return await prisma.build.create({ + data: { + id, + applicationId, + destinationDockerId, + gitSourceId, + githubAppId, + gitlabAppId, + status: 'running', + type + } + }); +} diff --git a/src/lib/database/checks.ts b/src/lib/database/checks.ts new file mode 100644 index 000000000..884c6d2bd --- /dev/null +++ b/src/lib/database/checks.ts @@ -0,0 +1,38 @@ +import { getDomain } from '$lib/common'; +import { prisma, PrismaErrorHandler } from './common'; + +export async function isBranchAlreadyUsed({ repository, branch, id }) { + const application = await prisma.application.findUnique({ + where: { id }, + include: { gitSource: true } + }); + return await prisma.application.findFirst({ + where: { branch, repository, gitSource: { type: application.gitSource.type }, id: { not: id } } + }); +} + +export async function isDockerNetworkExists({ network }) { + return await prisma.destinationDocker.findFirst({ where: { network } }); +} + +export async function isSecretExists({ id, name }) { + return await prisma.secret.findFirst({ where: { name, applicationId: id } }); +} + +export async function isDomainConfigured({ id, fqdn }) { + const domain = getDomain(fqdn); + const foundApp = await prisma.application.findFirst({ + where: { fqdn: { endsWith: domain }, id: { not: id } }, + select: { fqdn: true } + }); + const foundService = await prisma.service.findFirst({ + where: { fqdn: { endsWith: domain }, id: { not: id } }, + select: { fqdn: true } + }); + const coolifyFqdn = await prisma.setting.findFirst({ + where: { fqdn: { endsWith: domain }, id: { not: id } }, + select: { fqdn: true } + }); + if (foundApp || foundService || coolifyFqdn) return true; + return false; +} diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts new file mode 100644 index 000000000..816082d85 --- /dev/null +++ b/src/lib/database/common.ts @@ -0,0 +1,232 @@ +import { dev } from '$app/env'; +import { sentry } from '$lib/common'; +import * as Prisma from '@prisma/client'; +import { default as ProdPrisma } from '@prisma/client'; +import generator from 'generate-password'; +import forge from 'node-forge'; + +export function generatePassword(length = 24) { + return generator.generate({ + length, + numbers: true, + strict: true + }); +} + +let { PrismaClient } = Prisma; +let P = Prisma.Prisma; +if (!dev) { + PrismaClient = ProdPrisma.PrismaClient; + P = ProdPrisma.Prisma; +} +let prismaOptions = { + rejectOnNotFound: false +}; +if (dev) { + prismaOptions = { + errorFormat: 'pretty', + rejectOnNotFound: false, + log: [ + { + emit: 'event', + level: 'query' + } + ] + }; +} +export const prisma = new PrismaClient(prismaOptions); + +export function PrismaErrorHandler(e) { + sentry.captureException(e); + const payload = { + status: e.status || 500, + body: { + message: 'Ooops, something is not okay, are you okay?', + error: e.error || e.message + } + }; + if (e.name === 'NotFoundError') { + payload.status = 404; + } + if (e instanceof P.PrismaClientKnownRequestError) { + if (e.code === 'P2002') { + payload.body.message = 'Already exists. Choose another name.'; + } + } + // console.error(e) + return payload; +} +export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> { + return await new Promise(async (resolve, reject) => { + forge.pki.rsa.generateKeyPair({ bits: 4096, workers: -1 }, function (err, keys) { + if (keys) { + resolve({ + publicKey: forge.ssh.publicKeyToOpenSSH(keys.publicKey), + privateKey: forge.ssh.privateKeyToOpenSSH(keys.privateKey) + }); + } else { + reject(keys); + } + }); + }); +} + +export const supportedDatabaseTypesAndVersions = [ + { + name: 'mongodb', + fancyName: 'MongoDB', + baseImage: 'bitnami/mongodb', + versions: ['5.0.5', '4.4.11', '4.2.18', '4.0.27'] + }, + { name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0.27', '5.7.36'] }, + { + name: 'postgresql', + fancyName: 'PostgreSQL', + baseImage: 'bitnami/postgresql', + versions: ['14.1.0', '13.5.0', '12.9.0', '11.14.0', '10.19.0', '9.6.24'] + }, + { + name: 'redis', + fancyName: 'Redis', + baseImage: 'bitnami/redis', + versions: ['6.2.6', '6.0.16', '5.0.14'] + }, + { name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] } +]; +export const supportedServiceTypesAndVersions = [ + { + name: 'plausibleanalytics', + fancyName: 'Plausible Analytics', + baseImage: 'plausible/analytics', + versions: ['latest'] + }, + { name: 'nocodb', fancyName: 'NocoDB', baseImage: 'nocodb/nocodb', versions: ['latest'] }, + { name: 'minio', fancyName: 'MinIO', baseImage: 'minio/minio', versions: ['latest'] }, + { + name: 'vscodeserver', + fancyName: 'VSCode Server', + baseImage: 'codercom/code-server', + versions: ['latest'] + }, + { + name: 'wordpress', + fancyName: 'Wordpress', + baseImage: 'wordpress', + versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'] + } +]; + +export function getVersions(type) { + const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type); + if (found) { + return found.versions; + } + return []; +} +export function getDatabaseImage(type) { + const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type); + if (found) { + return found.baseImage; + } + return ''; +} +export function getServiceImage(type) { + const found = supportedServiceTypesAndVersions.find((t) => t.name === type); + if (found) { + return found.baseImage; + } + return ''; +} +export function generateDatabaseConfiguration(database) { + const { + id, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + defaultDatabase, + version, + type, + settings: { appendOnly } + } = database; + const baseImage = getDatabaseImage(type); + if (type === 'mysql') { + return { + // url: `mysql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 3306}/${defaultDatabase}`, + privatePort: 3306, + environmentVariables: { + MYSQL_USER: dbUser, + MYSQL_PASSWORD: dbUserPassword, + MYSQL_ROOT_PASSWORD: rootUserPassword, + MYSQL_ROOT_USER: rootUser, + MYSQL_DATABASE: defaultDatabase + }, + image: `${baseImage}:${version}`, + volume: `${id}-${type}-data:/bitnami/mysql/data`, + ulimits: {} + }; + } else if (type === 'mongodb') { + return { + // url: `mongodb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 27017}/${defaultDatabase}`, + privatePort: 27017, + environmentVariables: { + MONGODB_ROOT_USER: rootUser, + MONGODB_ROOT_PASSWORD: rootUserPassword + }, + image: `${baseImage}:${version}`, + volume: `${id}-${type}-data:/bitnami/mongodb`, + ulimits: {} + }; + } else if (type === 'postgresql') { + return { + // url: `psql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5432}/${defaultDatabase}`, + privatePort: 5432, + environmentVariables: { + POSTGRESQL_PASSWORD: dbUserPassword, + POSTGRESQL_USERNAME: dbUser, + POSTGRESQL_DATABASE: defaultDatabase + }, + image: `${baseImage}:${version}`, + volume: `${id}-${type}-data:/bitnami/postgresql`, + ulimits: {} + }; + } else if (type === 'redis') { + return { + // url: `redis://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 6379}/${defaultDatabase}`, + privatePort: 6379, + environmentVariables: { + REDIS_PASSWORD: dbUserPassword, + REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no' + }, + image: `${baseImage}:${version}`, + volume: `${id}-${type}-data:/bitnami/redis/data`, + ulimits: {} + }; + } else if (type === 'couchdb') { + return { + // url: `couchdb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5984}/${defaultDatabase}`, + privatePort: 5984, + environmentVariables: { + COUCHDB_PASSWORD: dbUserPassword, + COUCHDB_USER: dbUser + }, + image: `${baseImage}:${version}`, + volume: `${id}-${type}-data:/bitnami/couchdb`, + ulimits: {} + }; + } + // } else if (type === 'clickhouse') { + // return { + // url: `clickhouse://${dbUser}:${dbUserPassword}@${id}:${port}/${defaultDatabase}`, + // privatePort: 9000, + // image: `bitnami/clickhouse-server:${version}`, + // volume: `${id}-${type}-data:/var/lib/clickhouse`, + // ulimits: { + // nofile: { + // soft: 262144, + // hard: 262144 + // } + // } + // } + // } +} diff --git a/src/lib/database/databases.ts b/src/lib/database/databases.ts new file mode 100644 index 000000000..00afa4827 --- /dev/null +++ b/src/lib/database/databases.ts @@ -0,0 +1,148 @@ +import { decrypt, encrypt } from '$lib/crypto'; +import { dockerInstance } from '$lib/docker'; +import cuid from 'cuid'; +import { generatePassword } from '.'; +import { prisma, PrismaErrorHandler } from './common'; +import getPort from 'get-port'; +import { asyncExecShell, getEngine, removeContainer } from '$lib/common'; + +export async function listDatabases(teamId) { + return await prisma.database.findMany({ where: { teams: { some: { id: teamId } } } }); +} +export async function newDatabase({ name, teamId }) { + const dbUser = cuid(); + const dbUserPassword = encrypt(generatePassword()); + const rootUser = cuid(); + const rootUserPassword = encrypt(generatePassword()); + const defaultDatabase = cuid(); + + let publicPort = await getPort(); + let i = 0; + + do { + const usedPorts = await prisma.database.findMany({ where: { publicPort } }); + if (usedPorts.length === 0) break; + publicPort = await getPort(); + i++; + } while (i < 10); + if (i === 9) { + throw { + error: 'No free port found!? Is it possible?' + }; + } + return await prisma.database.create({ + data: { + name, + publicPort, + defaultDatabase, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + teams: { connect: { id: teamId } }, + settings: { create: { isPublic: false } } + } + }); +} + +export async function getDatabase({ id, teamId }) { + const body = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include: { destinationDocker: true, settings: true } + }); + + if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword); + if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword); + + return { ...body }; +} + +export async function removeDatabase({ id }) { + await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); + await prisma.database.delete({ where: { id } }); + return; +} + +export async function configureDatabaseType({ id, type }) { + return await prisma.database.update({ + where: { id }, + data: { type } + }); +} +export async function setDatabase({ + id, + version, + isPublic, + appendOnly +}: { + id: string; + version?: string; + isPublic?: boolean; + appendOnly?: boolean; +}) { + return await prisma.database.update({ + where: { id }, + data: { + version, + settings: { upsert: { update: { isPublic, appendOnly }, create: { isPublic, appendOnly } } } + } + }); +} +export async function updateDatabase({ + id, + name, + defaultDatabase, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + version +}) { + const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword); + const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword); + return await prisma.database.update({ + where: { id }, + data: { + name, + defaultDatabase, + dbUser, + dbUserPassword: encryptedDbUserPassword, + rootUser, + rootUserPassword: encryptedRootUserPassword, + version + } + }); +} + +// export async function setDatabaseSettings({ id, isPublic }) { +// try { +// await prisma.databaseSettings.update({ where: { databaseId: id }, data: { isPublic } }) +// return { status: 201 } +// } catch (e) { +// throw PrismaErrorHandler(e) +// } +// } + +export async function stopDatabase(database) { + let everStarted = false; + const { + id, + destinationDockerId, + destinationDocker: { engine } + } = database; + if (destinationDockerId) { + try { + const host = getEngine(engine); + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}` + ); + if (stdout) { + everStarted = true; + await removeContainer(id, engine); + } + } catch (error) { + // + } + } + return everStarted; +} diff --git a/src/lib/database/destinations.ts b/src/lib/database/destinations.ts new file mode 100644 index 000000000..a4e5ec228 --- /dev/null +++ b/src/lib/database/destinations.ts @@ -0,0 +1,132 @@ +import { asyncExecShell, getEngine } from '$lib/common'; +import { dockerInstance } from '$lib/docker'; +import { defaultProxyImageHttp, defaultProxyImageTcp, startCoolifyProxy } from '$lib/haproxy'; +import { getDatabaseImage } from '.'; +import { prisma, PrismaErrorHandler } from './common'; + +export async function listDestinations(teamId) { + return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } }); +} + +export async function configureDestinationForService({ id, destinationId }) { + return await prisma.service.update({ + where: { id }, + data: { destinationDocker: { connect: { id: destinationId } } } + }); +} +export async function configureDestinationForApplication({ id, destinationId }) { + return await prisma.application.update({ + where: { id }, + data: { destinationDocker: { connect: { id: destinationId } } } + }); +} +export async function configureDestinationForDatabase({ id, destinationId }) { + await prisma.database.update({ + where: { id }, + data: { destinationDocker: { connect: { id: destinationId } } } + }); + + const { + destinationDockerId, + destinationDocker: { engine }, + version, + type + } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }); + + if (destinationDockerId) { + const host = getEngine(engine); + if (type && version) { + const baseImage = getDatabaseImage(type); + asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`); + asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageTcp}`); + asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageHttp}`); + asyncExecShell(`DOCKER_HOST=${host} docker pull certbot/certbot:latest`); + asyncExecShell(`DOCKER_HOST=${host} docker pull alpine:latest`); + } + } +} +export async function updateDestination({ id, name, engine, network }) { + return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } }); +} + +export async function newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) { + const host = getEngine(engine); + const docker = dockerInstance({ destinationDocker: { engine, network } }); + const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } }); + if (found.length === 0) { + await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`); + } + await prisma.destinationDocker.create({ + data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed } + }); + const destinations = await prisma.destinationDocker.findMany({ where: { engine } }); + const destination = destinations.find((destination) => destination.network === network); + + if (destinations.length > 0) { + const proxyConfigured = destinations.find( + (destination) => destination.network !== network && destination.isCoolifyProxyUsed === true + ); + if (proxyConfigured) { + if (proxyConfigured.isCoolifyProxyUsed) { + isCoolifyProxyUsed = true; + } else { + isCoolifyProxyUsed = false; + } + } + await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } }); + } + if (isCoolifyProxyUsed) await startCoolifyProxy(engine); + return destination.id; +} +export async function removeDestination({ id }) { + const destination = await prisma.destinationDocker.delete({ where: { id } }); + if (destination.isCoolifyProxyUsed) { + const host = getEngine(destination.engine); + const { network } = destination; + const { stdout: found } = await asyncExecShell( + `DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=coolify-haproxy --format '{{.}}'` + ); + if (found) { + await asyncExecShell( + `DOCKER_HOST="${host}" docker network disconnect ${network} coolify-haproxy` + ); + await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`); + } + } +} + +export async function getDestination({ id, teamId }) { + return await prisma.destinationDocker.findFirst({ + where: { id, teams: { some: { id: teamId } } } + }); +} +export async function getDestinationByApplicationId({ id, teamId }) { + return await prisma.destinationDocker.findFirst({ + where: { application: { some: { id } }, teams: { some: { id: teamId } } } + }); +} + +export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) { + return await prisma.destinationDocker.updateMany({ + where: { engine }, + data: { isCoolifyProxyUsed } + }); + + // if (isCoolifyProxyUsed) { + // await installCoolifyProxy(engine) + // await configureNetworkCoolifyProxy(engine) + // } else { + // // TODO: must check if other destination is using the proxy??? or not? + // const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false }) + // if (!domain) { + // await uninstallCoolifyProxy(engine) + // } else { + // return { + // stastus: 500, + // body: { + // message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.' + // } + // } + // } + // } +} diff --git a/src/lib/database/gitSources.ts b/src/lib/database/gitSources.ts new file mode 100644 index 000000000..b2f43a092 --- /dev/null +++ b/src/lib/database/gitSources.ts @@ -0,0 +1,71 @@ +import { decrypt, encrypt } from '$lib/crypto'; +import { prisma, PrismaErrorHandler } from './common'; + +export async function listSources(teamId) { + return await prisma.gitSource.findMany({ + where: { teams: { some: { id: teamId } } }, + include: { githubApp: true, gitlabApp: true } + }); +} + +export async function newSource({ name, teamId, type, htmlUrl, apiUrl, organization }) { + return await prisma.gitSource.create({ + data: { + teams: { connect: { id: teamId } }, + name, + type, + htmlUrl, + apiUrl, + organization + } + }); +} +export async function removeSource({ id }) { + // TODO: Disconnect application with this sourceId! Maybe not needed? + const source = await prisma.gitSource.delete({ + where: { id }, + include: { githubApp: true, gitlabApp: true } + }); + if (source.githubAppId) await prisma.githubApp.delete({ where: { id: source.githubAppId } }); + if (source.gitlabAppId) await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } }); +} + +export async function getSource({ id, teamId }) { + let body = await prisma.gitSource.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include: { githubApp: true, gitlabApp: true } + }); + if (body?.githubApp?.clientSecret) + body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret); + if (body?.githubApp?.webhookSecret) + body.githubApp.webhookSecret = decrypt(body.githubApp.webhookSecret); + if (body?.githubApp?.privateKey) body.githubApp.privateKey = decrypt(body.githubApp.privateKey); + if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret); + return body; +} +export async function addSource({ id, appId, teamId, oauthId, groupName, appSecret }) { + const encrptedAppSecret = encrypt(appSecret); + return await prisma.gitlabApp.create({ + data: { + teams: { connect: { id: teamId } }, + appId, + oauthId, + groupName, + appSecret: encrptedAppSecret, + gitSource: { connect: { id } } + } + }); +} + +export async function configureGitsource({ id, gitSourceId }) { + return await prisma.application.update({ + where: { id }, + data: { gitSource: { connect: { id: gitSourceId } } } + }); +} +export async function updateGitsource({ id, name }) { + return await prisma.gitSource.update({ + where: { id }, + data: { name } + }); +} diff --git a/src/lib/database/github.ts b/src/lib/database/github.ts new file mode 100644 index 000000000..46f57159f --- /dev/null +++ b/src/lib/database/github.ts @@ -0,0 +1,44 @@ +import { decrypt, encrypt } from '$lib/crypto'; +import { prisma, PrismaErrorHandler } from './common'; + +export async function addInstallation({ gitSourceId, installation_id }) { + const source = await prisma.gitSource.findUnique({ + where: { id: gitSourceId }, + include: { githubApp: true } + }); + return await prisma.githubApp.update({ + where: { id: source.githubAppId }, + data: { installationId: Number(installation_id) } + }); +} + +export async function getUniqueGithubApp({ githubAppId }) { + let body = await prisma.githubApp.findUnique({ where: { id: githubAppId } }); + if (body.privateKey) body.privateKey = decrypt(body.privateKey); + return body; +} + +export async function createGithubApp({ + id, + client_id, + slug, + client_secret, + pem, + webhook_secret, + state +}) { + const encryptedClientSecret = encrypt(client_secret); + const encryptedWebhookSecret = encrypt(webhook_secret); + const encryptedPem = encrypt(pem); + return await prisma.githubApp.create({ + data: { + appId: id, + name: slug, + clientId: client_id, + clientSecret: encryptedClientSecret, + webhookSecret: encryptedWebhookSecret, + privateKey: encryptedPem, + gitSource: { connect: { id: state } } + } + }); +} diff --git a/src/lib/database/gitlab.ts b/src/lib/database/gitlab.ts new file mode 100644 index 000000000..e8020794b --- /dev/null +++ b/src/lib/database/gitlab.ts @@ -0,0 +1,37 @@ +import { encrypt } from '$lib/crypto'; +import { generateSshKeyPair, prisma, PrismaErrorHandler } from './common'; + +export async function updateDeployKey({ id, deployKeyId }) { + const application = await prisma.application.findUnique({ + where: { id }, + include: { gitSource: { include: { gitlabApp: true } } } + }); + return await prisma.gitlabApp.update({ + where: { id: application.gitSource.gitlabApp.id }, + data: { deployKeyId } + }); +} +export async function getSshKey({ id }) { + const application = await prisma.application.findUnique({ + where: { id }, + include: { gitSource: { include: { gitlabApp: true } } } + }); + return { status: 200, body: { publicKey: application.gitSource.gitlabApp.publicSshKey } }; +} +export async function generateSshKey({ id }) { + const application = await prisma.application.findUnique({ + where: { id }, + include: { gitSource: { include: { gitlabApp: true } } } + }); + if (!application.gitSource?.gitlabApp?.privateSshKey) { + const keys = await generateSshKeyPair(); + const encryptedPrivateKey = encrypt(keys.privateKey); + await prisma.gitlabApp.update({ + where: { id: application.gitSource.gitlabApp.id }, + data: { privateSshKey: encryptedPrivateKey, publicSshKey: keys.publicKey } + }); + return { status: 201, body: { publicKey: keys.publicKey } }; + } else { + return { status: 200 }; + } +} diff --git a/src/lib/database/index.ts b/src/lib/database/index.ts new file mode 100644 index 000000000..e03b564f7 --- /dev/null +++ b/src/lib/database/index.ts @@ -0,0 +1,14 @@ +export * from './applications'; +export * from './checks'; +export * from './common'; +export * from './destinations'; +export * from './github'; +export * from './gitlab'; +export * from './gitSources'; +export * from './logs'; +export * from './settings'; +export * from './users'; +export * from './teams'; +export * from './secrets'; +export * from './databases'; +export * from './services'; diff --git a/src/lib/database/logs.ts b/src/lib/database/logs.ts new file mode 100644 index 000000000..e4dd7a3e6 --- /dev/null +++ b/src/lib/database/logs.ts @@ -0,0 +1,13 @@ +import { prisma, PrismaErrorHandler } from './common'; + +export async function listLogs({ buildId, last = 0 }) { + try { + const body = await prisma.buildLog.findMany({ + where: { buildId, time: { gt: last } }, + orderBy: { time: 'asc' } + }); + return [...body]; + } catch (e) { + throw PrismaErrorHandler(e); + } +} diff --git a/src/lib/database/secrets.ts b/src/lib/database/secrets.ts new file mode 100644 index 000000000..06f6592b2 --- /dev/null +++ b/src/lib/database/secrets.ts @@ -0,0 +1,21 @@ +import { encrypt } from '$lib/crypto'; +import { prisma, PrismaErrorHandler } from './common'; + +export async function listSecrets({ applicationId }) { + return await prisma.secret.findMany({ + where: { applicationId }, + orderBy: { createdAt: 'desc' }, + select: { id: true, createdAt: true, name: true, isBuildSecret: true } + }); +} + +export async function createSecret({ id, name, value, isBuildSecret }) { + value = encrypt(value); + return await prisma.secret.create({ + data: { name, value, isBuildSecret, application: { connect: { id } } } + }); +} + +export async function removeSecret({ id, name }) { + return await prisma.secret.deleteMany({ where: { applicationId: id, name } }); +} diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts new file mode 100644 index 000000000..6b7ac2f3f --- /dev/null +++ b/src/lib/database/services.ts @@ -0,0 +1,137 @@ +import { decrypt, encrypt } from '$lib/crypto'; +import { dockerInstance } from '$lib/docker'; +import cuid from 'cuid'; +import { generatePassword } from '.'; +import { prisma, PrismaErrorHandler } from './common'; + +export async function listServices(teamId) { + return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } }); +} + +export async function newService({ name, teamId }) { + return await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } }); +} + +export async function getService({ id, teamId }) { + const body = await prisma.service.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include: { + destinationDocker: true, + plausibleAnalytics: true, + minio: true, + vscodeserver: true, + wordpress: true + } + }); + + if (body.plausibleAnalytics?.postgresqlPassword) + body.plausibleAnalytics.postgresqlPassword = decrypt( + body.plausibleAnalytics.postgresqlPassword + ); + if (body.plausibleAnalytics?.password) + body.plausibleAnalytics.password = decrypt(body.plausibleAnalytics.password); + if (body.plausibleAnalytics?.secretKeyBase) + body.plausibleAnalytics.secretKeyBase = decrypt(body.plausibleAnalytics.secretKeyBase); + + if (body.minio?.rootUserPassword) + body.minio.rootUserPassword = decrypt(body.minio.rootUserPassword); + + if (body.vscodeserver?.password) body.vscodeserver.password = decrypt(body.vscodeserver.password); + + if (body.wordpress?.mysqlPassword) + body.wordpress.mysqlPassword = decrypt(body.wordpress.mysqlPassword); + if (body.wordpress?.mysqlRootUserPassword) + body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword); + + return { ...body }; +} + +export async function configureServiceType({ id, type }) { + if (type === 'plausibleanalytics') { + const password = encrypt(generatePassword()); + const postgresqlUser = cuid(); + const postgresqlPassword = encrypt(generatePassword()); + const postgresqlDatabase = 'plausibleanalytics'; + const secretKeyBase = encrypt(generatePassword(64)); + + await prisma.service.update({ + where: { id }, + data: { + type, + plausibleAnalytics: { + create: { + postgresqlDatabase, + postgresqlUser, + postgresqlPassword, + password, + secretKeyBase + } + } + } + }); + } else if (type === 'nocodb') { + await prisma.service.update({ + where: { id }, + data: { type } + }); + } else if (type === 'minio') { + const rootUser = cuid(); + const rootUserPassword = encrypt(generatePassword()); + await prisma.service.update({ + where: { id }, + data: { type, minio: { create: { rootUser, rootUserPassword } } } + }); + } else if (type === 'vscodeserver') { + const password = encrypt(generatePassword()); + await prisma.service.update({ + where: { id }, + data: { type, vscodeserver: { create: { password } } } + }); + } else if (type === 'wordpress') { + const mysqlUser = cuid(); + const mysqlPassword = encrypt(generatePassword()); + const mysqlRootUser = cuid(); + const mysqlRootUserPassword = encrypt(generatePassword()); + await prisma.service.update({ + where: { id }, + data: { + type, + wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } } + } + }); + } +} +export async function setService({ id, version }) { + return await prisma.service.update({ + where: { id }, + data: { version } + }); +} + +export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) { + await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } }); + await prisma.service.update({ where: { id }, data: { name, fqdn } }); +} +export async function updateNocoDbOrMinioService({ id, fqdn, name }) { + return await prisma.service.update({ where: { id }, data: { fqdn, name } }); +} +export async function updateVsCodeServer({ id, fqdn, name }) { + return await prisma.service.update({ where: { id }, data: { fqdn, name } }); +} +export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConfig }) { + return await prisma.service.update({ + where: { id }, + data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } } + }); +} +export async function updateMinioService({ id, publicPort }) { + return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } }); +} + +export async function removeService({ id }) { + await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } }); + await prisma.minio.deleteMany({ where: { serviceId: id } }); + await prisma.vscodeserver.deleteMany({ where: { serviceId: id } }); + await prisma.wordpress.deleteMany({ where: { serviceId: id } }); + await prisma.service.delete({ where: { id } }); +} diff --git a/src/lib/database/settings.ts b/src/lib/database/settings.ts new file mode 100644 index 000000000..31d6b3c04 --- /dev/null +++ b/src/lib/database/settings.ts @@ -0,0 +1,8 @@ +import { decrypt } from '$lib/crypto'; +import { prisma } from './common'; + +export async function listSettings() { + let settings = await prisma.setting.findFirst({}); + if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword); + return settings; +} diff --git a/src/lib/database/teams.ts b/src/lib/database/teams.ts new file mode 100644 index 000000000..3e1cf9928 --- /dev/null +++ b/src/lib/database/teams.ts @@ -0,0 +1,20 @@ +import { prisma, PrismaErrorHandler } from './common'; + +export async function listTeams() { + return await prisma.team.findMany(); +} +export async function newTeam({ name, userId }) { + return await prisma.team.create({ + data: { + name, + permissions: { create: { user: { connect: { id: userId } }, permission: 'owner' } }, + users: { connect: { id: userId } } + } + }); +} +export async function getMyTeams({ userId }) { + return await prisma.permission.findMany({ + where: { userId }, + include: { team: { include: { _count: { select: { users: true } } } } } + }); +} diff --git a/src/lib/database/users.ts b/src/lib/database/users.ts new file mode 100644 index 000000000..663ede1a4 --- /dev/null +++ b/src/lib/database/users.ts @@ -0,0 +1,125 @@ +import cuid from 'cuid'; +import bcrypt from 'bcrypt'; + +import { prisma, PrismaErrorHandler } from './common'; +import { asyncExecShell, removeContainer, uniqueName } from '$lib/common'; + +import * as db from '$lib/database'; +import { startCoolifyProxy } from '$lib/haproxy'; + +export async function login({ email, password }) { + const saltRounds = 15; + const users = await prisma.user.count(); + const userFound = await prisma.user.findUnique({ + where: { email }, + include: { teams: true }, + rejectOnNotFound: false + }); + // Registration disabled if database is not seeded properly + const { isRegistrationEnabled, id } = await db.listSettings(); + + let uid = cuid(); + // Disable registration if we are registering the first user. + if (users === 0) { + await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } }); + // Create default network & start Coolify Proxy + asyncExecShell(`docker network create --attachable coolify`) + .then(() => { + console.log('Network created'); + }) + .catch(() => { + console.log('Network already exists'); + }); + + startCoolifyProxy('/var/run/docker.sock') + .then(() => { + console.log('Coolify Proxy Started'); + }) + .catch((err) => { + console.log(err); + }); + uid = '0'; + } + + if (userFound) { + if (userFound.type === 'email') { + const passwordMatch = await bcrypt.compare(password, userFound.password); + if (!passwordMatch) { + throw { + error: 'Wrong password or email address.' + }; + } + uid = userFound.id; + } + } else { + // If registration disabled, return 403 + if (!isRegistrationEnabled) { + throw { + error: 'Registration disabled by administrator.' + }; + } + + const hashedPassword = await bcrypt.hash(password, saltRounds); + if (users === 0) { + await prisma.user.create({ + data: { + id: uid, + email, + password: hashedPassword, + type: 'email', + teams: { + create: { + id: uid, + name: uniqueName(), + destinationDocker: { connect: { network: 'coolify' } } + } + }, + permission: { create: { teamId: uid, permission: 'owner' } } + }, + include: { teams: true } + }); + } else { + await prisma.user.create({ + data: { + id: uid, + email, + password: hashedPassword, + type: 'email', + teams: { + create: { + id: uid, + name: uniqueName() + } + }, + permission: { create: { teamId: uid, permission: 'owner' } } + }, + include: { teams: true } + }); + } + } + + // const token = jsonwebtoken.sign({}, secretKey, { + // expiresIn: 15778800, + // algorithm: 'HS256', + // audience: 'coolify', + // issuer: 'coolify', + // jwtid: uid, + // subject: `User:${uid}`, + // notBefore: -1000 + // }); + + return { + status: 200, + headers: { + 'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;` + }, + body: { + uid, + teamId: uid + } + }; +} + +export async function getUser({ userId }) { + return await prisma.user.findUnique({ where: { id: userId } }); +} diff --git a/src/lib/dayjs.ts b/src/lib/dayjs.ts new file mode 100644 index 000000000..49859a8c6 --- /dev/null +++ b/src/lib/dayjs.ts @@ -0,0 +1,7 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc.js'; +import relativeTime from 'dayjs/plugin/relativeTime.js'; +dayjs.extend(utc); +dayjs.extend(relativeTime); + +export { dayjs }; diff --git a/src/lib/docker.ts b/src/lib/docker.ts new file mode 100644 index 000000000..52f5d1bef --- /dev/null +++ b/src/lib/docker.ts @@ -0,0 +1,160 @@ +import Dockerode from 'dockerode'; +import { promises as fs } from 'fs'; +import { saveBuildLog } from './common'; + +export async function buildCacheImageWithNode(data, imageForBuild) { + const { + applicationId, + tag, + workdir, + docker, + buildId, + baseDirectory, + installCommand, + buildCommand, + debug, + secrets + } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${imageForBuild}`); + Dockerfile.push('WORKDIR /usr/src/app'); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (!secret.isBuildSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + }); + } + // TODO: If build command defined, install command should be the default yarn install + if (installCommand) { + Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); + try { + await fs.stat(`${workdir}/yarn.lock`); + Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`); + } catch (error) {} + try { + await fs.stat(`${workdir}/pnpm-lock.yaml`); + Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`); + } catch (error) {} + Dockerfile.push(`RUN ${installCommand}`); + } + Dockerfile.push(`COPY ./${baseDirectory || ''} ./`); + Dockerfile.push(`RUN ${buildCommand}`); + await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); + await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); +} + +export async function buildCacheImageWithCargo(data, imageForBuild) { + const { + applicationId, + tag, + workdir, + docker, + buildId, + baseDirectory, + installCommand, + buildCommand, + debug, + secrets + } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); + Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('RUN cargo install cargo-chef'); + Dockerfile.push('COPY . .'); + Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json'); + Dockerfile.push(`FROM ${imageForBuild}`); + Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('RUN cargo install cargo-chef'); + Dockerfile.push(`COPY --from=planner-${applicationId} /usr/src/app/recipe.json recipe.json`); + Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); + await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); + await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); +} + +export async function buildImage({ + applicationId, + tag, + workdir, + docker, + buildId, + isCache = false, + debug = false +}) { + if (isCache) { + saveBuildLog({ line: `Building cache image started.`, buildId, applicationId }); + } else { + saveBuildLog({ line: `Building image started.`, buildId, applicationId }); + } + if (!debug && isCache) { + saveBuildLog({ + line: `Debug turned off. To see more details, allow it in the configuration.`, + buildId, + applicationId + }); + } + + const stream = await docker.engine.buildImage( + { src: ['.'], context: workdir }, + { + dockerfile: isCache ? 'Dockerfile-cache' : 'Dockerfile', + t: `${applicationId}:${tag}${isCache ? '-cache' : ''}` + } + ); + await streamEvents({ stream, docker, buildId, applicationId, debug }); +} + +export function dockerInstance({ destinationDocker }): { engine: Dockerode; network: string } { + // new Docker({protocol:"ssh", host: '188.34.164.25',port: 22, username:'root', sshOptions: {agentForward: true, agent:process.env.SSH_AUTH_SOCK}}) + return { + engine: new Dockerode({ + socketPath: destinationDocker.engine + }), + network: destinationDocker.network + }; +} +export async function streamEvents({ stream, docker, buildId, applicationId, debug }) { + await new Promise((resolve, reject) => { + docker.engine.modem.followProgress(stream, onFinished, onProgress); + function onFinished(err, res) { + if (err) reject(err); + resolve(res); + } + function onProgress(event) { + if (event.error) { + reject(event.error); + } else if (event.stream) { + if (event.stream !== '\n') { + if (debug) + saveBuildLog({ line: `${event.stream.replace('\n', '')}`, buildId, applicationId }); + } + } + } + }); +} + +export const baseServiceConfigurationDocker = { + restart_policy: { + condition: 'any', + max_attempts: 6 + } +}; + +export const baseServiceConfigurationSwarm = { + replicas: 1, + restart_policy: { + condition: 'any', + max_attempts: 6 + }, + update_config: { + parallelism: 1, + delay: '10s', + order: 'start-first' + }, + rollback_config: { + parallelism: 1, + delay: '10s', + order: 'start-first', + failure_action: 'rollback' + } +}; diff --git a/src/lib/form.ts b/src/lib/form.ts new file mode 100644 index 000000000..08099576d --- /dev/null +++ b/src/lib/form.ts @@ -0,0 +1,81 @@ +import { toast } from '@zerodevx/svelte-toast'; +export function errorNotification(message: string) { + console.error(message); + if (typeof message !== 'string') { + toast.push('Ooops, something is not okay, are you okay?'); + } else { + toast.push(message); + } +} +export function enhance( + form: HTMLFormElement, + { + beforeSubmit, + final, + pending, + error, + result + }: { + beforeSubmit?: () => Promise; + final?: () => Promise; + pending?: (data: FormData, form: HTMLFormElement) => void; + error?: (res: Response, error: Error, form: HTMLFormElement) => void; + result?: (res: Response, form: HTMLFormElement) => void; + } +): { destroy: () => void } { + let current_token: unknown; + + async function handle_submit(e: Event) { + const token = (current_token = {}); + e.preventDefault(); + + let body = new FormData(form); + let parsedData = body; + + body.forEach((data, key) => { + if (data === '' || data === null) parsedData.delete(key); + }); + body = parsedData; + + if (beforeSubmit) await beforeSubmit(); + if (pending) pending(body, form); + + try { + const res = await fetch(form.action, { + method: form.method, + headers: { + accept: 'application/json' + }, + body + }); + + if (token !== current_token) return; + + if (res.ok) { + if (result) result(res, form); + } else if (error) { + error(res, null, form); + } else { + // TODO: Add error frontend here + const { message } = await res.json(); + errorNotification(message); + } + } catch (e) { + if (error) { + error(null, e, form); + } else { + throw e; + } + } finally { + if (final) final(); + } + } + + form.addEventListener('submit', handle_submit); + + return { + destroy() { + form.removeEventListener('submit', handle_submit); + } + }; +} diff --git a/src/lib/github.ts b/src/lib/github.ts new file mode 100644 index 000000000..0fb68ea0a --- /dev/null +++ b/src/lib/github.ts @@ -0,0 +1,9 @@ +export function dashify(str: string, options?: any): string { + if (typeof str !== 'string') return str; + return str + .trim() + .replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-')) + .replace(/^-+|-+$/g, '') + .replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m)) + .toLowerCase(); +} diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts new file mode 100644 index 000000000..9dc1942fa --- /dev/null +++ b/src/lib/haproxy/index.ts @@ -0,0 +1,634 @@ +import { dev } from '$app/env'; +import { asyncExecShell, getEngine } from '$lib/common'; +import got from 'got'; +import * as db from '$lib/database'; +import { letsEncrypt } from '$lib/letsencrypt'; + +const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555'; + +export const defaultProxyImage = `coolify-haproxy-alpine:latest`; +export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; +export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`; + +export async function haproxyInstance() { + const { proxyPassword } = await db.listSettings(); + return got.extend({ + prefixUrl: url, + username: 'admin', + password: proxyPassword + }); +} +export async function getRawConfiguration(): Promise { + return await (await haproxyInstance()).get(`v2/services/haproxy/configuration/raw`).json(); +} + +export async function getNextTransactionVersion(): Promise { + const raw = await getRawConfiguration(); + if (raw?._version) { + return raw._version; + } + return 1; +} + +export async function getNextTransactionId(): Promise { + const version = await getNextTransactionVersion(); + const newTransaction: NewTransaction = await ( + await haproxyInstance() + ) + .post('v2/services/haproxy/transactions', { + searchParams: { + version + } + }) + .json(); + return newTransaction.id; +} + +export async function completeTransaction(transactionId) { + const haproxy = await haproxyInstance(); + return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`); +} + +export async function removeProxyConfiguration({ domain }) { + const haproxy = await haproxyInstance(); + const transactionId = await getNextTransactionId(); + const backendFound = await haproxy + .get(`v2/services/haproxy/configuration/backends/${domain}`) + .json(); + if (backendFound) { + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${domain}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + await completeTransaction(transactionId); + } +} +export async function forceSSLOffApplication({ domain }) { + if (!dev) { + const haproxy = await haproxyInstance(); + await checkHAProxy(haproxy); + const transactionId = await getNextTransactionId(); + try { + const rules: any = await haproxy + .get(`v2/services/haproxy/configuration/http_request_rules`, { + searchParams: { + parent_name: 'http', + parent_type: 'frontend' + } + }) + .json(); + if (rules.data.length > 0) { + const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`)); + if (rule) { + await haproxy + .delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, { + searchParams: { + transaction_id: transactionId, + parent_name: 'http', + parent_type: 'frontend' + } + }) + .json(); + } + } + } catch (error) { + console.log(error); + } finally { + await completeTransaction(transactionId); + } + } else { + console.log(`[DEBUG] Removing ssl for ${domain}`); + } +} +export async function forceSSLOnApplication({ domain }) { + if (!dev) { + const haproxy = await haproxyInstance(); + await checkHAProxy(haproxy); + const transactionId = await getNextTransactionId(); + + try { + const rules: any = await haproxy + .get(`v2/services/haproxy/configuration/http_request_rules`, { + searchParams: { + parent_name: 'http', + parent_type: 'frontend' + } + }) + .json(); + let nextRule = 0; + if (rules.data.length > 0) { + const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`)); + if (rule) return; + nextRule = rules.data[rules.data.length - 1].index + 1; + } + await haproxy + .post(`v2/services/haproxy/configuration/http_request_rules`, { + searchParams: { + transaction_id: transactionId, + parent_name: 'http', + parent_type: 'frontend' + }, + json: { + index: nextRule, + cond: 'if', + cond_test: `{ hdr(Host) -i ${domain} } !{ ssl_fc }`, + type: 'redirect', + redir_type: 'scheme', + redir_value: 'https', + redir_code: 301 + } + }) + .json(); + } catch (error) { + console.log(error); + throw error; + } finally { + await completeTransaction(transactionId); + } + } else { + console.log(`[DEBUG] Adding ssl for ${domain}`); + } +} + +export async function deleteProxy({ id }) { + const haproxy = await haproxyInstance(); + try { + await checkHAProxy(haproxy); + } catch (error) { + return; + } + const transactionId = await getNextTransactionId(); + try { + await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json(); + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${id}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + await haproxy.get(`v2/services/haproxy/configuration/frontends/${id}`).json(); + await haproxy + .delete(`v2/services/haproxy/configuration/frontends/${id}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + } catch (error) { + console.log(error.response.body); + } finally { + await completeTransaction(transactionId); + } +} +// export async function configureProxyForDatabase({ id, port, isPublic, privatePort }) { +// const haproxy = await haproxyInstance() +// try { +// await checkHAProxy() +// } catch (error) { +// return +// } + +// let alreadyConfigured = false +// try { +// const backend: any = await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json() +// const server: any = await haproxy.get(`v2/services/haproxy/configuration/servers/${id}`, { +// searchParams: { +// backend: id +// }, +// }).json() +// if (backend.data.name === id) { +// if (server.data.port === privatePort) { +// if (server.data.check === 'enabled') { +// if (server.data.address === id) { +// alreadyConfigured = true +// } +// } +// } +// } +// } catch (error) { +// console.log('error getting backend or server', error.response.body) +// } +// if (alreadyConfigured) return + +// const transactionId = await getNextTransactionId() +// try { +// await haproxy.post('v2/services/haproxy/configuration/backends', { +// searchParams: { +// transaction_id: transactionId +// }, +// json: { +// "init-addr": "last,libc,none", +// "mode": "tcp", +// "name": id +// } +// }) +// await haproxy.post('v2/services/haproxy/configuration/servers', { +// searchParams: { +// transaction_id: transactionId, +// backend: id +// }, +// json: { +// "address": id, +// "check": "enabled", +// "name": id, +// "port": privatePort +// } +// }) +// await haproxy.post('v2/services/haproxy/configuration/frontends', { +// searchParams: { +// transaction_id: transactionId, +// backend: id +// }, +// json: { +// "default_backend": id, +// "mode": "tcp", +// "name": id +// } +// }) +// await haproxy.post('v2/services/haproxy/configuration/binds', { +// searchParams: { +// transaction_id: transactionId, +// frontend: id +// }, +// json: { +// "address": "*", +// "name": id, +// "port": port +// } +// }) +// } catch (error) { +// console.log(error.response.body) +// throw error.response.body +// } finally { +// try { +// await completeTransaction(transactionId) +// } catch (error) { +// console.log(error.response.body) +// } +// } +// await configureDatabaseVisibility({ id, isPublic }) +// } +export async function reloadHaproxy(engine) { + const host = getEngine(engine); + return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`); +} +export async function configureProxyForApplication({ domain, imageId, applicationId, port }) { + const haproxy = await haproxyInstance(); + await checkHAProxy(haproxy); + + let serverConfigured = false; + let backendAvailable: any = null; + + try { + backendAvailable = await haproxy + .get(`v2/services/haproxy/configuration/backends/${domain}`) + .json(); + const server: any = await haproxy + .get(`v2/services/haproxy/configuration/servers/${imageId}`, { + searchParams: { + backend: domain + } + }) + .json(); + + if (backendAvailable && server) { + // Very sophisticated way to check if the server is already configured in proxy + if (backendAvailable.data.forwardfor.enabled === 'enabled') { + if (backendAvailable.data.name === domain) { + if (server.data.check === 'enabled') { + if (server.data.address === applicationId) { + if (server.data.port === port) { + serverConfigured = true; + } + } + } + } + } + } + } catch (error) { + //console.log('error getting backend or server', error?.response?.body); + // + } + + if (serverConfigured) return; + const transactionId = await getNextTransactionId(); + if (backendAvailable) { + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${domain}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + } + try { + await haproxy.post('v2/services/haproxy/configuration/backends', { + searchParams: { + transaction_id: transactionId + }, + json: { + 'init-addr': 'last,libc,none', + forwardfor: { enabled: 'enabled' }, + name: domain + } + }); + + await haproxy.post('v2/services/haproxy/configuration/servers', { + searchParams: { + transaction_id: transactionId, + backend: domain + }, + json: { + address: imageId, + check: 'enabled', + name: imageId, + port: port + } + }); + } catch (error) { + throw error?.response?.body || error; + } finally { + await completeTransaction(transactionId); + } +} + +export async function configureCoolifyProxyOff({ domain }) { + const haproxy = await haproxyInstance(); + await checkHAProxy(haproxy); + + try { + const transactionId = await getNextTransactionId(); + await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${domain}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + await completeTransaction(transactionId); + if (!dev) { + await forceSSLOffApplication({ domain }); + } + } catch (error) { + throw error?.response?.body || error; + } +} +export async function checkHAProxy(haproxy) { + if (!haproxy) haproxy = await haproxyInstance(); + try { + await haproxy.get('v2/info'); + } catch (error) { + throw 'HAProxy is not running, but it should be!'; + } +} +export async function configureCoolifyProxyOn({ domain }) { + const haproxy = await haproxyInstance(); + await checkHAProxy(haproxy); + let serverConfigured = false; + let backendAvailable: any = null; + try { + backendAvailable = await haproxy + .get(`v2/services/haproxy/configuration/backends/${domain}`) + .json(); + const server: any = await haproxy + .get(`v2/services/haproxy/configuration/servers/coolify`, { + searchParams: { + backend: domain + } + }) + .json(); + if (backendAvailable && server) { + // Very sophisticated way to check if the server is already configured in proxy + if (backendAvailable.data.forwardfor.enabled === 'enabled') { + if (backendAvailable.data.name === domain) { + if (server.data.check === 'enabled') { + if (server.data.address === dev ? 'host.docker.internal' : 'coolify') { + if (server.data.port === 3000) { + serverConfigured = true; + } + } + } + } + } + } + } catch (error) {} + if (serverConfigured) return; + const transactionId = await getNextTransactionId(); + try { + await haproxy.post('v2/services/haproxy/configuration/backends', { + searchParams: { + transaction_id: transactionId + }, + json: { + adv_check: 'httpchk', + httpchk_params: { + method: 'GET', + uri: '/undead.json' + }, + 'init-addr': 'last,libc,none', + forwardfor: { enabled: 'enabled' }, + name: domain + } + }); + await haproxy.post('v2/services/haproxy/configuration/servers', { + searchParams: { + transaction_id: transactionId, + backend: domain + }, + json: { + address: dev ? 'host.docker.internal' : 'coolify', + check: 'enabled', + fall: 10, + name: 'coolify', + port: 3000 + } + }); + } catch (error) { + console.log(error); + throw error; + } finally { + await completeTransaction(transactionId); + } +} + +export async function stopTcpHttpProxy(destinationDocker, publicPort) { + const { engine } = destinationDocker; + const host = getEngine(engine); + const containerName = `haproxy-for-${publicPort}`; + const found = await checkContainer(engine, containerName); + try { + if (found) { + return await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}` + ); + } + } catch (error) { + return error; + } +} +export async function startTcpProxy(destinationDocker, id, publicPort, privatePort) { + const { network, engine } = destinationDocker; + const host = getEngine(engine); + + const containerName = `haproxy-for-${publicPort}`; + const found = await checkContainer(engine, containerName); + const foundDB = await checkContainer(engine, id); + + try { + if (foundDB && !found) { + return await asyncExecShell( + `DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}` + ); + } + } catch (error) { + return error; + } +} +export async function startHttpProxy(destinationDocker, id, publicPort, privatePort) { + const { network, engine } = destinationDocker; + const host = getEngine(engine); + + const containerName = `haproxy-for-${publicPort}`; + const found = await checkContainer(engine, containerName); + const foundDB = await checkContainer(engine, id); + + try { + if (foundDB && !found) { + return await asyncExecShell( + `DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}` + ); + } + } catch (error) { + return error; + } +} +export async function startCoolifyProxy(engine) { + const host = getEngine(engine); + const found = await checkContainer(engine, 'coolify-haproxy'); + const { proxyPassword, proxyUser } = await db.listSettings(); + if (!found) { + await asyncExecShell( + `DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}` + ); + } + await configureNetworkCoolifyProxy(engine); +} +export async function checkContainer(engine, container) { + const host = getEngine(engine); + let containerFound = false; + + try { + const { stdout } = await asyncExecShell( + `DOCKER_HOST="${host}" docker inspect --format '{{json .State}}' ${container}` + ); + + const parsedStdout = JSON.parse(stdout); + const status = parsedStdout.Status; + const isRunning = parsedStdout.Running; + + if (status === 'exited' || status === 'created') { + await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`); + } + if (isRunning) { + containerFound = true; + } + } catch (err) { + // Container not found + } + return containerFound; +} + +export async function stopCoolifyProxy(engine) { + const host = getEngine(engine); + const found = await checkContainer(engine, 'coolify-haproxy'); + await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false }); + try { + if (found) { + await asyncExecShell( + `DOCKER_HOST="${host}" docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy` + ); + } + } catch (error) { + return error; + } +} + +export async function configureNetworkCoolifyProxy(engine) { + const host = getEngine(engine); + const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } }); + destinations.forEach(async (destination) => { + try { + await asyncExecShell( + `DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy` + ); + } catch (err) { + // TODO: handle error + } + }); +} + +export async function configureSimpleServiceProxyOn({ id, domain, port }) { + const haproxy = await haproxyInstance(); + try { + await checkHAProxy(haproxy); + } catch (error) { + return; + } + try { + await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); + return; + } catch (error) {} + try { + const transactionId = await getNextTransactionId(); + await haproxy.post('v2/services/haproxy/configuration/backends', { + searchParams: { + transaction_id: transactionId + }, + json: { + 'init-addr': 'last,libc,none', + forwardfor: { enabled: 'enabled' }, + name: domain + } + }); + await haproxy.post('v2/services/haproxy/configuration/servers', { + searchParams: { + transaction_id: transactionId, + backend: domain + }, + json: { + address: id, + check: 'enabled', + name: id, + port: port + } + }); + await completeTransaction(transactionId); + } catch (error) { + console.log(error); + } +} + +export async function configureSimpleServiceProxyOff({ domain }) { + const haproxy = await haproxyInstance(); + await checkHAProxy(haproxy); + + try { + await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); + const transactionId = await getNextTransactionId(); + + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${domain}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + await completeTransaction(transactionId); + } catch (error) {} + return; +} diff --git a/src/lib/importers/github.ts b/src/lib/importers/github.ts new file mode 100644 index 000000000..e8c4bb79c --- /dev/null +++ b/src/lib/importers/github.ts @@ -0,0 +1,50 @@ +import { asyncExecShell, saveBuildLog } from '$lib/common'; +import got from 'got'; +import jsonwebtoken from 'jsonwebtoken'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; + +export default async function ({ + applicationId, + debug, + workdir, + githubAppId, + repository, + branch, + buildId +}): Promise { + try { + saveBuildLog({ line: 'GitHub importer started', buildId, applicationId }); + const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId }); + const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, ''); + + const payload = { + iat: Math.round(new Date().getTime() / 1000), + exp: Math.round(new Date().getTime() / 1000 + 60), + iss: appId + }; + const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, { + algorithm: 'RS256' + }); + const { token } = await got + .post(`https://api.github.com/app/installations/${installationId}/access_tokens`, { + headers: { + Authorization: `Bearer ${jwtToken}`, + Accept: 'application/vnd.github.machine-man-preview+json' + } + }) + .json(); + saveBuildLog({ + line: `Cloning ${repository}:${branch} branch.`, + buildId, + applicationId + }); + await asyncExecShell( + `git clone -q -b ${branch} https://x-access-token:${token}@github.com/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && cd ..` + ); + const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); + return commit.replace('\n', ''); + } catch (error) { + return PrismaErrorHandler(error); + } +} diff --git a/src/lib/importers/gitlab.ts b/src/lib/importers/gitlab.ts new file mode 100644 index 000000000..918bf2402 --- /dev/null +++ b/src/lib/importers/gitlab.ts @@ -0,0 +1,29 @@ +import { asyncExecShell, saveBuildLog } from '$lib/common'; +import { PrismaErrorHandler } from '$lib/database'; + +export default async function ({ + applicationId, + debug, + workdir, + repodir, + repository, + branch, + buildId, + privateSshKey +}): Promise { + saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId }); + await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`); + await asyncExecShell(`chmod 600 ${repodir}/id.rsa`); + + saveBuildLog({ + line: `Cloning ${repository}:${branch} branch.`, + buildId, + applicationId + }); + + await asyncExecShell( + `git clone -q -b ${branch} git@gitlab.com:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && cd ..` + ); + const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); + return commit.replace('\n', ''); +} diff --git a/src/lib/importers/index.ts b/src/lib/importers/index.ts new file mode 100644 index 000000000..193443890 --- /dev/null +++ b/src/lib/importers/index.ts @@ -0,0 +1,4 @@ +import github from './github'; +import gitlab from './gitlab'; + +export { github, gitlab }; diff --git a/src/lib/letsencrypt.ts b/src/lib/letsencrypt.ts new file mode 100644 index 000000000..9b92cbdcf --- /dev/null +++ b/src/lib/letsencrypt.ts @@ -0,0 +1,48 @@ +import { dev } from '$app/env'; +import { forceSSLOffApplication, forceSSLOnApplication, getNextTransactionId } from '$lib/haproxy'; +import { asyncExecShell, getEngine } from './common'; +import * as db from '$lib/database'; + +export async function letsEncrypt({ domain, isCoolify = false, id = null }) { + try { + if (dev) { + return await forceSSLOnApplication({ domain }); + } else { + if (isCoolify) { + await asyncExecShell( + `docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email` + ); + + const { stderr } = await asyncExecShell( + `docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem` + ); + if (stderr) throw new Error(stderr); + return; + } + let data: any = await db.prisma.application.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + if (!data) { + data = await db.prisma.service.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + } + // Set SSL with Let's encrypt + if (data.destinationDockerId && data.destinationDocker) { + const host = getEngine(data.destinationDocker.engine); + await asyncExecShell( + `DOCKER_HOST=${host} docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email` + ); + const { stderr } = await asyncExecShell( + `DOCKER_HOST=${host} docker run --rm --name bash -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem` + ); + if (stderr) throw new Error(stderr); + await forceSSLOnApplication({ domain }); + } + } + } catch (error) { + throw error; + } +} diff --git a/src/lib/queues/builder.ts b/src/lib/queues/builder.ts new file mode 100644 index 000000000..6ad7e0493 --- /dev/null +++ b/src/lib/queues/builder.ts @@ -0,0 +1,256 @@ +import crypto from 'crypto'; +import * as buildpacks from '../buildPacks'; +import * as importers from '../importers'; +import { dockerInstance } from '../docker'; +import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common'; +import { configureProxyForApplication, reloadHaproxy } from '../haproxy'; +import * as db from '$lib/database'; +import { decrypt } from '$lib/crypto'; +import { + copyBaseConfigurationFiles, + makeLabelForStandaloneApplication, + setDefaultConfiguration +} from '$lib/buildPacks/common'; +import { letsEncrypt } from '$lib/letsencrypt'; + +export default async function (job) { + /* + Edge cases: + 1 - Change build pack and redeploy, what should happen? + */ + let { + id: applicationId, + repository, + branch, + buildPack, + name, + destinationDocker, + destinationDockerId, + gitSource, + build_id: buildId, + configHash, + port, + installCommand, + buildCommand, + startCommand, + fqdn, + baseDirectory, + publishDirectory, + projectId, + secrets, + type, + pullmergeRequestId = null, + sourceBranch = null, + settings + } = job.data; + const { debug } = settings; + + let imageId = applicationId; + let domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + + // 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'; + } + // Not implemented yet + // if (destinationKubernetesId) { + // destinationType = 'kubernetes' + // } + + if (destinationType === 'docker') { + const docker = dockerInstance({ destinationDocker }); + const host = getEngine(destinationDocker.engine); + + const build = await db.createBuild({ + id: buildId, + applicationId, + destinationDockerId: destinationDocker.id, + gitSourceId: gitSource.id, + githubAppId: gitSource.githubApp?.id, + gitlabAppId: gitSource.gitlabApp?.id, + type + }); + + const { workdir, repodir } = await createDirectories({ repository, buildId: build.id }); + + const configuration = await setDefaultConfiguration(job.data); + + buildPack = configuration.buildPack; + port = configuration.port; + installCommand = configuration.installCommand; + startCommand = configuration.startCommand; + buildCommand = configuration.buildCommand; + publishDirectory = configuration.publishDirectory; + + let commit = await importers[gitSource.type]({ + applicationId, + debug, + workdir, + repodir, + githubAppId: gitSource.githubApp?.id, + gitlabAppId: gitSource.gitlabApp?.id, + repository, + branch, + buildId: build.id, + apiUrl: gitSource.apiUrl, + projectId, + deployKeyId: gitSource.gitlabApp?.deployKeyId || null, + privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null + }); + let tag = commit.slice(0, 7); + if (pullmergeRequestId) { + tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`; + } + + try { + await db.prisma.build.update({ where: { id: build.id }, data: { commit } }); + } catch (err) { + console.log(err); + } + + if (!pullmergeRequestId) { + const currentHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack, + port, + installCommand, + buildCommand, + startCommand, + secrets, + branch, + repository, + fqdn + }) + ) + .digest('hex'); + + if (configHash !== currentHash) { + await db.prisma.application.update({ + where: { id: applicationId }, + data: { configHash: currentHash } + }); + deployNeeded = true; + if (configHash) { + saveBuildLog({ line: 'Configuration changed.', buildId, applicationId }); + } + } else { + deployNeeded = false; + } + } else { + deployNeeded = true; + } + const image = await docker.engine.getImage(`${applicationId}:${tag}`); + + let imageFound = false; + try { + await image.inspect(); + imageFound = false; + } catch (error) { + // + } + if (!imageFound || deployNeeded) { + await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId); + if (buildpacks[buildPack]) + await buildpacks[buildPack]({ + buildId: build.id, + applicationId, + domain, + name, + type, + pullmergeRequestId, + buildPack, + repository, + branch, + projectId, + publishDirectory, + debug, + commit, + tag, + workdir, + docker, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets + }); + else { + saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId }); + throw new Error(`Build pack ${buildPack} not found.`); + } + deployNeeded = true; + } else { + deployNeeded = false; + saveBuildLog({ line: 'Nothing changed.', buildId, applicationId }); + } + + // Deploy to Docker Engine + try { + await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${imageId}`); + await asyncExecShell(`DOCKER_HOST=${host} docker rm ${imageId}`); + } catch (error) { + // + } + const envs = []; + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (!secret.isBuildSecret) { + envs.push(`--env ${secret.name}=${secret.value}`); + } + }); + } + const labels = makeLabelForStandaloneApplication({ + applicationId, + fqdn, + name, + type, + pullmergeRequestId, + buildPack, + repository, + branch, + projectId, + port, + commit, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory + }); + saveBuildLog({ line: 'Deployment started.', buildId, applicationId }); + const { stderr } = await asyncExecShell( + `DOCKER_HOST=${host} docker run ${envs.join()} ${labels.join( + ' ' + )} --name ${imageId} --network ${docker.network} --restart always -d ${applicationId}:${tag}` + ); + if (stderr) console.log(stderr); + saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); + + if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { + saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId }); + await configureProxyForApplication({ domain, imageId, applicationId, port }); + if (isHttps) await letsEncrypt({ domain, id: applicationId }); + await reloadHaproxy(destinationDocker.engine); + saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId }); + } else { + saveBuildLog({ + line: 'Coolify Proxy is not configured for this destination. Nothing else to do.', + buildId, + applicationId + }); + } + } +} diff --git a/src/lib/queues/cleanup.ts b/src/lib/queues/cleanup.ts new file mode 100644 index 000000000..47d2668e9 --- /dev/null +++ b/src/lib/queues/cleanup.ts @@ -0,0 +1,24 @@ +import { dev } from '$app/env'; +import { asyncExecShell, getEngine } from '$lib/common'; +import { prisma } from '$lib/database'; + +export default async function () { + if (!dev) { + const destinationDockers = await prisma.destinationDocker.findMany(); + for (const destinationDocker of destinationDockers) { + const host = getEngine(destinationDocker.engine); + try { + await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`); + } catch (error) { + // + console.log(error); + } + try { + await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`); + } catch (error) { + // + console.log(error); + } + } + } +} diff --git a/src/lib/queues/index.ts b/src/lib/queues/index.ts new file mode 100644 index 000000000..5ebef3106 --- /dev/null +++ b/src/lib/queues/index.ts @@ -0,0 +1,174 @@ +import * as Bullmq from 'bullmq'; +import { default as ProdBullmq, Job, QueueEvents, QueueScheduler } from 'bullmq'; +import cuid from 'cuid'; +import { dev } from '$app/env'; +import { prisma } from '$lib/database'; + +import builder from './builder'; +import logger from './logger'; +import cleanup from './cleanup'; +import proxy from './proxy'; +import ssl from './ssl'; +import sslrenewal from './sslrenewal'; + +import { asyncExecShell, saveBuildLog } from '$lib/common'; + +let { Queue, Worker } = Bullmq; +let redisHost = 'localhost'; + +if (!dev) { + Queue = ProdBullmq.Queue; + Worker = ProdBullmq.Worker; + redisHost = 'coolify-redis'; +} + +const connectionOptions = { + connection: { + host: redisHost + } +}; + +const cron = async () => { + new QueueScheduler('proxy', connectionOptions); + new QueueScheduler('cleanup', connectionOptions); + new QueueScheduler('ssl', connectionOptions); + new QueueScheduler('sslRenew', connectionOptions); + + const queue = { + proxy: new Queue('proxy', { ...connectionOptions }), + cleanup: new Queue('cleanup', { ...connectionOptions }), + ssl: new Queue('ssl', { ...connectionOptions }), + sslRenew: new Queue('sslRenew', { ...connectionOptions }) + }; + await queue.proxy.drain(); + await queue.cleanup.drain(); + await queue.ssl.drain(); + await queue.sslRenew.drain(); + + new Worker( + 'proxy', + async () => { + await proxy(); + }, + { + ...connectionOptions + } + ); + + new Worker( + 'ssl', + async () => { + await ssl(); + }, + { + ...connectionOptions + } + ); + + new Worker( + 'cleanup', + async () => { + await cleanup(); + }, + { + ...connectionOptions + } + ); + + new Worker( + 'sslRenew', + async () => { + await sslrenewal(); + }, + { + ...connectionOptions + } + ); + + await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } }); + // await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } }); + await queue.cleanup.add('cleanup', {}, { repeat: { every: 3600000 } }); + await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } }); + + const events = { + proxy: new QueueEvents('proxy', { ...connectionOptions }), + ssl: new QueueEvents('ssl', { ...connectionOptions }) + }; + + events.proxy.on('completed', (data) => { + // console.log(data) + }); + events.ssl.on('completed', (data) => { + // console.log(data) + }); +}; +cron().catch((error) => { + console.log('cron failed to start'); + console.log(error); +}); + +const buildQueueName = dev ? cuid() : 'build_queue'; +const buildQueue = new Queue(buildQueueName, connectionOptions); +const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), { + concurrency: 2, + ...connectionOptions +}); + +buildWorker.on('completed', async (job: Bullmq.Job) => { + try { + await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } }); + } catch (err) { + console.log(err); + } finally { + await asyncExecShell(`rm -fr ${job.data.workdir}`); + } + return; +}); + +buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => { + console.log(failedReason); + try { + await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } }); + } catch (error) { + console.log(error); + } finally { + await asyncExecShell(`rm -fr ${job.data.workdir}`); + } + saveBuildLog({ line: 'Failed build!', buildId: job.data.build_id, applicationId: job.data.id }); + saveBuildLog({ + line: `Reason: ${failedReason.toString()}`, + buildId: job.data.build_id, + applicationId: job.data.id + }); +}); + +// const letsEncryptQueueName = dev ? cuid() : 'letsencrypt_queue' +// const letsEncryptQueue = new Queue(letsEncryptQueueName, connectionOptions) + +// const letsEncryptWorker = new Worker(letsEncryptQueueName, async (job) => await letsencrypt(job), { +// concurrency: 1, +// ...connectionOptions +// }) +// letsEncryptWorker.on('completed', async () => { +// // TODO: Save letsencrypt logs as build logs! +// console.log('[DEBUG] Lets Encrypt job completed') +// }) + +// letsEncryptWorker.on('failed', async (job: Job, failedReason: string) => { +// try { +// await prisma.applicationSettings.updateMany({ where: { applicationId: job.data.id }, data: { forceSSL: false } }) +// } catch (error) { +// console.log(error) +// } +// console.log('[DEBUG] Lets Encrypt job failed') +// console.log(failedReason) +// }) + +const buildLogQueueName = dev ? cuid() : 'log_queue'; +const buildLogQueue = new Queue(buildLogQueueName, connectionOptions); +const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), { + concurrency: 1, + ...connectionOptions +}); + +export { buildQueue, buildLogQueue }; diff --git a/src/lib/queues/logger.ts b/src/lib/queues/logger.ts new file mode 100644 index 000000000..14665b9ad --- /dev/null +++ b/src/lib/queues/logger.ts @@ -0,0 +1,8 @@ +import { prisma } from '$lib/database'; +import { dev } from '$app/env'; + +export default async function (job) { + const { line, applicationId, buildId } = job.data; + if (dev) console.debug(`[${applicationId}] ${line}`); + await prisma.buildLog.create({ data: { line, buildId, time: Number(job.id), applicationId } }); +} diff --git a/src/lib/queues/proxy.ts b/src/lib/queues/proxy.ts new file mode 100644 index 000000000..90dedde80 --- /dev/null +++ b/src/lib/queues/proxy.ts @@ -0,0 +1,64 @@ +import { getDomain } from '$lib/common'; +import { prisma } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { + checkContainer, + configureCoolifyProxyOn, + configureProxyForApplication, + forceSSLOnApplication, + reloadHaproxy, + startCoolifyProxy +} from '$lib/haproxy'; +import * as db from '$lib/database'; + +export default async function () { + try { + // Check destination containers and configure proxy if needed + const destinationDockers = await prisma.destinationDocker.findMany({}); + for (const destination of destinationDockers) { + if (destination.isCoolifyProxyUsed) { + const docker = dockerInstance({ destinationDocker: destination }); + const containers = await docker.engine.listContainers(); + const configurations = containers.filter( + (container) => container.Labels['coolify.managed'] + ); + for (const configuration of configurations) { + const parsedConfiguration = JSON.parse( + Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString() + ); + if (configuration.Labels['coolify.type'] === 'standalone-application') { + const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration; + if (fqdn) { + const domain = getDomain(fqdn); + await configureProxyForApplication({ + domain, + imageId: pullmergeRequestId + ? `${applicationId}-${pullmergeRequestId}` + : applicationId, + applicationId, + port + }); + const isHttps = fqdn.startsWith('https://'); + if (isHttps) await forceSSLOnApplication({ domain }); + } + } + } + } + } + // Check Coolify FQDN and configure proxy if needed + const { fqdn } = await db.listSettings(); + if (fqdn) { + const domain = getDomain(fqdn); + const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); + if (!found) await startCoolifyProxy('/var/run/docker.sock'); + await configureCoolifyProxyOn({ domain }); + const isHttps = fqdn.startsWith('https://'); + if (isHttps) await forceSSLOnApplication({ domain }); + } + } catch (error) { + console.log(error); + throw error; + } finally { + // await reloadHaproxy('/var/run/docker.sock'); + } +} diff --git a/src/lib/queues/ssl.ts b/src/lib/queues/ssl.ts new file mode 100644 index 000000000..213788db6 --- /dev/null +++ b/src/lib/queues/ssl.ts @@ -0,0 +1,69 @@ +import { asyncExecShell, getDomain, getEngine } from '$lib/common'; +import { prisma } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { forceSSLOnApplication } from '$lib/haproxy'; +import * as db from '$lib/database'; +import { dev } from '$app/env'; + +export default async function () { + try { + const destinationDockers = await prisma.destinationDocker.findMany({}); + for (const destination of destinationDockers) { + if (destination.isCoolifyProxyUsed) { + const docker = dockerInstance({ destinationDocker: destination }); + const containers = await docker.engine.listContainers(); + const configurations = containers.filter( + (container) => container.Labels['coolify.managed'] + ); + for (const configuration of configurations) { + const parsedConfiguration = JSON.parse( + Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString() + ); + if (configuration.Labels['coolify.type'] === 'standalone-application') { + const { fqdn } = parsedConfiguration; + if (fqdn) { + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + if (isHttps) { + if (dev) { + console.log('DEV MODE: SSL is enabled'); + } else { + const host = getEngine(destination.engine); + await asyncExecShell( + `DOCKER_HOST=${host} docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email` + ); + const { stderr } = await asyncExecShell( + `DOCKER_HOST=${host} docker run --rm --name bash -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem` + ); + if (stderr) throw new Error(stderr); + } + } + } + } + } + } + } + const { fqdn } = await db.listSettings(); + if (fqdn) { + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + if (isHttps) { + if (dev) { + console.log('DEV MODE: SSL is enabled'); + } else { + await asyncExecShell( + `docker run --rm --name certbot -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email` + ); + + const { stderr } = await asyncExecShell( + `docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem` + ); + if (stderr) throw new Error(stderr); + } + } + } + } catch (error) { + console.log(error); + throw error; + } +} diff --git a/src/lib/queues/sslrenewal.ts b/src/lib/queues/sslrenewal.ts new file mode 100644 index 000000000..e16b4e3a1 --- /dev/null +++ b/src/lib/queues/sslrenewal.ts @@ -0,0 +1,11 @@ +import { asyncExecShell } from '$lib/common'; + +export default async function () { + try { + return await asyncExecShell( + `docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew` + ); + } catch (error) { + throw error; + } +} diff --git a/src/lib/request.ts b/src/lib/request.ts deleted file mode 100644 index a271422ad..000000000 --- a/src/lib/request.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { toast } from '@zerodevx/svelte-toast'; -import { browser } from '$app/env'; - -export async function request( - url, - session, - { - method, - body, - customHeaders - }: { - url?: string; - session?: any; - fetch?: any; - method?: string; - body?: any; - customHeaders?: Record; - } = {} -) { - let fetch; - if (browser) { - fetch = window.fetch; - } else { - fetch = session.fetch; - } - let headers = { 'content-type': 'application/json; charset=UTF-8' }; - if (method === 'DELETE') { - delete headers['content-type']; - } - const isGithub = url.match(/api.github.com/); - if (isGithub) { - headers = Object.assign(headers, { - Authorization: `token ${session.ghToken}` - }); - } - const config: any = { - method: method || (body ? 'POST' : 'GET'), - cache: isGithub ? 'no-cache' : 'default', - headers: { - ...headers, - ...customHeaders - } - }; - if (body) { - config.body = JSON.stringify(body); - } - const response = await fetch(url, config); - if (response.status >= 200 && response.status <= 299) { - if (response.headers.get('content-type').match(/application\/json/)) { - const json = await response.json(); - if (json?.success === false) { - browser && json.showToast !== false && toast.push(json.message); - return Promise.reject({ - status: response.status, - error: json.message - }); - } - return json; - } else if (response.headers.get('content-type').match(/text\/plain/)) { - return await response.text(); - } else if (response.headers.get('content-type').match(/multipart\/form-data/)) { - return await response.formData(); - } else { - if (response.headers.get('content-disposition')) { - const blob = await response.blob(); - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.download = response.headers.get('content-disposition').split('=')[1] || 'backup.gz'; - link.target = '_blank'; - link.setAttribute('type', 'hidden'); - document.body.appendChild(link); - link.click(); - link.remove(); - return; - } - return await response.blob(); - } - } else { - if (response.status === 401) { - browser && toast.push('Unauthorized'); - return Promise.reject({ - status: response.status, - error: 'Unauthorized' - }); - } else if (response.status >= 500) { - const error = (await response.json()).error; - browser && toast.push(error.message || error); - return Promise.reject({ - status: response.status, - error: error.message || error || 'Oops, something is not okay. Are you okay?' - }); - } else { - browser && toast.push(response.statusText); - return Promise.reject({ - status: response.status, - error: response.statusText - }); - } - } -} diff --git a/src/lib/settings.ts b/src/lib/settings.ts new file mode 100644 index 000000000..d9e7cff2c --- /dev/null +++ b/src/lib/settings.ts @@ -0,0 +1,7 @@ +export const publicPaths = [ + '/login', + '/webhooks/success', + '/webhooks/github', + '/webhooks/github/install', + '/webhooks/gitlab' +]; diff --git a/src/models/ApplicationLog.ts b/src/models/ApplicationLog.ts deleted file mode 100644 index 669fa9fb4..000000000 --- a/src/models/ApplicationLog.ts +++ /dev/null @@ -1,12 +0,0 @@ -import mongoose from 'mongoose'; -const { Schema } = mongoose; - -const ApplicationLogsSchema = new Schema({ - deployId: { type: String, required: true }, - event: { type: String, required: true } -}); - -ApplicationLogsSchema.set('timestamps', true); - -export default mongoose.models['logs-application'] || - mongoose.model('logs-application', ApplicationLogsSchema); diff --git a/src/models/Configuration.ts b/src/models/Configuration.ts deleted file mode 100644 index 6e37568fd..000000000 --- a/src/models/Configuration.ts +++ /dev/null @@ -1,59 +0,0 @@ -import mongoose from 'mongoose'; -const { Schema } = mongoose; -const ConfigurationSchema = new Schema({ - github: { - installation: { - id: { type: Number, required: true } - }, - app: { - id: { type: Number, required: true } - } - }, - repository: { - id: { type: Number, required: true }, - organization: { type: String, required: true }, - name: { type: String, required: true }, - branch: { type: String, required: true } - }, - general: { - deployId: { type: String, required: true }, - nickname: { type: String, required: true }, - workdir: { type: String, required: true }, - isPreviewDeploymentEnabled: { type: Boolean, required: true, default: false }, - pullRequest: { type: Number, required: true, default: 0 } - }, - build: { - pack: { type: String, required: true }, - directory: { type: String }, - command: { - build: { type: String }, - installation: { type: String }, - start: { type: String }, - python: { - module: { type: String }, - instance: { type: String } - } - }, - container: { - name: { type: String, required: true }, - tag: { type: String, required: true }, - baseSHA: { type: String, required: true } - } - }, - publish: { - directory: { type: String }, - domain: { type: String, required: true }, - path: { type: String }, - port: { type: Number }, - secrets: [{ - name: { type: String }, - value: { type: String }, - isBuild: { type: Boolean, default: false }, - }] - } -}); - -ConfigurationSchema.set('timestamps', true); - -export default mongoose.models['configuration'] || - mongoose.model('configuration', ConfigurationSchema); diff --git a/src/models/Deployment.ts b/src/models/Deployment.ts deleted file mode 100644 index 94a473539..000000000 --- a/src/models/Deployment.ts +++ /dev/null @@ -1,17 +0,0 @@ -import mongoose from 'mongoose'; -const { Schema } = mongoose; - -const DeploymentSchema = new Schema({ - deployId: { type: String, required: true }, - nickname: { type: String, required: true }, - repoId: { type: Number, required: true }, - organization: { type: String, required: true }, - name: { type: String, required: true }, - branch: { type: String, required: true }, - domain: { type: String, required: true }, - progress: { type: String, require: true, default: 'queued' } -}); - -DeploymentSchema.set('timestamps', true); - -export default mongoose.models['deployment'] || mongoose.model('deployment', DeploymentSchema); diff --git a/src/models/ServerLog.ts b/src/models/ServerLog.ts deleted file mode 100644 index 8949c22af..000000000 --- a/src/models/ServerLog.ts +++ /dev/null @@ -1,23 +0,0 @@ -import mongoose from 'mongoose'; -import { version } from '../../package.json'; -const { Schema, Document } = mongoose; - -// export interface ILogsServer extends Document { -// version: string; -// type: string; -// message: string; -// stack: string; -// seen: Boolean; -// } - -const LogsServerSchema = new Schema({ - version: { type: String, default: version }, - type: { type: String, required: true }, - message: { type: String, required: true }, - stack: { type: String }, - seen: { type: Boolean, default: false } -}); - -LogsServerSchema.set('timestamps', { createdAt: 'createdAt', updatedAt: false }); - -export default mongoose.models['logs-server'] || mongoose.model('logs-server', LogsServerSchema); diff --git a/src/models/Settings.ts b/src/models/Settings.ts deleted file mode 100644 index fcc1fa817..000000000 --- a/src/models/Settings.ts +++ /dev/null @@ -1,17 +0,0 @@ -import mongoose from 'mongoose'; -const { Schema } = mongoose; -export interface ISettings extends Document { - applicationName: string; - allowRegistration: string; - sendErrors: boolean; -} - -const SettingsSchema = new Schema({ - applicationName: { type: String, required: true, default: 'coolify' }, - allowRegistration: { type: Boolean, required: true, default: false }, - sendErrors: { type: Boolean, required: true, default: true } -}); - -SettingsSchema.set('timestamps', true); - -export default mongoose.models['settings'] || mongoose.model('settings', SettingsSchema); diff --git a/src/models/User.ts b/src/models/User.ts deleted file mode 100644 index 1da375cf6..000000000 --- a/src/models/User.ts +++ /dev/null @@ -1,21 +0,0 @@ -import mongoose from 'mongoose'; -const { Schema } = mongoose; -export interface IUser extends Document { - email: string; - avatar?: string; - uid: string; - type: string; - password: string; -} - -const UserSchema = new Schema({ - email: { type: String, required: true, unique: true }, - avatar: { type: String }, - uid: { type: String, required: true }, - type: { type: String, required: true, default: 'github' }, - password: { type: String } -}); - -UserSchema.set('timestamps', true); - -export default mongoose.models['user'] || mongoose.model('user', UserSchema); diff --git a/src/routes/__error.svelte b/src/routes/__error.svelte index 62f45fc13..6e9d9f184 100644 --- a/src/routes/__error.svelte +++ b/src/routes/__error.svelte @@ -1,15 +1,29 @@ -

    {error}

    \ No newline at end of file +
    +
    {status}
    +
    Ooops you are lost! But don't be afraid!
    +
    + You can find your way back here +
    +
    +
    {error.message} {error.stack}
    +
    +
    diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 46aa43f5d..b1aec030a 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -1,408 +1,568 @@ - - + + Coolify + + +{#if $session.uid} + + +{/if} +
    -{#if upgradeAvailable && $page.path !== '/success' && $page.path !== '/'} -
    -
    -
    -
    - {#if !upgradeDisabled} - - {:else if upgradeDone} - - {:else} - - {/if} -
    -
    -{/if} diff --git a/src/routes/api/v1/application/check/index.ts b/src/routes/api/v1/application/check/index.ts deleted file mode 100644 index a90153700..000000000 --- a/src/routes/api/v1/application/check/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { setDefaultConfiguration } from '$lib/api/applications/configuration'; -import { saveServerLog } from '$lib/api/applications/logging'; -import Configuration from '$models/Configuration'; -import type { Request } from '@sveltejs/kit'; - -export async function post(request: Request) { - try { - const { DOMAIN } = process.env; - const configuration = setDefaultConfiguration(request.body); - const sameDomainAndPath = await Configuration.find({ - 'publish.path': configuration.publish.path, - 'publish.domain': configuration.publish.domain - }).select('-_id -__v -createdAt -updatedAt'); - if (sameDomainAndPath.length > 1 || configuration.publish.domain === DOMAIN) { - return { - status: 200, - body: { - success: false, - message: 'Domain/path are already in use.' - } - }; - } - return { - status: 200, - body: { success: true, message: 'OK' } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/application/config/index.ts b/src/routes/api/v1/application/config/index.ts deleted file mode 100644 index fe413395c..000000000 --- a/src/routes/api/v1/application/config/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { docker } from '$lib/api/docker'; -import Configuration from '$models/Configuration'; -import type { Request } from '@sveltejs/kit'; - -export async function post(request: Request) { - const { nickname }: any = request.body || {}; - if (nickname) { - const configurationFound = await Configuration.find({ - 'general.nickname': nickname - }).select('-_id -__v -createdAt -updatedAt'); - if (configurationFound) { - return { - status: 200, - body: { - configuration: [...configurationFound] - } - }; - } - - const services = await docker.engine.listServices(); - const applications = services.filter( - (r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' - ); - const found = applications.find((r) => { - const configuration = r.Spec.Labels.configuration - ? JSON.parse(r.Spec.Labels.configuration) - : null; - - if (configuration.general.nickname === nickname) return r; - return null; - }); - - if (found) { - return { - status: 200, - body: { - success: true, - ...JSON.parse(found.Spec.Labels.configuration) - } - }; - } - return { - status: 500, - body: { - error: 'No configuration found.' - } - }; - } -} diff --git a/src/routes/api/v1/application/config/previewDeployment.ts b/src/routes/api/v1/application/config/previewDeployment.ts deleted file mode 100644 index 095e31e02..000000000 --- a/src/routes/api/v1/application/config/previewDeployment.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { updateServiceLabels } from '$lib/api/applications/configuration'; -import { execShellAsync } from '$lib/api/common'; -import { docker } from '$lib/api/docker'; -import ApplicationLog from '$models/ApplicationLog'; -import Configuration from '$models/Configuration'; -import Deployment from '$models/Deployment'; -import type { Request } from '@sveltejs/kit'; - -export async function post(request: Request) { - const { name, organization, branch, isPreviewDeploymentEnabled }: any = request.body || {}; - if (name && organization && branch) { - const configuration = await Configuration.findOneAndUpdate( - { - 'repository.name': name, - 'repository.organization': organization, - 'repository.branch': branch - }, - { - $set: { - 'general.isPreviewDeploymentEnabled': isPreviewDeploymentEnabled, - 'general.pullRequest': 0 - } - }, - { new: true } - ).select('-_id -__v -createdAt -updatedAt'); - if (!isPreviewDeploymentEnabled) { - const found = await Configuration.find({ - 'repository.name': name, - 'repository.organization': organization, - 'repository.branch': branch, - 'general.pullRequest': { $ne: 0 } - }); - for (const prDeployment of found) { - await Configuration.findOneAndRemove({ - 'repository.name': name, - 'repository.organization': organization, - 'repository.branch': branch, - 'publish.domain': prDeployment.publish.domain - }); - const deploys = await Deployment.find({ - organization, - branch, - name, - domain: prDeployment.publish.domain - }); - for (const deploy of deploys) { - await ApplicationLog.deleteMany({ deployId: deploy.deployId }); - await Deployment.deleteMany({ deployId: deploy.deployId }); - } - await execShellAsync(`docker stack rm ${prDeployment.build.container.name}`); - } - return { - status: 200, - body: { - organization, - name, - branch - } - }; - } - updateServiceLabels(configuration); - return { - status: 200, - body: { - success: true - } - }; - } - return { - status: 500, - body: { - error: 'Cannot save.' - } - }; -} diff --git a/src/routes/api/v1/application/deploy/index.ts b/src/routes/api/v1/application/deploy/index.ts deleted file mode 100644 index bb9b1a5d7..000000000 --- a/src/routes/api/v1/application/deploy/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import Deployment from '$models/Deployment'; -import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration'; -import cloneRepository from '$lib/api/applications/cloneRepository'; -import { cleanupTmp } from '$lib/api/common'; -import queueAndBuild from '$lib/api/applications/queueAndBuild'; -import Configuration from '$models/Configuration'; -import preChecks from '$lib/api/applications/preChecks'; -import preTasks from '$lib/api/applications/preTasks'; - -export async function post(request: Request) { - const configuration = setDefaultConfiguration(request.body); - if (!configuration) { - return { - status: 500, - body: { - error: 'Whaaat?' - } - }; - } - try { - await cloneRepository(configuration); - const nextStep = await preChecks(configuration); - if (nextStep === 0) { - cleanupTmp(configuration.general.workdir); - return { - status: 200, - body: { - success: false, - message: 'Nothing changed, no need to redeploy.' - } - }; - } - await preTasks(configuration) - - queueAndBuild(configuration, nextStep); - return { - status: 201, - body: { - message: 'Deployment queued.', - nickname: configuration.general.nickname, - name: configuration.build.container.name, - deployId: configuration.general.deployId - } - }; - } catch (error) { - console.log(error); - await Deployment.findOneAndUpdate({ nickname: configuration.general.nickname }, { $set: { progress: 'failed' } }); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/application/deploy/logs/[deployId].ts b/src/routes/api/v1/application/deploy/logs/[deployId].ts deleted file mode 100644 index 0c449b224..000000000 --- a/src/routes/api/v1/application/deploy/logs/[deployId].ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import ApplicationLog from '$models/ApplicationLog'; -import Deployment from '$models/Deployment'; -import dayjs from 'dayjs'; - -export async function get(request: Request) { - const { deployId } = request.params; - try { - const logs: any = await ApplicationLog.find({ deployId }) - .select('-_id -__v') - .sort({ createdAt: 'asc' }); - - const deploy: any = await Deployment.findOne({ deployId }).select('-_id -__v'); - const finalLogs: any = {}; - finalLogs.progress = deploy.progress; - finalLogs.events = logs.map((log) => log.event); - finalLogs.human = dayjs(deploy.updatedAt).from(dayjs(deploy.updatedAt)); - return { - status: 200, - body: { - ...finalLogs - } - }; - } catch (error) { - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/application/deploy/logs/index.ts b/src/routes/api/v1/application/deploy/logs/index.ts deleted file mode 100644 index e7f84d805..000000000 --- a/src/routes/api/v1/application/deploy/logs/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc.js'; -import relativeTime from 'dayjs/plugin/relativeTime.js'; -import Deployment from '$models/Deployment'; -dayjs.extend(utc); -dayjs.extend(relativeTime); -export async function get(request: Request) { - try { - const repoId = request.query.get('repoId'); - const branch = request.query.get('branch'); - const page = request.query.get('page'); - const onePage = 5; - const show = Number(page) * onePage || 5; - const deploy: any = await Deployment.find({ repoId, branch }) - .select('-_id -__v -repoId') - .sort({ createdAt: 'desc' }) - .limit(show); - const finalLogs = deploy.map((d) => { - const finalLogs = { ...d._doc }; - const updatedAt = dayjs(d.updatedAt).utc(); - finalLogs.took = updatedAt.diff(dayjs(d.createdAt)) / 1000; - finalLogs.since = updatedAt.fromNow(); - finalLogs.isPr = d.domain.startsWith('pr'); - return finalLogs; - }); - return { - status: 200, - body: { - success: true, - logs: finalLogs - } - }; - } catch (error) { - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/application/logs/index.ts b/src/routes/api/v1/application/logs/index.ts deleted file mode 100644 index 01f478d16..000000000 --- a/src/routes/api/v1/application/logs/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { docker } from '$lib/api/docker'; -import type { Request } from '@sveltejs/kit'; - -export async function get(request: Request) { - try { - const name = request.query.get('name'); - const service = await docker.engine.getService(`${name}_${name}`); - const logs = (await service.logs({ stdout: true, stderr: true, timestamps: true })) - .toString() - .split('\n') - .map((l) => l.slice(8)) - .filter((a) => a); - return { - status: 200, - body: { success: true, logs } - }; - } catch (error) { - console.log(error); - await saveServerLog(error); - return { - status: 500, - body: { - error: 'No such service. Is it under deployment?' - } - }; - } -} diff --git a/src/routes/api/v1/application/remove/index.ts b/src/routes/api/v1/application/remove/index.ts deleted file mode 100644 index b4b3b512a..000000000 --- a/src/routes/api/v1/application/remove/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { purgeImagesContainers } from '$lib/api/applications/cleanup'; -import Deployment from '$models/Deployment'; -import ApplicationLog from '$models/ApplicationLog'; -import { delay, execShellAsync } from '$lib/api/common'; -import Configuration from '$models/Configuration'; - -export async function post(request: Request) { - const { nickname } = request.body; - try { - const configurationFound = await Configuration.findOne({ - 'general.nickname': nickname - }); - if (configurationFound) { - const id = configurationFound._id; - if (configurationFound?.general?.pullRequest === 0) { - // Main deployment deletion request; deleting main + PRs - const allConfiguration = await Configuration.find({ - 'publish.domain': { $regex: `.*${configurationFound.publish.domain}`, $options: 'i' }, - 'publish.path': configurationFound.publish.path - }); - for (const config of allConfiguration) { - await execShellAsync(`docker stack rm ${config.build.container.name}`); - } - await Configuration.deleteMany({ - 'publish.domain': { $regex: `.*${configurationFound.publish.domain}`, $options: 'i' }, - 'publish.path': configurationFound.publish.path - }); - const deploys = await Deployment.find({ nickname }); - for (const deploy of deploys) { - await ApplicationLog.deleteMany({ deployId: deploy.deployId }); - await Deployment.deleteMany({ deployId: deploy.deployId }); - } - } else { - // Delete only PRs - await Configuration.findByIdAndRemove(id); - await execShellAsync(`docker stack rm ${configurationFound.build.container.name}`); - const deploys = await Deployment.find({ nickname }); - for (const deploy of deploys) { - await ApplicationLog.deleteMany({ deployId: deploy.deployId }); - await Deployment.deleteMany({ deployId: deploy.deployId }); - } - } - } - - return { - status: 200, - body: {} - }; - } catch (error) { - console.log(error); - return { - status: 500, - error: { - message: 'Nothing to do.' - } - }; - } -} diff --git a/src/routes/api/v1/dashboard/index.ts b/src/routes/api/v1/dashboard/index.ts deleted file mode 100644 index fe1ab230c..000000000 --- a/src/routes/api/v1/dashboard/index.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { docker } from '$lib/api/docker'; -import type { Request } from '@sveltejs/kit'; -import Configuration from '$models/Configuration'; -export async function get(request: Request) { - // Should update this to get data from mongodb and update db with the currently running services on start! - const dockerServices = await docker.engine.listServices(); - let databases: any = dockerServices.filter( - (r) => - r.Spec.Labels.managedBy === 'coolify' && - r.Spec.Labels.type === 'database' && - r.Spec.Labels.configuration - ); - let services: any = dockerServices.filter( - (r) => - r.Spec.Labels.managedBy === 'coolify' && - r.Spec.Labels.type === 'service' && - r.Spec.Labels.configuration - ); - databases = databases.map((r) => { - if (JSON.parse(r.Spec.Labels.configuration)) { - return { - configuration: JSON.parse(r.Spec.Labels.configuration) - }; - } - return {}; - }); - services = services.map((r) => { - if (JSON.parse(r.Spec.Labels.configuration)) { - return { - serviceName: r.Spec.Labels.serviceName, - configuration: JSON.parse(r.Spec.Labels.configuration) - }; - } - return {}; - }); - const configurations = await Configuration.find({ - 'general.pullRequest': { $in: [null, 0] } - }).select('-_id -__v -createdAt'); - const applications = []; - for (const configuration of configurations) { - const foundPRDeployments = await Configuration.find({ - 'repository.id': configuration.repository.id, - 'repository.branch': configuration.repository.branch, - 'general.pullRequest': { $ne: 0 } - }).select('-_id -__v -createdAt'); - const payload = { - configuration, - UpdatedAt: configuration.updatedAt, - prBuilds: foundPRDeployments.length > 0 ? true : false - }; - applications.push(payload); - } - return { - status: 200, - body: { - success: true, - applications: { - deployed: applications - }, - databases: { - deployed: databases - }, - services: { - deployed: services - } - } - }; -} diff --git a/src/routes/api/v1/databases/[deployId]/backup.ts b/src/routes/api/v1/databases/[deployId]/backup.ts deleted file mode 100644 index f8e6f4cbc..000000000 --- a/src/routes/api/v1/databases/[deployId]/backup.ts +++ /dev/null @@ -1,146 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import { saveServerLog } from '$lib/api/applications/logging'; -import { execShellAsync } from '$lib/api/common'; -import { docker } from '$lib/api/docker'; -import fs from 'fs'; - -export async function post(request: Request) { - const tmpdir = '/tmp/backups'; - const { deployId } = request.params; - try { - const now = new Date(); - const configuration = JSON.parse( - JSON.parse(await execShellAsync(`docker inspect ${deployId}_${deployId}`))[0].Spec.Labels - .configuration - ); - const type = configuration.general.type; - const serviceId = configuration.general.deployId; - const databaseService = (await docker.engine.listContainers()).find( - (r) => r.Labels['com.docker.stack.namespace'] === serviceId && r.State === 'running' - ); - const containerID = databaseService.Labels['com.docker.swarm.task.name']; - await execShellAsync(`mkdir -p ${tmpdir}`); - if (type === 'mongodb') { - if (databaseService) { - const username = configuration.database.usernames[0]; - const password = configuration.database.passwords[1]; - const databaseName = configuration.database.defaultDatabaseName; - const filename = `${databaseName}_${now.getTime()}.gz`; - const fullfilename = `${tmpdir}/${filename}`; - await execShellAsync( - `docker exec -i ${containerID} /bin/bash -c "mkdir -p ${tmpdir};mongodump --uri='mongodb://${username}:${password}@${deployId}:27017' -d ${databaseName} --gzip --archive=${fullfilename}"` - ); - await execShellAsync(`docker cp ${containerID}:${fullfilename} ${fullfilename}`); - await execShellAsync(`docker exec -i ${containerID} /bin/bash -c "rm -f ${fullfilename}"`); - return { - status: 200, - headers: { - 'Content-Type': 'application/octet-stream', - 'Content-Transfer-Encoding': 'binary', - 'Content-Disposition': `attachment; filename=${filename}` - }, - body: fs.readFileSync(`${fullfilename}`) - }; - } - } else if (type === 'postgresql') { - if (databaseService) { - const username = configuration.database.usernames[0]; - const password = configuration.database.passwords[0]; - const databaseName = configuration.database.defaultDatabaseName; - const filename = `${databaseName}_${now.getTime()}.sql.gz`; - const fullfilename = `${tmpdir}/${filename}`; - await execShellAsync( - `docker exec -i ${containerID} /bin/bash -c "PGPASSWORD=${password} pg_dump --username ${username} -Z 9 ${databaseName}" > ${fullfilename}` - ); - return { - status: 200, - headers: { - 'Content-Type': 'application/octet-stream', - 'Content-Transfer-Encoding': 'binary', - 'Content-Disposition': `attachment; filename=${filename}` - }, - body: fs.readFileSync(`${fullfilename}`) - }; - } - } else if (type === 'couchdb') { - if (databaseService) { - const databaseName = configuration.database.defaultDatabaseName; - const filename = `${databaseName}_${now.getTime()}.tar.gz`; - const fullfilename = `${tmpdir}/${filename}`; - await execShellAsync( - `docker exec -i ${containerID} /bin/bash -c "cd /bitnami/couchdb/data/ && tar -czvf - ." > ${fullfilename}` - ); - return { - status: 200, - headers: { - 'Content-Type': 'application/octet-stream', - 'Content-Transfer-Encoding': 'binary', - 'Content-Disposition': `attachment; filename=${filename}` - }, - body: fs.readFileSync(`${fullfilename}`) - }; - } - } else if (type === 'mysql') { - if (databaseService) { - const username = configuration.database.usernames[0]; - const password = configuration.database.passwords[0]; - const databaseName = configuration.database.defaultDatabaseName; - const filename = `${databaseName}_${now.getTime()}.sql.gz`; - const fullfilename = `${tmpdir}/${filename}`; - await execShellAsync( - `docker exec -i ${containerID} /bin/bash -c "mysqldump -u ${username} -p${password} ${databaseName} | gzip -9 -" > ${fullfilename}` - ); - return { - status: 200, - headers: { - 'Content-Type': 'application/octet-stream', - 'Content-Transfer-Encoding': 'binary', - 'Content-Disposition': `attachment; filename=${filename}` - }, - body: fs.readFileSync(`${fullfilename}`) - }; - } - } else if (type === 'redis') { - if (databaseService) { - const password = configuration.database.passwords[0]; - const databaseName = configuration.database.defaultDatabaseName; - const filename = `${databaseName}_${now.getTime()}.rdb`; - const fullfilename = `${tmpdir}/${filename}`; - await execShellAsync( - `docker exec -i ${containerID} /bin/bash -c "redis-cli --pass ${password} save"` - ); - await execShellAsync( - `docker cp ${containerID}:/bitnami/redis/data/dump.rdb ${fullfilename}` - ); - await execShellAsync( - `docker exec -i ${containerID} /bin/bash -c "rm -f /bitnami/redis/data/dump.rdb"` - ); - return { - status: 200, - headers: { - 'Content-Type': 'application/octet-stream', - 'Content-Transfer-Encoding': 'binary', - 'Content-Disposition': `attachment; filename=${filename}` - }, - body: fs.readFileSync(`${fullfilename}`) - }; - } - } - return { - status: 501, - body: { - error: `Backup method not implemented yet for ${type}.` - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } finally { - await execShellAsync(`rm -fr ${tmpdir}`); - } -} diff --git a/src/routes/api/v1/databases/[deployId]/index.ts b/src/routes/api/v1/databases/[deployId]/index.ts deleted file mode 100644 index a7a5ab7ab..000000000 --- a/src/routes/api/v1/databases/[deployId]/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { execShellAsync } from '$lib/api/common'; -import { docker } from '$lib/api/docker'; -import type { Request } from '@sveltejs/kit'; - -export async function del(request: Request) { - const { deployId } = request.params; - await execShellAsync(`docker stack rm ${deployId}`); - return { - status: 200, - body: {} - }; -} -export async function get(request: Request) { - const { deployId } = request.params; - - try { - const database = (await docker.engine.listServices()).find( - (r) => - r.Spec.Labels.managedBy === 'coolify' && - r.Spec.Labels.type === 'database' && - JSON.parse(r.Spec.Labels.configuration).general.deployId === deployId - ); - - if (database) { - const jsonEnvs = {}; - if (database.Spec.TaskTemplate.ContainerSpec.Env) { - for (const d of database.Spec.TaskTemplate.ContainerSpec.Env) { - const s = d.split('='); - jsonEnvs[s[0]] = s[1]; - } - } - const payload = { - config: JSON.parse(database.Spec.Labels.configuration), - envs: jsonEnvs || null - }; - - return { - status: 200, - body: { - ...payload - } - }; - } else { - return { - status: 500, - body: { - error: 'No database found.' - } - }; - } - } catch (error) { - return { - status: 500, - body: { - error: 'No database found.' - } - }; - } -} diff --git a/src/routes/api/v1/databases/deploy.ts b/src/routes/api/v1/databases/deploy.ts deleted file mode 100644 index 4aed627a2..000000000 --- a/src/routes/api/v1/databases/deploy.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { docker } from '$lib/api/docker'; -import type { Request } from '@sveltejs/kit'; -import yaml from 'js-yaml'; -import { promises as fs } from 'fs'; -import cuid from 'cuid'; -import generator from 'generate-password'; -import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator'; -import { execShellAsync } from '$lib/api/common'; - -function getUniq() { - return uniqueNamesGenerator({ dictionaries: [adjectives, animals, colors], length: 2 }); -} - -export async function post(request: Request) { - try { - const { type } = request.body; - let { defaultDatabaseName } = request.body; - const passwords = generator.generateMultiple(2, { - length: 24, - numbers: true, - strict: true - }); - const usernames = generator.generateMultiple(2, { - length: 10, - numbers: true, - strict: true - }); - // TODO: Query for existing db with the same name - const nickname = getUniq(); - - if (!defaultDatabaseName) defaultDatabaseName = nickname; - - const deployId = cuid(); - const configuration = { - general: { - workdir: `/tmp/${deployId}`, - deployId, - nickname, - type - }, - database: { - usernames, - passwords, - defaultDatabaseName - }, - deploy: { - name: nickname - } - }; - await execShellAsync(`mkdir -p ${configuration.general.workdir}`); - let generateEnvs = {}; - let image = null; - let volume = null; - let ulimits = {}; - if (type === 'mongodb') { - generateEnvs = { - MONGODB_ROOT_PASSWORD: passwords[0], - MONGODB_USERNAME: usernames[0], - MONGODB_PASSWORD: passwords[1], - MONGODB_DATABASE: defaultDatabaseName - }; - image = 'bitnami/mongodb:4.4'; - volume = `${configuration.general.deployId}-${type}-data:/bitnami/mongodb`; - } else if (type === 'postgresql') { - generateEnvs = { - POSTGRESQL_PASSWORD: passwords[0], - POSTGRESQL_USERNAME: usernames[0], - POSTGRESQL_DATABASE: defaultDatabaseName - }; - image = 'bitnami/postgresql:13.2.0'; - volume = `${configuration.general.deployId}-${type}-data:/bitnami/postgresql`; - } else if (type === 'couchdb') { - generateEnvs = { - COUCHDB_PASSWORD: passwords[0], - COUCHDB_USER: usernames[0] - }; - image = 'bitnami/couchdb:3'; - volume = `${configuration.general.deployId}-${type}-data:/bitnami/couchdb`; - } else if (type === 'mysql') { - generateEnvs = { - MYSQL_ROOT_PASSWORD: passwords[0], - MYSQL_ROOT_USER: usernames[0], - MYSQL_USER: usernames[1], - MYSQL_PASSWORD: passwords[1], - MYSQL_DATABASE: defaultDatabaseName - }; - image = 'bitnami/mysql:8.0'; - volume = `${configuration.general.deployId}-${type}-data:/bitnami/mysql/data`; - } else if (type === 'clickhouse') { - image = 'yandex/clickhouse-server'; - volume = `${configuration.general.deployId}-${type}-data:/var/lib/clickhouse`; - ulimits = { - nofile: { - soft: 262144, - hard: 262144 - } - }; - } else if (type === 'redis') { - image = 'bitnami/redis'; - volume = `${configuration.general.deployId}-${type}-data:/bitnami/redis/data`; - generateEnvs = { - REDIS_PASSWORD: passwords[0] - }; - } - - const stack = { - version: '3.8', - services: { - [configuration.general.deployId]: { - image, - networks: [`${docker.network}`], - environment: generateEnvs, - volumes: [volume], - ulimits, - deploy: { - replicas: 1, - update_config: { - parallelism: 0, - delay: '10s', - order: 'start-first' - }, - rollback_config: { - parallelism: 0, - delay: '10s', - order: 'start-first' - }, - labels: [ - 'managedBy=coolify', - 'type=database', - 'configuration=' + JSON.stringify(configuration) - ] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - }, - volumes: { - [`${configuration.general.deployId}-${type}-data`]: { - external: true - } - } - }; - await fs.writeFile(`${configuration.general.workdir}/stack.yml`, yaml.dump(stack)); - await execShellAsync( - `cat ${configuration.general.workdir}/stack.yml | docker stack deploy -c - ${configuration.general.deployId}` - ); - return { - status: 201, - body: { - message: 'Deployed.' - } - }; - } catch (error) { - console.log(error); - await saveServerLog(error); - return { - status: 500, - body: { - error - } - }; - } -} diff --git a/src/routes/api/v1/login/email.ts b/src/routes/api/v1/login/email.ts deleted file mode 100644 index 14f3206ba..000000000 --- a/src/routes/api/v1/login/email.ts +++ /dev/null @@ -1,104 +0,0 @@ -import mongoose from 'mongoose'; -import Settings from '$models/Settings'; -import User from '$models/User'; -import bcrypt from 'bcrypt'; -import cuid from 'cuid'; -import jsonwebtoken from 'jsonwebtoken'; -import type { Request } from '@sveltejs/kit'; - -const saltRounds = 15; - -export async function post(request: Request) { - const { email, password } = request.body; - const { JWT_SIGN_KEY } = process.env; - const settings = await Settings.findOne({ applicationName: 'coolify' }); - const registeredUsers = await User.find().countDocuments(); - const foundUser = await User.findOne({ email }); - try { - let uid = cuid(); - if (foundUser) { - if (foundUser.type === 'github') { - return { - status: 500, - body: { - error: 'Wrong password or email address.' - } - }; - } - uid = foundUser.uid; - if (!(await bcrypt.compare(password, foundUser.password))) { - return { - status: 500, - body: { - error: 'Wrong password or email address.' - } - }; - } - } else { - if (registeredUsers === 0) { - const newUser = new User({ - _id: new mongoose.Types.ObjectId(), - email, - uid, - type: 'email', - password: await bcrypt.hash(password, saltRounds) - }); - const defaultSettings = new Settings({ - _id: new mongoose.Types.ObjectId() - }); - try { - await newUser.save(); - await defaultSettings.save(); - } catch (error) { - return { - status: 500, - error: error.message || error - }; - } - } else { - if (!settings?.allowRegistration) { - return { - status: 500, - body: { - error: 'Registration disabled, enable it in settings.' - } - }; - } else { - const newUser = new User({ - _id: new mongoose.Types.ObjectId(), - email, - uid, - type: 'email', - password: await bcrypt.hash(password, saltRounds) - }); - try { - await newUser.save(); - } catch (error) { - return { - status: 500, - error: error.message || error - }; - } - } - } - } - const coolToken = jsonwebtoken.sign({}, JWT_SIGN_KEY, { - expiresIn: 15778800, - algorithm: 'HS256', - audience: 'coolLabs', - issuer: 'coolLabs', - jwtid: uid, - subject: `User:${uid}`, - notBefore: -1000 - }); - request.locals.session.data = { coolToken, ghToken: null }; - return { - status: 200, - body: { - message: 'Successfully logged in.' - } - }; - } catch (error) { - return { status: 500, body: { error: error.message || error } }; - } -} diff --git a/src/routes/api/v1/login/github/app.ts b/src/routes/api/v1/login/github/app.ts deleted file mode 100644 index 175ee2ab3..000000000 --- a/src/routes/api/v1/login/github/app.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import mongoose from 'mongoose'; -import User from '$models/User'; -import Settings from '$models/Settings'; -import cuid from 'cuid'; -import jsonwebtoken from 'jsonwebtoken'; -import { githubAPI } from '$lib/api/github'; - -export async function get(request: Request) { - const code = request.query.get('code'); - const { GITHUB_APP_CLIENT_SECRET, JWT_SIGN_KEY, VITE_GITHUB_APP_CLIENTID } = process.env; - try { - let uid = cuid(); - const { access_token } = await ( - await fetch( - `https://github.com/login/oauth/access_token?client_id=${VITE_GITHUB_APP_CLIENTID}&client_secret=${GITHUB_APP_CLIENT_SECRET}&code=${code}`, - { headers: { accept: 'application/json' } } - ) - ).json(); - const { avatar_url } = await (await githubAPI(request, '/user', access_token)).body; - const email = (await githubAPI(request, '/user/emails', access_token)).body.filter( - (e) => e.primary - )[0].email; - const settings = await Settings.findOne({ applicationName: 'coolify' }); - const registeredUsers = await User.find().countDocuments(); - const foundUser = await User.findOne({ email }); - if (foundUser) { - await User.findOneAndUpdate({ email }, { avatar: avatar_url }, { upsert: true, new: true }); - uid = foundUser.uid; - } else { - if (registeredUsers === 0) { - const newUser = new User({ - _id: new mongoose.Types.ObjectId(), - email, - avatar: avatar_url, - uid, - type: 'github' - }); - const defaultSettings = new Settings({ - _id: new mongoose.Types.ObjectId() - }); - try { - await newUser.save(); - await defaultSettings.save(); - } catch (error) { - return { - status: 500, - error: error.message || error - }; - } - } else { - if (!settings && registeredUsers > 0) { - return { - status: 500, - body: { - error: 'Registration disabled, enable it in settings.' - } - }; - } else { - if (!settings.allowRegistration) { - return { - status: 500, - body: { - error: 'You are not allowed here!' - } - }; - } else { - const newUser = new User({ - _id: new mongoose.Types.ObjectId(), - email, - avatar: avatar_url, - uid, - type: 'github' - }); - try { - await newUser.save(); - } catch (error) { - return { - status: 500, - body: { - error: error.message || error - } - }; - } - } - } - } - } - const coolToken = jsonwebtoken.sign({}, JWT_SIGN_KEY, { - expiresIn: 15778800, - algorithm: 'HS256', - audience: 'coolLabs', - issuer: 'coolLabs', - jwtid: uid, - subject: `User:${uid}`, - notBefore: -1000 - }); - request.locals.session.data = { coolToken, ghToken: access_token }; - return { - status: 302, - headers: { - location: `/success` - } - }; - } catch (error) { - return { status: 500, body: { error: error.message || error } }; - } -} diff --git a/src/routes/api/v1/logout/index.ts b/src/routes/api/v1/logout/index.ts deleted file mode 100644 index c06d54eab..000000000 --- a/src/routes/api/v1/logout/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -export async function del(request: Request) { - request.locals.session.destroy() - return { - body: { - ok: true - } - }; -} diff --git a/src/routes/api/v1/servers/cleanups/caches.ts b/src/routes/api/v1/servers/cleanups/caches.ts deleted file mode 100644 index 60d37da55..000000000 --- a/src/routes/api/v1/servers/cleanups/caches.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { execShellAsync } from '$lib/api/common'; -import type { Request } from '@sveltejs/kit'; - -export async function post(request: Request) { - try { - const output = await execShellAsync('docker builder prune -af'); - return { - status: 200, - body: { - message: 'OK', - output: output - .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '') - .split('\n') - .pop() - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/servers/cleanups/containers.ts b/src/routes/api/v1/servers/cleanups/containers.ts deleted file mode 100644 index af0981d1e..000000000 --- a/src/routes/api/v1/servers/cleanups/containers.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { execShellAsync } from '$lib/api/common'; -import type { Request } from '@sveltejs/kit'; - -export async function post(request: Request) { - try { - const output = await execShellAsync('docker container prune -f'); - return { - status: 200, - body: { - message: 'OK', - output: output - .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '') - .split('\n') - .pop() - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/servers/cleanups/images.ts b/src/routes/api/v1/servers/cleanups/images.ts deleted file mode 100644 index a139de2b8..000000000 --- a/src/routes/api/v1/servers/cleanups/images.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { execShellAsync } from '$lib/api/common'; -import type { Request } from '@sveltejs/kit'; - -export async function post(request: Request) { - try { - const output = await execShellAsync('docker image prune -af'); - return { - status: 200, - body: { - message: 'OK', - output: output - .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '') - .split('\n') - .pop() - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/servers/cleanups/volumes.ts b/src/routes/api/v1/servers/cleanups/volumes.ts deleted file mode 100644 index 8f02ee745..000000000 --- a/src/routes/api/v1/servers/cleanups/volumes.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { execShellAsync } from '$lib/api/common'; -import type { Request } from '@sveltejs/kit'; - -export async function post(request: Request) { - try { - const output = await execShellAsync('docker volume prune -f'); - return { - status: 200, - body: { - message: 'OK', - output: output - .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '') - .split('\n') - .pop() - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/servers/index.ts b/src/routes/api/v1/servers/index.ts deleted file mode 100644 index cece273aa..000000000 --- a/src/routes/api/v1/servers/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { execShellAsync } from '$lib/api/common'; -import { docker } from '$lib/api/docker'; -import type { Request } from '@sveltejs/kit'; -import systeminformation from 'systeminformation'; - -export async function get(request: Request) { - try { - const df = await execShellAsync(`docker system df --format '{{ json . }}'`); - const dockerReclaimable = df - .split('\n') - .filter((n) => n) - .map((s) => JSON.parse(s)); - - return { - status: 200, - body: { - hostname: await (await systeminformation.osInfo()).hostname, - filesystems: await ( - await systeminformation.fsSize() - ).filter((fs) => !fs.fs.match('/dev/loop') || !fs.fs.match('/var/lib/docker/')), - dockerReclaimable - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/services/[serviceName].ts b/src/routes/api/v1/services/[serviceName].ts deleted file mode 100644 index 5b1f1dabf..000000000 --- a/src/routes/api/v1/services/[serviceName].ts +++ /dev/null @@ -1,52 +0,0 @@ -import { execShellAsync } from '$lib/api/common'; -import { docker } from '$lib/api/docker'; -import type { Request } from '@sveltejs/kit'; - -export async function get(request: Request) { - const { serviceName } = request.params; - - try { - const service = (await docker.engine.listServices()).find( - (r) => - r.Spec.Labels.managedBy === 'coolify' && - r.Spec.Labels.type === 'service' && - r.Spec.Labels.serviceName === serviceName && - r.Spec.Name === `${serviceName}_${serviceName}` - ); - if (service) { - const payload = { - config: JSON.parse(service.Spec.Labels.configuration) - }; - return { - status: 200, - body: { - success: true, - ...payload - } - }; - } else { - return { - status: 200, - body: { - success: false, - showToast: false, - message: 'Not found' - } - }; - } - } catch (error) { - return { - status: 500, - body: { - success: false, - error: error.message || error - } - }; - } -} - -export async function del(request: Request) { - const { serviceName } = request.params; - await execShellAsync(`docker stack rm ${serviceName}`); - return { status: 200, body: {} }; -} diff --git a/src/routes/api/v1/services/deploy/code-server/index.ts b/src/routes/api/v1/services/deploy/code-server/index.ts deleted file mode 100644 index 8bab4b002..000000000 --- a/src/routes/api/v1/services/deploy/code-server/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import yaml from 'js-yaml'; -import { promises as fs } from 'fs'; -import { docker } from '$lib/api/docker'; -import { baseServiceConfiguration } from '$lib/api/applications/common'; -import { cleanupTmp, execShellAsync } from '$lib/api/common'; - -export async function post(request: Request) { - let { baseURL } = request.body; - const traefikURL = baseURL; - baseURL = `https://${baseURL}`; - const workdir = '/tmp/code-server'; - const deployId = 'code-server'; - // const environment = [ - // { name: 'DOCKER_USER', value: 'root' } - - // ]; - // const generateEnvsCodeServer = {}; - // for (const env of environment) generateEnvsCodeServer[env.name] = env.value; - - const stack = { - version: '3.8', - services: { - [deployId]: { - image: 'codercom/code-server', - command: 'code-server --disable-telemetry', - networks: [`${docker.network}`], - volumes: [`${deployId}-code-server-data:/home/coder`], - // environment: generateEnvsCodeServer, - deploy: { - ...baseServiceConfiguration, - labels: [ - 'managedBy=coolify', - 'type=service', - 'serviceName=code-server', - 'configuration=' + - JSON.stringify({ - baseURL - }), - 'traefik.enable=true', - 'traefik.http.services.' + deployId + '.loadbalancer.server.port=8080', - 'traefik.http.routers.' + deployId + '.entrypoints=websecure', - 'traefik.http.routers.' + - deployId + - '.rule=Host(`' + - traefikURL + - '`) && PathPrefix(`/`)', - 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', - 'traefik.http.routers.' + deployId + '.middlewares=global-compress' - ] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - }, - volumes: { - [`${deployId}-code-server-data`]: { - external: true - } - } - }; - await execShellAsync(`mkdir -p ${workdir}`); - await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); - await execShellAsync('docker stack rm code-server'); - await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`); - cleanupTmp(workdir); - return { - status: 200, - body: { message: 'OK' } - }; -} diff --git a/src/routes/api/v1/services/deploy/code-server/password.ts b/src/routes/api/v1/services/deploy/code-server/password.ts deleted file mode 100644 index a9f5f2cd0..000000000 --- a/src/routes/api/v1/services/deploy/code-server/password.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { execShellAsync } from '$lib/api/common'; -import type { Request } from '@sveltejs/kit'; -import yaml from 'js-yaml'; - -export async function get(request: Request) { - // const { POSTGRESQL_USERNAME, POSTGRESQL_PASSWORD, POSTGRESQL_DATABASE } = JSON.parse( - // JSON.parse( - // await execShellAsync( - // "docker service inspect code-server_code-server --format='{{json .Spec.Labels.configuration}}'" - // ) - // ) - // ).generateEnvsPostgres; - const containers = (await execShellAsync("docker ps -a --format='{{json .Names}}'")) - .replace(/"/g, '') - .trim() - .split('\n'); - const codeServer = containers.find((container) => container.startsWith('code-server')); - const configYaml = yaml.load( - await execShellAsync( - `docker exec ${codeServer} cat /home/coder/.config/code-server/config.yaml` - ) - ); - return { - status: 200, - body: { message: 'OK', password: configYaml.password } - }; -} diff --git a/src/routes/api/v1/services/deploy/minio/index.ts b/src/routes/api/v1/services/deploy/minio/index.ts deleted file mode 100644 index d51feddba..000000000 --- a/src/routes/api/v1/services/deploy/minio/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import yaml from 'js-yaml'; -import generator from 'generate-password'; -import { promises as fs } from 'fs'; -import { docker } from '$lib/api/docker'; -import { baseServiceConfiguration } from '$lib/api/applications/common'; -import { cleanupTmp, execShellAsync } from '$lib/api/common'; - -export async function post(request: Request) { - let { baseURL } = request.body; - const traefikURL = baseURL; - baseURL = `https://${baseURL}`; - const workdir = '/tmp/minio'; - const deployId = 'minio'; - const secrets = [ - { - name: 'MINIO_ROOT_USER', - value: generator.generate({ length: 12, numbers: true, strict: true }) - }, - { - name: 'MINIO_ROOT_PASSWORD', - value: generator.generate({ length: 24, numbers: true, strict: true }) - } - ]; - const generateEnvsMinIO = {}; - for (const secret of secrets) generateEnvsMinIO[secret.name] = secret.value; - - const stack = { - version: '3.8', - services: { - [deployId]: { - image: 'minio/minio', - command: 'server /data', - networks: [`${docker.network}`], - environment: generateEnvsMinIO, - volumes: [`${deployId}-minio-data:/data`], - deploy: { - ...baseServiceConfiguration, - labels: [ - 'managedBy=coolify', - 'type=service', - 'serviceName=minio', - 'configuration=' + - JSON.stringify({ - baseURL, - generateEnvsMinIO - }), - 'traefik.enable=true', - 'traefik.http.services.' + deployId + '.loadbalancer.server.port=9000', - 'traefik.http.routers.' + deployId + '.entrypoints=websecure', - 'traefik.http.routers.' + - deployId + - '.rule=Host(`' + - traefikURL + - '`) && PathPrefix(`/`)', - 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', - 'traefik.http.routers.' + deployId + '.middlewares=global-compress' - ] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - }, - volumes: { - [`${deployId}-minio-data`]: { - external: true - } - } - }; - await execShellAsync(`mkdir -p ${workdir}`); - await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); - await execShellAsync('docker stack rm minio'); - await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`); - cleanupTmp(workdir); - return { - status: 200, - body: { message: 'OK' } - }; -} diff --git a/src/routes/api/v1/services/deploy/nocodb/index.ts b/src/routes/api/v1/services/deploy/nocodb/index.ts deleted file mode 100644 index 82b8b84e0..000000000 --- a/src/routes/api/v1/services/deploy/nocodb/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import yaml from 'js-yaml'; -import { promises as fs } from 'fs'; -import { docker } from '$lib/api/docker'; -import { baseServiceConfiguration } from '$lib/api/applications/common'; -import { cleanupTmp, execShellAsync } from '$lib/api/common'; - -export async function post(request: Request) { - let { baseURL } = request.body; - const traefikURL = baseURL; - baseURL = `https://${baseURL}`; - const workdir = '/tmp/nocodb'; - const deployId = 'nocodb'; - const stack = { - version: '3.8', - services: { - [deployId]: { - image: 'nocodb/nocodb', - networks: [`${docker.network}`], - deploy: { - ...baseServiceConfiguration, - labels: [ - 'managedBy=coolify', - 'type=service', - 'serviceName=nocodb', - 'configuration=' + - JSON.stringify({ - baseURL - }), - 'traefik.enable=true', - 'traefik.http.services.' + deployId + '.loadbalancer.server.port=8080', - 'traefik.http.routers.' + deployId + '.entrypoints=websecure', - 'traefik.http.routers.' + - deployId + - '.rule=Host(`' + - traefikURL + - '`) && PathPrefix(`/`)', - 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', - 'traefik.http.routers.' + deployId + '.middlewares=global-compress' - ] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - } - }; - await execShellAsync(`mkdir -p ${workdir}`); - await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); - await execShellAsync('docker stack rm nocodb'); - await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`); - cleanupTmp(workdir); - return { - status: 200, - body: { message: 'OK' } - }; -} diff --git a/src/routes/api/v1/services/deploy/plausible/activate.ts b/src/routes/api/v1/services/deploy/plausible/activate.ts deleted file mode 100644 index 420aa43d1..000000000 --- a/src/routes/api/v1/services/deploy/plausible/activate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { execShellAsync } from '$lib/api/common'; -import type { Request } from '@sveltejs/kit'; - -export async function patch(request: Request) { - const { POSTGRESQL_USERNAME, POSTGRESQL_PASSWORD, POSTGRESQL_DATABASE } = JSON.parse( - JSON.parse( - await execShellAsync( - "docker service inspect plausible_plausible --format='{{json .Spec.Labels.configuration}}'" - ) - ) - ).generateEnvsPostgres; - const containers = (await execShellAsync("docker ps -a --format='{{json .Names}}'")) - .replace(/"/g, '') - .trim() - .split('\n'); - const postgresDB = containers.find((container) => container.startsWith('plausible_plausible_db')); - await execShellAsync( - `docker exec ${postgresDB} psql -H postgresql://${POSTGRESQL_USERNAME}:${POSTGRESQL_PASSWORD}@localhost:5432/${POSTGRESQL_DATABASE} -c "UPDATE users SET email_verified = true;"` - ); - return { - status: 200, - body: { message: 'OK' } - }; -} diff --git a/src/routes/api/v1/services/deploy/plausible/index.ts b/src/routes/api/v1/services/deploy/plausible/index.ts deleted file mode 100644 index c67f0cb14..000000000 --- a/src/routes/api/v1/services/deploy/plausible/index.ts +++ /dev/null @@ -1,187 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import generator from 'generate-password'; -import { promises as fs } from 'fs'; -import yaml from 'js-yaml'; -import { docker } from '$lib/api/docker'; -import { baseServiceConfiguration } from '$lib/api/applications/common'; -import { cleanupTmp, execShellAsync } from '$lib/api/common'; - -export async function post(request: Request) { - const { email, userName, userPassword } = request.body; - let { baseURL } = request.body; - const traefikURL = baseURL; - baseURL = `https://${baseURL}`; - const deployId = 'plausible'; - const workdir = '/tmp/plausible'; - const secretKey = generator.generate({ length: 64, numbers: true, strict: true }); - const generateEnvsPostgres = { - POSTGRESQL_PASSWORD: generator.generate({ length: 24, numbers: true, strict: true }), - POSTGRESQL_USERNAME: generator.generate({ length: 10, numbers: true, strict: true }), - POSTGRESQL_DATABASE: 'plausible' - }; - - const secrets = [ - { name: 'ADMIN_USER_EMAIL', value: email }, - { name: 'ADMIN_USER_NAME', value: userName }, - { name: 'ADMIN_USER_PWD', value: userPassword }, - { name: 'BASE_URL', value: baseURL }, - { name: 'SECRET_KEY_BASE', value: secretKey }, - { name: 'DISABLE_AUTH', value: 'false' }, - { name: 'DISABLE_REGISTRATION', value: 'true' }, - { - name: 'DATABASE_URL', - value: `postgresql://${generateEnvsPostgres.POSTGRESQL_USERNAME}:${generateEnvsPostgres.POSTGRESQL_PASSWORD}@plausible_db:5432/${generateEnvsPostgres.POSTGRESQL_DATABASE}` - }, - { name: 'CLICKHOUSE_DATABASE_URL', value: 'http://plausible_events_db:8123/plausible' } - ]; - - const generateEnvsClickhouse = {}; - for (const secret of secrets) generateEnvsClickhouse[secret.name] = secret.value; - - const clickhouseConfigXml = ` - - - warning - true - - - - - - - - - - `; - const clickhouseUserConfigXml = ` - - - - 0 - 0 - - - `; - - const clickhouseConfigs = [ - { - source: 'plausible-clickhouse-user-config.xml', - target: '/etc/clickhouse-server/users.d/logging.xml' - }, - { - source: 'plausible-clickhouse-config.xml', - target: '/etc/clickhouse-server/config.d/logging.xml' - }, - { source: 'plausible-init.query', target: '/docker-entrypoint-initdb.d/init.query' }, - { source: 'plausible-init-db.sh', target: '/docker-entrypoint-initdb.d/init-db.sh' } - ]; - - const initQuery = 'CREATE DATABASE IF NOT EXISTS plausible;'; - const initScript = 'clickhouse client --queries-file /docker-entrypoint-initdb.d/init.query'; - await execShellAsync(`mkdir -p ${workdir}`); - await fs.writeFile(`${workdir}/clickhouse-config.xml`, clickhouseConfigXml); - await fs.writeFile(`${workdir}/clickhouse-user-config.xml`, clickhouseUserConfigXml); - await fs.writeFile(`${workdir}/init.query`, initQuery); - await fs.writeFile(`${workdir}/init-db.sh`, initScript); - const stack = { - version: '3.8', - services: { - [deployId]: { - image: 'plausible/analytics:latest', - command: - 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"', - networks: [`${docker.network}`], - volumes: [`${deployId}-postgres-data:/var/lib/postgresql/data`], - environment: generateEnvsClickhouse, - deploy: { - ...baseServiceConfiguration, - labels: [ - 'managedBy=coolify', - 'type=service', - 'serviceName=plausible', - 'configuration=' + - JSON.stringify({ - email, - userName, - userPassword, - baseURL, - secretKey, - generateEnvsPostgres, - generateEnvsClickhouse - }), - 'traefik.enable=true', - 'traefik.http.services.' + deployId + '.loadbalancer.server.port=8000', - 'traefik.http.routers.' + deployId + '.entrypoints=websecure', - 'traefik.http.routers.' + - deployId + - '.rule=Host(`' + - traefikURL + - '`) && PathPrefix(`/`)', - 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', - 'traefik.http.routers.' + deployId + '.middlewares=global-compress' - ] - } - }, - plausible_db: { - image: 'bitnami/postgresql:13.2.0', - networks: [`${docker.network}`], - environment: generateEnvsPostgres, - deploy: { - ...baseServiceConfiguration, - labels: ['managedBy=coolify', 'type=service', 'serviceName=plausible'] - } - }, - plausible_events_db: { - image: 'yandex/clickhouse-server:21.3.2.5', - networks: [`${docker.network}`], - volumes: [`${deployId}-clickhouse-data:/var/lib/clickhouse`], - ulimits: { - nofile: { - soft: 262144, - hard: 262144 - } - }, - configs: [...clickhouseConfigs], - deploy: { - ...baseServiceConfiguration, - labels: ['managedBy=coolify', 'type=service', 'serviceName=plausible'] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - }, - volumes: { - [`${deployId}-clickhouse-data`]: { - external: true - }, - [`${deployId}-postgres-data`]: { - external: true - } - }, - configs: { - 'plausible-clickhouse-user-config.xml': { - file: `${workdir}/clickhouse-user-config.xml` - }, - 'plausible-clickhouse-config.xml': { - file: `${workdir}/clickhouse-config.xml` - }, - 'plausible-init.query': { - file: `${workdir}/init.query` - }, - 'plausible-init-db.sh': { - file: `${workdir}/init-db.sh` - } - } - }; - await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); - await execShellAsync('docker stack rm plausible'); - await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`); - cleanupTmp(workdir); - return { - status: 200, - body: { message: 'OK' } - }; -} diff --git a/src/routes/api/v1/services/deploy/wordpress/index.ts b/src/routes/api/v1/services/deploy/wordpress/index.ts deleted file mode 100644 index d277026d4..000000000 --- a/src/routes/api/v1/services/deploy/wordpress/index.ts +++ /dev/null @@ -1,170 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import yaml from 'js-yaml'; -import generator from 'generate-password'; -import { promises as fs } from 'fs'; -import { docker } from '$lib/api/docker'; -import { baseServiceConfiguration } from '$lib/api/applications/common'; -import { cleanupTmp, execShellAsync } from '$lib/api/common'; - -export async function post(request: Request) { - let { baseURL, remoteDB, database, wordpressExtraConfiguration } = request.body; - const traefikURL = baseURL; - baseURL = `https://${baseURL}`; - const workdir = '/tmp/wordpress'; - const deployId = `wp-${generator.generate({ length: 5, numbers: true, strict: true })}`; - const defaultDatabaseName = generator.generate({ length: 12, numbers: true, strict: true }); - const defaultDatabaseHost = `${deployId}-mysql`; - const defaultDatabaseUser = generator.generate({ length: 12, numbers: true, strict: true }); - const defaultDatabasePassword = generator.generate({ length: 24, numbers: true, strict: true }); - const defaultDatabaseRootPassword = generator.generate({ - length: 24, - numbers: true, - strict: true - }); - const defaultDatabaseRootUser = generator.generate({ length: 12, numbers: true, strict: true }); - let secrets = [ - { name: 'WORDPRESS_DB_HOST', value: defaultDatabaseHost }, - { name: 'WORDPRESS_DB_USER', value: defaultDatabaseUser }, - { name: 'WORDPRESS_DB_PASSWORD', value: defaultDatabasePassword }, - { name: 'WORDPRESS_DB_NAME', value: defaultDatabaseName }, - { name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration } - ]; - - const generateEnvsMySQL = { - MYSQL_ROOT_PASSWORD: defaultDatabaseRootPassword, - MYSQL_ROOT_USER: defaultDatabaseRootUser, - MYSQL_USER: defaultDatabaseUser, - MYSQL_PASSWORD: defaultDatabasePassword, - MYSQL_DATABASE: defaultDatabaseName - }; - const image = 'bitnami/mysql:8.0'; - const volume = `${deployId}-mysql-data:/bitnami/mysql/data`; - - if (remoteDB) { - secrets = [ - { name: 'WORDPRESS_DB_HOST', value: database.host }, - { name: 'WORDPRESS_DB_USER', value: database.user }, - { name: 'WORDPRESS_DB_PASSWORD', value: database.password }, - { name: 'WORDPRESS_DB_NAME', value: database.name }, - { name: 'WORDPRESS_TABLE_PREFIX', value: database.tablePrefix }, - { name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration } - ]; - } - - const generateEnvsWordpress = {}; - for (const secret of secrets) generateEnvsWordpress[secret.name] = secret.value; - let stack = { - version: '3.8', - services: { - [deployId]: { - image: 'wordpress', - networks: [`${docker.network}`], - environment: generateEnvsWordpress, - volumes: [`${deployId}-wordpress-data:/var/www/html`], - deploy: { - ...baseServiceConfiguration, - labels: [ - 'managedBy=coolify', - 'type=service', - 'serviceName=' + deployId, - 'configuration=' + - JSON.stringify({ - deployId, - baseURL, - generateEnvsWordpress - }), - 'traefik.enable=true', - 'traefik.http.services.' + deployId + '.loadbalancer.server.port=80', - 'traefik.http.routers.' + deployId + '.entrypoints=websecure', - 'traefik.http.routers.' + - deployId + - '.rule=Host(`' + - traefikURL + - '`) && PathPrefix(`/`)', - 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', - 'traefik.http.routers.' + deployId + '.middlewares=global-compress' - ] - } - }, - [`${deployId}-mysql`]: { - image, - networks: [`${docker.network}`], - environment: generateEnvsMySQL, - volumes: [volume], - deploy: { - ...baseServiceConfiguration, - labels: ['managedBy=coolify', 'type=service', 'serviceName=' + deployId] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - }, - volumes: { - [`${deployId}-wordpress-data`]: { - external: true - }, - [`${deployId}-mysql-data`]: { - external: true - } - } - }; - if (remoteDB) { - stack = { - version: '3.8', - services: { - [deployId]: { - image: 'wordpress', - networks: [`${docker.network}`], - environment: generateEnvsWordpress, - volumes: [`${deployId}-wordpress-data:/var/www/html`], - deploy: { - ...baseServiceConfiguration, - labels: [ - 'managedBy=coolify', - 'type=service', - 'serviceName=' + deployId, - 'configuration=' + - JSON.stringify({ - deployId, - baseURL, - generateEnvsWordpress - }), - 'traefik.enable=true', - 'traefik.http.services.' + deployId + '.loadbalancer.server.port=80', - 'traefik.http.routers.' + deployId + '.entrypoints=websecure', - 'traefik.http.routers.' + - deployId + - '.rule=Host(`' + - traefikURL + - '`) && PathPrefix(`/`)', - 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', - 'traefik.http.routers.' + deployId + '.middlewares=global-compress' - ] - } - } - }, - networks: { - [`${docker.network}`]: { - external: true - } - }, - volumes: { - [`${deployId}-wordpress-data`]: { - external: true - } - } - }; - } - await execShellAsync(`mkdir -p ${workdir}`); - await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); - await execShellAsync(`docker stack rm ${deployId}`); - await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`); - cleanupTmp(workdir); - return { - status: 200, - body: { message: 'OK' } - }; -} diff --git a/src/routes/api/v1/settings/index.ts b/src/routes/api/v1/settings/index.ts deleted file mode 100644 index a3d57be63..000000000 --- a/src/routes/api/v1/settings/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import Settings from '$models/Settings'; -import type { Request } from '@sveltejs/kit'; -const applicationName = 'coolify'; - -export async function get(request: Request) { - try { - const settings = await Settings.findOne({ applicationName }).select('-_id -__v'); - const payload = { - applicationName, - allowRegistration: false, - ...settings._doc - }; - return { - status: 200, - body: { - ...payload - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} -export async function post(request: Request) { - try { - const settings = await Settings.findOneAndUpdate( - { applicationName }, - { applicationName, ...request.body }, - { upsert: true, new: true } - ).select('-_id -__v'); - return { - status: 201, - body: { - ...settings._doc - } - }; - } catch (error) { - await saveServerLog(error); - return { - status: 500, - body: { - error: error.message || error - } - }; - } -} diff --git a/src/routes/api/v1/upgrade/index.ts b/src/routes/api/v1/upgrade/index.ts deleted file mode 100644 index 8caa19c78..000000000 --- a/src/routes/api/v1/upgrade/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { saveServerLog } from '$lib/api/applications/logging'; -import { execShellAsync } from '$lib/api/common'; -import type { Request } from '@sveltejs/kit'; - -export async function get(request: Request) { - const upgradeP1 = await execShellAsync( - 'bash -c "$(curl -fsSL https://get.coollabs.io/coolify/upgrade-p1.sh)"' - ); - await saveServerLog({ message: upgradeP1, type: 'UPGRADE-P-1' }); - execShellAsync( - 'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -u root coolify bash -c "$(curl -fsSL https://get.coollabs.io/coolify/upgrade-p2.sh)"' - ); - return { - status: 200, - body: { - message: "I'm trying, okay?" - } - }; -} diff --git a/src/routes/api/v1/webhooks/deploy.ts b/src/routes/api/v1/webhooks/deploy.ts deleted file mode 100644 index 21aaf7469..000000000 --- a/src/routes/api/v1/webhooks/deploy.ts +++ /dev/null @@ -1,230 +0,0 @@ -import type { Request } from '@sveltejs/kit'; -import crypto from 'crypto'; -import Deployment from '$models/Deployment'; -import { docker } from '$lib/api/docker'; -import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration'; -import cloneRepository from '$lib/api/applications/cloneRepository'; -import { cleanupTmp, execShellAsync } from '$lib/api/common'; -import queueAndBuild from '$lib/api/applications/queueAndBuild'; -import Configuration from '$models/Configuration'; -import ApplicationLog from '$models/ApplicationLog'; -import { cleanupStuckedDeploymentsInDB } from '$lib/api/applications/cleanup'; -export async function post(request: Request) { - let configuration; - const allowedGithubEvents = ['push', 'pull_request']; - const allowedPRActions = ['opened', 'reopened', 'synchronize', 'closed']; - const githubEvent = request.headers['x-github-event']; - const { GITHUP_APP_WEBHOOK_SECRET } = process.env; - const hmac = crypto.createHmac('sha256', GITHUP_APP_WEBHOOK_SECRET); - const digest = Buffer.from( - 'sha256=' + hmac.update(JSON.stringify(request.body)).digest('hex'), - 'utf8' - ); - const checksum = Buffer.from(request.headers['x-hub-signature-256'], 'utf8'); - if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { - return { - status: 500, - body: { - error: 'Invalid request.' - } - }; - } - - if (!allowedGithubEvents.includes(githubEvent)) { - return { - status: 500, - body: { - error: 'Event not allowed.' - } - }; - } - - // TODO: Monorepo support here. Find all configurations by id and update all deployments! Tough! - try { - const applications = await Configuration.find({ - 'repository.id': request.body.repository.id - }).select('-_id -__v -createdAt -updatedAt'); - if (githubEvent === 'push') { - configuration = applications.find((r) => { - if (request.body.ref.startsWith('refs')) { - if (r.repository.branch === request.body.ref.split('/')[2]) { - return r; - } - } - return null; - }); - } else if (githubEvent === 'pull_request') { - if (!allowedPRActions.includes(request.body.action)) { - return { - status: 500, - body: { - error: 'PR action is not allowed.' - } - }; - } - configuration = applications.find( - (r) => r.repository.branch === request.body['pull_request'].base.ref - ); - if (configuration) { - if (!configuration.general.isPreviewDeploymentEnabled) { - return { - status: 500, - body: { - error: 'PR deployments are not enabled.' - } - }; - } - configuration.general.pullRequest = request.body.number; - } - } - if (!configuration) { - return { - status: 500, - body: { - error: 'No configuration found.' - } - }; - } - configuration = setDefaultConfiguration(configuration); - const { id, organization, name, branch } = configuration.repository; - const { domain } = configuration.publish; - const { deployId, nickname, pullRequest } = configuration.general; - - if (request.body.action === 'closed') { - const deploys = await Deployment.find({ organization, branch, name, domain }); - for (const deploy of deploys) { - await ApplicationLog.deleteMany({ deployId: deploy.deployId }); - await Deployment.deleteMany({ deployId: deploy.deployId }); - } - await Configuration.findOneAndRemove({ - 'repository.id': id, - 'repository.organization': organization, - 'repository.name': name, - 'repository.branch': branch, - 'general.pullRequest': pullRequest - }); - await execShellAsync(`docker stack rm ${configuration.build.container.name}`); - return { - status: 200, - body: { - success: true, - message: 'Removed' - } - }; - } - await cloneRepository(configuration); - const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment( - configuration - ); - if (foundService && !forceUpdate && !imageChanged && !configChanged) { - cleanupTmp(configuration.general.workdir); - return { - status: 200, - body: { - success: false, - message: 'Nothing changed, no need to redeploy.' - } - }; - } - const alreadyQueued = await Deployment.find({ - repoId: id, - branch: branch, - organization: organization, - name: name, - domain: domain, - progress: { $in: ['queued', 'inprogress'] } - }); - if (alreadyQueued.length > 0) { - return { - status: 200, - body: { - success: false, - message: 'Already in the queue.' - } - }; - } - - await new Deployment({ - repoId: id, - branch, - deployId, - domain, - organization, - name, - nickname - }).save(); - - if (githubEvent === 'pull_request') { - await Configuration.findOneAndUpdate( - { - 'repository.id': id, - 'repository.organization': organization, - 'repository.name': name, - 'repository.branch': branch, - 'general.pullRequest': pullRequest - }, - { ...configuration }, - { upsert: true, new: true } - ); - } else { - await Configuration.findOneAndUpdate( - { - 'repository.id': id, - 'repository.organization': organization, - 'repository.name': name, - 'repository.branch': branch, - 'general.pullRequest': { $in: [null, 0] } - }, - { ...configuration }, - { upsert: true, new: true } - ); - } - - queueAndBuild(configuration, imageChanged); - return { - status: 201, - body: { - message: 'Deployment queued.', - nickname: configuration.general.nickname, - name: configuration.build.container.name, - deployId: configuration.general.deployId - } - }; - } catch (error) { - console.log(error); - // console.log(configuration) - if (configuration) { - cleanupTmp(configuration.general.workdir); - await Deployment.findOneAndUpdate( - { - repoId: configuration.repository.id, - branch: configuration.repository.branch, - organization: configuration.repository.organization, - name: configuration.repository.name, - domain: configuration.publish.domain - }, - { - repoId: configuration.repository.id, - branch: configuration.repository.branch, - organization: configuration.repository.organization, - name: configuration.repository.name, - domain: configuration.publish.domain, - progress: 'failed' - } - ); - } - - return { - status: 500, - body: { - error: error.message || error - } - }; - } finally { - try { - await cleanupStuckedDeploymentsInDB(); - } catch (error) { - console.log(error); - } - } -} diff --git a/src/routes/application/[nickname]/configuration.svelte b/src/routes/application/[nickname]/configuration.svelte deleted file mode 100644 index 9e90cf014..000000000 --- a/src/routes/application/[nickname]/configuration.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/src/routes/application/[nickname]/logs/[deployId].svelte b/src/routes/application/[nickname]/logs/[deployId].svelte deleted file mode 100644 index 00594c93e..000000000 --- a/src/routes/application/[nickname]/logs/[deployId].svelte +++ /dev/null @@ -1,83 +0,0 @@ - - -
    -
    Deployment log
    - - - - -
    -{#await loadLogs()} - -{:then} -
    -
    -
    -      {#if logs.length > 0}
    -        {#each logs as log}
    -          {log + '\n'}
    -        {/each}
    -      {:else}
    -        It's starting soon.
    -      {/if}
    -    
    -
    -
    -{/await} diff --git a/src/routes/application/[nickname]/logs/index.svelte b/src/routes/application/[nickname]/logs/index.svelte deleted file mode 100644 index 4fa0717c6..000000000 --- a/src/routes/application/[nickname]/logs/index.svelte +++ /dev/null @@ -1,137 +0,0 @@ - - -
    -
    Logs
    -
    -{#await loadDeploymentLogs()} - -{:then} -
    -
    -
    -
    Application logs
    - {#if logs.length === 0} -
    Waiting for the logs...
    - {:else} -
    -            {#each logs as log}
    -              {log + '\n'}
    -            {/each}
    -          
    - {/if} -
    -
    -
    Deployment logs
    - {#if deployments.length > 0} -
    - {#each deployments as deployment} -
    goto(`./logs/${deployment.deployId}`)} - > -
    -
    - {deployment.branch} -
    -
    {deployment.isPr ? 'PR' : ''}
    -
    -
    -
    -
    - {deployment.since} -
    - {#if deployment.progress === 'done'} -
    - Deployed in {deployment.took}s -
    - {:else if deployment.progress === 'failed'} -
    Failed
    - {:else} -
    Deploying...
    - {/if} -
    -
    - {/each} -
    - - {:else} -
    No deployments found
    - {/if} -
    -
    -
    -{:catch} -
    No logs found
    -{/await} - - diff --git a/src/routes/application/__layout.svelte b/src/routes/application/__layout.svelte deleted file mode 100644 index 3db4343b6..000000000 --- a/src/routes/application/__layout.svelte +++ /dev/null @@ -1,137 +0,0 @@ - - - - -{#await loadConfiguration()} - -{:then} - -
    - {#if $page.path.endsWith('configuration')} -
    -
    - {$application.publish.domain - ? `${$application.publish.domain}${ - $application.publish.path !== '/' ? $application.publish.path : '' - }` - : 'example.com'} - - - - - - - -
    -
    - {:else if $page.path === '/application/new'} -
    -
    - New Application -
    -
    - {/if} - -
    -{/await} diff --git a/src/routes/application/new.svelte b/src/routes/application/new.svelte deleted file mode 100644 index 21d65c11e..000000000 --- a/src/routes/application/new.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/routes/applications/[id]/__layout.svelte b/src/routes/applications/[id]/__layout.svelte new file mode 100644 index 000000000..72f99c69a --- /dev/null +++ b/src/routes/applications/[id]/__layout.svelte @@ -0,0 +1,376 @@ + + + + + + diff --git a/src/routes/applications/[id]/check.json.ts b/src/routes/applications/[id]/check.json.ts new file mode 100644 index 000000000..d8ed3fa09 --- /dev/null +++ b/src/routes/applications/[id]/check.json.ts @@ -0,0 +1,28 @@ +import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let { fqdn } = await event.request.json(); + fqdn = fqdn.toLowerCase(); + + try { + const found = await db.isDomainConfigured({ id, fqdn }); + if (found) { + throw { + message: `Domain ${getDomain(fqdn)} is already configured.` + }; + } + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/configuration/_BuildPack.svelte b/src/routes/applications/[id]/configuration/_BuildPack.svelte new file mode 100644 index 000000000..df04789c1 --- /dev/null +++ b/src/routes/applications/[id]/configuration/_BuildPack.svelte @@ -0,0 +1,44 @@ + + +
    handleSubmit(buildPack.name)}> + +
    diff --git a/src/routes/applications/[id]/configuration/_GithubRepositories.svelte b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte new file mode 100644 index 000000000..61ef2d665 --- /dev/null +++ b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte @@ -0,0 +1,170 @@ + + +{#if repositories.length === 0 && loading.repositories === false} +
    +
    No repositories configured for your Git Application.
    + +
    +{:else} +
    +
    + {#if loading.repositories} + + {:else} + + {/if} + + {#if loading.branches} + + {:else} + + {/if} +
    +
    + + +
    +
    +{/if} diff --git a/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte new file mode 100644 index 000000000..a97291deb --- /dev/null +++ b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte @@ -0,0 +1,334 @@ + + +
    +
    + {#if loading.base} + + {:else} + + {/if} + {#if loading.projects} + + {:else if !loading.projects && projects.length > 0} + + {:else} + + {/if} + + {#if loading.branches} + + {:else if !loading.branches && branches.length > 0} + + {:else} + + {/if} +
    +
    + +
    +
    diff --git a/src/routes/applications/[id]/configuration/buildpack.json.ts b/src/routes/applications/[id]/configuration/buildpack.json.ts new file mode 100644 index 000000000..0e798e600 --- /dev/null +++ b/src/routes/applications/[id]/configuration/buildpack.json.ts @@ -0,0 +1,41 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; +import { PrismaErrorHandler } from '$lib/database'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const application = await db.getApplication({ id, teamId }); + return { + status: 200, + body: { + type: application.gitSource.type, + projectId: application.projectId, + repository: application.repository, + branch: application.branch, + apiUrl: application.gitSource.apiUrl + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { buildPack } = await event.request.json(); + + try { + await db.configureBuildPack({ id, buildPack }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/configuration/buildpack.svelte b/src/routes/applications/[id]/configuration/buildpack.svelte new file mode 100644 index 000000000..b517689a6 --- /dev/null +++ b/src/routes/applications/[id]/configuration/buildpack.svelte @@ -0,0 +1,186 @@ + + + + +
    +
    Configure Build Pack
    +
    + +{#if scanning} +
    +
    Scanning repository to suggest a build pack for you...
    +
    +{:else} +
    + {#each buildPacks as buildPack} +
    + +
    + {/each} +
    +{/if} diff --git a/src/routes/applications/[id]/configuration/deploykey.json.ts b/src/routes/applications/[id]/configuration/deploykey.json.ts new file mode 100644 index 000000000..a0a69915a --- /dev/null +++ b/src/routes/applications/[id]/configuration/deploykey.json.ts @@ -0,0 +1,17 @@ +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { id } = event.params; + let { deployKeyId } = await event.request.json(); + + deployKeyId = Number(deployKeyId); + + try { + await db.updateDeployKey({ id, deployKeyId }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/configuration/destination.json.ts b/src/routes/applications/[id]/configuration/destination.json.ts new file mode 100644 index 000000000..ef73883db --- /dev/null +++ b/src/routes/applications/[id]/configuration/destination.json.ts @@ -0,0 +1,19 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { destinationId } = await event.request.json(); + + try { + await db.configureDestinationForApplication({ id, destinationId }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/configuration/destination.svelte b/src/routes/applications/[id]/configuration/destination.svelte new file mode 100644 index 000000000..2c796b5ed --- /dev/null +++ b/src/routes/applications/[id]/configuration/destination.svelte @@ -0,0 +1,91 @@ + + + + +
    +
    Configure Destination
    +
    +
    + {#if !destinations || destinations.length === 0} +
    +
    No configurable Destination found
    +
    + + + +
    +
    + {:else} +
    + {#each destinations as destination} +
    +
    handleSubmit(destination.id)}> + +
    +
    + {/each} +
    + {/if} +
    diff --git a/src/routes/applications/[id]/configuration/repository.json.ts b/src/routes/applications/[id]/configuration/repository.json.ts new file mode 100644 index 000000000..8e1c3aa16 --- /dev/null +++ b/src/routes/applications/[id]/configuration/repository.json.ts @@ -0,0 +1,47 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + const repository = event.url.searchParams.get('repository')?.toLocaleLowerCase() || undefined; + const branch = event.url.searchParams.get('branch')?.toLocaleLowerCase() || undefined; + + try { + const found = await db.isBranchAlreadyUsed({ repository, branch, id }); + if (found) { + throw { + error: `Branch ${branch} is already used by another application` + }; + } + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + let { repository, branch, projectId, webhookToken } = await event.request.json(); + + repository = repository.toLowerCase(); + branch = branch.toLowerCase(); + projectId = Number(projectId); + + try { + await db.configureGitRepository({ id, repository, branch, projectId, webhookToken }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/configuration/repository.svelte b/src/routes/applications/[id]/configuration/repository.svelte new file mode 100644 index 000000000..416292e99 --- /dev/null +++ b/src/routes/applications/[id]/configuration/repository.svelte @@ -0,0 +1,38 @@ + + + + +
    +
    Select a Repository / Project
    +
    +
    + {#if application.gitSource.type === 'github'} + + {:else if application.gitSource.type === 'gitlab'} + + {/if} +
    diff --git a/src/routes/applications/[id]/configuration/source.json.ts b/src/routes/applications/[id]/configuration/source.json.ts new file mode 100644 index 000000000..089dc3063 --- /dev/null +++ b/src/routes/applications/[id]/configuration/source.json.ts @@ -0,0 +1,18 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { gitSourceId } = await event.request.json(); + try { + await db.configureGitsource({ id, gitSourceId }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/configuration/source.svelte b/src/routes/applications/[id]/configuration/source.svelte new file mode 100644 index 000000000..00ba5a1b5 --- /dev/null +++ b/src/routes/applications/[id]/configuration/source.svelte @@ -0,0 +1,109 @@ + + + + +
    +
    Select a Git Source
    +
    +
    + {#if !sources || sources.length === 0} +
    +
    No configurable Git Source found
    +
    + + + +
    +
    + {:else} +
    + {#each sources as source} +
    +
    handleSubmit(source.id)}> + +
    +
    + {/each} +
    + {/if} +
    diff --git a/src/routes/applications/[id]/configuration/sshkey.json.ts b/src/routes/applications/[id]/configuration/sshkey.json.ts new file mode 100644 index 000000000..4514634f5 --- /dev/null +++ b/src/routes/applications/[id]/configuration/sshkey.json.ts @@ -0,0 +1,20 @@ +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { id } = event.params; + try { + return await db.getSshKey({ id }); + } catch (error) { + return PrismaErrorHandler(error); + } +}; +export const post: RequestHandler = async (event) => { + const { id } = event.params; + try { + return await db.generateSshKey({ id }); + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/delete.json.ts b/src/routes/applications/[id]/delete.json.ts new file mode 100644 index 000000000..1f3730fc5 --- /dev/null +++ b/src/routes/applications/[id]/delete.json.ts @@ -0,0 +1,19 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const del: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + await db.removeApplication({ id, teamId }); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/deploy.json.ts b/src/routes/applications/[id]/deploy.json.ts new file mode 100644 index 000000000..b6b1a0a95 --- /dev/null +++ b/src/routes/applications/[id]/deploy.json.ts @@ -0,0 +1,42 @@ +import * as db from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; +import cuid from 'cuid'; +import crypto from 'crypto'; +import { buildQueue } from '$lib/queues'; +import { getUserDetails } from '$lib/common'; +import { PrismaErrorHandler } from '$lib/database'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const buildId = cuid(); + const applicationFound = await db.getApplication({ id, teamId }); + if (!applicationFound.configHash) { + const configHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack: applicationFound.buildPack, + port: applicationFound.port, + installCommand: applicationFound.installCommand, + buildCommand: applicationFound.buildCommand, + startCommand: applicationFound.startCommand + }) + ) + .digest('hex'); + await db.prisma.application.update({ where: { id }, data: { configHash } }); + } + await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound }); + return { + status: 200, + body: { + buildId + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/index.json.ts b/src/routes/applications/[id]/index.json.ts new file mode 100644 index 000000000..eedd89d9f --- /dev/null +++ b/src/routes/applications/[id]/index.json.ts @@ -0,0 +1,87 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import { getGithubToken } from '$lib/components/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { checkContainer } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; +import jsonwebtoken from 'jsonwebtoken'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const appId = process.env['COOLIFY_APP_ID']; + let githubToken = null; + let ghToken = null; + let isRunning = false; + + const { id } = event.params; + try { + const application = await db.getApplication({ id, teamId }); + const { gitSource } = application; + if (gitSource?.type === 'github' && gitSource?.githubApp) { + const payload = { + iat: Math.round(new Date().getTime() / 1000), + exp: Math.round(new Date().getTime() / 1000 + 60), + iss: gitSource.githubApp.appId + }; + githubToken = jsonwebtoken.sign(payload, gitSource.githubApp.privateKey, { + algorithm: 'RS256' + }); + ghToken = await getGithubToken({ apiUrl: gitSource.apiUrl, application, githubToken }); + } + if (application.destinationDockerId) { + isRunning = await checkContainer(application.destinationDocker.engine, id); + } + return { + body: { + isRunning, + ghToken, + githubToken, + application, + appId + } + }; + } catch (error) { + console.log(error); + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + let { + name, + buildPack, + fqdn, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory + } = await event.request.json(); + + if (port) port = Number(port); + + try { + await db.configureApplication({ + id, + buildPack, + name, + fqdn, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory + }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte new file mode 100644 index 000000000..91bc1c69f --- /dev/null +++ b/src/routes/applications/[id]/index.svelte @@ -0,0 +1,390 @@ + + + + + + +
    + +
    +
    +
    General
    + {#if $session.isAdmin} + + {/if} +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    Application
    +
    +
    +
    + +
    + + +
    +
    + + {#if !staticDeployments.includes(application.buildPack)} +
    + +
    + +
    +
    + {/if} + {#if !notNodeDeployments.includes(application.buildPack)} +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + {/if} + +
    + +
    + + +
    +
    + {#if !notNodeDeployments.includes(application.buildPack)} +
    + +
    + + +
    +
    + {/if} +
    +
    +
    +
    Features
    +
    +
    + +
      + changeSettings('previews')} + title="Enable MR/PR Previews" + description="Creates previews from pull and merge requests." + /> +
    +
      + changeSettings('debug')} + title="Debug Logs" + description="Enable debug logs during build phase.
      (sensitive information could be visible in logs)" + /> +
    +
    +
    diff --git a/src/routes/applications/[id]/logs/_Loading.svelte b/src/routes/applications/[id]/logs/_Loading.svelte new file mode 100644 index 000000000..73fbe709e --- /dev/null +++ b/src/routes/applications/[id]/logs/_Loading.svelte @@ -0,0 +1,41 @@ +
    +
    +
    +
    + + diff --git a/src/routes/applications/[id]/logs/build/_BuildLog.svelte b/src/routes/applications/[id]/logs/build/_BuildLog.svelte new file mode 100644 index 000000000..98f080dd8 --- /dev/null +++ b/src/routes/applications/[id]/logs/build/_BuildLog.svelte @@ -0,0 +1,75 @@ + + +{#if loading} + +{:else} +
    + {#if currentStatus === 'running'} + + {/if} +
    + {#each logs as log} +
    {log.line + '\n'}
    + {/each} +
    +
    +{/if} diff --git a/src/routes/applications/[id]/logs/build/build.json.ts b/src/routes/applications/[id]/logs/build/build.json.ts new file mode 100644 index 000000000..38a51e043 --- /dev/null +++ b/src/routes/applications/[id]/logs/build/build.json.ts @@ -0,0 +1,28 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const buildId = event.url.searchParams.get('buildId'); + const sequence = Number(event.url.searchParams.get('sequence')); + try { + let logs = await db.prisma.buildLog.findMany({ + where: { buildId, time: { gt: sequence } }, + orderBy: { time: 'asc' } + }); + const { status } = await db.prisma.build.findFirst({ where: { id: buildId } }); + + return { + body: { + logs, + status + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/logs/build/index.json.ts b/src/routes/applications/[id]/logs/build/index.json.ts new file mode 100644 index 000000000..ce58e357b --- /dev/null +++ b/src/routes/applications/[id]/logs/build/index.json.ts @@ -0,0 +1,40 @@ +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dayjs } from '$lib/dayjs'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { id } = event.params; + const buildId = event.url.searchParams.get('buildId'); + const skip = Number(event.url.searchParams.get('skip')) || 0; + + let builds = []; + try { + const buildCount = await db.prisma.build.count({ where: { applicationId: id } }); + if (buildId) { + builds = await db.prisma.build.findMany({ where: { applicationId: id, id: buildId } }); + } else { + builds = await db.prisma.build.findMany({ + where: { applicationId: id }, + orderBy: { createdAt: 'desc' }, + take: 5, + skip + }); + } + builds = builds.map((build) => { + const updatedAt = dayjs(build.updatedAt).utc(); + build.took = updatedAt.diff(dayjs(build.createdAt)) / 1000; + build.since = updatedAt.fromNow(); + return build; + }); + return { + status: 200, + body: { + builds, + buildCount + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/logs/build/index.svelte b/src/routes/applications/[id]/logs/build/index.svelte new file mode 100644 index 000000000..28740e031 --- /dev/null +++ b/src/routes/applications/[id]/logs/build/index.svelte @@ -0,0 +1,143 @@ + + + + +
    +
    + Build logs of {getDomain(application.fqdn)} +
    +
    +
    +
    + {#each builds as build (build.id)} +
    loadBuild(build.id)} + class="tooltip-top flex cursor-pointer items-center justify-center rounded-r border-l-2 border-transparent py-4 no-underline transition-all duration-100 hover:bg-coolgray-400 hover:shadow-xl" + class:bg-coolgray-400={buildId === build.id} + class:border-red-500={build.status === 'failed'} + class:border-green-500={build.status === 'success'} + class:border-yellow-500={build.status === 'inprogress'} + > +
    +
    + {application.branch} +
    +
    + {build.type} +
    +
    +
    + +
    + {#if build.status === 'running'} +
    Running
    + {:else} +
    {build.since}
    +
    Finished in {build.took}s
    + {/if} +
    +
    + {/each} + {#if buildCount > 0 && !noMoreBuilds} + + {/if} +
    +
    + {#if buildId} + {#key buildId} + + {/key} + {/if} +
    +
    +{#if buildCount === 0} +
    No logs found
    +{/if} diff --git a/src/routes/applications/[id]/logs/index.json.ts b/src/routes/applications/[id]/logs/index.json.ts new file mode 100644 index 000000000..0ea806fb4 --- /dev/null +++ b/src/routes/applications/[id]/logs/index.json.ts @@ -0,0 +1,53 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dayjs } from '$lib/dayjs'; +import { dockerInstance } from '$lib/docker'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const { destinationDockerId, destinationDocker } = await db.prisma.application.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + if (destinationDockerId) { + const docker = dockerInstance({ destinationDocker }); + try { + const container = await docker.engine.getContainer(id); + if (container) { + return { + body: { + logs: (await container.logs({ stdout: true, stderr: true, timestamps: true })) + .toString() + .split('\n') + .map((l) => l.slice(8)) + .filter((a) => a) + } + }; + } + } catch (error) { + const { statusCode } = error; + if (statusCode === 404) { + return { + body: { + logs: [] + } + }; + } + } + } + return { + status: 200, + body: { + message: 'No logs found.' + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/logs/index.svelte b/src/routes/applications/[id]/logs/index.svelte new file mode 100644 index 000000000..ae45475fe --- /dev/null +++ b/src/routes/applications/[id]/logs/index.svelte @@ -0,0 +1,75 @@ + + + + +
    +
    + Application logs of {getDomain(application.fqdn)} +
    +
    +
    + {#if logs.length === 0} +
    Waiting for the logs...
    + {:else} +
    + +
    + {#each logs as log} + {log + '\n'} + {/each} +
    +
    + {/if} +
    diff --git a/src/routes/applications/[id]/previews/index.json.ts b/src/routes/applications/[id]/previews/index.json.ts new file mode 100644 index 000000000..29ffafdeb --- /dev/null +++ b/src/routes/applications/[id]/previews/index.json.ts @@ -0,0 +1,44 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import type { RequestHandler } from '@sveltejs/kit'; +import jsonwebtoken from 'jsonwebtoken'; + +export const get: RequestHandler = async (event) => { + const { status, body, teamId } = await getUserDetails(event, false); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const destinationDocker = await db.getDestinationByApplicationId({ id, teamId }); + const docker = dockerInstance({ destinationDocker }); + const listContainers = await docker.engine.listContainers({ + filters: { network: [destinationDocker.network] } + }); + const containers = listContainers.filter((container) => { + return ( + container.Labels['coolify.configuration'] && + container.Labels['coolify.type'] === 'standalone-application' + ); + }); + const jsonContainers = containers + .map((container) => + JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString()) + ) + .filter((container) => { + return ( + container.type !== 'manual' && + container.type !== 'webhook_commit' && + container.applicationId === id + ); + }); + return { + body: { + containers: jsonContainers + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/previews/index.svelte b/src/routes/applications/[id]/previews/index.svelte new file mode 100644 index 000000000..28c5efd6f --- /dev/null +++ b/src/routes/applications/[id]/previews/index.svelte @@ -0,0 +1,51 @@ + + + + +
    + +
    + +
    +
    + {#if containers.length > 0} + {#each containers as container} + +
    +
    {getDomain(container.fqdn)}
    +
    +
    + {/each} + {:else} +
    +
    No previews available
    +
    + {/if} +
    +
    diff --git a/src/routes/applications/[id]/secrets/_Secret.svelte b/src/routes/applications/[id]/secrets/_Secret.svelte new file mode 100644 index 000000000..fc24a1c63 --- /dev/null +++ b/src/routes/applications/[id]/secrets/_Secret.svelte @@ -0,0 +1,133 @@ + + +
    +
    +
    + + +
    +
    + + +
    + +
    +
    Is build variable?
    + +
    +
      +
    • +
      + Use isBuildSecret + + + + +
      +
    • +
    +
    +
    + {#if isNewSecret} +
    + +
    + {:else} +
    + +
    + {/if} +
    +
    diff --git a/src/routes/applications/[id]/secrets/index.json.ts b/src/routes/applications/[id]/secrets/index.json.ts new file mode 100644 index 000000000..0487531f6 --- /dev/null +++ b/src/routes/applications/[id]/secrets/index.json.ts @@ -0,0 +1,63 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + try { + const secrets = await db.listSecrets({ applicationId: event.params.id }); + return { + status: 200, + body: { + secrets: secrets.sort((a, b) => { + return ('' + a.name).localeCompare(b.name); + }) + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { name, value, isBuildSecret } = await event.request.json(); + + try { + const found = await db.isSecretExists({ id, name }); + if (found) { + throw { + error: `Secret ${name} already exists` + }; + } else { + await db.createSecret({ id, name, value, isBuildSecret }); + return { + status: 201 + }; + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; +export const del: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { name } = await event.request.json(); + + try { + await db.removeSecret({ id, name }); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/secrets/index.svelte b/src/routes/applications/[id]/secrets/index.svelte new file mode 100644 index 000000000..e751ce513 --- /dev/null +++ b/src/routes/applications/[id]/secrets/index.svelte @@ -0,0 +1,41 @@ + + + + +
    + +
    +
    +
    + {#each secrets as secret} + + {/each} + +
    +
    diff --git a/src/routes/applications/[id]/settings.json.ts b/src/routes/applications/[id]/settings.json.ts new file mode 100644 index 000000000..100b2a7ff --- /dev/null +++ b/src/routes/applications/[id]/settings.json.ts @@ -0,0 +1,19 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { debug, previews } = await event.request.json(); + + try { + await db.setApplicationSettings({ id, debug, previews }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/stop.json.ts b/src/routes/applications/[id]/stop.json.ts new file mode 100644 index 000000000..041dc5296 --- /dev/null +++ b/src/routes/applications/[id]/stop.json.ts @@ -0,0 +1,31 @@ +import { getDomain, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { removeProxyConfiguration } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const { destinationDocker, destinationDockerId, fqdn } = await db.getApplication({ + id, + teamId + }); + const domain = getDomain(fqdn); + if (destinationDockerId) { + const docker = dockerInstance({ destinationDocker }); + await docker.engine.getContainer(id).stop(); + } + await removeProxyConfiguration({ domain }); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/_Application.svelte b/src/routes/applications/_Application.svelte new file mode 100644 index 000000000..3de32da8f --- /dev/null +++ b/src/routes/applications/_Application.svelte @@ -0,0 +1,60 @@ + + + +
    + {#if buildPack === 'rust'} + + {:else if buildPack === 'node'} + + {:else if buildPack === 'react'} + + {:else if buildPack === 'svelte'} + + {:else if buildPack === 'vuejs'} + + {:else if buildPack === 'php'} + + {:else if buildPack === 'python'} + + {:else if buildPack === 'static'} + + {:else if buildPack === 'nestjs'} + + {:else if buildPack === 'nuxtjs'} + + {:else if buildPack === 'nextjs'} + + {:else if buildPack === 'gatsby'} + + {:else if buildPack === 'docker'} + + {/if} + +
    {application.name}
    + {#if application.fqdn} +
    {application.fqdn}
    + {/if} + {#if !application.gitSourceId || !application.destinationDockerId} +
    + Configuration missing +
    + {/if} +
    +
    diff --git a/src/routes/applications/index.json.ts b/src/routes/applications/index.json.ts new file mode 100644 index 000000000..a7f067082 --- /dev/null +++ b/src/routes/applications/index.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + try { + const applications = await db.listApplications(teamId); + return { + status: 200, + body: { + applications + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/applications/index.svelte b/src/routes/applications/index.svelte new file mode 100644 index 000000000..033fa03f9 --- /dev/null +++ b/src/routes/applications/index.svelte @@ -0,0 +1,58 @@ + + + + +
    +
    Applications
    + {#if $session.isAdmin} + + + + {/if} +
    +
    + {#if !applications || applications.length === 0} +
    +
    No applications found
    +
    + {:else} + {#each applications as application} + + {/each} + {/if} +
    diff --git a/src/routes/common/getUniqueName.json.ts b/src/routes/common/getUniqueName.json.ts new file mode 100644 index 000000000..b1b4a0050 --- /dev/null +++ b/src/routes/common/getUniqueName.json.ts @@ -0,0 +1,10 @@ +import { uniqueName } from '$lib/common'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async () => { + return { + body: { + name: uniqueName() + } + }; +}; diff --git a/src/routes/dashboard/__layout.svelte b/src/routes/dashboard/__layout.svelte deleted file mode 100644 index 74f8e2b5c..000000000 --- a/src/routes/dashboard/__layout.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - - - -
    - -
    diff --git a/src/routes/dashboard/applications.svelte b/src/routes/dashboard/applications.svelte deleted file mode 100644 index aa2ce9ffd..000000000 --- a/src/routes/dashboard/applications.svelte +++ /dev/null @@ -1,379 +0,0 @@ - - - - -
    -
    Applications
    - -
    -
    - {#if $dashboard?.applications?.deployed.length > 0} -
    -
    - {#each $dashboard.applications.deployed as application} -
    -
    { - goto(`/application/${application.configuration.general.nickname}/configuration`); - }} - > -
    - {#if application.configuration.build.pack === 'static'} - - {:else if application.configuration.build.pack === 'nestjs'} - - - - {:else if application.configuration.build.pack === 'react'} - - - - {:else if application.configuration.build.pack === 'gatsby'} - - - - {:else if application.configuration.build.pack === 'nuxtjs'} - - - - - - - - {:else if application.configuration.build.pack === 'svelte'} - - - - - {:else if application.configuration.build.pack === 'vuejs'} - - - - {:else if application.configuration.build.pack === 'nextjs'} - - - - {:else if application.configuration.build.pack === 'nodejs'} - - {:else if application.configuration.build.pack === 'php'} - - - - {:else if application.configuration.build.pack === 'docker'} - - - - {:else if application.configuration.build.pack === 'rust'} - - - - {:else if application.configuration.build.pack === 'python'} - - - - {/if} -
    -
    - {application.configuration.publish.domain}{application.configuration.publish - .path !== '/' - ? application.configuration.publish.path - : ''} -
    -
    - Last deployment
    - {new Intl.DateTimeFormat('default', dateOptions).format( - new Date(application.UpdatedAt) - )} -
    -
    -
    -
    -
    - {/each} -
    -
    - {:else} -
    No applications found
    - {/if} -
    diff --git a/src/routes/dashboard/databases.svelte b/src/routes/dashboard/databases.svelte deleted file mode 100644 index f3181c5f0..000000000 --- a/src/routes/dashboard/databases.svelte +++ /dev/null @@ -1,100 +0,0 @@ - - - - -
    -
    Databases
    - -
    -
    - {#if $dashboard.databases?.deployed.length > 0} -
    -
    - {#each $dashboard.databases.deployed as database} -
    - goto(`/database/${database.configuration.general.deployId}/configuration`)} - > -
    -
    - {#if database.configuration.general.type == 'mongodb'} - - {:else if database.configuration.general.type == 'postgresql'} - - {:else if database.configuration.general.type == 'mysql'} - - {:else if database.configuration.general.type == 'couchdb'} - - {:else if database.configuration.general.type == 'redis'} - - {:else if database.configuration.general.type == 'clickhouse'} - - {/if} -
    -
    - {database.configuration.general.nickname} -
    -
    - ({database.configuration.general.type}) -
    -
    -
    -
    -
    - {/each} -
    -
    - {:else} -
    No databases found
    - {/if} -
    diff --git a/src/routes/dashboard/services.svelte b/src/routes/dashboard/services.svelte deleted file mode 100644 index 0a230a161..000000000 --- a/src/routes/dashboard/services.svelte +++ /dev/null @@ -1,124 +0,0 @@ - - -
    -
    Services
    - -
    -
    - {#if $dashboard?.services?.deployed.length > 0} -
    -
    - {#each $dashboard?.services?.deployed as service} -
    openConfiguration(service)} - > -
    -
    - {#if service.serviceName == 'plausible'} -
    - plausible logo -
    Plausible Analytics
    -
    - {:else if service.serviceName == 'nocodb'} -
    - nocodedb -
    NocoDB
    -
    - {:else if service.serviceName == 'code-server'} -
    - - - - -
    VSCode Server
    -
    - {:else if service.serviceName == 'minio'} -
    - minio - -
    MinIO
    -
    - {:else if service.serviceName.match(/wp-/)} - - - -
    - Wordpress({service.configuration.baseURL.replace('https://', '')}) -
    - {/if} -
    -
    -
    - {/each} -
    -
    - {:else} -
    No services found
    - {/if} -
    diff --git a/src/routes/database/[name]/configuration.svelte b/src/routes/database/[name]/configuration.svelte deleted file mode 100644 index 2288d6b12..000000000 --- a/src/routes/database/[name]/configuration.svelte +++ /dev/null @@ -1,123 +0,0 @@ - - -{#await loadDatabaseConfig()} - -{:then} -
    -
    -
    {$database.config.general.nickname}
    -
    - {#if $database.config.general.type === 'mongodb'} - - {:else if $database.config.general.type === 'postgresql'} - - {:else if $database.config.general.type === 'mysql'} - - {:else if $database.config.general.type === 'couchdb'} - - {:else if $database.config.general.type === 'redis'} - - {/if} -
    -
    -
    -
    -
    -
    Database
    -
    -
    Connection string
    - {#if $database.config.general.type === 'mongodb'} - - {:else if $database.config.general.type === 'postgresql'} - - {:else if $database.config.general.type === 'mysql'} - - {:else if $database.config.general.type === 'couchdb'} - - {:else if $database.config.general.type === 'redis'} - - {:else if $database.config.general.type === 'clickhouse'} - - - {/if} -
    -
    - {#if $database.config.general.type === 'mongodb'} -
    -
    Root password
    - -
    - {/if} - {#if $database.config.general.type === 'redis'} -
    -
    Redis password
    - -
    - {/if} -
    -
    Backup
    -
    - -
    -
    -
    -{/await} diff --git a/src/routes/database/__layout.svelte b/src/routes/database/__layout.svelte deleted file mode 100644 index 00edc53da..000000000 --- a/src/routes/database/__layout.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - -{#if $page.path !== '/database/new'} - -{/if} -
    - -
    diff --git a/src/routes/database/new.svelte b/src/routes/database/new.svelte deleted file mode 100644 index 4253673dc..000000000 --- a/src/routes/database/new.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - -
    -
    - Select a database -
    -
    - - diff --git a/src/routes/databases/[id]/_Databases/_CouchDb.svelte b/src/routes/databases/[id]/_Databases/_CouchDb.svelte new file mode 100644 index 000000000..0d41ec184 --- /dev/null +++ b/src/routes/databases/[id]/_Databases/_CouchDb.svelte @@ -0,0 +1,78 @@ + + +
    +
    CouchDB
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    diff --git a/src/routes/databases/[id]/_Databases/_Databases.svelte b/src/routes/databases/[id]/_Databases/_Databases.svelte new file mode 100644 index 000000000..1483cab1b --- /dev/null +++ b/src/routes/databases/[id]/_Databases/_Databases.svelte @@ -0,0 +1,209 @@ + + +
    +
    +
    +
    General
    + {#if $session.isAdmin} + + {/if} +
    + +
    +
    + +
    + +
    +
    +
    + +
    + {#if database.destinationDockerId} +
    + +
    + {/if} +
    +
    + +
    + +
    + +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + {#if database.type === 'mysql'} + + {:else if database.type === 'postgresql'} + + {:else if database.type === 'mongodb'} + + {:else if database.type === 'redis'} + + {:else if database.type === 'couchdb'} + + {/if} +
    + +
    + +
    +
    +
    +
    +
    +
    Features
    +
    +
    +
      + changeSettings('isPublic')} + title="Set it public" + description="Your database will be reachable over the internet.
      Take security seriously in this case!" + /> +
    + {#if database.type === 'redis'} +
      + changeSettings('appendOnly')} + title="Change append only mode" + description="Useful if you would like to restore redis data from a backup.
      Database restart is required." + /> +
    + {/if} +
    +
    diff --git a/src/routes/databases/[id]/_Databases/_MongoDB.svelte b/src/routes/databases/[id]/_Databases/_MongoDB.svelte new file mode 100644 index 000000000..cbf3ffe35 --- /dev/null +++ b/src/routes/databases/[id]/_Databases/_MongoDB.svelte @@ -0,0 +1,37 @@ + + +
    +
    MongoDB
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    diff --git a/src/routes/databases/[id]/_Databases/_MySQL.svelte b/src/routes/databases/[id]/_Databases/_MySQL.svelte new file mode 100644 index 000000000..e361cc9fe --- /dev/null +++ b/src/routes/databases/[id]/_Databases/_MySQL.svelte @@ -0,0 +1,78 @@ + + +
    +
    MySQL
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    diff --git a/src/routes/databases/[id]/_Databases/_PostgreSQL.svelte b/src/routes/databases/[id]/_Databases/_PostgreSQL.svelte new file mode 100644 index 000000000..dc585c8e4 --- /dev/null +++ b/src/routes/databases/[id]/_Databases/_PostgreSQL.svelte @@ -0,0 +1,51 @@ + + +
    +
    PostgreSQL
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    diff --git a/src/routes/databases/[id]/_Databases/_Redis.svelte b/src/routes/databases/[id]/_Databases/_Redis.svelte new file mode 100644 index 000000000..ff8f83113 --- /dev/null +++ b/src/routes/databases/[id]/_Databases/_Redis.svelte @@ -0,0 +1,64 @@ + + +
    +
    Redis
    +
    +
    + +
    + +
    + +
    +
    + +
    diff --git a/src/routes/databases/[id]/__layout.svelte b/src/routes/databases/[id]/__layout.svelte new file mode 100644 index 000000000..2da542993 --- /dev/null +++ b/src/routes/databases/[id]/__layout.svelte @@ -0,0 +1,182 @@ + + + + + + diff --git a/src/routes/databases/[id]/configuration/destination.json.ts b/src/routes/databases/[id]/configuration/destination.json.ts new file mode 100644 index 000000000..24373beed --- /dev/null +++ b/src/routes/databases/[id]/configuration/destination.json.ts @@ -0,0 +1,19 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { destinationId } = await event.request.json(); + + try { + await db.configureDestinationForDatabase({ id, destinationId }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/[id]/configuration/destination.svelte b/src/routes/databases/[id]/configuration/destination.svelte new file mode 100644 index 000000000..1a8afc817 --- /dev/null +++ b/src/routes/databases/[id]/configuration/destination.svelte @@ -0,0 +1,93 @@ + + + + +
    +
    Configure Destination
    +
    +
    + {#if !destinations || destinations.length === 0} +
    +
    No configurable Destination found
    +
    + + + +
    +
    + {:else} +
    + {#each destinations as destination} +
    +
    handleSubmit(destination.id)}> + +
    +
    + {/each} +
    + {/if} +
    diff --git a/src/routes/databases/[id]/configuration/type.json.ts b/src/routes/databases/[id]/configuration/type.json.ts new file mode 100644 index 000000000..63685a9c5 --- /dev/null +++ b/src/routes/databases/[id]/configuration/type.json.ts @@ -0,0 +1,32 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + return { + status: 200, + body: { + types: supportedDatabaseTypesAndVersions + } + }; +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { type } = await event.request.json(); + + try { + await db.configureDatabaseType({ id, type }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/[id]/configuration/type.svelte b/src/routes/databases/[id]/configuration/type.svelte new file mode 100644 index 000000000..2d527a7b9 --- /dev/null +++ b/src/routes/databases/[id]/configuration/type.svelte @@ -0,0 +1,81 @@ + + + + +
    +
    Select a Database type
    +
    + +
    + {#each types as type} +
    +
    handleSubmit(type.name)}> + +
    +
    + {/each} +
    diff --git a/src/routes/databases/[id]/configuration/version.json.ts b/src/routes/databases/[id]/configuration/version.json.ts new file mode 100644 index 000000000..37d82fcaf --- /dev/null +++ b/src/routes/databases/[id]/configuration/version.json.ts @@ -0,0 +1,36 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { type } = await db.getDatabase({ id, teamId }); + + return { + status: 200, + body: { + versions: supportedDatabaseTypesAndVersions.find((name) => name.name === type).versions + } + }; +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { version } = await event.request.json(); + + try { + await db.setDatabase({ id, version }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/[id]/configuration/version.svelte b/src/routes/databases/[id]/configuration/version.svelte new file mode 100644 index 000000000..9e0761130 --- /dev/null +++ b/src/routes/databases/[id]/configuration/version.svelte @@ -0,0 +1,63 @@ + + + + +
    +
    Select a Database version
    +
    + +
    + {#each versions as version} +
    +
    handleSubmit(version)}> + +
    +
    + {/each} +
    diff --git a/src/routes/databases/[id]/delete.json.ts b/src/routes/databases/[id]/delete.json.ts new file mode 100644 index 000000000..7cbd5b756 --- /dev/null +++ b/src/routes/databases/[id]/delete.json.ts @@ -0,0 +1,22 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler, stopDatabase } from '$lib/database'; +import { deleteProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const del: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + try { + const database = await db.getDatabase({ id, teamId }); + if (database.destinationDockerId) { + const everStarted = await stopDatabase(database); + if (everStarted) await deleteProxy({ id }); + } + await db.removeDatabase({ id }); + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/[id]/index.json.ts b/src/routes/databases/[id]/index.json.ts new file mode 100644 index 000000000..1f08ab7e6 --- /dev/null +++ b/src/routes/databases/[id]/index.json.ts @@ -0,0 +1,71 @@ +import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { generateDatabaseConfiguration, getVersions, PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const database = await db.getDatabase({ id, teamId }); + const { destinationDockerId, destinationDocker } = database; + + let state = 'not started'; + if (destinationDockerId) { + const host = getEngine(destinationDocker.engine); + + try { + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}` + ); + + if (JSON.parse(stdout).Running) { + state = 'running'; + } + } catch (error) { + // if (!error.stderr.includes('No such object')) { + // console.log(error) + // } + } + } + const configuration = generateDatabaseConfiguration(database); + const settings = await db.listSettings(); + return { + body: { + privatePort: configuration?.privatePort, + database, + state, + versions: getVersions(database.type), + settings + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + const { name, defaultDatabase, dbUser, dbUserPassword, rootUser, rootUserPassword, version } = + await event.request.json(); + + try { + await db.updateDatabase({ + id, + name, + defaultDatabase, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + version + }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/[id]/index.svelte b/src/routes/databases/[id]/index.svelte new file mode 100644 index 000000000..d7d262699 --- /dev/null +++ b/src/routes/databases/[id]/index.svelte @@ -0,0 +1,67 @@ + + + + +
    +
    + {database.name} +
    + + {#if database.type === 'clickhouse'} + + {:else if database.type === 'couchdb'} + + {:else if database.type === 'mongodb'} + + {:else if database.type === 'mysql'} + + {:else if database.type === 'postgresql'} + + {:else if database.type === 'redis'} + + {/if} + +
    + + diff --git a/src/routes/databases/[id]/settings.json.ts b/src/routes/databases/[id]/settings.json.ts new file mode 100644 index 000000000..bda37c8db --- /dev/null +++ b/src/routes/databases/[id]/settings.json.ts @@ -0,0 +1,34 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database'; +import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body, teamId } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { isPublic, appendOnly = true } = await event.request.json(); + + try { + await db.setDatabase({ id, isPublic, appendOnly }); + const database = await db.getDatabase({ id, teamId }); + const { destinationDockerId, destinationDocker, publicPort } = database; + const { privatePort } = generateDatabaseConfiguration(database); + + if (destinationDockerId) { + if (isPublic) { + await startTcpProxy(destinationDocker, id, publicPort, privatePort); + } else { + await stopTcpHttpProxy(destinationDocker, publicPort); + } + } + + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/[id]/start.json.ts b/src/routes/databases/[id]/start.json.ts new file mode 100644 index 000000000..30c18f66f --- /dev/null +++ b/src/routes/databases/[id]/start.json.ts @@ -0,0 +1,82 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { generateDatabaseConfiguration, PrismaErrorHandler } from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { makeLabelForStandaloneDatabase } from '$lib/buildPacks/common'; +import { startTcpProxy } from '$lib/haproxy'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const database = await db.getDatabase({ id, teamId }); + const { + type, + destinationDockerId, + destinationDocker, + publicPort, + settings: { isPublic } + } = database; + const { privatePort, environmentVariables, image, volume, ulimits } = + generateDatabaseConfiguration(database); + + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + const engine = destinationDocker.engine; + const volumeName = volume.split(':')[0]; + const labels = await makeLabelForStandaloneDatabase({ id, image, volume }); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + + const composeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image, + networks: [network], + environment: environmentVariables, + volumes: [volume], + ulimits, + labels, + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [volumeName]: { + external: true + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + try { + await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${volumeName}`); + } catch (error) { + console.log(error); + } + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + if (isPublic) await startTcpProxy(destinationDocker, id, publicPort, privatePort); + return { + status: 200 + }; + } catch (error) { + throw { + error + }; + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/[id]/stop.json.ts b/src/routes/databases/[id]/stop.json.ts new file mode 100644 index 000000000..42b964403 --- /dev/null +++ b/src/routes/databases/[id]/stop.json.ts @@ -0,0 +1,25 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler, stopDatabase } from '$lib/database'; +import { stopTcpHttpProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const database = await db.getDatabase({ id, teamId }); + const everStarted = await stopDatabase(database); + if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort); + await db.setDatabase({ id, isPublic: false }); + + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/index.json.ts b/src/routes/databases/index.json.ts new file mode 100644 index 000000000..ae9b059e8 --- /dev/null +++ b/src/routes/databases/index.json.ts @@ -0,0 +1,20 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + try { + const databases = await db.listDatabases(teamId); + return { + status: 200, + body: { + databases + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/databases/index.svelte b/src/routes/databases/index.svelte new file mode 100644 index 000000000..f476b3c66 --- /dev/null +++ b/src/routes/databases/index.svelte @@ -0,0 +1,87 @@ + + + + +
    +
    Databases
    + + + +
    + + diff --git a/src/routes/destinations/[id]/_FoundApp.svelte b/src/routes/destinations/[id]/_FoundApp.svelte new file mode 100644 index 000000000..f183388fa --- /dev/null +++ b/src/routes/destinations/[id]/_FoundApp.svelte @@ -0,0 +1,77 @@ + + +
    +
    {app.domain}
    + {#if loading} +
    Loading...
    + {:else if app.foundByDomain} +
    + Domain already configured for + {app.foundName} +
    + {:else if app.foundByRepository} +
    + Repository already configured for + {app.foundName} +
    + {:else} + + {/if} +
    diff --git a/src/routes/destinations/[id]/_LocalDocker.svelte b/src/routes/destinations/[id]/_LocalDocker.svelte new file mode 100644 index 000000000..76d2c41bd --- /dev/null +++ b/src/routes/destinations/[id]/_LocalDocker.svelte @@ -0,0 +1,188 @@ + + +
    +
    +
    +
    Configuration
    + + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
      +
      ${ + cannotDisable + ? 'You cannot disable this proxy as FQDN is configured for Coolify.' + : '' + }`} + /> +
    +
    +
    +
    + + + diff --git a/src/routes/destinations/[id]/__layout.svelte b/src/routes/destinations/[id]/__layout.svelte new file mode 100644 index 000000000..9d9241097 --- /dev/null +++ b/src/routes/destinations/[id]/__layout.svelte @@ -0,0 +1,68 @@ + + + + + + diff --git a/src/routes/destinations/[id]/index.json.ts b/src/routes/destinations/[id]/index.json.ts new file mode 100644 index 000000000..3b878f9ca --- /dev/null +++ b/src/routes/destinations/[id]/index.json.ts @@ -0,0 +1,55 @@ +import { asyncExecShell, getEngine, getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { checkContainer } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const destination = await db.getDestination({ id, teamId }); + const settings = await db.listSettings(); + const state = await checkContainer(destination.engine, 'coolify-haproxy'); + return { + status: 200, + body: { + destination, + settings, + state + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { name, engine, network } = await event.request.json(); + + try { + await db.updateDestination({ id, name, engine, network }); + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const del: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + await db.removeDestination({ id }); + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/destinations/[id]/index.svelte b/src/routes/destinations/[id]/index.svelte new file mode 100644 index 000000000..a4527d46e --- /dev/null +++ b/src/routes/destinations/[id]/index.svelte @@ -0,0 +1,46 @@ + + + + +
    +
    Destination
    + > + {destination.name} +
    + + diff --git a/src/routes/destinations/[id]/scan.json.ts b/src/routes/destinations/[id]/scan.json.ts new file mode 100644 index 000000000..ab02cd2e6 --- /dev/null +++ b/src/routes/destinations/[id]/scan.json.ts @@ -0,0 +1,64 @@ +import { asyncExecShell, getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (request) => { + const { teamId, status, body } = await getUserDetails(request); + if (status === 401) return { status, body }; + + const { id } = request.params; + + const destinationDocker = await db.getDestination({ id, teamId }); + const docker = dockerInstance({ destinationDocker }); + const listContainers = await docker.engine.listContainers({ + filters: { network: [destinationDocker.network] } + }); + const containers = listContainers.filter((container) => { + return container.Labels['coolify.configuration']; + }); + const jsonContainers = containers + .map((container) => + JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString()) + ) + .filter((container) => container.type === 'manual'); + return { + body: { + containers: jsonContainers + } + }; +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + let { fqdn, projectId, repository, branch } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + if (projectId) projectId = Number(projectId); + + try { + const foundByDomain = await db.prisma.application.findFirst({ where: { fqdn } }); + const foundByRepository = await db.prisma.application.findFirst({ + where: { repository, branch, projectId } + }); + if (foundByDomain) { + return { + status: 200, + body: { by: 'domain', name: foundByDomain.name } + }; + } + if (foundByRepository) { + return { + status: 200, + body: { by: 'repository', name: foundByRepository.name } + }; + } + return { + status: 404 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/destinations/[id]/settings.json.ts b/src/routes/destinations/[id]/settings.json.ts new file mode 100644 index 000000000..952acf92f --- /dev/null +++ b/src/routes/destinations/[id]/settings.json.ts @@ -0,0 +1,18 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { engine, isCoolifyProxyUsed } = await event.request.json(); + + try { + await db.setDestinationSettings({ engine, isCoolifyProxyUsed }); + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/destinations/[id]/start.json.ts b/src/routes/destinations/[id]/start.json.ts new file mode 100644 index 000000000..b0d943bf9 --- /dev/null +++ b/src/routes/destinations/[id]/start.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import { PrismaErrorHandler } from '$lib/database'; +import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { engine } = await event.request.json(); + + try { + await startCoolifyProxy(engine); + return { + status: 200 + }; + } catch (error) { + await stopCoolifyProxy(engine); + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/destinations/[id]/stop.json.ts b/src/routes/destinations/[id]/stop.json.ts new file mode 100644 index 000000000..9735e2c64 --- /dev/null +++ b/src/routes/destinations/[id]/stop.json.ts @@ -0,0 +1,19 @@ +import { getUserDetails } from '$lib/common'; +import { PrismaErrorHandler } from '$lib/database'; +import { stopCoolifyProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { engine } = await event.request.json(); + try { + await stopCoolifyProxy(engine); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/destinations/index.json.ts b/src/routes/destinations/index.json.ts new file mode 100644 index 000000000..881dc29dd --- /dev/null +++ b/src/routes/destinations/index.json.ts @@ -0,0 +1,19 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (request) => { + const { teamId, status, body } = await getUserDetails(request); + if (status === 401) return { status, body }; + + try { + return { + body: { + destinations: await db.listDestinations(teamId) + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/destinations/index.svelte b/src/routes/destinations/index.svelte new file mode 100644 index 000000000..01f860b21 --- /dev/null +++ b/src/routes/destinations/index.svelte @@ -0,0 +1,66 @@ + + + + +
    +
    Destinations
    + {#if $session.isAdmin} + + + + {/if} +
    +
    + {#if !destinations || destinations.length === 0} +
    +
    No destination found
    +
    + {:else} +
    + {#each destinations as destination} + +
    +
    {destination.name}
    +
    {destination.network}
    +
    +
    + {/each} +
    + {/if} +
    diff --git a/src/routes/index.json.ts b/src/routes/index.json.ts new file mode 100644 index 000000000..562c1186f --- /dev/null +++ b/src/routes/index.json.ts @@ -0,0 +1,50 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { userId, teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + try { + const applicationsCount = await (await db.listApplications(teamId)).length; + const sourcesCount = await (await db.listSources(teamId)).length; + const destinationsCount = await (await db.listDestinations(teamId)).length; + const teamsCount = await (await db.getMyTeams({ userId })).length; + const databasesCount = await (await db.listDatabases(teamId)).length; + const servicesCount = await (await db.listServices(teamId)).length; + return { + body: { + applicationsCount, + sourcesCount, + destinationsCount, + teamsCount, + databasesCount, + servicesCount + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event, false); + if (status === 401) return { status, body }; + + const { cookie, value } = await event.request.json(); + const from = event.url.searchParams.get('from') || '/'; + + return { + status: 302, + body: {}, + headers: { + 'set-cookie': [ + `${cookie}=${value}; HttpOnly; Path=/; Max-Age=15778800;`, + 'gitlabToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' + ], + Location: from + } + }; +}; diff --git a/src/routes/index.svelte b/src/routes/index.svelte index e9f851d20..8abfe0fc7 100644 --- a/src/routes/index.svelte +++ b/src/routes/index.svelte @@ -1,116 +1,108 @@ - -
    -
    -
    -

    - Coolify -

    -

    - An open-source, hassle-free, self-hostable
    - Heroku - & Netlify alternative -

    - {#if loading} - - {:else} -
    - {#if !$session.isLoggedIn} - {#if $settings.clientId} - - {:else} -
    -
    -
    - - -
    -
    - - -
    -
    -
    - -
    -
    - {/if} - {:else} - - {/if} -
    - {/if} + + +
    +
    Dashboard
    +
    + +
    + diff --git a/src/routes/login/index.json.ts b/src/routes/login/index.json.ts new file mode 100644 index 000000000..26c912a93 --- /dev/null +++ b/src/routes/login/index.json.ts @@ -0,0 +1,33 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { email, password } = await event.request.json(); + + try { + const { body } = await db.login({ email, password }); + event.locals.session.data = body; + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const get: RequestHandler = async (event) => { + const { userId } = await getUserDetails(event, false); + if (!userId) { + return { + status: 401 + }; + } + try { + await db.getUser({ userId }); + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/login/index.svelte b/src/routes/login/index.svelte new file mode 100644 index 000000000..25da6b274 --- /dev/null +++ b/src/routes/login/index.svelte @@ -0,0 +1,74 @@ + + +
    + {#if $session.uid} +
    Already logged in...
    + {:else} +
    +
    +
    Coolify
    +
    v{$session.version}
    + + + +
    + +
    +
    +
    + {/if} +
    diff --git a/src/routes/logout/index.json.ts b/src/routes/logout/index.json.ts new file mode 100644 index 000000000..08ec57ac5 --- /dev/null +++ b/src/routes/logout/index.json.ts @@ -0,0 +1,15 @@ +export async function del({ locals }) { + locals.session.destroy(); + + return { + headers: { + 'set-cookie': [ + 'teamId=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT', + 'gitlabToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' + ] + }, + body: { + ok: true + } + }; +} diff --git a/src/routes/new/application/import.json.ts b/src/routes/new/application/import.json.ts new file mode 100644 index 000000000..4bfe22268 --- /dev/null +++ b/src/routes/new/application/import.json.ts @@ -0,0 +1,29 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + let { name, fqdn, port, buildCommand, startCommand, installCommand } = await event.request.json(); + + if (fqdn) fqdn = fqdn.toLowerCase(); + if (port) port = Number(port); + + try { + const { id } = await db.importApplication({ + name, + teamId, + fqdn, + port, + buildCommand, + startCommand, + installCommand + }); + return { status: 201, body: { id } }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/new/application/index.json.ts b/src/routes/new/application/index.json.ts new file mode 100644 index 000000000..0d685af21 --- /dev/null +++ b/src/routes/new/application/index.json.ts @@ -0,0 +1,19 @@ +import { getUserDetails, uniqueName } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { name } = await event.request.json(); + if (!name) return { status: 400, body: { error: 'Missing name.' } }; + + try { + const { id } = await db.newApplication({ name, teamId }); + return { status: 201, body: { id } }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/new/application/index.svelte b/src/routes/new/application/index.svelte new file mode 100644 index 000000000..0f267fd85 --- /dev/null +++ b/src/routes/new/application/index.svelte @@ -0,0 +1,53 @@ + + + + +
    +
    Add New Application
    +
    +
    +
    +
    + + +
    +
    +
    diff --git a/src/routes/new/database/index.json.ts b/src/routes/new/database/index.json.ts new file mode 100644 index 000000000..e6b46ad42 --- /dev/null +++ b/src/routes/new/database/index.json.ts @@ -0,0 +1,18 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { name } = await event.request.json(); + + try { + const { id } = await db.newDatabase({ name, teamId }); + return { status: 201, body: { id } }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/new/database/index.svelte b/src/routes/new/database/index.svelte new file mode 100644 index 000000000..4ee9ee6fb --- /dev/null +++ b/src/routes/new/database/index.svelte @@ -0,0 +1,59 @@ + + + + +
    +
    Add New Database
    +
    +
    +
    +
    + + +
    +
    +
    diff --git a/src/routes/new/destination/_Docker.svelte b/src/routes/new/destination/_Docker.svelte new file mode 100644 index 000000000..044d0f24b --- /dev/null +++ b/src/routes/new/destination/_Docker.svelte @@ -0,0 +1,101 @@ + + +
    +
    +
    +
    Configuration
    + +
    +
    + +
    + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    +
    +
      + (payload.isCoolifyProxyUsed = !payload.isCoolifyProxyUsed)} + isPadding={false} + title="Use Coolify Proxy?" + description="This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker). Databases will have their own proxy." + /> +
    +
    +
    +
    diff --git a/src/routes/new/destination/check.json.ts b/src/routes/new/destination/check.json.ts new file mode 100644 index 000000000..bb8f8f50f --- /dev/null +++ b/src/routes/new/destination/check.json.ts @@ -0,0 +1,23 @@ +import { getUserDetails } from '$lib/common'; +import { isDockerNetworkExists, PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { network } = await event.request.json(); + try { + const found = await isDockerNetworkExists({ network }); + if (found) { + throw { + error: `Network ${network} already configured for another team!` + }; + } + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/new/destination/docker.json.ts b/src/routes/new/destination/docker.json.ts new file mode 100644 index 000000000..aaef77bf9 --- /dev/null +++ b/src/routes/new/destination/docker.json.ts @@ -0,0 +1,19 @@ +import { asyncExecShell, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { name, engine, network, isCoolifyProxyUsed } = await event.request.json(); + + try { + const id = await db.newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }); + return { status: 200, body: { id } }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/new/destination/index.svelte b/src/routes/new/destination/index.svelte new file mode 100644 index 000000000..b95c2a067 --- /dev/null +++ b/src/routes/new/destination/index.svelte @@ -0,0 +1,43 @@ + + +
    +
    Add New Destination
    +
    +
    +
    Predefined destinations
    +
    + + +
    +
    +{#if selected === 'docker'} + +{:else} +
    Not implemented yet
    +{/if} diff --git a/src/routes/new/service/index.json.ts b/src/routes/new/service/index.json.ts new file mode 100644 index 000000000..4a42c65dc --- /dev/null +++ b/src/routes/new/service/index.json.ts @@ -0,0 +1,18 @@ +import { getUserDetails, uniqueName } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { name } = await event.request.json(); + + try { + const { id } = await db.newService({ name, teamId }); + return { status: 201, body: { id } }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/new/service/index.svelte b/src/routes/new/service/index.svelte new file mode 100644 index 000000000..f658a4ec2 --- /dev/null +++ b/src/routes/new/service/index.svelte @@ -0,0 +1,59 @@ + + + + +
    +
    Add New Service
    +
    +
    +
    +
    + + +
    +
    +
    diff --git a/src/routes/new/source/_Github.svelte b/src/routes/new/source/_Github.svelte new file mode 100644 index 000000000..d1d5f5c88 --- /dev/null +++ b/src/routes/new/source/_Github.svelte @@ -0,0 +1,101 @@ + + +
    +
    +
    +
    Configuration
    + +
    +
    + + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    diff --git a/src/routes/new/source/_Gitlab.svelte b/src/routes/new/source/_Gitlab.svelte new file mode 100644 index 000000000..5e7ad866e --- /dev/null +++ b/src/routes/new/source/_Gitlab.svelte @@ -0,0 +1,82 @@ + + +
    +
    +
    +
    Configuration
    + +
    +
    + + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    diff --git a/src/routes/new/source/index.json.ts b/src/routes/new/source/index.json.ts new file mode 100644 index 000000000..3debc81d6 --- /dev/null +++ b/src/routes/new/source/index.json.ts @@ -0,0 +1,17 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { name, type, htmlUrl, apiUrl, organization } = await event.request.json(); + try { + const { id } = await db.newSource({ name, teamId, type, htmlUrl, apiUrl, organization }); + return { status: 201, body: { id } }; + } catch (e) { + return PrismaErrorHandler(e); + } +}; diff --git a/src/routes/new/source/index.svelte b/src/routes/new/source/index.svelte new file mode 100644 index 000000000..7c9b4973c --- /dev/null +++ b/src/routes/new/source/index.svelte @@ -0,0 +1,66 @@ + + +
    +
    Add New Git Source
    +
    + +
    +
    Offical providers
    +
    + + + +
    +
    +
    + {#if gitSource.type === 'github'} + + {:else if gitSource.type === 'gitlab'} + + {:else if gitSource.type === 'bitbucket'} +
    Not implemented yet
    + {/if} +
    diff --git a/src/routes/new/team/index.json.ts b/src/routes/new/team/index.json.ts new file mode 100644 index 000000000..e8eae866d --- /dev/null +++ b/src/routes/new/team/index.json.ts @@ -0,0 +1,18 @@ +import { getUserDetails, uniqueName } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { userId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { name } = await event.request.json(); + + try { + const { id } = await db.newTeam({ name, userId }); + return { status: 201, body: { id } }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/new/team/index.svelte b/src/routes/new/team/index.svelte new file mode 100644 index 000000000..75c75ced2 --- /dev/null +++ b/src/routes/new/team/index.svelte @@ -0,0 +1,56 @@ + + + + +
    +
    Add New Team
    +
    + +
    +
    +
    + + +
    +
    +
    diff --git a/src/routes/servers/index.svelte b/src/routes/servers/index.svelte deleted file mode 100644 index ce68b4a9b..000000000 --- a/src/routes/servers/index.svelte +++ /dev/null @@ -1,101 +0,0 @@ - - - - -
    -
    -
    Servers
    -
    -
    - -
    -
    -
    {hostname}
    -
    Filesystem Usage
    - {#each filesystems as filesystem} - -
    - {filesystem.mount}: {(filesystem.available / 1024 / 1024).toFixed()}MB ({filesystem.use}%) free of {(filesystem.size /1024 /1024).toFixed()}MB -
    - {/each} -
    Docker Reclaimable
    - {#each dockerReclaimable as reclaimable} -
    - {reclaimable.Type}: {reclaimable.Reclaimable} of {reclaimable.Size} -
    - {/each} - - - - - -
    -
    diff --git a/src/routes/service/[name]/__layout.svelte b/src/routes/service/[name]/__layout.svelte deleted file mode 100644 index 5fcc23a3e..000000000 --- a/src/routes/service/[name]/__layout.svelte +++ /dev/null @@ -1,71 +0,0 @@ - - - - -
    - -
    diff --git a/src/routes/service/[name]/configuration.svelte b/src/routes/service/[name]/configuration.svelte deleted file mode 100644 index 44a8c9ffa..000000000 --- a/src/routes/service/[name]/configuration.svelte +++ /dev/null @@ -1,126 +0,0 @@ - - -{#await loadServiceConfig()} - -{:then} -
    -
    - {#if $page.params.name === 'plausible'} -
    Plausible Analytics
    - {:else if $page.params.name === 'nocodb'} -
    NocoDB
    - {:else if $page.params.name === 'code-server'} -
    VSCode Server
    - {:else if $page.params.name === 'minio'} -
    MinIO
    - {:else if $page.params.name.match(/wp-/)} -
    Wordpress({service.config.baseURL.replace('https://','')})
    - {/if} - -
    - {#if $page.params.name === 'plausible'} - plausible logo - {:else if $page.params.name === 'nocodb'} - nocodb logo - {:else if $page.params.name === 'code-server'} - - - - {:else if $page.params.name === 'minio'} - minio logo - {:else if $page.params.name.match(/wp-/)} - - - - {/if} -
    - - - - -
    -
    -
    -
    - {#if $page.params.name === 'plausible'} - - {:else if $page.params.name === 'nocodb'} -
    Nothing to show here. Enjoy using NocoDB!
    - {:else if $page.params.name === 'code-server'} - - {:else if $page.params.name === 'minio'} - - {:else if $page.params.name.match(/wp-/)} -
    Nothing to show here. Enjoy using WordPress!
    - {/if} -
    -
    -{/await} diff --git a/src/routes/service/new/[type]/__layout.svelte b/src/routes/service/new/[type]/__layout.svelte deleted file mode 100644 index 1846ab576..000000000 --- a/src/routes/service/new/[type]/__layout.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - -{#await checkService()} - -{:then} -
    - -
    -{/await} diff --git a/src/routes/service/new/[type]/index.svelte b/src/routes/service/new/[type]/index.svelte deleted file mode 100644 index 93ae461b6..000000000 --- a/src/routes/service/new/[type]/index.svelte +++ /dev/null @@ -1,509 +0,0 @@ - - -
    -
    - Deploy new - {#if $page.params.type === 'plausible'} - Plausible Analytics - {:else if $page.params.type === 'nocodb'} - NocoDB - {:else if $page.params.type === 'code-server'} - VSCode Server - {:else if $page.params.type === 'minio'} - MinIO - {:else if $page.params.type === 'wordpress'} - Wordpress - {/if} -
    -
    -{#if loading} - -{:else if $page.params.type === 'plausible'} -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    - -
    -{:else if $page.params.type === 'nocodb'} -
    -
    - - -
    - - -
    -{:else if $page.params.type === 'code-server'} -
    -
    - - -
    - - -
    -{:else if $page.params.type === 'minio'} -
    -
    - - -
    - - -
    -{:else if $page.params.type === 'wordpress'} -
    -
    - - -
    -
    -
    -
      -
    • -
      -

      Use remote MySQL database?

      -

      - If not, Coolify will create a local database for you. -

      -
      - -
    • -
    -
    - {#if $newWordpressService.remoteDB} -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    - {/if} -
    - - +
    +
    +
    +
    MySQL
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    diff --git a/src/routes/services/[id]/__layout.svelte b/src/routes/services/[id]/__layout.svelte new file mode 100644 index 000000000..0beb4df6c --- /dev/null +++ b/src/routes/services/[id]/__layout.svelte @@ -0,0 +1,202 @@ + + + + + + diff --git a/src/routes/services/[id]/check.json.ts b/src/routes/services/[id]/check.json.ts new file mode 100644 index 000000000..6aff4c7a2 --- /dev/null +++ b/src/routes/services/[id]/check.json.ts @@ -0,0 +1,26 @@ +import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + let { fqdn } = await event.request.json(); + + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + const found = await db.isDomainConfigured({ id, fqdn }); + return { + status: found ? 500 : 200, + body: { + error: found && `Domain ${getDomain(fqdn)} is already configured` + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/configuration/destination.json.ts b/src/routes/services/[id]/configuration/destination.json.ts new file mode 100644 index 000000000..7a2556b4b --- /dev/null +++ b/src/routes/services/[id]/configuration/destination.json.ts @@ -0,0 +1,19 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + + const { destinationId } = await event.request.json(); + + try { + await db.configureDestinationForService({ id, destinationId }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/configuration/destination.svelte b/src/routes/services/[id]/configuration/destination.svelte new file mode 100644 index 000000000..d7220df87 --- /dev/null +++ b/src/routes/services/[id]/configuration/destination.svelte @@ -0,0 +1,91 @@ + + + + +
    +
    Configure Destination
    +
    +
    + {#if !destinations || destinations.length === 0} +
    +
    No configurable Destination found
    +
    + + + +
    +
    + {:else} +
    + {#each destinations as destination} +
    +
    handleSubmit(destination.id)}> + +
    +
    + {/each} +
    + {/if} +
    diff --git a/src/routes/services/[id]/configuration/type.json.ts b/src/routes/services/[id]/configuration/type.json.ts new file mode 100644 index 000000000..02595a0c2 --- /dev/null +++ b/src/routes/services/[id]/configuration/type.json.ts @@ -0,0 +1,32 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler, supportedServiceTypesAndVersions } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + return { + status: 200, + body: { + types: supportedServiceTypesAndVersions + } + }; +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { type } = await event.request.json(); + + try { + await db.configureServiceType({ id, type }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/configuration/type.svelte b/src/routes/services/[id]/configuration/type.svelte new file mode 100644 index 000000000..5fb0a0559 --- /dev/null +++ b/src/routes/services/[id]/configuration/type.svelte @@ -0,0 +1,79 @@ + + + + +
    +
    Select a Service
    +
    + +
    + {#each types as type} +
    +
    handleSubmit(type.name)}> + +
    +
    + {/each} +
    diff --git a/src/routes/services/[id]/configuration/version.json.ts b/src/routes/services/[id]/configuration/version.json.ts new file mode 100644 index 000000000..e1e68e69b --- /dev/null +++ b/src/routes/services/[id]/configuration/version.json.ts @@ -0,0 +1,40 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler, supportedServiceTypesAndVersions } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const { type } = await db.getService({ id, teamId }); + return { + status: 200, + body: { + versions: supportedServiceTypesAndVersions.find((name) => name.name === type).versions + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { version } = await event.request.json(); + + try { + await db.setService({ id, version }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/configuration/version.svelte b/src/routes/services/[id]/configuration/version.svelte new file mode 100644 index 000000000..5f2930487 --- /dev/null +++ b/src/routes/services/[id]/configuration/version.svelte @@ -0,0 +1,63 @@ + + + + +
    +
    Select a Service version
    +
    + +
    + {#each versions as version} +
    +
    handleSubmit(version)}> + +
    +
    + {/each} +
    diff --git a/src/routes/services/[id]/delete.json.ts b/src/routes/services/[id]/delete.json.ts new file mode 100644 index 000000000..660882747 --- /dev/null +++ b/src/routes/services/[id]/delete.json.ts @@ -0,0 +1,18 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const del: RequestHandler = async (events) => { + const { teamId, status, body } = await getUserDetails(events); + if (status === 401) return { status, body }; + + const { id } = events.params; + + try { + await db.removeService({ id }); + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/index.json.ts b/src/routes/services/[id]/index.json.ts new file mode 100644 index 000000000..eefed08b0 --- /dev/null +++ b/src/routes/services/[id]/index.json.ts @@ -0,0 +1,69 @@ +import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { + generateDatabaseConfiguration, + getServiceImage, + getVersions, + PrismaErrorHandler +} from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker, type, version } = service; + + let isRunning = false; + if (destinationDockerId) { + const host = getEngine(destinationDocker.engine); + const docker = dockerInstance({ destinationDocker }); + const baseImage = getServiceImage(type); + docker.engine.pull(`${baseImage}:${version}`); + try { + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}` + ); + + if (JSON.parse(stdout).Running) { + isRunning = true; + } + } catch (error) { + // + } + } + return { + body: { + isRunning, + service + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +// export const post: RequestHandler = async (request) => { +// const { teamId, status, body } = await getUserDetails(request); +// if (status === 401) return { status, body } +// const { id } = request.params + +// const name = request.body.get('name') +// const defaultDatabase = request.body.get('defaultDatabase') +// const dbUser = request.body.get('dbUser') +// const dbUserPassword = request.body.get('dbUserPassword') +// const rootUser = request.body.get('rootUser') +// const rootUserPassword = request.body.get('rootUserPassword') +// const version = request.body.get('version') + +// try { +// return await db.updateDatabase({ id, name, defaultDatabase, dbUser, dbUserPassword, rootUser, rootUserPassword, version }) +// } catch (err) { +// return err +// } + +// } diff --git a/src/routes/services/[id]/index.svelte b/src/routes/services/[id]/index.svelte new file mode 100644 index 000000000..06dace107 --- /dev/null +++ b/src/routes/services/[id]/index.svelte @@ -0,0 +1,101 @@ + + + + +
    +
    + {service.name} +
    + {#if service.fqdn} + + + + + + + {/if} + +
    + {#if service.type === 'plausibleanalytics'} + + + + {:else if service.type === 'nocodb'} + + + + {:else if service.type === 'minio'} + + + + {:else if service.type === 'vscodeserver'} + + + + {:else if service.type === 'wordpress'} + + + + {/if} +
    +
    + + diff --git a/src/routes/services/[id]/minio/index.json.ts b/src/routes/services/[id]/minio/index.json.ts new file mode 100644 index 000000000..d025604b5 --- /dev/null +++ b/src/routes/services/[id]/minio/index.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let { name, fqdn } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + await db.updateNocoDbOrMinioService({ id, fqdn, name }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/minio/start.json.ts b/src/routes/services/[id]/minio/start.json.ts new file mode 100644 index 000000000..3b7d78525 --- /dev/null +++ b/src/routes/services/[id]/minio/start.json.ts @@ -0,0 +1,107 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { letsEncrypt } from '$lib/letsencrypt'; +import { + configureSimpleServiceProxyOn, + reloadHaproxy, + startHttpProxy, + startTcpProxy +} from '$lib/haproxy'; +import getPort from 'get-port'; +import { getDomain } from '$lib/components/common'; +import { PrismaErrorHandler } from '$lib/database'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { + type, + version, + fqdn, + destinationDockerId, + destinationDocker, + minio: { rootUser, rootUserPassword } + } = service; + + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + const publicPort = await getPort(); + const consolePort = 9001; + const apiPort = 9000; + const { workdir } = await createDirectories({ repository: type, buildId: id }); + + const config = { + image: `minio/minio:${version}`, + volume: `${id}-minio-data:/data`, + environmentVariables: { + MINIO_ROOT_USER: rootUser, + MINIO_ROOT_PASSWORD: rootUserPassword, + MINIO_BROWSER_REDIRECT_URL: fqdn + } + }; + const composeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: `minio/minio:${version}`, + command: `server /data --console-address ":${consolePort}"`, + environment: config.environmentVariables, + networks: [network], + volumes: [config.volume], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.volume.split(':')[0]]: { + external: true + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + try { + await asyncExecShell( + `DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}` + ); + } catch (error) { + console.log(error); + } + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + await configureSimpleServiceProxyOn({ id, domain, port: consolePort }); + + await db.updateMinioService({ id, publicPort }); + await startHttpProxy(destinationDocker, id, publicPort, apiPort); + + if (isHttps) { + await letsEncrypt({ domain, id }); + } + await reloadHaproxy(destinationDocker.engine); + return { + status: 200 + }; + } catch (error) { + console.log(error); + return PrismaErrorHandler(error); + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/minio/stop.json.ts b/src/routes/services/[id]/minio/stop.json.ts new file mode 100644 index 000000000..94e4f4be6 --- /dev/null +++ b/src/routes/services/[id]/minio/stop.json.ts @@ -0,0 +1,46 @@ +import { getEngine, getUserDetails, removeDestinationDocker } from '$lib/common'; +import { getDomain } from '$lib/components/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { checkContainer, configureSimpleServiceProxyOff, stopTcpHttpProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { + destinationDockerId, + destinationDocker, + fqdn, + minio: { publicPort } + } = service; + await db.updateMinioService({ id, publicPort: null }); + const domain = getDomain(fqdn); + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + const found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + } catch (error) { + console.error(error); + } + await stopTcpHttpProxy(destinationDocker, publicPort); + await configureSimpleServiceProxyOff({ domain }); + } + + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/nocodb/index.json.ts b/src/routes/services/[id]/nocodb/index.json.ts new file mode 100644 index 000000000..920ab24d8 --- /dev/null +++ b/src/routes/services/[id]/nocodb/index.json.ts @@ -0,0 +1,20 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + + let { name, fqdn } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + await db.updateNocoDbOrMinioService({ id, fqdn, name }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/nocodb/start.json.ts b/src/routes/services/[id]/nocodb/start.json.ts new file mode 100644 index 000000000..6d7d214a8 --- /dev/null +++ b/src/routes/services/[id]/nocodb/start.json.ts @@ -0,0 +1,66 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { letsEncrypt } from '$lib/letsencrypt'; +import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; +import { getDomain } from '$lib/components/common'; +import { PrismaErrorHandler } from '$lib/database'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { type, version, fqdn, destinationDockerId, destinationDocker } = service; + + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + + const composeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: `nocodb/nocodb:${version}`, + networks: [network], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + await configureSimpleServiceProxyOn({ id, domain, port: 8080 }); + + if (isHttps) { + await letsEncrypt({ domain, id }); + } + await reloadHaproxy(destinationDocker.engine); + return { + status: 200 + }; + } catch (error) { + console.log(error); + return PrismaErrorHandler(error); + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/nocodb/stop.json.ts b/src/routes/services/[id]/nocodb/stop.json.ts new file mode 100644 index 000000000..64a3141b3 --- /dev/null +++ b/src/routes/services/[id]/nocodb/stop.json.ts @@ -0,0 +1,39 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import { getDomain } from '$lib/components/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker, fqdn } = service; + const domain = getDomain(fqdn); + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + const found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + } catch (error) { + console.error(error); + } + await configureSimpleServiceProxyOff({ domain }); + } + + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/plausibleanalytics/activate.json.ts b/src/routes/services/[id]/plausibleanalytics/activate.json.ts new file mode 100644 index 000000000..2200fff3d --- /dev/null +++ b/src/routes/services/[id]/plausibleanalytics/activate.json.ts @@ -0,0 +1,33 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = await event.request.json(); + + try { + const { + destinationDockerId, + destinationDocker, + plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase } + } = await db.getService({ id, teamId }); + if (destinationDockerId) { + const docker = dockerInstance({ destinationDocker }); + const container = await docker.engine.getContainer(id); + const command = await container.exec({ + Cmd: [ + `psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"` + ] + }); + await command.start(); + } + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/plausibleanalytics/index.json.ts b/src/routes/services/[id]/plausibleanalytics/index.json.ts new file mode 100644 index 000000000..cd149685c --- /dev/null +++ b/src/routes/services/[id]/plausibleanalytics/index.json.ts @@ -0,0 +1,26 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + let { + name, + fqdn, + plausibleAnalytics: { email, username } + } = await event.request.json(); + + if (fqdn) fqdn = fqdn.toLowerCase(); + if (email) email = email.toLowerCase(); + + try { + await db.updatePlausibleAnalyticsService({ id, fqdn, name, email, username }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/plausibleanalytics/start.json.ts b/src/routes/services/[id]/plausibleanalytics/start.json.ts new file mode 100644 index 000000000..406e67adc --- /dev/null +++ b/src/routes/services/[id]/plausibleanalytics/start.json.ts @@ -0,0 +1,195 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { letsEncrypt } from '$lib/letsencrypt'; +import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; +import { getDomain } from '$lib/components/common'; +import { PrismaErrorHandler } from '$lib/database'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { + type, + version, + fqdn, + destinationDockerId, + destinationDocker, + plausibleAnalytics: { + id: plausibleDbId, + username, + email, + password, + postgresqlDatabase, + postgresqlPassword, + postgresqlUser, + secretKeyBase + } + } = service; + + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + + const config = { + plausibleAnalytics: { + image: `plausible/analytics:${version}`, + environmentVariables: { + ADMIN_USER_EMAIL: email, + ADMIN_USER_NAME: username, + ADMIN_USER_PWD: password, + BASE_URL: fqdn, + SECRET_KEY_BASE: secretKeyBase, + DISABLE_AUTH: 'false', + DISABLE_REGISTRATION: 'true', + DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}`, + CLICKHOUSE_DATABASE_URL: `http://${id}-clickhouse:8123/plausible` + } + }, + postgresql: { + volume: `${plausibleDbId}-postgresql-data:/var/lib/postgresql/data`, + image: 'bitnami/postgresql:13.2.0', + environmentVariables: { + POSTGRESQL_PASSWORD: postgresqlPassword, + POSTGRESQL_USERNAME: postgresqlUser, + POSTGRESQL_DATABASE: postgresqlDatabase + } + }, + clickhouse: { + volume: `${plausibleDbId}-clickhouse-data:/var/lib/clickhouse`, + image: 'yandex/clickhouse-server:21.3.2.5', + environmentVariables: {}, + ulimits: { + nofile: { + soft: 262144, + hard: 262144 + } + } + } + }; + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + const engine = destinationDocker.engine; + // const labels = await makeLabelForPlausibleAnalytics({ id, }) + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + + const clickhouseConfigXml = ` + + + warning + true + + + + + + + + + + `; + const clickhouseUserConfigXml = ` + + + + 0 + 0 + + + `; + + const initQuery = 'CREATE DATABASE IF NOT EXISTS plausible;'; + const initScript = 'clickhouse client --queries-file /docker-entrypoint-initdb.d/init.query'; + await fs.writeFile(`${workdir}/clickhouse-config.xml`, clickhouseConfigXml); + await fs.writeFile(`${workdir}/clickhouse-user-config.xml`, clickhouseUserConfigXml); + await fs.writeFile(`${workdir}/init.query`, initQuery); + await fs.writeFile(`${workdir}/init-db.sh`, initScript); + + const Dockerfile = ` +FROM ${config.clickhouse.image} +COPY ./clickhouse-config.xml /etc/clickhouse-server/users.d/logging.xml +COPY ./clickhouse-user-config.xml /etc/clickhouse-server/config.d/logging.xml +COPY ./init.query /docker-entrypoint-initdb.d/init.query +COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`; + + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile); + const composeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.plausibleAnalytics.image, + command: + 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"', + networks: [network], + environment: config.plausibleAnalytics.environmentVariables, + volumes: [config.postgresql.volume], + restart: 'always', + depends_on: [`${id}-postgresql`, `${id}-clickhouse`] + }, + [`${id}-postgresql`]: { + container_name: `${id}-postgresql`, + image: config.postgresql.image, + networks: [network], + environment: config.postgresql.environmentVariables, + volumes: [config.postgresql.volume], + restart: 'always' + }, + [`${id}-clickhouse`]: { + build: workdir, + container_name: `${id}-clickhouse`, + networks: [network], + environment: config.clickhouse.environmentVariables, + volumes: [config.clickhouse.volume], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.postgresql.volume.split(':')[0]]: { + external: true + }, + [config.clickhouse.volume.split(':')[0]]: { + external: true + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + try { + await asyncExecShell( + `DOCKER_HOST=${host} docker volume create ${config.postgresql.volume.split(':')[0]}` + ); + await asyncExecShell( + `DOCKER_HOST=${host} docker volume create ${config.clickhouse.volume.split(':')[0]}` + ); + } catch (error) { + console.log(error); + } + await asyncExecShell( + `DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d` + ); + await configureSimpleServiceProxyOn({ id, domain, port: 8000 }); + + if (isHttps) { + await letsEncrypt({ domain, id }); + } + await reloadHaproxy(destinationDocker.engine); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/plausibleanalytics/stop.json.ts b/src/routes/services/[id]/plausibleanalytics/stop.json.ts new file mode 100644 index 000000000..1c7fc9515 --- /dev/null +++ b/src/routes/services/[id]/plausibleanalytics/stop.json.ts @@ -0,0 +1,49 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import { getDomain } from '$lib/components/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker, fqdn } = service; + const domain = getDomain(fqdn); + + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + let found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + found = await checkContainer(engine, `${id}-postgresql`); + if (found) { + await removeDestinationDocker({ id: `${id}-postgresql`, engine }); + } + found = await checkContainer(engine, `${id}-clickhouse`); + if (found) { + await removeDestinationDocker({ id: `${id}-clickhouse`, engine }); + } + } catch (error) { + console.error(error); + } + + await configureSimpleServiceProxyOff({ domain }); + } + + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/vscodeserver/index.json.ts b/src/routes/services/[id]/vscodeserver/index.json.ts new file mode 100644 index 000000000..6824a31dd --- /dev/null +++ b/src/routes/services/[id]/vscodeserver/index.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let { name, fqdn } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + await db.updateVsCodeServer({ id, fqdn, name }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/vscodeserver/start.json.ts b/src/routes/services/[id]/vscodeserver/start.json.ts new file mode 100644 index 000000000..4e762c7c7 --- /dev/null +++ b/src/routes/services/[id]/vscodeserver/start.json.ts @@ -0,0 +1,93 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { letsEncrypt } from '$lib/letsencrypt'; +import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; +import { getDomain } from '$lib/components/common'; +import { PrismaErrorHandler } from '$lib/database'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { + type, + version, + fqdn, + destinationDockerId, + destinationDocker, + vscodeserver: { password } + } = service; + + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const config = { + image: `codercom/code-server:${version}`, + volume: `${id}-vscodeserver-data:/home/coder`, + environmentVariables: { + PASSWORD: password + } + }; + const composeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.image, + environment: config.environmentVariables, + networks: [network], + volumes: [config.volume], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.volume.split(':')[0]]: { + external: true + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + try { + await asyncExecShell( + `DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}` + ); + } catch (error) { + console.log(error); + } + + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + await configureSimpleServiceProxyOn({ id, domain, port: 8080 }); + + if (isHttps) { + await letsEncrypt({ domain, id }); + } + await reloadHaproxy(destinationDocker.engine); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/vscodeserver/stop.json.ts b/src/routes/services/[id]/vscodeserver/stop.json.ts new file mode 100644 index 000000000..e3faf42b5 --- /dev/null +++ b/src/routes/services/[id]/vscodeserver/stop.json.ts @@ -0,0 +1,38 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import { getDomain } from '$lib/components/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker, fqdn } = service; + const domain = getDomain(fqdn); + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + const found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + } catch (error) { + console.error(error); + } + await configureSimpleServiceProxyOff({ domain }); + } + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/wordpress/index.json.ts b/src/routes/services/[id]/wordpress/index.json.ts new file mode 100644 index 000000000..ec49b77ba --- /dev/null +++ b/src/routes/services/[id]/wordpress/index.json.ts @@ -0,0 +1,24 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + + let { + name, + fqdn, + wordpress: { extraConfig, mysqlDatabase } + } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + await db.updateWordpress({ id, fqdn, name, extraConfig, mysqlDatabase }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/wordpress/start.json.ts b/src/routes/services/[id]/wordpress/start.json.ts new file mode 100644 index 000000000..ffb0c11e7 --- /dev/null +++ b/src/routes/services/[id]/wordpress/start.json.ts @@ -0,0 +1,131 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { letsEncrypt } from '$lib/letsencrypt'; +import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; +import { getDomain } from '$lib/components/common'; +import { PrismaErrorHandler } from '$lib/database'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { + type, + version, + fqdn, + destinationDockerId, + destinationDocker, + wordpress: { + mysqlDatabase, + mysqlUser, + mysqlPassword, + extraConfig, + mysqlRootUser, + mysqlRootUserPassword + } + } = service; + + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const config = { + wordpress: { + image: `wordpress:${version}`, + volume: `${id}-wordpress-data:/var/www/html`, + environmentVariables: { + WORDPRESS_DB_HOST: `${id}-mysql`, + WORDPRESS_DB_USER: mysqlUser, + WORDPRESS_DB_PASSWORD: mysqlPassword, + WORDPRESS_DB_NAME: mysqlDatabase, + WORDPRESS_CONFIG_EXTRA: extraConfig + } + }, + mysql: { + image: `bitnami/mysql:5.7`, + volume: `${id}-mysql-data:/bitnami/mysql/data`, + environmentVariables: { + MYSQL_ROOT_PASSWORD: mysqlRootUserPassword, + MYSQL_ROOT_USER: mysqlRootUser, + MYSQL_USER: mysqlUser, + MYSQL_PASSWORD: mysqlPassword, + MYSQL_DATABASE: mysqlDatabase + } + } + }; + const composeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.wordpress.image, + environment: config.wordpress.environmentVariables, + networks: [network], + restart: 'always', + depends_on: [`${id}-mysql`] + }, + [`${id}-mysql`]: { + container_name: `${id}-mysql`, + image: config.mysql.image, + environment: config.mysql.environmentVariables, + networks: [network], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.mysql.volume.split(':')[0]]: { + external: true + }, + [config.wordpress.volume.split(':')[0]]: { + external: true + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + try { + await asyncExecShell( + `DOCKER_HOST=${host} docker volume create ${config.mysql.volume.split(':')[0]}` + ); + await asyncExecShell( + `DOCKER_HOST=${host} docker volume create ${config.wordpress.volume.split(':')[0]}` + ); + } catch (error) { + console.log(error); + } + + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + await configureSimpleServiceProxyOn({ id, domain, port: 80 }); + + if (isHttps) { + await letsEncrypt({ domain, id }); + } + await reloadHaproxy(destinationDocker.engine); + return { + status: 200 + }; + } catch (error) { + console.log(error); + return PrismaErrorHandler(error); + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/wordpress/stop.json.ts b/src/routes/services/[id]/wordpress/stop.json.ts new file mode 100644 index 000000000..f8f9d31dd --- /dev/null +++ b/src/routes/services/[id]/wordpress/stop.json.ts @@ -0,0 +1,42 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import { getDomain } from '$lib/components/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker, fqdn } = service; + const domain = getDomain(fqdn); + if (destinationDockerId) { + const engine = destinationDocker.engine; + try { + let found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + found = await checkContainer(engine, `${id}-mysql`); + if (found) { + await removeDestinationDocker({ id: `${id}-mysql`, engine }); + } + } catch (error) { + console.error(error); + } + await configureSimpleServiceProxyOff({ domain }); + } + + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/index.json.ts b/src/routes/services/index.json.ts new file mode 100644 index 000000000..f1a8afbf9 --- /dev/null +++ b/src/routes/services/index.json.ts @@ -0,0 +1,20 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + try { + const services = await db.listServices(teamId); + return { + body: { + services + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/index.svelte b/src/routes/services/index.svelte new file mode 100644 index 000000000..1800c5ed1 --- /dev/null +++ b/src/routes/services/index.svelte @@ -0,0 +1,85 @@ + + + + +
    +
    Services
    + + + +
    + + diff --git a/src/routes/settings.svelte b/src/routes/settings.svelte deleted file mode 100644 index 3001a2282..000000000 --- a/src/routes/settings.svelte +++ /dev/null @@ -1,190 +0,0 @@ - - - - -
    -
    -
    Settings
    -
    -
    - -
    -
    -
    -
    General
    -
    -
    -
      -
    • -
      -

      Registration allowed?

      -

      - Allow further registrations to the application. It's turned off after the first - registration. -

      -
      - -
    • -
    • -
      -

      Send errors automatically?

      -

      - Allow to send errors automatically to developer(s) at coolLabs (Andras Bacsai). This will help to fix bugs quicker. 🙏 -

      -
      - -
    • -
    -
    -
    -
    -
    -
    diff --git a/src/routes/settings/check.json.ts b/src/routes/settings/check.json.ts new file mode 100644 index 000000000..78e770421 --- /dev/null +++ b/src/routes/settings/check.json.ts @@ -0,0 +1,25 @@ +import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + + let { fqdn } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + const found = await db.isDomainConfigured({ id, fqdn }); + return { + status: found ? 500 : 200, + body: { + error: found && `Domain ${fqdn} is already configured` + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/settings/index.json.ts b/src/routes/settings/index.json.ts new file mode 100644 index 000000000..3a2498179 --- /dev/null +++ b/src/routes/settings/index.json.ts @@ -0,0 +1,112 @@ +import { dev } from '$app/env'; +import { getDomain, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { listSettings, PrismaErrorHandler } from '$lib/database'; +import { + checkContainer, + configureCoolifyProxyOff, + configureCoolifyProxyOn, + forceSSLOffApplication, + forceSSLOnApplication, + reloadHaproxy, + startCoolifyProxy +} from '$lib/haproxy'; +import { letsEncrypt } from '$lib/letsencrypt'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + try { + const settings = await listSettings(); + return { + status: 200, + body: { + settings + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const del: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (teamId !== '0') + return { + status: 401, + body: { + message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.' + } + }; + if (status === 401) return { status, body }; + + const { fqdn } = await event.request.json(); + + try { + await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } }); + const domain = getDomain(fqdn); + await configureCoolifyProxyOff({ domain }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (teamId !== '0') + return { + status: 401, + body: { + message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.' + } + }; + if (status === 401) return { status, body }; + + const { fqdn, isRegistrationEnabled } = await event.request.json(); + try { + const { + id, + fqdn: oldFqdn, + isRegistrationEnabled: oldIsRegistrationEnabled + } = await db.listSettings(); + if (oldIsRegistrationEnabled !== isRegistrationEnabled) { + await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } }); + } + if (oldFqdn && oldFqdn !== fqdn) { + const oldDomain = getDomain(oldFqdn); + if (oldFqdn) { + await configureCoolifyProxyOff({ domain: oldDomain }); + } + } + if (fqdn) { + const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); + if (!found) await startCoolifyProxy('/var/run/docker.sock'); + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + if (domain) { + await configureCoolifyProxyOn({ domain }); + if (isHttps && !dev) { + await letsEncrypt({ domain, isCoolify: true }); + await forceSSLOnApplication({ domain }); + await reloadHaproxy('/var/run/docker.sock'); + } + } + + await db.prisma.setting.update({ where: { id }, data: { fqdn } }); + await db.prisma.destinationDocker.updateMany({ + where: { engine: '/var/run/docker.sock' }, + data: { isCoolifyProxyUsed: true } + }); + } + + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/settings/index.svelte b/src/routes/settings/index.svelte new file mode 100644 index 000000000..48407e8b3 --- /dev/null +++ b/src/routes/settings/index.svelte @@ -0,0 +1,175 @@ + + + + +
    +
    Settings
    +
    +{#if $session.teamId === '0'} +
    +
    +
    +
    Global Settings
    + + {#if isFqdnSet} + + {/if} +
    +
    +
    +

    Domain (FQDN)

    +
    + + +
    +
    +
      + changeSettings('isRegistrationEnabled')} + /> +
    +
    +
    +
    +
    +
    HAProxy Settings
    +
    + stats page.`} + /> + +
    + + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +{/if} diff --git a/src/routes/sources/[id]/_Github.svelte b/src/routes/sources/[id]/_Github.svelte new file mode 100644 index 000000000..b3285fd6d --- /dev/null +++ b/src/routes/sources/[id]/_Github.svelte @@ -0,0 +1,91 @@ + + +{#if !source.githubAppId} + +{:else if source.githubApp?.installationId} +
    +
    +
    +
    General
    + {#if $session.isAdmin} + + + {/if} +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +{:else} + +{/if} diff --git a/src/routes/sources/[id]/_Gitlab.svelte b/src/routes/sources/[id]/_Gitlab.svelte new file mode 100644 index 000000000..4530cc0e0 --- /dev/null +++ b/src/routes/sources/[id]/_Gitlab.svelte @@ -0,0 +1,215 @@ + + +
    + {#if !source.gitlabApp?.appId} +
    +
    + +
    + +
    +
    + {#if payload.applicationType === 'group'} +
    + +
    + +
    +
    + {/if} + +
    + +
    + + + +
    +
    +
    Configuration
    + +
    + +
    + +
    + + +
    +
    + {#if payload.applicationType === 'group'} +
    + +
    + +
    +
    + {/if} +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + {:else} +
    +
    +
    +
    General
    + {#if $session.isAdmin} + + + {/if} +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + {/if} +
    diff --git a/src/routes/sources/[id]/__layout.svelte b/src/routes/sources/[id]/__layout.svelte new file mode 100644 index 000000000..0f5139558 --- /dev/null +++ b/src/routes/sources/[id]/__layout.svelte @@ -0,0 +1,68 @@ + + + + + + diff --git a/src/routes/sources/[id]/check.json.ts b/src/routes/sources/[id]/check.json.ts new file mode 100644 index 000000000..e30b70942 --- /dev/null +++ b/src/routes/sources/[id]/check.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + try { + const { oauthId } = await event.request.json(); + const found = await db.prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } }); + if (found) { + throw { + message: `GitLab App is already configured.` + }; + } + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/sources/[id]/gitlab.json.ts b/src/routes/sources/[id]/gitlab.json.ts new file mode 100644 index 000000000..b4f40d8aa --- /dev/null +++ b/src/routes/sources/[id]/gitlab.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + + try { + let { oauthId, groupName, appId, appSecret } = await event.request.json(); + + oauthId = Number(oauthId); + + await db.addSource({ id, teamId, oauthId, groupName, appId, appSecret }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/sources/[id]/index.json.ts b/src/routes/sources/[id]/index.json.ts new file mode 100644 index 000000000..1a3052763 --- /dev/null +++ b/src/routes/sources/[id]/index.json.ts @@ -0,0 +1,54 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (request) => { + const { teamId, status, body } = await getUserDetails(request); + if (status === 401) return { status, body }; + + const { id } = request.params; + try { + const source = await db.getSource({ id, teamId }); + const settings = await db.listSettings(); + return { + status: 200, + body: { + source, + settings + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const del: RequestHandler = async (request) => { + const { status, body } = await getUserDetails(request); + if (status === 401) return { status, body }; + + const { id } = request.params; + + try { + await db.removeSource({ id }); + return { status: 200 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + const { name } = await event.request.json(); + + try { + await db.updateGitsource({ id, name }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/sources/[id]/index.svelte b/src/routes/sources/[id]/index.svelte new file mode 100644 index 000000000..8f861c6dd --- /dev/null +++ b/src/routes/sources/[id]/index.svelte @@ -0,0 +1,49 @@ + + + + +
    +
    Git Source
    + > + {source.name} +
    + +
    + {#if source.type === 'github'} + + {:else if source.type === 'gitlab'} + + {/if} +
    diff --git a/src/routes/sources/[id]/newGithubApp.svelte b/src/routes/sources/[id]/newGithubApp.svelte new file mode 100644 index 000000000..80139acc2 --- /dev/null +++ b/src/routes/sources/[id]/newGithubApp.svelte @@ -0,0 +1,89 @@ + + + + +
    + Redirecting to Github... +
    diff --git a/src/routes/sources/index.json.ts b/src/routes/sources/index.json.ts new file mode 100644 index 000000000..bf774698e --- /dev/null +++ b/src/routes/sources/index.json.ts @@ -0,0 +1,16 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (request) => { + const { teamId, status, body } = await getUserDetails(request); + if (status === 401) return { status, body }; + + try { + const sources = await db.listSources(teamId); + return { status: 200, body: { sources } }; + } catch (err) { + return PrismaErrorHandler(err); + } +}; diff --git a/src/routes/sources/index.svelte b/src/routes/sources/index.svelte new file mode 100644 index 000000000..bd9039514 --- /dev/null +++ b/src/routes/sources/index.svelte @@ -0,0 +1,75 @@ + + + + +
    +
    Git Sources
    + {#if $session.isAdmin} + + + + {/if} +
    + diff --git a/src/routes/success.svelte b/src/routes/success.svelte deleted file mode 100644 index 796b7dfa4..000000000 --- a/src/routes/success.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -
    Succesfully logged in! 🎉
    -
    diff --git a/src/routes/teams/[id]/__layout.svelte b/src/routes/teams/[id]/__layout.svelte new file mode 100644 index 000000000..9ad4eb110 --- /dev/null +++ b/src/routes/teams/[id]/__layout.svelte @@ -0,0 +1,28 @@ + + + diff --git a/src/routes/teams/[id]/index.json.ts b/src/routes/teams/[id]/index.json.ts new file mode 100644 index 000000000..fc3a44195 --- /dev/null +++ b/src/routes/teams/[id]/index.json.ts @@ -0,0 +1,55 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { userId, status, body } = await getUserDetails(event, false); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const user = await db.prisma.user.findFirst({ + where: { id: userId, teams: { some: { id } } }, + include: { permission: true } + }); + if (!user) { + return { + status: 401 + }; + } + const permissions = await db.prisma.permission.findMany({ + where: { teamId: id }, + include: { user: { select: { id: true, email: true } } } + }); + const team = await db.prisma.team.findUnique({ where: { id }, include: { permissions: true } }); + const invitations = await db.prisma.teamInvitation.findMany({ where: { teamId: team.id } }); + return { + body: { + team, + permissions, + invitations + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { name } = await event.request.json(); + + try { + await db.prisma.team.update({ where: { id }, data: { name: { set: name } } }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/teams/[id]/index.svelte b/src/routes/teams/[id]/index.svelte new file mode 100644 index 000000000..c4f81d31e --- /dev/null +++ b/src/routes/teams/[id]/index.svelte @@ -0,0 +1,214 @@ + + + + +
    +
    Team
    + > + {team.name} +
    +
    +
    +
    +
    Settings
    +
    + +
    +
    + +
    + + +
    + {#if team.id === '0'} +
    + +
    + {/if} +
    + +
    +
    Members
    +
    +
    + + + + + + + {#each permissions as permission} + + + + {#if $session.isAdmin && permission.user.id !== $session.uid && permission.permission !== 'owner'} + + {:else} + + {/if} + + {/each} + + {#each invitations as invitation} + + + + {#if isAdmin(team.permissions[0].permission)} + + {:else} + + {/if} + + {/each} +
    EmailPermissionActions
    {permission.user.email} + {permission.user.id === $session.uid ? '(You)' : ''}{permission.permission} + + + No actions available
    {invitation.email} {invitation.permission} + + Pending invitation
    +
    +
    +{#if $session.isAdmin} +
    +
    +
    +
    Invite new member
    +
    + +
    +
    +
    +
    + +
    + + +
    +
    + +
    +{/if} diff --git a/src/routes/teams/[id]/invitation/accept.json.ts b/src/routes/teams/[id]/invitation/accept.json.ts new file mode 100644 index 000000000..253e48366 --- /dev/null +++ b/src/routes/teams/[id]/invitation/accept.json.ts @@ -0,0 +1,36 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dayjs } from '$lib/dayjs'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { userId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = await event.request.json(); + + try { + const invitation = await db.prisma.teamInvitation.findFirst({ + where: { uid: userId }, + rejectOnNotFound: true + }); + await db.prisma.team.update({ + where: { id: invitation.teamId }, + data: { users: { connect: { id: userId } } } + }); + await db.prisma.permission.create({ + data: { + user: { connect: { id: userId } }, + permission: invitation.permission, + team: { connect: { id: invitation.teamId } } + } + }); + await db.prisma.teamInvitation.delete({ where: { id } }); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/teams/[id]/invitation/invite.json.ts b/src/routes/teams/[id]/invitation/invite.json.ts new file mode 100644 index 000000000..076fa22df --- /dev/null +++ b/src/routes/teams/[id]/invitation/invite.json.ts @@ -0,0 +1,69 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dayjs } from '$lib/dayjs'; +import type { RequestHandler } from '@sveltejs/kit'; + +async function createInvitation({ email, uid, teamId, teamName, permission }) { + return await db.prisma.teamInvitation.create({ + data: { email, uid, teamId, teamName, permission } + }); +} + +export const post: RequestHandler = async (event) => { + const { userId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { email, permission, teamId, teamName } = await event.request.json(); + + try { + const userFound = await db.prisma.user.findUnique({ where: { email } }); + if (!userFound) { + throw { + error: `No user found with '${email}' email address.` + }; + } + const uid = userFound.id; + // Invitation to yourself?! + if (uid === userId) { + throw { + error: `Invitation to yourself? Whaaaaat?` + }; + } + const alreadyInTeam = await db.prisma.team.findFirst({ + where: { id: teamId, users: { some: { id: uid } } } + }); + if (alreadyInTeam) { + throw { + error: `Already in the team.` + }; + } + const invitationFound = await db.prisma.teamInvitation.findFirst({ where: { uid, teamId } }); + if (invitationFound) { + if (dayjs().toDate() < dayjs(invitationFound.createdAt).add(1, 'day').toDate()) { + throw { + error: 'Invitiation already pending on user confirmation.' + }; + } else { + await db.prisma.teamInvitation.delete({ where: { id: invitationFound.id } }); + await createInvitation({ email, uid, teamId, teamName, permission }); + return { + status: 200, + body: { + message: 'Invitiation sent.' + } + }; + } + } else { + await createInvitation({ email, uid, teamId, teamName, permission }); + return { + status: 200, + body: { + message: 'Invitiation sent.' + } + }; + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/teams/[id]/invitation/revoke.json.ts b/src/routes/teams/[id]/invitation/revoke.json.ts new file mode 100644 index 000000000..07f7a2387 --- /dev/null +++ b/src/routes/teams/[id]/invitation/revoke.json.ts @@ -0,0 +1,20 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dayjs } from '$lib/dayjs'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { userId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = await event.request.json(); + try { + await db.prisma.teamInvitation.delete({ where: { id } }); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/teams/[id]/permission/change.json.ts b/src/routes/teams/[id]/permission/change.json.ts new file mode 100644 index 000000000..018b7772b --- /dev/null +++ b/src/routes/teams/[id]/permission/change.json.ts @@ -0,0 +1,23 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { userId, newPermission, permissionId } = await event.request.json(); + + try { + await db.prisma.permission.updateMany({ + where: { id: permissionId, userId }, + data: { permission: { set: newPermission } } + }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/teams/[id]/remove/user.json.ts b/src/routes/teams/[id]/remove/user.json.ts new file mode 100644 index 000000000..6a82de694 --- /dev/null +++ b/src/routes/teams/[id]/remove/user.json.ts @@ -0,0 +1,24 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { userId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { teamId, uid } = await event.request.json(); + + try { + await db.prisma.team.update({ + where: { id: teamId }, + data: { users: { disconnect: { id: uid } } } + }); + await db.prisma.permission.deleteMany({ where: { userId: uid, teamId } }); + return { + status: 201 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/teams/index.json.ts b/src/routes/teams/index.json.ts new file mode 100644 index 000000000..d004ecb5d --- /dev/null +++ b/src/routes/teams/index.json.ts @@ -0,0 +1,26 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { userId, status, body } = await getUserDetails(event, false); + if (status === 401) return { status, body }; + + try { + const teams = await db.prisma.permission.findMany({ + where: { userId }, + include: { team: { include: { _count: { select: { users: true } } } } } + }); + const invitations = await db.prisma.teamInvitation.findMany({ where: { uid: userId } }); + return { + status: 200, + body: { + teams, + invitations + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/teams/index.svelte b/src/routes/teams/index.svelte new file mode 100644 index 000000000..c8572d5fc --- /dev/null +++ b/src/routes/teams/index.svelte @@ -0,0 +1,113 @@ + + + + +
    +
    Teams
    + {#if $session.isAdmin} + + + + {/if} +
    + +{#if invitations.length > 0} +
    +
    +
    Pending invitations
    +
    +
    + {#each invitations as invitation} +
    +
    + Invited to {invitation.teamName} with + {invitation.permission} permission. +
    + + +
    + {/each} +
    +
    +{/if} + diff --git a/src/routes/undead.json.ts b/src/routes/undead.json.ts new file mode 100644 index 000000000..bf9423d74 --- /dev/null +++ b/src/routes/undead.json.ts @@ -0,0 +1,8 @@ +export const get = async () => { + return { + status: 200, + body: { + message: 'Nope' + } + }; +}; diff --git a/src/routes/update.json.ts b/src/routes/update.json.ts new file mode 100644 index 000000000..505f307db --- /dev/null +++ b/src/routes/update.json.ts @@ -0,0 +1,73 @@ +import { dev } from '$app/env'; +import { asyncExecShell, version } from '$lib/common'; +import { asyncSleep } from '$lib/components/common'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; +import compare from 'compare-versions'; +import got from 'got'; + +export const get: RequestHandler = async () => { + try { + const currentVersion = version; + const versions = await got + .get(`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}`) + .json(); + const latestVersion = versions['coolify'].main.version; + const isUpdateAvailable = compare(latestVersion, currentVersion); + return { + body: { + isUpdateAvailable: isUpdateAvailable === 1, + latestVersion + } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { type } = await event.request.json(); + if (type === 'pull') { + try { + if (!dev) { + await asyncExecShell(`env | grep COOLIFY > .env`); + await asyncExecShell(`docker compose pull`); + return { + status: 200, + body: {} + }; + } else { + await asyncSleep(2000); + return { + status: 200, + body: {} + }; + } + } catch (error) { + return PrismaErrorHandler(error); + } + } else if (type === 'update') { + try { + if (!dev) { + await asyncExecShell( + `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:latest /bin/sh -c "env | grep COOLIFY > .env && docker stop -t 0 coolify && docker stop -t 0 coolify-redis && docker compose up -d --force-recreate"` + ); + return { + status: 200, + body: {} + }; + } else { + await asyncSleep(2000); + return { + status: 200, + body: {} + }; + } + } catch (error) { + return PrismaErrorHandler(error); + } + } + return { + status: 500 + }; +}; diff --git a/src/routes/webhooks/github/events.ts b/src/routes/webhooks/github/events.ts new file mode 100644 index 000000000..a370dc42c --- /dev/null +++ b/src/routes/webhooks/github/events.ts @@ -0,0 +1,177 @@ +import { getDomain, getTeam, getUserDetails, removeDestinationDocker } from '$lib/common'; +import * as db from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; +import cuid from 'cuid'; +import crypto from 'crypto'; +import { buildQueue } from '$lib/queues'; +import { checkContainer, removeProxyConfiguration } from '$lib/haproxy'; + +export const options: RequestHandler = async () => { + return { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' + } + }; +}; + +export const post: RequestHandler = async (event) => { + try { + const buildId = cuid(); + const allowedGithubEvents = ['push', 'pull_request']; + const allowedActions = ['opened', 'reopened', 'synchronize', 'closed']; + const githubEvent = event.request.headers.get('x-github-event').toLowerCase(); + const githubSignature = event.request.headers.get('x-hub-signature-256').toLowerCase(); + if (!allowedGithubEvents.includes(githubEvent)) { + return { + status: 500, + body: { + message: 'Event not allowed.' + } + }; + } + let repository, projectId, branch; + const body = await event.request.json(); + + if (githubEvent === 'push') { + repository = body.repository; + projectId = repository.id; + branch = body.ref.split('/')[2]; + } else if (githubEvent === 'pull_request') { + repository = body.pull_request.head.repo; + projectId = repository.id; + branch = body.pull_request.head.ref.split('/')[2]; + } + + const applicationFound = await db.getApplicationWebhook({ projectId, branch }); + if (applicationFound) { + const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret; + const hmac = crypto.createHmac('sha256', webhookSecret); + const digest = Buffer.from( + 'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'), + 'utf8' + ); + const checksum = Buffer.from(githubSignature, 'utf8'); + if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { + return { + status: 500, + body: { + message: 'SHA256 checksum failed. Are you doing something fishy?' + } + }; + } + if (githubEvent === 'push') { + if (!applicationFound.configHash) { + const configHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack: applicationFound.buildPack, + port: applicationFound.port, + installCommand: applicationFound.installCommand, + buildCommand: applicationFound.buildCommand, + startCommand: applicationFound.startCommand + }) + ) + .digest('hex'); + await db.prisma.application.updateMany({ + where: { branch, projectId }, + data: { configHash } + }); + } + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_commit', + ...applicationFound + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (githubEvent === 'pull_request') { + const pullmergeRequestId = body.number; + const pullmergeRequestAction = body.action; + const sourceBranch = body.pull_request.head.ref; + if (!allowedActions.includes(pullmergeRequestAction)) { + return { + status: 500, + body: { + message: 'Action not allowed.' + } + }; + } + + if (applicationFound.settings.previews) { + if (applicationFound.destinationDockerId) { + const isRunning = await checkContainer( + applicationFound.destinationDocker.engine, + applicationFound.id + ); + if (!isRunning) { + return { + status: 500, + body: { + message: 'Application not running.' + } + }; + } + } + if (pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') { + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_pr', + ...applicationFound, + sourceBranch, + pullmergeRequestId + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (pullmergeRequestAction === 'closed') { + if (applicationFound.destinationDockerId) { + const domain = getDomain(applicationFound.fqdn); + const id = `${applicationFound.id}-${pullmergeRequestId}`; + const engine = applicationFound.destinationDocker.engine; + await removeDestinationDocker({ id, engine }); + await removeProxyConfiguration({ domain: `${pullmergeRequestId}.${domain}` }); + } + return { + status: 200, + body: { + message: 'Removed preview. Thank you!' + } + }; + } + } else { + return { + status: 500, + body: { + message: 'Pull request previews are not enabled.' + } + }; + } + } + } + return { + status: 500, + body: { + message: 'Not handled event.' + } + }; + } catch (err) { + console.log(err); + return { + status: 500, + body: { + message: err.message + } + }; + } +}; diff --git a/src/routes/webhooks/github/index.ts b/src/routes/webhooks/github/index.ts new file mode 100644 index 000000000..b8fdd2e14 --- /dev/null +++ b/src/routes/webhooks/github/index.ts @@ -0,0 +1,41 @@ +import { dev } from '$app/env'; +import { getTeam } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const options = async () => { + return { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' + } + }; +}; + +export const get: RequestHandler = async (request) => { + const teamId = undefined; + const code = request.url.searchParams.get('code'); + const state = request.url.searchParams.get('state'); + try { + const { apiUrl } = await db.getSource({ id: state, teamId }); + const response = await fetch(`${apiUrl}/app-manifests/${code}/conversions`, { method: 'POST' }); + if (!response.ok) { + const error = await response.json(); + return { + status: 500, + body: { ...error } + }; + } + const { id, client_id, slug, client_secret, pem, webhook_secret } = await response.json(); + await db.createGithubApp({ id, client_id, slug, client_secret, pem, webhook_secret, state }); + return { + status: 302, + headers: { Location: `/webhooks/success` } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/webhooks/github/install.ts b/src/routes/webhooks/github/install.ts new file mode 100644 index 000000000..c535fb85b --- /dev/null +++ b/src/routes/webhooks/github/install.ts @@ -0,0 +1,29 @@ +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const options = async () => { + return { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' + } + }; +}; + +export const get: RequestHandler = async (request) => { + const gitSourceId = request.url.searchParams.get('gitSourceId'); + const installation_id = request.url.searchParams.get('installation_id'); + + try { + await db.addInstallation({ gitSourceId, installation_id }); + return { + status: 302, + headers: { Location: `/webhooks/success` } + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/webhooks/gitlab/events.ts b/src/routes/webhooks/gitlab/events.ts new file mode 100644 index 000000000..d89507a7c --- /dev/null +++ b/src/routes/webhooks/gitlab/events.ts @@ -0,0 +1,182 @@ +import { getTeam, getUserDetails, getDomain, removeDestinationDocker } from '$lib/common'; +import { checkContainer, removeProxyConfiguration } from '$lib/haproxy'; +import * as db from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; +import cuid from 'cuid'; +import crypto from 'crypto'; +import { buildQueue } from '$lib/queues'; +import { dev } from '$app/env'; + +export const options: RequestHandler = async () => { + return { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' + } + }; +}; + +export const post: RequestHandler = async (event) => { + const allowedActions = ['opened', 'reopen', 'close', 'open', 'update']; + const body = await event.request.json(); + const buildId = cuid(); + try { + const { object_kind: objectKind } = body; + if (objectKind === 'push') { + const { ref } = body; + const projectId = Number(body['project_id']); + const branch = ref.split('/')[2]; + const applicationFound = await db.getApplicationWebhook({ projectId, branch }); + if (applicationFound) { + if (!applicationFound.configHash) { + const configHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack: applicationFound.buildPack, + port: applicationFound.port, + installCommand: applicationFound.installCommand, + buildCommand: applicationFound.buildCommand, + startCommand: applicationFound.startCommand + }) + ) + .digest('hex'); + await db.prisma.application.updateMany({ + where: { branch, projectId }, + data: { configHash } + }); + } + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_commit', + ...applicationFound + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } + } else if (objectKind === 'merge_request') { + const webhookToken = event.request.headers.get('x-gitlab-token'); + if (!webhookToken) { + return { + status: 500, + body: { + message: 'Ooops, something is not okay, are you okay?' + } + }; + } + + const isDraft = body.object_attributes.work_in_progress; + const action = body.object_attributes.action; + const projectId = Number(body.project.id); + const sourceBranch = body.object_attributes.source_branch; + const targetBranch = body.object_attributes.target_branch; + const pullmergeRequestId = body.object_attributes.iid; + if (!allowedActions.includes(action)) { + return { + status: 500, + body: { + message: 'Action not allowed.' + } + }; + } + if (isDraft) { + return { + status: 500, + body: { + message: 'Draft MR, do nothing.' + } + }; + } + + const applicationFound = await db.getApplicationWebhook({ projectId, branch: targetBranch }); + if (applicationFound) { + if (applicationFound.settings.previews) { + if (applicationFound.destinationDockerId) { + const isRunning = await checkContainer( + applicationFound.destinationDocker.engine, + applicationFound.id + ); + if (!isRunning) { + return { + status: 500, + body: { + message: 'Application not running.' + } + }; + } + } + if (!dev && applicationFound.gitSource.gitlabApp.webhookToken !== webhookToken) { + return { + status: 500, + body: { + message: 'Ooops, something is not okay, are you okay?' + } + }; + } + if ( + action === 'opened' || + action === 'reopen' || + action === 'open' || + action === 'update' + ) { + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_mr', + ...applicationFound, + sourceBranch, + pullmergeRequestId + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (action === 'close') { + if (applicationFound.destinationDockerId) { + const domain = getDomain(applicationFound.fqdn); + const id = `${applicationFound.id}-${pullmergeRequestId}`; + const engine = applicationFound.destinationDocker.engine; + await removeProxyConfiguration({ domain: `${pullmergeRequestId}.${domain}` }); + await removeDestinationDocker({ id, engine }); + } + + return { + status: 200, + body: { + message: 'Removed preview. Thank you!' + } + }; + } + } + return { + status: 500, + body: { + message: 'Merge request previews are not enabled.' + } + }; + } + } + + return { + status: 500, + body: { + message: 'Not handled event.' + } + }; + } catch (err) { + console.log(err); + return { + status: 500, + body: { + message: err.message + } + }; + } +}; diff --git a/src/routes/webhooks/gitlab/index.ts b/src/routes/webhooks/gitlab/index.ts new file mode 100644 index 000000000..9f953bc88 --- /dev/null +++ b/src/routes/webhooks/gitlab/index.ts @@ -0,0 +1,59 @@ +import { dev } from '$app/env'; +import { getTeam } from '$lib/common'; +import * as db from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; +import got from 'got'; +import cookie from 'cookie'; + +export const options: RequestHandler = async () => { + return { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' + } + }; +}; + +export const get: RequestHandler = async (event) => { + const teamId = undefined; + const code = event.url.searchParams.get('code'); + const state = event.url.searchParams.get('state'); + try { + const application = await db.getApplication({ id: state, teamId }); + const { fqdn } = application; + const { appId, appSecret } = application.gitSource.gitlabApp; + const { htmlUrl } = application.gitSource; + + let domain = `http://${event.url.host}`; + if (fqdn) domain = fqdn; + + const { access_token } = await got + .post(`${htmlUrl}/oauth/token`, { + searchParams: { + client_id: appId, + client_secret: appSecret, + code, + state, + grant_type: 'authorization_code', + redirect_uri: `${domain}/webhooks/gitlab` + } + }) + .json(); + + return { + status: 302, + headers: { + Location: `/webhooks/success`, + 'Set-Cookie': [`gitlabToken=${access_token}; HttpOnly; Path=/; Max-Age=15778800;`] + } + }; + } catch (err) { + console.log(err); + return { + status: 500, + body: err.message + }; + } +}; diff --git a/src/routes/webhooks/success/index.svelte b/src/routes/webhooks/success/index.svelte new file mode 100644 index 000000000..9dd61a089 --- /dev/null +++ b/src/routes/webhooks/success/index.svelte @@ -0,0 +1,3 @@ + diff --git a/src/store/index.ts b/src/store/index.ts deleted file mode 100644 index 0c76db7b5..000000000 --- a/src/store/index.ts +++ /dev/null @@ -1,183 +0,0 @@ -import type { - Application, - Dashboard, - Database, - DateTimeFormatOptions, - GithubInstallations -} from 'src/global'; -import { writable } from 'svelte/store'; -export const settings = writable({ - clientId: null -}); -export const dashboard = writable({ - databases: { - deployed: [] - }, - applications: { - deployed: [] - }, - services: { - deployed: [] - } -}); -export const dateOptions: DateTimeFormatOptions = { - year: 'numeric', - month: 'short', - day: '2-digit', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - hour12: false -}; - -export const githubRepositories = writable([]); -export const githubInstallations = writable([]); -export const originalDomain = writable(null) -export const application = writable({ - github: { - installation: { - id: null - }, - app: { - id: null - } - }, - repository: { - id: null, - organization: 'new', - name: 'start', - branch: null - }, - general: { - deployId: null, - nickname: null, - workdir: null, - isPreviewDeploymentEnabled: false, - pullRequest: 0 - }, - build: { - pack: 'static', - directory: null, - command: { - build: null, - installation: null, - start: null, - python: { - module: null, - instance: null - } - }, - container: { - name: null, - tag: null, - baseSHA: null - } - }, - publish: { - directory: null, - domain: null, - path: '/', - port: null, - secrets: [] - } -}); -export const prApplication = writable([]); -export const initConf = writable({}); -export const initialApplication: Application = { - github: { - installation: { - id: null - }, - app: { - id: null - } - }, - repository: { - id: null, - organization: null, - name: null, - branch: null - }, - general: { - deployId: null, - nickname: null, - workdir: null, - isPreviewDeploymentEnabled: false, - pullRequest: 0 - }, - build: { - pack: 'static', - directory: null, - command: { - build: null, - installation: null, - start: null, - python: { - module: null, - instance: null - } - }, - container: { - name: null, - tag: null, - baseSHA: null - } - }, - publish: { - directory: null, - domain: null, - path: '/', - port: null, - secrets: [] - } -}; -export const initialDatabase: Database = { - config: { - general: { - workdir: null, - deployId: null, - nickname: null, - type: null - }, - database: { - username: null, - passwords: [], - defaultDatabaseName: null - }, - deploy: { - name: null - } - }, - envs: {} -}; -export const database = writable({ - config: {}, - envs: [] -}); -export const newService = writable({ - email: null, - userName: 'admin', - userPassword: null, - userPasswordAgain: null, - baseURL: null -}); -export const initialNewService = { - email: null, - userName: 'admin', - userPassword: null, - userPasswordAgain: null, - baseURL: null -}; -export const newWordpressService = writable({ - baseURL: null, - remoteDB: false, - database: { - host: null, - name: 'wordpress', - user: null, - password: null, - tablePrefix: 'wordpress' - }, - wordpressExtraConfiguration: null -}); -export const isPullRequestPermissionsGranted = writable(false); diff --git a/src/tailwind.css b/src/tailwind.css new file mode 100644 index 000000000..3bae89190 --- /dev/null +++ b/src/tailwind.css @@ -0,0 +1,288 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html { + @apply h-full min-h-full; +} +body { + @apply min-h-screen overflow-x-hidden bg-coolblack text-sm text-white; +} + +main, +.main { + width: calc(100% - 4rem); + margin-left: 4rem; +} + +input { + @apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm; +} +textarea { + @apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm; +} + +select { + @apply rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm; +} + +label { + @apply inline-block w-64 text-xs tracking-tight md:text-sm; +} + +button { + @apply rounded bg-coolgray-200 p-1 px-4 py-2 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600 md:text-sm; +} + +a { + @apply underline hover:text-white; +} + +.content { + @apply p-2 px-4; +} + +.title { + @apply mr-4 text-base tracking-tight md:text-2xl; +} +.nav-main { + @apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] border-r border-stone-800 bg-coolgray-200; +} + +.nav-side { + @apply absolute right-0 top-0 z-50 m-5 flex items-center justify-end space-x-2 bg-coolblack/40 text-white; +} + +.add-icon { + @apply rounded p-1 transition duration-200; +} + +.icons { + @apply rounded p-2 transition duration-200 hover:bg-coolgray-500 disabled:bg-coolblack !important; +} + +.arrow-right-applications { + @apply -ml-6 px-2 font-bold text-green-500; +} + +.border-gradient { + border-bottom: 2px solid transparent; + -o-border-image: linear-gradient( + 0.25turn, + rgba(255, 249, 34), + rgba(255, 0, 128), + rgba(56, 2, 155, 0) + ); + border-image: linear-gradient( + 0.25turn, + rgba(255, 249, 34), + rgba(255, 0, 128), + rgba(56, 2, 155, 0) + ); + border-image-slice: 1; +} +.border-gradient-full { + border: 4px solid transparent; + -o-border-image: linear-gradient( + 0.25turn, + rgba(255, 249, 34), + rgba(255, 0, 128), + rgba(56, 2, 155, 0) + ); + border-image: linear-gradient( + 0.25turn, + rgba(255, 249, 34), + rgba(255, 0, 128), + rgba(56, 2, 155, 0) + ); + border-image-slice: 1; +} +/** + * Tooltip Styles + */ + +/* Base styles for the element that has a tooltip */ +[data-tooltip], +.tooltip { + position: relative; + cursor: pointer; +} + +/* Base styles for the entire tooltip */ +[data-tooltip]:before, +[data-tooltip]:after, +.tooltip:before, +.tooltip:after { + position: absolute; + visibility: hidden; + opacity: 0; + transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out, + transform 0.2s cubic-bezier(0.71, 1.7, 0.77, 1.24); + transform: translate3d(0, 0, 0); + pointer-events: none; +} + +/* Show the entire tooltip on hover and focus */ +[data-tooltip]:hover:before, +[data-tooltip]:hover:after, +[data-tooltip]:focus:before, +[data-tooltip]:focus:after, +.tooltip:hover:before, +.tooltip:hover:after, +.tooltip:focus:before, +.tooltip:focus:after { + visibility: visible; + opacity: 1; +} + +/* Base styles for the tooltip's directional arrow */ +.tooltip:before, +[data-tooltip]:before { + z-index: 1001; + border: 6px solid transparent; + background: transparent; + content: ''; +} + +/* Base styles for the tooltip's content area */ +.tooltip:after, +[data-tooltip]:after { + z-index: 1000; + padding: 8px; + color: #fff; + content: attr(data-tooltip); + @apply min-w-[100px] rounded bg-coollabs text-center font-normal; +} + +/* Directions */ + +/* Top (default) */ +[data-tooltip]:before, +[data-tooltip]:after, +.tooltip:before, +.tooltip:after, +.tooltip-top:before, +.tooltip-top:after { + bottom: 100%; + left: 50%; +} + +/* Horizontally align top/bottom tooltips */ +[data-tooltip]:after, +.tooltip:after, +.tooltip-top:after { + margin-left: -80px; +} + +[data-tooltip]:hover:before, +[data-tooltip]:hover:after, +[data-tooltip]:focus:before, +[data-tooltip]:focus:after, +.tooltip:hover:before, +.tooltip:hover:after, +.tooltip:focus:before, +.tooltip:focus:after, +.tooltip-top:hover:before, +.tooltip-top:hover:after, +.tooltip-top:focus:before, +.tooltip-top:focus:after { + transform: translateY(-12px); +} + +/* Left */ +.tooltip-left:before, +.tooltip-left:after { + right: 100%; + bottom: 50%; + left: auto; +} + +.tooltip-left:hover:before, +.tooltip-left:hover:after, +.tooltip-left:focus:before, +.tooltip-left:focus:after { + transform: translateX(-12px); +} + +/* Bottom */ +.tooltip-bottom:before, +.tooltip-bottom:after { + top: 100%; + bottom: auto; + left: 50%; +} + +.tooltip-bottom:hover:before, +.tooltip-bottom:hover:after, +.tooltip-bottom:focus:before, +.tooltip-bottom:focus:after { + transform: translateY(12px); +} + +/* Right */ +.tooltip-right:before, +.tooltip-right:after { + bottom: 50%; + left: 100%; +} + +.tooltip-right:hover:before, +.tooltip-right:hover:after, +.tooltip-right:focus:before, +.tooltip-right:focus:after { + transform: translateX(12px); +} + +/* Move directional arrows down a bit for left/right tooltips */ +.tooltip-left:before, +.tooltip-right:before { + top: 12px; +} + +/* Vertically center tooltip content for left/right tooltips */ +.tooltip-left:after, +.tooltip-right:after { + margin-left: 0; + margin-bottom: -16px; +} + +.box-selection { + @apply min-w-[16rem] max-w-[24rem] justify-center rounded-lg border-transparent bg-coolgray-200 p-6 shadow-lg transition duration-150 hover:scale-105 hover:border-transparent hover:bg-coolgray-400; +} + +._toastBar { + @apply hidden !important; +} +._toastMsg { + @apply text-xs !important; +} +._toastBtn { + @apply text-xs !important; +} +._toastItem { + @apply bg-coollabs bg-opacity-80 !important; +} + +.lds-heart { + animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1); +} +@keyframes lds-heart { + 0% { + transform: scale(1); + } + 5% { + transform: scale(1.2); + } + 39% { + transform: scale(0.85); + } + 45% { + transform: scale(1); + } + 60% { + transform: scale(0.95); + } + 100% { + transform: scale(0.9); + } +} diff --git a/svelte.config.js b/svelte.config.js index d024846fc..4e8bc8966 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,39 +1,20 @@ -import dotEnvExtended from 'dotenv-extended'; -dotEnvExtended.load(); import preprocess from 'svelte-preprocess'; -import path from 'path'; import adapter from '@sveltejs/adapter-node'; -/** @type {import('@sveltejs/kit').Config} */ -export default { - preprocess: [ - preprocess({ - postcss: true - }) - ], + +const config = { + preprocess: preprocess(), kit: { - adapter: adapter({ - out: 'build' - }), - target: '#svelte', - hostHeader: 'X-Forwarded-Host', - floc: true, + adapter: adapter(), prerender: { enabled: false }, + floc: true, vite: { - server: { - hmr: { - port: 23456 - } - }, - resolve: { - alias: { - $components: path.resolve('./src/components/'), - $store: path.resolve('./src/store/index.ts'), - $api: path.resolve('./src/routes/api/'), - $models: path.resolve('./src/models/') - } + optimizeDeps: { + exclude: ['svelte-kit-cookie-session'] } } } }; + +export default config; diff --git a/tailwind.config.cjs b/tailwind.config.cjs index c34d59038..2d119a268 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,13 +1,7 @@ const defaultTheme = require('tailwindcss/defaultTheme'); const colors = require('tailwindcss/colors'); -const { tailwindExtractor } = require('tailwindcss/lib/lib/purgeUnusedStyles'); - -const svelteClassColonExtractor = (content) => { - return content.match(/(?<=class:)([a-zA-Z0-9_-]+)/gm) || []; -}; module.exports = { - mode: 'jit', - purge: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'], + content: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'], important: true, theme: { extend: { @@ -21,22 +15,23 @@ module.exports = { wiggle: 'wiggle 0.5s ease-in-out infinite' }, fontFamily: { - sans: ['Montserrat', ...defaultTheme.fontFamily.sans] + sans: ['Poppins', ...defaultTheme.fontFamily.sans] }, colors: { ...colors, + coollabs: '#6B16ED', + 'coollabs-100': '#7317FF', coolblack: '#161616', 'coolgray-100': '#181818', 'coolgray-200': '#202020', - 'coolgray-300': '#242424' + 'coolgray-300': '#242424', + 'coolgray-400': '#282828', + 'coolgray-500': '#323232' } } }, variants: { - extend: { - opacity: ['disabled'], - animation: ['hover', 'focus'] - } + extend: {} }, plugins: [] }; diff --git a/tsconfig.json b/tsconfig.json index 623afac64..0555d526a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,8 @@ "compilerOptions": { "moduleResolution": "node", "module": "es2020", - "lib": ["es2020"], - "target": "es2019", + "lib": ["es2020", "DOM"], + "target": "es2020", /** svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript to enforce using \`import type\` instead of \`import\` for Types. @@ -17,18 +17,14 @@ */ "sourceMap": true, "esModuleInterop": true, - "allowSyntheticDefaultImports": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "baseUrl": ".", "allowJs": true, "checkJs": true, "paths": { - "$lib/*": ["src/lib/*"], - "$store": ["src/store/index.ts"], - "$api/*": ["src/routes/api/*"], - "$models/*": ["src/models/*"], - "$components/*": ["src/components/*"] + "$lib": ["src/lib"], + "$lib/*": ["src/lib/*"] } }, "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]