minor upgrade adjustments for escaping and skipping (#4841)
[openemr.git] / gulpfile.js
blobbc30e9101f144816fb335f25dd9e564b025d8468
1 "use strict";
3 // modules
4 const csso = require('gulp-csso');
5 const del = require('del');
6 const fs = require('fs');
7 const glob = require('glob');
8 const gap = require('gulp-append-prepend');
9 const replace = require('replace-in-file');
10 const gulp = require('gulp');
11 const argv = require('minimist')(process.argv.slice(2));
12 const gulpif = require('gulp-if');
13 const prefix = require('autoprefixer');
14 const postcss = require('gulp-postcss');
15 const rename = require('gulp-rename');
16 const sass = require('gulp-dart-sass');
17 const sourcemaps = require('gulp-sourcemaps');
18 const gulp_watch = require('gulp-watch');
19 const injector = require('gulp-inject-string');
20 const colors = require('colors');
22 // package.json
23 const packages = require('./package.json');
25 const logprefix = "[OpenEMR]".bold.cyan + " ";
27 // configuration
28 let config = {
29     all: [], // must always be empty
31     // Command Line Arguments
32     dev: argv['dev'],
33     build: argv['b'],
34     install: argv['i'],
36     // Source file locations
37     src: {
38         styles: {
39             style_portal: 'interface/themes/patientportal-*.scss',
40             style_tabs: 'interface/themes/tabs_style_*.scss',
41             style_uni: 'interface/themes/oe-styles/style_*.scss',
42             style_color: 'interface/themes/colors/*.scss',
43             directional: 'interface/themes/directional.scss',
44             misc: 'interface/themes/misc/**/*.scss'
45         }
46     },
47     dist: {
48         assets: 'public/assets/'
49     },
50     dest: {
51         themes: 'public/themes',
52         misc_themes: 'public/themes/misc'
53     }
56 if (config.install) {
57     console.log("\nCopying OpenEMR dependencies using Gulp".bold.yellow + "\n");
58 } else if (config.build) {
59     console.log("\nBuilding OpenEMR themes using Gulp".bold.yellow + "\n");
60 } else if (config.dev) {
61     console.log("\nBuilding OpenEMR themes using Dev Flag for Gulp".bold.yellow + "\n");
62 } else if (config.all) {
63     console.log("\nBuilding OpenEMR themes using All Flag for Gulp".bold.yellow + "\n");
64 } else {
65     // This is used for gulp watch & other misc things
66     console.log("\nRunning Gulp for OpenEMR".bold.yellow + "\n");
69 function log_error(isSuccess, err) {
70     isSuccess = false;
71     console.error(logprefix + "An error occured! Check the log for details.");
72     // Log error to console
73     console.error(err.toString().red);
74     // Kills gulp on error since if we keep running it will
75     // still fail
76     process.exit(1);
79 // Clean up lingering static themes
80 function clean(done) {
81     del.sync([config.dest.themes + "/*"]);
82     done();
85 // Parses command line arguments
86 function ingest(done) {
87     if (config.dev && typeof config.dev !== "boolean") {
88         config.dev = true;
89     }
90     done();
93 // definition of header for all compiled css
94 const autoGeneratedHeader = `
95 /*! This style sheet was autogenerated using gulp + scss
96  *  For usage instructions, see: https://github.com/openemr/openemr/blob/master/interface/README.md
97  */
100 // standard themes css compilation
101 function styles_style_portal() {
102     let isSuccess = true;
103     return gulp.src(config.src.styles.style_portal)
104         .pipe(injector.replace('// bs4import', '@import "../../public/assets/bootstrap/scss/bootstrap";'))
105         .pipe(sourcemaps.init())
106         .pipe(sass().on('error', (err) => {
107             log_error(isSuccess, err);
108         }))
109         .pipe(postcss([prefix()]))
110         .pipe(gap.prependText(autoGeneratedHeader))
111         .pipe(gulpif(!config.dev, csso()))
112         .pipe(gulpif(!config.dev, sourcemaps.write()))
113         .on('error', (err) => {
114             log_error(isSuccess, err);
115         })
116         .pipe(gulp.dest(config.dest.themes))
117         .on('end', () => {
118             if (isSuccess) {
119                 console.log(logprefix + "Finished compiling OpenEMR portal styles");
120             }
121         });
123 // standard themes css compilation
124 function styles_style_uni() {
125     let isSuccess = true;
126     return gulp.src(config.src.styles.style_uni)
127         .pipe(gap.prependText('$compact-theme: false;\n'))
128         .pipe(injector.replace('// bs4import', '@import "../../../public/assets/bootstrap/scss/bootstrap";'))
129         .pipe(sourcemaps.init())
130         .pipe(sass().on('error', (err) => {
131             log_error(isSuccess, err);
132         }))
133         .pipe(postcss([prefix()]))
134         .pipe(gap.prependText(autoGeneratedHeader))
135         .pipe(gulpif(!config.dev, csso()))
136         .pipe(gulpif(!config.dev, sourcemaps.write()))
137         .on('error', (err) => {
138             log_error(isSuccess, err);
139         })
140         .pipe(gulp.dest(config.dest.themes))
141         .on('end', () => {
142             if (isSuccess) {
143                 console.log(logprefix + "Finished compiling OpenEMR base themes");
144             }
145         });
148 // standard themes compact css compilation
149 function styles_style_uni_compact() {
150     let isSuccess = true;
151     return gulp.src(config.src.styles.style_uni)
152         .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
153         .pipe(injector.replace('// bs4import', '@import "../oemr_compact_imports";'))
154         .pipe(sourcemaps.init())
155         .pipe(sass().on('error', (err) => {
156             log_error(isSuccess, err);
157         }))
158         .pipe(postcss([prefix()]))
159         .pipe(gap.prependText(autoGeneratedHeader))
160         .pipe(gulpif(!config.dev, csso()))
161         .pipe(gulpif(!config.dev, sourcemaps.write()))
162         .pipe(rename({
163             prefix: "compact_"
164         }))
165         .on('error', (err) => {
166             log_error(isSuccess, err);
167         })
168         .pipe(gulp.dest(config.dest.themes))
169         .on('end', () => {
170             if (isSuccess) {
171                 console.log(logprefix + "Finished compiling OpenEMR compact base themes");
172             }
173         });
176 // color themes css compilation
177 function styles_style_color() {
178     let isSuccess = true;
179     return gulp.src(config.src.styles.style_color)
180         .pipe(gap.prependText('$compact-theme: false;\n'))
181         .pipe(injector.replace('// bs4import', '@import "../../../public/assets/bootstrap/scss/bootstrap";'))
182         .pipe(sourcemaps.init())
183         .pipe(sass().on('error', (err) => {
184             log_error(isSuccess, err);
185         }))
186         .pipe(postcss([prefix()]))
187         .pipe(gap.prependText(autoGeneratedHeader))
188         .pipe(gulpif(!config.dev, csso()))
189         .pipe(gulpif(!config.dev, sourcemaps.write()))
190         .on('error', (err) => {
191             log_error(isSuccess, err);
192         })
193         .pipe(gulp.dest(config.dest.themes))
194         .on('end', () => {
195             if (isSuccess) {
196                 console.log(logprefix + "Finished compiling OpenEMR color themes");
197             }
198         });
201 // color themes compact css compilation
202 function styles_style_color_compact() {
203     let isSuccess = true;
204     return gulp.src(config.src.styles.style_color)
205         .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
206         .pipe(injector.replace('// bs4import', '@import "../oemr_compact_imports";'))
207         .pipe(sourcemaps.init())
208         .pipe(sass().on('error', (err) => {
209             log_error(isSuccess, err);
210         }))
211         .pipe(postcss([prefix()]))
212         .pipe(gap.prependText(autoGeneratedHeader))
213         .pipe(gulpif(!config.dev, csso()))
214         .pipe(gulpif(!config.dev, sourcemaps.write()))
215         .pipe(rename({
216             prefix: "compact_"
217         }))
218         .on('error', (err) => {
219             log_error(isSuccess, err);
220         })
221         .pipe(gulp.dest(config.dest.themes))
222         .on('end', () => {
223             if (isSuccess) {
224                 console.log(logprefix + "Finished compiling OpenEMR compact color themes");
225             }
226         });
229 // Tabs CSS compilation
230 function styles_style_tabs() {
231     let isSuccess = true;
232     return gulp.src(config.src.styles.style_tabs)
233         .pipe(sourcemaps.init())
234         .pipe(sass().on('error', (err) => {
235             log_error(isSuccess, err);
236         }))
237         .pipe(postcss([prefix()]))
238         .pipe(gap.prependText(autoGeneratedHeader))
239         .pipe(gulpif(!config.dev, csso()))
240         .pipe(gulpif(!config.dev, sourcemaps.write()))
241         .on('error', (err) => {
242             log_error(isSuccess, err);
243         })
244         .pipe(gulp.dest(config.dest.themes))
245         .on('end', () => {
246             if (isSuccess) {
247                 console.log(logprefix + "Finished compiling OpenEMR tab navigation styles");
248             }
249         });
252 // For anything else that needs to be moved, use misc themes
253 function styles_style_misc() {
254     let isSuccess = true;
255     return gulp.src(config.src.styles.misc)
256         .pipe(sourcemaps.init())
257         .pipe(sass().on('error', (err) => {
258             log_error(isSuccess, err);
259         }))
260         .pipe(postcss([prefix()]))
261         .pipe(gap.prependText(autoGeneratedHeader))
262         .pipe(gulpif(!config.dev, csso()))
263         .pipe(gulpif(!config.dev, sourcemaps.write()))
264         .on('error', (err) => {
265             log_error(isSuccess, err);
266         })
267         .pipe(gulp.dest(config.dest.misc_themes))
268         .on('end', () => {
269             if (isSuccess) {
270                 console.log(logprefix + "Finished compiling miscellaneous styles");
271             }
272         });
275 // rtl standard themes css compilation
276 function rtl_style_portal() {
277     let isSuccess = true;
278     return gulp.src(config.src.styles.style_portal)
279         .pipe(gap.prependText('$dir: rtl;\n@import "rtl";\n@import "directional";\n')) // watch out for this relative path!
280         .pipe(gap.appendText('@include if-rtl { @include rtl_style; @include portal_style; }\n'))
281         .pipe(injector.replace('// bs4import', '@import "oemr-rtl";'))
282         .pipe(sourcemaps.init())
283         .pipe(sass().on('error', (err) => {
284             log_error(isSuccess, err);
285         }))
286         .pipe(postcss([prefix()]))
287         .pipe(gap.prependText(autoGeneratedHeader))
288         .pipe(gulpif(!config.dev, csso()))
289         .pipe(gulpif(!config.dev, sourcemaps.write()))
290         .pipe(rename({
291             prefix: "rtl_"
292         }))
293         .on('error', (err) => {
294             log_error(isSuccess, err);
295         })
296         .pipe(gulp.dest(config.dest.themes))
297         .on('end', () => {
298             if (isSuccess) {
299                 console.log(logprefix + "Finished compiling portal styles");
300             }
301         });
304 // rtl standard themes css compilation
305 function rtl_style_uni() {
306     let isSuccess = true;
307     return gulp.src(config.src.styles.style_uni)
308         .pipe(gap.prependText('$compact-theme: false;\n$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
309         .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
310         .pipe(injector.replace('// bs4import', '@import "../oemr-rtl";'))
311         .pipe(sourcemaps.init())
312         .pipe(sass().on('error', (err) => {
313             log_error(isSuccess, err);
314         }))
315         .pipe(postcss([prefix()]))
316         .pipe(gap.prependText(autoGeneratedHeader))
317         .pipe(gulpif(!config.dev, csso()))
318         .pipe(gulpif(!config.dev, sourcemaps.write()))
319         .pipe(rename({
320             prefix: "rtl_"
321         }))
322         .on('error', (err) => {
323             log_error(isSuccess, err);
324         })
325         .pipe(gulp.dest(config.dest.themes))
326         .on('end', () => {
327             if (isSuccess) {
328                 console.log(logprefix + "Finished compiling RTL base themes");
329             }
330         });
333 // rtl standard themes compact css compilation
334 function rtl_style_uni_compact() {
335     let isSuccess = true;
336     return gulp.src(config.src.styles.style_uni)
337         .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
338         .pipe(gap.prependText('$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
339         .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
340         .pipe(injector.replace('// bs4import', '@import "../oemr_rtl_compact_imports";'))
341         .pipe(sourcemaps.init())
342         .pipe(sass().on('error', (err) => {
343             log_error(isSuccess, err);
344         }))
345         .pipe(postcss([prefix()]))
346         .pipe(gap.prependText(autoGeneratedHeader))
347         .pipe(gulpif(!config.dev, csso()))
348         .pipe(gulpif(!config.dev, sourcemaps.write()))
349         .pipe(rename({
350             prefix: "rtl_compact_"
351         }))
352         .on('error', (err) => {
353             log_error(isSuccess, err);
354         })
355         .pipe(gulp.dest(config.dest.themes))
356         .on('end', () => {
357             if (isSuccess) {
358                 console.log(logprefix + "Finished compiling RTL base compact themes");
359             }
360         });
363 // rtl color themes css compilation
364 function rtl_style_color() {
365     let isSuccess = true;
366     return gulp.src(config.src.styles.style_color)
367         .pipe(gap.prependText('$compact-theme: false;\n$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
368         .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
369         .pipe(injector.replace('// bs4import', '@import "../oemr-rtl";'))
370         .pipe(sourcemaps.init())
371         .pipe(sass().on('error', (err) => {
372             log_error(isSuccess, err);
373         }))
374         .pipe(postcss([prefix()]))
375         .pipe(gap.prependText(autoGeneratedHeader))
376         .pipe(gulpif(!config.dev, csso()))
377         .pipe(gulpif(!config.dev, sourcemaps.write()))
378         .pipe(rename({
379             prefix: "rtl_"
380         }))
381         .on('error', (err) => {
382             log_error(isSuccess, err);
383         })
384         .pipe(gulp.dest(config.dest.themes))
385         .on('end', () => {
386             if (isSuccess) {
387                 console.log(logprefix + "Compiled OpenEMR RTL color themes");
388             }
389         });
392 // rtl color themes compact css compilation
393 function rtl_style_color_compact() {
394     let isSuccess = true;
395     return gulp.src(config.src.styles.style_color)
396         .pipe(gap.prependText('@import "../compact-theme-defaults";\n'))
397         .pipe(gap.prependText('$dir: rtl;\n@import "../rtl";\n')) // watch out for this relative path!
398         .pipe(gap.appendText('@include if-rtl { @include rtl_style; #bigCal { border-right: 1px solid $black !important; } }\n'))
399         .pipe(injector.replace('// bs4import', '@import "../oemr_rtl_compact_imports";'))
400         .pipe(sourcemaps.init())
401         .pipe(sass().on('error', (err) => {
402             log_error(isSuccess, err);
403         }))
404         .pipe(postcss([prefix()]))
405         .pipe(gap.prependText(autoGeneratedHeader))
406         .pipe(gulpif(!config.dev, csso()))
407         .pipe(gulpif(!config.dev, sourcemaps.write()))
408         .pipe(rename({
409             prefix: "rtl_compact_"
410         }))
411         .on('error', (err) => {
412             log_error(isSuccess, err);
413         })
414         .pipe(gulp.dest(config.dest.themes))
415         .on('end', () => {
416             if (isSuccess) {
417                 console.log(logprefix + "Finished compiling RTL compact color themes");
418             }
419         });
422 // rtl standard themes css compilation
423 function rtl_style_tabs() {
424     let isSuccess = true;
425     return gulp.src(config.src.styles.style_tabs)
426         .pipe(gap.prependText('$dir: rtl;\n@import "rtl";\n')) // watch out for this relative path!
427         .pipe(sourcemaps.init())
428         .pipe(sass().on('error', (err) => {
429             log_error(isSuccess, err);
430         }))
431         .pipe(postcss([prefix()]))
432         .pipe(gap.prependText(autoGeneratedHeader))
433         .pipe(gulpif(!config.dev, csso()))
434         .pipe(gulpif(!config.dev, sourcemaps.write()))
435         .pipe(rename({
436             prefix: "rtl_"
437         }))
438         .on('error', (err) => {
439             log_error(isSuccess, err);
440         })
441         .pipe(gulp.dest(config.dest.themes))
442         .on('end', () => {
443             if (isSuccess) {
444                 console.log(logprefix + "Finished compiling RTL tabs styles");
445             }
446         });
449 // For anything else that needs to be moved, use misc themes
450 function rtl_style_misc() {
451     let isSuccess = true;
452     return gulp.src(config.src.styles.misc)
453         .pipe(gap.prependText('$dir: rtl;\n')) // Simply a flag here due to a hierarchy possibly being created
454         .pipe(sourcemaps.init())
455         .pipe(sass().on('error', (err) => {
456             log_error(isSuccess, err);
457         }))
458         .pipe(postcss([prefix()]))
459         .pipe(gap.prependText(autoGeneratedHeader))
460         .pipe(gulpif(!config.dev, csso()))
461         .pipe(gulpif(!config.dev, sourcemaps.write()))
462         .pipe(rename({
463             prefix: "rtl_"
464         }))
465         .on('error', (err) => {
466             log_error(isSuccess, err);
467         })
468         .pipe(gulp.dest(config.dest.misc_themes))
469         .on('end', () => {
470             if (isSuccess) {
471                 console.log(logprefix + "Compiled rest of RTL SCSS");
472             }
473         });
476 // compile themes
477 const styles = gulp.parallel(styles_style_color, styles_style_color_compact, styles_style_uni, styles_style_uni_compact, styles_style_portal, styles_style_tabs, styles_style_misc, rtl_style_color, rtl_style_color_compact, rtl_style_uni, rtl_style_uni_compact, rtl_style_portal, rtl_style_tabs, rtl_style_misc);
479 // Copies (and distills, if possible) assets from node_modules to public/assets
480 function install(done) {
481     console.log(logprefix + "Running OpenEMR gulp install task...");
482     // combine dependencies and napa sources into one object
483     const dependencies = packages.dependencies;
484     for (let key in packages.napa) {
485         if (packages.napa.hasOwnProperty(key)) {
486             dependencies[key] = packages.napa[key];
487         }
488     }
490     for (let key in dependencies) {
491         // check if the property/key is defined in the object itself, not in parent
492         if (dependencies.hasOwnProperty(key)) {
493             if (key == 'dwv') {
494                 // dwv is special and need to copy dist, decoders and locales
495                 gulp.src('node_modules/' + key + '/dist/**/*')
496                     .pipe(gulp.dest(config.dist.assets + key + '/dist'));
497                 gulp.src('node_modules/' + key + '/decoders/**/*')
498                     .pipe(gulp.dest(config.dist.assets + key + '/decoders'));
499                 gulp.src('node_modules/' + key + '/locales/**/*')
500                     .pipe(gulp.dest(config.dist.assets + key + '/locales'));
501             } else if (key == 'bootstrap' || key == 'bootstrap-rtl') {
502                 // bootstrap and bootstrap-v4-rtl are special and need to copy dist and scss
503                 gulp.src('node_modules/' + key + '/dist/**/*')
504                     .pipe(gulp.dest(config.dist.assets + key + '/dist'));
505                 gulp.src('node_modules/' + key + '/scss/**/*')
506                     .pipe(gulp.dest(config.dist.assets + key + '/scss'));
507             } else if (key == '@fortawesome/fontawesome-free') {
508                 // @fortawesome/fontawesome-free is special and need to copy css, scss, and webfonts
509                 gulp.src('node_modules/' + key + '/css/**/*')
510                     .pipe(gulp.dest(config.dist.assets + key + '/css'));
511                 gulp.src('node_modules/' + key + '/scss/**/*')
512                     .pipe(gulp.dest(config.dist.assets + key + '/scss'));
513                 gulp.src('node_modules/' + key + '/webfonts/**/*')
514                     .pipe(gulp.dest(config.dist.assets + key + '/webfonts'));
515             } else if (key == '@ttskch/select2-bootstrap4-theme') {
516                 // @ttskch/select2-bootstrap4-theme is special and need to copy dist and src
517                 gulp.src('node_modules/' + key + '/dist/**/*')
518                     .pipe(gulp.dest(config.dist.assets + key + '/dist'));
519                 gulp.src('node_modules/' + key + '/src/**/*')
520                     .pipe(gulp.dest(config.dist.assets + key + '/src'));
521             } else if (key == "moment") {
522                 gulp.src('node_modules/' + key + '/min/**/*')
523                     .pipe(gulp.dest(config.dist.assets + key + '/min'));
524                 gulp.src('node_modules/' + key + '/moment.js')
525                     .pipe(gulp.dest(config.dist.assets + key));
526             } else if (fs.existsSync('node_modules/' + key + '/dist')) {
527                 // only copy dist directory, if it exists
528                 gulp.src('node_modules/' + key + '/dist/**/*')
529                     .pipe(gulp.dest(config.dist.assets + key + '/dist'));
530             } else {
531                 // copy everything
532                 gulp.src('node_modules/' + key + '/**/*')
533                     .pipe(gulp.dest(config.dist.assets + key));
534             }
535         }
536     }
538     console.log(logprefix + "Finished running OpenEMR gulp install task");
539     done();
542 function watch() {
543     let isSuccess = true;
544     console.log(logprefix + "Running gulp watch task...");
545     // watch all changes and re-run styles
546     gulp.watch('./interface/**/*.scss', {
547             interval: 1000,
548             mode: 'poll'
549         }, styles)
550         .on('error', (err) => {
551             log_error(isSuccess, err);
552         });
554     // watch php separately since autoprefix is not needed
555     gulp_watch('./interface/themes/*.php', {
556             ignoreInitial: false
557         })
558         .pipe(gulp.dest(config.dest.themes))
559         .on('error', (err) => {
560             log_error(isSuccess, err);
561         });
563     // watch all changes to css files in themes and
564     // autoprefix them before copying to public
565     return gulp_watch('./interface/themes/*.css', {
566             ignoreInitial: false
567         })
568         .pipe(postcss([prefix()]))
569         .on('error', (err) => {
570             log_error(isSuccess, err);
571         })
572         .pipe(gulp.dest(config.dest.themes))
573         .on('end', () => {
574             if (isSuccess) {
575                 console.log(logprefix + "Finished running gulp watch task");
576             }
577         });
580 function sync() {
581     let isSuccess = true;
582     console.log(logprefix + "Running gulp sync task...");
583     // copy all leftover root-level components to the theme directory
584     // hoping this is only temporary
585     // Copy php file separately since we don't need to autoprefix them
586     gulp.src(['interface/themes/*.php'])
587         .pipe(gulp.dest(config.dest.themes))
588         .on('error', (err) => {
589             log_error(isSuccess, err);
590         });
592     // Copy CSS files and autoprefix them
593     return gulp.src(['interface/themes/*.css'])
594         .pipe(postcss([prefix()]))
595         .on('error', (err) => {
596             log_error(isSuccess, err);
597         })
598         .pipe(gulp.dest(config.dest.themes))
599         .on('end', () => {
600             if (isSuccess) {
601                 console.log(logprefix + "Finished running gulp sync task");
602             }
603         });
606 // Export watch task
607 exports.watch = watch;
609 // Export pertinent default task
610 // - Note that the default task runs if no other task is chosen,
611 //    which is generally how this script is always used (except in
612 //    rare case where the user is running the watch task).
613 if (config.install) {
614     exports.default = gulp.series(install);
615 } else {
616     exports.default = gulp.series(clean, ingest, styles, sync);