diff --git a/.env.dist b/.env.dist index 4a6eaed..a9f315f 100644 --- a/.env.dist +++ b/.env.dist @@ -1,11 +1,9 @@ -MYSQL_ROOT_PASSWORD= -MYSQL_HOST=db -MYSQL_DATABASE= -MYSQL_USER= -MYSQL_PASSWORD= +## can be generated with `openssl rand -base64 32` JWT_SECRET= -JWT_REFRESH_SECRET= -CORS=false -PORT=8081 -HOST= -SECURE_COOKIES= +# +## can be generated with `openssl rand -base64 32` +MYSQL_ROOT_PASSWORD= +# +## can be generated with `openssl rand -base64 32` +MYSQL_PASSWORD= +PASSWORD=${MYSQL_PASSWORD} diff --git a/.gitignore b/.gitignore index ddf73aa..baa62e3 100644 --- a/.gitignore +++ b/.gitignore @@ -124,8 +124,9 @@ sw.* data/ letsencrypt/ -# resources +# generated resources +app/src/main/resources/css-manifest.json +app/src/main/resources/static/styles* -resources/css-manifest.json -resources/docs/index.html -resources/static/*.css +# h2 db +*.db diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index d475a89..0000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,110 +0,0 @@ -/* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -*/ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = - "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: : " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index a5fcc11..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip \ No newline at end of file diff --git a/Caddyfile b/Caddyfile deleted file mode 100644 index acbe014..0000000 --- a/Caddyfile +++ /dev/null @@ -1,8 +0,0 @@ -simplenotes.be { - reverse_proxy http://localhost:8081 - header Strict-Transport-Security "max-age=31536000; includeSubDomains" -} - -www.simplenotes.be { - redir * https://simplenotes.be{path} -} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5cbc5c1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +FROM maven:3.6.3-jdk-14 as builder + +WORKDIR /tmp + +# Cache dependencies +COPY pom.xml . +COPY app/pom.xml app/pom.xml +COPY domain/pom.xml domain/pom.xml +COPY persistance/pom.xml persistance/pom.xml +COPY shared/pom.xml shared/pom.xml + +RUN mvn verify clean --fail-never + +COPY app/src app/src +COPY domain/src domain/src +COPY persistance/src persistance/src +COPY shared/src shared/src + +RUN mvn -Dstyle.color=always package + +FROM openjdk:14-alpine as jdkbuilder + +COPY --from=builder /tmp/app/target/app-*.jar /app/app.jar + +ENV MODULES java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.scripting,java.security.jgss,java.sql,java.sql.rowset,java.transaction.xa,java.xml,jdk.net + +RUN jlink --output /myjdk --module-path $JAVA_HOME/jmods --add-modules $MODULES + +FROM alpine + +ENV APPLICATION_USER simplenotes +RUN adduser -D -g '' $APPLICATION_USER + +RUN mkdir /app +RUN chown -R $APPLICATION_USER /app + +USER $APPLICATION_USER + +COPY --from=builder /tmp/app/target/app-*.jar /app/app.jar +COPY --from=jdkbuilder /myjdk /myjdk +WORKDIR /app + +CMD ["/myjdk/bin/java", "-server", "-XX:+UnlockExperimentalVMOptions", "-Xms256m", "-Xmx1g", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "app.jar"] diff --git a/Dockerfile.api b/Dockerfile.api deleted file mode 100644 index 46918cc..0000000 --- a/Dockerfile.api +++ /dev/null @@ -1,28 +0,0 @@ -FROM maven:3.6.3-jdk-14 as builder - -WORKDIR /tmp - -# Cache dependencies -COPY pom.xml . -RUN mvn verify clean --fail-never - -COPY resources resources -COPY src src -COPY test test - -RUN mvn -Dstyle.color=always package -DskipTests - -FROM openjdk:14-alpine - -ENV APPLICATION_USER ktor -RUN adduser -D -g '' $APPLICATION_USER - -RUN mkdir /app -RUN chown -R $APPLICATION_USER /app - -USER $APPLICATION_USER - -COPY --from=builder /tmp/target/api-*.jar /app/notes-api.jar -WORKDIR /app - -CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:InitialRAMFraction=2", "-XX:MinRAMFraction=2", "-XX:MaxRAMFraction=2", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "notes-api.jar"] diff --git a/api-doc/api.apib b/api-doc/api.apib deleted file mode 100644 index 295f47e..0000000 --- a/api-doc/api.apib +++ /dev/null @@ -1,8 +0,0 @@ -FORMAT: 1A -HOST: https://simplenotes.be/api - -# Notes API - - - - diff --git a/api-doc/notes/index.apib b/api-doc/notes/index.apib deleted file mode 100644 index 49d742e..0000000 --- a/api-doc/notes/index.apib +++ /dev/null @@ -1,130 +0,0 @@ -# Group Notes - -## Notes [/notes] - -### Create a Note [POST] - -+ Request (text/markdown; charset=UTF-8) - + Headers - - Authorization: Bearer - Accept: application/json - - + Body - - --- - title: example - tags: ["some", "tags"] - --- - # A story - - - a - - b - - -+ Response 201 (application/json) - - { - "title": "example", - "tags": [ - "some", - "tags" - ], - "markdown": "---\ntitle: example\ntags: [\"some\", \"tags\"]\n---\n# A story\n\n- a\n- b", - "html": "

A story

\n\n", - "uuid": "42aa1078-130e-47ee-b82d-b1d62f3ea054", - "updatedAt": "2020-07-16T01:03:46.7766" - } - -## Notes [/notes{?limit,after}] - -+ Parameters - + limit: 10 (number, optional) - The number of notes to return - + Default: `20` - + after: `9bd36653-6397-4c5b-b8b7-158d9de208ef` (string, optional) - The UUID of the note before the requested ones - - - -### Get All Notes [GET] - -+ Request - - + Headers - - Authorization: Bearer - Accept: application/json - -+ Response 200 (application/json) - + Body - - [ - { - "uuid": "42aa1078-130e-47ee-b82d-b1d62f3ea054", - "title": "example", - "updatedAt": "2020-07-16T01:03:46", - "tags": [ - "some", - "tags" - ] - }, - { - "uuid": "e61271e9-ba86-4428-a788-49946d1954c5", - "title": "test", - "updatedAt": "2020-07-16T00:22:02", - "tags": [ - "babar", - "fait", - "du", - "ski" - ] - }, - ] - - -## Note [/notes/{uuid}] - -+ Parameters - + uuid: `123e4567-e89b-12d3-a456-426614174000` (required, string) - The note UUID. - - -### Get a Note [GET] - -+ Request - + Headers - - Authorization: Bearer - Accept: application/json - -+ Response 200 (application/json) - + Body - - { - "uuid": "8ba68c64-11f1-4424-a0cb-cba54a65298f", - "title": "example", - "markdown": "---\ntitle: example\ntags: [\"some\", \"tags\"]\n---\n# A story\n\n- a\n- b", - "html": "

A story

\n
  • a
  • b
\n", - "updatedAt": "2020-07-16T01:13:37", - "tags": [ - "some", - "tags" - ] - } - -+ Response 404 - - -### Update a Note [PUT] - -#### TODO - -### Delete a Note [DELETE] - -+ Request - + Headers - - Authorization: Bearer - Accept: application/json - -+ Response 200 - -+ Response 404 diff --git a/api-doc/tags/index.apib b/api-doc/tags/index.apib deleted file mode 100644 index 9d56cf8..0000000 --- a/api-doc/tags/index.apib +++ /dev/null @@ -1,21 +0,0 @@ -# Group Tags - -## Tags [/tags] - -### Get all tags [GET] - -+ Request - + Headers - - Authorization: Bearer - Accept: application/json - -+ Response 200 (application/json) - + Body - - [ - "markdown", - "md", - "code", - "java" - ] diff --git a/api-doc/users/index.apib b/api-doc/users/index.apib deleted file mode 100644 index 6bfcfe2..0000000 --- a/api-doc/users/index.apib +++ /dev/null @@ -1,111 +0,0 @@ -# Group Accounts - -## Account [/user] - -### Create an account [POST] - -+ Request (application/json) - + Headers - - Accept: application/json - - + Body - - { - "username": "user", - "password": "apassword" - } - - -+ Response 200 - -+ Response 409 - -### Delete a user [DELETE] - -+ Request - + Headers - - Authorization: Bearer - Accept: application/json - -+ Response 200 - -+ Response 404 - - -## Authentication [/user/login] -Authenticate one user to access protected routing. - -### Authenticate a user [POST] - -+ Request (application/json) - + Headers - - Accept: application/json - - + Body - - { - "username": "user", - "password": "myrealpassword" - } - -+ Response 200 (application/json) - + Body - - { - "token": "", - "refreshToken": "" - } - -+ Response 401 - -## Token refresh [/user/refresh_token] - -### Refresh JWT token [POST] - -+ Request (application/json) - + Headers - - Accept: application/json - - + Body - - { - "refreshToken": "" - } - -+ Response 200 (application/json) - + Body - - { - "token": "", - "refreshToken": "" - } - - -+ Response 401 - -## User Info [/user/me] -Receive the username and email from the currently logged in user - -### Get User Info [GET] - -+ Request - + Headers - - Authorization: Bearer - Accept: application/json - - -+ Response 200 (application/json) - + Body - - { - "user": { - "username": "user" - } - } - -+ Response 401 diff --git a/app/pom.xml b/app/pom.xml new file mode 100644 index 0000000..f2c1b1b --- /dev/null +++ b/app/pom.xml @@ -0,0 +1,123 @@ + + + parent + be.simplenotes + 1.0-SNAPSHOT + + 4.0.0 + + app + + + + be.simplenotes + persistance + 1.0-SNAPSHOT + + + be.simplenotes + domain + 1.0-SNAPSHOT + + + be.simplenotes + shared + 1.0-SNAPSHOT + + + org.http4k + http4k-core + 3.254.0 + + + org.http4k + http4k-server-jetty + 3.254.0 + + + org.jetbrains.kotlinx + kotlinx-html-jvm + 0.7.1 + + + org.jetbrains.kotlinx + kotlinx-serialization-runtime + 0.20.0 + + + + be.simplenotes + shared + 1.0-SNAPSHOT + test-jar + test + + + org.http4k + http4k-testing-hamkrest + 3.254.0 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + true + + + be.simplenotes.app.SimpleNotesKt + + + + + com.h2database:h2 + + ** + + + + org.mariadb.jdbc:mariadb-java-client + + ** + + + + org.jetbrains.kotlin:kotlin-reflect + + ** + + + + *:* + + META-INF/maven/** + META-INF/proguard/** + META-INF/*.kotlin_module + META-INF/DEPENDENCIES* + META-INF/NOTICE* + META-INF/LICENSE* + LICENSE* + META-INF/README* + META-INF/native-image/** + + + + + + + + + + + diff --git a/app/src/main/kotlin/Config.kt b/app/src/main/kotlin/Config.kt new file mode 100644 index 0000000..181e561 --- /dev/null +++ b/app/src/main/kotlin/Config.kt @@ -0,0 +1,49 @@ +package be.simplenotes.app + +import be.simplenotes.shared.config.DataSourceConfig +import be.simplenotes.shared.config.JwtConfig +import be.simplenotes.shared.config.ServerConfig +import java.util.* +import java.util.concurrent.TimeUnit + +object Config { + //region Config loading + private val properties: Properties = javaClass + .getResource("/application.properties") + .openStream() + .use { + Properties().apply { load(it) } + } + + private val env = System.getenv() + + private fun value(key: String): String = + env[key.toUpperCase().replace(".", "_")] + ?: properties.getProperty(key) + ?: error("Missing config key $key") + //endregion + + val jwtConfig + get() = JwtConfig( + secret = value("jwt.secret"), + validity = value("jwt.validity").toLong(), + timeUnit = TimeUnit.HOURS, + ) + + val dataSourceConfig + get() = DataSourceConfig( + jdbcUrl = value("jdbcUrl"), + driverClassName = value("driverClassName"), + username = value("username"), + password = value("password"), + maximumPoolSize = value("maximumPoolSize").toInt(), + connectionTimeout = value("connectionTimeout").toLong() + ) + + val serverConfig + get() = ServerConfig( + host = value("host"), + port = value("port").toInt(), + ) + +} diff --git a/app/src/main/kotlin/Server.kt b/app/src/main/kotlin/Server.kt new file mode 100644 index 0000000..39f9530 --- /dev/null +++ b/app/src/main/kotlin/Server.kt @@ -0,0 +1,32 @@ +package be.simplenotes.app + +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.ServerConnector +import org.http4k.routing.RoutingHttpHandler +import org.http4k.server.ConnectorBuilder +import org.http4k.server.Jetty +import org.http4k.server.ServerConfig +import org.http4k.server.asServer +import org.slf4j.LoggerFactory +import be.simplenotes.shared.config.ServerConfig as SimpleNotesServeConfig + +class Server( + private val config: SimpleNotesServeConfig, + private val serverConfig: ServerConfig, + private val router: RoutingHttpHandler, +) { + fun start() { + router.asServer(serverConfig).start() + LoggerFactory.getLogger(javaClass).info("Listening on http://${config.host}:${config.port}") + } +} + +fun serverConfig(config: SimpleNotesServeConfig): ServerConfig { + val builder: ConnectorBuilder = { server: Server -> + ServerConnector(server).apply { + port = config.port + host = config.host + } + } + return Jetty(config.port, builder) +} diff --git a/app/src/main/kotlin/SimpleNotes.kt b/app/src/main/kotlin/SimpleNotes.kt new file mode 100644 index 0000000..11babfa --- /dev/null +++ b/app/src/main/kotlin/SimpleNotes.kt @@ -0,0 +1,92 @@ +package be.simplenotes.app + +import be.simplenotes.app.controllers.BaseController +import be.simplenotes.app.controllers.NoteController +import be.simplenotes.app.controllers.UserController +import be.simplenotes.app.filters.AuthFilter +import be.simplenotes.app.filters.AuthType +import be.simplenotes.app.routes.Router +import be.simplenotes.app.utils.StaticFileResolver +import be.simplenotes.app.utils.StaticFileResolverImpl +import be.simplenotes.app.views.BaseView +import be.simplenotes.app.views.NoteView +import be.simplenotes.app.views.UserView +import be.simplenotes.domain.domainModule +import be.simplenotes.persistance.DbMigrations +import be.simplenotes.persistance.persistanceModule +import be.simplenotes.shared.config.DataSourceConfig +import be.simplenotes.shared.config.JwtConfig +import org.http4k.core.RequestContexts +import org.koin.core.context.startKoin +import org.koin.core.qualifier.qualifier +import org.koin.dsl.module +import org.slf4j.LoggerFactory +import be.simplenotes.shared.config.ServerConfig as SimpleNotesServeConfig + + +fun main() { + val koin = startKoin { + modules( + persistanceModule, + configModule, + domainModule, + serverModule, + userModule, + baseModule, + noteModule, + ) + }.koin + + val dataSourceConfig = koin.get() + val jwtConfig = koin.get() + val serverConfig = koin.get() + val logger = LoggerFactory.getLogger("SimpleNotes") + logger.info("datasource: $dataSourceConfig") + logger.info("jwt: $jwtConfig") + logger.info("server: $serverConfig") + + val migrations = koin.get() + migrations.migrate() + + koin.get().start() +} + +val serverModule = module { + single { Server(get(), get(), get()) } + single { StaticFileResolverImpl() } + single { + Router( + get(), + get(), + get(), + requiredAuth = get(AuthType.Required.qualifier), + optionalAuth = get(AuthType.Optional.qualifier), + get() + )() + } + single { serverConfig(get()) } + single { RequestContexts() } + single(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get())() } + single(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get())() } +} + +val userModule = module { + single { UserController(get(), get()) } + single { UserView(get()) } +} + +val baseModule = module { + single { BaseController(get()) } + single { BaseView(get()) } +} + +val noteModule = module { + single { NoteController(get(), get()) } + single { NoteView(get()) } +} + +val configModule = module { + single { Config.dataSourceConfig } + single { Config.jwtConfig } + single { Config.serverConfig } +} diff --git a/app/src/main/kotlin/controllers/BaseController.kt b/app/src/main/kotlin/controllers/BaseController.kt new file mode 100644 index 0000000..37e5134 --- /dev/null +++ b/app/src/main/kotlin/controllers/BaseController.kt @@ -0,0 +1,13 @@ +package be.simplenotes.app.controllers + +import be.simplenotes.app.extensions.html +import be.simplenotes.app.views.BaseView +import be.simplenotes.domain.security.JwtPayload +import org.http4k.core.Request +import org.http4k.core.Response +import org.http4k.core.Status.Companion.OK + +class BaseController(private val view: BaseView) { + fun index(@Suppress("UNUSED_PARAMETER") request: Request, jwtPayload: JwtPayload?) = + Response(OK).html(view.renderHome(jwtPayload)) +} diff --git a/app/src/main/kotlin/controllers/NoteController.kt b/app/src/main/kotlin/controllers/NoteController.kt new file mode 100644 index 0000000..97ec25e --- /dev/null +++ b/app/src/main/kotlin/controllers/NoteController.kt @@ -0,0 +1,96 @@ +package be.simplenotes.app.controllers + +import be.simplenotes.app.extensions.html +import be.simplenotes.app.extensions.redirect +import be.simplenotes.app.views.NoteView +import be.simplenotes.domain.security.JwtPayload +import be.simplenotes.domain.usecases.NoteService +import be.simplenotes.domain.usecases.markdown.InvalidMeta +import be.simplenotes.domain.usecases.markdown.MissingMeta +import be.simplenotes.domain.usecases.markdown.ValidationError +import org.http4k.core.Method +import org.http4k.core.Request +import org.http4k.core.Response +import org.http4k.core.Status.Companion.BAD_REQUEST +import org.http4k.core.Status.Companion.NOT_FOUND +import org.http4k.core.Status.Companion.OK +import org.http4k.core.body.form +import org.http4k.routing.path +import java.util.* +import kotlin.math.abs + +class NoteController( + private val view: NoteView, + private val noteService: NoteService, +) { + + fun new(request: Request, jwtPayload: JwtPayload): Response { + if (request.method == Method.GET) return Response(OK).html(view.noteEditor(jwtPayload)) + + val markdownForm = request.form("markdown") ?: "" + + return noteService.create(jwtPayload.userId, markdownForm).fold({ + val html = when (it) { + MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm) + InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm) + is ValidationError -> view.noteEditor(jwtPayload, validationErrors = it.validationErrors, textarea = markdownForm) + } + Response(BAD_REQUEST).html(html) + }, { + Response.redirect("/notes/${it.uuid}") + }) + } + + fun list(request: Request, jwtPayload: JwtPayload): Response { + val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1 + val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage) + return Response(OK).html(view.notes(jwtPayload, notes, currentPage, pages)) + } + + fun note(request: Request, jwtPayload: JwtPayload): Response { + val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND) + + if (request.method == Method.POST && request.form("delete") != null) { + return if (noteService.delete(jwtPayload.userId, noteUuid)) + Response.redirect("/notes") // FIXME: flash cookie to show success ? + else + Response(NOT_FOUND) // FIXME: show an error + } + + val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND) + return Response(OK).html(view.renderedNote(jwtPayload, note)) + } + + fun edit(request: Request, jwtPayload: JwtPayload): Response { + val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND) + val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND) + + if (request.method == Method.GET) { + return Response(OK).html(view.noteEditor(jwtPayload, textarea = note.markdown)) + } + + val markdownForm = request.form("markdown") ?: "" + + return noteService.update(jwtPayload.userId, note.uuid, markdownForm).fold({ + val html = when (it) { + MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm) + InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm) + is ValidationError -> view.noteEditor(jwtPayload, validationErrors = it.validationErrors, textarea = markdownForm) + } + Response(BAD_REQUEST).html(html) + }, { + Response.redirect("/notes/${note.uuid}") + }) + + } + + private fun Request.uuidPath(): UUID? { + val uuidPath = path("uuid")!! + return try { + UUID.fromString(uuidPath)!! + } catch (e: IllegalArgumentException) { + null + } + } + +} diff --git a/app/src/main/kotlin/controllers/UserController.kt b/app/src/main/kotlin/controllers/UserController.kt new file mode 100644 index 0000000..9b68eab --- /dev/null +++ b/app/src/main/kotlin/controllers/UserController.kt @@ -0,0 +1,112 @@ +package be.simplenotes.app.controllers + +import be.simplenotes.app.extensions.html +import be.simplenotes.app.extensions.isSecure +import be.simplenotes.app.extensions.redirect +import be.simplenotes.app.views.UserView +import be.simplenotes.domain.security.JwtPayload +import be.simplenotes.domain.usecases.UserService +import be.simplenotes.domain.usecases.login.* +import be.simplenotes.domain.usecases.register.InvalidRegisterForm +import be.simplenotes.domain.usecases.register.RegisterForm +import be.simplenotes.domain.usecases.register.UserExists +import org.http4k.core.Method.GET +import org.http4k.core.Request +import org.http4k.core.Response +import org.http4k.core.Status.Companion.OK +import org.http4k.core.body.form +import org.http4k.core.cookie.Cookie +import org.http4k.core.cookie.SameSite +import org.http4k.core.cookie.cookie +import org.http4k.core.cookie.invalidateCookie + +class UserController( + private val userService: UserService, + private val userView: UserView, +) { + fun register(request: Request, jwtPayload: JwtPayload?): Response { + if (request.method == GET) return Response(OK).html( + userView.register(jwtPayload) + ) + + val result = userService.register(request.registerForm()) + + return result.fold( + { + val html = when (it) { + UserExists -> userView.register( + jwtPayload, + error = "User already exists" + ) + is InvalidRegisterForm -> + userView.register( + jwtPayload, + validationErrors = it.validationErrors + ) + } + Response(OK).html(html) + }, + { + Response.redirect("/login") + } + ) + } + + private fun Request.registerForm() = RegisterForm(form("username"), form("password")) + private fun Request.loginForm(): LoginForm = registerForm() + + fun login(request: Request, jwtPayload: JwtPayload?): Response { + if (request.method == GET) return Response(OK).html( + userView.login(jwtPayload) + ) + + val result = userService.login(request.loginForm()) + + return result.fold( + { + val html = when (it) { + Unregistered -> + userView.login( + jwtPayload, + error = "User does not exist" + ) + WrongPassword -> + userView.login( + jwtPayload, + error = "Wrong password" + ) + is InvalidLoginForm -> + userView.login( + jwtPayload, + validationErrors = it.validationErrors + ) + } + Response(OK).html(html) + }, + { token -> + Response.redirect("/").loginCookie(token, request.isSecure()) + } + ) + } + + private fun Response.loginCookie(token: Token, secure: Boolean): Response { + // FIXME: expires + // val expiresAt = JWT.decode(token).expiresAt + // LocalDateTime.ofEpochSecond(expiresAt.time, 0) + + return this.cookie( + Cookie( + name = "Authorization", + value = "Bearer $token", + path = "/", + httpOnly = true, + sameSite = SameSite.Lax, + secure = secure + ) + ) + } + + fun logout(@Suppress("UNUSED_PARAMETER") request: Request) = Response.redirect("/") + .invalidateCookie("Authorization") + +} diff --git a/app/src/main/kotlin/extensions/Http4kExtensions.kt b/app/src/main/kotlin/extensions/Http4kExtensions.kt new file mode 100644 index 0000000..5901284 --- /dev/null +++ b/app/src/main/kotlin/extensions/Http4kExtensions.kt @@ -0,0 +1,13 @@ +package be.simplenotes.app.extensions + +import org.http4k.core.Request +import org.http4k.core.Response +import org.http4k.core.Status.Companion.FOUND +import org.http4k.core.Status.Companion.MOVED_PERMANENTLY + +fun Response.html(html: String) = body(html).header("Content-Type", "text/html; charset=utf-8") + +fun Response.Companion.redirect(url: String, permanent: Boolean = false) = + Response(if (permanent) MOVED_PERMANENTLY else FOUND).header("Location", url) + +fun Request.isSecure() = header("X-Forwarded-Proto")?.contains("https") ?: false diff --git a/app/src/main/kotlin/filters/AuthFilter.kt b/app/src/main/kotlin/filters/AuthFilter.kt new file mode 100644 index 0000000..fcfbf66 --- /dev/null +++ b/app/src/main/kotlin/filters/AuthFilter.kt @@ -0,0 +1,45 @@ +package be.simplenotes.app.filters + +import be.simplenotes.app.extensions.redirect +import be.simplenotes.domain.security.JwtPayload +import be.simplenotes.domain.security.JwtPayloadExtractor +import org.http4k.core.Filter +import org.http4k.core.Request +import org.http4k.core.RequestContexts +import org.http4k.core.Response +import org.http4k.core.cookie.cookie + +enum class AuthType { + Optional, Required +} + +private const val authKey = "auth" + +class AuthFilter( + private val extractor: JwtPayloadExtractor, + private val authType: AuthType, + private val ctx: RequestContexts +) { + operator fun invoke() = Filter { next -> + { + val jwtPayload = it.bearerToken()?.let { token -> extractor(token) } + when { + jwtPayload != null -> { + ctx[it][authKey] = jwtPayload + next(it) + } + authType == AuthType.Required -> Response.redirect("/login") + else -> next(it) + } + } + } +} + +fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey] + +private fun Request.bearerToken(): String? = cookie("Authorization") + ?.value + ?.trim() + ?.takeIf { it.startsWith("Bearer") } + ?.substringAfter("Bearer") + ?.trim() diff --git a/app/src/main/kotlin/filters/ErrorFilter.kt b/app/src/main/kotlin/filters/ErrorFilter.kt new file mode 100644 index 0000000..6178364 --- /dev/null +++ b/app/src/main/kotlin/filters/ErrorFilter.kt @@ -0,0 +1,26 @@ +package be.simplenotes.app.filters + +import org.http4k.core.Filter +import org.http4k.core.Response +import org.http4k.core.Status +import org.slf4j.LoggerFactory + +object ErrorFilter { + private val logger = LoggerFactory.getLogger(javaClass) + + operator fun invoke(): Filter = Filter { next -> + { + try { + val response = next(it) + if (response.status == Status.NOT_FOUND) Response(Status.NOT_FOUND).body("TODO(NOT_FOUND)") + else response + } catch (e: Exception) { + logger.error(e.stackTraceToString()) + Response(Status.INTERNAL_SERVER_ERROR).body("TODO(INTERNAL_SERVER_ERROR)") + } catch (e: NotImplementedError) { + logger.error(e.stackTraceToString()) + Response(Status.INTERNAL_SERVER_ERROR).body("TODO(NotImplementedError)") + } + } + } +} diff --git a/app/src/main/kotlin/filters/ImmutableFilter.kt b/app/src/main/kotlin/filters/ImmutableFilter.kt new file mode 100644 index 0000000..05bd19c --- /dev/null +++ b/app/src/main/kotlin/filters/ImmutableFilter.kt @@ -0,0 +1,19 @@ +package be.simplenotes.app.filters + +import org.http4k.core.Filter +import org.http4k.core.HttpHandler +import org.http4k.core.Method +import org.http4k.core.Request + +object ImmutableFilter { + operator fun invoke(): Filter { + return Filter { next: HttpHandler -> + { request: Request -> + val response = next(request) + if (request.method == Method.GET) + response.header("Cache-Control", "public, max-age=31536000, immutable") + else response + } + } + } +} diff --git a/app/src/main/kotlin/filters/SecurityFilter.kt b/app/src/main/kotlin/filters/SecurityFilter.kt new file mode 100644 index 0000000..10abffe --- /dev/null +++ b/app/src/main/kotlin/filters/SecurityFilter.kt @@ -0,0 +1,20 @@ +package be.simplenotes.app.filters + +import org.http4k.core.Filter +import org.http4k.core.HttpHandler +import org.http4k.core.Request + +object SecurityFilter { + operator fun invoke(): Filter { + return Filter { next: HttpHandler -> + { request: Request -> + val response = next(request) + .header("X-Content-Type-Options", "nosniff") + + if (response.header("Content-Type")?.contains("text/html") == true) + response.header("Content-Security-Policy", "default-src 'self'") + else response + } + } + } +} diff --git a/app/src/main/kotlin/routes/Router.kt b/app/src/main/kotlin/routes/Router.kt new file mode 100644 index 0000000..6aeac84 --- /dev/null +++ b/app/src/main/kotlin/routes/Router.kt @@ -0,0 +1,72 @@ +package be.simplenotes.app.routes + +import be.simplenotes.app.controllers.BaseController +import be.simplenotes.app.controllers.NoteController +import be.simplenotes.app.controllers.UserController +import be.simplenotes.app.filters.ErrorFilter +import be.simplenotes.app.filters.ImmutableFilter +import be.simplenotes.app.filters.SecurityFilter +import be.simplenotes.app.filters.jwtPayload +import be.simplenotes.domain.security.JwtPayload +import org.http4k.core.* +import org.http4k.core.Method.GET +import org.http4k.core.Method.POST +import org.http4k.filter.ResponseFilters +import org.http4k.filter.ServerFilters.InitialiseRequestContext +import org.http4k.routing.* + +class Router( + private val baseController: BaseController, + private val userController: UserController, + private val noteController: NoteController, + private val requiredAuth: Filter, + private val optionalAuth: Filter, + private val contexts: RequestContexts, +) { + operator fun invoke(): RoutingHttpHandler { + + val basicRoutes = routes( + ImmutableFilter().then(static(ResourceLoader.Classpath(("/static")))), + ) + + fun public(request: Request, handler: PublicHandler) = handler(request, request.jwtPayload(contexts)) + fun protected(request: Request, handler: ProtectedHandler) = handler(request, request.jwtPayload(contexts)!!) + + val publicRoutes: RoutingHttpHandler = routes( + "/" bind GET to { public(it, baseController::index) }, + "/register" bind GET to { public(it, userController::register) }, + "/register" bind POST to { public(it, userController::register) }, + "/login" bind GET to { public(it, userController::login) }, + "/login" bind POST to { public(it, userController::login) }, + "/logout" bind POST to userController::logout, + ) + + val protectedRoutes = routes( + "/account" bind GET to { TODO() }, + "/export" bind POST to { TODO() }, + "/notes" bind GET to { protected(it, noteController::list) }, + "/notes/new" bind GET to { protected(it, noteController::new) }, + "/notes/new" bind POST to { protected(it, noteController::new) }, + "/notes/{uuid}" bind GET to { protected(it, noteController::note) }, + "/notes/{uuid}" bind POST to { protected(it, noteController::note) }, + "/notes/{uuid}/edit" bind GET to { protected(it, noteController::edit) }, + "/notes/{uuid}/edit" bind POST to { protected(it, noteController::edit) }, + ) + + val routes = routes( + basicRoutes, + optionalAuth.then(publicRoutes), + requiredAuth.then(protectedRoutes), + ) + + val globalFilters = ErrorFilter() + .then(InitialiseRequestContext(contexts)) + .then(SecurityFilter()) + .then(ResponseFilters.GZip()) + + return globalFilters.then(routes) + } +} + +private typealias PublicHandler = (Request, JwtPayload?) -> Response +private typealias ProtectedHandler = (Request, JwtPayload) -> Response diff --git a/app/src/main/kotlin/utils/StaticFilesResolver.kt b/app/src/main/kotlin/utils/StaticFilesResolver.kt new file mode 100644 index 0000000..987ab80 --- /dev/null +++ b/app/src/main/kotlin/utils/StaticFilesResolver.kt @@ -0,0 +1,24 @@ +package be.simplenotes.app.utils + +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration + +interface StaticFileResolver { + fun resolve(name: String): String? +} + +class StaticFileResolverImpl : StaticFileResolver { + private val mappings: Map + + init { + val json = Json(JsonConfiguration.Stable) + val manifest = javaClass.getResource("/css-manifest.json").readText() + val manifestObject = json.parseJson(manifest).jsonObject + val keys = manifestObject.keys + mappings = keys.map { + it to "/${manifestObject[it]!!.primitive.content}" + }.toMap() + } + + override fun resolve(name: String) = mappings[name] +} diff --git a/app/src/main/kotlin/views/BaseView.kt b/app/src/main/kotlin/views/BaseView.kt new file mode 100644 index 0000000..d2605a5 --- /dev/null +++ b/app/src/main/kotlin/views/BaseView.kt @@ -0,0 +1,18 @@ +package be.simplenotes.app.views + +import be.simplenotes.app.utils.StaticFileResolver +import be.simplenotes.domain.security.JwtPayload +import kotlinx.html.div +import kotlinx.html.h1 + +class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) { + fun renderHome(jwtPayload: JwtPayload?) = renderPage(title = "Home", jwtPayload = jwtPayload) { + div("centered container mx-auto flex justify-center items-center") { + div("bg-gray-800 md:w-1/3 w-full rounded-lg m-4 p-6 text-center") { + h1("text-3xl") { +"SimpleNotes" } + div("text-teal-400") { +"Welcome" } + div("text-gray-200") { +"TODO" } + } + } + } +} diff --git a/app/src/main/kotlin/views/NoteView.kt b/app/src/main/kotlin/views/NoteView.kt new file mode 100644 index 0000000..dc183c2 --- /dev/null +++ b/app/src/main/kotlin/views/NoteView.kt @@ -0,0 +1,131 @@ +package be.simplenotes.app.views + +import be.simplenotes.app.utils.StaticFileResolver +import be.simplenotes.app.views.components.Alert +import be.simplenotes.app.views.components.alert +import be.simplenotes.app.views.components.submitButton +import be.simplenotes.domain.model.PersistedNote +import be.simplenotes.domain.model.PersistedNoteMetadata +import be.simplenotes.domain.security.JwtPayload +import io.konform.validation.ValidationError +import kotlinx.html.* + +class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) { + + fun noteEditor( + jwtPayload: JwtPayload, + error: String? = null, + textarea: String? = null, + validationErrors: List = emptyList(), + ) = renderPage(title = "New note", jwtPayload = jwtPayload) { + div("container mx-auto p-4") { + // TODO: error + error?.let { alert(Alert.Warning, error) } + validationErrors.forEach { + alert(Alert.Warning, it.dataPath.substringAfter('.') + ": " + it.message) + } + form(method = FormMethod.post) { + textArea(classes = "w-full bg-gray-800 p-5 outline-none font-mono") { + attributes.also { + it["rows"] = "20" + it["id"] = "markdown" + it["name"] = "markdown" + it["aria-label"] = "markdown text area" + it["spellcheck"] = "false" + } + textarea?.let { + +it + } ?: +""" + |--- + |title: '' + |tags: [] + |--- + | + """.trimMargin("|") + } + submitButton("Save") + } + } + } + + fun notes(jwtPayload: JwtPayload, notes: List, currentPage: Int, numberOfPages: Int) = + renderPage(title = "Notes", jwtPayload = jwtPayload) { + div("container mx-auto p-4") { + div("flex justify-between mb-4") { + h1("text-2xl underline") { +"Notes" } + a( + href = "/notes/new", + classes = "text-gray-800 bg-green-500 hover:bg-green-700 " + + "inline ml-2 text-md font-semibold rounded px-4 py-2" + ) { +"New" } + } + if (notes.isNotEmpty()) { + + ul { + notes.forEach { (title, tags, _, uuid) -> + li("flex justify-between") { + a(classes = "text-blue-200 text-xl hover:underline", href = "/notes/${uuid}") { + +title + } + span { + tags.forEach { + span("tag ml-2") { +"#$it" } + } + } + } + } + } + if (numberOfPages > 1) + pagination(currentPage, numberOfPages) + } else + span { +"No notes yet" } // FIXME if too far in pagination, it it displayed + } + } + + private fun DIV.pagination(currentPage: Int, numberOfPages: Int) { + val links = mutableListOf>() + //if (currentPage > 1) links += "Previous" to "?page=${currentPage - 1}" + links += (1..numberOfPages).map { "$it" to "?page=$it" } + //if (currentPage < numberOfPages) links += "Next" to "?page=${currentPage + 1}" + + nav("pages") { + links.forEach { (name, href) -> + a(href, classes = if (name == currentPage.toString()) "active" else null) { +name } + } + } + } + + fun renderedNote(jwtPayload: JwtPayload, note: PersistedNote) = renderPage(note.meta.title, jwtPayload = jwtPayload) { + div("container mx-auto p-4") { + div("flex items-center justify-between mb-4") { + h1("text-3xl fond-bold underline") { +note.meta.title } + span { + note.meta.tags.forEach { + span("tag ml-2") { +"#$it" } + } + } + } + span("flex justify-end mb-4") { + a( + href = "/notes/${note.uuid}/edit", + classes = "mx-2 bg-teal-500 hover:bg-teal-600 focus:bg-teal-600" + + " focus:outline-none text-white font-bold py-2 px-4 rounded" + ) { +"Edit" } + form(method = FormMethod.post, classes = "inline") { + button( + type = ButtonType.submit, + name = "delete", + classes = "mx-2 bg-red-500 hover:bg-red-600 focus:bg-red-600" + + " focus:outline-none text-white font-bold py-2 px-4 rounded" + ) { +"Delete" } + } + } + div { + attributes["id"] = "note" + unsafe { + +note.html + } + } + } + } +} diff --git a/app/src/main/kotlin/views/UserView.kt b/app/src/main/kotlin/views/UserView.kt new file mode 100644 index 0000000..c05b76b --- /dev/null +++ b/app/src/main/kotlin/views/UserView.kt @@ -0,0 +1,73 @@ +package be.simplenotes.app.views + +import be.simplenotes.app.utils.StaticFileResolver +import be.simplenotes.app.views.components.Alert +import be.simplenotes.app.views.components.alert +import be.simplenotes.app.views.components.input +import be.simplenotes.app.views.components.submitButton +import be.simplenotes.domain.security.JwtPayload +import io.konform.validation.ValidationError +import kotlinx.html.* + +class UserView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) { + fun register( + jwtPayload: JwtPayload?, + error: String? = null, + validationErrors: List = emptyList(), + ) = accountForm("Register", jwtPayload, error, validationErrors, "Create an account", "Register") { + +"Already have an account? " + a(href = "/login", classes = "no-underline text-blue-500 font-bold") { +"Sign In" } + } + + fun login( + jwtPayload: JwtPayload?, + error: String? = null, + validationErrors: List = emptyList(), + new: Boolean = false, + ) = accountForm("Login", jwtPayload, error, validationErrors, "Sign In", "Sign In", new) { + +"Don't have an account yet? " + a(href = "/register", classes = "no-underline text-blue-500 font-bold") { +"Create an account" } + } + + private fun accountForm( + title: String, + jwtPayload: JwtPayload?, + error: String? = null, + validationErrors: List = emptyList(), + h1: String, + submit: String, + new: Boolean = false, + footer: FlowContent.() -> Unit, + ) = renderPage(title = title, jwtPayload = jwtPayload) { + div("centered container mx-auto flex justify-center items-center") { + div("w-full md:w-1/2 lg:w-1/3 m-4") { + h1("font-semibold text-lg mb-6 text-center") { +h1 } + div("p-8 mb-6") { + if (new) alert(Alert.Success, "Your account has been created") + error?.let { alert(Alert.Warning, error) } + form(method = FormMethod.post) { + input( + id = "username", + placeholder = "Username", + autoComplete = "username", + error = validationErrors.find { it.dataPath == ".username" }?.message + ) + input( + id = "password", + placeholder = "Password", + autoComplete = "new-password", + type = InputType.password, + error = validationErrors.find { it.dataPath == ".password" }?.message + ) + submitButton(submit) + } + } + div("text-center") { + p("text-gray-200 text-sm") { + footer() + } + } + } + } + } +} diff --git a/app/src/main/kotlin/views/View.kt b/app/src/main/kotlin/views/View.kt new file mode 100644 index 0000000..38db761 --- /dev/null +++ b/app/src/main/kotlin/views/View.kt @@ -0,0 +1,36 @@ +package be.simplenotes.app.views + +import be.simplenotes.app.utils.StaticFileResolver +import be.simplenotes.app.views.components.navbar +import be.simplenotes.domain.security.JwtPayload +import kotlinx.html.* +import kotlinx.html.stream.appendHTML + +abstract class View(private val staticFileResolver: StaticFileResolver) { + + private val styles = staticFileResolver.resolve("styles.css")!! + + fun renderPage( + title: String, + description: String? = null, + jwtPayload: JwtPayload?, + body: BODY.() -> Unit = {}, + ) = buildString { + appendLine("") + appendHTML().html { + attributes["lang"] = "en" + head { + meta(charset = "UTF-8") + meta(name = "viewport", content = "width=device-width, initial-scale=1") + title("$title - SimpleNotes") + description?.let { meta(name = "description", content = it) } + link(rel = "stylesheet", href = styles) + link(rel = "shortcut icon", href="/favicon.ico", type = "image/x-icon") + } + body("bg-gray-900 text-white") { + navbar(jwtPayload) + main { this@body.body() } + } + } + } +} diff --git a/app/src/main/kotlin/views/components/Alerts.kt b/app/src/main/kotlin/views/components/Alerts.kt new file mode 100644 index 0000000..4893be2 --- /dev/null +++ b/app/src/main/kotlin/views/components/Alerts.kt @@ -0,0 +1,19 @@ +package be.simplenotes.app.views.components + +import kotlinx.html.* + +fun FlowContent.alert(type: Alert, title: String, details: String? = null) { + val colors = when (type) { + Alert.Success -> "bg-green-500 border border-green-400 text-gray-800" + Alert.Warning -> "bg-red-500 border border-red-400 text-red-200" + } + div("$colors px-4 py-3 mb-4 rounded relative") { + attributes["role"] = "alert" + strong("font-bold") { +title } + details?.let { span("block sm:inline") { +details } } + } +} + +enum class Alert { + Success, Warning +} diff --git a/app/src/main/kotlin/views/components/Forms.kt b/app/src/main/kotlin/views/components/Forms.kt new file mode 100644 index 0000000..96b83d6 --- /dev/null +++ b/app/src/main/kotlin/views/components/Forms.kt @@ -0,0 +1,37 @@ +package be.simplenotes.app.views.components + +import kotlinx.html.* +import kotlinx.html.ButtonType.submit + +fun FlowContent.input( + type: InputType = InputType.text, + placeholder: String, + id: String, + autoComplete: String? = null, + error: String? = null +) { + val colors = "bg-gray-800 border-gray-700 focus:border-teal-500 text-white" + div("mb-8") { + input( + type = type, + classes = "$colors rounded w-full border appearance-none focus:outline-none text-base p-2" + ) { + attributes["placeholder"] = placeholder + attributes["aria-label"] = placeholder + attributes["name"] = id + attributes["id"] = id + autoComplete?.let { attributes["autocomplete"] = it } + } + error?.let { p("mt-2 text-red-500 text-sm italic") { +"$placeholder $error" } } + } +} + +fun FlowContent.submitButton(text: String) { + div("flex items-center mt-6") { + button( + type = submit, + classes = "bg-teal-500 hover:bg-teal-600 focus:bg-teal-600" + + " w-full focus:outline-none text-white font-bold py-2 px-4 rounded" + ) { +text } + } +} diff --git a/app/src/main/kotlin/views/components/Navbar.kt b/app/src/main/kotlin/views/components/Navbar.kt new file mode 100644 index 0000000..14f6be8 --- /dev/null +++ b/app/src/main/kotlin/views/components/Navbar.kt @@ -0,0 +1,29 @@ +package be.simplenotes.app.views.components + +import be.simplenotes.domain.security.JwtPayload +import kotlinx.html.* + +fun BODY.navbar(jwtPayload: JwtPayload?) { + nav("nav bg-teal-700 shadow-md flex items-center justify-between px-4") { + a(href = "/", classes = "text-2xl text-gray-100 font-bold") { +"SimpleNotes" } + ul { + if (jwtPayload != null) { + li("inline text-gray-100 ml-2 text-md font-semibold") { + a(href = "/notes") { +"Notes" } + } + li( + "text-gray-800 bg-green-500 hover:bg-green-700" + + " inline ml-2 text-md font-semibold rounded px-4 py-2" + ) { + form(classes = "inline", action = "/logout", method = FormMethod.post) { + button(type = ButtonType.submit) { +"Logout" } + } + } + } else { + li("inline text-gray-100 pl-2 text-md font-semibold") { + a(href = "/login") { +"Sign In" } + } + } + } + } +} diff --git a/app/src/main/resources/application.properties b/app/src/main/resources/application.properties new file mode 100644 index 0000000..9e93a26 --- /dev/null +++ b/app/src/main/resources/application.properties @@ -0,0 +1,12 @@ +host=localhost +port=8080 +# +jdbcUrl=jdbc:h2:./notes-db; +driverClassName=org.h2.Driver +username=h2 +password= +maximumPoolSize=10 +connectionTimeout=3000 +# +jwt.secret=PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms= +jwt.validity=24 diff --git a/resources/static/favicon.ico b/app/src/main/resources/static/favicon.ico similarity index 100% rename from resources/static/favicon.ico rename to app/src/main/resources/static/favicon.ico diff --git a/resources/static/robots.txt b/app/src/main/resources/static/robots.txt similarity index 100% rename from resources/static/robots.txt rename to app/src/main/resources/static/robots.txt diff --git a/app/src/test/kotlin/Empty.kt b/app/src/test/kotlin/Empty.kt new file mode 100644 index 0000000..59900e4 --- /dev/null +++ b/app/src/test/kotlin/Empty.kt @@ -0,0 +1 @@ +package be.simplenotes.app diff --git a/app/src/test/kotlin/filters/AuthFilterTest.kt b/app/src/test/kotlin/filters/AuthFilterTest.kt new file mode 100644 index 0000000..ec2cb95 --- /dev/null +++ b/app/src/test/kotlin/filters/AuthFilterTest.kt @@ -0,0 +1,94 @@ +package be.simplenotes.app.filters + +import be.simplenotes.domain.security.JwtPayload +import be.simplenotes.domain.security.JwtPayloadExtractor +import be.simplenotes.domain.security.SimpleJwt +import be.simplenotes.shared.config.JwtConfig +import com.natpryce.hamkrest.assertion.assertThat +import org.http4k.core.* +import org.http4k.core.Method.GET +import org.http4k.core.Status.Companion.FOUND +import org.http4k.core.Status.Companion.OK +import org.http4k.core.cookie.cookie +import org.http4k.filter.ServerFilters +import org.http4k.hamkrest.hasBody +import org.http4k.hamkrest.hasHeader +import org.http4k.hamkrest.hasStatus +import org.http4k.routing.bind +import org.http4k.routing.routes +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit + +internal class AuthFilterTest { + + // region setup + private val jwtConfig = JwtConfig("secret", 1, TimeUnit.HOURS) + private val simpleJwt = SimpleJwt(jwtConfig) + private val extractor = JwtPayloadExtractor(simpleJwt) + private val ctx = RequestContexts() + private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx)() + private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx)() + + private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) } + + private val app = ServerFilters.InitialiseRequestContext(ctx).then( + routes( + "/optional" bind GET to optionalAuth.then(echoJwtPayloadHandler), + "/protected" bind GET to requiredAuth.then(echoJwtPayloadHandler) + ) + ) + // endregion + + @Nested + inner class OptionalAuth { + @Test + fun `it should allow no token`() { + val response = app(Request(GET, "/optional")) + assertThat(response, hasStatus(OK)) + assertThat(response, hasBody("null")) + } + + @Test + fun `it should allow an invalid token`() { + val response = app(Request(GET, "/optional").cookie("Authorization", "Bearer nnkjnkjnk")) + assertThat(response, hasStatus(OK)) + assertThat(response, hasBody("null")) + } + + @Test + fun `it should allow a valid token`() { + val jwtPayload = JwtPayload(1, "user") + val token = simpleJwt.sign(jwtPayload) + val response = app(Request(GET, "/optional").cookie("Authorization", "Bearer $token")) + assertThat(response, hasStatus(OK)) + assertThat(response, hasBody("$jwtPayload")) + } + } + + @Nested + inner class RequiredAuth { + @Test + fun `it shouldn't allow a missing token`() { + val response = app(Request(GET, "/protected")) + assertThat(response, hasStatus(FOUND)) + assertThat(response, hasHeader("Location")) + } + + @Test + fun `it shouldn't allow an invalid token`() { + val response = app(Request(GET, "/protected").cookie("Authorization", "Bearer nnkjnkjnk")) + assertThat(response, hasStatus(FOUND)) + assertThat(response, hasHeader("Location")) + } + + @Test + fun `it should allow a valid token"`() { + val jwtPayload = JwtPayload(1, "user") + val token = simpleJwt.sign(jwtPayload) + val response = app(Request(GET, "/protected").cookie("Authorization", "Bearer $token")) + assertThat(response, hasStatus(OK)) + assertThat(response, hasBody("$jwtPayload")) + } + } +} diff --git a/css/package.json b/css/package.json new file mode 100644 index 0000000..72ccbbf --- /dev/null +++ b/css/package.json @@ -0,0 +1,15 @@ +{ + "name": "css", + "version": "1.0.0", + "scripts": { + "css": "NODE_ENV=dev MANIFEST=../app/src/main/resources/css-manifest.json postcss build styles.css --output ../app/src/main/resources/static/styles.css", + "css-purge": "NODE_ENV=production MANIFEST=../app/src/main/resources/css-manifest.json postcss build styles.css --output ../app/src/main/resources/static/styles.css" + }, + "dependencies": { + "autoprefixer": "^9.8.6", + "cssnano": "^4.1.10", + "postcss-cli": "^7.1.1", + "postcss-hash": "^2.0.0", + "tailwindcss": "^1.5.1" + } +} diff --git a/postcss.config.js b/css/postcss.config.js similarity index 88% rename from postcss.config.js rename to css/postcss.config.js index 704c21b..9c0d95a 100644 --- a/postcss.config.js +++ b/css/postcss.config.js @@ -12,7 +12,7 @@ module.exports = { require('postcss-hash')({ algorithm: 'sha256', trim: 20, - manifest: 'resources/css-manifest.json' + manifest: process.env.MANIFEST }), ] } diff --git a/css/styles.css b/css/styles.css index 537d05e..861cfe3 100644 --- a/css/styles.css +++ b/css/styles.css @@ -10,15 +10,54 @@ min-height: calc(100vh - 64px); } +.tag { + @apply italic font-semibold text-sm bg-teal-500 text-gray-900 rounded-full py-1 px-2 align-middle; +} + +nav.pages { + @apply flex pl-0 list-none rounded my-2 flex justify-center; +} + +nav.pages :first-child { + @apply rounded-l; +} + +nav.pages :last-child { + @apply rounded-r; +} + +nav.pages :not(:last-child) { + @apply border-r-0; +} + +nav.pages a { + @apply relative block py-2 px-3 leading-tight border bg-gray-800 border-gray-700 text-teal-300; +} + +nav.pages a:hover { + @apply bg-gray-700; +} + +nav.pages a.active { + @apply bg-teal-800 border-gray-700 text-white; +} + +nav.pages a.active:hover { + @apply bg-gray-700; +} + #note a { - @apply text-blue-700 underline; } + @apply text-blue-700 underline; +} #note p { - @apply my-4; } + @apply my-4; +} #note blockquote, #note figure { - @apply my-4 mx-10; } + @apply my-4 mx-10; +} #note hr { @apply border; } diff --git a/tailwind.config.js b/css/tailwind.config.js similarity index 68% rename from tailwind.config.js rename to css/tailwind.config.js index 06bf2cb..1e44389 100644 --- a/tailwind.config.js +++ b/css/tailwind.config.js @@ -1,8 +1,7 @@ module.exports = { purge: { - enabled: true, content: [ - 'resources/templates/**/*.html' + '../app/src/main/kotlin/views/**/*.kt' ] }, theme: { diff --git a/css/yarn.lock b/css/yarn.lock new file mode 100644 index 0000000..319ed54 --- /dev/null +++ b/css/yarn.lock @@ -0,0 +1,1851 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@fullhuman/postcss-purgecss@^2.1.2": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz#50a954757ec78696615d3e118e3fee2d9291882e" + integrity sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw== + dependencies: + postcss "7.0.32" + purgecss "^2.3.0" + +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/q@^1.5.1": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" + integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.0.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +autoprefixer@^9.4.5, autoprefixer@^9.8.6: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +binary-extensions@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" + integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.0.0, browserslist@^4.12.0: + version "4.14.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.0.tgz#2908951abfe4ec98737b72f34c3bcedc8d43b000" + integrity sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ== + dependencies: + caniuse-lite "^1.0.30001111" + electron-to-chromium "^1.3.523" + escalade "^3.0.2" + node-releases "^1.1.60" + +bytes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111: + version "1.0.30001112" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001112.tgz#0fffc3b934ff56ff0548c37bc9dad7d882bcf672" + integrity sha512-J05RTQlqsatidif/38aN3PGULCLrg8OYQOlJUKbeYVzC2mGZkZLIztwRlB3MtrfLmawUmjFlNJvy/uhwniIe1Q== + +chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +"chalk@^3.0.0 || ^4.0.0", chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.3.0: + version "3.4.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" + integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.4.0" + optionalDependencies: + fsevents "~2.1.2" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0, color@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + +commander@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-tree@1.0.0-alpha.39: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== + dependencies: + mdn-data "2.0.6" + source-map "^0.6.1" + +css-unit-converter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== + +css-what@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" + integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" + integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== + dependencies: + css-tree "1.0.0-alpha.39" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +dependency-graph@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" + integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +domelementtype@1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + +domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + +electron-to-chromium@^1.3.523: + version "1.3.526" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.526.tgz#0e004899edf75afc172cce1b8189aac5dca646aa" + integrity sha512-HiroW5ZbGwgT8kCnoEO8qnGjoTPzJxduvV/Vv/wH63eo2N6Zj3xT5fmmaSPAPUM05iN9/5fIEkIg3owTtV6QZg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +entities@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" + integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +fastq@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +fs-extra@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stdin@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" + integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== + +glob-parent@^5.1.0, glob-parent@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.0, glob@^7.1.2: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globby@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.0, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== + +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +import-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" + integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= + dependencies: + import-from "^2.1.0" + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-from@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" + integrity sha1-M1238qev/VOqpHHUuAId7ja387E= + dependencies: + resolve-from "^3.0.0" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + 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" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" + integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + dependencies: + universalify "^1.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.11, lodash@^4.17.15: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +node-emoji@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" + integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== + dependencies: + lodash.toarray "^4.4.0" + +node-releases@^1.1.60: + version "1.1.60" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" + integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.values@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +postcss-calc@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.3.tgz#d65cca92a3c52bf27ad37a5f732e0587b74f1623" + integrity sha512-IB/EAEmZhIMEIhG7Ov4x+l47UaXOS1n2f4FBUk/aKllQhtSCxWhTzn0nJgkqN7fo/jcWySvWTSB6Syk9L+31bA== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-cli@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-7.1.1.tgz#260f9546be260b2149bf32e28d785a0d79c9aab8" + integrity sha512-bYQy5ydAQJKCMSpvaMg0ThPBeGYqhQXumjbFOmWnL4u65CYXQ16RfS6afGQpit0dGv/fNzxbdDtx8dkqOhhIbg== + dependencies: + chalk "^4.0.0" + chokidar "^3.3.0" + dependency-graph "^0.9.0" + fs-extra "^9.0.0" + get-stdin "^7.0.0" + globby "^11.0.0" + postcss "^7.0.0" + postcss-load-config "^2.0.0" + postcss-reporter "^6.0.0" + pretty-hrtime "^1.0.3" + read-cache "^1.0.0" + yargs "^15.0.2" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-functions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" + integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= + dependencies: + glob "^7.1.2" + object-assign "^4.1.1" + postcss "^6.0.9" + postcss-value-parser "^3.3.0" + +postcss-hash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-hash/-/postcss-hash-2.0.0.tgz#185632a6d51383243197b7b9fde71443795d316c" + integrity sha512-hlRGfXYnpZrTg+g8Jdek/Z0DX3XHQ9tHcXhvlTnQFLnKuq/e2UZELuJNxgPg9oMxg9U6UsBkPCzMVmmGG4dgVw== + dependencies: + mkdirp "^0.5.1" + postcss "^7.0.2" + +postcss-js@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" + integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== + dependencies: + camelcase-css "^2.0.1" + postcss "^7.0.18" + +postcss-load-config@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" + integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== + dependencies: + cosmiconfig "^5.0.0" + import-cwd "^2.0.0" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-nested@^4.1.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.3.tgz#c6f255b0a720549776d220d00c4b70cd244136f6" + integrity sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw== + dependencies: + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-reporter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-6.0.1.tgz#7c055120060a97c8837b4e48215661aafb74245f" + integrity sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw== + dependencies: + chalk "^2.4.1" + lodash "^4.17.11" + log-symbols "^2.2.0" + postcss "^7.0.7" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + +postcss@7.0.32, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.7: + version "7.0.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +postcss@^6.0.9: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + +purgecss@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-2.3.0.tgz#5327587abf5795e6541517af8b190a6fb5488bb3" + integrity sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ== + dependencies: + commander "^5.0.0" + glob "^7.0.0" + postcss "7.0.32" + postcss-selector-parser "^6.0.2" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= + dependencies: + pify "^2.3.0" + +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== + dependencies: + picomatch "^2.2.1" + +reduce-css-calc@^2.1.6: + version "2.1.7" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" + integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve@^1.14.2: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +svgo@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +tailwindcss@^1.5.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.6.2.tgz#352da9e1b0d9154c95ce12483daa1c2fa1f1eea8" + integrity sha512-Cpa0kElG8Sg5sJSvTYi2frmIQZq0w37RLNNrYyy/W6HIWKspqSdTfb9tIN6X1gm4KV5a+TE/n7EKmn5Q9C7EUQ== + dependencies: + "@fullhuman/postcss-purgecss" "^2.1.2" + autoprefixer "^9.4.5" + browserslist "^4.12.0" + bytes "^3.0.0" + chalk "^3.0.0 || ^4.0.0" + color "^3.1.2" + detective "^5.2.0" + fs-extra "^8.0.0" + lodash "^4.17.15" + node-emoji "^1.8.1" + normalize.css "^8.0.1" + postcss "^7.0.11" + postcss-functions "^3.0.0" + postcss-js "^2.0.0" + postcss-nested "^4.1.1" + postcss-selector-parser "^6.0.0" + pretty-hrtime "^1.0.3" + reduce-css-calc "^2.1.6" + resolve "^1.14.2" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +vendors@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" diff --git a/deploy-docker-hub.sh b/deploy-docker-hub.sh new file mode 100755 index 0000000..818c5a9 --- /dev/null +++ b/deploy-docker-hub.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +rm app/src/main/resources/css-manifest.json +rm app/src/main/resources/static/styles* + +yarn --cwd css run css-purge \ + && docker build -t hubv/simplenotes . \ + && docker push hubv/simplenotes:latest diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index 46c5fa6..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: '2.2' - -services: - - db: - ports: - - 127.0.0.1:3306:3306 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index 6c2c5a5..0000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '2.2' - -services: - - api: - build: - dockerfile: Dockerfile.api - context: . - container_name: notes-api - env_file: - - .env - environment: - - TZ=Europe/Brussels - - MYSQL_HOST=db - ports: - - 127.0.0.1:8081:8081 - depends_on: - db: - condition: service_healthy - -volumes: - notes-caddy-data: - notes-caddy-config: diff --git a/docker-compose.yml b/docker-compose.yml index 3088bd4..c6e30f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,19 +4,47 @@ services: db: image: mariadb - container_name: notes-mariadb + container_name: simplenotes-mariadb env_file: - .env environment: - PUID=1000 - PGID=1000 - TZ=Europe/Brussels + - MYSQL_DATABASE=simplenotes + - MYSQL_USER=simplenotes + # .env: + # - MYSQL_ROOT_PASSWORD + # - MYSQL_PASSWORD volumes: - notes-db-volume:/var/lib/mysql + ports: + - 127.0.0.1:3306:3306 healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] timeout: 10s retries: 10 + simplenotes: + image: hubv/simplenotes + container_name: simplenotes + env_file: + - .env + environment: + - TZ=Europe/Brussels + - HOST=0.0.0.0 + - JDBCURL=jdbc:mariadb://db:3306/simplenotes + - DRIVERCLASSNAME=org.mariadb.jdbc.Driver + - USERNAME=simplenotes + # .env: + # - JWT_SECRET + # - PASSWORD + ports: + - 127.0.0.1:8080:8080 + depends_on: + db: + condition: service_healthy + + volumes: notes-db-volume: diff --git a/domain/pom.xml b/domain/pom.xml new file mode 100644 index 0000000..739e8b4 --- /dev/null +++ b/domain/pom.xml @@ -0,0 +1,57 @@ + + + parent + be.simplenotes + 1.0-SNAPSHOT + + 4.0.0 + + domain + + + + be.simplenotes + shared + 1.0-SNAPSHOT + + + be.simplenotes + shared + 1.0-SNAPSHOT + test-jar + test + + + + io.konform + konform-jvm + 0.2.0 + + + org.mindrot + jbcrypt + 0.4 + + + com.auth0 + java-jwt + 3.10.3 + + + com.vladsch.flexmark + flexmark + 0.62.2 + + + org.yaml + snakeyaml + 1.26 + + + com.googlecode.owasp-java-html-sanitizer + owasp-java-html-sanitizer + 20200615.1 + + + + diff --git a/domain/src/main/kotlin/DomainModule.kt b/domain/src/main/kotlin/DomainModule.kt new file mode 100644 index 0000000..ca5e239 --- /dev/null +++ b/domain/src/main/kotlin/DomainModule.kt @@ -0,0 +1,26 @@ +package be.simplenotes.domain + +import be.simplenotes.domain.security.BcryptPasswordHash +import be.simplenotes.domain.security.JwtPayloadExtractor +import be.simplenotes.domain.security.PasswordHash +import be.simplenotes.domain.security.SimpleJwt +import be.simplenotes.domain.usecases.NoteService +import be.simplenotes.domain.usecases.UserService +import be.simplenotes.domain.usecases.login.LoginUseCase +import be.simplenotes.domain.usecases.login.LoginUseCaseImpl +import be.simplenotes.domain.usecases.markdown.MarkdownConverter +import be.simplenotes.domain.usecases.markdown.MarkdownConverterImpl +import be.simplenotes.domain.usecases.register.RegisterUseCase +import be.simplenotes.domain.usecases.register.RegisterUseCaseImpl +import org.koin.dsl.module + +val domainModule = module { + single { LoginUseCaseImpl(get(), get(), get()) } + single { RegisterUseCaseImpl(get(), get()) } + single { UserService(get(), get()) } + single { BcryptPasswordHash() } + single { SimpleJwt(get()) } + single { JwtPayloadExtractor(get()) } + single { NoteService(get(), get()) } + single { MarkdownConverterImpl() } +} diff --git a/domain/src/main/kotlin/model/Note.kt b/domain/src/main/kotlin/model/Note.kt new file mode 100644 index 0000000..774278e --- /dev/null +++ b/domain/src/main/kotlin/model/Note.kt @@ -0,0 +1,30 @@ +package be.simplenotes.domain.model + +import java.time.LocalDateTime +import java.util.* + +data class NoteMetadata( + val title: String, + val tags: List, +) + +data class PersistedNoteMetadata( + val title: String, + val tags: List, + val updatedAt: LocalDateTime, + val uuid: UUID, +) + +data class Note( + val meta: NoteMetadata, + val markdown: String, + val html: String, +) + +data class PersistedNote( + val meta: NoteMetadata, + val markdown: String, + val html: String, + val updatedAt: LocalDateTime, + val uuid: UUID, +) diff --git a/domain/src/main/kotlin/model/User.kt b/domain/src/main/kotlin/model/User.kt new file mode 100644 index 0000000..ce87914 --- /dev/null +++ b/domain/src/main/kotlin/model/User.kt @@ -0,0 +1,4 @@ +package be.simplenotes.domain.model + +data class User(val username: String, val password: String) +data class PersistedUser(val username: String, val password: String, val id: Int) diff --git a/domain/src/main/kotlin/security/HtmlSanitizer.kt b/domain/src/main/kotlin/security/HtmlSanitizer.kt new file mode 100644 index 0000000..43a58ae --- /dev/null +++ b/domain/src/main/kotlin/security/HtmlSanitizer.kt @@ -0,0 +1,18 @@ +package be.simplenotes.domain.security + +import org.owasp.html.HtmlPolicyBuilder + +object HtmlSanitizer { + private val htmlPolicy = HtmlPolicyBuilder() + .allowElements("a") + .allowCommonBlockElements() + .allowCommonInlineFormattingElements() + .allowElements("pre") + .allowAttributes("class").onElements("code") + .allowUrlProtocols("http", "https") + .allowAttributes("href").onElements("a") + .requireRelNofollowOnLinks() + .toFactory()!! + + fun sanitize(unsafeHtml: String) = htmlPolicy.sanitize(unsafeHtml)!! +} diff --git a/domain/src/main/kotlin/security/JwtPayload.kt b/domain/src/main/kotlin/security/JwtPayload.kt new file mode 100644 index 0000000..63f89c2 --- /dev/null +++ b/domain/src/main/kotlin/security/JwtPayload.kt @@ -0,0 +1,21 @@ +package be.simplenotes.domain.security + +import be.simplenotes.domain.model.PersistedUser +import com.auth0.jwt.exceptions.JWTVerificationException + +data class JwtPayload(val userId: Int, val username: String) { + constructor(user: PersistedUser) : this(user.id, user.username) +} + +class JwtPayloadExtractor(private val jwt: SimpleJwt) { + operator fun invoke(token: String): JwtPayload? = try { + val decodedJWT = jwt.verifier.verify(token) + val id = decodedJWT.getClaim("id").asInt() ?: null + val username = decodedJWT.getClaim("username").asString() ?: null + id?.let { username?.let { JwtPayload(id, username) } } + } catch (e: JWTVerificationException) { + null + } catch (e: IllegalArgumentException) { + null + } +} diff --git a/src/features/PasswordHash.kt b/domain/src/main/kotlin/security/PasswordHash.kt similarity index 59% rename from src/features/PasswordHash.kt rename to domain/src/main/kotlin/security/PasswordHash.kt index d56668b..13650b5 100644 --- a/src/features/PasswordHash.kt +++ b/domain/src/main/kotlin/security/PasswordHash.kt @@ -1,13 +1,14 @@ -package be.vandewalleh.features +package be.simplenotes.domain.security import org.mindrot.jbcrypt.BCrypt -interface PasswordHash { +internal interface PasswordHash { fun crypt(password: String): String fun verify(password: String, hashedPassword: String): Boolean } -class BcryptPasswordHash : PasswordHash { - override fun crypt(password: String) = BCrypt.hashpw(password, BCrypt.gensalt())!! +internal class BcryptPasswordHash(test: Boolean = false) : PasswordHash { + private val rounds = if (test) 4 else 10 + override fun crypt(password: String) = BCrypt.hashpw(password, BCrypt.gensalt(rounds))!! override fun verify(password: String, hashedPassword: String) = BCrypt.checkpw(password, hashedPassword) } diff --git a/domain/src/main/kotlin/security/SimpleJwt.kt b/domain/src/main/kotlin/security/SimpleJwt.kt new file mode 100644 index 0000000..d0f4b21 --- /dev/null +++ b/domain/src/main/kotlin/security/SimpleJwt.kt @@ -0,0 +1,22 @@ +package be.simplenotes.domain.security + +import be.simplenotes.shared.config.JwtConfig +import com.auth0.jwt.JWT +import com.auth0.jwt.JWTVerifier +import com.auth0.jwt.algorithms.Algorithm +import java.util.* +import java.util.concurrent.TimeUnit + +class SimpleJwt(jwtConfig: JwtConfig) { + private val validityInMs = TimeUnit.MILLISECONDS.convert(jwtConfig.validity, jwtConfig.timeUnit) + private val algorithm = Algorithm.HMAC256(jwtConfig.secret) + + val verifier: JWTVerifier = JWT.require(algorithm).build() + fun sign(jwtPayload: JwtPayload): String = JWT.create() + .withClaim("id", jwtPayload.userId) + .withClaim("username", jwtPayload.username) + .withExpiresAt(getExpiration()) + .sign(algorithm) + + private fun getExpiration() = Date(System.currentTimeMillis() + validityInMs) +} diff --git a/domain/src/main/kotlin/usecases/NoteService.kt b/domain/src/main/kotlin/usecases/NoteService.kt new file mode 100644 index 0000000..684a323 --- /dev/null +++ b/domain/src/main/kotlin/usecases/NoteService.kt @@ -0,0 +1,45 @@ +package be.simplenotes.domain.usecases + +import arrow.core.Either +import be.simplenotes.domain.model.Note +import be.simplenotes.domain.model.PersistedNote +import be.simplenotes.domain.model.PersistedNoteMetadata +import be.simplenotes.domain.security.HtmlSanitizer +import be.simplenotes.domain.usecases.markdown.MarkdownConverter +import be.simplenotes.domain.usecases.markdown.MarkdownParsingError +import be.simplenotes.domain.usecases.repositories.NoteRepository +import java.util.* + +class NoteService( + private val markdownConverter: MarkdownConverter, + private val noteRepository: NoteRepository, +) { + + fun create(userId: Int, markdownText: String): Either = + markdownConverter + .renderDocument(markdownText) + .map { it.copy(html = HtmlSanitizer.sanitize(it.html)) } + .map { Note(it.metadata, markdown = markdownText, html = it.html) } + .map { noteRepository.create(userId, it) } + + fun update(userId: Int, uuid: UUID, markdownText: String): Either = + markdownConverter + .renderDocument(markdownText) + .map { it.copy(html = HtmlSanitizer.sanitize(it.html)) } + .map { Note(it.metadata, markdown = markdownText, html = it.html) } + .map { noteRepository.update(userId, uuid, it) } + + fun paginatedNotes(userId: Int, page: Int, itemsPerPage: Int = 20): PaginatedNotes { + val count = noteRepository.count(userId) + val offset = (page - 1) * itemsPerPage + val numberOfPages = (count / itemsPerPage) + 1 + val notes = if (count == 0) emptyList() else noteRepository.findAll(userId, itemsPerPage, offset) + return PaginatedNotes(numberOfPages, notes) + } + + fun find(userId: Int, uuid: UUID) = noteRepository.find(userId, uuid) + fun delete(userId: Int, uuid: UUID) = noteRepository.delete(userId, uuid) +} + +data class PaginatedNotes(val pages: Int, val notes: List) + diff --git a/domain/src/main/kotlin/usecases/UserService.kt b/domain/src/main/kotlin/usecases/UserService.kt new file mode 100644 index 0000000..d9cf340 --- /dev/null +++ b/domain/src/main/kotlin/usecases/UserService.kt @@ -0,0 +1,9 @@ +package be.simplenotes.domain.usecases + +import be.simplenotes.domain.usecases.login.LoginUseCase +import be.simplenotes.domain.usecases.register.RegisterUseCase + +class UserService( + loginUseCase: LoginUseCase, + registerUseCase: RegisterUseCase +) : LoginUseCase by loginUseCase, RegisterUseCase by registerUseCase diff --git a/domain/src/main/kotlin/usecases/login/LoginUseCaseImpl.kt b/domain/src/main/kotlin/usecases/login/LoginUseCaseImpl.kt new file mode 100644 index 0000000..a876d5f --- /dev/null +++ b/domain/src/main/kotlin/usecases/login/LoginUseCaseImpl.kt @@ -0,0 +1,25 @@ +package be.simplenotes.domain.usecases.login + +import arrow.core.Either +import arrow.core.extensions.fx +import arrow.core.filterOrElse +import arrow.core.rightIfNotNull +import be.simplenotes.domain.security.JwtPayload +import be.simplenotes.domain.security.PasswordHash +import be.simplenotes.domain.security.SimpleJwt +import be.simplenotes.domain.usecases.repositories.UserRepository +import be.simplenotes.domain.validation.UserValidations + +internal class LoginUseCaseImpl( + private val userRepository: UserRepository, + private val passwordHash: PasswordHash, + private val jwt: SimpleJwt +) : LoginUseCase { + override fun login(form: LoginForm) = Either.fx { + val user = !UserValidations.validateLogin(form) + !userRepository.find(user.username) + .rightIfNotNull { Unregistered } + .filterOrElse({ passwordHash.verify(form.password!!, it.password) }, { WrongPassword }) + .map { jwt.sign(JwtPayload(it)) } + } +} diff --git a/domain/src/main/kotlin/usecases/login/LoginUsecase.kt b/domain/src/main/kotlin/usecases/login/LoginUsecase.kt new file mode 100644 index 0000000..347ea89 --- /dev/null +++ b/domain/src/main/kotlin/usecases/login/LoginUsecase.kt @@ -0,0 +1,17 @@ +package be.simplenotes.domain.usecases.login + +import arrow.core.Either +import io.konform.validation.ValidationErrors + +sealed class LoginError +object Unregistered : LoginError() +object WrongPassword : LoginError() +class InvalidLoginForm(val validationErrors: ValidationErrors) : LoginError() + +typealias Token = String + +data class LoginForm(val username: String?, val password: String?) + +interface LoginUseCase { + fun login(form: LoginForm): Either +} diff --git a/domain/src/main/kotlin/usecases/markdown/MarkdownConverter.kt b/domain/src/main/kotlin/usecases/markdown/MarkdownConverter.kt new file mode 100644 index 0000000..bce7374 --- /dev/null +++ b/domain/src/main/kotlin/usecases/markdown/MarkdownConverter.kt @@ -0,0 +1,74 @@ +package be.simplenotes.domain.usecases.markdown + +import arrow.core.Either +import arrow.core.Try +import arrow.core.extensions.fx +import arrow.core.left +import arrow.core.right +import be.simplenotes.domain.model.NoteMetadata +import be.simplenotes.domain.validation.NoteValidations +import com.auth0.jwt.JWT +import com.vladsch.flexmark.html.HtmlRenderer +import com.vladsch.flexmark.parser.Parser +import io.konform.validation.ValidationErrors +import org.yaml.snakeyaml.Yaml +import org.yaml.snakeyaml.parser.ParserException +import org.yaml.snakeyaml.scanner.ScannerException + +sealed class MarkdownParsingError +object MissingMeta : MarkdownParsingError() +object InvalidMeta : MarkdownParsingError() +class ValidationError(val validationErrors: ValidationErrors) : MarkdownParsingError() + +data class Document(val metadata: NoteMetadata, val html: String) + +typealias MetaMdPair = Pair + +interface MarkdownConverter { + fun renderDocument(input: String): Either +} + +internal class MarkdownConverterImpl : MarkdownConverter { + private val yamlBoundPattern = "-{3}".toRegex() + private fun splitMetaFromDocument(input: String): Either { + val split = input.split(yamlBoundPattern, 3) + if (split.size < 3) return MissingMeta.left() + return (split[1].trim() to split[2].trim()).right() + } + + private val yaml = Yaml() + private fun parseMeta(input: String): Either { + val load: Map = try { + yaml.load(input) + } catch (e: ParserException) { + return InvalidMeta.left() + } catch (e: ScannerException) { + return InvalidMeta.left() + } + + val title = when (val titleNode = load["title"]) { + is String, is Number -> titleNode.toString() + else -> return InvalidMeta.left() + } + + val tagsNode = load["tags"] + val tags = if (tagsNode !is List<*>) + emptyList() + else + tagsNode.map { it.toString() } + + return NoteMetadata(title, tags).right() + } + + private val parser = Parser.builder().build() + private val renderer = HtmlRenderer.builder().build() + private fun renderMarkdown(markdown: String) = parser.parse(markdown).run(renderer::render) + + override fun renderDocument(input: String) = Either.fx { + val (meta, md) = !splitMetaFromDocument(input) + val parsedMeta = !parseMeta(meta) + !NoteValidations.validateMetadata(parsedMeta).toEither { }.swap() + val html = renderMarkdown(md) + Document(parsedMeta, html) + } +} diff --git a/domain/src/main/kotlin/usecases/register/RegisterUseCaseImpl.kt b/domain/src/main/kotlin/usecases/register/RegisterUseCaseImpl.kt new file mode 100644 index 0000000..a54fae7 --- /dev/null +++ b/domain/src/main/kotlin/usecases/register/RegisterUseCaseImpl.kt @@ -0,0 +1,22 @@ +package be.simplenotes.domain.usecases.register + +import arrow.core.Either +import arrow.core.filterOrElse +import arrow.core.leftIfNull +import be.simplenotes.domain.model.PersistedUser +import be.simplenotes.domain.security.PasswordHash +import be.simplenotes.domain.usecases.repositories.UserRepository +import be.simplenotes.domain.validation.UserValidations + +internal class RegisterUseCaseImpl( + private val userRepository: UserRepository, + private val passwordHash: PasswordHash +) : RegisterUseCase { + override fun register(form: RegisterForm): Either { + return UserValidations.validateRegister(form) + .filterOrElse({ !userRepository.exists(it.username) }, { UserExists }) + .map { it.copy(password = passwordHash.crypt(it.password)) } + .map { userRepository.create(it) } + .leftIfNull { UserExists } + } +} diff --git a/domain/src/main/kotlin/usecases/register/RegisterUsecase.kt b/domain/src/main/kotlin/usecases/register/RegisterUsecase.kt new file mode 100644 index 0000000..041f167 --- /dev/null +++ b/domain/src/main/kotlin/usecases/register/RegisterUsecase.kt @@ -0,0 +1,16 @@ +package be.simplenotes.domain.usecases.register + +import arrow.core.Either +import be.simplenotes.domain.model.PersistedUser +import be.simplenotes.domain.usecases.login.LoginForm +import io.konform.validation.ValidationErrors + +sealed class RegisterError +object UserExists : RegisterError() +class InvalidRegisterForm(val validationErrors: ValidationErrors) : RegisterError() + +typealias RegisterForm = LoginForm + +interface RegisterUseCase { + fun register(form: RegisterForm): Either +} diff --git a/domain/src/main/kotlin/usecases/repositories/NoteRepository.kt b/domain/src/main/kotlin/usecases/repositories/NoteRepository.kt new file mode 100644 index 0000000..734b045 --- /dev/null +++ b/domain/src/main/kotlin/usecases/repositories/NoteRepository.kt @@ -0,0 +1,17 @@ +package be.simplenotes.domain.usecases.repositories + +import be.simplenotes.domain.model.Note +import be.simplenotes.domain.model.PersistedNote +import be.simplenotes.domain.model.PersistedNoteMetadata +import java.util.* + +interface NoteRepository { + fun findAll(userId: Int, limit: Int = 20, offset: Int = 0): List + fun exists(userId: Int, uuid: UUID): Boolean + fun create(userId: Int, note: Note): PersistedNote + fun find(userId: Int, uuid: UUID): PersistedNote? + fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? + fun delete(userId: Int, uuid: UUID): Boolean + fun getTags(userId: Int): List + fun count(userId: Int): Int +} diff --git a/domain/src/main/kotlin/usecases/repositories/UserRepository.kt b/domain/src/main/kotlin/usecases/repositories/UserRepository.kt new file mode 100644 index 0000000..2952e6f --- /dev/null +++ b/domain/src/main/kotlin/usecases/repositories/UserRepository.kt @@ -0,0 +1,13 @@ +package be.simplenotes.domain.usecases.repositories + +import be.simplenotes.domain.model.PersistedUser +import be.simplenotes.domain.model.User + +interface UserRepository { + fun create(user: User): PersistedUser? + fun find(username: String): PersistedUser? + fun find(id: Int): PersistedUser? + fun exists(username: String): Boolean + fun exists(id: Int): Boolean + fun delete(id: Int): Boolean +} diff --git a/domain/src/main/kotlin/validation/NoteValidations.kt b/domain/src/main/kotlin/validation/NoteValidations.kt new file mode 100644 index 0000000..2bddce9 --- /dev/null +++ b/domain/src/main/kotlin/validation/NoteValidations.kt @@ -0,0 +1,36 @@ +package be.simplenotes.domain.validation + +import arrow.core.* +import be.simplenotes.domain.model.NoteMetadata +import be.simplenotes.domain.model.User +import be.simplenotes.domain.usecases.login.InvalidLoginForm +import be.simplenotes.domain.usecases.markdown.ValidationError +import io.konform.validation.Validation +import io.konform.validation.jsonschema.maxItems +import io.konform.validation.jsonschema.maxLength +import io.konform.validation.jsonschema.uniqueItems + +internal object NoteValidations { + private val metaValidator = Validation { + NoteMetadata::title required { + addConstraint("must not be blank") { it.isNotBlank() } + maxLength(50) + } + NoteMetadata::tags required { + maxItems(5) + uniqueItems(true) + } + + NoteMetadata::tags onEach { + maxLength(15) + addConstraint("must not be blank") { it.isNotBlank() } + } + } + + fun validateMetadata(meta: NoteMetadata): Option { + val errors = metaValidator.validate(meta).errors + return if (errors.isEmpty()) none() + else return ValidationError(errors).some() + } + +} diff --git a/domain/src/main/kotlin/validation/UserValidations.kt b/domain/src/main/kotlin/validation/UserValidations.kt new file mode 100644 index 0000000..a6a586d --- /dev/null +++ b/domain/src/main/kotlin/validation/UserValidations.kt @@ -0,0 +1,38 @@ +package be.simplenotes.domain.validation + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import be.simplenotes.domain.model.User +import be.simplenotes.domain.usecases.login.InvalidLoginForm +import be.simplenotes.domain.usecases.login.LoginForm +import be.simplenotes.domain.usecases.register.InvalidRegisterForm +import be.simplenotes.domain.usecases.register.RegisterForm +import io.konform.validation.Validation +import io.konform.validation.jsonschema.maxLength +import io.konform.validation.jsonschema.minLength + +internal object UserValidations { + private val loginValidator = Validation { + LoginForm::username required { + minLength(3) + maxLength(50) + } + LoginForm::password required { + minLength(8) + maxLength(72) // jbcrypt limit, see https://security.stackexchange.com/a/39851 + } + } + + fun validateLogin(form: LoginForm): Either { + val errors = loginValidator.validate(form).errors + return if (errors.isEmpty()) User(form.username!!, form.password!!).right() + else return InvalidLoginForm(errors).left() + } + + fun validateRegister(form: RegisterForm): Either { + val errors = loginValidator.validate(form).errors + return if (errors.isEmpty()) User(form.username!!, form.password!!).right() + else return InvalidRegisterForm(errors).left() + } +} diff --git a/domain/src/test/kotlin/Empty.kt b/domain/src/test/kotlin/Empty.kt new file mode 100644 index 0000000..8c13eef --- /dev/null +++ b/domain/src/test/kotlin/Empty.kt @@ -0,0 +1,5 @@ +package be.simplenotes.domain + +/** + * Empty file @see [root-package-declaration](https://discuss.kotlinlang.org/t/root-package-declaration-to-reduce-folder-clutter/2247/4) + */ diff --git a/domain/src/test/kotlin/security/JwtPayloadExtractorTest.kt b/domain/src/test/kotlin/security/JwtPayloadExtractorTest.kt new file mode 100644 index 0000000..ad2c59f --- /dev/null +++ b/domain/src/test/kotlin/security/JwtPayloadExtractorTest.kt @@ -0,0 +1,50 @@ +package be.simplenotes.domain.security + +import be.simplenotes.domain.usecases.login.Token +import be.simplenotes.shared.config.JwtConfig +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import com.natpryce.hamkrest.absent +import com.natpryce.hamkrest.assertion.assertThat +import com.natpryce.hamkrest.equalTo +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.util.concurrent.TimeUnit +import java.util.stream.Stream + +internal class JwtPayloadExtractorTest { + private val jwtConfig = JwtConfig("a secret", 1, TimeUnit.HOURS) + private val simpleJwt = SimpleJwt(jwtConfig) + private val jwtPayloadExtractor = JwtPayloadExtractor(simpleJwt) + + private fun createToken(username: String? = null, id: Int? = null, secret: String = jwtConfig.secret): Token { + val algo = Algorithm.HMAC256(secret) + return JWT.create().apply { + username?.let { withClaim("username", it) } + id?.let { withClaim("id", it) } + }.sign(algo) + } + + @Suppress("Unused") + private fun invalidTokens() = Stream.of( + createToken(id = 1), + createToken(username = "user"), + createToken(), + createToken(username = "user", id = 1, secret = "not the correct secret"), + createToken(username = "user", id = 1) + "\"efesfsef", + "something that is not even a token" + ) + + @ParameterizedTest(name = "[{index}] token `{0}` should be invalid") + @MethodSource("invalidTokens") + fun `parse invalid tokens`(token: String) { + assertThat(jwtPayloadExtractor(token), absent()) + } + + @Test + fun `parse valid token`() { + val token = createToken(username = "someone", id = 1) + assertThat(jwtPayloadExtractor(token), equalTo(JwtPayload(1, "someone"))) + } +} diff --git a/domain/src/test/kotlin/usecases/login/LoginUseCaseImplTest.kt b/domain/src/test/kotlin/usecases/login/LoginUseCaseImplTest.kt new file mode 100644 index 0000000..6d2f8f6 --- /dev/null +++ b/domain/src/test/kotlin/usecases/login/LoginUseCaseImplTest.kt @@ -0,0 +1,64 @@ +package be.simplenotes.domain.usecases.login + +import be.simplenotes.domain.model.PersistedUser +import be.simplenotes.domain.security.BcryptPasswordHash +import be.simplenotes.domain.security.SimpleJwt +import be.simplenotes.domain.usecases.repositories.UserRepository +import be.simplenotes.shared.config.JwtConfig +import be.simplenotes.shared.testutils.assertions.isLeftOfType +import be.simplenotes.shared.testutils.assertions.isRight +import com.natpryce.hamkrest.assertion.assertThat +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.concurrent.TimeUnit + +internal class LoginUseCaseImplTest { + // region setup + private val mockUserRepository = mockk() + private val passwordHash = BcryptPasswordHash(test = true) + private val jwtConfig = JwtConfig("a secret", 1, TimeUnit.HOURS) + private val simpleJwt = SimpleJwt(jwtConfig) + private val loginUseCase = LoginUseCaseImpl(mockUserRepository, passwordHash, simpleJwt) + + @BeforeEach + fun resetMocks() { + clearMocks(mockUserRepository) + } + // endregion + + @Test + fun `Login should fail with invalid form`() { + val form = LoginForm("", "a") + assertThat(loginUseCase.login(form), isLeftOfType()) + verify { mockUserRepository wasNot called } + } + + @Test + fun `Login should fail with non existing user`() { + val form = LoginForm("someusername", "somepassword") + every { mockUserRepository.find(form.username!!) } returns null + assertThat(loginUseCase.login(form), isLeftOfType()) + } + + @Test + fun `Login should fail with wrong password`() { + val form = LoginForm("someusername", "wrongpassword") + + every { mockUserRepository.find(form.username!!) } returns + PersistedUser(form.username!!, passwordHash.crypt("right password"), 1) + + assertThat(loginUseCase.login(form), isLeftOfType()) + } + + @Test + fun `Login should succeed with existing user and correct password`() { + val loginForm = LoginForm("someusername", "somepassword") + + every { mockUserRepository.find(loginForm.username!!) } returns + PersistedUser(loginForm.username!!, passwordHash.crypt(loginForm.password!!), 1) + + val res = loginUseCase.login(loginForm) + assertThat(res, isRight()) + } +} diff --git a/domain/src/test/kotlin/usecases/register/RegisterUseCaseImplTest.kt b/domain/src/test/kotlin/usecases/register/RegisterUseCaseImplTest.kt new file mode 100644 index 0000000..4a616d4 --- /dev/null +++ b/domain/src/test/kotlin/usecases/register/RegisterUseCaseImplTest.kt @@ -0,0 +1,50 @@ +package be.simplenotes.domain.usecases.register + +import be.simplenotes.domain.model.PersistedUser +import be.simplenotes.domain.security.BcryptPasswordHash +import be.simplenotes.domain.usecases.repositories.UserRepository +import be.simplenotes.shared.testutils.assertions.isLeftOfType +import be.simplenotes.shared.testutils.assertions.isRight +import com.natpryce.hamkrest.assertion.assertThat +import com.natpryce.hamkrest.equalTo +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class RegisterUseCaseImplTest { + + // region setup + private val mockUserRepository = mockk() + private val passwordHash = BcryptPasswordHash(test = true) + private val registerUseCase = RegisterUseCaseImpl(mockUserRepository, passwordHash) + + @BeforeEach + fun resetMocks() { + clearMocks(mockUserRepository) + } + // endregion + + @Test + fun `register should fail with invalid form`() { + val form = RegisterForm("", "a".repeat(10)) + assertThat(registerUseCase.register(form), isLeftOfType()) + verify { mockUserRepository wasNot called } + } + + @Test + fun `Register should fail with existing username`() { + val form = RegisterForm("someuser", "somepassword") + every { mockUserRepository.exists(form.username!!) } returns true + assertThat(registerUseCase.register(form), isLeftOfType()) + } + + @Test + fun `Register should succeed with new user`() { + val form = RegisterForm("someuser", "somepassword") + every { mockUserRepository.exists(form.username!!) } returns false + every { mockUserRepository.create(any()) } returns PersistedUser(form.username!!, form.password!!, 1) + val res = registerUseCase.register(form) + assertThat(res, isRight()) + res.map { assertThat(it.username, equalTo(form.username)) } + } +} diff --git a/domain/src/test/kotlin/validation/UserValidationsTest.kt b/domain/src/test/kotlin/validation/UserValidationsTest.kt new file mode 100644 index 0000000..5fd2ea3 --- /dev/null +++ b/domain/src/test/kotlin/validation/UserValidationsTest.kt @@ -0,0 +1,77 @@ +package be.simplenotes.domain.validation + +import be.simplenotes.domain.usecases.login.InvalidLoginForm +import be.simplenotes.domain.usecases.login.LoginForm +import be.simplenotes.domain.usecases.register.RegisterForm +import be.simplenotes.shared.testutils.assertions.isLeftOfType +import be.simplenotes.shared.testutils.assertions.isRight +import com.natpryce.hamkrest.assertion.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +internal class UserValidationsTest { + + @Nested + inner class Login { + + @Suppress("Unused") + fun invalidLoginForms(): Stream = Stream.of( + LoginForm(username = null, password = null), + LoginForm(username = "", password = ""), + LoginForm(username = "a", password = "aaaa"), + LoginForm(username = "a".repeat(51), password = "a".repeat(8)), + LoginForm(username = "a".repeat(10), password = "a".repeat(7)) + ) + + @ParameterizedTest + @MethodSource("invalidLoginForms") + fun `validate invalid logins`(form: LoginForm) { + assertThat(UserValidations.validateLogin(form), isLeftOfType()) + } + + @Suppress("Unused") + fun validLoginForms(): Stream = Stream.of( + LoginForm(username = "a".repeat(50), password = "a".repeat(72)), + LoginForm(username = "a".repeat(3), password = "a".repeat(8)) + ) + + @ParameterizedTest + @MethodSource("validLoginForms") + fun `validate valid logins`(form: LoginForm) { + assertThat(UserValidations.validateLogin(form), isRight()) + } + } + + @Nested + inner class Register { + + @Suppress("Unused") + fun invalidRegisterForms(): Stream = Stream.of( + RegisterForm(username = null, password = null), + RegisterForm(username = "", password = ""), + RegisterForm(username = "a", password = "aaaa"), + RegisterForm(username = "a".repeat(51), password = "a".repeat(8)), + RegisterForm(username = "a".repeat(10), password = "a".repeat(7)) + ) + + @ParameterizedTest + @MethodSource("invalidRegisterForms") + fun `validate invalid register`(form: LoginForm) { + assertThat(UserValidations.validateLogin(form), isLeftOfType()) + } + + @Suppress("Unused") + fun validRegisterForms(): Stream = Stream.of( + RegisterForm(username = "a".repeat(50), password = "a".repeat(72)), + RegisterForm(username = "a".repeat(3), password = "a".repeat(8)) + ) + + @ParameterizedTest + @MethodSource("validRegisterForms") + fun `validate valid register`(form: LoginForm) { + assertThat(UserValidations.validateLogin(form), isRight()) + } + } +} diff --git a/mvnw b/mvnw deleted file mode 100755 index 961a825..0000000 --- a/mvnw +++ /dev/null @@ -1,286 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - wget "$jarUrl" -O "$wrapperJarPath" - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - curl -o "$wrapperJarPath" "$jarUrl" - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index 830073a..0000000 --- a/mvnw.cmd +++ /dev/null @@ -1,161 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" -FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% -) else ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" - echo Finished downloading %WRAPPER_JAR% -) -@REM End of extension - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/package.json b/package.json deleted file mode 100644 index 403ee4d..0000000 --- a/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "css", - "version": "1.0.0", - "scripts": { - "css": "postcss build css/styles.css --output resources/static/styles.css", - "doc": "aglio -i api-doc/api.apib -o resources/docs/index.html --theme-variables slate" - }, - "dependencies": { - "aglio": "^2.3.0", - "cssnano": "^4.1.10", - "postcss-cli": "^7.1.1", - "postcss-hash": "^2.0.0", - "tailwindcss": "^1.5.1" - } -} diff --git a/persistance/pom.xml b/persistance/pom.xml new file mode 100644 index 0000000..44af852 --- /dev/null +++ b/persistance/pom.xml @@ -0,0 +1,62 @@ + + + parent + be.simplenotes + 1.0-SNAPSHOT + + 4.0.0 + + persistance + + + + be.simplenotes + domain + 1.0-SNAPSHOT + + + be.simplenotes + shared + 1.0-SNAPSHOT + + + be.simplenotes + shared + 1.0-SNAPSHOT + test-jar + test + + + + org.mariadb.jdbc + mariadb-java-client + 2.6.1 + + + com.h2database + h2 + 1.4.200 + + + org.flywaydb + flyway-core + 6.5.2 + + + com.zaxxer + HikariCP + 3.4.5 + + + me.liuwj.ktorm + ktorm-core + 3.0.0 + + + me.liuwj.ktorm + ktorm-support-mysql + 3.0.0 + + + + diff --git a/persistance/src/main/kotlin/DbMigrationsImpl.kt b/persistance/src/main/kotlin/DbMigrationsImpl.kt new file mode 100644 index 0000000..0a5bc71 --- /dev/null +++ b/persistance/src/main/kotlin/DbMigrationsImpl.kt @@ -0,0 +1,13 @@ +package be.simplenotes.persistance + +import org.flywaydb.core.Flyway +import javax.sql.DataSource + +internal class DbMigrationsImpl(private val dataSource: DataSource) : DbMigrations { + override fun migrate() { + Flyway.configure() + .dataSource(dataSource) + .load() + .migrate() + } +} diff --git a/persistance/src/main/kotlin/PersistanceModule.kt b/persistance/src/main/kotlin/PersistanceModule.kt new file mode 100644 index 0000000..9e00de3 --- /dev/null +++ b/persistance/src/main/kotlin/PersistanceModule.kt @@ -0,0 +1,36 @@ +package be.simplenotes.persistance + +import be.simplenotes.domain.usecases.repositories.NoteRepository +import be.simplenotes.domain.usecases.repositories.UserRepository +import be.simplenotes.persistance.notes.NoteRepositoryImpl +import be.simplenotes.persistance.users.UserRepositoryImpl +import be.simplenotes.shared.config.DataSourceConfig +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import me.liuwj.ktorm.database.* +import org.koin.dsl.module +import javax.sql.DataSource + +interface DbMigrations { + fun migrate() +} + +private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource { + val hikariConfig = HikariConfig().also { + it.jdbcUrl = conf.jdbcUrl + it.driverClassName = conf.driverClassName + it.username = conf.username + it.password = conf.password + it.maximumPoolSize = conf.maximumPoolSize + it.connectionTimeout = conf.connectionTimeout + } + return HikariDataSource(hikariConfig) +} + +val persistanceModule = module { + single { UserRepositoryImpl(get()) } + single { NoteRepositoryImpl(get()) } + single { DbMigrationsImpl(get()) } + single { hikariDataSource(get()) } + single { Database.connect(get()) } +} diff --git a/persistance/src/main/kotlin/extensions/KtormExtensions.kt b/persistance/src/main/kotlin/extensions/KtormExtensions.kt new file mode 100644 index 0000000..525a1b3 --- /dev/null +++ b/persistance/src/main/kotlin/extensions/KtormExtensions.kt @@ -0,0 +1,25 @@ +package be.simplenotes.persistance.extensions + +import me.liuwj.ktorm.schema.* +import java.nio.ByteBuffer +import java.sql.PreparedStatement +import java.sql.ResultSet +import java.sql.Types +import java.util.* + +internal class UuidBinarySqlType : SqlType(Types.BINARY, typeName = "uuidBinary") { + override fun doGetResult(rs: ResultSet, index: Int): UUID? { + val value = rs.getBytes(index) ?: return null + return ByteBuffer.wrap(value).let { b -> UUID(b.long, b.long) } + } + + override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: UUID) { + val bytes = ByteBuffer.allocate(16) + .putLong(parameter.mostSignificantBits) + .putLong(parameter.leastSignificantBits) + .array() + ps.setBytes(index, bytes) + } +} + +internal fun BaseTable.uuidBinary(name: String) = registerColumn(name, UuidBinarySqlType()) diff --git a/persistance/src/main/kotlin/notes/NoteRepositoryImpl.kt b/persistance/src/main/kotlin/notes/NoteRepositoryImpl.kt new file mode 100644 index 0000000..7cd6be7 --- /dev/null +++ b/persistance/src/main/kotlin/notes/NoteRepositoryImpl.kt @@ -0,0 +1,123 @@ +package be.simplenotes.persistance.notes + +import be.simplenotes.domain.model.Note +import be.simplenotes.domain.model.PersistedNote +import be.simplenotes.domain.model.PersistedNoteMetadata +import be.simplenotes.domain.usecases.repositories.NoteRepository +import me.liuwj.ktorm.database.* +import me.liuwj.ktorm.dsl.* +import me.liuwj.ktorm.entity.* +import java.time.LocalDateTime +import java.util.* +import kotlin.collections.HashMap + +internal class NoteRepositoryImpl(private val db: Database) : NoteRepository { + + @Throws(IllegalArgumentException::class) + override fun findAll(userId: Int, limit: Int, offset: Int): List { + require(limit > 0) { "limit should be positive" } + require(offset >= 0) { "offset should not be negative" } + + val notes = db.notes + .filterColumns { listOf(it.uuid, it.title, it.updatedAt) } + .filter { it.userId eq userId } + .sortedByDescending { it.updatedAt } + .take(limit) + .drop(offset) + .toList() + + if (notes.isEmpty()) return emptyList() + + val uuids = notes.map { note -> note.uuid } + + val tagsByUuid = db.tags + .filterColumns { listOf(it.noteUuid, it.name) } + .filter { it.noteUuid inList uuids } + .groupByTo(HashMap(), { it.note.uuid }, { it.name }) + + return notes.map { note -> + val tags = tagsByUuid[note.uuid] ?: emptyList() + note.toPersistedMetadata(tags) + } + } + + override fun exists(userId: Int, uuid: UUID): Boolean { + return db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) } + } + + override fun create(userId: Int, note: Note): PersistedNote { + val uuid = UUID.randomUUID() + val entity = note.toEntity(uuid, userId).apply { + this.updatedAt = LocalDateTime.now() + } + db.useTransaction { + db.notes.add(entity) + db.batchInsert(Tags) { + note.meta.tags.forEach { tagName -> + item { + it.noteUuid to uuid + it.name to tagName + } + } + } + } + + return entity.toPersistedNote(note.meta.tags) + } + + @Suppress("UNCHECKED_CAST") + override fun find(userId: Int, uuid: UUID): PersistedNote? { + val note = db.notes + .filterColumns { it.columns - it.userId } + .filter { it.uuid eq uuid } + .find { it.userId eq userId } + ?: return null + + val tags = db.tags + .filter { it.noteUuid eq uuid } + .mapColumns { it.name } as List + + return note.toPersistedNote(tags) + } + + override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? { + db.useTransaction { + val currentNote = db.notes + .find { it.uuid eq uuid and (it.userId eq userId) } + ?: return null + + currentNote.title = note.meta.title + currentNote.markdown = note.markdown + currentNote.html = note.html + currentNote.updatedAt = LocalDateTime.now() + currentNote.flushChanges() + + // delete all tags + db.delete(Tags) { + it.noteUuid eq uuid + } + + // put new ones + note.meta.tags.forEach { tagName -> + db.insert(Tags) { + it.name to tagName + it.noteUuid to uuid + } + } + return currentNote.toPersistedNote(note.meta.tags) + } + } + + override fun delete(userId: Int, uuid: UUID): Boolean = db.useTransaction { + db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1 + } + + @Suppress("UNCHECKED_CAST") + override fun getTags(userId: Int): List { + return db.sequenceOf(Tags) + .filter { it.note.userId eq userId } + .mapColumns(isDistinct = true) { it.name } as List + } + + override fun count(userId: Int) = db.notes.count { it.userId eq userId } +} diff --git a/persistance/src/main/kotlin/notes/Notes.kt b/persistance/src/main/kotlin/notes/Notes.kt new file mode 100644 index 0000000..89da92b --- /dev/null +++ b/persistance/src/main/kotlin/notes/Notes.kt @@ -0,0 +1,58 @@ +package be.simplenotes.persistance.notes + +import be.simplenotes.domain.model.Note +import be.simplenotes.domain.model.NoteMetadata +import be.simplenotes.domain.model.PersistedNote +import be.simplenotes.domain.model.PersistedNoteMetadata +import be.simplenotes.persistance.extensions.uuidBinary +import be.simplenotes.persistance.users.UserEntity +import be.simplenotes.persistance.users.Users +import me.liuwj.ktorm.database.* +import me.liuwj.ktorm.entity.* +import me.liuwj.ktorm.schema.* +import java.time.Instant +import java.time.LocalDateTime +import java.util.* + +internal open class Notes(alias: String?) : Table("Notes", alias) { + companion object : Notes(null) + + override fun aliased(alias: String) = Notes(alias) + + val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid } + val title = varchar("title").bindTo { it.title } + val markdown = text("markdown").bindTo { it.markdown } + val html = text("html").bindTo { it.html } + val userId = int("user_id").references(Users) { it.user } + val updatedAt = datetime("updated_at").bindTo { it.updatedAt } + val user get() = userId.referenceTable as Users +} + +internal val Database.notes get() = this.sequenceOf(Notes, withReferences = false) + +internal interface NoteEntity : Entity { + companion object : Entity.Factory() + + var uuid: UUID + var title: String + var markdown: String + var html: String + var updatedAt: LocalDateTime + + var user: UserEntity +} + +internal fun NoteEntity.toPersistedMetadata(tags: List) = PersistedNoteMetadata(title, tags, updatedAt, uuid) + +internal fun NoteEntity.toPersistedNote(tags: List) = PersistedNote(NoteMetadata(title, tags), markdown, html, updatedAt, uuid) + +internal fun Note.toEntity(uuid: UUID, userId: Int): NoteEntity { + val note = this + return NoteEntity { + this.title = note.meta.title + this.markdown = note.markdown + this.html = note.html + this.uuid = uuid + this.user["id"] = userId + } +} diff --git a/persistance/src/main/kotlin/notes/Tags.kt b/persistance/src/main/kotlin/notes/Tags.kt new file mode 100644 index 0000000..476fc9d --- /dev/null +++ b/persistance/src/main/kotlin/notes/Tags.kt @@ -0,0 +1,27 @@ +package be.simplenotes.persistance.notes + +import be.simplenotes.persistance.extensions.uuidBinary +import me.liuwj.ktorm.database.* +import me.liuwj.ktorm.entity.* +import me.liuwj.ktorm.schema.* + +internal open class Tags(alias: String?) : Table("Tags", alias) { + companion object : Tags(null) + + override fun aliased(alias: String) = Tags(alias) + + val id = int("id").primaryKey().bindTo { it.id } + val name = varchar("name").bindTo { it.name } + val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note } + val note get() = noteUuid.referenceTable as Notes +} + +internal val Database.tags get() = this.sequenceOf(Tags, withReferences = false) + +internal interface TagEntity : Entity { + companion object : Entity.Factory() + + val id: Int + var name: String + var note: NoteEntity +} diff --git a/persistance/src/main/kotlin/users/UserRepositoryImpl.kt b/persistance/src/main/kotlin/users/UserRepositoryImpl.kt new file mode 100644 index 0000000..f9d128b --- /dev/null +++ b/persistance/src/main/kotlin/users/UserRepositoryImpl.kt @@ -0,0 +1,26 @@ +package be.simplenotes.persistance.users + +import be.simplenotes.domain.model.PersistedUser +import be.simplenotes.domain.model.User +import be.simplenotes.domain.usecases.repositories.UserRepository +import me.liuwj.ktorm.database.* +import me.liuwj.ktorm.dsl.* +import me.liuwj.ktorm.entity.* +import java.sql.SQLIntegrityConstraintViolationException + +internal class UserRepositoryImpl(private val db: Database) : UserRepository { + override fun create(user: User): PersistedUser? { + return try { + db.useTransaction { db.users.add(user.toEntity()) } + find(user.username) + } catch (e: SQLIntegrityConstraintViolationException) { + null + } + } + + override fun find(username: String) = db.users.find { it.username eq username }?.toPersistedUser() + override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser() + override fun exists(username: String) = db.users.any { it.username eq username } + override fun exists(id: Int) = db.users.any { it.id eq id } + override fun delete(id: Int) = db.useTransaction { db.users.find { it.id eq id }?.delete() == 1 } +} diff --git a/persistance/src/main/kotlin/users/Users.kt b/persistance/src/main/kotlin/users/Users.kt new file mode 100644 index 0000000..20bd1af --- /dev/null +++ b/persistance/src/main/kotlin/users/Users.kt @@ -0,0 +1,45 @@ +package be.simplenotes.persistance.users + +import be.simplenotes.domain.model.PersistedUser +import be.simplenotes.domain.model.User +import me.liuwj.ktorm.database.* +import me.liuwj.ktorm.entity.* +import me.liuwj.ktorm.schema.* + +internal open class Users(alias: String?) : Table("Users", alias) { + companion object : Users(null) + + override fun aliased(alias: String) = Users(alias) + + val id = int("id").primaryKey().bindTo { it.id } + val username = varchar("username").bindTo { it.username } + val password = varchar("password").bindTo { it.password } +} + +internal interface UserEntity : Entity { + companion object : Entity.Factory() + + val id: Int + var username: String + var password: String +} + +internal fun UserEntity.toPersistedUser() = PersistedUser(username, password, id) + +internal fun PersistedUser.toEntity(): UserEntity { + val user = this + return UserEntity { + this.username = user.username + this.password = user.password + }.apply { this["id"] = user.id } +} + +internal fun User.toEntity(): UserEntity { + val user = this + return UserEntity { + this.username = user.username + this.password = user.password + } +} + +internal val Database.users get() = this.sequenceOf(Users, withReferences = false) diff --git a/resources/db/migration/V1__Create_tables.sql b/persistance/src/main/resources/db/migration/V1__Create_tables.sql similarity index 82% rename from resources/db/migration/V1__Create_tables.sql rename to persistance/src/main/resources/db/migration/V1__Create_tables.sql index a395f25..5f60fd8 100644 --- a/resources/db/migration/V1__Create_tables.sql +++ b/persistance/src/main/resources/db/migration/V1__Create_tables.sql @@ -5,8 +5,7 @@ create table Users password varchar(255) not null, constraint username unique (username) -) character set 'utf8mb4' - collate 'utf8mb4_general_ci'; +); create table Notes ( @@ -19,8 +18,7 @@ create table Notes constraint Notes_fk_user foreign key (user_id) references Users (id) on delete cascade -) character set 'utf8mb4' - collate 'utf8mb4_general_ci'; +); create index user_id on Notes (user_id); @@ -30,7 +28,6 @@ create table Tags name varchar(50) not null, note_uuid binary(16) not null, constraint Tags_fk_note foreign key (note_uuid) references Notes (uuid) on delete cascade -) character set 'utf8mb4' - collate 'utf8mb4_general_ci'; +); create index note_uuid on Tags (note_uuid); diff --git a/resources/logback.xml b/persistance/src/main/resources/logback.xml similarity index 59% rename from resources/logback.xml rename to persistance/src/main/resources/logback.xml index ad8c3e6..78f1512 100644 --- a/resources/logback.xml +++ b/persistance/src/main/resources/logback.xml @@ -2,19 +2,15 @@ true - %cyan(%d{YYYY-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %magenta(%logger{36}) - %msg%n + %cyan(%d{YYYY-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %green(%logger{36}) - %msg%n - + + - - - - - diff --git a/persistance/src/test/kotlin/Empty.kt b/persistance/src/test/kotlin/Empty.kt new file mode 100644 index 0000000..b98b168 --- /dev/null +++ b/persistance/src/test/kotlin/Empty.kt @@ -0,0 +1 @@ +package be.simplenotes.persistance diff --git a/persistance/src/test/kotlin/notes/NoteRepositoryImplTest.kt b/persistance/src/test/kotlin/notes/NoteRepositoryImplTest.kt new file mode 100644 index 0000000..7c0a96c --- /dev/null +++ b/persistance/src/test/kotlin/notes/NoteRepositoryImplTest.kt @@ -0,0 +1,272 @@ +package be.simplenotes.persistance.notes + +import be.simplenotes.domain.model.* +import be.simplenotes.domain.usecases.repositories.NoteRepository +import be.simplenotes.domain.usecases.repositories.UserRepository +import be.simplenotes.persistance.DbMigrations +import be.simplenotes.persistance.persistanceModule +import be.simplenotes.shared.config.DataSourceConfig +import me.liuwj.ktorm.database.* +import me.liuwj.ktorm.dsl.* +import me.liuwj.ktorm.entity.* +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.* +import org.junit.jupiter.api.parallel.ResourceLock +import org.koin.dsl.koinApplication +import org.koin.dsl.module +import java.sql.SQLIntegrityConstraintViolationException +import java.util.* +import javax.sql.DataSource + +@ResourceLock("h2") +internal class NoteRepositoryImplTest { + private val testModule = module { + single { dataSourceConfig() } + } + + private val koinApp = koinApplication { + modules(persistanceModule, testModule) + } + + private fun dataSourceConfig() = DataSourceConfig( + jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;", + driverClassName = "org.h2.Driver", + username = "h2", + password = "", + maximumPoolSize = 2, + connectionTimeout = 3000 + ) + + private val koin = koinApp.koin + + @AfterAll + fun afterAll() = koinApp.close() + + private val migration = koin.get() + private val dataSource = koin.get() + private val noteRepo = koin.get() + private val userRepo = koin.get() + private val db = koin.get() + + private lateinit var user1: PersistedUser + private lateinit var user2: PersistedUser + + @BeforeEach + fun beforeEach() { + Flyway.configure() + .dataSource(dataSource) + .load() + .clean() + + migration.migrate() + + user1 = userRepo.create(User("1", "1"))!! + user2 = userRepo.create(User("2", "2"))!! + } + + private fun createNote( + userId: Int, + title: String, + tags: List = emptyList(), + md: String = "md", + html: String = "html", + ): PersistedNote = noteRepo.create(userId, Note(NoteMetadata(title, tags), md, html)) + + private fun PersistedNote.toPersistedMeta() = PersistedNoteMetadata(meta.title, meta.tags, updatedAt, uuid) + + @Nested + @DisplayName("create()") + inner class Create { + + @Test + fun `create note for non existing user`() { + val note = Note(NoteMetadata("title", emptyList()), "md", "html") + + assertThatThrownBy { + noteRepo.create(1000, note) + }.isInstanceOf(SQLIntegrityConstraintViolationException::class.java) + } + + @Test + fun `create note for existing user`() { + val note = Note(NoteMetadata("title", emptyList()), "md", "html") + + assertThat(noteRepo.create(user1.id, note)) + .isEqualToIgnoringGivenFields(note, "uuid", "updatedAt") + .hasNoNullFieldsOrProperties() + + assertThat(db.notes.toList()) + .hasSize(1) + .first() + .isEqualToIgnoringGivenFields(note, "uuid", "updatedAt") + } + } + + @Nested + @DisplayName("findAll()") + inner class FindAll { + + @Test + fun `find all notes`() { + val notes1 = listOf( + createNote(user1.id, "1", listOf("a", "b")), + createNote(user1.id, "2"), + createNote(user1.id, "3", listOf("c")) + ) + + val notes2 = listOf( + createNote(user2.id, "4") + ) + + assertThat(noteRepo.findAll(user1.id)) + .hasSize(3) + .usingElementComparatorIgnoringFields("updatedAt") + .containsExactlyInAnyOrderElementsOf( + notes1.map { it.toPersistedMeta() } + ) + + assertThat(noteRepo.findAll(user2.id)) + .hasSize(1) + .usingElementComparatorIgnoringFields("updatedAt") + .containsExactlyInAnyOrderElementsOf( + notes2.map { it.toPersistedMeta() } + ) + + assertThat(noteRepo.findAll(1000)).isEmpty() + } + + @Test + fun pagination() { + (50 downTo 1).forEach { + createNote(user1.id, "$it") + } + + assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 0)) + .hasSize(20) + .allMatch { it.title.toInt() in 1..20 } + + assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 20)) + .hasSize(20) + .allMatch { it.title.toInt() in 21..40 } + + assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 40)) + .hasSize(10) + .allMatch { it.title.toInt() in 41..50 } + } + + } + + @Nested + @DisplayName("find() | exists()") + inner class FindExists { + @Test + @Suppress("UNCHECKED_CAST") + fun `find an existing note`() { + createNote(user1.id, "1", listOf("a", "b")) + + val note = db.notes.find { it.title eq "1" }!! + .let { entity -> + val tags = db.tags.filter { it.noteUuid eq entity.uuid }.mapColumns { it.name } as List + entity.toPersistedNote(tags) + } + + assertThat(noteRepo.find(user1.id, note.uuid)) + .isEqualTo(note) + + assertThat(noteRepo.exists(user1.id, note.uuid)) + .isTrue + } + + @Test + fun `find an existing note from the wrong user`() { + val note = createNote(user1.id, "1", listOf("a", "b")) + assertThat(noteRepo.find(user2.id, note.uuid)).isNull() + assertThat(noteRepo.exists(user2.id, note.uuid)).isFalse + } + + @Test + fun `find a non existing note`() { + createNote(user1.id, "1", listOf("a", "b")) + val uuid = UUID.randomUUID() + assertThat(noteRepo.find(user1.id, uuid)).isNull() + assertThat(noteRepo.exists(user2.id, uuid)).isFalse + } + } + + @Nested + @DisplayName("delete()") + inner class Delete { + + @Test + fun `delete an existing note for a user should succeed and then fail`() { + val note = createNote(user1.id, "1", listOf("a", "b")) + assertThat(noteRepo.delete(user1.id, note.uuid)) + .isTrue + + assertThat(noteRepo.delete(user1.id, note.uuid)) + .isFalse + } + + @Test + fun `delete an existing note for the wrong user`() { + val note = createNote(user1.id, "1", listOf("a", "b")) + assertThat(noteRepo.delete(1000, note.uuid)) + .isFalse + } + } + + @Nested + @DisplayName("getTags()") + inner class Tags { + + @Test + fun getTags() { + val notes1 = listOf( + createNote(user1.id, "1", listOf("a", "b")), + createNote(user1.id, "2"), + createNote(user1.id, "3", listOf("c", "a")) + ) + + val notes2 = listOf( + createNote(user2.id, "4", listOf("a")) + ) + + val user1Tags = notes1.flatMap { it.meta.tags }.toSet() + assertThat(noteRepo.getTags(user1.id)) + .containsExactlyInAnyOrderElementsOf(user1Tags) + + val user2Tags = notes2.flatMap { it.meta.tags }.toSet() + assertThat(noteRepo.getTags(user2.id)) + .containsExactlyInAnyOrderElementsOf(user2Tags) + + assertThat(noteRepo.getTags(1000)) + .isEmpty() + } + } + + @Nested + @DisplayName("update()") + inner class Update { + + @Test + fun getTags() { + val note1 = createNote(user1.id, "1", listOf("a", "b")) + val newNote1 = Note(meta = note1.meta, markdown = "new", "new") + assertThat(noteRepo.update(user1.id, note1.uuid, newNote1)) + .isNotNull + + assertThat(noteRepo.find(user1.id, note1.uuid)) + .isEqualToComparingOnlyGivenFields(newNote1, "meta", "markdown", "html") + + val note2 = createNote(user1.id, "2") + val newNote2 = Note(meta = note1.meta.copy(tags = listOf("a")), markdown = "new", "new") + assertThat(noteRepo.update(user1.id, note2.uuid, newNote2)) + .isNotNull + + assertThat(noteRepo.find(user1.id, note2.uuid)) + .isEqualToComparingOnlyGivenFields(newNote2, "meta", "markdown", "html") + } + } +} diff --git a/persistance/src/test/kotlin/users/UserRepositoryImplTest.kt b/persistance/src/test/kotlin/users/UserRepositoryImplTest.kt new file mode 100644 index 0000000..3eff7f0 --- /dev/null +++ b/persistance/src/test/kotlin/users/UserRepositoryImplTest.kt @@ -0,0 +1,116 @@ +package be.simplenotes.persistance.users + +import be.simplenotes.domain.model.User +import be.simplenotes.domain.usecases.repositories.UserRepository +import be.simplenotes.persistance.DbMigrations +import be.simplenotes.persistance.persistanceModule +import be.simplenotes.shared.config.DataSourceConfig +import me.liuwj.ktorm.database.* +import me.liuwj.ktorm.dsl.* +import me.liuwj.ktorm.entity.* +import org.assertj.core.api.Assertions.assertThat +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.ResourceLock +import org.koin.dsl.koinApplication +import org.koin.dsl.module +import javax.sql.DataSource + +@ResourceLock("h2") +internal class UserRepositoryImplTest { + + // region setup + private val testModule = module { + single { dataSourceConfig() } + } + + private val koinApp = koinApplication { + modules(persistanceModule, testModule) + } + + private fun dataSourceConfig() = DataSourceConfig( + jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;", + driverClassName = "org.h2.Driver", + username = "h2", + password = "", + maximumPoolSize = 2, + connectionTimeout = 3000 + ) + + private val koin = koinApp.koin + + @AfterAll + fun afterAll() = koinApp.close() + + private val migration = koin.get() + private val dataSource = koin.get() + private val userRepo = koin.get() + private val db = koin.get() + + @BeforeEach + fun beforeEach() { + Flyway.configure() + .dataSource(dataSource) + .load() + .clean() + migration.migrate() + } + // endregion setup + + @Test + fun `insert user`() { + val user = User("username", "test") + assertThat(userRepo.create(user)).isNotNull + assertThat(db.users.find { it.username eq user.username }).isNotNull + assertThat(db.users.toList()).hasSize(1) + assertThat(userRepo.create(user)).isNull() + } + + @Nested + inner class Query { + + @Test + fun `query existing user`() { + val user = User("username", "test") + userRepo.create(user) + + val foundUserMaybe = userRepo.find(user.username) + assertThat(foundUserMaybe).isNotNull + val foundUser = foundUserMaybe!! + assertThat(foundUser).isEqualToIgnoringGivenFields(user, "id") + assertThat(userRepo.exists(user.username)).isTrue + assertThat(userRepo.find(foundUser.id)).isEqualTo(foundUser) + assertThat(userRepo.exists(foundUser.id)).isTrue + } + + @Test + fun `query non existing user`() { + assertThat(userRepo.find("I don't exist")).isNull() + assertThat(userRepo.find(1)).isNull() + assertThat(userRepo.exists(1)).isFalse + assertThat(userRepo.exists("I don't exist")).isFalse + } + } + + @Nested + inner class Delete { + + @Test + fun `delete existing user`() { + val user = User("username", "test") + userRepo.create(user) + + val foundUser = userRepo.find(user.username)!! + assertThat(userRepo.delete(foundUser.id)).isTrue + assertThat(db.users.toList()).isEmpty() + } + + @Test + fun `delete non existing user`() { + assertThat(userRepo.delete(1)).isFalse + } + } +} diff --git a/pom.xml b/pom.xml index e369239..b35b4a5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,235 +1,72 @@ - - + 4.0.0 - be.vandewalleh - api - 0.0.1 - api + + be.simplenotes + parent + 1.0-SNAPSHOT + + + persistance + app + domain + shared + + + pom + - - 1.3.2 - 1.3.70 - 1.2.1 - 5.6.2 - 3.0.0 - 2.6.0 - 7.0.0 - 6.3.3 - 3.10.2 - 0.4 - 1.2.2 + 14 + 1.4.0-rc + 5.6.2 official - UTF-8 true - 12 - be.vandewalleh.NotesApplicationKt - 14 + + UTF-8 ${java.version} ${java.version} + ${java.version} - - - jcenter - https://jcenter.bintray.com - - true - - - true - - - - ktor - https://kotlin.bintray.com/ktor - - true - - - true - - - - korlibs - https://dl.bintray.com/korlibs/korlibs - - true - - - true - - - - jitpack - https://jitpack.io - - true - - - true - - - + - - com.soywiz.korlibs.korte - korte-jvm - 1.10.14 - org.jetbrains.kotlin kotlin-stdlib-jdk8 - ${kotlin_version} + ${kotlin.version} - io.ktor - ktor-server-netty - ${ktor_version} + org.koin + koin-core + 2.1.6 ch.qos.logback logback-classic - ${logback_version} + 1.2.3 - io.ktor - ktor-server-core - ${ktor_version} - - - io.ktor - ktor-jackson - ${ktor_version} - - - io.ktor - ktor-auth-jwt - ${ktor_version} - - - io.ktor - ktor-client-core - ${ktor_version} - - - org.kodein.di - kodein-di-jvm - ${kodein_version} + io.arrow-kt + arrow-core + 0.10.5 + + org.junit.jupiter junit-jupiter - ${junit_version} + ${junit.version} test org.junit.jupiter junit-jupiter-params - ${junit_version} + ${junit.version} test - org.mariadb.jdbc - mariadb-java-client - ${mariadb_version} - - - me.liuwj.ktorm - ktorm-core - ${ktorm_version} - - - me.liuwj.ktorm - ktorm-support-mysql - ${ktorm_version} - - - me.liuwj.ktorm - ktorm-jackson - ${ktorm_version} - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.11.1 - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - 2.11.1 - - - com.github.hekeki - huckleberry - 0.0.2-beta - test - - - org.flywaydb - flyway-core - ${flyway_version} - - - org.mindrot - jbcrypt - ${jbcrypt_version} - - - com.zaxxer - HikariCP - 3.4.2 - - - com.sksamuel.hoplite - hoplite-yaml - ${hoplite_version} - - - am.ik.yavi - yavi - 0.4.0 - - - com.vladsch.flexmark - flexmark-all - 0.62.2 - - - com.googlecode.owasp-java-html-sanitizer - owasp-java-html-sanitizer - 20200615.1 - - - - - com.github.javafaker - javafaker - 1.0.2 - test - - - io.ktor - ktor-server-tests - ${ktor_version} - test - - - org.testcontainers - mariadb - 1.14.3 - test - - - org.amshove.kluent - kluent - 1.61 - test - - - org.skyscreamer - jsonassert - 1.5.0 + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} test @@ -238,45 +75,58 @@ 1.10.0 test + + org.koin + koin-test + 2.1.6 + test + + + com.natpryce + hamkrest + 1.7.0.3 + test + + + org.assertj + assertj-core + 3.16.1 + test + + + + - ${project.basedir}/src - ${project.basedir}/test - - - ${project.basedir}/resources - - + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - ${main.class} - - org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4 + + + org.apache.maven.surefire + surefire-junit-platform + 3.0.0-M4 + + org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - 14 - 14 - kotlin-maven-plugin org.jetbrains.kotlin - ${kotlin_version} + ${kotlin.version} compile + process-sources compile @@ -286,64 +136,59 @@ test-compile - - - ${project.basedir}/test - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.4 - - - package - - shade - - - true - - - ${main.class} - - - - - io.ktor:ktor-client-* - - - - - org.jetbrains.kotlin:kotlin-reflect - - ** - - - - org.mariadb.jdbc:mariadb-java-client - - ** - - - - *:* - - META-INF/maven/** - META-INF/proguard/** - META-INF/native-image/** - META-INF/*.kotlin_module - - - - + + + -Xno-param-assertions + -Xno-call-assertions + -Xno-receiver-assertions + + + + + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib-jdk7 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib-common + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + + + + + + jcenter + https://jcenter.bintray.com + + + arrow + https://dl.bintray.com/arrow-kt/arrow-kt/ + + + diff --git a/resources/application.yaml b/resources/application.yaml deleted file mode 100644 index e88bd76..0000000 --- a/resources/application.yaml +++ /dev/null @@ -1,24 +0,0 @@ -database: - host: ${MYSQL_HOST:-localhost} - port: ${MYSQL_PORT:-3306} - name: ${MYSQL_DATABASE:-notes} - username: ${MYSQL_USER:-notes} - password: ${MYSQL_PASSWORD:-notes} - -server: - host: ${HOST:-127.0.0.1} - port: ${PORT:-8081} - cors: ${CORS:-false} - -jwt: - auth: - secret: ${JWT_SECRET:-uiqzRNiMYwbObn/Ps5xTasYVeu/63ZuI+1oB98Ez+lY=} # Can be generated with `openssl rand -base64 32` - validity: 24 - unit: HOURS - refresh: - secret: ${JWT_REFRESH_SECRET=-wWchkx44YGig4Q5Z7b7+E/3ymGEGd6PS7UGedMul3bg=} # Can be generated with `openssl rand -base64 32` - validity: 15 - unit: DAYS - -cookies: - secure: ${SECURE_COOKIES:-false} diff --git a/resources/docs/.gitkeep b/resources/docs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/resources/templates/__base__.twig b/resources/templates/__base__.twig deleted file mode 100644 index 483efd7..0000000 --- a/resources/templates/__base__.twig +++ /dev/null @@ -1,16 +0,0 @@ -{% import "__macros__.html" as macros %} - - - - - - - - {{ title }} - SimpleNotes - {{ macros.stylesheet("styles.css") }} - - -{% include "components/navbar.html" %} -{% block content %}default content{% endblock %} - - diff --git a/resources/templates/__macros__.twig b/resources/templates/__macros__.twig deleted file mode 100644 index 60b3ad5..0000000 --- a/resources/templates/__macros__.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% macro stylesheet(path) %} -{% set path = styles(path) %} - - -{% endmacro %} diff --git a/resources/templates/_uuid.twig b/resources/templates/_uuid.twig deleted file mode 100644 index 69661c0..0000000 --- a/resources/templates/_uuid.twig +++ /dev/null @@ -1,8 +0,0 @@ -{% set title = note.title %} {% extends "__base__.html" %} {% block content %} - -
-

{{ note.title }}

-
{{ note.html | raw }}
-
- -{% endblock %} diff --git a/resources/templates/components/alert.twig b/resources/templates/components/alert.twig deleted file mode 100644 index 276790b..0000000 --- a/resources/templates/components/alert.twig +++ /dev/null @@ -1,6 +0,0 @@ -{% macro warning(title, warning) %} - -{% endmacro %} diff --git a/resources/templates/components/forms.twig b/resources/templates/components/forms.twig deleted file mode 100644 index 79782d5..0000000 --- a/resources/templates/components/forms.twig +++ /dev/null @@ -1,28 +0,0 @@ - -{% macro input(args) %} -
- - -
-{% endmacro %} - -{% macro submit(value) %} -
- -
-{% endmacro %} diff --git a/resources/templates/components/navbar.twig b/resources/templates/components/navbar.twig deleted file mode 100644 index 62577e8..0000000 --- a/resources/templates/components/navbar.twig +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/resources/templates/error.twig b/resources/templates/error.twig deleted file mode 100644 index d88ed67..0000000 --- a/resources/templates/error.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% set title = status %} -{% extends "__base__.html" %} - -{% block content %} -
-
-

Error

-
{{ status }}
-
-
-{% endblock %} diff --git a/resources/templates/index.twig b/resources/templates/index.twig deleted file mode 100644 index cb30400..0000000 --- a/resources/templates/index.twig +++ /dev/null @@ -1,12 +0,0 @@ -{% set title = "Notes" %} -{% extends "__base__.html" %} - -{% block content %} -
-
-

SimpleNotes

-
Welcome
-
TODO
-
-
-{% endblock %} diff --git a/resources/templates/list.twig b/resources/templates/list.twig deleted file mode 100644 index 7de756a..0000000 --- a/resources/templates/list.twig +++ /dev/null @@ -1,24 +0,0 @@ -{% set title = "Notes" %} -{% extends "__base__.html" %} -{% block content %} -
-
-

Notes

- New -
- - {% if notes.size() > 0 -%} - - {% else %} - No notes :c - {% endif %} -
-{% endblock %} diff --git a/resources/templates/login.twig b/resources/templates/login.twig deleted file mode 100644 index 1584498..0000000 --- a/resources/templates/login.twig +++ /dev/null @@ -1,40 +0,0 @@ -{% set title = "Sign In" %} -{% extends "__base__.html" %} - -{% block content %} -
-
-

Sign In

-
- - {%- if error %} - {% import "components/alert.html" as alerts %} - {{ alerts.warning("Error", error) }} - {% endif -%} - -
- - {% import "components/forms.html" as forms %} - {{ forms.input({ - "id": "username", "label": "Username", - "placeholder": "Your Username", "autocomplete": "username" - }) }} - {{ forms.input({ - "id": "password", "label": "Password", "type": "password", - "placeholder": "Your Password", "autocomplete": "current-password" - }) }} - {{ forms.submit("Sign In") }} - -
-
-
-

- Don't have an account? - Create an Account -

-
-
-
-{% endblock %} diff --git a/resources/templates/new.twig b/resources/templates/new.twig deleted file mode 100644 index 5aea2b8..0000000 --- a/resources/templates/new.twig +++ /dev/null @@ -1,43 +0,0 @@ -{% set title = "Notes" %} -{% extends "__base__.html" %} -{% block content %} - -
-

{{ method }}

- - {%- if error %} - {% import "components/alert.html" as alerts %} -
- {{ alerts.warning("Error", error) }} -
- {% endif -%} - -
- - -
- -
-
- -

{{ value }}

-
-{% endblock %} diff --git a/resources/templates/register.twig b/resources/templates/register.twig deleted file mode 100644 index 7565776..0000000 --- a/resources/templates/register.twig +++ /dev/null @@ -1,42 +0,0 @@ -{% set title = "Create an Account" %} -{% extends "__base__.html" %} - -{% block content %} -
-
-

Create an Account

-
- - {%- if error %} - {% import "components/alert.html" as alerts %} - {% for e in error %} - {{ alerts.warning("Error", e) }} - {% endfor %} - {% endif -%} - -
- - {% import "components/forms.html" as forms %} - {{ forms.input({ - "id": "username", "label": "Username", - "placeholder": "Your Username", "autocomplete": "username" - }) }} - {{ forms.input({ - "id": "password", "label": "Password", "type": "password", - "placeholder": "Your Password", "autocomplete": "current-password" - }) }} - {{ forms.submit("Sign In") }} - -
-
-
-

- Already have an account? - Sign In -

-
-
-
-{% endblock %} diff --git a/shared/pom.xml b/shared/pom.xml new file mode 100644 index 0000000..6ca6678 --- /dev/null +++ b/shared/pom.xml @@ -0,0 +1,27 @@ + + + parent + be.simplenotes + 1.0-SNAPSHOT + + 4.0.0 + + shared + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + test-jar + + + + + + + diff --git a/shared/src/main/kotlin/Config.kt b/shared/src/main/kotlin/Config.kt new file mode 100644 index 0000000..68fb222 --- /dev/null +++ b/shared/src/main/kotlin/Config.kt @@ -0,0 +1,31 @@ +package be.simplenotes.shared.config + +import java.util.concurrent.TimeUnit + +data class DataSourceConfig( + val jdbcUrl: String, + val driverClassName: String, + val username: String, + val password: String, + val maximumPoolSize: Int, + val connectionTimeout: Long, +) { + override fun toString(): String { + return "DataSourceConfig(jdbcUrl='$jdbcUrl', driverClassName='$driverClassName', username='$username', password='***', maximumPoolSize=$maximumPoolSize, connectionTimeout=$connectionTimeout)" + } +} + +data class JwtConfig( + val secret: String, + val validity: Long, + val timeUnit: TimeUnit, +) { + override fun toString(): String { + return "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)" + } +} + +data class ServerConfig( + val host: String, + val port: Int, +) diff --git a/shared/src/test/kotlin/Empty.kt b/shared/src/test/kotlin/Empty.kt new file mode 100644 index 0000000..db52774 --- /dev/null +++ b/shared/src/test/kotlin/Empty.kt @@ -0,0 +1 @@ +package be.simplenotes.shared diff --git a/shared/src/test/kotlin/testutils/assertions/ArrowAssertions.kt b/shared/src/test/kotlin/testutils/assertions/ArrowAssertions.kt new file mode 100644 index 0000000..241ba13 --- /dev/null +++ b/shared/src/test/kotlin/testutils/assertions/ArrowAssertions.kt @@ -0,0 +1,42 @@ +package be.simplenotes.shared.testutils.assertions + +import arrow.core.Either +import com.natpryce.hamkrest.MatchResult +import com.natpryce.hamkrest.Matcher + +fun isLeft() = object : Matcher> { + override val description: String + get() = "is Either.Left<>" + + override fun invoke(actual: Either<*, *>) = when { + actual.isLeft() -> MatchResult.Match + else -> MatchResult.Mismatch("is Either.Right<>") + } +} + +fun isRight() = object : Matcher> { + override val description: String + get() = "is Either.Right<>" + + override fun invoke(actual: Either<*, *>) = when (actual) { + is Either.Right -> MatchResult.Match + is Either.Left -> { + val valueA = actual.a + MatchResult.Mismatch("is Either.Left<${if (valueA == null) "Null" else valueA::class.simpleName}>") + } + } +} + +inline fun isLeftOfType() = object : Matcher> { + override val description: String + get() = "is Either.Left<${A::class.qualifiedName}>" + + override fun invoke(actual: Either<*, *>) = when (actual) { + is Either.Right -> MatchResult.Mismatch("was Either.Right<>") + is Either.Left -> { + val valueA = actual.a + if (valueA is A) MatchResult.Match + else MatchResult.Mismatch("was Left<${if (valueA == null) "Null" else valueA::class.simpleName}>") + } + } +} diff --git a/shared/src/test/resources/junit-platform.properties b/shared/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..a8416a7 --- /dev/null +++ b/shared/src/test/resources/junit-platform.properties @@ -0,0 +1,4 @@ +junit.jupiter.testinstance.lifecycle.default=per_class +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=same_thread +junit.jupiter.execution.parallel.mode.classes.default=concurrent diff --git a/src/Configuration.kt b/src/Configuration.kt deleted file mode 100644 index ec079dc..0000000 --- a/src/Configuration.kt +++ /dev/null @@ -1,23 +0,0 @@ -package be.vandewalleh - -import com.sksamuel.hoplite.Masked -import java.util.concurrent.TimeUnit - -data class Config(val database: DatabaseConfig, val server: ServerConfig, val jwt: JwtConfig, val cookies: Cookies) { - override fun toString(): String { - return """ - Config( - database=$database, - server=$server, - jwt=$jwt, - cookies=$cookies - ) - """.trimIndent() - } -} - -data class DatabaseConfig(val host: String, val port: Int, val name: String, val username: String, val password: Masked) -data class ServerConfig(val host: String, val port: Int, val cors: Boolean) -data class JwtConfig(val auth: Jwt, val refresh: Jwt) -data class Jwt(val validity: Long, val unit: TimeUnit, val secret: Masked) -data class Cookies(val secure: Boolean) diff --git a/src/Dependencies.kt b/src/Dependencies.kt deleted file mode 100644 index 0b74fe0..0000000 --- a/src/Dependencies.kt +++ /dev/null @@ -1,101 +0,0 @@ -package be.vandewalleh - -import be.vandewalleh.auth.AuthenticationModule -import be.vandewalleh.auth.SimpleJWT -import be.vandewalleh.controllers.api.ApiNoteController -import be.vandewalleh.controllers.api.ApiTagController -import be.vandewalleh.controllers.api.ApiUserController -import be.vandewalleh.controllers.web.BaseController -import be.vandewalleh.controllers.web.NoteController -import be.vandewalleh.controllers.web.UserController -import be.vandewalleh.extensions.ApplicationBuilder -import be.vandewalleh.extensions.RoutingBuilder -import be.vandewalleh.factories.* -import be.vandewalleh.features.* -import be.vandewalleh.markdown.Markdown -import be.vandewalleh.repositories.NoteRepository -import be.vandewalleh.repositories.UserRepository -import be.vandewalleh.routing.api.ApiDocRoutes -import be.vandewalleh.routing.api.ApiNoteRoutes -import be.vandewalleh.routing.api.ApiTagRoutes -import be.vandewalleh.routing.api.ApiUserRoutes -import be.vandewalleh.routing.web.BaseRoutes -import be.vandewalleh.routing.web.NoteRoutes -import be.vandewalleh.routing.web.StaticRoutes -import be.vandewalleh.routing.web.UserRoutes -import com.soywiz.korte.TemplateProvider -import com.soywiz.korte.Templates -import io.ktor.features.* -import org.kodein.di.* -import org.kodein.di.DI -import org.slf4j.LoggerFactory - -val mainModule = DI.Module("main") { - bind() from singleton { NoteRepository(instance()) } - bind() from singleton { UserRepository(instance(), instance()) } - - bind() from singleton { configurationFactory() } - - bind() from setBinding() - bind().inSet() with singleton { ErrorHandler(instance()) } - bind().inSet() with singleton { ContentNegotiationFeature() } - bind().inSet() with singleton { CorsFeature(instance().server.cors) } - bind().inSet() with singleton { AuthenticationModule(instance(tag = "auth")) } - bind().inSet() with singleton { MigrationHook(instance()) } - bind().inSet() with singleton { ShutdownDatabaseConnection(instance()) } - bind().inSet() with singleton { SecurityReport() } - bind().inSet() with singleton { CachingHeadersFeature() } - bind().inSet() with singleton { CompressionFeature() } - bind().inSet() with singleton { CallLoggingFeature() } - - bind() from setBinding() - bind().inSet() with singleton { ApiDocRoutes() } - bind().inSet() with singleton { ApiNoteRoutes(instance()) } - bind().inSet() with singleton { ApiTagRoutes(instance()) } - bind().inSet() with singleton { ApiUserRoutes(instance()) } - - // API - bind() from singleton { ApiNoteController(instance(), instance()) } - bind() from singleton { ApiTagController(instance()) } - bind() from singleton { - ApiUserController( - userRepository = instance(), - authJWT = instance(tag = "auth"), - refreshJWT = instance(tag = "refresh"), - passwordHash = instance() - ) - } - - // web - bind().inSet() with singleton { BaseRoutes(instance()) } - bind().inSet() with singleton { UserRoutes(instance()) } - bind().inSet() with singleton { NoteRoutes(instance()) } - bind().inSet() with singleton { StaticRoutes() } - - bind() from singleton { NoteController(instance(), instance(), instance()) } - bind() from singleton { BaseController(instance()) } - bind() from singleton { - UserController( - userRepository = instance(), - authJWT = instance(tag = "auth"), - templates = instance(), - passwordHash = instance(), - useSecureCookies = instance().cookies.secure - ) - } - - bind() with singleton { ResourceTemplateProvider() } - bind() with singleton { templatesFactory(instance()) } - - bind(tag = "auth") with singleton { simpleJwtFactory(instance().jwt.auth) } - bind(tag = "refresh") with singleton { simpleJwtFactory(instance().jwt.refresh) } - - bind() from singleton { LoggerFactory.getLogger("Application") } - bind() from singleton { dataSourceFactory(instance().database) } - bind() from singleton { databaseFactory(instance()) } - bind() from singleton { Migration(instance()) } - - bind() with singleton { BcryptPasswordHash() } - - bind() from singleton { Markdown() } -} diff --git a/src/NotesApplication.kt b/src/NotesApplication.kt deleted file mode 100644 index 0e109d8..0000000 --- a/src/NotesApplication.kt +++ /dev/null @@ -1,59 +0,0 @@ -package be.vandewalleh - -import be.vandewalleh.extensions.ApplicationBuilder -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.application.Application -import io.ktor.application.log -import io.ktor.routing.routing -import io.ktor.server.engine.* -import io.ktor.server.netty.Netty -import org.kodein.di.DI -import org.kodein.di.description -import org.kodein.di.instance -import org.slf4j.Logger -import java.util.concurrent.TimeUnit - -fun main() { - val di = DI { import(mainModule) } - val config by di.instance() - val logger by di.instance() - logger.info("Running application with configuration $config") - logger.debug("Kodein bindings\n${di.container.tree.bindings.description()}") - serve(di) -} - -fun serve(di: DI) { - val config by di.instance() - val logger by di.instance() - val env = applicationEngineEnvironment { - module { - module(di) - } - log = logger - connector { - host = config.server.host - port = config.server.port - } - } - with(embeddedServer(Netty, env)) { - addShutdownHook { stop(1, 5, TimeUnit.SECONDS) } - start(wait = true) - } -} - -fun Application.module(di: DI) { - val builders: Set by di.instance() - - builders.forEach { - it.builder(this) - } - - routing { - trace { application.log.trace(it.buildText()) } - } - - val routingBuilders: Set by di.instance() - routingBuilders.forEach { - routing(it.builder) - } -} diff --git a/src/auth/AuthenticationModule.kt b/src/auth/AuthenticationModule.kt deleted file mode 100644 index 24a7582..0000000 --- a/src/auth/AuthenticationModule.kt +++ /dev/null @@ -1,54 +0,0 @@ -package be.vandewalleh.auth - -import be.vandewalleh.extensions.ApplicationBuilder -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.auth.jwt.* -import io.ktor.http.* -import io.ktor.http.auth.* -import io.ktor.request.* -import io.ktor.response.* -import io.ktor.util.pipeline.* - -class AuthenticationModule(authJwt: SimpleJWT) : ApplicationBuilder({ - install(Authentication) { - jwt { - verifier(authJwt.verifier) - authHeader { call -> - val token = call.request.header(HttpHeaders.Authorization) - ?: call.request.cookies["Authorization"] - token?.let { - try { - parseAuthorizationHeader(it) - } catch (e: IllegalArgumentException) { - null - } - } - } - validate { - UserPrincipal( - id = it.payload.getClaim("id").asInt(), - username = it.payload.getClaim("username").asString() - ) - } - challenge { scheme, realm -> - authChallenge(scheme, realm) - } - } - } -}) - -private suspend fun PipelineContext<*, ApplicationCall>.authChallenge(scheme: String, realm: String) { - if (call.request.uri.startsWith("/api")) - call.respond( - UnauthorizedResponse( - HttpAuthHeader.Parameterized( - scheme, - mapOf(HttpAuthHeader.Parameters.Realm to realm) - ) - ) - ) - else { - call.respondRedirect("/login") - } -} diff --git a/src/auth/SimpleJWT.kt b/src/auth/SimpleJWT.kt deleted file mode 100644 index faca94e..0000000 --- a/src/auth/SimpleJWT.kt +++ /dev/null @@ -1,21 +0,0 @@ -package be.vandewalleh.auth - -import com.auth0.jwt.JWT -import com.auth0.jwt.JWTVerifier -import com.auth0.jwt.algorithms.Algorithm -import java.util.* -import java.util.concurrent.TimeUnit - -class SimpleJWT(secret: String, validity: Long, unit: TimeUnit) { - private val validityInMs = TimeUnit.MILLISECONDS.convert(validity, unit) - private val algorithm = Algorithm.HMAC256(secret) - - val verifier: JWTVerifier = JWT.require(algorithm).build() - fun sign(id: Int, username: String): String = JWT.create() - .withClaim("id", id) - .withClaim("username", username) - .withExpiresAt(getExpiration()) - .sign(algorithm) - - private fun getExpiration() = Date(System.currentTimeMillis() + validityInMs) -} diff --git a/src/auth/UserPrincipal.kt b/src/auth/UserPrincipal.kt deleted file mode 100644 index e85d863..0000000 --- a/src/auth/UserPrincipal.kt +++ /dev/null @@ -1,10 +0,0 @@ -package be.vandewalleh.auth - -import io.ktor.auth.* - -/** - * Represents a simple user's principal identified by it's [id] and [username] - * @property id - * @property username - */ -data class UserPrincipal(val id: Int, val username: String) : Principal diff --git a/src/auth/UsernamePasswordCredential.kt b/src/auth/UsernamePasswordCredential.kt deleted file mode 100644 index 297b189..0000000 --- a/src/auth/UsernamePasswordCredential.kt +++ /dev/null @@ -1,10 +0,0 @@ -package be.vandewalleh.auth - -import io.ktor.auth.* - -/** - * Represents a simple user [username] and [password] credential pair - * @property username - * @property password - */ -data class UsernamePasswordCredential(val username: String, val password: String) : Credential diff --git a/src/controllers/api/ApiNoteController.kt b/src/controllers/api/ApiNoteController.kt deleted file mode 100644 index f8079c9..0000000 --- a/src/controllers/api/ApiNoteController.kt +++ /dev/null @@ -1,78 +0,0 @@ -package be.vandewalleh.controllers.api - -import be.vandewalleh.entities.Note -import be.vandewalleh.extensions.authenticatedUser -import be.vandewalleh.features.ValidationException -import be.vandewalleh.markdown.Markdown -import be.vandewalleh.markdown.MarkdownDocument -import be.vandewalleh.repositories.NoteRepository -import be.vandewalleh.utils.Either.Error -import be.vandewalleh.utils.Either.Success -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.request.* -import io.ktor.response.* -import java.util.* - -class ApiNoteController(private val noteRepository: NoteRepository, private val md: Markdown) { - - suspend fun create(call: ApplicationCall) { - val userId = call.authenticatedUser().id - val txt = call.receiveText() - - val doc: MarkdownDocument - - when (val result = md.renderDocument(txt)) { - is Error -> return call.respond(HttpStatusCode.BadRequest, result.error) - is Success -> doc = result.value - } - - val note = Note { - this.title = doc.meta.title - this.tags = doc.meta.tags - this.markdown = txt - this.html = doc.html - } - - val createdNote = noteRepository.create(userId, note) - call.respond(HttpStatusCode.Created, createdNote) - } - - suspend fun getAll(call: ApplicationCall) { - val userId = call.authenticatedUser().id - val limit = call.parameters["limit"]?.toInt() ?: 20 // FIXME validate - val after = call.parameters["after"]?.let { UUID.fromString(it) } // FIXME validate - val notes = noteRepository.findAll(userId, limit, after) - call.respond(notes) - } - - suspend fun getOne(call: ApplicationCall) { - val userId = call.authenticatedUser().id - val noteUuid = call.noteUuid() - - val response = noteRepository.find(userId, noteUuid) ?: return call.response.status(HttpStatusCode.NotFound) - call.respond(response) - } - - suspend fun update(call: ApplicationCall) { - TODO("Not implemented") - } - - suspend fun delete(call: ApplicationCall) { - val userId = call.authenticatedUser().id - val noteUuid = call.noteUuid() - - val success = noteRepository.delete(userId, noteUuid) - if (success) call.response.status(HttpStatusCode.NoContent) - else call.response.status(HttpStatusCode.NotFound) - } -} - -private fun ApplicationCall.noteUuid(): UUID { - val uuid = parameters["uuid"] - return try { - UUID.fromString(uuid) - } catch (e: IllegalArgumentException) { - throw ValidationException("`$uuid` is not a valid UUID") - } -} diff --git a/src/controllers/api/ApiTagController.kt b/src/controllers/api/ApiTagController.kt deleted file mode 100644 index 8f3cbc4..0000000 --- a/src/controllers/api/ApiTagController.kt +++ /dev/null @@ -1,13 +0,0 @@ -package be.vandewalleh.controllers.api - -import be.vandewalleh.extensions.authenticatedUser -import be.vandewalleh.repositories.NoteRepository -import io.ktor.application.* -import io.ktor.response.* - -class ApiTagController(private val noteRepository: NoteRepository) { - - suspend fun getAll(call: ApplicationCall) { - call.respond(noteRepository.getTags(call.authenticatedUser().id)) - } -} diff --git a/src/controllers/api/ApiUserController.kt b/src/controllers/api/ApiUserController.kt deleted file mode 100644 index 454b187..0000000 --- a/src/controllers/api/ApiUserController.kt +++ /dev/null @@ -1,87 +0,0 @@ -package be.vandewalleh.controllers.api - -import be.vandewalleh.auth.SimpleJWT -import be.vandewalleh.auth.UsernamePasswordCredential -import be.vandewalleh.extensions.authenticatedUser -import be.vandewalleh.features.PasswordHash -import be.vandewalleh.repositories.UserRepository -import be.vandewalleh.validation.receiveValidated -import be.vandewalleh.validation.registerValidator -import com.auth0.jwt.exceptions.JWTVerificationException -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.request.* -import io.ktor.response.* - -class ApiUserController( - private val authJWT: SimpleJWT, - private val refreshJWT: SimpleJWT, - private val userRepository: UserRepository, - private val passwordHash: PasswordHash -) { - - suspend fun create(call: ApplicationCall) { - val user = call.receiveValidated(registerValidator) - - if (userRepository.exists(user.username)) - return call.response.status(HttpStatusCode.Conflict) - - userRepository.create(user.username, user.password) - ?: return call.response.status(HttpStatusCode.Conflict) - - call.response.status(HttpStatusCode.Created) - } - - suspend fun login(call: ApplicationCall) { - val credential = call.receive() - - val user = userRepository.find(credential.username) - ?: return call.response.status(HttpStatusCode.Unauthorized) - - if (!passwordHash.verify(credential.password, user.password)) { - return call.response.status(HttpStatusCode.Unauthorized) - } - - val response = DualToken( - token = authJWT.sign(user.id, user.username), - refreshToken = refreshJWT.sign(user.id, user.username) - ) - return call.respond(response) - } - - suspend fun refreshToken(call: ApplicationCall) { - val token = call.receive().refreshToken - - val id = try { - val decodedJWT = refreshJWT.verifier.verify(token) - decodedJWT.getClaim("id").asInt() - } catch (e: JWTVerificationException) { - return call.response.status(HttpStatusCode.Unauthorized) - } - - val user = userRepository.find(id) ?: return call.response.status(HttpStatusCode.Unauthorized) - - val response = DualToken( - token = authJWT.sign(user.id, user.username), - refreshToken = refreshJWT.sign(user.id, user.username) - ) - return call.respond(response) - } - - suspend fun delete(call: ApplicationCall) { - val userId = call.authenticatedUser().id - val success = userRepository.delete(userId) - if (success) call.response.status(HttpStatusCode.OK) - else call.response.status(HttpStatusCode.NotFound) - } - - suspend fun info(call: ApplicationCall) { - val id = call.authenticatedUser().id - val info = userRepository.find(id) - if (info != null) call.respond(mapOf("user" to info)) - else call.response.status(HttpStatusCode.Unauthorized) - } -} - -private data class RefreshToken(val refreshToken: String) -private data class DualToken(val token: String, val refreshToken: String) diff --git a/src/controllers/web/BaseController.kt b/src/controllers/web/BaseController.kt deleted file mode 100644 index b89e3a6..0000000 --- a/src/controllers/web/BaseController.kt +++ /dev/null @@ -1,13 +0,0 @@ -package be.vandewalleh.controllers.web - -import be.vandewalleh.extensions.respondKorte -import com.soywiz.korte.Templates -import io.ktor.application.* - -class BaseController(private val templates: Templates) { - - suspend fun index(call: ApplicationCall) { - val template = templates.get("index.html") - call.respondKorte(template) - } -} diff --git a/src/controllers/web/NoteController.kt b/src/controllers/web/NoteController.kt deleted file mode 100644 index 6e41031..0000000 --- a/src/controllers/web/NoteController.kt +++ /dev/null @@ -1,76 +0,0 @@ -package be.vandewalleh.controllers.web - -import be.vandewalleh.entities.Note -import be.vandewalleh.extensions.authenticatedUser -import be.vandewalleh.extensions.respondKorte -import be.vandewalleh.markdown.Markdown -import be.vandewalleh.repositories.NoteRepository -import be.vandewalleh.utils.Either.Error -import be.vandewalleh.utils.Either.Success -import com.soywiz.korte.Templates -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.http.ContentType.* -import io.ktor.http.HttpStatusCode.Companion.BadRequest -import io.ktor.http.content.* -import io.ktor.request.* -import io.ktor.response.* -import java.util.* - -class NoteController( - private val noteRepository: NoteRepository, - private val templates: Templates, - private val md: Markdown -) { - - suspend fun renderList(call: ApplicationCall) { - val userId = call.authenticatedUser().id - val notes = noteRepository.findAll(userId) - val template = templates.get("list.html") - call.respondKorte(template, "notes" to notes) - } - - suspend fun renderOne(call: ApplicationCall) { - val uuidParam = call.parameters["uuid"] - - val uuid = try { - UUID.fromString(uuidParam) - } catch (e: RuntimeException) { - return - } - val note = noteRepository.find(userId = 1, noteUuid = uuid) ?: return - val template = templates.get("_uuid.html") - call.respondKorte(template, "note" to note) - } - - suspend fun new(call: ApplicationCall) { - val template = templates.get("new.html") - - if (call.request.httpMethod == HttpMethod.Get) return call.respondKorte(template) - - val params = call.receiveParameters() - val txt = params["markdown"] ?: return call.respondKorte(template) - - val result = md.renderDocument(txt) - - val doc = when (result) { - is Error -> return call.respondKorte( - template, - "error" to result.error.msg, - statusCode = BadRequest - ) - is Success -> result.value - } - - val note = Note { - this.title = doc.meta.title - this.tags = doc.meta.tags - this.markdown = txt - this.html = doc.html - } - - noteRepository.create(call.authenticatedUser().id, note) - - call.respondRedirect("/notes") - } -} diff --git a/src/controllers/web/UserController.kt b/src/controllers/web/UserController.kt deleted file mode 100644 index 3991e4c..0000000 --- a/src/controllers/web/UserController.kt +++ /dev/null @@ -1,135 +0,0 @@ -package be.vandewalleh.controllers.web - -import be.vandewalleh.auth.SimpleJWT -import be.vandewalleh.entities.User -import be.vandewalleh.extensions.respondKorte -import be.vandewalleh.features.PasswordHash -import be.vandewalleh.repositories.UserRepository -import be.vandewalleh.validation.registerValidator -import com.auth0.jwt.JWT -import com.soywiz.korte.Templates -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.http.HttpStatusCode.Companion.Unauthorized -import io.ktor.http.content.* -import io.ktor.request.* -import io.ktor.response.* -import io.ktor.util.date.GMTDate - -class UserController( - private val userRepository: UserRepository, - private val templates: Templates, - private val authJWT: SimpleJWT, - private val passwordHash: PasswordHash, - private val useSecureCookies: Boolean -) { - - suspend fun login(call: ApplicationCall) { - val template = templates.get("login.html") - - if (call.request.httpMethod == HttpMethod.Get) { - call.respondKorte(template) - return - } - - val params = call.receiveParameters() - val username = params["username"] - val password = params["password"] - - if (username == null || password == null) { - call.respondKorte(template) - return - } - - val user = userRepository.find(username) - - if (user == null) { - call.respondKorte( - template, - "error" to "Invalid credentials", - statusCode = Unauthorized - ) - return - } - - val verify = passwordHash.verify(password, user.password) - - if (!verify) { - call.respondKorte( - template, - "error" to "Invalid credentials", - statusCode = Unauthorized - ) - return - } - - val token = authJWT.sign(user.id, user.username) - - // FIXME - val expiresAt = JWT.decode(token).expiresAt - val gmtDate = GMTDate(expiresAt.time) - - // Bypass ktor secure verification since we are behind a reverse proxy - call.response.headers.append("Set-Cookie", renderSetCookieHeader(Cookie( - name = "Authorization", - value = "Bearer $token", - secure = useSecureCookies, - path = "/", - httpOnly = true, - expires = gmtDate, - extensions = mapOf( - "SameSite" to "Lax" - ) - ))) - - call.respondRedirect("/notes") - } - - suspend fun register(call: ApplicationCall) { - val template = templates.get("register.html") - - if (call.request.httpMethod == HttpMethod.Get) { - call.respondKorte(template) - return - } - - val params = call.receiveParameters() - val username = params["username"] - val password = params["password"] - - if (username == null || password == null) { - call.respondKorte(template) - return - } - - val validation = registerValidator.validate( - User { - this.username = username - this.password = password - } - ) - - if (!validation.isValid) { - call.respondKorte(template, "error" to validation.details().map { it.defaultMessage }) - return - } - - if (userRepository.exists(username)) { - call.respondKorte(template, "error" to "Please choose another username") - return - } - - if (userRepository.create(username, password) == null) { - // still need a check, for race conditions - call.respondKorte(template, "error" to "Please choose another username") - return - } - - call.respondRedirect("/login") - } - - suspend fun logout(call: ApplicationCall) { - call.response.cookies.appendExpired("Authorization", path = "/") - call.respondRedirect("/") - } -} diff --git a/src/entities/Note.kt b/src/entities/Note.kt deleted file mode 100644 index d3d8368..0000000 --- a/src/entities/Note.kt +++ /dev/null @@ -1,22 +0,0 @@ -package be.vandewalleh.entities - -import com.fasterxml.jackson.annotation.JsonIgnore -import me.liuwj.ktorm.entity.* -import java.time.LocalDateTime -import java.util.* - -interface Note : Entity { - companion object : Entity.Factory() - - var uuid: UUID - var title: String - var markdown: String - var html: String - var updatedAt: LocalDateTime - - @get:JsonIgnore - var user: User - - // Not part of the Notes table - var tags: List -} diff --git a/src/entities/Tag.kt b/src/entities/Tag.kt deleted file mode 100644 index b2bcb29..0000000 --- a/src/entities/Tag.kt +++ /dev/null @@ -1,11 +0,0 @@ -package be.vandewalleh.entities - -import me.liuwj.ktorm.entity.* - -interface Tag : Entity { - companion object : Entity.Factory() - - val id: Int - var name: String - var note: Note -} diff --git a/src/entities/User.kt b/src/entities/User.kt deleted file mode 100644 index 3b08b37..0000000 --- a/src/entities/User.kt +++ /dev/null @@ -1,17 +0,0 @@ -package be.vandewalleh.entities - -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.JsonProperty -import me.liuwj.ktorm.entity.* - -interface User : Entity { - companion object : Entity.Factory() - - @get:JsonIgnore - val id: Int - - var username: String - - @get:JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - var password: String -} diff --git a/src/extensions/ApplicationCallExtensions.kt b/src/extensions/ApplicationCallExtensions.kt deleted file mode 100644 index 07ae4b1..0000000 --- a/src/extensions/ApplicationCallExtensions.kt +++ /dev/null @@ -1,37 +0,0 @@ -package be.vandewalleh.extensions - -import be.vandewalleh.auth.UserPrincipal -import com.soywiz.korte.Template -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.http.* -import io.ktor.http.ContentType.* -import io.ktor.request.* -import io.ktor.response.* - -/** - * @return the userId for the currently authenticated user - */ -fun ApplicationCall.authenticatedUser() = principal()!! - -/** - * @return the userId for the currently authenticated user or null - */ -fun ApplicationCall.authenticatedUserOrNull() = principal() - -suspend fun ApplicationCall.respondKorte( - template: Template, - vararg args: Pair, - statusCode: HttpStatusCode = HttpStatusCode.OK -) { - val uri = request.path().trimEnd('/') - respondText(Text.Html.withCharset(Charsets.UTF_8), statusCode) { - template( - mapOf( - *args, - "url" to uri, - "user" to authenticatedUserOrNull() - ) - ) - } -} diff --git a/src/extensions/Coroutines.kt b/src/extensions/Coroutines.kt deleted file mode 100644 index a665342..0000000 --- a/src/extensions/Coroutines.kt +++ /dev/null @@ -1,9 +0,0 @@ -package be.vandewalleh.extensions - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -suspend inline fun launchIo(crossinline block: () -> T): T = - withContext(Dispatchers.IO) { - block() - } diff --git a/src/extensions/KtorExtensions.kt b/src/extensions/KtorExtensions.kt deleted file mode 100644 index 3318d62..0000000 --- a/src/extensions/KtorExtensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -package be.vandewalleh.extensions - -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.routing.* - -abstract class RoutingBuilder(val builder: Routing.() -> Unit) - -abstract class ApplicationBuilder(val builder: Application.() -> Unit) - -val ContentType.Text.Markdown: ContentType - get() = ContentType("text", "markdown") diff --git a/src/extensions/KtormExtensions.kt b/src/extensions/KtormExtensions.kt deleted file mode 100644 index 75b07a5..0000000 --- a/src/extensions/KtormExtensions.kt +++ /dev/null @@ -1,36 +0,0 @@ -package be.vandewalleh.extensions - -import be.vandewalleh.tables.Notes -import be.vandewalleh.tables.Tags -import be.vandewalleh.tables.Users -import me.liuwj.ktorm.database.* -import me.liuwj.ktorm.entity.* -import me.liuwj.ktorm.schema.* -import java.nio.ByteBuffer -import java.sql.PreparedStatement -import java.sql.ResultSet -import java.sql.Types -import java.util.UUID as JavaUUID - -class UuidBinarySqlType : SqlType(Types.BINARY, typeName = "uuidBinary") { - override fun doGetResult(rs: ResultSet, index: Int): JavaUUID? { - val value = rs.getBytes(index) ?: return null - return ByteBuffer.wrap(value).let { b -> JavaUUID(b.long, b.long) } - } - - override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: JavaUUID) { - val bytes = ByteBuffer.allocate(16) - .putLong(parameter.mostSignificantBits) - .putLong(parameter.leastSignificantBits) - .array() - ps.setBytes(index, bytes) - } -} - -fun BaseTable.uuidBinary(name: String): Column { - return registerColumn(name, UuidBinarySqlType()) -} - -val Database.users get() = this.sequenceOf(Users, withReferences = false) -val Database.notes get() = this.sequenceOf(Notes, withReferences = false) -val Database.tags get() = this.sequenceOf(Tags, withReferences = false) diff --git a/src/factories/ConfigurationFactory.kt b/src/factories/ConfigurationFactory.kt deleted file mode 100644 index 3552063..0000000 --- a/src/factories/ConfigurationFactory.kt +++ /dev/null @@ -1,7 +0,0 @@ -package be.vandewalleh.factories - -import be.vandewalleh.Config -import com.sksamuel.hoplite.ConfigLoader - -fun configurationFactory() = - ConfigLoader().loadConfigOrThrow("/application.yaml") diff --git a/src/factories/DataSourceFactory.kt b/src/factories/DataSourceFactory.kt deleted file mode 100644 index 25f38d9..0000000 --- a/src/factories/DataSourceFactory.kt +++ /dev/null @@ -1,20 +0,0 @@ -package be.vandewalleh.factories - -import be.vandewalleh.DatabaseConfig -import com.zaxxer.hikari.HikariConfig -import com.zaxxer.hikari.HikariDataSource - -fun dataSourceFactory(config: DatabaseConfig): HikariDataSource { - val host = config.host - val port = config.port - val name = config.name - - val hikariConfig = HikariConfig().apply { - jdbcUrl = "jdbc:mariadb://$host:$port/$name" - username = config.username - password = config.password.value - connectionTimeout = 3000 // 3 seconds - } - - return HikariDataSource(hikariConfig) -} diff --git a/src/factories/DatabaseFactory.kt b/src/factories/DatabaseFactory.kt deleted file mode 100644 index d3837ab..0000000 --- a/src/factories/DatabaseFactory.kt +++ /dev/null @@ -1,6 +0,0 @@ -package be.vandewalleh.factories - -import me.liuwj.ktorm.database.* -import javax.sql.DataSource - -fun databaseFactory(dataSource: DataSource) = Database.connect(dataSource) diff --git a/src/factories/SimpleJwtFactory.kt b/src/factories/SimpleJwtFactory.kt deleted file mode 100644 index 7f39546..0000000 --- a/src/factories/SimpleJwtFactory.kt +++ /dev/null @@ -1,6 +0,0 @@ -package be.vandewalleh.factories - -import be.vandewalleh.Jwt -import be.vandewalleh.auth.SimpleJWT - -fun simpleJwtFactory(jwt: Jwt) = SimpleJWT(jwt.secret.value, jwt.validity, jwt.unit) diff --git a/src/factories/TemplateProviderFactory.kt b/src/factories/TemplateProviderFactory.kt deleted file mode 100644 index 0e0d7a5..0000000 --- a/src/factories/TemplateProviderFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -package be.vandewalleh.factories - -import com.soywiz.korte.TemplateProvider - -class ResourceTemplateProvider : TemplateProvider { - override suspend fun get(template: String): String? { - - val tmp = when { - template.endsWith(".html") -> template.substringBefore(".html") - else -> template - } - - val resource = "/templates/${tmp}.twig" - return this.javaClass.getResource(resource)?.readText() - } -} diff --git a/src/factories/TemplatesFactory.kt b/src/factories/TemplatesFactory.kt deleted file mode 100644 index 25178a4..0000000 --- a/src/factories/TemplatesFactory.kt +++ /dev/null @@ -1,28 +0,0 @@ -package be.vandewalleh.factories - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.fasterxml.jackson.module.kotlin.readValue -import com.soywiz.korte.TeFunction -import com.soywiz.korte.TemplateConfig -import com.soywiz.korte.TemplateProvider -import com.soywiz.korte.Templates - -fun getResourceAsText(path: String): String { - return object {}.javaClass.getResource(path).readText() -} - -fun templatesFactory(templateProvider: TemplateProvider): Templates { - - val manifest = getResourceAsText("/css-manifest.json") - - val json = ObjectMapper().registerModule(KotlinModule()).readValue>(manifest) - - val fn = TeFunction("styles") { args -> - val path = args.firstOrNull() - json[path]?.let { "/$it" } - } - - val config = TemplateConfig(extraFunctions = listOf(fn)) - return Templates(templateProvider, config = config, cache = true) -} diff --git a/src/features/CachingHeadersFeature.kt b/src/features/CachingHeadersFeature.kt deleted file mode 100644 index cba3ec1..0000000 --- a/src/features/CachingHeadersFeature.kt +++ /dev/null @@ -1,23 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import io.ktor.application.* -import io.ktor.features.* -import io.ktor.http.* -import io.ktor.http.content.* - -// FIXME: replace with custom features, since immutable is not present -class CachingHeadersFeature : ApplicationBuilder({ - install(CachingHeaders) { - options { outgoingContent -> - when (outgoingContent.contentType?.withoutParameters()) { - ContentType.Text.CSS -> CachingOptions( - CacheControl.MaxAge( - maxAgeSeconds = 31557600 - ) - ) - else -> null - } - } - } -}) diff --git a/src/features/CallLoggingFeature.kt b/src/features/CallLoggingFeature.kt deleted file mode 100644 index 6025be4..0000000 --- a/src/features/CallLoggingFeature.kt +++ /dev/null @@ -1,12 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import io.ktor.application.* -import io.ktor.features.* -import org.slf4j.event.Level - -class CallLoggingFeature : ApplicationBuilder({ - install(CallLogging) { - this.level = Level.INFO - } -}) diff --git a/src/features/CompressionFeature.kt b/src/features/CompressionFeature.kt deleted file mode 100644 index 229dc47..0000000 --- a/src/features/CompressionFeature.kt +++ /dev/null @@ -1,11 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import io.ktor.application.* -import io.ktor.features.* - -class CompressionFeature : ApplicationBuilder({ - install(Compression) { - default() - } -}) diff --git a/src/features/ContentNegotiationFeature.kt b/src/features/ContentNegotiationFeature.kt deleted file mode 100644 index 95b867d..0000000 --- a/src/features/ContentNegotiationFeature.kt +++ /dev/null @@ -1,21 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.util.StdDateFormat -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import io.ktor.application.* -import io.ktor.features.* -import io.ktor.jackson.* -import me.liuwj.ktorm.jackson.* - -class ContentNegotiationFeature : ApplicationBuilder({ - install(ContentNegotiation) { - jackson { - registerModule(KtormModule()) - registerModule(JavaTimeModule()) - disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT) - dateFormat = StdDateFormat() - } - } -}) diff --git a/src/features/CorsFeature.kt b/src/features/CorsFeature.kt deleted file mode 100644 index 57bee73..0000000 --- a/src/features/CorsFeature.kt +++ /dev/null @@ -1,17 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import io.ktor.application.* -import io.ktor.features.* -import io.ktor.http.* - -class CorsFeature(enabled: Boolean) : ApplicationBuilder({ - if (enabled) { - install(CORS) { - anyHost() - header(HttpHeaders.ContentType) - header(HttpHeaders.Authorization) - methods.add(HttpMethod.Delete) - } - } -}) diff --git a/src/features/ErrorFeature.kt b/src/features/ErrorFeature.kt deleted file mode 100644 index ed7aa29..0000000 --- a/src/features/ErrorFeature.kt +++ /dev/null @@ -1,39 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import be.vandewalleh.extensions.respondKorte -import com.soywiz.korte.Templates -import io.ktor.application.* -import io.ktor.features.* -import io.ktor.http.* -import io.ktor.request.* -import io.ktor.response.* -import io.ktor.utils.io.errors.* -import java.sql.SQLTransientConnectionException - -class ErrorHandler(templates: Templates) : ApplicationBuilder({ - install(StatusPages) { - - jacksonErrors() - - status(HttpStatusCode.NotFound, HttpStatusCode.InternalServerError) { - if (call.request.path().startsWith("/api")) return@status - call.respondKorte(templates.get("error.html"), "status" to it.value) - } - - exception { - call.respond(HttpStatusCode.BadRequest) - } - exception { - call.respond(HttpStatusCode.BadRequest, ErrorResponse(it.error)) - } - - exception { - val error = mapOf("error" to "It seems the server can't connect to the database") - call.respond(HttpStatusCode.InternalServerError, error) - } - } -}) - -class ValidationException(val error: String) : RuntimeException() -class ErrorResponse(val error: String) diff --git a/src/features/JacksonErrors.kt b/src/features/JacksonErrors.kt deleted file mode 100644 index a6a0f9c..0000000 --- a/src/features/JacksonErrors.kt +++ /dev/null @@ -1,66 +0,0 @@ -package be.vandewalleh.features - -import com.fasterxml.jackson.core.JsonParseException -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.core.exc.InputCoercionException -import com.fasterxml.jackson.databind.JsonMappingException -import com.fasterxml.jackson.databind.exc.MismatchedInputException -import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException -import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException -import io.ktor.application.* -import io.ktor.features.* -import io.ktor.http.* -import io.ktor.response.* - -fun StatusPages.Configuration.jacksonErrors() { - exception { - val error = InvalidFormatError(it.path.firstOrNull()?.fieldName, it.targetType) - call.respond(HttpStatusCode.BadRequest, error) - } - exception { - val error = JsonParseError() - call.respond(HttpStatusCode.BadRequest, error) - } - exception { - val error = UnrecognizedPropertyError(it.path[0].fieldName) - call.respond(HttpStatusCode.BadRequest, error) - } - exception { - val error = MissingKotlinParameterError(it.path[0].fieldName) - call.respond(HttpStatusCode.BadRequest, error) - } - exception { - call.respond(HttpStatusCode.BadRequest, JsonProcessingError()) - } - exception { - if (it.cause is InputCoercionException) { - return@exception call.respond(HttpStatusCode.BadRequest, OutOfRangeError(it.path[0].fieldName)) - } - call.respond(HttpStatusCode.BadRequest, JsonProcessingError()) - } -} - -class InvalidFormatError(val value: Any?, targetType: Class<*>) { - val msg = "Wrong type" - val required = targetType.simpleName -} - -class UnrecognizedPropertyError(val field: Any?) { - val msg = "Unrecognized field" -} - -class MissingKotlinParameterError(val field: String) { - val msg = "Missing field" -} - -class JsonProcessingError { - val msg = "An error occurred while processing JSON" -} - -class JsonParseError { - val msg = "Invalid JSON" -} - -class OutOfRangeError(val field: String) { - val msg = "Numeric value out of range" -} diff --git a/src/features/Migration.kt b/src/features/Migration.kt deleted file mode 100644 index 1af3c7e..0000000 --- a/src/features/Migration.kt +++ /dev/null @@ -1,27 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import io.ktor.application.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.flywaydb.core.Flyway -import javax.sql.DataSource - -class MigrationHook(migration: Migration) : ApplicationBuilder({ - environment.monitor.subscribe(ApplicationStarted) { - CoroutineScope(Dispatchers.IO).launch { - migration.migrate() - } - } -}) - -class Migration(private val dataSource: DataSource) { - fun migrate() { - Flyway.configure() - .dataSource(dataSource) - .baselineOnMigrate(true) - .load() - .migrate() - } -} diff --git a/src/features/SecurityReport.kt b/src/features/SecurityReport.kt deleted file mode 100644 index fafa9de..0000000 --- a/src/features/SecurityReport.kt +++ /dev/null @@ -1,111 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.fasterxml.jackson.module.kotlin.readValue -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.request.* -import io.ktor.response.* -import io.ktor.routing.* -import io.ktor.util.* -import org.slf4j.LoggerFactory -import kotlin.collections.HashMap - -class SecurityReport : ApplicationBuilder({ - - val logger = LoggerFactory.getLogger(SecurityReport::class.java) - - routing { - post("/csp-reports") { - val txt = call.receiveText() - val json = ObjectMapper().registerModule(KotlinModule()).readValue(txt) - logger.info(json.toPrettyString()) - call.response.status(HttpStatusCode.OK) - } - } - - install(SecurityHeaders) { - reportOnly = false - reportUri = "/csp-reports" - cspValue = "default-src 'self'" - } -}) - -class SecurityHeaders(configuration: Configuration) { - private val logger = LoggerFactory.getLogger(key.name) - private val headers = buildHeaders(configuration) - - class Configuration { - var reportOnly = true - var reportUri = "/csp-reports" - - var csp = true - var cspValue = "default-src 'self'" - - var noSniff = true - var referrerPolicy = true - var xssFilter = true - var frameguard = true - var featurePolicy = false - } - - companion object Feature : - ApplicationFeature { - override val key = AttributeKey("SecurityHeaders") - - override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): SecurityHeaders { - - val configuration = SecurityHeaders.Configuration().apply(configure) - - val feature = SecurityHeaders(configuration) - - pipeline.sendPipeline.intercept(ApplicationSendPipeline.After) { subject -> - if (subject !is OutgoingContent) return@intercept - val contentType = subject.contentType ?: return@intercept - if (!ContentType.Text.Html.match(contentType.withoutParameters())) return@intercept - if (call.request.path().startsWith("/api")) return@intercept - feature.process(call, contentType) - } - - return feature - } - } - - fun process(call: ApplicationCall, contentType: ContentType) { - headers.forEach { (name, value) -> - call.response.headers.append(name, value) - } - - logger.debug("Added security headers") - } - - private fun buildHeaders(cfg: Configuration): HashMap { - val headers = HashMap() - - if (cfg.noSniff) headers["X-Content-Type-Options"] = "nosniff" - if (cfg.referrerPolicy) headers["Referrer-Policy"] = "no-referrer-when-downgrade" - if (cfg.xssFilter) headers["X-XSS-Protection"] = "1; mode=block" - if (cfg.frameguard) headers["X-Frame-Options"] = "DENY" - - if (cfg.csp) { - val cspValue = if (cfg.reportOnly) cfg.cspValue + "; report-uri ${cfg.reportUri}" else cfg.cspValue - val cspKey = if (cfg.reportOnly) "Content-Security-Policy-Report-Only" else "Content-Security-Policy" - headers[cspKey] = cspValue - } - - if (cfg.featurePolicy) { - - // really ?? https://github.com/w3c/webappsec-feature-policy/issues/189 - headers["Feature-Policy"] = - "accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; battery 'none'; camera 'none'; display-capture 'none'; document-domain 'none'; encrypted-media 'none'; fullscreen 'none'; geolocation 'none'; gyroscope 'none'; legacy-image-formats 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; oversized-images 'none'; payment 'none'; picture-in-picture 'none'; publickey-credentials-get 'none'; sync-xhr 'none'; unoptimized-images 'none'; unsized-media 'none'; usb 'none'; wake-lock 'none'; xr-spatial-tracking 'none'" - } - - // TODO: Strict-Transport-Security: "max-age=31536000; includeSubDomains" - - return headers - } -} diff --git a/src/features/ShutdownDatabaseConnection.kt b/src/features/ShutdownDatabaseConnection.kt deleted file mode 100644 index db91b1f..0000000 --- a/src/features/ShutdownDatabaseConnection.kt +++ /dev/null @@ -1,13 +0,0 @@ -package be.vandewalleh.features - -import be.vandewalleh.extensions.ApplicationBuilder -import com.zaxxer.hikari.HikariDataSource -import io.ktor.application.* - -class ShutdownDatabaseConnection(hikariDataSource: HikariDataSource) : ApplicationBuilder({ - environment.monitor.subscribe(ApplicationStopPreparing) { - if (!hikariDataSource.isClosed) { - hikariDataSource.close() - } - } -}) diff --git a/src/markdown/Markdown.kt b/src/markdown/Markdown.kt deleted file mode 100644 index 5fe5f03..0000000 --- a/src/markdown/Markdown.kt +++ /dev/null @@ -1,117 +0,0 @@ -package be.vandewalleh.markdown - -import am.ik.yavi.builder.ValidatorBuilder -import am.ik.yavi.builder.konstraint -import am.ik.yavi.core.Validator -import be.vandewalleh.utils.Either -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.fasterxml.jackson.module.kotlin.readValue -import com.sksamuel.hoplite.fp.valid -import com.vladsch.flexmark.html.HtmlRenderer -import com.vladsch.flexmark.parser.Parser -import org.owasp.html.HtmlPolicyBuilder -import java.util.regex.Pattern - -sealed class MarkdownParsingError(val msg: String) { - object MissingMeta : MarkdownParsingError("No metadata found") - object InvalidMeta : MarkdownParsingError("Invalid metadata") - class ValidationError(msg: String) : MarkdownParsingError(msg) -} - -private val metaValidator: Validator = ValidatorBuilder.of() - .konstraint(Meta::title) { - notNull().notBlank().lessThanOrEqual(50) - } - .konstraint(Meta::tags) { - lessThanOrEqual(5) - } - .build() - -class Markdown { - - private val yamlBoundPattern = Pattern.compile("^-{3}(\\s.*)?") - - private val parser = Parser.builder().build() - private val renderer = HtmlRenderer.builder().build() - - private val htmlPolicy = HtmlPolicyBuilder() - .allowElements("a") - .allowCommonBlockElements() - .allowCommonInlineFormattingElements() - .allowElements("pre") - .allowAttributes("class").onElements("code") - .allowUrlProtocols("https") - .allowAttributes("href").onElements("a") - .requireRelNofollowOnLinks() - .toFactory() - - fun convertToMarkdown(input: String): String { - val document = parser.parse(input) - return renderer.render(document) - } - - fun renderDocument(input: String): Either { - val lines = input.lines() - - var startIndex: Int? = null - var endIndex: Int? = null - - for ((i, line) in lines.withIndex()) { - val isYamlBound = yamlBoundPattern.matcher(line).matches() - if (isYamlBound) { - if (startIndex == null) { - startIndex = i - } else { - endIndex = i - break - } - } - } - - if (endIndex == null) return Either.Error(MarkdownParsingError.MissingMeta) - - lateinit var yaml: String - lateinit var doc: String - - if (startIndex != null) { - val startMeta = startIndex + 1 - val endMeta = endIndex - 1 - yaml = lines.slice(startMeta..endMeta) - .joinToString("\n") - } - - val startMd = endIndex + 1 - val endMd = lines.size - 1 - doc = lines.slice(startMd..endMd) - .joinToString("\n") - - val meta = try { - parseMeta(yaml) - } catch (e: Exception) { - return Either.Error(MarkdownParsingError.InvalidMeta) - } - - val metaValidation = metaValidator.validate(meta) - - metaValidation.valid() - - if (!metaValidation.isValid) { - val msg = metaValidation.details().firstOrNull()?.defaultMessage ?: "Invalid" // FIXME - return Either.Error(MarkdownParsingError.ValidationError(msg)) - } - - val unsafeHtml = convertToMarkdown(doc) - val safeHTML = htmlPolicy.sanitize(unsafeHtml) - - return Either.Success(MarkdownDocument(meta, safeHTML)) - } - - private val objectMapper: ObjectMapper = ObjectMapper(YAMLFactory()).apply { registerModule(KotlinModule()) } - - fun parseMeta(input: String): Meta = objectMapper.readValue(input) -} - -data class MarkdownDocument(val meta: Meta, val html: String) -data class Meta(val title: String, val tags: List = emptyList()) diff --git a/src/repositories/NoteRepository.kt b/src/repositories/NoteRepository.kt deleted file mode 100644 index 7c49faf..0000000 --- a/src/repositories/NoteRepository.kt +++ /dev/null @@ -1,145 +0,0 @@ -package be.vandewalleh.repositories - -import be.vandewalleh.entities.Note -import be.vandewalleh.extensions.launchIo -import be.vandewalleh.extensions.notes -import be.vandewalleh.extensions.tags -import be.vandewalleh.tables.Notes -import be.vandewalleh.tables.Tags -import me.liuwj.ktorm.database.* -import me.liuwj.ktorm.dsl.* -import me.liuwj.ktorm.entity.* -import java.time.LocalDateTime -import java.util.* -import kotlin.collections.HashMap - -/** - * service to handle database queries at the Notes level. - */ -class NoteRepository(private val db: Database) { - - /** - * returns a list of [Note] associated with the userId - */ - suspend fun findAll(userId: Int, limit: Int = 20, after: UUID? = null): List = launchIo { - - var previous: LocalDateTime? = null - - if (after != null) { - previous = db.notes - .filter { it.userId eq userId and (it.uuid eq after) } - .mapColumns { it.updatedAt } - .firstOrNull() ?: return@launchIo emptyList() - } - - val notes = db.notes - .filterColumns { listOf(it.uuid, it.title, it.updatedAt) } - .filter { - if (previous == null) it.userId eq userId - else (it.userId eq userId) and (it.updatedAt less previous) - } - .sortedByDescending { it.updatedAt } - .take(limit) - .toList() - - if (notes.isEmpty()) return@launchIo emptyList() - - val tagsByUuid = - db.tags - .filterColumns { listOf(it.noteUuid, it.name) } - .filter { it.noteUuid inList notes.map { note -> note.uuid } } - .groupByTo(HashMap(), { it.note.uuid }, { it.name }) - - notes.forEach { - val tags = tagsByUuid[it.uuid] - if (tags != null) it.tags = tags - } - - notes - } - - suspend fun exists(userId: Int, uuid: UUID) = launchIo { - db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) } - } - - suspend fun create(userId: Int, note: Note): Note = launchIo { - val uuid = UUID.randomUUID() - val newNote = note.copy().apply { - this["uuid"] = uuid - this.user["id"] = userId - this.updatedAt = LocalDateTime.now() - } - db.useTransaction { - db.notes.add(newNote) - db.batchInsert(Tags) { - note.tags.forEach { tagName -> - item { - it.noteUuid to uuid - it.name to tagName - } - } - } - } - newNote - } - - @Suppress("UNCHECKED_CAST") - suspend fun find(userId: Int, noteUuid: UUID): Note? = launchIo { - val note = - db.notes - .filterColumns { it.columns - it.userId } - .filter { it.uuid eq noteUuid } - .find { it.userId eq userId } - ?: return@launchIo null - - val tags = - db.sequenceOf(Tags, withReferences = false) - .filter { it.noteUuid eq noteUuid } - .mapColumns { it.name } as List - - note.also { it.tags = tags } - } - - suspend fun updateNote(userId: Int, note: Note): Boolean = launchIo { - if (note["uuid"] == null) error("UUID is required") - - db.useTransaction { - val currentNote = db.notes - .find { it.uuid eq note.uuid and (it.userId eq userId) } - ?: return@launchIo false - - currentNote.title = note.title - currentNote.markdown = note.markdown - currentNote.html = note.html - currentNote.updatedAt = LocalDateTime.now() - currentNote.flushChanges() - - // delete all tags - db.delete(Tags) { - it.noteUuid eq note.uuid - } - - // put new ones - note.tags.forEach { tagName -> - db.insert(Tags) { - it.name to tagName - it.noteUuid to note.uuid - } - } - } - true - } - - suspend fun delete(userId: Int, noteUuid: UUID): Boolean = launchIo { - db.useTransaction { - db.delete(Notes) { it.uuid eq noteUuid and (it.userId eq userId) } == 1 - } - } - - @Suppress("UNCHECKED_CAST") - suspend fun getTags(userId: Int): List = launchIo { - db.sequenceOf(Tags) - .filter { it.note.userId eq userId } - .mapColumns(isDistinct = true) { it.name } as List - } -} diff --git a/src/repositories/UserRepository.kt b/src/repositories/UserRepository.kt deleted file mode 100644 index 976b07c..0000000 --- a/src/repositories/UserRepository.kt +++ /dev/null @@ -1,65 +0,0 @@ -package be.vandewalleh.repositories - -import be.vandewalleh.entities.User -import be.vandewalleh.extensions.launchIo -import be.vandewalleh.extensions.users -import be.vandewalleh.features.PasswordHash -import be.vandewalleh.tables.Users -import me.liuwj.ktorm.database.* -import me.liuwj.ktorm.dsl.* -import me.liuwj.ktorm.entity.* -import java.sql.SQLIntegrityConstraintViolationException - -/** - * service to handle database queries for users. - */ -class UserRepository(private val db: Database, private val passwordHash: PasswordHash) { - - /** - * returns a user from it's username if found or null - */ - suspend fun find(username: String): User? = launchIo { - db.users.find { it.username eq username } - } - - suspend fun find(id: Int): User? = launchIo { - db.users.find { it.id eq id } - } - - suspend fun exists(username: String) = launchIo { - db.users.any { it.username eq username } - } - - suspend fun exists(userId: Int) = launchIo { - db.users.any { it.id eq userId } - } - - suspend fun create(username: String, password: String): User? { - val newUser = User { - this.username = username - this.password = passwordHash.crypt(password) - } - - return try { - launchIo { - db.useTransaction { - db.users.add(newUser) - newUser - } - } - } catch (e: SQLIntegrityConstraintViolationException) { - null - } - } - - suspend fun delete(userId: Int): Boolean = launchIo { - val updateCount = db.useTransaction { - db.delete(Users) { it.id eq userId } - } - when (updateCount) { - 1 -> true - 0 -> false - else -> error("??") - } - } -} diff --git a/src/routing/api/ApiDocRoutes.kt b/src/routing/api/ApiDocRoutes.kt deleted file mode 100644 index f2f5770..0000000 --- a/src/routing/api/ApiDocRoutes.kt +++ /dev/null @@ -1,10 +0,0 @@ -package be.vandewalleh.routing.api - -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.http.content.* - -class ApiDocRoutes : RoutingBuilder({ - static("/api/docs") { - defaultResource("docs/index.html") - } -}) diff --git a/src/routing/api/ApiNoteRoutes.kt b/src/routing/api/ApiNoteRoutes.kt deleted file mode 100644 index 3632afe..0000000 --- a/src/routing/api/ApiNoteRoutes.kt +++ /dev/null @@ -1,37 +0,0 @@ -package be.vandewalleh.routing.api - -import be.vandewalleh.controllers.api.ApiNoteController -import be.vandewalleh.extensions.Markdown -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.http.* -import io.ktor.routing.* - -class ApiNoteRoutes(ctrl: ApiNoteController) : RoutingBuilder({ - - route("/api/notes") { - - accept(ContentType.Application.Json) { - - authenticate { - - contentType(ContentType.Text.Markdown) { - post { ctrl.create(call) } - } - - get { ctrl.getAll(call) } - - route("/{uuid}") { - get { ctrl.getOne(call) } - - contentType(ContentType.Text.Markdown) { - put { ctrl.update(call) } - } - - delete { ctrl.delete(call) } - } - } - } - } -}) diff --git a/src/routing/api/ApiTagRoutes.kt b/src/routing/api/ApiTagRoutes.kt deleted file mode 100644 index a8c7a93..0000000 --- a/src/routing/api/ApiTagRoutes.kt +++ /dev/null @@ -1,18 +0,0 @@ -package be.vandewalleh.routing.api - -import be.vandewalleh.controllers.api.ApiTagController -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.http.* -import io.ktor.routing.* - -class ApiTagRoutes(ctrl: ApiTagController) : RoutingBuilder({ - route("/api/tags") { - accept(ContentType.Application.Json) { - authenticate { - get { ctrl.getAll(call) } - } - } - } -}) diff --git a/src/routing/api/ApiUserRoutes.kt b/src/routing/api/ApiUserRoutes.kt deleted file mode 100644 index 65c308d..0000000 --- a/src/routing/api/ApiUserRoutes.kt +++ /dev/null @@ -1,26 +0,0 @@ -package be.vandewalleh.routing.api - -import be.vandewalleh.controllers.api.ApiUserController -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.http.* -import io.ktor.routing.* - -class ApiUserRoutes(ctrl: ApiUserController) : RoutingBuilder({ - route("/api/user") { - accept(ContentType.Application.Json) { - - contentType(ContentType.Application.Json) { - post { ctrl.create(call) } - post("/login") { ctrl.login(call) } - post("/refresh_token") { ctrl.refreshToken(call) } - } - - authenticate { - delete { ctrl.delete(call) } - get("/me") { ctrl.info(call) } - } - } - } -}) diff --git a/src/routing/web/BaseRoutes.kt b/src/routing/web/BaseRoutes.kt deleted file mode 100644 index 96e643d..0000000 --- a/src/routing/web/BaseRoutes.kt +++ /dev/null @@ -1,13 +0,0 @@ -package be.vandewalleh.routing.web - -import be.vandewalleh.controllers.web.BaseController -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.routing.* - -class BaseRoutes(ctrl: BaseController) : RoutingBuilder({ - authenticate(optional = true) { - get("/") { ctrl.index(call) } - } -}) diff --git a/src/routing/web/NoteRoutes.kt b/src/routing/web/NoteRoutes.kt deleted file mode 100644 index b68ee5e..0000000 --- a/src/routing/web/NoteRoutes.kt +++ /dev/null @@ -1,20 +0,0 @@ -package be.vandewalleh.routing.web - -import be.vandewalleh.controllers.web.NoteController -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.routing.* - -class NoteRoutes(ctrl: NoteController) : RoutingBuilder({ - authenticate { - route("/notes") { - get { ctrl.renderList(call) } - get("/{uuid}") { ctrl.renderOne(call) } - route("/new") { - get { ctrl.new(call) } - post { ctrl.new(call) } - } - } - } -}) diff --git a/src/routing/web/StaticRoutes.kt b/src/routing/web/StaticRoutes.kt deleted file mode 100644 index e0eb3f4..0000000 --- a/src/routing/web/StaticRoutes.kt +++ /dev/null @@ -1,10 +0,0 @@ -package be.vandewalleh.routing.web - -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.http.content.* - -class StaticRoutes : RoutingBuilder({ - static { - resources("static") - } -}) diff --git a/src/routing/web/UserRoutes.kt b/src/routing/web/UserRoutes.kt deleted file mode 100644 index 5973b5d..0000000 --- a/src/routing/web/UserRoutes.kt +++ /dev/null @@ -1,27 +0,0 @@ -package be.vandewalleh.routing.web - -import be.vandewalleh.controllers.web.UserController -import be.vandewalleh.extensions.RoutingBuilder -import io.ktor.application.* -import io.ktor.auth.* -import io.ktor.routing.* - -class UserRoutes(ctrl: UserController) : RoutingBuilder({ - - authenticate(optional = true) { - - route("/login") { - get { ctrl.login(call) } - post { ctrl.login(call) } - } - - route("/register") { - get { ctrl.register(call) } - post { ctrl.register(call) } - } - } - - authenticate { - post("/logout") { ctrl.logout(call) } - } -}) diff --git a/src/tables/Notes.kt b/src/tables/Notes.kt deleted file mode 100644 index 489e5f8..0000000 --- a/src/tables/Notes.kt +++ /dev/null @@ -1,19 +0,0 @@ -package be.vandewalleh.tables - -import be.vandewalleh.entities.Note -import be.vandewalleh.extensions.uuidBinary -import me.liuwj.ktorm.schema.* - -open class Notes(alias: String?) : Table("Notes", alias) { - companion object : Notes(null) - - override fun aliased(alias: String) = Notes(alias) - - val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid } - val title = varchar("title").bindTo { it.title } - val markdown = text("markdown").bindTo { it.markdown } - val html = text("html").bindTo { it.html } - val userId = int("user_id").references(Users) { it.user } - val updatedAt = datetime("updated_at").bindTo { it.updatedAt } - val user get() = userId.referenceTable as Users -} diff --git a/src/tables/Tags.kt b/src/tables/Tags.kt deleted file mode 100644 index 6edbd8a..0000000 --- a/src/tables/Tags.kt +++ /dev/null @@ -1,16 +0,0 @@ -package be.vandewalleh.tables - -import be.vandewalleh.entities.Tag -import be.vandewalleh.extensions.uuidBinary -import me.liuwj.ktorm.schema.* - -open class Tags(alias: String?) : Table("Tags", alias) { - companion object : Tags(null) - - override fun aliased(alias: String) = Tags(alias) - - val id = int("id").primaryKey().bindTo { it.id } - val name = varchar("name").bindTo { it.name } - val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note } - val note get() = noteUuid.referenceTable as Notes -} diff --git a/src/tables/Users.kt b/src/tables/Users.kt deleted file mode 100644 index 2136fa8..0000000 --- a/src/tables/Users.kt +++ /dev/null @@ -1,14 +0,0 @@ -package be.vandewalleh.tables - -import be.vandewalleh.entities.User -import me.liuwj.ktorm.schema.* - -open class Users(alias: String?) : Table("Users", alias) { - companion object : Users(null) - - override fun aliased(alias: String) = Users(alias) - - val id = int("id").primaryKey().bindTo { it.id } - val username = varchar("username").bindTo { it.username } - val password = varchar("password").bindTo { it.password } -} diff --git a/src/utils/Either.kt b/src/utils/Either.kt deleted file mode 100644 index 1358aaf..0000000 --- a/src/utils/Either.kt +++ /dev/null @@ -1,6 +0,0 @@ -package be.vandewalleh.utils - -sealed class Either { - class Error(val error: A) : Either() - class Success(val value: B) : Either() -} diff --git a/src/validation/NoteValidation.kt b/src/validation/NoteValidation.kt deleted file mode 100644 index 6af2964..0000000 --- a/src/validation/NoteValidation.kt +++ /dev/null @@ -1,18 +0,0 @@ -package be.vandewalleh.validation - -import am.ik.yavi.builder.ValidatorBuilder -import am.ik.yavi.builder.konstraint -import am.ik.yavi.core.Validator -import be.vandewalleh.entities.Note - -val noteValidator: Validator = ValidatorBuilder.of() - .konstraint(Note::title) { - notNull().notBlank().lessThanOrEqual(50) - } - .konstraint(Note::tags) { - lessThanOrEqual(10) - } - .konstraint(Note::markdown) { - notNull().notBlank() - } - .build() diff --git a/src/validation/UserValidation.kt b/src/validation/UserValidation.kt deleted file mode 100644 index af61d05..0000000 --- a/src/validation/UserValidation.kt +++ /dev/null @@ -1,15 +0,0 @@ -package be.vandewalleh.validation - -import am.ik.yavi.builder.ValidatorBuilder -import am.ik.yavi.builder.konstraint -import am.ik.yavi.core.Validator -import be.vandewalleh.entities.User - -val registerValidator: Validator = ValidatorBuilder.of() - .konstraint(User::username) { - notNull().lessThanOrEqual(50).greaterThanOrEqual(3) - } - .konstraint(User::password) { - notNull().greaterThanOrEqual(6) - } - .build() diff --git a/src/validation/ValidationExtensions.kt b/src/validation/ValidationExtensions.kt deleted file mode 100644 index c98931a..0000000 --- a/src/validation/ValidationExtensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -package be.vandewalleh.validation - -import am.ik.yavi.core.Validator -import be.vandewalleh.features.ValidationException -import io.ktor.application.* -import io.ktor.request.* - -suspend inline fun ApplicationCall.receiveValidated(validator: Validator): T { - val value: T = receive() - validator.validate(value).throwIfInvalid { ValidationException(it.details()[0].defaultMessage) } - return value -} diff --git a/start-dev.sh b/start-dev.sh deleted file mode 100755 index bcb8032..0000000 --- a/start-dev.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -check_installed() { - if ! [ -x "$(command -v $1)" ]; then - echo "Error: $1 is not installed." >&2 - exit 1 - fi -} - -check_installed docker-compose - -docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build \ No newline at end of file diff --git a/start-prod.sh b/start-prod.sh deleted file mode 100755 index e822e50..0000000 --- a/start-prod.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -check_installed() { - if ! [ -x "$(command -v $1)" ]; then - echo "Error: $1 is not installed." >&2 - exit 1 - fi -} - -check_installed docker-compose - -docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build diff --git a/test/integration/routing/ApiUserControllerKtTest.kt b/test/integration/routing/ApiUserControllerKtTest.kt deleted file mode 100644 index 5ede7e5..0000000 --- a/test/integration/routing/ApiUserControllerKtTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -package integration.routing - -import be.vandewalleh.auth.SimpleJWT -import be.vandewalleh.entities.User -import be.vandewalleh.mainModule -import be.vandewalleh.module -import be.vandewalleh.repositories.UserRepository -import io.ktor.http.* -import io.ktor.server.testing.* -import io.mockk.coEvery -import io.mockk.mockk -import org.amshove.kluent.* -import org.junit.jupiter.api.* -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import utils.* - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class ApiUserControllerKtTest { - - private val userService = mockk() - - init { - // new user - coEvery { userService.exists("new") } returns false - coEvery { userService.create("new", any()) } returns User { - this.username = "new" - } - - // existing user - coEvery { userService.exists("existing") } returns true - coEvery { userService.create("existing", any()) } returns null - coEvery { userService.delete(1) } returns true andThen false - - // modified user - coEvery { userService.exists("modified") } returns true - coEvery { userService.exists(and(not("modified"), not("existing"))) } returns false - coEvery { userService.exists(1) } returns true - coEvery { userService.create("modified", any()) } returns null - } - - private val kodein = DI { - import(mainModule, allowOverride = true) - bind(overrides = true) with instance(userService) - } - - private val testEngine = TestApplicationEngine().apply { - start() - application.module(kodein) - } - - @Nested - inner class CreateUser { - @Test - fun `create a new user`() { - val res = testEngine.post("/user") { - json { - it["username"] = "new" - it["password"] = "test123abc" - } - } - res.status() `should be equal to` HttpStatusCode.Created - res.content `should strictly be equal to json` """{username:"new"}""" - } - - @Test - fun `create an existing user`() { - val res = testEngine.post("/user") { - json { - it["username"] = "existing" - it["password"] = "test123abc" - } - } - res.status() `should be equal to` HttpStatusCode.Conflict - res.content `should be equal to json` """{msg:"Conflict"}""" - } - } - - @Nested - inner class DeleteUser { - - @Test - fun `delete an existing user`() { - val authJwt by kodein.instance("auth") - val token = authJwt.sign(1, "") - - val res = testEngine.delete("/user") { - addHeader(HttpHeaders.Authorization, "Bearer $token") - } - res.status() `should be equal to` HttpStatusCode.OK - res.content `should be equal to json` """{msg:"OK"}""" - - // try again - val res2 = testEngine.delete("/user") { - setToken(token) - } - res2.status() `should be equal to` HttpStatusCode.NotFound - res2.content `should be equal to json` """{msg:"Not Found"}""" - } - } -} diff --git a/test/integration/routing/AuthControllerKtTest.kt b/test/integration/routing/AuthControllerKtTest.kt deleted file mode 100644 index 2c58b69..0000000 --- a/test/integration/routing/AuthControllerKtTest.kt +++ /dev/null @@ -1,223 +0,0 @@ -package integration.routing - -import be.vandewalleh.Config -import be.vandewalleh.auth.SimpleJWT -import be.vandewalleh.entities.User -import be.vandewalleh.features.PasswordHash -import be.vandewalleh.mainModule -import be.vandewalleh.module -import be.vandewalleh.repositories.UserRepository -import com.auth0.jwt.JWT -import com.auth0.jwt.algorithms.Algorithm -import io.ktor.http.* -import io.ktor.server.testing.* -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk -import org.amshove.kluent.* -import org.json.JSONObject -import org.junit.jupiter.api.* -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import utils.* -import java.util.* - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class AuthControllerKtTest { - - private val userService = mockk() - - private val kodein = DI { - import(mainModule, allowOverride = true) - bind(overrides = true) with instance(userService) - } - - private val passwordHash by kodein.instance() - - init { - - val user = User { - password = passwordHash.crypt("password") - username = "existing" - } - user["id"] = 1 - - coEvery { userService.find("existing") } returns user - coEvery { userService.exists(1) } returns true - coEvery { userService.find(1) } returns User { - username = "existing" - } - - val user2 = User { - password = passwordHash.crypt("right password") - username = "wrong" - } - user["id"] = 2 - coEvery { userService.find("wrong") } returns user2 - - coEvery { userService.find("notExisting") } returns null - - coEvery { userService.exists(3) } returns false - coEvery { userService.find(3) } returns null - } - - private val testEngine = TestApplicationEngine().apply { - start() - application.module(kodein) - } - - @Nested - inner class Login { - @Test - fun `login existing user with valid password`() { - val res = testEngine.post("/user/login") { - json { - it["username"] = "existing" - it["password"] = "password" - } - } - - coVerify { userService.find("existing") } - - res.status() `should be equal to` HttpStatusCode.OK - val jsonObject = JSONObject(res.content) - - val hasToken = jsonObject.has("token") - hasToken `should be equal to` true - - jsonObject.keyList() `should be equal to` listOf("token", "refreshToken") - - val authJwt by kodein.instance(tag = "auth") - val token = jsonObject.getString("token") - authJwt.verifier.verify(token) - - val refreshJwt by kodein.instance(tag = "refresh") - val refreshToken = jsonObject.getString("refreshToken") - refreshJwt.verifier.verify(refreshToken) - } - - @Test - fun `login existing user with invalid password`() { - val res = testEngine.post("/user/login") { - json { - it["username"] = "wrong" - it["password"] = "not this" - } - } - - coVerify { userService.find("wrong") } - - res.status() `should be equal to` HttpStatusCode.Unauthorized - res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" - } - - @Test - fun `login not existing user`() { - val res = testEngine.post("/user/login") { - json { - it["username"] = "notExisting" - it["password"] = "babababa" - } - } - - coVerify { userService.find("notExisting") } - - res.status() `should be equal to` HttpStatusCode.Unauthorized - res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" - } - - @Test - fun `login without body`() { - val res = testEngine.post("/user/login") { - addHeader(HttpHeaders.ContentType, "application/json") - } - res.status() `should be equal to` HttpStatusCode.BadRequest - } - } - - @Nested - inner class Refresh { - - @Test - fun `test valid refresh token`() { - val refreshJwt by kodein.instance(tag = "refresh") - val refreshToken = refreshJwt.sign(1, "") - - val res = testEngine.post("/user/refresh_token") { - json { - it["refreshToken"] = refreshToken - } - } - - val jsonObject = JSONObject(res.content) - jsonObject.keyList() `should be equal to` listOf("token", "refreshToken") - - coVerify { userService.exists(1) } - res.status() `should be equal to` HttpStatusCode.OK - } - - @Test - fun `test valid refresh token for deleted user`() { - val refreshJwt by kodein.instance(tag = "refresh") - val refreshToken = refreshJwt.sign(3, "") - - val res = testEngine.post("/user/refresh_token") { - json { - it["refreshToken"] = refreshToken - } - } - - coVerify { userService.exists(3) } - res.status() `should be equal to` HttpStatusCode.Unauthorized - res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" - } - - @Test - fun `test expired refresh token for existing user`() { - val config by kodein.instance() - val algorithm = Algorithm.HMAC256(config.jwt.refresh.secret.value) - - val expiredToken = JWT.create() - .withClaim("id", 1) - .withExpiresAt(Date(0)) // January 1, 1970, 00:00:00 GMT - .sign(algorithm) - - val res = testEngine.post("/user/refresh_token") { - json { - it["refreshToken"] = expiredToken - } - } - - res.status() `should be equal to` HttpStatusCode.Unauthorized - res.content `should strictly be equal to json` """{msg: "Unauthorized"}""" - } - } - - @Nested - inner class UserInfo { - - @Test - fun `test user info for existing user`() { - val authJwt by kodein.instance(tag = "auth") - val token = authJwt.sign(1, "") - val res = testEngine.get("/user/me") { - setToken(token) - } - res.content `should strictly be equal to json` """{user:{username:"existing"}}""" - res.status() `should be equal to` HttpStatusCode.OK - } - - @Test - fun `test user info on deleted user`() { - val authJwt by kodein.instance(tag = "auth") - val token = authJwt.sign(3, "") - val res = testEngine.get("/user/me") { - setToken(token) - } - res.status()!!.value `should not be in range` (200..299) - val jsonObject = JSONObject(res.content) - jsonObject.keyList() `should be equal to` listOf("msg") - } - } -} diff --git a/test/integration/services/NoteRepositoryTest.kt b/test/integration/services/NoteRepositoryTest.kt deleted file mode 100644 index 7788487..0000000 --- a/test/integration/services/NoteRepositoryTest.kt +++ /dev/null @@ -1,257 +0,0 @@ -package integration.services - -import am.ik.yavi.builder.ValidatorBuilder -import am.ik.yavi.core.CustomConstraint -import am.ik.yavi.core.Validator -import be.vandewalleh.entities.Note -import be.vandewalleh.features.Migration -import be.vandewalleh.mainModule -import be.vandewalleh.repositories.NoteRepository -import be.vandewalleh.repositories.UserRepository -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.util.StdDateFormat -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.readValue -import com.github.javafaker.Faker -import kotlinx.coroutines.runBlocking -import me.liuwj.ktorm.jackson.* -import org.amshove.kluent.* -import org.junit.jupiter.api.* -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import org.kodein.di.singleton -import utils.KMariadbContainer - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class NoteRepositoryTest { - - @Nested - inner class DB { - private val mariadb = KMariadbContainer().apply { start() } - - private val kodein = DI { - import(mainModule, allowOverride = true) - bind(overrides = true) from singleton { mariadb.datasource() } - } - - init { - val migration by kodein.instance() - migration.migrate() - } - - @Test - fun run() { - val userService by kodein.instance() - val user = runBlocking { userService.create("test", "test")!! } - val noteService by kodein.instance() - val note = runBlocking { - noteService.create( - user.id, - Note { - this.title = "a note" - this.markdown = - """ - |# Title - | - |😝😝😝😝 - |another line - """.trimMargin() - this.tags = listOf("a", "tag") - } - ) - } - - println(note) - - val objectMapper = ObjectMapper().apply { - registerModule(JavaTimeModule()) - registerModule(KtormModule()) - disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT) - dateFormat = StdDateFormat() - } - val json = objectMapper.writeValueAsString(note) - println(json) - } - - @Test - fun `test tag list`() { - val userService by kodein.instance() - val user = runBlocking { userService.create("test", "test")!! } - val user2 = runBlocking { userService.create("user2", "test")!! } - - val noteService by kodein.instance() - runBlocking { - noteService.create( - user.id, - Note { - title = "test" - markdown = "" - tags = listOf("same") - } - ) - noteService.create( - user.id, - Note { - title = "test2" - markdown = "" - tags = listOf("same") - } - ) - noteService.create( - user.id, - Note { - title = "test3" - markdown = "" - tags = listOf("another") - } - ) - noteService.create( - user2.id, - Note { - title = "test" - markdown = "" - tags = listOf("user2") - } - ) - } - val user1Tags = runBlocking { noteService.getTags(user.id) } - user1Tags `should be equal to` listOf("same", "another") - val user2Tags = runBlocking { noteService.getTags(user2.id) } - user2Tags `should be equal to` listOf("user2") - } - - @Test - fun `test patch note`() { - val noteService by kodein.instance() - val userService by kodein.instance() - val user = runBlocking { userService.create(Faker().name().username(), "test") }!! - val note = runBlocking { - noteService.create( - user.id, - Note { - this.title = "title" - this.markdown = "old content" - this.tags = emptyList() - } - ) - } - val get = runBlocking { noteService.find(user.id, note.uuid) } - - runBlocking { - noteService.updateNote( - user.id, - Note { - uuid = note.uuid - title = "new title" - } - ) - } - - val updated = runBlocking { noteService.find(user.id, note.uuid) } - println("updated: $updated") - } - } - - @Nested - inner class NoteValidation { - @Test - fun `test update constraints`() { - - val fieldPresentConstraint = object : CustomConstraint { - override fun defaultMessageFormat() = "fmt {0} {1} {2}" - - override fun messageKey() = "title|content|tags" - - override fun test(note: Note): Boolean { - val hasTitle = note["title"] != null - val hasContent = note["content"] != null - val hasTags = note["tags"] != null && note.tags.isNotEmpty() - return hasTitle || hasContent || hasTags - } - } - val userValidator: Validator = ValidatorBuilder() - .constraintOnTarget(fieldPresentConstraint, "present") - .build() - - userValidator.validate( - Note { - title = "this is a title" - } - ).isValid `should be equal to` true - - userValidator.validate( - Note { - markdown = "this is a title" - } - ).isValid `should be equal to` true - - userValidator.validate( - Note { - tags = emptyList() - } - ).isValid `should be equal to` false - - userValidator.validate( - Note { - tags = listOf("tags") - } - ).isValid `should be equal to` true - - userValidator.validate( - Note { - tags = listOf("tags") - title = "This is a title" - } - ).isValid `should be equal to` true - - userValidator.validate( - Note { - tags = listOf("tags") - title = "This is a title" - markdown = - """ - |# This is - | - |some markdown content - """.trimMargin() - } - ).isValid `should be equal to` true - - userValidator.validate( - Note { - tags = listOf("tags") - title = "This is a title" - markdown = - """ - |# This is - | - |some markdown content - """.trimMargin() - } - ).isValid `should be equal to` true - - userValidator.validate(Note()).isValid `should be equal to` false - } - } - - @Nested - inner class NoteEntity { - - @Test - fun `test entity`() { - val objectMapper = ObjectMapper().apply { - registerModule(JavaTimeModule()) - registerModule(KtormModule()) - disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT) - dateFormat = StdDateFormat() - } - val note: Note = objectMapper.readValue("""{"uuid": "2007e4d7-2986-4188-bde1-b99916d94bad"}""") - println(note.uuid) - println(note.uuid::class.qualifiedName) - println(note.uuid.leastSignificantBits) - println(note.uuid.mostSignificantBits) - } - } -} diff --git a/test/integration/services/UserRepositoryTest.kt b/test/integration/services/UserRepositoryTest.kt deleted file mode 100644 index cc8f457..0000000 --- a/test/integration/services/UserRepositoryTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package integration.services - -import be.vandewalleh.features.Migration -import be.vandewalleh.mainModule -import be.vandewalleh.repositories.UserRepository -import kotlinx.coroutines.runBlocking -import org.amshove.kluent.* -import org.junit.jupiter.api.* -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import org.kodein.di.singleton -import utils.KMariadbContainer - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@TestMethodOrder(MethodOrderer.OrderAnnotation::class) -class UserRepositoryTest { - - private val mariadb = KMariadbContainer().apply { start() } - - private val kodein = DI { - import(mainModule, allowOverride = true) - bind(overrides = true) from singleton { mariadb.datasource() } - } - - private val userService by kodein.instance() - - init { - val migration by kodein.instance() - migration.migrate() - } - - @Test - @Order(1) - fun `test create user`() { - runBlocking { - val username = "hubert" - val password = "password" - - userService.create(username, password) - val user = userService.find(username) - user `should not be` null - user?.username `should be equal to` username - } - } - - @Test - @Order(2) - fun `test create same user`() { - runBlocking { - userService.create(username = "hubert", password = "password") `should be` null - } - } - - @Test - @Order(3) - fun `test delete user`() { - runBlocking { - val id = userService.find("hubert")!!.id - userService.delete(id) - - userService.find("hubert") `should be` null - userService.find(id) `should be` null - } - } -} diff --git a/test/unit/MarkdownTest.kt b/test/unit/MarkdownTest.kt deleted file mode 100644 index f6649e6..0000000 --- a/test/unit/MarkdownTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package unit - -import be.vandewalleh.markdown.Markdown -import be.vandewalleh.markdown.Meta -import org.amshove.kluent.* -import org.junit.jupiter.api.* - -class MarkdownTest { - - @Test - fun a() { - val md = Markdown() - fun Markdown.convertTrim(input: String) = convertToMarkdown(input).trim() - - md.convertTrim("# title") `should be equal to` "

title

" - - md.convertTrim( - """ - |- 1 - |- 2 - |- 3 - """.trimMargin() - ) `should be equal to` - """ - |
    - |
  • 1
  • - |
  • 2
  • - |
  • 3
  • - |
- """.trimMargin() - - // md.parseMeta("title: test") `should be equal to` Meta("test") - - md.parseMeta( - """ - |title: test - |tags: - | - a - | - b - |""".trimMargin() - ) `should be equal to` - Meta("test", listOf("a", "b")) - } - - @Test - fun testMeta() { - val md = Markdown() - val out = md.renderDocument( - """ - | - |--- - | - |title: test - |tags: [a,b] - |--- - | - |# Title - | - |- a - |- b - """.trimMargin() - ) - - println(out) - } -} diff --git a/test/unit/validation/RegisterValidationTest.kt b/test/unit/validation/RegisterValidationTest.kt deleted file mode 100644 index ac983ba..0000000 --- a/test/unit/validation/RegisterValidationTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -package unit.validation - -import be.vandewalleh.entities.User -import be.vandewalleh.validation.registerValidator -import org.amshove.kluent.* -import org.junit.jupiter.api.* -import utils.firstInvalid - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class RegisterValidationTest { - - @Test - fun `valid register test`() { - val violations = registerValidator.validate( - User { - username = "hubert" - password = "definitelyNotMyPassword" - } - ) - - violations.isValid `should be equal to` true - } - - @Test - fun `username too long test`() { - val violations = registerValidator.validate( - User { - username = "6X9iboWmEOWjVjkO328ReTJ1gGPTTmB/ZGgBLhB6EzAJoWkJht8" - password = "definitelyNotMyPassword" - } - ) - - violations.isValid `should be equal to` false - violations.firstInvalid `should be equal to` "username" - } -} diff --git a/test/utils/Assertions.kt b/test/utils/Assertions.kt deleted file mode 100644 index 6b9bad8..0000000 --- a/test/utils/Assertions.kt +++ /dev/null @@ -1,19 +0,0 @@ -package utils - -import org.skyscreamer.jsonassert.JSONAssert - -infix fun String?.shouldBeEqualToJson(expected: String?) = JSONAssert.assertEquals(expected, this, false) - -infix fun String?.`should be equal to json`(expected: String?) = shouldBeEqualToJson(expected) - -infix fun String?.shouldStrictlyBeEqualToJson(expected: String?) = JSONAssert.assertEquals(expected, this, true) - -infix fun String?.`should strictly be equal to json`(expected: String?) = shouldStrictlyBeEqualToJson(expected) - -infix fun String?.shouldNotStrictlyBeEqualToJson(expected: String?) = JSONAssert.assertNotEquals(expected, this, true) - -infix fun String?.`should not strictly be equal to json`(expected: String?) = shouldNotStrictlyBeEqualToJson(expected) - -infix fun String?.shouldNotBeEqualToJson(expected: String?) = JSONAssert.assertNotEquals(expected, this, false) - -infix fun String?.`should not be equal to json`(expected: String?) = shouldNotBeEqualToJson(expected) diff --git a/test/utils/JsonAssertExtensions.kt b/test/utils/JsonAssertExtensions.kt deleted file mode 100644 index 1ee2569..0000000 --- a/test/utils/JsonAssertExtensions.kt +++ /dev/null @@ -1,23 +0,0 @@ -package utils - -import org.json.JSONObject - -operator fun JSONObject.set(name: String, value: String) { - this.put(name, value) -} - -operator fun JSONObject.set(name: String, value: Double) { - this.put(name, value) -} - -operator fun JSONObject.set(name: String, value: Long) { - this.put(name, value) -} - -operator fun JSONObject.set(name: String, value: Int) { - this.put(name, value) -} - -operator fun JSONObject.set(name: String, value: Boolean) { - this.put(name, value) -} diff --git a/test/utils/KMariadbContainer.kt b/test/utils/KMariadbContainer.kt deleted file mode 100644 index 0ff742c..0000000 --- a/test/utils/KMariadbContainer.kt +++ /dev/null @@ -1,17 +0,0 @@ -package utils - -import com.zaxxer.hikari.HikariConfig -import com.zaxxer.hikari.HikariDataSource -import org.testcontainers.containers.MariaDBContainer - -class KMariadbContainer : MariaDBContainer() { - fun datasource(): HikariDataSource { - val hikariConfig = HikariConfig().apply { - jdbcUrl = this@KMariadbContainer.jdbcUrl - username = this@KMariadbContainer.username - password = this@KMariadbContainer.password - } - - return HikariDataSource(hikariConfig) - } -} diff --git a/test/utils/KtorTestingExtensions.kt b/test/utils/KtorTestingExtensions.kt deleted file mode 100644 index bf578b7..0000000 --- a/test/utils/KtorTestingExtensions.kt +++ /dev/null @@ -1,51 +0,0 @@ -package utils - -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod -import io.ktor.server.testing.* -import org.json.JSONObject - -fun TestApplicationRequest.json(block: (JSONObject) -> Unit) { - addHeader(HttpHeaders.ContentType, "application/json") - setBody(JSONObject().apply(block).toString()) -} - -fun TestApplicationRequest.setToken(token: String) { - addHeader(HttpHeaders.Authorization, "Bearer $token") -} - -fun TestApplicationEngine.post( - uri: String, - setup: TestApplicationRequest.() -> Unit = {} -): TestApplicationResponse = handleRequest { - this.uri = uri - this.method = HttpMethod.Post - setup() -}.response - -fun TestApplicationEngine.get( - uri: String, - setup: TestApplicationRequest.() -> Unit = {} -): TestApplicationResponse = handleRequest { - this.uri = uri - this.method = HttpMethod.Get - setup() -}.response - -fun TestApplicationEngine.delete( - uri: String, - setup: TestApplicationRequest.() -> Unit = {} -): TestApplicationResponse = handleRequest { - this.uri = uri - this.method = HttpMethod.Delete - setup() -}.response - -fun TestApplicationEngine.put( - uri: String, - setup: TestApplicationRequest.() -> Unit = {} -): TestApplicationResponse = handleRequest { - this.uri = uri - this.method = HttpMethod.Put - setup() -}.response diff --git a/test/utils/OrgJsonExtensions.kt b/test/utils/OrgJsonExtensions.kt deleted file mode 100644 index 123ee6d..0000000 --- a/test/utils/OrgJsonExtensions.kt +++ /dev/null @@ -1,5 +0,0 @@ -package utils - -import org.json.JSONObject - -fun JSONObject.keyList(): List = keys().asSequence().toList() diff --git a/test/utils/ValidationExtensions.kt b/test/utils/ValidationExtensions.kt deleted file mode 100644 index 91e461a..0000000 --- a/test/utils/ValidationExtensions.kt +++ /dev/null @@ -1,6 +0,0 @@ -package utils - -import am.ik.yavi.core.ConstraintViolations - -val ConstraintViolations.firstInvalid: Any? - get() = this.violations().firstOrNull()?.name() diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 979eaa6..0000000 --- a/yarn.lock +++ /dev/null @@ -1,4375 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@fullhuman/postcss-purgecss@^2.1.2": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz#50a954757ec78696615d3e118e3fee2d9291882e" - integrity sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw== - dependencies: - postcss "7.0.32" - purgecss "^2.3.0" - -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== - dependencies: - "@nodelib/fs.stat" "2.0.3" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== - dependencies: - "@nodelib/fs.scandir" "2.1.3" - fastq "^1.6.0" - -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - -"@types/q@^1.5.1": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" - integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== - -accepts@1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" - integrity sha1-w8p0NJOGSMPg2cHjKN1otiLChMo= - dependencies: - mime-types "~2.1.11" - negotiator "0.6.1" - -acorn-globals@^1.0.3: - version "1.0.9" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf" - integrity sha1-VbtemGkVB7dFedBRNBMhfDgMVM8= - dependencies: - acorn "^2.1.0" - -acorn-node@^1.6.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" - integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== - dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" - -acorn-walk@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^1.0.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014" - integrity sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ= - -acorn@^2.1.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" - integrity sha1-q259nYhqrKiwhbwzEreaGYQz8Oc= - -acorn@^7.0.0: - version "7.3.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" - integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== - -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" - integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= - -aglio-theme-olio@^1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/aglio-theme-olio/-/aglio-theme-olio-1.6.3.tgz#918679e7ca4d8fb363a92daf9002ade48a265319" - integrity sha1-kYZ558pNj7NjqS2vkAKt5IomUxk= - dependencies: - coffee-script "^1.8.0" - highlight.js "^8.4.0" - jade "^1.8.2" - less "^2.1.2" - markdown-it "^4.3.0" - markdown-it-anchor "^2.1.0" - markdown-it-checkbox "^1.1.0" - markdown-it-container "^1.0.0" - markdown-it-emoji "^1.0.0" - moment "^2.8.4" - stylus "^0.51.1" - -aglio@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/aglio/-/aglio-2.3.0.tgz#9f7462f01520996415278a02d0e20b36ec96adcc" - integrity sha1-n3Ri8BUgmWQVJ4oC0OILNuyWrcw= - dependencies: - aglio-theme-olio "^1.6.3" - chokidar "^1.4.1" - cli-color "^1.1.0" - drafter "^1.0.0" - pretty-error "^1.2.0" - serve-static "^1.10.0" - socket.io "^1.3.7" - yargs "^3.31.0" - -ajv@^4.9.1: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - -ansi-regex@^2.0.0, ansi-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== - dependencies: - "@types/color-name" "^1.1.1" - color-convert "^2.0.1" - -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7, argparse@~1.0.2: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= - dependencies: - arr-flatten "^1.0.1" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.0.1, arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -arraybuffer.slice@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" - integrity sha1-8zshWfBTKj8xB6JywMz70a0peco= - -asap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/asap/-/asap-1.0.0.tgz#b2a45da5fdfa20b0496fc3768cc27c12fa916a7d" - integrity sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0= - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@^9.4.5: - version "9.8.5" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.5.tgz#2c225de229ddafe1d1424c02791d0c3e10ccccaa" - integrity sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg== - dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001097" - colorette "^1.2.0" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" - -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= - -aws4@^1.2.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" - integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== - -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= - -base64id@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" - integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY= - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= - dependencies: - callsite "1.0.0" - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -blob@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" - integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE= - -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= - -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= - dependencies: - hoek "2.x.x" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserslist@^4.0.0, browserslist@^4.12.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.13.0.tgz#42556cba011e1b0a2775b611cba6a8eca18e940d" - integrity sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ== - dependencies: - caniuse-lite "^1.0.30001093" - electron-to-chromium "^1.3.488" - escalade "^3.0.1" - node-releases "^1.1.58" - -bytes@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= - -camelcase-css@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= - -camelcase@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0: - version "1.0.30001102" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001102.tgz#3275e7a8d09548f955f665e532df88de0b63741a" - integrity sha512-fOjqRmHjRXv1H1YD6QVLb96iKqnu17TjcLSaX64TwhGYed0P1E1CCWZ9OujbbK4Z/7zax7zAzvQidzdtjx8RcA== - -caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097: - version "1.0.30001100" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001100.tgz#2a58615e0c01cf716ab349b20ca4d86ef944aa4e" - integrity sha512-0eYdp1+wFCnMlCj2oudciuQn2B9xAFq3WpgpcBIZTxk/1HNA/O2YA7rpeYhnOqsqAJq1AHUgx6i1jtafg7m2zA== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -"chalk@^3.0.0 || ^4.0.0", chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -character-parser@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-1.2.1.tgz#c0dde4ab182713b919b970959a123ecc1a30fcd6" - integrity sha1-wN3kqxgnE7kZuXCVmhI+zBow/NY= - -chokidar@^1.4.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg= - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -chokidar@^3.3.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" - integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-css@^3.1.9: - version "3.4.28" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff" - integrity sha1-vxlF6C/ICPVWlebd6uwBQA79A/8= - dependencies: - commander "2.8.x" - source-map "0.4.x" - -cli-color@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" - integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== - dependencies: - ansi-regex "^2.1.1" - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - memoizee "^0.4.14" - timers-ext "^0.1.5" - -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - -cliui@^3.0.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -coffee-script@^1.8.0: - version "1.12.7" - resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" - integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.0.0, color@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - -colorette@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" - integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== - -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@2.8.x: - version "2.8.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" - integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ= - dependencies: - graceful-readlink ">= 1.0.0" - -commander@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commander@~2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" - integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0= - -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= - -component-emitter@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" - integrity sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM= - -component-emitter@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -constantinople@~3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-3.0.2.tgz#4b945d9937907bcd98ee575122c3817516544141" - integrity sha1-S5RdmTeQe82Y7ldRIsOBdRZUQUE= - dependencies: - acorn "^2.1.0" - -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= - dependencies: - boom "2.x.x" - -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= - -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-parse@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-1.0.4.tgz#38b0503fbf9da9f54e9c1dbda60e145c77117bdd" - integrity sha1-OLBQP7+dqfVOnB29pg4UXHcRe90= - -css-parse@1.7.x: - version "1.7.0" - resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-1.7.0.tgz#321f6cf73782a6ff751111390fc05e2c657d8c9b" - integrity sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs= - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-stringify@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/css-stringify/-/css-stringify-1.0.5.tgz#b0d042946db2953bb9d292900a6cb5f6d0122031" - integrity sha1-sNBClG2ylTu50pKQCmy19tASIDE= - -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@1.0.0-alpha.39: - version "1.0.0-alpha.39" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" - integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== - dependencies: - mdn-data "2.0.6" - source-map "^0.6.1" - -css-unit-converter@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" - integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== - -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== - -css-what@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" - integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg== - -css@~1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/css/-/css-1.0.8.tgz#9386811ca82bccc9ee7fb5a732b1e2a317c8a3e7" - integrity sha1-k4aBHKgrzMnuf7WnMrHioxfIo+c= - dependencies: - css-parse "1.0.4" - css-stringify "1.0.5" - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== - dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" - postcss-unique-selectors "^4.0.1" - -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== - dependencies: - postcss "^7.0.0" - -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@^4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== - dependencies: - css-tree "1.0.0-alpha.39" - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -debug@*: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - integrity sha1-+HBX6ZWxofauaklgZkE3vFbwOdo= - dependencies: - ms "0.7.1" - -debug@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" - integrity sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w= - dependencies: - ms "0.7.2" - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -dependency-graph@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" - integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detective@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" - integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== - dependencies: - acorn-node "^1.6.1" - defined "^1.0.0" - minimist "^1.1.1" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dom-converter@~0.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" - integrity sha1-pF71cnuJDJv/5tfIduexnLDhfzs= - dependencies: - utila "~0.3" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -domhandler@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" - integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ= - dependencies: - domelementtype "1" - -domutils@1.1: - version "1.1.6" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" - integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU= - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -drafter.js@^2.6.0: - version "2.6.7" - resolved "https://registry.yarnpkg.com/drafter.js/-/drafter.js-2.6.7.tgz#59c43a8586527340cb35bddc54180559faeef001" - integrity sha1-WcQ6hYZSc0DLNb3cVBgFWfru8AE= - -drafter@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/drafter/-/drafter-1.2.0.tgz#f4d11e8dee1f27f9a40485b8740c44361e9cf859" - integrity sha1-9NEeje4fJ/mkBIW4dAxENh6c+Fk= - dependencies: - drafter.js "^2.6.0" - optionalDependencies: - protagonist "^1.6.0" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.3.488: - version "1.3.498" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz#fd7188c8a49d6d0b5df1df55a1f1a4bf2c177457" - integrity sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -engine.io-client@~1.8.4: - version "1.8.5" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.5.tgz#fe7fb60cb0dcf2fa2859489329cb5968dedeb11f" - integrity sha512-AYTgHyeVUPitsseqjoedjhYJapNVoSPShbZ+tEUX9/73jgZ/Z3sUlJf9oYgdEBBdVhupUpUqSxH0kBCXlQnmZg== - dependencies: - component-emitter "1.2.1" - component-inherit "0.0.3" - debug "2.3.3" - engine.io-parser "1.3.2" - has-cors "1.1.0" - indexof "0.0.1" - parsejson "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" - ws "~1.1.5" - xmlhttprequest-ssl "1.5.3" - yeast "0.1.2" - -engine.io-parser@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a" - integrity sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo= - dependencies: - after "0.8.2" - arraybuffer.slice "0.0.6" - base64-arraybuffer "0.1.5" - blob "0.0.4" - has-binary "0.1.7" - wtf-8 "1.0.0" - -engine.io@~1.8.4: - version "1.8.5" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.5.tgz#4ebe5e75c6dc123dee4afdce6e5fdced21eb93f6" - integrity sha512-j1DWIcktw4hRwrv6nWx++5nFH2X64x16MAG2P0Lmi5Dvdfi3I+Jhc7JKJIdAmDJa+5aZ/imHV7dWRPy2Cqjh3A== - dependencies: - accepts "1.3.3" - base64id "1.0.0" - cookie "0.3.1" - debug "2.3.3" - engine.io-parser "1.3.2" - ws "~1.1.5" - -entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== - -entities@~1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -errno@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== - dependencies: - prr "~1.0.1" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@^2.0.3, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - -es6-weak-map@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" - integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== - dependencies: - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - es6-symbol "^3.1.1" - -escalade@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" - integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= - dependencies: - is-posix-bracket "^0.1.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= - dependencies: - is-extglob "^1.0.0" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-glob@^3.1.1: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - -fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== - dependencies: - reusify "^1.0.4" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= - -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= - dependencies: - for-in "^1.0.1" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -fs-extra@^8.0.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.0.0: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-stdin@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" - integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob@3.2.x: - version "3.2.11" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" - integrity sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0= - dependencies: - inherits "2" - minimatch "0.3" - -glob@^7.0.0, glob@^7.1.2: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globby@^11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= - -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4= - -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio= - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - -has-binary@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c" - integrity sha1-aOYesWIQyVRaClzOBqhzkS/h5ow= - dependencies: - isarray "0.0.1" - -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.0, has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - -highlight.js@^8.4.0: - version "8.9.1" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-8.9.1.tgz#b8a9c5493212a9392f0222b649c9611497ebfb88" - integrity sha1-uKnFSTISqTkvAiK2SclhFJfr+4g= - -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= - -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - -htmlparser2@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" - integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4= - dependencies: - domelementtype "1" - domhandler "2.1" - domutils "1.1" - readable-stream "1.0" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -image-size@~0.5.0: - version "0.5.5" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" - integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= - -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - 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" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= - dependencies: - is-extglob "^1.0.0" - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= - -is-promise@^2.0.0, is-promise@^2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" - integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== - -is-promise@~1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-1.0.1.tgz#31573761c057e33c2e91aab9e96da08cefbe76e5" - integrity sha1-MVc3YcBX4zwukaq56W2gjO++duU= - -is-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== - dependencies: - has-symbols "^1.0.1" - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== - dependencies: - html-comment-regex "^1.1.0" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jade@^1.8.2: - version "1.11.0" - resolved "https://registry.yarnpkg.com/jade/-/jade-1.11.0.tgz#9c80e538c12d3fb95c8d9bb9559fa0cc040405fd" - integrity sha1-nIDlOMEtP7lcjZu5VZ+gzAQEBf0= - dependencies: - character-parser "1.2.1" - clean-css "^3.1.9" - commander "~2.6.0" - constantinople "~3.0.1" - jstransformer "0.0.2" - mkdirp "~0.5.0" - transformers "2.1.0" - uglify-js "^2.4.19" - void-elements "~2.0.1" - with "~4.0.0" - -js-yaml@^3.13.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json3@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - -jsonfile@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" - integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== - dependencies: - universalify "^1.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -jstransformer@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-0.0.2.tgz#7aae29a903d196cfa0973d885d3e47947ecd76ab" - integrity sha1-eq4pqQPRls+glz2IXT5HlH7Ndqs= - dependencies: - is-promise "^2.0.0" - promise "^6.0.1" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -less@^2.1.2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/less/-/less-2.7.3.tgz#cc1260f51c900a9ec0d91fb6998139e02507b63b" - integrity sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ== - optionalDependencies: - errno "^0.1.1" - graceful-fs "^4.1.2" - image-size "~0.5.0" - mime "^1.2.11" - mkdirp "^0.5.0" - promise "^7.1.1" - request "2.81.0" - source-map "^0.5.3" - -linkify-it@~1.2.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-1.2.4.tgz#0773526c317c8fd13bd534ee1d180ff88abf881a" - integrity sha1-B3NSbDF8j9E71TTuHRgP+Iq/iBo= - dependencies: - uc.micro "^1.0.1" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - -lodash.toarray@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" - integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash@^4.17.11, lodash@^4.17.15: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== - -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= - -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= - -lru-queue@0.1: - version "0.1.0" - resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" - integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= - dependencies: - es5-ext "~0.10.2" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -markdown-it-anchor@^2.1.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-2.7.1.tgz#372f67da7a4c4632ad0ebe4c9691726efe25342a" - integrity sha1-Ny9n2npMRjKtDr5MlpFybv4lNCo= - dependencies: - string "^3.0.1" - -markdown-it-checkbox@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/markdown-it-checkbox/-/markdown-it-checkbox-1.1.0.tgz#20cff97f33d77d172f9dcf1bcfc92cecc5330fac" - integrity sha1-IM/5fzPXfRcvnc8bz8ks7MUzD6w= - dependencies: - underscore "^1.8.2" - -markdown-it-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-1.0.0.tgz#d2e8269d467c056c6c9ef6b7b16f129a19b9f36d" - integrity sha1-0ugmnUZ8BWxsnva3sW8Smhm5820= - -markdown-it-emoji@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz#9bee0e9a990a963ba96df6980c4fddb05dfb4dcc" - integrity sha1-m+4OmpkKljupbfaYDE/dsF37Tcw= - -markdown-it@^4.3.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-4.4.0.tgz#3df373dbea587a9a7fef3e56311b68908f75c414" - integrity sha1-PfNz2+pYepp/7z5WMRtokI91xBQ= - dependencies: - argparse "~1.0.2" - entities "~1.1.1" - linkify-it "~1.2.0" - mdurl "~1.0.0" - uc.micro "^1.0.0" - -math-random@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" - integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -mdn-data@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" - integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== - -mdurl@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= - -memoizee@^0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" - integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== - dependencies: - d "1" - es5-ext "^0.10.45" - es6-weak-map "^2.0.2" - event-emitter "^0.3.5" - is-promise "^2.1" - lru-queue "0.1" - next-tick "1" - timers-ext "^0.1.5" - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^2.1.5: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -micromatch@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.7: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" - -mime@1.6.0, mime@^1.2.11: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -minimatch@0.3: - version "0.3.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" - integrity sha1-J12O2qxPG7MyZHIInnlJyDlGmd0= - dependencies: - lru-cache "2" - sigmund "~1.0.0" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.1.1, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@0.3.x: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= - -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -moment@^2.8.4: - version "2.27.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" - integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== - -ms@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" - integrity sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg= - -ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" - integrity sha1-riXPJRKziFodldfwN4aNhDESR2U= - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -nan@^2.12.1, nan@^2.6.2: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= - -next-tick@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" - integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== - -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - -node-emoji@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" - integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== - dependencies: - lodash.toarray "^4.4.0" - -node-releases@^1.1.58: - version "1.1.59" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.59.tgz#4d648330641cec704bff10f8e4fe28e453ab8e8e" - integrity sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw== - -normalize-path@^2.0.0, normalize-path@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -normalize.css@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" - integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== - -nth-check@^1.0.2, nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -oauth-sign@~0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - -object-assign@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" - integrity sha1-ejs9DpgGPUP0wD8uiubNUahog6A= - -object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -object.values@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -optimist@~0.3.5: - version "0.3.7" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9" - integrity sha1-yQlBrVnkJzMokjB00s8ufLxuwNk= - dependencies: - wordwrap "~0.0.2" - -options@>=0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - integrity sha1-7CLTEoBrtT5zF3Pnza788cZDEo8= - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parsejson@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" - integrity sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs= - dependencies: - better-assert "~1.0.0" - -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - dependencies: - better-assert "~1.0.0" - -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -performance-now@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" - integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU= - -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -postcss-calc@^7.0.1: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" - integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== - dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-cli@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-7.1.1.tgz#260f9546be260b2149bf32e28d785a0d79c9aab8" - integrity sha512-bYQy5ydAQJKCMSpvaMg0ThPBeGYqhQXumjbFOmWnL4u65CYXQ16RfS6afGQpit0dGv/fNzxbdDtx8dkqOhhIbg== - dependencies: - chalk "^4.0.0" - chokidar "^3.3.0" - dependency-graph "^0.9.0" - fs-extra "^9.0.0" - get-stdin "^7.0.0" - globby "^11.0.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - postcss-reporter "^6.0.0" - pretty-hrtime "^1.0.3" - read-cache "^1.0.0" - yargs "^15.0.2" - -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== - dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" - -postcss-functions@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" - integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= - dependencies: - glob "^7.1.2" - object-assign "^4.1.1" - postcss "^6.0.9" - postcss-value-parser "^3.3.0" - -postcss-hash@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-hash/-/postcss-hash-2.0.0.tgz#185632a6d51383243197b7b9fde71443795d316c" - integrity sha512-hlRGfXYnpZrTg+g8Jdek/Z0DX3XHQ9tHcXhvlTnQFLnKuq/e2UZELuJNxgPg9oMxg9U6UsBkPCzMVmmGG4dgVw== - dependencies: - mkdirp "^0.5.1" - postcss "^7.0.2" - -postcss-js@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" - integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== - dependencies: - camelcase-css "^2.0.1" - postcss "^7.0.18" - -postcss-load-config@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" - integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" - -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== - dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -postcss-nested@^4.1.1: - version "4.2.3" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.3.tgz#c6f255b0a720549776d220d00c4b70cd244136f6" - integrity sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw== - dependencies: - postcss "^7.0.32" - postcss-selector-parser "^6.0.2" - -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" - -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== - dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== - dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== - dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== - dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-reporter@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-6.0.1.tgz#7c055120060a97c8837b4e48215661aafb74245f" - integrity sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw== - dependencies: - chalk "^2.4.1" - lodash "^4.17.11" - log-symbols "^2.2.0" - postcss "^7.0.7" - -postcss-selector-parser@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" - integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== - dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== - dependencies: - is-svg "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" - -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== - dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" - -postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss@7.0.32, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.7: - version "7.0.32" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -postcss@^6.0.9: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - -pretty-error@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-1.2.0.tgz#f28058414bf34dd2e993496219337e9d25a5ada5" - integrity sha1-8oBYQUvzTdLpk0liGTN+nSWlraU= - dependencies: - renderkid "~1.0.0" - utila "~0.4" - -pretty-hrtime@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -promise@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-6.1.0.tgz#2ce729f6b94b45c26891ad0602c5c90e04c6eef6" - integrity sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY= - dependencies: - asap "~1.0.0" - -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - -promise@~2.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-2.0.0.tgz#46648aa9d605af5d2e70c3024bf59436da02b80e" - integrity sha1-RmSKqdYFr10ucMMCS/WUNtoCuA4= - dependencies: - is-promise "~1" - -protagonist@^1.6.0: - version "1.6.8" - resolved "https://registry.yarnpkg.com/protagonist/-/protagonist-1.6.8.tgz#16d5d6316f1b6d4e493ec9450639dbc35503be6e" - integrity sha1-FtXWMW8bbU5JPslFBjnbw1UDvm4= - dependencies: - nan "^2.6.2" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -purgecss@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-2.3.0.tgz#5327587abf5795e6541517af8b190a6fb5488bb3" - integrity sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ== - dependencies: - commander "^5.0.0" - glob "^7.0.0" - postcss "7.0.32" - postcss-selector-parser "^6.0.2" - -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= - -randomatic@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" - integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= - dependencies: - pify "^2.3.0" - -readable-stream@1.0: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.2: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readdirp@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== - dependencies: - picomatch "^2.2.1" - -reduce-css-calc@^2.1.6: - version "2.1.7" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" - integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== - dependencies: - css-unit-converter "^1.1.1" - postcss-value-parser "^3.3.0" - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== - dependencies: - is-equal-shallow "^0.1.3" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -renderkid@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-1.0.0.tgz#29e16c5f64be9bdd52989b6a630ac799c738158b" - integrity sha1-KeFsX2S+m91SmJtqYwrHmcc4FYs= - dependencies: - css-select "^1.1.0" - dom-converter "~0.1" - htmlparser2 "~3.3.0" - strip-ansi "^3.0.0" - utila "~0.3" - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -request@2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA= - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.14.2: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== - dependencies: - path-parse "^1.0.6" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= - dependencies: - align-text "^0.1.1" - -run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== - -safe-buffer@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= - -sax@~1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serve-static@^1.10.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= - dependencies: - hoek "2.x.x" - -socket.io-adapter@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b" - integrity sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s= - dependencies: - debug "2.3.3" - socket.io-parser "2.3.1" - -socket.io-client@1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.4.tgz#ec9f820356ed99ef6d357f0756d648717bdd4281" - integrity sha1-7J+CA1btme9tNX8HVtZIcXvdQoE= - dependencies: - backo2 "1.0.2" - component-bind "1.0.0" - component-emitter "1.2.1" - debug "2.3.3" - engine.io-client "~1.8.4" - has-binary "0.1.7" - indexof "0.0.1" - object-component "0.0.3" - parseuri "0.0.5" - socket.io-parser "2.3.1" - to-array "0.1.4" - -socket.io-parser@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0" - integrity sha1-3VMgJRA85Clpcya+/WQAX8/ltKA= - dependencies: - component-emitter "1.1.2" - debug "2.2.0" - isarray "0.0.1" - json3 "3.3.2" - -socket.io@^1.3.7: - version "1.7.4" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.4.tgz#2f7ecedc3391bf2d5c73e291fe233e6e34d4dd00" - integrity sha1-L37O3DORvy1cc+KR/iM+bjTU3QA= - dependencies: - debug "2.3.3" - engine.io "~1.8.4" - has-binary "0.1.7" - object-assign "4.1.0" - socket.io-adapter "0.5.0" - socket.io-client "1.7.4" - socket.io-parser "2.3.1" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@0.1.x, source-map@~0.1.7: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - integrity sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y= - dependencies: - amdefine ">=0.0.4" - -source-map@0.4.x: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string@^3.0.1: - version "3.3.3" - resolved "https://registry.yarnpkg.com/string/-/string-3.3.3.tgz#5ea211cd92d228e184294990a6cc97b366a77cb0" - integrity sha1-XqIRzZLSKOGEKUmQpsyXs2anfLA= - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringstream@~0.0.4: - version "0.0.6" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" - integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -stylus@^0.51.1: - version "0.51.1" - resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.51.1.tgz#d75c405c1d87d5e00ca5758d311cd79360f0fb99" - integrity sha1-11xAXB2H1eAMpXWNMRzXk2Dw+5k= - dependencies: - css-parse "1.7.x" - debug "*" - glob "3.2.x" - mkdirp "0.3.x" - sax "0.5.x" - source-map "0.1.x" - -supports-color@^5.3.0, supports-color@^5.4.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== - dependencies: - has-flag "^4.0.0" - -svgo@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -tailwindcss@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.5.1.tgz#a3eb10024f8e10786b8b6badcf9665b013bd03ee" - integrity sha512-mBOxIk+U+9xECC6wllWiupPVfLuwTDvHb4d+8XTdZ8oYrZEH+NpFSlbATF5xWuCJQxDOZ1Dz7C0KN5tylcFhFg== - dependencies: - "@fullhuman/postcss-purgecss" "^2.1.2" - autoprefixer "^9.4.5" - browserslist "^4.12.0" - bytes "^3.0.0" - chalk "^3.0.0 || ^4.0.0" - color "^3.1.2" - detective "^5.2.0" - fs-extra "^8.0.0" - lodash "^4.17.15" - node-emoji "^1.8.1" - normalize.css "^8.0.1" - postcss "^7.0.11" - postcss-functions "^3.0.0" - postcss-js "^2.0.0" - postcss-nested "^4.1.1" - postcss-selector-parser "^6.0.0" - pretty-hrtime "^1.0.3" - reduce-css-calc "^2.1.6" - resolve "^1.14.2" - -timers-ext@^0.1.5: - version "0.1.7" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" - integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== - dependencies: - es5-ext "~0.10.46" - next-tick "1" - -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - -tough-cookie@~2.3.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== - dependencies: - punycode "^1.4.1" - -transformers@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/transformers/-/transformers-2.1.0.tgz#5d23cb35561dd85dc67fb8482309b47d53cce9a7" - integrity sha1-XSPLNVYd2F3Gf7hIIwm0fVPM6ac= - dependencies: - css "~1.0.8" - promise "~2.0" - uglify-js "~2.2.5" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== - -uc.micro@^1.0.0, uc.micro@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - -uglify-js@^2.4.19: - version "2.8.29" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" - integrity sha1-KcVzMUgFe7Th913zW3qcty5qWd0= - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - -uglify-js@~2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.2.5.tgz#a6e02a70d839792b9780488b7b8b184c095c99c7" - integrity sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc= - dependencies: - optimist "~0.3.5" - source-map "~0.1.7" - -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= - -ultron@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po= - -underscore@^1.8.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" - integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -universalify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" - integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== - -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - -utila@~0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.3.3.tgz#d7e8e7d7e309107092b05f8d9688824d633a4226" - integrity sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY= - -utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= - -uuid@^3.0.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -void-elements@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= - -window-size@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= - -with@~4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/with/-/with-4.0.3.tgz#eefd154e9e79d2c8d3417b647a8f14d9fecce14e" - integrity sha1-7v0VTp550sjTQXtkeo8U2f7M4U4= - dependencies: - acorn "^1.0.1" - acorn-globals "^1.0.3" - -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= - -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -ws@~1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51" - integrity sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w== - dependencies: - options ">=0.0.5" - ultron "1.0.x" - -wtf-8@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" - integrity sha1-OS2LotDxw00e4tYw8V0O+2jhBIo= - -xmlhttprequest-ssl@1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" - integrity sha1-GFqIjATspGw+QHDZn3tJ3jUomS0= - -xtend@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= - -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@^15.0.2: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - -yargs@^3.31.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" - integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= - dependencies: - camelcase "^2.0.1" - cliui "^3.0.3" - decamelize "^1.1.1" - os-locale "^1.4.0" - string-width "^1.0.1" - window-size "^0.1.4" - y18n "^3.2.0" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" - -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=