1349 lines
49 KiB
JavaScript
1349 lines
49 KiB
JavaScript
'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(`Couldn’t 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
|