const fs = require('fs'); const { yamlParse } = require('yaml-cfn'); const merge = require('lodash/merge'); const resourceReducer = (acc, [key, value]) => { switch (value.Type) { case 'AWS::Serverless::Application': return merge(acc, { applications: { [key]: value } }); case 'AWS::Serverless::Function': return merge(acc, { functions: { [key]: value } }); default: return acc; } }; /** * Heavily inspired by https://github.com/SnappyTutorials/aws-sam-webpack-plugin * Parses SAM templates, and spits out entries for each AWS::Serverless::Function * Will recursively traverse nested stacks as well, for each AWS::Serverless::Application. * * Also copies yaml templates into .aws-sam/build folders, unmodified. * * Note, that nested stacks within nested stacks don't work; * shit breaks after first recursion because of file paths. * But we don't need that yet, so it will have to wait. * It is doable though! *just* have to continually prepend a parent's stack file path to a child. * * TODO: Handle nested stacks within nested stacks (i.e. second level of recursion) */ class SamPlugin { constructor() { this.entryPoints = {}; this.stacks = []; } /** * Get template at path * @param {string} [path]="." * @return {{}} Entry points */ entries(path = '.') { const templatePath = `${path}/template.yaml`; const yaml = fs.readFileSync(templatePath); if (!yaml) { throw new Error(`No template found at ${templatePath}`); } this.stacks.push(templatePath); const template = yamlParse(yaml.toString()); this.entryForTemplate(template, path); return this.entryPoints; } /** * Finds applications and functions within a template's Resources. * Adds functions to this.entries. * Recursively traverses nested stacks (applications), via this.entry(template path) * * Path prefix is used to ensure that input and output file structures are the same. * Nested stacks point to functions by path relative to their root. * * This lets us use an unmodified yaml file afterwards; the relative file paths are the same. * @param template - Parsed YAML template. * @param pathPrefix - Prefix to be added to entry point. */ entryForTemplate(template, pathPrefix) { const { Resources } = template; const initialValue = { applications: {}, functions: {} }; const { applications, functions, } = Object.entries(Resources).reduce(resourceReducer, initialValue); // Parse functions into entries Object .values(functions) .forEach((f) => { const path = [pathPrefix, f.Properties.CodeUri].join('/'); this.entryPoints[path] = path; }); // Recursively traverse nested stacks Object .values(applications) .forEach((value) => this.entries(`./${value.Properties.Location.replace('/template.yaml', '')}`)); } // Add outputTemplates as afterEmit hook. apply = (compiler) => compiler.hooks.afterEmit.tap('SamPlugin', this.outputTemplates); // Loop through stacks, and copy them from source to .aws-sam/build outputTemplates = () => this.stacks.forEach((stack) => { fs.copyFileSync(stack, `./.aws-sam/build/${stack.replace('./', '')}`); }); } module.exports = SamPlugin;