1 | var 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 |
|
19 | function 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 |
|
29 | function 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 |
|
41 | CIA.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 |
|
67 | CIA.prototype._hasInvalidOrMissingArguments = function(buildArguments){
|
68 | return this._hasMissingArguments || this._hadInvalidArguments || buildArguments.help;
|
69 | }
|
70 |
|
71 | CIA.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 |
|
110 | CIA.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 |
|
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 |
|
133 | CIA.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 |
|
174 | CIA.prototype._createShortHands = function(){
|
175 | return {
|
176 | "h": ["--help"],
|
177 | "d": ["--dependency"],
|
178 | "r": ["--recursive"]
|
179 | };
|
180 | }
|
181 |
|
182 | CIA.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 |
|
221 | CIA.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 |
|
232 | CIA.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 |
|
243 | CIA.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 |
|
255 | function shutdownAgent(logger, cfg, statusCode, agentEventsController, callback) {
|
256 | sendLastLogs(cfg, logger, function () {
|
257 | agentEventsController.submitAgentShutdownEvent().then(function () {
|
258 | callback(statusCode);
|
259 | })
|
260 | });}
|
261 |
|
262 |
|
263 | CIA.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 |
|
275 | CIA.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) {
|
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 |
|
296 | function 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 |
|
313 | function 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 |
|
319 | function 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 |
|
329 | module.exports.CIA = CIA;
|