UNPKG

14 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 packageJsonFile = getPackageJsonFile(buildArguments.workspacepath, logger)
59 var agentEventsController = createAgentEventsController(buildArguments.workspacepath, logger);
60 agentEventsController.submitAgentStartedEvent(packageJsonFile).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 "labid": String
171 };
172}
173
174CIA.prototype._createShortHands = function(){
175 return {
176 "h": ["--help"],
177 "d": ["--dependency"],
178 "r": ["--recursive"]
179 };
180}
181
182CIA.prototype._createDescription = function(){
183 return {
184 "workspacepath": " Path to the workspace where the source code exists",
185 "outputpath": " Path where the compiled output exists",
186 "instrumentForBrowsers": " Instrument javascript files for browser coverage",
187 "instrumentationOnly": " Skip sending mapping to the server. Used when instrumenting files on multiple steps",
188 "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').",
189 "copyAllFilesToOutput": " Copy all files from the workspacepath to the outputpath. On by default.",
190 "branch": " Branch name",
191 "build": " Build version",
192 "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.",
193 "appname": " Application Name",
194 "scm": " The Source Control Management used (currently only 'git' is supported)",
195 "help": " Show this help page",
196 "dependency": " Project dependencies. Pattern should be a semicolon-separated AppName@Branch@Build list. May be specified more than once.",
197 "dependenciesFile": " A path to a json file that is in the following format: [{\"appName\":\"\",\"branch\":\"\",\"build\":\"\"},{...}]",
198 "recursive": " Starts a recursive search for in a folder specified by the 'workspacepath' option",
199 "author": " Optional. The name or email of the user that should be associated with this build. May be specified more than once.",
200 "logsUrl": " Optional. The logs url of the build.",
201 "jobName": " Optional. The jenkins job name that triggered the build.",
202 "usebranchcoverage": " Optional. Include branches in scan",
203 "usebuildmappingv3": " Optional. Use V3 of build mapping",
204 "token": " Agent token used to authenticate requests by the server",
205 "buildsessionid": " Build Session ID to associate with this build",
206 "includedFiles": " Files glob pattern to include, relative to the workspacepath. Defaults to **/*.js. Comma-separated values are accepted.",
207 "excludedFiles": " Files glob pattern to exclude, relative to the workspacepath. Defaults to node_modules/**/*.js, test/**.js. Comma-separated values are accepted.",
208 "failBuild": " Makes the build scanner exit with non-zero code on success. Off by default",
209 "es6Modules": " Use ES6 'module' mode when scanning files.",
210 "useBabylon":"Use babylon for scanning files",
211 "babylonPlugins": "List of non default babylon parser plugins, separated by comma",
212 "sendContributors": "Send contributor details for advanced committer reports and features",
213 "delayShutdownInSeconds":"Shutdown time in seconds for the browser agent. Default is 30 seconds.",
214 "scmProvider": "The provider name of your Source Control Management (SCM) tool. Supported values are 'Github', 'Bitbucket' and 'Gitlab'. If not used, 'Github' is assumed.",
215 "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.",
216 "scmBaseUrl": "The URL to the repository which contains the code. If left blank, the url of the remote GIT origin is being used.",
217 "uniqueModuleId":"Unique module id, case-sensitive. This value should remain consistent between runs."
218 };
219}
220
221CIA.prototype._createDefaults = function(){
222 var defaultValuesResolver = new DefaultValuesResolver();
223 return {
224 "help": "",
225 "includedFiles": defaultValuesResolver.getIncludedFiles(),
226 "excludedFiles": defaultValuesResolver.getExcludedFiles(),
227 "prefixesOfExcludedFiles": process.env["SL_prefixesOfExcludedFiles"] ? process.env["SL_prefixesOfExcludedFiles"] : "~, :, webpack, node_modules",
228 "delayShutdownInSeconds":30
229 };
230}
231
232CIA.prototype.handleInvalidArgs = function(callback) {
233 try {
234 var usage = noptUsage(this.knownOpts, this.shortHands, this.description, this.defaults);
235 console.error(usage);
236 } catch (e) {
237 globalErrorHandler.setLastError(e);
238 }
239 return callback(1);
240}
241
242
243CIA.prototype.loadConfiguration = function(buildArguments, callback) {
244 cfgLoader(buildArguments.config).then(function (cfg) {
245 cfg.appName = buildArguments.appname;
246 if (buildArguments.token) {
247 cfg.token = buildArguments.token;
248 }
249 callback(null, cfg);
250 }).catch(function (err) {
251 callback(err)
252 });
253}
254
255function shutdownAgent(logger, cfg, statusCode, agentEventsController, callback) {
256 sendLastLogs(cfg, logger, function () {
257 agentEventsController.submitAgentShutdownEvent().then(function () {
258 callback(statusCode);
259 })
260 });}
261
262
263CIA.prototype.runBuildDiffProcess = function(cfg, buildMappingProxy, parsedDependencies, buildArguments, logger, agentEventsController, callback) {
264 var scmProviders = this.getSourceConrolProviders();
265 var buildDiffProcess = new BuildDiffProcess(cfg, buildMappingProxy, scmProviders, parsedDependencies, agentEventsController, logger);
266 buildDiffProcess.run(buildArguments).then(function () {
267 shutdownAgent(logger, cfg, 0, agentEventsController, callback);
268 }).catch(function (err) {
269 logger.error(err);
270 var statusCode = buildArguments.failBuild ? 1 : 0;
271 shutdownAgent(logger, cfg, statusCode, agentEventsController, callback);
272 });
273}
274
275CIA.prototype.getBuildArguments = function() {
276 nopt.typeDefs.git = noptTypes.git;
277 nopt.invalidHandler = function (key, val, types) {
278 context._hasMissingArguments = true;
279 console.error('Invalid value specified for ' + key + ': ' + val);
280 };
281 var buildArguments = nopt(this.knownOpts, this.shortHands, process.argv, 2);
282 for (var i in this.defaults) { //Add default values
283 if (!buildArguments[i]) {
284 buildArguments[i] = this.defaults[i];
285 }
286 }
287 buildArguments.includedFiles = expandCommaSeparatedValues(buildArguments.includedFiles);
288 buildArguments.excludedFiles = expandCommaSeparatedValues(buildArguments.excludedFiles);
289 buildArguments.babylonPlugins = expandCommaSeparatedValues(buildArguments.babylonPlugins);
290 return buildArguments;
291}
292
293
294
295
296function printConfiguration(cfg, logger)
297{
298 logger.info("***************************************************************");
299 logger.info("Current Configuration: ");
300 logger.info("***************************************************************");
301
302 for(p in cfg)
303 {
304 if (typeof cfg[p] != "function"){
305 logger.info(p + ":", cfg[p]);
306 }
307 }
308
309 logger.info("***************************************************************");
310
311}
312
313function createAgentEventsController(workspace, logger){
314 var loader = new SlJsInfra.ConfigLoader();
315 var agentConfig = loader.loadAgentConfiguration({});
316 return new SlJsInfra.AgentEventsController(agentConfig, AgentType.BUILD_SCANNER, Technology.NODEJS, false, logger);
317}
318
319function getPackageJsonFile(path, logger) {
320 try {
321 var packageFilePath = SlJsInfra.Utils.FileUtils.findFileUp("package.json", path);
322 return require(packageFilePath);
323 } catch (e) {
324 logger.info("Could not find package.json file in path '%s'");
325 return null
326 }
327}
328
329module.exports.CIA = CIA;