UNPKG

13.7 kBJavaScriptView Raw
1var nopt = require("nopt"),
2 noptTypes = require("./nopt-types.js"),
3 noptUsage = require("./nopt-usage-2.js"),
4 path = require("path"),
5 parseDependencies = require('./dependency-parser.js').parseDependencies,
6 BuildDiffProcess = require("./build-diff-process.js").BuildDiffProcess,
7 cfgLoader = require('./configuration-loader.js'),
8 readJsonSync = require('read-json-sync'),
9 loggerFactory = require("./sl-logger.js").logger,
10 expandCommaSeparatedValues = require('./utils').expandCommaSeparatedValues,
11 globalErrorHandler = require('./global-error-handler'),
12 SlJsInfra = require('sl-js-infra').SlJsInfra,
13 DefaultValuesResolver = require('./default-values-resolver'),
14 Technology = SlJsInfra.Entities.Technology,
15 AgentType = SlJsInfra.Entities.AgentType;
16
17
18//TODO: [SLDEV-4709] Verify if we can remove this completely. Should check how SlNodeJS invokes it.
19function sendLastLogs(cfg, logger, callback) {
20 if (cfg.sendLogs) {
21 logger.remoteStream.checkBuffer(true, function (err) {
22 callback();
23 })
24 } else {
25 callback();
26 }
27}
28
29function CIA(sourceConrolProviders) {
30 this._hasMissingArguments = false;
31 this._hadInvalidArguments = false;
32 this.knownOpts = this._createKnownOptions();
33 this.shortHands = this._createShortHands();
34 this.description = this._createDescription();
35 this.defaults = this._createDefaults();
36 this.getSourceConrolProviders = function () {
37 return sourceConrolProviders;
38 };
39}
40
41CIA.prototype.run = function run(callback) {
42 console.log('SeaLights Build agent ' + require("../package.json").version);
43 var context = this;
44 var buildArguments = this.getBuildArguments();
45 this._validateArguments(buildArguments);
46 if (this._hasInvalidOrMissingArguments(buildArguments)) {
47 return this.handleInvalidArgs(callback);
48 }
49 var parsedDependencies = this._tryParseDependenciesForIntegrationBuild(buildArguments);
50 this.loadConfiguration(buildArguments, function (err, cfg) {
51 if(err){
52 console.error(err.stack);
53 callback(buildArguments.failBuild ? 1 : 0);
54 } else {
55 var logger = loggerFactory(cfg);
56 printConfiguration(cfg, logger);
57 var buildMappingProxy = new SlJsInfra.BackendProxy(cfg, logger);
58 var packageFilePath = SlJsInfra.Utils.FileUtils.findFileUp("package.json", buildArguments.workspacepath);
59 var agentEventsController = createAgentEventsController(buildArguments.workspacepath, logger);
60 agentEventsController.submitAgentStartedEvent(require(packageFilePath)).then(function () {
61 context.runBuildDiffProcess(cfg, buildMappingProxy, parsedDependencies, buildArguments, logger, agentEventsController, callback);
62 })
63 }
64 })
65}
66
67CIA.prototype._hasInvalidOrMissingArguments = function(buildArguments){
68 return this._hasMissingArguments || this._hadInvalidArguments || buildArguments.help;
69}
70
71CIA.prototype._tryParseDependenciesForIntegrationBuild = function(buildArguments){
72 var parsedDependencies = [];
73 if (buildArguments.dependency && !this._hadInvalidArguments) {
74 try {
75 parsedDependencies = parseDependencies(buildArguments.dependency);
76 } catch (e) {
77 globalErrorHandler.setLastError(e)
78 this._hadInvalidArguments = true;
79 }
80 }
81
82 if (buildArguments.dependenciesFile && !this._hadInvalidArguments) {
83 try {
84 var parsedJson = readJsonSync(buildArguments.dependenciesFile);
85 if (!parsedJson || !Array.isArray(parsedJson)) {
86 throw 'Invalid file structure';
87 }
88
89 parsedJson.forEach(function (depItem) {
90 if (depItem.appName && depItem.build && depItem.branch) {
91 parsedDependencies.push({
92 appName: depItem.appName.toString(),
93 branch: depItem.branch.toString(),
94 build: depItem.build.toString()
95 });
96 } else {
97 throw 'Invalid dependency item: ' + JSON.stringify(depItem);
98 }
99 });
100
101 } catch (e) {
102 globalErrorHandler.setLastError(e, 'Error reading/parsing ' + buildArguments.dependenciesFile);
103 this._hadInvalidArguments = true;
104 }
105 }
106
107 return parsedDependencies;
108}
109
110CIA.prototype._validateArguments = function(buildArguments){
111 var context = this;
112 ["branch", "build", "appname"].forEach(function (key) {
113 if (!buildArguments[key]) {
114 console.error("Required argument '" + key + "' not specified");
115 context._hasMissingArguments = true;
116 }
117 });
118
119 //workspacepath, scm must all come together or not at all
120 var count = 0;
121 ["workspacepath", "scm"].forEach(function (key) {
122 if (buildArguments[key]) {
123 count++;
124 }
125 });
126
127 if (count != 0 && count != 2) {
128 console.error("The 'workspacepath' and 'scm' command-line arguments must be specified together or not at all");
129 this._hadInvalidArguments = true;
130 }
131}
132
133CIA.prototype._createKnownOptions = function(){
134 return {
135 "workspacepath": path,
136 "outputpath": String,
137 "instrumentForBrowsers": Boolean,
138 "downloadAgent": Boolean,
139 "instrumentationOnly": Boolean,
140 "branch": String,
141 "build": String,
142 "commit": String,
143 "appname": String,
144 "config": String,
145 "scm": "scm",
146 "help": Boolean,
147 "dependency": [String, Array],
148 "dependenciesFile": path,
149 "recursive": Boolean,
150 "author": [String, Array],
151 "logsUrl": String,
152 "jobName": String,
153 "usebranchcoverage": Boolean,
154 "usebuildmappingv3": Boolean,
155 "token": String,
156 "buildsessionid": String,
157 "includedFiles": [String, Array],
158 "excludedFiles": [String, Array],
159 "babylonPlugins": [String, Array],
160 "es6Modules": Boolean,
161 "failBuild": Boolean,
162 "uniqueModuleId": String,
163 "scmBaseUrl": String,
164 "scmVersion": String,
165 "scmProvider": String,
166 "useBabylon":Boolean,
167 "sendContributors": Boolean,
168 "delayShutdownInSeconds": Number,
169 "projectRoot": String
170 };
171}
172
173CIA.prototype._createShortHands = function(){
174 return {
175 "h": ["--help"],
176 "d": ["--dependency"],
177 "r": ["--recursive"]
178 };
179}
180
181CIA.prototype._createDescription = function(){
182 return {
183 "workspacepath": " Path to the workspace where the source code exists",
184 "outputpath": " Path where the compiled output exists",
185 "instrumentForBrowsers": " Instrument javascript files for browser coverage",
186 "instrumentationOnly": " Skip sending mapping to the server. Used when instrumenting files on multiple steps",
187 "downloadAgent": " Set this value to 'false' in order to prevent the instrumented javascript to try and download the browser test listener (for example, when using 'Karma').",
188 "copyAllFilesToOutput": " Copy all files from the workspacepath to the outputpath. On by default.",
189 "branch": " Branch name",
190 "build": " Build version",
191 "commit": " commit ID, as provided by the SCM (e.g. SHA1 for git, revision for SVN, changeset for TFS). May be automatically detected by the SCM.",
192 "appname": " Application Name",
193 "scm": " The Source Control Management used (currently only 'git' is supported)",
194 "help": " Show this help page",
195 "dependency": " Project dependencies. Pattern should be a semicolon-separated AppName@Branch@Build list. May be specified more than once.",
196 "dependenciesFile": " A path to a json file that is in the following format: [{\"appName\":\"\",\"branch\":\"\",\"build\":\"\"},{...}]",
197 "recursive": " Starts a recursive search for in a folder specified by the 'workspacepath' option",
198 "author": " Optional. The name or email of the user that should be associated with this build. May be specified more than once.",
199 "logsUrl": " Optional. The logs url of the build.",
200 "jobName": " Optional. The jenkins job name that triggered the build.",
201 "usebranchcoverage": " Optional. Include branches in scan",
202 "usebuildmappingv3": " Optional. Use V3 of build mapping",
203 "token": " Agent token used to authenticate requests by the server",
204 "buildsessionid": " Build Session ID to associate with this build",
205 "includedFiles": " Files glob pattern to include, relative to the workspacepath. Defaults to **/*.js. Comma-separated values are accepted.",
206 "excludedFiles": " Files glob pattern to exclude, relative to the workspacepath. Defaults to node_modules/**/*.js, test/**.js. Comma-separated values are accepted.",
207 "failBuild": " Makes the build scanner exit with non-zero code on success. Off by default",
208 "es6Modules": " Use ES6 'module' mode when scanning files.",
209 "useBabylon":"Use babylon for scanning files",
210 "babylonPlugins": "List of non default babylon parser plugins, separated by comma",
211 "sendContributors": "Send contributor details for advanced committer reports and features",
212 "delayShutdownInSeconds":"Shutdown time in seconds for the browser agent. Default is 30 seconds.",
213 "scmProvider": "The provider name of your Source Control Management (SCM) tool. Supported values are 'Github', 'Bitbucket' and 'Gitlab'. If not used, 'Github' is assumed.",
214 "scmVersion": "The version of your Source Control Management (SCM) tool. If left blank, cloud version is assumed. Otherwise, specify the version of your on-premise server.",
215 "scmBaseUrl": "The URL to the repository which contains the code. If left blank, the url of the remote GIT origin is being used.",
216 "uniqueModuleId":"Unique module id, case-sensitive. This value should remain consistent between runs."
217 };
218}
219
220CIA.prototype._createDefaults = function(){
221 var defaultValuesResolver = new DefaultValuesResolver();
222 return {
223 "help": "",
224 "includedFiles": defaultValuesResolver.getIncludedFiles(),
225 "excludedFiles": defaultValuesResolver.getExcludedFiles(),
226 "prefixesOfExcludedFiles": process.env["SL_prefixesOfExcludedFiles"] ? process.env["SL_prefixesOfExcludedFiles"] : "~, :, webpack, node_modules",
227 "delayShutdownInSeconds":30
228 };
229}
230
231CIA.prototype.handleInvalidArgs = function(callback) {
232 try {
233 var usage = noptUsage(this.knownOpts, this.shortHands, this.description, this.defaults);
234 console.error(usage);
235 } catch (e) {
236 globalErrorHandler.setLastError(e);
237 }
238 return callback(1);
239}
240
241
242CIA.prototype.loadConfiguration = function(buildArguments, callback) {
243 cfgLoader(buildArguments.config).then(function (cfg) {
244 cfg.appName = buildArguments.appname;
245 if (buildArguments.token) {
246 cfg.token = buildArguments.token;
247 }
248 callback(null, cfg);
249 }).catch(function (err) {
250 callback(err)
251 });
252}
253
254function shutdownAgent(logger, cfg, statusCode, agentEventsController, callback) {
255 sendLastLogs(cfg, logger, function () {
256 agentEventsController.submitAgentShutdownEvent().then(function () {
257 callback(statusCode);
258 })
259 });}
260
261
262CIA.prototype.runBuildDiffProcess = function(cfg, buildMappingProxy, parsedDependencies, buildArguments, logger, agentEventsController, callback) {
263 var scmProviders = this.getSourceConrolProviders();
264 var buildDiffProcess = new BuildDiffProcess(cfg, buildMappingProxy, scmProviders, parsedDependencies, logger);
265 buildDiffProcess.run(buildArguments).then(function () {
266 shutdownAgent(logger, cfg, 0, agentEventsController, callback);
267 }).catch(function (err) {
268 logger.error(err);
269 var statusCode = buildArguments.failBuild ? 1 : 0;
270 shutdownAgent(logger, cfg, statusCode, agentEventsController, callback);
271 });
272}
273
274CIA.prototype.getBuildArguments = function() {
275 nopt.typeDefs.git = noptTypes.git;
276 nopt.invalidHandler = function (key, val, types) {
277 context._hasMissingArguments = true;
278 console.error('Invalid value specified for ' + key + ': ' + val);
279 };
280 var buildArguments = nopt(this.knownOpts, this.shortHands, process.argv, 2);
281 for (var i in this.defaults) { //Add default values
282 if (!buildArguments[i]) {
283 buildArguments[i] = this.defaults[i];
284 }
285 }
286 buildArguments.includedFiles = expandCommaSeparatedValues(buildArguments.includedFiles);
287 buildArguments.excludedFiles = expandCommaSeparatedValues(buildArguments.excludedFiles);
288 buildArguments.babylonPlugins = expandCommaSeparatedValues(buildArguments.babylonPlugins);
289 return buildArguments;
290}
291
292
293
294
295function printConfiguration(cfg, logger)
296{
297 logger.info("***************************************************************");
298 logger.info("Current Configuration: ");
299 logger.info("***************************************************************");
300
301 for(p in cfg)
302 {
303 if (typeof cfg[p] != "function"){
304 logger.info(p + ":", cfg[p]);
305 }
306 }
307
308 logger.info("***************************************************************");
309
310}
311
312function createAgentEventsController(workspace, logger){
313 var loader = new SlJsInfra.ConfigLoader();
314 var agentConfig = loader.loadAgentConfiguration({});
315 return new SlJsInfra.AgentEventsController(agentConfig, AgentType.BUILD_SCANNER, Technology.NODEJS, false, logger);
316}
317
318module.exports.CIA = CIA;