aws-appsync-vtl
Version:
Push to and pull from AWS AppSync velocity templates of your resolvers
239 lines (238 loc) • 10.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Synchronizer = void 0;
const promises_1 = require("fs/promises");
const path_1 = __importDefault(require("path"));
const inquirer_1 = __importDefault(require("inquirer"));
const client_appsync_1 = require("@aws-sdk/client-appsync");
class Synchronizer {
constructor(config) {
this.pull = async (alwaysYes = false) => {
this.createIfMissing(this.resolversPath);
const { resolvers } = await this.appSync.send(new client_appsync_1.ListResolversCommand({
apiId: this.apiId,
typeName: 'Query',
}));
if (resolvers) {
for (const resolver of resolvers) {
this.pullResolver(resolver, alwaysYes);
}
}
};
this.deleteType = async () => {
const typeNames = await promises_1.readdir(this.resolversPath);
if (typeNames.length === 0) {
console.log('Nothing to do');
return;
}
const { selection } = await inquirer_1.default.prompt([
{
name: 'selection',
type: 'checkbox',
message: 'Choose type',
choices: typeNames,
},
]);
for (const type of selection) {
const typePath = path_1.default.join(this.resolversPath, type);
const resolvers = await promises_1.readdir(typePath);
for (const resolver of resolvers) {
await this.deleteResolverAt(type, resolver);
}
await promises_1.rmdir(typePath);
console.log(`Removed ${type}`);
}
};
this.deleteResolver = async () => {
const typeNames = await promises_1.readdir(this.resolversPath);
if (typeNames.length === 0) {
console.log('Nothing to do');
return;
}
const { type } = await inquirer_1.default.prompt([
{
name: 'type',
type: 'list',
message: 'Choose type',
choices: typeNames,
},
]);
const typePath = path_1.default.join(this.resolversPath, type);
const resolvers = await promises_1.readdir(typePath);
if (resolvers.length === 0) {
console.log(`No resolvers for the ${type} found`);
return;
}
const { resolverSelections } = await inquirer_1.default.prompt([
{
name: 'resolverSelections',
type: 'checkbox',
message: 'Choose type',
choices: resolvers,
},
]);
for (const resolver of resolverSelections) {
await this.deleteResolverAt(type, resolver);
}
};
this.deleteResolverAt = async (type, resolver) => {
const resolverPath = path_1.default.join(this.resolversPath, type, resolver);
const metaPath = path_1.default.join(resolverPath, 'meta.json');
const meta = await this.exists(metaPath)
? JSON.parse(await promises_1.readFile(metaPath, 'utf-8'))
: undefined;
if (meta) {
await this.appSync.send(new client_appsync_1.DeleteResolverCommand({
apiId: this.apiId,
typeName: type,
fieldName: resolver,
}));
}
await promises_1.rm(resolverPath, { recursive: true });
console.log(`Removed ${type}.${resolver}`);
};
this.push = async () => {
const typeNames = await promises_1.readdir(this.resolversPath);
const dataSources = (await this.getDataSources()).dataSources.map((ds) => ds.name);
for (const type of typeNames) {
const typePath = path_1.default.join(this.resolversPath, type);
const fieldNames = await promises_1.readdir(typePath);
for (const fieldName of fieldNames) {
const resolverPath = path_1.default.join(typePath, fieldName);
const metaPath = path_1.default.join(resolverPath, 'meta.json');
const requestPath = path_1.default.join(resolverPath, 'request.vm');
const responsePath = path_1.default.join(resolverPath, 'response.vm');
const responseTemplateExists = await this.exists(requestPath);
const requestTemplateExists = await this.exists(requestPath);
if (!requestTemplateExists && !responseTemplateExists) {
// eslint-disable-next-line no-continue
continue;
}
let ds = await this.exists(metaPath) ? JSON.parse(await promises_1.readFile(metaPath, 'utf-8')).dataSourceName : '';
const creating = !ds;
const info = creating ? 'Creating' : 'Updating';
if (creating) {
ds = (await inquirer_1.default.prompt([
{
name: 'ds',
type: 'list',
message: `Please set data source for ${type}.${fieldName}`,
choices: dataSources,
},
])).ds;
}
console.log(`${info} resolver: ${type}.${fieldName}`);
const requestMappingTemplate = await this.exists(requestPath) ? await promises_1.readFile(requestPath, 'utf-8') : undefined;
const responseMappingTemplate = await this.exists(responsePath) ? await promises_1.readFile(responsePath, 'utf-8') : undefined;
const input = {
apiId: this.apiId,
typeName: type,
fieldName,
dataSourceName: ds,
requestMappingTemplate,
responseMappingTemplate,
};
try {
const resp = await this.appSync.send(creating
? new client_appsync_1.CreateResolverCommand(input)
: new client_appsync_1.UpdateResolverCommand(input));
await promises_1.writeFile(metaPath, JSON.stringify(resp.resolver, null, 2));
}
catch (e) {
console.error(e);
}
}
}
};
this.pullResolver = async (resolver, alwaysYes) => {
if (!resolver.requestMappingTemplate && !resolver.responseMappingTemplate) {
// Nothing to do
return;
}
console.log(`Processing resolver: ${resolver.typeName}.${resolver.fieldName}`);
const resolverPath = path_1.default.join(this.resolversPath, resolver.typeName, resolver.fieldName);
await this.createIfMissing(resolverPath);
if (resolver.requestMappingTemplate) {
await this.processResolverTemplate(path_1.default.join(resolverPath, 'request.vm'), resolver.requestMappingTemplate, alwaysYes);
}
if (resolver.responseMappingTemplate) {
await this.processResolverTemplate(path_1.default.join(resolverPath, 'response.vm'), resolver.responseMappingTemplate, alwaysYes);
}
await promises_1.writeFile(path_1.default.join(resolverPath, 'meta.json'), JSON.stringify(resolver, null, 2));
};
this.processResolverTemplate = async (templatePath, template, alwaysYes) => {
const exists = await this.exists(templatePath);
if (exists && !alwaysYes) {
const choice = await inquirer_1.default.prompt([
{
name: 'override',
type: 'list',
message: `Template ${templatePath} already exists. Would you like to override it?`,
choices: [
'Yes', 'No',
],
},
]);
if (choice.override === 'No') {
return;
}
}
await promises_1.writeFile(templatePath, template);
};
this.getDataSources = async () => this.appSync.send(new client_appsync_1.ListDataSourcesCommand({
apiId: this.apiId,
}));
this.exists = async (path) => {
try {
await promises_1.access(path);
}
catch (e) {
if (e.code === 'ENOENT') {
return false;
}
throw e;
}
return true;
};
this.createIfMissing = async (path) => {
try {
await promises_1.access(path);
}
catch (e) {
if (e.code === 'ENOENT') {
await promises_1.mkdir(path, { recursive: true });
return true;
}
throw e;
}
return false;
};
this.apiId = config.apiId;
this.resolversPath = config.resolversPath;
this.appSync = new client_appsync_1.AppSyncClient({});
this.appSync.middlewareStack.add((next) => async (args) => {
// eslint-disable-next-line no-param-reassign
args.request.path = `/v1${args.request.path}`;
return next(args);
}, { name: 'path fixup', step: 'build', priority: 'high' });
}
}
exports.Synchronizer = Synchronizer;
Synchronizer.create = () => {
// eslint-disable-next-line global-require
const dotenv = require('dotenv');
dotenv.config();
if (!process.env.API_ID) {
throw new Error('process.env.API_ID must be set');
}
if (!process.env.RESOLVERS_PATH) {
throw new Error('process.env.RESOLVERS_PATH must be set');
}
return new Synchronizer({
apiId: process.env.API_ID,
resolversPath: process.env.RESOLVERS_PATH,
});
};