1 | 'use strict';
|
2 | const execa = require('execa');
|
3 | const ora = require('ora');
|
4 | const chalk = require('chalk');
|
5 |
|
6 | const allowedGlobalVariables = [
|
7 | 'full', 'except', 'all', 'verbose', 'break', 'interactive', 'warnings', 'list'
|
8 | ];
|
9 |
|
10 | module.exports = async (stage, input, options = {}, config) => {
|
11 | const configMicroservices = Object.keys(config.microservices);
|
12 | let microservices;
|
13 |
|
14 | microservices = input;
|
15 |
|
16 | assertStageIsValid(config, stage);
|
17 | assertMicroservicesAreValid(microservices, configMicroservices);
|
18 | setDefaults(config, options);
|
19 | assertVariablesAreValid(config, options);
|
20 |
|
21 | if (options.except === true) {
|
22 | microservices = getArrayDifference(configMicroservices, microservices);
|
23 | } else if (options.all === true) {
|
24 | microservices = configMicroservices;
|
25 | }
|
26 |
|
27 | const {variables} = config.stages[stage];
|
28 | if (variables && !isSubset(variables, Object.keys(options))) {
|
29 | throw new Error(`Not enough variables provided for stage "${stage}". Needed:
|
30 | "${variables}".`);
|
31 | }
|
32 |
|
33 | console.log(chalk`Beginning to work on stage {green.bold ${stage}}`);
|
34 | const originalStageScript = config.stages[stage].run;
|
35 | for (const microservice of microservices) {
|
36 | let spinner = ora(chalk`Working on stage {green.bold ${stage}} on microservice {red.bold ${microservice}}...`).start();
|
37 | if (!isStageAllowed(config, microservice, stage)) {
|
38 | spinner.warn(chalk`Working on stage {green.bold ${stage}} on microservice {red.bold ${microservice}}... {yellow.bold Skipped}: Stage not allowed`);
|
39 | continue;
|
40 | }
|
41 |
|
42 | const stageScript = interpolateVariables(originalStageScript, microservice, options);
|
43 | let {cwd} = config.stages[stage];
|
44 | if (cwd) {
|
45 | cwd = interpolateVariables(cwd, microservice, options);
|
46 | }
|
47 |
|
48 | try {
|
49 |
|
50 | const {stdout, stderr} = await processStage(stageScript, cwd);
|
51 | if (stderr) {
|
52 | if (options.warnings === false) {
|
53 | throw new Error(stderr);
|
54 | }
|
55 | }
|
56 |
|
57 | spinner = setSpinnerStatus(stderr, options, spinner);
|
58 |
|
59 | if (stdout) {
|
60 | if (options.verbose === true) {
|
61 | console.log(stdout);
|
62 | }
|
63 | }
|
64 |
|
65 | if (stderr) {
|
66 | console.error(stderr);
|
67 | }
|
68 | } catch (error) {
|
69 | spinner.fail();
|
70 |
|
71 | logError(error, options);
|
72 |
|
73 | if (options.break === true) {
|
74 | throw new Error('Aborting execution: --break set to true');
|
75 | }
|
76 | }
|
77 | }
|
78 |
|
79 | console.log('');
|
80 | };
|
81 |
|
82 | function logError(error, options) {
|
83 | const {stdout, stderr} = error;
|
84 | if (stdout && options.verbose === true) {
|
85 | console.log(stdout);
|
86 | }
|
87 |
|
88 | if (stderr) {
|
89 | console.error(stderr);
|
90 | }
|
91 |
|
92 | if (!stderr && !stdout && error) {
|
93 | console.error(error);
|
94 | }
|
95 | }
|
96 |
|
97 | function setSpinnerStatus(stderr, options, spinner) {
|
98 | if (stderr) {
|
99 | if (options.warnings === false) {
|
100 | return spinner.fail();
|
101 | }
|
102 |
|
103 | return spinner.warn();
|
104 | }
|
105 |
|
106 | return spinner.succeed();
|
107 | }
|
108 |
|
109 | function assertStageIsValid(config, stage) {
|
110 | const stages = Object.keys(config.stages);
|
111 | if (!stages.includes(stage)) {
|
112 | throw new Error(`Stage "${stage}" not included in the ones specified in the config file:
|
113 | "${Object.keys(config.stages)}".`);
|
114 | }
|
115 | }
|
116 |
|
117 | function isStageAllowed(config, microservice, stage) {
|
118 | const microserviceObject = config.microservices[microservice];
|
119 | return !(microserviceObject && microserviceObject.allowedStages && !microserviceObject.allowedStages.includes(stage));
|
120 | }
|
121 |
|
122 | function assertMicroservicesAreValid(microservices, configMicroservices) {
|
123 | if (!isSubset(microservices, configMicroservices)) {
|
124 | throw new Error(`Microservices "${microservices}" don't match with the ones specified in the config file:
|
125 | "${configMicroservices}".`);
|
126 | }
|
127 | }
|
128 |
|
129 | function assertVariablesAreValid(config, options) {
|
130 | const confVariables = Object.keys(config.variables);
|
131 | const currentVariables = Object.keys(options);
|
132 | const allVariables = confVariables.concat(allowedGlobalVariables);
|
133 | if (!isSubset(currentVariables, allVariables)) {
|
134 | throw new Error(`Variables "${currentVariables}" don't match with the ones specified in the config file:
|
135 | "${allVariables}".`);
|
136 | }
|
137 |
|
138 | for (const vari of currentVariables) {
|
139 | const allowedValues = config.variables[vari];
|
140 | if (allowedValues && !allowedValues.includes(`${options[vari]}`)) {
|
141 | throw new Error(`Value "${options[vari]}" of variable "${vari}" not allowed. Allowed:
|
142 | "${allowedValues}".`);
|
143 | }
|
144 | }
|
145 | }
|
146 |
|
147 | function setDefaults(config, options) {
|
148 | if (!config.variables.defaults) {
|
149 | return;
|
150 | }
|
151 |
|
152 | const defaults = Object.keys(config.variables.defaults);
|
153 |
|
154 | for (const vari of defaults) {
|
155 | if (options[vari]) {
|
156 | continue;
|
157 | }
|
158 |
|
159 | options[vari] = config.variables.defaults[vari];
|
160 | }
|
161 | }
|
162 |
|
163 | function interpolateVariables(string, microservice, options) {
|
164 | string = string.replace('$MICROSERVICE', microservice);
|
165 | for (const key of Object.keys(options)) {
|
166 | string = string.replace('$' + key, options[key]);
|
167 | }
|
168 |
|
169 | return string;
|
170 | }
|
171 |
|
172 | function isSubset(subset, set) {
|
173 | return subset.every(val => set.includes(val));
|
174 | }
|
175 |
|
176 | function getArrayDifference(minuend, substrahend) {
|
177 | return minuend.filter(x => !substrahend.includes(x));
|
178 | }
|
179 |
|
180 | function processStage(stageScript, cwd) {
|
181 | return execa('/bin/sh', ['-c', stageScript], {cwd});
|
182 | }
|