UNPKG

serverless

Version:

Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more

180 lines (160 loc) • 6.13 kB
'use strict'; const yaml = require('yaml-ast-parser'); const BbPromise = require('bluebird'); const fs = BbPromise.promisifyAll(require('fs')); const _ = require('lodash'); const os = require('os'); const chalk = require('chalk'); const log = require('./log/serverlessLog'); const findKeyChain = astContent => { let content = astContent; const chain = [content.key.value]; while (content.parent) { content = content.parent; if (content.key) { chain.push(content.key.value); } } return chain.reverse().join('.'); }; const parseAST = (ymlAstContent, astObject) => { let newAstObject = astObject || {}; if (ymlAstContent.mappings && _.isArray(ymlAstContent.mappings)) { _.forEach(ymlAstContent.mappings, v => { if (!v.value) { const errorMessage = [ 'Serverless: ', `${chalk.red('Your serverless.yml has an invalid value with key:')} `, `${chalk.red(`"${v.key.value}"`)}`, ].join(''); log(errorMessage); return; } if (v.key.kind === 0 && v.value.kind === 0) { newAstObject[findKeyChain(v)] = v.value; } else if (v.key.kind === 0 && (v.value.kind === 2 || v.value.kind === 3)) { newAstObject[findKeyChain(v)] = v.value; newAstObject = parseAST(v.value, newAstObject); } }); } else if (ymlAstContent.items && _.isArray(ymlAstContent.items)) { _.forEach(ymlAstContent.items, (v, i) => { if (v.kind === 0) { const key = `${findKeyChain(ymlAstContent.parent)}[${i}]`; newAstObject[key] = v; } }); } return newAstObject; }; const constructPlainObject = (ymlAstContent, branchObject) => { const newbranchObject = branchObject || {}; if (ymlAstContent.mappings && _.isArray(ymlAstContent.mappings)) { _.forEach(ymlAstContent.mappings, v => { if (!v.value) { // no need to log twice, parseAST will log errors return; } if (v.key.kind === 0 && v.value.kind === 0) { newbranchObject[v.key.value] = v.value.value; } else if (v.key.kind === 0 && v.value.kind === 2) { newbranchObject[v.key.value] = constructPlainObject(v.value, {}); } else if (v.key.kind === 0 && v.value.kind === 3) { const plainArray = []; _.forEach(v.value.items, c => { plainArray.push(c.value); }); newbranchObject[v.key.value] = plainArray; } }); } return newbranchObject; }; const addNewArrayItem = (ymlFile, pathInYml, newValue) => fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { const rawAstObject = yaml.load(yamlContent); const astObject = parseAST(rawAstObject); const plainObject = constructPlainObject(rawAstObject); const pathInYmlArray = pathInYml.split('.'); let currentNode = plainObject; for (let i = 0; i < pathInYmlArray.length - 1; i++) { const propertyName = pathInYmlArray[i]; const property = currentNode[propertyName]; if (_.isUndefined(property) || _.isObject(property)) { currentNode[propertyName] = property || {}; currentNode = currentNode[propertyName]; } else { throw new Error(`${property} can only be undefined or an object!`); } } const arrayPropertyName = _.last(pathInYmlArray); let arrayProperty = currentNode[arrayPropertyName]; if (_.isUndefined(arrayProperty) || _.isArray(arrayProperty)) { arrayProperty = arrayProperty || []; } else { throw new Error(`${arrayProperty} can only be undefined or an array!`); } currentNode[arrayPropertyName] = _.union(arrayProperty, [newValue]); const branchToReplaceName = _.head(pathInYmlArray); const newObject = {}; newObject[branchToReplaceName] = plainObject[branchToReplaceName]; const newText = yaml.dump(newObject); if (astObject[branchToReplaceName]) { const beginning = yamlContent.substring( 0, astObject[branchToReplaceName].parent.key.startPosition ); const end = yamlContent.substring( astObject[branchToReplaceName].endPosition, yamlContent.length ); return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); } return fs.writeFileAsync(ymlFile, `${yamlContent}${os.EOL}${newText}`); }); const removeExistingArrayItem = (ymlFile, pathInYml, removeValue) => fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { const rawAstObject = yaml.load(yamlContent); const astObject = parseAST(rawAstObject); if (astObject[pathInYml] && astObject[pathInYml].items) { const plainObject = constructPlainObject(rawAstObject); const pathInYmlArray = pathInYml.split('.'); let currentNode = plainObject; const pathInObjectTree = []; for (let i = 0; i < pathInYmlArray.length - 1; i++) { pathInObjectTree.push(currentNode); currentNode = currentNode[pathInYmlArray[i]]; } const arrayPropertyName = _.last(pathInYmlArray); const arrayProperty = currentNode[arrayPropertyName]; _.pull(arrayProperty, removeValue); if (_.isEmpty(arrayProperty)) { _.unset(currentNode, arrayPropertyName); pathInObjectTree.push(currentNode); for (let i = pathInObjectTree.length - 1; i > 0; i--) { if (_.keys(pathInObjectTree[i]).length > 0) { break; } _.unset(pathInObjectTree[i - 1], pathInYmlArray[i - 1]); } } const headObjectPath = _.head(pathInYmlArray); let newText = ''; if (plainObject[headObjectPath]) { const newObject = {}; newObject[headObjectPath] = plainObject[headObjectPath]; newText = yaml.dump(newObject); } const beginning = yamlContent.substring( 0, astObject[headObjectPath].parent.key.startPosition ); const end = yamlContent.substring(astObject[pathInYml].endPosition, yamlContent.length); return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); } return BbPromise.resolve(); }); module.exports = { addNewArrayItem, removeExistingArrayItem, };