Dynamic variables generated from CLI options

I have some CLI options, e.g.

# serverless.yaml snippet
custom:
  part1: ${opt:part1}
  part2: ${opt:part2}

  combined: ${file(./serverless-helpers.js):dynamic.combined}

The value of ${self:custom.part1} needs to be used as it is input (as a lower-case string value mostly), but it also needs to be used elsewhere in serverless.yaml as a title-case word among other title-case words.

If it is invoked like:

serverless package --part1=foo --part2=bar

I want to be able to dynamically generate a custom variable that would contain the value fooBar after combining these two CLI option values.

Let’s say the serverless-helper.js is like

module.exports.dynamic = async function(serverless) {
  // The `serverless` argument contains all the
  // information in the serverless.yaml file
  // serverless.cli.consoleLog('You can access Serverless config and methods as well!');
  // serverless.cli.consoleLog(serverless.service.custom);
  
  function toTitleCase(word) {
    console.log("input word:  " + word);
    let lower = word.toLowerCase();
    lower = lower.replace(lower[0], lower[0].toUpperCase());
    console.log("output word: " + word);
    return lower;
  }
  return Promise.resolve({
    combined: serverless.service.custom["part1"] + toTitleCase(serverless.service.custom["part2"])
  });
};

The js-helper seems to have no callbacks to hook into that could create the combined property after the CLI option values are interpreted. The combined property, as generated above, only gets the YAML values, not the CLI option values. So the console log contains messages like

input word:  ${opt:part1}
output word: ${opt:part1}
input word:  ${opt:part2}
output word: ${opt:part2}

Is there a way to register a helper-js function as a callback that is invoked when the CLI options are evaluated, so that the callback function could operate on the actual CLI option values?

Have a read of the maintainers note here. It may help. Basically you’re wanting recursive resolution. As that can get you in an infinite loops it’s not done. serverless/Variables.js at master · serverless/serverless · GitHub

Here’s something to try after looking at the getValueFromOptions function on line 640: What if you use the options value rather than the custom value?

  return Promise.resolve({
    combined: serverless.service.options["part1"] + toTitleCase(serverless.service.options["part2"])
  });

Thanks for the tip to checkout the serverless Variables.js - I arrived at something that works, via reading some source code and console logging the entire serverless object. This example applies a helper function to title-case some input option values. There is a result of parsing the input options already available in the serverless object.

// serverless-helpers.js

function toTitleCase(word) {
  console.log("input word:  " + word);
  let lower = word.toLowerCase();
  let title = lower.replace(lower[0], lower[0].toUpperCase());
  console.log("output word: " + title);
  return title;
}

module.exports.dynamic = function(serverless) {
  // The `serverless` argument contains all the information in the serverless.yaml file
  // serverless.cli.consoleLog('You can access Serverless config and methods as well!');
  // serverless.cli.consoleLog(serverless);  // this is useful for discovery of what is available
    
  const input_options = serverless.processedInput.options;
  return {
    part1Title: toTitleCase(input_options.part1),
    part2Title: toTitleCase(input_options.part2)
  };
};
# serverless.yaml snippet

custom:
  dynamicOpts: ${file(./serverless-helpers.js):dynamic}
  combined: prefix${self:custom.dynamicOpts.part1Title}${self:custom.dynamicOpts.part2Title}Suffix

This simple example assumes the input options are --part1={value} and --part2={value}, but the generalization is to traverse the properties of serverless.processedInput.options and apply any custom helpers to those values.