UNPKG

5.58 kBJavaScriptView Raw
1'use strict';
2const execa = require('execa');
3const ora = require('ora');
4const chalk = require('chalk');
5
6const allowedGlobalVariables = [
7 'all',
8 'break',
9 'config',
10 'except',
11 'full',
12 'interactive',
13 'list',
14 'spinner',
15 'verbose',
16 'warnings'
17];
18
19module.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 /* eslint-disable-next-line no-await-in-loop */
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
111function 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
126function 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
138function 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
146function isStageAllowed(config, microservice, stage) {
147 const microserviceObject = config.microservices[microservice];
148 return !(microserviceObject && microserviceObject.allowedStages && !microserviceObject.allowedStages.includes(stage));
149}
150
151function 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
158function 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
177function 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
193function 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
202function isSubset(subset, set) {
203 return subset.every(val => set.includes(val));
204}
205
206function getArrayDifference(minuend, substrahend) {
207 return minuend.filter(x => !substrahend.includes(x));
208}
209
210function processStage(stageScript, cwd) {
211 return execa('/bin/sh', ['-c', stageScript], {cwd});
212}