UNPKG

23.5 kBJavaScriptView Raw
1// Make this builder instance globally available.
2global.__builder = exports;
3
4var gulp = require('gulp');
5var gutil = require('gulp-util');
6var browserify = require('browserify');
7var _string = require('underscore.string');
8var fs = require('fs');
9var cwd = process.cwd();
10var args = require('./internal/args');
11var logger = require('./internal/logger');
12var notifier = require('./internal/notifier');
13var paths = require('./internal/paths');
14var dependencies = require('./internal/dependecies');
15var tests = require('./internal/tests');
16var maven = require('./internal/maven');
17var bundlegen = require('./internal/bundlegen');
18var ModuleSpec = require('@jenkins-cd/js-modules/js/ModuleSpec');
19var skipBundle = skipBundle();
20var skipTest = (args.isArgvSpecified('--skipTest') || args.isArgvSpecified('--skipTests'));
21var packageJson = require(cwd + '/package.json');
22
23var bundles = []; // see exports.bundle function
24var bundleDependencyTaskNames = ['log-env'];
25
26var rebundleRunning = false;
27var retestRunning = false;
28
29logger.logInfo('**********************************************************************');
30logger.logInfo('This build is using Jenkins JS Builder.');
31logger.logInfo(' For command line options and other help, go to');
32logger.logInfo(' https://www.npmjs.com/package/@jenkins-cd/js-builder');
33logger.logInfo('**********************************************************************');
34
35if (maven.isMavenProject) {
36 logger.logInfo("Maven project.");
37 if (maven.isHPI()) {
38 logger.logInfo("\t- Jenkins plugin (HPI): " + maven.getArtifactId());
39 }
40}
41
42exports.gulp = gulp;
43exports.browserify = browserify;
44
45// Expose the internal modules.
46exports.logger = logger;
47exports.paths = paths;
48exports.dependencies = dependencies;
49exports.maven = maven;
50exports.args = args;
51
52var langConfig = require('./internal/langConfig');
53var lintConfig = {
54 level: 'configured',
55 src: true,
56 tests: false
57};
58
59exports.isRebundle = function() {
60 return rebundleRunning;
61};
62
63exports.isRetest = function() {
64 return retestRunning;
65};
66
67exports.bundleCount = function() {
68 return bundles.length;
69};
70
71/**
72 * Set the JavaScript language configuration.
73 */
74exports.lang = function(config) {
75 if (typeof config === 'number') {
76 langConfig.ecmaVersion = parseInt(config);
77 } else if (typeof config === 'string') {
78 if (config === 'es5') {
79 langConfig.ecmaVersion = 5;
80 } else if (config === 'es6') {
81 langConfig.ecmaVersion = 6;
82 } else if (config === 'react') {
83 langConfig.ecmaVersion = 6;
84 }
85 } else {
86 if (config.ecmaVersion) {
87 langConfig.ecmaVersion = config.ecmaVersion;
88 }
89 }
90 return exports;
91};
92
93/**
94 * Set the lint config.
95 * @param config The lint config. A string of "none", "configured", or
96 * an .
97 */
98exports.lint = function(config) {
99 if (typeof config === 'string') {
100 if (config === 'none') {
101 lintConfig.level = 'none';
102 }
103 } else {
104 if (config.src) {
105 lintConfig.src = config.src;
106 }
107 if (config.tests) {
108 lintConfig.tests = config.tests;
109 }
110 }
111};
112
113exports.defineTasks = function(tasknames) {
114 if (!tasknames) {
115 tasknames = ['test'];
116 }
117
118 var envLogged = false;
119 gulp.task('log-env', function() {
120 if (envLogged === false) {
121 logger.logInfo("Source Dirs:");
122 logger.logInfo(" - src: " + paths.srcPaths);
123 logger.logInfo(" - test: " + paths.testSrcPath);
124 envLogged = true;
125 }
126 });
127
128 var defaults = [];
129
130 for (var i = 0; i < tasknames.length; i++) {
131 var taskname = tasknames[i];
132 var gulpTask = tasks[taskname];
133
134 if (!gulpTask) {
135 throw new Error("Unknown gulp task '" + taskname + "'.");
136 }
137
138 exports.defineTask(taskname, gulpTask);
139 if (taskname === 'lint' || taskname === 'test' || taskname === 'bundle') {
140 defaults.push(taskname);
141 }
142 }
143
144 if (defaults.length > 0) {
145 logger.logInfo('Defining default tasks...');
146 gulp.task('default', defaults);
147 }
148};
149
150exports.defineTask = function(taskname, gulpTask) {
151 if (taskname === 'test') {
152 if (!skipTest) {
153 // Want to make sure the 'bundle' task gets run with the 'test' task.
154 gulp.task('test', ['bundle'], gulpTask);
155 }
156 } else if (taskname === 'bundle') {
157 if (!skipBundle) {
158 // Define the bundle task so that it depends on the "sub" bundle tasks.
159 gulp.task('bundle', bundleDependencyTaskNames, gulpTask);
160 }
161 } else if (taskname === 'bundle:watch') {
162 // Run bundle at the start of bundle:watch
163 gulp.task('bundle:watch', ['bundle'], gulpTask);
164 } else if (taskname === 'test:watch') {
165 // Run test at the start of test:watch
166 gulp.task('test:watch', ['test'], gulpTask);
167 } else {
168 gulp.task(taskname, gulpTask);
169 }
170};
171
172exports.defineBundleTask = function(taskname, gulpTask) {
173 var bundleTaskName = taskname + '_bundle_' + bundleDependencyTaskNames.length;
174
175 bundleDependencyTaskNames.push(bundleTaskName);
176 exports.defineTask(bundleTaskName, gulpTask);
177 // Define the 'bundle' task again so it picks up the new dependency
178 exports.defineTask('bundle', tasks.bundle);
179};
180
181exports.src = function(newPaths) {
182 if (newPaths) {
183 paths.srcPaths = [];
184 if (typeof newPaths === 'string') {
185 paths.srcPaths.push(normalizePath(newPaths));
186 } else if (newPaths.constructor === Array) {
187 for (var i = 0; i < newPaths.length; i++) {
188 paths.srcPaths.push(normalizePath(newPaths[i]));
189 }
190 }
191 }
192 return paths.srcPaths;
193};
194
195exports.tests = function(newPath) {
196 if (newPath) {
197 paths.testSrcPath = normalizePath(newPath);
198 }
199 return paths.testSrcPath;
200};
201
202exports.startTestWebServer = tests.startTestWebServer;
203
204exports.onTaskStart = function(taskName, callback) {
205 gulp.on('task_start', function(event) {
206 if (event.task === taskName) {
207 callback();
208 }
209 });
210};
211
212exports.onTaskEnd = function(taskName, callback) {
213 gulp.on('task_stop', function(event) {
214 if (event.task === taskName) {
215 callback();
216 }
217 });
218};
219
220exports.import = function(module, to, config) {
221 var moduleMapping = toModuleMapping(module, to, config);
222 if (moduleMapping) {
223 bundlegen.addGlobalImportMapping(moduleMapping);
224 }
225 return exports;
226};
227
228exports.export = function(module) {
229 bundlegen.addGlobalExportMapping(module);
230 return exports;
231};
232
233exports.withExternalModuleMapping = function(from, to, config) {
234 logger.logWarn('DEPRECATED use of builder.withExternalModuleMapping function. Change to builder.import.');
235 return exports.import(from, to, config);
236};
237
238/**
239 * Add a listener to be called just before Browserify starts bundling.
240 * <p>
241 * The listener is called with the {@code bundle} as {@code this} and
242 * the {@code bundler} as as the only arg.
243 *
244 * @param listener The listener to add.
245 */
246exports.onPreBundle = function(listener) {
247 bundlegen.onPreBundle(listener);
248 return exports;
249};
250
251/**
252 * Add a listener to be called just after Browserify finishes bundling.
253 * <p>
254 * The listener is called with the {@code bundle} as {@code this} and
255 * the location of the generated bundle as as the only arg.
256 *
257 * @param listener The listener to add.
258 */
259exports.onPostBundle = function(listener) {
260 bundlegen.onPostBundle(listener);
261 return exports;
262};
263
264function normalizePath(path) {
265 path = _string.ltrim(path, './');
266 path = _string.ltrim(path, '/');
267 path = _string.rtrim(path, '/');
268
269 return path;
270}
271
272exports.bundle = function(resource, as) {
273 if (_string.endsWith(resource, '.css')) {
274 return bundleCss(resource, 'css', as);
275 } if (_string.endsWith(resource, '.less')) {
276 return bundleCss(resource, 'less', as);
277 } else {
278 return bundleJs(resource, as);
279 }
280};
281
282function bundleJs(moduleToBundle, as) {
283 if (!moduleToBundle) {
284 gutil.log(gutil.colors.red("Error: Invalid bundle registration for module 'moduleToBundle' must be specify."));
285 throw new Error("'bundle' registration failed. See error above.");
286 }
287
288 var bundle = {};
289
290 bundle.js = _string.strRightBack(moduleToBundle, '/'); // The short name of the javascript file (with extension but without path)
291 bundle.module = _string.strLeftBack(bundle.js, '.js'); // The short name with the .js extension removed
292 bundle.bundleDependencyModule = (moduleToBundle === bundle.module); // The specified module to bundle is the name of a module dependency.
293
294 if (!as) {
295 bundle.as = bundle.module;
296 } else {
297 bundle.as = _string.strLeftBack(as, '.js');
298 }
299
300 bundle.asModuleSpec = new ModuleSpec(bundle.as);
301 var loadBundleFileNamePrefix = bundle.asModuleSpec.getLoadBundleFileNamePrefix();
302 if (loadBundleFileNamePrefix) {
303 bundle.as = loadBundleFileNamePrefix;
304 }
305 bundle.bundleExportNamespace = bundle.asModuleSpec.namespace;
306
307 function assertBundleOutputUndefined() {
308 if (bundle.bundleInDir) {
309 gutil.log(gutil.colors.red("Error: Invalid bundle registration. Bundle output (inDir) already defined."));
310 throw new Error("'bundle' registration failed. See error above.");
311 }
312 }
313
314 bundle.bundleModule = moduleToBundle;
315 bundle.bundleOutputFile = bundle.as + '.js';
316 bundle.moduleMappings = [];
317 bundle.moduleExports = [];
318 bundle.exportEmptyModule = true;
319 bundle.useGlobalImportMappings = true;
320 bundle.useGlobalExportMappings = true;
321 bundle.minifyBundle = args.isArgvSpecified('--minify');
322 bundle.generateNoImportsBundle = function() {
323 if (skipBundle) {
324 return bundle;
325 }
326 // Create a self contained version of the bundle (no imports) - useful for
327 // testing and probably more.
328 defineJSBundleTask(bundle, false);
329 return bundle;
330 };
331 bundle.minify = function() {
332 if (skipBundle) {
333 return bundle;
334 }
335 bundle.minifyBundle = true;
336 return bundle;
337 };
338 bundle.inDir = function(dir) {
339 if (skipBundle) {
340 return bundle;
341 }
342 if (!dir) {
343 gutil.log(gutil.colors.red("Error: Invalid bundle registration for module '" + moduleToBundle + "'. You can't specify a 'null' dir name when calling inDir."));
344 throw new Error("'bundle' registration failed. See error above.");
345 }
346 assertBundleOutputUndefined();
347 bundle.bundleInDir = normalizePath(dir);
348 if (bundle.isDuplicate === false) {
349 gutil.log(gutil.colors.green("Bundle of '" + moduleToBundle + "' will be generated in directory '" + bundle.bundleInDir + "' as '" + bundle.as + ".js'."));
350 }
351 return bundle;
352 };
353 bundle.withTransforms = function(transforms) {
354 if (skipBundle) {
355 return bundle;
356 }
357 bundle.bundleTransforms = transforms;
358 return bundle;
359 };
360
361 bundle.ignoreGlobalImportMappings = function() {
362 bundle.useGlobalImportMappings = false;
363 return bundle;
364 };
365
366 bundle.ignoreGlobalModuleMappings = function() {
367 logger.logWarn('DEPRECATED use of bundle.ignoreGlobalModuleMappings function. Change to bundle.ignoreGlobalImportMappings.');
368 return bundle.ignoreGlobalImportMappings();
369 };
370
371 bundle.ignoreGlobalExportMappings = function() {
372 bundle.useGlobalExportMappings = false;
373 return bundle;
374 };
375
376 bundle.noEmptyModuleExport = function() {
377 bundle.exportEmptyModule = false;
378 return bundle;
379 };
380
381 bundle._import = function(moduleMapping) {
382 if (skipBundle) {
383 return bundle;
384 }
385
386 var toSpec = new ModuleSpec(moduleMapping.to);
387 if (toSpec.importAs() === bundle.importAs()) {
388 // Do not add mappings to itself.
389 return bundle;
390 }
391
392 for (var i in bundle.moduleMappings) {
393 if (bundle.moduleMappings[i].from === moduleMapping.from) {
394 logger.logWarn('Ignoring require mapping of "' + moduleMapping.from + '" to "' + moduleMapping.to + '". The bundle already has a mapping for "' + moduleMapping.from + '".');
395 return bundle;
396 }
397 }
398
399 if (moduleMapping.fromSpec) {
400 bundle.moduleMappings.push(moduleMapping);
401 } else {
402 bundle.moduleMappings.push(toModuleMapping(moduleMapping.from, moduleMapping.to, moduleMapping.config));
403 }
404 return bundle;
405 };
406
407 bundle.import = function(module, to, config) {
408 var moduleMapping = toModuleMapping(module, to, config);
409 bundle._import(moduleMapping);
410 return bundle;
411 };
412
413 bundle.withExternalModuleMapping = function(from, to, config) {
414 logger.logWarn('DEPRECATED use of bundle.withExternalModuleMapping function. Change to bundle.import.');
415 return bundle.import(from, to, config);
416 };
417
418 bundle.less = function(src, targetDir) {
419 if (skipBundle) {
420 return bundle;
421 }
422 bundle.lessSrcPath = src;
423 if (targetDir) {
424 bundle.lessTargetDir = targetDir;
425 }
426 return bundle;
427 };
428 bundle.namespace = function(toNamespace) {
429 bundle.bundleExportNamespace = toNamespace;
430 return bundle;
431 };
432 bundle.export = function(moduleName) {
433 if (skipBundle) {
434 return bundle;
435 }
436 dependencies.assertHasJenkinsJsModulesDependency('Cannot bundle "export".');
437
438 if (moduleName) {
439 if (moduleName === packageJson.name) {
440 // We are exporting the top/entry level module of the generated bundle.
441 // This is the "traditional" export use case.
442 bundle.bundleExport = true;
443 bundle.bundleExportNamespace = packageJson.name;
444 } else if (dependencies.getDependency(moduleName) !== undefined) {
445 // We are exporting some dependency of this module Vs exporting
446 // the top/entry level module of the generated bundle. This allows the bundle
447 // to control loading of a specific dependency (or set of) and then share that with
448 // other bundles, which is needed where we have "singleton" type modules
449 // e.g. react and react-dom.
450 bundle.moduleExports.push(moduleName);
451 } else {
452 logger.logError("Error: Cannot export module '" + moduleName + "' - not the name of this module or one of it's dependencies.");
453 logger.logError(" (if '" + moduleName + "' is the namespace you want to export to, use the 'bundle.namespace' function)");
454 }
455 } else {
456 if (bundle.bundleExportNamespace) {
457 bundle.bundleExport = true;
458 } else if (maven.isMavenProject) {
459 bundle.bundleExport = true;
460 // Use the maven artifactId as the namespace.
461 bundle.bundleExportNamespace = maven.getArtifactId();
462 if (!maven.isHPI()) {
463 logger.logWarn("\t- Bundling process will use the maven pom artifactId ('" + bundle.bundleExportNamespace + "') as the bundle export namespace. You can specify a namespace as a parameter to the 'export' method call.");
464 }
465 } else {
466 logger.logError("Error: This is not a maven project. You must define a namespace via the 'namespace' function on the bundle.");
467 return bundle;
468 }
469 logger.logInfo("\t- Bundle will be exported as '" + bundle.bundleExportNamespace + ":" + bundle.as + "'.");
470 }
471 return bundle;
472 };
473
474 bundle.importAs = function() {
475 if (bundle.bundleExportNamespace && bundle.bundleExportNamespace !== bundle.asModuleSpec.namespace) {
476 var modifiedSpec = new ModuleSpec(bundle.bundleExportNamespace + ':' + bundle.asModuleSpec.getLoadBundleName());
477 return modifiedSpec.importAs();
478 } else {
479 return bundle.asModuleSpec.importAs();
480 }
481 };
482
483 bundle.findModuleMapping = function(from) {
484 var moduleMappings = bundle.moduleMappings;
485 for (var i = 0; i < moduleMappings.length; i++) {
486 var mapping = moduleMappings[i];
487 if (from === mapping.from) {
488 return mapping;
489 }
490 }
491 return undefined;
492 };
493
494 if (skipBundle) {
495 return bundle;
496 }
497
498 bundle.isDuplicate = false;
499 for (var i = 0; i < bundles.length; i++) {
500 if (bundles[i].bundleModule === bundle.bundleModule && bundles[i].as === bundle.as) {
501 bundle.isDuplicate = true;
502 break;
503 }
504 }
505
506 if (bundle.isDuplicate === true) {
507 // Just return the bundle object, but do not register a task
508 // for creating it.
509 return bundle;
510 }
511
512 bundles.push(bundle);
513
514 function defineJSBundleTask(bundle, applyImports) {
515 var bundleTaskName = 'js_bundle_' + bundle.as;
516
517 if (!applyImports) {
518 bundleTaskName += '_no_imports';
519 }
520
521 exports.defineBundleTask(bundleTaskName, function() {
522 return bundlegen.doJSBundle(bundle, applyImports);
523 });
524 }
525
526 // Create a bundle with imports applied/transformed.
527 defineJSBundleTask(bundle, true);
528
529 return bundle;
530}
531
532function bundleCss(resource, format) {
533 var bundle = {
534 format: format
535 };
536
537 var folder = paths.parentDir(resource);
538
539 bundle.fileExtension = '.' + format;
540 bundle.shortName = _string.strRightBack(resource, '/');
541 bundle.as = _string.strLeftBack(bundle.shortName, bundle.fileExtension);
542 bundle.bundleExportNamespace = _string.strRightBack(folder, '/');
543
544 bundle.inDir = function(dir) {
545 if (skipBundle) {
546 return bundle;
547 }
548 if (!dir) {
549 logger.logError("Error: Invalid bundle registration for CSS resource '" + resource + "'. You can't specify a 'null' dir name when calling inDir.");
550 throw new Error("'bundle' registration failed. See error above.");
551 }
552 bundle.bundleInDir = normalizePath(dir);
553 return bundle;
554 };
555
556 var bundleTaskName = format + '_bundle_' + bundle.as;
557 exports.defineBundleTask(bundleTaskName, function() {
558 return bundlegen.doCSSBundle(bundle, resource);
559 });
560
561 return bundle;
562}
563
564function toModuleMapping(from, to, config) {
565 dependencies.assertHasJenkinsJsModulesDependency('Cannot process bundle "import".');
566
567 // 'to' is optional, so maybe the second arg is a
568 // config object.
569 if (to && typeof to === 'object') {
570 config = to;
571 to = undefined;
572 }
573
574 if (config === undefined) {
575 config = {};
576 } else if (typeof config === 'string') {
577 // config is the require mapping override (backward compatibility).
578 config = {
579 require: config
580 };
581 } else {
582 // Clone the config object because we're going to be
583 // making changes to it.
584 config = JSON.parse(JSON.stringify(config));
585 }
586
587 if (!from) {
588 var message = "Cannot call 'import' without defining the 'from' module name.";
589 logger.logError(message);
590 throw new Error(message);
591 }
592
593 var fromSpec = new ModuleSpec(from);
594
595 if (!to) {
596 var adjExt = require('./internal/adjunctexternal');
597 to = adjExt.bundleFor(exports, from);
598 // If still nothing, use a qualified version of the from
599 if (!to) {
600 to = fromSpec.importAs();
601 }
602 }
603
604 // special case because we are externalizing handlebars runtime for handlebarsify.
605 if (from === 'handlebars' && to === 'handlebars:handlebars3' && !config.require) {
606 config.require = 'jenkins-handlebars-rt/runtimes/handlebars3_rt';
607 }
608
609 return {
610 from: from,
611 fromSpec: fromSpec,
612 to: to,
613 config: config
614 };
615}
616
617function buildSrcWatchList(includeTestSrc) {
618 var watchList = [];
619
620 watchList.push('.watch_trigger');
621 watchList.push('./index.js');
622 for (var i = 0; i < paths.srcPaths.length; i++) {
623 var srcPath = paths.srcPaths[i];
624 watchList.push(srcPath + '/*.*');
625 watchList.push(srcPath + '/**/*.*');
626 }
627
628 if (includeTestSrc && includeTestSrc === true) {
629 watchList.push(paths.testSrcPath + '/**/*.*');
630 }
631
632 return watchList;
633}
634
635function rebundleLogging() {
636 if (rebundleRunning === true) {
637 logger.logInfo('*********************************************');
638 logger.logInfo('bundle:watch: watching for source changes again ...');
639 }
640}
641
642var tasks = {
643 test: tests.getTestTask(),
644 bundle: function() {
645 if (bundles.length === 0) {
646 logger.logWarn("Warning: Skipping 'bundle' task. No 'module' bundles are registered. Call require('jenkins-js-build').bundle([module]) in gulpfile.js.");
647 }
648 logger.logInfo('bundling: done');
649 rebundleLogging();
650 },
651 'bundle:watch': function() {
652 var watchList = buildSrcWatchList(false);
653 logger.logInfo('bundle:watch watch list: ' + watchList);
654 rebundleRunning = true;
655 gulp.watch(watchList, ['bundle']);
656 rebundleLogging();
657 },
658 'test:watch': function() {
659 var watchList = buildSrcWatchList(true);
660 logger.logInfo('test:watch watch list: ' + watchList);
661 retestRunning = true;
662 gulp.watch(watchList, ['test']);
663 },
664
665 lint: function() {
666 require('./internal/lint').exec(langConfig, lintConfig);
667 }
668};
669
670gulp.task('lint:watch', function () {
671 var watchList = buildSrcWatchList(true);
672 gulp.watch(watchList, ['lint']);
673});
674
675function skipBundle() {
676 // Can't skip bundle if there are handlebars file and a dependency on hbsify
677 if (dependencies.getDependency('hbsfy') && paths.hasSourceFiles('hbs')) {
678 return false;
679 }
680
681 return args.isArgvSpecified('--skipBundle');
682}
683
684if (args.isArgvSpecified('--h') || args.isArgvSpecified('--help')) {
685 skipBundle = true;
686 gulp.task('default', function() {});
687} else {
688 // Defined default tasks. Can be overridden.
689 var defaultTasks = [];
690 if (!args.isArgvSpecified('--skipLint')) {
691 defaultTasks.push('lint');
692 } else {
693 gulp.task('lint', function() {
694 logger.logInfo(' - lint skipped (--skipLint)');
695 });
696 }
697 if (!skipTest) {
698 defaultTasks.push('test');
699 } else {
700 gulp.task('test', function() {
701 logger.logInfo(' - tests skipped (--skipTests)');
702 });
703 }
704 if (!skipBundle) {
705 defaultTasks.push('bundle');
706 } else {
707 gulp.task('bundle', function() {
708 logger.logInfo(' - bundle skipped (--skipBundle)');
709 });
710 }
711 defaultTasks.push('bundle:watch');
712 defaultTasks.push('test:watch');
713 exports.defineTasks(defaultTasks);
714
715 dependencies.processExternalizedDependencies(this);
716
717 // Install plugins.
718 require('./internal/plugins').install(exports);
719}
720
721// Bundle registrations can also be made in
722// the package.json.
723bundlegen.registerPackageJsonBundles(exports);
\No newline at end of file