@itentialopensource/adapter-salesforce_metadata
Version: 
This adapter integrates with system described as: Salesforce Metadata API.
274 lines (249 loc) • 9.46 kB
JavaScript
/* eslint global-require:warn */
/* eslint import/no-dynamic-require:warn */
/* eslint no-param-reassign:warn */
const fs = require('fs-extra');
const acorn = require('acorn');
// Getting the base directory:
let adaptdir = __dirname;
if (adaptdir.endsWith('/utils')) {
  adaptdir = adaptdir.substring(0, adaptdir.length - 6);
}
function createObjectForFunction(
  funcName,
  funcArgs,
  entityPath,
  description,
  workflow
) {
  const funcObject = {};
  // if the entity path is not set, then the object is not created.
  if (entityPath !== undefined) {
    funcObject.method_signature = `${funcName}(${funcArgs.join(', ')})`;
    funcObject.path = entityPath;
    if (description === undefined) {
      funcObject.description = '';
      funcObject.workflow = 'No';
    } else {
      funcObject.description = description;
      funcObject.workflow = workflow;
    }
  }
  return funcObject;
}
function getPathFromEntity(entity, funcName) {
  let epath;
  if (entity === undefined || entity === '.generic') {
    epath = undefined;
  } else {
    // Access the action.js file for the certain entity to get the path
    const entityPath = `${adaptdir}/entities/${entity}/action.json`;
    const actionJSON = require(entityPath);
    actionJSON.actions.forEach((action) => {
      if (action.name === funcName) {
        if (typeof action.entitypath === 'object') {
          epath = '';
          const keys = Object.keys(action.entitypath);
          for (let k = 0; k < keys.length; k += 1) {
            epath += `${keys[k]}:${action.entitypath[keys[k]]} <br /> `;
          }
          epath = epath.substring(0, epath.length - 8);
        } else {
          epath = action.entitypath;
        }
      }
    });
  }
  return epath;
}
function recurseCallExpressions(statement, callList) {
  // Recursively finds all CallExpressions in the syntax tree
  if (statement.type === 'CallExpression') callList.push(statement);
  const keys = Object.keys(statement);
  for (let k = 0; k < keys.length; k += 1) {
    if (typeof statement[keys[k]] === 'object' && statement[keys[k]] !== null) {
      recurseCallExpressions(statement[keys[k]], callList);
    }
  }
}
function readFileUsingLib(filename, descriptionObj, workflowObj, functionList) {
  // read the file
  const aFile = fs.readFileSync(filename, 'utf8');
  // parsing the file to get the function and class declarations.
  const aFileFuncArgs = acorn.parse(aFile, { ecmaVersion: 2020 });
  let callName = 'identifyRequest';
  // Looping through all the declarations parsed:
  aFileFuncArgs.body.forEach((e) => {
    // Getting only the class declaration as it has our required functions.
    if (e.type === 'ClassDeclaration') {
      const methodDefinition = e.body;
      methodDefinition.body.forEach((method) => {
        // Getting method name and its params in the class.
        const funcName = method.key.name;
        const funcArgs = [];
        method.value.params.forEach((param) => {
          if (param.type === 'Identifier') {
            funcArgs.push(param.name);
          } else if (param.type === 'RestElement') {
            funcArgs.push(`...${param.argument.name}`);
          } else {
            const args = `${param.left.name} = ${param.right.raw}`;
            funcArgs.push(args);
          }
        });
        // Getting the entity for the method:
        const callList = [];
        method.value.body.body.forEach((statement) => {
          recurseCallExpressions(statement, callList);
        });
        const requests = [];
        for (let i = 0; i < callList.length; i += 1) {
          if (callList[i].callee.property && callList[i].callee.property.name === callName) {
            requests.push(callList[i]);
          }
        }
        if (requests.length > 0) {
          const expr = requests[0];
          if (expr.arguments.length < 2) {
            throw new Error(`Bad inputs in method ${funcName}`);
          }
          const entity = expr.arguments[0].value;
          const actionName = expr.arguments[1].value;
          if (expr !== undefined && (expr.arguments[0].type !== 'Literal' || expr.arguments[1].type !== 'Literal')) {
            const param1 = method.value.params[0];
            const param2 = method.value.params[1];
            if (param1.type !== 'Identifier' || param2.type !== 'Identifier'
                || expr.arguments[0].type !== 'Identifier' || expr.arguments[1].type !== 'Identifier'
                || param1.name !== expr.arguments[0].name || param2.name !== expr.arguments[1].name) {
              throw new Error(`identifyRequest proxy method ${funcName} unknown format`);
            } else if (callName !== 'identifyRequest') {
              throw new Error(`MethodDocumentor not yet programmed to handle multiple helper methods: 1) ${callName}, 2) ${funcName}`);
            }
            callName = funcName;
          }
          const entityPath = getPathFromEntity(entity, actionName);
          // Creating and storing the object for the method.
          if (entityPath !== undefined) {
            functionList.push(
              createObjectForFunction(
                funcName,
                funcArgs,
                entityPath,
                descriptionObj[funcName],
                workflowObj[funcName]
              )
            );
          }
        }
      });
    }
  });
}
function readJSONFile(filename, descriptionObj, workflowObj) {
  // Accessing the JSON file.
  const phJSON = require(filename);
  // Getting the methods array.
  const methodArray = phJSON.methods;
  methodArray.forEach((methodName) => {
    // Getting the method description and workflow:
    const funcName = methodName.name;
    descriptionObj[funcName] = methodName.summary ? methodName.summary : methodName.description;
    workflowObj[funcName] = methodName.task ? 'Yes' : 'No';
  });
}
function readMDFile(filename, functionList) {
  // Reading in the .md file and creating an array with each line as an element.
  const mdFile = fs.readFileSync(filename, 'utf-8');
  const fileSplit = mdFile.split('\n');
  // Storing the data that should added later to the updated data.
  const linesToAddLater = [];
  let index = fileSplit.length - 1;
  // Removing all the blank lines at the end of the file.
  if (fileSplit[index] === '') {
    while (fileSplit[index] === '') {
      linesToAddLater.push(fileSplit.pop());
      index -= 1;
    }
  }
  // Checking if the last 2 lines are <br> and </table>. If not, the file is corrupted and the
  // data at the end of the file should be fixed.
  if (fileSplit[index] === '<br>' || fileSplit[index - 1] === '</table>') {
    // Storing <br> and </table> to add later.
    linesToAddLater.push(fileSplit.pop());
    linesToAddLater.push(fileSplit.pop());
    index -= 2;
  } else {
    console.log('The file has bad content at the end.');
    return;
  }
  // if (fileSplit[index] !== '<br>' && fileSplit[index - 1] !== '</table>') {
  //   console.log('The file has bad content at the end.');
  //   return;
  // } else {
  //   // Storing <br> and </table> to add later.
  //   linesToAddLater.push(fileSplit.pop());
  //   linesToAddLater.push(fileSplit.pop());
  //   index -= 2;
  // }
  // Removing all the lines until the header tags are reached.
  while (!fileSplit[index].includes('<th')) {
    fileSplit.pop();
    index -= 1;
  }
  // Adding </tr> for the header row, because it got removed in the above loop.
  fileSplit.push('  </tr>');
  // Creating the tags for each method to be appended to the file.
  const tdBeginTag = '    <td style="padding:15px">';
  const tdEndTag = '</td>';
  functionList.forEach((func) => {
    const signCommand = `${tdBeginTag}${func.method_signature}${tdEndTag}`;
    const descCommand = `${tdBeginTag}${func.description}${tdEndTag}`;
    const pathCommand = `${tdBeginTag}${func.path}${tdEndTag}`;
    const workflowCommand = `${tdBeginTag}${func.workflow}${tdEndTag}`;
    fileSplit.push('  <tr>');
    fileSplit.push(signCommand);
    fileSplit.push(descCommand);
    fileSplit.push(pathCommand);
    fileSplit.push(workflowCommand);
    fileSplit.push('  </tr>');
  });
  // Adding </table> and <br> at the end of the file to complete the table and the file.
  while (linesToAddLater.length > 0) {
    fileSplit.push(linesToAddLater.pop());
  }
  // Writing all the content back into the file.
  fs.writeFileSync(filename, fileSplit.join('\n'), {
    encoding: 'utf-8',
    flag: 'w'
  });
}
function getFileInfo() {
  // If files don't exist:
  if (!fs.existsSync(`${adaptdir}/adapter.js`)) {
    console.log('Missing - utils/adapter.js');
    return;
  }
  if (!fs.existsSync(`${adaptdir}/pronghorn.json`)) {
    console.log('Missing - pronghorn.json');
    return;
  }
  if (!fs.existsSync(`${adaptdir}/CALLS.md`)) {
    console.log('Missing - CALLS.md');
    return;
  }
  const descriptionObj = {};
  const workflowObj = {};
  // Get the method descriptions and the workflow values from pronghorn.json file.
  readJSONFile(`${adaptdir}/pronghorn.json`, descriptionObj, workflowObj);
  // Get the method signature, entity path and create an object that contains all the info regarding
  // the method and push it to the functionList array.
  const functionList = [];
  readFileUsingLib(
    `${adaptdir}/adapter.js`,
    descriptionObj,
    workflowObj,
    functionList
  );
  // createMarkDown(functionList);
  readMDFile(`${adaptdir}/CALLS.md`, functionList);
}
getFileInfo();