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.cjs.js

1349 lines
49 KiB
JavaScript
Raw Normal View History

2021-03-09 18:44:13 +00:00
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
const urlLoader = require('@graphql-tools/url-loader');
const lodash = require('lodash');
const url = require('url');
require('isomorphic-fetch');
const jwt = require('jsonwebtoken');
const graphqlRequest = require('graphql-request');
const chalk = _interopDefault(require('chalk'));
const HttpsProxyAgent = _interopDefault(require('https-proxy-agent'));
const HttpProxyAgent = _interopDefault(require('http-proxy-agent'));
const Ajv = _interopDefault(require('ajv'));
const yaml = require('js-yaml');
const fs = require('fs');
const replaceall = _interopDefault(require('replaceall'));
const BbPromise = require('bluebird');
const stringify = _interopDefault(require('json-stable-stringify'));
const dotenv = require('dotenv');
const path = require('path');
const yamlParser = require('yaml-ast-parser');
const os = require('os');
const process$1 = require('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 = lodash.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$1 = new url.URL(endpoint);
const splittedPath = url$1.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$1.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$1.hostname);
const local = !shared && !isPrivate && !workspaceSlug;
if (isPrivate && !workspaceSlug) {
workspaceSlug = getWorkspaceFromPrivateOrigin(url$1.origin);
}
return {
clusterBaseUrl: url$1.origin,
service,
stage,
local,
isPrivate,
shared,
workspaceSlug,
clusterName: getClusterName(url$1.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 = jwt.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 graphqlRequest.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 BbPromise.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 (lodash.isArray(object)) {
return lodash.map(object, deepMapValuesIteratee);
}
else if (lodash.isObject(object) && !lodash.isDate(object) && !lodash.isFunction(object)) {
return lodash.extend({}, object, lodash.mapValues(object, deepMapValuesIteratee));
}
return callback(object, propertyPath);
};
deepMapValues(objectToPopulate, (property, propertyPath) => {
if (typeof property === 'string') {
const populateSingleProperty = this.populateProperty(property, true)
.then((newProperty) => lodash.set(objectToPopulate, propertyPath, newProperty))
.return();
populateAll.push(populateSingleProperty);
}
});
return BbPromise.all(populateAll).then(() => objectToPopulate);
}
populateProperty(propertyParam, populateInPlace) {
let property = populateInPlace ? propertyParam : lodash.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 BbPromise.resolve(property);
});
});
allValuesToPopulate.push(singleValueToPopulate);
});
return BbPromise.all(allValuesToPopulate).then(() => {
if (property !== this.json && !warned) {
return this.populateProperty(property);
}
return BbPromise.resolve(property);
});
}
return BbPromise.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 BbPromise.resolve(property);
}
property = valueToPopulate;
}
return BbPromise.resolve(property);
}
overwrite(variableStringsString) {
let finalValue;
const variableStringsArray = variableStringsString.split(',');
const allValuesFromSource = variableStringsArray.map((variableString) => this.getValueFromSource(variableString));
return BbPromise.all(allValuesFromSource).then((valuesFromSources) => {
valuesFromSources.find((valueFromSource) => {
finalValue = valueFromSource;
return (finalValue !== null &&
typeof finalValue !== 'undefined' &&
!(typeof finalValue === 'object' && lodash.isEmpty(finalValue)));
});
return BbPromise.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 BbPromise.resolve(valueToPopulate);
}
getValueFromString(variableString) {
const valueToPopulate = variableString.replace(/^['"]|['"]$/g, '');
return BbPromise.resolve(valueToPopulate);
}
getValueFromOptions(variableString) {
const requestedOption = variableString.split(':')[1];
const valueToPopulate = requestedOption !== '' || '' in this.options ? this.options[requestedOption] : this.options;
return BbPromise.resolve(valueToPopulate);
}
getValueFromSelf(variableString) {
const valueToPopulate = this.json;
const deepProperties = variableString.split(':')[1].split('.');
return this.getDeepValue(deepProperties, valueToPopulate);
}
getDeepValue(deepProperties, valueToPopulate) {
return BbPromise.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 BbPromise.resolve(computedValueToPopulate);
}, valueToPopulate);
}
warnIfNotFound(variableString, valueToPopulate) {
if (valueToPopulate === null ||
typeof valueToPopulate === 'undefined' ||
(typeof valueToPopulate === 'object' && lodash.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 {
fs.accessSync(filePath);
}
catch (_a) {
throw new Error(`${filePath} could not be found.`);
}
const file = fs.readFileSync(filePath, 'utf-8');
const json = yaml.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 = yamlParser.safeLoad(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 = path.dirname(definitionPath);
}
this.env = env;
this.out = out;
this.envVars = envVars;
}
async load(args, envPath, graceful) {
if (args.project) {
const flagPath = path.resolve(args.project);
try {
fs.accessSync(flagPath);
}
catch (_a) {
throw new Error(`Prisma definition path specified by --project '${flagPath}' does not exist`);
}
this.definitionPath = flagPath;
this.definitionDir = path.dirname(flagPath);
await this.loadDefinition(args, graceful);
this.validate();
return;
}
if (envPath) {
try {
fs.accessSync(envPath);
}
catch (_b) {
envPath = path.join(process.cwd(), envPath);
}
try {
fs.accessSync(envPath);
}
catch (_c) {
throw new Error(`--env-file path '${envPath}' does not exist`);
}
}
dotenv.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 = fs.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 jwt.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 = path.join(this.definitionDir, unresolvedTypesPath);
try {
fs.accessSync(typesPath);
const types = fs.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 = path.join(this.definitionDir, subscription.query);
try {
fs.accessSync(queryPath);
}
catch (_a) {
throw new Error(`Subscription query ${queryPath} provided in subscription "${name}" in prisma.yml does not exist.`);
}
query = fs.readFileSync(queryPath, 'utf-8');
}
return {
name,
query,
headers,
url,
};
});
}
return [];
}
replaceEndpoint(newEndpoint) {
this.definitionString = replaceYamlValue(this.definitionString, 'endpoint', newEndpoint);
fs.writeFileSync(this.definitionPath, this.definitionString);
}
addDatamodel(datamodel) {
this.definitionString += `\ndatamodel: ${datamodel}`;
fs.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 = path.join(this.home, '.prisma/config.yml');
fs.mkdirSync(path.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 = jwt.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 = yaml.safeDump(JSON.parse(JSON.stringify(rc)));
fs.writeFileSync(this.rcPath, rcString);
}
setActiveCluster(cluster) {
this.activeCluster = cluster;
}
async loadGlobalRC() {
if (this.rcPath) {
try {
fs.accessSync(this.rcPath);
const globalFile = fs.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 = yaml.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 } = fs.promises;
/**
* This loader loads a schema from a `prisma.yml` file
*/
class PrismaLoader extends urlLoader.UrlLoader {
loaderId() {
return 'prisma';
}
async canLoad(prismaConfigFilePath, options) {
if (typeof prismaConfigFilePath === 'string' && prismaConfigFilePath.endsWith('prisma.yml')) {
const joinedYmlPath = path.join(options.cwd || process$1.cwd(), prismaConfigFilePath);
try {
await access(joinedYmlPath);
return true;
}
catch (_a) {
return false;
}
}
return false;
}
async load(prismaConfigFilePath, options) {
const { graceful, envVars } = options;
const home = os.homedir();
const env = new Environment(home);
await env.load();
const joinedYmlPath = path.join(options.cwd || process$1.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 });
}
}
exports.PrismaLoader = PrismaLoader;
//# sourceMappingURL=index.cjs.js.map