in packages/amplify-provider-awscloudformation/src/graphql-transformer/transform-graphql-schema.ts [180:407]
export async function transformGraphQLSchema(context, options) {
let resourceName: string;
const backEndDir = context.amplify.pathManager.getBackendDirPath();
const flags = context.parameters.options;
if (flags['no-gql-override']) {
return;
}
let { resourceDir, parameters } = options;
const { forceCompile } = options;
// Compilation during the push step
const { resourcesToBeCreated, resourcesToBeUpdated, allResources } = await context.amplify.getResourceStatus(API_CATEGORY);
let resources = resourcesToBeCreated.concat(resourcesToBeUpdated);
// When build folder is missing include the API
// to be compiled without the backend/api/<api-name>/build
// cloud formation push will fail even if there is no changes in the GraphQL API
// https://github.com/aws-amplify/amplify-console/issues/10
const resourceNeedCompile = allResources
.filter(r => !resources.includes(r))
.filter(r => {
const buildDir = path.normalize(path.join(backEndDir, API_CATEGORY, r.resourceName, 'build'));
return !fs.existsSync(buildDir);
});
resources = resources.concat(resourceNeedCompile);
if (forceCompile) {
resources = resources.concat(allResources);
}
resources = resources.filter(resource => resource.service === 'AppSync');
if (!resourceDir) {
// There can only be one appsync resource
if (resources.length > 0) {
const resource = resources[0];
if (resource.providerPlugin !== providerName) {
return;
}
const { category, resourceName } = resource;
resourceDir = path.normalize(path.join(backEndDir, category, resourceName));
} else {
// No appsync resource to update/add
return;
}
}
let previouslyDeployedBackendDir = options.cloudBackendDirectory;
if (!previouslyDeployedBackendDir) {
if (resources.length > 0) {
const resource = resources[0];
if (resource.providerPlugin !== providerName) {
return;
}
const { category } = resource;
resourceName = resource.resourceName;
const cloudBackendRootDir = context.amplify.pathManager.getCurrentCloudBackendDirPath();
/* eslint-disable */
previouslyDeployedBackendDir = path.normalize(path.join(cloudBackendRootDir, category, resourceName));
/* eslint-enable */
}
}
const parametersFilePath = path.join(resourceDir, PARAMETERS_FILENAME);
if (!parameters && fs.existsSync(parametersFilePath)) {
try {
parameters = context.amplify.readJsonFile(parametersFilePath);
} catch (e) {
parameters = {};
}
}
let { authConfig }: { authConfig: AppSyncAuthConfiguration } = options;
if (_.isEmpty(authConfig) && !_.isEmpty(resources)) {
authConfig = await context.amplify.invokePluginMethod(
context,
AmplifyCategories.API,
AmplifySupportedService.APPSYNC,
'getAuthConfig',
[resources[0].resourceName],
);
// handle case where auth project is not migrated , if Auth not migrated above function will return empty Object
if (_.isEmpty(authConfig)) {
//
// If we don't have an authConfig from the caller, use it from the
// already read resources[0], which is an AppSync API.
//
if (resources[0].output.securityType) {
// Convert to multi-auth format if needed.
authConfig = {
defaultAuthentication: {
authenticationType: resources[0].output.securityType,
},
additionalAuthenticationProviders: [],
};
} else {
({ authConfig } = resources[0].output);
}
}
}
// for auth transformer we get any admin roles and a cognito identity pool to check for potential authenticated roles outside of the provided authRole
const adminRoles = await getAdminRoles(context, resourceName);
const identityPoolId = await getIdentityPoolId(context);
// for the predictions directive get storage config
const s3Resource = s3ResourceAlreadyExists(context);
const storageConfig = s3Resource ? getBucketName(context, s3Resource, backEndDir) : undefined;
const buildDir = path.normalize(path.join(resourceDir, 'build'));
const schemaFilePath = path.normalize(path.join(resourceDir, SCHEMA_FILENAME));
const schemaDirPath = path.normalize(path.join(resourceDir, SCHEMA_DIR_NAME));
let deploymentRootKey = await getPreviousDeploymentRootKey(previouslyDeployedBackendDir);
if (!deploymentRootKey) {
const deploymentSubKey = await hashDirectory(resourceDir);
deploymentRootKey = `${ROOT_APPSYNC_S3_KEY}/${deploymentSubKey}`;
}
const projectBucket = options.dryRun ? 'fake-bucket' : getProjectBucket(context);
const buildParameters = {
...parameters,
S3DeploymentBucket: projectBucket,
S3DeploymentRootKey: deploymentRootKey,
};
// If it is a dry run, don't create the build folder as it could make a follow-up command
// to not to trigger a build, hence a corrupt deployment.
if (!options.dryRun) {
fs.ensureDirSync(buildDir);
}
const project = await loadProject(resourceDir);
const lastDeployedProjectConfig = fs.existsSync(previouslyDeployedBackendDir)
? await loadProject(previouslyDeployedBackendDir)
: undefined;
const transformerVersion = await getTransformerVersion(context);
const docLink = getGraphQLTransformerAuthDocLink(transformerVersion);
const sandboxModeEnabled = schemaHasSandboxModeEnabled(project.schema, docLink);
const directiveMap = collectDirectivesByTypeNames(project.schema);
const hasApiKey =
authConfig.defaultAuthentication.authenticationType === 'API_KEY' ||
authConfig.additionalAuthenticationProviders.some(a => a.authenticationType === 'API_KEY');
const showSandboxModeMessage = sandboxModeEnabled && hasApiKey;
if (showSandboxModeMessage) {
const transformerVersion = await getTransformerVersion(context);
const docLink = getGraphQLTransformerAuthDocLink(transformerVersion);
showGlobalSandboxModeWarning(docLink);
} else warnOnAuth(directiveMap.types, docLink);
searchablePushChecks(context, directiveMap.types, parameters[ResourceConstants.PARAMETERS.AppSyncApiName]);
const transformerListFactory = getTransformerFactory(context, resourceDir);
if (sandboxModeEnabled && options.promptApiKeyCreation) {
const apiKeyConfig = await showSandboxModePrompts(context);
if (apiKeyConfig) authConfig.additionalAuthenticationProviders.push(apiKeyConfig);
}
let searchableTransformerFlag = false;
if (directiveMap.directives.includes('searchable')) {
searchableTransformerFlag = true;
}
// construct sanityCheckRules
const ff = new AmplifyCLIFeatureFlagAdapter();
const isNewAppSyncAPI: boolean = resourcesToBeCreated.some(resource => resource.service === 'AppSync');
const allowDestructiveUpdates = context?.input?.options?.[destructiveUpdatesFlag] || context?.input?.options?.force;
const sanityCheckRules = getSanityCheckRules(isNewAppSyncAPI, ff, allowDestructiveUpdates);
let resolverConfig = {};
if (!_.isEmpty(resources)) {
resolverConfig = await context.amplify.invokePluginMethod(
context,
AmplifyCategories.API,
AmplifySupportedService.APPSYNC,
'getResolverConfig',
[resources[0].resourceName],
);
}
/**
* if Auth is not migrated , we need to fetch resolver Config from transformer.conf.json
* since above function will return empt object
*/
if (_.isEmpty(resolverConfig)) {
resolverConfig = project.config.ResolverConfig;
}
const buildConfig: ProjectOptions<TransformerFactoryArgs> = {
...options,
buildParameters,
projectDirectory: resourceDir,
transformersFactory: transformerListFactory,
transformersFactoryArgs: {
addSearchableTransformer: searchableTransformerFlag,
storageConfig,
authConfig,
adminRoles,
identityPoolId,
},
rootStackFileName: 'cloudformation-template.json',
currentCloudBackendDirectory: previouslyDeployedBackendDir,
minify: options.minify,
projectConfig: project,
lastDeployedProjectConfig,
authConfig,
sandboxModeEnabled,
sanityCheckRules,
resolverConfig: resolverConfig,
};
const transformerOutput = await buildAPIProject(buildConfig);
context.print.success(`GraphQL schema compiled successfully.\n\nEdit your schema at ${schemaFilePath} or \
place .graphql files in a directory at ${schemaDirPath}`);
if (isAuthModeUpdated(options)) {
parameters.AuthModeLastUpdated = new Date();
}
if (!options.dryRun) {
JSONUtilities.writeJson(parametersFilePath, parameters);
}
return transformerOutput;
}