All files / src/models variables.js

81.03% Statements 94/116
73.07% Branches 19/26
100% Functions 4/4
81.03% Lines 94/116

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117193x 193x 193x 193x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 12x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 193x 193x 193x 193x 193x 193x 2x 2x 2x 2x 2x 1x 1x 1x 2x         1x 1x 2x 1x 1x         1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x     1x 1x 2x 193x 8x 8x 8x 8x 1x 1x 1x     1x 1x 1x               1x 1x 1x 1x 1x 7x 7x 8x 193x 193x 10x 10x 10x 10x 8x 10x 2x 10x   10x 10x  
import { ERROR_TYPES, parseRaw, toNameValueObject, validateName } from '@clevercloud/client/esm/utils/env-vars.js';
import _countBy from 'lodash/countBy.js';
import readline from 'node:readline';
 
function readStdin() {
  return new Promise((resolve, reject) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
      terminal: false,
    });
 
    const lines = [];
    rl.on('line', (line) => {
      lines.push(line);
    });
 
    rl.on('close', () => {
      const text = lines.join('\n');
      resolve(text);
    });
 
    rl.on('error', reject);
  });
}
 
// The JSON input format for variables is:
// an array of objects, each having:
// a "name" property with a string and value
// a "value" property with a string and value
// TODO: This should be moved and unit tested in the clever-client repo
function parseFromJson(rawStdin) {
  let variables;
  try {
    variables = JSON.parse(rawStdin);
  } catch (e) {
    throw new Error(`Error when parsing JSON input: ${e.message}`);
  }
 
  if (!Array.isArray(variables) || variables.some((entry) => typeof entry !== 'object')) {
    throw new Error(
      'The input was valid JSON but it does not follow the correct format. It must be an array of objects.',
    );
  }
 
  const someEntriesDontHaveNameAndValueAsString = variables.some(({ name, value }) => {
    return typeof name !== 'string' || typeof value !== 'string';
  });
  if (someEntriesDontHaveNameAndValueAsString) {
    throw new Error(
      'The input was a valid JSON array of objects but all entries must have properties "name" and "value" of type string. Ex: { "name": "THE_NAME", "value": "the value" }',
    );
  }
 
  const namesOccurences = _countBy(variables, 'name');
  const duplicatedNames = Object.entries(namesOccurences)
    .filter(([_name, count]) => count > 1)
    .map(([name]) => `"${name}"`)
    .join(', ');
 
  if (duplicatedNames.length !== 0) {
    throw new Error(`Some variable names defined multiple times: ${duplicatedNames}`);
  }
 
  const invalidNames = variables
    .filter(({ name }) => !validateName(name))
    .map(({ name }) => `"${name}"`)
    .join(', ');
 
  if (invalidNames.length !== 0) {
    throw new Error(`Some variable names are invalid: ${invalidNames}`);
  }
 
  return toNameValueObject(variables);
}
 
function parseFromNameEqualsValue(rawStdin) {
  const { variables, errors } = parseRaw(rawStdin);
 
  if (errors.length !== 0) {
    const formattedErrors = errors
      .map(({ type, name, pos }) => {
        if (type === ERROR_TYPES.INVALID_NAME) {
          return `line ${pos.line}: ${name} is not a valid variable name`;
        }
        if (type === ERROR_TYPES.DUPLICATED_NAME) {
          return `line ${pos.line}: be careful, the name ${name} is already defined`;
        }
        if (type === ERROR_TYPES.INVALID_LINE) {
          return `line ${pos.line}: this line is not valid, the correct pattern is: NAME="VALUE"`;
        }
        if (type === ERROR_TYPES.INVALID_VALUE) {
          return `line ${pos.line}: the value is not valid, if you use quotes, you need to escape them like this: \\" or quote the whole value.`;
        }
        return 'Unknown error in your input';
      })
      .join('\n');
 
    throw new Error(formattedErrors);
  }
 
  return toNameValueObject(variables);
}
 
export async function readVariablesFromStdin(format) {
  const rawStdin = await readStdin();
 
  switch (format) {
    case 'name-equals-value':
      return parseFromNameEqualsValue(rawStdin);
    case 'json':
      return parseFromJson(rawStdin);
    default:
      throw new Error("Unrecognized environment input format. Available formats are 'name-equals-value' and 'json'");
  }
}