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