Friday, 15 May 2020

Documenting from the metadata source with a Salesforce CLI Plug-In - Part 5


In Part 1 of this series, I explained how to generate a plugin and clone the example command.
In Part 2 I covered finding and loading the package.json manifest and my custom configuration file.
In Part 3, I explained how to load and process source format metadata files.
In Part 4, I showed how to enrich fields by pulling in information stored outside of the field definition file.

In this week's exciting episode, I'll put it all together and generate the HTML output. This has been through several iterations :

  • When I first came up with the idea, I did what I usually do which is hardcode some very basic HTML in the source code, as at this point I'm rarely sure things are going to work.
  • Prior to my first presentation of the plug-in, I decided to move the HTML out to files that were easily changed, but still as fragments. This made showing the code easier, but meant I had to write a fair bit of boilerplate code, but was all I really had time for.
  • Once I'd done the first round of talks, I had the time to revisit and move over to a template framework, which was always going to be the correct solution. This was the major update to the plug-in I referred to in the last post.
Template Framework

I'd been using EJS in YASP (Yet Another Side Project) and liked how easy it was to integrate. EJS stands for Easy JavaScript templates, and it's well named. You embed plain old JavaScript into HTML markup and call a function passing the template and the data that needs to be processed by the JavaScript. If you've written Visualforce pages or email templates in the past, this pattern is very familiar (although you have to set up all of the data that you need prior to passing it to the generator function - it won't figure out what is needed based on what you've used like Visualforce will!).

A snippet from my HTML page that lists the groups of objects:

<% content.groups.forEach(function(group){ %>
        <td><%= group.title %></td>
        <td><a href="<%= %>">Click to View</a></td>
<% }); %>

and as long as I pass the function that generates the HTML an object with a property of content, that matches the expected structure, I'm golden.

To use EJS in my plug-in, I install the node module,  :

npm install --save ejs

(Note that I'm using the --save option, as otherwise this won't be identified as a dependency and thus won't be added to the plug-in. Exactly what happened when I created the initial version and installed it on my Windows machine!)

I add the following line to my Typescript file to import the function that renders the HTML from the template:

import { renderFile } from 'ejs';

and I can then call renderFile(template, data) to asynchronously generate the HTML.

Preparing the Data

As Salesforce CLI plug-ins are written in Typescript by default, it's straightforward to keep track of the structure of your objects as you can strongly type them. Continuing with the example of my groups, the data for these is stored in an object with the following structure:

interface ObjectsContent {
    counter: number;
    groups: Array<ObjectGroupContent>;
    footer?: object;

Rather than pass this directly, I store this as the content of a more generic object that includes some standard information, like the plug-in version and the date the pages were generated:
let data={
             content: content,
                 generatedDate : this.generatedDate,
                 version : this.config.version

Rendering the HTML

Once I have the data in the format I need, I execute the rendering function passing the template filename - note that as this is asynchronous I have to return a promise to my calling function:

return new Promise((resolve, reject) => {
    renderFile(templateFile, data, (err, html) => {
        if (err) {
        else {

and my calling function loops through the groups and gets the rendered HTML when the promise resolves, which it writes to the report directory:
this.generator.generateHTML(join('objects', 'objects.ejs'), this.content)
.then(html => {
    writeFileSync(this.indexFile, html);    

Moar Metadata

The latest version of the plugin generates HTML markup for an object's Validation Rules and Record types - the example repo of Salesforce metadata has been updated to include these and the newly generated HTML is available on Heroku at :

Related Posts

No comments:

Post a comment