diff --git a/.changeset/shy-cameras-tease.md b/.changeset/shy-cameras-tease.md new file mode 100644 index 00000000000..070c5fbd55b --- /dev/null +++ b/.changeset/shy-cameras-tease.md @@ -0,0 +1,15 @@ +--- +'@keystonejs/adapter-prisma': major +'@keystonejs/fields': major +'@keystonejs/fields-auto-increment': major +'@keystonejs/fields-cloudinary-image': major +'@keystonejs/fields-content': major +'@keystonejs/fields-location-google': major +'@keystonejs/fields-mongoid': major +'@keystonejs/fields-oembed': major +'@keystonejs/fields-unsplash': major +'@keystonejs/test-utils': major +'@keystonejs/api-tests': major +--- + +Added support for a Prisma adapter to Keystone. diff --git a/.gitignore b/.gitignore index b90088a3a5d..6d14263a45a 100644 --- a/.gitignore +++ b/.gitignore @@ -113,4 +113,6 @@ tags # End of https://www.gitignore.io/api/vim projects/ -temp/ \ No newline at end of file +temp/ + +.api-test-prisma-clients diff --git a/.prettierignore b/.prettierignore index eee6805410e..8b7c793c4bd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,3 +12,4 @@ packages/arch/www/public/**/* **/dist **/.next **/.keystone +.api-test-prisma-clients diff --git a/package.json b/package.json index c833a744f45..e4e87eb8df0 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "repository": "https://github.com/keystonejs/keystone", "homepage": "https://github.com/keystonejs/keystone", "scripts": { + "prisma": "prisma", "dev": "yarn demo todo dev", "demo": "yarn --cwd ./examples", "website": "manypkg run @keystonejs/website", @@ -25,7 +26,7 @@ "lint:types": "tsc", "lint": "yarn lint:prettier && yarn lint:eslint && yarn lint:markdown && yarn lint:types", "test": "yarn lint && yarn test:unit && yarn cypress:run", - "test:unit": "cross-env DISABLE_LOGGING=true NODE_ENV=test jest --maxWorkers=1 --logHeapUsage", + "test:unit": "cross-env DISABLE_LOGGING=true NODE_ENV=test jest --no-watchman --maxWorkers=1 --logHeapUsage", "test:unit:debug": "cross-env NODE_ENV=test node --inspect-brk `which jest` --runInBand", "benchmark": "yarn workspace @keystonejs/benchmarks go", "changeset": "changeset", diff --git a/packages/adapter-prisma/.npmignore b/packages/adapter-prisma/.npmignore new file mode 100644 index 00000000000..851b108d115 --- /dev/null +++ b/packages/adapter-prisma/.npmignore @@ -0,0 +1,2 @@ +**/*.md +**/*.test.js diff --git a/packages/adapter-prisma/CHANGELOG.md b/packages/adapter-prisma/CHANGELOG.md new file mode 100644 index 00000000000..cacdc90e9a8 --- /dev/null +++ b/packages/adapter-prisma/CHANGELOG.md @@ -0,0 +1 @@ +# @keystonejs/adapter-prisma diff --git a/packages/adapter-prisma/README.md b/packages/adapter-prisma/README.md new file mode 100644 index 00000000000..dd9d78386f5 --- /dev/null +++ b/packages/adapter-prisma/README.md @@ -0,0 +1,83 @@ + + +# Prisma database adapter + +[![View changelog](https://img.shields.io/badge/changelogs.xyz-Explore%20Changelog-brightgreen)](https://changelogs.xyz/@keystonejs/adapter-prisma) + +> The Keystone Prisma adapter is not currently production-ready. It depends on the [`prisma-migrate`](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-migrate) system which is currently flagged as `EXPERIMENTAL`. Once `prisma-migrate` is out of experimental mode we will release a production-ready version of `@keystonejs/adapter-prisma`. + +The [Prisma](https://www.prisma.io/) adapter allows Keystone to connect a database using a client generated by Prisma. + +> This adapter currently only supports `PostgreSQL`. Future releases will enable support for all database backends which are [supported by Prisma](https://www.prisma.io/docs/reference/database-connectors/database-features). + +## Usage + +```javascript +const { PrismaAdapter } = require('@keystonejs/adapter-prisma'); + +const keystone = new Keystone({ + adapter: new PrismaAdapter({ url: 'postgres://...' }), +}); +``` + +## Config + +### `url` + +_**Default:**_ `DATABASE_URL` + +The connection string for your database, in the form `postgres://:@:/`. +By default it will use the value of the environment variable `DATABASE_URL`. + +### `getPrismaPath` + +_**Default:**_ `({ prismaSchema }) => '.prisma'` + +A function which returns a directory name for storing the generated Prisma schema and client. + +### `getDbSchemaName` + +_**Default:**_ `({ prismaSchema }) => 'public'` + +A function which returns a database schema name to use for storage of all Keystone tables in your database. + +> You can also set the schema name by including the suffix `?schema=...` in your `DATABASE_URL` or `url`. In this case you should set this value to `() => null`. + +### `enableLogging` + +_**Default:**_ `false` + +Enables logging at the [`query`](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/logging#overview) level in the Prisma client. + +### `dropDatabase` + +_**Default:**_ `false` + +Allow the adapter to drop the entire database and recreate the tables / foreign keys based on the list schema in your application. This option is ignored in production, i.e. when the environment variable NODE_ENV === 'production'. + +## Setup + +Before running Keystone with the Prisma adapter you will need to have a PostgreSQL database to connect to. + +If you already have a database then you can use its connection string in the `url` config option. +If you don't have a database already then you can create one locally with the following commands. + +```shell allowCopy=false showLanguage=false +createdb -U postgres keystone +psql keystone -U postgres -c "CREATE USER keystone5 PASSWORD 'k3yst0n3'" +psql keystone -U postgres -c "GRANT ALL ON DATABASE keystone TO keystone5;" +``` + +If using the above, you will want to set a connection string of: + +```javascript +const keystone = new Keystone({ + adapter: new PrismaAdapter({ url: `postgres://keystone5:k3yst0n3@localhost:5432/keystone` }), +}); +``` + +See the [adapters setup](/docs/quick-start/adapters.md) guide for more details on how to setup a database. diff --git a/packages/adapter-prisma/index.js b/packages/adapter-prisma/index.js new file mode 100644 index 00000000000..775cbae4cd9 --- /dev/null +++ b/packages/adapter-prisma/index.js @@ -0,0 +1,3 @@ +const { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter } = require('./lib/adapter-prisma'); + +module.exports = { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter }; diff --git a/packages/adapter-prisma/lib/adapter-prisma.js b/packages/adapter-prisma/lib/adapter-prisma.js new file mode 100644 index 00000000000..b5af3eb0a6e --- /dev/null +++ b/packages/adapter-prisma/lib/adapter-prisma.js @@ -0,0 +1,621 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); +const cuid = require('cuid'); +const { getGenerators, formatSchema } = require('@prisma/sdk'); +const { BaseKeystoneAdapter, BaseListAdapter, BaseFieldAdapter } = require('@keystonejs/keystone'); +const { defaultObj, mapKeys, identity, flatten } = require('@keystonejs/utils'); + +class PrismaAdapter extends BaseKeystoneAdapter { + constructor() { + super(...arguments); + this.name = 'prisma'; + + this.getPrismaPath = this.config.getPrismaPath || (() => '.prisma'); + this.getDbSchemaName = this.config.getDbSchemaName || (() => 'public'); + this.enableLogging = this.config.enableLogging || false; + this.url = this.config.url || process.env.DATABASE_URL; + } + + async _connect({ rels }) { + await this._generateClient(rels); + const { PrismaClient } = require(this.clientPath); + this.prisma = new PrismaClient({ + log: this.enableLogging && ['query'], + datasources: { postgresql: { url: this._url() } }, + }); + await this.prisma.$connect(); + } + + _url() { + // By default we put `schema=public` onto all `DATABASE_URL` values. + // If this isn't what a user wants, they can update `getSchemaName` to return either + // a different dbSchemaName, or null if they just want to use the DATABASE_URL as it is. + // TODO: Should we default to 'public' or null? + return this.dbSchemaName ? `${this.url}?schema=${this.dbSchemaName}` : this.url; + } + + async _generateClient(rels) { + // 1. Generate a formatted schema + + // 2. Check for existing schema + // 2a1. If they're the same, we're golden + // 2a2. If they're different, generate and run a migration + // 2b. If it doesn't exist, generate and run a migration + const clientDir = 'generated-client'; + const prismaSchema = await this._generatePrismaSchema({ rels, clientDir }); + // See if there is a prisma client available for this hash + const prismaPath = this.getPrismaPath({ prismaSchema }); + this.schemaPath = path.join(prismaPath, 'schema.prisma'); + this.clientPath = path.resolve(`${prismaPath}/${clientDir}`); + this.dbSchemaName = this.getDbSchemaName({ prismaSchema }); + + // // If any of our critical directories are missing, or if the schema has changed, then + // // we've got things to do. + // const schemaState = 'matching'; + // if (schemaState !== 'matching') { + // // Write schema + // } + // if (!fs.existsSync(this.clientPath) || schemaState !== 'matching') { + // // Generate prisma client + // } + // if (/* something */ false){ + // // generate and run a migration + // } + + if ( + !fs.existsSync(this.clientPath) || + !fs.existsSync(this.schemaPath) || + fs.readFileSync(this.schemaPath, { encoding: 'utf-8' }) !== prismaSchema + ) { + if (fs.existsSync(this.clientPath)) { + const existing = fs.readFileSync(this.schemaPath, { encoding: 'utf-8' }); + if (existing === prismaSchema) { + // 2a1. If they're the same, we're golden + } else { + // 2a2. If they're different, generate and run a migration + // Write prisma file + this._writePrismaSchema({ prismaSchema }); + + this._saveMigration({ name: `keystone-${cuid()}` }); + this._executeMigrations(); + } + } else { + this._writePrismaSchema({ prismaSchema }); + + // Generate prisma client + await this._generatePrismaClient(); + + // Need to generate and run a migration!!! + this._saveMigration({ name: 'init' }); + this._executeMigrations(); + } + } + } + + async _writePrismaSchema({ prismaSchema }) { + // Make output dir (you know, just in case!) + fs.mkdirSync(this.clientPath, { recursive: true }); + + // Write prisma file + fs.writeSync(fs.openSync(this.schemaPath, 'w'), prismaSchema); + } + + async _generatePrismaClient() { + const generator = (await getGenerators({ schemaPath: this.schemaPath }))[0]; + await generator.generate(); + generator.stop(); + } + + async _generatePrismaSchema({ rels, clientDir }) { + const models = Object.values(this.listAdapters).map(listAdapter => { + const scalarFields = flatten( + listAdapter.fieldAdapters.filter(f => !f.field.isRelationship).map(f => f.getPrismaSchema()) + ); + const relFields = [ + ...flatten( + listAdapter.fieldAdapters + .map(({ field }) => field) + .filter(f => f.isRelationship) + .map(f => { + const r = rels.find(r => r.left === f || r.right === f); + const isLeft = r.left === f; + if (r.cardinality === 'N:N') { + const relName = r.tableName; + return [`${f.path} ${f.refListKey}[] @relation("${relName}", references: [id])`]; + } else { + const relName = `${r.tableName}${r.columnName}`; + if ( + (r.cardinality === 'N:1' && isLeft) || + (r.cardinality === '1:N' && !isLeft) || + (r.cardinality === '1:1' && isLeft) + ) { + // We're the owner of the foreign key column + return [ + `${f.path} ${f.refListKey}? @relation("${relName}", fields: [${f.path}Id], references: [id])`, + `${f.path}Id Int? @map("${r.columnName}")`, + ]; + } else if (r.cardinality === '1:1') { + return [`${f.path} ${f.refListKey}? @relation("${relName}")`]; + } else { + return [`${f.path} ${f.refListKey}[] @relation("${relName}")`]; + } + } + }) + ), + ...flatten( + rels + .filter(({ right }) => !right) + .filter(({ left }) => left.refListKey === listAdapter.key) + .filter(({ cardinality }) => cardinality === 'N:N') + .map(({ left: { path, listKey }, tableName }) => [ + `from_${path} ${listKey}[] @relation("${tableName}", references: [id])`, + ]) + ), + ]; + + return ` + model ${listAdapter.key} { + ${[...scalarFields, ...relFields].join('\n ')} + }`; + }); + + const enums = flatten( + Object.values(this.listAdapters).map(listAdapter => + flatten( + listAdapter.fieldAdapters + .filter(f => !f.field.isRelationship) + .filter(f => f.path !== 'id') + .map(f => f.getPrismaEnums()) + ) + ) + ); + + const header = ` + datasource postgresql { + url = env("DATABASE_URL") + provider = "postgresql" + } + generator client { + provider = "prisma-client-js" + output = "${clientDir}" + previewFeatures = ["insensitiveFilters"] + }`; + return await formatSchema({ schema: header + models.join('\n') + '\n' + enums.join('\n') }); + } + + async postConnect({ rels }) { + Object.values(this.listAdapters).forEach(listAdapter => { + listAdapter._postConnect({ rels, prisma: this.prisma }); + }); + + if (this.config.dropDatabase && process.env.NODE_ENV !== 'production') { + await this.dropDatabase(); + } + return []; + } + + // This will drop all the tables in the backing database. Use wisely. + async dropDatabase() { + let migrationNeeded = true; + for (const { tablename } of await this.prisma.$queryRaw( + `SELECT tablename FROM pg_tables WHERE schemaname='${this.dbSchemaName}'` + )) { + if (tablename.includes('_Migration')) { + migrationNeeded = false; + } + await this.prisma.$queryRaw( + `TRUNCATE TABLE \"${this.dbSchemaName}\".\"${tablename}\" CASCADE;` + ); + } + for (const { relname } of await this.prisma.$queryRaw( + `SELECT c.relname FROM pg_class AS c JOIN pg_namespace AS n ON c.relnamespace = n.oid WHERE c.relkind='S' AND n.nspname='${this.dbSchemaName}';` + )) { + if (!relname.includes('_Migration')) { + await this.prisma.$queryRaw( + `ALTER SEQUENCE \"${this.dbSchemaName}\".\"${relname}\" RESTART WITH 1;` + ); + } + } + + if (migrationNeeded) { + this._saveMigration({ name: 'init' }); + this._executeMigrations(); + } + } + + _saveMigration({ name }) { + this._runPrismaCmd(`migrate save --name ${name} --experimental`); + } + + _executeMigrations() { + this._runPrismaCmd('migrate up --experimental'); + } + + _runPrismaCmd(cmd) { + return execSync(`yarn prisma ${cmd} --schema ${this.schemaPath}`, { + env: { ...process.env, DATABASE_URL: this._url() }, + encoding: 'utf-8', + }); + } + + disconnect() { + return this.prisma.$disconnect(); + // Everything below here is being cleaned up in an attempt to help out the garbage collector + // delete this.prisma; + // Object.values(this.listAdapters).forEach(listAdapter => { + // delete listAdapter.prisma; + // }); + // delete require.cache[require.resolve(this.clientPath)]; + } + + getDefaultPrimaryKeyConfig() { + // Required here due to circular refs + const { AutoIncrement } = require('@keystonejs/fields-auto-increment'); + return AutoIncrement.primaryKeyDefaults[this.name].getConfig(); + } + + async checkDatabaseVersion() { + // FIXME: Decide what/how we want to check things here + } +} + +class PrismaListAdapter extends BaseListAdapter { + constructor(key, parentAdapter) { + super(...arguments); + this.getListAdapterByKey = parentAdapter.getListAdapterByKey.bind(parentAdapter); + } + + _postConnect({ rels, prisma }) { + // https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/models#queries-crud + // "By default the name of the property is the lowercase form of the model name, + // e.g. user for a User model or post for a Post model." + this.model = prisma[this.key.slice(0, 1).toLowerCase() + this.key.slice(1)]; + this.fieldAdapters.forEach(fieldAdapter => { + fieldAdapter.rel = rels.find( + ({ left, right }) => + left.adapter === fieldAdapter || (right && right.adapter === fieldAdapter) + ); + }); + } + + ////////// Mutations ////////// + _include() { + // We don't have a "real key" (i.e. a column in the table) if: + // * We're a N:N + // * We're the right hand side of a 1:1 + // * We're the 1 side of a 1:N or N:1 (e.g we are the one with config: many) + const include = defaultObj( + this.fieldAdapters + .filter(({ isRelationship }) => isRelationship) + .filter(a => a.config.many || (a.rel.cardinality === '1:1' && a.rel.right.adapter === a)) + .map(a => a.path), + { select: { id: true } } + ); + return Object.keys(include).length > 0 ? include : undefined; + } + + async _create(_data) { + return this.model.create({ + data: mapKeys(_data, (value, path) => + this.fieldAdaptersByPath[path] && this.fieldAdaptersByPath[path].isRelationship + ? { + connect: Array.isArray(value) + ? value.map(x => ({ id: Number(x) })) + : { id: Number(value) }, + } + : this.fieldAdaptersByPath[path] && this.fieldAdaptersByPath[path].gqlToPrisma + ? this.fieldAdaptersByPath[path].gqlToPrisma(value) + : value + ), + include: this._include(), + }); + } + + async _update(id, _data) { + const include = this._include(); + const existingItem = await this.model.findOne({ where: { id: Number(id) }, include }); + return this.model.update({ + where: { id: Number(id) }, + data: mapKeys(_data, (value, path) => { + if ( + this.fieldAdaptersByPath[path] && + this.fieldAdaptersByPath[path].isRelationship && + Array.isArray(value) + ) { + const vs = value.map(x => Number(x)); + const toDisconnect = existingItem[path].filter(({ id }) => !vs.includes(id)); + const toConnect = vs + .filter(id => !existingItem[path].map(({ id }) => id).includes(id)) + .map(id => ({ id })); + return { + disconnect: toDisconnect.length ? toDisconnect : undefined, + connect: toConnect.length ? toConnect : undefined, + }; + } + return this.fieldAdaptersByPath[path] && this.fieldAdaptersByPath[path].isRelationship + ? value === null + ? { disconnect: true } + : { connect: { id: Number(value) } } + : value; + }), + include, + }); + } + + async _delete(id) { + return this.model.delete({ where: { id: Number(id) } }); + } + + ////////// Queries ////////// + async _itemsQuery(args, { meta = false, from = {} } = {}) { + const filter = this.prismaFilter({ args, meta, from }); + if (meta) { + let count = await this.model.count(filter); + const { first, skip } = args; + + // Adjust the count as appropriate + if (skip !== undefined) { + count -= skip; + } + if (first !== undefined) { + count = Math.min(count, first); + } + count = Math.max(0, count); // Don't want to go negative from a skip! + return { count }; + } else { + return this.model.findMany(filter); + } + } + + prismaFilter({ args: { where = {}, first, skip, sortBy, orderBy, search }, meta, from }) { + const ret = {}; + const allWheres = this.processWheres(where); + + if (allWheres) { + ret.where = allWheres; + } + + if (from.fromId) { + if (!ret.where) { + ret.where = {}; + } + const a = from.fromList.adapter.fieldAdaptersByPath[from.fromField]; + if (a.rel.cardinality === 'N:N') { + const path = a.rel.right + ? a.field === a.rel.right // Two-sided + ? a.rel.left.path + : a.rel.right.path + : `from_${a.rel.left.path}`; // One-sided + ret.where[path] = { some: { id: Number(from.fromId) } }; + } else { + ret.where[a.rel.columnName] = { id: Number(from.fromId) }; + } + } + + // TODO: Implement configurable search fields for lists + const searchField = this.fieldAdaptersByPath['name']; + if (search !== undefined && searchField) { + if (searchField.fieldName === 'Text') { + // FIXME: Think about regex + if (!ret.where) ret.where = { name: search }; + else ret.where = { AND: [ret.where, { name: search }] }; + // const f = escapeRegExp; + // this._query.andWhere(`${baseTableAlias}.name`, '~*', f(search)); + } else { + // Return no results + if (!ret.where) ret.where = { AND: [{ name: null }, { NOT: { name: null } }] }; + else ret.where = { AND: [ret.where, { name: null }, { NOT: { name: null } }] }; + } + } + + // Add query modifiers as required + if (!meta) { + if (first !== undefined) { + // SELECT ... LIMIT + ret.take = first; + } + if (skip !== undefined) { + // SELECT ... OFFSET + ret.skip = skip; + } + if (orderBy !== undefined) { + // SELECT ... ORDER BY + const [orderField, orderDirection] = orderBy.split('_'); + const sortKey = this.fieldAdaptersByPath[orderField].sortKey || orderField; + ret.orderBy = { [sortKey]: orderDirection.toLowerCase() }; + } + if (sortBy !== undefined) { + // SELECT ... ORDER BY [, , ...] + if (!ret.orderBy) ret.orderBy = {}; + sortBy.forEach(s => { + const [orderField, orderDirection] = s.split('_'); + const sortKey = this.fieldAdaptersByPath[orderField].sortKey || orderField; + ret.orderBy[sortKey] = orderDirection.toLowerCase(); + }); + } + + this.fieldAdapters + .filter(a => a.isRelationship && a.rel.cardinality === '1:1' && a.rel.right === a.field) + .forEach(({ path }) => { + if (!ret.include) ret.include = {}; + ret.include[path] = true; + }); + } + return ret; + } + + processWheres(where) { + const processRelClause = (fieldPath, clause) => + this.getListAdapterByKey(this.fieldAdaptersByPath[fieldPath].refListKey).processWheres( + clause + ); + const wheres = Object.entries(where).map(([condition, value]) => { + if (condition === 'AND' || condition === 'OR') { + return { [condition]: value.map(w => this.processWheres(w)) }; + } else if ( + this.fieldAdaptersByPath[condition] && + this.fieldAdaptersByPath[condition].isRelationship + ) { + // Non-many relationship. Traverse the sub-query, using the referenced list as a root. + return { [condition]: processRelClause(condition, value) }; + } else { + // See if any of our fields know what to do with this condition + let dbPath = condition; + let fieldAdapter = this.fieldAdaptersByPath[dbPath]; + while (!fieldAdapter && dbPath.includes('_')) { + dbPath = dbPath.split('_').slice(0, -1).join('_'); + fieldAdapter = this.fieldAdaptersByPath[dbPath]; + } + + // FIXME: ask the field adapter if it supports the condition type + const supported = + fieldAdapter && fieldAdapter.getQueryConditions(fieldAdapter.dbPath)[condition]; + if (supported) { + return supported(value); + } else { + // Many relationship + const [fieldPath, constraintType] = condition.split('_'); + return { [fieldPath]: { [constraintType]: processRelClause(fieldPath, value) } }; + } + } + }); + + return wheres.length === 0 ? undefined : wheres.length === 1 ? wheres[0] : { AND: wheres }; + } +} + +class PrismaFieldAdapter extends BaseFieldAdapter { + constructor() { + super(...arguments); + } + + _schemaField({ type, extra = '' }) { + const { isRequired, isUnique } = this.config; + return `${this.path} ${type}${isRequired || this.field.isPrimaryKey ? '' : '?'} ${ + this.field.isPrimaryKey ? '@id' : '' + } ${isUnique && !this.field.isPrimaryKey ? '@unique' : ''} ${extra}`; + } + + getPrismaSchema() { + return [this._schemaField({ type: 'String' })]; + } + + getPrismaEnums() { + return []; + } + + // The following methods provide helpers for constructing the return values of `getQueryConditions`. + // Each method takes: + // `dbPath`: The database field/column name to be used in the comparison + // `f`: (non-string methods only) A value transformation function which converts from a string type + // provided by graphQL into a native adapter type. + equalityConditions(dbPath, f = identity) { + return { + [this.path]: value => ({ [dbPath]: { equals: f(value) } }), + [`${this.path}_not`]: value => + value === null + ? { NOT: { [dbPath]: { equals: f(value) } } } + : { + OR: [{ NOT: { [dbPath]: { equals: f(value) } } }, { [dbPath]: { equals: null } }], + }, + }; + } + + equalityConditionsInsensitive(dbPath, f = identity) { + return { + [`${this.path}_i`]: value => ({ [dbPath]: { equals: f(value), mode: 'insensitive' } }), + [`${this.path}_not_i`]: value => + value === null + ? { NOT: { [dbPath]: { equals: f(value), mode: 'insensitive' } } } + : { + OR: [ + { NOT: { [dbPath]: { equals: f(value), mode: 'insensitive' } } }, + { [dbPath]: null }, + ], + }, + }; + } + + inConditions(dbPath, f = identity) { + return { + [`${this.path}_in`]: value => + value.includes(null) + ? { OR: [{ [dbPath]: { in: value.filter(x => x !== null).map(f) } }, { [dbPath]: null }] } + : { [dbPath]: { in: value.map(f) } }, + [`${this.path}_not_in`]: value => + value.includes(null) + ? { + AND: [ + { NOT: { [dbPath]: { in: value.filter(x => x !== null).map(f) } } }, + { NOT: { [dbPath]: null } }, + ], + } + : { + OR: [{ NOT: { [dbPath]: { in: value.map(f) } } }, { [dbPath]: null }], + }, + }; + } + + orderingConditions(dbPath, f = identity) { + return { + [`${this.path}_lt`]: value => ({ [dbPath]: { lt: f(value) } }), + [`${this.path}_lte`]: value => ({ [dbPath]: { lte: f(value) } }), + [`${this.path}_gt`]: value => ({ [dbPath]: { gt: f(value) } }), + [`${this.path}_gte`]: value => ({ [dbPath]: { gte: f(value) } }), + }; + } + + stringConditions(dbPath, f = identity) { + return { + [`${this.path}_contains`]: value => ({ [dbPath]: { contains: f(value) } }), + [`${this.path}_not_contains`]: value => ({ + OR: [{ NOT: { [dbPath]: { contains: f(value) } } }, { [dbPath]: null }], + }), + [`${this.path}_starts_with`]: value => ({ [dbPath]: { startsWith: f(value) } }), + [`${this.path}_not_starts_with`]: value => ({ + OR: [{ NOT: { [dbPath]: { startsWith: f(value) } } }, { [dbPath]: null }], + }), + [`${this.path}_ends_with`]: value => ({ [dbPath]: { endsWith: f(value) } }), + [`${this.path}_not_ends_with`]: value => ({ + OR: [{ NOT: { [dbPath]: { endsWith: f(value) } } }, { [dbPath]: null }], + }), + }; + } + + stringConditionsInsensitive(dbPath, f = identity) { + return { + [`${this.path}_contains_i`]: value => ({ + [dbPath]: { contains: f(value), mode: 'insensitive' }, + }), + [`${this.path}_not_contains_i`]: value => ({ + OR: [ + { NOT: { [dbPath]: { contains: f(value), mode: 'insensitive' } } }, + { [dbPath]: null }, + ], + }), + [`${this.path}_starts_with_i`]: value => ({ + [dbPath]: { startsWith: f(value), mode: 'insensitive' }, + }), + [`${this.path}_not_starts_with_i`]: value => ({ + OR: [ + { NOT: { [dbPath]: { startsWith: f(value), mode: 'insensitive' } } }, + { [dbPath]: null }, + ], + }), + [`${this.path}_ends_with_i`]: value => ({ + [dbPath]: { endsWith: f(value), mode: 'insensitive' }, + }), + [`${this.path}_not_ends_with_i`]: value => ({ + OR: [ + { NOT: { [dbPath]: { endsWith: f(value), mode: 'insensitive' } } }, + { [dbPath]: null }, + ], + }), + }; + } +} + +PrismaAdapter.defaultListAdapterClass = PrismaListAdapter; + +module.exports = { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter }; diff --git a/packages/adapter-prisma/package.json b/packages/adapter-prisma/package.json new file mode 100644 index 00000000000..120b7e67065 --- /dev/null +++ b/packages/adapter-prisma/package.json @@ -0,0 +1,20 @@ +{ + "name": "@keystonejs/adapter-prisma", + "description": "KeystoneJS Prisma Database Adapter", + "version": "0.0.0", + "author": "The KeystoneJS Development Team", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "dependencies": { + "@keystonejs/fields-auto-increment": "^6.0.3", + "@keystonejs/keystone": "^16.0.0", + "@keystonejs/utils": "^5.4.3", + "@prisma/cli": "2.7.1", + "@prisma/client": "2.7.1", + "@prisma/sdk": "2.7.1", + "cuid": "^2.1.8" + }, + "repository": "https://github.com/keystonejs/keystone/tree/master/packages/adapter-prisma" +} diff --git a/packages/adapter-prisma/tests/adapter-prisma.test.js b/packages/adapter-prisma/tests/adapter-prisma.test.js new file mode 100644 index 00000000000..ad9fb473085 --- /dev/null +++ b/packages/adapter-prisma/tests/adapter-prisma.test.js @@ -0,0 +1,60 @@ +const { PrismaAdapter } = require('../lib/adapter-prisma'); + +global.console = { + error: jest.fn(), + warn: jest.fn(), + log: jest.fn(), +}; + +describe('Prisma Adapter', () => { + test.skip('throws when database cannot be found using connection string', async () => { + const testAdapter = new PrismaAdapter({ + knexOptions: { connection: 'postgres://localhost/undefined_database' }, + }); + const result = await testAdapter._connect().catch(result => result); + + expect(result).toBeInstanceOf(Error); + expect(global.console.error).toHaveBeenCalledWith( + "Could not connect to database: 'undefined_database'" + ); + }); + + test.skip('throws when database cannot be found using connection object', async () => { + const testAdapter = new PrismaAdapter({ + knexOptions: { + connection: { + host: '127.0.0.1', + user: 'your_database_user', + password: 'your_database_password', + database: 'undefined_database', + }, + }, + }); + const result = await testAdapter._connect().catch(result => result); + + expect(result).toBeInstanceOf(Error); + expect(global.console.error).toHaveBeenCalledWith( + "Could not connect to database: 'undefined_database'" + ); + }); + + describe('checkDatabaseVersion', () => { + test.skip('throws when database version is unsupported', async () => { + const testAdapter = new PrismaAdapter(); + await testAdapter._connect(); + testAdapter.minVer = '50.5.5'; + const result = await testAdapter.checkDatabaseVersion().catch(result => result); + expect(result).toBeInstanceOf(Error); + testAdapter.disconnect(); + }); + + test.skip('does not throw when database version is supported', async () => { + const testAdapter = new PrismaAdapter(); + await testAdapter._connect(); + testAdapter.minVer = '1.0.0'; + const result = await testAdapter.checkDatabaseVersion().catch(result => result); + expect(result).not.toBeInstanceOf(Error); + testAdapter.disconnect(); + }); + }); +}); diff --git a/packages/fields-auto-increment/package.json b/packages/fields-auto-increment/package.json index 2d1acfd3587..e8e5bffa149 100644 --- a/packages/fields-auto-increment/package.json +++ b/packages/fields-auto-increment/package.json @@ -12,6 +12,7 @@ "dependencies": { "@babel/runtime": "^7.11.2", "@keystonejs/adapter-knex": "^11.0.5", + "@keystonejs/adapter-prisma": "^0.0.0", "@keystonejs/fields": "^17.1.2" }, "devDependencies": { diff --git a/packages/fields-auto-increment/src/Implementation.js b/packages/fields-auto-increment/src/Implementation.js index 7f7e98dec39..0beeb4e0cbc 100644 --- a/packages/fields-auto-increment/src/Implementation.js +++ b/packages/fields-auto-increment/src/Implementation.js @@ -1,5 +1,6 @@ import { Implementation } from '@keystonejs/fields'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; export class AutoIncrementImplementation extends Implementation { constructor(path, { gqlType, isUnique = true, access = {}, ...config } = {}, context = {}) { @@ -103,3 +104,52 @@ export class KnexAutoIncrementInterface extends KnexFieldAdapter { }; } } + +export class PrismaAutoIncrementInterface extends PrismaFieldAdapter { + constructor() { + super(...arguments); + + // Default isUnique to true if not specified + this.isUnique = typeof this.config.isUnique === 'undefined' ? true : !!this.config.isUnique; + this.isIndexed = !!this.config.isIndexed && !this.config.isUnique; + } + + getPrismaSchema() { + return [this._schemaField({ type: 'Int', extra: '@default(autoincrement())' })]; + } + + gqlToPrisma(value) { + // If we're an ID type then we'll be getting strings from GQL + return Number(value); + // console.log(this.field.gqlType); + // return this.field.gqlType === 'ID' ? Number(value) : value; + } + + equalityConditions(dbPath, f) { + return { + [this.path]: value => ({ [dbPath]: f(value) }), + [`${this.path}_not`]: value => ({ NOT: { [this.path]: f(value) } }), + }; + } + + inConditions(dbPath, f) { + return { + [`${this.path}_in`]: value => + value.includes(null) + ? { [dbPath]: { in: f(value.filter(x => x !== null)) } } + : { [dbPath]: { in: f(value) } }, + [`${this.path}_not_in`]: value => + value.includes(null) + ? { AND: [{ NOT: { [dbPath]: { in: f(value.filter(x => x !== null)) } } }] } + : { NOT: { [dbPath]: { in: f(value) } } }, + }; + } + + getQueryConditions(dbPath) { + return { + ...this.equalityConditions(dbPath, x => Number(x) || -1), + ...this.orderingConditions(dbPath, x => Number(x) || -1), + ...this.inConditions(dbPath, x => x.map(xx => Number(xx) || -1)), + }; + } +} diff --git a/packages/fields-auto-increment/src/index.js b/packages/fields-auto-increment/src/index.js index a89826520e6..5043424d690 100644 --- a/packages/fields-auto-increment/src/index.js +++ b/packages/fields-auto-increment/src/index.js @@ -1,4 +1,8 @@ -import { AutoIncrementImplementation, KnexAutoIncrementInterface } from './Implementation'; +import { + AutoIncrementImplementation, + KnexAutoIncrementInterface, + PrismaAutoIncrementInterface, +} from './Implementation'; import { Integer } from '@keystonejs/fields'; export const AutoIncrement = { @@ -11,6 +15,7 @@ export const AutoIncrement = { }, adapters: { knex: KnexAutoIncrementInterface, + prisma: PrismaAutoIncrementInterface, }, primaryKeyDefaults: { @@ -18,5 +23,9 @@ export const AutoIncrement = { // Uniqueness, non-nullability and GraphQL type are implied getConfig: () => ({ type: AutoIncrement }), }, + prisma: { + // Uniqueness, non-nullability and GraphQL type are implied + getConfig: () => ({ type: AutoIncrement }), + }, }, }; diff --git a/packages/fields-auto-increment/src/test-fixtures.js b/packages/fields-auto-increment/src/test-fixtures.js index bfd0d83cd9a..7d198cee18e 100644 --- a/packages/fields-auto-increment/src/test-fixtures.js +++ b/packages/fields-auto-increment/src/test-fixtures.js @@ -9,7 +9,7 @@ export const exampleValue = matrixValue => (matrixValue === 'ID' ? '35' : 35); export const exampleValue2 = matrixValue => (matrixValue === 'ID' ? '36' : 36); export const supportsUnique = true; export const fieldName = 'orderNumber'; -export const skipCreateTest = true; +export const skipCreateTest = false; export const skipUpdateTest = true; // `AutoIncrement` field type is not supported by `mongoose`. So, we need to filter it out while performing `API` tests. @@ -23,7 +23,7 @@ export const fieldConfig = matrixValue => ({ export const getTestFields = matrixValue => ({ name: { type: Text }, - orderNumber: { type, gqlType: matrixValue }, + orderNumber: { type, gqlType: matrixValue, access: { create: true } }, }); export const initItems = () => { diff --git a/packages/fields-cloudinary-image/src/Implementation.js b/packages/fields-cloudinary-image/src/Implementation.js index 259c9ac08c7..48f45c5c2ff 100644 --- a/packages/fields-cloudinary-image/src/Implementation.js +++ b/packages/fields-cloudinary-image/src/Implementation.js @@ -93,5 +93,11 @@ class CloudinaryImage extends File.implementation { const MongoCloudinaryImageInterface = File.adapters.mongoose; const KnexCloudinaryImageInterface = File.adapters.knex; +const PrismaCloudinaryImageInterface = File.adapters.prisma; -export { CloudinaryImage, MongoCloudinaryImageInterface, KnexCloudinaryImageInterface }; +export { + CloudinaryImage, + MongoCloudinaryImageInterface, + KnexCloudinaryImageInterface, + PrismaCloudinaryImageInterface, +}; diff --git a/packages/fields-cloudinary-image/src/index.js b/packages/fields-cloudinary-image/src/index.js index 792b71d7687..d45901bee39 100644 --- a/packages/fields-cloudinary-image/src/index.js +++ b/packages/fields-cloudinary-image/src/index.js @@ -4,6 +4,7 @@ import { CloudinaryImage as Implementation, MongoCloudinaryImageInterface, KnexCloudinaryImageInterface, + PrismaCloudinaryImageInterface, } from './Implementation'; import { ImageBlock } from './ImageBlock'; @@ -18,6 +19,7 @@ export const CloudinaryImage = { adapters: { mongoose: MongoCloudinaryImageInterface, knex: KnexCloudinaryImageInterface, + prisma: PrismaCloudinaryImageInterface, }, blocks: { image: ImageBlock, diff --git a/packages/fields-cloudinary-image/src/test-fixtures.skip.js b/packages/fields-cloudinary-image/src/test-fixtures.skip.js index 6dbcaff9a5c..05413a02a84 100644 --- a/packages/fields-cloudinary-image/src/test-fixtures.skip.js +++ b/packages/fields-cloudinary-image/src/test-fixtures.skip.js @@ -41,6 +41,7 @@ export const type = CloudinaryImage; export const supportsUnique = false; export const fieldName = 'image'; export const subfieldName = 'originalFilename'; +export const unSupportedAdapterList = ['prisma']; // This function will run after all the tests are completed. // We use it to cleanup the resources (e.g Cloudinary images) which are no longer required. diff --git a/packages/fields-content/src/Implementation.js b/packages/fields-content/src/Implementation.js index 85909833f39..7298a7e8788 100644 --- a/packages/fields-content/src/Implementation.js +++ b/packages/fields-content/src/Implementation.js @@ -93,7 +93,10 @@ export class Content extends Relationship.implementation { // `ContentType`. // Including the list name + path to make sure these input types are unique // to this list+field and don't collide. - const type = `${GQL_TYPE_PREFIX}_${itemQueryName}_${path}`; + const type = + listConfig.listAdapter.parentAdapter.name === 'prisma' + ? `KS_${GQL_TYPE_PREFIX}_${itemQueryName}_${path}` // Prisma doesn't support leading underscores + : `${GQL_TYPE_PREFIX}_${itemQueryName}_${path}`; // Normalise blocks to always be a tuple with a config object let blocks = (Array.isArray(inputBlocks) ? inputBlocks : []).map(block => @@ -236,3 +239,5 @@ export class Content extends Relationship.implementation { export class MongoContentInterface extends Relationship.adapters.mongoose {} export class KnexContentInterface extends Relationship.adapters.knex {} + +export class PrismaContentInterface extends Relationship.adapters.prisma {} diff --git a/packages/fields-content/src/index.js b/packages/fields-content/src/index.js index 31ea9a029fa..8cf198574a1 100644 --- a/packages/fields-content/src/index.js +++ b/packages/fields-content/src/index.js @@ -3,6 +3,7 @@ import { Content as ContentType, MongoContentInterface, KnexContentInterface, + PrismaContentInterface, } from './Implementation'; import { blockquote, @@ -26,6 +27,7 @@ export const Content = { adapters: { mongoose: MongoContentInterface, knex: KnexContentInterface, + prisma: PrismaContentInterface, }, blocks: { blockquote, diff --git a/packages/fields-location-google/package.json b/packages/fields-location-google/package.json index 2919a529b9c..5e8b606314d 100644 --- a/packages/fields-location-google/package.json +++ b/packages/fields-location-google/package.json @@ -16,6 +16,7 @@ "@emotion/core": "^10.0.35", "@keystonejs/adapter-knex": "^11.0.5", "@keystonejs/adapter-mongoose": "^9.0.6", + "@keystonejs/adapter-prisma": "^0.0.0", "@keystonejs/fields": "^17.1.2", "google-maps-react": "^2.0.6", "mongoose": "~5.9.29", diff --git a/packages/fields-location-google/src/Implementation.js b/packages/fields-location-google/src/Implementation.js index 5fdff2393f0..45039baa882 100644 --- a/packages/fields-location-google/src/Implementation.js +++ b/packages/fields-location-google/src/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '@keystonejs/fields'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import mongoose from 'mongoose'; import fetch from 'node-fetch'; @@ -148,7 +149,7 @@ export class KnexLocationGoogleInterface extends CommonLocationInterface(KnexFie // We totally can index these values, it's just not trivial. See issue #1297 if (this.config.isIndexed) { throw ( - `The Location field type doesn't support indexes on Knex. ` + + `The LocationGoogle field type doesn't support indexes on Knex. ` + `Check the config for ${this.path} on the ${this.field.listKey} list` ); } @@ -160,3 +161,21 @@ export class KnexLocationGoogleInterface extends CommonLocationInterface(KnexFie if (this.defaultTo) column.defaultTo(this.defaultTo); } } + +export class PrismaLocationGoogleInterface extends CommonLocationInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + // Error rather than ignoring invalid config + // We totally can index these values, it's just not trivial. See issue #1297 + if (this.config.isIndexed) { + throw ( + `The LocationGoogle field type doesn't support indexes on Prisma. ` + + `Check the config for ${this.path} on the ${this.field.listKey} list` + ); + } + } + + getPrismaSchema() { + return [this._schemaField({ type: 'Json' })]; + } +} diff --git a/packages/fields-location-google/src/index.js b/packages/fields-location-google/src/index.js index 42cfefafdc4..203e38a136b 100644 --- a/packages/fields-location-google/src/index.js +++ b/packages/fields-location-google/src/index.js @@ -3,6 +3,7 @@ import { LocationGoogleImplementation, MongoLocationGoogleInterface, KnexLocationGoogleInterface, + PrismaLocationGoogleInterface, } from './Implementation'; import path from 'path'; @@ -20,5 +21,6 @@ export const LocationGoogle = { adapters: { mongoose: MongoLocationGoogleInterface, knex: KnexLocationGoogleInterface, + prisma: PrismaLocationGoogleInterface, }, }; diff --git a/packages/fields-mongoid/package.json b/packages/fields-mongoid/package.json index a59d82a9ba9..4c866e40c8d 100644 --- a/packages/fields-mongoid/package.json +++ b/packages/fields-mongoid/package.json @@ -16,6 +16,7 @@ "@babel/runtime": "^7.11.2", "@keystonejs/adapter-knex": "^11.0.5", "@keystonejs/adapter-mongoose": "^9.0.6", + "@keystonejs/adapter-prisma": "^0.0.0", "@keystonejs/fields": "^17.1.2", "react": "^16.13.1" }, diff --git a/packages/fields-mongoid/src/Implementation.js b/packages/fields-mongoid/src/Implementation.js index 24cd96edf91..31352e85528 100644 --- a/packages/fields-mongoid/src/Implementation.js +++ b/packages/fields-mongoid/src/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '@keystonejs/fields'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; export class MongoIdImplementation extends Implementation { get _supportsUnique() { @@ -138,3 +139,52 @@ export class KnexMongoIdInterface extends KnexFieldAdapter { }; } } + +export class PrismaMongoIdInterface extends PrismaFieldAdapter { + constructor() { + super(...arguments); + this.isUnique = !!this.config.isUnique; + this.isIndexed = !!this.config.isIndexed && !this.config.isUnique; + } + + getPrismaSchema() { + return [this._schemaField({ type: 'String' })]; + } + + setupHooks({ addPreSaveHook, addPostReadHook }) { + addPreSaveHook(item => { + // Only run the hook if the item actually contains the field + // NOTE: Can't use hasOwnProperty here, as the mongoose data object + // returned isn't a POJO + if (!(this.path in item)) { + return item; + } + + if (item[this.path]) { + if (typeof item[this.path] === 'string' && validator(item[this.path])) { + item[this.path] = normaliseValue(item[this.path]); + } else { + // Should have been caught by the validator?? + throw new Error(`Invalid MongoID value given for '${this.path}'`); + } + } else { + item[this.path] = null; + } + + return item; + }); + addPostReadHook(item => { + if (item[this.path]) { + item[this.path] = normaliseValue(item[this.path]); + } + return item; + }); + } + + getQueryConditions(dbPath) { + return { + ...this.equalityConditions(dbPath, normaliseValue), + ...this.inConditions(dbPath, normaliseValue), + }; + } +} diff --git a/packages/fields-mongoid/src/index.js b/packages/fields-mongoid/src/index.js index 53069555483..e797354e995 100644 --- a/packages/fields-mongoid/src/index.js +++ b/packages/fields-mongoid/src/index.js @@ -3,6 +3,7 @@ import { MongoIdImplementation, MongooseMongoIdInterface, KnexMongoIdInterface, + PrismaMongoIdInterface, } from './Implementation'; import { Text } from '@keystonejs/fields'; @@ -19,13 +20,23 @@ export const MongoId = { adapters: { knex: KnexMongoIdInterface, mongoose: MongooseMongoIdInterface, + prisma: PrismaMongoIdInterface, }, primaryKeyDefaults: { knex: { getConfig: () => { throw ( - `The Uuid field type doesn't provide a default primary key field configuration for knex. ` + + `The MongoId field type doesn't provide a default primary key field configuration for knex. ` + + `You'll need to supply your own 'id' field for each list or use a different field type for your ` + + `ids (eg '@keystonejs/fields-auto-increment').` + ); + }, + }, + prisma: { + getConfig: () => { + throw ( + `The MongoId field type doesn't provide a default primary key field configuration for Prisma. ` + `You'll need to supply your own 'id' field for each list or use a different field type for your ` + `ids (eg '@keystonejs/fields-auto-increment').` ); diff --git a/packages/fields-oembed/package.json b/packages/fields-oembed/package.json index d1fc5aaed82..f24dd4ab1b4 100644 --- a/packages/fields-oembed/package.json +++ b/packages/fields-oembed/package.json @@ -19,6 +19,7 @@ "@iframely/embed.js": "^1.3.1", "@keystonejs/adapter-knex": "^11.0.6", "@keystonejs/adapter-mongoose": "^9.0.7", + "@keystonejs/adapter-prisma": "^0.0.0", "@keystonejs/fields": "^17.1.3", "@keystonejs/fields-content": "^8.0.5", "node-fetch": "^2.6.1", diff --git a/packages/fields-oembed/src/Implementation.js b/packages/fields-oembed/src/Implementation.js index 10915b1c2ea..9a82f7db43f 100644 --- a/packages/fields-oembed/src/Implementation.js +++ b/packages/fields-oembed/src/Implementation.js @@ -1,5 +1,6 @@ import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import { Implementation } from '@keystonejs/fields'; export class OEmbed extends Implementation { @@ -298,3 +299,22 @@ export class KnexOEmbedInterface extends CommonOEmbedInterface(KnexFieldAdapter) if (this.defaultTo) column.defaultTo(this.defaultTo); } } + +export class PrismaOEmbedInterface extends CommonOEmbedInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + + // Error rather than ignoring invalid config + // We totally can index these values, it's just not trivial. See issue #1297 + if (this.config.isIndexed) { + throw ( + `The OEmbed field type doesn't support indexes on Prisma. ` + + `Check the config for ${this.path} on the ${this.field.listKey} list` + ); + } + } + + getPrismaSchema() { + return [this._schemaField({ type: 'Json' })]; + } +} diff --git a/packages/fields-oembed/src/index.js b/packages/fields-oembed/src/index.js index 7ba49b0ea2f..f5fc04d4985 100644 --- a/packages/fields-oembed/src/index.js +++ b/packages/fields-oembed/src/index.js @@ -4,6 +4,7 @@ import { OEmbed as Implementation, MongoOEmbedInterface, KnexOEmbedInterface, + PrismaOEmbedInterface, } from './Implementation'; import { OEmbedBlock } from './OEmbedBlock'; export { IframelyOEmbedAdapter } from './iframely/iframely'; @@ -19,6 +20,7 @@ export const OEmbed = { adapters: { mongoose: MongoOEmbedInterface, knex: KnexOEmbedInterface, + prisma: PrismaOEmbedInterface, }, blocks: { oEmbed: OEmbedBlock, diff --git a/packages/fields-oembed/src/test-fixtures.js b/packages/fields-oembed/src/test-fixtures.js index fc4a6718154..9d1fc969828 100644 --- a/packages/fields-oembed/src/test-fixtures.js +++ b/packages/fields-oembed/src/test-fixtures.js @@ -11,6 +11,7 @@ export const exampleValue2 = () => 'https://codesandbox.io'; export const supportsUnique = false; export const fieldName = 'portfolio'; export const subfieldName = 'originalUrl'; +export const unSupportedAdapterList = ['prisma']; const iframelyAdapter = new IframelyOEmbedAdapter({ apiKey: process.env.IFRAMELY_API_KEY || 'iframely_api_key', diff --git a/packages/fields-unsplash/package.json b/packages/fields-unsplash/package.json index 922d4d6ab99..f6631130e8b 100644 --- a/packages/fields-unsplash/package.json +++ b/packages/fields-unsplash/package.json @@ -17,6 +17,7 @@ "@emotion/core": "^10.0.35", "@keystonejs/adapter-knex": "^11.0.6", "@keystonejs/adapter-mongoose": "^9.0.7", + "@keystonejs/adapter-prisma": "^0.0.0", "@keystonejs/fields": "^17.1.3", "@keystonejs/fields-content": "^8.0.5", "node-fetch": "^2.6.1", diff --git a/packages/fields-unsplash/src/Implementation.js b/packages/fields-unsplash/src/Implementation.js index 2d642669ba4..cf56b3f012e 100644 --- a/packages/fields-unsplash/src/Implementation.js +++ b/packages/fields-unsplash/src/Implementation.js @@ -1,5 +1,6 @@ import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import UnsplashAPI, { toJson } from 'unsplash-js'; import queryString from 'query-string'; @@ -294,3 +295,21 @@ export class KnexUnsplashInterface extends CommonUnsplashInterface(KnexFieldAdap if (this.defaultTo) column.defaultTo(this.defaultTo); } } + +export class PrismaUnsplashInterface extends CommonUnsplashInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + + // Error rather than ignoring invalid config + // We totally can index these values, it's just not trivial. See issue #1297 + if (this.config.isIndexed) { + throw ( + `The Unsplash field type doesn't support indexes on Prisma. ` + + `Check the config for ${this.path} on the ${this.field.listKey} list` + ); + } + } + getPrismaSchema() { + return [this._schemaField({ type: 'Json' })]; + } +} diff --git a/packages/fields-unsplash/src/index.js b/packages/fields-unsplash/src/index.js index 79e5da50629..78dc1a76af5 100644 --- a/packages/fields-unsplash/src/index.js +++ b/packages/fields-unsplash/src/index.js @@ -3,6 +3,7 @@ import { Unsplash as Implementation, MongoUnsplashInterface, KnexUnsplashInterface, + PrismaUnsplashInterface, } from './Implementation'; import { UnsplashBlock } from './UnsplashBlock'; @@ -19,6 +20,7 @@ export const Unsplash = { adapters: { mongoose: MongoUnsplashInterface, knex: KnexUnsplashInterface, + prisma: PrismaUnsplashInterface, }, blocks: { unsplashImage: UnsplashBlock, diff --git a/packages/fields-unsplash/src/test-fixtures.skip.js b/packages/fields-unsplash/src/test-fixtures.skip.js index 15ce0e1cfc7..e0a72340691 100644 --- a/packages/fields-unsplash/src/test-fixtures.skip.js +++ b/packages/fields-unsplash/src/test-fixtures.skip.js @@ -11,6 +11,7 @@ export const exampleValue = () => 'U0tBTn8UR8I'; export const exampleValue2 = () => 'xrVDYZRGdw4'; export const fieldName = 'heroImage'; export const subfieldName = 'unsplashId'; +export const unSupportedAdapterList = ['prisma']; export const fieldConfig = () => ({ accessKey: process.env.UNSPLASH_KEY || 'unsplash_key', secretKey: process.env.UNSPLASH_SECRET || 'unplash_secret', diff --git a/packages/fields/package.json b/packages/fields/package.json index ba95a0e2c0f..930af6ca389 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -35,6 +35,7 @@ "@keystonejs/access-control": "^6.3.0", "@keystonejs/adapter-knex": "^11.0.6", "@keystonejs/adapter-mongoose": "^9.0.7", + "@keystonejs/adapter-prisma": "^0.0.0", "@keystonejs/app-admin-ui": "^7.3.6", "@keystonejs/server-side-graphql-client": "^1.1.2", "@keystonejs/utils": "^5.4.3", diff --git a/packages/fields/src/types/CalendarDay/Implementation.js b/packages/fields/src/types/CalendarDay/Implementation.js index cd05416de85..33db315dc46 100644 --- a/packages/fields/src/types/CalendarDay/Implementation.js +++ b/packages/fields/src/types/CalendarDay/Implementation.js @@ -2,6 +2,8 @@ import { formatISO, parseISO, compareAsc, compareDesc, isValid } from 'date-fns' import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; + export class CalendarDay extends Implementation { constructor(path, { format = 'yyyy-MM-dd', dateFrom, dateTo }) { super(...arguments); @@ -143,3 +145,41 @@ export class KnexCalendarDayInterface extends CommonCalendarInterface(KnexFieldA }); } } + +export class PrismaCalendarDayInterface extends CommonCalendarInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + } + + getPrismaSchema() { + return [this._schemaField({ type: 'DateTime' })]; + } + + _stringToDate(s) { + return s && new Date(s + 'T00:00:00+0000'); + } + + getQueryConditions(dbPath) { + return { + ...this.equalityConditions(dbPath, this._stringToDate), + ...this.orderingConditions(dbPath, this._stringToDate), + ...this.inConditions(dbPath, this._stringToDate), + }; + } + + setupHooks({ addPreSaveHook, addPostReadHook }) { + addPreSaveHook(item => { + if (item[this.path]) { + item[this.path] = this._stringToDate(item[this.path]); + } + return item; + }); + + addPostReadHook(item => { + if (item[this.path]) { + item[this.path] = formatISO(item[this.path], { representation: 'date' }); + } + return item; + }); + } +} diff --git a/packages/fields/src/types/CalendarDay/index.js b/packages/fields/src/types/CalendarDay/index.js index 9c0ad91a0ec..d72f403452b 100644 --- a/packages/fields/src/types/CalendarDay/index.js +++ b/packages/fields/src/types/CalendarDay/index.js @@ -1,5 +1,10 @@ import { resolveView } from '../../resolve-view'; -import { CalendarDay, MongoCalendarDayInterface, KnexCalendarDayInterface } from './Implementation'; +import { + CalendarDay, + MongoCalendarDayInterface, + KnexCalendarDayInterface, + PrismaCalendarDayInterface, +} from './Implementation'; export default { type: 'CalendarDay', @@ -13,5 +18,6 @@ export default { adapters: { mongoose: MongoCalendarDayInterface, knex: KnexCalendarDayInterface, + prisma: PrismaCalendarDayInterface, }, }; diff --git a/packages/fields/src/types/Checkbox/Implementation.js b/packages/fields/src/types/Checkbox/Implementation.js index 4626a366119..f0a8eea3af4 100644 --- a/packages/fields/src/types/Checkbox/Implementation.js +++ b/packages/fields/src/types/Checkbox/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; export class Checkbox extends Implementation { constructor() { @@ -60,3 +61,24 @@ export class KnexCheckboxInterface extends KnexFieldAdapter { return this.equalityConditions(dbPath); } } + +export class PrismaCheckboxInterface extends PrismaFieldAdapter { + constructor() { + super(...arguments); + + // Error rather than ignoring invalid config + if (this.config.isIndexed) { + throw ( + `The Checkbox field type doesn't support indexes on Prisma. ` + + `Check the config for ${this.path} on the ${this.field.listKey} list` + ); + } + } + getPrismaSchema() { + return [this._schemaField({ type: 'Boolean' })]; + } + + getQueryConditions(dbPath) { + return this.equalityConditions(dbPath); + } +} diff --git a/packages/fields/src/types/Checkbox/index.js b/packages/fields/src/types/Checkbox/index.js index 48140881702..5ff9a0ce085 100644 --- a/packages/fields/src/types/Checkbox/index.js +++ b/packages/fields/src/types/Checkbox/index.js @@ -1,5 +1,10 @@ import { resolveView } from '../../resolve-view'; -import { Checkbox, MongoCheckboxInterface, KnexCheckboxInterface } from './Implementation'; +import { + Checkbox, + MongoCheckboxInterface, + KnexCheckboxInterface, + PrismaCheckboxInterface, +} from './Implementation'; export default { type: 'Checkbox', @@ -13,5 +18,6 @@ export default { adapters: { mongoose: MongoCheckboxInterface, knex: KnexCheckboxInterface, + prisma: PrismaCheckboxInterface, }, }; diff --git a/packages/fields/src/types/DateTime/Implementation.js b/packages/fields/src/types/DateTime/Implementation.js index 1cb7abb41e4..1e972043dff 100644 --- a/packages/fields/src/types/DateTime/Implementation.js +++ b/packages/fields/src/types/DateTime/Implementation.js @@ -3,6 +3,7 @@ import { Kind } from 'graphql/language'; import { DateTime, FixedOffsetZone } from 'luxon'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import { Implementation } from '../../Implementation'; class _DateTime extends Implementation { @@ -219,4 +220,26 @@ export class KnexDateTimeInterface extends CommonDateTimeInterface(KnexFieldAdap } } +export class PrismaDateTimeInterface extends CommonDateTimeInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + + this.utcPath = `${this.path}_utc`; + this.offsetPath = `${this.path}_offset`; + this.realKeys = [this.utcPath, this.offsetPath]; + this.sortKey = this.utcPath; + this.dbPath = this.utcPath; + + this.isUnique = !!this.config.isUnique; + this.isIndexed = !!this.config.isIndexed && !this.config.isUnique; + } + + getPrismaSchema() { + return [ + `${this.path}_utc DateTime? ${this.config.isUnique ? '@unique' : ''}`, + `${this.path}_offset String?`, + ]; + } +} + export { _DateTime as DateTime }; diff --git a/packages/fields/src/types/DateTime/index.js b/packages/fields/src/types/DateTime/index.js index 115047c898c..8cbfbd90127 100644 --- a/packages/fields/src/types/DateTime/index.js +++ b/packages/fields/src/types/DateTime/index.js @@ -1,4 +1,9 @@ -import { DateTime, MongoDateTimeInterface, KnexDateTimeInterface } from './Implementation'; +import { + DateTime, + MongoDateTimeInterface, + KnexDateTimeInterface, + PrismaDateTimeInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -13,5 +18,6 @@ export default { adapters: { mongoose: MongoDateTimeInterface, knex: KnexDateTimeInterface, + prisma: PrismaDateTimeInterface, }, }; diff --git a/packages/fields/src/types/DateTimeUtc/Implementation.js b/packages/fields/src/types/DateTimeUtc/Implementation.js index 68c78607f66..8374a1a028a 100644 --- a/packages/fields/src/types/DateTimeUtc/Implementation.js +++ b/packages/fields/src/types/DateTimeUtc/Implementation.js @@ -1,6 +1,7 @@ import { DateTime } from 'luxon'; import { Implementation } from '../../Implementation'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; export class DateTimeUtcImplementation extends Implementation { @@ -89,3 +90,36 @@ export class KnexDateTimeUtcInterface extends KnexFieldAdapter { }; } } + +export class PrismaDateTimeUtcInterface extends PrismaFieldAdapter { + constructor() { + super(...arguments); + this.isUnique = !!this.config.isUnique; + this.isIndexed = !!this.config.isIndexed && !this.config.isUnique; + } + + getPrismaSchema() { + return [this._schemaField({ type: 'DateTime' })]; + } + + _stringToDate(s) { + return s && new Date(s); + } + + getQueryConditions(dbPath) { + return { + ...this.equalityConditions(dbPath, this._stringToDate), + ...this.orderingConditions(dbPath, this._stringToDate), + ...this.inConditions(dbPath, this._stringToDate), + }; + } + + setupHooks({ addPreSaveHook }) { + addPreSaveHook(item => { + if (item[this.path]) { + item[this.path] = this._stringToDate(item[this.path]); + } + return item; + }); + } +} diff --git a/packages/fields/src/types/DateTimeUtc/index.js b/packages/fields/src/types/DateTimeUtc/index.js index 0257737cbdd..33a1acc7ee1 100644 --- a/packages/fields/src/types/DateTimeUtc/index.js +++ b/packages/fields/src/types/DateTimeUtc/index.js @@ -2,6 +2,7 @@ import { DateTimeUtcImplementation, MongoDateTimeUtcInterface, KnexDateTimeUtcInterface, + PrismaDateTimeUtcInterface, } from './Implementation'; import DateTime from '../DateTime'; @@ -17,5 +18,6 @@ export default { adapters: { mongoose: MongoDateTimeUtcInterface, knex: KnexDateTimeUtcInterface, + prisma: PrismaDateTimeUtcInterface, }, }; diff --git a/packages/fields/src/types/Decimal/test-fixtures.js b/packages/fields/src/types/Decimal/test-fixtures.js index 8f896c16cdb..ccd79cc4d51 100644 --- a/packages/fields/src/types/Decimal/test-fixtures.js +++ b/packages/fields/src/types/Decimal/test-fixtures.js @@ -7,6 +7,7 @@ export const exampleValue = () => '6.28'; export const exampleValue2 = () => '6.45'; export const supportsUnique = true; export const fieldName = 'price'; +export const unSupportedAdapterList = ['prisma']; export const getTestFields = () => ({ name: { type: Text }, diff --git a/packages/fields/src/types/File/Implementation.js b/packages/fields/src/types/File/Implementation.js index 03fbc6e279c..78382568905 100644 --- a/packages/fields/src/types/File/Implementation.js +++ b/packages/fields/src/types/File/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import mongoose from 'mongoose'; // Disabling the getter of mongoose >= 5.1.0 @@ -159,3 +160,21 @@ export class KnexFileInterface extends CommonFileInterface(KnexFieldAdapter) { if (this.defaultTo) column.defaultTo(this.defaultTo); } } + +export class PrismaFileInterface extends CommonFileInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + + // Error rather than ignoring invalid config + // We totally can index these values, it's just not trivial. See issue #1297 + if (this.config.isIndexed) { + throw ( + `The File field type doesn't support indexes on Prisma. ` + + `Check the config for ${this.path} on the ${this.field.listKey} list` + ); + } + } + getPrismaSchema() { + return [this._schemaField({ type: 'Json' })]; + } +} diff --git a/packages/fields/src/types/File/index.js b/packages/fields/src/types/File/index.js index 7838d426d18..b767ebf90f9 100644 --- a/packages/fields/src/types/File/index.js +++ b/packages/fields/src/types/File/index.js @@ -1,4 +1,4 @@ -import { File, MongoFileInterface, KnexFileInterface } from './Implementation'; +import { File, MongoFileInterface, KnexFileInterface, PrismaFileInterface } from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -12,5 +12,6 @@ export default { adapters: { mongoose: MongoFileInterface, knex: KnexFileInterface, + prisma: PrismaFileInterface, }, }; diff --git a/packages/fields/src/types/File/test-fixtures.js b/packages/fields/src/types/File/test-fixtures.js index 56aa7e96681..853bb56411c 100644 --- a/packages/fields/src/types/File/test-fixtures.js +++ b/packages/fields/src/types/File/test-fixtures.js @@ -11,6 +11,7 @@ export const type = File; export const supportsUnique = false; export const fieldName = 'image'; export const subfieldName = 'originalFilename'; +export const unSupportedAdapterList = ['prisma']; // Grab all the image files from the directory const directory = './files'; diff --git a/packages/fields/src/types/Float/Implementation.js b/packages/fields/src/types/Float/Implementation.js index c157287804e..e84637aca21 100644 --- a/packages/fields/src/types/Float/Implementation.js +++ b/packages/fields/src/types/Float/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; export class Float extends Implementation { constructor() { @@ -66,3 +67,13 @@ export class KnexFloatInterface extends CommonFloatInterface(KnexFieldAdapter) { if (typeof this.defaultTo !== 'undefined') column.defaultTo(this.defaultTo); } } + +export class PrismaFloatInterface extends CommonFloatInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + } + + getPrismaSchema() { + return this._schemaField({ type: 'Float' }); + } +} diff --git a/packages/fields/src/types/Float/index.js b/packages/fields/src/types/Float/index.js index e9e1ee45844..080c4f2bad6 100644 --- a/packages/fields/src/types/Float/index.js +++ b/packages/fields/src/types/Float/index.js @@ -1,4 +1,9 @@ -import { Float, MongoFloatInterface, KnexFloatInterface } from './Implementation'; +import { + Float, + MongoFloatInterface, + KnexFloatInterface, + PrismaFloatInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -12,5 +17,6 @@ export default { adapters: { mongoose: MongoFloatInterface, knex: KnexFloatInterface, + prisma: PrismaFloatInterface, }, }; diff --git a/packages/fields/src/types/Integer/Implementation.js b/packages/fields/src/types/Integer/Implementation.js index 30151bd2cac..86c64058e67 100644 --- a/packages/fields/src/types/Integer/Implementation.js +++ b/packages/fields/src/types/Integer/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; export class Integer extends Implementation { constructor() { @@ -73,3 +74,13 @@ export class KnexIntegerInterface extends CommonIntegerInterface(KnexFieldAdapte if (typeof this.defaultTo !== 'undefined') column.defaultTo(this.defaultTo); } } + +export class PrismaIntegerInterface extends CommonIntegerInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + } + + getPrismaSchema() { + return this._schemaField({ type: 'Int' }); + } +} diff --git a/packages/fields/src/types/Integer/index.js b/packages/fields/src/types/Integer/index.js index 7df524823af..ac4316aaf99 100644 --- a/packages/fields/src/types/Integer/index.js +++ b/packages/fields/src/types/Integer/index.js @@ -1,4 +1,9 @@ -import { Integer, MongoIntegerInterface, KnexIntegerInterface } from './Implementation'; +import { + Integer, + MongoIntegerInterface, + KnexIntegerInterface, + PrismaIntegerInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -12,5 +17,6 @@ export default { adapters: { mongoose: MongoIntegerInterface, knex: KnexIntegerInterface, + prisma: PrismaIntegerInterface, }, }; diff --git a/packages/fields/src/types/Password/Implementation.js b/packages/fields/src/types/Password/Implementation.js index 19a5d1ae815..7ca58efeea5 100644 --- a/packages/fields/src/types/Password/Implementation.js +++ b/packages/fields/src/types/Password/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import dumbPasswords from 'dumb-passwords'; const bcryptHashRegex = /^\$2[aby]?\$\d{1,2}\$[.\/A-Za-z0-9]{53}$/; @@ -170,3 +171,32 @@ export class KnexPasswordInterface extends CommonPasswordInterface(KnexFieldAdap }; } } + +export class PrismaPasswordInterface extends CommonPasswordInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + + // Error rather than ignoring invalid config + if (this.config.isUnique || this.config.isIndexed) { + throw ( + `The Password field type doesn't support indexes on Prisma. ` + + `Check the config for ${this.path} on the ${this.field.listKey} list` + ); + } + } + + getPrismaSchema() { + return [this._schemaField({ type: 'String' })]; + } + + getQueryConditions(dbPath) { + // JM: I wonder if performing a regex match here leaks any timing info that + // could be used to extract information about the hash.. :/ + return { + // FIXME: Prisma needs to support regex matching... + [`${this.path}_is_set`]: value => (value ? { NOT: { [dbPath]: null } } : { [dbPath]: null }), + // ? b.where(dbPath, '~', bcryptHashRegex.source) + // : b.where(dbPath, '!~', bcryptHashRegex.source).orWhereNull(dbPath), + }; + } +} diff --git a/packages/fields/src/types/Password/index.js b/packages/fields/src/types/Password/index.js index 79fedf73303..3adcaf78aa3 100644 --- a/packages/fields/src/types/Password/index.js +++ b/packages/fields/src/types/Password/index.js @@ -1,4 +1,9 @@ -import { Password, MongoPasswordInterface, KnexPasswordInterface } from './Implementation'; +import { + Password, + MongoPasswordInterface, + KnexPasswordInterface, + PrismaPasswordInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -13,5 +18,6 @@ export default { adapters: { mongoose: MongoPasswordInterface, knex: KnexPasswordInterface, + prisma: PrismaPasswordInterface, }, }; diff --git a/packages/fields/src/types/Relationship/Implementation.js b/packages/fields/src/types/Relationship/Implementation.js index 6d17e065232..4e50cee9c03 100644 --- a/packages/fields/src/types/Relationship/Implementation.js +++ b/packages/fields/src/types/Relationship/Implementation.js @@ -1,6 +1,7 @@ import mongoose from 'mongoose'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import { Implementation } from '../../Implementation'; import { resolveNested } from './nested-mutations'; @@ -424,3 +425,30 @@ export class KnexRelationshipInterface extends KnexFieldAdapter { }; } } + +export class PrismaRelationshipInterface extends PrismaFieldAdapter { + constructor() { + super(...arguments); + this.idPath = `${this.dbPath}Id`; + this.isRelationship = true; + + // Default isIndexed to true if it's not explicitly provided + // Mutually exclusive with isUnique + this.isUnique = typeof this.config.isUnique === 'undefined' ? false : !!this.config.isUnique; + this.isIndexed = + typeof this.config.isIndexed === 'undefined' + ? !this.config.isUnique + : !!this.config.isIndexed; + + // JM: It bugs me this is duplicated in the implementation but initialisation order makes it hard to avoid + const [refListKey, refFieldPath] = this.config.ref.split('.'); + this.refListKey = refListKey; + this.refFieldPath = refFieldPath; + } + + getQueryConditions(dbPath) { + return { + [`${this.path}_is_null`]: value => (value ? { [dbPath]: null } : { NOT: { [dbPath]: null } }), + }; + } +} diff --git a/packages/fields/src/types/Relationship/index.js b/packages/fields/src/types/Relationship/index.js index 4dbdb12b80d..1d4955d8af5 100644 --- a/packages/fields/src/types/Relationship/index.js +++ b/packages/fields/src/types/Relationship/index.js @@ -2,6 +2,7 @@ import { Relationship, MongoRelationshipInterface, KnexRelationshipInterface, + PrismaRelationshipInterface, } from './Implementation'; import { resolveView } from '../../resolve-view'; @@ -18,5 +19,6 @@ export default { adapters: { mongoose: MongoRelationshipInterface, knex: KnexRelationshipInterface, + prisma: PrismaRelationshipInterface, }, }; diff --git a/packages/fields/src/types/Select/Implementation.js b/packages/fields/src/types/Select/Implementation.js index be5f61da347..1b8e8b71d21 100644 --- a/packages/fields/src/types/Select/Implementation.js +++ b/packages/fields/src/types/Select/Implementation.js @@ -2,6 +2,7 @@ import inflection from 'inflection'; import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; function initOptions(options) { let optionsArray = options; @@ -170,3 +171,34 @@ export class KnexSelectInterface extends CommonSelectInterface(KnexFieldAdapter) if (typeof this.defaultTo !== 'undefined') column.defaultTo(this.defaultTo); } } + +export class PrismaSelectInterface extends CommonSelectInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + this.isUnique = !!this.config.isUnique; + this.isIndexed = !!this.config.isIndexed && !this.config.isUnique; + const dataType = this.config.dataType || 'enum'; + this._enumName = + dataType === 'enum' && `${this.field.listKey}${inflection.classify(this.path)}Enum`; + } + + getPrismaEnums() { + if (this.field.dataType === 'enum') { + return [ + `enum ${this._enumName} { + ${this.field.options.map(i => i.value).join('\n')} + }`, + ]; + } else return []; + } + + getPrismaSchema() { + if (this.field.dataType === 'enum') { + return [this._schemaField({ type: this._enumName })]; + } else if (this.field.dataType === 'integer') { + return [this._schemaField({ type: 'Int' })]; + } else { + return [this._schemaField({ type: 'String' })]; + } + } +} diff --git a/packages/fields/src/types/Select/index.js b/packages/fields/src/types/Select/index.js index 1ce193a2765..a91311facc2 100644 --- a/packages/fields/src/types/Select/index.js +++ b/packages/fields/src/types/Select/index.js @@ -1,4 +1,9 @@ -import { Select, MongoSelectInterface, KnexSelectInterface } from './Implementation'; +import { + Select, + MongoSelectInterface, + KnexSelectInterface, + PrismaSelectInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -13,5 +18,6 @@ export default { adapters: { mongoose: MongoSelectInterface, knex: KnexSelectInterface, + prisma: PrismaSelectInterface, }, }; diff --git a/packages/fields/src/types/Slug/Implementation.js b/packages/fields/src/types/Slug/Implementation.js index 4ddefc82f24..9b8b7e8f490 100644 --- a/packages/fields/src/types/Slug/Implementation.js +++ b/packages/fields/src/types/Slug/Implementation.js @@ -5,6 +5,7 @@ import { Text, MongoTextInterface as MongoSlugInterface, KnexTextInterface as KnexSlugInterface, + PrismaTextInterface as PrismaSlugInterface, } from '../Text/Implementation'; const MAX_UNIQUE_ATTEMPTS = 100; @@ -240,4 +241,4 @@ export class SlugImplementation extends Text { } } -export { MongoSlugInterface, KnexSlugInterface }; +export { MongoSlugInterface, KnexSlugInterface, PrismaSlugInterface }; diff --git a/packages/fields/src/types/Slug/index.js b/packages/fields/src/types/Slug/index.js index 2a0502b2373..1ff7421b16d 100644 --- a/packages/fields/src/types/Slug/index.js +++ b/packages/fields/src/types/Slug/index.js @@ -1,4 +1,9 @@ -import { SlugImplementation, MongoSlugInterface, KnexSlugInterface } from './Implementation'; +import { + SlugImplementation, + MongoSlugInterface, + KnexSlugInterface, + PrismaSlugInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; const Slug = { @@ -12,6 +17,7 @@ const Slug = { adapters: { knex: KnexSlugInterface, mongoose: MongoSlugInterface, + prisma: PrismaSlugInterface, }, }; diff --git a/packages/fields/src/types/Text/Implementation.js b/packages/fields/src/types/Text/Implementation.js index a318b966059..8d85ac850a4 100644 --- a/packages/fields/src/types/Text/Implementation.js +++ b/packages/fields/src/types/Text/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; export class Text extends Implementation { constructor(path, { isMultiline }) { @@ -75,3 +76,15 @@ export class KnexTextInterface extends CommonTextInterface(KnexFieldAdapter) { if (typeof this.defaultTo !== 'undefined') column.defaultTo(this.defaultTo); } } + +export class PrismaTextInterface extends CommonTextInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + this.isUnique = !!this.config.isUnique; + this.isIndexed = !!this.config.isIndexed && !this.config.isUnique; + } + + getPrismaSchema() { + return [this._schemaField({ type: 'String' })]; + } +} diff --git a/packages/fields/src/types/Text/index.js b/packages/fields/src/types/Text/index.js index 200a3bdee8b..4ce653c34b7 100644 --- a/packages/fields/src/types/Text/index.js +++ b/packages/fields/src/types/Text/index.js @@ -1,4 +1,4 @@ -import { Text, MongoTextInterface, KnexTextInterface } from './Implementation'; +import { Text, MongoTextInterface, KnexTextInterface, PrismaTextInterface } from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -12,5 +12,6 @@ export default { adapters: { mongoose: MongoTextInterface, knex: KnexTextInterface, + prisma: PrismaTextInterface, }, }; diff --git a/packages/fields/src/types/Url/index.js b/packages/fields/src/types/Url/index.js index 09b744b80c1..27dbcf7189c 100644 --- a/packages/fields/src/types/Url/index.js +++ b/packages/fields/src/types/Url/index.js @@ -1,4 +1,9 @@ -import { Text, MongoTextInterface, KnexTextInterface } from '../Text/Implementation'; +import { + Text, + MongoTextInterface, + KnexTextInterface, + PrismaTextInterface, +} from '../Text/Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -13,5 +18,6 @@ export default { adapters: { mongoose: MongoTextInterface, knex: KnexTextInterface, + prisma: PrismaTextInterface, }, }; diff --git a/packages/fields/src/types/Uuid/Implementation.js b/packages/fields/src/types/Uuid/Implementation.js index 32697d02d21..2b708d17fc1 100644 --- a/packages/fields/src/types/Uuid/Implementation.js +++ b/packages/fields/src/types/Uuid/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; export class UuidImplementation extends Implementation { constructor(path, { caseTo = 'lower' }) { @@ -137,3 +138,26 @@ export class KnexUuidInterface extends KnexFieldAdapter { }; } } + +export class PrismaUuidInterface extends PrismaFieldAdapter { + constructor() { + super(...arguments); + + // TODO: Warning on invalid config for primary keys? + if (!this.field.isPrimaryKey) { + this.isUnique = !!this.config.isUnique; + this.isIndexed = !!this.config.isIndexed && !this.config.isUnique; + } + } + + getPrismaSchema() { + return [this._schemaField({ type: 'String' })]; + } + + getQueryConditions(dbPath) { + return { + ...this.equalityConditions(dbPath, this.field.normaliseValue), + ...this.inConditions(dbPath, this.field.normaliseValue), + }; + } +} diff --git a/packages/fields/src/types/Uuid/index.js b/packages/fields/src/types/Uuid/index.js index 9fd50df0d03..9f222ce8efd 100644 --- a/packages/fields/src/types/Uuid/index.js +++ b/packages/fields/src/types/Uuid/index.js @@ -1,4 +1,9 @@ -import { UuidImplementation, MongoUuidInterface, KnexUuidInterface } from './Implementation'; +import { + UuidImplementation, + MongoUuidInterface, + KnexUuidInterface, + PrismaUuidInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; const Uuid = { @@ -12,6 +17,7 @@ const Uuid = { adapters: { knex: KnexUuidInterface, mongoose: MongoUuidInterface, + prisma: PrismaUuidInterface, }, primaryKeyDefaults: { @@ -30,6 +36,15 @@ const Uuid = { ); }, }, + prisma: { + getConfig: client => { + throw ( + `The Uuid field type doesn't provide a default primary key field configuration for the ` + + `'${client}' prisma client. You'll need to supply your own 'id' field for each list or use a ` + + `different field type for your ids (eg '@keystonejs/fields-auto-increment').` + ); + }, + }, mongoose: { getConfig: () => { throw ( diff --git a/packages/fields/src/types/Virtual/Implementation.js b/packages/fields/src/types/Virtual/Implementation.js index 0566a56783d..6f9c02b7691 100644 --- a/packages/fields/src/types/Virtual/Implementation.js +++ b/packages/fields/src/types/Virtual/Implementation.js @@ -1,6 +1,7 @@ import { Implementation } from '../../Implementation'; import { MongooseFieldAdapter } from '@keystonejs/adapter-mongoose'; import { KnexFieldAdapter } from '@keystonejs/adapter-knex'; +import { PrismaFieldAdapter } from '@keystonejs/adapter-prisma'; import { parseFieldAccess } from '@keystonejs/access-control'; export class Virtual extends Implementation { @@ -81,3 +82,13 @@ export class KnexVirtualInterface extends CommonTextInterface(KnexFieldAdapter) } addToTableSchema() {} } + +export class PrismaVirtualInterface extends CommonTextInterface(PrismaFieldAdapter) { + constructor() { + super(...arguments); + this.realKeys = []; + } + getPrismaSchema() { + return []; + } +} diff --git a/packages/fields/src/types/Virtual/index.js b/packages/fields/src/types/Virtual/index.js index b5df99a2958..61814be7c41 100644 --- a/packages/fields/src/types/Virtual/index.js +++ b/packages/fields/src/types/Virtual/index.js @@ -1,4 +1,9 @@ -import { Virtual, MongoVirtualInterface, KnexVirtualInterface } from './Implementation'; +import { + Virtual, + MongoVirtualInterface, + KnexVirtualInterface, + PrismaVirtualInterface, +} from './Implementation'; import { resolveView } from '../../resolve-view'; export default { @@ -13,5 +18,6 @@ export default { adapters: { mongoose: MongoVirtualInterface, knex: KnexVirtualInterface, + prisma: PrismaVirtualInterface, }, }; diff --git a/packages/test-utils/lib/test-utils.js b/packages/test-utils/lib/test-utils.js index dcb0c3f964c..a91c269e8be 100644 --- a/packages/test-utils/lib/test-utils.js +++ b/packages/test-utils/lib/test-utils.js @@ -1,3 +1,5 @@ +const path = require('path'); +const crypto = require('crypto'); const express = require('express'); const supertest = require('supertest-light'); const MongoDBMemoryServer = require('mongodb-memory-server-core').default; @@ -6,6 +8,7 @@ const { Keystone } = require('@keystonejs/keystone'); const { GraphQLApp } = require('@keystonejs/app-graphql'); const { KnexAdapter } = require('@keystonejs/adapter-knex'); const { MongooseAdapter } = require('@keystonejs/adapter-mongoose'); +const { PrismaAdapter } = require('@keystonejs/adapter-prisma'); async function setupServer({ adapterName, @@ -15,7 +18,9 @@ async function setupServer({ keystoneOptions, graphqlOptions = {}, }) { - const Adapter = { mongoose: MongooseAdapter, knex: KnexAdapter }[adapterName]; + const Adapter = { mongoose: MongooseAdapter, knex: KnexAdapter, prisma: PrismaAdapter }[ + adapterName + ]; const argGenerator = { mongoose: getMongoMemoryServerConfig, @@ -26,6 +31,21 @@ async function setupServer({ process.env.DATABASE_URL || process.env.KNEX_URI || 'postgres://localhost/keystone', }, }), + prisma: () => ({ + dropDatabase: true, + url: process.env.DATABASE_URL, + // Put the generated client at a unique path + getPrismaPath: ({ prismaSchema }) => + path.join( + '.api-test-prisma-clients', + crypto.createHash('sha256').update(prismaSchema).digest('hex') + ), + // Slice down to the hash make a valid postgres schema name + getDbSchemaName: ({ prismaSchema }) => + crypto.createHash('sha256').update(prismaSchema).digest('hex').slice(0, 16), + // Turn this on if you need verbose debug info + enableLogging: false, + }), }[adapterName]; const keystone = new Keystone({ @@ -179,6 +199,12 @@ function multiAdapterRunners(only) { before: _before('knex'), after: _after(() => {}), }, + { + runner: _keystoneRunner('prisma', () => {}), + adapterName: 'prisma', + before: _before('prisma'), + after: _after(() => {}), + }, ].filter(a => typeof only === 'undefined' || a.adapterName === only); } diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 4d5da9aab60..c2547cd24c0 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -10,6 +10,7 @@ "dependencies": { "@keystonejs/adapter-knex": "^11.0.6", "@keystonejs/adapter-mongoose": "^9.0.7", + "@keystonejs/adapter-prisma": "^0.0.0", "@keystonejs/app-graphql": "^6.1.3", "@keystonejs/keystone": "^16.0.0", "express": "^4.17.1", diff --git a/tests/api-tests/access-control/utils.js b/tests/api-tests/access-control/utils.js index 844cd4d5430..caa8f5481a1 100644 --- a/tests/api-tests/access-control/utils.js +++ b/tests/api-tests/access-control/utils.js @@ -3,8 +3,8 @@ const { Text } = require('@keystonejs/fields'); const { PasswordAuthStrategy } = require('@keystonejs/auth-password'); const { objMerge } = require('@keystonejs/utils'); -const FAKE_ID = { mongoose: '5b3eabd9e9f2e3e4866742ea', knex: 137 }; -const FAKE_ID_2 = { mongoose: '5b3eabd9e9f2e3e4866742eb', knex: 138 }; +const FAKE_ID = { mongoose: '5b3eabd9e9f2e3e4866742ea', knex: 137, prisma: 137 }; +const FAKE_ID_2 = { mongoose: '5b3eabd9e9f2e3e4866742eb', knex: 138, prisma: 138 }; const yesNo = truthy => (truthy ? 'Yes' : 'No'); diff --git a/tests/api-tests/fields/unique.test.js b/tests/api-tests/fields/unique.test.js index 00642c93044..1e609809c0e 100644 --- a/tests/api-tests/fields/unique.test.js +++ b/tests/api-tests/fields/unique.test.js @@ -68,7 +68,9 @@ multiAdapterRunners().map(({ runner, adapterName, after }) => expect(errors2).toHaveProperty('0.message'); expect(errors2[0].message).toEqual( - expect.stringMatching(/duplicate key|to be unique/) + expect.stringMatching( + /duplicate key|to be unique|Unique constraint failed on the fields/ + ) ); }) ); @@ -91,7 +93,9 @@ multiAdapterRunners().map(({ runner, adapterName, after }) => expect(errors).toHaveProperty('0.message'); expect(errors[0].message).toEqual( - expect.stringMatching(/duplicate key|to be unique/) + expect.stringMatching( + /duplicate key|to be unique|Unique constraint failed on the fields/ + ) ); }) ); @@ -123,7 +127,12 @@ multiAdapterRunners().map(({ runner, adapterName, after }) => testModules .map(require) - .filter(({ supportsUnique }) => !supportsUnique && supportsUnique !== null) + .filter( + ({ supportsUnique, unSupportedAdapterList = [] }) => + !supportsUnique && + supportsUnique !== null && + !unSupportedAdapterList.includes(adapterName) + ) .forEach(mod => { (mod.testMatrix || ['default']).forEach(matrixValue => { describe(`${mod.name} - ${matrixValue} - isUnique`, () => { diff --git a/yarn.lock b/yarn.lock index 4194f63be4c..1f0c0eb4ab1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4036,6 +4036,127 @@ dependencies: object-assign "^4.1.1" +"@prisma/ci-info@2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@prisma/ci-info/-/ci-info-2.1.2.tgz#3da64f54584bde0aaf4b42f298a6c63f025aeb3f" + integrity sha512-RhAHY+wp6Nqu89Tp3zfUVkpGfqk4TfngeOWaMGgmhP7mB2ASDtOl8dkwxHmI8eN4edo+luyjPmbJBC4kST321A== + +"@prisma/cli@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.7.1.tgz#98f2cb434bb931341e6c6292c7bab601e5f842f8" + integrity sha512-0uA+gWkNQ35DveVHDPltiTCTr4wcXtEhnPs463IEM+Xn8dTv9x0gtZiYHSuQM3t7uwlOxj1rurBsqSbiljynfQ== + +"@prisma/client@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.7.1.tgz#0a37ddff7fe80ae3a86dfa620c1141c8607be6c2" + integrity sha512-IEWDCuvIaQTira8/jAyf+uY+AuPPUFDIXMSN4zEA/gvoJv2woq7RmkaubS+NQVgDbbyOR6F3UcXLiFTYQDzZkQ== + dependencies: + pkg-up "^3.1.0" + +"@prisma/debug@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-2.7.1.tgz#8ac51f9735fe07bc5e29ef247ff18625ccb96c4a" + integrity sha512-qF9xBjNWtf7yZBAKtHJB+Sp0kxdF/K5rUyVk9VoMNNgTrIFkaOcDY68DUlldxh+txjydUT4YacR5P4qXZocsqg== + dependencies: + debug "^4.1.1" + +"@prisma/engine-core@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/engine-core/-/engine-core-2.7.1.tgz#87dd6deba49508e3475bc668c3ede2060d782b31" + integrity sha512-vbNeKBl6Nl1KJuwCCwva8v4BXuv/jydA/Yl5Jnrf11KGM3BSCRavDGj4N1rxdxXpnp+L4Icb2OkxXYgdEWKPdA== + dependencies: + "@prisma/debug" "2.7.1" + "@prisma/generator-helper" "2.7.1" + "@prisma/get-platform" "2.7.1" + chalk "^4.0.0" + cross-fetch "^3.0.4" + execa "^4.0.2" + get-stream "^5.1.0" + indent-string "^4.0.0" + new-github-issue-url "^0.2.1" + p-retry "^4.2.0" + terminal-link "^2.1.1" + undici "git://github.com/nodejs/undici.git#e76f6a37836537f08c2d9b7d8805d6ff21d1e744" + +"@prisma/fetch-engine@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-2.7.1.tgz#855a32cdcb22e3c105b5f10e5490e25b7a96d02a" + integrity sha512-BmJW8RGQCMAtYSgmCOH3X4trdzzejum4yuutruDKJQDP67fPmd8Waq/SRlnDcjhDs+2ZI6lKMaj6qTCU9Qd+MQ== + dependencies: + "@prisma/debug" "2.7.1" + "@prisma/get-platform" "2.7.1" + chalk "^4.0.0" + execa "^4.0.0" + find-cache-dir "^3.3.1" + hasha "^5.2.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + make-dir "^3.0.2" + node-fetch "^2.6.0" + p-filter "^2.1.0" + p-map "^4.0.0" + p-queue "^6.4.0" + p-retry "^4.2.0" + progress "^2.0.3" + rimraf "^3.0.2" + temp-dir "^2.0.0" + tempy "^0.6.0" + +"@prisma/generator-helper@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/generator-helper/-/generator-helper-2.7.1.tgz#31158537cb79777f42ec2b15e71943edd6c39924" + integrity sha512-gG2BRDefyIIXSx6uMFTof8Nd0jkZjPs6ExvTcra7xi36+oqkdgjOmq1vp6x3+VfC+EXzY1qReBZViuZZSFH4Cg== + dependencies: + "@prisma/debug" "2.7.1" + "@types/cross-spawn" "^6.0.1" + chalk "^4.0.0" + cross-spawn "^7.0.2" + +"@prisma/get-platform@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-2.7.1.tgz#3da81ad3ea96c7ff8bed7cb3a4058c1f919dd123" + integrity sha512-2G/dbvIUC8rC4pKR+MX9jdcqnTbdSvYvhTLhc99y5Z2pI2dV+/vb3dWSXHWqmLWsmY482n9X+HngxQGnX4V4bQ== + dependencies: + "@prisma/debug" "2.7.1" + +"@prisma/sdk@2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@prisma/sdk/-/sdk-2.7.1.tgz#cb3f33ef74e335dc0741dcc0b89fbb4c8c8508e0" + integrity sha512-vXPxcoxVuxXkNcMOx9+Bx89uiTfTDSJ/dOiNjWKV3Y7NaQj5kphik7wdt76QQees7Zu3cG6MwHRY7Awbkc6ZLw== + dependencies: + "@prisma/debug" "2.7.1" + "@prisma/engine-core" "2.7.1" + "@prisma/fetch-engine" "2.7.1" + "@prisma/generator-helper" "2.7.1" + "@prisma/get-platform" "2.7.1" + "@timsuchanek/copy" "^1.4.5" + archiver "^4.0.0" + arg "^4.1.3" + chalk "4.1.0" + checkpoint-client "1.1.11" + cli-truncate "^2.1.0" + dotenv "^8.2.0" + execa "^4.0.0" + global-dirs "^2.0.1" + globby "^11.0.0" + has-yarn "^2.1.0" + make-dir "^3.0.2" + node-fetch "2.6.1" + p-map "^4.0.0" + read-pkg-up "^7.0.1" + resolve-pkg "^2.0.0" + rimraf "^3.0.2" + string-width "^4.2.0" + strip-ansi "6.0.0" + strip-indent "3.0.0" + tar "^6.0.1" + temp-dir "^2.0.0" + temp-write "^4.0.0" + tempy "^0.6.0" + terminal-link "^2.1.1" + tmp "0.2.1" + url-parse "^1.4.7" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -4358,6 +4479,21 @@ dependencies: defer-to-connect "^2.0.0" +"@timsuchanek/copy@^1.4.5": + version "1.4.5" + resolved "https://registry.yarnpkg.com/@timsuchanek/copy/-/copy-1.4.5.tgz#8e9658c056e24e1928a88bed45f9eac6a72b7c40" + integrity sha512-N4+2/DvfwzQqHYL/scq07fv8yXbZc6RyUxKJoE8Clm14JpLOf9yNI4VB4D6RsV3h9zgzZ4loJUydHKM7pp3blw== + dependencies: + "@timsuchanek/sleep-promise" "^8.0.1" + commander "^2.19.0" + mkdirp "^1.0.4" + prettysize "^2.0.0" + +"@timsuchanek/sleep-promise@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@timsuchanek/sleep-promise/-/sleep-promise-8.0.1.tgz#81c0754b345138a519b51c2059771eb5f9b97818" + integrity sha512-cxHYbrXfnCWsklydIHSw5GCMHUPqpJ/enxWSyVHNOgNe61sit/+aOXTTI+VOdWkvVaJsI2vsB9N4+YDNITawOQ== + "@tinymce/tinymce-react@^3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@tinymce/tinymce-react/-/tinymce-react-3.6.1.tgz#ed4f1047fbaa37018f687887be21b6b6ab4cdb54" @@ -4365,6 +4501,11 @@ dependencies: prop-types "^15.6.2" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@types/accepts@*", "@types/accepts@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" @@ -4478,6 +4619,13 @@ dependencies: "@types/express" "*" +"@types/cross-spawn@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.2.tgz#168309de311cd30a2b8ae720de6475c2fbf33ac7" + integrity sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw== + dependencies: + "@types/node" "*" + "@types/debug@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.29.tgz#a1e514adfbd92f03a224ba54d693111dbf1f3754" @@ -4898,6 +5046,11 @@ dependencies: "@types/node" "*" +"@types/retry@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + "@types/semver@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" @@ -6199,6 +6352,35 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-4.0.2.tgz#43c72865eadb4ddaaa2fb74852527b6a450d927c" + integrity sha512-B9IZjlGwaxF33UN4oPbfBkyA4V1SxNLeIhR1qY8sRXSsbdUkEHrrOvwlYFPx+8uQeCe9M+FG6KgO+imDmQ79CQ== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.0" + buffer-crc32 "^0.2.1" + glob "^7.1.6" + readable-stream "^3.6.0" + tar-stream "^2.1.2" + zip-stream "^3.0.1" + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -6443,6 +6625,11 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -7399,7 +7586,7 @@ buffer-alloc@^1.1.0, buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" -buffer-crc32@~0.2.3: +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= @@ -7883,6 +8070,14 @@ chalk@4.0.0, chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@4.1.0, chalk@^4.1.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" + chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -7900,14 +8095,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.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" - change-case@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.1.0.tgz#0e611b7edc9952df2e8513b27b42de72647dd17e" @@ -8001,6 +8188,20 @@ cheerio@1.0.0-rc.3, cheerio@^1.0.0-rc.3: lodash "^4.15.0" parse5 "^3.0.1" +checkpoint-client@1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/checkpoint-client/-/checkpoint-client-1.1.11.tgz#992818640b9ef12d66304bf973d993760b1e6926" + integrity sha512-p+eDmbuKlP6oHgknetUoqWTHnQsWfSbDlaMlKgwNh8RiEdLQVZ5z1rcU4+0iBynZe2z8sJHHSdWo9VQTmGWRLw== + dependencies: + "@prisma/ci-info" "2.1.2" + cross-spawn "7.0.3" + env-paths "2.2.0" + fast-write-atomic "0.2.1" + make-dir "3.1.0" + ms "2.1.2" + node-fetch "2.6.0" + uuid "8.1.0" + cheerio@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -8128,6 +8329,11 @@ chownr@^1.1.2, chownr@^1.1.3: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.0, chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -8271,6 +8477,14 @@ cli-truncate@^1.1.0: slice-ansi "^1.0.0" string-width "^2.0.0" +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" @@ -8600,6 +8814,16 @@ compose-function@3.0.3: dependencies: arity-n "^1.0.4" +compress-commons@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-3.0.0.tgz#833944d84596e537224dd91cf92f5246823d4f1d" + integrity sha512-FyDqr8TKX5/X0qo+aVfaZ+PVmNJHJeckFBlq8jZGSJOgnynhfifoyl24qaqdUdDIBe0EVTHByN6NAkqYvE/2Xg== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^3.0.1" + normalize-path "^3.0.0" + readable-stream "^2.3.7" + compressible@~2.0.16: version "2.0.16" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.16.tgz#a49bf9858f3821b64ce1be0296afc7380466a77f" @@ -8929,7 +9153,15 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -crc@^3.8.0: +crc32-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85" + integrity sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w== + dependencies: + crc "^3.4.4" + readable-stream "^3.4.0" + +crc@^3.4.4, crc@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== @@ -9031,7 +9263,7 @@ cross-fetch@3.0.5: dependencies: node-fetch "2.6.0" -cross-fetch@^3.0.6: +cross-fetch@^3.0.4, cross-fetch@^3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== @@ -9047,6 +9279,15 @@ cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@7.0.3, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -9067,15 +9308,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1: shebang-command "^2.0.0" which "^2.0.1" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -9110,6 +9342,11 @@ crypto-random-string@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + css-blank-pseudo@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" @@ -10721,6 +10958,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== +env-paths@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" + integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== + envinfo@^5.8.1: version "5.12.1" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.12.1.tgz#83068c33e0972eb657d6bc69a6df30badefb46ef" @@ -11371,7 +11613,7 @@ eventemitter3@^3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== @@ -11879,6 +12121,11 @@ fast-url-parser@^1.1.3: dependencies: punycode "^1.3.2" +fast-write-atomic@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz#7ee8ef0ce3c1f531043c09ae8e5143361ab17ede" + integrity sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw== + fastparse@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" @@ -13911,6 +14158,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -13944,6 +14196,14 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.0.tgz#33094d1f69c40a4a6ac7be53d5fe3ff95a269e0c" + integrity sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + hast-to-hyperscript@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-5.0.0.tgz#5106cbba78edb7c95e2e8a49079371eb196c1ced" @@ -14437,6 +14697,15 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-proxy-middleware@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab" @@ -16682,6 +16951,13 @@ lazy-cache@^1.0.3: resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + dependencies: + readable-stream "^2.0.5" + lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -16991,11 +17267,16 @@ lodash.deburr@^4.1.0: resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b" integrity sha1-3bG7s+8HRYwBd7oH3hRCLLAz/5s= -lodash.defaults@^4.0.1: +lodash.defaults@^4.0.1, lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= + lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" @@ -17011,7 +17292,7 @@ lodash.filter@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= -lodash.flatten@^4.2.0: +lodash.flatten@^4.2.0, lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= @@ -17156,6 +17437,11 @@ lodash.unescape@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= + lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -17365,6 +17651,13 @@ mailgun-js@^0.18.0: proxy-agent "~3.0.0" tsscmp "~1.0.0" +make-dir@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -18065,6 +18358,14 @@ minizlib@^2.1.0: minipass "^3.0.0" yallist "^4.0.0" +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -18449,7 +18750,7 @@ mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp dependencies: minimist "^1.2.5" -mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -18741,6 +19042,11 @@ netmask@^1.0.6: resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= +new-github-issue-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/new-github-issue-url/-/new-github-issue-url-0.2.1.tgz#e17be1f665a92de465926603e44b9f8685630c1d" + integrity sha512-md4cGoxuT4T4d/HDOXbrUHkTKrp/vp+m3aOA7XXVYwNsUNMK49g3SQicTSeV5GIz/5QVGAeYRAOlyp9OvlgsYA== + next-tick@^1.0.0, next-tick@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -19726,6 +20032,13 @@ p-map@^3.0.0: dependencies: aggregate-error "^3.0.0" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-pipe@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" @@ -19738,6 +20051,14 @@ p-queue@^5.0.0: dependencies: eventemitter3 "^3.1.0" +p-queue@^6.4.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.0.tgz#263f2b73add4cefca81d8d6b2696ee74b326de2f" + integrity sha512-zPHXPNy9jZsiym0PpJjvnHQysx1fSd/QdaNVwiDRLU2KFChD6h9CkCB6b8i3U8lBwJyA+mHgNZCzcy77glUssQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.1.0" + p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" @@ -19753,6 +20074,14 @@ p-reflect@^2.1.0: resolved "https://registry.yarnpkg.com/p-reflect/-/p-reflect-2.1.0.tgz#5d67c7b3c577c4e780b9451fc9129675bd99fe67" integrity sha512-paHV8NUz8zDHu5lhr/ngGWQiW067DK/+IbJ+RfZ4k+s8y4EKyYCz8pGYWjxCg35eHztpJAt+NUgvN4L+GCbPlg== +p-retry@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.2.0.tgz#ea9066c6b44f23cab4cd42f6147cdbbc6604da5d" + integrity sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.12.0" + p-settle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/p-settle/-/p-settle-4.1.1.tgz#37fbceb2b02c9efc28658fc8d36949922266035f" @@ -19775,6 +20104,13 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -21560,6 +21896,11 @@ pretty-time@^1.1.0: resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== +prettysize@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prettysize/-/prettysize-2.0.0.tgz#902c02480d865d9cc0813011c9feb4fa02ce6996" + integrity sha512-VVtxR7sOh0VsG8o06Ttq5TrI1aiZKmC+ClSn4eBPaNf4SHr5lzbYW+kYGX3HocBL/MfpVrRfFZ9V3vCbLaiplg== + prism-react-renderer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz#1c1be61b1eb9446a146ca7a50b7bcf36f2a70a44" @@ -21990,10 +22331,10 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" - integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg== +querystringify@^2.0.0, querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== quick-format-unescaped@^4.0.1: version "4.0.1" @@ -22702,7 +23043,7 @@ readable-stream@3, readable-stream@^3.0.6: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.3.0, readable-stream@^2.3.5: +readable-stream@^2.0.5, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.7: 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== @@ -23486,6 +23827,13 @@ resolve-pathname@^2.2.0: resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== +resolve-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-2.0.0.tgz#ac06991418a7623edc119084edc98b0e6bf05a41" + integrity sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ== + dependencies: + resolve-from "^5.0.0" + resolve-url-loader@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0" @@ -24407,6 +24755,15 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + sliced@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" @@ -25205,6 +25562,13 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@3.0.0, strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -25217,13 +25581,6 @@ strip-indent@^2.0.0: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - strip-json-comments@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" @@ -25539,7 +25896,7 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar-stream@^2.1.4: +tar-stream@^2.1.2, tar-stream@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa" integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw== @@ -25575,6 +25932,18 @@ tar@^5.0.5: mkdirp "^0.5.0" yallist "^4.0.0" +tar@^6.0.1: + version "6.0.5" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" + integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + tarn@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tarn/-/tarn-2.0.0.tgz#c68499f69881f99ae955b4317ca7d212d942fdee" @@ -25590,6 +25959,22 @@ temp-dir@^1.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + +temp-write@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320" + integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== + dependencies: + graceful-fs "^4.1.15" + is-stream "^2.0.0" + make-dir "^3.0.0" + temp-dir "^1.0.0" + uuid "^3.3.2" + tempfile@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" @@ -25598,6 +25983,16 @@ tempfile@^2.0.0: temp-dir "^1.0.0" uuid "^3.0.1" +tempy@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.6.0.tgz#65e2c35abc06f1124a97f387b08303442bde59f3" + integrity sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw== + dependencies: + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + term-size@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" @@ -25851,6 +26246,13 @@ title-case@^2.1.0: no-case "^2.2.0" upper-case "^1.0.3" +tmp@0.2.1, tmp@^0.2.1, tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmp@^0.0.31: version "0.0.31" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" @@ -25865,13 +26267,6 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -tmp@^0.2.1, tmp@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -26202,6 +26597,11 @@ type-fest@^0.13.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + type-fest@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" @@ -26222,7 +26622,7 @@ type-fest@^0.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== -type-fest@^0.8.1: +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -26336,6 +26736,10 @@ underscore@^1.7.0: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f" integrity sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ== +"undici@git://github.com/nodejs/undici.git#e76f6a37836537f08c2d9b7d8805d6ff21d1e744": + version "1.3.1" + resolved "git://github.com/nodejs/undici.git#e76f6a37836537f08c2d9b7d8805d6ff21d1e744" + unfetch@^4.0.0, unfetch@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db" @@ -26506,6 +26910,13 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + unist-builder@2.0.3, unist-builder@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" @@ -26830,6 +27241,14 @@ url-parse@^1.1.8, url-parse@^1.4.3: querystringify "^2.0.0" requires-port "^1.0.0" +url-parse@^1.4.7: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" @@ -26949,6 +27368,11 @@ uuid@3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" + integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== + uuid@8.3.0, uuid@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" @@ -28300,6 +28724,15 @@ zen-observable@^0.8.14: resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== +zip-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-3.0.1.tgz#cb8db9d324a76c09f9b76b31a12a48638b0b9708" + integrity sha512-r+JdDipt93ttDjsOVPU5zaq5bAyY+3H19bDrThkvuVxC0xMQzU1PJcS6D+KrP3u96gH9XLomcHPb+2skoDjulQ== + dependencies: + archiver-utils "^2.1.0" + compress-commons "^3.0.0" + readable-stream "^3.6.0" + zwitch@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.3.tgz#159fae4b3f737db1e42bf321d3423e4c96688a18"