This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
admin-panel/graphql-types/node_modules/@graphql-tools/prisma-loader/index.esm.js

1343 lines
49 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { UrlLoader } from '@graphql-tools/url-loader';
import { invert, cloneDeep, isEmpty, isArray, map, isObject, isDate, isFunction, extend, mapValues, set } from 'lodash';
import { URL as URL$1 } from 'url';
import 'isomorphic-fetch';
import { sign, decode } from 'jsonwebtoken';
import { GraphQLClient } from 'graphql-request';
import chalk from 'chalk';
import HttpsProxyAgent from 'https-proxy-agent';
import HttpProxyAgent from 'http-proxy-agent';
import Ajv from 'ajv';
import { safeLoad, safeDump } from 'js-yaml';
import { accessSync, readFileSync, writeFileSync, mkdirSync, promises } from 'fs';
import replaceall from 'replaceall';
import { resolve, all, reduce } from 'bluebird';
import stringify from 'json-stable-stringify';
import { config } from 'dotenv';
import { dirname, resolve as resolve$1, join } from 'path';
import { safeLoad as safeLoad$1 } from 'yaml-ast-parser';
import { homedir } from 'os';
import { cwd } from 'process';
const cloudApiEndpoint = process.env.CLOUD_API_ENDPOINT || 'https://api.cloud.prisma.sh';
const clusterEndpointMap = {
'prisma-eu1': 'https://eu1.prisma.sh',
'prisma-us1': 'https://us1.prisma.sh',
};
const clusterEndpointMapReverse = invert(clusterEndpointMap);
function getClusterName(origin) {
if (clusterEndpointMapReverse[origin]) {
return clusterEndpointMapReverse[origin];
}
if (origin.endsWith('prisma.sh')) {
return origin.split('_')[0].replace(/https?:\/\//, '');
}
if (isLocal(origin)) {
return 'local';
}
return 'default';
}
const getWorkspaceFromPrivateOrigin = (origin) => {
const split = origin.split('_');
if (split.length > 1) {
return split[1].split('.')[0];
}
return null;
};
const isLocal = (origin) => origin.includes('localhost') || origin.includes('127.0.0.1');
function parseEndpoint(endpoint) {
/*
Terminology:
local - hosted locally using docker and accessed using localhost or prisma or local web proxy like domain.dev
shared - demo server
isPrivate - private hosted by Prisma or private and self-hosted, important that in our terminology a local server is not private
*/
const url = new URL$1(endpoint);
const splittedPath = url.pathname.split('/');
// assuming, that the pathname always starts with a leading /, we always can ignore the first element of the split array
const service = splittedPath.length > 3 ? splittedPath[2] : splittedPath[1] || 'default';
const stage = splittedPath.length > 3 ? splittedPath[3] : splittedPath[2] || 'default';
// This logic might break for self-hosted servers incorrectly yielding a "workspace" simply if the UX has
// enough "/"es like if https://custom.dev/not-a-workspace/ is the base Prisma URL then for default/default service/stage
// pair. This function would incorrectly return not-a-workspace as a workspace.
let workspaceSlug = splittedPath.length > 3 ? splittedPath[1] : null;
const shared = ['eu1.prisma.sh', 'us1.prisma.sh'].includes(url.host);
// When using localAliases, do an exact match because of 'prisma' option which is added for local docker networking access
const localAliases = ['localhost', '127.0.0.1', 'prisma'];
const isPrivate = !shared && !localAliases.includes(url.hostname);
const local = !shared && !isPrivate && !workspaceSlug;
if (isPrivate && !workspaceSlug) {
workspaceSlug = getWorkspaceFromPrivateOrigin(url.origin);
}
return {
clusterBaseUrl: url.origin,
service,
stage,
local,
isPrivate,
shared,
workspaceSlug,
clusterName: getClusterName(url.origin),
};
}
// code from https://raw.githubusercontent.com/request/request/5ba8eb44da7cd639ca21070ea9be20d611b85f66/lib/getProxyFromURI.js
function formatHostname(hostname) {
// canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
return hostname.replace(/^\.*/, '.').toLowerCase();
}
function parseNoProxyZone(zone) {
zone = zone.trim().toLowerCase();
const zoneParts = zone.split(':', 2);
const zoneHost = formatHostname(zoneParts[0]);
const zonePort = zoneParts[1];
const hasPort = zone.indexOf(':') > -1;
return { hostname: zoneHost, port: zonePort, hasPort: hasPort };
}
function uriInNoProxy(uri, noProxy) {
const port = uri.port || (uri.protocol === 'https:' ? '443' : '80');
const hostname = formatHostname(uri.hostname);
const noProxyList = noProxy.split(',');
// iterate through the noProxyList until it finds a match.
return noProxyList.map(parseNoProxyZone).some(function (noProxyZone) {
const isMatchedAt = hostname.indexOf(noProxyZone.hostname);
const hostnameMatched = isMatchedAt > -1 && isMatchedAt === hostname.length - noProxyZone.hostname.length;
if (noProxyZone.hasPort) {
return port === noProxyZone.port && hostnameMatched;
}
return hostnameMatched;
});
}
function getProxyFromURI(uri) {
// Decide the proper request proxy to use based on the request URI object and the
// environmental variables (NO_PROXY, HTTP_PROXY, etc.)
// respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html)
const noProxy = process.env.NO_PROXY || process.env.no_proxy || '';
// if the noProxy is a wildcard then return null
if (noProxy === '*') {
return null;
}
// if the noProxy is not empty and the uri is found return null
if (noProxy !== '' && uriInNoProxy(uri, noProxy)) {
return null;
}
// Check for HTTP or HTTPS Proxy in environment Else default to null
if (uri.protocol === 'http:') {
return process.env.HTTP_PROXY || process.env.http_proxy || null;
}
if (uri.protocol === 'https:') {
return (process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null);
}
// if none of that works, return null
// (What uri protocol are you using then?)
return null;
}
function getProxyAgent(url) {
const uri = new URL(url);
const proxy = getProxyFromURI(uri);
if (!proxy) {
return undefined;
}
const proxyUri = new URL(proxy);
if (proxyUri.protocol === 'http:') {
// eslint-disable-next-line
// @ts-ignore
return new HttpProxyAgent(proxy);
}
if (proxyUri.protocol === 'https:') {
// eslint-disable-next-line
// @ts-ignore
return new HttpsProxyAgent(proxy);
}
return undefined;
}
const debug = require('debug')('environment');
class Cluster {
constructor(out, name, baseUrl, clusterSecret, local = true, shared = false, isPrivate = false, workspaceSlug) {
this.out = out;
this.name = name;
// All `baseUrl` extension points in this class
// adds a trailing slash. Here we remove it from
// the passed `baseUrl` in order to avoid double
// slashes.
this.baseUrl = baseUrl.replace(/\/$/, '');
this.clusterSecret = clusterSecret;
this.local = local;
this.shared = shared;
this.isPrivate = isPrivate;
this.workspaceSlug = workspaceSlug;
this.hasOldDeployEndpoint = false;
}
async getToken(serviceName, workspaceSlug, stageName) {
// public clusters just take the token
const needsAuth = await this.needsAuth();
debug({ needsAuth });
if (!needsAuth) {
return null;
}
if (this.name === 'shared-public-demo') {
return '';
}
if (this.isPrivate && process.env.PRISMA_MANAGEMENT_API_SECRET) {
return this.getLocalToken();
}
if (this.shared || (this.isPrivate && !process.env.PRISMA_MANAGEMENT_API_SECRET)) {
return this.generateClusterToken(serviceName, workspaceSlug, stageName);
}
else {
return this.getLocalToken();
}
}
getLocalToken() {
if (!this.clusterSecret && !process.env.PRISMA_MANAGEMENT_API_SECRET) {
return null;
}
if (!this.cachedToken) {
const grants = [{ target: `*/*`, action: '*' }];
const secret = process.env.PRISMA_MANAGEMENT_API_SECRET || this.clusterSecret;
try {
const algorithm = process.env.PRISMA_MANAGEMENT_API_SECRET ? 'HS256' : 'RS256';
this.cachedToken = sign({ grants }, secret, {
expiresIn: '5y',
algorithm,
});
}
catch (e) {
throw new Error(`Could not generate token for cluster ${chalk.bold(this.getDeployEndpoint())}. Did you provide the env var PRISMA_MANAGEMENT_API_SECRET?
Original error: ${e.message}`);
}
}
return this.cachedToken;
}
get cloudClient() {
return new GraphQLClient(cloudApiEndpoint, {
headers: {
Authorization: `Bearer ${this.clusterSecret}`,
},
agent: getProxyAgent(cloudApiEndpoint),
});
}
async generateClusterToken(serviceName, workspaceSlug = this.workspaceSlug || '*', stageName) {
const query = `
mutation ($input: GenerateClusterTokenRequest!) {
generateClusterToken(input: $input) {
clusterToken
}
}
`;
const { generateClusterToken: { clusterToken }, } = await this.cloudClient.request(query, {
input: {
workspaceSlug,
clusterName: this.name,
serviceName,
stageName,
},
});
return clusterToken;
}
async addServiceToCloudDBIfMissing(serviceName, workspaceSlug = this.workspaceSlug, stageName) {
const query = `
mutation ($input: GenerateClusterTokenRequest!) {
addServiceToCloudDBIfMissing(input: $input)
}
`;
const serviceCreated = await this.cloudClient.request(query, {
input: {
workspaceSlug,
clusterName: this.name,
serviceName,
stageName,
},
});
return serviceCreated.addServiceToCloudDBIfMissing;
}
getApiEndpoint(service, stage, workspaceSlug) {
if (!this.shared && service === 'default' && stage === 'default') {
return this.baseUrl;
}
if (!this.shared && stage === 'default') {
return `${this.baseUrl}/${service}`;
}
if (this.isPrivate || this.local) {
return `${this.baseUrl}/${service}/${stage}`;
}
const workspaceString = workspaceSlug ? `${workspaceSlug}/` : '';
return `${this.baseUrl}/${workspaceString}${service}/${stage}`;
}
getWSEndpoint(service, stage, workspaceSlug) {
return this.getApiEndpoint(service, stage, workspaceSlug).replace(/^http/, 'ws');
}
getImportEndpoint(service, stage, workspaceSlug) {
return this.getApiEndpoint(service, stage, workspaceSlug) + `/import`;
}
getExportEndpoint(service, stage, workspaceSlug) {
return this.getApiEndpoint(service, stage, workspaceSlug) + `/export`;
}
getDeployEndpoint() {
return `${this.baseUrl}/${this.hasOldDeployEndpoint ? 'cluster' : 'management'}`;
}
async isOnline() {
const version = await this.getVersion();
return typeof version === 'string';
}
async getVersion() {
// first try new api
try {
const result = await this.request(`{
serverInfo {
version
}
}`);
const res = await result.json();
const { data, errors } = res;
if (errors && errors[0].code === 3016 && errors[0].message.includes('management@default')) {
this.hasOldDeployEndpoint = true;
return await this.getVersion();
}
if (data && data.serverInfo) {
return data.serverInfo.version;
}
}
catch (e) {
debug(e);
}
// if that doesn't work, try the old one
try {
const result = await this.request(`{
serverInfo {
version
}
}`);
const res = await result.json();
const { data } = res;
return data.serverInfo.version;
}
catch (e) {
debug(e);
}
return null;
}
request(query, variables) {
return fetch(this.getDeployEndpoint(), {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
agent: getProxyAgent(this.getDeployEndpoint()),
});
}
async needsAuth() {
try {
const result = await this.request(`{
listProjects {
name
}
}`);
const data = await result.json();
if (data.errors && data.errors.length > 0) {
return true;
}
return false;
}
catch (e) {
debug('Assuming that the server needs authentication');
debug(e.toString());
return true;
}
}
toJSON() {
return {
name: this.name,
baseUrl: this.baseUrl,
local: this.local,
clusterSecret: this.clusterSecret,
shared: this.shared,
isPrivate: this.isPrivate,
workspaceSlug: this.workspaceSlug,
};
}
}
const schema = JSON.parse(`{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "JSON schema for Prisma prisma.yml files",
"definitions": {
"subscription": {
"description": "A piece of code that you should run.",
"type": "object",
"properties": {
"query": {
"type": "string"
},
"webhook": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"url": {
"type": "string"
},
"headers": {
"type": "object"
}
},
"required": ["url"]
}
]
}
},
"required": ["query", "webhook"]
}
},
"properties": {
"datamodel": {
"description": "Type definitions for database models, relations, enums and other types",
"type": ["string", "array"],
"items": {
"type": ["string", "array"]
}
},
"secret": {
"description": "Secret for securing the API Endpoint",
"type": "string",
"items": {
"type": "string"
}
},
"disableAuth": {
"description": "Disable authentication for the endpoint",
"type": "boolean",
"items": {
"type": "boolean"
}
},
"generate": {
"type": "array",
"items": {
"type": "object",
"properties": {
"generator": {
"type": "string"
},
"output": {
"type": "string"
}
}
}
},
"seed": {
"description": "Database seed",
"type": "object",
"properties": {
"import": {
"type": "string"
},
"run": {
"type": "string"
}
}
},
"subscriptions": {
"description": "All server-side subscriptions",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/subscription"
}
},
"custom": {
"description": "Custom field to use in variable interpolations with \${self:custom.field}",
"type": "object"
},
"hooks": {
"description": "Command hooks. Current available hooks are: post-deploy.",
"type": "object"
},
"endpoint": {
"description": "Endpoint the service will be reachable at. This also determines the cluster the service will deployed to.",
"type": "string",
"items": {
"type": "string"
}
},
"databaseType": {
"type": "string",
"oneOf": [{ "enum": ["relational", "document"] }]
}
},
"additionalProperties": false
}`);
class Output {
log(...args) {
console.log(args);
}
warn(...args) {
console.warn(args);
}
getErrorPrefix(fileName, type = 'error') {
return `[${type.toUpperCase()}] in ${fileName}: `;
}
}
class Variables {
constructor(fileName, options = {}, out = new Output(), envVars) {
this.overwriteSyntax = /,/g;
this.envRefSyntax = /^env:/g;
this.selfRefSyntax = /^self:/g;
this.stringRefSyntax = /('.*')|(".*")/g;
this.optRefSyntax = /^opt:/g;
// eslint-disable-next-line
this.variableSyntax = new RegExp(
// eslint-disable-next-line
'\\${([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}', 'g');
this.out = out;
this.fileName = fileName;
this.options = options;
this.envVars = envVars || process.env;
}
populateJson(json) {
this.json = json;
return this.populateObject(this.json).then(() => {
return resolve(this.json);
});
}
populateObject(objectToPopulate) {
const populateAll = [];
const deepMapValues = (object, callback, propertyPath) => {
const deepMapValuesIteratee = (value, key) => deepMapValues(value, callback, propertyPath ? propertyPath.concat(key) : [key]);
if (isArray(object)) {
return map(object, deepMapValuesIteratee);
}
else if (isObject(object) && !isDate(object) && !isFunction(object)) {
return extend({}, object, mapValues(object, deepMapValuesIteratee));
}
return callback(object, propertyPath);
};
deepMapValues(objectToPopulate, (property, propertyPath) => {
if (typeof property === 'string') {
const populateSingleProperty = this.populateProperty(property, true)
.then((newProperty) => set(objectToPopulate, propertyPath, newProperty))
.return();
populateAll.push(populateSingleProperty);
}
});
return all(populateAll).then(() => objectToPopulate);
}
populateProperty(propertyParam, populateInPlace) {
let property = populateInPlace ? propertyParam : cloneDeep(propertyParam);
const allValuesToPopulate = [];
let warned = false;
if (typeof property === 'string' && property.match(this.variableSyntax)) {
property.match(this.variableSyntax).forEach(matchedString => {
const variableString = matchedString
.replace(this.variableSyntax, (_, varName) => varName.trim())
.replace(/\s/g, '');
let singleValueToPopulate = null;
if (variableString.match(this.overwriteSyntax)) {
singleValueToPopulate = this.overwrite(variableString);
}
else {
singleValueToPopulate = this.getValueFromSource(variableString).then((valueToPopulate) => {
if (typeof valueToPopulate === 'object') {
return this.populateObject(valueToPopulate);
}
return valueToPopulate;
});
}
singleValueToPopulate = singleValueToPopulate.then(valueToPopulate => {
if (this.warnIfNotFound(variableString, valueToPopulate)) {
warned = true;
}
return this.populateVariable(property, matchedString, valueToPopulate).then((newProperty) => {
property = newProperty;
return resolve(property);
});
});
allValuesToPopulate.push(singleValueToPopulate);
});
return all(allValuesToPopulate).then(() => {
if (property !== this.json && !warned) {
return this.populateProperty(property);
}
return resolve(property);
});
}
return resolve(property);
}
populateVariable(propertyParam, matchedString, valueToPopulate) {
let property = propertyParam;
if (typeof valueToPopulate === 'string') {
property = replaceall(matchedString, valueToPopulate, property);
}
else {
if (property !== matchedString) {
if (typeof valueToPopulate === 'number') {
property = replaceall(matchedString, String(valueToPopulate), property);
}
else {
const errorMessage = [
'Trying to populate non string value into',
` a string for variable ${matchedString}.`,
' Please make sure the value of the property is a string.',
].join('');
this.out.warn(this.out.getErrorPrefix(this.fileName, 'warning') + errorMessage);
}
return resolve(property);
}
property = valueToPopulate;
}
return resolve(property);
}
overwrite(variableStringsString) {
let finalValue;
const variableStringsArray = variableStringsString.split(',');
const allValuesFromSource = variableStringsArray.map((variableString) => this.getValueFromSource(variableString));
return all(allValuesFromSource).then((valuesFromSources) => {
valuesFromSources.find((valueFromSource) => {
finalValue = valueFromSource;
return (finalValue !== null &&
typeof finalValue !== 'undefined' &&
!(typeof finalValue === 'object' && isEmpty(finalValue)));
});
return resolve(finalValue);
});
}
getValueFromSource(variableString) {
if (variableString.match(this.envRefSyntax)) {
return this.getValueFromEnv(variableString);
}
else if (variableString.match(this.optRefSyntax)) {
return this.getValueFromOptions(variableString);
}
else if (variableString.match(this.selfRefSyntax)) {
return this.getValueFromSelf(variableString);
}
else if (variableString.match(this.stringRefSyntax)) {
return this.getValueFromString(variableString);
}
const errorMessage = [
`Invalid variable reference syntax for variable ${variableString}.`,
' You can only reference env vars, options, & files.',
' You can check our docs for more info.',
].join('');
this.out.warn(this.out.getErrorPrefix(this.fileName, 'warning') + errorMessage);
}
getValueFromEnv(variableString) {
const requestedEnvVar = variableString.split(':')[1];
const valueToPopulate = requestedEnvVar !== '' || '' in this.envVars ? this.envVars[requestedEnvVar] : this.envVars;
return resolve(valueToPopulate);
}
getValueFromString(variableString) {
const valueToPopulate = variableString.replace(/^['"]|['"]$/g, '');
return resolve(valueToPopulate);
}
getValueFromOptions(variableString) {
const requestedOption = variableString.split(':')[1];
const valueToPopulate = requestedOption !== '' || '' in this.options ? this.options[requestedOption] : this.options;
return resolve(valueToPopulate);
}
getValueFromSelf(variableString) {
const valueToPopulate = this.json;
const deepProperties = variableString.split(':')[1].split('.');
return this.getDeepValue(deepProperties, valueToPopulate);
}
getDeepValue(deepProperties, valueToPopulate) {
return reduce(deepProperties, (computedValueToPopulateParam, subProperty) => {
let computedValueToPopulate = computedValueToPopulateParam;
if (typeof computedValueToPopulate === 'undefined') {
computedValueToPopulate = {};
}
else if (subProperty !== '' || '' in computedValueToPopulate) {
computedValueToPopulate = computedValueToPopulate[subProperty];
}
if (typeof computedValueToPopulate === 'string' && computedValueToPopulate.match(this.variableSyntax)) {
return this.populateProperty(computedValueToPopulate);
}
return resolve(computedValueToPopulate);
}, valueToPopulate);
}
warnIfNotFound(variableString, valueToPopulate) {
if (valueToPopulate === null ||
typeof valueToPopulate === 'undefined' ||
(typeof valueToPopulate === 'object' && isEmpty(valueToPopulate))) {
let varType;
if (variableString.match(this.envRefSyntax)) {
varType = 'environment variable';
}
else if (variableString.match(this.optRefSyntax)) {
varType = 'option';
}
else if (variableString.match(this.selfRefSyntax)) {
varType = 'self reference';
}
this.out.warn(this.out.getErrorPrefix(this.fileName, 'warning') +
`A valid ${varType} to satisfy the declaration '${variableString}' could not be found.`);
return true;
}
return false;
}
}
const debug$1 = require('debug')('yaml');
const ajv = new Ajv();
const validate = ajv.compile(schema);
// this is used by the playground, which accepts additional properties
const validateGraceful = ajv.compile({ ...schema, additionalProperties: true });
async function readDefinition(filePath, args, out = new Output(), envVars, graceful) {
try {
accessSync(filePath);
}
catch (_a) {
throw new Error(`${filePath} could not be found.`);
}
const file = readFileSync(filePath, 'utf-8');
const json = safeLoad(file);
// we need this copy because populateJson runs inplace
const jsonCopy = { ...json };
const vars = new Variables(filePath, args, out, envVars);
const populatedJson = await vars.populateJson(json);
if (populatedJson.custom) {
delete populatedJson.custom;
}
const valid = graceful ? validateGraceful(populatedJson) : validate(populatedJson);
// TODO activate as soon as the backend sends valid yaml
if (!valid) {
const errorMessage = `Invalid prisma.yml file` + '\n' + printErrors(graceful ? validateGraceful.errors : validate.errors);
throw new Error(errorMessage);
}
return {
definition: populatedJson,
rawJson: jsonCopy,
};
}
function printErrors(errors, name = 'prisma.yml') {
return errors
.map((e) => {
const paramsKey = stringify(e.params);
if (betterMessagesByParams[paramsKey]) {
return betterMessagesByParams[paramsKey];
}
const params = Object.keys(e.params)
.map(key => `${key}: ${e.params[key]}`)
.join(', ');
debug$1(stringify(e.params));
return `${name}${e.dataPath} ${e.message}. ${params}`;
})
.join('\n');
}
const betterMessagesByParams = {
// this is not up-to-date, stages are in again!
// https://github.com/prisma/framework/issues/1461
'{"additionalProperty":"stages"}': 'prisma.yml should NOT have a "stages" property anymore. Stages are now just provided as CLI args.\nRead more here: https://goo.gl/SUD5i5',
'{"additionalProperty":"types"}': 'prisma.yml should NOT have a "types" property anymore. It has been renamed to "datamodel"',
};
/**
* Comments out the current entry of a specific key in a yaml document and creates a new value next to it
* @param key key in yaml document to comment out
* @param newValue new value to add in the document
*/
function replaceYamlValue(input, key, newValue) {
const ast = safeLoad$1(input);
const position = getPosition(ast, key);
const newEntry = `${key}: ${newValue}\n`;
if (!position) {
return input + '\n' + newEntry;
}
return (input.slice(0, position.start) +
'#' +
input.slice(position.start, position.end) +
newEntry +
input.slice(position.end));
}
function getPosition(ast, key) {
const mapping = ast.mappings.find((m) => m.key.value === key);
if (!mapping) {
return undefined;
}
return {
start: mapping.startPosition,
end: mapping.endPosition + 1,
};
}
class PrismaDefinitionClass {
constructor(env, definitionPath, envVars = process.env, out) {
this.secrets = null;
this.definitionPath = definitionPath;
if (definitionPath) {
this.definitionDir = dirname(definitionPath);
}
this.env = env;
this.out = out;
this.envVars = envVars;
}
async load(args, envPath, graceful) {
if (args.project) {
const flagPath = resolve$1(args.project);
try {
accessSync(flagPath);
}
catch (_a) {
throw new Error(`Prisma definition path specified by --project '${flagPath}' does not exist`);
}
this.definitionPath = flagPath;
this.definitionDir = dirname(flagPath);
await this.loadDefinition(args, graceful);
this.validate();
return;
}
if (envPath) {
try {
accessSync(envPath);
}
catch (_b) {
envPath = join(process.cwd(), envPath);
}
try {
accessSync(envPath);
}
catch (_c) {
throw new Error(`--env-file path '${envPath}' does not exist`);
}
}
config({ path: envPath });
if (this.definitionPath) {
await this.loadDefinition(args, graceful);
this.validate();
}
else {
throw new Error(`Couldnt find \`prisma.yml\` file. Are you in the right directory?`);
}
}
async loadDefinition(args, graceful) {
const { definition, rawJson } = await readDefinition(this.definitionPath, args, this.out, this.envVars, graceful);
this.rawEndpoint = rawJson.endpoint;
this.definition = definition;
this.rawJson = rawJson;
this.definitionString = readFileSync(this.definitionPath, 'utf-8');
this.typesString = this.getTypesString(this.definition);
const secrets = this.definition.secret;
this.secrets = secrets ? secrets.replace(/\s/g, '').split(',') : null;
}
get endpoint() {
return (this.definition && this.definition.endpoint) || process.env.PRISMA_MANAGEMENT_API_ENDPOINT;
}
get clusterBaseUrl() {
if (!this.definition || !this.endpoint) {
return undefined;
}
const { clusterBaseUrl } = parseEndpoint(this.endpoint);
return clusterBaseUrl;
}
get service() {
if (!this.definition) {
return undefined;
}
if (!this.endpoint) {
return undefined;
}
const { service } = parseEndpoint(this.endpoint);
return service;
}
get stage() {
if (!this.definition) {
return undefined;
}
if (!this.endpoint) {
return undefined;
}
const { stage } = parseEndpoint(this.endpoint);
return stage;
}
get cluster() {
if (!this.definition) {
return undefined;
}
if (!this.endpoint) {
return undefined;
}
const { clusterName } = parseEndpoint(this.endpoint);
return clusterName;
}
validate() {
// shared clusters need a workspace
const clusterName = this.getClusterName();
const cluster = this.env.clusterByName(clusterName);
if (this.definition &&
clusterName &&
cluster &&
cluster.shared &&
!cluster.isPrivate &&
!this.getWorkspace() &&
clusterName !== 'shared-public-demo') {
throw new Error(`Your \`cluster\` property in the prisma.yml is missing the workspace slug.
Make sure that your \`cluster\` property looks like this: ${chalk.bold('<workspace>/<cluster-name>')}. You can also remove the cluster property from the prisma.yml
and execute ${chalk.bold.green('prisma deploy')} again, to get that value auto-filled.`);
}
if (this.definition &&
this.definition.endpoint &&
clusterName &&
cluster &&
cluster.shared &&
!cluster.isPrivate &&
!this.getWorkspace() &&
clusterName !== 'shared-public-demo') {
throw new Error(`The provided endpoint ${this.definition.endpoint} points to a demo cluster, but is missing the workspace slug. A valid demo endpoint looks like this: https://eu1.prisma.sh/myworkspace/service-name/stage-name`);
}
if (this.definition && this.definition.endpoint && !this.definition.endpoint.startsWith('http')) {
throw new Error(`${chalk.bold(this.definition.endpoint)} is not a valid endpoint. It must start with http:// or https://`);
}
}
getToken(serviceName, stageName) {
if (this.secrets) {
const data = {
data: {
service: `${serviceName}@${stageName}`,
roles: ['admin'],
},
};
return sign(data, this.secrets[0], {
expiresIn: '7d',
});
}
return undefined;
}
async getCluster(_ = false) {
if (this.definition && this.endpoint) {
const clusterData = parseEndpoint(this.endpoint);
const cluster = await this.getClusterByEndpoint(clusterData);
this.env.removeCluster(clusterData.clusterName);
this.env.addCluster(cluster);
return cluster;
}
return undefined;
}
findClusterByBaseUrl(baseUrl) {
return this.env.clusters.find(c => c.baseUrl.toLowerCase() === baseUrl);
}
async getClusterByEndpoint(data) {
if (data.clusterBaseUrl && !process.env.PRISMA_MANAGEMENT_API_SECRET) {
const cluster = this.findClusterByBaseUrl(data.clusterBaseUrl);
if (cluster) {
return cluster;
}
}
const { clusterName, clusterBaseUrl, isPrivate, local, shared, workspaceSlug } = data;
// if the cluster could potentially be served by the cloud api, fetch the available
// clusters from the cloud api
if (!local) {
await this.env.fetchClusters();
const cluster = this.findClusterByBaseUrl(data.clusterBaseUrl);
if (cluster) {
return cluster;
}
}
return new Cluster(this.out, clusterName, clusterBaseUrl, shared || isPrivate ? this.env.cloudSessionKey : undefined, local, shared, isPrivate, workspaceSlug);
}
getTypesString(definition) {
const typesPaths = definition.datamodel
? Array.isArray(definition.datamodel)
? definition.datamodel
: [definition.datamodel]
: [];
let allTypes = '';
typesPaths.forEach(unresolvedTypesPath => {
const typesPath = join(this.definitionDir, unresolvedTypesPath);
try {
accessSync(typesPath);
const types = readFileSync(typesPath, 'utf-8');
allTypes += types + '\n';
}
catch (_a) {
throw new Error(`The types definition file "${typesPath}" could not be found.`);
}
});
return allTypes;
}
getClusterName() {
return this.cluster || null;
}
getWorkspace() {
if (this.definition && this.endpoint) {
const { workspaceSlug } = parseEndpoint(this.endpoint);
if (workspaceSlug) {
return workspaceSlug;
}
}
return null;
}
async getDeployName() {
const cluster = await this.getCluster();
return concatName(cluster, this.service, this.getWorkspace());
}
getSubscriptions() {
if (this.definition && this.definition.subscriptions) {
return Object.entries(this.definition.subscriptions).map(([name, subscription]) => {
const url = typeof subscription.webhook === 'string' ? subscription.webhook : subscription.webhook.url;
const headers = typeof subscription.webhook === 'string' ? [] : transformHeaders(subscription.webhook.headers);
let query = subscription.query;
if (subscription.query.endsWith('.graphql')) {
const queryPath = join(this.definitionDir, subscription.query);
try {
accessSync(queryPath);
}
catch (_a) {
throw new Error(`Subscription query ${queryPath} provided in subscription "${name}" in prisma.yml does not exist.`);
}
query = readFileSync(queryPath, 'utf-8');
}
return {
name,
query,
headers,
url,
};
});
}
return [];
}
replaceEndpoint(newEndpoint) {
this.definitionString = replaceYamlValue(this.definitionString, 'endpoint', newEndpoint);
writeFileSync(this.definitionPath, this.definitionString);
}
addDatamodel(datamodel) {
this.definitionString += `\ndatamodel: ${datamodel}`;
writeFileSync(this.definitionPath, this.definitionString);
this.definition.datamodel = datamodel;
}
async getEndpoint(serviceInput, stageInput) {
const cluster = await this.getCluster();
const service = serviceInput || this.service;
const stage = stageInput || this.stage;
const workspace = this.getWorkspace();
if (service && stage && cluster) {
return cluster.getApiEndpoint(service, stage, workspace);
}
return null;
}
getHooks(hookType) {
if (this.definition && this.definition.hooks && this.definition.hooks[hookType]) {
const hooks = this.definition.hooks[hookType];
if (typeof hooks !== 'string' && !Array.isArray(hooks)) {
throw new Error(`Hook ${hookType} provided in prisma.yml must be string or an array of strings.`);
}
return typeof hooks === 'string' ? [hooks] : hooks;
}
return [];
}
}
function concatName(cluster, name, workspace) {
if (cluster.shared) {
const workspaceString = workspace ? `${workspace}~` : '';
return `${workspaceString}${name}`;
}
return name;
}
function transformHeaders(headers) {
if (!headers) {
return [];
}
return Object.entries(headers).map(([name, value]) => ({ name, value }));
}
class ClusterNotFound extends Error {
constructor(name) {
super(`Cluster '${name}' is neither a known shared cluster nor defined in your global .prismarc.`);
}
}
class ClusterNotSet extends Error {
constructor() {
super(`No cluster set. In order to run this command, please set the "cluster" property in your prisma.yml`);
}
}
const debug$2 = require('debug')('Environment');
class Environment {
constructor(home, out = new Output(), version) {
this.sharedClusters = ['prisma-eu1', 'prisma-us1'];
this.clusterEndpointMap = clusterEndpointMap;
this.globalRC = {};
this.clustersFetched = false;
this.out = out;
this.home = home;
this.version = version;
this.rcPath = join(this.home, '.prisma/config.yml');
mkdirSync(dirname(this.rcPath), { recursive: true });
}
async load() {
await this.loadGlobalRC();
}
get cloudSessionKey() {
return process.env.PRISMA_CLOUD_SESSION_KEY || this.globalRC.cloudSessionKey;
}
async renewToken() {
if (this.cloudSessionKey) {
const data = decode(this.cloudSessionKey);
if (!data.exp) {
return;
}
const timeLeft = data.exp * 1000 - Date.now();
if (timeLeft < 1000 * 60 * 60 * 24 && timeLeft > 0) {
try {
const res = await this.requestCloudApi(`
mutation {
renewToken
}
`);
if (res.renewToken) {
this.globalRC.cloudSessionKey = res.renewToken;
this.saveGlobalRC();
}
}
catch (e) {
debug$2(e);
}
}
}
}
async fetchClusters() {
if (!this.clustersFetched && this.cloudSessionKey) {
const renewPromise = this.renewToken();
try {
const res = (await Promise.race([
this.requestCloudApi(`
query prismaCliGetClusters {
me {
memberships {
workspace {
id
slug
clusters {
id
name
connectInfo {
endpoint
}
customConnectionInfo {
endpoint
}
}
}
}
}
}
`),
// eslint-disable-next-line
new Promise((_, r) => setTimeout(() => r(), 6000)),
]));
if (!res) {
return;
}
if (res.me && res.me.memberships && Array.isArray(res.me.memberships)) {
// clean up all prisma-eu1 and prisma-us1 clusters if they already exist
this.clusters = this.clusters.filter(c => c.name !== 'prisma-eu1' && c.name !== 'prisma-us1');
res.me.memberships.forEach((m) => {
m.workspace.clusters.forEach((cluster) => {
const endpoint = cluster.connectInfo
? cluster.connectInfo.endpoint
: cluster.customConnectionInfo
? cluster.customConnectionInfo.endpoint
: this.clusterEndpointMap[cluster.name];
this.addCluster(new Cluster(this.out, cluster.name, endpoint, this.globalRC.cloudSessionKey, false, ['prisma-eu1', 'prisma-us1'].includes(cluster.name), !['prisma-eu1', 'prisma-us1'].includes(cluster.name), m.workspace.slug));
});
});
}
}
catch (e) {
debug$2(e);
}
await renewPromise;
}
}
clusterByName(name, throws = false) {
if (!this.clusters) {
return;
}
const cluster = this.clusters.find(c => c.name === name);
if (!throws) {
return cluster;
}
if (!cluster) {
if (!name) {
throw new ClusterNotSet();
}
throw new ClusterNotFound(name);
}
return cluster;
}
setToken(token) {
this.globalRC.cloudSessionKey = token;
}
addCluster(cluster) {
const existingClusterIndex = this.clusters.findIndex(c => {
if (cluster.workspaceSlug) {
return c.workspaceSlug === cluster.workspaceSlug && c.name === cluster.name;
}
else {
return c.name === cluster.name;
}
});
if (existingClusterIndex > -1) {
this.clusters.splice(existingClusterIndex, 1);
}
this.clusters.push(cluster);
}
removeCluster(name) {
this.clusters = this.clusters.filter(c => c.name !== name);
}
saveGlobalRC() {
const rc = {
cloudSessionKey: this.globalRC.cloudSessionKey ? this.globalRC.cloudSessionKey.trim() : undefined,
clusters: this.getLocalClusterConfig(),
};
// parse & stringify to rm undefined for yaml parser
const rcString = safeDump(JSON.parse(JSON.stringify(rc)));
writeFileSync(this.rcPath, rcString);
}
setActiveCluster(cluster) {
this.activeCluster = cluster;
}
async loadGlobalRC() {
if (this.rcPath) {
try {
accessSync(this.rcPath);
const globalFile = readFileSync(this.rcPath, 'utf-8');
await this.parseGlobalRC(globalFile);
}
catch (_a) {
await this.parseGlobalRC();
}
}
else {
await this.parseGlobalRC();
}
}
async parseGlobalRC(globalFile) {
if (globalFile) {
this.globalRC = await this.loadYaml(globalFile, this.rcPath);
}
this.clusters = this.initClusters(this.globalRC);
}
async loadYaml(file, filePath = null) {
if (file) {
let content;
try {
content = safeLoad(file);
}
catch (e) {
throw new Error(`Yaml parsing error in ${filePath}: ${e.message}`);
}
const variables = new Variables(filePath || 'no filepath provided', this.args, this.out);
content = await variables.populateJson(content);
return content;
}
else {
return {};
}
}
initClusters(rc) {
const sharedClusters = this.getSharedClusters(rc);
return [...sharedClusters];
}
getSharedClusters(rc) {
return this.sharedClusters.map(clusterName => {
return new Cluster(this.out, clusterName, this.clusterEndpointMap[clusterName], rc && rc.cloudSessionKey, false, true);
});
}
getLocalClusterConfig() {
return this.clusters
.filter(c => !c.shared && c.clusterSecret !== this.cloudSessionKey && !c.isPrivate)
.reduce((acc, cluster) => {
return {
...acc,
[cluster.name]: {
host: cluster.baseUrl,
clusterSecret: cluster.clusterSecret,
},
};
}, {});
}
async requestCloudApi(query) {
const res = await fetch('https://api.cloud.prisma.sh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cloudSessionKey}`,
'X-Cli-Version': this.version,
},
body: JSON.stringify({
query,
}),
proxy: getProxyAgent('https://api.cloud.prisma.sh'),
});
const json = await res.json();
return json.data;
}
}
const { access } = promises;
/**
* This loader loads a schema from a `prisma.yml` file
*/
class PrismaLoader extends UrlLoader {
loaderId() {
return 'prisma';
}
async canLoad(prismaConfigFilePath, options) {
if (typeof prismaConfigFilePath === 'string' && prismaConfigFilePath.endsWith('prisma.yml')) {
const joinedYmlPath = join(options.cwd || cwd(), prismaConfigFilePath);
try {
await access(joinedYmlPath);
return true;
}
catch (_a) {
return false;
}
}
return false;
}
async load(prismaConfigFilePath, options) {
const { graceful, envVars } = options;
const home = homedir();
const env = new Environment(home);
await env.load();
const joinedYmlPath = join(options.cwd || cwd(), prismaConfigFilePath);
const definition = new PrismaDefinitionClass(env, joinedYmlPath, envVars);
await definition.load({}, undefined, graceful);
const serviceName = definition.service;
const stage = definition.stage;
const clusterName = definition.cluster;
if (!clusterName) {
throw new Error(`No cluster set. Please set the "cluster" property in your prisma.yml`);
}
const cluster = await definition.getCluster();
if (!cluster) {
throw new Error(`Cluster ${clusterName} provided in prisma.yml could not be found in global ~/.prisma/config.yml.
Please check in ~/.prisma/config.yml, if the cluster exists.
You can use \`docker-compose up -d\` to start a new cluster.`);
}
const token = definition.getToken(serviceName, stage);
const url = cluster.getApiEndpoint(serviceName, stage, definition.getWorkspace() || undefined);
const headers = token
? {
Authorization: `Bearer ${token}`,
}
: undefined;
return super.load(url, { headers });
}
}
export { PrismaLoader };
//# sourceMappingURL=index.esm.js.map